diff --git a/DEPS b/DEPS
index da5b85f..1821c2a 100644
--- a/DEPS
+++ b/DEPS
@@ -149,7 +149,7 @@
   # 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': '45faa0849352bb3c2f6ee6fa28cf91b790607d99',
+  'swiftshader_revision': '3ed33cee5e61f854c36fb44b7253dda3052ff5ce',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -196,7 +196,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'ccc29087522abefc852d1294595ae6db7e86d649',
+  'catapult_revision': 'dc658bb37c70d3153cda3e9993a3874ccd8871a9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -260,7 +260,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'aa9d6ad09d0d43ad2a35263fb45c272f40f95937',
+  'dawn_revision': '8c1a90199a43f04f86539141b394f05014cea77a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -435,7 +435,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'c2575332bb4bee4a0e8330765cb8e8980bb554ec',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '4dc42cedf3ed6cc91ad1c99092d62991180bc0d1',
       'condition': 'checkout_ios',
   },
 
@@ -752,7 +752,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a6918d7b72a9b0c1ff2c41d9515e14ec72256f93',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '28dfd94b57ae4aca22c40a3521fb6480d1c155be',
       'condition': 'checkout_linux',
   },
 
@@ -777,7 +777,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'ffb1ffb8228460b9c495216507daa90717c6193a',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'cc43111eb273d5f660e6913970fa5da314c8b988',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1010,7 +1010,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  'd64e328624e09cbc36e7077598bf0ff367dcdb4c',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '8256c8b297c8b7c7ee4de24edff82ed67d6ef207',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + '51ca718c3adf0ddedacd7df25fe45f67dc5a9ce1',
@@ -1119,7 +1119,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'c0eace27e740347e63d7730ee9b4eba492e7ea1e',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '282b28c1234c109a45003a5470512982177e77c1',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1334,7 +1334,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@5b257f91fc81cfb1633b415deca73312a57947fa',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7e53cb9263b40dd836a58e0ae548e8a24d98905f',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 59aaaad..8943046 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -678,6 +678,7 @@
     'build/android/gyp/generate_linker_version_script.pydeps',
     'build/android/gyp/ijar.pydeps',
     'build/android/gyp/java_cpp_enum.pydeps',
+    'build/android/gyp/java_cpp_strings.pydeps',
     'build/android/gyp/javac.pydeps',
     'build/android/gyp/jinja_template.pydeps',
     'build/android/gyp/lint.pydeps',
diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc
index dd22654..8dc38b91 100644
--- a/android_webview/browser/aw_browser_context.cc
+++ b/android_webview/browser/aw_browser_context.cc
@@ -294,8 +294,6 @@
 
 content::BackgroundFetchDelegate*
 AwBrowserContext::GetBackgroundFetchDelegate() {
-  // TODO(crbug.com/766077): Resolve whether to support or disable background
-  // fetch on WebView.
   return nullptr;
 }
 
diff --git a/android_webview/browser/gfx/browser_view_renderer.cc b/android_webview/browser/gfx/browser_view_renderer.cc
index d61008b..9e13c46 100644
--- a/android_webview/browser/gfx/browser_view_renderer.cc
+++ b/android_webview/browser/gfx/browser_view_renderer.cc
@@ -161,10 +161,12 @@
   if (g_memory_override_in_bytes) {
     bytes_limit = static_cast<size_t>(g_memory_override_in_bytes);
   } else {
-    gfx::Rect interest_rect =
-        offscreen_pre_raster_ || external_draw_constraints_.is_layer
-            ? gfx::Rect(size_)
-            : last_on_draw_global_visible_rect_;
+    // Note we are using |last_on_draw_global_visible_rect_| rather than
+    // |external_draw_constraints_.viewport_size|. This is to reduce budget
+    // for a webview that's much smaller than the surface it's rendering.
+    gfx::Rect interest_rect = offscreen_pre_raster_
+                                  ? gfx::Rect(size_)
+                                  : last_on_draw_global_visible_rect_;
     size_t width = interest_rect.width();
     size_t height = interest_rect.height();
     bytes_limit = kMemoryMultiplier * kBytesPerPixel * width * height;
@@ -226,35 +228,24 @@
   gfx::Transform transform_for_tile_priority =
       external_draw_constraints_.transform;
 
-  gfx::Rect viewport_rect_for_tile_priority =
-      ComputeViewportRectForTilePriority();
+  // Do not override (ie leave empty) for offscreen raster.
+  gfx::Size viewport_size_for_tile_priority =
+      offscreen_pre_raster_ ? gfx::Size()
+                            : external_draw_constraints_.viewport_size;
 
   scoped_refptr<content::SynchronousCompositor::FrameFuture> future =
-      compositor_->DemandDrawHwAsync(size_, viewport_rect_for_tile_priority,
+      compositor_->DemandDrawHwAsync(size_,
+                                     gfx::Rect(viewport_size_for_tile_priority),
                                      transform_for_tile_priority);
   std::unique_ptr<ChildFrame> child_frame = std::make_unique<ChildFrame>(
-      std::move(future), compositor_id_,
-      viewport_rect_for_tile_priority.IsEmpty(), transform_for_tile_priority,
-      offscreen_pre_raster_, external_draw_constraints_.is_layer);
+      std::move(future), compositor_id_, viewport_size_for_tile_priority,
+      transform_for_tile_priority, offscreen_pre_raster_);
 
   ReturnUnusedResource(
       current_compositor_frame_consumer_->SetFrameOnUI(std::move(child_frame)));
   return true;
 }
 
-gfx::Rect BrowserViewRenderer::ComputeViewportRectForTilePriority() {
-  // If the WebView is on a layer, WebView does not know what transform is
-  // applied onto the layer so global visible rect does not make sense here.
-  // In this case, just use the surface rect for tiling.
-  // Leave viewport_rect_for_tile_priority empty if offscreen_pre_raster_ is on.
-  gfx::Rect viewport_rect_for_tile_priority;
-
-  if (!offscreen_pre_raster_ && !external_draw_constraints_.is_layer) {
-    viewport_rect_for_tile_priority = last_on_draw_global_visible_rect_;
-  }
-  return viewport_rect_for_tile_priority;
-}
-
 void BrowserViewRenderer::OnParentDrawConstraintsUpdated(
     CompositorFrameConsumer* compositor_frame_consumer) {
   DCHECK(compositor_frame_consumer);
diff --git a/android_webview/browser/gfx/browser_view_renderer.h b/android_webview/browser/gfx/browser_view_renderer.h
index 1919ccd..24d2fff 100644
--- a/android_webview/browser/gfx/browser_view_renderer.h
+++ b/android_webview/browser/gfx/browser_view_renderer.h
@@ -179,7 +179,6 @@
   void ReturnResourceFromParent(
       CompositorFrameConsumer* compositor_frame_consumer);
   void ReleaseHardware();
-  gfx::Rect ComputeViewportRectForTilePriority();
 
   gfx::Vector2d max_scroll_offset() const;
 
diff --git a/android_webview/browser/gfx/browser_view_renderer_unittest.cc b/android_webview/browser/gfx/browser_view_renderer_unittest.cc
index 7b084b1..b183f35 100644
--- a/android_webview/browser/gfx/browser_view_renderer_unittest.cc
+++ b/android_webview/browser/gfx/browser_view_renderer_unittest.cc
@@ -162,8 +162,10 @@
   TestAnimateInAndOutOfScreen() : on_draw_count_(0), draw_gl_count_on_rt_(0) {}
 
   void StartTest() override {
-    new_constraints_ = ParentCompositorDrawConstraints(
-        false, gfx::Transform(), window_->surface_size().IsEmpty());
+    initial_constraints_ = ParentCompositorDrawConstraints(
+        window_->surface_size(), gfx::Transform());
+    new_constraints_ = ParentCompositorDrawConstraints(window_->surface_size(),
+                                                       gfx::Transform());
     new_constraints_.transform.Scale(2.0, 2.0);
     browser_view_renderer_->PostInvalidate(ActiveCompositor());
   }
@@ -212,12 +214,7 @@
   bool DrawConstraintsEquals(
       const ParentCompositorDrawConstraints& constraints1,
       const ParentCompositorDrawConstraints& constraints2) {
-    if (constraints1.is_layer != constraints2.is_layer ||
-        constraints1.transform != constraints2.transform)
-      return false;
-
-    return !constraints1.is_layer ||
-           constraints1.surface_rect_empty == constraints2.surface_rect_empty;
+    return constraints1 == constraints2;
   }
 
   void OnParentDrawConstraintsUpdated() override {
diff --git a/android_webview/browser/gfx/child_frame.cc b/android_webview/browser/gfx/child_frame.cc
index 9855691..1aeb95a 100644
--- a/android_webview/browser/gfx/child_frame.cc
+++ b/android_webview/browser/gfx/child_frame.cc
@@ -14,17 +14,14 @@
 ChildFrame::ChildFrame(
     scoped_refptr<content::SynchronousCompositor::FrameFuture> frame_future,
     const CompositorID& compositor_id,
-    bool viewport_rect_for_tile_priority_empty,
+    const gfx::Size& viewport_size_for_tile_priority,
     const gfx::Transform& transform_for_tile_priority,
-    bool offscreen_pre_raster,
-    bool is_layer)
+    bool offscreen_pre_raster)
     : frame_future(std::move(frame_future)),
       compositor_id(compositor_id),
-      viewport_rect_for_tile_priority_empty(
-          viewport_rect_for_tile_priority_empty),
+      viewport_size_for_tile_priority(viewport_size_for_tile_priority),
       transform_for_tile_priority(transform_for_tile_priority),
-      offscreen_pre_raster(offscreen_pre_raster),
-      is_layer(is_layer) {}
+      offscreen_pre_raster(offscreen_pre_raster) {}
 
 ChildFrame::~ChildFrame() {
 }
diff --git a/android_webview/browser/gfx/child_frame.h b/android_webview/browser/gfx/child_frame.h
index aeb839a..483e63db 100644
--- a/android_webview/browser/gfx/child_frame.h
+++ b/android_webview/browser/gfx/child_frame.h
@@ -11,6 +11,7 @@
 #include "base/containers/circular_deque.h"
 #include "base/macros.h"
 #include "content/public/browser/android/synchronous_compositor.h"
+#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/transform.h"
 
 namespace viz {
@@ -24,10 +25,9 @@
   ChildFrame(
       scoped_refptr<content::SynchronousCompositor::FrameFuture> frame_future,
       const CompositorID& compositor_id,
-      bool viewport_rect_for_tile_priority_empty,
+      const gfx::Size& viewport_size_for_tile_priority,
       const gfx::Transform& transform_for_tile_priority,
-      bool offscreen_pre_raster,
-      bool is_layer);
+      bool offscreen_pre_raster);
   ~ChildFrame();
 
   // Helper to move frame from |frame_future| to |frame|.
@@ -40,10 +40,9 @@
   std::unique_ptr<viz::CompositorFrame> frame;
   // The id of the compositor this |frame| comes from.
   const CompositorID compositor_id;
-  const bool viewport_rect_for_tile_priority_empty;
+  const gfx::Size viewport_size_for_tile_priority;
   const gfx::Transform transform_for_tile_priority;
   const bool offscreen_pre_raster;
-  const bool is_layer;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ChildFrame);
diff --git a/android_webview/browser/gfx/hardware_renderer.cc b/android_webview/browser/gfx/hardware_renderer.cc
index bf7baa6..5d598107 100644
--- a/android_webview/browser/gfx/hardware_renderer.cc
+++ b/android_webview/browser/gfx/hardware_renderer.cc
@@ -12,6 +12,7 @@
 #include "android_webview/browser/gfx/parent_compositor_draw_constraints.h"
 #include "android_webview/browser/gfx/render_thread_manager.h"
 #include "android_webview/browser/gfx/surfaces_instance.h"
+#include "base/macros.h"
 #include "base/trace_event/trace_event.h"
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
@@ -143,8 +144,7 @@
   // Need to post the new transform matrix back to child compositor
   // because there is no onDraw during a Render Thread animation, and child
   // compositor might not have the tiles rasterized as the animation goes on.
-  ParentCompositorDrawConstraints draw_constraints(params->is_layer, transform,
-                                                   viewport.IsEmpty());
+  ParentCompositorDrawConstraints draw_constraints(viewport, transform);
   if (!child_frame_.get() || draw_constraints.NeedUpdate(*child_frame_)) {
     render_thread_manager_->PostExternalDrawConstraintsToChildCompositorOnRT(
         draw_constraints);
@@ -159,6 +159,8 @@
   surfaces_->DrawAndSwap(viewport, clip, transform, surface_size_,
                          viz::SurfaceId(frame_sink_id_, child_id_),
                          device_scale_factor_, params->color_space);
+  // TODO(crbug.com/938956): Implement presentation feedbacks.
+  ignore_result(support_->TakePresentationFeedbacks());
 }
 
 void HardwareRenderer::AllocateSurface() {
diff --git a/android_webview/browser/gfx/parent_compositor_draw_constraints.cc b/android_webview/browser/gfx/parent_compositor_draw_constraints.cc
index 2d6cd58f..7f49c8bb 100644
--- a/android_webview/browser/gfx/parent_compositor_draw_constraints.cc
+++ b/android_webview/browser/gfx/parent_compositor_draw_constraints.cc
@@ -8,36 +8,22 @@
 
 namespace android_webview {
 
-ParentCompositorDrawConstraints::ParentCompositorDrawConstraints()
-    : is_layer(false), surface_rect_empty(true) {}
+ParentCompositorDrawConstraints::ParentCompositorDrawConstraints() = default;
 
 ParentCompositorDrawConstraints::ParentCompositorDrawConstraints(
-    bool is_layer,
-    const gfx::Transform& transform,
-    bool surface_rect_empty)
-    : is_layer(is_layer),
-      transform(transform),
-      surface_rect_empty(surface_rect_empty) {}
+    const gfx::Size& viewport_size,
+    const gfx::Transform& transform)
+    : viewport_size(viewport_size), transform(transform) {}
 
 bool ParentCompositorDrawConstraints::NeedUpdate(
     const ChildFrame& frame) const {
-  if (is_layer != frame.is_layer ||
-      transform != frame.transform_for_tile_priority) {
-    return true;
-  }
-
-  // Viewport for tile priority does not depend on surface rect in this case.
-  if (frame.offscreen_pre_raster || is_layer)
-    return false;
-
-  // Workaround for corner case. See crbug.com/417479.
-  return frame.viewport_rect_for_tile_priority_empty && !surface_rect_empty;
+  return viewport_size != frame.viewport_size_for_tile_priority ||
+         transform != frame.transform_for_tile_priority;
 }
 
 bool ParentCompositorDrawConstraints::operator==(
     const ParentCompositorDrawConstraints& other) const {
-  return is_layer == other.is_layer && transform == other.transform &&
-         surface_rect_empty == other.surface_rect_empty;
+  return viewport_size == other.viewport_size && transform == other.transform;
 }
 
 }  // namespace android_webview
diff --git a/android_webview/browser/gfx/parent_compositor_draw_constraints.h b/android_webview/browser/gfx/parent_compositor_draw_constraints.h
index c5bb094..16f5aa40 100644
--- a/android_webview/browser/gfx/parent_compositor_draw_constraints.h
+++ b/android_webview/browser/gfx/parent_compositor_draw_constraints.h
@@ -5,6 +5,7 @@
 #ifndef ANDROID_WEBVIEW_BROWSER_GFX_PARENT_COMPOSITOR_DRAW_CONSTRAINTS_H_
 #define ANDROID_WEBVIEW_BROWSER_GFX_PARENT_COMPOSITOR_DRAW_CONSTRAINTS_H_
 
+#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/transform.h"
 
 namespace android_webview {
@@ -12,14 +13,12 @@
 class ChildFrame;
 
 struct ParentCompositorDrawConstraints {
-  bool is_layer;
+  gfx::Size viewport_size;
   gfx::Transform transform;
-  bool surface_rect_empty;
 
   ParentCompositorDrawConstraints();
-  ParentCompositorDrawConstraints(bool is_layer,
-                                  const gfx::Transform& transform,
-                                  bool surface_rect_empty);
+  ParentCompositorDrawConstraints(const gfx::Size& viewport_size,
+                                  const gfx::Transform& transform);
   bool NeedUpdate(const ChildFrame& frame) const;
 
   bool operator==(const ParentCompositorDrawConstraints& other) const;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java
index 0779796..555513cb 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java
@@ -6,9 +6,9 @@
 
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.media.AudioManager;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
 import android.provider.MediaStore;
@@ -19,6 +19,7 @@
 import android.webkit.URLUtil;
 import android.widget.FrameLayout;
 
+import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ThreadUtils;
@@ -158,8 +159,9 @@
         // to be always run.
         boolean result = mContentsClient.onConsoleMessage(
                 new AwConsoleMessage(message, sourceId, lineNumber, messageLevel));
-        boolean isAppDebuggable = mContext.getApplicationInfo().FLAG_DEBUGGABLE != 0;
-        boolean isOsDebuggable = !Build.TYPE.equals("user");
+        boolean isAppDebuggable =
+                (mContext.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+        boolean isOsDebuggable = BuildInfo.isDebugAndroid();
         // Always return true if not debuggable so js console messages won't be mirrored to logcat
         // for privacy.
         if (!isAppDebuggable && !isOsDebuggable) {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
index 04f3e4d..fbc58f46 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
@@ -26,6 +26,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.util.TestWebServer;
 
 import java.util.ArrayList;
@@ -102,8 +103,6 @@
             String responseStr =
                     "<html><head><title>TEST!</title></head><body>HELLO!</body></html>";
             String url = webServer.setResponse(path, responseStr, null);
-
-            url = webServer.setResponse(path, responseStr, null);
             mActivityTestRule.loadUrlSync(
                     mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
             final String jsCookieName = "js-test" + cookieSuffix;
@@ -164,6 +163,42 @@
 
     @Test
     @MediumTest
+    @Feature({"AndroidWebView", "Privacy"})
+    public void testAcceptCookie_falseDoNotSendCookies() throws Throwable {
+        mCookieManager.setAcceptCookie(false);
+        AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
+
+        EmbeddedTestServer embeddedTestServer = EmbeddedTestServer.createAndStartServer(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        try {
+            final String url = embeddedTestServer.getURL("/echoheader?Cookie");
+            String cookieName = "java-test";
+            mCookieManager.setCookie(url, cookieName + "=should-not-work");
+
+            String cookie = mCookieManager.getCookie(url);
+            Assert.assertNotNull(
+                    "Setting cookies should still affect the CookieManager itself", cookie);
+
+            mActivityTestRule.loadUrlSync(
+                    mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+            String jsValue = getCookieWithJavaScript(cookieName);
+            String message =
+                    "WebView should not expose cookies to JavaScript (with setAcceptCookie "
+                    + "disabled)";
+            Assert.assertEquals(message, "\"\"", jsValue);
+
+            final String cookieHeader = mActivityTestRule.getJavaScriptResultBodyTextContent(
+                    mAwContents, mContentsClient);
+            message = "WebView should not expose cookies via the Cookie header (with "
+                    + "setAcceptCookie disabled)";
+            Assert.assertEquals(message, "None", cookieHeader);
+        } finally {
+            embeddedTestServer.stopAndDestroyServer();
+        }
+    }
+
+    @Test
+    @MediumTest
     @Feature({"AndroidWebView"})
     public void testEmbedderCanSeeRestrictedCookies() throws Throwable {
         TestWebServer webServer = TestWebServer.start();
@@ -198,6 +233,12 @@
                         + "; expires=' + expirationDate.toUTCString();");
     }
 
+    private String getCookieWithJavaScript(final String name) throws Throwable {
+        return JSUtils.executeJavaScriptAndWaitForResult(
+                InstrumentationRegistry.getInstrumentation(), mAwContents,
+                mContentsClient.getOnEvaluateJavaScriptResultHelper(), "document.cookie");
+    }
+
     @Test
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
@@ -877,13 +918,15 @@
 
     private void validateCookies(String responseCookie, String... expectedCookieNames) {
         String[] cookies = responseCookie.split(";");
-        Set<String> foundCookieNames = new HashSet<String>();
+        // Convert to sets, since Set#equals() hooks in nicely with assertEquals()
+        Set<String> foundCookieNamesSet = new HashSet<String>();
         for (String cookie : cookies) {
-            foundCookieNames.add(cookie.substring(0, cookie.indexOf("=")).trim());
+            foundCookieNamesSet.add(cookie.substring(0, cookie.indexOf("=")).trim());
         }
-        List<String> expectedCookieNamesList = Arrays.asList(expectedCookieNames);
-        Assert.assertEquals(expectedCookieNamesList.size(), foundCookieNames.size());
-        Assert.assertTrue(foundCookieNames.containsAll(expectedCookieNamesList));
+        Set<String> expectedCookieNamesSet =
+                new HashSet<String>(Arrays.asList(expectedCookieNames));
+        Assert.assertEquals("Found cookies list differs from expected list", expectedCookieNamesSet,
+                foundCookieNamesSet);
     }
 
     private String makeExpiringCookie(String cookie, int secondsTillExpiry) {
diff --git a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
index 4b79bfb0..057ae68 100644
--- a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
+++ b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
@@ -107,6 +107,31 @@
     getter onleavepictureinpicture
     setter onleavepictureinpicture
 
+# Background Fetch API is not enabled in Android webview, crbug.com/766077
+interface BackgroundFetchManager
+    method fetch
+    method get
+    method getIds
+interface BackgroundFetchRecord
+    getter request
+    getter responseReady
+interface BackgroundFetchRegistration : EventTarget
+    getter downloadTotal
+    getter downloaded
+    getter failureReason
+    getter id
+    getter onprogress
+    getter recordsAvailable
+    getter result
+    getter uploadTotal
+    getter uploaded
+    method abort
+    method match
+    method matchAll
+    setter onprogress
+interface ServiceWorkerRegistration : EventTarget
+    getter backgroundFetch
+
 # support_webgl2_compute_context not supported on android, crbug.com/865569
 interface WebGL2ComputeRenderingContext
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index f05c393..2e5ea4a 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -157,8 +157,6 @@
     "app_list/app_list_controller_observer.h",
     "app_list/app_list_presenter_delegate_impl.cc",
     "app_list/app_list_presenter_delegate_impl.h",
-    "app_list/home_launcher_gesture_handler.cc",
-    "app_list/home_launcher_gesture_handler.h",
     "ash_export.h",
     "ash_service.cc",
     "assistant/assistant_alarm_timer_controller.cc",
@@ -309,6 +307,12 @@
     "highlighter/highlighter_result_view.h",
     "highlighter/highlighter_view.cc",
     "highlighter/highlighter_view.h",
+    "home_screen/home_launcher_gesture_handler.cc",
+    "home_screen/home_launcher_gesture_handler.h",
+    "home_screen/home_launcher_gesture_handler_observer.h",
+    "home_screen/home_screen_controller.cc",
+    "home_screen/home_screen_controller.h",
+    "home_screen/home_screen_delegate.h",
     "host/ash_window_tree_host.cc",
     "host/ash_window_tree_host.h",
     "host/ash_window_tree_host_init_params.h",
@@ -1558,7 +1562,6 @@
     "app_list/app_list_controller_impl_unittest.cc",
     "app_list/app_list_presenter_delegate_unittest.cc",
     "app_list/app_list_unittest.cc",
-    "app_list/home_launcher_gesture_handler_unittest.cc",
     "app_list/model/app_list_item_list_unittest.cc",
     "app_list/model/app_list_model_unittest.cc",
     "app_list/presenter/app_list_presenter_impl_unittest.cc",
@@ -1616,6 +1619,7 @@
     "frame/non_client_frame_view_ash_unittest.cc",
     "highlighter/highlighter_controller_unittest.cc",
     "highlighter/highlighter_gesture_util_unittest.cc",
+    "home_screen/home_launcher_gesture_handler_unittest.cc",
     "ime/ime_controller_unittest.cc",
     "ime/ime_focus_handler_unittest.cc",
     "keyboard/arc/arc_input_method_surface_manager_unittest.cc",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index faf058c..307529d6e 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -9,7 +9,6 @@
 
 #include "ash/app_list/app_list_controller_observer.h"
 #include "ash/app_list/app_list_presenter_delegate_impl.h"
-#include "ash/app_list/home_launcher_gesture_handler.h"
 #include "ash/app_list/model/app_list_folder_item.h"
 #include "ash/app_list/model/app_list_item.h"
 #include "ash/app_list/model/app_list_view_state.h"
@@ -23,6 +22,8 @@
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/assistant/util/assistant_util.h"
 #include "ash/assistant/util/deep_link_util.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/session/session_controller.h"
@@ -94,9 +95,7 @@
 
 AppListControllerImpl::AppListControllerImpl()
     : model_(std::make_unique<app_list::AppListModel>()),
-      presenter_(std::make_unique<AppListPresenterDelegateImpl>(this)),
-      home_launcher_gesture_handler_(
-          std::make_unique<HomeLauncherGestureHandler>(this)) {
+      presenter_(std::make_unique<AppListPresenterDelegateImpl>(this)) {
   model_->AddObserver(this);
 
   SessionController* session_controller = Shell::Get()->session_controller();
@@ -118,6 +117,8 @@
   shell->mru_window_tracker()->AddObserver(this);
   if (app_list_features::IsEmbeddedAssistantUIEnabled())
     shell->assistant_controller()->ui_controller()->AddModelObserver(this);
+  shell->home_screen_controller()->home_launcher_gesture_handler()->AddObserver(
+      this);
 }
 
 AppListControllerImpl::~AppListControllerImpl() = default;
@@ -557,6 +558,9 @@
 // Shell shutdown.
 void AppListControllerImpl::OnShellDestroying() {
   Shell* shell = Shell::Get();
+  shell->home_screen_controller()
+      ->home_launcher_gesture_handler()
+      ->RemoveObserver(this);
   if (app_list_features::IsEmbeddedAssistantUIEnabled())
     shell->assistant_controller()->ui_controller()->RemoveModelObserver(this);
   shell->mru_window_tracker()->RemoveObserver(this);
@@ -692,9 +696,6 @@
   // TODO(wutao): Handle tablet mode.
   switch (new_visibility) {
     case AssistantVisibility::kVisible:
-      if (!assistant::util::IsEmbeddedUiEntryPoint(entry_point.value()))
-        break;
-
       if (IsTabletMode()) {
         MinimizeAllWindows();
       } else if (!IsVisible()) {
@@ -730,6 +731,26 @@
   }
 }
 
+void AppListControllerImpl::OnHomeLauncherAnimationComplete(
+    bool shown,
+    int64_t display_id) {
+  CloseAssistantUi(shown ? AssistantExitPoint::kLauncherOpen
+                         : AssistantExitPoint::kLauncherClose);
+}
+
+void AppListControllerImpl::UpdateYPositionAndOpacityForHomeLauncher(
+    int y_position_in_screen,
+    float opacity,
+    UpdateAnimationSettingsCallback callback) {
+  presenter_.UpdateYPositionAndOpacityForHomeLauncher(
+      y_position_in_screen, opacity, std::move(callback));
+}
+
+void AppListControllerImpl::UpdateAfterHomeLauncherShown() {
+  // Show or hide the expand arrow view.
+  UpdateExpandArrowVisibility();
+}
+
 void AppListControllerImpl::Back() {
   presenter_.GetView()->Back();
 }
@@ -741,8 +762,12 @@
   if (!IsTabletMode())
     return ToggleAppList(display_id, show_source, event_time_stamp);
 
-  bool handled = home_launcher_gesture_handler_->ShowHomeLauncher(
-      Shell::Get()->display_manager()->GetDisplayForId(display_id));
+  bool handled =
+      Shell::Get()
+          ->home_screen_controller()
+          ->home_launcher_gesture_handler()
+          ->ShowHomeLauncher(
+              Shell::Get()->display_manager()->GetDisplayForId(display_id));
 
   if (!handled) {
     if (Shell::Get()->overview_controller()->IsSelecting()) {
@@ -869,8 +894,15 @@
       client_->OpenSearchResult(result_id, event_flags);
   }
 
-  if (IsTabletMode() && presenter_.IsVisible())
-    presenter_.GetView()->CloseOpenedPage();
+  ResetHomeLauncherIfShown();
+}
+
+void AppListControllerImpl::LogResultLaunchHistogram(
+    app_list::SearchResultLaunchLocation launch_location,
+    int suggestion_index) {
+  app_list::RecordSearchLaunchIndexAndQueryLength(
+      launch_location, static_cast<int>(last_raw_query_.size()),
+      suggestion_index);
 }
 
 void AppListControllerImpl::LogSearchClick(
@@ -939,8 +971,7 @@
   if (client_)
     client_->ActivateItem(id, event_flags);
 
-  if (IsTabletMode() && presenter_.IsVisible())
-    presenter_.GetView()->CloseOpenedPage();
+  ResetHomeLauncherIfShown();
 }
 
 void AppListControllerImpl::GetContextMenuModel(
@@ -966,16 +997,18 @@
 bool AppListControllerImpl::ProcessHomeLauncherGesture(
     ui::GestureEvent* event,
     const gfx::Point& screen_location) {
+  HomeLauncherGestureHandler* home_launcher_gesture_handler =
+      Shell::Get()->home_screen_controller()->home_launcher_gesture_handler();
   switch (event->type()) {
     case ui::ET_SCROLL_FLING_START:
     case ui::ET_GESTURE_SCROLL_BEGIN:
-      return home_launcher_gesture_handler_->OnPressEvent(
+      return home_launcher_gesture_handler->OnPressEvent(
           HomeLauncherGestureHandler::Mode::kSlideDownToHide, screen_location);
     case ui::ET_GESTURE_SCROLL_UPDATE:
-      return home_launcher_gesture_handler_->OnScrollEvent(
+      return home_launcher_gesture_handler->OnScrollEvent(
           screen_location, event->details().scroll_y());
     case ui::ET_GESTURE_END:
-      return home_launcher_gesture_handler_->OnReleaseEvent(screen_location);
+      return home_launcher_gesture_handler->OnReleaseEvent(screen_location);
     default:
       break;
   }
@@ -993,8 +1026,10 @@
     return false;
   }
 
-  return home_launcher_gesture_handler_ &&
-         home_launcher_gesture_handler_->mode() !=
+  HomeScreenController* home_screen_controller =
+      Shell::Get()->home_screen_controller();
+  return home_screen_controller &&
+         home_screen_controller->home_launcher_gesture_handler()->mode() !=
              HomeLauncherGestureHandler::Mode::kSlideUpToShow;
 }
 
@@ -1040,23 +1075,6 @@
     client_->OnAppListTargetVisibilityChanged(visible);
 }
 
-void AppListControllerImpl::NotifyHomeLauncherTargetPositionChanged(
-    bool showing,
-    int64_t display_id) {
-  for (auto& observer : observers_)
-    observer.OnHomeLauncherTargetPositionChanged(showing, display_id);
-}
-
-void AppListControllerImpl::NotifyHomeLauncherAnimationComplete(
-    bool shown,
-    int64_t display_id) {
-  CloseAssistantUi(shown ? AssistantExitPoint::kLauncherOpen
-                         : AssistantExitPoint::kLauncherClose);
-
-  for (auto& observer : observers_)
-    observer.OnHomeLauncherAnimationComplete(shown, display_id);
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Private used only:
 
@@ -1157,4 +1175,17 @@
   Shelf::ForWindow(presenter_.GetWindow())->MaybeUpdateShelfBackground();
 }
 
+void AppListControllerImpl::ResetHomeLauncherIfShown() {
+  if (!IsTabletMode() || !presenter_.IsVisible())
+    return;
+
+  auto* const keyboard_controller = keyboard::KeyboardController::Get();
+  if (keyboard_controller->IsKeyboardVisible())
+    keyboard_controller->HideKeyboardByUser();
+  presenter_.GetView()->CloseOpenedPage();
+
+  // Refresh the suggestion chips with empty query.
+  StartSearch(base::string16());
+}
+
 }  // namespace ash
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index 3a20e51..f1180ade 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -19,6 +19,8 @@
 #include "ash/ash_export.h"
 #include "ash/assistant/model/assistant_ui_model_observer.h"
 #include "ash/display/window_tree_host_manager.h"
+#include "ash/home_screen/home_launcher_gesture_handler_observer.h"
+#include "ash/home_screen/home_screen_delegate.h"
 #include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/interfaces/app_list.mojom.h"
@@ -42,7 +44,6 @@
 namespace ash {
 
 class AppListControllerObserver;
-class HomeLauncherGestureHandler;
 
 // Ash's AppListController owns the AppListModel and implements interface
 // functions that allow Chrome to modify and observe the Shelf and AppListModel
@@ -60,7 +61,9 @@
       public DefaultVoiceInteractionObserver,
       public WindowTreeHostManager::Observer,
       public ash::MruWindowTracker::Observer,
-      public AssistantUiModelObserver {
+      public AssistantUiModelObserver,
+      public HomeLauncherGestureHandlerObserver,
+      public HomeScreenDelegate {
  public:
   using AppListItemMetadataPtr = mojom::AppListItemMetadataPtr;
   using SearchResultMetadataPtr = mojom::SearchResultMetadataPtr;
@@ -147,9 +150,6 @@
                                  app_list::AppListShowSource show_source,
                                  base::TimeTicks event_time_stamp);
   app_list::AppListViewState GetAppListViewState();
-  HomeLauncherGestureHandler* home_launcher_gesture_handler() {
-    return home_launcher_gesture_handler_.get();
-  }
 
   // Called when a window starts/ends dragging. If we're in tablet mode and home
   // launcher is enabled, we should hide the home launcher during dragging a
@@ -166,6 +166,9 @@
   void LogSearchClick(const std::string& result_id,
                       int suggestion_index,
                       ash::mojom::AppListLaunchedFrom launched_from) override;
+  void LogResultLaunchHistogram(
+      app_list::SearchResultLaunchLocation launch_location,
+      int suggestion_index) override;
   void InvokeSearchResultAction(const std::string& result_id,
                                 int action_index,
                                 int event_flags) override;
@@ -206,11 +209,6 @@
   void NotifyAppListVisibilityChanged(bool visible, int64_t display_id);
   void NotifyAppListTargetVisibilityChanged(bool visible);
 
-  // HomeLauncher visibility announcements are for tablet mode AppList.
-  void NotifyHomeLauncherTargetPositionChanged(bool showing,
-                                               int64_t display_id);
-  void NotifyHomeLauncherAnimationComplete(bool shown, int64_t display_id);
-
   void FlushForTesting();
 
   // ShellObserver:
@@ -251,6 +249,16 @@
       base::Optional<AssistantEntryPoint> entry_point,
       base::Optional<AssistantExitPoint> exit_point) override;
 
+  // HomeLauncherGestureHandlerObserver:
+  void OnHomeLauncherAnimationComplete(bool shown, int64_t display_id) override;
+
+  // HomeScreenDelegate:
+  void UpdateYPositionAndOpacityForHomeLauncher(
+      int y_position_in_screen,
+      float opacity,
+      UpdateAnimationSettingsCallback callback) override;
+  void UpdateAfterHomeLauncherShown() override;
+
   bool onscreen_keyboard_shown() const { return onscreen_keyboard_shown_; }
 
   // Performs the 'back' action for the active page.
@@ -269,9 +277,6 @@
   // Returns current visibility of the Assistant page.
   bool IsShowingEmbeddedAssistantUI() const;
 
-  // Updates the visibility of expand arrow view.
-  void UpdateExpandArrowVisibility();
-
   // Get updated app list view state after dragging from shelf.
   app_list::AppListViewState CalculateStateAfterShelfDrag(
       const ui::GestureEvent& gesture_in_screen,
@@ -292,11 +297,16 @@
   // Update the visibility of Assistant functionality.
   void UpdateAssistantVisibility();
 
+  // Updates the visibility of expand arrow view.
+  void UpdateExpandArrowVisibility();
+
   int64_t GetDisplayIdToShowAppListOn();
 
   // Shows the home launcher in tablet mode.
   void ShowHomeLauncher();
 
+  void ResetHomeLauncherIfShown();
+
   base::string16 last_raw_query_;
 
   mojom::AppListClientPtr client_;
@@ -311,10 +321,6 @@
   // Bindings for the AppListController interface.
   mojo::BindingSet<mojom::AppListController> bindings_;
 
-  // Owned pointer to the object which handles gestures related to the home
-  // launcher.
-  std::unique_ptr<HomeLauncherGestureHandler> home_launcher_gesture_handler_;
-
   // Whether the on-screen keyboard is shown.
   bool onscreen_keyboard_shown_ = false;
 
diff --git a/ash/app_list/app_list_controller_impl_unittest.cc b/ash/app_list/app_list_controller_impl_unittest.cc
index 5f88541eb..4c62417 100644
--- a/ash/app_list/app_list_controller_impl_unittest.cc
+++ b/ash/app_list/app_list_controller_impl_unittest.cc
@@ -4,21 +4,30 @@
 
 #include "ash/app_list/app_list_controller_impl.h"
 
-#include "ash/app_list/home_launcher_gesture_handler.h"
+#include "ash/app_list/app_list_metrics.h"
 #include "ash/app_list/views/app_list_main_view.h"
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/app_list/views/contents_view.h"
 #include "ash/app_list/views/expand_arrow_view.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
+#include "base/strings/string16.cc"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 
 namespace ash {
 
 namespace {
 
+using ::app_list::kAppListResultLaunchIndexAndQueryLength;
+using ::app_list::kAppListTileLaunchIndexAndQueryLength;
+using ::app_list::SearchResultLaunchLocation;
+
 bool IsTabletMode() {
   return Shell::Get()
       ->tablet_mode_controller()
@@ -68,7 +77,7 @@
   // because w1 still exists.
   wm::ActivateWindow(w1.get());
   Shell::Get()
-      ->app_list_controller()
+      ->home_screen_controller()
       ->home_launcher_gesture_handler()
       ->ShowHomeLauncher(display::Screen::GetScreen()->GetPrimaryDisplay());
   EXPECT_EQ(mojom::WindowStateType::MINIMIZED,
@@ -85,4 +94,74 @@
   EXPECT_FALSE(GetExpandArrowViewVisibility());
 }
 
+class AppListControllerImplMetricsTest : public AshTestBase {
+ public:
+  AppListControllerImplMetricsTest() = default;
+  ~AppListControllerImplMetricsTest() override = default;
+
+  void SetUp() override {
+    AshTestBase::SetUp();
+    controller_ = ash::Shell::Get()->app_list_controller();
+  }
+
+  AppListControllerImpl* controller_;
+  const base::HistogramTester histogram_tester_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AppListControllerImplMetricsTest);
+};
+
+TEST_F(AppListControllerImplMetricsTest, LogSingleResultListClick) {
+  histogram_tester_.ExpectTotalCount(kAppListResultLaunchIndexAndQueryLength,
+                                     0);
+  controller_->StartSearch(base::string16());
+  controller_->LogResultLaunchHistogram(SearchResultLaunchLocation::kResultList,
+                                        4);
+  histogram_tester_.ExpectUniqueSample(kAppListResultLaunchIndexAndQueryLength,
+                                       4, 1);
+}
+
+TEST_F(AppListControllerImplMetricsTest, LogSingleTileListClick) {
+  histogram_tester_.ExpectTotalCount(kAppListTileLaunchIndexAndQueryLength, 0);
+  controller_->StartSearch(base::ASCIIToUTF16("aaaa"));
+  controller_->LogResultLaunchHistogram(SearchResultLaunchLocation::kTileList,
+                                        4);
+  histogram_tester_.ExpectUniqueSample(kAppListTileLaunchIndexAndQueryLength,
+                                       32, 1);
+}
+
+TEST_F(AppListControllerImplMetricsTest, LogOneClickInEveryBucket) {
+  histogram_tester_.ExpectTotalCount(kAppListResultLaunchIndexAndQueryLength,
+                                     0);
+  for (int query_length = 0; query_length < 11; ++query_length) {
+    const base::string16 query =
+        base::ASCIIToUTF16(std::string(query_length, 'a'));
+    for (int click_index = 0; click_index < 7; ++click_index) {
+      controller_->StartSearch(query);
+      controller_->LogResultLaunchHistogram(
+          SearchResultLaunchLocation::kResultList, click_index);
+    }
+  }
+
+  histogram_tester_.ExpectTotalCount(kAppListResultLaunchIndexAndQueryLength,
+                                     77);
+  for (int query_length = 0; query_length < 11; ++query_length) {
+    for (int click_index = 0; click_index < 7; ++click_index) {
+      histogram_tester_.ExpectBucketCount(
+          kAppListResultLaunchIndexAndQueryLength,
+          7 * query_length + click_index, 1);
+    }
+  }
+}
+
+TEST_F(AppListControllerImplMetricsTest, LogManyClicksInOneBucket) {
+  histogram_tester_.ExpectTotalCount(kAppListTileLaunchIndexAndQueryLength, 0);
+  controller_->StartSearch(base::ASCIIToUTF16("aaaa"));
+  for (int i = 0; i < 50; ++i)
+    controller_->LogResultLaunchHistogram(SearchResultLaunchLocation::kTileList,
+                                          4);
+  histogram_tester_.ExpectUniqueSample(kAppListTileLaunchIndexAndQueryLength,
+                                       32, 50);
+}
+
 }  // namespace ash
diff --git a/ash/app_list/app_list_controller_observer.h b/ash/app_list/app_list_controller_observer.h
index 227d384..920e69b 100644
--- a/ash/app_list/app_list_controller_observer.h
+++ b/ash/app_list/app_list_controller_observer.h
@@ -14,13 +14,6 @@
  public:
   // Called when the AppList is shown or dismissed.
   virtual void OnAppListVisibilityChanged(bool shown, int64_t display_id) {}
-  // Called when the HomeLauncher has started to be dragged, or a positional
-  // animation has begin.
-  virtual void OnHomeLauncherTargetPositionChanged(bool showing,
-                                                   int64_t display_id) {}
-  // Called when the HomeLauncher positional animation has completed.
-  virtual void OnHomeLauncherAnimationComplete(bool shown, int64_t display_id) {
-  }
 };
 
 }  // namespace ash
diff --git a/ash/app_list/app_list_metrics.cc b/ash/app_list/app_list_metrics.cc
index e4f9ff1..57a1e07 100644
--- a/ash/app_list/app_list_metrics.cc
+++ b/ash/app_list/app_list_metrics.cc
@@ -4,6 +4,8 @@
 
 #include "ash/app_list/app_list_metrics.h"
 
+#include <algorithm>
+
 #include "ash/app_list/model/app_list_model.h"
 #include "ash/app_list/model/search/search_model.h"
 #include "ash/app_list/model/search/search_result.h"
@@ -27,6 +29,16 @@
   return smoothness;
 }
 
+// These constants affect logging, and  should not be changed without
+// deprecating the following UMA histograms:
+//  - Apps.AppListTileClickIndexAndQueryLength
+//  - Apps.AppListResultClickIndexAndQueryLength
+constexpr int kMaxLoggedQueryLength = 10;
+constexpr int kMaxLoggedSuggestionIndex = 6;
+constexpr int kMaxLoggedHistogramValue =
+    (kMaxLoggedSuggestionIndex + 1) * kMaxLoggedQueryLength +
+    kMaxLoggedSuggestionIndex;
+
 }  // namespace
 
 // The UMA histogram that logs smoothness of folder show/hide animation.
@@ -100,6 +112,29 @@
       ApplistSearchResultOpenedSource::kMaxApplistSearchResultOpenedSource);
 }
 
+void RecordSearchLaunchIndexAndQueryLength(
+    SearchResultLaunchLocation launch_location,
+    int query_length,
+    int suggestion_index) {
+  if (suggestion_index < 0) {
+    LOG(ERROR) << "Received invalid suggestion index.";
+    return;
+  }
+
+  query_length = std::min(query_length, kMaxLoggedQueryLength);
+  suggestion_index = std::min(suggestion_index, kMaxLoggedSuggestionIndex);
+  const int logged_value =
+      (kMaxLoggedSuggestionIndex + 1) * query_length + suggestion_index;
+
+  if (launch_location == SearchResultLaunchLocation::kResultList) {
+    UMA_HISTOGRAM_EXACT_LINEAR(kAppListResultLaunchIndexAndQueryLength,
+                               logged_value, kMaxLoggedHistogramValue);
+  } else if (launch_location == SearchResultLaunchLocation::kTileList) {
+    UMA_HISTOGRAM_EXACT_LINEAR(kAppListTileLaunchIndexAndQueryLength,
+                               logged_value, kMaxLoggedHistogramValue);
+  }
+}
+
 void RecordZeroStateSearchResultUserActionHistogram(
     ZeroStateSearchResultUserActionType action) {
   UMA_HISTOGRAM_ENUMERATION(kAppListZeroStateSearchResultUserActionHistogram,
diff --git a/ash/app_list/app_list_metrics.h b/ash/app_list/app_list_metrics.h
index 29e1dce5..6bb5ad9e7 100644
--- a/ash/app_list/app_list_metrics.h
+++ b/ash/app_list/app_list_metrics.h
@@ -59,6 +59,16 @@
 // The UMA histogram that logs how the app list is shown.
 constexpr char kAppListToggleMethodHistogram[] = "Apps.AppListShowSource";
 
+// The UMA histogram that logs the index launched item in the results list and
+// the query length.
+constexpr char kAppListResultLaunchIndexAndQueryLength[] =
+    "Apps.AppListResultLaunchIndexAndQueryLength";
+
+// The UMA histogram that logs the index launched item in the app tile list and
+// the query length.
+constexpr char kAppListTileLaunchIndexAndQueryLength[] =
+    "Apps.AppListTileLaunchIndexAndQueryLength";
+
 // The UMA histogram that logs which page gets opened by the user.
 constexpr char kPageOpenedHistogram[] = "Apps.AppListPageOpened";
 
@@ -189,6 +199,14 @@
   kMaxAppListAppMovingType = 5,
 };
 
+// Different places a search result can be launched from. These values do not
+// persist to logs, so can be changed as-needed. However, changes should be
+// reflected in RecordSearchLaunchIndexAndQueryLength().
+enum SearchResultLaunchLocation {
+  kResultList = 0,
+  kTileList = 1,
+};
+
 void RecordFolderShowHideAnimationSmoothness(int actual_frames,
                                              int ideal_duration_ms,
                                              float refresh_rate);
@@ -208,6 +226,11 @@
     const AppListModel* model,
     const SearchModel* search_model);
 
+APP_LIST_EXPORT void RecordSearchLaunchIndexAndQueryLength(
+    SearchResultLaunchLocation launch_location,
+    int query_length,
+    int suggestion_index);
+
 }  // namespace app_list
 
 #endif  // ASH_APP_LIST_APP_LIST_METRICS_H_
diff --git a/ash/app_list/app_list_view_delegate.h b/ash/app_list/app_list_view_delegate.h
index afb1107..7312d46 100644
--- a/ash/app_list/app_list_view_delegate.h
+++ b/ash/app_list/app_list_view_delegate.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/app_list/app_list_metrics.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/public/cpp/ash_public_export.h"
 #include "ash/public/interfaces/app_list.mojom.h"
@@ -60,6 +61,16 @@
       int suggestion_index,
       ash::mojom::AppListLaunchedFrom launched_from) = 0;
 
+  // Called to log UMA metrics for the launch of an item either in the app tile
+  // list or the search result list. The |launch_location| argument determines
+  // which histogram to log to. |suggestion_index| represents the index of the
+  // launched item in its list view, not the overall position in the suggestion
+  // window. For instance, the first launcher result item is index 0, regardless
+  // of if there is an answer card above it.
+  virtual void LogResultLaunchHistogram(
+      app_list::SearchResultLaunchLocation launch_location,
+      int suggestion_index) = 0;
+
   // Called to invoke a custom action on a result with |result_id|.
   // |action_index| corresponds to the index of an icon in
   // |result.action_icons()|.
diff --git a/ash/app_list/test/app_list_test_view_delegate.h b/ash/app_list/test/app_list_test_view_delegate.h
index 36b1897d..8b1885cb 100644
--- a/ash/app_list/test/app_list_test_view_delegate.h
+++ b/ash/app_list/test/app_list_test_view_delegate.h
@@ -63,6 +63,9 @@
   void LogSearchClick(const std::string& result_id,
                       int suggestion_index,
                       ash::mojom::AppListLaunchedFrom launched_from) override {}
+  void LogResultLaunchHistogram(
+      app_list::SearchResultLaunchLocation launch_location,
+      int suggestion_index) override {}
   void InvokeSearchResultAction(const std::string& result_id,
                                 int action_index,
                                 int event_flags) override {}
diff --git a/ash/app_list/views/assistant/assistant_page_view.cc b/ash/app_list/views/assistant/assistant_page_view.cc
index 4e42f02c..dbd85ba 100644
--- a/ash/app_list/views/assistant/assistant_page_view.cc
+++ b/ash/app_list/views/assistant/assistant_page_view.cc
@@ -10,6 +10,12 @@
 #include "ash/app_list/app_list_view_delegate.h"
 #include "ash/app_list/views/assistant/assistant_main_view.h"
 #include "ash/app_list/views/contents_view.h"
+#include "ash/assistant/model/assistant_ui_model.h"
+#include "ash/assistant/ui/assistant_view_delegate.h"
+#include "ash/assistant/util/assistant_util.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/search_box/search_box_constants.h"
 #include "ui/views/background.h"
 #include "ui/views/focus/focus_manager.h"
@@ -32,9 +38,16 @@
     ash::AssistantViewDelegate* assistant_view_delegate)
     : assistant_view_delegate_(assistant_view_delegate) {
   InitLayout();
+
+  // |assistant_view_delegate_| could be nullptr in test.
+  if (assistant_view_delegate_)
+    assistant_view_delegate_->AddUiModelObserver(this);
 }
 
-AssistantPageView::~AssistantPageView() = default;
+AssistantPageView::~AssistantPageView() {
+  if (assistant_view_delegate_)
+    assistant_view_delegate_->RemoveUiModelObserver(this);
+}
 
 void AssistantPageView::InitLayout() {
   SetPaintToLayer();
@@ -50,7 +63,6 @@
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
-  // |assistant_view_delegate_| could be nullptr in test.
   if (assistant_view_delegate_) {
     assistant_main_view_ = new AssistantMainView(assistant_view_delegate_);
     AddChildView(assistant_main_view_);
@@ -75,6 +87,11 @@
   mask_->layer()->SetBounds(GetLocalBounds());
 }
 
+void AssistantPageView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+  View::GetAccessibleNodeData(node_data);
+  node_data->SetName(l10n_util::GetStringUTF16(IDS_ASH_ASSISTANT_WINDOW));
+}
+
 void AssistantPageView::OnMouseEvent(ui::MouseEvent* event) {
   switch (event->type()) {
     case ui::ET_MOUSE_PRESSED:
@@ -137,4 +154,23 @@
       this, GetWidget(), /*reverse=*/true, /*dont_loop=*/false);
 }
 
+void AssistantPageView::OnUiVisibilityChanged(
+    ash::AssistantVisibility new_visibility,
+    ash::AssistantVisibility old_visibility,
+    base::Optional<ash::AssistantEntryPoint> entry_point,
+    base::Optional<ash::AssistantExitPoint> exit_point) {
+  if (!assistant_view_delegate_)
+    return;
+
+  if (new_visibility != ash::AssistantVisibility::kVisible)
+    return;
+
+  const bool prefer_voice = assistant_view_delegate_->IsTabletMode() ||
+                            assistant_view_delegate_->IsLaunchWithMicOpen();
+  if (!ash::assistant::util::IsVoiceEntryPoint(entry_point.value(),
+                                               prefer_voice)) {
+    NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
+  }
+}
+
 }  // namespace app_list
diff --git a/ash/app_list/views/assistant/assistant_page_view.h b/ash/app_list/views/assistant/assistant_page_view.h
index 844800c..399a9e1 100644
--- a/ash/app_list/views/assistant/assistant_page_view.h
+++ b/ash/app_list/views/assistant/assistant_page_view.h
@@ -7,8 +7,10 @@
 
 #include "ash/app_list/app_list_export.h"
 #include "ash/app_list/views/app_list_page.h"
+#include "ash/assistant/model/assistant_ui_model_observer.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/macros.h"
+#include "base/optional.h"
 
 namespace ash {
 class AssistantViewDelegate;
@@ -23,7 +25,8 @@
 class AssistantMainView;
 
 // The Assistant page for the app list.
-class APP_LIST_EXPORT AssistantPageView : public AppListPage {
+class APP_LIST_EXPORT AssistantPageView : public AppListPage,
+                                          public ash::AssistantUiModelObserver {
  public:
   explicit AssistantPageView(
       ash::AssistantViewDelegate* assistant_view_delegate);
@@ -37,6 +40,7 @@
   gfx::Size CalculatePreferredSize() const override;
   void RequestFocus() override;
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
   // ui::EventHandler:
   void OnMouseEvent(ui::MouseEvent* event) override;
@@ -48,6 +52,13 @@
   views::View* GetFirstFocusableView() override;
   views::View* GetLastFocusableView() override;
 
+  // AssistantUiModelObserver:
+  void OnUiVisibilityChanged(
+      ash::AssistantVisibility new_visibility,
+      ash::AssistantVisibility old_visibility,
+      base::Optional<ash::AssistantEntryPoint> entry_point,
+      base::Optional<ash::AssistantExitPoint> exit_point) override;
+
  private:
   ash::AssistantViewDelegate* const assistant_view_delegate_;
 
diff --git a/ash/app_list/views/assistant/dialog_plate.cc b/ash/app_list/views/assistant/dialog_plate.cc
index 6b6571a..2fea69f 100644
--- a/ash/app_list/views/assistant/dialog_plate.cc
+++ b/ash/app_list/views/assistant/dialog_plate.cc
@@ -415,17 +415,9 @@
       textfield_->RequestFocus();
       break;
     case ash::InputModality::kVoice:
+      animated_voice_input_toggle_->RequestFocus();
+      break;
     case ash::InputModality::kStylus:
-      // When not using |kKeyboard| input modality we need to explicitly clear
-      // focus if the focused view is |textfield_| or |voice_input_toggle_| to
-      // prevent it from being read by ChromeVox. Clearing focus also allows
-      // AssistantContainerView's focus traversal to be reset.
-      views::FocusManager* focus_manager = GetFocusManager();
-      if (focus_manager &&
-          (focus_manager->GetFocusedView() == textfield_ ||
-           focus_manager->GetFocusedView() == voice_input_toggle_)) {
-        focus_manager->ClearFocus();
-      }
       break;
   }
 }
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index 32c7f4b4..d432f1e 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -485,6 +485,7 @@
     keyboard_controller->HideKeyboardByUser();
     return true;
   }
+
   ash::AppListState state = view_to_state_[GetActivePageIndex()];
   switch (state) {
     case ash::AppListState::kStateStart:
diff --git a/ash/app_list/views/search_result_list_view.cc b/ash/app_list/views/search_result_list_view.cc
index 787d0e8..c14ef523 100644
--- a/ash/app_list/views/search_result_list_view.cc
+++ b/ash/app_list/views/search_result_list_view.cc
@@ -43,6 +43,7 @@
   for (int i = 0; i < kMaxResults; ++i) {
     search_result_views_.emplace_back(
         new SearchResultView(this, view_delegate_));
+    search_result_views_.back()->set_index_in_search_result_list_view(i);
     results_container_->AddChildView(search_result_views_.back());
     AddObservedResultView(search_result_views_.back());
   }
@@ -124,6 +125,9 @@
   if (view_delegate_ && view->result()) {
     RecordSearchResultOpenSource(view->result(), view_delegate_->GetModel(),
                                  view_delegate_->GetSearchModel());
+    view_delegate_->LogResultLaunchHistogram(
+        SearchResultLaunchLocation::kResultList,
+        view->get_index_in_search_result_list_view());
     view_delegate_->OpenSearchResult(view->result()->id(), event_flags);
   }
 }
diff --git a/ash/app_list/views/search_result_tile_item_view.cc b/ash/app_list/views/search_result_tile_item_view.cc
index a4e6ed0..ecb334c 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -257,6 +257,8 @@
   RecordSearchResultOpenSource(result(), view_delegate_->GetModel(),
                                view_delegate_->GetSearchModel());
   view_delegate_->OpenSearchResult(result()->id(), event.flags());
+  view_delegate_->LogResultLaunchHistogram(
+      SearchResultLaunchLocation::kTileList, index_in_item_list_view_);
   view_delegate_->LogSearchClick(
       result()->id(), index_in_item_list_view_,
       ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox);
diff --git a/ash/app_list/views/search_result_view.h b/ash/app_list/views/search_result_view.h
index cec2ae5..79130484 100644
--- a/ash/app_list/views/search_result_view.h
+++ b/ash/app_list/views/search_result_view.h
@@ -64,6 +64,16 @@
   // Computes the button's spoken feedback name.
   base::string16 ComputeAccessibleName() const;
 
+  // Gets the index of this result in the |SearchResultListView|.
+  int get_index_in_search_result_list_view() const {
+    return index_in_search_result_list_view_;
+  }
+
+  // Stores the index of this result in the |SearchResultListView|.
+  void set_index_in_search_result_list_view(size_t index) {
+    index_in_search_result_list_view_ = index;
+  }
+
   void set_is_last_result(bool is_last) { is_last_result_ = is_last; }
 
   // AppListMenuModelAdapter::Delegate overrides:
@@ -151,6 +161,10 @@
   // Whether the removal confirmation dialog is invoked by long press touch.
   bool confirm_remove_by_long_press_ = false;
 
+  // The index of this item in the search_result_tile_item_list_view, only
+  // used for logging.
+  int index_in_search_result_list_view_ = -1;
+
   base::WeakPtrFactory<SearchResultView> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(SearchResultView);
diff --git a/ash/ash_service_unittest.cc b/ash/ash_service_unittest.cc
index 9bd86bd..62a9fcac9 100644
--- a/ash/ash_service_unittest.cc
+++ b/ash/ash_service_unittest.cc
@@ -91,19 +91,20 @@
 
   // service_manager::test::ServiceTest:
   void SetUp() override {
-    aura::test::EnvTestHelper().SetMode(aura::Env::Mode::MUS);
+    old_mode_ = aura::test::EnvTestHelper().SetMode(aura::Env::Mode::MUS);
   }
 
   void TearDown() override {
     // Unset the screen installed by the test.
     display::Screen::SetScreenInstance(nullptr);
-    aura::test::EnvTestHelper().SetMode(aura::Env::Mode::LOCAL);
+    aura::test::EnvTestHelper().SetMode(old_mode_);
   }
 
  protected:
   service_manager::Connector* connector() { return test_service_.connector(); }
 
  private:
+  aura::Env::Mode old_mode_ = aura::Env::Mode::LOCAL;
   base::test::ScopedTaskEnvironment task_environment_;
   service_manager::TestServiceManager test_service_manager_;
   service_manager::TestService test_service_;
diff --git a/ash/assistant/assistant_interaction_controller.cc b/ash/assistant/assistant_interaction_controller.cc
index d4d8e2e..b3e746c5 100644
--- a/ash/assistant/assistant_interaction_controller.cc
+++ b/ash/assistant/assistant_interaction_controller.cc
@@ -16,8 +16,10 @@
 #include "ash/assistant/model/assistant_ui_element.h"
 #include "ash/assistant/model/assistant_ui_model.h"
 #include "ash/assistant/ui/assistant_ui_constants.h"
+#include "ash/assistant/util/assistant_util.h"
 #include "ash/assistant/util/deep_link_util.h"
 #include "ash/assistant/util/histogram_util.h"
+#include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/interfaces/voice_interaction_controller.mojom.h"
 #include "ash/session/session_controller.h"
@@ -209,10 +211,13 @@
     HighlighterEnabledState state) {
   switch (state) {
     case HighlighterEnabledState::kEnabled:
-      model_.SetInputModality(InputModality::kStylus);
+      // Skip setting input modality to stylus when the embedded Assistant
+      // feature is enabled to prevent highlighter aborting sessions in
+      // OnUiModeChanged.
+      if (!app_list_features::IsEmbeddedAssistantUIEnabled())
+        model_.SetInputModality(InputModality::kStylus);
       break;
     case HighlighterEnabledState::kDisabledByUser:
-      FALLTHROUGH;
     case HighlighterEnabledState::kDisabledBySessionComplete:
       model_.SetInputModality(InputModality::kKeyboard);
       break;
@@ -227,6 +232,7 @@
 
 void AssistantInteractionController::OnHighlighterSelectionRecognized(
     const gfx::Rect& rect) {
+  assistant_controller_->ui_controller()->ShowUi(AssistantEntryPoint::kStylus);
   StartMetalayerInteraction(/*region=*/rect);
 }
 
@@ -620,38 +626,36 @@
   DCHECK_EQ(AssistantVisibility::kVisible,
             assistant_controller_->ui_controller()->model()->visibility());
 
-  bool launch_with_mic_open =
+  const bool launch_with_mic_open =
       Shell::Get()->voice_interaction_controller()->launch_with_mic_open();
+  const bool prefer_voice = launch_with_mic_open || IsTabletMode();
 
-  switch (entry_point) {
-    case AssistantEntryPoint::kLauncherSearchBoxMic:
-      launch_with_mic_open = true;
-      FALLTHROUGH;
-    case AssistantEntryPoint::kHotkey:
-    case AssistantEntryPoint::kLauncherSearchBox:
-    case AssistantEntryPoint::kLongPressLauncher: {
-      // When the user prefers it or when we are in tablet mode, launching
-      // Assistant UI will immediately start a voice interaction.
-      if (launch_with_mic_open || IsTabletMode()) {
-        StartVoiceInteraction();
-        should_attempt_warmer_welcome_ = false;
-      }
-      break;
-    }
-    case AssistantEntryPoint::kStylus:
-      model_.SetInputModality(InputModality::kStylus);
-      FALLTHROUGH;
-    case AssistantEntryPoint::kDeepLink:
-    case AssistantEntryPoint::kHotword:
-    case AssistantEntryPoint::kLauncherSearchResult:
-      should_attempt_warmer_welcome_ = false;
-      break;
-    case AssistantEntryPoint::kUnspecified:
-    case AssistantEntryPoint::kSetup:
-      // No action necessary.
-      break;
+  // We don't explicitly start a new voice interaction if the entry point
+  // is hotword since in such cases a voice interaction will already be in
+  // progress.
+  if (assistant::util::IsVoiceEntryPoint(entry_point, prefer_voice) &&
+      entry_point != AssistantEntryPoint::kHotword) {
+    should_attempt_warmer_welcome_ = false;
+    StartVoiceInteraction();
+    return;
   }
 
+  if (entry_point == AssistantEntryPoint::kStylus) {
+    should_attempt_warmer_welcome_ = false;
+    // When the embedded Assistant feature is enabled, we call ShowUi(kStylus)
+    // OnHighlighterSelectionRecognized. But we are not actually using stylus.
+    if (!app_list_features::IsEmbeddedAssistantUIEnabled())
+      model_.SetInputModality(InputModality::kStylus);
+    return;
+  }
+
+  if (!chromeos::assistant::features::IsWarmerWelcomeEnabled())
+    return;
+
+  should_attempt_warmer_welcome_ =
+      should_attempt_warmer_welcome_ &&
+      assistant::util::ShouldAttemptWarmerWelcome(entry_point);
+
   // Explicitly check the interaction state to ensure warmer welcome will
   // not interrupt any ongoing active interactions. This happens, for example,
   // when the first Assistant launch of the current user session is trigger by
@@ -660,31 +664,30 @@
   if (model_.interaction_state() == InteractionState::kActive)
     should_attempt_warmer_welcome_ = false;
 
+  if (!should_attempt_warmer_welcome_)
+    return;
+
   // TODO(yileili): Currently WW is only triggered when the first Assistant
   // launch of the user session does not automatically start an interaction that
   // would otherwise cause us to interrupt the user. Need further UX design to
   // attempt WW after the first interaction.
-  if (should_attempt_warmer_welcome_) {
-    if (chromeos::assistant::features::IsWarmerWelcomeEnabled()) {
-      auto* pref_service =
-          Shell::Get()->session_controller()->GetLastActiveUserPrefService();
+  auto* pref_service =
+      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
 
-      DCHECK(pref_service);
+  DCHECK(pref_service);
 
-      auto num_warmer_welcome_triggered =
-          pref_service->GetInteger(prefs::kAssistantNumWarmerWelcomeTriggered);
-      if (num_warmer_welcome_triggered < kWarmerWelcomesMaxTimesTriggered) {
-        // If the user has opted to launch Assistant with the mic open, we can
-        // reasonably assume there is an expectation of TTS.
-        assistant_->StartWarmerWelcomeInteraction(
-            num_warmer_welcome_triggered,
-            /*allow_tts=*/launch_with_mic_open);
-        pref_service->SetInteger(prefs::kAssistantNumWarmerWelcomeTriggered,
-                                 ++num_warmer_welcome_triggered);
-      }
-    }
-    should_attempt_warmer_welcome_ = false;
+  auto num_warmer_welcome_triggered =
+      pref_service->GetInteger(prefs::kAssistantNumWarmerWelcomeTriggered);
+  if (num_warmer_welcome_triggered < kWarmerWelcomesMaxTimesTriggered) {
+    // If the user has opted to launch Assistant with the mic open, we can
+    // reasonably assume there is an expectation of TTS.
+    assistant_->StartWarmerWelcomeInteraction(
+        num_warmer_welcome_triggered,
+        /*allow_tts=*/launch_with_mic_open);
+    pref_service->SetInteger(prefs::kAssistantNumWarmerWelcomeTriggered,
+                             ++num_warmer_welcome_triggered);
   }
+  should_attempt_warmer_welcome_ = false;
 }
 
 void AssistantInteractionController::StartMetalayerInteraction(
diff --git a/ash/assistant/assistant_ui_controller.cc b/ash/assistant/assistant_ui_controller.cc
index d601a62..5ded0031 100644
--- a/ash/assistant/assistant_ui_controller.cc
+++ b/ash/assistant/assistant_ui_controller.cc
@@ -35,6 +35,8 @@
 // Toast -----------------------------------------------------------------------
 
 constexpr int kToastDurationMs = 2500;
+
+constexpr char kStylusPromptToastId[] = "stylus_prompt_for_embedded_ui";
 constexpr char kUnboundServiceToastId[] =
     "assistant_controller_unbound_service";
 
@@ -188,9 +190,13 @@
 
 void AssistantUiController::OnHighlighterEnabledChanged(
     HighlighterEnabledState state) {
-  // TODO(wutao): Behavior is not defined.
-  if (model_.ui_mode() == AssistantUiMode::kLauncherEmbeddedUi)
+  if (app_list_features::IsEmbeddedAssistantUIEnabled()) {
+    if (state == HighlighterEnabledState::kEnabled) {
+      ShowToast(kStylusPromptToastId, IDS_ASH_ASSISTANT_PROMPT_STYLUS);
+      CloseUi(AssistantExitPoint::kStylus);
+    }
     return;
+  }
 
   switch (state) {
     case HighlighterEnabledState::kEnabled:
@@ -328,10 +334,11 @@
 
   // Metalayer should not be sticky. Disable when the UI is no longer visible.
   if (old_visibility == AssistantVisibility::kVisible) {
-    Shell::Get()->highlighter_controller()->AbortSession();
+    if (exit_point != AssistantExitPoint::kStylus)
+      Shell::Get()->highlighter_controller()->AbortSession();
 
     // Only record the exit point when Assistant UI becomes invisible to
-    // avoid duplicate happens (e.g., pressing ESC key).
+    // avoid recording duplicate events (e.g. pressing ESC key).
     assistant::util::RecordAssistantExitPoint(exit_point.value());
   }
 }
@@ -356,11 +363,7 @@
     return;
   }
 
-  if (app_list_features::IsEmbeddedAssistantUIEnabled() &&
-      assistant::util::IsEmbeddedUiEntryPoint(entry_point)) {
-    // No container view when embedded in launcher.
-    DCHECK(!container_view_);
-
+  if (app_list_features::IsEmbeddedAssistantUIEnabled()) {
     model_.SetUiMode(AssistantUiMode::kLauncherEmbeddedUi);
     model_.SetVisible(entry_point);
     return;
@@ -445,9 +448,10 @@
     return;
   }
 
-  // TODO(wutao): Behavior is not defined.
-  if (model_.ui_mode() == AssistantUiMode::kLauncherEmbeddedUi)
+  if (app_list_features::IsEmbeddedAssistantUIEnabled()) {
+    model_.SetUiMode(AssistantUiMode::kLauncherEmbeddedUi);
     return;
+  }
 
   InputModality input_modality = assistant_controller_->interaction_controller()
                                      ->model()
@@ -567,6 +571,9 @@
 }
 
 void AssistantUiController::CreateContainerView() {
+  DCHECK(!container_view_);
+  DCHECK(!app_list_features::IsEmbeddedAssistantUIEnabled());
+
   container_view_ =
       new AssistantContainerView(assistant_controller_->view_delegate());
   container_view_->GetWidget()->AddObserver(this);
diff --git a/ash/assistant/assistant_view_delegate_impl.cc b/ash/assistant/assistant_view_delegate_impl.cc
index cc263c9..de9f901 100644
--- a/ash/assistant/assistant_view_delegate_impl.cc
+++ b/ash/assistant/assistant_view_delegate_impl.cc
@@ -131,6 +131,10 @@
   return Shell::Get()->GetRootWindowForNewWindows();
 }
 
+bool AssistantViewDelegateImpl::IsLaunchWithMicOpen() const {
+  return Shell::Get()->voice_interaction_controller()->launch_with_mic_open();
+}
+
 bool AssistantViewDelegateImpl::IsTabletMode() const {
   return Shell::Get()
       ->tablet_mode_controller()
diff --git a/ash/assistant/assistant_view_delegate_impl.h b/ash/assistant/assistant_view_delegate_impl.h
index ca21607..6bd6096 100644
--- a/ash/assistant/assistant_view_delegate_impl.h
+++ b/ash/assistant/assistant_view_delegate_impl.h
@@ -54,6 +54,7 @@
   void GetNavigableContentsFactoryForView(
       content::mojom::NavigableContentsFactoryRequest request) override;
   aura::Window* GetRootWindowForNewWindows() override;
+  bool IsLaunchWithMicOpen() const override;
   bool IsTabletMode() const override;
   void OnDialogPlateButtonPressed(AssistantButtonId id) override;
   void OnDialogPlateContentsCommitted(const std::string& text) override;
diff --git a/ash/assistant/ui/assistant_view_delegate.h b/ash/assistant/ui/assistant_view_delegate.h
index 540d79a..d4382013 100644
--- a/ash/assistant/ui/assistant_view_delegate.h
+++ b/ash/assistant/ui/assistant_view_delegate.h
@@ -142,6 +142,9 @@
   // Returns the root window that newly created windows should be added to.
   virtual aura::Window* GetRootWindowForNewWindows() = 0;
 
+  // Returns true if user prefers to start with voice interaction.
+  virtual bool IsLaunchWithMicOpen() const = 0;
+
   // Returns true if in tablet mode.
   virtual bool IsTabletMode() const = 0;
 
diff --git a/ash/assistant/util/assistant_util.cc b/ash/assistant/util/assistant_util.cc
index 280e954..16f9cc8 100644
--- a/ash/assistant/util/assistant_util.cc
+++ b/ash/assistant/util/assistant_util.cc
@@ -20,15 +20,39 @@
   return new_visibility == AssistantVisibility::kClosed;
 }
 
-bool IsEmbeddedUiEntryPoint(AssistantEntryPoint entry_point) {
-  return entry_point == AssistantEntryPoint::kDeepLink ||
-         entry_point == AssistantEntryPoint::kHotkey ||
-         entry_point == AssistantEntryPoint::kHotword ||
-         entry_point == AssistantEntryPoint::kLauncherSearchBox ||
-         entry_point == AssistantEntryPoint::kLauncherSearchBoxMic ||
-         entry_point == AssistantEntryPoint::kLauncherSearchResult ||
-         entry_point == AssistantEntryPoint::kLongPressLauncher ||
-         entry_point == AssistantEntryPoint::kUnspecified;
+bool IsVoiceEntryPoint(AssistantEntryPoint entry_point, bool prefer_voice) {
+  switch (entry_point) {
+    case AssistantEntryPoint::kHotword:
+    case AssistantEntryPoint::kLauncherSearchBoxMic:
+      return true;
+    case AssistantEntryPoint::kHotkey:
+    case AssistantEntryPoint::kLauncherSearchBox:
+    case AssistantEntryPoint::kLongPressLauncher:
+      return prefer_voice;
+    case AssistantEntryPoint::kUnspecified:
+    case AssistantEntryPoint::kDeepLink:
+    case AssistantEntryPoint::kLauncherSearchResult:
+    case AssistantEntryPoint::kSetup:
+    case AssistantEntryPoint::kStylus:
+      return false;
+  }
+}
+
+bool ShouldAttemptWarmerWelcome(AssistantEntryPoint entry_point) {
+  switch (entry_point) {
+    case AssistantEntryPoint::kDeepLink:
+    case AssistantEntryPoint::kHotword:
+    case AssistantEntryPoint::kLauncherSearchBoxMic:
+    case AssistantEntryPoint::kLauncherSearchResult:
+    case AssistantEntryPoint::kStylus:
+      return false;
+    case AssistantEntryPoint::kUnspecified:
+    case AssistantEntryPoint::kHotkey:
+    case AssistantEntryPoint::kLauncherSearchBox:
+    case AssistantEntryPoint::kLongPressLauncher:
+    case AssistantEntryPoint::kSetup:
+      return true;
+  }
 }
 
 }  // namespace util
diff --git a/ash/assistant/util/assistant_util.h b/ash/assistant/util/assistant_util.h
index 0c239595..720b20871 100644
--- a/ash/assistant/util/assistant_util.h
+++ b/ash/assistant/util/assistant_util.h
@@ -24,10 +24,16 @@
 COMPONENT_EXPORT(ASSISTANT_UTIL)
 bool IsFinishingSession(AssistantVisibility new_visibility);
 
-// Returns true if the |entry_point| should show the embedded Assistant UI in
-// Launcher.
+// Returns true if the |entry_point| should start Assistant with a voice
+// interaction.
+// |prefer_voice| is true if user prefers voice input modality or if the device
+// is in tablet mode.
 COMPONENT_EXPORT(ASSISTANT_UTIL)
-bool IsEmbeddedUiEntryPoint(AssistantEntryPoint entry_point);
+bool IsVoiceEntryPoint(AssistantEntryPoint entry_point, bool prefer_voice);
+
+// Returns true if the |entry_point| should attempt warmer welcome.
+COMPONENT_EXPORT(ASSISTANT_UTIL)
+bool ShouldAttemptWarmerWelcome(AssistantEntryPoint entry_point);
 
 }  // namespace util
 }  // namespace assistant
diff --git a/ash/autoclick/autoclick_unittest.cc b/ash/autoclick/autoclick_unittest.cc
index 900d90e..96c61d3 100644
--- a/ash/autoclick/autoclick_unittest.cc
+++ b/ash/autoclick/autoclick_unittest.cc
@@ -515,8 +515,7 @@
   EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[1].flags());
 }
 
-// TODO(crbug.com/935651): The test has flaky crashes.
-TEST_F(AutoclickTest, DISABLED_WaitsToDrawAnimationAfterDwellBegins) {
+TEST_F(AutoclickTest, WaitsToDrawAnimationAfterDwellBegins) {
   float ratio = GetAutoclickController()->GetStartGestureDelayRatioForTesting();
   int full_delay = ceil(1.0 / ratio) * 5;
   int animation_delay = 5;
@@ -626,9 +625,8 @@
   Shell::Get()->accessibility_controller()->SetAutoclickEnabled(false);
 }
 
-TEST_F(
-    AutoclickTest,
-    DISABLED_StartsGestureOnTrayButtonButDoesNotClickIfMouseMovedWhenPaused) {
+TEST_F(AutoclickTest,
+       StartsGestureOnTrayButtonButDoesNotClickIfMouseMovedWhenPaused) {
   Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true);
   GetAutoclickController()->set_revert_to_left_click(false);
   GetAutoclickController()->SetAutoclickEventType(
diff --git a/ash/app_list/home_launcher_gesture_handler.cc b/ash/home_screen/home_launcher_gesture_handler.cc
similarity index 93%
rename from ash/app_list/home_launcher_gesture_handler.cc
rename to ash/home_screen/home_launcher_gesture_handler.cc
index 0a9ca90..a03895d9 100644
--- a/ash/app_list/home_launcher_gesture_handler.cc
+++ b/ash/home_screen/home_launcher_gesture_handler.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 "ash/app_list/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
 
-#include "ash/app_list/app_list_controller_impl.h"
-#include "ash/app_list/model/app_list_view_state.h"
+#include "ash/home_screen/home_launcher_gesture_handler_observer.h"
+#include "ash/home_screen/home_screen_controller.h"
+#include "ash/home_screen/home_screen_delegate.h"
 #include "ash/root_window_controller.h"
 #include "ash/scoped_animation_disabler.h"
 #include "ash/screen_util.h"
@@ -23,6 +24,7 @@
 #include "ash/wm/workspace_controller.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/logging.h"
 #include "base/metrics/user_metrics.h"
 #include "base/numerics/ranges.h"
 #include "services/ws/public/mojom/window_tree_constants.mojom.h"
@@ -270,9 +272,7 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedWindowModifier);
 };
 
-HomeLauncherGestureHandler::HomeLauncherGestureHandler(
-    AppListControllerImpl* app_list_controller)
-    : app_list_controller_(app_list_controller) {
+HomeLauncherGestureHandler::HomeLauncherGestureHandler() {
   tablet_mode_observer_.Add(Shell::Get()->tablet_mode_controller());
 }
 
@@ -297,7 +297,7 @@
   last_event_location_ = base::make_optional(location);
 
   if (mode != Mode::kNone) {
-    app_list_controller_->NotifyHomeLauncherTargetPositionChanged(
+    NotifyHomeLauncherTargetPositionChanged(
         mode == Mode::kSlideUpToShow /*showing*/, display_.id());
   }
 
@@ -402,6 +402,30 @@
   return window2_->window();
 }
 
+void HomeLauncherGestureHandler::AddObserver(
+    HomeLauncherGestureHandlerObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void HomeLauncherGestureHandler::RemoveObserver(
+    HomeLauncherGestureHandlerObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void HomeLauncherGestureHandler::NotifyHomeLauncherTargetPositionChanged(
+    bool showing,
+    int64_t display_id) {
+  for (auto& observer : observers_)
+    observer.OnHomeLauncherTargetPositionChanged(showing, display_id);
+}
+
+void HomeLauncherGestureHandler::NotifyHomeLauncherAnimationComplete(
+    bool shown,
+    int64_t display_id) {
+  for (auto& observer : observers_)
+    observer.OnHomeLauncherAnimationComplete(shown, display_id);
+}
+
 void HomeLauncherGestureHandler::OnWindowDestroying(aura::Window* window) {
   if (window1_ && window == GetWindow1()) {
     for (auto* hidden_window : hidden_windows_)
@@ -440,10 +464,10 @@
 }
 
 void HomeLauncherGestureHandler::OnImplicitAnimationsCompleted() {
-  float app_list_opacity = 1.f;
+  float home_launcher_opacity = 1.f;
   const bool is_final_state_show = IsFinalStateShow();
-  app_list_controller_->NotifyHomeLauncherAnimationComplete(
-      is_final_state_show /*shown*/, display_.id());
+  NotifyHomeLauncherAnimationComplete(is_final_state_show /*shown*/,
+                                      display_.id());
   if (Shell::Get()->overview_controller()->IsSelecting()) {
     if (overview_active_on_gesture_start_ && is_final_state_show) {
       // Exit overview if event is released on the top half. This will also
@@ -452,14 +476,18 @@
       Shell::Get()->overview_controller()->ToggleOverview(
           OverviewSession::EnterExitOverviewType::kSwipeFromShelf);
     } else {
-      app_list_opacity = 0.f;
+      home_launcher_opacity = 0.f;
     }
   }
 
+  HomeScreenDelegate* home_screen_delegate =
+      Shell::Get()->home_screen_controller()->delegate();
+  DCHECK(home_screen_delegate);
+
   // Return the app list to its original opacity and transform without
   // animation.
-  app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher(
-      0, app_list_opacity, base::NullCallback());
+  home_screen_delegate->UpdateYPositionAndOpacityForHomeLauncher(
+      0, home_launcher_opacity, base::NullCallback());
 
   if (!window1_) {
     RemoveObserversAndStopTracking();
@@ -477,8 +505,7 @@
     window2_->ResetOpacityAndTransform();
 
   if (is_final_state_show) {
-    // Show or hide the expand arrow view.
-    app_list_controller_->UpdateExpandArrowVisibility();
+    home_screen_delegate->UpdateAfterHomeLauncherShown();
 
     std::vector<aura::Window*> windows_to_hide_minimize;
     windows_to_hide_minimize.push_back(GetWindow1());
@@ -518,13 +545,11 @@
   UpdateWindows(is_final_state_show ? 1.0 : 0.0, /*animate=*/true);
 
   if (!is_final_state_show && mode_ == Mode::kSlideDownToHide) {
-    app_list_controller_->NotifyHomeLauncherTargetPositionChanged(
-        false /*showing*/, display_.id());
+    NotifyHomeLauncherTargetPositionChanged(false /*showing*/, display_.id());
     base::RecordAction(
         base::UserMetricsAction("AppList_HomeLauncherToMRUWindow"));
   } else if (is_final_state_show && mode_ == Mode::kSlideUpToShow) {
-    app_list_controller_->NotifyHomeLauncherTargetPositionChanged(
-        true /*showing*/, display_.id());
+    NotifyHomeLauncherTargetPositionChanged(true /*showing*/, display_.id());
     base::RecordAction(
         base::UserMetricsAction("AppList_CurrentWindowToHomeLauncher"));
   }
@@ -552,7 +577,10 @@
   const int y_position = gfx::Tween::IntValueBetween(
       progress, work_area.bottom(), display_.bounds().y());
   const float opacity = gfx::Tween::FloatValueBetween(progress, 0.f, 1.f);
-  app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher(
+  HomeScreenDelegate* home_screen_delegate =
+      Shell::Get()->home_screen_controller()->delegate();
+  DCHECK(home_screen_delegate);
+  home_screen_delegate->UpdateYPositionAndOpacityForHomeLauncher(
       y_position, opacity,
       animate ? base::BindRepeating(&HomeLauncherGestureHandler::UpdateSettings,
                                     base::Unretained(this))
diff --git a/ash/app_list/home_launcher_gesture_handler.h b/ash/home_screen/home_launcher_gesture_handler.h
similarity index 90%
rename from ash/app_list/home_launcher_gesture_handler.h
rename to ash/home_screen/home_launcher_gesture_handler.h
index 3846e055..60acede 100644
--- a/ash/app_list/home_launcher_gesture_handler.h
+++ b/ash/home_screen/home_launcher_gesture_handler.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 ASH_APP_LIST_HOME_LAUNCHER_GESTURE_HANDLER_H_
-#define ASH_APP_LIST_HOME_LAUNCHER_GESTURE_HANDLER_H_
+#ifndef ASH_HOME_SCREEN_HOME_LAUNCHER_GESTURE_HANDLER_H_
+#define ASH_HOME_SCREEN_HOME_LAUNCHER_GESTURE_HANDLER_H_
 
 #include <map>
 #include <vector>
@@ -22,7 +22,7 @@
 
 namespace ash {
 
-class AppListControllerImpl;
+class HomeLauncherGestureHandlerObserver;
 
 // HomeLauncherGestureHandler makes modifications to a window's transform and
 // opacity when gesture drag events are received and forwarded to it.
@@ -45,8 +45,7 @@
     kSlideDownToHide,
   };
 
-  explicit HomeLauncherGestureHandler(
-      AppListControllerImpl* app_list_controller);
+  HomeLauncherGestureHandler();
   ~HomeLauncherGestureHandler() override;
 
   // Called by owner of this object when a gesture event is received. |location|
@@ -73,6 +72,13 @@
 
   bool IsDragInProgress() const { return last_event_location_.has_value(); }
 
+  void AddObserver(HomeLauncherGestureHandlerObserver* observer);
+  void RemoveObserver(HomeLauncherGestureHandlerObserver* obsever);
+
+  void NotifyHomeLauncherTargetPositionChanged(bool showing,
+                                               int64_t display_id);
+  void NotifyHomeLauncherAnimationComplete(bool shown, int64_t display_id);
+
   // TODO(sammiequon): Investigate if it is needed to observe potential window
   // visibility changes, if they can happen.
   // aura::WindowObserver:
@@ -170,15 +176,14 @@
   ScopedObserver<TabletModeController, TabletModeObserver>
       tablet_mode_observer_{this};
 
-  // Unowned and guaranteed to be non null for the lifetime of this.
-  AppListControllerImpl* app_list_controller_;
-
   // The display where the windows are being processed.
   display::Display display_;
 
+  base::ObserverList<HomeLauncherGestureHandlerObserver> observers_;
+
   DISALLOW_COPY_AND_ASSIGN(HomeLauncherGestureHandler);
 };
 
 }  // namespace ash
 
-#endif  // ASH_APP_LIST_HOME_LAUNCHER_GESTURE_HANDLER_H_
+#endif  // ASH_HOME_SCREEN_HOME_LAUNCHER_GESTURE_HANDLER_H_
diff --git a/ash/home_screen/home_launcher_gesture_handler_observer.h b/ash/home_screen/home_launcher_gesture_handler_observer.h
new file mode 100644
index 0000000..6a94a85
--- /dev/null
+++ b/ash/home_screen/home_launcher_gesture_handler_observer.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_HOME_SCREEN_HOME_LAUNCHER_GESTURE_HANDLER_OBSERVER_H_
+#define ASH_HOME_SCREEN_HOME_LAUNCHER_GESTURE_HANDLER_OBSERVER_H_
+
+#include "base/observer_list_types.h"
+
+namespace ash {
+
+class HomeLauncherGestureHandlerObserver : public base::CheckedObserver {
+ public:
+  // Called when the HomeLauncher has started to be dragged, or a positional
+  // animation has begun.
+  virtual void OnHomeLauncherTargetPositionChanged(bool showing,
+                                                   int64_t display_id) {}
+
+  // Called when the HomeLauncher positional animation has completed.
+  virtual void OnHomeLauncherAnimationComplete(bool shown, int64_t display_id) {
+  }
+};
+
+}  // namespace ash
+
+#endif  // ASH_HOME_SCREEN_HOME_LAUNCHER_GESTURE_HANDLER_OBSERVER_H_
diff --git a/ash/app_list/home_launcher_gesture_handler_unittest.cc b/ash/home_screen/home_launcher_gesture_handler_unittest.cc
similarity index 98%
rename from ash/app_list/home_launcher_gesture_handler_unittest.cc
rename to ash/home_screen/home_launcher_gesture_handler_unittest.cc
index 5bc6abc..16ce4d3 100644
--- a/ash/app_list/home_launcher_gesture_handler_unittest.cc
+++ b/ash/home_screen/home_launcher_gesture_handler_unittest.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/app_list/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
 
-#include "ash/app_list/app_list_controller_impl.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
@@ -47,7 +47,9 @@
   }
 
   HomeLauncherGestureHandler* GetGestureHandler() {
-    return Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
+    return Shell::Get()
+        ->home_screen_controller()
+        ->home_launcher_gesture_handler();
   }
 
   void DoPress(Mode mode) {
diff --git a/ash/home_screen/home_screen_controller.cc b/ash/home_screen/home_screen_controller.cc
new file mode 100644
index 0000000..daab566
--- /dev/null
+++ b/ash/home_screen/home_screen_controller.cc
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/home_screen/home_screen_controller.h"
+
+#include "ash/home_screen/home_launcher_gesture_handler.h"
+
+namespace ash {
+
+HomeScreenController::HomeScreenController()
+    : home_launcher_gesture_handler_(
+          std::make_unique<HomeLauncherGestureHandler>()) {}
+
+HomeScreenController::~HomeScreenController() = default;
+
+void HomeScreenController::SetDelegate(HomeScreenDelegate* delegate) {
+  delegate_ = delegate;
+}
+
+}  // namespace ash
diff --git a/ash/home_screen/home_screen_controller.h b/ash/home_screen/home_screen_controller.h
new file mode 100644
index 0000000..b9d6ea407
--- /dev/null
+++ b/ash/home_screen/home_screen_controller.h
@@ -0,0 +1,48 @@
+// 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_HOME_SCREEN_HOME_SCREEN_CONTROLLER_H_
+#define ASH_HOME_SCREEN_HOME_SCREEN_CONTROLLER_H_
+
+#include <memory>
+
+#include "ash/ash_export.h"
+#include "base/macros.h"
+
+namespace ash {
+
+class HomeLauncherGestureHandler;
+class HomeScreenDelegate;
+
+// HomeScreenController handles the home launcher (e.g., tablet-mode app list)
+// and owns the HomeLauncherGestureHandler that transitions the launcher window
+// and other windows when the launcher is shown, hidden or animated.
+class ASH_EXPORT HomeScreenController {
+ public:
+  HomeScreenController();
+  ~HomeScreenController();
+
+  // Sets the delegate for home screen animations.
+  void SetDelegate(HomeScreenDelegate* delegate);
+
+  HomeLauncherGestureHandler* home_launcher_gesture_handler() {
+    return home_launcher_gesture_handler_.get();
+  }
+
+  HomeScreenDelegate* delegate() { return delegate_; }
+
+ private:
+  // Not owned.
+  HomeScreenDelegate* delegate_ = nullptr;
+
+  // Owned pointer to the object which handles gestures related to the home
+  // launcher.
+  std::unique_ptr<HomeLauncherGestureHandler> home_launcher_gesture_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(HomeScreenController);
+};
+
+}  // namespace ash
+
+#endif  // ASH_HOME_SCREEN_HOME_SCREEN_CONTROLLER_H_
diff --git a/ash/home_screen/home_screen_delegate.h b/ash/home_screen/home_screen_delegate.h
new file mode 100644
index 0000000..314fbf9
--- /dev/null
+++ b/ash/home_screen/home_screen_delegate.h
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_HOME_SCREEN_HOME_SCREEN_DELEGATE_H_
+#define ASH_HOME_SCREEN_HOME_SCREEN_DELEGATE_H_
+
+#include "base/callback.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+
+namespace ash {
+
+// Delegate for implementation-specific home screen behavior.
+class HomeScreenDelegate {
+ public:
+  // Callback which fills out the passed settings object, allowing the caller to
+  // animate with the given settings.
+  using UpdateAnimationSettingsCallback =
+      base::RepeatingCallback<void(ui::ScopedLayerAnimationSettings* settings,
+                                   bool observe)>;
+
+  virtual ~HomeScreenDelegate() = default;
+
+  // Updates the y position and opacity of the home launcher view. If |callback|
+  // is non-null, it should be called with animation settings.
+  virtual void UpdateYPositionAndOpacityForHomeLauncher(
+      int y_position_in_screen,
+      float opacity,
+      UpdateAnimationSettingsCallback callback) = 0;
+
+  // Updates the home launcher view after its show animation has completed.
+  virtual void UpdateAfterHomeLauncherShown() = 0;
+};
+
+}  // namespace ash
+
+#endif  // ASH_HOME_SCREEN_HOME_SCREEN_DELEGATE_H_
diff --git a/ash/keyboard/virtual_keyboard_controller_unittest.cc b/ash/keyboard/virtual_keyboard_controller_unittest.cc
index fec9ece..1f4e679 100644
--- a/ash/keyboard/virtual_keyboard_controller_unittest.cc
+++ b/ash/keyboard/virtual_keyboard_controller_unittest.cc
@@ -43,21 +43,6 @@
   VirtualKeyboardControllerTest() = default;
   ~VirtualKeyboardControllerTest() override = default;
 
-  void SetUp() override {
-    AshTestBase::SetUp();
-    ws::InputDeviceClientTestApi().SetKeyboardDevices({});
-    ws::InputDeviceClientTestApi().SetTouchscreenDevices({});
-    keyboard_controller_ = keyboard::KeyboardController::Get();
-  }
-
-  void TearDown() override {
-    // Ensure inputs devices are reset for the next test.
-    ws::InputDeviceClientTestApi().SetKeyboardDevices({});
-    ws::InputDeviceClientTestApi().SetTouchscreenDevices({});
-
-    AshTestBase::TearDown();
-  }
-
   display::Display GetPrimaryDisplay() {
     return display::Screen::GetScreen()->GetPrimaryDisplay();
   }
@@ -81,7 +66,9 @@
     focusable_window->Focus();
   }
 
-  keyboard::KeyboardController* keyboard_controller_ = nullptr;
+  keyboard::KeyboardController* keyboard_controller() {
+    return keyboard::KeyboardController::Get();
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(VirtualKeyboardControllerTest);
@@ -136,7 +123,7 @@
             client.last_keyset_);
 
   // Simulate the keyboard hiding.
-  if (keyboard_controller_->HasObserver(GetVirtualKeyboardController())) {
+  if (keyboard_controller()->HasObserver(GetVirtualKeyboardController())) {
     GetVirtualKeyboardController()->OnKeyboardHidden(
         false /* is_temporary_hide */);
   }
@@ -161,33 +148,33 @@
   Shell::Get()->ime_controller()->SetClient(client.CreateInterfacePtr());
 
   // Should show the keyboard by enabling it temporarily.
-  EXPECT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
-  EXPECT_FALSE(
-      keyboard_controller_->IsEnableFlagSet(KeyboardEnableFlag::kShelfEnabled));
+  EXPECT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
+  EXPECT_FALSE(keyboard_controller()->IsEnableFlagSet(
+      KeyboardEnableFlag::kShelfEnabled));
 
   GetVirtualKeyboardController()->ForceShowKeyboardWithKeyset(
       chromeos::input_method::mojom::ImeKeyset::kEmoji);
   Shell::Get()->ime_controller()->FlushMojoForTesting();
 
-  EXPECT_TRUE(
-      keyboard_controller_->IsEnableFlagSet(KeyboardEnableFlag::kShelfEnabled));
-  EXPECT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  EXPECT_TRUE(keyboard_controller()->IsEnableFlagSet(
+      KeyboardEnableFlag::kShelfEnabled));
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
 
   // Keyset should be emoji.
   EXPECT_EQ(chromeos::input_method::mojom::ImeKeyset::kEmoji,
             client.last_keyset_);
 
   // Simulate the keyboard hiding.
-  if (keyboard_controller_->HasObserver(GetVirtualKeyboardController())) {
+  if (keyboard_controller()->HasObserver(GetVirtualKeyboardController())) {
     GetVirtualKeyboardController()->OnKeyboardHidden(
         false /* is_temporary_hide */);
   }
   base::RunLoop().RunUntilIdle();
 
   // The keyboard should still be disabled again.
-  EXPECT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
-  EXPECT_FALSE(
-      keyboard_controller_->IsEnableFlagSet(KeyboardEnableFlag::kShelfEnabled));
+  EXPECT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
+  EXPECT_FALSE(keyboard_controller()->IsEnableFlagSet(
+      KeyboardEnableFlag::kShelfEnabled));
 
   // Keyset should be reset to none.
   Shell::Get()->ime_controller()->FlushMojoForTesting();
@@ -206,25 +193,25 @@
       chromeos::input_method::mojom::ImeKeyset::kEmoji);
   Shell::Get()->ime_controller()->FlushMojoForTesting();
 
-  EXPECT_TRUE(
-      keyboard_controller_->IsEnableFlagSet(KeyboardEnableFlag::kShelfEnabled));
-  EXPECT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  EXPECT_TRUE(keyboard_controller()->IsEnableFlagSet(
+      KeyboardEnableFlag::kShelfEnabled));
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
 
   // Keyset should be emoji.
   EXPECT_EQ(chromeos::input_method::mojom::ImeKeyset::kEmoji,
             client.last_keyset_);
 
   // Simulate the keyboard hiding temporarily.
-  if (keyboard_controller_->HasObserver(GetVirtualKeyboardController())) {
+  if (keyboard_controller()->HasObserver(GetVirtualKeyboardController())) {
     GetVirtualKeyboardController()->OnKeyboardHidden(
         true /* is_temporary_hide */);
   }
   base::RunLoop().RunUntilIdle();
 
   // The keyboard should still be enabled.
-  EXPECT_TRUE(
-      keyboard_controller_->IsEnableFlagSet(KeyboardEnableFlag::kShelfEnabled));
-  EXPECT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  EXPECT_TRUE(keyboard_controller()->IsEnableFlagSet(
+      KeyboardEnableFlag::kShelfEnabled));
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
 
   // Keyset should still be emoji.
   EXPECT_EQ(chromeos::input_method::mojom::ImeKeyset::kEmoji,
@@ -283,13 +270,13 @@
   keyboard_devices.push_back(ui::InputDevice(
       1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL, "keyboard"));
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboard_devices);
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
   // Remove the internal keyboard. Virtual keyboard should now show.
   ws::InputDeviceClientTestApi().SetKeyboardDevices({});
-  EXPECT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
   // Replug in the internal keyboard. Virtual keyboard should hide.
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboard_devices);
-  EXPECT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  EXPECT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
 }
 
 TEST_F(VirtualKeyboardControllerAutoTest, DisabledIfNoTouchScreen) {
@@ -299,10 +286,10 @@
       ui::TouchscreenDevice(1, ui::InputDeviceType::INPUT_DEVICE_USB,
                             "Touchscreen", gfx::Size(800, 600), 0));
   ws::InputDeviceClientTestApi().SetTouchscreenDevices(devices);
-  EXPECT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  EXPECT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
   // Remove touchscreen. Keyboard should hide.
   ws::InputDeviceClientTestApi().SetTouchscreenDevices({});
-  EXPECT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  EXPECT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
 }
 
 TEST_F(VirtualKeyboardControllerAutoTest, SuppressedIfExternalKeyboardPresent) {
@@ -315,26 +302,26 @@
   keyboard_devices.push_back(
       ui::InputDevice(1, ui::InputDeviceType::INPUT_DEVICE_USB, "keyboard"));
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboard_devices);
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_TRUE(IsVirtualKeyboardSuppressed());
   // Toggle show keyboard. Keyboard should be visible.
   ResetObserver();
   GetVirtualKeyboardController()->ToggleIgnoreExternalKeyboard();
-  ASSERT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_TRUE(IsVirtualKeyboardSuppressed());
   // Toggle show keyboard. Keyboard should be hidden.
   ResetObserver();
   GetVirtualKeyboardController()->ToggleIgnoreExternalKeyboard();
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_TRUE(IsVirtualKeyboardSuppressed());
   // Remove external keyboard. Should be notified that the keyboard is not
   // suppressed.
   ResetObserver();
   ws::InputDeviceClientTestApi().SetKeyboardDevices({});
-  ASSERT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_FALSE(IsVirtualKeyboardSuppressed());
 }
@@ -349,7 +336,7 @@
   keyboards.push_back(
       ui::InputDevice(3, ui::InputDeviceType::INPUT_DEVICE_USB, "keyboard"));
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboards);
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
 }
 
 // Tests tablet mode interaction without disabling the internal keyboard.
@@ -363,13 +350,13 @@
   keyboard_devices.push_back(ui::InputDevice(
       1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL, "Keyboard"));
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboard_devices);
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
   // Toggle tablet mode on.
   TabletModeControllerTestApi().EnterTabletMode();
-  ASSERT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
   // Toggle tablet mode off.
   TabletModeControllerTestApi().LeaveTabletMode();
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
 }
 
 // Tests that keyboard gets suppressed in tablet mode.
@@ -387,19 +374,19 @@
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboard_devices);
   // Toggle tablet mode on.
   TabletModeControllerTestApi().EnterTabletMode();
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_TRUE(IsVirtualKeyboardSuppressed());
   // Toggle show keyboard. Keyboard should be visible.
   ResetObserver();
   GetVirtualKeyboardController()->ToggleIgnoreExternalKeyboard();
-  ASSERT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_TRUE(IsVirtualKeyboardSuppressed());
   // Toggle show keyboard. Keyboard should be hidden.
   ResetObserver();
   GetVirtualKeyboardController()->ToggleIgnoreExternalKeyboard();
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_TRUE(IsVirtualKeyboardSuppressed());
   // Remove external keyboard. Should be notified that the keyboard is not
@@ -407,12 +394,12 @@
   ResetObserver();
   keyboard_devices.pop_back();
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboard_devices);
-  ASSERT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
   ASSERT_TRUE(notified());
   ASSERT_FALSE(IsVirtualKeyboardSuppressed());
   // Toggle tablet mode oFF.
   TabletModeControllerTestApi().LeaveTabletMode();
-  ASSERT_FALSE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_FALSE(keyboard_controller()->IsKeyboardEnableRequested());
 }
 
 class VirtualKeyboardControllerAlwaysEnabledTest
@@ -444,7 +431,7 @@
   keyboard_devices.push_back(
       ui::InputDevice(1, ui::InputDeviceType::INPUT_DEVICE_USB, "keyboard"));
   ws::InputDeviceClientTestApi().SetKeyboardDevices(keyboard_devices);
-  ASSERT_TRUE(keyboard_controller_->IsKeyboardEnableRequested());
+  ASSERT_TRUE(keyboard_controller()->IsKeyboardEnableRequested());
 }
 
 // Test for http://crbug.com/297858. |GetContainerForDefaultDisplay| should
@@ -594,10 +581,10 @@
   UpdateDisplay("500x500,500x500");
 
   // Show in secondary display.
-  keyboard_controller_->ShowKeyboardInDisplay(GetSecondaryDisplay());
-  EXPECT_EQ(GetSecondaryRootWindow(), keyboard_controller_->GetRootWindow());
+  keyboard_controller()->ShowKeyboardInDisplay(GetSecondaryDisplay());
+  EXPECT_EQ(GetSecondaryRootWindow(), keyboard_controller()->GetRootWindow());
   ASSERT_TRUE(keyboard::WaitUntilShown());
-  EXPECT_TRUE(!keyboard_controller_->GetKeyboardWindow()->bounds().IsEmpty());
+  EXPECT_TRUE(!keyboard_controller()->GetKeyboardWindow()->bounds().IsEmpty());
 }
 
 }  // namespace ash
diff --git a/ash/login/login_screen_controller.cc b/ash/login/login_screen_controller.cc
index d676e35..05c2970 100644
--- a/ash/login/login_screen_controller.cc
+++ b/ash/login/login_screen_controller.cc
@@ -485,11 +485,13 @@
 }
 
 void LoginScreenController::SetKioskApps(
-    std::vector<mojom::KioskAppInfoPtr> kiosk_apps) {
+    std::vector<mojom::KioskAppInfoPtr> kiosk_apps,
+    SetKioskAppsCallback callback) {
   Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())
       ->shelf_widget()
       ->login_shelf_view()
       ->SetKioskApps(std::move(kiosk_apps));
+  std::move(callback).Run(true);
 }
 
 void LoginScreenController::ShowKioskAppError(const std::string& message) {
diff --git a/ash/login/login_screen_controller.h b/ash/login/login_screen_controller.h
index f177af2..ffba8952 100644
--- a/ash/login/login_screen_controller.h
+++ b/ash/login/login_screen_controller.h
@@ -5,6 +5,8 @@
 #ifndef ASH_LOGIN_LOGIN_SCREEN_CONTROLLER_H_
 #define ASH_LOGIN_LOGIN_SCREEN_CONTROLLER_H_
 
+#include <vector>
+
 #include "ash/ash_export.h"
 #include "ash/login/login_screen_controller_observer.h"
 #include "ash/public/cpp/system_tray_focus_observer.h"
@@ -158,7 +160,8 @@
       std::vector<mojom::InputMethodItemPtr> keyboard_layouts) override;
   void SetPublicSessionShowFullManagementDisclosure(
       bool is_full_management_disclosure_needed) override;
-  void SetKioskApps(std::vector<mojom::KioskAppInfoPtr> kiosk_apps) override;
+  void SetKioskApps(std::vector<mojom::KioskAppInfoPtr> kiosk_apps,
+                    SetKioskAppsCallback callback) override;
   void ShowKioskAppError(const std::string& message) override;
   void NotifyOobeDialogState(mojom::OobeDialogState state) override;
   void SetAddUserButtonEnabled(bool enable) override;
diff --git a/ash/login/login_screen_test_api.cc b/ash/login/login_screen_test_api.cc
index 41deee0..9cdbc73 100644
--- a/ash/login/login_screen_test_api.cc
+++ b/ash/login/login_screen_test_api.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "ash/login/ui/lock_contents_view.h"
 #include "ash/login/ui/lock_screen.h"
@@ -44,6 +45,75 @@
 
 }  // anonymous namespace
 
+class ShelfTestUiUpdateDelegate : public LoginShelfView::TestUiUpdateDelegate {
+ public:
+  using Callback = mojom::LoginScreenTestApi::WaitForUiUpdateCallback;
+
+  // Returns instance owned by LoginShelfView. Installs instance of
+  // ShelfTestUiUpdateDelegate when needed.
+  static ShelfTestUiUpdateDelegate* Get(LoginShelfView* shelf) {
+    if (!shelf->test_ui_update_delegate()) {
+      shelf->InstallTestUiUpdateDelegate(
+          std::make_unique<ShelfTestUiUpdateDelegate>());
+    }
+    return static_cast<ShelfTestUiUpdateDelegate*>(
+        shelf->test_ui_update_delegate());
+  }
+
+  ShelfTestUiUpdateDelegate() = default;
+  ~ShelfTestUiUpdateDelegate() override {
+    for (PendingCallback& entry : heap_)
+      std::move(entry.callback).Run(false);
+  }
+
+  // Returns UI update count.
+  int64_t ui_update_count() const { return ui_update_count_; }
+
+  // Add a callback to be invoked when ui update count is greater than
+  // |previous_update_count|. Note |callback| could be invoked synchronously
+  // when the current ui update count is already greater than
+  // |previous_update_count|.
+  void AddCallback(int64_t previous_update_count, Callback callback) {
+    if (previous_update_count < ui_update_count_) {
+      std::move(callback).Run(true);
+    } else {
+      heap_.emplace_back(previous_update_count, std::move(callback));
+      std::push_heap(heap_.begin(), heap_.end());
+    }
+  }
+
+  // LoginShelfView::TestUiUpdateDelegate
+  void OnUiUpdate() override {
+    ++ui_update_count_;
+    while (!heap_.empty() && heap_.front().old_count < ui_update_count_) {
+      std::move(heap_.front().callback).Run(true);
+      std::pop_heap(heap_.begin(), heap_.end());
+      heap_.pop_back();
+    }
+  }
+
+ private:
+  struct PendingCallback {
+    PendingCallback(int64_t old_count, Callback callback)
+        : old_count(old_count), callback(std::move(callback)) {}
+
+    bool operator<(const PendingCallback& right) const {
+      // We need min_heap, therefore this returns true when another element on
+      // the right is less than this count. (regular heap is max_heap).
+      return old_count > right.old_count;
+    }
+
+    int64_t old_count = 0;
+    Callback callback;
+  };
+
+  std::vector<PendingCallback> heap_;
+
+  int64_t ui_update_count_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(ShelfTestUiUpdateDelegate);
+};
+
 // static
 void LoginScreenTestApi::BindRequest(mojom::LoginScreenTestApiRequest request) {
   mojo::MakeStrongBinding(std::make_unique<LoginScreenTestApi>(),
@@ -105,7 +175,33 @@
 void LoginScreenTestApi::GetUiUpdateCount(GetUiUpdateCountCallback callback) {
   LoginShelfView* view = GetLoginShelfView();
 
-  std::move(callback).Run(view ? view->ui_update_count() : 0);
+  std::move(callback).Run(
+      view ? ShelfTestUiUpdateDelegate::Get(view)->ui_update_count() : 0);
+}
+
+void LoginScreenTestApi::LaunchApp(const std::string& app_id,
+                                   LaunchAppCallback callback) {
+  LoginShelfView* view = GetLoginShelfView();
+
+  std::move(callback).Run(view && view->LaunchAppForTesting(app_id));
+}
+
+void LoginScreenTestApi::ClickAddUserButton(
+    ClickAddUserButtonCallback callback) {
+  LoginShelfView* view = GetLoginShelfView();
+
+  std::move(callback).Run(view && view->SimulateAddUserButtonForTesting());
+}
+
+void LoginScreenTestApi::WaitForUiUpdate(int64_t previous_update_count,
+                                         WaitForUiUpdateCallback callback) {
+  LoginShelfView* view = GetLoginShelfView();
+  if (view) {
+    ShelfTestUiUpdateDelegate::Get(view)->AddCallback(previous_update_count,
+                                                      std::move(callback));
+  } else {
+    std::move(callback).Run(false);
+  }
 }
 
 }  // namespace ash
diff --git a/ash/login/login_screen_test_api.h b/ash/login/login_screen_test_api.h
index a61a35c..907881df 100644
--- a/ash/login/login_screen_test_api.h
+++ b/ash/login/login_screen_test_api.h
@@ -5,6 +5,8 @@
 #ifndef ASH_LOGIN_LOGIN_SCREEN_TEST_API_H_
 #define ASH_LOGIN_LOGIN_SCREEN_TEST_API_H_
 
+#include <string>
+
 #include "ash/public/interfaces/login_screen_test_api.test-mojom.h"
 #include "base/macros.h"
 #include "components/account_id/account_id.h"
@@ -29,6 +31,16 @@
                       const std::string& password,
                       SubmitPasswordCallback callback) override;
   void GetUiUpdateCount(GetUiUpdateCountCallback callback) override;
+  void LaunchApp(const std::string& app_id,
+                 LaunchAppCallback callback) override;
+  void ClickAddUserButton(ClickAddUserButtonCallback callback) override;
+  // This blocks until UI update number becomes greater than the
+  // |previous_update_count|.  Where |previous_update_count| presumably is
+  // coming from GetUiUpdateCount().  This way test remembers the "current" UI
+  // update version, performs some actions, and then waits until UI switches to
+  // the new version.
+  void WaitForUiUpdate(int64_t previous_update_count,
+                       WaitForUiUpdateCallback callback) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoginScreenTestApi);
diff --git a/ash/metrics/pointer_metrics_recorder.cc b/ash/metrics/pointer_metrics_recorder.cc
index 85e00e1..2ae3082 100644
--- a/ash/metrics/pointer_metrics_recorder.cc
+++ b/ash/metrics/pointer_metrics_recorder.cc
@@ -29,18 +29,38 @@
 
 // Find the input type, form factor and destination combination of the down
 // event. Used to get the UMA histogram bucket.
-DownEventMetric FindCombination(DownEventSource input_type,
-                                DownEventFormFactor form_factor,
-                                int destination) {
+DownEventMetric FindCombinationDeprecated(DownEventSource input_type,
+                                          DownEventFormFactor form_factor,
+                                          int destination) {
+  if (destination == static_cast<int>(AppType::CROSTINI_APP))
+    destination = static_cast<int>(AppType::OTHERS);
+  constexpr int kAppCountDeprecated = kAppCount - 1;
   int num_combination_per_input =
-      kAppCount * static_cast<int>(DownEventFormFactor::kFormFactorCount);
+      kAppCountDeprecated *
+      static_cast<int>(DownEventFormFactor::kFormFactorCount);
   int result = static_cast<int>(input_type) * num_combination_per_input +
-               static_cast<int>(form_factor) * kAppCount + destination;
+               static_cast<int>(form_factor) * kAppCountDeprecated +
+               destination;
   DCHECK(result >= 0 &&
          result < static_cast<int>(DownEventMetric::kCombinationCount));
   return static_cast<DownEventMetric>(result);
 }
 
+DownEventMetric2 FindCombination(int destination,
+                                 DownEventSource input_type,
+                                 DownEventFormFactor form_factor) {
+  constexpr int kNumCombinationPerDestination =
+      static_cast<int>(DownEventSource::kSourceCount) *
+      static_cast<int>(DownEventFormFactor::kFormFactorCount);
+  int result = destination * kNumCombinationPerDestination +
+               static_cast<int>(DownEventFormFactor::kFormFactorCount) *
+                   static_cast<int>(input_type) +
+               static_cast<int>(form_factor);
+  DCHECK(result >= 0 &&
+         result <= static_cast<int>(DownEventMetric2::kMaxValue));
+  return static_cast<DownEventMetric2>(result);
+}
+
 void RecordUMA(ui::EventPointerType type, ui::EventTarget* event_target) {
   DCHECK_NE(type, ui::EventPointerType::POINTER_TYPE_UNKNOWN);
   views::Widget* target = views::Widget::GetTopLevelWidgetForNativeView(
@@ -79,8 +99,13 @@
 
   UMA_HISTOGRAM_ENUMERATION(
       "Event.DownEventCount.PerInputFormFactorDestinationCombination",
-      FindCombination(input_type, form_factor, GetDestination(target)),
+      FindCombinationDeprecated(input_type, form_factor,
+                                GetDestination(target)),
       DownEventMetric::kCombinationCount);
+
+  UMA_HISTOGRAM_ENUMERATION(
+      "Event.DownEventCount.PerInputFormFactorDestinationCombination2",
+      FindCombination(GetDestination(target), input_type, form_factor));
 }
 
 }  // namespace
diff --git a/ash/metrics/pointer_metrics_recorder.h b/ash/metrics/pointer_metrics_recorder.h
index bacc4da3..3532421 100644
--- a/ash/metrics/pointer_metrics_recorder.h
+++ b/ash/metrics/pointer_metrics_recorder.h
@@ -32,6 +32,74 @@
   kSourceCount,
 };
 
+// App type (Destination), Input and FormFactor Combination of the down event.
+// This enum is used to back an UMA histogram and new values should
+// be inserted immediately above kCombinationCount.
+enum class DownEventMetric2 {
+  // All "Unknown" types are deprecated, never occur in practice.
+  kOthersUnknownClamshell = 0,
+  kOthersUnknownTabletLandscape = 1,
+  kOthersUnknownTabletPortrait = 2,
+  kOthersMouseClamshell = 3,
+  kOthersMouseTabletLandscape = 4,
+  kOthersMouseTabletPortrait = 5,
+  kOthersStylusClamshell = 6,
+  kOthersStylusTabletLandscape = 7,
+  kOthersStylusTabletPortrait = 8,
+  kOthersTouchClamshell = 9,
+  kOthersTouchTabletLandscape = 10,
+  kOthersTouchTabletPortrait = 11,
+  kBrowserUnknownClamshell = 12,
+  kBrowserUnknownTabletLandscape = 13,
+  kBrowserUnknownTabletPortrait = 14,
+  kBrowserMouseClamshell = 15,
+  kBrowserMouseTabletLandscape = 16,
+  kBrowserMouseTabletPortrait = 17,
+  kBrowserStylusClamshell = 18,
+  kBrowserStylusTabletLandscape = 19,
+  kBrowserStylusTabletPortrait = 20,
+  kBrowserTouchClamshell = 21,
+  kBrowserTouchTabletLandscape = 22,
+  kBrowserTouchTabletPortrait = 23,
+  kChromeAppUnknownClamshell = 24,
+  kChromeAppUnknownTabletLandscape = 25,
+  kChromeAppUnknownTabletPortrait = 26,
+  kChromeAppMouseClamshell = 27,
+  kChromeAppMouseTabletLandscape = 28,
+  kChromeAppMouseTabletPortrait = 29,
+  kChromeAppStylusClamshell = 30,
+  kChromeAppStylusTabletLandscape = 31,
+  kChromeAppStylusTabletPortrait = 32,
+  kChromeAppTouchClamshell = 33,
+  kChromeAppTouchTabletLandscape = 34,
+  kChromeAppTouchTabletPortrait = 35,
+  kArcAppUnknownClamshell = 36,
+  kArcAppUnknownTabletLandscape = 37,
+  kArcAppUnknownTabletPortrait = 38,
+  kArcAppMouseClamshell = 39,
+  kArcAppMouseTabletLandscape = 40,
+  kArcAppMouseTabletPortrait = 41,
+  kArcAppStylusClamshell = 42,
+  kArcAppStylusTabletLandscape = 43,
+  kArcAppStylusTabletPortrait = 44,
+  kArcAppTouchClamshell = 45,
+  kArcAppTouchTabletLandscape = 46,
+  kArcAppTouchTabletPortrait = 47,
+  kCrostiniAppUnknownClamshell = 48,
+  kCrostiniAppUnknownTabletLandscape = 49,
+  kCrostiniAppUnknownTabletPortrait = 50,
+  kCrostiniAppMouseClamshell = 51,
+  kCrostiniAppMouseTabletLandscape = 52,
+  kCrostiniAppMouseTabletPortrait = 53,
+  kCrostiniAppStylusClamshell = 54,
+  kCrostiniAppStylusTabletLandscape = 55,
+  kCrostiniAppStylusTabletPortrait = 56,
+  kCrostiniAppTouchClamshell = 57,
+  kCrostiniAppTouchTabletLandscape = 58,
+  kCrostiniAppTouchTabletPortrait = 59,
+  kMaxValue = kCrostiniAppTouchTabletPortrait
+};
+
 // Input, FormFactor, and Destination Combination of the down event.
 // This enum is used to back an UMA histogram and new values should
 // be inserted immediately above kCombinationCount.
diff --git a/ash/metrics/pointer_metrics_recorder_unittest.cc b/ash/metrics/pointer_metrics_recorder_unittest.cc
index f479e54..d24aa697 100644
--- a/ash/metrics/pointer_metrics_recorder_unittest.cc
+++ b/ash/metrics/pointer_metrics_recorder_unittest.cc
@@ -20,9 +20,12 @@
 namespace ash {
 namespace {
 
-const char kCombinationHistogramName[] =
+const char kCombinationDeprecatedHistogramName[] =
     "Event.DownEventCount.PerInputFormFactorDestinationCombination";
 
+const char kCombinationHistogramName[] =
+    "Event.DownEventCount.PerInputFormFactorDestinationCombination2";
+
 // Test fixture for the PointerMetricsRecorder class.
 class PointerMetricsRecorderTest : public AshTestBase {
  public:
@@ -116,7 +119,7 @@
                           base::TimeTicks(), 0, 0);
   pointer_metrics_recorder_->OnMouseEvent(&mouse_up);
 
-  histogram_tester_->ExpectTotalCount(kCombinationHistogramName, 0);
+  histogram_tester_->ExpectTotalCount(kCombinationDeprecatedHistogramName, 0);
 }
 
 // Verifies that down events from different combination of input type, form
@@ -131,225 +134,518 @@
                   DownEventFormFactor::kClamshell, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
       kCombinationHistogramName,
-      static_cast<int>(DownEventMetric::kMouseClamshellOthers), 1);
-
-  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
-                  DownEventFormFactor::kClamshell, AppType::BROWSER);
-  histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
-      static_cast<int>(DownEventMetric::kMouseClamshellBrowser), 1);
-
-  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
-                  DownEventFormFactor::kClamshell, AppType::CHROME_APP);
-  histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
-      static_cast<int>(DownEventMetric::kMouseClamshellChromeApp), 1);
-
-  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
-                  DownEventFormFactor::kClamshell, AppType::ARC_APP);
-  histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
-      static_cast<int>(DownEventMetric::kMouseClamshellArcApp), 1);
+      static_cast<int>(DownEventMetric2::kOthersMouseClamshell), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModeLandscape, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
       kCombinationHistogramName,
-      static_cast<int>(DownEventMetric::kMouseTabletLandscapeOthers), 1);
+      static_cast<int>(DownEventMetric2::kOthersMouseTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModePortrait, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kOthersMouseTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kClamshell, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kOthersStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kOthersStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModePortrait, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kOthersStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kClamshell, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kOthersStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kOthersStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModePortrait, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kOthersStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserMouseClamshell), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModeLandscape, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
       kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserMouseTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModePortrait, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserMouseTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kClamshell, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModePortrait, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kClamshell, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModePortrait, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kBrowserStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppMouseClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModeLandscape,
+                  AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppMouseTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModePortrait,
+                  AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppMouseTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kClamshell, AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModeLandscape,
+                  AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModePortrait,
+                  AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kClamshell, AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModeLandscape,
+                  AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModePortrait,
+                  AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kChromeAppStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppMouseClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppMouseTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModePortrait, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppMouseTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kClamshell, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModePortrait, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kClamshell, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModePortrait, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kArcAppStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppMouseClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModeLandscape,
+                  AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppMouseTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModePortrait,
+                  AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppMouseTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kClamshell, AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModeLandscape,
+                  AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
+                  DownEventFormFactor::kTabletModePortrait,
+                  AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppStylusTabletPortrait), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kClamshell, AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppStylusClamshell), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModeLandscape,
+                  AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppStylusTabletLandscape), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
+                  DownEventFormFactor::kTabletModePortrait,
+                  AppType::CROSTINI_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationHistogramName,
+      static_cast<int>(DownEventMetric2::kCrostiniAppStylusTabletPortrait), 1);
+
+  histogram_tester_->ExpectTotalCount(kCombinationHistogramName, 45);
+}
+
+// Verifies that down events from different combination of input type, form
+// factor and destination are recorded.
+TEST_F(PointerMetricsRecorderTest, DownEventPerCombinationDeprecated) {
+  int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
+  display::DisplayManager* display_manager = Shell::Get()->display_manager();
+  display::test::ScopedSetInternalDisplayId set_internal(display_manager,
+                                                         display_id);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationDeprecatedHistogramName,
+      static_cast<int>(DownEventMetric::kMouseClamshellOthers), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationDeprecatedHistogramName,
+      static_cast<int>(DownEventMetric::kMouseClamshellBrowser), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::CHROME_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationDeprecatedHistogramName,
+      static_cast<int>(DownEventMetric::kMouseClamshellChromeApp), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kClamshell, AppType::ARC_APP);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationDeprecatedHistogramName,
+      static_cast<int>(DownEventMetric::kMouseClamshellArcApp), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::OTHERS);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationDeprecatedHistogramName,
+      static_cast<int>(DownEventMetric::kMouseTabletLandscapeOthers), 1);
+
+  CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
+                  DownEventFormFactor::kTabletModeLandscape, AppType::BROWSER);
+  histogram_tester_->ExpectBucketCount(
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kMouseTabletLandscapeBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModeLandscape,
                   AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kMouseTabletLandscapeChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModeLandscape, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kMouseTabletLandscapeArcApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModePortrait, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kMouseTabletPortraitOthers), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModePortrait, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kMouseTabletPortraitBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModePortrait,
                   AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kMouseTabletPortraitChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_MOUSE,
                   DownEventFormFactor::kTabletModePortrait, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kMouseTabletPortraitArcApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kClamshell, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellOthers), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kClamshell, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kClamshell, AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kClamshell, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellArcApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModeLandscape, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeOthers), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModeLandscape, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModeLandscape,
                   AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModeLandscape, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeArcApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModePortrait, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitOthers), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModePortrait, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModePortrait,
                   AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_PEN,
                   DownEventFormFactor::kTabletModePortrait, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitArcApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kClamshell, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellOthers), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kClamshell, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kClamshell, AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kClamshell, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusClamshellArcApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModeLandscape, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeOthers), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModeLandscape, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModeLandscape,
                   AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModeLandscape, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletLandscapeArcApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModePortrait, AppType::OTHERS);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitOthers), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModePortrait, AppType::BROWSER);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitBrowser), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModePortrait,
                   AppType::CHROME_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitChromeApp), 1);
 
   CreateDownEvent(ui::EventPointerType::POINTER_TYPE_TOUCH,
                   DownEventFormFactor::kTabletModePortrait, AppType::ARC_APP);
   histogram_tester_->ExpectBucketCount(
-      kCombinationHistogramName,
+      kCombinationDeprecatedHistogramName,
       static_cast<int>(DownEventMetric::kStylusTabletPortraitArcApp), 1);
 
-  histogram_tester_->ExpectTotalCount(kCombinationHistogramName, 36);
+  histogram_tester_->ExpectTotalCount(kCombinationDeprecatedHistogramName, 36);
 }
 
 }  // namespace ash
diff --git a/ash/mus_property_mirror_ash_unittest.cc b/ash/mus_property_mirror_ash_unittest.cc
index 16d1783..e664dfd 100644
--- a/ash/mus_property_mirror_ash_unittest.cc
+++ b/ash/mus_property_mirror_ash_unittest.cc
@@ -59,18 +59,6 @@
             *window_2->GetProperty(aura::client::kTitleKey));
   EXPECT_NE(window_1->GetProperty(aura::client::kTitleKey),
             window_2->GetProperty(aura::client::kTitleKey));
-
-  window_1->ClearProperty(aura::client::kWindowIconKey);
-  EXPECT_EQ(nullptr, window_1->GetProperty(aura::client::kWindowIconKey));
-  window_2->ClearProperty(aura::client::kWindowIconKey);
-  EXPECT_EQ(nullptr, window_2->GetProperty(aura::client::kWindowIconKey));
-  window_1->SetProperty(aura::client::kWindowIconKey, new gfx::ImageSkia());
-  EXPECT_NE(nullptr, window_1->GetProperty(aura::client::kWindowIconKey));
-  mus_property_mirror_ash.MirrorPropertyFromWidgetWindowToRootWindow(
-      window_1.get(), window_2.get(), aura::client::kWindowIconKey);
-  EXPECT_NE(nullptr, window_2->GetProperty(aura::client::kWindowIconKey));
-  EXPECT_NE(window_1->GetProperty(aura::client::kWindowIconKey),
-            window_2->GetProperty(aura::client::kWindowIconKey));
 }
 
 }  // namespace ash
diff --git a/ash/public/cpp/app_types.h b/ash/public/cpp/app_types.h
index 005f5f8..f68e777 100644
--- a/ash/public/cpp/app_types.h
+++ b/ash/public/cpp/app_types.h
@@ -16,7 +16,8 @@
   BROWSER,
   CHROME_APP,
   ARC_APP,
-  APP_TYPE_LAST = ARC_APP,
+  CROSTINI_APP,
+  APP_TYPE_LAST = CROSTINI_APP,
 };
 
 const int kAppCount = static_cast<int>(AppType::APP_TYPE_LAST) + 1;
diff --git a/ash/public/cpp/mus_property_mirror_ash.cc b/ash/public/cpp/mus_property_mirror_ash.cc
index 2792c80a..f2c7f20 100644
--- a/ash/public/cpp/mus_property_mirror_ash.cc
+++ b/ash/public/cpp/mus_property_mirror_ash.cc
@@ -50,8 +50,6 @@
   } else if (key == kWindowPinTypeKey) {
     ash::mojom::WindowPinType value = window->GetProperty(kWindowPinTypeKey);
     root_window->SetProperty(kWindowPinTypeKey, value);
-  } else if (key == aura::client::kAppIconKey) {
-    MirrorOwnedProperty(window, root_window, aura::client::kAppIconKey);
   } else if (key == kRestoreBoundsOverrideKey) {
     MirrorOwnedProperty(window, root_window, kRestoreBoundsOverrideKey);
   } else if (key == kRestoreWindowStateTypeOverrideKey) {
@@ -73,9 +71,6 @@
   } else if (key == aura::client::kTopViewInset) {
     root_window->SetProperty(aura::client::kTopViewInset,
                              window->GetProperty(aura::client::kTopViewInset));
-
-  } else if (key == aura::client::kWindowIconKey) {
-    MirrorOwnedProperty(window, root_window, aura::client::kWindowIconKey);
   } else if (key == kFrameActiveColorKey) {
     root_window->SetProperty(kFrameActiveColorKey,
                              window->GetProperty(kFrameActiveColorKey));
diff --git a/ash/public/interfaces/login_screen.mojom b/ash/public/interfaces/login_screen.mojom
index 764af8ed..923832c 100644
--- a/ash/public/interfaces/login_screen.mojom
+++ b/ash/public/interfaces/login_screen.mojom
@@ -195,7 +195,8 @@
       bool show_full_management_disclosure);
 
   // Update the kiosk app data for the login screen.
-  SetKioskApps(array<KioskAppInfo> kiosk_apps);
+  // Returns true on success.
+  SetKioskApps(array<KioskAppInfo> kiosk_apps) => (bool success);
 
   // Display a toast describing the latest kiosk app launch error.
   ShowKioskAppError(string message);
diff --git a/ash/public/interfaces/login_screen_test_api.test-mojom b/ash/public/interfaces/login_screen_test_api.test-mojom
index 1b1393dd..3598b59 100644
--- a/ash/public/interfaces/login_screen_test_api.test-mojom
+++ b/ash/public/interfaces/login_screen_test_api.test-mojom
@@ -25,4 +25,16 @@
 
   // Fetches current UI update count.
   GetUiUpdateCount() => (int64 count);
+
+  // Simulates Kiosk App launch. Returns true if launch attempt was successful.
+  // (I.e. app was found, and launch event was generated.)
+  LaunchApp(string app_id) => (bool found);
+
+  // Simulate Click on AddUser button.
+  // Returns true if request was successful.
+  ClickAddUserButton() => (bool success);
+
+  // Blocks until UI update counter is greated than |previous_update_count|.
+  // Returns true on success, false on error.
+  WaitForUiUpdate(int64 previous_update_count) => (bool success);
 };
diff --git a/ash/shelf/login_shelf_view.cc b/ash/shelf/login_shelf_view.cc
index f175e78f..7482844 100644
--- a/ash/shelf/login_shelf_view.cc
+++ b/ash/shelf/login_shelf_view.cc
@@ -4,7 +4,9 @@
 
 #include "ash/shelf/login_shelf_view.h"
 
+#include <algorithm>
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "ash/focus_cycler.h"
@@ -18,7 +20,6 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller.h"
-#include "ash/session/session_observer.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_constants.h"
 #include "ash/shelf/shelf_widget.h"
@@ -31,6 +32,8 @@
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/tray_action/tray_action.h"
 #include "ash/wm/lock_state_controller.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/metrics/user_metrics.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "skia/ext/image_operations.h"
@@ -126,8 +129,7 @@
                     GetButtonHighlightPath(view).release());
 }
 
-class LoginShelfButton : public views::LabelButton,
-                         public ash::SessionObserver {
+class LoginShelfButton : public views::LabelButton {
  public:
   LoginShelfButton(views::ButtonListener* listener,
                    const base::string16& text,
@@ -157,9 +159,7 @@
     SetTextSubpixelRenderingEnabled(false);
 
     SetImageLabelSpacing(kImageLabelSpacingDp);
-    SetTextColor(views::Button::STATE_NORMAL, kButtonTextColor);
-    SetTextColor(views::Button::STATE_HOVERED, kButtonTextColor);
-    SetTextColor(views::Button::STATE_PRESSED, kButtonTextColor);
+    SetEnabledTextColors(kButtonTextColor);
     SetTextColor(
         views::Button::STATE_DISABLED,
         SkColorSetA(kButtonTextColor, login_constants::kButtonDisabledAlpha));
@@ -195,30 +195,29 @@
     return ink_drop;
   }
 
-  // ash::SessionObserver:
-  void OnSessionStateChanged(session_manager::SessionState state) override {
-    if (state == session_manager::SessionState::OOBE) {
-      SetTextColor(views::Button::STATE_NORMAL, gfx::kGoogleGrey600);
-      SetTextColor(views::Button::STATE_HOVERED, gfx::kGoogleGrey600);
-      SetTextColor(views::Button::STATE_PRESSED, gfx::kGoogleGrey600);
-      SetImage(views::Button::STATE_NORMAL,
-               gfx::CreateVectorIcon(icon_, gfx::kGoogleGrey600));
-    } else {
-      SetTextColor(views::Button::STATE_NORMAL, kButtonTextColor);
-      SetTextColor(views::Button::STATE_HOVERED, kButtonTextColor);
-      SetTextColor(views::Button::STATE_PRESSED, kButtonTextColor);
-      SetImage(views::Button::STATE_NORMAL,
-               gfx::CreateVectorIcon(icon_, kButtonTextColor));
-    }
+  void PaintDarkColors() {
+    SetEnabledTextColors(gfx::kGoogleGrey600);
+    SetImage(views::Button::STATE_NORMAL,
+             gfx::CreateVectorIcon(icon_, gfx::kGoogleGrey600));
+  }
+
+  void PaintLightColors() {
+    SetEnabledTextColors(kButtonTextColor);
+    SetImage(views::Button::STATE_NORMAL,
+             gfx::CreateVectorIcon(icon_, kButtonTextColor));
   }
 
  private:
-  ash::ScopedSessionObserver observer_{this};
   const gfx::VectorIcon& icon_;
 
   DISALLOW_COPY_AND_ASSIGN(LoginShelfButton);
 };
 
+void StartAddUser() {
+  Shell::Get()->login_screen_controller()->ShowGaiaSignin(
+      true /*can_close*/, base::nullopt /*prefilled_account*/);
+}
+
 }  // namespace
 
 class KioskAppsButton : public views::MenuButton,
@@ -226,8 +225,9 @@
                         public ui::SimpleMenuModel,
                         public ui::SimpleMenuModel::Delegate {
  public:
-  KioskAppsButton(const base::string16& text, const gfx::ImageSkia& image)
-      : MenuButton(text, this), ui::SimpleMenuModel(this) {
+  KioskAppsButton()
+      : MenuButton(l10n_util::GetStringUTF16(IDS_ASH_SHELF_APPS_BUTTON), this),
+        ui::SimpleMenuModel(this) {
     SetFocusBehavior(FocusBehavior::ALWAYS);
     SetInstallFocusRingOnFocus(true);
     focus_ring()->SetColor(kFocusBorderColor);
@@ -244,15 +244,25 @@
 
     SetTextSubpixelRenderingEnabled(false);
 
-    SetImage(views::Button::STATE_NORMAL, image);
+    SetImage(views::Button::STATE_NORMAL,
+             CreateVectorIcon(kShelfAppsButtonIcon, kButtonIconColor));
     SetImageLabelSpacing(kImageLabelSpacingDp);
-    SetTextColor(views::Button::STATE_NORMAL, kButtonTextColor);
-    SetTextColor(views::Button::STATE_HOVERED, kButtonTextColor);
-    SetTextColor(views::Button::STATE_PRESSED, kButtonTextColor);
+    SetEnabledTextColors(kButtonTextColor);
     label()->SetFontList(views::Label::GetDefaultFontList().Derive(
         1, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
   }
 
+  bool LaunchAppForTesting(const std::string& app_id) {
+    for (size_t i = 0; i < kiosk_apps_.size(); ++i) {
+      if (kiosk_apps_[i]->identifier->get_app_id() != app_id)
+        continue;
+
+      ExecuteCommand(i, 0);
+      return true;
+    }
+    return false;
+  }
+
   // Replace the existing items list with a new list of kiosk app menu items.
   void SetApps(std::vector<mojom::KioskAppInfoPtr> kiosk_apps) {
     kiosk_apps_ = std::move(kiosk_apps);
@@ -300,6 +310,18 @@
     return ink_drop;
   }
 
+  void PaintDarkColors() {
+    SetEnabledTextColors(gfx::kGoogleGrey600);
+    SetImage(views::Button::STATE_NORMAL,
+             CreateVectorIcon(kShelfAppsButtonIcon, gfx::kGoogleGrey600));
+  }
+
+  void PaintLightColors() {
+    SetEnabledTextColors(kButtonTextColor);
+    SetImage(views::Button::STATE_NORMAL,
+             CreateVectorIcon(kShelfAppsButtonIcon, kButtonIconColor));
+  }
+
   // views::MenuButtonListener:
   void OnMenuButtonClicked(MenuButton* source,
                            const gfx::Point& point,
@@ -353,6 +375,8 @@
   DISALLOW_COPY_AND_ASSIGN(KioskAppsButton);
 };
 
+LoginShelfView::TestUiUpdateDelegate::~TestUiUpdateDelegate() = default;
+
 LoginShelfView::LoginShelfView(
     LockScreenActionBackgroundController* lock_screen_action_background)
     : lock_screen_action_background_(lock_screen_action_background),
@@ -376,9 +400,7 @@
   };
   add_button(kShutdown, IDS_ASH_SHELF_SHUTDOWN_BUTTON,
              kShelfShutdownButtonIcon);
-  kiosk_apps_button_ = new KioskAppsButton(
-      l10n_util::GetStringUTF16(IDS_ASH_SHELF_APPS_BUTTON),
-      CreateVectorIcon(kShelfAppsButtonIcon, kButtonIconColor));
+  kiosk_apps_button_ = new KioskAppsButton();
   kiosk_apps_button_->set_id(kApps);
   AddChildView(kiosk_apps_button_);
   add_button(kRestart, IDS_ASH_SHELF_RESTART_BUTTON, kShelfShutdownButtonIcon);
@@ -484,8 +506,7 @@
       Shell::Get()->login_screen_controller()->LoginAsGuest();
       break;
     case kAddUser:
-      Shell::Get()->login_screen_controller()->ShowGaiaSignin(
-          true /*can_close*/, base::nullopt /*prefilled_account*/);
+      StartAddUser();
       break;
     case kParentAccess:
       Shell::Get()->login_screen_controller()->SetShowParentAccessDialog(true);
@@ -495,6 +516,26 @@
   }
 }
 
+bool LoginShelfView::LaunchAppForTesting(const std::string& app_id) {
+  return kiosk_apps_button_->enabled() &&
+         kiosk_apps_button_->LaunchAppForTesting(app_id);
+}
+
+bool LoginShelfView::SimulateAddUserButtonForTesting() {
+  views::View* add_user_button = GetViewByID(kAddUser);
+  if (!add_user_button->enabled())
+    return false;
+
+  StartAddUser();
+  return true;
+}
+
+void LoginShelfView::InstallTestUiUpdateDelegate(
+    std::unique_ptr<TestUiUpdateDelegate> delegate) {
+  DCHECK(!test_ui_update_delegate_.get());
+  test_ui_update_delegate_ = std::move(delegate);
+}
+
 void LoginShelfView::SetKioskApps(
     std::vector<mojom::KioskAppInfoPtr> kiosk_apps) {
   kiosk_apps_button_->SetApps(std::move(kiosk_apps));
@@ -561,6 +602,14 @@
 }
 
 void LoginShelfView::UpdateUi() {
+  // Make sure observers are notified.
+  base::ScopedClosureRunner fire_observer(base::BindOnce(
+      [](LoginShelfView* self) {
+        if (self->test_ui_update_delegate())
+          self->test_ui_update_delegate()->OnUiUpdate();
+      },
+      base::Unretained(this)));
+
   SessionState session_state =
       Shell::Get()->session_controller()->GetSessionState();
   if (session_state == SessionState::ACTIVE) {
@@ -568,9 +617,9 @@
     // to avoid affecting calculation of the shelf size.
     for (int i = 0; i < child_count(); ++i)
       child_at(i)->SetVisible(false);
+
     return;
   }
-  ++ui_update_count_;
   bool show_reboot = Shell::Get()->shutdown_controller()->reboot_on_shutdown();
   mojom::TrayActionState tray_action_state =
       Shell::Get()->tray_action()->GetLockScreenNoteState();
@@ -580,7 +629,7 @@
        tray_action_state == mojom::TrayActionState::kLaunching) &&
       !LockScreenActionBackgroundAnimating();
 
-  // The following should be kept in sync with |updateUI_| in md_header_bar.js.
+  // TODO: https://crbug.com/935849
   GetViewByID(kShutdown)->SetVisible(!show_reboot &&
                                      !is_lock_screen_note_in_foreground);
   GetViewByID(kRestart)->SetVisible(show_reboot &&
@@ -628,11 +677,41 @@
   kiosk_apps_button_->SetVisible(
       (!dialog_visible ||
        dialog_state_ == mojom::OobeDialogState::GAIA_SIGNIN) &&
-      kiosk_apps_button_->HasApps() && is_login_primary);
+      kiosk_apps_button_->HasApps() && (is_login_primary || is_oobe));
+
+  UpdateButtonColors(is_oobe);
   Layout();
   UpdateButtonUnionBounds();
 }
 
+void LoginShelfView::UpdateButtonColors(bool use_dark_colors) {
+  if (use_dark_colors) {
+    static_cast<LoginShelfButton*>(GetViewByID(kShutdown))->PaintDarkColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kRestart))->PaintDarkColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kSignOut))->PaintDarkColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kCloseNote))->PaintDarkColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kCancel))->PaintDarkColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kParentAccess))
+        ->PaintDarkColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kBrowseAsGuest))
+        ->PaintDarkColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kAddUser))->PaintDarkColors();
+    kiosk_apps_button_->PaintDarkColors();
+  } else {
+    static_cast<LoginShelfButton*>(GetViewByID(kShutdown))->PaintLightColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kRestart))->PaintLightColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kSignOut))->PaintLightColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kCloseNote))->PaintLightColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kCancel))->PaintLightColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kParentAccess))
+        ->PaintLightColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kBrowseAsGuest))
+        ->PaintLightColors();
+    static_cast<LoginShelfButton*>(GetViewByID(kAddUser))->PaintLightColors();
+    kiosk_apps_button_->PaintLightColors();
+  }
+}
+
 void LoginShelfView::UpdateButtonUnionBounds() {
   button_union_bounds_ = gfx::Rect();
   View::Views children = GetChildrenInZOrder();
diff --git a/ash/shelf/login_shelf_view.h b/ash/shelf/login_shelf_view.h
index 9daee64..bc6e0906 100644
--- a/ash/shelf/login_shelf_view.h
+++ b/ash/shelf/login_shelf_view.h
@@ -5,6 +5,8 @@
 #ifndef ASH_SHELF_LOGIN_SHELF_VIEW_H_
 #define ASH_SHELF_LOGIN_SHELF_VIEW_H_
 
+#include <memory>
+#include <string>
 #include <vector>
 
 #include "ash/ash_export.h"
@@ -61,6 +63,13 @@
     kParentAccess    // Unlock child device with Parent Access Code.
   };
 
+  // Stores and notifies UiUpdate test callbacks.
+  class TestUiUpdateDelegate {
+   public:
+    virtual ~TestUiUpdateDelegate();
+    virtual void OnUiUpdate() = 0;
+  };
+
   explicit LoginShelfView(
       LockScreenActionBackgroundController* lock_screen_action_background);
   ~LoginShelfView() override;
@@ -101,9 +110,21 @@
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
-  int ui_update_count() const { return ui_update_count_; }
   gfx::Rect get_button_union_bounds() const { return button_union_bounds_; }
 
+  // Test API. Returns true if request was successful (i.e. button was
+  // clickable).
+  bool LaunchAppForTesting(const std::string& app_id);
+  bool SimulateAddUserButtonForTesting();
+
+  // Adds test delegate. Delegate will become owned by LoginShelfView.
+  void InstallTestUiUpdateDelegate(
+      std::unique_ptr<TestUiUpdateDelegate> delegate);
+
+  TestUiUpdateDelegate* test_ui_update_delegate() {
+    return test_ui_update_delegate_.get();
+  }
+
  protected:
   // TrayActionObserver:
   void OnLockScreenNoteStateChanged(mojom::TrayActionState state) override;
@@ -129,6 +150,10 @@
   // policy updates, session state changes etc.
   void UpdateUi();
 
+  // Updates the color of all buttons. Uses dark colors if |use_dark_colors| is
+  // true, light colors otherwise.
+  void UpdateButtonColors(bool use_dark_colors);
+
   // Updates the total bounds of all buttons.
   void UpdateButtonUnionBounds();
 
@@ -157,7 +182,7 @@
   KioskAppsButton* kiosk_apps_button_ = nullptr;  // Owned by view hierarchy
 
   // This is used in tests to wait until UI is updated.
-  int ui_update_count_ = 0;
+  std::unique_ptr<TestUiUpdateDelegate> test_ui_update_delegate_;
 
   // The bounds of all the buttons that this view is showing. Useful for
   // letting events that target the "empty space" pass through. These
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 410a5f4..118ea1b 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -11,9 +11,10 @@
 #include "ash/animation/animation_change_type.h"
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/app_list/app_list_metrics.h"
-#include "ash/app_list/home_launcher_gesture_handler.h"
 #include "ash/app_list/model/app_list_view_state.h"
 #include "ash/app_list/views/app_list_view.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
@@ -181,6 +182,10 @@
   Shell::Get()->AddShellObserver(this);
   Shell::Get()->overview_controller()->AddObserver(this);
   Shell::Get()->app_list_controller()->AddObserver(this);
+  Shell::Get()
+      ->home_screen_controller()
+      ->home_launcher_gesture_handler()
+      ->AddObserver(this);
   Shell::Get()->lock_state_controller()->AddObserver(this);
   Shell::Get()->activation_client()->AddObserver(this);
   Shell::Get()->locale_update_controller()->AddObserver(this);
@@ -201,8 +206,14 @@
   Shell::Get()->locale_update_controller()->RemoveObserver(this);
   Shell::Get()->RemoveShellObserver(this);
   Shell::Get()->lock_state_controller()->RemoveObserver(this);
-  // AppListController is destroyed early when Shell is being destroyed, it may
-  // not exist.
+  // The following are destroyed early when Shell is being destroyed so they
+  // may not exist.
+  if (Shell::Get()->home_screen_controller()) {
+    Shell::Get()
+        ->home_screen_controller()
+        ->home_launcher_gesture_handler()
+        ->RemoveObserver(this);
+  }
   if (Shell::Get()->app_list_controller())
     Shell::Get()->app_list_controller()->RemoveObserver(this);
   if (Shell::Get()->overview_controller())
@@ -331,9 +342,7 @@
             shelf_widget_->GetWindowBoundsInScreen().Contains(
                 display::Screen::GetScreen()->GetCursorScreenPoint());
       }
-      auto_hide_timer_.Start(
-          FROM_HERE, base::TimeDelta::FromMilliseconds(kAutoHideDelayMS), this,
-          &ShelfLayoutManager::UpdateAutoHideStateNow);
+      StartAutoHideTimer();
     }
   } else {
     StopAutoHideTimer();
@@ -355,10 +364,22 @@
     return;
 
   if (event->type() == ui::ET_MOUSE_PRESSED ||
-      (event->type() == ui::ET_MOUSE_MOVED &&
-       GetVisibleShelfBounds().Contains(
-           display::Screen::GetScreen()->GetCursorScreenPoint()))) {
-    UpdateAutoHideState();
+      event->type() == ui::ET_MOUSE_MOVED) {
+    if (GetVisibleShelfBounds().Contains(
+            display::Screen::GetScreen()->GetCursorScreenPoint())) {
+      UpdateAutoHideState();
+      last_seen_mouse_position_was_over_shelf_ = true;
+    } else {
+      // The event happened outside the shelf's bounds. If it's a click, hide
+      // the shelf immediately. If it's a mouse-out, hide after a delay (but
+      // only if it really is a mouse-out, meaning the mouse actually exited the
+      // shelf bounds as opposed to having been outside all along).
+      if (event->type() == ui::ET_MOUSE_PRESSED)
+        UpdateAutoHideState();
+      else if (last_seen_mouse_position_was_over_shelf_)
+        StartAutoHideTimer();
+      last_seen_mouse_position_was_over_shelf_ = false;
+    }
   }
 }
 
@@ -1048,6 +1069,12 @@
   StopAutoHideTimer();
 }
 
+void ShelfLayoutManager::StartAutoHideTimer() {
+  auto_hide_timer_.Start(FROM_HERE,
+                         base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
+                         this, &ShelfLayoutManager::UpdateAutoHideStateNow);
+}
+
 void ShelfLayoutManager::StopAutoHideTimer() {
   auto_hide_timer_.Stop();
   mouse_over_shelf_when_auto_hide_timer_started_ = false;
@@ -1172,7 +1199,7 @@
 
   gfx::Point cursor_position_in_screen =
       display::Screen::GetScreen()->GetCursorScreenPoint();
-  // Cursor is invisible in talbet mode and plug in an external mouse in tablet
+  // Cursor is invisible in tablet mode and plug in an external mouse in tablet
   // mode will switch to clamshell mode.
   if (shelf_region.Contains(cursor_position_in_screen) &&
       !IsTabletModeEnabled()) {
@@ -1296,7 +1323,7 @@
     GestureDragStatus previous_drag_status = gesture_drag_status_;
     gesture_drag_status_ = GESTURE_DRAG_APPLIST_IN_PROGRESS;
     HomeLauncherGestureHandler* home_launcher_handler =
-        Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
+        Shell::Get()->home_screen_controller()->home_launcher_gesture_handler();
     if (home_launcher_handler->OnPressEvent(
             HomeLauncherGestureHandler::Mode::kSlideUpToShow,
             gesture_in_screen.location())) {
@@ -1327,7 +1354,7 @@
     const ui::GestureEvent& gesture_in_screen) {
   if (ShouldHomeGestureHandleEvent(gesture_in_screen.details().scroll_y())) {
     HomeLauncherGestureHandler* home_launcher_handler =
-        Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
+        Shell::Get()->home_screen_controller()->home_launcher_gesture_handler();
     if (home_launcher_handler->OnScrollEvent(
             gesture_in_screen.location(),
             gesture_in_screen.details().scroll_y())) {
@@ -1391,7 +1418,7 @@
     return;
 
   HomeLauncherGestureHandler* home_launcher_handler =
-      Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
+      Shell::Get()->home_screen_controller()->home_launcher_gesture_handler();
   DCHECK(home_launcher_handler);
   if (home_launcher_handler->OnReleaseEvent(gesture_in_screen.location())) {
     gesture_drag_status_ = GESTURE_DRAG_NONE;
@@ -1414,7 +1441,7 @@
 void ShelfLayoutManager::CancelGestureDrag() {
   if (gesture_drag_status_ == GESTURE_DRAG_APPLIST_IN_PROGRESS) {
     HomeLauncherGestureHandler* home_launcher_handler =
-        Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
+        Shell::Get()->home_screen_controller()->home_launcher_gesture_handler();
     DCHECK(home_launcher_handler);
     if (home_launcher_handler->IsDragInProgress())
       home_launcher_handler->Cancel();
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index 32bfaf4f..e405f28 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -9,6 +9,7 @@
 
 #include "ash/app_list/app_list_controller_observer.h"
 #include "ash/ash_export.h"
+#include "ash/home_screen/home_launcher_gesture_handler_observer.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/session/session_observer.h"
 #include "ash/shelf/shelf.h"
@@ -52,6 +53,7 @@
 // On mus, widget bounds management is handled by the window manager.
 class ASH_EXPORT ShelfLayoutManager
     : public AppListControllerObserver,
+      public HomeLauncherGestureHandlerObserver,
       public ShellObserver,
       public OverviewObserver,
       public ::wm::ActivationChangeObserver,
@@ -153,6 +155,8 @@
 
   // AppListControllerObserver:
   void OnAppListVisibilityChanged(bool shown, int64_t display_id) override;
+
+  // HomeLauncherGestureHandlerObserver:
   void OnHomeLauncherTargetPositionChanged(bool showing,
                                            int64_t display_id) override;
   void OnHomeLauncherAnimationComplete(bool shown, int64_t display_id) override;
@@ -304,6 +308,10 @@
   // Updates the auto hide state immediately.
   void UpdateAutoHideStateNow();
 
+  // Starts the auto hide timer, so that the shelf will be hidden after the
+  // timeout (unless something else happens to interrupt / reset it).
+  void StartAutoHideTimer();
+
   // Stops the auto hide timer and clears
   // |mouse_over_shelf_when_auto_hide_timer_started_|.
   void StopAutoHideTimer();
@@ -407,6 +415,11 @@
   // False when neither the auto hide timer nor the timer task are running.
   bool mouse_over_shelf_when_auto_hide_timer_started_ = false;
 
+  // Whether the mouse pointer (not the touch pointer) was over the shelf last
+  // time we saw it. This is used to differentiate between mouse and touch in
+  // the shelf autohide behavior.
+  bool last_seen_mouse_position_was_over_shelf_ = false;
+
   base::ObserverList<ShelfLayoutManagerObserver>::Unchecked observers_;
 
   // The shelf reacts to gesture-drags, and can be set to auto-hide for certain
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index aeeb9e49..f837b99 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -9,11 +9,11 @@
 
 #include "ash/accelerators/accelerator_controller.h"
 #include "ash/accelerators/accelerator_table.h"
-#include "ash/app_list/app_list_controller_impl.h"
-#include "ash/app_list/home_launcher_gesture_handler.h"
 #include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/focus_cycler.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller_test_api.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -438,6 +438,18 @@
     return true;
   }
 
+  // Performs a swipe up gesture to show an auto-hidden shelf.
+  void SwipeUpToShowShelf() {
+    gfx::Rect display_bounds =
+        display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
+    const gfx::Point start(display_bounds.bottom_center());
+    const gfx::Point end(start + gfx::Vector2d(0, -80));
+    const base::TimeDelta kTimeDelta = base::TimeDelta::FromMilliseconds(100);
+    const int kNumScrollSteps = 4;
+    GetEventGenerator()->GestureScrollSequence(start, end, kTimeDelta,
+                                               kNumScrollSteps);
+  }
+
  private:
   base::TimeTicks timestamp_;
   gfx::Point current_point_;
@@ -838,15 +850,7 @@
   wm::ActivateWindow(window.get());
 
   if (autohide_shelf) {
-    gfx::Rect display_bounds =
-        display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
-    const gfx::Point start(display_bounds.bottom_center());
-    const gfx::Point end(start + gfx::Vector2d(0, -80));
-    const base::TimeDelta kTimeDelta = base::TimeDelta::FromMilliseconds(100);
-    const int kNumScrollSteps = 4;
-    // Swipe up to show the auto-hide shelf.
-    GetEventGenerator()->GestureScrollSequence(start, end, kTimeDelta,
-                                               kNumScrollSteps);
+    SwipeUpToShowShelf();
     EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
   }
 
@@ -867,7 +871,7 @@
   // The home launcher gesture handler should not be handling any window
   // initially.
   HomeLauncherGestureHandler* gesture_handler =
-      Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
+      Shell::Get()->home_screen_controller()->home_launcher_gesture_handler();
   ASSERT_TRUE(gesture_handler);
   ASSERT_FALSE(gesture_handler->GetWindow1());
 
@@ -2586,6 +2590,51 @@
   }
 }
 
+// Tests the auto-hide shelf status when moving the mouse in and out.
+TEST_F(ShelfLayoutManagerTest, AutoHideShelfOnMouseMove) {
+  // Create one window, or the shelf won't auto-hide.
+  CreateTestWidget();
+  Shelf* shelf = GetPrimaryShelf();
+  ui::test::EventGenerator* generator = GetEventGenerator();
+  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
+
+  // Set the shelf to auto-hide.
+  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
+  layout_manager->LayoutShelf();
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
+
+  // Swipe up to show the shelf.
+  SwipeUpToShowShelf();
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // Move the mouse far away from the shelf, but without having been on the
+  // shelf first. This isn't technically a mouse-out event, so the shelf should
+  // not hide.
+  generator->MoveMouseTo(0, 0);
+  ASSERT_FALSE(TriggerAutoHideTimeout());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // Now place the mouse on the shelf, then move away. The shelf should hide.
+  generator->MoveMouseTo(1, display.bounds().bottom() - 1);
+  generator->MoveMouseTo(0, 0);
+  ASSERT_TRUE(TriggerAutoHideTimeout());
+  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
+
+  // Now let's show the shelf again.
+  SwipeUpToShowShelf();
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // Move the mouse away, but move it back within the shelf immediately. The
+  // shelf should remain shown.
+  generator->MoveMouseTo(0, 0);
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+  generator->MoveMouseTo(1, display.bounds().bottom() - 1);
+  ASSERT_FALSE(TriggerAutoHideTimeout());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+}
+
 // Tests the auto-hide shelf status with mouse events.
 TEST_F(ShelfLayoutManagerTest, AutoHideShelfOnMouseEvents) {
   views::Widget* widget = CreateTestWidget();
@@ -2600,17 +2649,11 @@
   EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
   EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
 
-  gfx::Rect display_bounds =
-      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
-  const gfx::Point start(display_bounds.bottom_center());
-  const gfx::Point end(start + gfx::Vector2d(0, -80));
-  const base::TimeDelta kTimeDelta = base::TimeDelta::FromMilliseconds(100);
-  const int kNumScrollSteps = 4;
   // Swipe up to show the auto-hide shelf.
-  generator->GestureScrollSequence(start, end, kTimeDelta, kNumScrollSteps);
+  SwipeUpToShowShelf();
   EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
 
-  // Move the mouse should not hide the AUTO_HIDE_SHOWN shelf.
+  // Move the mouse should not hide the AUTO_HIDE_SHOWN shelf immediately.
   generator->MoveMouseTo(5, 5);
   EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
 
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index cb893301..bae639d4 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -528,6 +528,20 @@
   // * when views based shelf is disabled
   // * in UNKNOWN state - it might be called before shelf was initialized
   // * on secondary screens in states other than ACTIVE
+  //
+  // TODO(alemate): better handle show-hide for some UI screens:
+  // https://crbug.com/935842
+  // https://crbug.com/935844
+  // https://crbug.com/935846
+  // https://crbug.com/935847
+  // https://crbug.com/935852
+  // https://crbug.com/935853
+  // https://crbug.com/935856
+  // https://crbug.com/935857
+  // https://crbug.com/935858
+  // https://crbug.com/935860
+  // https://crbug.com/935861
+  // https://crbug.com/935863
   bool using_views_shelf = IsUsingViewsShelf();
   bool unknown_state = state == session_manager::SessionState::UNKNOWN;
   bool hide_on_secondary_screen = shelf_->ShouldHideOnSecondaryDisplay(state);
diff --git a/ash/shelf/shelf_window_watcher.cc b/ash/shelf/shelf_window_watcher.cc
index dbf0413..964e3c54 100644
--- a/ash/shelf/shelf_window_watcher.cc
+++ b/ash/shelf/shelf_window_watcher.cc
@@ -65,7 +65,9 @@
   }
 
   // Prefer app icons over window icons, they're typically larger.
-  gfx::ImageSkia* image = window->GetProperty(aura::client::kAppIconKey);
+  gfx::ImageSkia* image = window->GetProperty(aura::client::kAppIconLargeKey);
+  if (!image || image->isNull())
+    image = window->GetProperty(aura::client::kAppIconKey);
   if (!image || image->isNull())
     image = window->GetProperty(aura::client::kWindowIconKey);
   if (!image || image->isNull()) {
diff --git a/ash/shell.cc b/ash/shell.cc
index 0c5710b..1eee4d16 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -54,6 +54,7 @@
 #include "ash/frame/non_client_frame_view_ash.h"
 #include "ash/high_contrast/high_contrast_controller.h"
 #include "ash/highlighter/highlighter_controller.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/host/ash_window_tree_host_init_params.h"
 #include "ash/ime/ime_controller.h"
 #include "ash/ime/ime_focus_handler.h"
@@ -748,7 +749,11 @@
   // Depends on |tablet_mode_controller_|.
   shelf_controller_->Shutdown();
 
-  // Destroy |app_list_controller_| early than |tablet_mode_controller_| since
+  // Destroy |home_screen_controller_| before |app_list_controller_| since
+  // the former delegates to the latter.
+  home_screen_controller_.reset();
+
+  // Destroy |app_list_controller_| earlier than |tablet_mode_controller_| since
   // the former may use the latter before destruction.
   app_list_controller_.reset();
 
@@ -1165,17 +1170,16 @@
 
   magnification_controller_ = std::make_unique<MagnificationController>();
   mru_window_tracker_ = std::make_unique<MruWindowTracker>();
-
-  // |assistant_controller_| needs to be created before |app_list_controller_|
-  // since it is used by the latter in constructor.
   assistant_controller_ = chromeos::switches::IsAssistantEnabled()
                               ? std::make_unique<AssistantController>()
                               : nullptr;
+  home_screen_controller_ = std::make_unique<HomeScreenController>();
 
-  // |tablet_mode_controller_| |mru_window_tracker_|, and
-  // |assistant_controller_| are put before |app_list_controller_| as they are
-  // used in constructor.
+  // |tablet_mode_controller_| |mru_window_tracker_|,
+  // |assistant_controller_| and |home_screen_controller_| are put before
+  // |app_list_controller_| as they are used in its constructor.
   app_list_controller_ = std::make_unique<AppListControllerImpl>();
+  home_screen_controller_->SetDelegate(app_list_controller_.get());
 
   autoclick_controller_ = std::make_unique<AutoclickController>();
 
diff --git a/ash/shell.h b/ash/shell.h
index 8a2f8cf..678c69d 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -128,6 +128,7 @@
 class FocusCycler;
 class HighContrastController;
 class HighlighterController;
+class HomeScreenController;
 class ImeController;
 class ImeFocusHandler;
 class ImmersiveContext;
@@ -406,6 +407,9 @@
   EventTransformationHandler* event_transformation_handler() {
     return event_transformation_handler_.get();
   }
+  HomeScreenController* home_screen_controller() {
+    return home_screen_controller_.get();
+  }
   FirstRunHelper* first_run_helper() { return first_run_helper_.get(); }
   ::wm::FocusController* focus_controller() { return focus_controller_.get(); }
   ::wm::FocusRules* focus_rules() { return focus_rules_; }
@@ -729,6 +733,7 @@
   std::unique_ptr<DragDropController> drag_drop_controller_;
   std::unique_ptr<FirstRunHelper> first_run_helper_;
   std::unique_ptr<FocusCycler> focus_cycler_;
+  std::unique_ptr<HomeScreenController> home_screen_controller_;
   std::unique_ptr<ImeController> ime_controller_;
   std::unique_ptr<ImeFocusHandler> ime_focus_handler_;
   std::unique_ptr<ImmersiveContext> immersive_context_;
diff --git a/ash/test/ash_test_base.cc b/ash/test/ash_test_base.cc
index ccaead4..113ce77 100644
--- a/ash/test/ash_test_base.cc
+++ b/ash/test/ash_test_base.cc
@@ -39,6 +39,8 @@
 #include "components/account_id/account_id.h"
 #include "components/user_manager/user_names.h"
 #include "mojo/public/cpp/bindings/map.h"
+#include "services/ws/public/cpp/input_devices/input_device_client.h"
+#include "services/ws/public/cpp/input_devices/input_device_client_test_api.h"
 #include "services/ws/public/cpp/property_type_converters.h"
 #include "services/ws/public/mojom/window_manager.mojom.h"
 #include "services/ws/public/mojom/window_tree_constants.mojom.h"
@@ -187,6 +189,10 @@
   // Some tests set an internal display id,
   // reset it here, so other tests will continue in a clean environment.
   display::Display::SetInternalDisplayId(display::kInvalidDisplayId);
+
+  // Tests can add devices, so reset the lists for future tests.
+  ws::InputDeviceClientTestApi().SetTouchscreenDevices({});
+  ws::InputDeviceClientTestApi().SetKeyboardDevices({});
 }
 
 // static
diff --git a/ash/wm/overview/caption_container_view.cc b/ash/wm/overview/caption_container_view.cc
index fed7332..9c67eb8 100644
--- a/ash/wm/overview/caption_container_view.cc
+++ b/ash/wm/overview/caption_container_view.cc
@@ -243,7 +243,11 @@
           kHorizontalLabelPaddingDp));
   AddChildWithLayer(listener_button_, header_view_);
 
-  gfx::ImageSkia* icon = window->GetProperty(aura::client::kAppIconKey);
+  // Prefer kAppIconSmallKey (set by the client in Mash), then kAppIconKey and
+  // kWindowIconKey (set for client windows in classic Ash but not Mash).
+  gfx::ImageSkia* icon = window->GetProperty(aura::client::kAppIconSmallKey);
+  if (!icon || icon->size().IsEmpty())
+    icon = window->GetProperty(aura::client::kAppIconKey);
   if (!icon || icon->size().IsEmpty())
     icon = window->GetProperty(aura::client::kWindowIconKey);
   if (icon && !icon->size().IsEmpty()) {
diff --git a/ash/wm/overview/overview_utils.cc b/ash/wm/overview/overview_utils.cc
index 8d688ab..da5f3af 100644
--- a/ash/wm/overview/overview_utils.cc
+++ b/ash/wm/overview/overview_utils.cc
@@ -6,8 +6,8 @@
 
 #include <utility>
 
-#include "ash/app_list/app_list_controller_impl.h"
-#include "ash/app_list/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
@@ -295,7 +295,7 @@
     return false;
 
   if (Shell::Get()
-          ->app_list_controller()
+          ->home_screen_controller()
           ->home_launcher_gesture_handler()
           ->mode() == HomeLauncherGestureHandler::Mode::kSlideUpToShow) {
     return true;
diff --git a/ash/wm/splitview/split_view_utils.cc b/ash/wm/splitview/split_view_utils.cc
index 8ddaa2f..115c838f 100644
--- a/ash/wm/splitview/split_view_utils.cc
+++ b/ash/wm/splitview/split_view_utils.cc
@@ -73,14 +73,16 @@
       *out_tween_type = gfx::Tween::LINEAR_OUT_SLOW_IN;
       return;
     case SPLITVIEW_ANIMATION_TEXT_FADE_IN:
-    case SPLITVIEW_ANIMATION_TEXT_FADE_OUT:
     case SPLITVIEW_ANIMATION_TEXT_SLIDE_IN:
-    case SPLITVIEW_ANIMATION_TEXT_SLIDE_OUT:
-      if (type == SPLITVIEW_ANIMATION_TEXT_SLIDE_IN)
-        *out_delay = kLabelAnimationDelayMs;
+      *out_delay = kLabelAnimationDelayMs;
       *out_duration = kLabelAnimationMs;
       *out_tween_type = gfx::Tween::LINEAR_OUT_SLOW_IN;
       return;
+    case SPLITVIEW_ANIMATION_TEXT_FADE_OUT:
+    case SPLITVIEW_ANIMATION_TEXT_SLIDE_OUT:
+      *out_duration = kLabelAnimationMs;
+      *out_tween_type = gfx::Tween::FAST_OUT_LINEAR_IN;
+      return;
     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_FADE_OUT:
     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_OUT:
       *out_delay = kOtherFadeOutDelayMs;
diff --git a/ash/wm/window_animations.cc b/ash/wm/window_animations.cc
index ba01102..ee66b65d 100644
--- a/ash/wm/window_animations.cc
+++ b/ash/wm/window_animations.cc
@@ -10,8 +10,8 @@
 #include <utility>
 #include <vector>
 
-#include "ash/app_list/app_list_controller_impl.h"
-#include "ash/app_list/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_launcher_gesture_handler.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/public/cpp/window_animation_types.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
@@ -246,17 +246,17 @@
 }
 
 bool AnimateShowWindow_SlideDown(aura::Window* window) {
-  AppListControllerImpl* app_list_controller =
-      Shell::Get()->app_list_controller();
+  HomeScreenController* home_screen_controller =
+      Shell::Get()->home_screen_controller();
   const TabletModeController* tablet_mode_controller =
       Shell::Get()->tablet_mode_controller();
 
-  if (app_list_controller && tablet_mode_controller &&
+  if (home_screen_controller && tablet_mode_controller &&
       tablet_mode_controller->IsTabletModeWindowManagerEnabled()) {
     // Slide down the window from above screen to show and, meanwhile, slide
     // down the home launcher off screen.
     HomeLauncherGestureHandler* handler =
-        app_list_controller->home_launcher_gesture_handler();
+        home_screen_controller->home_launcher_gesture_handler();
     if (handler &&
         handler->HideHomeLauncherForWindow(
             display::Screen::GetScreen()->GetDisplayNearestView(window),
diff --git a/base/android/child_process_service.cc b/base/android/child_process_service.cc
index 93c03d41..9297b98 100644
--- a/base/android/child_process_service.cc
+++ b/base/android/child_process_service.cc
@@ -5,6 +5,7 @@
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/android/library_loader/library_loader_hooks.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/file_descriptor_store.h"
 #include "base/logging.h"
 #include "base/macros.h"
@@ -72,5 +73,9 @@
   _exit(0);
 }
 
+void JNI_ChildProcessService_DumpProcessStack(JNIEnv* env) {
+  base::debug::DumpWithoutCrashing();
+}
+
 }  // namespace android
 }  // namespace base
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
index eef08c4..fe89894 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -437,6 +437,19 @@
         notifyChildProcessDied();
     }
 
+    /**
+     * Dumps the stack of the child process without crashing it.
+     */
+    public void dumpProcessStack() {
+        assert isRunningOnLauncherThread();
+        IChildProcessService service = mService;
+        try {
+            if (service != null) service.dumpProcessStack();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to dump process stack.", e);
+        }
+    }
+
     @VisibleForTesting
     protected void onServiceConnectedOnLauncherThread(IBinder service) {
         assert isRunningOnLauncherThread();
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
index 2364197c..6a5aaa8 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
@@ -187,6 +187,19 @@
                 }
             });
         }
+
+        @Override
+        public void dumpProcessStack() {
+            assert mServiceBound;
+            synchronized (mLibraryInitializedLock) {
+                if (!mLibraryInitialized) {
+                    Log.e(TAG, "Cannot dump process stack before native is loaded");
+                    return;
+                }
+            }
+            nativeDumpProcessStack();
+        }
+
     };
 
     /**
@@ -361,4 +374,9 @@
      * Force the child process to exit.
      */
     private static native void nativeExitChildProcess();
+
+    /**
+     * Dumps the child process stack without crashing it.
+     */
+    private static native void nativeDumpProcessStack();
 }
diff --git a/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl b/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl
index bca9c171..78e1baf3 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl
+++ b/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl
@@ -23,4 +23,7 @@
 
   // Notifies about memory pressure. The argument is MemoryPressureLevel enum.
   oneway void onMemoryPressure(int pressure);
+
+  // Dumps the stack for the child process without crashing it.
+  oneway void dumpProcessStack();
 }
diff --git a/base/profiler/native_stack_sampler_win.cc b/base/profiler/native_stack_sampler_win.cc
index 0a32704..29e2f434 100644
--- a/base/profiler/native_stack_sampler_win.cc
+++ b/base/profiler/native_stack_sampler_win.cc
@@ -188,31 +188,21 @@
 #endif
 }
 
-// Movable type representing a recorded stack frame.
+// Represents a recorded stack frame.
 struct RecordedFrame {
-  RecordedFrame() {}
-
-  RecordedFrame(RecordedFrame&& other)
-      : instruction_pointer(other.instruction_pointer),
-        module(std::move(other.module)) {}
-
-  RecordedFrame& operator=(RecordedFrame&& other) {
-    instruction_pointer = other.instruction_pointer;
-    module = std::move(other.module);
-    return *this;
-  }
+  RecordedFrame(const void* instruction_pointer,
+                const ModuleCache::Module* module)
+      : instruction_pointer(instruction_pointer), module(module) {}
 
   const void* instruction_pointer;
-  ScopedModuleHandle module;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(RecordedFrame);
+  const ModuleCache::Module* module;
 };
 
 // Walks the stack represented by |context| from the current frame downwards,
 // recording the instruction pointer and associated module for each frame in
 // |stack|.
-NativeStackSamplerError RecordStack(CONTEXT* context,
+NativeStackSamplerError RecordStack(ModuleCache* module_cache,
+                                    CONTEXT* context,
                                     std::vector<RecordedFrame>* stack) {
 #ifdef _WIN64
   DCHECK(stack->empty());
@@ -222,17 +212,14 @@
   // fewer.
   stack->reserve(128);
 
-  Win32StackFrameUnwinder frame_unwinder;
+  Win32StackFrameUnwinder frame_unwinder(module_cache);
   while (ContextPC(context)) {
     const void* instruction_pointer =
         reinterpret_cast<const void*>(ContextPC(context));
-    ScopedModuleHandle module;
+    const ModuleCache::Module* module = nullptr;
     if (!frame_unwinder.TryUnwind(context, &module))
       return NATIVE_STACK_SAMPLER_TRY_UNWIND_FAILED;
-    RecordedFrame frame;
-    frame.instruction_pointer = instruction_pointer;
-    frame.module = std::move(module);
-    stack->push_back(std::move(frame));
+    stack->emplace_back(instruction_pointer, module);
   }
   return NATIVE_STACK_SAMPLER_SUCCESS;
 #else
@@ -341,6 +328,7 @@
     const void* base_address,
     void* stack_copy_buffer,
     size_t stack_copy_buffer_size,
+    ModuleCache* module_cache,
     std::vector<RecordedFrame>* stack,
     ProfileBuilder* profile_builder,
     NativeStackSamplerTestDelegate* test_delegate) {
@@ -400,7 +388,7 @@
     RewritePointersToStackMemory(top, bottom, &thread_context,
                                  stack_copy_buffer);
 
-    return RecordStack(&thread_context, stack);
+    return RecordStack(module_cache, &thread_context, stack);
   }
 }
 
@@ -460,7 +448,8 @@
   std::vector<RecordedFrame> stack;
   NativeStackSamplerError error_code = SuspendThreadAndRecordStack(
       thread_handle_.Get(), thread_stack_base_address_, stack_buffer->buffer(),
-      stack_buffer->size(), &stack, profile_builder, test_delegate_);
+      stack_buffer->size(), module_cache_, &stack, profile_builder,
+      test_delegate_);
 
   if (error_code != NATIVE_STACK_SAMPLER_SUCCESS) {
     TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("cpu_profiler"),
@@ -483,7 +472,7 @@
 
   for (const auto& frame : stack) {
     frames.emplace_back(reinterpret_cast<uintptr_t>(frame.instruction_pointer),
-                        module_cache_->GetModuleForHandle(frame.module.Get()));
+                        frame.module);
   }
 
   return frames;
diff --git a/base/profiler/win32_stack_frame_unwinder.cc b/base/profiler/win32_stack_frame_unwinder.cc
index 204b3983..cfbcbd2 100644
--- a/base/profiler/win32_stack_frame_unwinder.cc
+++ b/base/profiler/win32_stack_frame_unwinder.cc
@@ -15,34 +15,13 @@
 
 // Win32UnwindFunctions -------------------------------------------------------
 
-const HMODULE ModuleHandleTraits::kNonNullModuleForTesting =
-    reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1));
-
-// static
-bool ModuleHandleTraits::CloseHandle(HMODULE handle) {
-  if (handle == kNonNullModuleForTesting)
-    return true;
-
-  return ::FreeLibrary(handle) != 0;
-}
-
-// static
-bool ModuleHandleTraits::IsHandleValid(HMODULE handle) {
-  return handle != nullptr;
-}
-
-// static
-HMODULE ModuleHandleTraits::NullHandle() {
-  return nullptr;
-}
-
 namespace {
 
 // Implements the UnwindFunctions interface for the corresponding Win32
 // functions.
 class Win32UnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
-public:
-  Win32UnwindFunctions();
+ public:
+  explicit Win32UnwindFunctions(ModuleCache* module_cache);
   ~Win32UnwindFunctions() override;
 
   PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
@@ -53,14 +32,17 @@
                      PRUNTIME_FUNCTION runtime_function,
                      CONTEXT* context) override;
 
-  ScopedModuleHandle GetModuleForProgramCounter(
+  const ModuleCache::Module* GetModuleForProgramCounter(
       DWORD64 program_counter) override;
 
-private:
+ private:
+  ModuleCache* module_cache_;
+
   DISALLOW_COPY_AND_ASSIGN(Win32UnwindFunctions);
 };
 
-Win32UnwindFunctions::Win32UnwindFunctions() {}
+Win32UnwindFunctions::Win32UnwindFunctions(ModuleCache* module_cache)
+    : module_cache_(module_cache) {}
 Win32UnwindFunctions::~Win32UnwindFunctions() {}
 
 PRUNTIME_FUNCTION Win32UnwindFunctions::LookupFunctionEntry(
@@ -90,18 +72,9 @@
 #endif
 }
 
-ScopedModuleHandle Win32UnwindFunctions::GetModuleForProgramCounter(
+const ModuleCache::Module* Win32UnwindFunctions::GetModuleForProgramCounter(
     DWORD64 program_counter) {
-  HMODULE module_handle = nullptr;
-  // GetModuleHandleEx() increments the module reference count, which is then
-  // managed and ultimately decremented by ScopedModuleHandle.
-  if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
-                           reinterpret_cast<LPCTSTR>(program_counter),
-                           &module_handle)) {
-    const DWORD error = ::GetLastError();
-    DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
-  }
-  return ScopedModuleHandle(module_handle);
+  return module_cache_->GetModuleForAddress(program_counter);
 }
 
 }  // namespace
@@ -111,19 +84,20 @@
 Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
 Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
 
-Win32StackFrameUnwinder::Win32StackFrameUnwinder()
-    : Win32StackFrameUnwinder(std::make_unique<Win32UnwindFunctions>()) {}
+Win32StackFrameUnwinder::Win32StackFrameUnwinder(ModuleCache* module_cache)
+    : Win32StackFrameUnwinder(
+          std::make_unique<Win32UnwindFunctions>(module_cache)) {}
 
 Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
 
 bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
-                                        ScopedModuleHandle* module) {
+                                        const ModuleCache::Module** module) {
 #ifdef _WIN64
-  // TODO(wittman): update base::ModuleCache to return a ScopedModuleHandle and
-  // use it for this module lookup.
-  ScopedModuleHandle frame_module =
+  *module = nullptr;
+
+  const ModuleCache::Module* frame_module =
       unwind_functions_->GetModuleForProgramCounter(ContextPC(context));
-  if (!frame_module.IsValid()) {
+  if (!frame_module) {
     // There's no loaded module containing the instruction pointer. This can be
     // due to executing code that is not in a module. In particular,
     // runtime-generated code associated with third-party injected DLLs
@@ -184,7 +158,7 @@
     }
   }
 
-  module->Set(frame_module.Take());
+  *module = frame_module;
   return true;
 #else
   NOTREACHED();
diff --git a/base/profiler/win32_stack_frame_unwinder.h b/base/profiler/win32_stack_frame_unwinder.h
index 144cc6e..0d1da36 100644
--- a/base/profiler/win32_stack_frame_unwinder.h
+++ b/base/profiler/win32_stack_frame_unwinder.h
@@ -11,7 +11,7 @@
 
 #include "base/base_export.h"
 #include "base/macros.h"
-#include "base/win/scoped_handle.h"
+#include "base/sampling_heap_profiler/module_cache.h"
 #include "build/build_config.h"
 
 namespace base {
@@ -38,26 +38,6 @@
 }
 #endif
 
-// Traits class to adapt GenericScopedHandle for HMODULES.
-class ModuleHandleTraits : public win::HandleTraits {
- public:
-  using Handle = HMODULE;
-
-  static bool BASE_EXPORT CloseHandle(HMODULE handle);
-  static bool BASE_EXPORT IsHandleValid(HMODULE handle);
-  static HMODULE BASE_EXPORT NullHandle();
-
-  BASE_EXPORT static const HMODULE kNonNullModuleForTesting;
-
- private:
-  DISALLOW_IMPLICIT_CONSTRUCTORS(ModuleHandleTraits);
-};
-
-// HMODULE is not really a handle, and has reference count semantics, so the
-// standard VerifierTraits does not apply.
-using ScopedModuleHandle =
-    win::GenericScopedHandle<ModuleHandleTraits, win::DummyVerifierTraits>;
-
 // Instances of this class are expected to be created and destroyed for each
 // stack unwinding. This class is not used while the target thread is suspended,
 // so may allocate from the default heap.
@@ -78,7 +58,7 @@
 
     // Returns the module containing |program_counter|. Can return null if the
     // module has been unloaded.
-    virtual ScopedModuleHandle GetModuleForProgramCounter(
+    virtual const ModuleCache::Module* GetModuleForProgramCounter(
         DWORD64 program_counter) = 0;
 
    protected:
@@ -88,13 +68,13 @@
     DISALLOW_COPY_AND_ASSIGN(UnwindFunctions);
   };
 
-  Win32StackFrameUnwinder();
+  explicit Win32StackFrameUnwinder(ModuleCache* module_cache);
   ~Win32StackFrameUnwinder();
 
   // Attempts to unwind the frame represented by the stack and instruction
   // pointers in |context|. If successful, updates |context| and provides the
-  // module associated with the frame in |module|.
-  bool TryUnwind(CONTEXT* context, ScopedModuleHandle* module);
+  // module associated with the frame in *|module|.
+  bool TryUnwind(CONTEXT* context, const ModuleCache::Module** module);
 
  private:
   // This function is for internal and test purposes only.
diff --git a/base/profiler/win32_stack_frame_unwinder_unittest.cc b/base/profiler/win32_stack_frame_unwinder_unittest.cc
index cecfe22..bc6d986 100644
--- a/base/profiler/win32_stack_frame_unwinder_unittest.cc
+++ b/base/profiler/win32_stack_frame_unwinder_unittest.cc
@@ -17,6 +17,17 @@
 
 namespace {
 
+// Stub module for testing.
+class TestModule : public ModuleCache::Module {
+ public:
+  uintptr_t GetBaseAddress() const override { return 0; }
+  std::string GetId() const override { return ""; }
+  FilePath GetDebugBasename() const override { return FilePath(); }
+  size_t GetSize() const override { return 0; }
+};
+
+const TestModule valid_module;
+
 class TestUnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
  public:
   TestUnwindFunctions();
@@ -27,7 +38,7 @@
                      DWORD64 program_counter,
                      PRUNTIME_FUNCTION runtime_function,
                      CONTEXT* context) override;
-  ScopedModuleHandle GetModuleForProgramCounter(
+  const ModuleCache::Module* GetModuleForProgramCounter(
       DWORD64 program_counter) override;
 
   // Instructs GetModuleForProgramCounter to return null on the next call.
@@ -90,13 +101,11 @@
   EXPECT_EQ(&runtime_functions_.back(), runtime_function);
 }
 
-ScopedModuleHandle TestUnwindFunctions::GetModuleForProgramCounter(
+const ModuleCache::Module* TestUnwindFunctions::GetModuleForProgramCounter(
     DWORD64 program_counter) {
   bool return_non_null_value = module_is_loaded_;
   module_is_loaded_ = true;
-  return ScopedModuleHandle(return_non_null_value ?
-                            ModuleHandleTraits::kNonNullModuleForTesting :
-                            nullptr);
+  return return_non_null_value ? &valid_module : nullptr;
 }
 
 void TestUnwindFunctions::SetUnloadedModule() {
@@ -149,28 +158,28 @@
 TEST_F(Win32StackFrameUnwinderTest, FramesWithUnwindInfo) {
   std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
   CONTEXT context = {0};
-  ScopedModuleHandle module;
+  const ModuleCache::Module* module = nullptr;
 
   unwind_functions_->SetHasRuntimeFunction(&context);
   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
-  EXPECT_TRUE(module.IsValid());
+  EXPECT_NE(nullptr, module);
 
   unwind_functions_->SetHasRuntimeFunction(&context);
-  module.Set(nullptr);
+  module = nullptr;
   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
-  EXPECT_TRUE(module.IsValid());
+  EXPECT_NE(nullptr, module);
 
   unwind_functions_->SetHasRuntimeFunction(&context);
-  module.Set(nullptr);
+  module = nullptr;
   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
-  EXPECT_TRUE(module.IsValid());
+  EXPECT_NE(nullptr, module);
 }
 
 // Checks that an instruction pointer in an unloaded module fails to unwind.
 TEST_F(Win32StackFrameUnwinderTest, UnloadedModule) {
   std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
   CONTEXT context = {0};
-  ScopedModuleHandle module;
+  const ModuleCache::Module* module = nullptr;
 
   unwind_functions_->SetUnloadedModule();
   EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
@@ -181,7 +190,7 @@
 TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) {
   std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
   CONTEXT context = {0};
-  ScopedModuleHandle module;
+  const ModuleCache::Module* module = nullptr;
   DWORD64 next_ip = 0x0123456789abcdef;
   DWORD64 original_rsp = reinterpret_cast<DWORD64>(&next_ip);
   context.Rsp = original_rsp;
@@ -190,17 +199,17 @@
   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
   EXPECT_EQ(next_ip, context.Rip);
   EXPECT_EQ(original_rsp + 8, context.Rsp);
-  EXPECT_TRUE(module.IsValid());
+  EXPECT_NE(nullptr, module);
 
   unwind_functions_->SetHasRuntimeFunction(&context);
-  module.Set(nullptr);
+  module = nullptr;
   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
-  EXPECT_TRUE(module.IsValid());
+  EXPECT_NE(nullptr, module);
 
   unwind_functions_->SetHasRuntimeFunction(&context);
-  module.Set(nullptr);
+  module = nullptr;
   EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
-  EXPECT_TRUE(module.IsValid());
+  EXPECT_NE(nullptr, module);
 }
 
 // Checks that a frame below the top of the stack with missing unwind info
@@ -210,10 +219,10 @@
     // First stack, with a bad function below the top of the stack.
     std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
     CONTEXT context = {0};
-    ScopedModuleHandle module;
+    const ModuleCache::Module* module = nullptr;
     unwind_functions_->SetHasRuntimeFunction(&context);
     EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
-    EXPECT_TRUE(module.IsValid());
+    EXPECT_NE(nullptr, module);
 
     unwind_functions_->SetNoRuntimeFunction(&context);
     EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
diff --git a/base/sampling_heap_profiler/module_cache.h b/base/sampling_heap_profiler/module_cache.h
index 2b5189b..45cee33 100644
--- a/base/sampling_heap_profiler/module_cache.h
+++ b/base/sampling_heap_profiler/module_cache.h
@@ -20,6 +20,16 @@
 
 namespace base {
 
+// Supports cached lookup of modules by address, with caching based on module
+// address ranges.
+//
+// Cached lookup is necessary on Mac for performance, due to an inefficient
+// dladdr implementation. See https://crrev.com/487092.
+//
+// Cached lookup is beneficial on Windows to minimize use of the loader
+// lock. Note however that the cache retains a handle to looked-up modules for
+// its lifetime, which may result in pinning modules in memory that were
+// transiently loaded by the OS.
 class BASE_EXPORT ModuleCache {
  public:
   // Module represents a binary module (executable or library) and its
@@ -77,16 +87,6 @@
   friend bool MayTriggerUnwInitLocalCrash(uint64_t);
 #endif
 
-#if defined(OS_WIN)
-  const Module* GetModuleForHandle(HMODULE module_handle);
-  static std::unique_ptr<Module> CreateModuleForHandle(HMODULE module_handle);
-  friend class NativeStackSamplerWin;
-
-  // The module objects, indexed by the module handle.
-  // TODO(wittman): Merge this state into modules_cache_map_ and remove
-  std::map<HMODULE, std::unique_ptr<Module>> win_module_cache_;
-#endif
-
   std::map<uintptr_t, std::unique_ptr<Module>> modules_cache_map_;
 };
 
diff --git a/base/sampling_heap_profiler/module_cache_win.cc b/base/sampling_heap_profiler/module_cache_win.cc
index a192054..9cc2374 100644
--- a/base/sampling_heap_profiler/module_cache_win.cc
+++ b/base/sampling_heap_profiler/module_cache_win.cc
@@ -63,85 +63,92 @@
   *build_id = UTF16ToUTF8(buffer);
 }
 
-}  // namespace
+// Traits class to adapt GenericScopedHandle for HMODULES.
+class ModuleHandleTraits : public win::HandleTraits {
+ public:
+  using Handle = HMODULE;
+
+  static bool CloseHandle(HMODULE handle) { return ::FreeLibrary(handle) != 0; }
+  static bool IsHandleValid(HMODULE handle) { return handle != nullptr; }
+  static HMODULE NullHandle() { return nullptr; }
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(ModuleHandleTraits);
+};
+
+// HMODULE is not really a handle, and has reference count semantics, so the
+// standard VerifierTraits does not apply.
+using ScopedModuleHandle =
+    win::GenericScopedHandle<ModuleHandleTraits, win::DummyVerifierTraits>;
 
 class WindowsModule : public ModuleCache::Module {
  public:
-  WindowsModule(uintptr_t base_address,
+  WindowsModule(ScopedModuleHandle module_handle,
+                const MODULEINFO module_info,
                 const std::string& id,
-                const FilePath& debug_basename,
-                size_t size)
-      : base_address_(base_address),
+                const FilePath& debug_basename)
+      : module_handle_(std::move(module_handle)),
+        module_info_(module_info),
         id_(id),
-        debug_basename_(debug_basename),
-        size_(size) {}
+        debug_basename_(debug_basename) {}
 
   WindowsModule(const WindowsModule&) = delete;
   WindowsModule& operator=(const WindowsModule&) = delete;
 
   // ModuleCache::Module
-  uintptr_t GetBaseAddress() const override { return base_address_; }
+  uintptr_t GetBaseAddress() const override {
+    return reinterpret_cast<uintptr_t>(module_info_.lpBaseOfDll);
+  }
+
   std::string GetId() const override { return id_; }
   FilePath GetDebugBasename() const override { return debug_basename_; }
-  size_t GetSize() const override { return size_; }
+  size_t GetSize() const override { return module_info_.SizeOfImage; }
 
  private:
-  uintptr_t base_address_;
+  ScopedModuleHandle module_handle_;
+  const MODULEINFO module_info_;
   std::string id_;
   FilePath debug_basename_;
-  size_t size_;
 };
 
-// static
-std::unique_ptr<ModuleCache::Module> ModuleCache::CreateModuleForAddress(
-    uintptr_t address) {
+ScopedModuleHandle GetModuleHandleForAddress(DWORD64 address) {
   HMODULE module_handle = nullptr;
+  // GetModuleHandleEx() increments the module reference count, which is then
+  // managed and ultimately decremented by ScopedModuleHandle.
   if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                            reinterpret_cast<LPCTSTR>(address),
                            &module_handle)) {
-    DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(::GetLastError()));
-    return nullptr;
+    const DWORD error = ::GetLastError();
+    DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
   }
-  std::unique_ptr<Module> module = CreateModuleForHandle(module_handle);
-  ::CloseHandle(module_handle);
-  return module;
+  return ScopedModuleHandle(module_handle);
 }
 
-const ModuleCache::Module* ModuleCache::GetModuleForHandle(
-    HMODULE module_handle) {
-  if (!module_handle)
-    return nullptr;
-
-  auto loc = win_module_cache_.find(module_handle);
-  if (loc != win_module_cache_.end())
-    return loc->second.get();
-
-  std::unique_ptr<ModuleCache::Module> module =
-      ModuleCache::CreateModuleForHandle(module_handle);
-  if (!module)
-    return nullptr;
-
-  const auto result = win_module_cache_.insert(
-      std::make_pair(module_handle, std::move(module)));
-  return result.first->second.get();
-}
-
-// static
-std::unique_ptr<ModuleCache::Module> ModuleCache::CreateModuleForHandle(
-    HMODULE module_handle) {
+std::unique_ptr<ModuleCache::Module> CreateModuleForHandle(
+    ScopedModuleHandle module_handle) {
   FilePath pdb_name;
   std::string build_id;
-  GetDebugInfoForModule(module_handle, &build_id, &pdb_name);
+  GetDebugInfoForModule(module_handle.Get(), &build_id, &pdb_name);
 
   MODULEINFO module_info;
-  if (!::GetModuleInformation(GetCurrentProcessHandle(), module_handle,
+  if (!::GetModuleInformation(GetCurrentProcessHandle(), module_handle.Get(),
                               &module_info, sizeof(module_info))) {
     return nullptr;
   }
 
-  return std::make_unique<WindowsModule>(
-      reinterpret_cast<uintptr_t>(module_info.lpBaseOfDll), build_id, pdb_name,
-      module_info.SizeOfImage);
+  return std::make_unique<WindowsModule>(std::move(module_handle), module_info,
+                                         build_id, pdb_name);
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<ModuleCache::Module> ModuleCache::CreateModuleForAddress(
+    uintptr_t address) {
+  ScopedModuleHandle module_handle = GetModuleHandleForAddress(address);
+  if (!module_handle.IsValid())
+    return nullptr;
+  return CreateModuleForHandle(std::move(module_handle));
 }
 
 }  // namespace base
diff --git a/base/test/android/javatests/src/org/chromium/base/test/DestroyActivitiesRule.java b/base/test/android/javatests/src/org/chromium/base/test/DestroyActivitiesRule.java
index ffbc213..b1104b3 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/DestroyActivitiesRule.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/DestroyActivitiesRule.java
@@ -14,6 +14,9 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.concurrent.TimeoutException;
 
 /**
@@ -22,6 +25,21 @@
  */
 public class DestroyActivitiesRule extends ExternalResource {
     private static final String TAG = "DestroyActivities";
+    private final Set<Activity> mBlacklistedActivities =
+            Collections.newSetFromMap(new WeakHashMap<>());
+
+    private boolean allActivitiesDestroyedOrBlacklisted() {
+        if (ApplicationStatus.isEveryActivityDestroyed()) {
+            return true;
+        }
+        for (Activity a : ApplicationStatus.getRunningActivities()) {
+            if (!mBlacklistedActivities.contains(a)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     public void after() {
         if (!ApplicationStatus.isInitialized()) {
@@ -34,7 +52,7 @@
                     public void onActivityStateChange(Activity activity, int newState) {
                         switch (newState) {
                             case ActivityState.DESTROYED:
-                                if (ApplicationStatus.isEveryActivityDestroyed()) {
+                                if (allActivitiesDestroyedOrBlacklisted()) {
                                     allDestroyedCalledback.notifyCalled();
                                     ApplicationStatus.unregisterActivityStateListener(this);
                                 }
@@ -51,13 +69,13 @@
                 };
 
         ThreadUtils.runOnUiThread(() -> {
-            if (ApplicationStatus.isEveryActivityDestroyed()) {
+            if (allActivitiesDestroyedOrBlacklisted()) {
                 allDestroyedCalledback.notifyCalled();
             } else {
                 ApplicationStatus.registerStateListenerForAllActivities(activityStateListener);
             }
             for (Activity a : ApplicationStatus.getRunningActivities()) {
-                if (!a.isFinishing()) {
+                if (!a.isFinishing() && !mBlacklistedActivities.contains(a)) {
                     a.finish();
                 }
             }
@@ -70,9 +88,8 @@
             Log.w(TAG, "Activity failed to be destroyed after a test");
 
             ThreadUtils.runOnUiThreadBlocking(() -> {
-                for (Activity a : ApplicationStatus.getRunningActivities()) {
-                    ApplicationStatus.onStateChangeForTesting(a, ActivityState.DESTROYED);
-                }
+                mBlacklistedActivities.addAll(ApplicationStatus.getRunningActivities());
+
                 // Make sure subsequent tests don't have these notifications firing.
                 ApplicationStatus.unregisterActivityStateListener(activityStateListener);
             });
diff --git a/build/android/gyp/create_stub_manifest.py b/build/android/gyp/create_stub_manifest.py
deleted file mode 100755
index ecaca9d..0000000
--- a/build/android/gyp/create_stub_manifest.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-
-"""Generates a manifest with reference attributes removed."""
-
-import argparse
-import re
-
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument(
-      '--input', required=True, help='Path to the input manifest.')
-  parser.add_argument(
-      '--output', required=True, help='Path to the output manifest.')
-  args = parser.parse_args()
-
-  with open(args.input) as manifest_file:
-    stub_manifest = manifest_file.read()
-
-  stub_manifest = re.sub(
-      r'<meta-data[^>]*android:resource[^>]*/>',
-      '',
-      stub_manifest,
-      flags=re.MULTILINE)
-  stub_manifest = re.sub(r'android:[^=]*=\s*"@[^"]+"', '', stub_manifest)
-
-  with open(args.output, 'w') as out_file:
-    out_file.write(stub_manifest)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/build/android/gyp/java_cpp_enum.py b/build/android/gyp/java_cpp_enum.py
index e7374410..2d493f7 100755
--- a/build/android/gyp/java_cpp_enum.py
+++ b/build/android/gyp/java_cpp_enum.py
@@ -15,6 +15,7 @@
 import zipfile
 
 from util import build_utils
+from util import java_cpp_utils
 
 # List of C++ types that are compatible with the Java code generated by this
 # script.
@@ -115,8 +116,8 @@
     self.comments = StripEntries(self.comments)
 
   def _NormalizeNames(self):
-    self.entries = _TransformKeys(self.entries, _KCamelToShouty)
-    self.comments = _TransformKeys(self.comments, _KCamelToShouty)
+    self.entries = _TransformKeys(self.entries, java_cpp_utils.KCamelToShouty)
+    self.comments = _TransformKeys(self.comments, java_cpp_utils.KCamelToShouty)
 
 
 def _TransformKeys(d, func):
@@ -133,25 +134,6 @@
   return ret
 
 
-def _KCamelToShouty(s):
-  """Convert |s| from kCamelCase or CamelCase to SHOUTY_CASE.
-
-  kFooBar -> FOO_BAR
-  FooBar -> FOO_BAR
-  FooBAR9 -> FOO_BAR9
-  FooBARBaz -> FOO_BAR_BAZ
-  """
-  if not re.match(r'^k?([A-Z][^A-Z]+|[A-Z0-9]+)+$', s):
-    return s
-  # Strip the leading k.
-  s = re.sub(r'^k', '', s)
-  # Add _ between title words and anything else.
-  s = re.sub(r'([^_])([A-Z][^A-Z_0-9]+)', r'\1_\2', s)
-  # Add _ between lower -> upper transitions.
-  s = re.sub(r'([^A-Z_0-9])([A-Z])', r'\1_\2', s)
-  return s.upper()
-
-
 class DirectiveSet(object):
   class_name_override_key = 'CLASS_NAME_OVERRIDE'
   enum_package_key = 'ENUM_PACKAGE'
@@ -334,8 +316,6 @@
       if single_line_enum:
         self._ParseSingleLineEnum(single_line_enum.group('enum_entries'))
 
-def GetScriptName():
-  return os.path.basename(os.path.abspath(sys.argv[0]))
 
 def DoGenerate(source_paths):
   for source_path in source_paths:
@@ -395,15 +375,15 @@
     }
     enum_comments = enum_definition.comments.get(enum_name)
     if enum_comments:
-        enum_comments_indent = '   * '
-        comments_line_wrapper = textwrap.TextWrapper(
-                initial_indent=enum_comments_indent,
-                subsequent_indent=enum_comments_indent,
-                width=100)
-        enum_entries_string.append('  /**')
-        enum_entries_string.append(
-                '\n'.join(comments_line_wrapper.wrap(enum_comments)))
-        enum_entries_string.append('   */')
+      enum_comments_indent = '   * '
+      comments_line_wrapper = textwrap.TextWrapper(
+          initial_indent=enum_comments_indent,
+          subsequent_indent=enum_comments_indent,
+          width=100)
+      enum_entries_string.append('  /**')
+      enum_entries_string.append('\n'.join(
+          comments_line_wrapper.wrap(enum_comments)))
+      enum_entries_string.append('   */')
     enum_entries_string.append(enum_template.substitute(values))
     enum_names.append(enum_definition.class_name + '.' + enum_name)
   enum_entries_string = '\n'.join(enum_entries_string)
@@ -419,7 +399,7 @@
       'ENUM_ENTRIES': enum_entries_string,
       'PACKAGE': enum_definition.enum_package,
       'INT_DEF': enum_names_string,
-      'SCRIPT_NAME': GetScriptName(),
+      'SCRIPT_NAME': java_cpp_utils.GetScriptName(),
       'SOURCE_PATH': source_path,
       'YEAR': str(date.today().year)
   }
diff --git a/build/android/gyp/java_cpp_enum.pydeps b/build/android/gyp/java_cpp_enum.pydeps
index 32c8de5..d5869ed 100644
--- a/build/android/gyp/java_cpp_enum.pydeps
+++ b/build/android/gyp/java_cpp_enum.pydeps
@@ -4,4 +4,5 @@
 java_cpp_enum.py
 util/__init__.py
 util/build_utils.py
+util/java_cpp_utils.py
 util/md5_check.py
diff --git a/build/android/gyp/java_cpp_enum_tests.py b/build/android/gyp/java_cpp_enum_tests.py
index 703293b..5717047c 100755
--- a/build/android/gyp/java_cpp_enum_tests.py
+++ b/build/android/gyp/java_cpp_enum_tests.py
@@ -5,7 +5,7 @@
 
 """Tests for enum_preprocess.py.
 
-This test suite containss various tests for the C++ -> Java enum generator.
+This test suite contains various tests for the C++ -> Java enum generator.
 """
 
 import collections
@@ -13,8 +13,9 @@
 import unittest
 
 import java_cpp_enum
-from java_cpp_enum import EnumDefinition, GenerateOutput, GetScriptName
+from java_cpp_enum import EnumDefinition, GenerateOutput
 from java_cpp_enum import HeaderParser
+from util import java_cpp_utils
 
 
 class TestPreprocess(unittest.TestCase):
@@ -65,8 +66,8 @@
     long_comment = ('This is a multiple line comment that is really long. '
                     'This is a multiple line comment that is')
     self.assertEqual(
-            expected % (date.today().year, GetScriptName(), long_comment),
-            output)
+        expected % (date.today().year, java_cpp_utils.GetScriptName(),
+                    long_comment), output)
 
   def testParseSimpleEnum(self):
     test_data = """
diff --git a/build/android/gyp/java_cpp_strings.py b/build/android/gyp/java_cpp_strings.py
new file mode 100755
index 0000000..59fc04d
--- /dev/null
+++ b/build/android/gyp/java_cpp_strings.py
@@ -0,0 +1,212 @@
+#!/user/bin/env python
+#
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import os
+import re
+import sys
+import zipfile
+
+from util import build_utils
+from util import java_cpp_utils
+
+
+def _ToUpper(match):
+  return match.group(1).upper()
+
+
+def _GetClassName(source_path):
+  name = os.path.basename(os.path.abspath(source_path))
+  (name, _) = os.path.splitext(name)
+  name = re.sub(r'_([a-z])', _ToUpper, name)
+  name = re.sub(r'^(.)', _ToUpper, name)
+  return name
+
+
+class _String(object):
+
+  def __init__(self, name, value, comments):
+    self.name = java_cpp_utils.KCamelToShouty(name)
+    self.value = value
+    self.comments = '\n'.join('    ' + x for x in comments)
+
+  def Format(self):
+    return '%s\n    public static final String %s = %s;' % (
+        self.comments, self.name, self.value)
+
+
+def ParseTemplateFile(lines):
+  package_re = re.compile(r'^package (.*);')
+  class_re = re.compile(r'.*class (.*) {')
+  package = ''
+  class_name = ''
+  for line in lines:
+    package_line = package_re.match(line)
+    if package_line:
+      package = package_line.groups()[0]
+    class_line = class_re.match(line)
+    if class_line:
+      class_name = class_line.groups()[0]
+      break
+  return package, class_name
+
+
+# TODO(crbug.com/937282): It should be possible to parse a file for more than
+# string constants. However, this currently only handles extracting string
+# constants from a file (and all string constants from that file). Work will
+# be needed if we want to annotate specific constants or non string constants
+# in the file to be parsed.
+class StringFileParser(object):
+  SINGLE_LINE_COMMENT_RE = re.compile(r'\s*(// [^\n]*)')
+  STRING_RE = re.compile(r'\s*const char k(.*)\[\]\s*=\s*(?:(".*"))?')
+  VALUE_RE = re.compile(r'\s*("[^"]*")')
+
+  def __init__(self, lines, path=''):
+    self._lines = lines
+    self._path = path
+    self._in_string = False
+    self._in_comment = False
+    self._package = ''
+    self._current_comments = []
+    self._current_name = ''
+    self._current_value = ''
+    self._strings = []
+
+  def _Reset(self):
+    self._current_comments = []
+    self._current_name = ''
+    self._current_value = ''
+    self._in_string = False
+    self._in_comment = False
+
+  def _AppendString(self):
+    self._strings.append(
+        _String(self._current_name, self._current_value,
+                self._current_comments))
+    self._Reset()
+
+  def _ParseValue(self, line):
+    value_line = StringFileParser.VALUE_RE.match(line)
+    if value_line:
+      self._current_value = value_line.groups()[0]
+      self._AppendString()
+    else:
+      self._Reset()
+
+  def _ParseComment(self, line):
+    comment_line = StringFileParser.SINGLE_LINE_COMMENT_RE.match(line)
+    if comment_line:
+      self._current_comments.append(comment_line.groups()[0])
+      self._in_comment = True
+      self._in_string = True
+      return True
+    else:
+      self._in_comment = False
+      return False
+
+  def _ParseString(self, line):
+    string_line = StringFileParser.STRING_RE.match(line)
+    if string_line:
+      self._current_name = string_line.groups()[0]
+      if string_line.groups()[1]:
+        self._current_value = string_line.groups()[1]
+        self._AppendString()
+      return True
+    else:
+      self._in_string = False
+      return False
+
+  def _ParseLine(self, line):
+    if not self._in_string:
+      self._ParseComment(line)
+      return
+
+    if self._in_comment:
+      if self._ParseComment(line):
+        return
+      if not self._ParseString(line):
+        self._Reset()
+      return
+
+    if self._in_string:
+      self._ParseValue(line)
+
+  def Parse(self):
+    for line in self._lines:
+      self._ParseLine(line)
+    return self._strings
+
+
+def _GenerateOutput(template, source_path, template_path, strings):
+  description_template = """
+    // This following string constants were inserted by
+    //     {SCRIPT_NAME}
+    // From
+    //     {SOURCE_PATH}
+    // Into
+    //     {TEMPLATE_PATH}
+
+"""
+  values = {
+      'SCRIPT_NAME': java_cpp_utils.GetScriptName(),
+      'SOURCE_PATH': source_path,
+      'TEMPLATE_PATH': template_path,
+  }
+  description = description_template.format(**values)
+  native_strings = '\n\n'.join(x.Format() for x in strings)
+
+  values = {
+      'NATIVE_STRINGS': description + native_strings,
+  }
+  return template.format(**values)
+
+
+def _ParseStringFile(path):
+  with open(path) as f:
+    return StringFileParser(f.readlines(), path).Parse()
+
+
+def _Generate(source_paths, template_path):
+  with open(template_path) as f:
+    lines = f.readlines()
+    template = ''.join(lines)
+    for source_path in source_paths:
+      strings = _ParseStringFile(source_path)
+      package, class_name = ParseTemplateFile(lines)
+      package_path = package.replace('.', os.path.sep)
+      file_name = class_name + '.java'
+      output_path = os.path.join(package_path, file_name)
+      output = _GenerateOutput(template, source_path, template_path, strings)
+      yield output, output_path
+
+
+def _Main(argv):
+  parser = argparse.ArgumentParser()
+
+  parser.add_argument(
+      '--srcjar',
+      required=True,
+      help='When specified, a .srcjar at the given path is '
+      'created instead of individual .java files.')
+
+  parser.add_argument(
+      '--template',
+      required=True,
+      help='Can be used to provide a context into which the'
+      'new string constants will be inserted.')
+
+  parser.add_argument(
+      'inputs', nargs='+', help='Input file(s)', metavar='INPUTFILE')
+  args = parser.parse_args(argv)
+
+  with build_utils.AtomicOutput(args.srcjar) as f:
+    with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as srcjar:
+      for data, path in _Generate(args.inputs, args.template):
+        build_utils.AddToZipHermetic(srcjar, path, data=data)
+
+
+if __name__ == '__main__':
+  _Main(sys.argv[1:])
diff --git a/build/android/gyp/java_cpp_strings.pydeps b/build/android/gyp/java_cpp_strings.pydeps
new file mode 100644
index 0000000..901b580
--- /dev/null
+++ b/build/android/gyp/java_cpp_strings.pydeps
@@ -0,0 +1,8 @@
+# Generated by running:
+#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/java_cpp_strings.pydeps build/android/gyp/java_cpp_strings.py
+../../gn_helpers.py
+java_cpp_strings.py
+util/__init__.py
+util/build_utils.py
+util/java_cpp_utils.py
+util/md5_check.py
diff --git a/build/android/gyp/java_cpp_strings_tests.py b/build/android/gyp/java_cpp_strings_tests.py
new file mode 100755
index 0000000..d81b336
--- /dev/null
+++ b/build/android/gyp/java_cpp_strings_tests.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+# 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.
+
+"""Tests for java_cpp_strings.py.
+
+This test suite contains various tests for the C++ -> Java string generator.
+"""
+
+import unittest
+
+import java_cpp_strings
+
+
+class _TestStringsParser(unittest.TestCase):
+
+  def testParseComments(self):
+    test_data = """
+/**
+ * This should be ignored as well.
+ */
+
+// Comment followed by a blank line.
+
+// Comment followed by unrelated code.
+int foo() { return 3; }
+
+// Real comment.
+const char kASwitch[] = "a-value";
+
+// Real comment that spans
+// multiple lines.
+const char kAnotherSwitch[] = "another-value";
+
+// Comment followed by nothing.
+""".split('\n')
+    strings = java_cpp_strings.StringFileParser(test_data).Parse()
+    self.assertEqual(2, len(strings))
+    self.assertEqual('A_SWITCH', strings[0].name)
+    self.assertEqual('"a-value"', strings[0].value)
+    self.assertEqual(1, len(strings[0].comments.split('\n')))
+    self.assertEqual('ANOTHER_SWITCH', strings[1].name)
+    self.assertEqual('"another-value"', strings[1].value)
+    self.assertEqual(2, len(strings[1].comments.split('\n')))
+
+  def testStringValues(self):
+    test_data = """
+// Single line string constants.
+const char kAString[] = "a-value";
+
+// Single line switch with a big space.
+const char kAStringWithSpace[]                      = "a-value";
+
+// Wrapped constant definition.
+const char kAStringWithAVeryLongNameThatWillHaveToWrap[] =
+    "a-string-with-a-very-long-name-that-will-have-to-wrap";
+
+// This is erroneous and should be ignored.
+const char kInvalidLineBreak[] =
+
+    "invalid-line-break";
+""".split('\n')
+    strings = java_cpp_strings.StringFileParser(test_data).Parse()
+    self.assertEqual(3, len(strings))
+    self.assertEqual('A_STRING', strings[0].name)
+    self.assertEqual('"a-value"', strings[0].value)
+    self.assertEqual('A_STRING_WITH_SPACE', strings[1].name)
+    self.assertEqual('"a-value"', strings[1].value)
+    self.assertEqual('A_STRING_WITH_A_VERY_LONG_NAME_THAT_WILL_HAVE_TO_WRAP',
+                     strings[2].name)
+    self.assertEqual('"a-string-with-a-very-long-name-that-will-have-to-wrap"',
+                     strings[2].value)
+
+  def testTemplateParsing(self):
+    test_data = """
+// Copyright {YEAR} 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.
+
+// This file is autogenerated by
+//     {SCRIPT_NAME}
+// From
+//     {SOURCE_PATH}, and
+//     {TEMPLATE_PATH}
+
+package my.java.package;
+
+public any sort of class MyClass {{
+
+{NATIVE_STRINGS}
+
+}}
+""".split('\n')
+    package, class_name = java_cpp_strings.ParseTemplateFile(test_data)
+    self.assertEqual('my.java.package', package)
+    self.assertEqual('MyClass', class_name)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/build/android/gyp/util/java_cpp_utils.py b/build/android/gyp/util/java_cpp_utils.py
new file mode 100755
index 0000000..0b97486
--- /dev/null
+++ b/build/android/gyp/util/java_cpp_utils.py
@@ -0,0 +1,32 @@
+#!/user/bin/env python
+#
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import re
+import sys
+
+
+def GetScriptName():
+  return os.path.basename(os.path.abspath(sys.argv[0]))
+
+
+def KCamelToShouty(s):
+  """Convert |s| from kCamelCase or CamelCase to SHOUTY_CASE.
+
+  kFooBar -> FOO_BAR
+  FooBar -> FOO_BAR
+  FooBAR9 -> FOO_BAR9
+  FooBARBaz -> FOO_BAR_BAZ
+  """
+  if not re.match(r'^k?([A-Z][^A-Z]+|[A-Z0-9]+)+$', s):
+    return s
+  # Strip the leading k.
+  s = re.sub(r'^k', '', s)
+  # Add _ between title words and anything else.
+  s = re.sub(r'([^_])([A-Z][^A-Z_0-9]+)', r'\1_\2', s)
+  # Add _ between lower -> upper transitions.
+  s = re.sub(r'([^A-Z_0-9])([A-Z])', r'\1_\2', s)
+  return s.upper()
diff --git a/build/android/pylib/constants/__init__.py b/build/android/pylib/constants/__init__.py
index 919f506..b98f2a3 100644
--- a/build/android/pylib/constants/__init__.py
+++ b/build/android/pylib/constants/__init__.py
@@ -129,24 +129,27 @@
 
 # TODO(jbudorick): Rework this into testing/buildbot/
 PYTHON_UNIT_TEST_SUITES = {
-  'pylib_py_unittests': {
-    'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android'),
-    'test_modules': [
-      'devil.android.device_utils_test',
-      'devil.android.md5sum_test',
-      'devil.utils.cmd_helper_test',
-      'pylib.results.json_results_test',
-      'pylib.utils.proguard_test',
-    ]
-  },
-  'gyp_py_unittests': {
-    'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'gyp'),
-    'test_modules': [
-      'java_cpp_enum_tests',
-      'java_google_api_keys_tests',
-      'extract_unwind_tables_tests',
-    ]
-  },
+    'pylib_py_unittests': {
+        'path':
+        os.path.join(DIR_SOURCE_ROOT, 'build', 'android'),
+        'test_modules': [
+            'devil.android.device_utils_test',
+            'devil.android.md5sum_test',
+            'devil.utils.cmd_helper_test',
+            'pylib.results.json_results_test',
+            'pylib.utils.proguard_test',
+        ]
+    },
+    'gyp_py_unittests': {
+        'path':
+        os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'gyp'),
+        'test_modules': [
+            'java_cpp_enum_tests',
+            'java_cpp_strings_tests',
+            'java_google_api_keys_tests',
+            'extract_unwind_tables_tests',
+        ]
+    },
 }
 
 LOCAL_MACHINE_TESTS = ['junit', 'python']
diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn
index bcc9c42f..4d6ba29 100644
--- a/build/config/BUILD.gn
+++ b/build/config/BUILD.gn
@@ -126,6 +126,8 @@
     defines += [ "SAFE_BROWSING_DB_LOCAL" ]
   } else if (safe_browsing_mode == 2) {
     defines += [ "SAFE_BROWSING_DB_REMOTE" ]
+  } else if (safe_browsing_mode == 3) {
+    defines += [ "SAFE_BROWSING_DB_LOCAL" ]
   }
   if (is_official_build) {
     defines += [ "OFFICIAL_BUILD" ]
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index f5cc8e7..a0d7350 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -2417,7 +2417,6 @@
   #       asset information.
   #   deps: Specifies the dependencies of this target.
   #   dex_path: Path to classes.dex file to include (optional).
-  #   exclude_dex: Omit .dex files from the .apk (optional).
   #   packaged_resources_path: Path to .ap_ to use.
   #   output_apk_path: Output path for the generated .apk.
   #   native_lib_placeholders: List of placeholder filenames to add to the apk
@@ -2454,7 +2453,6 @@
         _secondary_native_lib_placeholders =
             invoker.secondary_native_lib_placeholders
       }
-      _exclude_dex = defined(invoker.exclude_dex) && invoker.exclude_dex
 
       script = "//build/android/gyp/apkbuilder.py"
       depfile = "$target_gen_dir/$target_name.d"
@@ -2520,7 +2518,7 @@
       if (defined(invoker.write_asset_list) && invoker.write_asset_list) {
         args += [ "--write-asset-list" ]
       }
-      if (defined(invoker.dex_path) && !_exclude_dex) {
+      if (defined(invoker.dex_path)) {
         _rebased_dex_path = rebase_path(invoker.dex_path, root_build_dir)
         args += [ "--dex-file=$_rebased_dex_path" ]
       }
@@ -2602,7 +2600,6 @@
                              [
                                "apk_name",
                                "assets_build_config",
-                               "exclude_dex",
                                "native_lib_placeholders",
                                "native_libs_filearg",
                                "packaged_resources_path",
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 18d6448..2b587d3e 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -635,6 +635,85 @@
     }
   }
 
+  # Declare a target for generating Java classes with string constants matching
+  # those found in C++ files using a python script.
+  #
+  # This target will create a single .srcjar. Adding this target to an
+  # android_library target's srcjar_deps will make the generated java files be
+  # included in that library's final outputs.
+  #
+  # Variables
+  #   sources: list of files to be processed by the script. For each string
+  #            constant in the source files, the script will add a corresponding
+  #            Java string to the specified template file.
+  # Example
+  #   java_cpp_strings("foo_switches") {
+  #     sources = [
+  #       "src/foo_switches.cc",
+  #     ]
+  #     template = "src/templates/FooSwitches.java.tmpl
+  #   }
+  #
+  # foo_switches.cc:
+  #
+  # // A switch.
+  # const char kASwitch = "a-switch";
+  #
+  # FooSwitches.java.tmpl
+  #
+  # // Copyright {YEAR} 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.
+  #
+  # // This file is autogenerated by
+  # //     {SCRIPT_NAME}
+  # // From
+  # //     {SOURCE_PATH}, and
+  # //     {TEMPLATE_PATH}
+  #
+  # package my.java.package;
+  #
+  # public abstract class FooSwitches {{
+  #     // ...snip...
+  # {NATIVE_STRINGS}
+  #     // ...snip...
+  # }}
+  #
+  # result:
+  #   A FooSwitches.java file, defining a class named FooSwitches in the package
+  #   my.java.package.
+  template("java_cpp_strings") {
+    set_sources_assignment_filter([])
+    action_with_pydeps(target_name) {
+      forward_variables_from(invoker,
+                             [
+                               "sources",
+                               "testonly",
+                               "visibility",
+                             ])
+
+      # The sources aren't compiled so don't check their dependencies.
+      check_includes = false
+      script = "//build/android/gyp/java_cpp_strings.py"
+
+      _srcjar_path = "${target_gen_dir}/${target_name}.srcjar"
+      _rebased_srcjar_path = rebase_path(_srcjar_path, root_build_dir)
+      _rebased_sources = rebase_path(invoker.sources, root_build_dir)
+      _rebased_template = rebase_path(invoker.template, root_build_dir)
+
+      args = [
+        "--srcjar=$_rebased_srcjar_path",
+        "--template=$_rebased_template",
+      ]
+      args += _rebased_sources
+      sources += [ invoker.template ]
+
+      outputs = [
+        _srcjar_path,
+      ]
+    }
+  }
+
   # Declare a target for processing a Jinja template.
   #
   # Variables
@@ -1962,7 +2041,6 @@
   #     resources with acceptable/non-acceptable optimizations.
   #   verify_android_configuration: Enables verification of expected merged
   #     manifest and proguard flags based on a golden file.
-  #   exclude_dex: Omit .dex files (even if they exist) from the final APK.
   template("android_apk_or_module") {
     forward_variables_from(invoker, [ "testonly" ])
 
@@ -2699,7 +2777,6 @@
       create_apk("$_create_apk_target") {
         forward_variables_from(invoker,
                                [
-                                 "exclude_dex",
                                  "native_lib_placeholders",
                                  "public_deps",
                                  "secondary_native_lib_placeholders",
@@ -2974,7 +3051,6 @@
                                "emma_never_instrument",
                                "enable_chromium_linker_tests",
                                "enable_multidex",
-                               "exclude_dex",
                                "final_apk_path",
                                "firebase_app_id",
                                "generate_buildconfig_java",
@@ -4267,46 +4343,6 @@
       ]
     }
   }
-
-  # Generate an APK stub that contains a stripped down version of the manifest,
-  # a resources.arsc containing only the package name string, and signing
-  # artifacts.
-  #
-  # Variables:
-  #   android_manifest: Path to the android manifest file.
-  #   android_manifest_dep: Target that generates the manifest (optional).
-  #   apk_name: Name of the final .apk.
-  template("create_stub_apk") {
-    _stub_manifest = "$target_gen_dir/$target_name/AndroidManifest_stub.xml"
-    _stub_manifest_target = "${target_name}__stub_manifest"
-    action(_stub_manifest_target) {
-      script = "//build/android/gyp/create_stub_manifest.py"
-      inputs = [
-        invoker.android_manifest,
-      ]
-      outputs = [
-        _stub_manifest,
-      ]
-      args = [
-        "--input",
-        rebase_path(invoker.android_manifest),
-        "--output",
-        rebase_path(_stub_manifest),
-      ]
-      if (defined(invoker.android_manifest_dep)) {
-        deps = [
-          invoker.android_manifest_dep,
-        ]
-      }
-    }
-
-    android_apk(target_name) {
-      apk_name = invoker.apk_name
-      android_manifest = _stub_manifest
-      android_manifest_dep = ":$_stub_manifest_target"
-      exclude_dex = true
-    }
-  }
 }
 
 # Generate an Android resources target that contains localized strings
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index 5b60633c..0b5bbbad 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -211,7 +211,14 @@
     # Use lower symbol level in Simple Chrome build for faster link time.
     # For Simple Chrome, this should take precedence over is_official_build,
     # turned on by --internal.
-    symbol_level = 1
+    if ((target_cpu == "x64" || target_cpu == "x86") && !is_debug) {
+      # For release x86/x64 build, specify symbol_level=0 for faster link time.
+      # x86/x64 shows backtraces with symbol_level=0 (arm requires
+      # symbol_level=1).
+      symbol_level = 0
+    } else {
+      symbol_level = 1
+    }
   } else if ((!is_nacl && !is_linux && !is_fuchsia) || is_debug ||
              is_official_build || is_chromecast) {
     # Linux builds slower by having symbols as part of the target binary,
diff --git a/build/config/coverage/BUILD.gn b/build/config/coverage/BUILD.gn
index ae5435bb..2604411 100644
--- a/build/config/coverage/BUILD.gn
+++ b/build/config/coverage/BUILD.gn
@@ -25,25 +25,20 @@
       }
     }
 
-    # Coverage flags are only on by default when instrument all source files.
-    # Otherwise, coverage flags are dynamically passed to the compile command
-    # via the //build/toolchain/clang_code_coverage_wrapper.py script.
-    if (coverage_instrumentation_input_file == "") {
-      cflags = [
-        "-fprofile-instr-generate",
-        "-fcoverage-mapping",
+    cflags = [
+      "-fprofile-instr-generate",
+      "-fcoverage-mapping",
 
-        # Following experimental flags removes unused header functions from the
-        # coverage mapping data embedded in the test binaries, and the reduction
-        # of binary size enables building Chrome's large unit test targets on
-        # MacOS. Please refer to crbug.com/796290 for more details.
-        "-mllvm",
-        "-limited-coverage-experimental=true",
-      ]
+      # Following experimental flags removes unused header functions from the
+      # coverage mapping data embedded in the test binaries, and the reduction
+      # of binary size enables building Chrome's large unit test targets on
+      # MacOS. Please refer to crbug.com/796290 for more details.
+      "-mllvm",
+      "-limited-coverage-experimental=true",
+    ]
 
-      if (!is_win) {
-        cflags += [ "-fno-use-cxa-atexit" ]
-      }
+    if (!is_win) {
+      cflags += [ "-fno-use-cxa-atexit" ]
     }
   }
 }
diff --git a/build/config/features.gni b/build/config/features.gni
index b26eb33e..fdf05dfdd 100644
--- a/build/config/features.gni
+++ b/build/config/features.gni
@@ -30,12 +30,16 @@
 
   # Variable safe_browsing is used to control the build time configuration for
   # safe browsing feature. Safe browsing can be compiled in 3 different levels:
-  # 0 disables it, 1 enables it fully, and 2 enables mobile protection via an
-  # external API.
+  # 0 disables it, 1 enables it fully, 2 enables mobile protection via an
+  # external API, and 3 enables mobile protection via internal API.
   if (is_ios || is_chromecast) {
     safe_browsing_mode = 0
   } else if (is_android) {
-    safe_browsing_mode = 2
+    if (notouch_build) {
+      safe_browsing_mode = 3
+    } else {
+      safe_browsing_mode = 2
+    }
   } else {
     safe_browsing_mode = 1
   }
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index d9d3747..d93fbe46 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-bccf588306321fe071f6421918793ce1ad1c0148
\ No newline at end of file
+8d562ec8b8d81d11fa8b6bb199ca07049ab9e074
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 8916285..f5a1ede 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-d2912e75c6c98188ef6e209ef6168974a8ec4320
\ No newline at end of file
+6687834853f97abfde3948deff3c7bb37d23ec08
\ No newline at end of file
diff --git a/build/toolchain/clang_code_coverage_wrapper.py b/build/toolchain/clang_code_coverage_wrapper.py
index eb493bfa..96978056 100755
--- a/build/toolchain/clang_code_coverage_wrapper.py
+++ b/build/toolchain/clang_code_coverage_wrapper.py
@@ -2,11 +2,19 @@
 # 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.
-"""Adds code coverage flags to the invocations of the Clang C/C++ compiler.
+"""Removes code coverage flags from invocations of the Clang C/C++ compiler.
 
-This script is used to instrument a subset of the source files, and the list of
-files to instrument is specified by an input file that is passed to this script
-as a command-line argument.
+If the GN arg `use_clang_coverage=true`, this script will be invoked by default.
+GN will add coverage instrumentation flags to almost all source files.
+
+This script is used to remove instrumentation flags from a subset of the source
+files. By default, it will not remove flags from any files. If the option
+--files-to-instrument is passed, this script will remove flags from all files
+except the ones listed in --files-to-instrument.
+
+This script also contains hard-coded exclusion lists of files to never
+instrument, indexed by target operating system. Files in these lists have their
+flags removed in both modes. The OS can be selected with --target-os.
 
 The path to the coverage instrumentation input file should be relative to the
 root build directory, and the file consists of multiple lines where each line
@@ -37,6 +45,8 @@
 import sys
 
 # Flags used to enable coverage instrumentation.
+# Flags should be listed in the same order that they are added in
+# build/config/coverage/BUILD.gn
 _COVERAGE_FLAGS = [
     '-fprofile-instr-generate', '-fcoverage-mapping',
     # Following experimental flags remove unused header functions from the
@@ -46,6 +56,41 @@
     '-mllvm', '-limited-coverage-experimental=true'
 ]
 
+# Map of exclusion lists indexed by target OS.
+# If no target OS is defined, or one is defined that doesn't have a specific
+# entry, use the 'default' exclusion_list. Anything added to 'default' will
+# apply to all platforms that don't have their own specific list.
+_COVERAGE_EXCLUSION_LIST_MAP = {
+    'default': [],
+    'chromeos': [
+        # These files caused clang to crash while compiling them. They are
+        # excluded pending an investigation into the underlying compiler bug.
+        '../../third_party/webrtc/p2p/base/p2p_transport_channel.cc',
+        '../../third_party/icu/source/common/uts46.cpp',
+        '../../third_party/icu/source/common/ucnvmbcs.cpp',
+        '../../base/android/android_image_reader_compat.cc',
+    ]
+}
+
+
+def _remove_flags_from_command(command):
+  # We need to remove the coverage flags for this file, but we only want to
+  # remove them if we see the exact sequence defined in _COVERAGE_FLAGS.
+  # That ensures that we only remove the flags added by GN when
+  # "use_clang_coverage" is true. Otherwise, we would remove flags set by
+  # other parts of the build system.
+  start_flag = _COVERAGE_FLAGS[0]
+  num_flags = len(_COVERAGE_FLAGS)
+  start_idx = 0
+  try:
+    while True:
+      idx = command.index(start_flag, start_idx)
+      start_idx = idx + 1
+      if command[idx:idx+num_flags] == _COVERAGE_FLAGS:
+        del command[idx:idx+num_flags]
+        break
+  except ValueError:
+    pass
 
 def main():
   # TODO(crbug.com/898695): Make this wrapper work on Windows platform.
@@ -54,16 +99,23 @@
   arg_parser.add_argument(
       '--files-to-instrument',
       type=str,
-      required=True,
       help='Path to a file that contains a list of file names to instrument.')
+  arg_parser.add_argument(
+      '--target-os',
+      required=False,
+      help='The OS to compile for.')
   arg_parser.add_argument('args', nargs=argparse.REMAINDER)
   parsed_args = arg_parser.parse_args()
 
-  if not os.path.isfile(parsed_args.files_to_instrument):
+  if (parsed_args.files_to_instrument and
+      not os.path.isfile(parsed_args.files_to_instrument)):
     raise Exception('Path to the coverage instrumentation file: "%s" doesn\'t '
                     'exist.' % parsed_args.files_to_instrument)
 
   compile_command = parsed_args.args
+  if not any('clang' in s for s in compile_command):
+    return subprocess.call(compile_command)
+
   try:
     # The command is assumed to use Clang as the compiler, and the path to the
     # source file is behind the -c argument, and the path to the source path is
@@ -79,12 +131,19 @@
     raise Exception('Source file to be compiled is missing from the command.')
 
   compile_source_file = compile_command[index_dash_c + 1]
-  with open(parsed_args.files_to_instrument) as f:
-    if compile_source_file + '\n' in f.read():
-      compile_command.extend(_COVERAGE_FLAGS)
+  target_os = parsed_args.target_os
+  if target_os not in _COVERAGE_EXCLUSION_LIST_MAP:
+    target_os = 'default'
+  exclusion_list = _COVERAGE_EXCLUSION_LIST_MAP[target_os]
+
+  if compile_source_file in exclusion_list:
+    _remove_flags_from_command(compile_command)
+  elif parsed_args.files_to_instrument:
+    with open(parsed_args.files_to_instrument) as f:
+      if compile_source_file not in f.read():
+        _remove_flags_from_command(compile_command)
 
   return subprocess.call(compile_command)
 
-
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 47f18c8a..7326a84 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -194,25 +194,48 @@
       compiler_prefix = "${analyzer_wrapper} " + compiler_prefix
     }
 
-    if (defined(toolchain_args.coverage_instrumentation_input_file)) {
-      toolchain_coverage_instrumentation_input_file =
-          toolchain_args.coverage_instrumentation_input_file
+    # A specific toolchain may wish to avoid coverage instrumentation, so we
+    # allow the global "use_clang_coverage" arg to be overridden.
+    if (defined(toolchain_args.use_clang_coverage)) {
+      toolchain_use_clang_coverage = toolchain_args.use_clang_coverage
     } else {
-      toolchain_coverage_instrumentation_input_file =
-          coverage_instrumentation_input_file
+      toolchain_use_clang_coverage = use_clang_coverage
     }
-    _use_clang_coverage_wrapper =
-        toolchain_coverage_instrumentation_input_file != ""
-    if (_use_clang_coverage_wrapper) {
+
+    # For a coverage build, we use the wrapper script globally so that it can
+    # remove coverage cflags from files that should not have them.
+    if (toolchain_use_clang_coverage) {
       assert(!use_clang_static_analyzer,
              "Clang static analyzer wrapper and Clang code coverage wrapper " +
                  "cannot be used together.")
 
+      # "coverage_instrumentation_input_file" is set in args.gn, but it can be
+      # overridden by a toolchain config.
+      if (defined(toolchain_args.coverage_instrumentation_input_file)) {
+        toolchain_coverage_instrumentation_input_file =
+            toolchain_args.coverage_instrumentation_input_file
+      } else {
+        toolchain_coverage_instrumentation_input_file =
+            coverage_instrumentation_input_file
+      }
+
       _coverage_wrapper =
           rebase_path("//build/toolchain/clang_code_coverage_wrapper.py",
-                      root_build_dir) + " --files-to-instrument=" +
-          rebase_path(toolchain_coverage_instrumentation_input_file,
                       root_build_dir)
+
+      # The wrapper needs to know what OS we target because it uses that to
+      # select a list of files that should not be instrumented.
+      _coverage_wrapper = _coverage_wrapper + " --target-os=" + target_os
+
+      # We want to instrument everything if there is no input file set.
+      # If there is a file we need to give it to the wrapper script so it can
+      # instrument only those files.
+      if (toolchain_coverage_instrumentation_input_file != "") {
+        _coverage_wrapper =
+            _coverage_wrapper + " --files-to-instrument=" +
+            rebase_path(toolchain_coverage_instrumentation_input_file,
+                        root_build_dir)
+      }
       compiler_prefix = "${_coverage_wrapper} " + compiler_prefix
     }
 
diff --git a/cc/OWNERS b/cc/OWNERS
index e12309d..4d4bbfa 100644
--- a/cc/OWNERS
+++ b/cc/OWNERS
@@ -48,6 +48,9 @@
 # input, scrolling
 bokan@chromium.org
 
+# scroll snap
+majidvp@chromium.org
+
 # general
 enne@chromium.org
 danakj@chromium.org
diff --git a/cc/layers/heads_up_display_layer_impl.h b/cc/layers/heads_up_display_layer_impl.h
index 352b71bd..0315c2c 100644
--- a/cc/layers/heads_up_display_layer_impl.h
+++ b/cc/layers/heads_up_display_layer_impl.h
@@ -14,7 +14,6 @@
 #include "base/time/time.h"
 #include "cc/cc_export.h"
 #include "cc/layers/layer_impl.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/resources/memory_history.h"
 #include "cc/resources/resource_pool.h"
 #include "cc/trees/debug_rect_history.h"
@@ -159,9 +158,6 @@
 
   base::TimeTicks time_of_last_graph_update_;
 
-  // color space for OOPR
-  const RasterColorSpace raster_color_space_;
-
   DISALLOW_COPY_AND_ASSIGN(HeadsUpDisplayLayerImpl);
 };
 
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index ddfa824..6c428fba 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -8,8 +8,6 @@
 cc_component("paint") {
   output_name = "cc_paint"
   sources = [
-    "color_space_transfer_cache_entry.cc",
-    "color_space_transfer_cache_entry.h",
     "decode_stashing_image_provider.cc",
     "decode_stashing_image_provider.h",
     "decoded_draw_image.cc",
diff --git a/cc/paint/color_space_transfer_cache_entry.cc b/cc/paint/color_space_transfer_cache_entry.cc
deleted file mode 100644
index 31f2743..0000000
--- a/cc/paint/color_space_transfer_cache_entry.cc
+++ /dev/null
@@ -1,59 +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 "cc/paint/color_space_transfer_cache_entry.h"
-
-#include "ui/gfx/ipc/color/gfx_param_traits.h"
-
-namespace cc {
-
-ClientColorSpaceTransferCacheEntry::ClientColorSpaceTransferCacheEntry(
-    const RasterColorSpace& raster_color_space)
-    : id_(raster_color_space.color_space_id) {
-  DCHECK(raster_color_space.color_space.IsValid());
-  IPC::ParamTraits<gfx::ColorSpace>::Write(&pickle_,
-                                           raster_color_space.color_space);
-  DCHECK_LE(pickle_.size(), UINT32_MAX);
-}
-
-ClientColorSpaceTransferCacheEntry::~ClientColorSpaceTransferCacheEntry() =
-    default;
-
-uint32_t ClientColorSpaceTransferCacheEntry::Id() const {
-  return id_;
-}
-
-uint32_t ClientColorSpaceTransferCacheEntry::SerializedSize() const {
-  return static_cast<uint32_t>(pickle_.size());
-}
-
-bool ClientColorSpaceTransferCacheEntry::Serialize(
-    base::span<uint8_t> data) const {
-  DCHECK_GE(data.size(), pickle_.size());
-  memcpy(data.data(), pickle_.data(), pickle_.size());
-  return true;
-}
-
-ServiceColorSpaceTransferCacheEntry::ServiceColorSpaceTransferCacheEntry() =
-    default;
-
-ServiceColorSpaceTransferCacheEntry::~ServiceColorSpaceTransferCacheEntry() =
-    default;
-
-size_t ServiceColorSpaceTransferCacheEntry::CachedSize() const {
-  return sizeof(gfx::ColorSpace);
-}
-
-bool ServiceColorSpaceTransferCacheEntry::Deserialize(
-    GrContext* context,
-    base::span<const uint8_t> data) {
-  base::Pickle pickle(reinterpret_cast<const char*>(data.data()), data.size());
-  base::PickleIterator iterator(pickle);
-  if (!IPC::ParamTraits<gfx::ColorSpace>::Read(&pickle, &iterator,
-                                               &color_space_))
-    return false;
-  return color_space_.IsValid();
-}
-
-}  // namespace cc
diff --git a/cc/paint/color_space_transfer_cache_entry.h b/cc/paint/color_space_transfer_cache_entry.h
deleted file mode 100644
index 4923c73..0000000
--- a/cc/paint/color_space_transfer_cache_entry.h
+++ /dev/null
@@ -1,57 +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.
-
-#ifndef CC_PAINT_COLOR_SPACE_TRANSFER_CACHE_ENTRY_H_
-#define CC_PAINT_COLOR_SPACE_TRANSFER_CACHE_ENTRY_H_
-
-#include "base/containers/span.h"
-#include "base/pickle.h"
-#include "cc/paint/paint_export.h"
-#include "cc/paint/transfer_cache_entry.h"
-#include "ui/gfx/color_space.h"
-
-namespace cc {
-
-struct RasterColorSpace {
-  RasterColorSpace() = default;
-  RasterColorSpace(const gfx::ColorSpace& color_space, int color_space_id)
-      : color_space(color_space), color_space_id(color_space_id) {}
-
-  gfx::ColorSpace color_space;
-  int color_space_id = -1;
-};
-
-class CC_PAINT_EXPORT ClientColorSpaceTransferCacheEntry final
-    : public ClientTransferCacheEntryBase<TransferCacheEntryType::kColorSpace> {
- public:
-  explicit ClientColorSpaceTransferCacheEntry(
-      const RasterColorSpace& raster_color_space);
-  ~ClientColorSpaceTransferCacheEntry() override;
-  uint32_t Id() const override;
-  uint32_t SerializedSize() const override;
-  bool Serialize(base::span<uint8_t> data) const final;
-
- private:
-  int id_;
-  base::Pickle pickle_;
-};
-
-class CC_PAINT_EXPORT ServiceColorSpaceTransferCacheEntry final
-    : public ServiceTransferCacheEntryBase<
-          TransferCacheEntryType::kColorSpace> {
- public:
-  ServiceColorSpaceTransferCacheEntry();
-  ~ServiceColorSpaceTransferCacheEntry() override;
-  size_t CachedSize() const override;
-  bool Deserialize(GrContext* context, base::span<const uint8_t> data) override;
-
-  const gfx::ColorSpace& color_space() const { return color_space_; }
-
- private:
-  gfx::ColorSpace color_space_;
-};
-
-}  // namespace cc
-
-#endif  // CC_PAINT_COLOR_SPACE_TRANSFER_CACHE_ENTRY_H_
diff --git a/cc/paint/transfer_cache_entry.cc b/cc/paint/transfer_cache_entry.cc
index d87ac46..4d519b66 100644
--- a/cc/paint/transfer_cache_entry.cc
+++ b/cc/paint/transfer_cache_entry.cc
@@ -7,7 +7,6 @@
 #include <memory>
 
 #include "base/logging.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/paint/image_transfer_cache_entry.h"
 #include "cc/paint/raw_memory_transfer_cache_entry.h"
 #include "cc/paint/shader_transfer_cache_entry.h"
@@ -21,8 +20,6 @@
       return std::make_unique<ServiceRawMemoryTransferCacheEntry>();
     case TransferCacheEntryType::kImage:
       return std::make_unique<ServiceImageTransferCacheEntry>();
-    case TransferCacheEntryType::kColorSpace:
-      return std::make_unique<ServiceColorSpaceTransferCacheEntry>();
     case TransferCacheEntryType::kShader:
       // ServiceShader/TextBlobTransferCache is only created via
       // CreateLocalEntry and is never serialized/deserialized.
@@ -46,7 +43,6 @@
 bool ServiceTransferCacheEntry::UsesGrContext(TransferCacheEntryType type) {
   switch (type) {
     case TransferCacheEntryType::kRawMemory:
-    case TransferCacheEntryType::kColorSpace:
     case TransferCacheEntryType::kShader:
       return false;
     case TransferCacheEntryType::kImage:
diff --git a/cc/paint/transfer_cache_entry.h b/cc/paint/transfer_cache_entry.h
index 5f7dabe1..ec5f6270 100644
--- a/cc/paint/transfer_cache_entry.h
+++ b/cc/paint/transfer_cache_entry.h
@@ -22,7 +22,6 @@
 enum class TransferCacheEntryType : uint32_t {
   kRawMemory,
   kImage,
-  kColorSpace,
   kShader,
   // Add new entries above this line, make sure to update kLast.
   kLast = kShader,
diff --git a/cc/raster/raster_source.h b/cc/raster/raster_source.h
index b73992a..d4d45ff5 100644
--- a/cc/raster/raster_source.h
+++ b/cc/raster/raster_source.h
@@ -14,7 +14,6 @@
 #include "cc/cc_export.h"
 #include "cc/debug/rendering_stats_instrumentation.h"
 #include "cc/layers/recording_source.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/paint/image_id.h"
 #include "third_party/skia/include/core/SkPicture.h"
 #include "ui/gfx/color_space.h"
@@ -40,8 +39,6 @@
     bool use_lcd_text = true;
 
     ImageProvider* image_provider = nullptr;
-
-    RasterColorSpace raster_color_space;
   };
 
   // Helper function to apply a few common operations before passing the canvas
diff --git a/cc/test/fake_tile_manager_client.cc b/cc/test/fake_tile_manager_client.cc
index 0b9acc3..8411d78 100644
--- a/cc/test/fake_tile_manager_client.cc
+++ b/cc/test/fake_tile_manager_client.cc
@@ -23,8 +23,8 @@
   return nullptr;
 }
 
-RasterColorSpace FakeTileManagerClient::GetRasterColorSpace() const {
-  return RasterColorSpace();
+const gfx::ColorSpace& FakeTileManagerClient::GetRasterColorSpace() const {
+  return color_space_;
 }
 
 size_t FakeTileManagerClient::GetFrameIndexForImage(
diff --git a/cc/test/fake_tile_manager_client.h b/cc/test/fake_tile_manager_client.h
index dd8be51..791beb93 100644
--- a/cc/test/fake_tile_manager_client.h
+++ b/cc/test/fake_tile_manager_client.h
@@ -27,10 +27,13 @@
   std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue(
       TreePriority tree_priority) override;
   void SetIsLikelyToRequireADraw(bool is_likely_to_require_a_draw) override {}
-  RasterColorSpace GetRasterColorSpace() const override;
+  const gfx::ColorSpace& GetRasterColorSpace() const override;
   void RequestImplSideInvalidationForCheckerImagedTiles() override {}
   size_t GetFrameIndexForImage(const PaintImage& paint_image,
                                WhichTree tree) const override;
+
+ private:
+  gfx::ColorSpace color_space_;
 };
 
 }  // namespace cc
diff --git a/cc/tiles/tile_manager.cc b/cc/tiles/tile_manager.cc
index ae2af26..43d282e6 100644
--- a/cc/tiles/tile_manager.cc
+++ b/cc/tiles/tile_manager.cc
@@ -1172,7 +1172,7 @@
         tile->id(), tile->invalidated_content_rect(), tile->invalidated_id(),
         &invalidated_rect);
   }
-  const RasterColorSpace& raster_color_space = client_->GetRasterColorSpace();
+  const gfx::ColorSpace& raster_color_space = client_->GetRasterColorSpace();
   bool partial_tile_decode = false;
   if (resource) {
     resource_content_id = tile->invalidated_id();
@@ -1181,7 +1181,7 @@
   } else {
     resource = resource_pool_->AcquireResource(tile->desired_texture_size(),
                                                DetermineResourceFormat(tile),
-                                               raster_color_space.color_space);
+                                               raster_color_space);
     DCHECK(resource);
   }
 
@@ -1269,9 +1269,6 @@
 
   PlaybackImageProvider image_provider(image_controller_.cache(),
                                        std::move(settings));
-
-  playback_settings.raster_color_space = raster_color_space;
-
   PaintWorkletImageProvider paint_worklet_image_provider(
       image_controller_.paint_worklet_image_cache());
   DispatchingImageProvider dispatching_image_provider(
diff --git a/cc/tiles/tile_manager.h b/cc/tiles/tile_manager.h
index 244eea4..c4024e2 100644
--- a/cc/tiles/tile_manager.h
+++ b/cc/tiles/tile_manager.h
@@ -18,7 +18,6 @@
 #include "base/sequenced_task_runner.h"
 #include "base/values.h"
 #include "cc/base/unique_notifier.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/raster/raster_buffer_provider.h"
 #include "cc/raster/raster_source.h"
 #include "cc/resources/memory_history.h"
@@ -82,7 +81,7 @@
   virtual void SetIsLikelyToRequireADraw(bool is_likely_to_require_a_draw) = 0;
 
   // Requests the color space into which tiles should be rasterized.
-  virtual RasterColorSpace GetRasterColorSpace() const = 0;
+  virtual const gfx::ColorSpace& GetRasterColorSpace() const = 0;
 
   // Requests that a pending tree be scheduled to invalidate content on the
   // pending on active tree. This is currently used when tiles that are
@@ -199,7 +198,7 @@
           resource_pool_->AcquireResource(
               tiles[i]->desired_texture_size(),
               raster_buffer_provider_->GetResourceFormat(),
-              client_->GetRasterColorSpace().color_space);
+              client_->GetRasterColorSpace());
       raster_buffer_provider_->AcquireBufferForRaster(resource, 0, 0);
       // The raster here never really happened, cuz tests. So just add an
       // arbitrary sync token.
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 483243c..51e5f965c 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1573,16 +1573,22 @@
   is_likely_to_require_a_draw_ = is_likely_to_require_a_draw;
 }
 
-RasterColorSpace LayerTreeHostImpl::GetRasterColorSpace() const {
-  RasterColorSpace result;
+const gfx::ColorSpace& LayerTreeHostImpl::GetRasterColorSpace() const {
+  int dummy;
+  return GetRasterColorSpaceAndId(&dummy);
+}
+
+const gfx::ColorSpace& LayerTreeHostImpl::GetRasterColorSpaceAndId(
+    int* id) const {
+  const gfx::ColorSpace* result = nullptr;
   // The pending tree will have the most recently updated color space, so
   // prefer that.
   if (pending_tree_) {
-    result.color_space = pending_tree_->raster_color_space();
-    result.color_space_id = pending_tree_->raster_color_space_id();
+    result = &pending_tree_->raster_color_space();
+    *id = pending_tree_->raster_color_space_id();
   } else if (active_tree_) {
-    result.color_space = active_tree_->raster_color_space();
-    result.color_space_id = active_tree_->raster_color_space_id();
+    result = &active_tree_->raster_color_space();
+    *id = active_tree_->raster_color_space_id();
   }
 
   // If we are likely to software composite the resource, we use sRGB because
@@ -1591,11 +1597,11 @@
   // (not specifying a color space indicates that no color conversion is
   // required).
   if (!layer_tree_frame_sink_ || !layer_tree_frame_sink_->context_provider() ||
-      !result.color_space.IsValid()) {
-    result.color_space = default_color_space_;
-    result.color_space_id = default_color_space_id_;
+      !result || !result->IsValid()) {
+    result = &default_color_space_;
+    *id = default_color_space_id_;
   }
-  return result;
+  return *result;
 }
 
 void LayerTreeHostImpl::RequestImplSideInvalidationForCheckerImagedTiles() {
@@ -2382,7 +2388,8 @@
   // For simplicity, clobber all resources when the color space changes.
   // This is mostly to clear the image decode caches, which don't handle
   // multiple color space at once.
-  int color_space_id = GetRasterColorSpace().color_space_id;
+  int color_space_id = -1;
+  GetRasterColorSpaceAndId(&color_space_id);
   bool color_space_changed = last_color_space_id_ != color_space_id;
   last_color_space_id_ = color_space_id;
 
@@ -3060,14 +3067,14 @@
                                                 tile_format),
         settings_.decoded_image_working_set_budget_bytes, max_texture_size_,
         paint_image_generator_client_id_,
-        GetRasterColorSpace().color_space.ToSkColorSpace());
+        GetRasterColorSpace().ToSkColorSpace());
   } else {
     bool gpu_compositing = !!layer_tree_frame_sink_->context_provider();
     image_decode_cache_ = std::make_unique<SoftwareImageDecodeCache>(
         viz::ResourceFormatToClosestSkColorType(gpu_compositing, tile_format),
         settings_.decoded_image_working_set_budget_bytes,
         paint_image_generator_client_id_,
-        GetRasterColorSpace().color_space.ToSkColorSpace());
+        GetRasterColorSpace().ToSkColorSpace());
   }
 
   // Pass the single-threaded synchronous task graph runner to the worker pool
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index bbcdfce..6f07f25 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -427,7 +427,7 @@
   std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue(
       TreePriority tree_priority) override;
   void SetIsLikelyToRequireADraw(bool is_likely_to_require_a_draw) override;
-  RasterColorSpace GetRasterColorSpace() const override;
+  const gfx::ColorSpace& GetRasterColorSpace() const override;
   void RequestImplSideInvalidationForCheckerImagedTiles() override;
   size_t GetFrameIndexForImage(const PaintImage& paint_image,
                                WhichTree tree) const override;
@@ -759,6 +759,8 @@
   BeginFrameTracker current_begin_frame_tracker_;
 
  private:
+  const gfx::ColorSpace& GetRasterColorSpaceAndId(int* id) const;
+
   void CollectScrollDeltas(ScrollAndScaleSet* scroll_info) const;
   void CollectScrollbarUpdates(ScrollAndScaleSet* scroll_info) const;
 
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 97ef9ed..8288c54dd 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -13630,12 +13630,11 @@
   LayerTreeSettings settings = DefaultSettings();
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
   // The default raster color space should be sRGB.
-  EXPECT_EQ(host_impl_->GetRasterColorSpace().color_space,
-            gfx::ColorSpace::CreateSRGB());
+  EXPECT_EQ(host_impl_->GetRasterColorSpace(), gfx::ColorSpace::CreateSRGB());
   // The raster color space should update with tree activation.
   host_impl_->active_tree()->SetRasterColorSpace(
       2, gfx::ColorSpace::CreateDisplayP3D65());
-  EXPECT_EQ(host_impl_->GetRasterColorSpace().color_space,
+  EXPECT_EQ(host_impl_->GetRasterColorSpace(),
             gfx::ColorSpace::CreateDisplayP3D65());
 }
 
@@ -13643,12 +13642,10 @@
   LayerTreeSettings settings = DefaultSettings();
   CreateHostImpl(settings, FakeLayerTreeFrameSink::CreateSoftware());
   // Software composited resources should always use sRGB as their color space.
-  EXPECT_EQ(host_impl_->GetRasterColorSpace().color_space,
-            gfx::ColorSpace::CreateSRGB());
+  EXPECT_EQ(host_impl_->GetRasterColorSpace(), gfx::ColorSpace::CreateSRGB());
   host_impl_->active_tree()->SetRasterColorSpace(
       2, gfx::ColorSpace::CreateDisplayP3D65());
-  EXPECT_EQ(host_impl_->GetRasterColorSpace().color_space,
-            gfx::ColorSpace::CreateSRGB());
+  EXPECT_EQ(host_impl_->GetRasterColorSpace(), gfx::ColorSpace::CreateSRGB());
 }
 
 TEST_F(LayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) {
diff --git a/chrome/MAJOR_BRANCH_DATE b/chrome/MAJOR_BRANCH_DATE
index 3d70feb..7ba2073 100644
--- a/chrome/MAJOR_BRANCH_DATE
+++ b/chrome/MAJOR_BRANCH_DATE
@@ -1 +1 @@
-MAJOR_BRANCH_DATE=2019-01-25
+MAJOR_BRANCH_DATE=2019-03-08
diff --git a/chrome/VERSION b/chrome/VERSION
index 0123f0e..da3bc62 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
-MAJOR=74
+MAJOR=75
 MINOR=0
-BUILD=3728
+BUILD=3730
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index db5560ca..08e8ab0 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -136,6 +136,16 @@
   }
 }
 
+if (notouch_build) {
+  android_resources("chrome_touchless_java_resources") {
+    resource_dirs = [ "//chrome/android/touchless/java/res" ]
+    deps = [
+      ":chrome_app_java_resources",
+    ]
+    custom_package = "org.chromium.chrome.touchless"
+  }
+}
+
 android_resources("chrome_download_java_resources") {
   resource_dirs = [ "//chrome/android/java/res_download" ]
   deps = [
@@ -355,6 +365,7 @@
 
   srcjar_deps = [
     ":chrome_android_java_enums_srcjar",
+    ":chrome_android_java_switches_srcjar",
     ":chrome_android_java_google_api_keys_srcjar",
     ":photo_picker_aidl",
     ":resource_id_javagen",
@@ -417,6 +428,9 @@
     ]
   }
   srcjar_deps += [ ":chrome_vr_android_java_enums_srcjar" ]
+  if (notouch_build) {
+    deps += [ ":chrome_touchless_java_resources" ]
+  }
 
   # Add the actual implementation where necessary so that downstream targets
   # can provide their own implementations.
@@ -436,10 +450,6 @@
     "//chrome/android/features/autofill_assistant:java",
     "//chrome/android/features/media_router:java",
   ]
-
-  if (notouch_build) {
-    deps += [ "//chrome/android/features/touchless:java" ]
-  }
 }
 
 android_library("bundle_canary_java") {
@@ -496,6 +506,13 @@
   ]
 }
 
+java_cpp_strings("chrome_android_java_switches_srcjar") {
+  sources = [
+    "//chrome/common/chrome_switches.cc",
+  ]
+  template = "//chrome/android/java_templates/ChromeSwitches.java.tmpl"
+}
+
 proto_java_library("document_tab_model_info_proto_java") {
   proto_path = "java/src/org/chromium/chrome/browser/tabmodel/document"
   sources = [
@@ -718,6 +735,7 @@
     "//components/web_restrictions:provider_java",
     "//content/public/android:content_java",
     "//content/public/test/android:content_java_test_support",
+    "//media/base/android:java_switches",
     "//media/base/android:media_java",
     "//mojo/public/java:bindings_java",
     "//mojo/public/java:system_java",
@@ -1695,6 +1713,13 @@
   }
 }
 
+# TODO(estevenson): Remove this once we switch to using bundle targets to
+# generate APK stubs.
+android_resources("trichrome_dummy_resources") {
+  custom_package = "org.chromium.trichromelibrary"
+  resource_dirs = [ "trichrome/res_dummy" ]
+}
+
 monochrome_public_apk_or_module_tmpl("trichrome_chrome_apk") {
   apk_name = "TrichromeChrome"
   target_type = "android_apk"
@@ -1814,7 +1839,6 @@
     ":chrome_test_java",
     "//chrome/android/features/autofill_assistant:test_java",
     "//chrome/android/features/media_router:test_java",
-    "//chrome/android/features/touchless:touchless_java_test",
     "//chrome/android/webapk/libs/runtime_library:runtime_library_javatests",
     "//chrome/android/webapk/shell_apk:shell_apk_javatests",
     "//chrome/browser/profiling_host:profiling_host_javatests",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 7d9af53..f2833952 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -30,6 +30,7 @@
   "channel=$android_channel",
   "enable_vr=$enable_vr",
   "include_arcore_manifest_flag=$package_arcore",
+  "notouch_build=$notouch_build",
 ]
 
 # A template used to declare any target that will implement a full Chromium
diff --git a/chrome/android/features/autofill_assistant/BUILD.gn b/chrome/android/features/autofill_assistant/BUILD.gn
index fc7d093..9b73788 100644
--- a/chrome/android/features/autofill_assistant/BUILD.gn
+++ b/chrome/android/features/autofill_assistant/BUILD.gn
@@ -54,7 +54,6 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestOptions.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequestSection.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java",
   ]
 }
diff --git a/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request.xml
index 5fcba66..6eb21d64 100644
--- a/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request.xml
+++ b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request.xml
@@ -67,7 +67,4 @@
         android:text="@string/autofill_assistant_3rd_party_privacy_notice"
         android:textAppearance="@style/TextAppearance.AssistantBlackCaption"
         android:background="@drawable/autofill_assistant_lightblue_rect_bg"/>
-
-    <include layout="@layout/autofill_assistant_payment_request_bottom_bar" />
-
 </org.chromium.chrome.browser.widget.BoundedLinearLayout>
diff --git a/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request_bottom_bar.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request_bottom_bar.xml
deleted file mode 100644
index 027daa0..0000000
--- a/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request_bottom_bar.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-
-<!-- Autofill Assistant specific bottom of the payment request UI. -->
-<org.chromium.chrome.browser.autofill_assistant.payment.PaymentRequestBottomBar
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/bottom_bar"
-    android:layout_height="wrap_content"
-    android:layout_width="match_parent"
-    android:paddingTop="@dimen/editor_dialog_section_large_spacing"
-    android:background="@color/modern_primary_color"
-    android:gravity="end"
-    android:orientation="horizontal"
-    android:visibility="gone" >
-
-    <org.chromium.ui.widget.ButtonCompat
-        android:id="@+id/button_secondary"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/autofill_assistant_bottombar_horizontal_spacing"
-        android:minHeight="36dp"
-        android:text="@string/cancel"
-        style="@style/TextButton" />
-
-    <org.chromium.ui.widget.ButtonCompat
-        android:id="@+id/button_primary"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minHeight="36dp"
-        android:text="@string/next"
-        style="@style/FilledButton.Flat" />
-</org.chromium.chrome.browser.autofill_assistant.payment.PaymentRequestBottomBar>
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
index 9ee3ffb..fc864cf 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantCarouselModel;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
+import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip.Type;
 import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
@@ -24,7 +25,6 @@
 import org.chromium.content_public.browser.WebContents;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -201,22 +201,17 @@
         for (int i = 0; i < texts.length; i++) {
             final int suggestionIndex = i;
             chips.add(new AssistantChip(AssistantChip.Type.CHIP_ASSISTIVE, texts[i],
-                    () -> safeNativeOnSuggestionSelected(suggestionIndex)));
+                    /* disabled= */ false, () -> safeNativeOnSuggestionSelected(suggestionIndex)));
         }
         AssistantCarouselModel model = getModel().getSuggestionsModel();
         model.set(ALIGNMENT, AssistantCarouselModel.Alignment.START);
         setChips(model, chips);
     }
 
-    @CalledByNative
-    private void clearActions() {
-        getModel().getActionsModel().getChipsModel().set(Collections.emptyList());
-    }
-
     /** Creates an empty list of chips. */
     @CalledByNative
     private static List<AssistantChip> createChipList() {
-        return new ArrayList<AssistantChip>();
+        return new ArrayList<>();
     }
 
     /**
@@ -224,7 +219,7 @@
      */
     @CalledByNative
     private void addActionButton(List<AssistantChip> chips, String text, int actionIndex) {
-        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, text,
+        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, text, /* disabled= */ false,
                 () -> safeNativeOnActionSelected(actionIndex)));
     }
 
@@ -234,8 +229,8 @@
      */
     @CalledByNative
     private void addHighlightedActionButton(
-            List<AssistantChip> chips, String text, int actionIndex) {
-        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_FILLED_BLUE, text,
+            List<AssistantChip> chips, String text, int actionIndex, boolean disabled) {
+        chips.add(new AssistantChip(Type.BUTTON_FILLED_BLUE, text, disabled,
                 () -> safeNativeOnActionSelected(actionIndex)));
     }
 
@@ -245,7 +240,7 @@
      */
     @CalledByNative
     private void addCancelButton(List<AssistantChip> chips, String text, int actionIndex) {
-        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, text,
+        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, text, /* disabled= */ false,
                 () -> safeNativeOnCancelButtonClicked(actionIndex)));
     }
 
@@ -254,8 +249,8 @@
      */
     @CalledByNative
     private void addCloseButton(List<AssistantChip> chips, String text) {
-        chips.add(new AssistantChip(
-                AssistantChip.Type.BUTTON_HAIRLINE, text, this::safeNativeOnCloseButtonClicked));
+        chips.add(new AssistantChip(AssistantChip.Type.BUTTON_HAIRLINE, text, /* disabled= */ false,
+                this::safeNativeOnCloseButtonClicked));
     }
 
     @CalledByNative
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java
index 821a5c42..6a792ed 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java
@@ -38,8 +38,8 @@
         mView.setLayoutManager(mLayoutManager);
         mView.addItemDecoration(new SpaceItemDecoration(context));
         mView.setAdapter(new RecyclerViewAdapter<>(
-                new SimpleRecyclerViewMcp<>(model.getChipsModel(), AssistantChip::getType,
-                        AssistantChipViewHolder::bind),
+                new SimpleRecyclerViewMcp<>(model.getChipsModel(),
+                        AssistantChipViewHolder::getViewType, AssistantChipViewHolder::bind),
                 AssistantChipViewHolder::create));
 
         // Carousel is initially hidden.
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java
index e6b9ed9..f246287f 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java
@@ -13,24 +13,27 @@
  * A chip to display to the user.
  */
 public class AssistantChip {
-    @IntDef({Type.CHIP_ASSISTIVE, Type.BUTTON_FILLED_BLUE, Type.BUTTON_HAIRLINE,
-            Type.BUTTON_FILLED_DISABLED})
+    @IntDef({Type.CHIP_ASSISTIVE, Type.BUTTON_FILLED_BLUE, Type.BUTTON_HAIRLINE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {
         int CHIP_ASSISTIVE = 0;
         int BUTTON_FILLED_BLUE = 1;
         int BUTTON_HAIRLINE = 2;
-        int BUTTON_FILLED_DISABLED = 3;
+
+        /** The number of types. Increment this value if you add a type. */
+        int CHIP_TYPE_NUMBER = 3;
     }
 
     private final @Type int mType;
     private final String mText;
+    private final boolean mDisabled;
     private final Runnable mSelectedListener;
 
-    public AssistantChip(@Type int type, String text, Runnable selectedListener) {
+    public AssistantChip(@Type int type, String text, boolean disabled, Runnable selectedListener) {
         mType = type;
         mText = text;
         mSelectedListener = selectedListener;
+        mDisabled = disabled;
     }
 
     public int getType() {
@@ -41,6 +44,10 @@
         return mText;
     }
 
+    public boolean isDisabled() {
+        return mDisabled;
+    }
+
     public Runnable getSelectedListener() {
         return mSelectedListener;
     }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java
index f83d313..5f7a682 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java
@@ -26,12 +26,11 @@
     static AssistantChipViewHolder create(ViewGroup parent, int viewType) {
         LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
         int resId = -1;
-        switch (viewType) {
+        switch (viewType % AssistantChip.Type.CHIP_TYPE_NUMBER) {
             // TODO: inflate normal chrome buttons instead.
             case AssistantChip.Type.CHIP_ASSISTIVE:
                 resId = R.layout.autofill_assistant_chip_assistive;
                 break;
-            case AssistantChip.Type.BUTTON_FILLED_DISABLED:
             case AssistantChip.Type.BUTTON_FILLED_BLUE:
                 resId = R.layout.autofill_assistant_button_filled;
                 break;
@@ -43,13 +42,24 @@
         }
 
         TextView view = (TextView) layoutInflater.inflate(resId, /* root= */ null);
-        if (viewType == AssistantChip.Type.BUTTON_FILLED_DISABLED) {
+        if (viewType >= AssistantChip.Type.CHIP_TYPE_NUMBER) {
             view.setEnabled(false);
         }
 
         return new AssistantChipViewHolder(view);
     }
 
+    static int getViewType(AssistantChip chip) {
+        // We add AssistantChip.Type.CHIP_TYPE_NUMBER to differentiate between enabled and disabled
+        // chips of the same type. Ideally, we should return a (type, disabled) tuple but
+        // RecyclerView does not allow that.
+        if (chip.isDisabled()) {
+            return chip.getType() + AssistantChip.Type.CHIP_TYPE_NUMBER;
+        }
+
+        return chip.getType();
+    }
+
     public void bind(AssistantChip chip) {
         mText.setText(chip.getText());
         mText.setOnClickListener(ignoredView -> chip.getSelectedListener().run());
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java
index fe3dd0b..10e64d8 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java
@@ -9,6 +9,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
+import org.chromium.chrome.browser.payments.AutofillContact;
 
 /** Delegate for the Payment Request UI. */
 @JNINamespace("autofill_assistant")
@@ -25,20 +26,37 @@
         mNativeAssistantOverlayDelegate = nativeAssistantPaymentRequestDelegate;
     }
 
-    public void onPaymentInformationSelected(
-            AutofillAssistantPaymentRequest.SelectedPaymentInformation selectedInformation) {
+    public void onContactInfoChanged(AutofillContact contact) {
         if (mNativeAssistantOverlayDelegate != 0) {
-            nativeOnGetPaymentInformation(mNativeAssistantOverlayDelegate,
-                    selectedInformation.succeed, selectedInformation.card,
-                    selectedInformation.address, selectedInformation.payerName,
-                    selectedInformation.payerPhone, selectedInformation.payerEmail,
-                    selectedInformation.isTermsAndConditionsAccepted);
+            String name = null;
+            String phone = null;
+            String email = null;
+
+            if (contact != null) {
+                name = contact.getPayerName();
+                phone = contact.getPayerPhone();
+                email = contact.getPayerEmail();
+            }
+
+            nativeOnContactInfoChanged(mNativeAssistantOverlayDelegate, name, phone, email);
         }
     }
 
-    public void onCancelButtonClicked() {
+    public void onShippingAddressChanged(PersonalDataManager.AutofillProfile address) {
         if (mNativeAssistantOverlayDelegate != 0) {
-            nativeOnCancelButtonClicked(mNativeAssistantOverlayDelegate);
+            nativeOnShippingAddressChanged(mNativeAssistantOverlayDelegate, address);
+        }
+    }
+
+    public void onCreditCardChanged(PersonalDataManager.CreditCard card) {
+        if (mNativeAssistantOverlayDelegate != 0) {
+            nativeOnCreditCardChanged(mNativeAssistantOverlayDelegate, card);
+        }
+    }
+
+    public void onTermsAndConditionsChanged(@AssistantTermsAndConditionsState int state) {
+        if (mNativeAssistantOverlayDelegate != 0) {
+            nativeOnTermsAndConditionsChanged(mNativeAssistantOverlayDelegate, state);
         }
     }
 
@@ -47,11 +65,12 @@
         mNativeAssistantOverlayDelegate = 0;
     }
 
-    private native void nativeOnGetPaymentInformation(long nativeAssistantPaymentRequestDelegate,
-            boolean succeed, @Nullable PersonalDataManager.CreditCard card,
-            @Nullable PersonalDataManager.AutofillProfile address, @Nullable String payerName,
-            @Nullable String payerPhone, @Nullable String payerEmail,
-            boolean isTermsAndConditionsAccepted);
-
-    private native void nativeOnCancelButtonClicked(long nativeAssistantPaymentRequestDelegate);
+    private native void nativeOnContactInfoChanged(long nativeAssistantPaymentRequestDelegate,
+            @Nullable String payerName, @Nullable String payerPhone, @Nullable String payerEmail);
+    private native void nativeOnShippingAddressChanged(long nativeAssistantPaymentRequestDelegate,
+            @Nullable PersonalDataManager.AutofillProfile address);
+    private native void nativeOnCreditCardChanged(long nativeAssistantPaymentRequestDelegate,
+            @Nullable PersonalDataManager.CreditCard card);
+    private native void nativeOnTermsAndConditionsChanged(
+            long nativeAssistantPaymentRequestDelegate, int state);
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
index 1c2b416..d3a52d7b 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
@@ -12,7 +12,6 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
-import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
@@ -213,13 +212,27 @@
                         mWebContents.getLastCommittedUrl()),
                 SecurityStateModel.getSecurityLevelForWebContents(mWebContents),
                 new ShippingStrings(PaymentShippingType.SHIPPING));
-        // This payment request is embedded in another flow, so update the 'Pay' button text to
-        // 'Confirm'.
-        mUI.updatePayButtonText(R.string.autofill_assistant_payment_info_confirm);
 
         mAddressEditor.setEditorDialog(mUI.getEditorDialog());
         mCardEditor.setEditorDialog(mUI.getCardEditorDialog());
         if (mContactEditor != null) mContactEditor.setEditorDialog(mUI.getEditorDialog());
+
+        // Notify initial selections.
+        if (mContactSection != null && mContactSection.getSelectedItem() != null) {
+            onSectionOptionSelected(
+                    PaymentRequestUI.DataType.CONTACT_DETAILS, mContactSection.getSelectedItem());
+        }
+
+        if (mShippingAddressesSection != null
+                && mShippingAddressesSection.getSelectedItem() != null) {
+            onSectionOptionSelected(PaymentRequestUI.DataType.SHIPPING_ADDRESSES,
+                    mShippingAddressesSection.getSelectedItem());
+        }
+
+        if (mPaymentMethodsSection != null && mPaymentMethodsSection.getSelectedItem() != null) {
+            onSectionOptionSelected(PaymentRequestUI.DataType.PAYMENT_METHODS,
+                    mPaymentMethodsSection.getSelectedItem());
+        }
     }
 
     private void createShippingSection(
@@ -295,12 +308,13 @@
     }
 
     @PaymentRequestUI.SelectionResult
-    public int onSectionOptionSelected(@PaymentRequestUI.DataType int optionType,
-            EditableOption option, Callback<PaymentInformation> checkedCallback) {
+    public int onSectionOptionSelected(
+            @PaymentRequestUI.DataType int optionType, EditableOption option) {
         if (optionType == PaymentRequestUI.DataType.SHIPPING_ADDRESSES) {
             AutofillAddress address = (AutofillAddress) option;
             if (address.isComplete()) {
                 mShippingAddressesSection.setSelectedItem(option);
+                mDelegate.onShippingAddressChanged(address.getProfile());
             } else {
                 editAddress(address);
                 return PaymentRequestUI.SelectionResult.EDITOR_LAUNCH;
@@ -309,6 +323,7 @@
             AutofillContact contact = (AutofillContact) option;
             if (contact.isComplete()) {
                 mContactSection.setSelectedItem(option);
+                mDelegate.onContactInfoChanged(contact);
             } else {
                 editContact(contact);
                 return PaymentRequestUI.SelectionResult.EDITOR_LAUNCH;
@@ -317,6 +332,7 @@
             AutofillPaymentInstrument card = (AutofillPaymentInstrument) option;
             if (card.isComplete()) {
                 mPaymentMethodsSection.setSelectedItem(option);
+                mDelegate.onCreditCardChanged(card.getCard());
             } else {
                 editCard(card);
                 return PaymentRequestUI.SelectionResult.EDITOR_LAUNCH;
@@ -485,48 +501,11 @@
         return networksByString;
     }
 
-    public boolean onPayClicked(EditableOption selectedShippingAddress,
-            EditableOption selectedShippingOption, EditableOption selectedPaymentMethod,
-            boolean isTermsAndConditionsAccepted) {
-        SelectedPaymentInformation selectedPaymentInformation = new SelectedPaymentInformation();
-
-        selectedPaymentInformation.isTermsAndConditionsAccepted = isTermsAndConditionsAccepted;
-        selectedPaymentInformation.card =
-                ((AutofillPaymentInstrument) selectedPaymentMethod).getCard();
-        if (mPaymentOptions.mRequestShipping && selectedShippingAddress != null) {
-            selectedPaymentInformation.address =
-                    ((AutofillAddress) selectedShippingAddress).getProfile();
-        }
-        if (mPaymentOptions.mRequestPayerName || mPaymentOptions.mRequestPayerPhone
-                || mPaymentOptions.mRequestPayerEmail) {
-            EditableOption selectedContact =
-                    mContactSection != null ? mContactSection.getSelectedItem() : null;
-            if (selectedContact != null) {
-                selectedPaymentInformation.payerName =
-                        ((AutofillContact) selectedContact).getPayerName();
-                selectedPaymentInformation.payerPhone =
-                        ((AutofillContact) selectedContact).getPayerPhone();
-                selectedPaymentInformation.payerEmail =
-                        ((AutofillContact) selectedContact).getPayerEmail();
-            }
-        }
-        selectedPaymentInformation.succeed = true;
-        mDelegate.onPaymentInformationSelected(selectedPaymentInformation);
-        return false;
-    }
-
-    public void onDismiss() {
-        SelectedPaymentInformation selectedPaymentInformation = new SelectedPaymentInformation();
-        selectedPaymentInformation.succeed = false;
-        mDelegate.onPaymentInformationSelected(selectedPaymentInformation);
-        close();
-    }
-
-    public void onCancelButtonClicked() {
-        mDelegate.onCancelButtonClicked();
-    }
-
     public void onCardAndAddressSettingsClicked() {
         // TODO(crbug.com/806868): Allow user to control cards and addresses.
     }
+
+    public void onTermsAndConditionsChanged(@AssistantTermsAndConditionsState int state) {
+        mDelegate.onTermsAndConditionsChanged(state);
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java
deleted file mode 100644
index a4a0ac4..0000000
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java
+++ /dev/null
@@ -1,31 +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.
-
-package org.chromium.chrome.browser.autofill_assistant.payment;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import org.chromium.chrome.autofill_assistant.R;
-
-/** Autofill Assistant specific bottom bar for the payment request UI. */
-public class PaymentRequestBottomBar extends LinearLayout {
-    private View mPrimaryButton;
-    private View mSecondaryButton;
-
-    /** Constructor for when the PaymentRequestBottomBar is inflated from XML. */
-    public PaymentRequestBottomBar(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mPrimaryButton = findViewById(R.id.button_primary);
-        mSecondaryButton = findViewById(R.id.button_secondary);
-    }
-}
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java
index 72be9090..b4f5221 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java
@@ -13,7 +13,6 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.Activity;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.support.annotation.IntDef;
 import android.support.v4.view.animation.LinearOutSlowInInterpolator;
 import android.text.TextUtils;
@@ -24,7 +23,6 @@
 import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.RadioButton;
@@ -58,8 +56,8 @@
  *
  * Note: This is an Autofill Assistant specific fork of payments/ui/PaymentRequestUI.java.
  */
-public class PaymentRequestUI implements DialogInterface.OnDismissListener, View.OnClickListener,
-                                         PaymentRequestSection.SectionDelegate {
+public class PaymentRequestUI
+        implements View.OnClickListener, PaymentRequestSection.SectionDelegate {
     @IntDef({DataType.SHIPPING_ADDRESSES, DataType.SHIPPING_OPTIONS, DataType.CONTACT_DETAILS,
             DataType.PAYMENT_METHODS})
     @Retention(RetentionPolicy.SOURCE)
@@ -106,8 +104,6 @@
 
     private FadingEdgeScrollView mPaymentContainer;
     private LinearLayout mPaymentContainerLayout;
-    private ViewGroup mBottomBar;
-    private Button mPayButton;
     private View mSpinnyLayout;
     // View used to store a view to be replaced with the current payment request UI.
     private View mBackupView;
@@ -201,7 +197,6 @@
                 } else {
                     expand(null);
                 }
-                updatePayButtonEnabled();
             }
         };
 
@@ -270,11 +265,10 @@
                         result.getPaymentMethods()
                                 .getDisplaySelectedItemSummaryInSingleLineInNormalMode());
                 updateSection(DataType.PAYMENT_METHODS, result.getPaymentMethods());
-                updatePayButtonEnabled();
 
                 // Hide the loading indicators and show the real sections.
                 changeSpinnerVisibility(false);
-                mRequestView.addOnLayoutChangeListener(new SheetEnlargingAnimator(false));
+                mRequestView.addOnLayoutChangeListener(new SheetEnlargingAnimator());
             }
         });
     }
@@ -301,13 +295,6 @@
         TextView messageView = (TextView) mRequestView.findViewById(R.id.message);
         messageView.setText(R.string.payments_loading_message);
 
-        // Set up the buttons.
-        mBottomBar = (ViewGroup) mRequestView.findViewById(R.id.bottom_bar);
-        mPayButton = (Button) mBottomBar.findViewById(R.id.button_primary);
-        mPayButton.setOnClickListener(this);
-        mBottomBar.findViewById(R.id.button_secondary)
-                .setOnClickListener((unusedView) -> mClient.onCancelButtonClicked());
-
         // Set terms & conditions text.
         mAcceptThirdPartyConditions = mRequestView.findViewById(R.id.terms_checkbox_agree);
         mReviewThirdPartyConditions = mRequestView.findViewById(R.id.terms_checkbox_review);
@@ -393,9 +380,6 @@
 
         mRequestView.addOnLayoutChangeListener(new PeekingAnimator());
 
-        // Enabled in updatePayButtonEnabled() when the user has selected all payment options.
-        mPayButton.setEnabled(false);
-
         // Force the initial appearance of edit chevrons next to all sections.
         updateSectionVisibility();
     }
@@ -424,15 +408,6 @@
     }
 
     /**
-     * Update default text on the pay button to the given text.
-     *
-     * @param textResId The resource id of the text to be shown on the button.
-     */
-    public void updatePayButtonText(int textResId) {
-        mPayButton.setText(textResId);
-    }
-
-    /**
      * Updates the line items in response to a changed shipping address or option.
      *
      * @param cart The shopping cart, including the line items and the total.
@@ -470,7 +445,6 @@
         boolean isFinishingEditItem = mIsEditingPaymentItem;
         mIsEditingPaymentItem = false;
         updateSectionButtons();
-        updatePayButtonEnabled();
     }
 
     // Only show shipping option section once there are shipping options.
@@ -500,19 +474,17 @@
         if (section == mShippingAddressSection
                 && mShippingAddressSectionInformation.getSelectedItem() != option) {
             mShippingAddressSectionInformation.setSelectedItem(option);
-            result = mClient.onSectionOptionSelected(
-                    DataType.SHIPPING_ADDRESSES, option, mUpdateSectionsCallback);
+            result = mClient.onSectionOptionSelected(DataType.SHIPPING_ADDRESSES, option);
         } else if (section == mShippingOptionSection
                 && mShippingOptionsSectionInformation.getSelectedItem() != option) {
             mShippingOptionsSectionInformation.setSelectedItem(option);
-            result = mClient.onSectionOptionSelected(
-                    DataType.SHIPPING_OPTIONS, option, mUpdateSectionsCallback);
+            result = mClient.onSectionOptionSelected(DataType.SHIPPING_OPTIONS, option);
         } else if (section == mContactDetailsSection) {
             mContactDetailsSectionInformation.setSelectedItem(option);
-            result = mClient.onSectionOptionSelected(DataType.CONTACT_DETAILS, option, null);
+            result = mClient.onSectionOptionSelected(DataType.CONTACT_DETAILS, option);
         } else if (section == mPaymentMethodSection) {
             mPaymentMethodSectionInformation.setSelectedItem(option);
-            result = mClient.onSectionOptionSelected(DataType.PAYMENT_METHODS, option, null);
+            result = mClient.onSectionOptionSelected(DataType.PAYMENT_METHODS, option);
         }
 
         updateStateFromResult(section, result);
@@ -573,8 +545,6 @@
         } else {
             expand(null);
         }
-
-        updatePayButtonEnabled();
     }
 
     @Override
@@ -620,39 +590,19 @@
             expand(mContactDetailsSection);
         } else if (v == mPaymentMethodSection) {
             expand(mPaymentMethodSection);
-        } else if (v == mPayButton) {
-            processPayButton();
-        }
-
-        updatePayButtonEnabled();
-    }
-
-    private void processPayButton() {
-        assert !mIsShowingSpinner;
-        mIsProcessingPayClicked = true;
-
-        boolean shouldShowSpinner = mClient.onPayClicked(mShippingAddressSectionInformation == null
-                        ? null
-                        : mShippingAddressSectionInformation.getSelectedItem(),
-                mShippingOptionsSectionInformation == null
-                        ? null
-                        : mShippingOptionsSectionInformation.getSelectedItem(),
-                mPaymentMethodSectionInformation.getSelectedItem(),
-                mAcceptThirdPartyConditions.isChecked());
-
-        if (shouldShowSpinner) {
-            changeSpinnerVisibility(true);
+        } else if (v == mAcceptThirdPartyConditions || v == mReviewThirdPartyConditions) {
+            mClient.onTermsAndConditionsChanged(getTermsAndConditionState());
         }
     }
 
-    /**
-     * Called when user cancelled out of the UI that was shown after they clicked [PAY] button.
-     */
-    public void onPayButtonProcessingCancelled() {
-        assert mIsProcessingPayClicked;
-        mIsProcessingPayClicked = false;
-        changeSpinnerVisibility(false);
-        updatePayButtonEnabled();
+    private int getTermsAndConditionState() {
+        if (mAcceptThirdPartyConditions.isChecked()) {
+            return AssistantTermsAndConditionsState.ACCEPTED;
+        } else if (mReviewThirdPartyConditions.isChecked()) {
+            return AssistantTermsAndConditionsState.REQUIRES_REVIEW;
+        } else {
+            return AssistantTermsAndConditionsState.NOT_SELECTED;
+        }
     }
 
     /**
@@ -681,7 +631,6 @@
 
         if (showSpinner) {
             mPaymentContainer.setVisibility(View.GONE);
-            mBottomBar.setVisibility(View.GONE);
             mSpinnyLayout.setVisibility(View.VISIBLE);
 
             // Turn the bottom sheet back into a collapsed bottom sheet showing only the spinner.
@@ -691,7 +640,6 @@
             mRequestView.requestLayout();
         } else {
             mPaymentContainer.setVisibility(View.VISIBLE);
-            mBottomBar.setVisibility(View.VISIBLE);
             mSpinnyLayout.setVisibility(View.GONE);
 
             if (mIsExpandedToFullHeight) {
@@ -702,25 +650,6 @@
         }
     }
 
-    private void updatePayButtonEnabled() {
-        boolean contactInfoOk = !mRequestContactDetails
-                || (mContactDetailsSectionInformation != null
-                        && mContactDetailsSectionInformation.getSelectedItem() != null);
-        boolean shippingInfoOk = !mRequestShipping
-                || (mShippingAddressSectionInformation != null
-                        && mShippingAddressSectionInformation.getSelectedItem() != null);
-        boolean shippingOptionInfoOk = !mRequestShippingOption
-                || (mShippingOptionsSectionInformation != null
-                        && mShippingOptionsSectionInformation.getSelectedItem() != null);
-        boolean termsOptionOk =
-                mAcceptThirdPartyConditions.isChecked() || mReviewThirdPartyConditions.isChecked();
-        mPayButton.setEnabled(contactInfoOk && shippingInfoOk && shippingOptionInfoOk
-                && mPaymentMethodSectionInformation != null
-                && mPaymentMethodSectionInformation.getSelectedItem() != null
-                && !mIsClientCheckingSelection && !mIsEditingPaymentItem && !mIsClosing
-                && termsOptionOk);
-    }
-
     /** @return Whether or not the dialog can be closed via the X close button. */
     private boolean isAcceptingCloseButton() {
         return mSheetAnimator == null && mSectionAnimator == null && !mIsProcessingPayClicked
@@ -748,7 +677,7 @@
         if (!mIsExpandedToFullHeight && section != null) {
             // Container now takes the full height of the screen, animating towards it.
             mRequestView.getLayoutParams().height = LayoutParams.MATCH_PARENT;
-            mRequestView.addOnLayoutChangeListener(new SheetEnlargingAnimator(true));
+            mRequestView.addOnLayoutChangeListener(new SheetEnlargingAnimator());
             mPaymentContainerLayout.requestLayout();
 
             // Disable all but the first button.
@@ -823,32 +752,6 @@
         }
     }
 
-    /**
-     * Called when the dialog is dismissed. Can be caused by:
-     * <ul>
-     *  <li>User click on the "back" button on the phone.</li>
-     *  <li>User click on the "X" button in the top-right corner of the dialog.</li>
-     *  <li>User click on the "CANCEL" button on the bottom of the dialog.</li>
-     *  <li>Successfully processing the payment.</li>
-     *  <li>Failure to process the payment.</li>
-     *  <li>The JavaScript calling the abort() method in PaymentRequest API.</li>
-     *  <li>The PaymentRequest JavaScript object being destroyed.</li>
-     *  <li>User closing all incognito windows with PaymentRequest UI open in an incognito
-     *      window.</li>
-     * </ul>
-     */
-    @Override
-    public void onDismiss(DialogInterface dialog) {
-        dismiss();
-    }
-
-    private void dismiss() {
-        mIsClosing = true;
-        if (mEditorDialog.isShowing()) mEditorDialog.dismiss();
-        if (mCardEditorDialog.isShowing()) mCardEditorDialog.dismiss();
-        mClient.onDismiss();
-    }
-
     @Override
     public String getAdditionalText(PaymentRequestSection section) {
         if (section == mShippingAddressSection) {
@@ -930,12 +833,9 @@
     /** Animates the bottom sheet expanding to a larger sheet. */
     private class SheetEnlargingAnimator
             extends AnimatorListenerAdapter implements OnLayoutChangeListener {
-        private final boolean mIsBottomBarLockedInPlace;
         private int mContainerHeightDifference;
 
-        public SheetEnlargingAnimator(boolean isBottomBarLockedInPlace) {
-            mIsBottomBarLockedInPlace = isBottomBarLockedInPlace;
-        }
+        public SheetEnlargingAnimator() {}
 
         /**
          * Updates the animation.
@@ -947,20 +847,6 @@
             // the translation until it is in the right place on screen.
             float containerTranslation = mContainerHeightDifference * progress;
             mRequestView.setTranslationY(containerTranslation);
-
-            if (mIsBottomBarLockedInPlace) {
-                // The bottom bar is translated along the dialog so that is looks like it stays in
-                // place at the bottom while the entire bottom sheet is translating upwards.
-                mBottomBar.setTranslationY(-containerTranslation);
-
-                // The payment container is sandwiched between the header and the bottom bar.
-                // Expansion animates by changing where its "bottom" is, letting its shadows appear
-                // and disappear as it changes size.
-                int paymentContainerBottom =
-                        Math.min(mPaymentContainer.getTop() + mPaymentContainer.getMeasuredHeight(),
-                                mBottomBar.getTop());
-                mPaymentContainer.setBottom(paymentContainerBottom);
-            }
         }
 
         @Override
@@ -991,7 +877,6 @@
         public void onAnimationEnd(Animator animation) {
             // Reset the layout so that everything is in the expected place.
             mRequestView.setTranslationY(0);
-            mBottomBar.setTranslationY(0);
             mRequestView.requestLayout();
 
             // Indicate that the dialog is ready to use.
diff --git a/chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd b/chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd
index 4e19a82c..72fd9b0 100644
--- a/chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd
+++ b/chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd
@@ -133,9 +133,6 @@
       <message name="IDS_AUTOFILL_ASSISTANT_FIRST_RUN_ACCESSIBILITY" desc="Accessibility description of Autofill Assistant first run screen is shown.">
         Google Assistant in Chrome first run screen is shown
       </message>
-      <message name="IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM" desc="Text on the payment request primary button to confirm payment information [CHAR-LIMIT=32]">
-        Continue
-      </message>
       <message name="IDS_AUTOFILL_ASSISTANT_GIVE_UP"
                desc="Text label that is shown when autofill assistant cannot help anymore, because of a user action."
                internal_comment="TODO(wnwen): Remove duplication in components/autofill_assistant_strings.grdp">
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
index 11df2137..5a124d4 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
@@ -180,10 +180,11 @@
 
     private void testChips(InOrder inOrder, AssistantCarouselModel carouselModel,
             AssistantCarouselCoordinator carouselCoordinator) {
-        List<AssistantChip> chips = Arrays.asList(
-                new AssistantChip(
-                        AssistantChip.Type.CHIP_ASSISTIVE, "chip 0", () -> {/* do nothing */}),
-                new AssistantChip(AssistantChip.Type.CHIP_ASSISTIVE, "chip 1", mRunnableMock));
+        List<AssistantChip> chips =
+                Arrays.asList(new AssistantChip(AssistantChip.Type.CHIP_ASSISTIVE, "chip 0",
+                                      /* disabled= */ false, () -> {/* do nothing */}),
+                        new AssistantChip(AssistantChip.Type.CHIP_ASSISTIVE, "chip 1",
+                                /* disabled= */ false, mRunnableMock));
         ThreadUtils.runOnUiThreadBlocking(() -> carouselModel.getChipsModel().set(chips));
         RecyclerView chipsViewContainer = carouselCoordinator.getView();
         Assert.assertEquals(2, chipsViewContainer.getAdapter().getItemCount());
diff --git a/chrome/android/features/touchless/BUILD.gn b/chrome/android/features/touchless/BUILD.gn
deleted file mode 100644
index b7a485f..0000000
--- a/chrome/android/features/touchless/BUILD.gn
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/config.gni")
-import("//build/config/android/rules.gni")
-
-android_library("java") {
-  java_files =
-      [ "java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java" ]
-
-  deps = [
-    "//base:base_java",
-    "//chrome/android:chrome_java",
-    "//content/public/android:content_java",
-    "//ui/android:ui_java",
-  ]
-}
-
-android_library("touchless_java_test") {
-  testonly = true
-
-  java_files = [ "javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java" ]
-
-  deps = [
-    ":java",
-    "//base:base_java",
-    "//base:base_java_test_support",
-    "//chrome/android:browser_java_test_support",
-    "//chrome/android:chrome_java",
-    "//chrome/android:chrome_test_java",
-    "//chrome/android:chrome_test_util_java",
-    "//chrome/test/android:chrome_java_test_support",
-    "//components/safe_browsing/android:safe_browsing_java",
-    "//content/public/android:content_java",
-    "//content/public/test/android:content_java_test_support",
-    "//net/android:net_java_test_support",
-    "//third_party/android_support_test_runner:runner_java",
-    "//third_party/junit:junit",
-  ]
-}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
index 6f1e7da..359094a 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
@@ -228,32 +228,38 @@
 
     @Override
     public void onInternalError(@InternalFeedError int internalError) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnInternalError(mNativeFeedLoggingBridge, internalError);
     }
 
     @Override
     public void onTokenCompleted(boolean wasSynthetic, int contentCount, int tokenCount) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnTokenCompleted(mNativeFeedLoggingBridge, wasSynthetic, contentCount, tokenCount);
     }
 
     @Override
     public void onTokenFailedToComplete(boolean wasSynthetic, int failureCount) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnTokenFailedToComplete(mNativeFeedLoggingBridge, wasSynthetic, failureCount);
     }
 
     @Override
     public void onServerRequest(@RequestReason int requestReason) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnServerRequest(mNativeFeedLoggingBridge, requestReason);
     }
 
     @Override
     public void onZeroStateShown(@ZeroStateShowReason int zeroStateShowReason) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnZeroStateShown(mNativeFeedLoggingBridge, zeroStateShowReason);
     }
 
     @Override
     public void onZeroStateRefreshCompleted(int newContentCount, int newTokenCount) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnZeroStateRefreshCompleted(mNativeFeedLoggingBridge, newContentCount, newTokenCount);
     }
 
     @Override
@@ -377,6 +383,16 @@
             long nativeFeedLoggingBridge, long spinnerShownTimeMs, int spinnerType);
     private native void nativeOnPietFrameRenderingEvent(
             long nativeFeedLoggingBridge, int[] pietErrorCodes);
+    private native void nativeOnInternalError(long nativeFeedLoggingBridge, int internalError);
+    private native void nativeOnTokenCompleted(
+            long nativeFeedLoggingBridge, boolean wasSynthetic, int contentCount, int tokenCount);
+    private native void nativeOnTokenFailedToComplete(
+            long nativeFeedLoggingBridge, boolean wasSynthetic, int failureCount);
+    private native void nativeOnServerRequest(long nativeFeedLoggingBridge, int requestReason);
+    private native void nativeOnZeroStateShown(
+            long nativeFeedLoggingBridge, int zeroStateShowReason);
+    private native void nativeOnZeroStateRefreshCompleted(
+            long nativeFeedLoggingBridge, int newContentCount, int newTokenCount);
     private native void nativeOnContentTargetVisited(
             long nativeFeedLoggingBridge, long visitTimeMs, boolean isOffline, boolean returnToNtp);
     private native void nativeReportScrolledAfterOpen(long nativeFeedLoggingBridge);
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index e21cba7a..91dba26 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -545,6 +545,8 @@
             {{ self.chrome_activity_common() }}>
             {{ self.extra_web_rendering_activity_definitions() }}
         </activity>
+        {% set notouch_build = notouch_build|default(0) %}
+        {% if notouch_build == "true" %}
         <activity android:name="org.chromium.chrome.browser.touchless.NoTouchActivity"
             android:theme="@style/Theme.Chromium.Activity"
             android:exported="false"
@@ -553,6 +555,7 @@
             {{ self.supports_video_persistence() }} >
             {{ self.extra_web_rendering_activity_definitions() }}
         </activity>
+        {% endif %}
 
         <activity android:name="org.chromium.chrome.browser.sync.ui.PassphraseActivity"
             android:theme="@style/Theme.Chromium.Activity"
diff --git a/chrome/android/java/README.md b/chrome/android/java/README.md
index 51d6ca0..c14b8eb3d 100644
--- a/chrome/android/java/README.md
+++ b/chrome/android/java/README.md
@@ -63,17 +63,31 @@
 
 ### Fixing build failures
 
-1. Ensure your args.gn contains these args:
-```
-enable_chrome_android_internal = false
-is_java_debug = false
-```
+1. Ensure that:
+
+   * Your args.gn contains these args:
+
+   ```
+   enable_chrome_android_internal = false
+   is_java_debug = false
+   ```
+
+   * Your local branch is up-to-date with master
+
 
 2. Run:
 
-```
-autoninja -C $CHROMIUM_OUTPUT_DIR monochrome_public_apk
-```
+   For AndroidManifest failures:
+
+   ```
+   autoninja -C $CHROMIUM_OUTPUT_DIR monochrome_public_apk__merge_manifests
+   ```
+
+   For Proguard flags failures:
+
+   ```
+   autoninja -C $CHROMIUM_OUTPUT_DIR monochrome_public_apk
+   ```
 
 3. Run the command suggested in the error message to copy the contents of the
    generated file to the expected file path
diff --git a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
index 6aeba47..c522db1c 100644
--- a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
+++ b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
@@ -816,16 +816,6 @@
         android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize|uiMode|density"
         android:exported="false"
         android:hardwareAccelerated="false"
-        android:launchMode="singleTask"
-        android:name="org.chromium.chrome.browser.touchless.NoTouchActivity"
-        android:resizeableActivity="true"
-        android:supportsPictureInPicture="true"
-        android:theme="@style/Theme.Chromium.Activity"
-        android:windowSoftInputMode="adjustResize"/>
-    <activity
-        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize|uiMode|density"
-        android:exported="false"
-        android:hardwareAccelerated="false"
         android:name="org.chromium.chrome.browser.customtabs.CustomTabActivity"
         android:resizeableActivity="true"
         android:supportsPictureInPicture="true"
diff --git a/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml b/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml
index f682754..01d8599 100644
--- a/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml
+++ b/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml
@@ -11,7 +11,7 @@
     <LinearLayout
         android:id="@+id/main_content"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/bottom_sheet_peek_height"
+        android:layout_height="match_parent"
         android:orientation="horizontal"
         android:gravity="center_vertical"
         android:background="@color/modern_primary_color">
diff --git a/chrome/android/java/res/layout/omnibox_answer_suggestion.xml b/chrome/android/java/res/layout/omnibox_answer_suggestion.xml
index ac24dbff..ee2b403b 100644
--- a/chrome/android/java/res/layout/omnibox_answer_suggestion.xml
+++ b/chrome/android/java/res/layout/omnibox_answer_suggestion.xml
@@ -60,7 +60,7 @@
     <ImageView
         android:background="?attr/selectableItemBackground"
         android:clickable="true"
-        android:contentDescription="@null"
+        android:contentDescription="@string/accessibility_omnibox_btn_refine"
         android:focusable="true"
         android:id="@id/omnibox_answer_refine_icon"
         android:layout_alignBottom="@id/omnibox_answer"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 52afb27..0d8ebcc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -1469,21 +1469,11 @@
         // Create after native initialization so subclasses that override this method have a chance
         // to setup.
         mPageViewTimer = createPageViewTimer();
-
-        if (shouldInitializeBottomSheet()) {
-            ViewGroup coordinator = findViewById(R.id.coordinator);
-            getLayoutInflater().inflate(R.layout.bottom_sheet, coordinator);
-            mBottomSheet = coordinator.findViewById(R.id.bottom_sheet);
-            mBottomSheet.init(coordinator, this);
-
-            ((BottomContainer) findViewById(R.id.bottom_container)).setBottomSheet(mBottomSheet);
-
-            mBottomSheetController = new BottomSheetController(this, mActivityTabProvider,
-                    mScrimView, mBottomSheet,
-                    getCompositorViewHolder().getLayoutManager().getOverlayPanelManager(),
+        if (shouldInitializeBottomSheet()
+                && FeatureUtilities.areContextualSuggestionsEnabled(this)) {
+            initializeBottomSheet(
                     !ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_SUGGESTIONS_BUTTON));
-
-            mComponent.resolveContextualSuggestionsCoordinator();
+            getComponent().resolveContextualSuggestionsCoordinator();
         }
     }
 
@@ -1495,6 +1485,24 @@
     }
 
     /**
+     * Initializes the {@link BottomSheet} and {@link BottomSheetController} for use.
+     * @param suppressSheetForContextualSearch Whether the sheet should be suppressed when
+     *                                         Contextual search is showing.
+     */
+    protected void initializeBottomSheet(boolean suppressSheetForContextualSearch) {
+        ViewGroup coordinator = findViewById(R.id.coordinator);
+        getLayoutInflater().inflate(R.layout.bottom_sheet, coordinator);
+        mBottomSheet = coordinator.findViewById(R.id.bottom_sheet);
+        mBottomSheet.init(coordinator, this);
+
+        ((BottomContainer) findViewById(R.id.bottom_container)).setBottomSheet(mBottomSheet);
+
+        mBottomSheetController = new BottomSheetController(this, mActivityTabProvider, mScrimView,
+                mBottomSheet, getCompositorViewHolder().getLayoutManager().getOverlayPanelManager(),
+                suppressSheetForContextualSearch);
+    }
+
+    /**
      * @return Whether native initialization has been completed for this activity.
      */
     public boolean didFinishNativeInitialization() {
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 5f1f5a0a..0b66e5f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -972,6 +972,10 @@
             };
             OnClickListener bookmarkClickHandler = v -> addOrEditBookmark(getActivityTab());
 
+            if (shouldInitializeBottomSheet() && FeatureUtilities.isTabGroupsAndroidEnabled()) {
+                initializeBottomSheet(false);
+            }
+
             getToolbarManager().initializeWithNative(mTabModelSelectorImpl,
                     getFullscreenManager().getBrowserVisibilityDelegate(), getFindToolbarManager(),
                     mOverviewModeController, mLayoutManager, tabSwitcherClickHandler,
@@ -1580,7 +1584,9 @@
     @Override
     protected void initializeToolbar() {
         super.initializeToolbar();
-        if (!isTablet() && FeatureUtilities.isBottomToolbarEnabled()) {
+        if (!isTablet()
+                && (FeatureUtilities.isBottomToolbarEnabled()
+                        || FeatureUtilities.isTabGroupsAndroidEnabled())) {
             getToolbarManager().enableBottomToolbar();
         }
     }
@@ -2584,7 +2590,8 @@
 
     @Override
     protected boolean shouldInitializeBottomSheet() {
-        return FeatureUtilities.areContextualSuggestionsEnabled(this);
+        return FeatureUtilities.areContextualSuggestionsEnabled(this)
+                || FeatureUtilities.isTabGroupsAndroidEnabled();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 0ebec871..ae2c6f2d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -691,6 +691,7 @@
         Point viewportSize = getViewportSize();
         setSize(mTabVisible.getWebContents(), mTabVisible.getContentView(), viewportSize.x,
                 viewportSize.y);
+        onViewportChanged();
     }
 
     @Override
@@ -699,6 +700,7 @@
         Point viewportSize = getViewportSize();
         setSize(mTabVisible.getWebContents(), mTabVisible.getContentView(), viewportSize.x,
                 viewportSize.y);
+        onViewportChanged();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java
index ab913f8..887b8357 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java
@@ -149,4 +149,13 @@
     public static boolean isInTestingMode() {
         return sIsInTestingMode;
     }
+
+    /**
+     * Provides update for animation in testing mode.
+     * @return Whether update was successful or not.
+     */
+    @VisibleForTesting
+    public final boolean pushUpdateInTestingMode(long deltaTimeMs) {
+        return sIsInTestingMode ? pushUpdate(deltaTimeMs) : false;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java
index 205ca3e..7071f3f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java
@@ -6,10 +6,12 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.RectF;
 import android.support.annotation.IntDef;
+import android.util.Pair;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.Interpolator;
 
@@ -17,6 +19,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
+import org.chromium.chrome.browser.compositor.animation.FloatProperty;
 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation;
@@ -32,6 +35,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Iterator;
 
 /**
  * Handles all the drawing and events of a stack of stackTabs.
@@ -222,6 +227,7 @@
 
     // Running set of animations applied to tabs.
     private ChromeAnimation<?> mTabAnimations;
+    private Pair<AnimatorSet, ArrayList<FloatProperty>> mAnimatorSetTabAnimations;
     private Animator mViewAnimations;
 
     // The parent Layout
@@ -541,17 +547,23 @@
                 // Build the AnimatorSet using the TabSwitcherAnimationFactory.
                 // This will give us the appropriate AnimatorSet based on the current
                 // state of the tab switcher and the OverviewAnimationType specified.
-                mTabAnimations = mAnimationFactory.createAnimatorSetForType(type, this, mStackTabs,
-                        focusIndex, sourceIndex, mSpacing, getDiscardRange());
+                mTabAnimations = mAnimationFactory.createChromeAnimationSetForType(type, this,
+                        mStackTabs, focusIndex, sourceIndex, mSpacing, getDiscardRange());
+                mAnimatorSetTabAnimations = mAnimationFactory.createAnimatorSetForType(type, this,
+                        mStackTabs, focusIndex, sourceIndex, mSpacing, getDiscardRange());
             }
 
             if (mTabAnimations != null) mTabAnimations.start();
+            if (mAnimatorSetTabAnimations != null) mAnimatorSetTabAnimations.first.start();
             if (mViewAnimations != null) mViewAnimations.start();
-            if (mTabAnimations != null || mViewAnimations != null) {
+            if (mTabAnimations != null || mAnimatorSetTabAnimations != null
+                    || mViewAnimations != null) {
                 mLayout.onStackAnimationStarted();
             }
 
-            if ((mTabAnimations == null && mViewAnimations == null) || finishImmediately) {
+            if ((mTabAnimations == null && mAnimatorSetTabAnimations == null
+                        && mViewAnimations == null)
+                    || finishImmediately) {
                 finishAnimation(time);
             }
         }
@@ -566,8 +578,12 @@
      */
     protected void finishAnimation(long time) {
         if (mTabAnimations != null) mTabAnimations.updateAndFinish();
+        if (mAnimatorSetTabAnimations != null) mAnimatorSetTabAnimations.first.end();
         if (mViewAnimations != null) mViewAnimations.end();
-        if (mTabAnimations != null || mViewAnimations != null) mLayout.onStackAnimationFinished();
+        if (mTabAnimations != null || mAnimatorSetTabAnimations != null
+                || mViewAnimations != null) {
+            mLayout.onStackAnimationFinished();
+        }
 
         switch (mOverviewAnimationType) {
             case OverviewAnimationType.ENTER_STACK:
@@ -621,6 +637,7 @@
         mOverviewAnimationType = OverviewAnimationType.NONE;
 
         mTabAnimations = null;
+        mAnimatorSetTabAnimations = null;
         mViewAnimations = null;
     }
 
@@ -685,8 +702,8 @@
                         || mOverviewAnimationType == OverviewAnimationType.UNDISCARD
                         || mOverviewAnimationType == OverviewAnimationType.DISCARD_ALL)
                     && (type == OverviewAnimationType.DISCARD
-                               || type == OverviewAnimationType.UNDISCARD
-                               || type == OverviewAnimationType.DISCARD_ALL)) {
+                            || type == OverviewAnimationType.UNDISCARD
+                            || type == OverviewAnimationType.DISCARD_ALL)) {
                 return true;
             }
         }
@@ -702,6 +719,17 @@
                 || mOverviewAnimationType == OverviewAnimationType.UNDISCARD
                 || mOverviewAnimationType == OverviewAnimationType.DISCARD_ALL) {
             mTabAnimations.cancel(null, StackTab.Property.SCROLL_OFFSET);
+            if (mAnimatorSetTabAnimations != null) {
+                Iterator<FloatProperty> propertyIterator =
+                        mAnimatorSetTabAnimations.second.iterator();
+                Iterator<Animator> animatorIterator =
+                        mAnimatorSetTabAnimations.first.getChildAnimations().iterator();
+
+                while (animatorIterator.hasNext()) {
+                    CompositorAnimator a = (CompositorAnimator) animatorIterator.next();
+                    if (propertyIterator.next() == StackTab.SCROLL_OFFSET) a.cancel();
+                }
+            }
             return true;
         }
         return false;
@@ -731,30 +759,45 @@
     public boolean onUpdateCompositorAnimations(long time, boolean jumpToEnd) {
         if (!jumpToEnd) updateScrollOffset(time);
 
-        boolean finished = true;
+        boolean animatorSetFinished = true;
+        boolean chromeAnimationsFinished = true;
         if (mTabAnimations != null) {
             if (jumpToEnd) {
-                finished = mTabAnimations.finished();
+                chromeAnimationsFinished = mTabAnimations.finished();
             } else {
-                finished = mTabAnimations.update(time);
+                chromeAnimationsFinished = mTabAnimations.update(time);
             }
+        }
+        if (mAnimatorSetTabAnimations != null) {
+            animatorSetFinished = jumpToEnd ? true : !mAnimatorSetTabAnimations.first.isRunning();
+        }
+        if (mTabAnimations != null || mAnimatorSetTabAnimations != null) {
             finishAnimationsIfDone(time, jumpToEnd);
         }
 
         if (jumpToEnd) forceScrollStop();
-        return finished;
+        return chromeAnimationsFinished || animatorSetFinished;
     }
 
     private void finishAnimationsIfDone(long time, boolean jumpToEnd) {
         boolean hasViewAnimations = mViewAnimations != null;
-        boolean hasTabAnimations = mTabAnimations != null;
-        boolean hasAnimations = hasViewAnimations || hasTabAnimations;
         boolean isViewFinished = hasViewAnimations ? !mViewAnimations.isRunning() : true;
+
+        boolean hasTabAnimations = mTabAnimations != null;
         boolean isTabFinished = hasTabAnimations ? mTabAnimations.finished() : true;
 
+        boolean hasAnimatorSetTabAnimations = mAnimatorSetTabAnimations != null;
+        boolean isAnimatorSetTabFinished =
+                hasAnimatorSetTabAnimations ? !mAnimatorSetTabAnimations.first.isRunning() : true;
+
+        boolean hasAnimations =
+                hasViewAnimations || hasTabAnimations || hasAnimatorSetTabAnimations;
+
         boolean shouldFinish = jumpToEnd && hasAnimations;
         shouldFinish |= hasAnimations && (!hasViewAnimations || isViewFinished)
-                && (!hasTabAnimations || isTabFinished);
+                && (!hasTabAnimations || isTabFinished)
+                && (!hasAnimatorSetTabAnimations || isAnimatorSetTabFinished);
+
         if (shouldFinish) finishAnimation(time);
     }
 
@@ -846,9 +889,9 @@
     private void discard(float x, float y, float amountX, float amountY) {
         if (mStackTabs == null
                 || (mOverviewAnimationType != OverviewAnimationType.NONE
-                           && mOverviewAnimationType != OverviewAnimationType.DISCARD
-                           && mOverviewAnimationType != OverviewAnimationType.DISCARD_ALL
-                           && mOverviewAnimationType != OverviewAnimationType.UNDISCARD)) {
+                        && mOverviewAnimationType != OverviewAnimationType.DISCARD
+                        && mOverviewAnimationType != OverviewAnimationType.DISCARD_ALL
+                        && mOverviewAnimationType != OverviewAnimationType.UNDISCARD)) {
             return;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java
index 36f389db..0f583bd4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java
@@ -6,11 +6,14 @@
 
 import static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.AnimatableAnimation.addAnimation;
 
+import android.animation.AnimatorSet;
 import android.support.annotation.IntDef;
+import android.util.Pair;
 import android.view.animation.Interpolator;
 
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
+import org.chromium.chrome.browser.compositor.animation.FloatProperty;
 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation;
 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable;
 import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation;
@@ -19,6 +22,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 
 /**
  * A factory that builds animations for the tab stack.
@@ -165,8 +169,9 @@
      * @param discardRange  The range of the discard amount value.
      * @return              The resulting TabSwitcherAnimation that will animate the tabs.
      */
-    public ChromeAnimation<?> createAnimatorSetForType(@OverviewAnimationType int type, Stack stack,
-            StackTab[] tabs, int focusIndex, int sourceIndex, int spacing, float discardRange) {
+    public ChromeAnimation<?> createChromeAnimationSetForType(@OverviewAnimationType int type,
+            Stack stack, StackTab[] tabs, int focusIndex, int sourceIndex, int spacing,
+            float discardRange) {
         if (tabs == null) return null;
         switch (type) {
             case OverviewAnimationType.ENTER_STACK:
@@ -194,6 +199,27 @@
         }
     }
 
+    /**
+     * The wrapper method responsible for delegating the animations request to the appropriate
+     * helper method.  Not all parameters are used for each request.
+     *
+     * @param type          The type of animation to be created.  This is what
+     *                      determines which helper method is called.
+     * @param stack         The current stack.
+     * @param tabs          The tabs that make up the current stack that will
+     *                      be animated.
+     * @param focusIndex    The index of the tab that is the focus of this animation.
+     * @param sourceIndex   The index of the tab that triggered this animation.
+     * @param spacing       The default spacing between the tabs.
+     * @param discardRange  The range of the discard amount value.
+     * @return              The resulting TabSwitcherAnimation that will animate the tabs.
+     */
+    public Pair<AnimatorSet, ArrayList<FloatProperty>> createAnimatorSetForType(
+            @OverviewAnimationType int type, Stack stack, StackTab[] tabs, int focusIndex,
+            int sourceIndex, int spacing, float discardRange) {
+        return null;
+    }
+
     protected abstract float getScreenSizeInScrollDirection();
 
     protected abstract float getScreenPositionInScrollDirection(StackTab tab);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index f5410e48..3e3be8f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -490,7 +490,7 @@
             mTranslateController.cacheNativeTranslateData();
         } else if (!TextUtils.isEmpty(selection)) {
             boolean shouldPrefetch = mPolicy.shouldPrefetchSearchResult();
-            mSearchRequest = new ContextualSearchRequest(selection, null, null, shouldPrefetch);
+            mSearchRequest = new ContextualSearchRequest(selection, shouldPrefetch);
             mTranslateController.forceAutoDetectTranslateUnlessDisabled(mSearchRequest);
             mDidStartLoadingResolvedSearchRequest = false;
             mSearchPanel.setSearchTerm(selection);
@@ -680,6 +680,8 @@
      * @param quickActionCategory The {@link QuickActionCategory} for the quick action.
      * @param loggedEventId The EventID logged by the server, which should be recorded and sent back
      *        to the server along with user action results in a subsequent request.
+     * @param searchUrlFull The URL for the full search to present in the overlay, or empty.
+     * @param searchUrlPreload The URL for the search to preload into the overlay, or empty.
      */
     @CalledByNative
     public void onSearchTermResolutionResponse(boolean isNetworkUnavailable, int responseCode,
@@ -687,11 +689,11 @@
             final String mid, boolean doPreventPreload, int selectionStartAdjust,
             int selectionEndAdjust, final String contextLanguage, final String thumbnailUrl,
             final String caption, final String quickActionUri, final int quickActionCategory,
-            final long loggedEventId) {
+            final long loggedEventId, final String searchUrlFull, final String searchUrlPreload) {
         mNetworkCommunicator.handleSearchTermResolutionResponse(isNetworkUnavailable, responseCode,
                 searchTerm, displayText, alternateTerm, mid, doPreventPreload, selectionStartAdjust,
                 selectionEndAdjust, contextLanguage, thumbnailUrl, caption, quickActionUri,
-                quickActionCategory, loggedEventId);
+                quickActionCategory, loggedEventId, searchUrlFull, searchUrlPreload);
     }
 
     @Override
@@ -699,7 +701,8 @@
             String searchTerm, String displayText, String alternateTerm, String mid,
             boolean doPreventPreload, int selectionStartAdjust, int selectionEndAdjust,
             String contextLanguage, String thumbnailUrl, String caption, String quickActionUri,
-            int quickActionCategory, long loggedEventId) {
+            int quickActionCategory, long loggedEventId, String searchUrlFull,
+            String searchUrlPreload) {
         if (!mInternalStateController.isStillWorkingOn(InternalState.RESOLVING)) return;
 
         // Show an appropriate message for what to search for.
@@ -765,8 +768,8 @@
             // TODO(donnd): Instead of preloading, we should prefetch (ie the URL should not
             // appear in the user's history until the user views it).  See crbug.com/406446.
             boolean shouldPreload = !doPreventPreload && mPolicy.shouldPrefetchSearchResult();
-            mSearchRequest =
-                    new ContextualSearchRequest(searchTerm, alternateTerm, mid, shouldPreload);
+            mSearchRequest = new ContextualSearchRequest(
+                    searchTerm, alternateTerm, mid, shouldPreload, searchUrlFull, searchUrlPreload);
             // Trigger translation, if enabled.
             mTranslateController.forceTranslateIfNeeded(mSearchRequest, contextLanguage);
             mDidStartLoadingResolvedSearchRequest = false;
@@ -1045,8 +1048,8 @@
                 // is in progress or we should do a verbatim search now.
                 if (mSearchRequest == null && mPolicy.shouldCreateVerbatimRequest()
                         && !TextUtils.isEmpty(mSelectionController.getSelectedText())) {
-                    mSearchRequest = new ContextualSearchRequest(
-                            mSelectionController.getSelectedText(), null, null, false);
+                    mSearchRequest =
+                            new ContextualSearchRequest(mSelectionController.getSelectedText());
                     mDidStartLoadingResolvedSearchRequest = false;
                 }
                 if (mSearchRequest != null
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java
index 880b84a..72013609 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java
@@ -41,12 +41,15 @@
      * @param quickActionCategory The {@link QuickActionCategory} for the quick action.
      * @param loggedEventId The EventID logged by the server, which should be recorded and sent back
      *        to the server along with user action results in a subsequent request.
+     * @param searchUrlFull The URL for the full search to present in the overlay, or empty.
+     * @param searchUrlPreload The URL for the search to preload into the overlay, or empty.
      */
     void handleSearchTermResolutionResponse(boolean isNetworkUnavailable, int responseCode,
             String searchTerm, String displayText, String alternateTerm, String mid,
             boolean doPreventPreload, int selectionStartAdjust, int selectionEndAdjust,
             String contextLanguage, String thumbnailUrl, String caption, String quickActionUri,
-            int quickActionCategory, long loggedEventId);
+            int quickActionCategory, long loggedEventId, String searchUrlFull,
+            String searchUrlPreload);
 
     /**
      * @return Whether the device is currently online.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequest.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequest.java
index 088dc04..2cfd110a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequest.java
@@ -10,6 +10,7 @@
 
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
+import org.chromium.chrome.browser.util.UrlUtilities;
 
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -27,6 +28,7 @@
     private boolean mIsLowPriority;
     private boolean mHasFailedLowPriorityLoad;
     private boolean mIsTranslationForced;
+    private boolean mIsFullSearchUrlProvided;
 
     private static final String GWS_LOW_PRIORITY_SEARCH_PATH = "s";
     private static final String GWS_SEARCH_NO_SUGGESTIONS_PARAM = "sns";
@@ -49,26 +51,48 @@
      * @param searchTerm The resolved search term.
      */
     ContextualSearchRequest(String searchTerm) {
-        this(searchTerm, null, null, false);
+        this(searchTerm, false);
     }
 
     /**
-     * Creates a search request for the given search term with the given alternate term and
-     * low-priority loading capability.
+     * Creates a search request for the given search term without any alternate term and
+     * for low-priority loading capability if specified in the second parameter.
+     * @param searchTerm The resolved search term.
+     * @param isLowPriorityEnabled Whether the request can be made at a low priority.
+     */
+    ContextualSearchRequest(String searchTerm, boolean isLowPriorityEnabled) {
+        this(searchTerm, null, null, isLowPriorityEnabled, null, null);
+    }
+
+    /**
+     * Creates a search request for the given search term, unless the full search URL is provided
+     * in the {@code searchUrlFull}.  When the full URL is not provided the request also uses the
+     * given alternate term, mid, and low-priority loading capability. <p>
+     * If the {@code searchUrlPreload} is provided then the {@code searchUrlFull} should also be
+     * provided.
      * @param searchTerm The resolved search term.
      * @param alternateTerm The alternate search term.
      * @param mid The MID for an entity to use to trigger a Knowledge Panel, or an empty string.
      *            A MID is a unique identifier for an entity in the Search Knowledge Graph.
      * @param isLowPriorityEnabled Whether the request can be made at a low priority.
+     * @param searchUrlFull The URL for the full search to present in the overlay, or empty.
+     * @param searchUrlPreload The URL for the search to preload into the overlay, or empty.
      */
     ContextualSearchRequest(String searchTerm, @Nullable String alternateTerm, @Nullable String mid,
-            boolean isLowPriorityEnabled) {
+            boolean isLowPriorityEnabled, @Nullable String searchUrlFull,
+            @Nullable String searchUrlPreload) {
         mWasPrefetch = isLowPriorityEnabled;
-        mNormalPriorityUri = getUriTemplate(searchTerm, alternateTerm, mid, false);
+        mIsFullSearchUrlProvided = isGoogleUrl(searchUrlFull);
+        mNormalPriorityUri = mIsFullSearchUrlProvided
+                ? Uri.parse(searchUrlFull)
+                : getUriTemplate(searchTerm, alternateTerm, mid, false);
         if (isLowPriorityEnabled) {
-            // TODO(donnd): Call TemplateURL once we have an API for 3rd-party providers.
-            Uri baseLowPriorityUri = getUriTemplate(searchTerm, alternateTerm, mid, true);
-            mLowPriorityUri = makeLowPriorityUri(baseLowPriorityUri);
+            if (isGoogleUrl(searchUrlPreload)) {
+                mLowPriorityUri = Uri.parse(searchUrlPreload);
+            } else {
+                Uri baseLowPriorityUri = getUriTemplate(searchTerm, alternateTerm, mid, true);
+                mLowPriorityUri = makeLowPriorityUri(baseLowPriorityUri);
+            }
         } else {
             mLowPriorityUri = null;
         }
@@ -140,8 +164,8 @@
 
         URL url;
         try {
-            url = new URL(searchUrl.replaceAll(CTXS_PARAM_PATTERN, CTXR_PARAM)
-                    .replaceAll(PF_PARAM, ""));
+            url = new URL(
+                    searchUrl.replaceAll(CTXS_PARAM_PATTERN, CTXR_PARAM).replaceAll(PF_PARAM, ""));
         } catch (MalformedURLException e) {
             url = null;
         }
@@ -156,6 +180,9 @@
      */
     void forceTranslation(String sourceLanguage, String targetLanguage) {
         mIsTranslationForced = true;
+        // If the server is providing a full URL then we shouldn't alter it.
+        if (mIsFullSearchUrlProvided) return;
+
         if (mLowPriorityUri != null) {
             mLowPriorityUri = makeTranslateUri(mLowPriorityUri, sourceLanguage, targetLanguage);
         }
@@ -172,7 +199,7 @@
     }
 
     /**
-     * @return Whether translation was forced for this request.
+     * @return Whether translation was forced for this request (for testing only).
      */
     @VisibleForTesting
     boolean isTranslationForced() {
@@ -200,6 +227,16 @@
     }
 
     /**
+     * Judges if the given URL looks like a Google URL.
+     * @param someUrl A URL to judge.
+     * @return Whether it's pointing to Google infrastructure or not.
+     */
+    @VisibleForTesting
+    boolean isGoogleUrl(@Nullable String someUrl) {
+        return !TextUtils.isEmpty(someUrl) && UrlUtilities.nativeIsGoogleDomainUrl(someUrl, true);
+    }
+
+    /**
      * @return a low-priority {@code Uri} from the given base {@code Uri}.
      */
     private Uri makeLowPriorityUri(Uri baseUri) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java
index 2986804..2d0879b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java
@@ -33,6 +33,7 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.chrome.browser.ChromeApplication;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.download.DownloadNotificationUmaHelper.UmaDownloadResumption;
 import org.chromium.chrome.browser.download.items.OfflineContentAggregatorNotificationBridgeUiFactory;
 import org.chromium.chrome.browser.init.BrowserParts;
@@ -312,7 +313,9 @@
      * @return delegate for interactions with the entry
      */
     static DownloadServiceDelegate getServiceDelegate(ContentId id) {
-        if (LegacyHelpers.isLegacyDownload(id)) {
+        if (LegacyHelpers.isLegacyDownload(id)
+                && !ChromeFeatureList.isEnabled(
+                        ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
             return DownloadManagerService.getDownloadManagerService();
         }
         return OfflineContentAggregatorNotificationBridgeUiFactory.instance();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
index 764e012..cb70925 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
@@ -20,6 +20,7 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.util.FeatureUtilities;
@@ -70,7 +71,9 @@
     private static DownloadNotificationService sDownloadNotificationService;
 
     public static void setDownloadNotificationService(DownloadNotificationService service) {
-        sDownloadNotificationService = service;
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
+            sDownloadNotificationService = service;
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
index 4c8b00f..e5d898f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
@@ -221,6 +221,7 @@
         }
     }
 
+    private final boolean mUseNewDownloadPath;
     private final boolean mIsIncognito;
     private final Handler mHandler = new Handler();
     private final DownloadProgressInfoBar.Client mClient = new DownloadProgressInfoBarClient();
@@ -258,6 +259,8 @@
 
     /** Constructor. */
     public DownloadInfoBarController(boolean isIncognito) {
+        mUseNewDownloadPath =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER);
         mIsIncognito = isIncognito;
         mHandler.post(() -> getOfflineContentProvider().addObserver(this));
     }
@@ -273,6 +276,8 @@
 
     /** Updates the InfoBar when new information about a download comes in. */
     public void onDownloadItemUpdated(DownloadItem downloadItem) {
+        if (mUseNewDownloadPath) return;
+
         OfflineItem offlineItem = DownloadInfo.createOfflineItem(downloadItem.getDownloadInfo());
         if (!isVisibleToUser(offlineItem)) return;
 
@@ -291,6 +296,7 @@
 
     /** Updates the InfoBar after a download has been removed. */
     public void onDownloadItemRemoved(ContentId contentId) {
+        if (mUseNewDownloadPath) return;
         onItemRemoved(contentId);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
index c200116..0dbba8fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
@@ -13,8 +13,10 @@
 import android.os.Environment;
 import android.support.v4.app.NotificationManagerCompat;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StrictModeContext;
@@ -262,20 +264,35 @@
         return ContextUtils.getApplicationContext();
     }
 
+    /**
+     * This function is meant to be called as the last step of a download. It will add the download
+     * to the android's DownloadManager and determine if the download can be resolved to any
+     * activity so that it can be auto-opened.
+     */
     @CalledByNative
-    private static void addCompletedDownload(String fileName, String description, String mimeType,
-            String filePath, long fileSizeBytes, String originalUrl, String referrer,
-            String downloadGuid, long callbackId) {
-        AsyncTask<Long> task = new AsyncTask<Long>() {
+    private static void addCompletedDownload(String fileName, String description,
+            String originalMimeType, String filePath, long fileSizeBytes, String originalUrl,
+            String referrer, String downloadGuid, long callbackId) {
+        final String mimeType = ChromeDownloadDelegate.remapGenericMimeType(
+                originalMimeType, originalUrl, fileName);
+        AsyncTask<Pair<Long, Boolean>> task = new AsyncTask<Pair<Long, Boolean>>() {
             @Override
-            protected Long doInBackground() {
-                return addCompletedDownload(fileName, description, mimeType, filePath,
-                        fileSizeBytes, originalUrl, referrer, downloadGuid);
+            protected Pair<Long, Boolean> doInBackground() {
+                long downloadId = ContentUriUtils.isContentUri(filePath)
+                        ? DownloadItem.INVALID_DOWNLOAD_ID
+                        : addCompletedDownload(fileName, description, mimeType, filePath,
+                                fileSizeBytes, originalUrl, referrer, downloadGuid);
+                boolean success = ContentUriUtils.isContentUri(filePath)
+                        || downloadId != DownloadItem.INVALID_DOWNLOAD_ID;
+                boolean canResolve = success
+                        && DownloadManagerService.canResolveDownload(
+                                filePath, mimeType, downloadId);
+                return Pair.create(downloadId, canResolve);
             }
 
             @Override
-            protected void onPostExecute(Long downloadId) {
-                nativeOnAddCompletedDownloadDone(callbackId, downloadId);
+            protected void onPostExecute(Pair<Long, Boolean> result) {
+                nativeOnAddCompletedDownloadDone(callbackId, result.first, result.second);
             }
         };
         try {
@@ -283,7 +300,7 @@
         } catch (RejectedExecutionException e) {
             // Reaching thread limit, update will be reschduled for the next run.
             Log.e(TAG, "Thread limit reached, reschedule notification update later.");
-            nativeOnAddCompletedDownloadDone(callbackId, DownloadItem.INVALID_DOWNLOAD_ID);
+            nativeOnAddCompletedDownloadDone(callbackId, DownloadItem.INVALID_DOWNLOAD_ID, false);
         }
     }
 
@@ -424,5 +441,6 @@
         }
     }
 
-    private static native void nativeOnAddCompletedDownloadDone(long callbackId, long downloadId);
+    private static native void nativeOnAddCompletedDownloadDone(
+            long callbackId, long downloadId, boolean canResolve);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index 72b0684d..d648b8a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -886,6 +886,23 @@
                                 : ExternalNavigationDelegateImpl.resolveIntent(intent, true);
     }
 
+    /**
+     * Return whether a download item can be resolved to any activity.
+     * @param filePath The file path for the download.
+     * @param mimeType The mime type of the download
+     * @param systemDownloadId They download ID generated from the android DownloadManager.
+     * @return True, if the download can be handled by an activity, false otherwise
+     */
+    public static boolean canResolveDownload(
+            String filePath, String mimeType, long systemDownloadId) {
+        assert !ThreadUtils.runningOnUiThread();
+        if (isOMADownloadDescription(mimeType)) return true;
+
+        Intent intent = getLaunchIntentForDownload(filePath, systemDownloadId,
+                DownloadManagerService.isSupportedMimeType(mimeType), null, null);
+        return intent != null && ExternalNavigationDelegateImpl.resolveIntent(intent, true);
+    }
+
     /** See {@link #openDownloadedContent(Context, String, boolean, boolean, String, long)}. */
     protected void openDownloadedContent(final DownloadInfo downloadInfo, final long downloadId,
             @DownloadOpenSource int source) {
@@ -1196,18 +1213,25 @@
      */
     public void onSuccessNotificationShown(
             DownloadInfo info, boolean canResolve, int notificationId, long systemDownloadId) {
-        if (canResolve && shouldOpenAfterDownload(info.getMimeType(), info.hasUserGesture())) {
-            DownloadItem item = new DownloadItem(false, info);
-            item.setSystemDownloadId(systemDownloadId);
-            handleAutoOpenAfterDownload(item);
-        } else {
-            DownloadInfoBarController infobarController =
-                    getInfoBarController(info.isOffTheRecord());
-            if (infobarController != null) {
-                infobarController.onNotificationShown(info.getContentId(), notificationId);
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
+            if (canResolve && shouldOpenAfterDownload(info.getMimeType(), info.hasUserGesture())) {
+                DownloadItem item = new DownloadItem(false, info);
+                item.setSystemDownloadId(systemDownloadId);
+                handleAutoOpenAfterDownload(item);
+            } else {
+                DownloadInfoBarController infobarController =
+                        getInfoBarController(info.isOffTheRecord());
+                if (infobarController != null) {
+                    infobarController.onNotificationShown(info.getContentId(), notificationId);
+                }
+                mDownloadSnackbarController.onDownloadSucceeded(
+                        info, notificationId, systemDownloadId, canResolve, false);
             }
-            mDownloadSnackbarController.onDownloadSucceeded(
-                    info, notificationId, systemDownloadId, canResolve, false);
+        } else {
+            if (getInfoBarController(info.isOffTheRecord()) != null) {
+                getInfoBarController(info.isOffTheRecord())
+                        .onNotificationShown(info.getContentId(), notificationId);
+            }
         }
 
         if (BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
index ef0ae9ffd..2b65a3e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
@@ -33,6 +33,7 @@
 
 import org.chromium.base.ContentUriUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.media.MediaViewerUtils;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
@@ -242,7 +243,9 @@
 
                 if (downloadUpdate.getIsOpenable()) {
                     Intent intent;
-                    if (LegacyHelpers.isLegacyDownload(downloadUpdate.getContentId())) {
+                    if (LegacyHelpers.isLegacyDownload(downloadUpdate.getContentId())
+                            && !ChromeFeatureList.isEnabled(
+                                    ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
                         Preconditions.checkNotNull(downloadUpdate.getContentId());
                         Preconditions.checkArgument(downloadUpdate.getSystemDownloadId() != -1
                                 || ContentUriUtils.isContentUri(downloadUpdate.getFilePath()));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
index c70a6f77..cbad533 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
@@ -88,6 +88,8 @@
             mSupportFullWidthImages = !DeviceFormFactor.isNonMultiDisplayContextOnTablet(
                     ContextUtils.getApplicationContext());
             mUseGenericViewTypes = SysUtils.isLowEndDevice();
+            mUseNewDownloadPath = ChromeFeatureList.isEnabled(
+                    ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER);
         }
 
         public Builder setIsOffTheRecord(boolean isOffTheRecord) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
index a8a466d34..4666b99 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
@@ -242,7 +242,7 @@
                 shownState = CircularProgressView.UiState.PAUSED;
                 break;
             case OfflineItemState.INTERRUPTED:
-                shownState = item.isResumable ? CircularProgressView.UiState.PAUSED
+                shownState = item.isResumable ? CircularProgressView.UiState.RUNNING
                                               : CircularProgressView.UiState.RETRY;
                 break;
             case OfflineItemState.COMPLETE: // Intentional fallthrough.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java
index 81930c7b..d884cda 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java
@@ -7,6 +7,7 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.offline_items_collection.OfflineContentProvider;
 
@@ -17,7 +18,7 @@
 public class OfflineContentAggregatorFactory {
     // TODO(crbug.com/857543): Remove this after downloads have implemented it.
     // We need only one provider, since OfflineContentAggregator lives in the original profile.
-    private static DownloadBlockedOfflineContentProvider sBlockedProvider;
+    private static OfflineContentProvider sProvider;
 
     private OfflineContentAggregatorFactory() {}
 
@@ -32,9 +33,9 @@
     public static void setOfflineContentProviderForTests(
             @Nullable OfflineContentProvider provider) {
         if (provider == null) {
-            sBlockedProvider = null;
+            sProvider = null;
         } else {
-            sBlockedProvider = new DownloadBlockedOfflineContentProvider(provider);
+            sProvider = getProvider(provider);
         }
     }
 
@@ -45,11 +46,18 @@
      * @return An {@link OfflineContentProvider} instance.
      */
     public static OfflineContentProvider forProfile(Profile profile) {
-        if (sBlockedProvider == null) {
-            sBlockedProvider = new DownloadBlockedOfflineContentProvider(
-                    nativeGetOfflineContentAggregatorForProfile(profile));
+        if (sProvider == null) {
+            sProvider = getProvider(nativeGetOfflineContentAggregatorForProfile(profile));
         }
-        return sBlockedProvider;
+        return sProvider;
+    }
+
+    private static OfflineContentProvider getProvider(OfflineContentProvider provider) {
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
+            return provider;
+        } else {
+            return new DownloadBlockedOfflineContentProvider(provider);
+        }
     }
 
     private static native OfflineContentProvider nativeGetOfflineContentAggregatorForProfile(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
index 66277b6..05e30fc7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
@@ -61,6 +61,70 @@
             .ReadableObjectPropertyKey<ListModel<ExploreSitesCategory>> CATEGORY_LIST_KEY =
             new PropertyModel.ReadableObjectPropertyKey<>();
 
+    /**
+     * Custom layout manager that fixes the scrollbar size based on number of items
+     * to provide a scrollbar utility that will not shift as a recylcer view scrolls
+     * between items of different heights.
+     */
+    private class StableScrollLayoutManager extends LinearLayoutManager {
+        // Fixes the scrollbar size so it will not resize.
+        private int mScrollValue;
+
+        StableScrollLayoutManager(Context context) {
+            super(context);
+            setSmoothScrollbarEnabled(false);
+        }
+
+        @Override
+        public int computeVerticalScrollExtent(RecyclerView.State state) {
+            final int count = getItemCount();
+            if (count > 0) {
+                mScrollValue = getHeight() / count;
+                return mScrollValue;
+            }
+            return 0;
+        }
+
+        @Override
+        public int computeVerticalScrollRange(RecyclerView.State state) {
+            // Fix the scroll range.
+            return Math.max((getItemCount() - 1) * mScrollValue, 0);
+        }
+
+        @Override
+        public int computeVerticalScrollOffset(RecyclerView.State state) {
+            final int count = getChildCount();
+            // If this was called before the recycler view fully initialized itself, return 0.
+            if (count <= 0) return 0;
+
+            // Snap to bottom if we scrolled to the bottom.
+            if (findLastCompletelyVisibleItemPosition() == getItemCount() - 1) {
+                return Math.max((getItemCount() - 1) * mScrollValue, 0);
+            }
+
+            // Find the first visible view and check that views are properly initialized.
+            // This includes if a view was recycled or swapped out just now.
+            int firstPos = findFirstVisibleItemPosition();
+            if (firstPos == RecyclerView.NO_POSITION) return 0;
+            View view = findViewByPosition(firstPos);
+            if (view == null) return 0;
+
+            // Top of the view in pixels
+            final int top = getDecoratedTop(view);
+            int height = getDecoratedMeasuredHeight(view);
+            int heightOfScreen;
+            if (height <= 0) {
+                heightOfScreen = 0;
+            } else {
+                heightOfScreen = Math.abs(mScrollValue * top / height);
+            }
+            if (heightOfScreen == 0 && firstPos > 0) {
+                return mScrollValue * firstPos - 1;
+            }
+            return (mScrollValue * firstPos) + heightOfScreen;
+        }
+    }
+
     @IntDef({CatalogLoadingState.LOADING, CatalogLoadingState.SUCCESS, CatalogLoadingState.ERROR})
     @Retention(RetentionPolicy.SOURCE)
     public @interface CatalogLoadingState {
@@ -109,7 +173,7 @@
                          .build();
 
         Context context = mView.getContext();
-        mLayoutManager = new LinearLayoutManager(context);
+        mLayoutManager = new StableScrollLayoutManager(context);
         int iconSizePx = context.getResources().getDimensionPixelSize(R.dimen.tile_view_icon_size);
         RoundedIconGenerator iconGenerator = new RoundedIconGenerator(iconSizePx, iconSizePx,
                 iconSizePx / 2,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
index d3af51d5..2394eff6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
@@ -24,7 +24,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.touchless.TouchlessNewTabPage;
+import org.chromium.chrome.browser.touchless.TouchlessDelegate;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.content_public.browser.LoadUrlParams;
 
@@ -46,7 +46,7 @@
             }
 
             if (FeatureUtilities.isNoTouchModeEnabled()) {
-                return new TouchlessNewTabPage(activity, new TabShim(tab));
+                return TouchlessDelegate.createTouchlessNewTabPage(activity, new TabShim(tab));
             }
 
             if (ChromeFeatureList.isEnabled(ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
index 8896d15..74a06c14 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
@@ -338,7 +338,7 @@
         int numberOfSuggestionsExposed = getNumberOfSuggestionsExposed();
         if (!canUpdateSuggestions(numberOfSuggestionsExposed)) {
             mIsDataStale = true;
-            Log.d(TAG, "updateSuggestions: Category %d is stale, it can't replace suggestions.",
+            Log.d(TAG, "updateModels: Category %d is stale, it can't replace suggestions.",
                     getCategory());
             return;
         }
@@ -354,8 +354,7 @@
 
         if (numberOfSuggestionsExposed > 0) {
             mIsDataStale = true;
-            Log.d(TAG,
-                    "updateSuggestions: Category %d is stale, will keep already seen suggestions.",
+            Log.d(TAG, "updateModels: Category %d is stale, will keep already seen suggestions.",
                     getCategory());
         }
         appendSuggestions(suggestions, /* keepSectionSize = */ true,
@@ -377,8 +376,7 @@
 
         int numberOfSuggestionsExposed = getNumberOfSuggestionsExposed();
         if (keepSectionSize) {
-            Log.d(TAG, "updateSuggestions: keeping the first %d suggestion",
-                    numberOfSuggestionsExposed);
+            Log.d(TAG, "updateModels: keeping the first %d suggestion", numberOfSuggestionsExposed);
             int numSuggestionsToAppend =
                     Math.max(0, suggestions.size() - numberOfSuggestionsExposed);
             int itemCount = mSuggestions.size();
@@ -432,7 +430,7 @@
         if (!hasSuggestions()) return true; // If we don't have any, we always accept updates.
 
         if (CardsVariationParameters.ignoreUpdatesForExistingSuggestions()) {
-            Log.d(TAG, "updateSuggestions: replacing existing suggestion disabled");
+            Log.d(TAG, "updateModels: replacing existing suggestion disabled");
             NewTabPageUma.recordUIUpdateResult(
                     NewTabPageUma.ContentSuggestionsUIUpdateResult.FAIL_DISABLED);
             return false;
@@ -441,7 +439,7 @@
         if (numberOfSuggestionsExposed >= getSuggestionsCount() || mHasAppended) {
             // In case that suggestions got removed, we assume they already were seen. This might
             // be over-simplifying things, but given the rare occurences it should be good enough.
-            Log.d(TAG, "updateSuggestions: replacing existing suggestion not possible, all seen");
+            Log.d(TAG, "updateModels: replacing existing suggestion not possible, all seen");
             NewTabPageUma.recordUIUpdateResult(
                     NewTabPageUma.ContentSuggestionsUIUpdateResult.FAIL_ALL_SEEN);
             return false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java
index 4c10d75..81e4f70f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateConfigs.java
@@ -4,15 +4,22 @@
 
 package org.chromium.chrome.browser.omaha;
 
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
 
 import org.chromium.base.CommandLine;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState;
 import org.chromium.chrome.browser.omaha.inline.FakeAppUpdateManagerWrapper;
 import org.chromium.components.variations.VariationsAssociatedData;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
+
 /**
  * Helper class for retrieving experiment configuration values and for manually testing update
  * functionality.  Use the following switches to test locally:
@@ -43,6 +50,35 @@
     private static final String INLINE_UPDATE_INSTALL_FAILED_SWITCH_VALUE =
             "inline_update_install_failed";
 
+    private static final String UPDATE_FLOW_PARAM_NAME = "flow";
+
+    /** Possible update flow configurations. */
+    @IntDef({UpdateFlowConfiguration.NEVER_SHOW, UpdateFlowConfiguration.INTENT_ONLY,
+            UpdateFlowConfiguration.INLINE_ONLY, UpdateFlowConfiguration.BEST_EFFORT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UpdateFlowConfiguration {
+        /** Turns off all update indicators. */
+        int NEVER_SHOW = 1;
+
+        /**
+         * Requires Omaha to say an update is available, and only ever Intents out to Play Store.
+         */
+        int INTENT_ONLY = 2;
+
+        /**
+         * Requires both Omaha and Play Store to say an update is available. Only ever uses the
+         * inline update flow.
+         */
+        int INLINE_ONLY = 3;
+
+        /**
+         * Checks both Omaha and Play Store. If Omaha says there is an update available, but Play
+         * Store says there is no update available, Intents out to Play Store. If both Omaha and
+         * Play Store says an update is available, uses the inline update flow.
+         */
+        int BEST_EFFORT = 4;
+    }
+
     /**
      * @return The minimum required storage to show the update prompt or {@code -1} if there is no
      * minimum.
@@ -165,4 +201,39 @@
         }
         return value;
     }
+
+    /**
+     * When the inline update flow is enabled, returns the variation for the current session. With
+     * the inline update flow disabled it returns {@link UpdateFlowConfiguration#INTENT_ONLY}.
+     * @return the current inline update flow configuration.
+     */
+    @UpdateFlowConfiguration
+    static int getConfiguration() {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.INLINE_UPDATE_FLOW)) {
+            // Always use the the old flow if the inline update flow feature is not enabled.
+            return UpdateFlowConfiguration.INTENT_ONLY;
+        }
+
+        switch (getFieldTrialConfigurationLowerCase()) {
+            case "never_show":
+                return UpdateFlowConfiguration.NEVER_SHOW;
+            case "intent_only":
+                return UpdateFlowConfiguration.INTENT_ONLY;
+            case "inline_only":
+                return UpdateFlowConfiguration.INLINE_ONLY;
+            case "best_effort":
+                return UpdateFlowConfiguration.BEST_EFFORT;
+            default:
+                // By just turning on the feature flag, assume INLINE_ONLY.
+                return UpdateFlowConfiguration.INLINE_ONLY;
+        }
+    }
+
+    @NonNull
+    private static String getFieldTrialConfigurationLowerCase() {
+        String configuration = ChromeFeatureList.getFieldTrialParamByFeature(
+                ChromeFeatureList.INLINE_UPDATE_FLOW, UPDATE_FLOW_PARAM_NAME);
+        if (configuration == null) return "";
+        return configuration.toLowerCase(Locale.US);
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java
index 1ad526a5..377cbdc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java
@@ -10,6 +10,7 @@
 import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
 import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
+import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateInteractionSource;
 import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState;
 import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateStatus;
 import org.chromium.chrome.browser.tab.Tab;
@@ -60,12 +61,13 @@
     }
 
     private void restartChrome() {
-        UpdateStatusProvider.getInstance().finishInlineUpdate();
+        UpdateStatusProvider.getInstance().finishInlineUpdate(UpdateInteractionSource.FROM_INFOBAR);
     }
 
     private void retryUpdate() {
         if (mActivity == null) return;
-        UpdateStatusProvider.getInstance().startInlineUpdate(mActivity);
+        UpdateStatusProvider.getInstance().retryInlineUpdate(
+                UpdateInteractionSource.FROM_INFOBAR, mActivity);
     }
 
     private void showRestartInfobar() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
index 666bc49..ef289237 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
@@ -25,6 +25,7 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateInteractionSource;
 import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState;
 import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateStatus;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
@@ -208,13 +209,16 @@
                 }
                 break;
             case UpdateState.INLINE_UPDATE_AVAILABLE:
-                UpdateStatusProvider.getInstance().startInlineUpdate(activity);
+                UpdateStatusProvider.getInstance().startInlineUpdate(
+                        UpdateInteractionSource.FROM_MENU, activity);
                 break;
             case UpdateState.INLINE_UPDATE_READY:
-                UpdateStatusProvider.getInstance().finishInlineUpdate();
+                UpdateStatusProvider.getInstance().finishInlineUpdate(
+                        UpdateInteractionSource.FROM_MENU);
                 break;
             case UpdateState.INLINE_UPDATE_FAILED:
-                UpdateStatusProvider.getInstance().startInlineUpdate(activity);
+                UpdateStatusProvider.getInstance().retryInlineUpdate(
+                        UpdateInteractionSource.FROM_MENU, activity);
                 break;
             case UpdateState.UNSUPPORTED_OS_VERSION:
             // Intentional fall through.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
index 506f339..bd9d701 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
@@ -28,6 +28,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.base.task.AsyncTask.Status;
@@ -48,19 +49,37 @@
  * For manually testing this functionality, see {@link UpdateConfigs}.
  */
 public class UpdateStatusProvider implements ActivityStateListener {
-    /** Possible update states. */
+    /**
+     * Possible sources of user interaction regarding updates.
+     * Treat this as append only as it is used by UMA.
+     */
+    @IntDef({UpdateInteractionSource.FROM_MENU, UpdateInteractionSource.FROM_INFOBAR})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UpdateInteractionSource {
+        int FROM_MENU = 0;
+        int FROM_INFOBAR = 1;
+
+        int COUNT = 2;
+    }
+
+    /**
+     * Possible update states.
+     * Treat this as append only as it is used by UMA.
+     */
     @IntDef({UpdateState.NONE, UpdateState.UPDATE_AVAILABLE, UpdateState.UNSUPPORTED_OS_VERSION,
             UpdateState.INLINE_UPDATE_AVAILABLE, UpdateState.INLINE_UPDATE_DOWNLOADING,
             UpdateState.INLINE_UPDATE_READY, UpdateState.INLINE_UPDATE_FAILED})
     @Retention(RetentionPolicy.SOURCE)
     public @interface UpdateState {
-        int NONE = 1;
-        int UPDATE_AVAILABLE = 2;
-        int UNSUPPORTED_OS_VERSION = 3;
-        int INLINE_UPDATE_AVAILABLE = 4;
-        int INLINE_UPDATE_DOWNLOADING = 5;
-        int INLINE_UPDATE_READY = 6;
-        int INLINE_UPDATE_FAILED = 7;
+        int NONE = 0;
+        int UPDATE_AVAILABLE = 1;
+        int UNSUPPORTED_OS_VERSION = 2;
+        int INLINE_UPDATE_AVAILABLE = 3;
+        int INLINE_UPDATE_DOWNLOADING = 4;
+        int INLINE_UPDATE_READY = 5;
+        int INLINE_UPDATE_FAILED = 6;
+
+        int COUNT = 7;
     }
 
     /** A set of properties that represent the current update state for Chrome. */
@@ -119,6 +138,9 @@
     private final UpdateQuery mOmahaQuery;
     private @Nullable UpdateStatus mStatus;
 
+    /** Whether or not we've recorded the initial update status yet. */
+    private boolean mRecordedInitialStatus;
+
     /** @return Returns a singleton of {@link UpdateStatusProvider}. */
     public static UpdateStatusProvider getInstance() {
         return LazyHolder.INSTANCE;
@@ -181,16 +203,32 @@
 
     /**
      * Starts the inline update process, if possible.
+     * @source         The source of the action (the UI that caused it).
      * @param activity An {@link Activity} that will be used to interact with Play.
      */
-    public void startInlineUpdate(Activity activity) {
+    public void startInlineUpdate(@UpdateInteractionSource int source, Activity activity) {
         if (mStatus == null || mStatus.updateState != UpdateState.INLINE_UPDATE_AVAILABLE) return;
+        RecordHistogram.recordEnumeratedHistogram(
+                "GoogleUpdate.Inline.UI.Start.Source", source, UpdateInteractionSource.COUNT);
+        mInlineController.startUpdate(activity);
+    }
+
+    /**
+     * Retries the inline update process, if possible.
+     * @param activity An {@link Activity} that will be used to interact with Play.
+     */
+    public void retryInlineUpdate(@UpdateInteractionSource int source, Activity activity) {
+        if (mStatus == null || mStatus.updateState != UpdateState.INLINE_UPDATE_AVAILABLE) return;
+        RecordHistogram.recordEnumeratedHistogram(
+                "GoogleUpdate.Inline.UI.Retry.Source", source, UpdateInteractionSource.COUNT);
         mInlineController.startUpdate(activity);
     }
 
     /** Finishes the inline update process, which may involve restarting the app. */
-    public void finishInlineUpdate() {
+    public void finishInlineUpdate(@UpdateInteractionSource int source) {
         if (mStatus == null || mStatus.updateState != UpdateState.INLINE_UPDATE_READY) return;
+        RecordHistogram.recordEnumeratedHistogram(
+                "GoogleUpdate.Inline.UI.Install.Source", source, UpdateInteractionSource.COUNT);
         mInlineController.completeUpdate();
     }
 
@@ -243,15 +281,44 @@
             }
         } else {
             @UpdateState
+            int omahaState = mOmahaQuery.getResult().updateState;
+            @UpdateState
             int inlineState = mInlineController.getStatus();
-            if (inlineState != UpdateState.NONE) {
-                mStatus.updateState = inlineState;
-            }
+            mStatus.updateState = resolveOmahaAndInlineStatus(
+                    UpdateConfigs.getConfiguration(), omahaState, inlineState);
+        }
+
+        if (!mRecordedInitialStatus) {
+            RecordHistogram.recordEnumeratedHistogram(
+                    "GoogleUpdate.StartUp.State", mStatus.updateState, UpdateState.COUNT);
+            mRecordedInitialStatus = true;
         }
 
         pingObservers();
     }
 
+    @VisibleForTesting
+    static @UpdateState int resolveOmahaAndInlineStatus(
+            @UpdateConfigs.UpdateFlowConfiguration int configuration, @UpdateState int omahaState,
+            @UpdateState int inlineState) {
+        switch (configuration) {
+            case UpdateConfigs.UpdateFlowConfiguration.NEVER_SHOW:
+                return UpdateState.NONE;
+            case UpdateConfigs.UpdateFlowConfiguration.INLINE_ONLY:
+                if (omahaState != UpdateState.UPDATE_AVAILABLE) return omahaState;
+                if (inlineState == UpdateState.NONE) return UpdateState.NONE;
+                return inlineState;
+            case UpdateConfigs.UpdateFlowConfiguration.BEST_EFFORT:
+                if (omahaState != UpdateState.UPDATE_AVAILABLE) return omahaState;
+                if (inlineState == UpdateState.NONE) return omahaState;
+                return inlineState;
+            case UpdateConfigs.UpdateFlowConfiguration.INTENT_ONLY: // Intentional fall through.
+            default:
+                // Fall back to use Omaha only and use the old flow.
+                return omahaState;
+        }
+    }
+
     private static final class LazyHolder {
         private static final UpdateStatusProvider INSTANCE = new UpdateStatusProvider();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/PlayInlineUpdateController.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/PlayInlineUpdateController.java
index 4dc37b1..597d515 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/PlayInlineUpdateController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/inline/PlayInlineUpdateController.java
@@ -8,6 +8,7 @@
 import android.content.IntentSender.SendIntentException;
 import android.os.Handler;
 import android.os.Looper;
+import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 
 import com.google.android.play.core.appupdate.AppUpdateInfo;
@@ -15,12 +16,17 @@
 import com.google.android.play.core.install.InstallState;
 import com.google.android.play.core.install.InstallStateUpdatedListener;
 import com.google.android.play.core.install.model.AppUpdateType;
+import com.google.android.play.core.install.model.InstallErrorCode;
 import com.google.android.play.core.install.model.InstallStatus;
 import com.google.android.play.core.install.model.UpdateAvailability;
 
 import org.chromium.base.Log;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Helper class for gluing interactions with the Play store's AppUpdateManager with Chrome.  This
  * involves hooking up to Play as a listener for install state changes, should only happen if we are
@@ -28,6 +34,50 @@
  */
 public class PlayInlineUpdateController
         implements InlineUpdateController, InstallStateUpdatedListener {
+    /**
+     * Converts Play's InstallErrorCode enum to a stable monotomically incrementing Chrome enum.
+     * This is used for metric stability.
+     * Treat this as append only as it is used by UMA.
+     */
+    @IntDef({InstallErrorCodeMetrics.NO_ERROR, InstallErrorCodeMetrics.NO_ERROR_PARTIALLY_ALLOWED,
+            InstallErrorCodeMetrics.ERROR_UNKNOWN, InstallErrorCodeMetrics.ERROR_API_NOT_AVAILABLE,
+            InstallErrorCodeMetrics.ERROR_INVALID_REQUEST,
+            InstallErrorCodeMetrics.ERROR_INSTALL_UNAVAILABLE,
+            InstallErrorCodeMetrics.ERROR_INSTALL_NOT_ALLOWED,
+            InstallErrorCodeMetrics.ERROR_DOWNLOAD_NOT_PRESENT,
+            InstallErrorCodeMetrics.ERROR_INTERNAL_ERROR, InstallErrorCodeMetrics.ERROR_UNTRACKED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InstallErrorCodeMetrics {
+        int NO_ERROR = 0;
+        int NO_ERROR_PARTIALLY_ALLOWED = 1;
+        int ERROR_UNKNOWN = 2;
+        int ERROR_API_NOT_AVAILABLE = 3;
+        int ERROR_INVALID_REQUEST = 4;
+        int ERROR_INSTALL_UNAVAILABLE = 5;
+        int ERROR_INSTALL_NOT_ALLOWED = 6;
+        int ERROR_DOWNLOAD_NOT_PRESENT = 7;
+        int ERROR_INTERNAL_ERROR = 8;
+        int ERROR_UNTRACKED = 9;
+
+        int COUNT = 10;
+    }
+
+    /**
+     * A list of possible Play API call site failures.
+     * Treat this as append only as it is used by UMA.
+     */
+    @IntDef({CallFailure.START_FAILED, CallFailure.START_EXCEPTION, CallFailure.COMPLETE_FAILED,
+            CallFailure.QUERY_FAILED})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface CallFailure {
+        int START_FAILED = 0;
+        int START_EXCEPTION = 1;
+        int COMPLETE_FAILED = 2;
+        int QUERY_FAILED = 3;
+
+        int COUNT = 4;
+    }
+
     private static final String TAG = "PlayInline";
     private static final int RESULT_IN_APP_UPDATE_FAILED = 1;
     private static final int REQUEST_CODE = 8123;
@@ -80,9 +130,12 @@
             boolean success = mAppUpdateManager.startUpdateFlowForResult(
                     mAppUpdateInfo, AppUpdateType.FLEXIBLE, activity, REQUEST_CODE);
             Log.i(TAG, "startUpdateFlowForResult() returned " + success);
+
+            if (!success) recordCallFailure(CallFailure.START_FAILED);
         } catch (SendIntentException exception) {
             mInstallStatus = InstallStatus.FAILED;
             Log.i(TAG, "startUpdateFlowForResult() threw an exception.");
+            recordCallFailure(CallFailure.START_EXCEPTION);
         }
         // TODO(dtrainor): Use success.
     }
@@ -96,6 +149,7 @@
                 })
                 .addOnFailureListener(exception -> {
                     Log.i(TAG, "completeUpdate() failed.");
+                    recordCallFailure(CallFailure.COMPLETE_FAILED);
                     mInstallStatus = InstallStatus.FAILED;
                     pushStatus();
                 });
@@ -106,6 +160,14 @@
     public void onStateUpdate(InstallState state) {
         Log.i(TAG,
                 "onStateUpdate(" + state.installStatus() + ", " + state.installErrorCode() + ")");
+
+        if (state.installStatus() != mInstallStatus) {
+            RecordHistogram.recordEnumeratedHistogram("GoogleUpdate.Inline.StateChange.Error."
+                            + installStatusToEnumSuffix(state.installStatus()),
+                    installErrorCodeToMetrics(state.installErrorCode()),
+                    InstallErrorCodeMetrics.COUNT);
+        }
+
         mInstallStatus = state.installStatus();
         pushStatus();
     }
@@ -126,6 +188,7 @@
                     mUpdateAvailability = UpdateAvailability.UNKNOWN;
                     mInstallStatus = InstallStatus.UNKNOWN;
                     Log.i(TAG, "pullCurrentState() failed.");
+                    recordCallFailure(CallFailure.QUERY_FAILED);
                     pushStatus();
                 });
     }
@@ -173,4 +236,60 @@
 
         return newStatus;
     }
+
+    private static String installStatusToEnumSuffix(@InstallStatus int status) {
+        switch (status) {
+            case InstallStatus.UNKNOWN:
+                return "Unknown";
+            case InstallStatus.REQUIRES_UI_INTENT:
+                return "RequiresUiIntent";
+            case InstallStatus.PENDING:
+                return "Pending";
+            case InstallStatus.DOWNLOADING:
+                return "Downloading";
+            case InstallStatus.DOWNLOADED:
+                return "Downloaded";
+            case InstallStatus.INSTALLING:
+                return "Installing";
+            case InstallStatus.INSTALLED:
+                return "Installed";
+            case InstallStatus.FAILED:
+                return "Failed";
+            case InstallStatus.CANCELED:
+                return "Canceled";
+            default:
+                return "Untracked";
+        }
+    }
+
+    private static @InstallErrorCodeMetrics int installErrorCodeToMetrics(
+            @InstallErrorCode int error) {
+        switch (error) {
+            case InstallErrorCode.NO_ERROR:
+                return InstallErrorCodeMetrics.NO_ERROR;
+            case InstallErrorCode.NO_ERROR_PARTIALLY_ALLOWED:
+                return InstallErrorCodeMetrics.NO_ERROR_PARTIALLY_ALLOWED;
+            case InstallErrorCode.ERROR_UNKNOWN:
+                return InstallErrorCodeMetrics.ERROR_UNKNOWN;
+            case InstallErrorCode.ERROR_API_NOT_AVAILABLE:
+                return InstallErrorCodeMetrics.ERROR_API_NOT_AVAILABLE;
+            case InstallErrorCode.ERROR_INVALID_REQUEST:
+                return InstallErrorCodeMetrics.ERROR_INVALID_REQUEST;
+            case InstallErrorCode.ERROR_INSTALL_UNAVAILABLE:
+                return InstallErrorCodeMetrics.ERROR_INSTALL_UNAVAILABLE;
+            case InstallErrorCode.ERROR_INSTALL_NOT_ALLOWED:
+                return InstallErrorCodeMetrics.ERROR_INSTALL_NOT_ALLOWED;
+            case InstallErrorCode.ERROR_DOWNLOAD_NOT_PRESENT:
+                return InstallErrorCodeMetrics.ERROR_DOWNLOAD_NOT_PRESENT;
+            case InstallErrorCode.ERROR_INTERNAL_ERROR:
+                return InstallErrorCodeMetrics.ERROR_INTERNAL_ERROR;
+            default:
+                return InstallErrorCodeMetrics.ERROR_UNTRACKED;
+        }
+    }
+
+    private static void recordCallFailure(@CallFailure int failure) {
+        RecordHistogram.recordEnumeratedHistogram(
+                "GoogleUpdate.Inline.CallFailure", failure, CallFailure.COUNT);
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java
index 997dd54..1405cb2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java
@@ -53,7 +53,7 @@
         } else if (SuggestionListProperties.EMBEDDER.equals(propertyKey)) {
             view.listView.setEmbedder(model.get(SuggestionListProperties.EMBEDDER));
         } else if (SuggestionListProperties.SUGGESTION_MODELS.equals(propertyKey)) {
-            view.adapter.updateSuggestions(model.get(SuggestionListProperties.SUGGESTION_MODELS));
+            view.adapter.updateModels(model.get(SuggestionListProperties.SUGGESTION_MODELS));
             view.listView.setSelection(0);
         } else if (SuggestionListProperties.USE_DARK_BACKGROUND.equals(propertyKey)) {
             view.listView.refreshPopupBackground(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 26d4038e..81032a4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.payments;
 
-import android.app.Activity;
 import android.content.Context;
 import android.os.Handler;
 import android.support.annotation.Nullable;
@@ -485,9 +484,38 @@
                 && mMethodData.keySet().iterator().next().startsWith(UrlConstants.HTTPS_URL_PREFIX);
     }
 
-    private void buildUI(Activity activity) {
+    /** @return Whether the UI was built. */
+    private boolean buildUI(ChromeActivity activity) {
         assert activity != null;
 
+        // Catch any time the user switches tabs. Because the dialog is modal, a user shouldn't be
+        // allowed to switch tabs, which can happen if the user receives an external Intent.
+        mObservedTabModelSelector = activity.getTabModelSelector();
+        mObservedTabModel = activity.getCurrentTabModel();
+        mObservedTabModelSelector.addObserver(mSelectorObserver);
+        mObservedTabModel.addObserver(mTabModelObserver);
+
+        // Only the currently selected tab is allowed to show the payment UI.
+        if (TabModelUtils.getCurrentWebContents(mObservedTabModel) != mWebContents) {
+            mJourneyLogger.setNotShown(NotShownReason.OTHER);
+            disconnectFromClientWithDebugMessage(
+                    "Background tab is not allowed to show PaymentRequest UI");
+            if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
+            return false;
+        }
+
+        // Catch any time the user enters the overview mode and dismiss the payment UI.
+        if (activity instanceof ChromeTabbedActivity) {
+            mOverviewModeBehavior = ((ChromeTabbedActivity) activity).getOverviewModeBehavior();
+            if (mOverviewModeBehavior.overviewVisible()) {
+                mJourneyLogger.setNotShown(NotShownReason.OTHER);
+                disconnectFromClientWithDebugMessage("In tab overview mode");
+                if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
+                return false;
+            }
+            mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
+        }
+
         List<AutofillProfile> profiles = null;
         if (mRequestShipping || mRequestPayerName || mRequestPayerPhone || mRequestPayerEmail) {
             profiles = PersonalDataManager.getInstance().getProfilesToSuggest(
@@ -531,6 +559,8 @@
         mAddressEditor.setEditorDialog(mUI.getEditorDialog());
         mCardEditor.setEditorDialog(mUI.getCardEditorDialog());
         if (mContactEditor != null) mContactEditor.setEditorDialog(mUI.getEditorDialog());
+
+        return true;
     }
 
     private void createShippingSection(
@@ -622,43 +652,16 @@
             return;
         }
 
-        // Catch any time the user switches tabs. Because the dialog is modal, a user shouldn't be
-        // allowed to switch tabs, which can happen if the user receives an external Intent.
-        mObservedTabModelSelector = chromeActivity.getTabModelSelector();
-        mObservedTabModel = chromeActivity.getCurrentTabModel();
-        mObservedTabModelSelector.addObserver(mSelectorObserver);
-        mObservedTabModel.addObserver(mTabModelObserver);
-
-        // Only the currently selected tab is allowed to show the payment UI.
-        if (TabModelUtils.getCurrentWebContents(mObservedTabModel) != mWebContents) {
-            mJourneyLogger.setNotShown(NotShownReason.OTHER);
-            disconnectFromClientWithDebugMessage(
-                    "Background tab is not allowed to show PaymentRequest UI");
-            if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
-            return;
-        }
-
-        // Catch any time the user enters the overview mode and dismiss the payment UI.
-        if (chromeActivity instanceof ChromeTabbedActivity) {
-            mOverviewModeBehavior =
-                    ((ChromeTabbedActivity) chromeActivity).getOverviewModeBehavior();
-            if (mOverviewModeBehavior.overviewVisible()) {
-                mJourneyLogger.setNotShown(NotShownReason.OTHER);
-                disconnectFromClientWithDebugMessage("In tab overview mode");
-                if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
-                return;
-            }
-            mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
-        }
-
         mIsUserGestureShow = isUserGesture;
-        buildUI(chromeActivity);
-        if (!mShouldSkipShowingPaymentRequestUi) mUI.show();
+        if (!mShouldSkipShowingPaymentRequestUi) {
+            if (!buildUI(chromeActivity)) return;
+            mUI.show();
+        }
 
-        triggerPaymentAppUiSkipIfApplicable();
+        triggerPaymentAppUiSkipIfApplicable(chromeActivity);
     }
 
-    private void triggerPaymentAppUiSkipIfApplicable() {
+    private void triggerPaymentAppUiSkipIfApplicable(ChromeActivity chromeActivity) {
         // If we are skipping showing the Payment Request UI, we should call into the
         // PaymentApp immediately after we determine the instruments are ready and UI is shown.
         if (mShouldSkipShowingPaymentRequestUi && isFinishedQueryingPaymentApps()
@@ -667,6 +670,7 @@
 
             PaymentInstrument selectedInstrument =
                     (PaymentInstrument) mPaymentMethodsSection.getSelectedItem();
+            if (!buildUI(chromeActivity)) return;
 
             // Do not skip to payment app if it is not the only one, it's not pre-selected, or if
             // skip-UI requires a user gesture in show(), which was not present.
@@ -1829,15 +1833,20 @@
             respondHasEnrolledInstrumentQuery(mHasEnrolledInstrument);
         }
 
+        ChromeActivity chromeActivity = ChromeActivity.fromWebContents(mWebContents);
+        if (chromeActivity == null) {
+            mJourneyLogger.setNotShown(NotShownReason.OTHER);
+            disconnectFromClientWithDebugMessage("Unable to find Chrome activity");
+            if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
+            return;
+        }
+
         // The list of payment instruments is ready to display.
         mPaymentMethodsSection = new SectionInformation(PaymentRequestUI.DataType.PAYMENT_METHODS,
                 selection, new ArrayList<>(mPendingInstruments));
         if (mPaymentMethodsSectionAdditionalTextResourceId != 0) {
-            Context context = ChromeActivity.fromWebContents(mWebContents);
-            if (context != null) {
-                mPaymentMethodsSection.setAdditionalText(
-                        context.getString(mPaymentMethodsSectionAdditionalTextResourceId));
-            }
+            mPaymentMethodsSection.setAdditionalText(
+                    chromeActivity.getString(mPaymentMethodsSectionAdditionalTextResourceId));
         }
 
         // Record the number suggested payment methods and whether at least one of them was
@@ -1855,7 +1864,7 @@
 
         SettingsAutofillAndPaymentsObserver.getInstance().registerObserver(this);
 
-        triggerPaymentAppUiSkipIfApplicable();
+        triggerPaymentAppUiSkipIfApplicable(chromeActivity);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SingleCategoryPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SingleCategoryPreferences.java
index 3e18e05..1aa2e8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SingleCategoryPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SingleCategoryPreferences.java
@@ -151,9 +151,9 @@
 
             resetList();
 
-            boolean hasEntries = mCategory.showSites(SiteSettingsCategory.Type.USB)
-                    ? addChosenObjects(sites)
-                    : addWebsites(sites);
+            int chooserDataType = mCategory.getChooserDataType();
+            boolean hasEntries =
+                    chooserDataType == -1 ? addWebsites(sites) : addChosenObjects(sites);
 
             if (!hasEntries && mEmptyView != null)
                 mEmptyView.setText(R.string.no_saved_website_settings);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SiteSettingsCategory.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SiteSettingsCategory.java
index e24cf81..fa1c27d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SiteSettingsCategory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/SiteSettingsCategory.java
@@ -169,6 +169,19 @@
     }
 
     /**
+     * Get the chooser data type {@link ContentSettingsType} corresponding to the given
+     * {@link ContentSettingsType}.
+     */
+    public static int chooserDataTypeFrom(@ContentSettingsType int type) {
+        switch (type) {
+            case ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_GUARD:
+                return ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_CHOOSER_DATA;
+            default:
+                return -1; // Conversion unavailable.
+        }
+    }
+
+    /**
      * Convert Type into preference String
      */
     public static String preferenceKey(@Type int type) {
@@ -223,6 +236,14 @@
     }
 
     /**
+     * Returns the {@link ContentSettingsType} representing the chooser data type for this category,
+     * or -1 if this category does not have a chooser data type.
+     */
+    public @ContentSettingsType int getChooserDataType() {
+        return chooserDataTypeFrom(contentSettingsType(mCategory));
+    }
+
+    /**
      * Returns whether this category is the specified type.
      */
     public boolean showSites(@Type int type) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java
index 7d3d038..1ec681f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java
@@ -132,7 +132,8 @@
         // Autoplay permission is per-origin.
         queue.add(new ExceptionInfoFetcher(ContentSettingsType.CONTENT_SETTINGS_TYPE_AUTOPLAY));
         // USB device permission is per-origin and per-embedder.
-        queue.add(new UsbInfoFetcher());
+        queue.add(new ChooserExceptionInfoFetcher(
+                ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_GUARD));
         // Clipboard info is per-origin.
         queue.add(new PermissionInfoFetcher(PermissionInfo.Type.CLIPBOARD));
         // Sensors permission is per-origin.
@@ -211,7 +212,8 @@
             queue.add(new ExceptionInfoFetcher(ContentSettingsType.CONTENT_SETTINGS_TYPE_AUTOPLAY));
         } else if (category.showSites(SiteSettingsCategory.Type.USB)) {
             // USB device permission is per-origin.
-            queue.add(new UsbInfoFetcher());
+            queue.add(new ChooserExceptionInfoFetcher(
+                    ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_GUARD));
         } else if (category.showSites(SiteSettingsCategory.Type.CLIPBOARD)) {
             // Clipboard permission is per-origin.
             queue.add(new PermissionInfoFetcher(PermissionInfo.Type.CLIPBOARD));
@@ -313,11 +315,19 @@
         }
     }
 
-    private class UsbInfoFetcher extends Task {
+    private class ChooserExceptionInfoFetcher extends Task {
+        final @ContentSettingsType int mChooserDataType;
+
+        public ChooserExceptionInfoFetcher(@ContentSettingsType int type) {
+            mChooserDataType = SiteSettingsCategory.chooserDataTypeFrom(type);
+        }
+
         @Override
         public void run() {
-            for (ChosenObjectInfo info : WebsitePreferenceBridge.getChosenObjectInfo(
-                         ContentSettingsType.CONTENT_SETTINGS_TYPE_USB_CHOOSER_DATA)) {
+            if (mChooserDataType == -1) return;
+
+            for (ChosenObjectInfo info :
+                    WebsitePreferenceBridge.getChosenObjectInfo(mChooserDataType)) {
                 String origin = info.getOrigin();
                 if (origin == null) continue;
                 findOrCreateSite(origin, info.getEmbedder()).addChosenObjectInfo(info);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/OWNERS
new file mode 100644
index 0000000..e58f618d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/OWNERS
@@ -0,0 +1,3 @@
+file://components/send_tab_to_self/OWNERS
+
+# COMPONENT: UI>Browser>Sharing
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
index b961ce7..9eefd34 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
@@ -6,6 +6,7 @@
   'Tab\.java': [
     "-chrome",
     "+chrome/android/java/src/org/chromium/chrome/browser/tab",
+    "-components",
     "+components/embedder_support/android/java/src/org/chromium/components/embedder_support/view",
     "+components/navigation_interception/android/java/src/org/chromium/components/navigation_interception",
     "+ui/android/java/src/org/chromium/ui/base",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridContainerViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridContainerViewBinder.java
index d586932..8eb9d48 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridContainerViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridContainerViewBinder.java
@@ -18,6 +18,9 @@
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
+/**
+ * ViewBinder for TabGridContainer.
+ */
 class TabGridContainerViewBinder {
     /**
      * Bind the given model to the given view, updating the payload in propertyKey.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java
index 87f9d1e0..f6ba2e39 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java
@@ -16,7 +16,7 @@
 import org.chromium.chrome.R;
 
 /**
- * {@link android.support.v7.widget.RecyclerView.ViewHolder} for tab grid. Owns the tab info card
+ * {@link RecyclerView.ViewHolder} for tab grid. Owns the tab info card
  * and the associated view hierarchy.
  */
 class TabGridViewHolder extends RecyclerView.ViewHolder {
@@ -24,9 +24,9 @@
     public final TextView title;
     public final ImageView thumbnail;
     public final ImageView closeButton;
-    public int mTabId;
+    private int mTabId;
 
-    public TabGridViewHolder(View itemView) {
+    private TabGridViewHolder(View itemView) {
         super(itemView);
         this.thumbnail = itemView.findViewById(R.id.tab_thumbnail);
         this.title = itemView.findViewById(R.id.tab_title);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabProperties.java
index 2fe1a3e..2f5cf26e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabProperties.java
@@ -39,5 +39,5 @@
                     THUMBNAIL_FETCHER, TITLE, IS_SELECTED};
 
     public static final PropertyKey[] ALL_KEYS_TAB_STRIP = new PropertyKey[] {
-            TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, IS_SELECTED};
+            TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, IS_SELECTED, TITLE};
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java
index bd77276..16231f95 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java
@@ -16,9 +16,9 @@
 import org.chromium.ui.modelutil.PropertyModel;
 
 /**
- * A coordinator for BottomTabStrip component. Manages the communication with
- * {@link TabListCoordinator} & @{link BottomTabGridCoordinator} as well as the
- * life-cycle of shared component objects.
+ * A coordinator for TabStripBottomToolbar component. Manages the communication with
+ * {@link TabListCoordinator}, {@link BottomTabGridCoordinator}, and
+ * {@link TabStripToolbarCoordinator}, as well as the life-cycle of shared component objects.
  */
 public class TabStripBottomToolbarCoordinator
         implements Destroyable, TabStripBottomToolbarMediator.ResetHandler {
@@ -32,8 +32,8 @@
     /**
      * Creates a new {@link TabStripBottomToolbarCoordinator}
      */
-    public TabStripBottomToolbarCoordinator(Context context, ViewGroup parentView) {
-        mContext = context;
+    public TabStripBottomToolbarCoordinator(ViewGroup parentView) {
+        mContext = parentView.getContext();
         mTabStripToolbarModel = new PropertyModel(TabStripToolbarViewProperties.ALL_KEYS);
 
         mTabStripToolbarCoordinator =
@@ -59,7 +59,7 @@
 
     /**
      * Handles a reset event originated from {@link TabStripBottomToolbarMediator}
-     * when the bottom sheet is collaped.
+     * when the bottom sheet is collapsed.
      *
      * @param tabModel current {@link TabModel} instance.
      */
@@ -70,7 +70,7 @@
 
     /**
      * Handles a reset event originated from {@link TabStripBottomToolbarMediator}
-     * when the bottom sheet is expanded and the component.
+     * when the bottom sheet is expanded.
      *
      * @param tabModel current {@link TabModel} instance.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java
index 444890d..5925ad2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java
@@ -29,7 +29,7 @@
     interface ResetHandler {
         /**
          * Handles a reset event originated from {@link TabStripBottomToolbarMediator}
-         * when the bottom sheet is collaped.
+         * when the bottom sheet is collapsed.
          *
          * @param tabModel current {@link TabModel} instance.
          */
@@ -37,7 +37,7 @@
 
         /**
          * Handles a reset event originated from {@link TabStripBottomToolbarMediator}
-         * when the bottom sheet is expanded and the component.
+         * when the bottom sheet is expanded.
          *
          * @param tabModel current {@link TabModel} instance.
          */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java
index b0104e5..8b14fa5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java
@@ -17,7 +17,7 @@
 import org.chromium.ui.modelutil.PropertyModel;
 
 /**
- * {@link org.chromium.ui.modelutil.SimpleRecyclerViewMcp.ViewBinder} for tab grid.
+ * {@link org.chromium.ui.modelutil.SimpleRecyclerViewMcp.ViewBinder} for tab strip.
  * This class supports both full and partial updates to the {@link TabStripViewHolder}.
  */
 class TabStripViewBinder {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java
index 89b14cfa..1dab4e1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java
@@ -13,13 +13,13 @@
 import org.chromium.chrome.R;
 
 /**
- * {@link RecyclerView.ViewHolder} for tabstrip.
+ * {@link RecyclerView.ViewHolder} for tab strip.
  */
 class TabStripViewHolder extends RecyclerView.ViewHolder {
-    public int mTabId;
+    private int mTabId;
     public final ImageButton button;
 
-    public TabStripViewHolder(View itemView) {
+    private TabStripViewHolder(View itemView) {
         super(itemView);
         this.button = itemView.findViewById(R.id.tab_strip_item_button);
     }
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 f1b21971..b7a64eb 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
@@ -303,11 +303,6 @@
             }
         };
 
-        mIsBottomToolbarVisible = FeatureUtilities.isBottomToolbarEnabled()
-                && (!FeatureUtilities.isAdaptiveToolbarEnabled()
-                        || mActivity.getResources().getConfiguration().orientation
-                                != Configuration.ORIENTATION_LANDSCAPE);
-
         mIncognitoStateProvider = new IncognitoStateProvider(mActivity);
         mTabCountProvider = new TabCountProvider();
         mThemeColorProvider = themeColorProvider;
@@ -315,7 +310,6 @@
 
         mToolbarProvider = AsyncViewProvider.of(controlContainer, R.id.toolbar_stub, R.id.toolbar);
         mToolbar = new TopToolbarCoordinator(controlContainer, mToolbarProvider);
-        mToolbar.onBottomToolbarVisibilityChanged(mIsBottomToolbarVisible);
         mToolbarProvider.whenLoaded((toolbar)
                                             -> onToolbarInflationComplete(menuHandler,
                                                     appMenuPropertiesDelegate, invalidator));
@@ -786,7 +780,13 @@
                 mActivity.findViewById(R.id.bottom_controls_stub),
                 mActivity.getActivityTabProvider(), homeButtonListener, searchAcceleratorListener,
                 shareButtonListener);
+
+        mIsBottomToolbarVisible = FeatureUtilities.isBottomToolbarEnabled()
+                && (!FeatureUtilities.isAdaptiveToolbarEnabled()
+                        || mActivity.getResources().getConfiguration().orientation
+                                != Configuration.ORIENTATION_LANDSCAPE);
         mBottomControlsCoordinator.setBottomControlsVisible(mIsBottomToolbarVisible);
+        mToolbar.onBottomToolbarVisibilityChanged(mIsBottomToolbarVisible);
 
         Toast.setGlobalExtraYOffset(
                 mActivity.getResources().getDimensionPixelSize(R.dimen.bottom_toolbar_height));
@@ -978,7 +978,7 @@
                     mTabModelSelector.getModel(isIncognito).closeAllTabs();
                 };
                 mAppMenuButtonHelper.setOnClickRunnable(() -> recordBottomToolbarUseForIPH());
-                mBottomControlsCoordinator.initializeWithNative(
+                mBottomControlsCoordinator.initializeWithNative(mActivity,
                         mActivity.getCompositorViewHolder().getResourceManager(),
                         mActivity.getCompositorViewHolder().getLayoutManager(),
                         wrapBottomToolbarClickListenerForIPH(tabSwitcherClickHandler),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
index 284d42d..e93ec6b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
@@ -4,21 +4,25 @@
 
 package org.chromium.chrome.browser.toolbar.bottom;
 
+import android.support.annotation.Nullable;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
+import org.chromium.chrome.browser.tasks.tab_list_ui.TabStripBottomToolbarCoordinator;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.toolbar.bottom.BottomControlsViewBinder.ViewHolder;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -36,7 +40,8 @@
     private final BottomControlsMediator mMediator;
 
     /** The coordinator for the split toolbar's bottom toolbar component. */
-    private final BottomToolbarCoordinator mBottomToolbarCoordinator;
+    private @Nullable BottomToolbarCoordinator mBottomToolbarCoordinator;
+    private @Nullable TabStripBottomToolbarCoordinator mTabStripCoordinator;
 
     /**
      * Build the coordinator that manages the bottom controls.
@@ -63,9 +68,14 @@
         mMediator = new BottomControlsMediator(model, fullscreenManager,
                 root.getResources().getDimensionPixelOffset(R.dimen.bottom_toolbar_height));
 
-        mBottomToolbarCoordinator = new BottomToolbarCoordinator(
-                root.findViewById(R.id.bottom_toolbar_stub), tabProvider, homeButtonListener,
-                searchAcceleratorListener, shareButtonListener);
+        if (FeatureUtilities.isTabGroupsAndroidEnabled()) {
+            mTabStripCoordinator = new TabStripBottomToolbarCoordinator(
+                    root.findViewById(R.id.bottom_container_slot));
+        } else {
+            mBottomToolbarCoordinator = new BottomToolbarCoordinator(
+                    root.findViewById(R.id.bottom_toolbar_stub), tabProvider, homeButtonListener,
+                    searchAcceleratorListener, shareButtonListener);
+        }
     }
 
     /**
@@ -73,6 +83,7 @@
      * dependencies.
      * <p>
      * Calling this must occur after the native library have completely loaded.
+     * @param chromeActivity ChromeActivity instance to use.
      * @param resourceManager A {@link ResourceManager} for loading textures into the compositor.
      * @param layoutManager A {@link LayoutManager} to attach overlays to.
      * @param tabSwitcherListener An {@link OnClickListener} that is triggered when the
@@ -88,56 +99,80 @@
      * @param incognitoStateProvider Notifies components when incognito mode is entered or exited.
      * @param topToolbarRoot The root {@link ViewGroup} of the top toolbar.
      */
-    public void initializeWithNative(ResourceManager resourceManager, LayoutManager layoutManager,
-            OnClickListener tabSwitcherListener, OnClickListener newTabClickListener,
-            OnClickListener closeTabsClickListener, AppMenuButtonHelper menuButtonHelper,
-            OverviewModeBehavior overviewModeBehavior, WindowAndroid windowAndroid,
-            TabCountProvider tabCountProvider, IncognitoStateProvider incognitoStateProvider,
-            ViewGroup topToolbarRoot) {
+    public void initializeWithNative(ChromeActivity chromeActivity, ResourceManager resourceManager,
+            LayoutManager layoutManager, OnClickListener tabSwitcherListener,
+            OnClickListener newTabClickListener, OnClickListener closeTabsClickListener,
+            AppMenuButtonHelper menuButtonHelper, OverviewModeBehavior overviewModeBehavior,
+            WindowAndroid windowAndroid, TabCountProvider tabCountProvider,
+            IncognitoStateProvider incognitoStateProvider, ViewGroup topToolbarRoot) {
         mMediator.setLayoutManager(layoutManager);
         mMediator.setResourceManager(resourceManager);
         mMediator.setToolbarSwipeHandler(layoutManager.getToolbarSwipeHandler());
         mMediator.setWindowAndroid(windowAndroid);
 
-        mBottomToolbarCoordinator.initializeWithNative(tabSwitcherListener, newTabClickListener,
-                closeTabsClickListener, menuButtonHelper, overviewModeBehavior, tabCountProvider,
-                incognitoStateProvider, topToolbarRoot);
+        if (mBottomToolbarCoordinator != null) {
+            mBottomToolbarCoordinator.initializeWithNative(tabSwitcherListener, newTabClickListener,
+                    closeTabsClickListener, menuButtonHelper, overviewModeBehavior,
+                    tabCountProvider, incognitoStateProvider, topToolbarRoot);
+        }
+
+        if (mTabStripCoordinator != null) {
+            mTabStripCoordinator.initializeWithNative(chromeActivity.getTabModelSelector(),
+                    chromeActivity.getTabContentManager(), chromeActivity,
+                    chromeActivity.getBottomSheetController());
+            mMediator.setBottomControlsVisible(true);
+        }
     }
 
     /**
      * @param isVisible Whether the bottom control is visible.
      */
     public void setBottomControlsVisible(boolean isVisible) {
+        // TabStripCoordinator manages its own visibility
+        if (mTabStripCoordinator != null) return;
+
         mMediator.setBottomControlsVisible(isVisible);
-        mBottomToolbarCoordinator.setBottomToolbarVisible(isVisible);
+        if (mBottomToolbarCoordinator != null) {
+            mBottomToolbarCoordinator.setBottomToolbarVisible(isVisible);
+        }
     }
 
     /**
      * Show the update badge over the bottom toolbar's app menu.
      */
     public void showAppMenuUpdateBadge() {
-        mBottomToolbarCoordinator.showAppMenuUpdateBadge();
+        if (mBottomToolbarCoordinator != null) {
+            mBottomToolbarCoordinator.showAppMenuUpdateBadge();
+        }
     }
 
     /**
      * Remove the update badge.
      */
     public void removeAppMenuUpdateBadge() {
-        mBottomToolbarCoordinator.removeAppMenuUpdateBadge();
+        if (mBottomToolbarCoordinator != null) {
+            mBottomToolbarCoordinator.removeAppMenuUpdateBadge();
+        }
     }
 
     /**
      * @return Whether the update badge is showing.
      */
     public boolean isShowingAppMenuUpdateBadge() {
-        return mBottomToolbarCoordinator.isShowingAppMenuUpdateBadge();
+        if (mBottomToolbarCoordinator != null) {
+            return mBottomToolbarCoordinator.isShowingAppMenuUpdateBadge();
+        }
+        return false;
     }
 
     /**
      * @return The wrapper for the browsing mode toolbar's app menu button.
      */
     public MenuButton getMenuButtonWrapper() {
-        return mBottomToolbarCoordinator.getMenuButtonWrapper();
+        if (mBottomToolbarCoordinator != null) {
+            return mBottomToolbarCoordinator.getMenuButtonWrapper();
+        }
+        return null;
     }
 
     /**
@@ -153,7 +188,8 @@
      * Clean up any state when the bottom controls component is destroyed.
      */
     public void destroy() {
-        mBottomToolbarCoordinator.destroy();
+        if (mBottomToolbarCoordinator != null) mBottomToolbarCoordinator.destroy();
+        if (mTabStripCoordinator != null) mTabStripCoordinator.destroy();
         mMediator.destroy();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java
index be4df4d..37f1df9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java
@@ -44,8 +44,10 @@
             final boolean showCompositedView =
                     model.get(BottomControlsProperties.COMPOSITED_VIEW_VISIBLE);
             view.sceneLayer.setIsVisible(showCompositedView);
-            model.get(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT)
-                    .setBottomToolbarSceneLayersVisibility(showCompositedView);
+            if (model.get(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT) != null) {
+                model.get(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT)
+                        .setBottomToolbarSceneLayersVisibility(showCompositedView);
+            }
             model.get(BottomControlsProperties.LAYOUT_MANAGER).requestUpdate();
         } else if (BottomControlsProperties.LAYOUT_MANAGER == propertyKey) {
             assert view.sceneLayer == null;
@@ -57,6 +59,7 @@
                     .addSceneOverlayToBack(view.sceneLayer);
         } else if (BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT == propertyKey) {
             assert view.sceneLayer != null;
+            assert model.get(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT) != null;
             model.get(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT)
                     .setBottomToolbarSceneLayers(new ScrollingBottomViewSceneLayer(view.sceneLayer),
                             new ScrollingBottomViewSceneLayer(view.sceneLayer),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java
index 4861197..9c198ab7f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java
@@ -304,6 +304,7 @@
 
         @Override
         public boolean shouldRecognizeSwipe(MotionEvent e1, MotionEvent e2) {
+            if (FeatureUtilities.isTabGroupsAndroidEnabled()) return false;
             if (FeatureUtilities.isGridTabSwitcherEnabled(getContext())) return false;
             if (isOnTabStrip(e1)) return false;
             if (mToolbar != null && mToolbar.shouldIgnoreSwipeGesture()) return false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
index 45c28145..3d12cad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
@@ -30,6 +30,7 @@
 import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
 import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.tabmodel.DocumentModeAssassin;
+import org.chromium.chrome.browser.touchless.TouchlessDelegate;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.variations.VariationsAssociatedData;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -535,7 +536,8 @@
     private static void cacheTabGroupsAndroidEnabled() {
         ChromePreferenceManager.getInstance().writeBoolean(
                 ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY,
-                ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID));
+                !DeviceClassManager.enableAccessibilityLayout()
+                        && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID));
     }
 
     /**
@@ -557,8 +559,7 @@
     private static boolean isDeviceEligibleForTabGroups() {
         return !SysUtils.isLowEndDevice()
                 && !DeviceFormFactor.isNonMultiDisplayContextOnTablet(
-                        ContextUtils.getApplicationContext())
-                && !DeviceClassManager.enableAccessibilityLayout();
+                        ContextUtils.getApplicationContext());
     }
 
     /**
@@ -574,7 +575,7 @@
      * @return Whether no-touch-mode is enabled.
      */
     public static boolean isNoTouchModeEnabled() {
-        return CommandLine.getInstance().hasSwitch(ChromeSwitches.NO_TOUCH_MODE);
+        return TouchlessDelegate.TOUCHLESS_MODE_ENABLED;
     }
 
     /**
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 68e8433..829cfd50 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -4044,7 +4044,7 @@
         Site paused
       </message>
       <message name="IDS_USAGE_STATS_SITE_PAUSED_EXPLANATION" desc="Message when a website is suspended due to exceeding a user-defined limit">
-        Your <ph name="FQDN">%1$s</ph> timer ran out. It'll start again tomorrow.
+        Your <ph name="FQDN">%1$s<ex>www.amazon.com</ex></ph> timer ran out. It'll start again tomorrow.
       </message>
 
       <!-- Bottom Tab Grid strings -->
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 95252780..5207b6a 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/android/config.gni")
 import("//chrome/android/feed/feed_java_sources.gni")
 import("//components/feed/features.gni")
 import("//components/offline_pages/buildflags/features.gni")
@@ -37,7 +38,6 @@
   "java/src/org/chromium/chrome/browser/ChromeKeyboardVisibilityDelegate.java",
   "java/src/org/chromium/chrome/browser/ChromeStrictMode.java",
   "java/src/org/chromium/chrome/browser/ChromeStringConstants.java",
-  "java/src/org/chromium/chrome/browser/ChromeSwitches.java",
   "java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java",
   "java/src/org/chromium/chrome/browser/ChromeTabbedActivity2.java",
   "java/src/org/chromium/chrome/browser/ChromeVersionInfo.java",
@@ -1634,7 +1634,6 @@
   "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/ViewShiftingActionBarDelegate.java",
-  "java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java",
   "java/src/org/chromium/chrome/browser/tracing/TracingController.java",
   "java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java",
   "java/src/org/chromium/chrome/browser/tracing/TracingNotificationService.java",
@@ -1842,6 +1841,16 @@
   chrome_java_sources += [ "java/src/org/chromium/chrome/browser/component_updater/VrAssetsComponentInstaller.java" ]
 }
 
+if (notouch_build) {
+  chrome_java_sources += [
+    "touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java",
+    "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java",
+    "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java",
+  ]
+} else {
+  chrome_java_sources += [ "touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java" ]
+}
+
 chrome_test_java_sources = [
   "javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java",
   "javatests/src/org/chromium/chrome/browser/AudioTest.java",
@@ -2358,6 +2367,10 @@
   "javatests/src/org/chromium/chrome/test/util/ChromeSigninUtilsTest.java",
 ]
 
+if (notouch_build) {
+  chrome_test_java_sources += [ "touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java" ]
+}
+
 chrome_junit_test_java_sources = [
   "junit/src/org/chromium/chrome/browser/AppIndexingUtilTest.java",
   "junit/src/org/chromium/chrome/browser/BitmapCacheTest.java",
@@ -2397,6 +2410,8 @@
   "junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImplTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/EventOffsetHandlerTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java",
+  "junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java",
+  "junit/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutUpdateHost.java",
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java",
   "junit/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulatorTest.java",
   "junit/src/org/chromium/chrome/browser/contextual_suggestions/FetchHelperTest.java",
@@ -2482,6 +2497,7 @@
   "junit/src/org/chromium/chrome/browser/offlinepages/prefetch/OfflineNotificationBackgroundTaskUnitTest.java",
   "junit/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchBackgroundTaskUnitTest.java",
   "junit/src/org/chromium/chrome/browser/omaha/ResponseParserTest.java",
+  "junit/src/org/chromium/chrome/browser/omaha/UpdateStatusProviderTest.java",
   "junit/src/org/chromium/chrome/browser/omaha/VersionNumberTest.java",
   "junit/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java",
   "junit/src/org/chromium/chrome/browser/omnibox/AutocompleteStateUnitTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java b/chrome/android/java_templates/ChromeSwitches.java.tmpl
similarity index 82%
rename from chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java
rename to chrome/android/java_templates/ChromeSwitches.java.tmpl
index 26aa391..0fa50fbf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java
+++ b/chrome/android/java_templates/ChromeSwitches.java.tmpl
@@ -8,14 +8,10 @@
  * Contains all of the command line switches that are specific to the chrome/
  * portion of Chromium on Android.
  */
-public abstract class ChromeSwitches {
+public abstract class ChromeSwitches {{
     // Switches used from Java.  Please continue switch style used Chrome where
     // options-have-hyphens and are_not_split_with_underscores.
 
-    /** Mimic a low end device */
-    public static final String ENABLE_ACCESSIBILITY_TAB_SWITCHER =
-            "enable-accessibility-tab-switcher";
-
     /** Whether fullscreen support is disabled (auto hiding controls, etc...). */
     public static final String DISABLE_FULLSCREEN = "disable-fullscreen";
 
@@ -68,40 +64,19 @@
     public static final String DISABLE_LOFI_SNACKBAR = "disable-lo-fi-snackbar";
 
     /**
-     * Forces the update state to be set to the given state if the value is {@link
-     * org.chromium.chrome.browser.omaha.UpdateMenuItemHelper#NONE_SWITCH_VALUE}, {@link
-     * org.chromium.chrome.browser.omaha.UpdateMenuItemHelper#UPDATE_AVAILABLE_SWITCH_VALUE},
-     * {@link
-     * org.chromium.chrome.browser.omaha.UpdateMenuItemHelper#UNSUPPORTED_OS_VERSION_SWITCH_VALUE}.
+     * Forces the update state to be set to the given state if the value is {{@link
+     * org.chromium.chrome.browser.omaha.UpdateMenuItemHelper#NONE_SWITCH_VALUE}}, {{@link
+     * org.chromium.chrome.browser.omaha.UpdateMenuItemHelper#UPDATE_AVAILABLE_SWITCH_VALUE}},
+     * {{@link
+     * org.chromium.chrome.browser.omaha.UpdateMenuItemHelper#UNSUPPORTED_OS_VERSION_SWITCH_VALUE}}.
      */
     public static final String FORCE_UPDATE_MENU_UPDATE_TYPE = "force-update-menu-type";
 
     /**
-     * Forces the update menu badge to show. This requires the update type to be valid as well.
-     *
-     * @see #FORCE_UPDATE_MENU_UPDATE_TYPE
-     */
-    public static final String FORCE_SHOW_UPDATE_MENU_BADGE = "force-show-update-menu-badge";
-
-    /**
-     * Sets the market URL for Chrome for use in testing. This requires setting {@link
-     * #FORCE_UPDATE_MENU_UPDATE_TYPE} to {@link
-     * org.chromium.chrome.browser.omaha.UpdateMenuItemHelper#UPDATE_AVAILABLE_SWITCH_VALUE}.
-     * @see #FORCE_UPDATE_MENU_UPDATE_TYPE
-     */
-    public static final String MARKET_URL_FOR_TESTING = "market-url-for-testing";
-
-    /**
      * Disable multiwindow tab merging for testing.
      */
     public static final String DISABLE_TAB_MERGING_FOR_TESTING = "disable-tab-merging";
 
-    /**
-     * Turn on No Touch Mode, which will replace ChromeTabbedActivity with a single tab, non-touchy
-     * alternative.
-     */
-    public static final String NO_TOUCH_MODE = "no-touch-mode";
-
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Native Switches
     ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -122,18 +97,6 @@
     public static final String GOOGLE_BASE_URL = "google-base-url";
 
     /**
-     * Disable domain reliability
-     * Native switch - switches::kDisableDomainReliability
-     */
-    public static final String DISABLE_DOMAIN_RELIABILITY = "disable-domain-reliability";
-
-    /**
-     * Specifies Android phone page loading progress bar animation.
-     * Native switch - switches::kProgressBarAnimation
-     */
-    public static final String PROGRESS_BAR_ANIMATION = "progress-bar-animation";
-
-    /**
      * Enables overscroll of the on screen keyboard. With this flag on, the OSK will only resize the
      * visual viewport.
      * Native switch - switches::kEnableOSKOverscroll
@@ -229,6 +192,8 @@
     public static final String DISABLE_GOOGLE_PLAY_SERVICES_FOR_TESTING =
             "disable-google-play-services-for-testing";
 
+{NATIVE_STRINGS}
+
     // Prevent instantiation.
-    private ChromeSwitches() {}
-}
+    private ChromeSwitches() {{}}
+}}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
index c700c62..e83df53a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
@@ -183,6 +183,8 @@
         private final String mQuickActionUri;
         private final int mQuickActionCategory;
         private final long mLoggedEventId;
+        private final String mSearchUrlFull;
+        private final String mSearchUrlPreload;
 
         boolean mDidStartResolution;
         boolean mDidFinishResolution;
@@ -206,12 +208,16 @@
          * @param loggedEventId         The EventID logged by the server, which should be recorded
          *                              and sent back to the server along with user action results
          *                              in a subsequent request.
+         * @param searchUrlFull         The URL for the full search to present in the overlay, or
+         *                              empty.
+         * @param searchUrlPreload      The URL for the search to preload into the overlay, or
+         *                              empty.
          */
         FakeTapSearch(String nodeId, boolean isNetworkUnavailable, int responseCode,
                 String searchTerm, String displayText, String alternateTerm, String mid,
                 boolean doPreventPreload, int startAdjust, int endAdjust, String contextLanguage,
                 String thumbnailUrl, String caption, String quickActionUri, int quickActionCategory,
-                long loggedEventId) {
+                long loggedEventId, String searchUrlFull, String searchUrlPreload) {
             super(nodeId);
 
             mIsNetworkUnavailable = isNetworkUnavailable;
@@ -229,6 +235,8 @@
             mQuickActionUri = quickActionUri;
             mQuickActionCategory = quickActionCategory;
             mLoggedEventId = loggedEventId;
+            mSearchUrlFull = searchUrlFull;
+            mSearchUrlPreload = searchUrlPreload;
         }
 
         /**
@@ -242,7 +250,7 @@
                 String searchTerm, String displayText) {
             this(nodeId, isNetworkUnavailable, responseCode, searchTerm, displayText,
                     "alternate-term", "", false, -7, 0, "", "", "", "", QuickActionCategory.NONE,
-                    0L);
+                    0L, "", "");
         }
 
         @Override
@@ -315,7 +323,8 @@
                         handleSearchTermResolutionResponse(mIsNetworkUnavailable, mResponseCode,
                                 mSearchTerm, mDisplayText, mAlternateTerm, mMid, mDoPreventPreload,
                                 mStartAdjust, mEndAdjust, mContextLanguage, mThumbnailUrl, mCaption,
-                                mQuickActionUri, mQuickActionCategory, mLoggedEventId);
+                                mQuickActionUri, mQuickActionCategory, mLoggedEventId,
+                                mSearchUrlFull, mSearchUrlPreload);
 
                         mActiveFakeTapSearch = null;
                         mDidFinishResolution = true;
@@ -334,30 +343,38 @@
      */
     public class FakeSlowResolveSearch extends FakeTapSearch {
         /**
-         * @param nodeId
-         * @param isNetworkUnavailable
-         * @param responseCode
-         * @param searchTerm
-         * @param displayText
-         * @param alternateTerm
-         * @param mid
-         * @param doPreventPreload
-         * @param startAdjust
-         * @param endAdjust
-         * @param contextLanguage
-         * @param thumbnailUrl
-         * @param caption
-         * @param quickActionUri
-         * @param quickActionCategory
+         * @param nodeId                The id of the node where the touch event will be simulated.
+         * @param isNetworkUnavailable  Whether the network is unavailable.
+         * @param responseCode          The HTTP response code of the resolution.
+         * @param searchTerm            The resolved search term.
+         * @param displayText           The display text.
+         * @param alternateTerm         The alternate text.
+         * @param mid                   The MID to specify a KP, or an empty string.
+         * @param doPreventPreload      Whether search preload should be prevented.
+         * @param startAdjust           The start adjustment of the selection.
+         * @param endAdjust             The end adjustment of the selection.
+         * @param contextLanguage       The language of the context determined by the server.
+         * @param thumbnailUrl          The URL of a thumbnail to display.
+         * @param caption               The caption to display.
+         * @param quickActionUri        The URI for the intent associated with the quick action.
+         * @param quickActionCategory   The category for the quick action.
+         * @param loggedEventId         The EventID logged by the server, which should be recorded
+         *                              and sent back to the server along with user action results
+         *                              in a subsequent request.
+         * @param searchUrlFull         The URL for the full search to present in the overlay, or
+         *                              empty.
+         * @param searchUrlPreload      The URL for the search to preload into the overlay, or
+         *                              empty.
          */
         FakeSlowResolveSearch(String nodeId, boolean isNetworkUnavailable, int responseCode,
                 String searchTerm, String displayText, String alternateTerm, String mid,
                 boolean doPreventPreload, int startAdjust, int endAdjust, String contextLanguage,
                 String thumbnailUrl, String caption, String quickActionUri, int quickActionCategory,
-                long loggedEventId) {
+                long loggedEventId, String searchUrlFull, String searchUrlPreload) {
             super(nodeId, isNetworkUnavailable, responseCode, searchTerm, displayText,
                     alternateTerm, mid, doPreventPreload, startAdjust, endAdjust, contextLanguage,
-                    thumbnailUrl, caption, quickActionUri, quickActionCategory, loggedEventId);
+                    thumbnailUrl, caption, quickActionUri, quickActionCategory, loggedEventId,
+                    searchUrlFull, searchUrlPreload);
         }
 
         @Override
@@ -583,11 +600,12 @@
             String searchTerm, String displayText, String alternateTerm, String mid,
             boolean doPreventPreload, int selectionStartAdjust, int selectionEndAdjust,
             String contextLanguage, String thumbnailUrl, String caption, String quickActionUri,
-            int quickActionCategory, long loggedEventId) {
+            int quickActionCategory, long loggedEventId, String searchUrlFull,
+            String searchUrlPreload) {
         mBaseManager.handleSearchTermResolutionResponse(isNetworkUnavailable, responseCode,
                 searchTerm, displayText, alternateTerm, mid, doPreventPreload, selectionStartAdjust,
                 selectionEndAdjust, contextLanguage, thumbnailUrl, caption, quickActionUri,
-                quickActionCategory, loggedEventId);
+                quickActionCategory, loggedEventId, searchUrlFull, searchUrlPreload);
     }
 
     @Override
@@ -636,23 +654,24 @@
         registerFakeTapSearch(new FakeTapSearch("term", false, 200, "Term", "Term"));
         registerFakeTapSearch(
                 new FakeTapSearch("resolution", false, 200, "Resolution", "Resolution"));
-        registerFakeTapSearch(new FakeTapSearch("german", false, 200, "Deutsche", "Deutsche",
-                "alternate-term", "", false, 0, 0, "de", "", "", "", QuickActionCategory.NONE, 0));
+        registerFakeTapSearch(
+                new FakeTapSearch("german", false, 200, "Deutsche", "Deutsche", "alternate-term",
+                        "", false, 0, 0, "de", "", "", "", QuickActionCategory.NONE, 0, "", ""));
         registerFakeTapSearch(
                 new FakeTapSearch("intelligence", false, 200, "Intelligence", "Intelligence"));
 
         // Register a fake tap search that will fake a logged event ID from the server.
         registerFakeTapSearch(new FakeTapSearch("intelligence-logged-event-id", false, 200,
                 "Intelligence", "Intelligence", "alternate-term", "", false, 0, 0, "", "", "", "",
-                QuickActionCategory.NONE, LOGGED_EVENT_ID));
+                QuickActionCategory.NONE, LOGGED_EVENT_ID, "", ""));
 
         // Register a resolving search of "States" that expands to "United States".
         registerFakeSlowResolveSearch(new FakeSlowResolveSearch("states", false, 200, "States",
                 "States", "alternate-term", "", false, -7, 0, "", "", "", "",
-                QuickActionCategory.NONE, 0));
+                QuickActionCategory.NONE, 0, "", ""));
         registerFakeSlowResolveSearch(new FakeSlowResolveSearch("search", false, 200, "Search",
                 "Search", "alternate-term", "", false, 0, 0, "", "", "", "",
-                QuickActionCategory.NONE, 0));
+                QuickActionCategory.NONE, 0, "", ""));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index e33a43d..a4552ab 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -472,12 +472,14 @@
         private final String mQuickActionUri;
         private final int mQuickActionCategory;
         private final long mLoggedEventId;
+        private final String mSearchUrlFull;
+        private final String mSearchUrlPreload;
 
         public FakeResponseOnMainThread(boolean isNetworkUnavailable, int responseCode,
                 String searchTerm, String displayText, String alternateTerm, String mid,
                 boolean doPreventPreload, int startAdjust, int endAdjudst, String contextLanguage,
                 String thumbnailUrl, String caption, String quickActionUri, int quickActionCategory,
-                long loggedEventId) {
+                long loggedEventId, String searchUrlFull, String searchUrlPreload) {
             mIsNetworkUnavailable = isNetworkUnavailable;
             mResponseCode = responseCode;
             mSearchTerm = searchTerm;
@@ -493,6 +495,8 @@
             mQuickActionUri = quickActionUri;
             mQuickActionCategory = quickActionCategory;
             mLoggedEventId = loggedEventId;
+            mSearchUrlFull = searchUrlFull;
+            mSearchUrlPreload = searchUrlPreload;
         }
 
         @Override
@@ -500,7 +504,8 @@
             mFakeServer.handleSearchTermResolutionResponse(mIsNetworkUnavailable, mResponseCode,
                     mSearchTerm, mDisplayText, mAlternateTerm, mMid, mDoPreventPreload,
                     mStartAdjust, mEndAdjust, mContextLanguage, mThumbnailUrl, mCaption,
-                    mQuickActionUri, mQuickActionCategory, mLoggedEventId);
+                    mQuickActionUri, mQuickActionCategory, mLoggedEventId, mSearchUrlFull,
+                    mSearchUrlPreload);
         }
     }
 
@@ -511,7 +516,7 @@
     private void fakeResponse(boolean isNetworkUnavailable, int responseCode,
             String searchTerm, String displayText, String alternateTerm, boolean doPreventPreload) {
         fakeResponse(isNetworkUnavailable, responseCode, searchTerm, displayText, alternateTerm,
-                null, doPreventPreload, 0, 0, "", "", "", "", QuickActionCategory.NONE, 0);
+                null, doPreventPreload, 0, 0, "", "", "", "", QuickActionCategory.NONE, 0, "", "");
     }
 
     /**
@@ -521,12 +526,14 @@
     private void fakeResponse(boolean isNetworkUnavailable, int responseCode, String searchTerm,
             String displayText, String alternateTerm, String mid, boolean doPreventPreload,
             int startAdjust, int endAdjust, String contextLanguage, String thumbnailUrl,
-            String caption, String quickActionUri, int quickActionCategory, long loggedEventId) {
+            String caption, String quickActionUri, int quickActionCategory, long loggedEventId,
+            String searchUrlFull, String searchUrlPreload) {
         if (mFakeServer.getSearchTermRequested() != null) {
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(new FakeResponseOnMainThread(
-                    isNetworkUnavailable, responseCode, searchTerm, displayText, alternateTerm, mid,
-                    doPreventPreload, startAdjust, endAdjust, contextLanguage, thumbnailUrl,
-                    caption, quickActionUri, quickActionCategory, loggedEventId));
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                    new FakeResponseOnMainThread(isNetworkUnavailable, responseCode, searchTerm,
+                            displayText, alternateTerm, mid, doPreventPreload, startAdjust,
+                            endAdjust, contextLanguage, thumbnailUrl, caption, quickActionUri,
+                            quickActionCategory, loggedEventId, searchUrlFull, searchUrlPreload));
         }
     }
 
@@ -2387,7 +2394,7 @@
         waitForPanelToPeek();
 
         fakeResponse(false, 200, "Intelligence", "United States Intelligence", "alternate-term",
-                null, false, -14, 0, "", "", "", "", QuickActionCategory.NONE, 0);
+                null, false, -14, 0, "", "", "", "", QuickActionCategory.NONE, 0, "", "");
         waitForSelectionToBe("United States Intelligence");
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequestTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequestTest.java
index 130a293c..5dae7e5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequestTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRequestTest.java
@@ -38,9 +38,10 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mRequest = new ContextualSearchRequest("barack obama", "barack", "", true);
+                mRequest =
+                        new ContextualSearchRequest("barack obama", "barack", "", true, null, null);
                 mNormalPriorityOnlyRequest =
-                        new ContextualSearchRequest("woody allen", "allen", "", false);
+                        new ContextualSearchRequest("woody allen", "allen", "", false, null, null);
             }
         });
     }
@@ -75,4 +76,28 @@
         Assert.assertFalse(mRequest.isUsingLowPriority());
         Assert.assertFalse(mNormalPriorityOnlyRequest.isUsingLowPriority());
     }
+
+    @Test
+    @SmallTest
+    @Feature({"ContextualSearch"})
+    public void testServerProvidedUrls() {
+        String serverUrlFull = "https://www.google.com/search?obama&ctxs=2";
+        String serverUrlPreload = "https://www.google.com/s?obama&ctxs=2&pf=c&sns=1";
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mRequest = new ContextualSearchRequest(
+                        "", "", "", true, serverUrlFull, serverUrlPreload);
+                mNormalPriorityOnlyRequest =
+                        new ContextualSearchRequest("", "", "", false, serverUrlFull, null);
+            }
+        });
+        Assert.assertTrue(mRequest.isUsingLowPriority());
+        Assert.assertEquals(serverUrlPreload, mRequest.getSearchUrl());
+        mRequest.setNormalPriority();
+        Assert.assertFalse(mRequest.isUsingLowPriority());
+        Assert.assertFalse(mNormalPriorityOnlyRequest.isUsingLowPriority());
+        Assert.assertEquals(serverUrlFull, mRequest.getSearchUrl());
+        Assert.assertEquals(serverUrlFull, mNormalPriorityOnlyRequest.getSearchUrl());
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
index 4cc2adf..e44d0c6c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
@@ -99,7 +99,7 @@
         public void startSearchTermResolutionRequest(String selection) {
             // Skip native calls and immediately "resolve" the search term.
             onSearchTermResolutionResponse(true, 200, selection, selection, "", "", false, 0, 10,
-                    "", "", "", "", QuickActionCategory.NONE, 0);
+                    "", "", "", "", QuickActionCategory.NONE, 0, "", "");
         }
 
         @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
index 125cbdf..bc7e60b6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java
@@ -145,6 +145,7 @@
         features.put(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO, false);
         features.put(ChromeFeatureList.DOWNLOAD_HOME_V2, false);
         features.put(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, false);
+        features.put(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER, false);
         ChromeFeatureList.setTestFeatures(features);
 
         mStubbedProvider = new StubbedProvider();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
index 7618ab2b..1c7c920 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
@@ -164,6 +164,7 @@
     @Test
     @SmallTest
     @Feature({"Download"})
+    @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
     public void testAccelerated() {
         OfflineItem offlineItem = createOfflineItem(OfflineItemState.IN_PROGRESS);
         offlineItem.isAccelerated = true;
@@ -174,6 +175,7 @@
     @Test
     @SmallTest
     @Feature({"Download"})
+    @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
     public void testMultipleDownloadInProgress() {
         OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
         mTestController.onDownloadItemUpdated(createDownloadItem(item1));
@@ -187,6 +189,7 @@
     @Test
     @SmallTest
     @Feature({"Download"})
+    @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
     public void testAcceleratedChangesToDownloadingAfterDelay() {
         OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
         item1.isAccelerated = true;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
index b612ce86..26eb375 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
@@ -45,6 +45,7 @@
  */
 @RunWith(ParameterizedRunner.class)
 @UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
+@Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
 public class DownloadNotificationServiceTest {
     private static final ContentId ID1 =
             LegacyHelpers.buildLegacyContentId(false, UUID.randomUUID().toString());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
index 8d4ba0aa..1c678b0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
@@ -20,12 +20,18 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.download.items.OfflineContentAggregatorFactory;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.components.offline_items_collection.OfflineContentProvider;
+import org.chromium.components.offline_items_collection.OfflineItem;
+import org.chromium.components.offline_items_collection.OfflineItemState;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -194,6 +200,22 @@
         }
     }
 
+    private class TestDownloadBackendObserver implements OfflineContentProvider.Observer {
+        @Override
+        public void onItemsAdded(ArrayList<OfflineItem> items) {}
+
+        @Override
+        public void onItemRemoved(ContentId id) {}
+
+        @Override
+        public void onItemUpdated(OfflineItem item) {
+            if (item.state == OfflineItemState.COMPLETE) {
+                mLastDownloadFilePath = item.filePath;
+                mHttpDownloadFinished.notifyCalled();
+            }
+        }
+    }
+
     @Override
     public Statement apply(final Statement base, Description description) {
         return super.apply(new Statement() {
@@ -222,6 +244,8 @@
                             new SystemDownloadNotifier(), new Handler(), UPDATE_DELAY_MILLIS));
             DownloadController.setDownloadNotificationService(
                     DownloadManagerService.getDownloadManagerService());
+            OfflineContentAggregatorFactory.forProfile(null).addObserver(
+                    new TestDownloadBackendObserver());
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
index 5ab4ff8..0666e59 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
@@ -63,6 +63,16 @@
     public void setUpTest() throws Exception {
         super.setUpTest();
         MockitoAnnotations.initMocks(this);
+
+        Map<String, Boolean> features = new HashMap<>();
+        features.put(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE, true);
+        features.put(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO, true);
+        features.put(ChromeFeatureList.DOWNLOAD_HOME_V2, true);
+        features.put(ChromeFeatureList.OFFLINE_PAGES_PREFETCHING, true);
+        features.put(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, false);
+        features.put(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER, false);
+        ChromeFeatureList.setTestFeatures(features);
+
         StubbedOfflineContentProvider stubbedOfflineContentProvider =
                 new StubbedOfflineContentProvider();
         OfflineContentAggregatorFactory.setOfflineContentProviderForTests(
@@ -79,14 +89,6 @@
         stubbedOfflineContentProvider.addItem(item3);
 
         TrackerFactory.setTrackerForTests(mTracker);
-
-        Map<String, Boolean> features = new HashMap<>();
-        features.put(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE, true);
-        features.put(ChromeFeatureList.DOWNLOAD_HOME_SHOW_STORAGE_INFO, true);
-        features.put(ChromeFeatureList.DOWNLOAD_HOME_V2, true);
-        features.put(ChromeFeatureList.OFFLINE_PAGES_PREFETCHING, true);
-        features.put(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, false);
-        ChromeFeatureList.setTestFeatures(features);
     }
 
     private void setUpUi() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserNavigationTest.java
index 75c0989..715669d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserNavigationTest.java
@@ -24,6 +24,7 @@
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.UrlConstants;
@@ -34,17 +35,20 @@
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.vr.rules.ChromeTabbedActivityVrTestRule;
 import org.chromium.chrome.browser.vr.util.NativeUiUtils;
+import org.chromium.chrome.browser.vr.util.RenderTestUtils;
 import org.chromium.chrome.browser.vr.util.VrBrowserTransitionUtils;
 import org.chromium.chrome.browser.vr.util.VrInfoBarUtils;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.NewTabPageTestUtils;
+import org.chromium.chrome.test.util.RenderTestRule;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.ClickUtils;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -63,6 +67,10 @@
     @Rule
     public ChromeTabbedActivityVrTestRule mTestRule = new ChromeTabbedActivityVrTestRule();
 
+    @Rule
+    public RenderTestRule mRenderTestRule =
+            new RenderTestRule("components/test/data/vr_browser_ui/render_tests");
+
     private WebXrVrTestFramework mWebXrVrTestFramework;
     private WebVrTestFramework mWebVrTestFramework;
     private VrBrowserTestFramework mVrBrowserTestFramework;
@@ -486,43 +494,83 @@
     }
 
     /**
-     * Tests navigation from a fullscreened WebVR to a WebVR page.
+     * Tests that the navigation buttons work only when they should, and are greyed out when not
+     * usable.
      */
     @Test
     @MediumTest
-    public void testNavigationButtons() throws IllegalArgumentException, InterruptedException {
+    @Feature({"Browser", "RenderTest"})
+    public void testNavigationButtons()
+            throws IllegalArgumentException, InterruptedException, IOException {
+        // TODO(https://crbug.com/930840): Remove this when the weird gradient behavior is fixed.
+        mRenderTestRule.setPixelDiffThreshold(2);
         Assert.assertFalse(
                 "Back button is enabled.", VrBrowserTransitionUtils.isBackButtonEnabled());
         Assert.assertFalse(
                 "Forward button is enabled.", VrBrowserTransitionUtils.isForwardButtonEnabled());
+        NativeUiUtils.clickElementAndWaitForUiQuiescence(
+                UserFriendlyElementName.OVERFLOW_MENU, new PointF());
+        RenderTestUtils.dumpAndCompare(NativeUiUtils.FRAME_BUFFER_SUFFIX_BROWSER_UI,
+                "navigation_buttons_both_disabled", mRenderTestRule);
+
         // Opening a new tab shouldn't enable the back button
-        mTestRule.loadUrlInNewTab(getUrl(Page.PAGE_2D), false, TabLaunchType.FROM_CHROME_UI);
+        mTestRule.loadUrlInNewTab("about:blank", false, TabLaunchType.FROM_CHROME_UI);
+        final int tabId = mTestRule.getActivity().getActivityTab().getId();
         Assert.assertFalse(
                 "Back button is enabled.", VrBrowserTransitionUtils.isBackButtonEnabled());
         Assert.assertFalse(
                 "Forward button is enabled.", VrBrowserTransitionUtils.isForwardButtonEnabled());
+        // Actually click on the back button and ensure it doesn't go back to the first tab.
+        NativeUiUtils.clickElement(UserFriendlyElementName.BACK_BUTTON, new PointF());
+        NativeUiUtils.clickElementAndWaitForUiQuiescence(
+                UserFriendlyElementName.OVERFLOW_MENU, new PointF());
+        RenderTestUtils.dumpAndCompare(NativeUiUtils.FRAME_BUFFER_SUFFIX_BROWSER_UI,
+                "navigation_buttons_both_disabled", mRenderTestRule);
+        Assert.assertEquals("Back button navigated to previous tab", tabId,
+                mTestRule.getActivity().getActivityTab().getId());
+
         // Navigating to a new page should enable the back button
-        mTestRule.loadUrl(getUrl(Page.PAGE_WEBVR));
+        mTestRule.loadUrl("chrome://version/");
         Assert.assertTrue(
                 "Back button is disabled.", VrBrowserTransitionUtils.isBackButtonEnabled());
         Assert.assertFalse(
                 "Forward button is enabled.", VrBrowserTransitionUtils.isForwardButtonEnabled());
-        // Navigating back should disable the back button and enable the forward button
-        VrBrowserTransitionUtils.navigateBack();
+        // Overflow menu should still be visible since navigation doesn't close it.
+        NativeUiUtils.waitForUiQuiescence();
+        RenderTestUtils.dumpAndCompare(NativeUiUtils.FRAME_BUFFER_SUFFIX_BROWSER_UI,
+                "navigation_buttons_back_enabled", mRenderTestRule);
+
+        // Navigating back should disable the back button and enable the forward button.
+        // We need to click twice - once to close the overflow menu, and once to actually click.
+        NativeUiUtils.clickElement(UserFriendlyElementName.BACK_BUTTON, new PointF());
+        NativeUiUtils.clickElement(UserFriendlyElementName.BACK_BUTTON, new PointF());
         ChromeTabUtils.waitForTabPageLoaded(
-                mTestRule.getActivity().getActivityTab(), getUrl(Page.PAGE_2D));
+                mTestRule.getActivity().getActivityTab(), "about:blank");
         Assert.assertFalse(
                 "Back button is enabled.", VrBrowserTransitionUtils.isBackButtonEnabled());
         Assert.assertTrue(
                 "Forward button is disabled.", VrBrowserTransitionUtils.isForwardButtonEnabled());
+        // Once again, click on the back button and ensure it doesn't go back to the first tab.
+        NativeUiUtils.clickElement(UserFriendlyElementName.BACK_BUTTON, new PointF());
+        NativeUiUtils.clickElementAndWaitForUiQuiescence(
+                UserFriendlyElementName.OVERFLOW_MENU, new PointF());
+        RenderTestUtils.dumpAndCompare(NativeUiUtils.FRAME_BUFFER_SUFFIX_BROWSER_UI,
+                "navigation_buttons_forward_enabled", mRenderTestRule);
+        Assert.assertEquals("Back button navigated to previous tab", tabId,
+                mTestRule.getActivity().getActivityTab().getId());
+
         // Navigating forward should disable the forward button and enable the back button
-        VrBrowserTransitionUtils.navigateForward();
+        NativeUiUtils.clickElement(UserFriendlyElementName.FORWARD_BUTTON, new PointF());
         ChromeTabUtils.waitForTabPageLoaded(
-                mTestRule.getActivity().getActivityTab(), getUrl(Page.PAGE_WEBVR));
+                mTestRule.getActivity().getActivityTab(), "chrome://version/");
         Assert.assertTrue(
                 "Back button is disabled.", VrBrowserTransitionUtils.isBackButtonEnabled());
         Assert.assertFalse(
                 "Forward button is enabled.", VrBrowserTransitionUtils.isForwardButtonEnabled());
+        NativeUiUtils.clickElementAndWaitForUiQuiescence(
+                UserFriendlyElementName.OVERFLOW_MENU, new PointF());
+        RenderTestUtils.dumpAndCompare(NativeUiUtils.FRAME_BUFFER_SUFFIX_BROWSER_UI,
+                "navigation_buttons_back_enabled", mRenderTestRule);
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/XrTestFramework.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/XrTestFramework.java
index 43b7a48..2a7b57b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/XrTestFramework.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/XrTestFramework.java
@@ -352,9 +352,12 @@
         Assert.assertTrue("Did not get a focused frame", rfh != null);
         final CountDownLatch latch = new CountDownLatch(1);
         final AtomicReference<String> result = new AtomicReference<String>();
-        rfh.executeJavaScript(js, (String r) -> {
-            result.set(r);
-            latch.countDown();
+        // The JS execution needs to be started on the UI thread to avoid hitting a DCHECK.
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            rfh.executeJavaScript(js, (String r) -> {
+                result.set(r);
+                latch.countDown();
+            });
         });
         try {
             if (!latch.await(timeout, TimeUnit.MILLISECONDS) && failOnTimeout) {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java
index 165283a..a2e25d4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java
@@ -21,9 +21,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.CallbackHelper;
-import org.chromium.chrome.browser.compositor.layouts.Layout;
-import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
-import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
+import org.chromium.chrome.browser.compositor.layouts.MockLayoutUpdateHost;
 import org.chromium.chrome.browser.util.MathUtils;
 
 import java.util.ArrayList;
@@ -38,47 +36,13 @@
         sdk = Build.VERSION_CODES.N_MR1)
 public final class CompositorAnimatorTest {
     /** A mock implementation of {@link LayoutUpdateHost} that tracks update requests. */
-    private static class MockLayoutUpdateHost implements LayoutUpdateHost {
+    private static class MockLayoutUpdateHostWithCallback extends MockLayoutUpdateHost {
         private final CallbackHelper mUpdateCallbackHelper = new CallbackHelper();
 
         @Override
         public void requestUpdate() {
             mUpdateCallbackHelper.notifyCalled();
         }
-
-        @Override
-        public void startHiding(int nextTabId, boolean hintAtTabSelection) {}
-
-        @Override
-        public void doneHiding() {}
-
-        @Override
-        public void doneShowing() {}
-
-        @Override
-        public boolean isActiveLayout(Layout layout) {
-            return true;
-        }
-
-        @Override
-        public void initLayoutTabFromHost(final int tabId) {}
-
-        @Override
-        public LayoutTab createLayoutTab(int id, boolean incognito, boolean showCloseButton,
-                boolean isTitleNeeded, float maxContentWidth, float maxContentHeight) {
-            return null;
-        }
-
-        @Override
-        public void releaseTabLayout(int id) {}
-
-        @Override
-        public void releaseResourcesForTab(int tabId) {}
-
-        @Override
-        public CompositorAnimationHandler getAnimationHandler() {
-            return null;
-        }
     }
 
     /** An animation update listener that counts calls to its methods. */
@@ -116,7 +80,7 @@
     }
 
     /** A mock {@link LayoutUpdateHost} to track update requests. */
-    private MockLayoutUpdateHost mHost;
+    private MockLayoutUpdateHostWithCallback mHost;
 
     /** The handler that is responsible for managing all {@link CompositorAnimator}s. */
     private CompositorAnimationHandler mHandler;
@@ -130,7 +94,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mHost = new MockLayoutUpdateHost();
+        mHost = new MockLayoutUpdateHostWithCallback();
 
         mHandler = new CompositorAnimationHandler(mHost);
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java
new file mode 100644
index 0000000..d3251d7
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java
@@ -0,0 +1,72 @@
+// 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.
+
+package org.chromium.chrome.browser.compositor.layouts;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.compositor.animation.CompositorAnimationHandler;
+import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
+
+/**
+ * Unit tests for {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class CompositorAnimationHandlerTest {
+    private static final long FAST_DURATION_MS = 100;
+    private static final long SLOW_DURATION_MS = 1000;
+
+    private CompositorAnimator mFastAnimation;
+    private CompositorAnimator mSlowAnimation;
+    private CompositorAnimationHandler mAnimations;
+    private LayoutUpdateHost mUpdateHost;
+
+    @Test
+    @SmallTest
+    public void testConcurrentAnimationsFinishSeparately() {
+        mUpdateHost = new MockLayoutUpdateHostWithAnimationHandler(mAnimations);
+        mAnimations = new CompositorAnimationHandler(mUpdateHost);
+
+        CompositorAnimator mFastAnimation =
+                CompositorAnimator.ofFloat(mAnimations, 0.f, 1.f, FAST_DURATION_MS, null);
+        CompositorAnimator mSlowAnimation =
+                CompositorAnimator.ofFloat(mAnimations, 0.f, 1.f, SLOW_DURATION_MS, null);
+
+        mFastAnimation.start();
+        mSlowAnimation.start();
+
+        CompositorAnimationHandler.setTestingMode(true);
+
+        // Advances time to check that the fast animation will finish first.
+        mAnimations.pushUpdateInTestingMode(1 + FAST_DURATION_MS);
+        Assert.assertFalse(mFastAnimation.isRunning());
+        Assert.assertTrue(mSlowAnimation.isRunning());
+
+        // Advances time to check that all animations are finished.
+        mAnimations.pushUpdateInTestingMode(1 + SLOW_DURATION_MS);
+        Assert.assertFalse(mFastAnimation.isRunning());
+        Assert.assertFalse(mSlowAnimation.isRunning());
+    }
+
+    /** A mock implementation of {@link LayoutUpdateHost} with animation handler. */
+    private class MockLayoutUpdateHostWithAnimationHandler extends MockLayoutUpdateHost {
+        private CompositorAnimationHandler mAnimationHandler;
+
+        MockLayoutUpdateHostWithAnimationHandler(CompositorAnimationHandler animationHandler) {
+            mAnimationHandler = animationHandler;
+        }
+
+        @Override
+        public CompositorAnimationHandler getAnimationHandler() {
+            return mAnimationHandler;
+        }
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutUpdateHost.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutUpdateHost.java
new file mode 100644
index 0000000..69a0645
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutUpdateHost.java
@@ -0,0 +1,48 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.compositor.layouts;
+
+import org.chromium.chrome.browser.compositor.animation.CompositorAnimationHandler;
+import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
+
+/** A mock implementation of {@link LayoutUpdateHost}. */
+public class MockLayoutUpdateHost implements LayoutUpdateHost {
+    @Override
+    public void requestUpdate() {}
+
+    @Override
+    public void startHiding(int nextTabId, boolean hintAtTabSelection) {}
+
+    @Override
+    public void doneHiding() {}
+
+    @Override
+    public void doneShowing() {}
+
+    @Override
+    public boolean isActiveLayout(Layout layout) {
+        return true;
+    }
+
+    @Override
+    public void initLayoutTabFromHost(final int tabId) {}
+
+    @Override
+    public LayoutTab createLayoutTab(int id, boolean incognito, boolean showCloseButton,
+            boolean isTitleNeeded, float maxContentWidth, float maxContentHeight) {
+        return null;
+    }
+
+    @Override
+    public void releaseTabLayout(int id) {}
+
+    @Override
+    public void releaseResourcesForTab(int tabId) {}
+
+    @Override
+    public CompositorAnimationHandler getAnimationHandler() {
+        return null;
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
index 13576e7..a3bc770f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
@@ -58,6 +58,7 @@
     public void setUp() {
         mModel = new ListItemModel();
         Map<String, Boolean> testFeatures = new HashMap<>();
+        testFeatures.put(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER, true);
         ChromeFeatureList.setTestFeatures(testFeatures);
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index 6b9a8ed0..d53925c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -603,7 +603,7 @@
     /**
      * Tests that the More button appends new suggestions after dismissing all items. The tricky
      * condition is that if a section is empty, we issue a fetch instead of a fetch-more. This means
-     * we are using the 'updateSuggestions()' flow to append to the list the user is looking at.
+     * we are using the 'updateModels()' flow to append to the list the user is looking at.
      */
     @Test
     @Feature({"Ntp"})
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omaha/UpdateStatusProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/UpdateStatusProviderTest.java
new file mode 100644
index 0000000..c2f3c7f
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/UpdateStatusProviderTest.java
@@ -0,0 +1,126 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omaha;
+
+import static org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState.INLINE_UPDATE_AVAILABLE;
+import static org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState.INLINE_UPDATE_DOWNLOADING;
+import static org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState.INLINE_UPDATE_FAILED;
+import static org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState.INLINE_UPDATE_READY;
+import static org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState.NONE;
+import static org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState.UNSUPPORTED_OS_VERSION;
+import static org.chromium.chrome.browser.omaha.UpdateStatusProvider.UpdateState.UPDATE_AVAILABLE;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import org.chromium.base.test.util.Feature;
+
+/**
+ * Unit tests for UpdateStatusProvider.
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class UpdateStatusProviderTest {
+    private static void verify(@UpdateStatusProvider.UpdateState int expected,
+            @UpdateConfigs.UpdateFlowConfiguration int configuration,
+            @UpdateStatusProvider.UpdateState int omahaState,
+            @UpdateStatusProvider.UpdateState int inlineState) {
+        @UpdateStatusProvider.UpdateState
+        int result = UpdateStatusProvider.resolveOmahaAndInlineStatus(
+                configuration, omahaState, inlineState);
+        Assert.assertEquals("{config=" + configuration + ", omaha=" + omahaState
+                        + ", inline=" + inlineState + "}",
+                expected, result);
+    }
+
+    @Test
+    @Feature("omaha")
+    public void testNeverShow() {
+        @UpdateConfigs.UpdateFlowConfiguration
+        int config = UpdateConfigs.UpdateFlowConfiguration.NEVER_SHOW;
+        verify(NONE, config, NONE, NONE);
+        verify(NONE, config, NONE, INLINE_UPDATE_AVAILABLE);
+        verify(NONE, config, NONE, INLINE_UPDATE_DOWNLOADING);
+        verify(NONE, config, NONE, INLINE_UPDATE_READY);
+        verify(NONE, config, NONE, INLINE_UPDATE_FAILED);
+        verify(NONE, config, UNSUPPORTED_OS_VERSION, NONE);
+        verify(NONE, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_AVAILABLE);
+        verify(NONE, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_DOWNLOADING);
+        verify(NONE, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_READY);
+        verify(NONE, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_FAILED);
+        verify(NONE, config, UPDATE_AVAILABLE, NONE);
+        verify(NONE, config, UPDATE_AVAILABLE, INLINE_UPDATE_AVAILABLE);
+        verify(NONE, config, UPDATE_AVAILABLE, INLINE_UPDATE_DOWNLOADING);
+        verify(NONE, config, UPDATE_AVAILABLE, INLINE_UPDATE_READY);
+        verify(NONE, config, UPDATE_AVAILABLE, INLINE_UPDATE_FAILED);
+    }
+
+    @Test
+    @Feature("omaha")
+    public void testInlineOnly() {
+        @UpdateConfigs.UpdateFlowConfiguration
+        int config = UpdateConfigs.UpdateFlowConfiguration.INLINE_ONLY;
+        verify(NONE, config, NONE, NONE);
+        verify(NONE, config, NONE, INLINE_UPDATE_AVAILABLE);
+        verify(NONE, config, NONE, INLINE_UPDATE_DOWNLOADING);
+        verify(NONE, config, NONE, INLINE_UPDATE_READY);
+        verify(NONE, config, NONE, INLINE_UPDATE_FAILED);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, NONE);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_AVAILABLE);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_DOWNLOADING);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_READY);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_FAILED);
+        verify(NONE, config, UPDATE_AVAILABLE, NONE);
+        verify(INLINE_UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, INLINE_UPDATE_AVAILABLE);
+        verify(INLINE_UPDATE_DOWNLOADING, config, UPDATE_AVAILABLE, INLINE_UPDATE_DOWNLOADING);
+        verify(INLINE_UPDATE_READY, config, UPDATE_AVAILABLE, INLINE_UPDATE_READY);
+        verify(INLINE_UPDATE_FAILED, config, UPDATE_AVAILABLE, INLINE_UPDATE_FAILED);
+    }
+
+    @Test
+    @Feature("omaha")
+    public void testIntentOnly() {
+        @UpdateConfigs.UpdateFlowConfiguration
+        int config = UpdateConfigs.UpdateFlowConfiguration.INTENT_ONLY;
+        verify(NONE, config, NONE, NONE);
+        verify(NONE, config, NONE, INLINE_UPDATE_AVAILABLE);
+        verify(NONE, config, NONE, INLINE_UPDATE_DOWNLOADING);
+        verify(NONE, config, NONE, INLINE_UPDATE_READY);
+        verify(NONE, config, NONE, INLINE_UPDATE_FAILED);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, NONE);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_AVAILABLE);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_DOWNLOADING);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_READY);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_FAILED);
+        verify(UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, NONE);
+        verify(UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, INLINE_UPDATE_AVAILABLE);
+        verify(UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, INLINE_UPDATE_DOWNLOADING);
+        verify(UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, INLINE_UPDATE_READY);
+        verify(UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, INLINE_UPDATE_FAILED);
+    }
+
+    @Test
+    @Feature("omaha")
+    public void testBestEffort() {
+        @UpdateConfigs.UpdateFlowConfiguration
+        int config = UpdateConfigs.UpdateFlowConfiguration.BEST_EFFORT;
+        verify(NONE, config, NONE, NONE);
+        verify(NONE, config, NONE, INLINE_UPDATE_AVAILABLE);
+        verify(NONE, config, NONE, INLINE_UPDATE_DOWNLOADING);
+        verify(NONE, config, NONE, INLINE_UPDATE_READY);
+        verify(NONE, config, NONE, INLINE_UPDATE_FAILED);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, NONE);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_AVAILABLE);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_DOWNLOADING);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_READY);
+        verify(UNSUPPORTED_OS_VERSION, config, UNSUPPORTED_OS_VERSION, INLINE_UPDATE_FAILED);
+        verify(UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, NONE);
+        verify(INLINE_UPDATE_AVAILABLE, config, UPDATE_AVAILABLE, INLINE_UPDATE_AVAILABLE);
+        verify(INLINE_UPDATE_DOWNLOADING, config, UPDATE_AVAILABLE, INLINE_UPDATE_DOWNLOADING);
+        verify(INLINE_UPDATE_READY, config, UPDATE_AVAILABLE, INLINE_UPDATE_READY);
+        verify(INLINE_UPDATE_FAILED, config, UPDATE_AVAILABLE, INLINE_UPDATE_FAILED);
+    }
+}
diff --git a/chrome/android/touchless/OWNERS b/chrome/android/touchless/OWNERS
new file mode 100644
index 0000000..af2050f
--- /dev/null
+++ b/chrome/android/touchless/OWNERS
@@ -0,0 +1,5 @@
+mthiesse@chromium.org
+yfriedman@chromium.org
+
+# COMPONENT: Mobile>Touchless
+# OS: Android
diff --git a/chrome/android/touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java b/chrome/android/touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
new file mode 100644
index 0000000..6d7d43f
--- /dev/null
+++ b/chrome/android/touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.touchless;
+
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.native_page.NativePage;
+import org.chromium.chrome.browser.native_page.NativePageHost;
+
+/**
+ * The fallback version of TouchlessDelegate, when touchless mode isn't enabled.
+ */
+public class TouchlessDelegate {
+    public static final boolean TOUCHLESS_MODE_ENABLED = false;
+
+    public static NativePage createTouchlessNewTabPage(
+            ChromeActivity activity, NativePageHost host) {
+        return null;
+    }
+
+    public static boolean isTouchlessNewTabPage(NativePage nativePage) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/touchless/java/DEPS b/chrome/android/touchless/java/DEPS
new file mode 100644
index 0000000..838459a9
--- /dev/null
+++ b/chrome/android/touchless/java/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+content/public/android/java/src/org/chromium/content_public",
+]
diff --git a/chrome/android/java/res/layout/new_tab_page_touchless.xml b/chrome/android/touchless/java/res/layout/new_tab_page_touchless.xml
similarity index 100%
rename from chrome/android/java/res/layout/new_tab_page_touchless.xml
rename to chrome/android/touchless/java/res/layout/new_tab_page_touchless.xml
diff --git a/chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
similarity index 100%
rename from chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
rename to chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
new file mode 100644
index 0000000..f9c8b6bb
--- /dev/null
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.touchless;
+
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.native_page.NativePage;
+import org.chromium.chrome.browser.native_page.NativePageHost;
+
+/**
+ *  Provides an entry point into touchless code from the rest of Chrome.
+ */
+public class TouchlessDelegate {
+    public static final boolean TOUCHLESS_MODE_ENABLED = true;
+
+    public static NativePage createTouchlessNewTabPage(
+            ChromeActivity activity, NativePageHost host) {
+        return new TouchlessNewTabPage(activity, host);
+    }
+
+    public static boolean isTouchlessNewTabPage(NativePage nativePage) {
+        return nativePage instanceof TouchlessNewTabPage;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java
similarity index 96%
rename from chrome/android/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java
rename to chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java
index 60573de..9fd815b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java
@@ -7,11 +7,11 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.native_page.BasicNativePage;
 import org.chromium.chrome.browser.native_page.NativePageHost;
+import org.chromium.chrome.touchless.R;
 
 /**
  * Condensed new tab page for touchless devices.
diff --git a/chrome/android/features/touchless/DEPS b/chrome/android/touchless/javatests/DEPS
similarity index 100%
rename from chrome/android/features/touchless/DEPS
rename to chrome/android/touchless/javatests/DEPS
diff --git a/chrome/android/features/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java b/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
similarity index 98%
rename from chrome/android/features/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
rename to chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
index 82e92ef..7c56b94a 100644
--- a/chrome/android/features/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
+++ b/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
@@ -31,7 +31,7 @@
  * Tests for NoTouchActivity.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ChromeSwitches.NO_TOUCH_MODE})
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class NoTouchActivityTest {
     private static final String TEST_PATH = "/chrome/test/data/android/simple.html";
 
diff --git a/chrome/android/trichrome.gni b/chrome/android/trichrome.gni
index cf22063a..52466dd 100644
--- a/chrome/android/trichrome.gni
+++ b/chrome/android/trichrome.gni
@@ -112,5 +112,6 @@
             [ "//base/android/proguard/disable_all_obfuscation.flags" ]
       }
     }
+    deps += [ "//chrome/android:trichrome_dummy_resources" ]
   }
 }
diff --git a/chrome/android/trichrome/res_dummy/values/strings.xml b/chrome/android/trichrome/res_dummy/values/strings.xml
new file mode 100644
index 0000000..a0d71c24
--- /dev/null
+++ b/chrome/android/trichrome/res_dummy/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<!-- DO NOT ADD MORE RESOURCES HERE -->
+<resources>
+    <string name="dummy"></string>
+</resources>
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index e5242fd8..94b6f22 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -1209,9 +1209,6 @@
   <message name="IDS_OFFLINE_LOGIN_HTML" desc="Text which suggests enter as an existing user when valid network isn't presented.">
     If you've already registered on this device, you can <ph name="LINK2_START">$1<ex>&gt;a&lt;</ex></ph>sign in as an existing user<ph name="LINK2_END">$2<ex>&gt;/a&lt;</ex></ph>.
   </message>
-  <message name="IDS_KIOSK_APPS_BUTTON" desc="Text shown on a button that brings up the kiosk apps menu on login screen">
-    Apps
-  </message>
   <message name="IDS_LOGIN_USER_ADDING_BANNER" desc="Text shown on a banner in user adding screen.">
     Add an account to multiple sign-in. All signed-in accounts can be accessed without a password, so this feature should only be used with trusted accounts.
   </message>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 902d34f..ef530460 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5187,6 +5187,9 @@
       <message name="IDS_REOPEN_TAB_PROMO" desc="Text shown on promotional UI appearing next to the app menu button">
         Reopen a tab if you accidentally closed it
       </message>
+      <message name="IDS_REOPEN_TAB_PROMO_SCREENREADER" desc="Text announced encouraging users to reopen a tab with the given shortcut">
+        <ph name="SHORTCUT">$1<ex>CTRL+SHIFT+T</ex></ph> can reopen accidentally closed tabs
+      </message>
 
       <!-- Browser Hung Plugin Detector -->
       <if expr="is_win">
diff --git a/chrome/app/profiles_strings.grdp b/chrome/app/profiles_strings.grdp
index 9c602ca..9d15311f 100644
--- a/chrome/app/profiles_strings.grdp
+++ b/chrome/app/profiles_strings.grdp
@@ -325,6 +325,39 @@
   <message name="IDS_DEFAULT_AVATAR_LABEL_25" desc="The label for the sun and cloud avatar.">
     Sun and clouds
   </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_27" desc="The label for the cat avatar.">
+    Cat
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_28" desc="The label for the corgi avatar.">
+    Corgi
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_29" desc="The label for the dragon avatar.">
+    Dragon
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_30" desc="The label for the elephant avatar.">
+    Elephant
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_31" desc="The label for the fox avatar.">
+    Fox
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_32" desc="The label for the monkey avatar.">
+    Monkey
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_33" desc="The label for the panda avatar.">
+    Panda
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_34" desc="The label for the penguin avatar.">
+    Penguin
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_35" desc="The label for the butterfly avatar.">
+    Butterfly
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_36" desc="The label for the rabbit avatar.">
+    Rabbit
+  </message>
+  <message name="IDS_DEFAULT_AVATAR_LABEL_37" desc="The label for the unicorn avatar.">
+    Unicorn
+  </message>
 
   <message name="IDS_PROFILES_LOCAL_PROFILE_STATE" desc="This is displayed underneath the profile name for profiles that are not signed in to sync.">
     Not signed in
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_cat.png b/chrome/app/theme/default_100_percent/common/origami/avatar_cat.png
new file mode 100644
index 0000000..8b8dc618
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_cat.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_corgi.png b/chrome/app/theme/default_100_percent/common/origami/avatar_corgi.png
new file mode 100644
index 0000000..733d8a3
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_corgi.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_dragon.png b/chrome/app/theme/default_100_percent/common/origami/avatar_dragon.png
new file mode 100644
index 0000000..826fd46
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_dragon.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_elephant.png b/chrome/app/theme/default_100_percent/common/origami/avatar_elephant.png
new file mode 100644
index 0000000..7a8b48bc
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_elephant.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_fox.png b/chrome/app/theme/default_100_percent/common/origami/avatar_fox.png
new file mode 100644
index 0000000..7aba442
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_fox.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_monkey.png b/chrome/app/theme/default_100_percent/common/origami/avatar_monkey.png
new file mode 100644
index 0000000..00386f4
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_monkey.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_panda.png b/chrome/app/theme/default_100_percent/common/origami/avatar_panda.png
new file mode 100644
index 0000000..408f67f
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_panda.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_penguin.png b/chrome/app/theme/default_100_percent/common/origami/avatar_penguin.png
new file mode 100644
index 0000000..13d699b
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_penguin.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_pinkbutterfly.png b/chrome/app/theme/default_100_percent/common/origami/avatar_pinkbutterfly.png
new file mode 100644
index 0000000..1d847119
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_pinkbutterfly.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_rabbit.png b/chrome/app/theme/default_100_percent/common/origami/avatar_rabbit.png
new file mode 100644
index 0000000..1f842a17
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_rabbit.png
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/origami/avatar_unicorn.png b/chrome/app/theme/default_100_percent/common/origami/avatar_unicorn.png
new file mode 100644
index 0000000..7df13dc
--- /dev/null
+++ b/chrome/app/theme/default_100_percent/common/origami/avatar_unicorn.png
Binary files differ
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 1119bf6a..1793f310 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -209,6 +209,19 @@
       <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_24" file="common/profile_avatar_note.png" />
       <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_25" file="common/profile_avatar_sun_cloud.png" />
       <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_26" file="common/profile_avatar_placeholder.png" />
+      <if expr="not is_android and not chromeos">
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_27" file="common/origami/avatar_cat.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_28" file="common/origami/avatar_corgi.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_29" file="common/origami/avatar_dragon.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_30" file="common/origami/avatar_elephant.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_31" file="common/origami/avatar_fox.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_32" file="common/origami/avatar_monkey.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_33" file="common/origami/avatar_panda.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_34" file="common/origami/avatar_penguin.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_35" file="common/origami/avatar_pinkbutterfly.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_36" file="common/origami/avatar_rabbit.png" />
+        <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_37" file="common/origami/avatar_unicorn.png" />
+      </if>
       <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE" file="common/profile_avatar_placeholder_large.png" />
       <structure type="chrome_scaled_image" name="IDR_PROFILES_DICE_TURN_ON_SYNC" file="common/turn_on_sync_illustration.png" />
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2cf51abe..880d2fdc 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -989,6 +989,8 @@
     "page_load_metrics/observers/https_engagement_metrics/https_engagement_service.h",
     "page_load_metrics/observers/https_engagement_metrics/https_engagement_service_factory.cc",
     "page_load_metrics/observers/https_engagement_metrics/https_engagement_service_factory.h",
+    "page_load_metrics/observers/largest_contentful_paint_handler.cc",
+    "page_load_metrics/observers/largest_contentful_paint_handler.h",
     "page_load_metrics/observers/live_tab_count_page_load_metrics_observer.cc",
     "page_load_metrics/observers/live_tab_count_page_load_metrics_observer.h",
     "page_load_metrics/observers/loading_predictor_page_load_metrics_observer.cc",
@@ -1322,6 +1324,8 @@
     "previews/previews_service.h",
     "previews/previews_service_factory.cc",
     "previews/previews_service_factory.h",
+    "previews/previews_top_host_provider_impl.cc",
+    "previews/previews_top_host_provider_impl.h",
     "previews/previews_ui_tab_helper.cc",
     "previews/previews_ui_tab_helper.h",
     "previews/resource_loading_hints/resource_loading_hints_web_contents_observer.cc",
@@ -1559,6 +1563,8 @@
     "signin/signin_util.h",
     "site_details.cc",
     "site_details.h",
+    "site_isolation_policy.cc",
+    "site_isolation_policy.h",
     "speech/chrome_speech_recognition_manager_delegate.cc",
     "speech/chrome_speech_recognition_manager_delegate.h",
     "speech/speech_recognizer.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 1ce55a3b..1c354ac 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -643,6 +643,16 @@
 #endif  // OS_ANDROID
 
 #if defined(OS_ANDROID)
+const FeatureEntry::FeatureParam
+    kInterestFeedLargerImagesFeatureVariationConstant[] = {
+        {"feed_ui_enabled", "true"},
+        {"feed_server_endpoint",
+         "https://www.google.com/httpservice/noretry/DiscoverClankService/"
+         "FeedQuery"}};
+const FeatureEntry::FeatureVariation kInterestFeedFeatureVariations[] = {
+    {"(larger images)", kInterestFeedLargerImagesFeatureVariationConstant,
+     base::size(kInterestFeedLargerImagesFeatureVariationConstant), nullptr}};
+
 const FeatureEntry::FeatureVariation kRemoteSuggestionsFeatureVariations[] = {
     {"via content suggestion server (backed by ChromeReader)", nullptr, 0,
      "3313421"},
@@ -1793,6 +1803,10 @@
      flag_descriptions::kSSLCommittedInterstitialsDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kSSLCommittedInterstitials)},
 #if defined(OS_ANDROID)
+    {"enable-site-isolation-for-password-sites",
+     flag_descriptions::kSiteIsolationForPasswordSitesName,
+     flag_descriptions::kSiteIsolationForPasswordSitesDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(features::kSiteIsolationForPasswordSites)},
     {"enable-site-per-process", flag_descriptions::kStrictSiteIsolationName,
      flag_descriptions::kStrictSiteIsolationDescription, kOsAndroid,
      SINGLE_VALUE_TYPE(switches::kSitePerProcess)},
@@ -2226,7 +2240,9 @@
     {"interest-feed-content-suggestions",
      flag_descriptions::kInterestFeedContentSuggestionsName,
      flag_descriptions::kInterestFeedContentSuggestionsDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(feed::kInterestFeedContentSuggestions)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(feed::kInterestFeedContentSuggestions,
+                                    kInterestFeedFeatureVariations,
+                                    "InterestFeedContentSuggestions")},
     {"enable-ntp-remote-suggestions",
      flag_descriptions::kEnableNtpRemoteSuggestionsName,
      flag_descriptions::kEnableNtpRemoteSuggestionsDescription, kOsAndroid,
@@ -2530,6 +2546,13 @@
      kOsAll,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillUpstreamEditableExpirationDate)},
+    {"enable-autofill-do-not-upload-save-unsupported-cards",
+     flag_descriptions::kEnableAutofillDoNotUploadSaveUnsupportedCardsName,
+     flag_descriptions::
+         kEnableAutofillDoNotUploadSaveUnsupportedCardsDescription,
+     kOsAll,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillDoNotUploadSaveUnsupportedCards)},
     {"enable-autofill-import-non-focusable-credit-card-forms",
      flag_descriptions::kEnableAutofillImportNonFocusableCreditCardFormsName,
      flag_descriptions::
@@ -2777,9 +2800,15 @@
          kOutOfProcessHeapProfilingKeepSmallAllocationsDescription,
      kOsAll, SINGLE_VALUE_TYPE(heap_profiling::kMemlogKeepSmallAllocations)},
 
-    {"memlog-sampling", flag_descriptions::kOutOfProcessHeapProfilingSampling,
-     flag_descriptions::kOutOfProcessHeapProfilingSamplingDescription, kOsAll,
-     SINGLE_VALUE_TYPE(heap_profiling::kMemlogSampling)},
+    {"memlog-in-process",
+     flag_descriptions::kOutOfProcessHeapProfilingInProcess,
+     flag_descriptions::kOutOfProcessHeapProfilingInProcessDescription, kOsAll,
+     SINGLE_VALUE_TYPE(heap_profiling::kMemlogInProcess)},
+
+    {"memlog-sampling-rate",
+     flag_descriptions::kOutOfProcessHeapProfilingSamplingRate,
+     flag_descriptions::kOutOfProcessHeapProfilingSamplingRateDescription,
+     kOsAll, SINGLE_VALUE_TYPE(heap_profiling::kMemlogSamplingRate)},
 
     {"memlog-stack-mode", flag_descriptions::kOOPHPStackModeName,
      flag_descriptions::kOOPHPStackModeDescription, kOsAll,
@@ -3023,6 +3052,12 @@
      flag_descriptions::kShowManagedUiDescription,
      kOsWin | kOsMac | kOsLinux | kOsCrOS,
      FEATURE_VALUE_TYPE(features::kShowManagedUi)},
+
+    {"link-managed-notice-to-management-page",
+     flag_descriptions::kLinkManagedNoticeToChromeUIManagementURLName,
+     flag_descriptions::kLinkManagedNoticeToChromeUIManagementURLDescription,
+     kOsWin | kOsMac | kOsLinux,
+     FEATURE_VALUE_TYPE(features::kLinkManagedNoticeToChromeUIManagementURL)},
 #endif  // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
 
 #if !defined(OS_ANDROID)
diff --git a/chrome/browser/accessibility/OWNERS b/chrome/browser/accessibility/OWNERS
index 2c85844e..84f2c83 100644
--- a/chrome/browser/accessibility/OWNERS
+++ b/chrome/browser/accessibility/OWNERS
@@ -1,4 +1,6 @@
 file://ui/accessibility/OWNERS
 
+katie@chromium.org
+
 # TEAM: chromium-accessibility@chromium.org
 # COMPONENT: UI>Accessibility
diff --git a/chrome/browser/accessibility/accessibility_labels_service.cc b/chrome/browser/accessibility/accessibility_labels_service.cc
index 5c75bc3..122e4ddc 100644
--- a/chrome/browser/accessibility/accessibility_labels_service.cc
+++ b/chrome/browser/accessibility/accessibility_labels_service.cc
@@ -34,12 +34,13 @@
 }
 
 // static
-void AccessibilityLabelsService::InitOffTheRecordPrefs(Profile* profile) {
-  DCHECK(profile->IsOffTheRecord());
-  profile->GetPrefs()->SetBoolean(prefs::kAccessibilityImageLabelsEnabled,
-                                  false);
-  profile->GetPrefs()->SetBoolean(prefs::kAccessibilityImageLabelsOptInAccepted,
-                                  false);
+void AccessibilityLabelsService::InitOffTheRecordPrefs(
+    Profile* off_the_record_profile) {
+  DCHECK(off_the_record_profile->IsOffTheRecord());
+  off_the_record_profile->GetPrefs()->SetBoolean(
+      prefs::kAccessibilityImageLabelsEnabled, false);
+  off_the_record_profile->GetPrefs()->SetBoolean(
+      prefs::kAccessibilityImageLabelsOptInAccepted, false);
 }
 
 void AccessibilityLabelsService::Init() {
@@ -70,8 +71,7 @@
 
   // Hidden behind a feature flag.
   if (base::FeatureList::IsEnabled(
-          features::kExperimentalAccessibilityLabels) &&
-      accessibility_state_utils::IsScreenReaderEnabled()) {
+          features::kExperimentalAccessibilityLabels)) {
     bool enabled = profile_->GetPrefs()->GetBoolean(
         prefs::kAccessibilityImageLabelsEnabled);
     ax_mode.set_mode(ui::AXMode::kLabelImages, enabled);
diff --git a/chrome/browser/accessibility/accessibility_labels_service.h b/chrome/browser/accessibility/accessibility_labels_service.h
index 949e948..8213c146 100644
--- a/chrome/browser/accessibility/accessibility_labels_service.h
+++ b/chrome/browser/accessibility/accessibility_labels_service.h
@@ -27,7 +27,7 @@
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
   // Off the record profiles will default to having the feature disabled.
-  static void InitOffTheRecordPrefs(Profile* profile);
+  static void InitOffTheRecordPrefs(Profile* off_the_record_profile);
 
   void Init();
 
diff --git a/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.cc b/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.cc
index 0812abe..1e53230d 100644
--- a/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.cc
+++ b/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.cc
@@ -32,64 +32,81 @@
       AttachCurrentThread(), java_assistant_payment_request_delegate_);
 }
 
-void AssistantPaymentRequestDelegate::OnGetPaymentInformation(
+void AssistantPaymentRequestDelegate::OnContactInfoChanged(
     JNIEnv* env,
-    const JavaParamRef<jobject>& jcaller,
-    jboolean jsucceed,
-    const JavaParamRef<jobject>& jcard,
-    const JavaParamRef<jobject>& jaddress,
-    const JavaParamRef<jstring>& jpayer_name,
-    const JavaParamRef<jstring>& jpayer_phone,
-    const JavaParamRef<jstring>& jpayer_email,
-    jboolean jis_terms_and_services_accepted) {
-  std::unique_ptr<PaymentInformation> payment_info =
-      std::make_unique<PaymentInformation>();
-  payment_info->succeed = jsucceed;
-  payment_info->is_terms_and_conditions_accepted =
-      jis_terms_and_services_accepted;
-  if (payment_info->succeed) {
-    if (jcard != nullptr) {
-      payment_info->card = std::make_unique<autofill::CreditCard>();
-      autofill::PersonalDataManagerAndroid::PopulateNativeCreditCardFromJava(
-          jcard, env, payment_info->card.get());
+    const base::android::JavaParamRef<jobject>& jcaller,
+    const base::android::JavaParamRef<jstring>& jpayer_name,
+    const base::android::JavaParamRef<jstring>& jpayer_phone,
+    const base::android::JavaParamRef<jstring>& jpayer_email) {
+  std::string name;
+  std::string phone;
+  std::string email;
 
-      auto guid = payment_info->card->billing_address_id();
-      if (!guid.empty()) {
-        autofill::AutofillProfile* profile =
-            autofill::PersonalDataManagerFactory::GetForProfile(
-                ProfileManager::GetLastUsedProfile())
-                ->GetProfileByGUID(guid);
-        if (profile != nullptr)
-          payment_info->billing_address =
-              std::make_unique<autofill::AutofillProfile>(*profile);
-      }
-    }
-    if (jaddress != nullptr) {
-      payment_info->shipping_address =
-          std::make_unique<autofill::AutofillProfile>();
-      autofill::PersonalDataManagerAndroid::PopulateNativeProfileFromJava(
-          jaddress, env, payment_info->shipping_address.get());
-    }
-    if (jpayer_name != nullptr) {
-      base::android::ConvertJavaStringToUTF8(env, jpayer_name,
-                                             &payment_info->payer_name);
-    }
-    if (jpayer_phone != nullptr) {
-      base::android::ConvertJavaStringToUTF8(env, jpayer_phone,
-                                             &payment_info->payer_phone);
-    }
-    if (jpayer_email != nullptr) {
-      base::android::ConvertJavaStringToUTF8(env, jpayer_email,
-                                             &payment_info->payer_email);
-    }
+  if (jpayer_name) {
+    base::android::ConvertJavaStringToUTF8(env, jpayer_name, &name);
   }
-  ui_controller_->OnGetPaymentInformation(std::move(payment_info));
+
+  if (jpayer_phone) {
+    base::android::ConvertJavaStringToUTF8(env, jpayer_phone, &phone);
+  }
+
+  if (jpayer_email) {
+    base::android::ConvertJavaStringToUTF8(env, jpayer_email, &email);
+  }
+
+  ui_controller_->OnContactInfoChanged(name, phone, email);
 }
 
-void AssistantPaymentRequestDelegate::OnCancelButtonClicked(
+void AssistantPaymentRequestDelegate::OnShippingAddressChanged(
     JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& jcaller) {
-  ui_controller_->OnCancelButtonClicked();
+    const base::android::JavaParamRef<jobject>& jcaller,
+    const base::android::JavaParamRef<jobject>& jaddress) {
+  if (!jaddress) {
+    ui_controller_->OnShippingAddressChanged(nullptr);
+    return;
+  }
+
+  auto shipping_address = std::make_unique<autofill::AutofillProfile>();
+  autofill::PersonalDataManagerAndroid::PopulateNativeProfileFromJava(
+      jaddress, env, shipping_address.get());
+  ui_controller_->OnShippingAddressChanged(std::move(shipping_address));
+}
+
+void AssistantPaymentRequestDelegate::OnCreditCardChanged(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& jcaller,
+    const base::android::JavaParamRef<jobject>& jcard) {
+  if (!jcard) {
+    ui_controller_->OnCreditCardChanged(nullptr);
+    return;
+  }
+
+  auto card = std::make_unique<autofill::CreditCard>();
+  autofill::PersonalDataManagerAndroid::PopulateNativeCreditCardFromJava(
+      jcard, env, card.get());
+
+  auto guid = card->billing_address_id();
+  if (!guid.empty()) {
+    autofill::AutofillProfile* profile =
+        autofill::PersonalDataManagerFactory::GetForProfile(
+            ProfileManager::GetLastUsedProfile())
+            ->GetProfileByGUID(guid);
+    if (profile != nullptr) {
+      auto billing_address =
+          std::make_unique<autofill::AutofillProfile>(*profile);
+      ui_controller_->OnBillingAddressChanged(std::move(billing_address));
+    }
+  }
+
+  ui_controller_->OnCreditCardChanged(std::move(card));
+}
+
+void AssistantPaymentRequestDelegate::OnTermsAndConditionsChanged(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& jcaller,
+    jint state) {
+  ui_controller_->OnTermsAndConditionsChanged(
+      static_cast<TermsAndConditionsState>(state));
 }
 
 base::android::ScopedJavaGlobalRef<jobject>
diff --git a/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.h b/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.h
index 43c48bf9..87545be 100644
--- a/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.h
+++ b/chrome/browser/android/autofill_assistant/assistant_payment_request_delegate.h
@@ -15,22 +15,26 @@
   explicit AssistantPaymentRequestDelegate(UiControllerAndroid* ui_controller);
   ~AssistantPaymentRequestDelegate();
 
-  void OnGetPaymentInformation(
+  void OnContactInfoChanged(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& jcaller,
-      jboolean jsucceed,
-      const base::android::JavaParamRef<jobject>& jcard,
-      const base::android::JavaParamRef<jobject>& jaddress,
       const base::android::JavaParamRef<jstring>& jpayer_name,
       const base::android::JavaParamRef<jstring>& jpayer_phone,
-      const base::android::JavaParamRef<jstring>& jpayer_email,
-      jboolean jis_terms_and_services_accepted);
+      const base::android::JavaParamRef<jstring>& jpayer_email);
 
-  // TODO(crbug.com/806868): Remove this once PR is using the actions carousel
-  // to show its buttons.
-  void OnCancelButtonClicked(
+  void OnShippingAddressChanged(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& jcaller);
+      const base::android::JavaParamRef<jobject>& jcaller,
+      const base::android::JavaParamRef<jobject>& jaddress);
+
+  void OnCreditCardChanged(JNIEnv* env,
+                           const base::android::JavaParamRef<jobject>& jcaller,
+                           const base::android::JavaParamRef<jobject>& jcard);
+
+  void OnTermsAndConditionsChanged(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& jcaller,
+      jint state);
 
   base::android::ScopedJavaGlobalRef<jobject> GetJavaObject();
 
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index 9ae616f..a9685ea 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -321,13 +321,6 @@
 
   JNIEnv* env = AttachCurrentThread();
 
-  // TODO(crbug.com/806868): Remove this case once payment request reuses the
-  // carousel.
-  if (ui_delegate_->GetPaymentRequestOptions()) {
-    Java_AutofillAssistantUiController_clearActions(env, java_object_);
-    return;
-  }
-
   bool has_close_or_cancel = false;
   auto chips = Java_AutofillAssistantUiController_createChipList(env);
   const auto& actions = ui_delegate_->GetActions();
@@ -338,7 +331,7 @@
     switch (action.type) {
       case HIGHLIGHTED_ACTION:
         Java_AutofillAssistantUiController_addHighlightedActionButton(
-            env, java_object_, chips, text, i);
+            env, java_object_, chips, text, i, action.disabled);
         break;
 
       case NORMAL_ACTION:
@@ -524,16 +517,36 @@
                                                     GetModel());
 }
 
-void UiControllerAndroid::OnGetPaymentInformation(
-    std::unique_ptr<PaymentInformation> payment_info) {
-  ui_delegate_->SetPaymentInformation(std::move(payment_info));
+void UiControllerAndroid::OnShippingAddressChanged(
+    std::unique_ptr<autofill::AutofillProfile> address) {
+  ui_delegate_->SetShippingAddress(std::move(address));
+}
+
+void UiControllerAndroid::OnBillingAddressChanged(
+    std::unique_ptr<autofill::AutofillProfile> address) {
+  ui_delegate_->SetBillingAddress(std::move(address));
+}
+
+void UiControllerAndroid::OnContactInfoChanged(std::string name,
+                                               std::string phone,
+                                               std::string email) {
+  ui_delegate_->SetContactInfo(name, phone, email);
+}
+
+void UiControllerAndroid::OnCreditCardChanged(
+    std::unique_ptr<autofill::CreditCard> card) {
+  ui_delegate_->SetCreditCard(std::move(card));
+}
+
+void UiControllerAndroid::OnTermsAndConditionsChanged(
+    TermsAndConditionsState state) {
+  ui_delegate_->SetTermsAndConditions(state);
 }
 
 void UiControllerAndroid::OnPaymentRequestChanged(
     const PaymentRequestOptions* payment_options) {
   JNIEnv* env = AttachCurrentThread();
   auto jmodel = GetPaymentRequestModel();
-  UpdateActions();
   if (!payment_options) {
     Java_AssistantPaymentRequestModel_clearOptions(env, jmodel);
     return;
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.h b/chrome/browser/android/autofill_assistant/ui_controller_android.h
index 51914b5b..5b60599 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.h
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.h
@@ -75,9 +75,15 @@
   void OnFeedbackButtonClicked();
 
   // Called by AssistantPaymentRequestDelegate:
-  void OnGetPaymentInformation(
-      std::unique_ptr<PaymentInformation> payment_info);
-  void OnCancelButtonClicked();
+  void OnShippingAddressChanged(
+      std::unique_ptr<autofill::AutofillProfile> address);
+  void OnBillingAddressChanged(
+      std::unique_ptr<autofill::AutofillProfile> address);
+  void OnContactInfoChanged(std::string name,
+                            std::string phone,
+                            std::string email);
+  void OnCreditCardChanged(std::unique_ptr<autofill::CreditCard> card);
+  void OnTermsAndConditionsChanged(TermsAndConditionsState state);
 
   // Called by Java.
   void SnackbarResult(JNIEnv* env,
@@ -140,6 +146,7 @@
   // action after a short delay unless the user taps the undo button.
   void ShowSnackbar(const std::string& message,
                     base::OnceCallback<void()> action);
+  void OnCancelButtonClicked();
   void OnCancelButtonWithActionIndexClicked(int action_index);
   void OnCancel(int action_index);
 
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
index 05848399..4104345 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
@@ -10,6 +10,7 @@
 #include "base/base64.h"
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/json/json_string_value_serializer.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -58,6 +59,8 @@
 const char kContextualSearchThumbnail[] = "thumbnail";
 const char kContextualSearchAction[] = "action";
 const char kContextualSearchCategory[] = "category";
+const char kContextualSearchSearchUrlFull[] = "search_url_full";
+const char kContextualSearchSearchUrlPreload[] = "search_url_preload";
 
 const char kActionCategoryAddress[] = "ADDRESS";
 const char kActionCategoryEmail[] = "EMAIL";
@@ -74,6 +77,7 @@
 
 // The version of the Contextual Cards API that we want to invoke.
 const int kContextualCardsUrlActions = 3;
+const int kContextualCardsDefinitions = 4;
 
 const int kResponseCodeUninitialized = -1;
 
@@ -215,12 +219,14 @@
   std::string quick_action_uri = "";
   int64_t logged_event_id = 0;
   QuickActionCategory quick_action_category = QUICK_ACTION_CATEGORY_NONE;
+  std::string search_url_full = "";
+  std::string search_url_preload = "";
 
   DecodeSearchTermFromJsonResponse(
       json_string, &search_term, &display_text, &alternate_term, &mid,
       &prevent_preload, &mention_start, &mention_end, &context_language,
       &thumbnail_url, &caption, &quick_action_uri, &quick_action_category,
-      &logged_event_id);
+      &logged_event_id, &search_url_full, &search_url_preload);
   if (mention_start != 0 || mention_end != 0) {
     // Sanity check that our selection is non-zero and it is less than
     // 100 characters as that would make contextual search bar hide.
@@ -242,7 +248,8 @@
       is_invalid, response_code, search_term, display_text, alternate_term, mid,
       prevent_preload == kDoPreventPreloadValue, start_adjust, end_adjust,
       context_language, thumbnail_url, caption, quick_action_uri,
-      quick_action_category, logged_event_id));
+      quick_action_category, logged_event_id, search_url_full,
+      search_url_preload));
 }
 
 std::string ContextualSearchDelegate::BuildRequestUrl(
@@ -258,7 +265,13 @@
   TemplateURLRef::SearchTermsArgs search_terms_args =
       TemplateURLRef::SearchTermsArgs(base::string16());
 
+  // Set the Coca-integration version based on our current active feature,
+  // or an override param from our field trial.
   int contextual_cards_version = kContextualCardsUrlActions;
+  if (base::FeatureList::IsEnabled(
+          chrome::android::kContextualSearchDefinitions)) {
+    contextual_cards_version = kContextualCardsDefinitions;
+  }
   if (field_trial_->GetContextualCardsVersion() != 0) {
     contextual_cards_version = field_trial_->GetContextualCardsVersion();
   }
@@ -432,7 +445,9 @@
     std::string* caption,
     std::string* quick_action_uri,
     QuickActionCategory* quick_action_category,
-    int64_t* logged_event_id) {
+    int64_t* logged_event_id,
+    std::string* search_url_full,
+    std::string* search_url_preload) {
   bool contains_xssi_escape =
       base::StartsWith(response, kXssiEscape, base::CompareCase::SENSITIVE);
   const std::string& proper_json =
@@ -504,6 +519,11 @@
     }
   }
 
+  // Contextual Cards V4 may also provide full search URLs to use in the
+  // overlay.
+  dict->GetString(kContextualSearchSearchUrlFull, search_url_full);
+  dict->GetString(kContextualSearchSearchUrlPreload, search_url_preload);
+
   // Any Contextual Cards integration.
   // For testing purposes check if there was a diagnostic from Contextual
   // Cards and output that into the log.
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.h b/chrome/browser/android/contextualsearch/contextual_search_delegate.h
index 47903fb0..13d64af 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate.h
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.h
@@ -150,7 +150,9 @@
       std::string* caption,
       std::string* quick_action_uri,
       QuickActionCategory* quick_action_category,
-      int64_t* logged_event_id);
+      int64_t* logged_event_id,
+      std::string* search_url_full,
+      std::string* search_url_preload);
 
   // Extracts the start and end location from a mentions list, and sets the
   // integers referenced by |startResult| and |endResult|.
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc b/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc
index 71f975d8..04acfee 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc
@@ -529,7 +529,12 @@
       "\"info_text\":\"44th U.S. President\","
       "\"display_text\":\"Barack Obama\", \"mentions\":[0,15],"
       "\"selected_text\":\"obama\", \"resolved_term\":\"barack obama\","
-      "\"logged_event_id\":\"1234567890123456789\"}";
+      "\"logged_event_id\":\"1234567890123456789\","
+      "\"search_url_full\":\"https://www.google.com/"
+      "search?q=define+obscure&ctxs=2\","
+      "\"search_url_preload\":\"https://www.google.com/"
+      "search?q=define+obscure&ctxs=2&pf=c&sns=1\""
+      "}";
   std::string search_term;
   std::string display_text;
   std::string alternate_term;
@@ -543,12 +548,14 @@
   std::string quick_action_uri;
   int64_t logged_event_id;
   QuickActionCategory quick_action_category = QUICK_ACTION_CATEGORY_NONE;
+  std::string search_url_full;
+  std::string search_url_preload;
 
   delegate_->DecodeSearchTermFromJsonResponse(
       json_with_escape, &search_term, &display_text, &alternate_term, &mid,
       &prevent_preload, &mention_start, &mention_end, &context_language,
       &thumbnail_url, &caption, &quick_action_uri, &quick_action_category,
-      &logged_event_id);
+      &logged_event_id, &search_url_full, &search_url_preload);
 
   EXPECT_EQ("obama", search_term);
   EXPECT_EQ("Barack Obama", display_text);
@@ -561,6 +568,10 @@
   EXPECT_EQ("", quick_action_uri);
   EXPECT_EQ(QUICK_ACTION_CATEGORY_NONE, quick_action_category);
   EXPECT_EQ(1234567890123456789, logged_event_id);
+  EXPECT_EQ("https://www.google.com/search?q=define+obscure&ctxs=2",
+            search_url_full);
+  EXPECT_EQ("https://www.google.com/search?q=define+obscure&ctxs=2&pf=c&sns=1",
+            search_url_preload);
 }
 
 TEST_F(ContextualSearchDelegateTest, ResponseWithLanguage) {
diff --git a/chrome/browser/android/contextualsearch/contextual_search_field_trial.cc b/chrome/browser/android/contextualsearch/contextual_search_field_trial.cc
index be5a340..4b41395 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_field_trial.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_field_trial.cc
@@ -5,7 +5,9 @@
 #include "chrome/browser/android/contextualsearch/contextual_search_field_trial.h"
 
 #include "base/command_line.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/strings/string_number_conversions.h"
+#include "chrome/browser/android/chrome_feature_list.h"
 #include "components/variations/variations_associated_data.h"
 
 namespace {
@@ -117,6 +119,12 @@
     std::string param_string = GetSwitch(name);
     if (param_string.empty())
       param_string = GetParam(name);
+    // If we still didn't get a param, try getting a Feature param.
+    if (param_string.empty()) {
+      // For now, we just support the Contextual Search Definitions feature.
+      param_string = base::GetFieldTrialParamValueByFeature(
+          chrome::android::kContextualSearchDefinitions, name);
+    }
 
     int param_int;
     if (!param_string.empty() && base::StringToInt(param_string, &param_int))
diff --git a/chrome/browser/android/contextualsearch/contextual_search_manager.cc b/chrome/browser/android/contextualsearch/contextual_search_manager.cc
index afac380..409fd0c 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_manager.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_manager.cc
@@ -180,6 +180,12 @@
   base::android::ScopedJavaLocalRef<jstring> j_quick_action_uri =
       base::android::ConvertUTF8ToJavaString(
           env, resolved_search_term.quick_action_uri);
+  base::android::ScopedJavaLocalRef<jstring> j_search_url_full =
+      base::android::ConvertUTF8ToJavaString(
+          env, resolved_search_term.search_url_full);
+  base::android::ScopedJavaLocalRef<jstring> j_search_url_preload =
+      base::android::ConvertUTF8ToJavaString(
+          env, resolved_search_term.search_url_preload);
   Java_ContextualSearchManager_onSearchTermResolutionResponse(
       env, java_manager_, resolved_search_term.is_invalid,
       resolved_search_term.response_code, j_search_term, j_display_text,
@@ -188,7 +194,8 @@
       resolved_search_term.selection_end_adjust, j_context_language,
       j_thumbnail_url, j_caption, j_quick_action_uri,
       resolved_search_term.quick_action_category,
-      resolved_search_term.logged_event_id);
+      resolved_search_term.logged_event_id, j_search_url_full,
+      j_search_url_preload);
 }
 
 void ContextualSearchManager::OnTextSurroundingSelectionAvailable(
diff --git a/chrome/browser/android/contextualsearch/resolved_search_term.cc b/chrome/browser/android/contextualsearch/resolved_search_term.cc
index df5ea61..cb30ae5 100644
--- a/chrome/browser/android/contextualsearch/resolved_search_term.cc
+++ b/chrome/browser/android/contextualsearch/resolved_search_term.cc
@@ -21,7 +21,9 @@
       caption(""),
       quick_action_uri(""),
       quick_action_category(QUICK_ACTION_CATEGORY_NONE),
-      logged_event_id(0) {}
+      logged_event_id(0),
+      search_url_full(""),
+      search_url_preload("") {}
 
 ResolvedSearchTerm::ResolvedSearchTerm(
     bool is_invalid,
@@ -38,7 +40,9 @@
     const std::string& caption,
     const std::string& quick_action_uri,
     const QuickActionCategory& quick_action_category,
-    int64_t logged_event_id)
+    int64_t logged_event_id,
+    const std::string& search_url_full,
+    const std::string& search_url_preload)
     : is_invalid(is_invalid),
       response_code(response_code),
       search_term(search_term),
@@ -53,6 +57,8 @@
       caption(caption),
       quick_action_uri(quick_action_uri),
       quick_action_category(quick_action_category),
-      logged_event_id(logged_event_id) {}
+      logged_event_id(logged_event_id),
+      search_url_full(search_url_full),
+      search_url_preload(search_url_preload) {}
 
 ResolvedSearchTerm::~ResolvedSearchTerm() {}
diff --git a/chrome/browser/android/contextualsearch/resolved_search_term.h b/chrome/browser/android/contextualsearch/resolved_search_term.h
index 537c1c9..edc8546 100644
--- a/chrome/browser/android/contextualsearch/resolved_search_term.h
+++ b/chrome/browser/android/contextualsearch/resolved_search_term.h
@@ -41,7 +41,9 @@
                      const std::string& caption,
                      const std::string& quick_action_uri,
                      const QuickActionCategory& quick_action_category,
-                     int64_t logged_event_id);
+                     int64_t logged_event_id,
+                     const std::string& search_url_full,
+                     const std::string& search_url_preload);
   ~ResolvedSearchTerm();
 
   const bool is_invalid;
@@ -60,6 +62,8 @@
   const std::string quick_action_uri;
   const QuickActionCategory quick_action_category;
   const int64_t logged_event_id;  // Often 0.
+  const std::string search_url_full;
+  const std::string search_url_preload;
 
   DISALLOW_COPY_AND_ASSIGN(ResolvedSearchTerm);
 };
diff --git a/chrome/browser/android/download/download_controller.cc b/chrome/browser/android/download/download_controller.cc
index 20cba72..69f0c95 100644
--- a/chrome/browser/android/download/download_controller.cc
+++ b/chrome/browser/android/download/download_controller.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/android/download/download_manager_service.h"
 #include "chrome/browser/android/download/download_utils.h"
 #include "chrome/browser/android/tab_android.h"
+#include "chrome/browser/download/download_offline_content_provider.h"
 #include "chrome/browser/download/download_stats.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/offline_pages/android/offline_page_bridge.h"
@@ -386,6 +387,10 @@
   if (download::AutoResumptionHandler::Get())
     download::AutoResumptionHandler::Get()->OnDownloadStarted(download_item);
 
+  DownloadUtils::GetDownloadOfflineContentProvider(
+      content::DownloadItemUtils::GetBrowserContext(download_item))
+      ->OnDownloadStarted(download_item);
+
   OnDownloadUpdated(download_item);
 }
 
diff --git a/chrome/browser/android/download/download_manager_bridge.cc b/chrome/browser/android/download/download_manager_bridge.cc
index e4f494a..a0f7423 100644
--- a/chrome/browser/android/download/download_manager_bridge.cc
+++ b/chrome/browser/android/download/download_manager_bridge.cc
@@ -24,14 +24,15 @@
 static void JNI_DownloadManagerBridge_OnAddCompletedDownloadDone(
     JNIEnv* env,
     jlong callback_id,
-    jlong download_id) {
+    jlong download_id,
+    jboolean can_resolve) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(callback_id);
 
   // Convert java long long int to c++ pointer, take ownership.
   std::unique_ptr<AddCompletedDownloadCallback> cb(
       reinterpret_cast<AddCompletedDownloadCallback*>(callback_id));
-  std::move(*cb).Run(download_id);
+  std::move(*cb).Run(download_id, can_resolve);
 }
 
 void DownloadManagerBridge::AddCompletedDownload(
diff --git a/chrome/browser/android/download/download_manager_bridge.h b/chrome/browser/android/download/download_manager_bridge.h
index 5c87f8c..c726b1d5 100644
--- a/chrome/browser/android/download/download_manager_bridge.h
+++ b/chrome/browser/android/download/download_manager_bridge.h
@@ -9,7 +9,7 @@
 #include "components/download/public/common/download_item.h"
 
 using DownloadItem = download::DownloadItem;
-using AddCompletedDownloadCallback = base::OnceCallback<void(int64_t)>;
+using AddCompletedDownloadCallback = base::OnceCallback<void(int64_t, bool)>;
 
 // This class pairs with DownloadManagerBridge on Java side, that handles all
 // the android DownloadManager related functionalities. Both classes have only
diff --git a/chrome/browser/android/download/download_utils.cc b/chrome/browser/android/download/download_utils.cc
index 27670e0..dc8afe3 100644
--- a/chrome/browser/android/download/download_utils.cc
+++ b/chrome/browser/android/download/download_utils.cc
@@ -8,15 +8,21 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/android/chrome_feature_list.h"
+#include "chrome/browser/download/download_offline_content_provider.h"
 #include "chrome/browser/download/offline_item_utils.h"
+#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/download/public/common/download_utils.h"
+#include "components/offline_items_collection/core/offline_content_aggregator.h"
+#include "content/public/browser/browser_context.h"
 #include "jni/DownloadUtils_jni.h"
 #include "ui/base/l10n/l10n_util.h"
 
 using base::android::ConvertUTF16ToJavaString;
 using base::android::JavaParamRef;
 using base::android::ScopedJavaLocalRef;
+using OfflineContentAggregator =
+    offline_items_collection::OfflineContentAggregator;
 
 namespace {
 // If received bytes is more than the size limit and resumption will restart
@@ -24,6 +30,9 @@
 int kDefaultAutoResumptionSizeLimit = 10 * 1024 * 1024;  // 10 MB
 const char kAutoResumptionSizeLimitParamName[] = "AutoResumptionSizeLimit";
 
+static DownloadOfflineContentProvider* g_download_provider = nullptr;
+static DownloadOfflineContentProvider* g_download_provider_incognito = nullptr;
+
 }  // namespace
 
 static ScopedJavaLocalRef<jstring> JNI_DownloadUtils_GetFailStateMessage(
@@ -68,3 +77,30 @@
              ? size_limit
              : kDefaultAutoResumptionSizeLimit;
 }
+
+// static
+DownloadOfflineContentProvider*
+DownloadUtils::GetDownloadOfflineContentProvider(
+    content::BrowserContext* browser_context) {
+  OfflineContentAggregator* aggregator =
+      OfflineContentAggregatorFactory::GetForBrowserContext(browser_context);
+  bool is_off_the_record =
+      browser_context ? browser_context->IsOffTheRecord() : false;
+
+  if (!g_download_provider) {
+    std::string name_space = OfflineContentAggregator::CreateUniqueNameSpace(
+        OfflineItemUtils::GetDownloadNamespacePrefix(false), false);
+    g_download_provider =
+        new DownloadOfflineContentProvider(aggregator, name_space);
+  }
+
+  if (!g_download_provider_incognito) {
+    std::string name_space = OfflineContentAggregator::CreateUniqueNameSpace(
+        OfflineItemUtils::GetDownloadNamespacePrefix(true), true);
+    g_download_provider_incognito =
+        new DownloadOfflineContentProvider(aggregator, name_space);
+  }
+
+  return is_off_the_record ? g_download_provider_incognito
+                           : g_download_provider;
+}
diff --git a/chrome/browser/android/download/download_utils.h b/chrome/browser/android/download/download_utils.h
index 73c0b27a..4b5f964 100644
--- a/chrome/browser/android/download/download_utils.h
+++ b/chrome/browser/android/download/download_utils.h
@@ -7,11 +7,19 @@
 
 #include "base/files/file_path.h"
 
+namespace content {
+class BrowserContext;
+}
+
+class DownloadOfflineContentProvider;
+
 // Native side of DownloadUtils.java.
 class DownloadUtils {
  public:
   static base::FilePath GetUriStringForPath(const base::FilePath& file_path);
   static int GetAutoResumptionSizeLimit();
+  static DownloadOfflineContentProvider* GetDownloadOfflineContentProvider(
+      content::BrowserContext* browser_context);
 };
 
 #endif  // CHROME_BROWSER_ANDROID_DOWNLOAD_DOWNLOAD_UTILS_H_
diff --git a/chrome/browser/android/explore_sites/blacklist_site_task_unittest.cc b/chrome/browser/android/explore_sites/blacklist_site_task_unittest.cc
index f760629..6d4cf4e 100644
--- a/chrome/browser/android/explore_sites/blacklist_site_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/blacklist_site_task_unittest.cc
@@ -21,6 +21,7 @@
 using offline_pages::TaskTestBase;
 
 namespace explore_sites {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 const char kGoogleUrl[] = "https://www.google.com";
 
@@ -77,7 +78,8 @@
 }
 
 TEST_F(ExploreSitesBlacklistSiteTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
   BlacklistSiteTask task(store(), kGoogleUrl);
   RunTask(&task);
 
diff --git a/chrome/browser/android/explore_sites/clear_activities_task_unittest.cc b/chrome/browser/android/explore_sites/clear_activities_task_unittest.cc
index 5feb9187..2e0568e5 100644
--- a/chrome/browser/android/explore_sites/clear_activities_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/clear_activities_task_unittest.cc
@@ -24,6 +24,7 @@
 namespace explore_sites {
 
 namespace {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 const char kInsertActivitySql[] =
     "INSERT INTO activity (time, category_type, url) VALUES (?, ?, ?);";
@@ -151,7 +152,8 @@
 }
 
 TEST_F(ClearActivitiesTaskTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
   ClearActivities(kJanuary2017, kJune2017);
 
   // A database failure should be completed but return with an error.
diff --git a/chrome/browser/android/explore_sites/clear_catalog_task_unittest.cc b/chrome/browser/android/explore_sites/clear_catalog_task_unittest.cc
index bc38706..093038a 100644
--- a/chrome/browser/android/explore_sites/clear_catalog_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/clear_catalog_task_unittest.cc
@@ -124,7 +124,8 @@
 };
 
 TEST_F(ExploreSitesClearCatalogTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(
+      ExploreSitesStore::InitializationStatus::kFailure, false);
 
   // A database failure should be completed but return with an error.
   EXPECT_FALSE(RunTaskWithResult());
diff --git a/chrome/browser/android/explore_sites/explore_sites_store.cc b/chrome/browser/android/explore_sites/explore_sites_store.cc
index a023e983..b3887d3 100644
--- a/chrome/browser/android/explore_sites/explore_sites_store.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_store.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/android/explore_sites/explore_sites_store.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -20,53 +22,10 @@
 
 namespace explore_sites {
 namespace {
+using offline_pages::SqlStoreBase;
 
 const char kExploreSitesStoreFileName[] = "ExploreSitesStore.db";
 
-bool PrepareDirectory(const base::FilePath& path) {
-  base::File::Error error = base::File::FILE_OK;
-  if (!base::DirectoryExists(path.DirName())) {
-    if (!base::CreateDirectoryAndGetError(path.DirName(), &error)) {
-      LOG(ERROR) << "Failed to create explore sites db directory: "
-                 << base::File::ErrorToString(error);
-      return false;
-    }
-  }
-  return true;
-}
-
-bool InitializeSync(sql::Database* db,
-                    const base::FilePath& path,
-                    bool in_memory) {
-  // These values are default.
-  db->set_page_size(4096);
-  db->set_cache_size(500);
-  db->set_histogram_tag("ExploreSitesStore");
-  db->set_exclusive_locking();
-
-  if (!in_memory && !PrepareDirectory(path))
-    return false;
-
-  bool open_db_result = in_memory ? db->OpenInMemory() : db->Open(path);
-
-  if (!open_db_result) {
-    LOG(ERROR) << "Failed to open database, in memory: " << in_memory;
-    return false;
-  }
-  db->Preload();
-
-  return ExploreSitesSchema::CreateOrUpgradeIfNeeded(db);
-}
-
-void CloseDatabaseSync(
-    sql::Database* db,
-    scoped_refptr<base::SingleThreadTaskRunner> callback_runner,
-    base::OnceClosure callback) {
-  if (db)
-    db->Close();
-  callback_runner->PostTask(FROM_HERE, std::move(callback));
-}
-
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 enum class ExploreSitesStoreEvent {
@@ -83,107 +42,77 @@
 
 }  // namespace
 
-// static
-constexpr base::TimeDelta ExploreSitesStore::kClosingDelay;
-
 ExploreSitesStore::ExploreSitesStore(
     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
-    : blocking_task_runner_(std::move(blocking_task_runner)),
-      in_memory_(true),
-      db_(nullptr, base::OnTaskRunnerDeleter(blocking_task_runner_)),
-      initialization_status_(InitializationStatus::NOT_INITIALIZED),
-      weak_ptr_factory_(this),
-      closing_weak_ptr_factory_(this) {}
+    : SqlStoreBase("ExploreSitesStore",
+                   std::move(blocking_task_runner),
+                   base::FilePath()) {}
 
 ExploreSitesStore::ExploreSitesStore(
     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
     const base::FilePath& path)
-    : blocking_task_runner_(std::move(blocking_task_runner)),
-      db_file_path_(path.AppendASCII(kExploreSitesStoreFileName)),
-      in_memory_(false),
-      db_(nullptr, base::OnTaskRunnerDeleter(blocking_task_runner_)),
-      initialization_status_(InitializationStatus::NOT_INITIALIZED),
-      weak_ptr_factory_(this),
-      closing_weak_ptr_factory_(this) {}
+    : SqlStoreBase("ExploreSitesStore",
+                   std::move(blocking_task_runner),
+                   path.AppendASCII(kExploreSitesStoreFileName)) {}
 
 ExploreSitesStore::~ExploreSitesStore() {}
 
-void ExploreSitesStore::SetInitializationStatusForTest(
-    InitializationStatus status) {
-  initialization_status_ = status;
+base::OnceCallback<bool(sql::Database* db)>
+ExploreSitesStore::GetSchemaInitializationFunction() {
+  return base::BindOnce(&ExploreSitesSchema::CreateOrUpgradeIfNeeded);
 }
 
-void ExploreSitesStore::Initialize(base::OnceClosure pending_command) {
+void ExploreSitesStore::OnOpenStart(base::TimeTicks last_closing_time) {
   TRACE_EVENT_ASYNC_BEGIN1("explore_sites", "ExploreSitesStore", this,
-                           "is reopen", !last_closing_time_.is_null());
-  DCHECK_EQ(initialization_status_, InitializationStatus::NOT_INITIALIZED);
-  initialization_status_ = InitializationStatus::INITIALIZING;
-
-  if (!last_closing_time_.is_null()) {
+                           "is reopen", !last_closing_time.is_null());
+  if (!last_closing_time.is_null()) {
     ReportStoreEvent(ExploreSitesStoreEvent::kReopened);
   } else {
     ReportStoreEvent(ExploreSitesStoreEvent::kOpenedFirstTime);
   }
-
-  // This is how we reset a pointer and provide deleter. This is necessary to
-  // ensure that we can close the store more than once.
-  db_ = DatabaseUniquePtr(new sql::Database,
-                          base::OnTaskRunnerDeleter(blocking_task_runner_));
-
-  base::PostTaskAndReplyWithResult(
-      blocking_task_runner_.get(), FROM_HERE,
-      base::BindOnce(&InitializeSync, db_.get(), db_file_path_, in_memory_),
-      base::BindOnce(&ExploreSitesStore::OnInitializeDone,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     std::move(pending_command)));
 }
 
-void ExploreSitesStore::OnInitializeDone(base::OnceClosure pending_command,
-                                         bool success) {
-  // TODO(carlosk): Add initializing error reporting here.
+void ExploreSitesStore::OnOpenDone(bool success) {
   TRACE_EVENT_ASYNC_STEP_PAST1("explore_sites", "ExploreSitesStore", this,
                                "Initializing", "succeeded", success);
-  DCHECK_EQ(initialization_status_, InitializationStatus::INITIALIZING);
-  if (success) {
-    initialization_status_ = InitializationStatus::SUCCESS;
-  } else {
-    initialization_status_ = InitializationStatus::FAILURE;
-    db_.reset();
+  if (!success) {
     TRACE_EVENT_ASYNC_END0("explore_sites", "ExploreSitesStore", this);
   }
-
-  CHECK(!pending_command.is_null());
-  std::move(pending_command).Run();
-
-  // Once pending commands are empty, we get back to NOT_INITIALIZED state, to
-  // make it possible to retry initialization next time a DB operation is
-  // attempted.
-  if (initialization_status_ == InitializationStatus::FAILURE)
-    initialization_status_ = InitializationStatus::NOT_INITIALIZED;
 }
 
-void ExploreSitesStore::CloseInternal() {
-  if (initialization_status_ != InitializationStatus::SUCCESS) {
+void ExploreSitesStore::OnTaskBegin(bool is_initialized) {
+  TRACE_EVENT_ASYNC_BEGIN1("explore_sites",
+                           "ExploreSites Store: task execution", this,
+                           "is store loaded", is_initialized);
+}
+
+void ExploreSitesStore::OnTaskRunComplete() {
+  // Note: the time recorded for this trace step will include thread hop wait
+  // times to the background thread and back.
+  TRACE_EVENT_ASYNC_STEP_PAST0(
+      "explore_sites", "ExploreSites Store: task execution", this, "Task");
+}
+
+void ExploreSitesStore::OnTaskReturnComplete() {
+  TRACE_EVENT_ASYNC_STEP_PAST0(
+      "explore_sites", "ExploreSites Store: task execution", this, "Callback");
+  TRACE_EVENT_ASYNC_END0("explore_sites", "ExploreSites Store: task execution",
+                         this);
+}
+
+void ExploreSitesStore::OnCloseStart(
+    InitializationStatus initialization_status) {
+  if (initialization_status != InitializationStatus::kSuccess) {
     ReportStoreEvent(ExploreSitesStoreEvent::kCloseSkipped);
     return;
   }
   TRACE_EVENT_ASYNC_STEP_PAST0("explore_sites", "ExploreSitesStore", this,
                                "Open");
 
-  last_closing_time_ = offline_pages::OfflineTimeNow();
   ReportStoreEvent(ExploreSitesStoreEvent::kClosed);
-
-  initialization_status_ = InitializationStatus::NOT_INITIALIZED;
-  blocking_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &CloseDatabaseSync, db_.get(), base::ThreadTaskRunnerHandle::Get(),
-          base::BindOnce(&ExploreSitesStore::CloseInternalDone,
-                         weak_ptr_factory_.GetWeakPtr(), std::move(db_))));
 }
 
-void ExploreSitesStore::CloseInternalDone(DatabaseUniquePtr db) {
-  db.reset();
+void ExploreSitesStore::OnCloseComplete() {
   TRACE_EVENT_ASYNC_STEP_PAST0("explore_sites", "ExploreSitesStore", this,
                                "Closing");
   TRACE_EVENT_ASYNC_END0("explore_sites", "ExploreSitesStore", this);
diff --git a/chrome/browser/android/explore_sites/explore_sites_store.h b/chrome/browser/android/explore_sites/explore_sites_store.h
index 3a6a354c..a68a13c 100644
--- a/chrome/browser/android/explore_sites/explore_sites_store.h
+++ b/chrome/browser/android/explore_sites/explore_sites_store.h
@@ -16,6 +16,7 @@
 #include "base/task_runner_util.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
+#include "components/offline_pages/task/sql_store_base.h"
 
 namespace sql {
 class Database;
@@ -23,36 +24,14 @@
 
 namespace explore_sites {
 
-enum class InitializationStatus {
-  NOT_INITIALIZED,
-  INITIALIZING,
-  SUCCESS,
-  FAILURE,
-};
-
 // ExploreSitesStore is a front end to SQLite store hosting the explore sites
 // web catalog.
 //
 // The store controls the pointer to the SQLite database and only makes it
 // available to the |RunCallback| of the |Execute| method on the blocking
 // thread.
-class ExploreSitesStore {
+class ExploreSitesStore : public offline_pages::SqlStoreBase {
  public:
-  // Definition of the callback that is going to run the core of the command in
-  // the |Execute| method.
-  template <typename T>
-  using RunCallback = base::OnceCallback<T(sql::Database*)>;
-
-  // Definition of the callback used to pass the result back to the caller of
-  // |Execute| method.
-  template <typename T>
-  using ResultCallback = base::OnceCallback<void(T)>;
-
-  // Defines inactivity time of DB after which it is going to be closed.
-  // TODO(dewittj): Derive appropriate value in a scientific way.
-  static constexpr base::TimeDelta kClosingDelay =
-      base::TimeDelta::FromSeconds(20);
-
   // Creates an instance of |ExploreSitesStore| with an in-memory SQLite
   // database.
   explicit ExploreSitesStore(
@@ -64,121 +43,19 @@
       scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
       const base::FilePath& database_dir);
 
-  ~ExploreSitesStore();
+  ~ExploreSitesStore() override;
 
-  // Executes a |run_callback| on SQL store on the blocking thread, and posts
-  // its result back to calling thread through |result_callback|.
-  // Calling |Execute| when store is NOT_INITIALIZED will cause the store
-  // initialization to start.
-  // Store initialization status needs to be SUCCESS for run_callback to run.
-  // If initialization fails, |result_callback| is invoked with |default_value|.
-  template <typename T>
-  void Execute(RunCallback<T> run_callback,
-               ResultCallback<T> result_callback,
-               T default_value) {
-    CHECK_NE(initialization_status_, InitializationStatus::INITIALIZING);
-
-    if (initialization_status_ == InitializationStatus::NOT_INITIALIZED) {
-      Initialize(base::BindOnce(
-          &ExploreSitesStore::Execute<T>, weak_ptr_factory_.GetWeakPtr(),
-          std::move(run_callback), std::move(result_callback),
-          std::move(default_value)));
-      return;
-    }
-
-    TRACE_EVENT_ASYNC_BEGIN1(
-        "explore_sites", "ExploreSites Store: task execution", this,
-        "is store loaded",
-        initialization_status_ == InitializationStatus::SUCCESS);
-    // Ensure that any scheduled close operations are canceled.
-    closing_weak_ptr_factory_.InvalidateWeakPtrs();
-
-    sql::Database* db = initialization_status_ == InitializationStatus::SUCCESS
-                            ? db_.get()
-                            : nullptr;
-    if (!db) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(result_callback), std::move(default_value)));
-    } else {
-      base::PostTaskAndReplyWithResult(
-          blocking_task_runner_.get(), FROM_HERE,
-          base::BindOnce(std::move(run_callback), db),
-          base::BindOnce(&ExploreSitesStore::RescheduleClosing<T>,
-                         weak_ptr_factory_.GetWeakPtr(),
-                         std::move(result_callback)));
-    }
-  }
-
-  // Gets the initialization status of the store.
-  InitializationStatus initialization_status() const {
-    return initialization_status_;
-  }
-
-  void SetInitializationStatusForTest(InitializationStatus status);
-
- private:
-  using DatabaseUniquePtr =
-      std::unique_ptr<sql::Database, base::OnTaskRunnerDeleter>;
-
-  // Used internally to initialize connection.
-  void Initialize(base::OnceClosure pending_command);
-
-  // Used to conclude opening/resetting DB connection.
-  void OnInitializeDone(base::OnceClosure pending_command, bool success);
-
-  // Reschedules the closing with a delay. Ensures that |result_callback| is
-  // called.
-  template <typename T>
-  void RescheduleClosing(ResultCallback<T> result_callback, T result) {
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&ExploreSitesStore::CloseInternal,
-                       closing_weak_ptr_factory_.GetWeakPtr()),
-        kClosingDelay);
-
-    // Note: the time recorded for this trace step will include thread hop wait
-    // times to the background thread and back.
-    TRACE_EVENT_ASYNC_STEP_PAST0(
-        "explore_sites", "ExploreSites Store: task execution", this, "Task");
-    std::move(result_callback).Run(std::move(result));
-    TRACE_EVENT_ASYNC_STEP_PAST0("explore_sites",
-                                 "ExploreSites Store: task execution", this,
-                                 "Callback");
-    TRACE_EVENT_ASYNC_END0("explore_sites",
-                           "ExploreSites Store: task execution", this);
-  }
-
-  // Internal function initiating the closing.
-  void CloseInternal();
-
-  // Completes the closing. Main purpose is to destroy the db pointer.
-  void CloseInternalDone(DatabaseUniquePtr db);
-
-  // Background thread where all SQL access should be run.
-  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
-
-  // Path to the database on disk.
-  base::FilePath db_file_path_;
-
-  // Only open the store in memory. Used for testing.
-  bool in_memory_;
-
-  // Database connection.
-  std::unique_ptr<sql::Database, base::OnTaskRunnerDeleter> db_;
-
-  // Initialization status of the store.
-  InitializationStatus initialization_status_;
-
-  // Time of the last time the store was closed. Kept for metrics reporting.
-  base::Time last_closing_time_;
-
-  // Weak pointer to control the callback.
-  base::WeakPtrFactory<ExploreSitesStore> weak_ptr_factory_;
-  // Weak pointer to cancel closing of the store.
-  base::WeakPtrFactory<ExploreSitesStore> closing_weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExploreSitesStore);
+ protected:
+  // SqlStoreBase:
+  base::OnceCallback<bool(sql::Database* db)> GetSchemaInitializationFunction()
+      override;
+  void OnOpenStart(base::TimeTicks last_open_time) override;
+  void OnOpenDone(bool success) override;
+  void OnTaskBegin(bool is_initialized) override;
+  void OnTaskRunComplete() override;
+  void OnTaskReturnComplete() override;
+  void OnCloseStart(InitializationStatus status_before_close) override;
+  void OnCloseComplete() override;
 };
 
 }  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/explore_sites_store_unittest.cc b/chrome/browser/android/explore_sites/explore_sites_store_unittest.cc
index 49494ccb..dabb0a5 100644
--- a/chrome/browser/android/explore_sites/explore_sites_store_unittest.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_store_unittest.cc
@@ -24,6 +24,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace explore_sites {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 class ExploreSitesStoreTest : public testing::Test {
  public:
@@ -88,7 +89,8 @@
       base::BindLambdaForTesting([&](sql::Database* db) { return true; });
   bool result = ExecuteSync<bool>(store.get(), run_callback, false);
   EXPECT_TRUE(result);
-  EXPECT_EQ(InitializationStatus::SUCCESS, store->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store->initialization_status_for_testing());
 }
 
 TEST_F(ExploreSitesStoreTest, StoreCloses) {
@@ -103,13 +105,14 @@
 
   FastForwardBy(ExploreSitesStore::kClosingDelay);
   PumpLoop();
-  EXPECT_EQ(InitializationStatus::NOT_INITIALIZED,
-            store->initialization_status());
+  EXPECT_EQ(InitializationStatus::kNotInitialized,
+            store->initialization_status_for_testing());
 
   // Executing something causes initialization.
   ExecuteSync<bool>(store.get(), run_callback, false);
 
-  EXPECT_EQ(InitializationStatus::SUCCESS, store->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store->initialization_status_for_testing());
 }
 
 }  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/get_catalog_task_unittest.cc b/chrome/browser/android/explore_sites/get_catalog_task_unittest.cc
index c669a55..9f10865 100644
--- a/chrome/browser/android/explore_sites/get_catalog_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/get_catalog_task_unittest.cc
@@ -24,6 +24,7 @@
 
 namespace explore_sites {
 namespace {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 void ValidateTestingCatalog(GetCatalogTask::CategoryList* catalog) {
   EXPECT_FALSE(catalog == nullptr);
@@ -271,7 +272,8 @@
 }
 
 TEST_F(ExploreSitesGetCatalogTaskTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
 
   GetCatalogTask task(store(), false,
                       base::BindOnce(&ExpectFailedGetCatalogResult));
diff --git a/chrome/browser/android/explore_sites/get_images_task_unittest.cc b/chrome/browser/android/explore_sites/get_images_task_unittest.cc
index cd4af90..df6cdaa 100644
--- a/chrome/browser/android/explore_sites/get_images_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/get_images_task_unittest.cc
@@ -22,6 +22,7 @@
 using offline_pages::TaskTestBase;
 
 namespace explore_sites {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 class ExploreSitesGetImagesTaskTest : public TaskTestBase {
  public:
@@ -89,7 +90,8 @@
 }
 
 TEST_F(ExploreSitesGetImagesTaskTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
 
   GetImagesTask task(store(), 1, StoreResult());
   RunTask(&task);
diff --git a/chrome/browser/android/explore_sites/get_version_task_unittest.cc b/chrome/browser/android/explore_sites/get_version_task_unittest.cc
index 8e8f323..d880a4b 100644
--- a/chrome/browser/android/explore_sites/get_version_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/get_version_task_unittest.cc
@@ -21,6 +21,7 @@
 using offline_pages::TaskTestBase;
 
 namespace explore_sites {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 class ExploreSitesGetVersionTaskTest : public TaskTestBase {
  public:
@@ -69,7 +70,8 @@
 };
 
 TEST_F(ExploreSitesGetVersionTaskTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
   GetVersionTask task(store(),
                       base::BindLambdaForTesting(
                           [&](std::string result) { EXPECT_EQ("", result); }));
diff --git a/chrome/browser/android/explore_sites/history_statistics_reporter.cc b/chrome/browser/android/explore_sites/history_statistics_reporter.cc
index 9771da3..65a734c 100644
--- a/chrome/browser/android/explore_sites/history_statistics_reporter.cc
+++ b/chrome/browser/android/explore_sites/history_statistics_reporter.cc
@@ -56,7 +56,6 @@
   base::Time last_report_time = prefs_->GetTime(kWeeklyStatsReportingTimestamp);
   if (last_report_time > clock_->Now() - base::TimeDelta::FromDays(7))
     return;
-  prefs_->SetTime(kWeeklyStatsReportingTimestamp, clock_->Now());
 
   base::TimeDelta computeStatisticsDelay =
       base::TimeDelta::FromSeconds(kComputeStatisticsDelaySeconds);
@@ -103,5 +102,7 @@
   if (!result.success)
     return;
   UMA_HISTOGRAM_COUNTS_1000("ExploreSites.MonthlyHostCount", result.count);
+  // Remember when stats were reported to skip attempts until next week.
+  prefs_->SetTime(kWeeklyStatsReportingTimestamp, clock_->Now());
 }
 }  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/history_statistics_reporter_unittest.cc b/chrome/browser/android/explore_sites/history_statistics_reporter_unittest.cc
index a8a4ee8..621f76ea 100644
--- a/chrome/browser/android/explore_sites/history_statistics_reporter_unittest.cc
+++ b/chrome/browser/android/explore_sites/history_statistics_reporter_unittest.cc
@@ -192,8 +192,6 @@
 }
 
 TEST_F(HistoryStatisticsReporterTest, OneRunPerWeekSaveTimestamp) {
-  base::Time time_now = task_runner()->GetMockClock()->Now();
-
   ASSERT_TRUE(LoadHistory());
 
   ScheduleReportAndRunUntilIdle();
@@ -202,6 +200,7 @@
   histograms().ExpectTotalCount("History.DatabaseMonthlyHostCountTime", 1);
 
   // Reporter should have left the time of request in Prefs.
+  base::Time time_now = task_runner()->GetMockClock()->Now();
   EXPECT_EQ(time_now, prefs()->GetTime(kWeeklyStatsReportingTimestamp));
 }
 
@@ -217,8 +216,6 @@
 }
 
 TEST_F(HistoryStatisticsReporterTest, OneRunPerWeekReadTimestampAfterWeek) {
-  base::Time time_now = task_runner()->GetMockClock()->Now();
-
   ASSERT_TRUE(LoadHistory());
 
   prefs()->SetTime(
@@ -229,6 +226,7 @@
   // More than a week since last query, should have gone through.
   histograms().ExpectTotalCount("History.DatabaseMonthlyHostCountTime", 1);
   // Reporter should have left the time of request in Prefs.
+  base::Time time_now = task_runner()->GetMockClock()->Now();
   EXPECT_EQ(time_now, prefs()->GetTime(kWeeklyStatsReportingTimestamp));
 }
 
diff --git a/chrome/browser/android/explore_sites/import_catalog_task_unittest.cc b/chrome/browser/android/explore_sites/import_catalog_task_unittest.cc
index dc5cc6e..f367ae0 100644
--- a/chrome/browser/android/explore_sites/import_catalog_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/import_catalog_task_unittest.cc
@@ -23,6 +23,7 @@
 using offline_pages::TaskTestBase;
 
 namespace explore_sites {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 const char kVersionToken[] = "12345";
 const char kGoogleUrl[] = "https://www.google.com";
@@ -82,7 +83,8 @@
 };
 
 TEST_F(ExploreSitesImportCatalogTaskTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
   ImportCatalogTask task(
       store(), kVersionToken, std::make_unique<Catalog>(),
       base::BindOnce(&ExploreSitesImportCatalogTaskTest::OnImportTaskDone,
diff --git a/chrome/browser/android/explore_sites/increment_shown_count_task_unittest.cc b/chrome/browser/android/explore_sites/increment_shown_count_task_unittest.cc
index ef061f2..4a58908 100644
--- a/chrome/browser/android/explore_sites/increment_shown_count_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/increment_shown_count_task_unittest.cc
@@ -17,7 +17,7 @@
 using offline_pages::TaskTestBase;
 
 namespace explore_sites {
-
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 class ExploreSitesIncrementShownCountTaskTest : public TaskTestBase {
  public:
   ExploreSitesIncrementShownCountTaskTest() = default;
@@ -71,7 +71,8 @@
 }
 
 TEST_F(ExploreSitesIncrementShownCountTaskTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
   IncrementShownCountTask task(store(), 1);
   RunTask(&task);
 
diff --git a/chrome/browser/android/explore_sites/record_site_click_task_unittest.cc b/chrome/browser/android/explore_sites/record_site_click_task_unittest.cc
index c3b33f5..a659ec6 100644
--- a/chrome/browser/android/explore_sites/record_site_click_task_unittest.cc
+++ b/chrome/browser/android/explore_sites/record_site_click_task_unittest.cc
@@ -19,6 +19,7 @@
 using offline_pages::TaskTestBase;
 
 namespace explore_sites {
+using InitializationStatus = ExploreSitesStore::InitializationStatus;
 
 const char kUrl[] = "https://www.example.com";
 const int kType = 5;
@@ -70,7 +71,8 @@
 }
 
 TEST_F(ExploreSitesRecordSiteClickTest, StoreFailure) {
-  store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
+  store()->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                             false);
   RecordSiteClickTask task(store(), kUrl, kType);
   RunTask(&task);
 
diff --git a/chrome/browser/android/feed/feed_logging_bridge.cc b/chrome/browser/android/feed/feed_logging_bridge.cc
index 34877dc..18a0a0f 100644
--- a/chrome/browser/android/feed/feed_logging_bridge.cc
+++ b/chrome/browser/android/feed/feed_logging_bridge.cc
@@ -171,6 +171,54 @@
   feed_logging_metrics_->OnPietFrameRenderingEvent(std::move(piet_error_codes));
 }
 
+void FeedLoggingBridge::OnInternalError(JNIEnv* j_env,
+                                        const JavaRef<jobject>& j_this,
+                                        const jint j_internal_error) {
+  feed_logging_metrics_->OnInternalError(j_internal_error);
+}
+
+void FeedLoggingBridge::OnTokenCompleted(
+    JNIEnv* j_env,
+    const base::android::JavaRef<jobject>& j_this,
+    const jboolean j_was_synthetic,
+    const jint j_content_count,
+    const jint j_token_count) {
+  feed_logging_metrics_->OnTokenCompleted(j_was_synthetic, j_content_count,
+                                          j_token_count);
+}
+
+void FeedLoggingBridge::OnTokenFailedToComplete(
+    JNIEnv* j_env,
+    const base::android::JavaRef<jobject>& j_this,
+    const jboolean j_was_synthetic,
+    const jint j_failure_count) {
+  feed_logging_metrics_->OnTokenFailedToComplete(j_was_synthetic,
+                                                 j_failure_count);
+}
+
+void FeedLoggingBridge::OnServerRequest(
+    JNIEnv* j_env,
+    const base::android::JavaRef<jobject>& j_this,
+    const jint j_request_reason) {
+  feed_logging_metrics_->OnServerRequest(j_request_reason);
+}
+
+void FeedLoggingBridge::OnZeroStateShown(
+    JNIEnv* j_env,
+    const base::android::JavaRef<jobject>& j_this,
+    const jint j_zero_state_show_reason) {
+  feed_logging_metrics_->OnZeroStateShown(j_zero_state_show_reason);
+}
+
+void FeedLoggingBridge::OnZeroStateRefreshCompleted(
+    JNIEnv* j_env,
+    const base::android::JavaRef<jobject>& j_this,
+    const jint j_new_content_count,
+    const jint j_new_token_count) {
+  feed_logging_metrics_->OnZeroStateRefreshCompleted(j_new_content_count,
+                                                     j_new_token_count);
+}
+
 void FeedLoggingBridge::OnContentTargetVisited(JNIEnv* j_env,
                                                const JavaRef<jobject>& j_this,
                                                const jlong visit_time_ms,
diff --git a/chrome/browser/android/feed/feed_logging_bridge.h b/chrome/browser/android/feed/feed_logging_bridge.h
index 2f51994..344dd643 100644
--- a/chrome/browser/android/feed/feed_logging_bridge.h
+++ b/chrome/browser/android/feed/feed_logging_bridge.h
@@ -102,6 +102,35 @@
       const base::android::JavaRef<jobject>& j_this,
       const base::android::JavaRef<jintArray>& j_piet_error_codes);
 
+  void OnInternalError(JNIEnv* j_env,
+                       const base::android::JavaRef<jobject>& j_this,
+                       const jint j_internal_error);
+
+  void OnTokenCompleted(JNIEnv* j_env,
+                        const base::android::JavaRef<jobject>& j_this,
+                        const jboolean j_was_synthetic,
+                        const jint j_content_count,
+                        const jint j_token_count);
+
+  void OnTokenFailedToComplete(JNIEnv* j_env,
+                               const base::android::JavaRef<jobject>& j_this,
+                               const jboolean j_was_synthetic,
+                               const jint j_failure_count);
+
+  void OnServerRequest(JNIEnv* j_env,
+                       const base::android::JavaRef<jobject>& j_this,
+                       const jint j_request_reason);
+
+  void OnZeroStateShown(JNIEnv* j_env,
+                        const base::android::JavaRef<jobject>& j_this,
+                        const jint j_zero_state_show_reason);
+
+  void OnZeroStateRefreshCompleted(
+      JNIEnv* j_env,
+      const base::android::JavaRef<jobject>& j_this,
+      const jint j_new_content_count,
+      const jint j_new_token_count);
+
   void OnContentTargetVisited(JNIEnv* j_env,
                               const base::android::JavaRef<jobject>& j_this,
                               const jlong visit_time_ms,
diff --git a/chrome/browser/android/profiles/profile_downloader_android.cc b/chrome/browser/android/profiles/profile_downloader_android.cc
index 8dbe433..0e84f7e 100644
--- a/chrome/browser/android/profiles/profile_downloader_android.cc
+++ b/chrome/browser/android/profiles/profile_downloader_android.cc
@@ -168,8 +168,8 @@
           GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
     gfx::Image avatar_image = entry->GetAvatarIcon();
     if (!avatar_image.IsEmpty() &&
-        avatar_image.Width() > profiles::kAvatarIconWidth &&
-        avatar_image.Height() > profiles::kAvatarIconHeight &&
+        avatar_image.Width() > profiles::kAvatarIconSize &&
+        avatar_image.Height() > profiles::kAvatarIconSize &&
         avatar_image.AsImageSkia().bitmap()) {
       jbitmap = gfx::ConvertToJavaBitmap(avatar_image.AsImageSkia().bitmap());
     }
diff --git a/chrome/browser/android/send_tab_to_self/OWNERS b/chrome/browser/android/send_tab_to_self/OWNERS
new file mode 100644
index 0000000..e58f618d
--- /dev/null
+++ b/chrome/browser/android/send_tab_to_self/OWNERS
@@ -0,0 +1,3 @@
+file://components/send_tab_to_self/OWNERS
+
+# COMPONENT: UI>Browser>Sharing
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.cc b/chrome/browser/android/tab_web_contents_delegate_android.cc
index 95d0d75b..d305c2e3 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.cc
+++ b/chrome/browser/android/tab_web_contents_delegate_android.cc
@@ -14,6 +14,7 @@
 #include "base/command_line.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
+#include "base/rand_util.h"
 #include "chrome/browser/android/chrome_feature_list.h"
 #include "chrome/browser/android/feature_utilities.h"
 #include "chrome/browser/android/hung_renderer_infobar_delegate.h"
@@ -495,13 +496,18 @@
 void JNI_TabWebContentsDelegateAndroid_OnRendererUnresponsive(
     JNIEnv* env,
     const JavaParamRef<jobject>& java_web_contents) {
+  // Rate limit the number of stack dumps so we don't overwhelm our crash
+  // reports.
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(java_web_contents);
+  if (base::RandDouble() < 0.01)
+    web_contents->GetMainFrame()->GetProcess()->DumpProcessStack();
+
   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kEnableHungRendererInfoBar)) {
     return;
   }
 
-  content::WebContents* web_contents =
-        content::WebContents::FromJavaWebContents(java_web_contents);
   InfoBarService* infobar_service =
       InfoBarService::FromWebContents(web_contents);
   DCHECK(!FindHungRendererInfoBar(infobar_service));
diff --git a/chrome/browser/background_fetch/background_fetch_browsertest.cc b/chrome/browser/background_fetch/background_fetch_browsertest.cc
index 33aaa1b..638359da3 100644
--- a/chrome/browser/background_fetch/background_fetch_browsertest.cc
+++ b/chrome/browser/background_fetch/background_fetch_browsertest.cc
@@ -6,7 +6,6 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/command_line.h"
 #include "base/logging.h"
 #include "base/run_loop.h"
 #include "base/strings/string_util.h"
@@ -229,12 +228,6 @@
             std::make_unique<OfflineContentProviderObserver>()) {}
   ~BackgroundFetchBrowserTest() override = default;
 
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    // Background Fetch is available as an experimental Web Platform feature.
-    command_line->AppendSwitch(
-        switches::kEnableExperimentalWebPlatformFeatures);
-  }
-
   void SetUpOnMainThread() override {
     https_server_ = std::make_unique<net::EmbeddedTestServer>(
         net::EmbeddedTestServer::TYPE_HTTPS);
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 099ca237..05d6fa88 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -482,11 +482,16 @@
       <include name="IDR_SIGNIN_EMAIL_CONFIRMATION_JS" file="resources\signin\signin_email_confirmation\signin_email_confirmation.js" type="BINDATA" />
       <include name="IDR_SIGNIN_ERROR_HTML" file="resources\signin\signin_error\signin_error.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
       <include name="IDR_SIGNIN_ERROR_JS" file="resources\signin\signin_error\signin_error.js" type="BINDATA" />
+      <include name="IDR_USB_DEVICE_ENUMERATION_OPTIONS_MOJOM_LITE_JS" file="${root_gen_dir}\device\usb\public\mojom\device_enumeration_options.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_DEVICE_MANAGER_CLIENT_MOJOM_LITE_JS" file="${root_gen_dir}\device\usb\public\mojom\device_manager_client.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_DEVICE_MANAGER_MOJOM_LITE_JS" file="${root_gen_dir}\device\usb\public\mojom\device_manager.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_DEVICE_MANAGER_TEST_MOJOM_LITE_JS" file="${root_gen_dir}\device\usb\public\mojom\device_manager_test.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_DEVICE_MOJOM_LITE_JS" file="${root_gen_dir}\device\usb\public\mojom\device.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
       <include name="IDR_USB_INTERNALS_CSS" file="resources\usb_internals\usb_internals.css" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_INTERNALS_DEVICES_PAGE_JS" file="resources\usb_internals\devices_page.js" type="BINDATA" compress="gzip" />
       <include name="IDR_USB_INTERNALS_HTML" file="resources\usb_internals\usb_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
       <include name="IDR_USB_INTERNALS_JS" file="resources\usb_internals\usb_internals.js" type="BINDATA" compress="gzip" />
-      <include name="IDR_USB_INTERNALS_MOJO_JS" file="${root_gen_dir}\chrome\browser\ui\webui\usb_internals\usb_internals.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
-      <include name="IDR_USB_DEVICE_MANAGER_TEST_MOJO_JS" file="${root_gen_dir}\device\usb\public\mojom\device_manager_test.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_INTERNALS_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\ui\webui\usb_internals\usb_internals.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
       <include name="IDR_WEBRTC_LOGS_HTML" file="resources\media\webrtc_logs.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
       <include name="IDR_WEBRTC_LOGS_JS" file="resources\media\webrtc_logs.js" type="BINDATA" />
       <include name="IDR_WEBSTORE_MANIFEST" file="resources\webstore_app\manifest.json" type="BINDATA" />
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 8bfd708e..4a65579 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4203,7 +4203,7 @@
     throttles.push_back(std::move(background_tab_navigation_throttle));
 #endif
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   std::unique_ptr<content::NavigationThrottle>
       password_protection_navigation_throttle =
           safe_browsing::MaybeCreateNavigationThrottle(handle);
diff --git a/chrome/browser/chrome_navigation_browsertest.cc b/chrome/browser/chrome_navigation_browsertest.cc
index 9fe5909..f25ff94 100644
--- a/chrome/browser/chrome_navigation_browsertest.cc
+++ b/chrome/browser/chrome_navigation_browsertest.cc
@@ -35,6 +35,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/site_isolation_policy.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
@@ -1400,3 +1401,71 @@
   content::WaitForLoadStop(main_contents);
   ASSERT_EQ(GURL("about:blank"), main_contents->GetLastCommittedURL());
 }
+
+// This test class turns on the mode where sites where the user enters a
+// password are dynamically added to the list of sites requiring a dedicated
+// process.  It also disables strict site isolation so that the effects of
+// password isolation can be observed.
+class SiteIsolationForPasswordSitesBrowserTest
+    : public ChromeNavigationBrowserTest {
+ protected:
+  void SetUp() override {
+    feature_list_.InitWithFeatures({features::kSiteIsolationForPasswordSites},
+                                   {features::kSitePerProcess});
+    ChromeNavigationBrowserTest::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Verifies that a site gets process-isolated after a password is typed on a
+// page from that site.
+IN_PROC_BROWSER_TEST_F(SiteIsolationForPasswordSitesBrowserTest,
+                       SiteIsIsolatedAfterEnteringPassword) {
+  // This test requires dynamic isolated origins to be enabled.
+  if (!content::SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled())
+    return;
+
+  GURL url(embedded_test_server()->GetURL("sub.foo.com",
+                                          "/password/password_form.html"));
+  ui_test_utils::NavigateToURL(browser(), url);
+  content::WebContents* contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // foo.com should not be isolated to start with. Verify that a cross-site
+  // iframe does not become an OOPIF.
+  std::string kAppendIframe = R"(
+      var i = document.createElement('iframe');
+      i.id = 'child';
+      document.body.appendChild(i);)";
+  EXPECT_TRUE(ExecJs(contents, kAppendIframe));
+  GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
+  EXPECT_TRUE(NavigateIframeToURL(contents, "child", bar_url));
+  content::RenderFrameHost* child = ChildFrameAt(contents->GetMainFrame(), 0);
+  EXPECT_FALSE(child->IsCrossProcessSubframe());
+
+  // Fill a form and submit through a <input type="submit"> button.
+  content::TestNavigationObserver observer(contents);
+  std::string kFillAndSubmit =
+      "document.getElementById('username_field').value = 'temp';"
+      "document.getElementById('password_field').value = 'random';"
+      "document.getElementById('input_submit_button').click()";
+  EXPECT_TRUE(content::ExecJs(contents, kFillAndSubmit));
+  observer.Wait();
+
+  // Open a fresh tab (forcing a new BrowsingInstance), navigate to foo.com,
+  // and verify that a cross-site iframe now becomes an OOPIF.
+  AddBlankTabAndShow(browser());
+  EXPECT_EQ(2, browser()->tab_strip_model()->count());
+  content::WebContents* new_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  EXPECT_NE(new_contents, contents);
+
+  ui_test_utils::NavigateToURL(browser(), url);
+  EXPECT_TRUE(ExecJs(new_contents, kAppendIframe));
+  EXPECT_TRUE(NavigateIframeToURL(new_contents, "child", bar_url));
+  content::RenderFrameHost* new_child =
+      ChildFrameAt(new_contents->GetMainFrame(), 0);
+  EXPECT_TRUE(new_child->IsCrossProcessSubframe());
+}
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 9b5206c..34f999b 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -972,8 +972,8 @@
     "fileapi/mtp_watcher_manager.h",
     "fileapi/recent_arc_media_source.cc",
     "fileapi/recent_arc_media_source.h",
-    "fileapi/recent_download_source.cc",
-    "fileapi/recent_download_source.h",
+    "fileapi/recent_disk_source.cc",
+    "fileapi/recent_disk_source.h",
     "fileapi/recent_drive_source.cc",
     "fileapi/recent_drive_source.h",
     "fileapi/recent_file.cc",
@@ -2338,7 +2338,7 @@
     "fileapi/file_access_permissions_unittest.cc",
     "fileapi/file_system_backend_unittest.cc",
     "fileapi/recent_arc_media_source_unittest.cc",
-    "fileapi/recent_download_source_unittest.cc",
+    "fileapi/recent_disk_source_unittest.cc",
     "fileapi/recent_model_unittest.cc",
     "fileapi/test/fake_recent_source.cc",
     "hats/hats_finch_helper_unittest.cc",
@@ -2587,7 +2587,6 @@
     ":arc_test_support",
     ":attestation_proto",
     ":test_support",
-    ":time_limit_tests",
     ":user_activity_event_proto",
     "//ash",
     "//ash/system/message_center/arc:test_support",
@@ -2758,37 +2757,3 @@
 
   seed_corpus = "smb_client/fuzzer_data/smb_url_corpus"
 }
-
-# Build target related to the time limit processor tests. Must be kept
-# separated from the other unit tests because it is the only one allowed
-# to use the :protobuf_full dependency.
-source_set("time_limit_tests") {
-  testonly = true
-  check_includes = false
-
-  sources = [
-    "child_accounts/time_limit_consistency_test/consistency_golden_converter.cc",
-    "child_accounts/time_limit_consistency_test/consistency_golden_converter_unittest.cc",
-    "child_accounts/time_limit_consistency_test/consistency_golden_loader.cc",
-    "child_accounts/time_limit_consistency_test/consistency_golden_loader_unittest.cc",
-  ]
-  deps = [
-    ":chromeos",
-    ":consistency_golden_proto",
-    "//base",
-    "//testing/gmock",
-    "//testing/gtest",
-    "//third_party/protobuf:protobuf_full",
-  ]
-  data = [
-    "child_accounts/time_limit_consistency_test/goldens/",
-    "child_accounts/time_limit_consistency_test/test_goldens/",
-  ]
-}
-
-proto_library("consistency_golden_proto") {
-  sources = [
-    "child_accounts/time_limit_consistency_test/goldens/consistency_golden.proto",
-  ]
-  generate_python = false
-}
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_data.cc b/chrome/browser/chromeos/app_mode/kiosk_app_data.cc
index 46696ae9..6556008 100644
--- a/chrome/browser/chromeos/app_mode/kiosk_app_data.cc
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_data.cc
@@ -54,6 +54,8 @@
 constexpr char kInvalidWebstoreResponseError[] =
     "Invalid Chrome Web Store reponse";
 
+bool ignore_kiosk_app_data_load_failures_for_testing = false;
+
 // Returns true for valid kiosk app manifest.
 bool IsValidKioskAppManifest(const extensions::Manifest& manifest) {
   bool kiosk_enabled;
@@ -106,7 +108,7 @@
   }
 
  private:
-  ~CrxLoader() override {}
+  ~CrxLoader() override = default;
 
   // extensions::SandboxedUnpackerClient
   void OnUnpackSuccess(
@@ -213,7 +215,7 @@
  private:
   friend class base::RefCounted<WebstoreDataParser>;
 
-  ~WebstoreDataParser() override {}
+  ~WebstoreDataParser() override = default;
 
   void ReportFailure() {
     if (client_)
@@ -277,9 +279,14 @@
       status_(STATUS_INIT),
       update_url_(update_url),
       crx_file_(cached_crx),
-      weak_factory_(this) {}
+      weak_factory_(this) {
+  if (ignore_kiosk_app_data_load_failures_for_testing) {
+    LOG(WARNING) << "Force KioskAppData loaded for testing.";
+    SetStatus(STATUS_LOADED);
+  }
+}
 
-KioskAppData::~KioskAppData() {}
+KioskAppData::~KioskAppData() = default;
 
 void KioskAppData::Load() {
   SetStatus(STATUS_LOADING);
@@ -351,6 +358,12 @@
 }
 
 void KioskAppData::SetStatus(Status status) {
+  if (status == STATUS_ERROR &&
+      ignore_kiosk_app_data_load_failures_for_testing) {
+    LOG(WARNING) << "Ignoring KioskAppData error for testing. Force OK.";
+    status = STATUS_LOADED;
+  }
+
   if (status_ == status)
     return;
 
@@ -445,6 +458,11 @@
   StartFetch();
 }
 
+// static
+void KioskAppData::SetIgnoreKioskAppDataLoadFailuresForTesting(bool value) {
+  ignore_kiosk_app_data_load_failures_for_testing = value;
+}
+
 void KioskAppData::OnWebstoreParseSuccess(
     const SkBitmap& icon,
     const std::string& required_platform_version) {
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_data.h b/chrome/browser/chromeos/app_mode/kiosk_app_data.h
index ffe5baea..c1cc646 100644
--- a/chrome/browser/chromeos/app_mode/kiosk_app_data.h
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_data.h
@@ -92,6 +92,10 @@
   void OnIconLoadSuccess(const gfx::ImageSkia& icon) override;
   void OnIconLoadFailure() override;
 
+  // Tests do not always fake app data download.
+  // This allows to ignore download errors.
+  static void SetIgnoreKioskAppDataLoadFailuresForTesting(bool value);
+
  private:
   class CrxLoader;
   class WebstoreDataParser;
diff --git a/chrome/browser/chromeos/arc/tts/arc_tts_service_unittest.cc b/chrome/browser/chromeos/arc/tts/arc_tts_service_unittest.cc
index 966c4e1..bfc3f9f 100644
--- a/chrome/browser/chromeos/arc/tts/arc_tts_service_unittest.cc
+++ b/chrome/browser/chromeos/arc/tts/arc_tts_service_unittest.cc
@@ -41,6 +41,7 @@
   bool IsSpeaking() override { return false; }
   void SpeakOrEnqueue(content::TtsUtterance* utterance) override {}
   void Stop() override {}
+  void Stop(const GURL& source_url) override {}
   void Pause() override {}
   void Resume() override {}
   void GetVoices(content::BrowserContext* browser_context,
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.cc b/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.cc
deleted file mode 100644
index d9b9c5a..0000000
--- a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.h"
-
-#include "base/files/dir_reader_posix.h"
-#include "base/files/file.h"
-#include "base/files/file_util.h"
-#include "base/path_service.h"
-#include "base/strings/strcat.h"
-#include "base/strings/string_util.h"
-#include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl.h"
-#include "third_party/protobuf/src/google/protobuf/text_format.h"
-
-namespace chromeos {
-namespace time_limit_consistency {
-namespace {
-
-base::FilePath GetGoldensPath() {
-  base::FilePath path;
-  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
-
-  return path.Append(
-      FILE_PATH_LITERAL("chrome/browser/chromeos/child_accounts/"
-                        "time_limit_consistency_test/goldens"));
-}
-
-}  // namespace
-
-std::vector<GoldenParam> LoadGoldenCases() {
-  return LoadGoldenCasesFromPath(GetGoldensPath());
-}
-
-std::vector<GoldenParam> LoadGoldenCasesFromPath(
-    const base::FilePath& directory_path) {
-  std::vector<GoldenParam> golden_params_list;
-  base::DirReaderPosix dir_reader(directory_path.value().c_str());
-
-  while (dir_reader.Next()) {
-    if (!base::EndsWith(dir_reader.name(), ".textproto",
-                        base::CompareCase::INSENSITIVE_ASCII)) {
-      continue;
-    }
-
-    ConsistencyGolden golden_suite;
-    base::File golden_file(directory_path.Append(dir_reader.name()),
-                           base::File::FLAG_OPEN | base::File::FLAG_READ);
-    google::protobuf::io::FileInputStream stream(golden_file.GetPlatformFile());
-    google::protobuf::TextFormat::Parse(&stream, &golden_suite);
-
-    // Ignore suites that don't include CHROME_OS as a supported platform.
-    bool chromeos_supported =
-        std::count(golden_suite.supported_platforms().begin(),
-                   golden_suite.supported_platforms().end(), CHROME_OS) > 0;
-    if (!chromeos_supported)
-      continue;
-
-    std::string suite_name = dir_reader.name();
-    base::ReplaceFirstSubstringAfterOffset(&suite_name, 0, ".textproto", "");
-    for (int i = 0; i < golden_suite.cases_size(); i++) {
-      golden_params_list.push_back(
-          GoldenParam({suite_name, i, golden_suite.cases(i)}));
-    }
-  }
-
-  return golden_params_list;
-}
-
-}  // namespace time_limit_consistency
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.h b/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.h
deleted file mode 100644
index a9aac373..0000000
--- a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// A utility for loading golden files to be used by the time limit processor
-// consistency tests.
-
-#ifndef CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMIT_CONSISTENCY_TEST_CONSISTENCY_GOLDEN_LOADER_H_
-#define CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMIT_CONSISTENCY_TEST_CONSISTENCY_GOLDEN_LOADER_H_
-
-#include <string>
-#include <vector>
-
-#include "base/files/file_path.h"
-#include "chrome/browser/chromeos/child_accounts/time_limit_consistency_test/goldens/consistency_golden.pb.h"
-
-namespace chromeos {
-namespace time_limit_consistency {
-
-// Holds information for one golden case and metadata used to generate the name
-// for its test case (i.e. the name of the golden file it belongs and its index
-// inside it).
-struct GoldenParam {
-  const std::string suite_name;
-  const int index;
-  const ConsistencyGoldenCase golden_case;
-};
-
-// Loads all cases from all available golden files into a list of GoldenParams.
-std::vector<GoldenParam> LoadGoldenCases();
-
-// Loads all cases from all golden files from a given path into a list of
-// GoldenParams. LoadGoldenCases() uses this function under the hood. Should not
-// be called directly except for testing the golden loader itself.
-std::vector<GoldenParam> LoadGoldenCasesFromPath(
-    const base::FilePath& directory_path);
-
-}  // namespace time_limit_consistency
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMIT_CONSISTENCY_TEST_CONSISTENCY_GOLDEN_LOADER_H_
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader_unittest.cc b/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader_unittest.cc
deleted file mode 100644
index b5c7f16..0000000
--- a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader_unittest.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/child_accounts/time_limit_consistency_test/consistency_golden_loader.h"
-
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/path_service.h"
-#include "chrome/browser/chromeos/child_accounts/time_limit_consistency_test/proto_matcher.h"
-#include "chrome/browser/chromeos/child_accounts/time_limit_test_utils.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace chromeos {
-
-namespace utils = time_limit_test_utils;
-
-namespace time_limit_consistency {
-
-using ConsistencyGoldenLoaderTest = testing::Test;
-
-base::FilePath GetTestGoldensPath() {
-  base::FilePath path;
-  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
-
-  return path.Append(
-      FILE_PATH_LITERAL("chrome/browser/chromeos/child_accounts/"
-                        "time_limit_consistency_test/test_goldens"));
-}
-
-// Expected outcome is to ignore the test_golden_unsupported suite and return
-// only the case within test_golden.
-TEST_F(ConsistencyGoldenLoaderTest, LoadTestGoldenCases) {
-  std::vector<GoldenParam> goldens_list =
-      LoadGoldenCasesFromPath(GetTestGoldensPath());
-
-  ConsistencyGoldenCase golden_case;
-  golden_case.mutable_current_state()->set_time_millis(42);
-
-  ASSERT_EQ(goldens_list.size(), 1ul);
-  EXPECT_EQ(goldens_list[0].suite_name, "test_golden");
-  EXPECT_EQ(goldens_list[0].index, 0);
-  EXPECT_THAT(goldens_list[0].golden_case, EqualsProto(golden_case));
-}
-
-}  // namespace time_limit_consistency
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/goldens/consistency_golden.proto b/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/goldens/consistency_golden.proto
deleted file mode 100644
index c3cec2e..0000000
--- a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/goldens/consistency_golden.proto
+++ /dev/null
@@ -1,108 +0,0 @@
-syntax = "proto2";
-
-// Used to generate the ChromeOS C++ namespace
-package chromeos.time_limit_consistency;
-
-// Used to generate the Java package
-option java_package = "com.google.kids.timelimit.consistency";
-option java_multiple_files = true;
-
-// The platforms where the test suite may be supported.
-enum SupportedPlatform {
-  UNSPECIFIED_PLATFORM = 0;
-  ANDROID = 1;
-  CHROME_OS = 2;
-}
-
-// Policies which may be active.
-enum ConsistencyGoldenPolicy {
-  UNSPECIFIED_POLICY = 0;
-  NO_ACTIVE_POLICY = 1;
-  OVERRIDE = 2;
-  FIXED_LIMIT = 3;
-  USAGE_LIMIT = 4;
-}
-
-// Days of the week.
-enum ConsistencyGoldenEffectiveDay {
-  UNSPECIFIED_EFFECTIVE_DAY = 0;
-  MONDAY = 1;
-  TUESDAY = 2;
-  WEDNESDAY = 3;
-  THURSDAY = 4;
-  FRIDAY = 5;
-  SATURDAY = 6;
-  SUNDAY = 7;
-}
-
-// The main object, represents one test suite (and one golden file).
-message ConsistencyGolden {
-  // The platforms where the test is supported. Required
-  repeated SupportedPlatform supported_platforms = 1;
-
-  // A list of test cases. Required
-  repeated ConsistencyGoldenCase cases = 2;
-}
-
-// Message representing one test case
-message ConsistencyGoldenCase {
-  // Input policy data. Required
-  optional ConsistencyGoldenInput input = 1;
-
-  // Simulates the current state when executing. Required
-  optional ConsistencyGoldenCurrentState current_state = 2;
-
-  // The test's output, used for both the expected and the actual results.
-  // Required
-  optional ConsistencyGoldenOutput output = 3;
-}
-
-// The policies configured by the parent.
-message ConsistencyGoldenInput {
-  // List of bedtime configurations for different days of the week.
-  repeated ConsistencyGoldenWindowLimitEntry window_limits = 1;
-}
-
-// Bedtime configuration for a given day.
-message ConsistencyGoldenWindowLimitEntry {
-  // Which day of the week this configuration relates to. Required
-  optional ConsistencyGoldenEffectiveDay effective_day = 1;
-
-  // At which hour and minute this bedtime should start. Required
-  optional ConsistencyGoldenTimeOfDay starts_at = 2;
-
-  // At which hour and minute this bedtime should end. Required
-  optional ConsistencyGoldenTimeOfDay ends_at = 3;
-}
-
-// Represents a moment of a day.
-message ConsistencyGoldenTimeOfDay {
-  // A given hour. [0-23]. Required
-  optional int32 hour = 1;
-
-  // A given minute. [0-59]. Required
-  optional int32 minute = 2;
-}
-
-// Information to represent the current state when executing.
-message ConsistencyGoldenCurrentState {
-  // A timestamp for the desired current time. Required
-  optional int64 time_millis = 1;
-
-  // String representing the desired timezone, formatted like "GMT+2"/"GMT-3"
-  // or "America/Sao_Paulo". Required
-  optional string timezone = 2;
-}
-
-// Information on the output.
-message ConsistencyGoldenOutput {
-  // Whether the device is locked. Required
-  optional bool is_locked = 1;
-
-  // What is the policy currently taking place. Required
-  optional ConsistencyGoldenPolicy active_policy = 2;
-
-  // Timestamp of when is the device supposed to unlock.
-  // This field must be present if and only if the device is locked.
-  optional int64 next_unlocking_time_millis = 3;
-}
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/proto_matcher.h b/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/proto_matcher.h
deleted file mode 100644
index 8bea859..0000000
--- a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/proto_matcher.h
+++ /dev/null
@@ -1,43 +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.
-//
-// A gMock matcher for comparing protos and producing a human-readable
-// message if the assertion fails.
-
-#ifndef CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMIT_CONSISTENCY_TEST_PROTO_MATCHER_H_
-#define CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMIT_CONSISTENCY_TEST_PROTO_MATCHER_H_
-
-#include <string>
-
-#include "testing/gmock/include/gmock/gmock.h"
-#include "third_party/protobuf/src/google/protobuf/text_format.h"
-
-namespace chromeos {
-namespace time_limit_consistency {
-
-MATCHER_P(EqualsProto, message, "equals golden proto") {
-  std::string expected_serialized, actual_serialized;
-  message.SerializeToString(&expected_serialized);
-  arg.SerializeToString(&actual_serialized);
-
-  if (expected_serialized == actual_serialized) {
-    return true;
-  }
-
-  std::string expected_readable, actual_readable;
-  google::protobuf::TextFormat::PrintToString(message, &expected_readable);
-  google::protobuf::TextFormat::PrintToString(arg, &actual_readable);
-
-  *result_listener << "\n\noutput parses to: \n----------\n"
-                   << actual_readable
-                   << "---------\n\n and should be: \n----------\n"
-                   << expected_readable << "----------";
-
-  return expected_serialized == actual_serialized;
-}
-
-}  // namespace time_limit_consistency
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_TIME_LIMIT_CONSISTENCY_TEST_PROTO_MATCHER_H_
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/test_goldens/test_golden.textproto b/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/test_goldens/test_golden.textproto
deleted file mode 100644
index aefe7d7..0000000
--- a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/test_goldens/test_golden.textproto
+++ /dev/null
@@ -1,6 +0,0 @@
-supported_platforms: CHROME_OS
-cases {
-  current_state {
-    time_millis: 42
-  }
-}
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/test_goldens/test_golden_unsupported.textproto b/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/test_goldens/test_golden_unsupported.textproto
deleted file mode 100644
index 86c65f4..0000000
--- a/chrome/browser/chromeos/child_accounts/time_limit_consistency_test/test_goldens/test_golden_unsupported.textproto
+++ /dev/null
@@ -1,6 +0,0 @@
-supported_platforms: ANDROID
-cases {
-  current_state {
-    time_millis: 43
-  }
-}
diff --git a/chrome/browser/chromeos/diagnosticsd/diagnosticsd_messaging.cc b/chrome/browser/chromeos/diagnosticsd/diagnosticsd_messaging.cc
index 8879749..bbf008e 100644
--- a/chrome/browser/chromeos/diagnosticsd/diagnosticsd_messaging.cc
+++ b/chrome/browser/chromeos/diagnosticsd/diagnosticsd_messaging.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/extensions/api/messaging/native_message_port.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/services/diagnosticsd/public/mojom/diagnosticsd.mojom.h"
+#include "extensions/browser/api/messaging/channel_endpoint.h"
 #include "extensions/browser/api/messaging/message_service.h"
 #include "extensions/browser/api/messaging/native_message_host.h"
 #include "extensions/browser/extension_registry.h"
@@ -344,7 +345,7 @@
       message_service->GetChannelDelegate(), port_id,
       std::move(native_message_host));
   message_service->OpenChannelToExtension(
-      -1 /* source_process_id */, -1 /* source_routing_id */, port_id,
+      extensions::ChannelEndpoint(profile), port_id,
       extensions::MessagingEndpoint::ForNativeApp(kDiagnosticsdUiMessageHost),
       std::move(native_message_port), extension_id, GURL(),
       std::string() /* channel_name */);
diff --git a/chrome/browser/chromeos/events/event_rewriter_unittest.cc b/chrome/browser/chromeos/events/event_rewriter_unittest.cc
index 91d0d16..1e0a48d 100644
--- a/chrome/browser/chromeos/events/event_rewriter_unittest.cc
+++ b/chrome/browser/chromeos/events/event_rewriter_unittest.cc
@@ -1038,13 +1038,12 @@
     CheckKeyTestCase(rewriter_, test);
 }
 
-TEST_F(EventRewriterTest, TestRewriteModifiersRemapMany) {
+TEST_F(EventRewriterTest, TestRewriteModifiersRemapEscapeToAlt) {
   // Remap Escape to Alt.
   chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
   IntegerPrefMember escape;
   InitModifierKeyPref(&escape, prefs::kLanguageRemapEscapeKeyTo,
                       ui::chromeos::ModifierKey::kAltKey);
-
   rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
 
   KeyTestCase e2a_tests[] = {
@@ -1061,13 +1060,17 @@
 
   for (const auto& test : e2a_tests)
     CheckKeyTestCase(rewriter_, test);
+}
 
+TEST_F(EventRewriterTest, TestRewriteModifiersRemapAltToControl) {
   // Remap Alt to Control.
+  chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
   IntegerPrefMember alt;
   InitModifierKeyPref(&alt, prefs::kLanguageRemapAltKeyTo,
                       ui::chromeos::ModifierKey::kControlKey);
+  rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
 
-  KeyTestCase a2c_tests[] = {
+  std::vector<KeyTestCase> a2c_tests = {
       // Press left Alt. Confirm the event is now VKEY_CONTROL.
       {ui::ET_KEY_PRESSED,
        {ui::VKEY_MENU, ui::DomCode::ALT_LEFT, ui::EF_ALT_DOWN, ui::DomKey::ALT},
@@ -1091,13 +1094,29 @@
 
   for (const auto& test : a2c_tests)
     CheckKeyTestCase(rewriter_, test);
+}
+
+TEST_F(EventRewriterTest, TestRewriteModifiersRemapUnderEscapeControlAlt) {
+  chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
+
+  // Remap Escape to Alt.
+  IntegerPrefMember escape;
+  InitModifierKeyPref(&escape, prefs::kLanguageRemapEscapeKeyTo,
+                      ui::chromeos::ModifierKey::kAltKey);
+
+  // Remap Alt to Control.
+  IntegerPrefMember alt;
+  InitModifierKeyPref(&alt, prefs::kLanguageRemapAltKeyTo,
+                      ui::chromeos::ModifierKey::kControlKey);
 
   // Remap Control to Search.
   IntegerPrefMember control;
   InitModifierKeyPref(&control, prefs::kLanguageRemapControlKeyTo,
                       ui::chromeos::ModifierKey::kSearchKey);
 
-  KeyTestCase c2s_tests[] = {
+  rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
+
+  std::vector<KeyTestCase> c2s_tests = {
       // Press left Control. Confirm the event is now VKEY_LWIN.
       {ui::ET_KEY_PRESSED,
        {ui::VKEY_CONTROL, ui::DomCode::CONTROL_LEFT, ui::EF_CONTROL_DOWN,
@@ -1136,15 +1155,46 @@
 
   for (const auto& test : c2s_tests)
     CheckKeyTestCase(rewriter_, test);
+}
+
+TEST_F(EventRewriterTest,
+       TestRewriteModifiersRemapUnderEscapeControlAltSearch) {
+  chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
+
+  // Remap Escape to Alt.
+  IntegerPrefMember escape;
+  InitModifierKeyPref(&escape, prefs::kLanguageRemapEscapeKeyTo,
+                      ui::chromeos::ModifierKey::kAltKey);
+
+  // Remap Alt to Control.
+  IntegerPrefMember alt;
+  InitModifierKeyPref(&alt, prefs::kLanguageRemapAltKeyTo,
+                      ui::chromeos::ModifierKey::kControlKey);
+
+  // Remap Control to Search.
+  IntegerPrefMember control;
+  InitModifierKeyPref(&control, prefs::kLanguageRemapControlKeyTo,
+                      ui::chromeos::ModifierKey::kSearchKey);
 
   // Remap Search to Backspace.
   IntegerPrefMember search;
   InitModifierKeyPref(&search, prefs::kLanguageRemapSearchKeyTo,
                       ui::chromeos::ModifierKey::kBackspaceKey);
 
-  KeyTestCase s2b_tests[] = {
+  rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
+
+  std::vector<KeyTestCase> s2b_tests = {
       // Release Control and Escape, as Search and Alt would transform Backspace
       // to Delete.
+      {ui::ET_KEY_PRESSED,
+       {ui::VKEY_CONTROL, ui::DomCode::CONTROL_LEFT, ui::EF_NONE,
+        ui::DomKey::CONTROL},
+       {ui::VKEY_LWIN, ui::DomCode::META_LEFT, ui::EF_COMMAND_DOWN,
+        ui::DomKey::META}},
+      {ui::ET_KEY_PRESSED,
+       {ui::VKEY_ESCAPE, ui::DomCode::ESCAPE, ui::EF_NONE, ui::DomKey::ESCAPE},
+       {ui::VKEY_MENU, ui::DomCode::ALT_LEFT, ui::EF_ALT_DOWN,
+        ui::DomKey::ALT}},
       {ui::ET_KEY_RELEASED,
        {ui::VKEY_CONTROL, ui::DomCode::CONTROL_LEFT, ui::EF_NONE,
         ui::DomKey::CONTROL},
@@ -1163,13 +1213,18 @@
 
   for (const auto& test : s2b_tests)
     CheckKeyTestCase(rewriter_, test);
+}
 
+TEST_F(EventRewriterTest, TestRewriteModifiersRemapBackspaceToEscape) {
   // Remap Backspace to Escape.
+  chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
   IntegerPrefMember backspace;
   InitModifierKeyPref(&backspace, prefs::kLanguageRemapBackspaceKeyTo,
                       ui::chromeos::ModifierKey::kEscapeKey);
 
-  KeyTestCase b2e_tests[] = {
+  rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
+
+  std::vector<KeyTestCase> b2e_tests = {
       // Press Backspace. Confirm the event is now VKEY_ESCAPE.
       {ui::ET_KEY_PRESSED,
        {ui::VKEY_BACK, ui::DomCode::BACKSPACE, ui::EF_NONE,
@@ -2156,8 +2211,15 @@
                     ui::KeyboardCode key_code,
                     ui::DomCode code,
                     ui::DomKey key) {
-    ui::KeyEvent press(type, key_code, code, ui::EF_NONE, key,
-                       ui::EventTimeForNow());
+    SendKeyEvent(type, key_code, code, key, ui::EF_NONE);
+  }
+
+  void SendKeyEvent(ui::EventType type,
+                    ui::KeyboardCode key_code,
+                    ui::DomCode code,
+                    ui::DomKey key,
+                    int flags) {
+    ui::KeyEvent press(type, key_code, code, flags, key, ui::EventTimeForNow());
     ui::EventDispatchDetails details = Send(&press);
     CHECK(!details.dispatcher_destroyed);
   }
@@ -2533,6 +2595,55 @@
   EXPECT_TRUE(events[0]->flags() & ui::EF_ALT_DOWN);
 }
 
+// Tests edge cases of key event rewriting (see https://crbug.com/913209).
+TEST_F(EventRewriterAshTest, KeyEventRewritingEdgeCases) {
+  std::vector<std::unique_ptr<ui::Event>> events;
+
+  // Edge case 1: Press the Launcher button first. Then press the Up Arrow
+  // button.
+  SendKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_COMMAND, ui::DomCode::META_LEFT,
+               ui::DomKey::META);
+  SendKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_UP, ui::DomCode::ARROW_UP,
+               ui::DomKey::ARROW_UP, ui::EF_COMMAND_DOWN);
+
+  PopEvents(&events);
+  EXPECT_EQ(2u, events.size());
+  events.clear();
+
+  SendKeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_COMMAND, ui::DomCode::META_LEFT,
+               ui::DomKey::META);
+  PopEvents(&events);
+
+  // When releasing the Launcher button, the rewritten event should be released
+  // as well.
+  EXPECT_EQ(2u, events.size());
+  EXPECT_EQ(ui::VKEY_COMMAND,
+            static_cast<ui::KeyEvent*>(events[0].get())->key_code());
+  EXPECT_EQ(ui::VKEY_PRIOR,
+            static_cast<ui::KeyEvent*>(events[1].get())->key_code());
+
+  events.clear();
+
+  // Edge case 2: Press the Up Arrow button first. Then press the Launch button.
+  SendKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_UP, ui::DomCode::ARROW_UP,
+               ui::DomKey::ARROW_UP);
+  SendKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_COMMAND, ui::DomCode::META_LEFT,
+               ui::DomKey::META);
+
+  PopEvents(&events);
+  EXPECT_EQ(2u, events.size());
+  events.clear();
+
+  SendKeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_UP, ui::DomCode::ARROW_UP,
+               ui::DomKey::ARROW_UP, ui::EF_COMMAND_DOWN);
+  PopEvents(&events);
+
+  // When releasing the Up Arrow button, the rewritten event should be blocked.
+  EXPECT_EQ(1u, events.size());
+  EXPECT_EQ(ui::VKEY_UP,
+            static_cast<ui::KeyEvent*>(events[0].get())->key_code());
+}
+
 class StickyKeysOverlayTest : public EventRewriterAshTest {
  public:
   StickyKeysOverlayTest() : overlay_(NULL) {}
diff --git a/chrome/browser/chromeos/extensions/file_manager/event_router.cc b/chrome/browser/chromeos/extensions/file_manager/event_router.cc
index 80a6f29a..cc6d6c26 100644
--- a/chrome/browser/chromeos/extensions/file_manager/event_router.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/event_router.cc
@@ -584,7 +584,7 @@
 void EventRouter::AddFileWatch(const base::FilePath& local_path,
                                const base::FilePath& virtual_path,
                                const std::string& extension_id,
-                               const BoolCallback& callback) {
+                               BoolCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!callback.is_null());
 
@@ -604,7 +604,7 @@
     if (is_on_drive) {
       // For Drive, file watching is done via OnDirectoryChanged().
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(callback, true));
+          FROM_HERE, base::BindOnce(std::move(callback), true));
     } else {
       // For local files, start watching using FileWatcher.
       watcher->WatchLocalFile(
@@ -612,14 +612,14 @@
           base::Bind(&EventRouter::HandleFileWatchNotification,
                      weak_factory_.GetWeakPtr(),
                      static_cast<drive::FileChange*>(nullptr)),
-          callback);
+          std::move(callback));
     }
 
     file_watchers_[watch_path] = std::move(watcher);
   } else {
     iter->second->AddExtension(extension_id);
     base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(callback, true));
+        FROM_HERE, base::BindOnce(std::move(callback), true));
   }
 }
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/event_router.h b/chrome/browser/chromeos/extensions/file_manager/event_router.h
index 4f6269e..71b8dc3 100644
--- a/chrome/browser/chromeos/extensions/file_manager/event_router.h
+++ b/chrome/browser/chromeos/extensions/file_manager/event_router.h
@@ -75,7 +75,7 @@
   // KeyedService overrides.
   void Shutdown() override;
 
-  typedef base::Callback<void(bool success)> BoolCallback;
+  using BoolCallback = base::OnceCallback<void(bool success)>;
 
   // Adds a file watch at |local_path|, associated with |virtual_path|, for
   // an extension with |extension_id|.
@@ -88,7 +88,7 @@
   void AddFileWatch(const base::FilePath& local_path,
                     const base::FilePath& virtual_path,
                     const std::string& extension_id,
-                    const BoolCallback& callback);
+                    BoolCallback callback);
 
   // Removes a file watch at |local_path| for an extension with |extension_id|.
   //
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc b/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc
index 35b5ce7..d75f086 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc
@@ -27,35 +27,38 @@
 
 void FileStreamMd5Digester::GetMd5Digest(
     std::unique_ptr<storage::FileStreamReader> stream_reader,
-    const ResultCallback& callback) {
+    ResultCallback callback) {
+  // Only one digest can be running at a time.
+  DCHECK(callback_.is_null());
+
+  callback_ = std::move(callback);
   reader_ = std::move(stream_reader);
   base::MD5Init(&md5_context_);
 
   // Start the read/hash.
-  ReadNextChunk(callback);
+  ReadNextChunk();
 }
 
-void FileStreamMd5Digester::ReadNextChunk(const ResultCallback& callback) {
+void FileStreamMd5Digester::ReadNextChunk() {
   const int result =
       reader_->Read(buffer_.get(), kMd5DigestBufferSize,
                     base::BindOnce(&FileStreamMd5Digester::OnChunkRead,
-                                   base::Unretained(this), callback));
+                                   base::Unretained(this)));
   if (result != net::ERR_IO_PENDING)
-    OnChunkRead(callback, result);
+    OnChunkRead(result);
 }
 
-void FileStreamMd5Digester::OnChunkRead(const ResultCallback& callback,
-                                        int bytes_read) {
+void FileStreamMd5Digester::OnChunkRead(int bytes_read) {
   if (bytes_read < 0) {
     // Error - just return empty string.
-    callback.Run("");
+    std::move(callback_).Run("");
     return;
   } else if (bytes_read == 0) {
     // EOF.
     base::MD5Digest digest;
     base::MD5Final(&digest, &md5_context_);
     std::string result = base::MD5DigestToBase16(digest);
-    callback.Run(result);
+    std::move(callback_).Run(result);
     return;
   }
 
@@ -64,7 +67,7 @@
                   base::StringPiece(buffer_->data(), bytes_read));
 
   // Kick off the next read.
-  ReadNextChunk(callback);
+  ReadNextChunk();
 }
 
 }  // namespace util
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.h b/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.h
index 658c107..e0d080a3d 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.h
+++ b/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.h
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "base/callback_forward.h"
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/md5.h"
 #include "base/memory/ref_counted.h"
@@ -24,7 +24,7 @@
 // stream.
 class FileStreamMd5Digester {
  public:
-  typedef base::Callback<void(const std::string&)> ResultCallback;
+  using ResultCallback = base::OnceCallback<void(const std::string&)>;
 
   FileStreamMd5Digester();
   ~FileStreamMd5Digester();
@@ -35,18 +35,19 @@
   // Only one stream can be processed at a time by each digester.  Do not call
   // GetMd5Digest before the results of a previous call have been returned.
   void GetMd5Digest(std::unique_ptr<storage::FileStreamReader> stream_reader,
-                    const ResultCallback& callback);
+                    ResultCallback callback);
 
  private:
   // Kicks off a read of the next chunk from the stream.
-  void ReadNextChunk(const ResultCallback& callback);
+  void ReadNextChunk();
   // Handles the incoming chunk of data from a stream read.
-  void OnChunkRead(const ResultCallback& callback, int bytes_read);
+  void OnChunkRead(int bytes_read);
 
   // Maximum chunk size for read operations.
   std::unique_ptr<storage::FileStreamReader> reader_;
   scoped_refptr<net::IOBuffer> buffer_;
   base::MD5Context md5_context_;
+  ResultCallback callback_;
 
   DISALLOW_COPY_AND_ASSIGN(FileStreamMd5Digester);
 };
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
index 126a291..b9009e14 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
@@ -252,11 +252,11 @@
 // Calls a response callback (on the UI thread) with a file content hash
 // computed on the IO thread.
 void ComputeChecksumRespondOnUIThread(
-    const base::Callback<void(const std::string&)>& callback,
+    base::OnceCallback<void(const std::string&)> callback,
     const std::string& hash) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
-                           base::BindOnce(callback, hash));
+                           base::BindOnce(std::move(callback), hash));
 }
 
 // Calls a response callback on the UI thread.
@@ -445,7 +445,7 @@
   // Obsolete. Fallback code if storage::WatcherManager is not implemented.
   event_router->AddFileWatch(
       file_system_url.path(), file_system_url.virtual_path(), extension_id(),
-      base::Bind(&FileWatchFunctionBase::RespondWith, this));
+      base::BindOnce(&FileWatchFunctionBase::RespondWith, this));
 }
 
 void FileManagerPrivateInternalRemoveFileWatchFunction::
@@ -961,16 +961,16 @@
       file_system_context->CreateFileStreamReader(
           file_system_url, 0, storage::kMaximumLength, base::Time());
 
-  FileStreamMd5Digester::ResultCallback result_callback = base::Bind(
+  FileStreamMd5Digester::ResultCallback result_callback = base::BindOnce(
       &ComputeChecksumRespondOnUIThread,
-      base::Bind(
+      base::BindOnce(
           &FileManagerPrivateInternalComputeChecksumFunction::RespondWith,
           this));
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
       base::BindOnce(&FileStreamMd5Digester::GetMd5Digest,
-                     base::Unretained(digester_.get()), base::Passed(&reader),
-                     result_callback));
+                     base::Unretained(digester_.get()), std::move(reader),
+                     std::move(result_callback)));
 
   return RespondLater();
 }
diff --git a/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc b/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc
index 1cd6b88..29d6a74 100644
--- a/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc
+++ b/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "content/public/common/child_process_host.h"
 #include "content/public/test/browser_test.h"
+#include "extensions/browser/api/messaging/channel_endpoint.h"
 #include "extensions/browser/api/messaging/message_service.h"
 #include "extensions/browser/api/messaging/native_message_host.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
@@ -88,8 +89,7 @@
         message_service->GetChannelDelegate(), port_id,
         std::move(native_message_host));
     message_service->OpenChannelToExtension(
-        content::ChildProcessHost::kInvalidUniqueID /* source_process_id */,
-        MSG_ROUTING_NONE /* source_routing_id */, port_id,
+        extensions::ChannelEndpoint(profile()), port_id,
         extensions::MessagingEndpoint::ForNativeApp(kFakeNativeAppName),
         std::move(native_message_port), extension_->id(), GURL(),
         std::string() /* channel_name */);
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 2edcaf5..ce6ff531 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -931,7 +931,8 @@
         TestCase("myFilesFolderRename"),
         TestCase("myFilesFolderRename").EnableMyFilesVolume(),
         TestCase("myFilesUpdatesChildren"),
-        TestCase("myFilesUpdatesChildren").EnableMyFilesVolume()));
+        TestCase("myFilesUpdatesChildren").EnableMyFilesVolume(),
+        TestCase("myFilesAutoExpandOnce").EnableMyFilesVolume()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     InstallLinuxPackageDialog, /* install_linux_package_dialog.js */
@@ -952,6 +953,8 @@
         TestCase("recentsDownloads"),
         TestCase("recentsDrive").DisableDriveFs(),
         TestCase("recentsDrive").EnableDriveFs(),
+        TestCase("recentsCrostiniNotMounted"),
+        TestCase("recentsCrostiniMounted"),
         TestCase("recentsDownloadsAndDrive").DisableDriveFs(),
         TestCase("recentsDownloadsAndDrive").EnableDriveFs(),
         TestCase("recentsDownloadsAndDriveWithOverlap").DisableDriveFs(),
diff --git a/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc b/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
index f8a2884..67b5f5d3 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
@@ -250,6 +250,12 @@
         chromeos::PowerManagerClient::Get(), disk_mount_manager_.get());
   }
 
+  void TearDown() override {
+    main_profile_.reset();
+    disk_mount_manager_.reset();
+    chromeos::PowerManagerClient::Shutdown();
+  }
+
   Profile* profile() const { return main_profile_->profile(); }
   VolumeManager* volume_manager() const {
     return main_profile_->volume_manager();
diff --git a/chrome/browser/chromeos/fileapi/recent_download_source.cc b/chrome/browser/chromeos/fileapi/recent_disk_source.cc
similarity index 70%
rename from chrome/browser/chromeos/fileapi/recent_download_source.cc
rename to chrome/browser/chromeos/fileapi/recent_disk_source.cc
index 223f745..c82752a 100644
--- a/chrome/browser/chromeos/fileapi/recent_download_source.cc
+++ b/chrome/browser/chromeos/fileapi/recent_disk_source.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/fileapi/recent_download_source.h"
+#include "chrome/browser/chromeos/fileapi/recent_disk_source.h"
 
 #include <utility>
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/string_util.h"
 #include "base/task/post_task.h"
 #include "base/time/time.h"
-#include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "storage/browser/fileapi/external_mount_points.h"
@@ -72,21 +72,23 @@
 
 }  // namespace
 
-const char RecentDownloadSource::kLoadHistogramName[] =
-    "FileBrowser.Recent.LoadDownloads";
-
-RecentDownloadSource::RecentDownloadSource(Profile* profile)
-    : mount_point_name_(
-          file_manager::util::GetDownloadsMountPointName(profile)),
+RecentDiskSource::RecentDiskSource(std::string mount_point_name,
+                                   bool ignore_dotfiles,
+                                   int max_depth,
+                                   std::string uma_histogram_name)
+    : mount_point_name_(std::move(mount_point_name)),
+      ignore_dotfiles_(ignore_dotfiles),
+      max_depth_(max_depth),
+      uma_histogram_name_(std::move(uma_histogram_name)),
       weak_ptr_factory_(this) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 }
 
-RecentDownloadSource::~RecentDownloadSource() {
+RecentDiskSource::~RecentDiskSource() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 }
 
-void RecentDownloadSource::GetRecentFiles(Params params) {
+void RecentDiskSource::GetRecentFiles(Params params) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!params_.has_value());
   DCHECK(build_start_time_.is_null());
@@ -94,20 +96,29 @@
   DCHECK_EQ(0, inflight_stats_);
   DCHECK(recent_files_.empty());
 
+  // Return immediately if mount point does not exist.
+  storage::ExternalMountPoints* mount_points =
+      storage::ExternalMountPoints::GetSystemInstance();
+  base::FilePath path;
+  if (!mount_points->GetRegisteredPath(mount_point_name_, &path)) {
+    std::move(params.callback()).Run({});
+    return;
+  }
+
   params_.emplace(std::move(params));
 
   DCHECK(params_.has_value());
 
   build_start_time_ = base::TimeTicks::Now();
 
-  ScanDirectory(base::FilePath());
+  ScanDirectory(base::FilePath(), 1);
 }
 
-void RecentDownloadSource::ScanDirectory(const base::FilePath& path) {
+void RecentDiskSource::ScanDirectory(const base::FilePath& path, int depth) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(params_.has_value());
 
-  storage::FileSystemURL url = BuildDownloadsURL(path);
+  storage::FileSystemURL url = BuildDiskURL(path);
 
   ++inflight_readdirs_;
   base::PostTaskWithTraits(
@@ -115,12 +126,13 @@
       base::BindOnce(
           &ReadDirectoryOnIOThread,
           base::WrapRefCounted(params_.value().file_system_context()), url,
-          base::Bind(&RecentDownloadSource::OnReadDirectory,
-                     weak_ptr_factory_.GetWeakPtr(), path)));
+          base::BindRepeating(&RecentDiskSource::OnReadDirectory,
+                              weak_ptr_factory_.GetWeakPtr(), path, depth)));
 }
 
-void RecentDownloadSource::OnReadDirectory(
+void RecentDiskSource::OnReadDirectory(
     const base::FilePath& path,
+    const int depth,
     base::File::Error result,
     storage::FileSystemOperation::FileEntryList entries,
     bool has_more) {
@@ -128,11 +140,21 @@
   DCHECK(params_.has_value());
 
   for (const auto& entry : entries) {
+    // Ignore directories and files that start with dot.
+    if (ignore_dotfiles_ &&
+        base::StartsWith(entry.name.value(), ".",
+                         base::CompareCase::INSENSITIVE_ASCII)) {
+      continue;
+    }
     base::FilePath subpath = path.Append(entry.name);
+
     if (entry.type == filesystem::mojom::FsFileType::DIRECTORY) {
-      ScanDirectory(subpath);
+      if (max_depth_ > 0 && depth >= max_depth_) {
+        continue;
+      }
+      ScanDirectory(subpath, depth + 1);
     } else {
-      storage::FileSystemURL url = BuildDownloadsURL(subpath);
+      storage::FileSystemURL url = BuildDiskURL(subpath);
       ++inflight_stats_;
       base::PostTaskWithTraits(
           FROM_HERE, {BrowserThread::IO},
@@ -140,7 +162,7 @@
               &GetMetadataOnIOThread,
               base::WrapRefCounted(params_.value().file_system_context()), url,
               storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
-              base::BindOnce(&RecentDownloadSource::OnGetMetadata,
+              base::BindOnce(&RecentDiskSource::OnGetMetadata,
                              weak_ptr_factory_.GetWeakPtr(), url)));
     }
   }
@@ -152,9 +174,9 @@
   OnReadOrStatFinished();
 }
 
-void RecentDownloadSource::OnGetMetadata(const storage::FileSystemURL& url,
-                                         base::File::Error result,
-                                         const base::File::Info& info) {
+void RecentDiskSource::OnGetMetadata(const storage::FileSystemURL& url,
+                                     base::File::Error result,
+                                     const base::File::Info& info) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(params_.has_value());
 
@@ -169,7 +191,7 @@
   OnReadOrStatFinished();
 }
 
-void RecentDownloadSource::OnReadOrStatFinished() {
+void RecentDiskSource::OnReadOrStatFinished() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (inflight_readdirs_ > 0 || inflight_stats_ > 0)
@@ -183,8 +205,8 @@
   }
 
   DCHECK(!build_start_time_.is_null());
-  UMA_HISTOGRAM_TIMES(kLoadHistogramName,
-                      base::TimeTicks::Now() - build_start_time_);
+  UmaHistogramTimes(uma_histogram_name_,
+                    base::TimeTicks::Now() - build_start_time_);
   build_start_time_ = base::TimeTicks();
 
   Params params = std::move(params_.value());
@@ -199,14 +221,13 @@
   std::move(params.callback()).Run(std::move(files));
 }
 
-storage::FileSystemURL RecentDownloadSource::BuildDownloadsURL(
+storage::FileSystemURL RecentDiskSource::BuildDiskURL(
     const base::FilePath& path) const {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(params_.has_value());
 
   storage::ExternalMountPoints* mount_points =
       storage::ExternalMountPoints::GetSystemInstance();
-
   return mount_points->CreateExternalFileSystemURL(params_.value().origin(),
                                                    mount_point_name_, path);
 }
diff --git a/chrome/browser/chromeos/fileapi/recent_disk_source.h b/chrome/browser/chromeos/fileapi/recent_disk_source.h
new file mode 100644
index 0000000..13fc983
--- /dev/null
+++ b/chrome/browser/chromeos/fileapi/recent_disk_source.h
@@ -0,0 +1,89 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_FILEAPI_RECENT_DISK_SOURCE_H_
+#define CHROME_BROWSER_CHROMEOS_FILEAPI_RECENT_DISK_SOURCE_H_
+
+#include <memory>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "chrome/browser/chromeos/fileapi/recent_file.h"
+#include "chrome/browser/chromeos/fileapi/recent_model.h"
+#include "chrome/browser/chromeos/fileapi/recent_source.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+
+namespace chromeos {
+
+// RecentSource implementation for local disks.
+// Used for Downloads and fuse-based Crostini.
+//
+// All member functions must be called on the UI thread.
+class RecentDiskSource : public RecentSource {
+ public:
+  // Create a RecentDiskSource for the volume registered to |mount_point_name|.
+  // Does nothing if no volume is registered at |mount_point_name|.
+  // If |ignore_dotfiles| is true, recents will ignore directories and files
+  // starting with a dot.  Set |max_depth| to zero for unlimited depth.
+  RecentDiskSource(std::string mount_point_name,
+                   bool ignore_dotfiles,
+                   int max_depth,
+                   std::string uma_histogram_name);
+  ~RecentDiskSource() override;
+
+  // RecentSource overrides:
+  void GetRecentFiles(Params params) override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(RecentDiskSourceTest, GetRecentFiles_UmaStats);
+
+  static const char kLoadHistogramName[];
+
+  void ScanDirectory(const base::FilePath& path, int depth);
+  void OnReadDirectory(const base::FilePath& path,
+                       int depth,
+                       base::File::Error result,
+                       storage::FileSystemOperation::FileEntryList entries,
+                       bool has_more);
+  void OnGetMetadata(const storage::FileSystemURL& url,
+                     base::File::Error result,
+                     const base::File::Info& info);
+  void OnReadOrStatFinished();
+
+  storage::FileSystemURL BuildDiskURL(const base::FilePath& path) const;
+
+  const std::string mount_point_name_;
+  const bool ignore_dotfiles_;
+  const int max_depth_;
+  const std::string uma_histogram_name_;
+
+  // Parameters given to GetRecentFiles().
+  base::Optional<Params> params_;
+
+  // Time when the build started.
+  base::TimeTicks build_start_time_;
+  // Number of ReadDirectory() calls in flight.
+  int inflight_readdirs_ = 0;
+  // Number of GetMetadata() calls in flight.
+  int inflight_stats_ = 0;
+  // Most recently modified files.
+  std::priority_queue<RecentFile, std::vector<RecentFile>, RecentFileComparator>
+      recent_files_;
+
+  base::WeakPtrFactory<RecentDiskSource> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecentDiskSource);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_FILEAPI_RECENT_DISK_SOURCE_H_
diff --git a/chrome/browser/chromeos/fileapi/recent_disk_source_unittest.cc b/chrome/browser/chromeos/fileapi/recent_disk_source_unittest.cc
new file mode 100644
index 0000000..692a589
--- /dev/null
+++ b/chrome/browser/chromeos/fileapi/recent_disk_source_unittest.cc
@@ -0,0 +1,223 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/time/time.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
+#include "chrome/browser/chromeos/fileapi/recent_disk_source.h"
+#include "chrome/browser/chromeos/fileapi/recent_file.h"
+#include "chrome/browser/chromeos/fileapi/recent_source.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/fileapi/file_system_mount_option.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+
+class RecentDiskSourceTest : public testing::Test {
+ public:
+  RecentDiskSourceTest() : origin_("https://example.com/") {}
+
+  void SetUp() override {
+    profile_ = std::make_unique<TestingProfile>();
+
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    file_system_context_ = content::CreateFileSystemContextForTesting(
+        nullptr, temp_dir_.GetPath());
+
+    mount_point_name_ =
+        file_manager::util::GetDownloadsMountPointName(profile_.get());
+    storage::ExternalMountPoints* mount_points =
+        storage::ExternalMountPoints::GetSystemInstance();
+
+    mount_points->RevokeFileSystem(mount_point_name_);
+    ASSERT_TRUE(mount_points->RegisterFileSystem(
+        mount_point_name_, storage::kFileSystemTypeTest,
+        storage::FileSystemMountOption(), base::FilePath()));
+
+    source_ = std::make_unique<RecentDiskSource>(
+        mount_point_name_, false /* ignore_dotfiles */, 0 /* max_depth */,
+        uma_histogram_name_);
+  }
+
+ protected:
+  bool CreateEmptyFile(const std::string& filename, const base::Time& time) {
+    base::File file(temp_dir_.GetPath().Append(filename),
+                    base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+    if (!file.IsValid())
+      return false;
+
+    return file.SetTimes(time, time);
+  }
+
+  std::vector<RecentFile> GetRecentFiles(size_t max_files,
+                                         const base::Time& cutoff_time) {
+    std::vector<RecentFile> files;
+
+    base::RunLoop run_loop;
+
+    source_->GetRecentFiles(RecentSource::Params(
+        file_system_context_.get(), origin_, max_files, cutoff_time,
+        base::BindOnce(
+            [](base::RunLoop* run_loop, std::vector<RecentFile>* out_files,
+               std::vector<RecentFile> files) {
+              run_loop->Quit();
+              *out_files = std::move(files);
+            },
+            &run_loop, &files)));
+
+    run_loop.Run();
+
+    return files;
+  }
+
+  content::TestBrowserThreadBundle thread_bundle_;
+  const GURL origin_;
+  std::unique_ptr<TestingProfile> profile_;
+  base::ScopedTempDir temp_dir_;
+  scoped_refptr<storage::FileSystemContext> file_system_context_;
+  std::string mount_point_name_;
+  const std::string uma_histogram_name_ = "uma_histogram_name";
+  std::unique_ptr<RecentDiskSource> source_;
+  base::Time base_time_;
+};
+
+TEST_F(RecentDiskSourceTest, GetRecentFiles) {
+  // Oldest
+  ASSERT_TRUE(CreateEmptyFile("1.jpg", base::Time::FromJavaTime(1000)));
+  ASSERT_TRUE(CreateEmptyFile("2.jpg", base::Time::FromJavaTime(2000)));
+  ASSERT_TRUE(CreateEmptyFile("3.jpg", base::Time::FromJavaTime(3000)));
+  ASSERT_TRUE(CreateEmptyFile("4.jpg", base::Time::FromJavaTime(4000)));
+  // Newest
+
+  std::vector<RecentFile> files = GetRecentFiles(3, base::Time());
+
+  std::sort(files.begin(), files.end(), RecentFileComparator());
+
+  ASSERT_EQ(3u, files.size());
+  EXPECT_EQ("4.jpg", files[0].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(4000), files[0].last_modified());
+  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(3000), files[1].last_modified());
+  EXPECT_EQ("2.jpg", files[2].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(2000), files[2].last_modified());
+}
+
+TEST_F(RecentDiskSourceTest, GetRecentFiles_CutoffTime) {
+  // Oldest
+  ASSERT_TRUE(CreateEmptyFile("1.jpg", base::Time::FromJavaTime(1000)));
+  ASSERT_TRUE(CreateEmptyFile("2.jpg", base::Time::FromJavaTime(2000)));
+  ASSERT_TRUE(CreateEmptyFile("3.jpg", base::Time::FromJavaTime(3000)));
+  ASSERT_TRUE(CreateEmptyFile("4.jpg", base::Time::FromJavaTime(4000)));
+  // Newest
+
+  std::vector<RecentFile> files =
+      GetRecentFiles(3, base::Time::FromJavaTime(2500));
+
+  std::sort(files.begin(), files.end(), RecentFileComparator());
+
+  ASSERT_EQ(2u, files.size());
+  EXPECT_EQ("4.jpg", files[0].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(4000), files[0].last_modified());
+  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(3000), files[1].last_modified());
+}
+
+TEST_F(RecentDiskSourceTest, IgnoreDotFiles) {
+  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append(".ignore")));
+  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("noignore")));
+
+  // Oldest
+  ASSERT_TRUE(
+      CreateEmptyFile("noignore/1.jpg", base::Time::FromJavaTime(1000)));
+  ASSERT_TRUE(CreateEmptyFile(".ignore/2.jpg", base::Time::FromJavaTime(2000)));
+  ASSERT_TRUE(CreateEmptyFile("3.jpg", base::Time::FromJavaTime(3000)));
+  ASSERT_TRUE(CreateEmptyFile(".4.jpg", base::Time::FromJavaTime(4000)));
+  // Newest
+
+  std::vector<RecentFile> files = GetRecentFiles(4, base::Time());
+
+  std::sort(files.begin(), files.end(), RecentFileComparator());
+
+  ASSERT_EQ(4u, files.size());
+  EXPECT_EQ(".4.jpg", files[0].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(4000), files[0].last_modified());
+  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(3000), files[1].last_modified());
+  EXPECT_EQ("2.jpg", files[2].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(2000), files[2].last_modified());
+  EXPECT_EQ("1.jpg", files[3].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(1000), files[3].last_modified());
+
+  source_ = std::make_unique<RecentDiskSource>(
+      mount_point_name_, true /* ignore_dotfiles */, 0 /* max_depth */,
+      uma_histogram_name_);
+
+  files = GetRecentFiles(4, base::Time());
+
+  std::sort(files.begin(), files.end(), RecentFileComparator());
+
+  ASSERT_EQ(2u, files.size());
+  EXPECT_EQ("3.jpg", files[0].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(3000), files[0].last_modified());
+  EXPECT_EQ("1.jpg", files[1].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(1000), files[1].last_modified());
+}
+
+TEST_F(RecentDiskSourceTest, MaxDepth) {
+  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("a")));
+  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("a/b")));
+  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("a/b/c")));
+
+  // Oldest
+  ASSERT_TRUE(CreateEmptyFile("1.jpg", base::Time::FromJavaTime(1000)));
+  ASSERT_TRUE(CreateEmptyFile("a/2.jpg", base::Time::FromJavaTime(2000)));
+  ASSERT_TRUE(CreateEmptyFile("a/b/3.jpg", base::Time::FromJavaTime(3000)));
+  ASSERT_TRUE(CreateEmptyFile("a/b/c/4.jpg", base::Time::FromJavaTime(4000)));
+  // Newest
+
+  std::vector<RecentFile> files = GetRecentFiles(4, base::Time());
+  ASSERT_EQ(4u, files.size());
+
+  source_ = std::make_unique<RecentDiskSource>(mount_point_name_, false, 2,
+                                               uma_histogram_name_);
+
+  files = GetRecentFiles(4, base::Time());
+
+  std::sort(files.begin(), files.end(), RecentFileComparator());
+
+  ASSERT_EQ(2u, files.size());
+  EXPECT_EQ("2.jpg", files[0].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(2000), files[0].last_modified());
+  EXPECT_EQ("1.jpg", files[1].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(1000), files[1].last_modified());
+}
+
+TEST_F(RecentDiskSourceTest, GetRecentFiles_UmaStats) {
+  base::HistogramTester histogram_tester;
+
+  GetRecentFiles(3, base::Time());
+
+  histogram_tester.ExpectTotalCount(uma_histogram_name_, 1);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/fileapi/recent_download_source.h b/chrome/browser/chromeos/fileapi/recent_download_source.h
deleted file mode 100644
index 4b96e225..0000000
--- a/chrome/browser/chromeos/fileapi/recent_download_source.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_FILEAPI_RECENT_DOWNLOAD_SOURCE_H_
-#define CHROME_BROWSER_CHROMEOS_FILEAPI_RECENT_DOWNLOAD_SOURCE_H_
-
-#include <memory>
-#include <queue>
-#include <string>
-#include <vector>
-
-#include "base/files/file.h"
-#include "base/files/file_path.h"
-#include "base/gtest_prod_util.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/optional.h"
-#include "base/time/time.h"
-#include "chrome/browser/chromeos/fileapi/recent_file.h"
-#include "chrome/browser/chromeos/fileapi/recent_model.h"
-#include "chrome/browser/chromeos/fileapi/recent_source.h"
-#include "storage/browser/fileapi/file_system_operation.h"
-
-class Profile;
-
-namespace chromeos {
-
-// RecentSource implementation for Downloads files.
-//
-// All member functions must be called on the UI thread.
-class RecentDownloadSource : public RecentSource {
- public:
-  explicit RecentDownloadSource(Profile* profile);
-  ~RecentDownloadSource() override;
-
-  // RecentSource overrides:
-  void GetRecentFiles(Params params) override;
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(RecentDownloadSourceTest, GetRecentFiles_UmaStats);
-
-  static const char kLoadHistogramName[];
-
-  void ScanDirectory(const base::FilePath& path);
-  void OnReadDirectory(const base::FilePath& path,
-                       base::File::Error result,
-                       storage::FileSystemOperation::FileEntryList entries,
-                       bool has_more);
-  void OnGetMetadata(const storage::FileSystemURL& url,
-                     base::File::Error result,
-                     const base::File::Info& info);
-  void OnReadOrStatFinished();
-
-  storage::FileSystemURL BuildDownloadsURL(const base::FilePath& path) const;
-
-  const std::string mount_point_name_;
-
-  // Parameters given to GetRecentFiles().
-  base::Optional<Params> params_;
-
-  // Time when the build started.
-  base::TimeTicks build_start_time_;
-  // Number of ReadDirectory() calls in flight.
-  int inflight_readdirs_ = 0;
-  // Number of GetMetadata() calls in flight.
-  int inflight_stats_ = 0;
-  // Most recently modified files.
-  std::priority_queue<RecentFile, std::vector<RecentFile>, RecentFileComparator>
-      recent_files_;
-
-  base::WeakPtrFactory<RecentDownloadSource> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(RecentDownloadSource);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_FILEAPI_RECENT_DOWNLOAD_SOURCE_H_
diff --git a/chrome/browser/chromeos/fileapi/recent_download_source_unittest.cc b/chrome/browser/chromeos/fileapi/recent_download_source_unittest.cc
deleted file mode 100644
index f697a34c..0000000
--- a/chrome/browser/chromeos/fileapi/recent_download_source_unittest.cc
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <algorithm>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/files/file.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/time/time.h"
-#include "chrome/browser/chromeos/file_manager/path_util.h"
-#include "chrome/browser/chromeos/fileapi/recent_download_source.h"
-#include "chrome/browser/chromeos/fileapi/recent_file.h"
-#include "chrome/browser/chromeos/fileapi/recent_source.h"
-#include "chrome/test/base/testing_profile.h"
-#include "content/public/test/test_browser_thread_bundle.h"
-#include "storage/browser/fileapi/external_mount_points.h"
-#include "storage/browser/fileapi/file_system_context.h"
-#include "storage/browser/fileapi/file_system_url.h"
-#include "storage/browser/test/test_file_system_context.h"
-#include "storage/common/fileapi/file_system_mount_option.h"
-#include "storage/common/fileapi/file_system_types.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace chromeos {
-
-class RecentDownloadSourceTest : public testing::Test {
- public:
-  RecentDownloadSourceTest() : origin_("https://example.com/") {}
-
-  void SetUp() override {
-    profile_ = std::make_unique<TestingProfile>();
-
-    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-
-    file_system_context_ = content::CreateFileSystemContextForTesting(
-        nullptr, temp_dir_.GetPath());
-
-    RegisterFakeDownloadsFileSystem();
-
-    source_ = std::make_unique<RecentDownloadSource>(profile_.get());
-  }
-
- protected:
-  void RegisterFakeDownloadsFileSystem() const {
-    storage::ExternalMountPoints* mount_points =
-        storage::ExternalMountPoints::GetSystemInstance();
-    std::string mount_point_name =
-        file_manager::util::GetDownloadsMountPointName(profile_.get());
-
-    mount_points->RevokeFileSystem(mount_point_name);
-    ASSERT_TRUE(mount_points->RegisterFileSystem(
-        mount_point_name, storage::kFileSystemTypeTest,
-        storage::FileSystemMountOption(), base::FilePath()));
-  }
-
-  bool CreateEmptyFile(const std::string& filename, const base::Time& time) {
-    base::File file(temp_dir_.GetPath().Append(filename),
-                    base::File::FLAG_CREATE | base::File::FLAG_WRITE);
-    if (!file.IsValid())
-      return false;
-
-    return file.SetTimes(time, time);
-  }
-
-  std::vector<RecentFile> GetRecentFiles(size_t max_files,
-                                         const base::Time& cutoff_time) {
-    std::vector<RecentFile> files;
-
-    base::RunLoop run_loop;
-
-    source_->GetRecentFiles(RecentSource::Params(
-        file_system_context_.get(), origin_, max_files, cutoff_time,
-        base::BindOnce(
-            [](base::RunLoop* run_loop, std::vector<RecentFile>* out_files,
-               std::vector<RecentFile> files) {
-              run_loop->Quit();
-              *out_files = std::move(files);
-            },
-            &run_loop, &files)));
-
-    run_loop.Run();
-
-    return files;
-  }
-
-  content::TestBrowserThreadBundle thread_bundle_;
-  const GURL origin_;
-  std::unique_ptr<TestingProfile> profile_;
-  base::ScopedTempDir temp_dir_;
-  scoped_refptr<storage::FileSystemContext> file_system_context_;
-  std::unique_ptr<RecentDownloadSource> source_;
-  base::Time base_time_;
-};
-
-TEST_F(RecentDownloadSourceTest, GetRecentFiles) {
-  // Oldest
-  ASSERT_TRUE(CreateEmptyFile("1.jpg", base::Time::FromJavaTime(1000)));
-  ASSERT_TRUE(CreateEmptyFile("2.jpg", base::Time::FromJavaTime(2000)));
-  ASSERT_TRUE(CreateEmptyFile("3.jpg", base::Time::FromJavaTime(3000)));
-  ASSERT_TRUE(CreateEmptyFile("4.jpg", base::Time::FromJavaTime(4000)));
-  // Newest
-
-  std::vector<RecentFile> files = GetRecentFiles(3, base::Time());
-
-  std::sort(files.begin(), files.end(), RecentFileComparator());
-
-  ASSERT_EQ(3u, files.size());
-  EXPECT_EQ("4.jpg", files[0].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(4000), files[0].last_modified());
-  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(3000), files[1].last_modified());
-  EXPECT_EQ("2.jpg", files[2].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(2000), files[2].last_modified());
-}
-
-TEST_F(RecentDownloadSourceTest, GetRecentFiles_CutoffTime) {
-  // Oldest
-  ASSERT_TRUE(CreateEmptyFile("1.jpg", base::Time::FromJavaTime(1000)));
-  ASSERT_TRUE(CreateEmptyFile("2.jpg", base::Time::FromJavaTime(2000)));
-  ASSERT_TRUE(CreateEmptyFile("3.jpg", base::Time::FromJavaTime(3000)));
-  ASSERT_TRUE(CreateEmptyFile("4.jpg", base::Time::FromJavaTime(4000)));
-  // Newest
-
-  std::vector<RecentFile> files =
-      GetRecentFiles(3, base::Time::FromJavaTime(2500));
-
-  std::sort(files.begin(), files.end(), RecentFileComparator());
-
-  ASSERT_EQ(2u, files.size());
-  EXPECT_EQ("4.jpg", files[0].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(4000), files[0].last_modified());
-  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(3000), files[1].last_modified());
-}
-
-TEST_F(RecentDownloadSourceTest, GetRecentFiles_UmaStats) {
-  base::HistogramTester histogram_tester;
-
-  GetRecentFiles(3, base::Time());
-
-  histogram_tester.ExpectTotalCount(RecentDownloadSource::kLoadHistogramName,
-                                    1);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/fileapi/recent_model.cc b/chrome/browser/chromeos/fileapi/recent_model.cc
index 7bd00f0a..68e51ff 100644
--- a/chrome/browser/chromeos/fileapi/recent_model.cc
+++ b/chrome/browser/chromeos/fileapi/recent_model.cc
@@ -15,8 +15,9 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/chromeos/fileapi/recent_arc_media_source.h"
-#include "chrome/browser/chromeos/fileapi/recent_download_source.h"
+#include "chrome/browser/chromeos/fileapi/recent_disk_source.h"
 #include "chrome/browser/chromeos/fileapi/recent_drive_source.h"
 #include "chrome/browser/chromeos/fileapi/recent_file.h"
 #include "chrome/browser/chromeos/fileapi/recent_model_factory.h"
@@ -41,7 +42,16 @@
     Profile* profile) {
   std::vector<std::unique_ptr<RecentSource>> sources;
   sources.emplace_back(std::make_unique<RecentArcMediaSource>(profile));
-  sources.emplace_back(std::make_unique<RecentDownloadSource>(profile));
+  // Crostini.
+  sources.emplace_back(std::make_unique<RecentDiskSource>(
+      file_manager::util::GetCrostiniMountPointName(profile),
+      true /* ignore_dotfiles */, 4 /* max_depth */,
+      "FileBrowser.Recent.LoadCrostini"));
+  // Downloads / MyFiles.
+  sources.emplace_back(std::make_unique<RecentDiskSource>(
+      file_manager::util::GetDownloadsMountPointName(profile),
+      false /* ignore_dotfiles */, 0 /* max_depth unlimited */,
+      "FileBrowser.Recent.LoadDownloads"));
   sources.emplace_back(std::make_unique<RecentDriveSource>(profile));
   return sources;
 }
diff --git a/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc b/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc
index 1c29312..3f1f9e1 100644
--- a/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc
+++ b/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc
@@ -234,8 +234,8 @@
   ASSERT_TRUE(disabled_listener.WaitUntilSatisfied());
   ASSERT_TRUE(disabled_listener.was_satisfied());
 
-  ui::IMEBridge::Get()->SetInputContextHandler(NULL);
-  ui::IMEBridge::Get()->SetCandidateWindowHandler(NULL);
+  ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
+  ui::IMEBridge::Get()->SetCandidateWindowHandler(nullptr);
 }
 
 IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest,
@@ -1020,8 +1020,8 @@
     EXPECT_EQ("", mock_input_context->last_commit_text());
   }
 
-  ui::IMEBridge::Get()->SetInputContextHandler(NULL);
-  ui::IMEBridge::Get()->SetCandidateWindowHandler(NULL);
+  ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
+  ui::IMEBridge::Get()->SetCandidateWindowHandler(nullptr);
 }
 
 IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest, RestrictedKeyboard) {
@@ -1134,6 +1134,9 @@
     ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
     ASSERT_TRUE(focus_listener.was_satisfied());
   }
+
+  ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
+  ui::IMEBridge::Get()->SetCandidateWindowHandler(nullptr);
 }
 
 IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest, ShouldDoLearning) {
@@ -1163,6 +1166,9 @@
   engine_handler->FocusIn(context);
   ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
   ASSERT_TRUE(focus_listener.was_satisfied());
+
+  ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
+  ui::IMEBridge::Get()->SetCandidateWindowHandler(nullptr);
 }
 
 }  // namespace
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
index 476d32d..0963fa9 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
@@ -163,11 +163,8 @@
   }
   os << "\n";
   os << "extra_input_methods (size=" << extra_input_methods.size() << "):";
-  for (std::map<std::string, InputMethodDescriptor>::const_iterator it =
-           extra_input_methods.begin();
-       it != extra_input_methods.end();
-       ++it) {
-    os << " '" << it->first << "' => '" << it->second.id() << "',\n";
+  for (const auto& entry : extra_input_methods) {
+    os << " '" << entry.first << "' => '" << entry.second.id() << "',\n";
   }
   os << "pending_input_method_id: '" << pending_input_method_id << "'\n";
   os << "input_view_url: '" << input_view_url << "'\n";
@@ -194,8 +191,7 @@
     if (descriptor) {
       result->push_back(*descriptor);
     } else {
-      std::map<std::string, InputMethodDescriptor>::const_iterator ix =
-          extra_input_methods.find(input_method_id);
+      const auto ix = extra_input_methods.find(input_method_id);
       if (ix != extra_input_methods.end())
         result->push_back(ix->second);
       else
@@ -226,8 +222,7 @@
   const InputMethodDescriptor* ime =
       manager_->util_.GetInputMethodDescriptorFromId(input_method_id);
   if (!ime) {
-    std::map<std::string, InputMethodDescriptor>::const_iterator ix =
-        extra_input_methods.find(input_method_id);
+    const auto ix = extra_input_methods.find(input_method_id);
     if (ix != extra_input_methods.end())
       ime = &ix->second;
   }
@@ -626,13 +621,10 @@
 
   // Remove the extra input methods with |extension_id|.
   std::map<std::string, InputMethodDescriptor> new_extra_input_methods;
-  for (std::map<std::string, InputMethodDescriptor>::iterator i =
-           extra_input_methods.begin();
-       i != extra_input_methods.end();
-       ++i) {
+  for (const auto& entry : extra_input_methods) {
     if (extension_id !=
-        extension_ime_util::GetExtensionIDFromInputMethodID(i->first))
-      new_extra_input_methods[i->first] = i->second;
+        extension_ime_util::GetExtensionIDFromInputMethodID(entry.first))
+      new_extra_input_methods[entry.first] = entry.second;
   }
   extra_input_methods.swap(new_extra_input_methods);
 
@@ -654,12 +646,10 @@
     InputMethodDescriptors* result) {
   // Build the extension input method descriptors from the extra input
   // methods cache |extra_input_methods|.
-  std::map<std::string, InputMethodDescriptor>::iterator iter;
-  for (iter = extra_input_methods.begin(); iter != extra_input_methods.end();
-       ++iter) {
-    if (extension_ime_util::IsExtensionIME(iter->first) ||
-        extension_ime_util::IsArcIME(iter->first)) {
-      result->push_back(iter->second);
+  for (const auto& entry : extra_input_methods) {
+    if (extension_ime_util::IsExtensionIME(entry.first) ||
+        extension_ime_util::IsArcIME(entry.first)) {
+      result->push_back(entry.second);
     }
   }
 }
@@ -672,30 +662,25 @@
   bool active_imes_changed = false;
   bool switch_to_pending = false;
 
-  for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter =
-           extra_input_methods.begin();
-       extra_iter != extra_input_methods.end();
-       ++extra_iter) {
-    if (extension_ime_util::IsComponentExtensionIME(extra_iter->first))
+  for (const auto& entry : extra_input_methods) {
+    if (extension_ime_util::IsComponentExtensionIME(entry.first))
       continue;  // Do not filter component extension.
 
-    if (pending_input_method_id == extra_iter->first)
+    if (pending_input_method_id == entry.first)
       switch_to_pending = true;
 
-    std::vector<std::string>::iterator active_iter =
+    const auto active_iter =
         std::find(active_input_method_ids.begin(),
-                  active_input_method_ids.end(),
-                  extra_iter->first);
+                  active_input_method_ids.end(), entry.first);
 
     bool active = active_iter != active_input_method_ids.end();
-    bool enabled =
-        base::ContainsValue(enabled_extension_imes, extra_iter->first);
+    bool enabled = base::ContainsValue(enabled_extension_imes, entry.first);
 
     if (active && !enabled)
       active_input_method_ids.erase(active_iter);
 
     if (!active && enabled)
-      active_input_method_ids.push_back(extra_iter->first);
+      active_input_method_ids.push_back(entry.first);
 
     if (active == !enabled)
       active_imes_changed = true;
@@ -822,7 +807,7 @@
     return;
   }
 
-  std::vector<std::string>::const_iterator iter =
+  const auto iter =
       std::find(active_input_method_ids.begin(), active_input_method_ids.end(),
                 last_used_input_method.id());
   if (iter == active_input_method_ids.end()) {
@@ -835,9 +820,9 @@
 
 void InputMethodManagerImpl::StateImpl::SwitchToNextInputMethodInternal(
     const std::vector<std::string>& input_method_ids,
-    const std::string& current_input_methodid) {
-  std::vector<std::string>::const_iterator iter = std::find(
-      input_method_ids.begin(), input_method_ids.end(), current_input_methodid);
+    const std::string& current_input_method_id) {
+  auto iter = std::find(input_method_ids.begin(), input_method_ids.end(),
+                        current_input_method_id);
   if (iter != input_method_ids.end())
     ++iter;
   if (iter == input_method_ids.end())
@@ -1098,8 +1083,8 @@
       extension_ime_util::GetExtensionIDFromInputMethodID(descriptor.id());
   const std::string& component_id =
       extension_ime_util::GetComponentIDByInputMethodID(descriptor.id());
-  if (engine_map_.find(profile) == engine_map_.end() ||
-      engine_map_[profile].find(extension_id) == engine_map_[profile].end()) {
+  if (!engine_map_.count(profile) ||
+      !engine_map_[profile].count(extension_id)) {
     LOG_IF(ERROR, base::SysInfo::IsRunningOnChromeOS())
         << "IMEEngine for \"" << extension_id << "\" is not registered";
   }
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_session.cc b/chrome/browser/chromeos/login/demo_mode/demo_session.cc
index b8a59f1..cd42564 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_session.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_session.cc
@@ -503,6 +503,9 @@
       }
       break;
     case session_manager::SessionState::ACTIVE:
+      if (ShouldRemoveSplashScreen())
+        RemoveSplashScreen();
+
       // SystemTrayClient may not exist in unit tests.
       if (SystemTrayClient::Get() &&
           base::FeatureList::IsEnabled(
@@ -551,6 +554,15 @@
   splash_screen_removed_ = true;
 }
 
+bool DemoSession::ShouldRemoveSplashScreen() {
+  // TODO(crbug.com/934979): Launch screensaver after active session starts, so
+  // that there's no need to check session state here.
+  return base::FeatureList::IsEnabled(switches::kShowSplashScreenInDemoMode) &&
+         session_manager::SessionManager::Get()->session_state() ==
+             session_manager::SessionState::ACTIVE &&
+         screensaver_activated_;
+}
+
 void DemoSession::OnExtensionInstalled(content::BrowserContext* browser_context,
                                        const extensions::Extension* extension,
                                        bool is_update) {
@@ -564,11 +576,11 @@
 }
 
 void DemoSession::OnAppWindowActivated(extensions::AppWindow* app_window) {
-  if (!base::FeatureList::IsEnabled(switches::kShowSplashScreenInDemoMode) ||
-      app_window->extension_id() != GetScreensaverAppId()) {
+  if (app_window->extension_id() != GetScreensaverAppId())
     return;
-  }
-  RemoveSplashScreen();
+  screensaver_activated_ = true;
+  if (ShouldRemoveSplashScreen())
+    RemoveSplashScreen();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_session.h b/chrome/browser/chromeos/login/demo_mode/demo_session.h
index cac8b84f..a161db1 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_session.h
+++ b/chrome/browser/chromeos/login/demo_mode/demo_session.h
@@ -182,6 +182,11 @@
   // Removes the splash screen.
   void RemoveSplashScreen();
 
+  // Returns whether splash screen should be removed. The splash screen should
+  // be removed when both active session starts (i.e. login screen is destroyed)
+  // and screensaver is shown, to ensure a smooth transition.
+  bool ShouldRemoveSplashScreen();
+
   // session_manager::SessionManagerObserver:
   void OnSessionStateChanged() override;
 
@@ -222,6 +227,7 @@
   std::unique_ptr<base::OneShotTimer> remove_splash_screen_fallback_timer_;
 
   bool splash_screen_removed_ = false;
+  bool screensaver_activated_ = false;
 
   base::WeakPtrFactory<DemoSession> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_session_unittest.cc b/chrome/browser/chromeos/login/demo_mode/demo_session_unittest.cc
index 1fa3a5f..b4c0c6b 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_session_unittest.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_session_unittest.cc
@@ -421,7 +421,8 @@
       demo_session->resources()->GetAbsolutePath(base::FilePath("foo.txt")));
 }
 
-TEST_F(DemoSessionTest, ShowSplashScreenUntilScreensaverShown) {
+// TODO(crbug.com/939687): Reenable the test.
+TEST_F(DemoSessionTest, DISABLED_ShowAndRemoveSplashScreen) {
   DemoSession* demo_session = DemoSession::StartIfInDemoMode();
   ASSERT_TRUE(demo_session);
 
@@ -461,16 +462,23 @@
       screensaver_app.get());
   demo_session->OnAppWindowActivated(app_window);
   wallpaper_controller_client_->FlushForTesting();
+  // The splash screen is not removed until active session starts.
+  EXPECT_EQ(1, test_wallpaper_controller_.show_always_on_top_wallpaper_count());
+  EXPECT_EQ(0,
+            test_wallpaper_controller_.remove_always_on_top_wallpaper_count());
+  session_manager_->SetSessionState(session_manager::SessionState::ACTIVE);
+  wallpaper_controller_client_->FlushForTesting();
   EXPECT_EQ(1, test_wallpaper_controller_.show_always_on_top_wallpaper_count());
   EXPECT_EQ(1,
             test_wallpaper_controller_.remove_always_on_top_wallpaper_count());
-  app_window->OnNativeClose();
-
-  // The timer is cleared after splash screen is removed by the screensaver.
+  // The timer is cleared after splash screen is removed.
   EXPECT_FALSE(demo_session->GetTimerForTesting());
+
+  app_window->OnNativeClose();
 }
 
-TEST_F(DemoSessionTest, ShowSplashScreenUntilTimeout) {
+// TODO(crbug.com/939687): Reenable the test.
+TEST_F(DemoSessionTest, DISABLED_RemoveSplashScreenWhenTimeout) {
   DemoSession* demo_session = DemoSession::StartIfInDemoMode();
   ASSERT_TRUE(demo_session);
 
@@ -523,6 +531,13 @@
   EXPECT_EQ(1, test_wallpaper_controller_.show_always_on_top_wallpaper_count());
   EXPECT_EQ(1,
             test_wallpaper_controller_.remove_always_on_top_wallpaper_count());
+  // Entering active session will not trigger splash screen removal anymore.
+  session_manager_->SetSessionState(session_manager::SessionState::ACTIVE);
+  wallpaper_controller_client_->FlushForTesting();
+  EXPECT_EQ(1, test_wallpaper_controller_.show_always_on_top_wallpaper_count());
+  EXPECT_EQ(1,
+            test_wallpaper_controller_.remove_always_on_top_wallpaper_count());
+
   app_window->OnNativeClose();
 }
 
diff --git a/chrome/browser/chromeos/login/kiosk_browsertest.cc b/chrome/browser/chromeos/login/kiosk_browsertest.cc
index 42af883..9e68965 100644
--- a/chrome/browser/chromeos/login/kiosk_browsertest.cc
+++ b/chrome/browser/chromeos/login/kiosk_browsertest.cc
@@ -6,6 +6,7 @@
 #include <vector>
 
 #include "apps/test/app_window_waiter.h"
+#include "ash/public/interfaces/login_screen_test_api.test-mojom-test-utils.h"
 #include "ash/public/interfaces/wallpaper.mojom.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -20,6 +21,7 @@
 #include "base/system/sys_info.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/app_mode/fake_cws.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_data.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_launch_error.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h"
@@ -49,7 +51,6 @@
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
-#include "chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
@@ -63,6 +64,7 @@
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/web_ui.h"
+#include "content/public/common/service_manager_connection.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/app_window/app_window.h"
@@ -84,6 +86,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "services/audio/public/cpp/fake_system_info.h"
 #include "services/identity/public/cpp/identity_manager.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "ui/aura/window.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/keyboard/public/keyboard_switches.h"
@@ -206,14 +209,6 @@
 const char kTestClientId[] = "fake-client-id";
 const char kTestAppScope[] = "https://www.googleapis.com/auth/userinfo.profile";
 
-// Test JS API.
-const char kLaunchAppForTestNewAPI[] =
-    "login.AccountPickerScreen.runAppForTesting";
-const char kLaunchAppForTestOldAPI[] = "login.AppsMenuButton.runAppForTesting";
-const char kCheckDiagnosticModeNewAPI[] = "$('oobe').confirmDiagnosticMode_";
-const char kCheckDiagnosticModeOldAPI[] =
-    "$('show-apps-button').confirmDiagnosticMode_";
-
 // Helper function for GetConsumerKioskAutoLaunchStatusCallback.
 void ConsumerKioskAutoLaunchStatusCheck(
     KioskAppManager::ConsumerKioskAutoLaunchStatus* out_status,
@@ -459,6 +454,22 @@
  public:
   KioskTest() : settings_helper_(false), fake_cws_(new FakeCWS) {
     set_exit_when_last_browser_closes(false);
+
+    // This test does not operate any real App, so App data does not exist.
+    // Depending on timing, the asynchronous check for app data may or may not
+    // complete before test checks pass. And if the check does complete, it will
+    // mark app status KioskAppData::STATUS_ERROR, and exclude it from the list
+    // of populated apps.
+    //
+    // Then, any Update UI event (asynchronous) (like
+    // LoginDisplayHostCommon::OnStartSignInScreenCommon() will invoke
+    // SendKioskApps() and destroy test configuration.
+    //
+    // We default to ignore test data, as most of test cases use app ids only,
+    // So individual checks should revert it to default when needed.
+    //
+    // TODO(https://crbug.com/937244): Remove this.
+    KioskAppData::SetIgnoreKioskAppDataLoadFailuresForTesting(true);
   }
 
   ~KioskTest() override {}
@@ -515,11 +526,18 @@
       command_line->AppendSwitch(switches::kEnableConsumerKiosk);
   }
 
-  void LaunchApp(const std::string& app_id, bool diagnostic_mode) {
-    bool new_kiosk_ui = KioskAppMenuHandler::EnableNewKioskUI();
-    GetLoginUI()->CallJavascriptFunctionUnsafe(
-        new_kiosk_ui ? kLaunchAppForTestNewAPI : kLaunchAppForTestOldAPI,
-        base::Value(app_id), base::Value(diagnostic_mode));
+  bool LaunchApp(const std::string& app_id, bool diagnostic_mode) {
+    // TODO(https://crbug.com/932323): Implement or remove diagnostic mode.
+    if (diagnostic_mode)
+      return false;
+    ash::mojom::LoginScreenTestApiPtr test_api_;
+    content::ServiceManagerConnection::GetForProcess()
+        ->GetConnector()
+        ->BindInterface(ash::mojom::kServiceName, &test_api_);
+    ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
+    bool found;
+    login_screen.LaunchApp(app_id, &found);
+    return found;
   }
 
   void ReloadKioskApps() {
@@ -536,10 +554,10 @@
   }
 
   void SetupTestAppUpdateCheck() {
-    if (!test_app_version().empty()) {
-      fake_cws_->SetUpdateCrx(test_app_id(), test_crx_file(),
-                              test_app_version());
-    }
+    if (test_app_version().empty())
+      return;
+
+    fake_cws_->SetUpdateCrx(test_app_id(), test_crx_file(), test_app_version());
   }
 
   void ReloadAutolaunchKioskApps() {
@@ -558,18 +576,10 @@
     chromeos::WizardController::SkipPostLoginScreensForTesting();
     chromeos::WizardController* wizard_controller =
         chromeos::WizardController::default_controller();
-    if (wizard_controller) {
+    if (wizard_controller)
       wizard_controller->SkipToLoginForTesting(LoginScreenContext());
-      OobeScreenWaiter(OobeScreen::SCREEN_GAIA_SIGNIN).Wait();
-    } else {
-      // No wizard and running with an existing profile and it should land
-      // on account picker when new kiosk UI is enabled. Otherwise, just
-      // wait for the login signal from Gaia.
-      if (KioskAppMenuHandler::EnableNewKioskUI())
-        OobeScreenWaiter(OobeScreen::SCREEN_ACCOUNT_PICKER).Wait();
-      else
-        OobeScreenWaiter(OobeScreen::SCREEN_GAIA_SIGNIN).Wait();
-    }
+
+    OobeScreenWaiter(OobeScreen::SCREEN_GAIA_SIGNIN).Wait();
   }
 
   void PrepareAppLaunch() {
@@ -590,7 +600,7 @@
     if (!network_setup_cb.is_null())
       network_setup_cb.Run();
 
-    LaunchApp(test_app_id(), false);
+    EXPECT_TRUE(LaunchApp(test_app_id(), false));
   }
 
   const extensions::Extension* GetInstalledApp() {
@@ -978,20 +988,19 @@
             chromeos::KioskAppLaunchError::Get());
 }
 
-IN_PROC_BROWSER_TEST_F(KioskTest, LaunchInDiagnosticMode) {
+// TODO: https://crbug.com/932323
+IN_PROC_BROWSER_TEST_F(KioskTest, DISABLED_LaunchInDiagnosticMode) {
+  const char kCheckDiagnosticModeOldAPI[] =
+      "$('show-apps-button').confirmDiagnosticMode_";
+
   PrepareAppLaunch();
   SimulateNetworkOnline();
 
-  LaunchApp(kTestKioskApp, true);
+  EXPECT_TRUE(LaunchApp(kTestKioskApp, true));
 
-  bool new_kiosk_ui = KioskAppMenuHandler::EnableNewKioskUI();
-  test::OobeJS()
-      .CreateWaiter(new_kiosk_ui ? kCheckDiagnosticModeNewAPI
-                                 : kCheckDiagnosticModeOldAPI)
-      ->Wait();
+  test::OobeJS().CreateWaiter(kCheckDiagnosticModeOldAPI)->Wait();
 
-  std::string diagnosticMode(new_kiosk_ui ?
-      kCheckDiagnosticModeNewAPI : kCheckDiagnosticModeOldAPI);
+  std::string diagnosticMode(kCheckDiagnosticModeOldAPI);
   test::OobeJS().Evaluate(
       "(function() {"
       "var e = new Event('click');" +
@@ -1196,7 +1205,9 @@
 #if defined(ADDRESS_SANITIZER)
 #define MAYBE_DoNotLaunchWhenUntrusted DISABLED_DoNotLaunchWhenUntrusted
 #else
-#define MAYBE_DoNotLaunchWhenUntrusted DoNotLaunchWhenUntrusted
+// TODO(https://crbug.com/934109): Fix kiosk launch when the device is
+// untrusted.
+#define MAYBE_DoNotLaunchWhenUntrusted DISABLED_DoNotLaunchWhenUntrusted
 #endif
 IN_PROC_BROWSER_TEST_F(KioskTest, MAYBE_DoNotLaunchWhenUntrusted) {
   PrepareAppLaunch();
@@ -1207,7 +1218,7 @@
       CrosSettingsProvider::PERMANENTLY_UNTRUSTED);
 
   // Check that the attempt to start a kiosk app fails with an error.
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   bool ignored = false;
   EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
       GetLoginUI()->GetWebContents(),
@@ -1405,7 +1416,7 @@
     set_test_crx_file(crx_file);
     PrepareAppLaunch();
     SimulateNetworkOnline();
-    LaunchApp(test_app_id(), false);
+    EXPECT_TRUE(LaunchApp(test_app_id(), false));
     WaitForAppLaunchSuccess();
     EXPECT_EQ(version, GetInstalledAppVersion().GetString());
   }
@@ -1426,7 +1437,7 @@
     // Launch the primary app.
     StartUIForAppLaunch();
     SimulateNetworkOnline();
-    LaunchApp(test_app_id(), false);
+    EXPECT_TRUE(LaunchApp(test_app_id(), false));
     WaitForAppLaunchWithOptions(false, true);
 
     // Verify the primary app and the secondary apps are all installed.
@@ -1583,7 +1594,7 @@
   set_test_app_id(kTestOfflineEnabledKioskApp);
   StartUIForAppLaunch();
   SimulateNetworkOffline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("1.0.0", GetInstalledAppVersion().GetString());
@@ -1604,7 +1615,7 @@
       KioskAppManager::Get()->HasCachedCrx(kTestOfflineEnabledKioskApp));
   StartUIForAppLaunch();
   SimulateNetworkOffline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("1.0.0", GetInstalledAppVersion().GetString());
@@ -1632,7 +1643,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOffline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   // v2 app should have been installed.
@@ -1651,7 +1662,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("1.0.0", GetInstalledAppVersion().GetString());
@@ -1670,7 +1681,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("2.0.0", GetInstalledAppVersion().GetString());
@@ -1687,7 +1698,7 @@
   set_test_app_id(kTestOfflineEnabledKioskApp);
   StartUIForAppLaunch();
   SimulateNetworkOffline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
   EXPECT_EQ("1.0.0", GetInstalledAppVersion().GetString());
 
@@ -1715,7 +1726,7 @@
   set_test_app_id(kTestOfflineEnabledKioskApp);
   StartUIForAppLaunch();
   SimulateNetworkOffline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
   EXPECT_EQ("2.0.0", GetInstalledAppVersion().GetString());
 }
@@ -1842,7 +1853,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("2.0.0", GetInstalledAppVersion().GetString());
@@ -1904,7 +1915,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("1.0.0", GetInstalledAppVersion().GetString());
@@ -1926,7 +1937,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("2.0.0", GetInstalledAppVersion().GetString());
@@ -1950,7 +1961,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchSuccess();
 
   EXPECT_EQ("2.0.0", GetInstalledAppVersion().GetString());
@@ -1981,7 +1992,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchWithOptions(false, true);
 
   // Verify the secondary app kTestSecondaryApp1 is removed.
@@ -2009,7 +2020,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchWithOptions(false, true);
 
   // Verify the secondary app kTestSecondaryApp3 is installed.
@@ -2044,7 +2055,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchWithOptions(false, true);
 
   // Verify the secondary app is removed.
@@ -2077,7 +2088,7 @@
 
   StartUIForAppLaunch();
   SimulateNetworkOnline();
-  LaunchApp(test_app_id(), false);
+  EXPECT_TRUE(LaunchApp(test_app_id(), false));
   WaitForAppLaunchWithOptions(false, true);
 
   // Verify the shared module is updated to the new version after primary app
@@ -2198,7 +2209,7 @@
                             "");
 
   PrepareAppLaunch();
-  LaunchApp(kTestEnterpriseKioskApp, false);
+  EXPECT_TRUE(LaunchApp(kTestEnterpriseKioskApp, false));
 
   KioskSessionInitializedWaiter().Wait();
 
@@ -2268,7 +2279,7 @@
   waiter.WaitForAppData();
 
   PrepareAppLaunch();
-  LaunchApp(kTestEnterpriseKioskApp, false);
+  EXPECT_TRUE(LaunchApp(kTestEnterpriseKioskApp, false));
   WaitForAppLaunchWithOptions(false /* check_launch_data */,
                               true /* terminate_app */);
 
diff --git a/chrome/browser/chromeos/login/lock/screen_locker_tester.cc b/chrome/browser/chromeos/login/lock/screen_locker_tester.cc
index 6aca187..4af64ab 100644
--- a/chrome/browser/chromeos/login/lock/screen_locker_tester.cc
+++ b/chrome/browser/chromeos/login/lock/screen_locker_tester.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/login/lock/screen_locker_tester.h"
 
+#include <cstdint>
 #include <string>
 
 #include "ash/public/cpp/ash_switches.h"
@@ -35,16 +36,6 @@
 namespace chromeos {
 namespace {
 
-// Helper to use inside a loop instead of using RunLoop::RunUntilIdle() to avoid
-// the loop being a busy loop that prevents renderer from doing its job. Use
-// only when there is no better way to synchronize.
-void GiveItSomeTime(base::TimeDelta delta) {
-  base::RunLoop run_loop;
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE, run_loop.QuitClosure(), delta);
-  run_loop.Run();
-}
-
 bool IsScreenLockerLocked() {
   return ScreenLocker::default_screen_locker() &&
          ScreenLocker::default_screen_locker()->locked();
@@ -137,23 +128,18 @@
   return IsScreenLockerLocked() && is_ui_shown;
 }
 
-bool ScreenLockerTester::IsRestartButtonShown() {
+bool ScreenLockerTester::IsLockRestartButtonShown() {
   if (!IsScreenLockerLocked())
     return false;
 
-  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
-  bool is_restart_button_shown;
-  login_screen.IsRestartButtonShown(&is_restart_button_shown);
-  return IsScreenLockerLocked() && is_restart_button_shown;
+  return login_screen_tester_.IsRestartButtonShown() && IsScreenLockerLocked();
 }
 
-bool ScreenLockerTester::IsShutdownButtonShown() {
+bool ScreenLockerTester::IsLockShutdownButtonShown() {
   if (!IsScreenLockerLocked())
     return false;
 
-  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
-  bool is_shutdown_button_shown;
-  login_screen.IsShutdownButtonShown(&is_shutdown_button_shown);
+  bool is_shutdown_button_shown = login_screen_tester_.IsShutdownButtonShown();
   return IsScreenLockerLocked() && is_shutdown_button_shown;
 }
 
@@ -165,17 +151,11 @@
 }
 
 int64_t ScreenLockerTester::GetUiUpdateCount() {
-  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
-  int64_t ui_update_count = 0;
-  login_screen.GetUiUpdateCount(&ui_update_count);
-  return ui_update_count;
+  return login_screen_tester_.GetUiUpdateCount();
 }
 
-// Blocks until LoginShelfView::ui_update_count() is greater then
-// |previous_update_count|.
 void ScreenLockerTester::WaitForUiUpdate(int64_t previous_update_count) {
-  while (GetUiUpdateCount() <= previous_update_count) {
-    GiveItSomeTime(base::TimeDelta::FromMilliseconds(100));
-  }
+  login_screen_tester_.WaitForUiUpdate(previous_update_count);
 }
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/lock/screen_locker_tester.h b/chrome/browser/chromeos/login/lock/screen_locker_tester.h
index 179e653..bcd9044b 100644
--- a/chrome/browser/chromeos/login/lock/screen_locker_tester.h
+++ b/chrome/browser/chromeos/login/lock/screen_locker_tester.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "ash/public/interfaces/login_screen_test_api.test-mojom.h"
+#include "chrome/browser/chromeos/login/test/login_screen_tester.h"
 
 class AccountId;
 
@@ -31,23 +32,21 @@
   bool IsLocked();
 
   // Returns true if Restart button is visible.
-  bool IsRestartButtonShown();
+  bool IsLockRestartButtonShown();
 
   // Returns true if Shutdown button is visible.
-  bool IsShutdownButtonShown();
+  bool IsLockShutdownButtonShown();
 
   // Enters and submits the given password for the given account.
   void UnlockWithPassword(const AccountId& account_id,
                           const std::string& password);
 
-  // Returns LoginShelfView update count.
+  // LoginScreenTester proxy methods:
   int64_t GetUiUpdateCount();
-
-  // Blocks until LoginShelfView::ui_update_count() is creater then
-  // |previous_update_count|.
   void WaitForUiUpdate(int64_t previous_update_count);
 
  private:
+  test::LoginScreenTester login_screen_tester_;
   ash::mojom::LoginScreenTestApiPtr test_api_;
 
   DISALLOW_COPY_AND_ASSIGN(ScreenLockerTester);
diff --git a/chrome/browser/chromeos/login/login_ui_keyboard_browsertest.cc b/chrome/browser/chromeos/login/login_ui_keyboard_browsertest.cc
index 199241e..5317c05 100644
--- a/chrome/browser/chromeos/login/login_ui_keyboard_browsertest.cc
+++ b/chrome/browser/chromeos/login/login_ui_keyboard_browsertest.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/chromeos/login/login_manager_test.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/login_screen_tester.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/settings/scoped_testing_cros_settings.h"
@@ -264,7 +265,7 @@
                                         ->GetActiveInputMethodIds());
 
   // Switch to Gaia.
-  test::OobeJS().Evaluate("$('add-user-button').click()");
+  ASSERT_TRUE(test::LoginScreenTester().ClickAddUserButton());
   OobeScreenWaiter(OobeScreen::SCREEN_GAIA_SIGNIN).Wait();
   CheckGaiaKeyboard();
 
diff --git a/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc b/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc
index 845144c7..755de0b6 100644
--- a/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc
+++ b/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/login/login_manager_test.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
+#include "chrome/browser/chromeos/login/test/login_screen_tester.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
@@ -109,9 +110,7 @@
   {
     OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_GAIA_SIGNIN);
     ProxyAuthDialogWaiter auth_dialog_waiter;
-    ASSERT_TRUE(content::ExecuteScript(
-        LoginDisplayHost::default_host()->GetOobeWebContents(),
-        "$('add-user-button').click()"));
+    ASSERT_TRUE(test::LoginScreenTester().ClickAddUserButton());
     screen_waiter.Wait();
     auth_dialog_waiter.Wait();
     ASSERT_TRUE(auth_dialog_waiter.login_handler());
diff --git a/chrome/browser/chromeos/login/saml/saml_browsertest.cc b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
index d1f5a66..ff4c845 100644
--- a/chrome/browser/chromeos/login/saml/saml_browsertest.cc
+++ b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
@@ -36,6 +36,7 @@
 #include "chrome/browser/chromeos/login/test/https_forwarder.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
+#include "chrome/browser/chromeos/login/test/login_screen_tester.h"
 #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
@@ -1181,8 +1182,8 @@
       GetLoginUI()->GetWebContents(),
       "$('gaia-signin').gaiaAuthHost_.addEventListener('ready', function() {"
       "  window.domAutomationController.send('ready');"
-      "});"
-      "$('add-user-button').click();"));
+      "});"));
+  ASSERT_TRUE(test::LoginScreenTester().ClickAddUserButton());
   content::DOMMessageQueue message_queue;
   std::string message;
   ASSERT_TRUE(message_queue.WaitForMessage(&message));
@@ -1193,13 +1194,20 @@
   login_screen_load_observer_->Wait();
   ASSERT_TRUE(
       content::ExecuteScript(GetLoginUI()->GetWebContents(),
-                             "$('saml-interstitial').addEventListener("
-                             "    'samlInterstitialPageReady',"
-                             "    function() {"
-                             "        window.domAutomationController.send("
-                             "            'samlInterstitialPageReady');"
-                             "    });"
-                             "$('add-user-button').click();"));
+                             "{"
+                             "  let notify = function() {"
+                             "    window.domAutomationController.send("
+                             "        'samlInterstitialPageReady');"
+                             "  };"
+                             "  if($('gaia-signin')."
+                             "      samlInterstitialPageReady) {"
+                             "    window.setTimeout(notify,0);"
+                             "  } else {"
+                             "    $('saml-interstitial').addEventListener("
+                             "        'samlInterstitialPageReady', notify);"
+                             "  }"
+                             "}"));
+  ASSERT_TRUE(test::LoginScreenTester().ClickAddUserButton());
 
   content::DOMMessageQueue message_queue;
   std::string message;
diff --git a/chrome/browser/chromeos/login/screens/core_oobe_view.h b/chrome/browser/chromeos/login/screens/core_oobe_view.h
index 9e6a4e6..57dcfde1 100644
--- a/chrome/browser/chromeos/login/screens/core_oobe_view.h
+++ b/chrome/browser/chromeos/login/screens/core_oobe_view.h
@@ -35,7 +35,6 @@
   virtual void ClearErrors() = 0;
   virtual void ReloadContent(const base::DictionaryValue& dictionary) = 0;
   virtual void ReloadEulaContent(const base::DictionaryValue& dictionary) = 0;
-  virtual void ShowControlBar(bool show) = 0;
   virtual void SetVirtualKeyboardShown(bool shown) = 0;
   virtual void SetClientAreaSize(int width, int height) = 0;
   virtual void ShowDeviceResetScreen() = 0;
diff --git a/chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.cc b/chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.cc
index ab79259..a188264a 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.cc
+++ b/chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.cc
@@ -6,9 +6,7 @@
 
 #include "chrome/browser/chromeos/login/signin/oauth2_login_manager.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/signin/gaia_cookie_manager_service_factory.h"
-#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
-#include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 namespace chromeos {
@@ -17,9 +15,7 @@
     : BrowserContextKeyedServiceFactory(
           "OAuth2LoginManager",
           BrowserContextDependencyManager::GetInstance()) {
-  DependsOn(SigninManagerFactory::GetInstance());
-  DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
-  DependsOn(GaiaCookieManagerServiceFactory::GetInstance());
+  DependsOn(IdentityManagerFactory::GetInstance());
 }
 
 OAuth2LoginManagerFactory::~OAuth2LoginManagerFactory() {}
diff --git a/chrome/browser/chromeos/login/test/fake_gaia_mixin.cc b/chrome/browser/chromeos/login/test/fake_gaia_mixin.cc
index ec68ef8..c38dc0c 100644
--- a/chrome/browser/chromeos/login/test/fake_gaia_mixin.cc
+++ b/chrome/browser/chromeos/login/test/fake_gaia_mixin.cc
@@ -104,4 +104,8 @@
   }
 }
 
+void FakeGaiaMixin::TearDownOnMainThread() {
+  EXPECT_TRUE(gaia_https_forwarder_.Stop());
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/test/fake_gaia_mixin.h b/chrome/browser/chromeos/login/test/fake_gaia_mixin.h
index 867762f..3092eddb 100644
--- a/chrome/browser/chromeos/login/test/fake_gaia_mixin.h
+++ b/chrome/browser/chromeos/login/test/fake_gaia_mixin.h
@@ -77,6 +77,7 @@
   void SetUp() override;
   void SetUpCommandLine(base::CommandLine* command_line) override;
   void SetUpOnMainThread() override;
+  void TearDownOnMainThread() override;
 
  private:
   net::EmbeddedTestServer* embedded_test_server_;
diff --git a/chrome/browser/chromeos/login/test/https_forwarder.cc b/chrome/browser/chromeos/login/test/https_forwarder.cc
index 67c285f..90c75e4 100644
--- a/chrome/browser/chromeos/login/test/https_forwarder.cc
+++ b/chrome/browser/chromeos/login/test/https_forwarder.cc
@@ -120,4 +120,8 @@
   return forwarding_server_->Start();
 }
 
+bool HTTPSForwarder::Stop() {
+  return forwarding_server_->Stop();
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/test/https_forwarder.h b/chrome/browser/chromeos/login/test/https_forwarder.h
index a04a5fd..9a454fdb 100644
--- a/chrome/browser/chromeos/login/test/https_forwarder.h
+++ b/chrome/browser/chromeos/login/test/https_forwarder.h
@@ -36,6 +36,8 @@
   bool Initialize(const std::string& ssl_host,
                   const GURL& forward_target) WARN_UNUSED_RESULT;
 
+  bool Stop() WARN_UNUSED_RESULT;
+
  private:
   std::string ssl_host_;
 
diff --git a/chrome/browser/chromeos/login/test/login_screen_tester.cc b/chrome/browser/chromeos/login/test/login_screen_tester.cc
new file mode 100644
index 0000000..3b8052f7a
--- /dev/null
+++ b/chrome/browser/chromeos/login/test/login_screen_tester.cc
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/login/test/login_screen_tester.h"
+
+#include "ash/public/interfaces/constants.mojom.h"
+#include "ash/public/interfaces/login_screen_test_api.test-mojom-test-utils.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace chromeos {
+namespace test {
+
+LoginScreenTester::LoginScreenTester() {
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->BindInterface(ash::mojom::kServiceName, &test_api_);
+}
+
+LoginScreenTester::~LoginScreenTester() = default;
+
+int64_t LoginScreenTester::GetUiUpdateCount() {
+  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
+  int64_t ui_update_count = 0;
+  login_screen.GetUiUpdateCount(&ui_update_count);
+  return ui_update_count;
+}
+
+bool LoginScreenTester::IsRestartButtonShown() {
+  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
+  bool is_restart_button_shown;
+  login_screen.IsRestartButtonShown(&is_restart_button_shown);
+  return is_restart_button_shown;
+}
+
+bool LoginScreenTester::IsShutdownButtonShown() {
+  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
+  bool is_shutdown_button_shown;
+  login_screen.IsShutdownButtonShown(&is_shutdown_button_shown);
+  return is_shutdown_button_shown;
+}
+
+bool LoginScreenTester::ClickAddUserButton() {
+  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
+  bool success;
+  login_screen.ClickAddUserButton(&success);
+  return success;
+}
+
+bool LoginScreenTester::WaitForUiUpdate(int64_t previous_update_count) {
+  ash::mojom::LoginScreenTestApiAsyncWaiter login_screen(test_api_.get());
+  bool success;
+  login_screen.WaitForUiUpdate(previous_update_count, &success);
+  return success;
+}
+
+}  // namespace test
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/test/login_screen_tester.h b/chrome/browser/chromeos/login/test/login_screen_tester.h
new file mode 100644
index 0000000..957ec4a
--- /dev/null
+++ b/chrome/browser/chromeos/login/test/login_screen_tester.h
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_TEST_LOGIN_SCREEN_TESTER_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_TEST_LOGIN_SCREEN_TESTER_H_
+
+#include <cstdint>
+
+#include "ash/public/interfaces/login_screen_test_api.test-mojom-test-utils.h"
+#include "base/macros.h"
+
+namespace chromeos {
+namespace test {
+
+// High-level API to ash::mojom::LoginScreenTestApi.
+class LoginScreenTester {
+ public:
+  LoginScreenTester();
+  ~LoginScreenTester();
+
+  // Blocking mojo calls.
+  int64_t GetUiUpdateCount();
+  bool IsRestartButtonShown();
+  bool IsShutdownButtonShown();
+
+  // Returns true on success (i.e. button is  not disabled).
+  bool ClickAddUserButton();
+
+  // Blocks until LoginShelfView::ui_update_count() is greater then
+  // |previous_update_count|. Returns true on success, false on error.
+  bool WaitForUiUpdate(int64_t previous_update_count);
+
+ private:
+  ash::mojom::LoginScreenTestApiPtr test_api_;
+
+  DISALLOW_COPY_AND_ASSIGN(LoginScreenTester);
+};
+
+}  // namespace test
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_TEST_LOGIN_SCREEN_TESTER_H_
diff --git a/chrome/browser/chromeos/login/test/oobe_base_test.cc b/chrome/browser/chromeos/login/test/oobe_base_test.cc
index ee23b45..5f435b8 100644
--- a/chrome/browser/chromeos/login/test/oobe_base_test.cc
+++ b/chrome/browser/chromeos/login/test/oobe_base_test.cc
@@ -125,9 +125,9 @@
 }
 
 void OobeBaseTest::TearDownOnMainThread() {
-  EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
-
   mixin_host_.TearDownOnMainThread();
+  // Embedded test server should always be shutdown after any https forwarders.
+  EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
   extensions::ExtensionApiTest::TearDownOnMainThread();
 }
 
diff --git a/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.cc b/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.cc
index b558547..3248a54 100644
--- a/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.cc
+++ b/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.cc
@@ -4,14 +4,18 @@
 
 #include "chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h"
 
+#include <utility>
+
 #include "ash/public/interfaces/kiosk_app_info.mojom.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_data.h"
 #include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_launch_error.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager_observer.h"
 #include "chrome/browser/ui/ash/login_screen_client.h"
+#include "content/public/browser/notification_service.h"
 #include "extensions/grit/extensions_browser_resources.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image_skia.h"
@@ -42,6 +46,16 @@
   SendKioskApps();
 }
 
+void KioskAppMenuUpdater::OnKioskAppsSet(bool success) {
+  if (!success)
+    return;
+
+  content::NotificationService::current()->Notify(
+      chrome::NOTIFICATION_KIOSK_APPS_LOADED,
+      content::NotificationService::AllSources(),
+      content::NotificationService::NoDetails());
+}
+
 void KioskAppMenuUpdater::SendKioskApps() {
   if (!LoginScreenClient::HasInstance())
     return;
@@ -81,8 +95,9 @@
     }
     output.push_back(std::move(mojo_app));
   }
-
-  LoginScreenClient::Get()->login_screen()->SetKioskApps(std::move(output));
+  LoginScreenClient::Get()->login_screen()->SetKioskApps(
+      std::move(output), base::BindOnce(&KioskAppMenuUpdater::OnKioskAppsSet,
+                                        weak_factory_.GetWeakPtr()));
 
   KioskAppLaunchError::Error error = KioskAppLaunchError::Get();
   if (error == KioskAppLaunchError::NONE)
diff --git a/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h b/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h
index 96e092c0..af668605 100644
--- a/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h
+++ b/chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_CHROMEOS_LOGIN_UI_KIOSK_APP_MENU_UPDATER_H_
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
@@ -33,9 +34,14 @@
   void OnArcKioskAppsChanged() override;
 
  private:
+  // Mojo SendKioskApps() callback.
+  void OnKioskAppsSet(bool success);
+
   ScopedObserver<KioskAppManager, KioskAppMenuUpdater> kiosk_observer_;
   ScopedObserver<ArcKioskAppManager, KioskAppMenuUpdater> arc_kiosk_observer_;
 
+  base::WeakPtrFactory<KioskAppMenuUpdater> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(KioskAppMenuUpdater);
 };
 
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_common.cc b/chrome/browser/chromeos/login/ui/login_display_host_common.cc
index 8e42ed59..4a16485b 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_common.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_common.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/chromeos/login/arc_kiosk_controller.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_app_launcher.h"
 #include "chrome/browser/chromeos/login/existing_user_controller.h"
+#include "chrome/browser/chromeos/login/screens/gaia_view.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -267,4 +268,35 @@
   base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
 }
 
+void LoginDisplayHostCommon::OnStartSignInScreenCommon() {
+  kiosk_updater_.SendKioskApps();
+}
+
+void LoginDisplayHostCommon::ShowGaiaDialogCommon(
+    const base::Optional<AccountId>& prefilled_account) {
+  DCHECK(GetOobeUI());
+
+  if (prefilled_account) {
+    // Make sure gaia displays |account| if requested.
+    if (!GetLoginDisplay()->delegate()->IsSigninInProgress())
+      GetOobeUI()->GetGaiaScreenView()->ShowGaiaAsync(prefilled_account);
+    LoadWallpaper(*prefilled_account);
+  } else {
+    // Two criteria here:
+    // 1) If we have started a wizard other than Gaia signin (signified by the
+    // current_screen() changing), we need to reload the Gaia screen, otherwise
+    // dialog_->Show() will show the wrong screen.
+    // 2) While login is being loaded in, the current_screen is UNKNOWN. During
+    // this time, the GaiaScreenView is initialized, after which
+    // ShowGaiaAsync() is called to load up the Gaia screen. If we try to
+    // ShowGaiaAsync() before this initialization is complete, the Gaia screen
+    // UI can crash and get stuck.
+    if (GetOobeUI()->current_screen() != OobeScreen::SCREEN_GAIA_SIGNIN &&
+        GetOobeUI()->current_screen() != OobeScreen::SCREEN_UNKNOWN) {
+      GetOobeUI()->GetGaiaScreenView()->ShowGaiaAsync(base::nullopt);
+    }
+    LoadSigninWallpaper();
+  }
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_common.h b/chrome/browser/chromeos/login/ui/login_display_host_common.h
index 140d4b7..0fa53d7 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_common.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_common.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
@@ -74,6 +75,12 @@
   // Marks display host for deletion.
   void ShutdownDisplayHost();
 
+  // Common code for OnStartSignInScreen() call above.
+  void OnStartSignInScreenCommon();
+
+  // Common code for ShowGaiaDialog() call above.
+  void ShowGaiaDialogCommon(const base::Optional<AccountId>& prefilled_account);
+
   // Active instance of authentication prewarmer.
   std::unique_ptr<AuthPrewarmer> auth_prewarmer_;
 
@@ -106,6 +113,8 @@
   // Called after host deletion.
   std::vector<base::OnceClosure> completion_callbacks_;
 
+  KioskAppMenuUpdater kiosk_updater_;
+
   base::WeakPtrFactory<LoginDisplayHostCommon> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(LoginDisplayHostCommon);
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
index 9fade08..0d6d9ff 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
@@ -213,12 +213,12 @@
 
   user_selection_screen_->InitEasyUnlock();
 
-  kiosk_updater_.SendKioskApps();
-
   system_info_updater_->StartRequest();
 
   // Update status of add user button in the shelf.
   UpdateAddUserButtonStatus();
+
+  OnStartSignInScreenCommon();
 }
 
 void LoginDisplayHostMojo::OnPreferencesChanged() {
@@ -248,26 +248,7 @@
   if (users_.empty())
     can_close_dialog_ = false;
 
-  if (prefilled_account) {
-    // Make sure gaia displays |account| if requested.
-    if (!login_display_->IsSigninInProgress())
-      GetOobeUI()->GetGaiaScreenView()->ShowGaiaAsync(prefilled_account);
-    LoadWallpaper(*prefilled_account);
-  } else {
-    // Two criteria here:
-    // 1) If we have started a wizard other than Gaia signin (signified by the
-    // current_screen() changing), we need to reload the Gaia screen, otherwise
-    // dialog_->Show() will show the wrong screen. 2) While login is being
-    // loaded in, the current_screen is UNKNOWN. During this time, the
-    // GaiaScreenView is initialized, after which ShowGaiaAsync() is called to
-    // load up the Gaia screen. If we try to ShowGaiaAsync() before this
-    // initialization is complete, the Gaia screen UI can crash and get stuck.
-    if (GetOobeUI()->current_screen() != OobeScreen::SCREEN_GAIA_SIGNIN &&
-        GetOobeUI()->current_screen() != OobeScreen::SCREEN_UNKNOWN) {
-      GetOobeUI()->GetGaiaScreenView()->ShowGaiaAsync(base::nullopt);
-    }
-    LoadSigninWallpaper();
-  }
+  ShowGaiaDialogCommon(prefilled_account);
 
   dialog_->Show();
 }
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
index b2d12fb..5c8b37af 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
@@ -12,7 +12,6 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
-#include "chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host_common.h"
 #include "chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.h"
 #include "chrome/browser/ui/ash/login_screen_client.h"
@@ -157,8 +156,6 @@
   // The account id of the user pod that's being focused.
   AccountId focused_pod_account_id_;
 
-  KioskAppMenuUpdater kiosk_updater_;
-
   // Fetches system information and sends it to the UI over mojo.
   std::unique_ptr<MojoSystemInfoDispatcher> system_info_updater_;
 
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
index d11c511b..3f4e802d 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
@@ -672,6 +672,9 @@
   CHECK(login_display_);
   GetOobeUI()->ShowSigninScreen(context, login_display_.get(),
                                 login_display_.get());
+
+  OnStartSignInScreenCommon();
+
   TRACE_EVENT_ASYNC_STEP_INTO0("ui", "ShowLoginWebUI", kShowLoginWebUIid,
                                "WaitForScreenStateInitialize");
 
@@ -1081,7 +1084,10 @@
 void LoginDisplayHostWebUI::ShowGaiaDialog(
     bool can_close,
     const base::Optional<AccountId>& prefilled_account) {
-  NOTREACHED();
+  // This is a special case, when WebUI sign-in screen shown with Views-based
+  // launch bar. Then "Add user" button will be Views-based, and user click
+  // will result in this call.
+  ShowGaiaDialogCommon(prefilled_account);
 }
 
 void LoginDisplayHostWebUI::HideOobeDialog() {
diff --git a/chrome/browser/chromeos/login/ui/webui_login_view.cc b/chrome/browser/chromeos/login/ui/webui_login_view.cc
index 1369359..8392681 100644
--- a/chrome/browser/chromeos/login/ui/webui_login_view.cc
+++ b/chrome/browser/chromeos/login/ui/webui_login_view.cc
@@ -400,7 +400,6 @@
   if (!GetOobeUI())
     return;
   CoreOobeView* view = GetOobeUI()->GetCoreOobeView();
-  view->ShowControlBar(!visible);
   view->SetVirtualKeyboardShown(visible);
 }
 
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index 78f01ea..27646a3 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -2091,7 +2091,6 @@
   ASSERT_FALSE(JSExecuteBooleanExpression("$('error-message').hidden"));
   ASSERT_TRUE(JSExecuteBooleanExpression(
       "$('error-message').classList.contains('ui-state-local-state-error')"));
-  ASSERT_TRUE(JSExecuteBooleanExpression("$('login-header-bar').hidden"));
 
   // Emulates user click on the "Restart and Powerwash" button.
   ASSERT_EQ(0, fake_session_manager_client()->start_device_wipe_call_count());
diff --git a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
index 6ff9284..e3f5ec6c 100644
--- a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
@@ -2601,6 +2601,8 @@
           broker));
 }
 
+// TODO(crbug.com/939700): Fix this test.
+#if 0
 IN_PROC_BROWSER_TEST_F(ManagedSessionsTest, PacHttpsUrlStrippingEnabled) {
   SetManagedSessionsEnabled(/* managed_sessions_enabled */ true);
 
@@ -2633,6 +2635,7 @@
       chromeos::ChromeUserManager::Get()->IsFullManagementDisclosureNeeded(
           broker));
 }
+#endif
 
 class TermsOfServiceDownloadTest : public DeviceLocalAccountTest,
                                    public testing::WithParamInterface<bool> {
diff --git a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
index db454bd..778935e3 100644
--- a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
@@ -408,6 +408,7 @@
   }
 
   ~DeviceStatusCollectorTest() override {
+    chromeos::PowerManagerClient::Shutdown();
     chromeos::LoginState::Shutdown();
     chromeos::CrasAudioHandler::Shutdown();
     chromeos::KioskAppManager::Shutdown();
@@ -2173,13 +2174,10 @@
     : public DeviceStatusCollectorTest {
  protected:
   void SetUp() override {
-    RestartStatusCollector(base::BindRepeating(&GetEmptyVolumeInfo),
-                           base::BindRepeating(&GetEmptyCPUStatistics),
-                           base::BindRepeating(&GetEmptyCPUTempInfo),
-                           base::BindRepeating(&GetEmptyAndroidStatus),
-                           base::BindRepeating(&GetEmptyTpmStatus));
+    DeviceStatusCollectorTest::SetUp();
+    scoped_testing_cros_settings_.device_settings()->SetBoolean(
+        chromeos::kReportDeviceNetworkInterfaces, true);
 
-    chromeos::DBusThreadManager::Initialize();
     chromeos::NetworkHandler::Initialize();
     base::RunLoop().RunUntilIdle();
 
@@ -2288,8 +2286,8 @@
   }
 
   void TearDown() override {
-    status_collector_.reset();
     chromeos::NetworkHandler::Shutdown();
+    DeviceStatusCollectorTest::TearDown();
   }
 
   void VerifyNetworkReporting() {
diff --git a/chrome/browser/chromeos/policy/off_hours/device_off_hours_controller_unittest.cc b/chrome/browser/chromeos/policy/off_hours/device_off_hours_controller_unittest.cc
index 2996280..d764a2f 100644
--- a/chrome/browser/chromeos/policy/off_hours/device_off_hours_controller_unittest.cc
+++ b/chrome/browser/chromeos/policy/off_hours/device_off_hours_controller_unittest.cc
@@ -115,7 +115,10 @@
         device_settings_service_->device_off_hours_controller();
   }
 
-  void TearDown() override { chromeos::SystemClockClient::Shutdown(); }
+  void TearDown() override {
+    chromeos::SystemClockClient::Shutdown();
+    chromeos::DeviceSettingsTestBase::TearDown();
+  }
 
   void UpdateDeviceSettings() {
     device_policy_->Build();
diff --git a/chrome/browser/chromeos/settings/device_settings_test_helper.cc b/chrome/browser/chromeos/settings/device_settings_test_helper.cc
index a12bd017..7e731020 100644
--- a/chrome/browser/chromeos/settings/device_settings_test_helper.cc
+++ b/chrome/browser/chromeos/settings/device_settings_test_helper.cc
@@ -38,7 +38,9 @@
 DeviceSettingsTestBase::DeviceSettingsTestBase()
     : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
 
-DeviceSettingsTestBase::~DeviceSettingsTestBase() = default;
+DeviceSettingsTestBase::~DeviceSettingsTestBase() {
+  CHECK(teardown_called_);
+}
 
 void DeviceSettingsTestBase::SetUp() {
   device_policy_ = std::make_unique<policy::DevicePolicyBuilder>();
@@ -66,6 +68,7 @@
 }
 
 void DeviceSettingsTestBase::TearDown() {
+  teardown_called_ = true;
   OwnerSettingsServiceChromeOSFactory::SetDeviceSettingsServiceForTesting(
       nullptr);
   FlushDeviceSettings();
diff --git a/chrome/browser/chromeos/settings/device_settings_test_helper.h b/chrome/browser/chromeos/settings/device_settings_test_helper.h
index 9f761f4..9025aea5 100644
--- a/chrome/browser/chromeos/settings/device_settings_test_helper.h
+++ b/chrome/browser/chromeos/settings/device_settings_test_helper.h
@@ -85,6 +85,8 @@
   std::unique_ptr<TestingProfile> profile_;
 
  private:
+  bool teardown_called_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(DeviceSettingsTestBase);
 };
 
diff --git a/chrome/browser/chromeos/shutdown_policy_browsertest.cc b/chrome/browser/chromeos/shutdown_policy_browsertest.cc
index a9610fbd..28a6bc74 100644
--- a/chrome/browser/chromeos/shutdown_policy_browsertest.cc
+++ b/chrome/browser/chromeos/shutdown_policy_browsertest.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
 #include "chrome/browser/chromeos/login/lock/screen_locker_tester.h"
+#include "chrome/browser/chromeos/login/test/login_screen_tester.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
 #include "chrome/browser/chromeos/policy/device_policy_builder.h"
@@ -49,26 +50,6 @@
 
 namespace chromeos {
 
-namespace {
-
-const char kWaitForHiddenStateScript[] =
-    "var screenElement = document.getElementById('%s');"
-    "var expectation = %s;"
-    "function SendReplyIfAsExpected() {"
-    "  if (screenElement.hidden != expectation)"
-    "    return false;"
-    "  domAutomationController.send(screenElement.hidden);"
-    "  observer.disconnect();"
-    "  return true;"
-    "}"
-    "var observer = new MutationObserver(SendReplyIfAsExpected);"
-    "if (!SendReplyIfAsExpected()) {"
-    "  var options = { attributes: true };"
-    "  observer.observe(screenElement, options);"
-    "}";
-
-}  // namespace
-
 class ShutdownPolicyBaseTest
     : public policy::DevicePolicyCrosBrowserTest,
       public DeviceSettingsService::Observer {
@@ -97,13 +78,6 @@
     MarkAsEnterpriseOwned();
   }
 
-  // A helper functions which prepares the script by injecting the element_id of
-  // the element whose hiddenness we want to check and the expectation.
-  std::string PrepareScript(const std::string& element_id, bool expectation) {
-    return base::StringPrintf(kWaitForHiddenStateScript, element_id.c_str(),
-                              expectation ? "true" : "false");
-  }
-
   // Updates the device shutdown policy and sets it to |reboot_on_shutdown|.
   void UpdateRebootOnShutdownPolicy(bool reboot_on_shutdown) {
     policy::DevicePolicyBuilder* builder = device_policy();
@@ -240,8 +214,8 @@
 
 IN_PROC_BROWSER_TEST_F(ShutdownPolicyLockerTest, TestBasic) {
   ScreenLockerTester tester;
-  EXPECT_FALSE(tester.IsRestartButtonShown());
-  EXPECT_TRUE(tester.IsShutdownButtonShown());
+  EXPECT_FALSE(tester.IsLockRestartButtonShown());
+  EXPECT_TRUE(tester.IsLockShutdownButtonShown());
 }
 
 IN_PROC_BROWSER_TEST_F(ShutdownPolicyLockerTest, PolicyChange) {
@@ -250,15 +224,15 @@
   UpdateRebootOnShutdownPolicy(true);
   RefreshDevicePolicy();
   tester.WaitForUiUpdate(ui_update_count);
-  EXPECT_TRUE(tester.IsRestartButtonShown());
-  EXPECT_FALSE(tester.IsShutdownButtonShown());
+  EXPECT_TRUE(tester.IsLockRestartButtonShown());
+  EXPECT_FALSE(tester.IsLockShutdownButtonShown());
 
   ui_update_count = tester.GetUiUpdateCount();
   UpdateRebootOnShutdownPolicy(false);
   RefreshDevicePolicy();
   tester.WaitForUiUpdate(ui_update_count);
-  EXPECT_FALSE(tester.IsRestartButtonShown());
-  EXPECT_TRUE(tester.IsShutdownButtonShown());
+  EXPECT_FALSE(tester.IsLockRestartButtonShown());
+  EXPECT_TRUE(tester.IsLockShutdownButtonShown());
 }
 
 class ShutdownPolicyLoginTest : public ShutdownPolicyBaseTest {
@@ -286,8 +260,7 @@
         content::NotificationService::AllSources()).Wait();
     LoginDisplayHost* host = LoginDisplayHost::default_host();
     ASSERT_TRUE(host);
-    contents_ = host->GetOobeWebContents();
-    ASSERT_TRUE(contents_);
+    ASSERT_TRUE(host->GetOobeWebContents());
 
     // Wait for the login UI to be ready.
     WaitUntilOobeUIIsReady(host->GetOobeUI());
@@ -302,34 +275,31 @@
     }
   }
 
-  // Checks whether the element identified by |element_id| is hidden and only
-  // returns if the expectation is fulfilled.
-  void PrepareAndRunScript(const std::string& element_id, bool expectation) {
-    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
-        contents_, PrepareScript(element_id, expectation), &result_));
-  }
-
  private:
-  content::WebContents* contents_ = nullptr;
-
   DISALLOW_COPY_AND_ASSIGN(ShutdownPolicyLoginTest);
 };
 
 IN_PROC_BROWSER_TEST_F(ShutdownPolicyLoginTest, PolicyNotSet) {
-  PrepareAndRunScript("restart-header-bar-item", true);
-  PrepareAndRunScript("shutdown-header-bar-item", false);
+  test::LoginScreenTester tester;
+  EXPECT_FALSE(tester.IsRestartButtonShown());
+  EXPECT_TRUE(tester.IsShutdownButtonShown());
 }
 
 IN_PROC_BROWSER_TEST_F(ShutdownPolicyLoginTest, PolicyChange) {
+  test::LoginScreenTester tester;
+  int ui_update_count = tester.GetUiUpdateCount();
   UpdateRebootOnShutdownPolicy(true);
   RefreshDevicePolicy();
-  PrepareAndRunScript("restart-header-bar-item", false);
-  PrepareAndRunScript("shutdown-header-bar-item", true);
+  tester.WaitForUiUpdate(ui_update_count);
+  EXPECT_TRUE(tester.IsRestartButtonShown());
+  EXPECT_FALSE(tester.IsShutdownButtonShown());
 
+  ui_update_count = tester.GetUiUpdateCount();
   UpdateRebootOnShutdownPolicy(false);
   RefreshDevicePolicy();
-  PrepareAndRunScript("restart-header-bar-item", true);
-  PrepareAndRunScript("shutdown-header-bar-item", false);
+  tester.WaitForUiUpdate(ui_update_count);
+  EXPECT_FALSE(tester.IsRestartButtonShown());
+  EXPECT_TRUE(tester.IsShutdownButtonShown());
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
index 52a4897a..9116786d 100644
--- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
@@ -18,7 +18,6 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/scoped_feature_list.h"
-#include "base/test/scoped_path_override.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/browser/download/chrome_download_manager_delegate.h"
@@ -264,7 +263,7 @@
       DownloadConfirmationResult result,
       const base::FilePath& virtual_path);
 
-  base::FilePath GetDefaultDownloadPath() const;
+  base::FilePath GetDownloadDirectory() const { return test_download_dir_; }
   TestChromeDownloadManagerDelegate* delegate();
   content::MockDownloadManager* download_manager();
   DownloadPrefs* download_prefs();
@@ -274,8 +273,7 @@
   void GetNextId(uint32_t next_id) { download_ids_.emplace_back(next_id); }
 
  private:
-  base::ScopedPathOverride download_dir_override_{
-      chrome::DIR_DEFAULT_DOWNLOADS};
+  base::FilePath test_download_dir_;
   sync_preferences::TestingPrefServiceSyncable* pref_service_;
   std::unique_ptr<content::MockDownloadManager> download_manager_;
   std::unique_ptr<TestChromeDownloadManagerDelegate> delegate_;
@@ -288,13 +286,18 @@
 }
 
 void ChromeDownloadManagerDelegateTest::SetUp() {
-  DownloadPrefs::ReinitializeDefaultDownloadDirectoryForTesting();
   ChromeRenderViewHostTestHarness::SetUp();
 
   CHECK(profile());
+
+  test_download_dir_ = profile()->GetPath().AppendASCII("TestDownloadDir");
+  ASSERT_TRUE(base::CreateDirectory(test_download_dir_));
+
   delegate_ =
       std::make_unique<::testing::NiceMock<TestChromeDownloadManagerDelegate>>(
           profile());
+  download_prefs()->SkipSanitizeDownloadTargetPathForTesting();
+  download_prefs()->SetDownloadPath(test_download_dir_);
   delegate_->SetDownloadManager(download_manager_.get());
   pref_service_ = profile()->GetTestingPrefService();
   web_contents()->SetDelegate(&web_contents_delegate_);
@@ -357,8 +360,7 @@
 
 base::FilePath ChromeDownloadManagerDelegateTest::GetPathInDownloadDir(
     const char* relative_path) {
-  base::FilePath full_path =
-      GetDefaultDownloadPath().AppendASCII(relative_path);
+  base::FilePath full_path = GetDownloadDirectory().AppendASCII(relative_path);
   return full_path.NormalizePathSeparators();
 }
 
@@ -406,13 +408,6 @@
   return result;
 }
 
-base::FilePath ChromeDownloadManagerDelegateTest::GetDefaultDownloadPath()
-    const {
-  base::FilePath path;
-  CHECK(base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &path));
-  return path;
-}
-
 void ChromeDownloadManagerDelegateTest::OnConfirmationCallbackComplete(
     const DownloadTargetDeterminerDelegate::ConfirmationCallback& callback,
     DownloadConfirmationResult result,
@@ -603,9 +598,8 @@
 TEST_F(ChromeDownloadManagerDelegateTest, CheckForFileExistence) {
   const char kData[] = "helloworld";
   const size_t kDataLength = sizeof(kData) - 1;
-  base::FilePath existing_path = GetDefaultDownloadPath().AppendASCII("foo");
-  base::FilePath non_existent_path =
-      GetDefaultDownloadPath().AppendASCII("bar");
+  base::FilePath existing_path = GetDownloadDirectory().AppendASCII("foo");
+  base::FilePath non_existent_path = GetDownloadDirectory().AppendASCII("bar");
   base::WriteFile(existing_path, kData, kDataLength);
 
   std::unique_ptr<download::MockDownloadItem> download_item =
diff --git a/chrome/browser/download/download_core_service_impl.cc b/chrome/browser/download/download_core_service_impl.cc
index 582326f..b857f90e 100644
--- a/chrome/browser/download/download_core_service_impl.cc
+++ b/chrome/browser/download/download_core_service_impl.cc
@@ -25,6 +25,10 @@
 #include "chrome/browser/extensions/api/downloads/downloads_api.h"
 #endif
 
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/download/download_utils.h"
+#endif
+
 using content::BrowserContext;
 using content::DownloadManager;
 using content::DownloadManagerDelegate;
@@ -64,19 +68,11 @@
                      new DownloadHistory::HistoryAdapter(history))));
   }
 
-  download_provider_.reset(new DownloadOfflineContentProvider(
-      OfflineContentAggregatorFactory::GetForBrowserContext(
-          profile_->GetOriginalProfile()),
-      offline_items_collection::OfflineContentAggregator::CreateUniqueNameSpace(
-          OfflineItemUtils::GetDownloadNamespacePrefix(
-              profile_->IsOffTheRecord()),
-          profile_->IsOffTheRecord())));
-
   // Pass an empty delegate when constructing the DownloadUIController. The
   // default delegate does all the notifications we need.
   download_ui_.reset(new DownloadUIController(
       manager, std::unique_ptr<DownloadUIController::Delegate>(),
-      download_provider_.get()));
+      CreateDownloadOfflineContentProvider()));
 
 #if !defined(OS_ANDROID)
   download_shelf_controller_.reset(new DownloadShelfController(profile_));
@@ -90,6 +86,22 @@
   return manager_delegate_.get();
 }
 
+DownloadOfflineContentProvider*
+DownloadCoreServiceImpl::CreateDownloadOfflineContentProvider() {
+#if defined(OS_ANDROID)
+  return DownloadUtils::GetDownloadOfflineContentProvider(profile_);
+#else
+  download_provider_.reset(new DownloadOfflineContentProvider(
+      OfflineContentAggregatorFactory::GetForBrowserContext(
+          profile_->GetOriginalProfile()),
+      offline_items_collection::OfflineContentAggregator::CreateUniqueNameSpace(
+          OfflineItemUtils::GetDownloadNamespacePrefix(
+              profile_->IsOffTheRecord()),
+          profile_->IsOffTheRecord())));
+  return download_provider_.get();
+#endif
+}
+
 DownloadHistory* DownloadCoreServiceImpl::GetDownloadHistory() {
   if (!download_manager_created_) {
     GetDownloadManagerDelegate();
diff --git a/chrome/browser/download/download_core_service_impl.h b/chrome/browser/download/download_core_service_impl.h
index 23ee925..894d2ae 100644
--- a/chrome/browser/download/download_core_service_impl.h
+++ b/chrome/browser/download/download_core_service_impl.h
@@ -55,6 +55,8 @@
   void Shutdown() override;
 
  private:
+  DownloadOfflineContentProvider* CreateDownloadOfflineContentProvider();
+
   bool download_manager_created_;
   Profile* profile_;
 
diff --git a/chrome/browser/download/download_offline_content_provider.cc b/chrome/browser/download/download_offline_content_provider.cc
index 679721e..22dcce7 100644
--- a/chrome/browser/download/download_offline_content_provider.cc
+++ b/chrome/browser/download/download_offline_content_provider.cc
@@ -198,7 +198,6 @@
   if (item->GetState() == DownloadItem::COMPLETE) {
     // TODO(crbug.com/938152): May be move this to DownloadItem.
     AddCompletedDownload(item);
-    return;
   }
 
   UpdateObservers(item);
@@ -219,23 +218,23 @@
 
 void DownloadOfflineContentProvider::AddCompletedDownload(DownloadItem* item) {
 #if defined(OS_ANDROID)
-  if (!item->GetTargetFilePath().IsContentUri()) {
-    DownloadManagerBridge::AddCompletedDownload(
-        item, base::BindOnce(
-                  &DownloadOfflineContentProvider::AddCompletedDownloadDone,
-                  weak_ptr_factory_.GetWeakPtr(), item));
-  } else {
-    AddCompletedDownloadDone(item, -1);
-  }
-#else
-  AddCompletedDownloadDone(item, -1);
+  if (completed_downloads_.find(item->GetGuid()) != completed_downloads_.end())
+    return;
+  completed_downloads_.insert(item->GetGuid());
+
+  DownloadManagerBridge::AddCompletedDownload(
+      item,
+      base::BindOnce(&DownloadOfflineContentProvider::AddCompletedDownloadDone,
+                     weak_ptr_factory_.GetWeakPtr(), item));
 #endif
 }
 
 void DownloadOfflineContentProvider::AddCompletedDownloadDone(
     DownloadItem* item,
-    int64_t system_download_id) {
-  UpdateObservers(item);
+    int64_t system_download_id,
+    bool can_resolve) {
+  if (can_resolve && item->HasUserGesture())
+    item->OpenDownload();
 }
 
 DownloadItem* DownloadOfflineContentProvider::GetDownload(
diff --git a/chrome/browser/download/download_offline_content_provider.h b/chrome/browser/download/download_offline_content_provider.h
index 8a4f9d40..e2acc7d 100644
--- a/chrome/browser/download/download_offline_content_provider.h
+++ b/chrome/browser/download/download_offline_content_provider.h
@@ -72,13 +72,16 @@
                             VisualsCallback callback,
                             const SkBitmap& bitmap);
   void AddCompletedDownload(DownloadItem* item);
-  void AddCompletedDownloadDone(DownloadItem* item, int64_t system_download_id);
+  void AddCompletedDownloadDone(DownloadItem* item,
+                                int64_t system_download_id,
+                                bool can_resolve);
   void UpdateObservers(DownloadItem* item);
 
   base::ObserverList<OfflineContentProvider::Observer>::Unchecked observers_;
   OfflineContentAggregator* aggregator_;
   std::string name_space_;
   DownloadManager* manager_;
+  std::set<std::string> completed_downloads_;
 
   base::WeakPtrFactory<DownloadOfflineContentProvider> weak_ptr_factory_;
 
diff --git a/chrome/browser/download/download_prefs.cc b/chrome/browser/download/download_prefs.cc
index f1c854a..884ccbf 100644
--- a/chrome/browser/download/download_prefs.cc
+++ b/chrome/browser/download/download_prefs.cc
@@ -277,11 +277,6 @@
 }
 
 // static
-void DownloadPrefs::ReinitializeDefaultDownloadDirectoryForTesting() {
-  GetDefaultDownloadDirectorySingleton().Initialize();
-}
-
-// static
 const base::FilePath& DownloadPrefs::GetDefaultDownloadDirectory() {
   return GetDefaultDownloadDirectorySingleton().path();
 }
@@ -433,6 +428,10 @@
   SaveAutoOpenState();
 }
 
+void DownloadPrefs::SkipSanitizeDownloadTargetPathForTesting() {
+  skip_sanitize_download_target_path_for_testing_ = true;
+}
+
 void DownloadPrefs::SaveAutoOpenState() {
   std::string extensions;
   for (auto it = auto_open_.begin(); it != auto_open_.end(); ++it) {
@@ -453,6 +452,9 @@
 base::FilePath DownloadPrefs::SanitizeDownloadTargetPath(
     const base::FilePath& path) const {
 #if defined(OS_CHROMEOS)
+  if (skip_sanitize_download_target_path_for_testing_)
+    return path;
+
   base::FilePath migrated_drive_path;
   // Managed prefs may force a legacy Drive path as the download path. Ensure
   // the path is valid when DriveFS is enabled.
diff --git a/chrome/browser/download/download_prefs.h b/chrome/browser/download/download_prefs.h
index 7e7f0ea..351a9b0 100644
--- a/chrome/browser/download/download_prefs.h
+++ b/chrome/browser/download/download_prefs.h
@@ -46,8 +46,6 @@
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  static void ReinitializeDefaultDownloadDirectoryForTesting();
-
   // Returns the default download directory.
   static const base::FilePath& GetDefaultDownloadDirectory();
 
@@ -116,6 +114,10 @@
 
   void ResetAutoOpen();
 
+  // If this is called, the download target path will not be sanitized going
+  // forward - whatever has been passed to SetDownloadPath will be used.
+  void SkipSanitizeDownloadTargetPathForTesting();
+
  private:
   void SaveAutoOpenState();
 
@@ -152,6 +154,10 @@
   bool should_open_pdf_in_system_reader_;
 #endif
 
+  // If this is true, SanitizeDownloadTargetPath will always return the passed
+  // path verbatim.
+  bool skip_sanitize_download_target_path_for_testing_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(DownloadPrefs);
 };
 
diff --git a/chrome/browser/download/download_target_determiner_unittest.cc b/chrome/browser/download/download_target_determiner_unittest.cc
index 1a616604..b09367b 100644
--- a/chrome/browser/download/download_target_determiner_unittest.cc
+++ b/chrome/browser/download/download_target_determiner_unittest.cc
@@ -8,7 +8,6 @@
 #include <string>
 #include <vector>
 
-#include "base/at_exit.h"
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/location.h"
@@ -19,7 +18,6 @@
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_path_override.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/value_conversions.h"
 #include "build/build_config.h"
@@ -289,11 +287,7 @@
   void VerifyDownloadTarget(const DownloadTestCase& test_case,
                             const DownloadTargetInfo* target_info);
 
-  base::FilePath test_download_dir() const {
-    base::FilePath path;
-    CHECK(base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &path));
-    return path;
-  }
+  base::FilePath test_download_dir() const { return test_download_dir_; }
 
   const base::FilePath& test_virtual_dir() const {
     return test_virtual_dir_;
@@ -310,10 +304,7 @@
  private:
   void SetUpFileTypePolicies();
 
-  // Resets the global cached DefaultDownloadDirectory instance.
-  base::ShadowingAtExitManager at_exit_manager_;
-  base::ScopedPathOverride download_dir_override_{
-      chrome::DIR_DEFAULT_DOWNLOADS};
+  base::FilePath test_download_dir_;
   std::unique_ptr<DownloadPrefs> download_prefs_;
   ::testing::NiceMock<MockDownloadTargetDeterminerDelegate> delegate_;
   NullWebContentsDelegate web_contents_delegate_;
@@ -326,10 +317,14 @@
 void DownloadTargetDeterminerTest::SetUp() {
   ChromeRenderViewHostTestHarness::SetUp();
   CHECK(profile());
-  download_prefs_.reset(new DownloadPrefs(profile()));
+
+  test_download_dir_ = profile()->GetPath().AppendASCII("TestDownloadDir");
+
+  download_prefs_ = std::make_unique<DownloadPrefs>(profile());
+  download_prefs_->SkipSanitizeDownloadTargetPathForTesting();
+  download_prefs_->SetDownloadPath(test_download_dir());
   web_contents()->SetDelegate(&web_contents_delegate_);
   test_virtual_dir_ = test_download_dir().Append(FILE_PATH_LITERAL("virtual"));
-  download_prefs_->SetDownloadPath(test_download_dir());
   delegate_.SetupDefaults();
   SetUpFileTypePolicies();
 #if defined(OS_ANDROID)
@@ -2383,7 +2378,7 @@
     : public DownloadTargetDeterminerTest {
  public:
   DownloadTargetDeterminerTestWithPlugin()
-      : old_plugin_service_filter_(NULL) {}
+      : old_plugin_service_filter_(nullptr) {}
 
   void SetUp() override {
     DownloadTargetDeterminerTest::SetUp();
@@ -2434,8 +2429,8 @@
   {
     ForceRefreshOfPlugins();
     std::vector<content::WebPluginInfo> info;
-    ASSERT_FALSE(plugin_service->GetPluginInfoArray(
-        GURL(), kTestMIMEType, false, &info, NULL));
+    ASSERT_FALSE(plugin_service->GetPluginInfoArray(GURL(), kTestMIMEType,
+                                                    false, &info, nullptr));
     ASSERT_EQ(0u, info.size())
         << "Name: " << info[0].name << ", Path: " << info[0].path.value();
   }
@@ -2503,8 +2498,8 @@
   {
     ForceRefreshOfPlugins();
     std::vector<content::WebPluginInfo> info;
-    ASSERT_FALSE(plugin_service->GetPluginInfoArray(
-        GURL(), kTestMIMEType, false, &info, NULL));
+    ASSERT_FALSE(plugin_service->GetPluginInfoArray(GURL(), kTestMIMEType,
+                                                    false, &info, nullptr));
     ASSERT_EQ(0u, info.size())
         << "Name: " << info[0].name << ", Path: " << info[0].path.value();
   }
diff --git a/chrome/browser/download/offline_item_utils.cc b/chrome/browser/download/offline_item_utils.cc
index d9fce380..3e72fb6 100644
--- a/chrome/browser/download/offline_item_utils.cc
+++ b/chrome/browser/download/offline_item_utils.cc
@@ -60,9 +60,10 @@
 
 OfflineItem OfflineItemUtils::CreateOfflineItem(const std::string& name_space,
                                                 DownloadItem* download_item) {
+  auto* browser_context =
+      content::DownloadItemUtils::GetBrowserContext(download_item);
   bool off_the_record =
-      content::DownloadItemUtils::GetBrowserContext(download_item)
-          ->IsOffTheRecord();
+      browser_context ? browser_context->IsOffTheRecord() : false;
 
   OfflineItem item;
   item.id = ContentId(name_space, download_item->GetGuid());
@@ -80,6 +81,7 @@
   item.is_openable = download_item->CanOpenDownload();
   item.file_path = download_item->GetTargetFilePath();
   item.mime_type = download_item->GetMimeType();
+  // TODO(shaktisahu): Handle any null or generic mime types.
 
   item.page_url = download_item->GetTabUrl();
   item.original_url = download_item->GetOriginalUrl();
diff --git a/chrome/browser/extensions/api/automation/automation_apitest.cc b/chrome/browser/extensions/api/automation/automation_apitest.cc
index 58e27e6..1e3cd80 100644
--- a/chrome/browser/extensions/api/automation/automation_apitest.cc
+++ b/chrome/browser/extensions/api/automation/automation_apitest.cc
@@ -7,6 +7,7 @@
 #include "base/path_service.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
@@ -17,11 +18,14 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/api/automation_internal.h"
 #include "chrome/common/extensions/chrome_extension_messages.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/ax_event_notification_details.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -69,10 +73,18 @@
   }
 
  public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kExperimentalAccessibilityLabels);
+    ExtensionApiTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     ExtensionApiTest::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
   }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TestRendererAccessibilityEnabled) {
@@ -102,6 +114,34 @@
       << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, ImageLabels) {
+  StartEmbeddedTestServer();
+  const GURL url = GetURLForPath(kDomain, "/index.html");
+  ui_test_utils::NavigateToURL(browser(), url);
+
+  // Enable image labels.
+  browser()->profile()->GetPrefs()->SetBoolean(
+      prefs::kAccessibilityImageLabelsEnabled, true);
+
+  // Initially there should be no accessibility mode set.
+  ASSERT_EQ(1, browser()->tab_strip_model()->count());
+  content::WebContents* const web_contents =
+      browser()->tab_strip_model()->GetWebContentsAt(0);
+  ASSERT_EQ(ui::AXMode(), web_contents->GetAccessibilityMode());
+
+  // Enable automation.
+  base::FilePath extension_path =
+      test_data_dir_.AppendASCII("automation/tests/basic");
+  ExtensionTestMessageListener got_tree(kGotTree, false /* no reply */);
+  LoadExtension(extension_path);
+  ASSERT_TRUE(got_tree.WaitUntilSatisfied());
+
+  // Now the AXMode should include kLabelImages.
+  ui::AXMode expected_mode = ui::kAXModeWebContentsOnly;
+  expected_mode.set_mode(ui::AXMode::kLabelImages, true);
+  EXPECT_EQ(expected_mode, web_contents->GetAccessibilityMode());
+}
+
 IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) {
   StartEmbeddedTestServer();
   ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
diff --git a/chrome/browser/extensions/api/image_writer_private/image_writer_private_api.cc b/chrome/browser/extensions/api/image_writer_private/image_writer_private_api.cc
index 1f8be3c..0556e1d3 100644
--- a/chrome/browser/extensions/api/image_writer_private/image_writer_private_api.cc
+++ b/chrome/browser/extensions/api/image_writer_private/image_writer_private_api.cc
@@ -6,9 +6,12 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/task/post_task.h"
 #include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
 #include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "extensions/browser/api/file_handlers/app_file_handler_util.h"
@@ -37,6 +40,13 @@
 }
 
 bool ImageWriterPrivateWriteFromUrlFunction::RunAsync() {
+#if defined(OS_CHROMEOS)
+  if (GetProfile()->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled) ||
+      GetProfile()->GetPrefs()->GetBoolean(prefs::kExternalStorageReadOnly)) {
+    error_ = image_writer::error::kDeviceWriteError;
+    return false;
+  }
+#endif
   std::unique_ptr<image_writer_api::WriteFromUrl::Params> params(
       image_writer_api::WriteFromUrl::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
@@ -69,6 +79,13 @@
 }
 
 bool ImageWriterPrivateWriteFromFileFunction::RunAsync() {
+#if defined(OS_CHROMEOS)
+  if (GetProfile()->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled) ||
+      GetProfile()->GetPrefs()->GetBoolean(prefs::kExternalStorageReadOnly)) {
+    error_ = image_writer::error::kDeviceWriteError;
+    return false;
+  }
+#endif
   std::string filesystem_name;
   std::string filesystem_path;
   std::string storage_unit_id;
@@ -117,6 +134,14 @@
 }
 
 bool ImageWriterPrivateDestroyPartitionsFunction::RunAsync() {
+#if defined(OS_CHROMEOS)
+  if (GetProfile()->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled) ||
+      GetProfile()->GetPrefs()->GetBoolean(prefs::kExternalStorageReadOnly)) {
+    error_ = image_writer::error::kDeviceWriteError;
+    return false;
+  }
+
+#endif
   std::unique_ptr<image_writer_api::DestroyPartitions::Params> params(
       image_writer_api::DestroyPartitions::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
@@ -138,6 +163,13 @@
 }
 
 bool ImageWriterPrivateListRemovableStorageDevicesFunction::RunAsync() {
+#if defined(OS_CHROMEOS)
+  if (GetProfile()->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
+    // Return an empty device list.
+    OnDeviceListReady(base::MakeRefCounted<StorageDeviceList>());
+    return true;
+  }
+#endif
   RemovableStorageProvider::GetAllDevices(base::BindOnce(
       &ImageWriterPrivateListRemovableStorageDevicesFunction::OnDeviceListReady,
       this));
diff --git a/chrome/browser/extensions/api/image_writer_private/image_writer_private_api_unittest.cc b/chrome/browser/extensions/api/image_writer_private/image_writer_private_api_unittest.cc
new file mode 100644
index 0000000..c248936
--- /dev/null
+++ b/chrome/browser/extensions/api/image_writer_private/image_writer_private_api_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/strings/pattern.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
+#include "chrome/browser/extensions/api/image_writer_private/removable_storage_provider.h"
+#include "chrome/browser/extensions/api/image_writer_private/test_utils.h"
+#include "chrome/browser/extensions/extension_api_unittest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/common/extensions/api/image_writer_private.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "extensions/browser/api/file_system/file_system_api.h"
+#include "extensions/browser/api_unittest.h"
+
+namespace extensions {
+#if defined(OS_CHROMEOS)
+using api::image_writer_private::RemovableStorageDevice;
+class ImageWriterPrivateApiUnittest : public ExtensionApiUnittest {
+ public:
+  ImageWriterPrivateApiUnittest() {}
+
+  void SetUp() override {
+    ExtensionApiUnittest::SetUp();
+    auto device_list = base::MakeRefCounted<StorageDeviceList>();
+    RemovableStorageDevice expected;
+    expected.vendor = "Vendor 1";
+    expected.model = "Model 1";
+    expected.capacity = image_writer::kTestFileSize;
+    expected.removable = true;
+    device_list->data.push_back(std::move(expected));
+    RemovableStorageProvider::SetDeviceListForTesting(device_list);
+  }
+
+  void TearDown() override {
+    RemovableStorageProvider::ClearDeviceListForTesting();
+    ExtensionApiUnittest::TearDown();
+  }
+};
+
+TEST_F(ImageWriterPrivateApiUnittest,
+       TestStorageDisabledPolicyReturnsEmptyList) {
+  PrefService* prefs = profile()->GetPrefs();
+  prefs->SetBoolean(prefs::kExternalStorageDisabled, true);
+  auto function = base::MakeRefCounted<
+      ImageWriterPrivateListRemovableStorageDevicesFunction>();
+  std::unique_ptr<base::ListValue> devices =
+      RunFunctionAndReturnList(function.get(), "[]");
+  ASSERT_TRUE(devices.get() ? devices.get()->empty() : false)
+      << "Under policy ListDevices should return an empty list.";
+}
+
+TEST_F(ImageWriterPrivateApiUnittest,
+       TestExternalStorageReadOnlyPolicyFailsWrite) {
+  PrefService* prefs = profile()->GetPrefs();
+  prefs->SetBoolean(prefs::kExternalStorageDisabled, false);
+  prefs->SetBoolean(prefs::kExternalStorageReadOnly, true);
+  auto function =
+      base::MakeRefCounted<ImageWriterPrivateWriteFromFileFunction>();
+  ASSERT_TRUE(
+      base::MatchPattern(RunFunctionAndReturnError(function.get(), "[]"),
+                         image_writer::error::kDeviceWriteError));
+}
+
+#endif  // if defined(OS_CHROMEOS)
+
+}  // namespace extensions
\ No newline at end of file
diff --git a/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc b/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc
index be98e09..cbf553d 100644
--- a/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc
+++ b/chrome/browser/extensions/api/image_writer_private/image_writer_private_apitest.cc
@@ -3,18 +3,23 @@
 // found in the LICENSE file.
 
 #include "base/bind.h"
+#include "base/strings/pattern.h"
 #include "build/build_config.h"
-#include "chrome/browser/extensions/api/image_writer_private/operation.h"
+#include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
 #include "chrome/browser/extensions/api/image_writer_private/removable_storage_provider.h"
 #include "chrome/browser/extensions/api/image_writer_private/test_utils.h"
 #include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
 #include "chrome/common/extensions/api/image_writer_private.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/api/file_system/file_system_api.h"
-
+#include "extensions/browser/api_unittest.h"
 namespace extensions {
 
 using api::image_writer_private::RemovableStorageDevice;
+using extension_function_test_utils::RunFunctionAndReturnError;
 using extensions::image_writer::FakeImageWriterClient;
 
 class ImageWriterPrivateApiTest : public ExtensionApiTest {
@@ -101,5 +106,4 @@
   ASSERT_TRUE(RunPlatformAppTest("image_writer_private/write_from_file"))
       << message_;
 }
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.cc b/chrome/browser/extensions/bookmark_app_extension_util.cc
index dbb6d114..5675c05 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.cc
+++ b/chrome/browser/extensions/bookmark_app_extension_util.cc
@@ -8,11 +8,13 @@
 
 #include "base/callback.h"
 #include "build/build_config.h"
+#include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
+#include "extensions/browser/extension_prefs.h"
 #include "extensions/common/extension.h"
 
 #if defined(OS_MACOSX)
@@ -78,7 +80,16 @@
 #endif  // defined(OS_CHROMEOS)
 }
 
-bool CanBookmarkAppReparentTab(bool shortcut_created) {
+bool CanBookmarkAppReparentTab(Profile* profile,
+                               const Extension* extension,
+                               bool shortcut_created) {
+  // Reparent the web contents into its own window only if that is the
+  // extension's launch type.
+  if (!extension ||
+      extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile),
+                                extension) != extensions::LAUNCH_TYPE_WINDOW) {
+    return false;
+  }
 #if defined(OS_MACOSX)
   // On macOS it is only possible to reparent the window when the shortcut (app
   // shim) was created.  See https://crbug.com/915571.
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.h b/chrome/browser/extensions/bookmark_app_extension_util.h
index f332c08c..fcde5da8 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.h
+++ b/chrome/browser/extensions/bookmark_app_extension_util.h
@@ -26,7 +26,9 @@
 bool CanBookmarkAppBePinnedToShelf();
 void BookmarkAppPinToShelf(const Extension* extension);
 
-bool CanBookmarkAppReparentTab(bool shortcut_created);
+bool CanBookmarkAppReparentTab(Profile* profile,
+                               const Extension* extension,
+                               bool shortcut_created);
 void BookmarkAppReparentTab(content::WebContents* contents,
                             const Extension* extension);
 
diff --git a/chrome/browser/extensions/bookmark_app_helper.cc b/chrome/browser/extensions/bookmark_app_helper.cc
index bdab6320..6db1194 100644
--- a/chrome/browser/extensions/bookmark_app_helper.cc
+++ b/chrome/browser/extensions/bookmark_app_helper.cc
@@ -490,12 +490,14 @@
     BookmarkAppPinToShelf(extension);
 
   // If there is a browser, it means that the app is being installed in the
-  // foreground: window reparenting needed.
+  // foreground.
   const bool reparent_tab =
       (chrome::FindBrowserWithWebContents(contents_) != nullptr);
+
   // TODO(loyso): Reparenting must be implemented in
   // chrome/browser/ui/web_applications/ UI layer as a post-install step.
-  if (reparent_tab && CanBookmarkAppReparentTab(shortcut_created)) {
+  if (reparent_tab &&
+      CanBookmarkAppReparentTab(profile_, extension, shortcut_created)) {
     DCHECK(!profile_->IsOffTheRecord());
     BookmarkAppReparentTab(contents_, extension);
     if (CanBookmarkAppRevealAppShim())
diff --git a/chrome/browser/extensions/chrome_component_extension_resource_manager.cc b/chrome/browser/extensions/chrome_component_extension_resource_manager.cc
index aaae659..e9a5ef1 100644
--- a/chrome/browser/extensions/chrome_component_extension_resource_manager.cc
+++ b/chrome/browser/extensions/chrome_component_extension_resource_manager.cc
@@ -42,6 +42,8 @@
     {"chrome_app/chrome_app_icon_32.png", IDR_CHROME_APP_ICON_32},
     {"chrome_app/chrome_app_icon_192.png", IDR_CHROME_APP_ICON_192},
     {"pdf/ink/ink_lib_binary.js", IDR_INK_LIB_BINARY_JS},
+    {"pdf/ink/pthread-main.js", IDR_INK_PTHREAD_MAIN_JS},
+    {"pdf/ink/glcore_base.js.mem", IDR_INK_GLCORE_BASE_JS_MEM, true},
     {"pdf/ink/glcore_base.wasm", IDR_INK_GLCORE_BASE_WASM, true},
     {"pdf/ink/glcore_wasm_bootstrap_compiled.js",
      IDR_INK_GLCORE_WASM_BOOTSTRAP_COMPILED_JS},
diff --git a/chrome/browser/extensions/chrome_url_request_util.cc b/chrome/browser/extensions/chrome_url_request_util.cc
index 659f17f..51e835b 100644
--- a/chrome/browser/extensions/chrome_url_request_util.cc
+++ b/chrome/browser/extensions/chrome_url_request_util.cc
@@ -239,10 +239,6 @@
   void OnMimeTypeRead(scoped_refptr<base::RefCountedMemory> data,
                       std::string* read_mime_type,
                       bool read_result) {
-    if (!read_result) {
-      client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
-      return;
-    }
     network::ResourceResponseHead head;
     head.request_start = base::TimeTicks::Now();
     head.response_start = base::TimeTicks::Now();
diff --git a/chrome/browser/extensions/service_worker_messaging_apitest.cc b/chrome/browser/extensions/service_worker_messaging_apitest.cc
index a6ce1a08..e7571b75 100644
--- a/chrome/browser/extensions/service_worker_messaging_apitest.cc
+++ b/chrome/browser/extensions/service_worker_messaging_apitest.cc
@@ -105,6 +105,14 @@
   EXPECT_TRUE(reply_listener.WaitUntilSatisfied());
 }
 
+// Tests chrome.tabs.sendMessage from SW extension to content script.
+IN_PROC_BROWSER_TEST_P(ServiceWorkerMessagingTest, WorkerToTab) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  ASSERT_TRUE(
+      RunExtensionTest("service_worker/messaging/send_message_worker_to_tab"))
+      << message_;
+}
+
 INSTANTIATE_TEST_SUITE_P(ServiceWorkerMessagingTestWithNativeBindings,
                          ServiceWorkerMessagingTest,
                          ::testing::Values(NATIVE_BINDINGS));
diff --git a/chrome/browser/feature_engagement/incognito_window/incognito_window_tracker.cc b/chrome/browser/feature_engagement/incognito_window/incognito_window_tracker.cc
index 01a71bf..b35cbe66 100644
--- a/chrome/browser/feature_engagement/incognito_window/incognito_window_tracker.cc
+++ b/chrome/browser/feature_engagement/incognito_window/incognito_window_tracker.cc
@@ -97,8 +97,8 @@
   // Owned by its native widget. Will be destroyed when its widget is destroyed.
   incognito_promo_ = FeaturePromoBubbleView::CreateOwned(
       app_menu_button, views::BubbleBorder::TOP_RIGHT,
-      GetPromoStringSpecifier(),
-      FeaturePromoBubbleView::ActivationAction::ACTIVATE);
+      FeaturePromoBubbleView::ActivationAction::ACTIVATE,
+      GetPromoStringSpecifier());
   views::Widget* widget = incognito_promo_->GetWidget();
   incognito_promo_observer_.Add(widget);
   app_menu_button->SetPromoFeature(InProductHelpFeature::kIncognitoWindow);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 2654ceb..93375091 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -247,11 +247,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "autofill-preview-style",
-    "owners": [ "ftirelo" ],
-    "expiry_milestone": 73
-  },
-  {
     "name": "autofill-profile-client-validation",
     "owners": [ "parastoog" ],
     "expiry_milestone": 77
@@ -378,7 +373,7 @@
   },
   {
     "name": "composited-layer-borders",
-    // "owners": [ "your-team" ],
+    "owners": [ "ccameron" ],
     "expiry_milestone": 76
   },
   {
@@ -651,7 +646,7 @@
   },
   {
     "name": "disallow-unsafe-http-downloads",
-    // "owners": [ "your-team" ],
+    "owners": [ "cthomp" ],
     "expiry_milestone": 76
   },
   {
@@ -838,6 +833,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-autofill-do-not-upload-save-unsupported-cards",
+    "owners": [ "annelim@google.com", "jsaul@google.com" ],
+    "expiry_milestone": 76
+  },
+  {
     "name": "enable-autofill-import-non-focusable-credit-card-forms",
     "owners": [ "hozhng@google.com", "jiahuiguo@google.com" ],
     "expiry_milestone": 76
@@ -953,11 +953,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-chromevox-developer-option",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-client-lo-fi",
     "owners": [ "//components/data_reduction_proxy/OWNERS" ],
     "expiry_milestone": 76
@@ -1178,11 +1173,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-gesture-navigation",
-    "owners": [ "jinsukkim@chromium.org" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-google-branded-context-menu",
     "owners": [ "edwardjung@chromium.org" ],
     "expiry_milestone": 76
@@ -1222,11 +1212,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-home-launcher",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-homepage-tile",
     "owners": [ "dimich", "twellington" ],
     "expiry_milestone": 78
@@ -1408,8 +1393,8 @@
   },
   {
     "name": "enable-notification-indicator",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
+    "owners": [ "newcomer" ],
+    "expiry_milestone": 78
   },
   {
     "name": "enable-ntlm-v2",
@@ -1452,11 +1437,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-nup-printing",
-    "owners": [ "skau" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-offer-store-unmasked-wallet-cards",
     // "owners": [ "your-team" ],
     "expiry_milestone": 76
@@ -1542,11 +1522,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-policy-tool",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-previews-android-omnibox-ui",
     "owners": [ "//components/data_reduction_proxy/OWNERS" ],
     "expiry_milestone": 76
@@ -1638,12 +1613,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-service-worker-servicification",
-    "owners": [ "worker-dev@chromium.org" ],
-    // Enabled by default in M72.
-    "expiry_milestone": 73
-  },
-  {
     "name": "enable-shill-sandboxing",
     // "owners": [ "your-team" ],
     "expiry_milestone": 76
@@ -1665,6 +1634,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-site-isolation-for-password-sites",
+    "owners": [ "site-isolation-dev", "alexmos", "lukasza" ],
+    "expiry_milestone": 77
+  },
+  {
     "name": "enable-site-per-process",
     "owners": [ "site-isolation-dev", "creis", "lukasza" ],
     // Even after shipping some form of Site Isolation on Android, we want to
@@ -1743,11 +1717,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-sync-pseudo-uss-passwords",
-    "owners": [ "mastiz", "//components/sync/OWNERS" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-sync-pseudo-uss-preferences",
     "owners": [ "mastiz", "//components/sync/OWNERS" ],
     "expiry_milestone": 76
@@ -2226,6 +2195,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "link-managed-notice-to-management-page",
+    "owners": [ "chrome-enterprise-team-core@google.com" ],
+    "expiry_milestone": 76
+  },
+  {
     "name": "list-all-display-modes",
     "owners": [ "//ui/display/OWNERS" ],
     // This flag is used for debugging and development purposes to list all
@@ -2234,8 +2208,12 @@
   },
   {
     "name": "load-media-router-component-extension",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
+    "owners": [ "mfoltz", "media-dev" ],
+    // This flag has two purposes: in-team development/Q&A, and allowing
+    // Chromium users to load this extension, which isn't normally distributed
+    // with Chromium. It can be removed once the extension is removed, which has
+    // external dependencies.
+    "expiry_milestone": -1
   },
   {
     "name": "lsd-permission-prompt",
@@ -2296,7 +2274,16 @@
     "expiry_milestone": -1
   },
   {
-    "name": "memlog-sampling",
+    "name": "memlog-in-process",
+    "owners": [ "erikchen", "ssid", "etienneb", "alph" ],
+    // Memlog is Chrome's heap profiler. It is used for both automated and
+    // manual performance analysis. This flag allows a user or developer to
+    // capture a memlog without disturbing the situation under test by
+    // restarting to apply a switch. It should not be removed.
+    "expiry_milestone": -1
+  },
+  {
+    "name": "memlog-sampling-rate",
     "owners": [ "erikchen", "ssid", "etienneb", "alph" ],
     // Memlog is Chrome's heap profiler. It is used for both automated and
     // manual performance analysis. This flag allows a user or developer to
@@ -2384,11 +2371,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "offline-bookmarks",
-    "owners": [ "jianli", "offline-dev" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "offline-indicator-always-http-probe",
     "owners": [ "jianli", "offline-dev" ],
     "expiry_milestone": 76
@@ -2885,13 +2867,8 @@
     "expiry_milestone": 76
   },
   {
-    "name": "slide-top-chrome-with-page-scrolls",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "smart-text-selection",
-    // "owners": [ "your-team" ],
+    "owners": [ "djacobo", "linben" ],
     "expiry_milestone": 76
   },
   {
@@ -2935,11 +2912,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "sync-standalone-transport",
-    "owners": [ "treib", "//components/sync/OWNERS" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "sync-support-secondary-account",
     "owners": [ "treib", "//components/sync/OWNERS" ],
     "expiry_milestone": 76
@@ -3032,7 +3004,7 @@
   },
   {
     "name": "ui-show-composited-layer-borders",
-    // "owners": [ "your-team" ],
+    "owners": [ "ccameron" ],
     "expiry_milestone": 76
   },
   {
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 678e3ed1..d41d125 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -115,7 +115,8 @@
     "Allows autofill to fill dynamically changing forms";
 
 const char kAutofillNoLocalSaveOnUploadSuccessName[] =
-    "Disable locally saving card when credit card upload succeeds";
+    "Disable saving local copy of uploaded card when credit card upload "
+    "succeeds";
 const char kAutofillNoLocalSaveOnUploadSuccessDescription[] =
     "When enabled, no local copy of server card will be saved when credit card "
     "upload succeeds.";
@@ -411,6 +412,12 @@
     "offering card upload to Google Payments, the offer-to-save dialog "
     "displays an expiration date selector.";
 
+const char kEnableAutofillDoNotUploadSaveUnsupportedCardsName[] =
+    "Prevents upload save on cards from unsupported networks";
+const char kEnableAutofillDoNotUploadSaveUnsupportedCardsDescription[] =
+    "If enabled, cards from unsupported networks will not be offered upload "
+    "save, and will instead be offered local save.";
+
 const char kEnableAutofillImportNonFocusableCreditCardFormsName[] =
     "Allow credit card import from forms that disappear after entry";
 const char kEnableAutofillImportNonFocusableCreditCardFormsDescription[] =
@@ -756,10 +763,12 @@
     "with hit-test data computed from the CompositorFrame.";
 
 const char kEnableOutOfProcessHeapProfilingName[] =
-    "Out of process heap profiling start mode.";
+    "Chrome heap profiling start mode.";
 const char kEnableOutOfProcessHeapProfilingDescription[] =
-    "Creates a profiling service that records stacktraces for all live, "
-    "malloced objects. Heap dumps can be obtained at chrome://tracing "
+    "Starts heap profiling service that records sampled memory allocation "
+    "profile having each sample attributed with a callstack. "
+    "The sampling resolution is controlled with --memlog-sampling flag. "
+    "Recorded heap dumps can be obtained at chrome://tracing "
     "[category:memory-infra] and chrome://memory-internals. This setting "
     "controls which processes are profiled. As long as this setting is not "
     "disabled, users can start profiling any given process in "
@@ -776,8 +785,14 @@
     "Profile a random sampling of renderer processes, ensuring only one is "
     "ever profiled at a time.";
 
+const char kOutOfProcessHeapProfilingInProcess[] =
+    "Run the heap profiling service in the browser process.";
+const char kOutOfProcessHeapProfilingInProcessDescription[] =
+    "Makes profiling service (if enabled) to be executed within the browser "
+    "process. By default the service is run in a dedicated utility process.";
+
 const char kOutOfProcessHeapProfilingKeepSmallAllocations[] =
-    "Emit small allocations in memlog heap dumps.";
+    "Emit small allocations in memlog heap dumps (deprecated).";
 const char kOutOfProcessHeapProfilingKeepSmallAllocationsDescription[] =
     "By default, small allocations are pruned from the heap dump. This reduces "
     "the size of the compressed trace by 100x. If pruning is disabled, the "
@@ -787,11 +802,12 @@
     "automatically uploaded traces will always be pruned. This only affects "
     "manually taken memory-infra traces.";
 
-const char kOutOfProcessHeapProfilingSampling[] = "Sample memlog allocations";
-const char kOutOfProcessHeapProfilingSamplingDescription[] =
-    "Use a poisson process to sample allocations. Defaults to a sample rate of "
-    "10000. This results in low noise for large and/or frequent allocations ["
-    "[size * frequency >> 10000]. This means that aggregate numbers [e.g. "
+const char kOutOfProcessHeapProfilingSamplingRate[] =
+    "Sampling interval in bytes for memlog allocations.";
+const char kOutOfProcessHeapProfilingSamplingRateDescription[] =
+    "Use a poisson process to sample allocations. Defaults to a sampling rate "
+    "of 100KB. This results in low noise for large and/or frequent allocations "
+    "[size * frequency >> 100KB]. This means that aggregate numbers [e.g. "
     "total size of malloc-ed objects] and large and/or frequent allocations "
     "can be trusted with high fidelity.";
 
@@ -2632,6 +2648,12 @@
 const char kSiteExplorationUiDescription[] =
     "Show site suggestions in the Exploration UI";
 
+const char kSiteIsolationForPasswordSitesName[] =
+    "Site Isolation For Password Sites";
+const char kSiteIsolationForPasswordSitesDescription[] =
+    "Security mode that enables site isolation for sites based on "
+    "password-oriented heuristics, such as a user typing in a password.";
+
 const char kStrictSiteIsolationName[] = "Strict site isolation";
 const char kStrictSiteIsolationDescription[] =
     "Security mode that enables site isolation for all sites (SitePerProcess). "
@@ -2758,6 +2780,12 @@
 const char kHappinessTrackingSurveysForDesktopDescription[] =
     "Enable showing Happiness Tracking Surveys to users on Desktop";
 
+const char kLinkManagedNoticeToChromeUIManagementURLName[] =
+    "Link managed notice to the management page";
+const char kLinkManagedNoticeToChromeUIManagementURLDescription[] =
+    "Makes the managed notice 'Managed by your organization' a link to "
+    "chrome://management";
+
 const char kOmniboxDriveSuggestionsName[] =
     "Omnibox Google Drive Document suggestions";
 const char kOmniboxDriveSuggestionsDescriptions[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1dff92a13..9128e079 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -278,6 +278,9 @@
 extern const char
     kEnableAutofillCreditCardUploadEditableExpirationDateDescription[];
 
+extern const char kEnableAutofillDoNotUploadSaveUnsupportedCardsName[];
+extern const char kEnableAutofillDoNotUploadSaveUnsupportedCardsDescription[];
+
 extern const char kEnableAutofillImportNonFocusableCreditCardFormsName[];
 extern const char kEnableAutofillImportNonFocusableCreditCardFormsDescription[];
 
@@ -471,8 +474,10 @@
 extern const char kEnableOutOfProcessHeapProfilingModeRendererSampling[];
 extern const char kOutOfProcessHeapProfilingKeepSmallAllocations[];
 extern const char kOutOfProcessHeapProfilingKeepSmallAllocationsDescription[];
-extern const char kOutOfProcessHeapProfilingSampling[];
-extern const char kOutOfProcessHeapProfilingSamplingDescription[];
+extern const char kOutOfProcessHeapProfilingInProcess[];
+extern const char kOutOfProcessHeapProfilingInProcessDescription[];
+extern const char kOutOfProcessHeapProfilingSamplingRate[];
+extern const char kOutOfProcessHeapProfilingSamplingRateDescription[];
 
 extern const char kOOPHPStackModeName[];
 extern const char kOOPHPStackModeDescription[];
@@ -1548,6 +1553,9 @@
 extern const char kSiteExplorationUiName[];
 extern const char kSiteExplorationUiDescription[];
 
+extern const char kSiteIsolationForPasswordSitesName[];
+extern const char kSiteIsolationForPasswordSitesDescription[];
+
 extern const char kSpannableInlineAutocompleteName[];
 extern const char kSpannableInlineAutocompleteDescription[];
 
@@ -1621,6 +1629,9 @@
 extern const char kHappinessTrackingSurveysForDesktopName[];
 extern const char kHappinessTrackingSurveysForDesktopDescription[];
 
+extern const char kLinkManagedNoticeToChromeUIManagementURLName[];
+extern const char kLinkManagedNoticeToChromeUIManagementURLDescription[];
+
 extern const char kOmniboxDriveSuggestionsName[];
 extern const char kOmniboxDriveSuggestionsDescriptions[];
 
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
index 7b6cf39..5b7214b 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
@@ -103,91 +103,26 @@
   return page->GetTypeForTesting();
 }
 
-// Sets the absolute Site Engagement |score| for the testing origin.
-void SetEngagementScore(Browser* browser, const GURL& url, double score) {
-  SiteEngagementService::Get(browser->profile())
-      ->ResetBaseScoreForURL(url, score);
-}
-
-bool IsUrlShowing(Browser* browser) {
-  return !browser->location_bar_model()->GetFormattedFullURL().empty();
-}
-
-// Simulates a link click navigation. We don't use
-// ui_test_utils::NavigateToURL(const GURL&) because it simulates the user
-// typing the URL, causing the site to have a site engagement score of at
-// least LOW.
-void NavigateToURL(Browser* browser, const GURL& url) {
-  NavigateParams params(browser, url, ui::PAGE_TRANSITION_LINK);
-  params.initiator_origin = url::Origin::Create(GURL("about:blank"));
-  params.disposition = WindowOpenDisposition::CURRENT_TAB;
-  params.is_renderer_initiated = true;
-  ui_test_utils::NavigateToURL(&params);
-}
-
-// Same as NavigateToUrl, but wait for the load to complete before returning.
-void NavigateToURLSync(Browser* browser, const GURL& url) {
-  content::TestNavigationObserver navigation_observer(
-      browser->tab_strip_model()->GetActiveWebContents(), 1);
-  NavigateToURL(browser, url);
-  navigation_observer.Wait();
-}
-
-// Load given URL and verify that it loaded an interstitial and hid the URL.
-void LoadAndCheckInterstitialAt(Browser* browser, const GURL& url) {
-  content::WebContents* web_contents =
-      browser->tab_strip_model()->GetActiveWebContents();
-
-  EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
-
-  NavigateToURLSync(browser, url);
-  EXPECT_EQ(LookalikeUrlInterstitialPage::kTypeForTesting,
-            GetInterstitialType(web_contents));
-  EXPECT_FALSE(IsUrlShowing(browser));
-}
-
 void SendInterstitialCommand(content::WebContents* web_contents,
                              SecurityInterstitialCommand command) {
   GetCurrentInterstitial(web_contents)
       ->CommandReceived(base::NumberToString(command));
 }
 
-void SendInterstitialCommandSync(Browser* browser,
-                                 SecurityInterstitialCommand command) {
-  content::WebContents* web_contents =
-      browser->tab_strip_model()->GetActiveWebContents();
-
-  EXPECT_TRUE(GetCurrentInterstitial(web_contents));
-
-  content::TestNavigationObserver navigation_observer(web_contents, 1);
-  SendInterstitialCommand(web_contents, command);
-  navigation_observer.Wait();
-
-  EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
-  EXPECT_TRUE(IsUrlShowing(browser));
-}
-
-// Verify that no interstitial is shown, regardless of feature state.
-void TestInterstitialNotShown(Browser* browser, const GURL& navigated_url) {
-  content::WebContents* web_contents =
-      browser->tab_strip_model()->GetActiveWebContents();
-
-  NavigateToURLSync(browser, navigated_url);
-  EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
-
-  // Navigate to an empty page. This will happen after any
-  // LookalikeUrlService tasks, so will effectively wait for those tasks to
-  // finish.
-  NavigateToURLSync(browser, GURL("about:blank"));
-  EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
-}
-
 }  // namespace
 
 class LookalikeUrlNavigationThrottleBrowserTest
     : public InProcessBrowserTest,
       public testing::WithParamInterface<UIEnabled> {
  protected:
+  // Sets the absolute Site Engagement |score| for the testing origin.
+  static void SetEngagementScore(Browser* browser,
+                                 const GURL& url,
+                                 double score) {
+    SiteEngagementService::Get(browser->profile())
+        ->ResetBaseScoreForURL(url, score);
+  }
+
   void SetUp() override {
     if (ui_enabled()) {
       feature_list_.InitAndEnableFeature(
@@ -250,9 +185,50 @@
         test_ukm_recorder()->GetEntriesByName(UkmEntry::kEntryName).empty());
   }
 
+  void TestInterstitialNotShown(Browser* browser, const GURL& navigated_url) {
+    content::WebContents* web_contents =
+        browser->tab_strip_model()->GetActiveWebContents();
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      NavigateToURL(browser, navigated_url);
+      navigation_observer.Wait();
+      EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+      EXPECT_TRUE(IsUrlShowing(browser));
+    }
+    {
+      // Navigate to an empty page. This will happen after any
+      // LookalikeUrlService tasks, so will effectively wait for those tasks to
+      // finish.
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      NavigateToURL(browser, GURL("about:blank"));
+      navigation_observer.Wait();
+      EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+      EXPECT_TRUE(IsUrlShowing(browser));
+    }
+  }
+
+  // Tests only that the interstitial is shown (when enabled) when the user
+  // tries to visit the provided URL.
+  void TestOnlyInterstitialShown(Browser* browser, const GURL& navigated_url) {
+    if (!ui_enabled()) {
+      return;
+    }
+
+    content::WebContents* web_contents =
+        browser->tab_strip_model()->GetActiveWebContents();
+
+    content::TestNavigationObserver navigation_observer(web_contents, 1);
+    NavigateToURL(browser, navigated_url);
+    navigation_observer.Wait();
+
+    EXPECT_EQ(LookalikeUrlInterstitialPage::kTypeForTesting,
+              GetInterstitialType(web_contents));
+    EXPECT_FALSE(IsUrlShowing(browser));
+  }
+
   // Tests that the histogram event |expected_event| is recorded. If the UI is
-  // enabled, additional events for interstitial display and link click will
-  // also be tested.
+  // enabled, additinal events for interstitial display and link click will also
+  // be tested.
   void TestHistogramEventsRecordedAndInterstitialShown(
       Browser* browser,
       base::HistogramTester* histograms,
@@ -260,46 +236,31 @@
       const GURL& expected_suggested_url,
       LookalikeUrlNavigationThrottle::NavigationSuggestionEvent
           expected_event) {
-    if (!ui_enabled()) {
-      TestInterstitialNotShown(browser, navigated_url);
+    if (ui_enabled()) {
+      // If the feature is enabled, the UI will be displayed.
+      TestInterstitialShown(browser, navigated_url, expected_suggested_url);
       histograms->ExpectTotalCount(
           LookalikeUrlNavigationThrottle::kHistogramName, 1);
       histograms->ExpectBucketCount(
           LookalikeUrlNavigationThrottle::kHistogramName, expected_event, 1);
 
+      histograms->ExpectTotalCount(kInterstitialDecisionMetric, 2);
+      histograms->ExpectBucketCount(kInterstitialDecisionMetric,
+                                    MetricsHelper::SHOW, 1);
+      histograms->ExpectBucketCount(kInterstitialDecisionMetric,
+                                    MetricsHelper::DONT_PROCEED, 1);
+
+      histograms->ExpectTotalCount(kInterstitialInteractionMetric, 1);
+      histograms->ExpectBucketCount(kInterstitialInteractionMetric,
+                                    MetricsHelper::TOTAL_VISITS, 1);
       return;
     }
 
-    history::HistoryService* const history_service =
-        HistoryServiceFactory::GetForProfile(
-            browser->profile(), ServiceAccessType::EXPLICIT_ACCESS);
-    ui_test_utils::WaitForHistoryToLoad(history_service);
-
-    LoadAndCheckInterstitialAt(browser, navigated_url);
-    SendInterstitialCommandSync(browser,
-                                SecurityInterstitialCommand::CMD_DONT_PROCEED);
-    EXPECT_EQ(expected_suggested_url,
-              browser->tab_strip_model()->GetActiveWebContents()->GetURL());
-
-    // Clicking the link in the interstitial should also remove the original
-    // URL from history.
-    ui_test_utils::HistoryEnumerator enumerator(browser->profile());
-    EXPECT_FALSE(base::ContainsValue(enumerator.urls(), navigated_url));
-
+    TestInterstitialNotShown(browser, navigated_url);
     histograms->ExpectTotalCount(LookalikeUrlNavigationThrottle::kHistogramName,
                                  1);
     histograms->ExpectBucketCount(
         LookalikeUrlNavigationThrottle::kHistogramName, expected_event, 1);
-
-    histograms->ExpectTotalCount(kInterstitialDecisionMetric, 2);
-    histograms->ExpectBucketCount(kInterstitialDecisionMetric,
-                                  MetricsHelper::SHOW, 1);
-    histograms->ExpectBucketCount(kInterstitialDecisionMetric,
-                                  MetricsHelper::DONT_PROCEED, 1);
-
-    histograms->ExpectTotalCount(kInterstitialInteractionMetric, 1);
-    histograms->ExpectBucketCount(kInterstitialInteractionMetric,
-                                  MetricsHelper::TOTAL_VISITS, 1);
   }
 
   // Tests that the histogram event |expected_event| is recorded. If the UI is
@@ -311,59 +272,146 @@
       const GURL& navigated_url,
       LookalikeUrlNavigationThrottle::NavigationSuggestionEvent
           expected_event) {
-    if (!ui_enabled()) {
-      TestInterstitialNotShown(browser, navigated_url);
+    if (ui_enabled()) {
+      // If the feature is enabled, the UI will be displayed.
+      DisplayThenIgnoreInterstitial(browser, navigated_url);
       histograms->ExpectTotalCount(
           LookalikeUrlNavigationThrottle::kHistogramName, 1);
       histograms->ExpectBucketCount(
           LookalikeUrlNavigationThrottle::kHistogramName, expected_event, 1);
 
+      histograms->ExpectTotalCount(kInterstitialDecisionMetric, 2);
+      histograms->ExpectBucketCount(kInterstitialDecisionMetric,
+                                    MetricsHelper::SHOW, 1);
+      histograms->ExpectBucketCount(kInterstitialDecisionMetric,
+                                    MetricsHelper::PROCEED, 1);
+
+      histograms->ExpectTotalCount(kInterstitialInteractionMetric, 1);
+      histograms->ExpectBucketCount(kInterstitialInteractionMetric,
+                                    MetricsHelper::TOTAL_VISITS, 1);
+
+      TestInterstitialNotShown(browser, navigated_url);
+
       return;
     }
 
-    history::HistoryService* const history_service =
-        HistoryServiceFactory::GetForProfile(
-            browser->profile(), ServiceAccessType::EXPLICIT_ACCESS);
-    ui_test_utils::WaitForHistoryToLoad(history_service);
-
-    LoadAndCheckInterstitialAt(browser, navigated_url);
-
-    // Clicking the ignore button in the interstitial should remove the
-    // interstitial and navigate to the original URL.
-    SendInterstitialCommandSync(browser,
-                                SecurityInterstitialCommand::CMD_PROCEED);
-    EXPECT_EQ(navigated_url,
-              browser->tab_strip_model()->GetActiveWebContents()->GetURL());
-
-    // Clicking the link should cause the original URL to appear in history.
-    ui_test_utils::HistoryEnumerator enumerator(browser->profile());
-    EXPECT_TRUE(base::ContainsValue(enumerator.urls(), navigated_url));
-
+    TestInterstitialNotShown(browser, navigated_url);
     histograms->ExpectTotalCount(LookalikeUrlNavigationThrottle::kHistogramName,
                                  1);
     histograms->ExpectBucketCount(
         LookalikeUrlNavigationThrottle::kHistogramName, expected_event, 1);
-
-    histograms->ExpectTotalCount(kInterstitialDecisionMetric, 2);
-    histograms->ExpectBucketCount(kInterstitialDecisionMetric,
-                                  MetricsHelper::SHOW, 1);
-    histograms->ExpectBucketCount(kInterstitialDecisionMetric,
-                                  MetricsHelper::PROCEED, 1);
-
-    histograms->ExpectTotalCount(kInterstitialInteractionMetric, 1);
-    histograms->ExpectBucketCount(kInterstitialInteractionMetric,
-                                  MetricsHelper::TOTAL_VISITS, 1);
-
-    TestInterstitialNotShown(browser, navigated_url);
   }
 
   ukm::TestUkmRecorder* test_ukm_recorder() { return test_ukm_recorder_.get(); }
 
   base::SimpleTestClock* test_clock() { return &test_clock_; }
 
+ protected:
   virtual bool ui_enabled() const { return GetParam() == UIEnabled::kEnabled; }
 
- private:
+  static bool IsUrlShowing(Browser* browser) {
+    return !browser->location_bar_model()->GetFormattedFullURL().empty();
+  }
+
+  // Simulates a link click navigation. We don't use
+  // ui_test_utils::NavigateToURL(const GURL&) because it simulates the user
+  // typing the URL, causing the site to have a site engagement score of at
+  // least LOW.
+  static void NavigateToURL(Browser* browser, const GURL& url) {
+    NavigateParams params(browser, url, ui::PAGE_TRANSITION_LINK);
+    params.initiator_origin = url::Origin::Create(GURL("about:blank"));
+    params.disposition = WindowOpenDisposition::CURRENT_TAB;
+    params.is_renderer_initiated = true;
+    ui_test_utils::NavigateToURL(&params);
+  }
+
+  // Checks that navigating to |navigated_url| results in displaying an
+  // interstitial suggesting navigation to |expected_suggestion_url|.
+  // Both |navigated_url| and |expected_suggested_url| can be ASCII or IDN.
+  static void TestInterstitialShown(Browser* browser,
+                                    const GURL& navigated_url,
+                                    const GURL& expected_suggested_url) {
+    history::HistoryService* const history_service =
+        HistoryServiceFactory::GetForProfile(
+            browser->profile(), ServiceAccessType::EXPLICIT_ACCESS);
+    ui_test_utils::WaitForHistoryToLoad(history_service);
+
+    content::WebContents* web_contents =
+        browser->tab_strip_model()->GetActiveWebContents();
+
+    EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+    EXPECT_TRUE(IsUrlShowing(browser));
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      NavigateToURL(browser, navigated_url);
+      navigation_observer.Wait();
+    }
+
+    EXPECT_EQ(LookalikeUrlInterstitialPage::kTypeForTesting,
+              GetInterstitialType(web_contents));
+    EXPECT_FALSE(IsUrlShowing(browser));
+
+    // Clicking the link in the interstitial should remove the interstitial and
+    // navigate to the suggested URL.
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      SendInterstitialCommand(web_contents,
+                              SecurityInterstitialCommand::CMD_DONT_PROCEED);
+      navigation_observer.Wait();
+    }
+
+    EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+    EXPECT_EQ(expected_suggested_url, web_contents->GetURL());
+    EXPECT_TRUE(IsUrlShowing(browser));
+
+    // Clicking the link in the interstitial should also remove the original URL
+    // from history.
+    ui_test_utils::HistoryEnumerator enumerator(browser->profile());
+    EXPECT_FALSE(base::ContainsValue(enumerator.urls(), navigated_url));
+  }
+
+  // Checks that navigating to |navigated_url| results in displaying an
+  // interstitial, that, when ignored, proceeds to |navigated_url|.
+  // |navigated_url| can be ASCII or IDN.
+  static void DisplayThenIgnoreInterstitial(Browser* browser,
+                                            const GURL& navigated_url) {
+    history::HistoryService* const history_service =
+        HistoryServiceFactory::GetForProfile(
+            browser->profile(), ServiceAccessType::EXPLICIT_ACCESS);
+    ui_test_utils::WaitForHistoryToLoad(history_service);
+
+    content::WebContents* web_contents =
+        browser->tab_strip_model()->GetActiveWebContents();
+
+    EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      NavigateToURL(browser, navigated_url);
+      navigation_observer.Wait();
+    }
+
+    EXPECT_EQ(LookalikeUrlInterstitialPage::kTypeForTesting,
+              GetInterstitialType(web_contents));
+    EXPECT_FALSE(IsUrlShowing(browser));
+
+    // Clicking the ignore button in the interstitial should remove the
+    // interstitial and navigate to the original URL.
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      SendInterstitialCommand(web_contents,
+                              SecurityInterstitialCommand::CMD_PROCEED);
+      navigation_observer.Wait();
+    }
+
+    EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+    EXPECT_EQ(navigated_url, web_contents->GetURL());
+    EXPECT_TRUE(IsUrlShowing(browser));
+
+    // Clicking the link should result in the original URL appearing in history.
+    ui_test_utils::HistoryEnumerator enumerator(browser->profile());
+    EXPECT_TRUE(base::ContainsValue(enumerator.urls(), navigated_url));
+  }
+
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
   base::SimpleTestClock test_clock_;
@@ -371,6 +419,64 @@
 
 class LookalikeUrlInterstitialPageBrowserTest
     : public LookalikeUrlNavigationThrottleBrowserTest {
+ public:
+  // Checks that navigating to |navigated_url| displays an interstitial with a
+  // hidden URL, and then navigating to other pages shows/hides the URLs
+  // appropriately both when a URL is expected to be hidden (by navigating to
+  // "chrome://newtab") and when expected to be shown (by navigating to
+  // |subsequent_url|).
+  static void TestInterstitialHidesUrlThenRestores(Browser* browser,
+                                                   const GURL& navigated_url,
+                                                   const GURL& subsequent_url) {
+    content::WebContents* web_contents =
+        browser->tab_strip_model()->GetActiveWebContents();
+
+    EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+    EXPECT_TRUE(IsUrlShowing(browser));
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      NavigateToURL(browser, navigated_url);
+      navigation_observer.Wait();
+    }
+
+    EXPECT_EQ(LookalikeUrlInterstitialPage::kTypeForTesting,
+              GetInterstitialType(web_contents));
+    EXPECT_FALSE(IsUrlShowing(browser));
+
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      ui_test_utils::NavigateToURL(browser, subsequent_url);
+      navigation_observer.Wait();
+    }
+
+    EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+    EXPECT_TRUE(IsUrlShowing(browser));
+
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      ui_test_utils::NavigateToURL(browser, GURL("chrome://newtab"));
+      navigation_observer.Wait();
+    }
+
+    EXPECT_FALSE(IsUrlShowing(browser));
+  }
+
+  // Load the given URL and verify that it loaded an interstitial.
+  void LoadInterstitialAt(const GURL& navigated_url) {
+    content::WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+
+    EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
+    {
+      content::TestNavigationObserver navigation_observer(web_contents, 1);
+      NavigateToURL(browser(), navigated_url);
+      navigation_observer.Wait();
+    }
+
+    EXPECT_EQ(LookalikeUrlInterstitialPage::kTypeForTesting,
+              GetInterstitialType(web_contents));
+  }
+
  protected:
   bool ui_enabled() const override { return true; }
 };
@@ -682,28 +788,23 @@
   CheckUkm({kNavigatedUrl}, "MatchType", MatchType::kEditDistance);
 }
 
-// Navigate to lookalike domains that redirect to benign domains and ensure that
-// we display an interstitial along the way.
-IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
+// Navigate to lookalike domains that redirect to benign domains. Ensure that
+// we display an interstitial along the way if configured via a feature param.
+IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
                        Interstitial_CapturesRedirects) {
   {
     // Verify it works when the lookalike domain is the first in the chain
     const GURL kNavigatedUrl =
         GetLongRedirect("goooglé.com", "example.net", "example.com");
     SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
-    LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
+    TestOnlyInterstitialShown(browser(), kNavigatedUrl);
   }
-
-  // LoadAndCheckInterstitialAt assumes there's not an interstitial already
-  // showing (since otherwise it can't be sure that the navigation caused it).
-  NavigateToURLSync(browser(), GetURL("example.com"));
-
   {
     // ...or when it's later in the chain
     const GURL kNavigatedUrl =
         GetLongRedirect("example.net", "goooglé.com", "example.com");
     SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
-    LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
+    TestOnlyInterstitialShown(browser(), kNavigatedUrl);
   }
 }
 
@@ -716,7 +817,9 @@
     return;
 
   const GURL navigated_url = GetURL("goooglé.com");
+
   TestInterstitialNotShown(browser(), navigated_url);
+
   CheckUkm({navigated_url}, "UserAction", UserAction::kInterstitialNotShown);
 }
 
@@ -727,8 +830,14 @@
   const GURL navigated_url = GetURL("goooglé.com");
   const GURL subsequent_url = GetURL("example.com");
 
-  LoadAndCheckInterstitialAt(browser(), navigated_url);
-  NavigateToURLSync(browser(), subsequent_url);
+  LoadInterstitialAt(navigated_url);
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  content::TestNavigationObserver navigation_observer(web_contents, 1);
+  ui_test_utils::NavigateToURL(browser(), subsequent_url);
+  navigation_observer.Wait();
+
   CheckUkm({navigated_url}, "UserAction", UserAction::kCloseOrBack);
 }
 
@@ -738,9 +847,15 @@
                        UkmRecordedAfterSuggestionAccepted) {
   const GURL navigated_url = GetURL("goooglé.com");
 
-  LoadAndCheckInterstitialAt(browser(), navigated_url);
-  SendInterstitialCommandSync(browser(),
-                              SecurityInterstitialCommand::CMD_DONT_PROCEED);
+  LoadInterstitialAt(navigated_url);
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  content::TestNavigationObserver navigation_observer(web_contents, 1);
+  SendInterstitialCommand(web_contents,
+                          SecurityInterstitialCommand::CMD_DONT_PROCEED);
+  navigation_observer.Wait();
+
   CheckUkm({navigated_url}, "UserAction", UserAction::kAcceptSuggestion);
 }
 
@@ -750,58 +865,23 @@
                        UkmRecordedAfterSuggestionIgnored) {
   const GURL navigated_url = GetURL("goooglé.com");
 
-  LoadAndCheckInterstitialAt(browser(), navigated_url);
-  SendInterstitialCommandSync(browser(),
-                              SecurityInterstitialCommand::CMD_PROCEED);
+  LoadInterstitialAt(navigated_url);
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  content::TestNavigationObserver navigation_observer(web_contents, 1);
+  SendInterstitialCommand(web_contents,
+                          SecurityInterstitialCommand::CMD_PROCEED);
+  navigation_observer.Wait();
+
   CheckUkm({navigated_url}, "UserAction", UserAction::kClickThrough);
 }
 
 // Verify that the URL shows normally on pages after a lookalike interstitial.
 IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
                        UrlShownAfterInterstitial) {
-  LoadAndCheckInterstitialAt(browser(), GetURL("goooglé.com"));
-
-  // URL should be showing again when we navigate to a normal URL
-  NavigateToURLSync(browser(), GetURL("example.com"));
-  EXPECT_TRUE(IsUrlShowing(browser()));
-
-  // URL should still get hidden when we navigate to a page with a hidden URL.
-  NavigateToURLSync(browser(), GURL("chrome://newtab"));
-  EXPECT_FALSE(IsUrlShowing(browser()));
-}
-
-// Verify that bypassing warnings in the main profile does not affect incognito.
-IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
-                       MainProfileDoesNotAffectIncognito) {
-  const GURL kNavigatedUrl = GetURL("googlé.com");
-
-  // Set low engagement scores in the main profile and in incognito.
-  Browser* incognito = CreateIncognitoBrowser();
-  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
-  SetEngagementScore(incognito, kNavigatedUrl, kLowEngagement);
-
-  LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
-  // PROCEEDing will disable the interstitial on subsequent navigations
-  SendInterstitialCommandSync(browser(),
-                              SecurityInterstitialCommand::CMD_PROCEED);
-
-  LoadAndCheckInterstitialAt(incognito, kNavigatedUrl);
-}
-
-// Verify that bypassing warnings in incognito does not affect the main profile.
-IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
-                       IncognitoDoesNotAffectMainProfile) {
-  const GURL kNavigatedUrl = GetURL("googlé.com");
-
-  // Set low engagement scores in the main profile and in incognito.
-  Browser* incognito = CreateIncognitoBrowser();
-  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
-  SetEngagementScore(incognito, kNavigatedUrl, kLowEngagement);
-
-  LoadAndCheckInterstitialAt(incognito, kNavigatedUrl);
-  // PROCEEDing will disable the interstitial on subsequent navigations
-  SendInterstitialCommandSync(incognito,
-                              SecurityInterstitialCommand::CMD_PROCEED);
-
-  LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
+  const GURL kNavigatedUrl = GetURL("goooglé.com");
+  const GURL kSubsequentUrl = GetURL("example.com");
+  TestInterstitialHidesUrlThenRestores(browser(), kNavigatedUrl,
+                                       kSubsequentUrl);
 }
diff --git a/chrome/browser/media/media_engagement_score.cc b/chrome/browser/media/media_engagement_score.cc
index bdda9b1..9a32200 100644
--- a/chrome/browser/media/media_engagement_score.cc
+++ b/chrome/browser/media/media_engagement_score.cc
@@ -57,6 +57,13 @@
   return std::make_unique<base::DictionaryValue>();
 }
 
+void GetIntegerFromScore(base::DictionaryValue* dict,
+                         base::StringPiece key,
+                         int* out) {
+  if (base::Value* v = dict->FindKeyOfType(key, base::Value::Type::INTEGER))
+    *out = v->GetInt();
+}
+
 }  // namespace
 
 // static.
@@ -101,21 +108,31 @@
   if (!score_dict_)
     return;
 
-  score_dict_->GetInteger(kVisitsKey, &visits_);
-  score_dict_->GetInteger(kMediaPlaybacksKey, &media_playbacks_);
-  score_dict_->GetBoolean(kHasHighScoreKey, &is_high_);
-  score_dict_->GetInteger(kAudiblePlaybacksKey, &audible_playbacks_);
-  score_dict_->GetInteger(kSignificantPlaybacksKey, &significant_playbacks_);
-  score_dict_->GetInteger(kVisitsWithMediaTagKey, &visits_with_media_tag_);
-  score_dict_->GetInteger(kHighScoreChanges, &high_score_changes_);
-  score_dict_->GetInteger(kSignificantMediaPlaybacksKey,
-                          &media_element_playbacks_);
-  score_dict_->GetInteger(kSignificantAudioContextPlaybacksKey,
-                          &audio_context_playbacks_);
+  GetIntegerFromScore(score_dict_.get(), kVisitsKey, &visits_);
+  GetIntegerFromScore(score_dict_.get(), kMediaPlaybacksKey, &media_playbacks_);
+  GetIntegerFromScore(score_dict_.get(), kAudiblePlaybacksKey,
+                      &audible_playbacks_);
+  GetIntegerFromScore(score_dict_.get(), kSignificantPlaybacksKey,
+                      &significant_playbacks_);
+  GetIntegerFromScore(score_dict_.get(), kVisitsWithMediaTagKey,
+                      &visits_with_media_tag_);
+  GetIntegerFromScore(score_dict_.get(), kHighScoreChanges,
+                      &high_score_changes_);
+  GetIntegerFromScore(score_dict_.get(), kSignificantMediaPlaybacksKey,
+                      &media_element_playbacks_);
+  GetIntegerFromScore(score_dict_.get(), kSignificantAudioContextPlaybacksKey,
+                      &audio_context_playbacks_);
 
-  double internal_time;
-  if (score_dict_->GetDouble(kLastMediaPlaybackTimeKey, &internal_time))
-    last_media_playback_time_ = base::Time::FromInternalValue(internal_time);
+  if (base::Value* value = score_dict_->FindKeyOfType(
+          kHasHighScoreKey, base::Value::Type::BOOLEAN)) {
+    is_high_ = value->GetBool();
+  }
+
+  if (base::Value* value = score_dict_->FindKeyOfType(
+          kLastMediaPlaybackTimeKey, base::Value::Type::DOUBLE)) {
+    last_media_playback_time_ =
+        base::Time::FromInternalValue(value->GetDouble());
+  }
 
   // Recalculate the total score and high bit. If the high bit changed we
   // should commit this. This should only happen if we change the threshold
@@ -184,21 +201,34 @@
   int stored_media_element_playbacks = 0;
   int stored_audio_context_playbacks = 0;
 
-  score_dict_->GetInteger(kVisitsKey, &stored_visits);
-  score_dict_->GetInteger(kMediaPlaybacksKey, &stored_media_playbacks);
-  score_dict_->GetDouble(kLastMediaPlaybackTimeKey,
-                         &stored_last_media_playback_internal);
-  score_dict_->GetBoolean(kHasHighScoreKey, &is_high);
-  score_dict_->GetInteger(kAudiblePlaybacksKey, &stored_audible_playbacks);
-  score_dict_->GetInteger(kSignificantPlaybacksKey,
-                          &stored_significant_playbacks);
-  score_dict_->GetInteger(kVisitsWithMediaTagKey,
-                          &stored_visits_with_media_tag);
-  score_dict_->GetInteger(kHighScoreChanges, &high_score_changes);
-  score_dict_->GetInteger(kSignificantMediaPlaybacksKey,
-                          &stored_media_element_playbacks);
-  score_dict_->GetInteger(kSignificantAudioContextPlaybacksKey,
-                          &stored_audio_context_playbacks);
+  if (!score_dict_)
+    return false;
+
+  if (base::Value* value = score_dict_->FindKeyOfType(
+          kHasHighScoreKey, base::Value::Type::BOOLEAN)) {
+    is_high = value->GetBool();
+  }
+
+  if (base::Value* value = score_dict_->FindKeyOfType(
+          kLastMediaPlaybackTimeKey, base::Value::Type::DOUBLE)) {
+    stored_last_media_playback_internal = value->GetDouble();
+  }
+
+  GetIntegerFromScore(score_dict_.get(), kVisitsKey, &stored_visits);
+  GetIntegerFromScore(score_dict_.get(), kMediaPlaybacksKey,
+                      &stored_media_playbacks);
+  GetIntegerFromScore(score_dict_.get(), kAudiblePlaybacksKey,
+                      &stored_audible_playbacks);
+  GetIntegerFromScore(score_dict_.get(), kSignificantPlaybacksKey,
+                      &stored_significant_playbacks);
+  GetIntegerFromScore(score_dict_.get(), kVisitsWithMediaTagKey,
+                      &stored_visits_with_media_tag);
+  GetIntegerFromScore(score_dict_.get(), kHighScoreChanges,
+                      &high_score_changes);
+  GetIntegerFromScore(score_dict_.get(), kSignificantMediaPlaybacksKey,
+                      &stored_media_element_playbacks);
+  GetIntegerFromScore(score_dict_.get(), kSignificantAudioContextPlaybacksKey,
+                      &stored_audio_context_playbacks);
 
   bool changed = stored_visits != visits() ||
                  stored_media_playbacks != media_playbacks() ||
diff --git a/chrome/browser/offline_pages/android/offline_page_model_factory.cc b/chrome/browser/offline_pages/android/offline_page_model_factory.cc
index 3096336..5a43cba 100644
--- a/chrome/browser/offline_pages/android/offline_page_model_factory.cc
+++ b/chrome/browser/offline_pages/android/offline_page_model_factory.cc
@@ -65,7 +65,7 @@
   std::unique_ptr<ArchiveManager> archive_manager(new DownloadArchiveManager(
       temporary_archives_dir, persistent_archives_dir,
       DownloadPrefs::GetDefaultDownloadDirectory(), background_task_runner,
-      profile));
+      profile->GetPrefs()));
   auto clock = std::make_unique<base::DefaultClock>();
 
   std::unique_ptr<SystemDownloadManager> download_manager(
diff --git a/chrome/browser/offline_pages/download_archive_manager.cc b/chrome/browser/offline_pages/download_archive_manager.cc
index ea0e22e2..5e939c2d9 100644
--- a/chrome/browser/offline_pages/download_archive_manager.cc
+++ b/chrome/browser/offline_pages/download_archive_manager.cc
@@ -5,7 +5,6 @@
 #include <string>
 
 #include "chrome/browser/offline_pages/download_archive_manager.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 
@@ -16,20 +15,20 @@
     const base::FilePath& private_archives_dir,
     const base::FilePath& public_archives_dir,
     const scoped_refptr<base::SequencedTaskRunner>& task_runner,
-    Profile* profile)
+    PrefService* prefs)
     : ArchiveManager(temporary_archives_dir,
                      private_archives_dir,
                      public_archives_dir,
                      task_runner),
-      profile_(profile) {}
+      prefs_(prefs) {}
 
 DownloadArchiveManager::~DownloadArchiveManager() {}
 
 const base::FilePath& DownloadArchiveManager::GetPublicArchivesDir() {
-  if (profile_) {
+  if (prefs_) {
     // Use the preference set by the download location dialog, if present.
     std::string directory_preference =
-        profile_->GetPrefs()->GetString(prefs::kDownloadDefaultDirectory);
+        prefs_->GetString(prefs::kDownloadDefaultDirectory);
     if (!directory_preference.empty()) {
       download_archives_dir_ = base::FilePath(directory_preference);
       // Must set the member variable so the reference will outlive the
diff --git a/chrome/browser/offline_pages/download_archive_manager.h b/chrome/browser/offline_pages/download_archive_manager.h
index 868d493..92fe23f 100644
--- a/chrome/browser/offline_pages/download_archive_manager.h
+++ b/chrome/browser/offline_pages/download_archive_manager.h
@@ -10,7 +10,7 @@
 #include "base/memory/ref_counted.h"
 #include "components/offline_pages/core/archive_manager.h"
 
-class Profile;
+class PrefService;
 
 namespace base {
 class SequencedTaskRunner;
@@ -27,13 +27,13 @@
       const base::FilePath& private_archives_dir,
       const base::FilePath& public_archives_dir,
       const scoped_refptr<base::SequencedTaskRunner>& task_runner,
-      Profile* profile);
+      PrefService* prefs);
   ~DownloadArchiveManager() override;
 
   const base::FilePath& GetPublicArchivesDir() override;
 
  private:
-  Profile* profile_;
+  PrefService* prefs_;
   base::FilePath download_archives_dir_;
 
   DISALLOW_COPY_AND_ASSIGN(DownloadArchiveManager);
diff --git a/chrome/browser/offline_pages/download_archive_manager_unittest.cc b/chrome/browser/offline_pages/download_archive_manager_unittest.cc
index 6b7ca69..8528bd4 100644
--- a/chrome/browser/offline_pages/download_archive_manager_unittest.cc
+++ b/chrome/browser/offline_pages/download_archive_manager_unittest.cc
@@ -5,9 +5,10 @@
 #include "chrome/browser/offline_pages/download_archive_manager.h"
 
 #include "base/macros.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/download/download_prefs.h"
 #include "chrome/common/pref_names.h"
-#include "chrome/test/base/testing_profile.h"
-#include "components/prefs/pref_service.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -30,26 +31,26 @@
   void TearDown() override;
   void PumpLoop();
 
-  TestingProfile* profile() { return &profile_; }
+  sync_preferences::TestingPrefServiceSyncable* prefs() { return &prefs_; }
   DownloadArchiveManager* archive_manager() { return archive_manager_.get(); }
 
  private:
   content::TestBrowserThreadBundle browser_thread_bundle_;
-  TestingProfile profile_;
+  sync_preferences::TestingPrefServiceSyncable prefs_;
   std::unique_ptr<DownloadArchiveManager> archive_manager_;
   DISALLOW_COPY_AND_ASSIGN(DownloadArchiveManagerTest);
 };
 
 void DownloadArchiveManagerTest::SetUp() {
   // Set up preferences to point to kChromePublicSdCardDir.
-  profile()->GetPrefs()->SetString(prefs::kDownloadDefaultDirectory,
-                                   kChromePublicSdCardDir);
+  DownloadPrefs::RegisterProfilePrefs(prefs()->registry());
+  prefs()->SetString(prefs::kDownloadDefaultDirectory, kChromePublicSdCardDir);
 
   // Create a DownloadArchiveManager to use.
   archive_manager_.reset(new DownloadArchiveManager(
       base::FilePath(kTemporaryDir), base::FilePath(kPrivateDir),
       base::FilePath(kPublicDir), base::ThreadTaskRunnerHandle::Get(),
-      profile()));
+      prefs()));
 }
 
 void DownloadArchiveManagerTest::TearDown() {
@@ -61,7 +62,7 @@
   ASSERT_EQ(kChromePublicSdCardDir, download_dir.AsUTF8Unsafe());
 }
 
-TEST_F(DownloadArchiveManagerTest, NullProfile) {
+TEST_F(DownloadArchiveManagerTest, NullPrefs) {
   DownloadArchiveManager download_archive_manager(
       base::FilePath(kTemporaryDir), base::FilePath(kPrivateDir),
       base::FilePath(kPublicDir), base::ThreadTaskRunnerHandle::Get(), nullptr);
diff --git a/chrome/browser/offline_pages/offline_page_request_handler.cc b/chrome/browser/offline_pages/offline_page_request_handler.cc
index 4bb0f68..4daa8e8 100644
--- a/chrome/browser/offline_pages/offline_page_request_handler.cc
+++ b/chrome/browser/offline_pages/offline_page_request_handler.cc
@@ -618,7 +618,7 @@
   // after intermediate redirects for authentication. Previously this case was
   // not handled and some pages might be saved with same URLs. Though we fixed
   // the problem, we still need to support those pages already saved with this
-  if (url_ == GetCurrentOfflinePage().original_url &&
+  if (url_ == GetCurrentOfflinePage().original_url_if_different &&
       url_ != GetCurrentOfflinePage().url) {
     ReportRequestResult(RequestResult::REDIRECTED, network_state_);
     Redirect(GetCurrentOfflinePage().url);
diff --git a/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc b/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
index 6f912794..77fb6dc 100644
--- a/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
+++ b/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
@@ -1835,7 +1835,7 @@
 
   // Check if the original URL is still present.
   OfflinePageItem page = this->GetPage(offline_id);
-  EXPECT_EQ(kUrl, page.original_url);
+  EXPECT_EQ(kUrl, page.original_url_if_different);
 
   // No redirect should be triggered when original URL is same as final URL.
   this->LoadPage(kUrl);
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
index c50a94fd..a8d3df3 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
@@ -204,7 +204,8 @@
 // ad, even if it navigates to a non-ad page. This function labels all of a
 // page's frames, even those that fail to commit.
 void AdsPageLoadMetricsObserver::OnDidFinishSubFrameNavigation(
-    content::NavigationHandle* navigation_handle) {
+    content::NavigationHandle* navigation_handle,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
   FrameTreeNodeId frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
   bool is_adframe = DetectAds(navigation_handle);
 
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h
index c427898..ff5f1ce 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h
@@ -58,7 +58,8 @@
   void ReadyToCommitNextNavigation(
       content::NavigationHandle* navigation_handle) override;
   void OnDidFinishSubFrameNavigation(
-      content::NavigationHandle* navigation_handle) override;
+      content::NavigationHandle* navigation_handle,
+      const page_load_metrics::PageLoadExtraInfo& extra_info) override;
   void OnDidInternalNavigationAbort(
       content::NavigationHandle* navigation_handle) override;
   ObservePolicy FlushMetricsOnAppEnterBackground(
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
index a276f66d..a1b70bad 100644
--- a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
@@ -170,7 +170,8 @@
 }
 
 void AMPPageLoadMetricsObserver::OnDidFinishSubFrameNavigation(
-    content::NavigationHandle* navigation_handle) {
+    content::NavigationHandle* navigation_handle,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
   if (!navigation_handle->HasCommitted())
     return;
 
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
index 30d28cf8..35b984e 100644
--- a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
@@ -68,7 +68,8 @@
   void OnCommitSameDocumentNavigation(
       content::NavigationHandle* navigation_handle) override;
   void OnDidFinishSubFrameNavigation(
-      content::NavigationHandle* navigation_handle) override;
+      content::NavigationHandle* navigation_handle,
+      const page_load_metrics::PageLoadExtraInfo&) override;
   void OnFrameDeleted(content::RenderFrameHost* rfh) override;
   void OnDomContentLoadedEventStart(
       const page_load_metrics::mojom::PageLoadTiming& timing,
diff --git a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
index e044b9d..2e95332 100644
--- a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
@@ -11,6 +11,7 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
 #include "content/public/common/process_type.h"
 #include "net/http/http_response_headers.h"
@@ -96,6 +97,12 @@
     "PageLoad.Experimental.PaintTiming.NavigationToLargestContentPaint";
 const char kHistogramLargestContentPaintContentType[] =
     "PageLoad.Experimental.PaintTiming.LargestContentPaint.ContentType";
+const char kHistogramLargestContentPaintAllFrames[] =
+    "PageLoad.Experimental.PaintTiming.NavigationToLargestContentPaint."
+    "AllFrames";
+const char kHistogramLargestContentPaintAllFramesContentType[] =
+    "PageLoad.Experimental.PaintTiming.LargestContentPaint.AllFrames."
+    "ContentType";
 const char kHistogramTimeToInteractive[] =
     "PageLoad.Experimental.NavigationToInteractive";
 const char kHistogramInteractiveToInteractiveDetection[] =
@@ -269,7 +276,8 @@
       cache_bytes_(0),
       network_bytes_(0),
       network_bytes_including_headers_(0),
-      redirect_chain_size_(0) {}
+      redirect_chain_size_(0),
+      largest_contentful_paint_handler_() {}
 
 CorePageLoadMetricsObserver::~CorePageLoadMetricsObserver() {}
 
@@ -785,6 +793,17 @@
         largest_content_type);
   }
 
+  const page_load_metrics::TimingInfo& paint =
+      largest_contentful_paint_handler_.MergeMainFrameAndSubframes();
+  if (!paint.IsEmpty() &&
+      WasStartedInForegroundOptionalEventInForeground(paint.Time(), info)) {
+    PAGE_LOAD_HISTOGRAM(internal::kHistogramLargestContentPaintAllFrames,
+                        paint.Time().value());
+    UMA_HISTOGRAM_ENUMERATION(
+        internal::kHistogramLargestContentPaintAllFramesContentType,
+        paint.Type());
+  }
+
   if (timing.paint_timing->first_paint &&
       !timing.paint_timing->first_meaningful_paint) {
     RecordFirstMeaningfulPaintStatus(
@@ -909,3 +928,18 @@
   PAGE_RESOURCE_COUNT_HISTOGRAM(internal::kHistogramTotalCompletedResources,
                                 num_cache_resources_ + num_network_resources_);
 }
+
+void CorePageLoadMetricsObserver::OnTimingUpdate(
+    content::RenderFrameHost* subframe_rfh,
+    const page_load_metrics::mojom::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
+  largest_contentful_paint_handler_.RecordTiming(timing.paint_timing,
+                                                 subframe_rfh);
+}
+
+void CorePageLoadMetricsObserver::OnDidFinishSubFrameNavigation(
+    content::NavigationHandle* navigation_handle,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
+  largest_contentful_paint_handler_.OnDidFinishSubFrameNavigation(
+      navigation_handle, extra_info);
+}
diff --git a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.h
index d490998c..bdb6a4b 100644
--- a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_CORE_PAGE_LOAD_METRICS_OBSERVER_H_
 #define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_CORE_PAGE_LOAD_METRICS_OBSERVER_H_
 
+#include "chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "services/metrics/public/cpp/ukm_source.h"
 
@@ -31,6 +32,8 @@
 extern const char kHistogramLastTextPaint[];
 extern const char kHistogramLargestContentPaint[];
 extern const char kHistogramLargestContentPaintContentType[];
+extern const char kHistogramLargestContentPaintAllFrames[];
+extern const char kHistogramLargestContentPaintAllFramesContentType[];
 extern const char kHistogramTimeToInteractive[];
 extern const char kHistogramParseDuration[];
 extern const char kHistogramParseBlockedOnScriptLoad[];
@@ -220,6 +223,15 @@
       const std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr>&
           resources) override;
 
+  void OnTimingUpdate(
+      content::RenderFrameHost* subframe_rfh,
+      const page_load_metrics::mojom::PageLoadTiming& timing,
+      const page_load_metrics::PageLoadExtraInfo& extra_info) override;
+
+  void OnDidFinishSubFrameNavigation(
+      content::NavigationHandle* navigation_handle,
+      const page_load_metrics::PageLoadExtraInfo& extra_info) override;
+
  private:
   void RecordTimingHistograms(
       const page_load_metrics::mojom::PageLoadTiming& timing,
@@ -260,6 +272,9 @@
 
   base::TimeTicks first_paint_;
 
+  page_load_metrics::LargestContentfulPaintHandler
+      largest_contentful_paint_handler_;
+
   DISALLOW_COPY_AND_ASSIGN(CorePageLoadMetricsObserver);
 };
 
diff --git a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc
index 9ddf11b..0440fc2 100644
--- a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc
@@ -40,6 +40,7 @@
 
   void SetUp() override {
     page_load_metrics::PageLoadMetricsObserverTestHarness::SetUp();
+    page_load_metrics::LargestContentfulPaintHandler::SetTestMode(true);
   }
 };
 
@@ -614,8 +615,8 @@
   SimulateTimingUpdate(timing);
   NavigateAndCommit(GURL(kDefaultTestUrl2));
 
-  histogram_tester().ExpectTotalCount(
-      internal::kHistogramFirstMeaningfulPaint, 1);
+  histogram_tester().ExpectTotalCount(internal::kHistogramFirstMeaningfulPaint,
+                                      1);
   histogram_tester().ExpectTotalCount(
       internal::kHistogramParseStartToFirstMeaningfulPaint, 1);
   histogram_tester().ExpectBucketCount(
@@ -630,6 +631,7 @@
   // Pick a value that lines up with a histogram bucket.
   timing.paint_timing->largest_image_paint =
       base::TimeDelta::FromMilliseconds(4780);
+  timing.paint_timing->largest_image_paint_size = 10u;
   PopulateRequiredTimingFields(&timing);
 
   NavigateAndCommit(GURL(kDefaultTestUrl));
@@ -642,25 +644,23 @@
       testing::ElementsAre(base::Bucket(4780, 1)));
 }
 
-TEST_F(CorePageLoadMetricsObserverTest, LargestImagePaintIgnoreSubFrame) {
+TEST_F(CorePageLoadMetricsObserverTest,
+       LargestContentfulPaintAllFrames_OnlySubframeProvided) {
   const char kSubframeTestUrl[] = "https://google.com/subframe.html";
 
-  // Create a main frame timing with a largest_image_paint that happens late.
   page_load_metrics::mojom::PageLoadTiming timing;
   page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  // Pick a value that lines up with a histogram bucket.
-  timing.paint_timing->largest_image_paint =
-      base::TimeDelta::FromMilliseconds(4780);
+  timing.navigation_start = base::Time::FromDoubleT(100);
+  // Intentionally not set candidates for the main frame.
   PopulateRequiredTimingFields(&timing);
 
-  // Create a subframe timing with a largest_image_paint that happens before the
-  // largest_image_paint in the main frame timing.
+  // Create a subframe timing with a largest_image_paint.
   page_load_metrics::mojom::PageLoadTiming subframe_timing;
   page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
-  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(200);
   subframe_timing.paint_timing->largest_image_paint =
-      base::TimeDelta::FromMilliseconds(0);
+      base::TimeDelta::FromMilliseconds(4780);
+  subframe_timing.paint_timing->largest_image_paint_size = 100u;
   PopulateRequiredTimingFields(&subframe_timing);
 
   // Commit the main frame and a subframe.
@@ -678,10 +678,270 @@
   // Navigate again to force histogram recording in the main frame.
   NavigateAndCommit(GURL(kDefaultTestUrl2));
 
-  // Ensure that the largest_image_paint timing for the main frame is recorded.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  internal::kHistogramLargestContentPaintAllFrames),
+              testing::ElementsAre(base::Bucket(4780, 1)));
   EXPECT_THAT(
-      histogram_tester().GetAllSamples(internal::kHistogramLargestImagePaint),
-      testing::ElementsAre(base::Bucket(4780, 1)));
+      histogram_tester().GetAllSamples(
+          internal::kHistogramLargestContentPaintAllFramesContentType),
+      testing::ElementsAre(base::Bucket(
+          static_cast<base::HistogramBase::Sample>(LargestContentType::kImage),
+          1)));
+}
+
+TEST_F(CorePageLoadMetricsObserverTest,
+       LargestContentfulPaintAllFrames_OnlyMainFrameProvided) {
+  const char kSubframeTestUrl[] = "https://google.com/subframe.html";
+
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  // Pick a value that lines up with a histogram bucket.
+  timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  timing.paint_timing->largest_image_paint_size = 50u;
+  PopulateRequiredTimingFields(&timing);
+
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  // Intentionally not set candidates for the subframes.
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kDefaultTestUrl));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Navigate again to force histogram recording in the main frame.
+  NavigateAndCommit(GURL(kDefaultTestUrl2));
+
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  internal::kHistogramLargestContentPaintAllFrames),
+              testing::ElementsAre(base::Bucket(4780, 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          internal::kHistogramLargestContentPaintAllFramesContentType),
+      testing::ElementsAre(base::Bucket(
+          static_cast<base::HistogramBase::Sample>(LargestContentType::kImage),
+          1)));
+}
+
+// This is to test whether LargestContentfulPaintAllFrames could merge
+// candidates from different frames correctly. The merging will substitutes the
+// existing candidate if a larger candidate from subframe is provided.
+TEST_F(CorePageLoadMetricsObserverTest,
+       LargestContentfulPaintAllFrames_MergeFromFramesBySize_SubframeLarger) {
+  const char kSubframeTestUrl[] = "https://google.com/subframe.html";
+
+  // Create a main frame timing with a largest_image_paint that happens late.
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  // Pick a value that lines up with a histogram bucket.
+  timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(10000);
+  timing.paint_timing->largest_image_paint_size = 50u;
+  PopulateRequiredTimingFields(&timing);
+
+  // Create a candidate in subframe with a larger size.
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  subframe_timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  subframe_timing.paint_timing->largest_image_paint_size = 100u;
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kDefaultTestUrl));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Navigate again to force histogram recording in the main frame.
+  NavigateAndCommit(GURL(kDefaultTestUrl2));
+
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  internal::kHistogramLargestContentPaintAllFrames),
+              testing::ElementsAre(base::Bucket(4780, 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          internal::kHistogramLargestContentPaintAllFramesContentType),
+      testing::ElementsAre(base::Bucket(
+          static_cast<base::HistogramBase::Sample>(LargestContentType::kImage),
+          1)));
+}
+
+// This is to test whether LargestContentfulPaintAllFrames could merge
+// candidates from different frames correctly. The merging will substitutes the
+// existing candidate if a larger candidate from main frame is provided.
+TEST_F(CorePageLoadMetricsObserverTest,
+       LargestContentfulPaintAllFrames_MergeFromFramesBySize_MainFrameLarger) {
+  const char kSubframeTestUrl[] = "https://google.com/subframe.html";
+
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  // Pick a value that lines up with a histogram bucket.
+  timing.paint_timing->largest_text_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  timing.paint_timing->largest_text_paint_size = 100u;
+  PopulateRequiredTimingFields(&timing);
+
+  // Create a candidate in subframe with a smaller size.
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  subframe_timing.paint_timing->largest_text_paint =
+      base::TimeDelta::FromMilliseconds(300);
+  subframe_timing.paint_timing->largest_text_paint_size = 50u;
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kDefaultTestUrl));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Navigate again to force histogram recording in the main frame.
+  NavigateAndCommit(GURL(kDefaultTestUrl2));
+
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  internal::kHistogramLargestContentPaintAllFrames),
+              testing::ElementsAre(base::Bucket(4780, 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          internal::kHistogramLargestContentPaintAllFramesContentType),
+      testing::ElementsAre(base::Bucket(
+          static_cast<base::HistogramBase::Sample>(LargestContentType::kText),
+          1)));
+}
+
+// This tests a trade-off we have made - aggregating all subframe candidates,
+// which makes LCP unable to substitute the subframe candidate with a smaller
+// candidate. This test provides two subframe candidates, the later larger than
+// the first one.
+TEST_F(CorePageLoadMetricsObserverTest,
+       LargestContentfulPaintAllFrames_SubframesCandidateOnlyGetLarger_Larger) {
+  const char kSubframeTestUrl[] = "https://google.com/subframe.html";
+
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  PopulateRequiredTimingFields(&timing);
+
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  subframe_timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  subframe_timing.paint_timing->largest_image_paint_size = 50u;
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kDefaultTestUrl));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  subframe_timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(300);
+  subframe_timing.paint_timing->largest_image_paint_size = 10u;
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Navigate again to force histogram recording in the main frame.
+  NavigateAndCommit(GURL(kDefaultTestUrl2));
+
+  // Ensure that the largest_image_paint timing for the main frame is recorded.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  internal::kHistogramLargestContentPaintAllFrames),
+              testing::ElementsAre(base::Bucket(4780, 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          internal::kHistogramLargestContentPaintAllFramesContentType),
+      testing::ElementsAre(base::Bucket(
+          static_cast<base::HistogramBase::Sample>(LargestContentType::kImage),
+          1)));
+}
+
+// This tests a trade-off we have made - aggregating all subframe candidates,
+// which makes LCP unable to substitute the subframe candidate with a smaller
+// candidate. This test provides two subframe candidates, the later smaller than
+// the first one.
+TEST_F(
+    CorePageLoadMetricsObserverTest,
+    LargestContentfulPaintAllFrames_SubframesCandidateOnlyGetLarger_Smaller) {
+  const char kSubframeTestUrl[] = "https://google.com/subframe.html";
+
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  PopulateRequiredTimingFields(&timing);
+
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  subframe_timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  subframe_timing.paint_timing->largest_image_paint_size = 10u;
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kDefaultTestUrl));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  subframe_timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(990);
+  subframe_timing.paint_timing->largest_image_paint_size = 50u;
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Navigate again to force histogram recording in the main frame.
+  NavigateAndCommit(GURL(kDefaultTestUrl2));
+
+  // Ensure that the largest_image_paint timing for the main frame is recorded.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  internal::kHistogramLargestContentPaintAllFrames),
+              testing::ElementsAre(base::Bucket(990, 1)));
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples(
+          internal::kHistogramLargestContentPaintAllFramesContentType),
+      testing::ElementsAre(base::Bucket(
+          static_cast<base::HistogramBase::Sample>(LargestContentType::kImage),
+          1)));
 }
 
 TEST_F(CorePageLoadMetricsObserverTest,
@@ -710,11 +970,13 @@
 
   timing.paint_timing->largest_image_paint =
       base::TimeDelta::FromMilliseconds(1000);
+  timing.paint_timing->largest_image_paint_size = 10u;
   PopulateRequiredTimingFields(&timing);
   SimulateTimingUpdate(timing);
 
   timing.paint_timing->largest_image_paint =
       base::TimeDelta::FromMilliseconds(4780);
+  timing.paint_timing->largest_image_paint_size = 5u;
   PopulateRequiredTimingFields(&timing);
   SimulateTimingUpdate(timing);
   // Navigate again to force histogram recording.
@@ -815,6 +1077,7 @@
   // Pick a value that lines up with a histogram bucket.
   timing.paint_timing->largest_text_paint =
       base::TimeDelta::FromMilliseconds(4780);
+  timing.paint_timing->largest_text_paint_size = 10u;
   PopulateRequiredTimingFields(&timing);
 
   NavigateAndCommit(GURL(kDefaultTestUrl));
@@ -850,11 +1113,9 @@
   page_load_metrics::mojom::PageLoadTiming timing;
   page_load_metrics::InitPageLoadTimingForTest(&timing);
   timing.navigation_start = base::Time::FromDoubleT(1);
-  // When either the time and size is 0, they are not set and should be excluded
-  // from recording to UMA. We are using the size as the condition of exclusion.
-  timing.paint_timing->largest_text_paint =
-      base::TimeDelta::FromMilliseconds(4780);
-  timing.paint_timing->largest_text_paint_size = 0;
+  // When the size is 0, the timing is regarded as not set and should be
+  // excluded from recording to UMA.
+  timing.paint_timing->largest_text_paint_size = 0u;
   PopulateRequiredTimingFields(&timing);
 
   NavigateAndCommit(GURL(kDefaultTestUrl));
diff --git a/chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.cc b/chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.cc
new file mode 100644
index 0000000..38664672
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.cc
@@ -0,0 +1,178 @@
+// 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/page_load_metrics/observers/largest_contentful_paint_handler.h"
+#include "chrome/common/page_load_metrics/page_load_metrics.mojom.h"
+#include "content/public/browser/render_frame_host.h"
+
+namespace page_load_metrics {
+
+// TODO(crbug/616901): True in test only. Since we are unable to config
+// navigation start in tests, we disable the offsetting to make the test
+// deterministic.
+static bool g_disable_subframe_navigation_start_offset = false;
+
+namespace {
+
+const TimingInfo& MergeTimingsBySizeAndTime(const TimingInfo& timing1,
+                                            const TimingInfo& timing2) {
+  // When both are empty, just return either.
+  if (timing1.IsEmpty() && timing2.IsEmpty())
+    return timing1;
+
+  if (timing1.IsEmpty() && !timing2.IsEmpty())
+    return timing2;
+  if (!timing1.IsEmpty() && timing2.IsEmpty())
+    return timing1;
+  if (timing1.Size() > timing2.Size())
+    return timing1;
+  if (timing1.Size() < timing2.Size())
+    return timing2;
+  // When both sizes are equal
+  DCHECK(timing1.Time());
+  DCHECK(timing2.Time());
+  if (timing1.Time().value() < timing2.Time().value())
+    return timing1;
+  return timing2;
+}
+
+void MergeForSubframesWithAdjustedTime(
+    TimingInfo* inout_timing,
+    const base::Optional<base::TimeDelta>& candidate_new_time,
+    const uint64_t& candidate_new_size) {
+  DCHECK(inout_timing);
+  const TimingInfo new_candidate(candidate_new_time, candidate_new_size,
+                                 inout_timing->Type());
+  const TimingInfo& merged_candidate =
+      MergeTimingsBySizeAndTime(new_candidate, *inout_timing);
+  inout_timing->Reset(merged_candidate.Time(), merged_candidate.Size());
+}
+
+void MergeForSubframes(
+    TimingInfo* inout_timing,
+    const base::Optional<base::TimeDelta>& candidate_new_time,
+    const uint64_t& candidate_new_size,
+    base::TimeDelta navigation_start_offset) {
+  MergeForSubframesWithAdjustedTime(
+      inout_timing,
+      candidate_new_time ? navigation_start_offset + candidate_new_time.value()
+                         : candidate_new_time,
+      candidate_new_size);
+}
+
+bool IsSubframe(content::RenderFrameHost* subframe_rfh) {
+  return subframe_rfh != nullptr && subframe_rfh->GetParent() != nullptr;
+}
+
+}  // namespace
+
+TimingInfo::TimingInfo(PageLoadMetricsObserver::LargestContentType type)
+    : time_(base::Optional<base::TimeDelta>()), size_(0), type_(type) {}
+TimingInfo::TimingInfo(
+    const base::Optional<base::TimeDelta>& time,
+    const uint64_t& size,
+    const page_load_metrics::PageLoadMetricsObserver::LargestContentType type)
+    : time_(time), size_(size), type_(type) {}
+
+TimingInfo::TimingInfo(const TimingInfo& other) = default;
+
+// static
+void LargestContentfulPaintHandler::SetTestMode(bool enabled) {
+  g_disable_subframe_navigation_start_offset = enabled;
+}
+
+void TimingInfo::Reset(const base::Optional<base::TimeDelta>& time,
+                       const uint64_t& size) {
+  size_ = size;
+  time_ = time;
+  DCHECK(HasConsistentTimeAndSize());
+}
+
+ContentfulPaint::ContentfulPaint()
+    : text_(PageLoadMetricsObserver::LargestContentType::kText),
+      image_(PageLoadMetricsObserver::LargestContentType::kImage) {}
+
+const TimingInfo& ContentfulPaint::MergeTextAndImageTiming() {
+  return MergeTimingsBySizeAndTime(text_, image_);
+}
+
+LargestContentfulPaintHandler::LargestContentfulPaintHandler() = default;
+LargestContentfulPaintHandler::~LargestContentfulPaintHandler() = default;
+
+void LargestContentfulPaintHandler::RecordTiming(
+    const mojom::PaintTimingPtr& timing,
+    content::RenderFrameHost* subframe_rfh) {
+  if (!IsSubframe(subframe_rfh)) {
+    RecordMainFrameTiming(timing);
+    return;
+  }
+  // For subframes
+  base::TimeDelta navigation_start_offset;
+  if (!g_disable_subframe_navigation_start_offset) {
+    const auto it = subframe_navigation_start_offset_.find(
+        subframe_rfh->GetFrameTreeNodeId());
+    if (it == subframe_navigation_start_offset_.end()) {
+      // We received timing information for an untracked load. Ignore it.
+      return;
+    }
+    navigation_start_offset = it->second;
+  }
+  RecordSubframeTiming(timing, navigation_start_offset);
+}
+
+const TimingInfo& LargestContentfulPaintHandler::MergeMainFrameAndSubframes() {
+  const TimingInfo& main_frame_timing =
+      main_frame_contentful_paint_.MergeTextAndImageTiming();
+  const TimingInfo& subframe_timing =
+      subframe_contentful_paint_.MergeTextAndImageTiming();
+  return MergeTimingsBySizeAndTime(main_frame_timing, subframe_timing);
+}
+
+// We handle subframe and main frame differently. For main frame, we directly
+// substitute the candidate when we receive a new one. For subframes (plural),
+// we merge the candidates from different subframes by keeping the largest one.
+// Note that the merging of subframes' timings will make
+// |subframe_contentful_paint_| unable to be replaced with a smaller paint (it
+// should have been able when a large ephemeral element is removed). This is a
+// trade-off we make to keep a simple algorithm, otherwise we will have to
+// track one candidate per subframe.
+void LargestContentfulPaintHandler::RecordSubframeTiming(
+    const mojom::PaintTimingPtr& timing,
+    const base::TimeDelta& navigation_start_offset) {
+  MergeForSubframes(&subframe_contentful_paint_.Text(),
+                    timing->largest_text_paint, timing->largest_text_paint_size,
+                    navigation_start_offset);
+  MergeForSubframes(&subframe_contentful_paint_.Image(),
+                    timing->largest_image_paint,
+                    timing->largest_image_paint_size, navigation_start_offset);
+}
+
+void LargestContentfulPaintHandler::RecordMainFrameTiming(
+    const mojom::PaintTimingPtr& timing) {
+  main_frame_contentful_paint_.Text().Reset(timing->largest_text_paint,
+                                            timing->largest_text_paint_size);
+  main_frame_contentful_paint_.Image().Reset(timing->largest_image_paint,
+                                             timing->largest_image_paint_size);
+}
+
+void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation(
+    content::NavigationHandle* navigation_handle,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
+  if (!navigation_handle->HasCommitted())
+    return;
+
+  // We have a new committed navigation, so discard information about the
+  // previously committed navigation.
+  subframe_navigation_start_offset_.erase(
+      navigation_handle->GetFrameTreeNodeId());
+
+  if (extra_info.navigation_start > navigation_handle->NavigationStart())
+    return;
+  base::TimeDelta navigation_delta =
+      navigation_handle->NavigationStart() - extra_info.navigation_start;
+  subframe_navigation_start_offset_.insert(std::make_pair(
+      navigation_handle->GetFrameTreeNodeId(), navigation_delta));
+}
+
+}  // namespace page_load_metrics
diff --git a/chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.h b/chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.h
new file mode 100644
index 0000000..184fc2f
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.h
@@ -0,0 +1,97 @@
+// 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_PAGE_LOAD_METRICS_OBSERVERS_LARGEST_CONTENTFUL_PAINT_HANDLER_H_
+#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_LARGEST_CONTENTFUL_PAINT_HANDLER_H_
+
+#include <map>
+
+#include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
+#include "chrome/common/page_load_metrics/page_load_metrics.mojom.h"
+#include "chrome/common/page_load_metrics/page_load_timing.h"
+
+namespace page_load_metrics {
+
+class TimingInfo {
+ public:
+  explicit TimingInfo(
+      page_load_metrics::PageLoadMetricsObserver::LargestContentType);
+  explicit TimingInfo(
+      const base::Optional<base::TimeDelta>&,
+      const uint64_t& size,
+      const page_load_metrics::PageLoadMetricsObserver::LargestContentType);
+  explicit TimingInfo(const TimingInfo& other);
+  void Reset(const base::Optional<base::TimeDelta>&, const uint64_t& size);
+  base::Optional<base::TimeDelta> Time() const {
+    DCHECK(HasConsistentTimeAndSize());
+    return time_;
+  }
+  uint64_t Size() const {
+    DCHECK(HasConsistentTimeAndSize());
+    return size_;
+  }
+  page_load_metrics::PageLoadMetricsObserver::LargestContentType Type() const {
+    DCHECK(HasConsistentTimeAndSize());
+    return type_;
+  }
+  bool IsEmpty() const {
+    DCHECK(HasConsistentTimeAndSize());
+    // |size_| will be 0 as well, as checked by the DCHECK.
+    return !time_;
+  }
+
+ private:
+  TimingInfo() = delete;
+  // This is only for DCHECK. We will never need the inconsistent state.
+  bool HasConsistentTimeAndSize() const {
+    return (time_ && size_) || (!time_ && !size_);
+  }
+  base::Optional<base::TimeDelta> time_;
+  uint64_t size_;
+  page_load_metrics::PageLoadMetricsObserver::LargestContentType type_;
+};
+
+class ContentfulPaint {
+ public:
+  ContentfulPaint();
+  TimingInfo& Text() { return text_; }
+  TimingInfo& Image() { return image_; }
+  const TimingInfo& MergeTextAndImageTiming();
+
+ private:
+  TimingInfo text_;
+  TimingInfo image_;
+};
+
+class LargestContentfulPaintHandler {
+ public:
+  static void SetTestMode(bool enabled);
+  LargestContentfulPaintHandler();
+  ~LargestContentfulPaintHandler();
+  using FrameTreeNodeId =
+      page_load_metrics::PageLoadMetricsObserver::FrameTreeNodeId;
+  void RecordTiming(const page_load_metrics::mojom::PaintTimingPtr&,
+                    content::RenderFrameHost* subframe_rfh);
+  // We merge the candidates from main frame and subframe to get the largest
+  // candidate across all frames.
+  const TimingInfo& MergeMainFrameAndSubframes();
+  void OnDidFinishSubFrameNavigation(
+      content::NavigationHandle* navigation_handle,
+      const page_load_metrics::PageLoadExtraInfo& extra_info);
+
+ private:
+  void RecordSubframeTiming(const mojom::PaintTimingPtr& timing,
+                            const base::TimeDelta& navigation_start_offset);
+  void RecordMainFrameTiming(const page_load_metrics::mojom::PaintTimingPtr&);
+  ContentfulPaint main_frame_contentful_paint_;
+  ContentfulPaint subframe_contentful_paint_;
+
+  // Navigation start offsets for the most recently committed document in each
+  // frame.
+  std::map<FrameTreeNodeId, base::TimeDelta> subframe_navigation_start_offset_;
+  DISALLOW_COPY_AND_ASSIGN(LargestContentfulPaintHandler);
+};
+
+}  // namespace page_load_metrics
+
+#endif  // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_LARGEST_CONTENTFUL_PAINT_HANDLER_H_
diff --git a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc
index 08ea526f..8b833c58 100644
--- a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc
@@ -162,7 +162,8 @@
 }
 
 void PageCappingPageLoadMetricsObserver::OnDidFinishSubFrameNavigation(
-    content::NavigationHandle* navigation_handle) {
+    content::NavigationHandle* navigation_handle,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
   // If the page is not paused, there is no need to pause new frames.
   if (page_capping_state_ != PageCappingState::kPagePaused)
     return;
diff --git a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h
index 5d07f47..24182de 100644
--- a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h
@@ -81,7 +81,8 @@
   ObservePolicy OnCommit(content::NavigationHandle* navigation_handle,
                          ukm::SourceId source_id) override;
   void OnDidFinishSubFrameNavigation(
-      content::NavigationHandle* navigation_handle) override;
+      content::NavigationHandle* navigation_handle,
+      const page_load_metrics::PageLoadExtraInfo& extra_info) override;
   void MediaStartedPlaying(
       const content::WebContentsObserver::MediaPlayerInfo& video_type,
       content::RenderFrameHost* render_frame_host) override;
diff --git a/chrome/browser/page_load_metrics/observers/previews_lite_page_redirect_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/previews_lite_page_redirect_metrics_observer.cc
index fd890fcf..71975e61 100644
--- a/chrome/browser/page_load_metrics/observers/previews_lite_page_redirect_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/previews_lite_page_redirect_metrics_observer.cc
@@ -48,13 +48,14 @@
   data->set_session_key(info->drp_session_key);
   data->set_page_id(info->page_id);
   data->set_effective_connection_type(previews_data->navigation_ect());
+  data->set_lite_page_received(info->status ==
+                               previews::ServerLitePageStatus::kSuccess);
 
   data->set_connection_type(net::NetworkChangeNotifier::GetConnectionType());
   data->set_request_url(handle->GetURL());
   data->set_black_listed(previews_data->black_listed_for_lite_page());
   data->set_used_data_reduction_proxy(true);
   data->set_client_lofi_requested(false);
-  data->set_lite_page_received(false);
   data->set_lofi_policy_received(false);
   data->set_lofi_received(false);
   data->set_was_cached_data_reduction_proxy_response(false);
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
index 8707928..a8c61d8 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
@@ -9,6 +9,7 @@
 
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/engagement/site_engagement_service.h"
+#include "chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/metrics/net/network_metrics_provider.h"
@@ -80,7 +81,8 @@
 
 UkmPageLoadMetricsObserver::UkmPageLoadMetricsObserver(
     network::NetworkQualityTracker* network_quality_tracker)
-    : network_quality_tracker_(network_quality_tracker) {
+    : network_quality_tracker_(network_quality_tracker),
+      largest_contentful_paint_handler_() {
   DCHECK(network_quality_tracker_);
 }
 
@@ -286,6 +288,14 @@
     builder.SetExperimental_PaintTiming_NavigationToLargestContentPaint(
         largest_content_paint_time.value().InMilliseconds());
   }
+  const page_load_metrics::TimingInfo& paint =
+      largest_contentful_paint_handler_.MergeMainFrameAndSubframes();
+  if (!paint.IsEmpty() &&
+      WasStartedInForegroundOptionalEventInForeground(paint.Time(), info)) {
+    builder
+        .SetExperimental_PaintTiming_NavigationToLargestContentPaintAllFrames(
+            paint.Time().value().InMilliseconds());
+  }
   if (timing.interactive_timing->interactive) {
     base::TimeDelta time_to_interactive =
         timing.interactive_timing->interactive.value();
@@ -510,3 +520,11 @@
 
   return rounded_document_engagement_score;
 }
+
+void UkmPageLoadMetricsObserver::OnTimingUpdate(
+    content::RenderFrameHost* subframe_rfh,
+    const page_load_metrics::mojom::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
+  largest_contentful_paint_handler_.RecordTiming(timing.paint_timing,
+                                                 subframe_rfh);
+}
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
index 907ab59..f60950b 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/time/time.h"
+#include "chrome/browser/page_load_metrics/observers/largest_contentful_paint_handler.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "net/http/http_response_info.h"
 #include "services/metrics/public/cpp/ukm_source.h"
@@ -69,6 +70,11 @@
   void OnLoadedResource(const page_load_metrics::ExtraRequestCompleteInfo&
                             extra_request_complete_info) override;
 
+  void OnTimingUpdate(
+      content::RenderFrameHost* subframe_rfh,
+      const page_load_metrics::mojom::PageLoadTiming& timing,
+      const page_load_metrics::PageLoadExtraInfo& extra_info) override;
+
  private:
   // Records page load timing related metrics available in PageLoadTiming, such
   // as first contentful paint.
@@ -138,6 +144,9 @@
   // The connection info for the committed URL.
   base::Optional<net::HttpResponseInfo::ConnectionInfo> connection_info_;
 
+  page_load_metrics::LargestContentfulPaintHandler
+      largest_contentful_paint_handler_;
+
   DISALLOW_COPY_AND_ASSIGN(UkmPageLoadMetricsObserver);
 };
 
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
index 71926210..7383cb3 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
@@ -24,6 +24,9 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/metrics_proto/system_profile.pb.h"
 
+using content::NavigationSimulator;
+using content::RenderFrameHost;
+using content::RenderFrameHostTester;
 using testing::AnyNumber;
 using testing::Mock;
 using testing::Return;
@@ -34,6 +37,7 @@
 
 const char kTestUrl1[] = "https://www.google.com/";
 const char kTestUrl2[] = "https://www.example.com/";
+const char kSubframeTestUrl[] = "https://www.google.com/subframe.html";
 
 class MockNetworkQualityProvider : public network::NetworkQualityTracker {
  public:
@@ -56,6 +60,7 @@
 
   void SetUp() override {
     page_load_metrics::PageLoadMetricsObserverTestHarness::SetUp();
+    page_load_metrics::LargestContentfulPaintHandler::SetTestMode(true);
 
     EXPECT_CALL(mock_network_quality_provider_, GetEffectiveConnectionType())
         .Times(AnyNumber())
@@ -244,6 +249,7 @@
   timing.navigation_start = base::Time::FromDoubleT(1);
   timing.paint_timing->largest_image_paint =
       base::TimeDelta::FromMilliseconds(600);
+  timing.paint_timing->largest_image_paint_size = 50u;
   PopulateRequiredTimingFields(&timing);
 
   NavigateAndCommit(GURL(kTestUrl1));
@@ -366,18 +372,22 @@
   timing.paint_timing->last_image_paint = base::TimeDelta::FromMilliseconds(60);
   timing.paint_timing->largest_image_paint =
       base::TimeDelta::FromMilliseconds(60);
+  timing.paint_timing->largest_image_paint_size = 10u;
   timing.paint_timing->last_text_paint = base::TimeDelta::FromMilliseconds(60);
   timing.paint_timing->largest_text_paint =
       base::TimeDelta::FromMilliseconds(60);
+  timing.paint_timing->largest_text_paint_size = 10u;
   SimulateTimingUpdate(timing);
 
   timing.paint_timing->last_image_paint =
       base::TimeDelta::FromMilliseconds(600);
   timing.paint_timing->largest_image_paint =
       base::TimeDelta::FromMilliseconds(600);
+  timing.paint_timing->largest_image_paint_size = 10u;
   timing.paint_timing->last_text_paint = base::TimeDelta::FromMilliseconds(600);
   timing.paint_timing->largest_text_paint =
       base::TimeDelta::FromMilliseconds(600);
+  timing.paint_timing->largest_text_paint_size = 10u;
   SimulateTimingUpdate(timing);
 
   // Simulate closing the tab.
@@ -416,6 +426,7 @@
   timing.navigation_start = base::Time::FromDoubleT(1);
   timing.paint_timing->largest_text_paint =
       base::TimeDelta::FromMilliseconds(600);
+  timing.paint_timing->largest_text_paint_size = 50u;
   PopulateRequiredTimingFields(&timing);
 
   NavigateAndCommit(GURL(kTestUrl1));
@@ -537,6 +548,153 @@
   }
 }
 
+TEST_F(UkmPageLoadMetricsObserverTest,
+       LargestContentPaintAllFrames_OnlySubframe) {
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  PopulateRequiredTimingFields(&timing);
+
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  subframe_timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  subframe_timing.paint_timing->largest_image_paint_size = 100u;
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kTestUrl1));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Simulate closing the tab.
+  DeleteContents();
+
+  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
+      test_ukm_recorder().GetMergedEntriesByName(PageLoad::kEntryName);
+  EXPECT_EQ(1ul, merged_entries.size());
+
+  for (const auto& kv : merged_entries) {
+    test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
+                                                GURL(kTestUrl1));
+    test_ukm_recorder().ExpectEntryMetric(
+        kv.second.get(),
+        PageLoad::
+            kExperimental_PaintTiming_NavigationToLargestContentPaintAllFramesName,
+        4780);
+    EXPECT_TRUE(test_ukm_recorder().EntryHasMetric(
+        kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
+  }
+}
+
+TEST_F(UkmPageLoadMetricsObserverTest,
+       LargestContentPaintAllFrames_OnlyMainFrame) {
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  timing.paint_timing->largest_image_paint_size = 100u;
+  PopulateRequiredTimingFields(&timing);
+
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kTestUrl1));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Simulate closing the tab.
+  DeleteContents();
+
+  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
+      test_ukm_recorder().GetMergedEntriesByName(PageLoad::kEntryName);
+  EXPECT_EQ(1ul, merged_entries.size());
+
+  for (const auto& kv : merged_entries) {
+    test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
+                                                GURL(kTestUrl1));
+    test_ukm_recorder().ExpectEntryMetric(
+        kv.second.get(),
+        PageLoad::
+            kExperimental_PaintTiming_NavigationToLargestContentPaintAllFramesName,
+        4780);
+    EXPECT_TRUE(test_ukm_recorder().EntryHasMetric(
+        kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
+  }
+}
+
+// This is to test whether LargestContentPaintAllFrames can merge the candidates
+// from different frames correctly. The metric should pick the larger candidate
+// during merging.
+TEST_F(UkmPageLoadMetricsObserverTest,
+       LargestContentPaintAllFrames_MergeFrameCandidateBySize) {
+  page_load_metrics::mojom::PageLoadTiming timing;
+  page_load_metrics::InitPageLoadTimingForTest(&timing);
+  timing.navigation_start = base::Time::FromDoubleT(1);
+  timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(4780);
+  timing.paint_timing->largest_image_paint_size = 50u;
+  PopulateRequiredTimingFields(&timing);
+
+  page_load_metrics::mojom::PageLoadTiming subframe_timing;
+  page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
+  subframe_timing.navigation_start = base::Time::FromDoubleT(2);
+  timing.paint_timing->largest_image_paint =
+      base::TimeDelta::FromMilliseconds(990);
+  timing.paint_timing->largest_image_paint_size = 100u;
+  PopulateRequiredTimingFields(&subframe_timing);
+
+  // Commit the main frame and a subframe.
+  NavigateAndCommit(GURL(kTestUrl1));
+  RenderFrameHost* subframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSubframeTestUrl),
+          RenderFrameHostTester::For(web_contents()->GetMainFrame())
+              ->AppendChild("subframe"));
+
+  // Simulate timing updates in the main frame and the subframe.
+  SimulateTimingUpdate(timing);
+  SimulateTimingUpdate(subframe_timing, subframe);
+
+  // Simulate closing the tab.
+  DeleteContents();
+
+  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
+      test_ukm_recorder().GetMergedEntriesByName(PageLoad::kEntryName);
+  EXPECT_EQ(1ul, merged_entries.size());
+
+  for (const auto& kv : merged_entries) {
+    test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
+                                                GURL(kTestUrl1));
+    test_ukm_recorder().ExpectEntryMetric(
+        kv.second.get(),
+        PageLoad::
+            kExperimental_PaintTiming_NavigationToLargestContentPaintAllFramesName,
+        990);
+    EXPECT_TRUE(test_ukm_recorder().EntryHasMetric(
+        kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
+  }
+}
+
 TEST_F(UkmPageLoadMetricsObserverTest, LastTextPaint) {
   page_load_metrics::mojom::PageLoadTiming timing;
   page_load_metrics::InitPageLoadTimingForTest(&timing);
diff --git a/chrome/browser/page_load_metrics/observers/use_counter/ukm_features.cc b/chrome/browser/page_load_metrics/observers/use_counter/ukm_features.cc
index 5407752..90d0e77 100644
--- a/chrome/browser/page_load_metrics/observers/use_counter/ukm_features.cc
+++ b/chrome/browser/page_load_metrics/observers/use_counter/ukm_features.cc
@@ -99,6 +99,7 @@
           WebFeature::kV8MediaCapabilities_DecodingInfo_Method,
           WebFeature::kOpenerNavigationDownloadCrossOrigin,
           WebFeature::kLinkRelPrerender,
+          WebFeature::kAdClickNavigation,
       }));
   return *opt_in_features;
 }
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_observer.h b/chrome/browser/page_load_metrics/page_load_metrics_observer.h
index 86d2b9f..0ecc67b 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/page_load_metrics_observer.h
@@ -361,7 +361,8 @@
   // that |navigation_handle| will be destroyed soon after this call. Don't
   // hold a reference to it.
   virtual void OnDidFinishSubFrameNavigation(
-      content::NavigationHandle* navigation_handle) {}
+      content::NavigationHandle* navigation_handle,
+      const PageLoadExtraInfo& extra_info) {}
 
   // OnCommitSameDocumentNavigation is triggered when a same-document navigation
   // commits within the main frame of the current page. Note that
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.cc b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.cc
index 9943ed9..72342a32 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.cc
@@ -65,6 +65,11 @@
   return observed_page_fields_.IsSet(field);
 }
 
+bool PageLoadMetricsTestWaiter::DidObserveWebFeature(
+    blink::mojom::WebFeature feature) const {
+  return observed_web_features_.test(static_cast<size_t>(feature));
+}
+
 void PageLoadMetricsTestWaiter::Wait() {
   if (ExpectationsSatisfied())
     return;
@@ -161,7 +166,8 @@
 }
 
 void PageLoadMetricsTestWaiter::OnDidFinishSubFrameNavigation(
-    content::NavigationHandle* navigation_handle) {
+    content::NavigationHandle* navigation_handle,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
   if (SubframeNavigationExpectationsSatisfied())
     return;
 
@@ -297,9 +303,10 @@
 
 void PageLoadMetricsTestWaiter::WaiterMetricsObserver::
     OnDidFinishSubFrameNavigation(
-        content::NavigationHandle* navigation_handle) {
+        content::NavigationHandle* navigation_handle,
+        const page_load_metrics::PageLoadExtraInfo& extra_info) {
   if (waiter_)
-    waiter_->OnDidFinishSubFrameNavigation(navigation_handle);
+    waiter_->OnDidFinishSubFrameNavigation(navigation_handle, extra_info);
 }
 
 }  // namespace page_load_metrics
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h
index 6de54ef..e10595a 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h
+++ b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h
@@ -58,6 +58,9 @@
   // Whether the given TimingField was observed in the page.
   bool DidObserveInPage(TimingField field) const;
 
+  // Whether the given WebFeature was observed in the page.
+  bool DidObserveWebFeature(blink::mojom::WebFeature feature) const;
+
   // Waits for PageLoadMetrics events that match the fields set by the add
   // expectation methods. All matching fields must be set to end this wait.
   void Wait();
@@ -109,7 +112,8 @@
         const page_load_metrics::PageLoadExtraInfo& extra_info) override;
 
     void OnDidFinishSubFrameNavigation(
-        content::NavigationHandle* navigation_handle) override;
+        content::NavigationHandle* navigation_handle,
+        const page_load_metrics::PageLoadExtraInfo& extra_info) override;
 
    private:
     const base::WeakPtr<PageLoadMetricsTestWaiter> waiter_;
@@ -180,7 +184,8 @@
                                const PageLoadExtraInfo& extra_info);
 
   void OnDidFinishSubFrameNavigation(
-      content::NavigationHandle* navigation_handle);
+      content::NavigationHandle* navigation_handle,
+      const page_load_metrics::PageLoadExtraInfo& extra_info);
 
   void OnTrackerCreated(page_load_metrics::PageLoadTracker* tracker) override;
 
diff --git a/chrome/browser/page_load_metrics/page_load_tracker.cc b/chrome/browser/page_load_metrics/page_load_tracker.cc
index a64c199a..45f948a 100644
--- a/chrome/browser/page_load_metrics/page_load_tracker.cc
+++ b/chrome/browser/page_load_metrics/page_load_tracker.cc
@@ -375,8 +375,9 @@
 
 void PageLoadTracker::DidFinishSubFrameNavigation(
     content::NavigationHandle* navigation_handle) {
+  PageLoadExtraInfo extra_info(ComputePageLoadExtraInfo());
   for (const auto& observer : observers_) {
-    observer->OnDidFinishSubFrameNavigation(navigation_handle);
+    observer->OnDidFinishSubFrameNavigation(navigation_handle, extra_info);
   }
 }
 
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index dbf20286..03f06bb 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/prerender/prerender_contents.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/site_isolation_policy.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -82,7 +83,7 @@
 #include "third_party/re2/src/re2/re2.h"
 #include "url/url_constants.h"
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #endif
@@ -436,7 +437,7 @@
 void ChromePasswordManagerClient::CheckSafeBrowsingReputation(
     const GURL& form_action,
     const GURL& frame_url) {
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   safe_browsing::PasswordProtectionService* pps =
       GetPasswordProtectionService();
   if (pps) {
@@ -451,7 +452,7 @@
     popup_controller_->HideAndDestroy();
 }
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 safe_browsing::PasswordProtectionService*
 ChromePasswordManagerClient::GetPasswordProtectionService() const {
   return safe_browsing::ChromePasswordProtectionService::
@@ -926,15 +927,24 @@
 
 void ChromePasswordManagerClient::ShowManualFallbackForSaving(
     const autofill::PasswordForm& password_form) {
+  content::RenderFrameHost* frame =
+      password_manager_driver_bindings_.GetCurrentTargetFrame();
   if (!password_manager::bad_message::CheckChildProcessSecurityPolicy(
-          password_manager_driver_bindings_.GetCurrentTargetFrame(),
-          password_form,
+          frame, password_form,
           BadMessageReason::CPMD_BAD_ORIGIN_SHOW_FALLBACK_FOR_SAVING))
     return;
   password_manager::PasswordManagerDriver* driver =
-      driver_factory_->GetDriverForFrame(
-          password_manager_driver_bindings_.GetCurrentTargetFrame());
+      driver_factory_->GetDriverForFrame(frame);
   GetPasswordManager()->ShowManualFallbackForSaving(driver, password_form);
+
+  if (SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled()) {
+    // This function signals that the user is typing a password into
+    // |password_form|.  Use this as a heuristic to start site-isolating the
+    // form's site.  This is intended to be used primarily when full site
+    // isolation is not used, such as on Android.
+    content::SiteInstance::StartIsolatingSite(
+        frame->GetSiteInstance()->GetBrowserContext(), password_form.origin);
+  }
 }
 
 void ChromePasswordManagerClient::SameDocumentNavigation(
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index ee5598a..1c806447 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -134,7 +134,7 @@
 
   void HidePasswordGenerationPopup();
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   safe_browsing::PasswordProtectionService* GetPasswordProtectionService()
       const override;
 
diff --git a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
index 945a22b..04d2782 100644
--- a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
+++ b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
@@ -626,7 +626,14 @@
   DISALLOW_COPY_AND_ASSIGN(MachineLevelUserCloudPolicyPolicyFetchTest);
 };
 
-IN_PROC_BROWSER_TEST_P(MachineLevelUserCloudPolicyPolicyFetchTest, Test) {
+// Crashes on Win only.  http://crbug.com/939261
+#if defined(OS_WIN)
+#define MAYBE_Test DISABLED_Test
+#else
+#define MAYBE_Test Test
+#endif
+
+IN_PROC_BROWSER_TEST_P(MachineLevelUserCloudPolicyPolicyFetchTest, MAYBE_Test) {
   MachineLevelUserCloudPolicyManager* manager =
       g_browser_process->browser_policy_connector()
           ->machine_level_user_cloud_policy_manager();
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index b9276a7..e610f9ba 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -1009,25 +1009,44 @@
 IN_PROC_BROWSER_TEST_P(
     PreviewsLitePageServerBrowserTest,
     DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsReloadDisabled)) {
-  // Start with a non-preview load.
-  g_browser_process->network_quality_tracker()
-      ->ReportEffectiveConnectionTypeForTesting(
-          net::EFFECTIVE_CONNECTION_TYPE_3G);
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {}, {previews::features::kPreviewsReloadsAreSoftOptOuts});
 
-  ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
-  VerifyPreviewNotLoaded();
+  content::ReloadType tests[] = {
+      content::ReloadType::NORMAL,
+      content::ReloadType::BYPASSING_CACHE,
+      content::ReloadType::ORIGINAL_REQUEST_URL,
+  };
+  for (content::ReloadType type : tests) {
+    // Start with a non-preview load.
+    g_browser_process->network_quality_tracker()
+        ->ReportEffectiveConnectionTypeForTesting(
+            net::EFFECTIVE_CONNECTION_TYPE_3G);
 
-  // Set the conditions so a Preview would trigger if not for the reload.
-  g_browser_process->network_quality_tracker()
-      ->ReportEffectiveConnectionTypeForTesting(
-          net::EFFECTIVE_CONNECTION_TYPE_2G);
-  GetWebContents()->GetController().Reload(content::ReloadType::NORMAL, false);
-  VerifyPreviewNotLoaded();
+    ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
+    VerifyPreviewNotLoaded();
+
+    // Set the conditions so a Preview would trigger if not for the reload.
+    g_browser_process->network_quality_tracker()
+        ->ReportEffectiveConnectionTypeForTesting(
+            net::EFFECTIVE_CONNECTION_TYPE_2G);
+    GetWebContents()->GetController().Reload(type, false);
+    VerifyPreviewNotLoaded();
+
+    // Verify that a reload on a preview page triggers a redirect back to the
+    // original page.
+    ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
+    VerifyPreviewLoaded();
+
+    GetWebContents()->GetController().Reload(type, false);
+    VerifyPreviewNotLoaded();
+  }
 }
 
 IN_PROC_BROWSER_TEST_P(
     PreviewsLitePageServerBrowserTest,
-    DISABLE_ON_WIN_MAC_CHROMESOS(ReloadingLitePagesDisablesLitePages)) {
+    DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsReloadDisabled_SoftOptOut)) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       {previews::features::kPreviewsReloadsAreSoftOptOuts}, {});
@@ -1037,10 +1056,6 @@
 
   GetWebContents()->GetController().Reload(content::ReloadType::NORMAL, false);
   VerifyPreviewNotLoaded();
-
-  // Check the the rule is still present.
-  ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
-  VerifyPreviewNotLoaded();
 }
 
 IN_PROC_BROWSER_TEST_P(
@@ -1051,6 +1066,15 @@
   VerifyPreviewLoaded();
   VerifyInfoStatus(&histogram_tester, previews::ServerLitePageStatus::kSuccess);
 
+  PreviewsServiceFactory::GetForProfile(
+      Profile::FromBrowserContext(browser()
+                                      ->tab_strip_model()
+                                      ->GetActiveWebContents()
+                                      ->GetBrowserContext()))
+      ->previews_ui_service()
+      ->previews_decider_impl()
+      ->SetIgnorePreviewsBlacklistDecision(false /* ignored */);
+
   PreviewsUITabHelper::FromWebContents(GetWebContents())
       ->ReloadWithoutPreviews();
   VerifyPreviewNotLoaded();
@@ -1086,9 +1110,9 @@
     VerifyInfoStatus(&histogram_tester,
                      previews::ServerLitePageStatus::kRedirect);
     ClearDeciderState();
-      histogram_tester.ExpectBucketCount(
-          "Previews.ServerLitePage.ServerResponse",
-          PreviewsLitePageNavigationThrottle::ServerResponse::kRedirect, 1);
+    histogram_tester.ExpectBucketCount(
+        "Previews.ServerLitePage.ServerResponse",
+        PreviewsLitePageNavigationThrottle::ServerResponse::kRedirect, 1);
   }
 
   {
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle.cc b/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
index d7b2633..7bdacbf 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
@@ -298,8 +298,6 @@
 
 bool PreviewsLitePageNavigationThrottle::IsEligibleForPreview() const {
   DCHECK(navigation_handle()->IsInMainFrame());
-  DCHECK_NE(navigation_handle()->GetReloadType(),
-            content::ReloadType::ORIGINAL_REQUEST_URL);
 
   if (data_reduction_proxy::HasURLRedirectCycle(
           navigation_handle()->GetRedirectChain()) ||
@@ -321,6 +319,8 @@
     return false;
   }
 
+  DCHECK_EQ(navigation_handle()->GetReloadType(), content::ReloadType::NONE);
+
   if (previews::IsLitePageRedirectPreviewDomain(navigation_handle()->GetURL()))
     return false;
 
diff --git a/chrome/browser/previews/previews_oneplatform_hints_browsertest.cc b/chrome/browser/previews/previews_oneplatform_hints_browsertest.cc
new file mode 100644
index 0000000..000c27d
--- /dev/null
+++ b/chrome/browser/previews/previews_oneplatform_hints_browsertest.cc
@@ -0,0 +1,173 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/task/task_scheduler/task_scheduler.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/metrics/subprocess_metrics_provider.h"
+#include "chrome/browser/previews/previews_service.h"
+#include "chrome/browser/previews/previews_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h"
+#include "components/optimization_guide/hints_component_info.h"
+#include "components/optimization_guide/optimization_guide_service.h"
+#include "components/optimization_guide/proto/hints.pb.h"
+#include "components/optimization_guide/test_hints_component_creator.h"
+#include "components/previews/content/previews_decider_impl.h"
+#include "components/previews/content/previews_optimization_guide.h"
+#include "components/previews/content/previews_ui_service.h"
+#include "components/previews/core/previews_black_list.h"
+#include "components/previews/core/previews_constants.h"
+#include "components/previews/core/previews_features.h"
+#include "components/previews/core/previews_switches.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "services/network/public/cpp/network_quality_tracker.h"
+
+namespace {
+
+// Fetch and calculate the total number of samples from all the bins for
+// |histogram_name|. Note: from some browertests run (such as chromeos) there
+// might be two profiles created, and this will return the total sample count
+// across profiles.
+int GetTotalHistogramSamples(const base::HistogramTester* histogram_tester,
+                             const std::string& histogram_name) {
+  std::vector<base::Bucket> buckets =
+      histogram_tester->GetAllSamples(histogram_name);
+  int total = 0;
+  for (const auto& bucket : buckets)
+    total += bucket.count;
+
+  return total;
+}
+
+// Retries fetching |histogram_name| until it contains at least |count| samples.
+int RetryForHistogramUntilCountReached(
+    const base::HistogramTester* histogram_tester,
+    const std::string& histogram_name,
+    int count) {
+  int total = 0;
+  while (true) {
+    base::TaskScheduler::GetInstance()->FlushForTesting();
+    base::RunLoop().RunUntilIdle();
+
+    total = GetTotalHistogramSamples(histogram_tester, histogram_name);
+    if (total >= count)
+      return total;
+  }
+}
+
+}  // namespace
+
+// This test class sets up everything but does not enable any features.
+class PreviewsOnePlatformNoFeaturesBrowserTest : public InProcessBrowserTest {
+ public:
+  PreviewsOnePlatformNoFeaturesBrowserTest() = default;
+  ~PreviewsOnePlatformNoFeaturesBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    https_server_.reset(
+        new net::EmbeddedTestServer(net::EmbeddedTestServer::TYPE_HTTPS));
+    https_server_->ServeFilesFromSourceDirectory("chrome/test/data/previews");
+    ASSERT_TRUE(https_server_->Start());
+
+    InProcessBrowserTest::SetUpOnMainThread();
+  }
+
+  void SetUpCommandLine(base::CommandLine* cmd) override {
+    cmd->AppendSwitch("enable-spdy-proxy-auth");
+
+    // Due to race conditions, it's possible that blacklist data is not loaded
+    // at the time of first navigation. That may prevent Preview from
+    // triggering, and causing the test to flake.
+    cmd->AppendSwitch(previews::switches::kIgnorePreviewsBlacklist);
+  }
+
+  const GURL& https_url() const { return https_url_; }
+  const base::HistogramTester* GetHistogramTester() {
+    return &histogram_tester_;
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+ private:
+  void TearDownOnMainThread() override {
+    EXPECT_TRUE(https_server_->ShutdownAndWaitUntilComplete());
+
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
+  std::unique_ptr<net::EmbeddedTestServer> https_server_;
+  GURL https_url_;
+
+  base::HistogramTester histogram_tester_;
+
+  DISALLOW_COPY_AND_ASSIGN(PreviewsOnePlatformNoFeaturesBrowserTest);
+};
+
+// This test class enables OnePlatform Hints.
+class PreviewsOnePlatformHintsBrowserTest
+    : public PreviewsOnePlatformNoFeaturesBrowserTest {
+ public:
+  PreviewsOnePlatformHintsBrowserTest() = default;
+
+  ~PreviewsOnePlatformHintsBrowserTest() override = default;
+
+  void SetUp() override {
+    // Enabled OnePlatformHints with |kPreviewsOnePlatformHints|.
+    scoped_feature_list_.InitWithFeatures(
+        {previews::features::kPreviews, previews::features::kOptimizationHints,
+         previews::features::kPreviewsOnePlatformHints},
+        {});
+    // Call to inherited class to match same set up with feature flags added.
+    PreviewsOnePlatformNoFeaturesBrowserTest::SetUp();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PreviewsOnePlatformHintsBrowserTest);
+};
+
+// This test creates new browser with no profile and loads a random page with
+// the feature flags enables the PreviewsOnePlatformHints. We confirm that the
+// top_host_provider_impl executes and does not crash by checking UMA
+// histograms for the total number of TopEngagementSites and
+// the total number of sites returned controlled by the experiments flag
+// |max_oneplatform_update_hosts|.
+IN_PROC_BROWSER_TEST_F(PreviewsOnePlatformHintsBrowserTest,
+                       OnePlatformHintsEnabled) {
+  const base::HistogramTester* histogram_tester = GetHistogramTester();
+
+  // Expect that the browser initialization will record at least one sample
+  // in each of the follow histograms as One Platform Hints are enabled.
+  EXPECT_GE(RetryForHistogramUntilCountReached(
+                histogram_tester,
+                "Previews.HintsFetcher.GetHintsRequest.HostCount", 1),
+            1);
+}
+
+IN_PROC_BROWSER_TEST_F(PreviewsOnePlatformNoFeaturesBrowserTest,
+                       OnePlatformNoFeatures) {
+  const base::HistogramTester* histogram_tester = GetHistogramTester();
+
+  // Expect that the histogram for HintsFetcher to be 0 because the OnePlatform
+  // is not enabled.
+  histogram_tester->ExpectTotalCount(
+      "Previews.HintsFetcher.GetHintsRequest.HostCount", 0);
+}
diff --git a/chrome/browser/previews/previews_service.cc b/chrome/browser/previews/previews_service.cc
index c9c03b60..d917986 100644
--- a/chrome/browser/previews/previews_service.cc
+++ b/chrome/browser/previews/previews_service.cc
@@ -102,7 +102,10 @@
 }
 
 PreviewsService::PreviewsService(content::BrowserContext* browser_context)
-    : previews_lite_page_decider_(
+    : previews_top_host_provider_(
+          std::make_unique<previews::PreviewsTopHostProviderImpl>(
+              browser_context)),
+      previews_lite_page_decider_(
           std::make_unique<PreviewsLitePageDecider>(browser_context)) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
@@ -133,7 +136,8 @@
           profile_path.Append(chrome::kPreviewsOptOutDBFilename)),
       optimization_guide_service
           ? std::make_unique<previews::PreviewsOptimizationGuide>(
-                optimization_guide_service, ui_task_runner, profile_path)
+                optimization_guide_service, ui_task_runner, profile_path,
+                previews_top_host_provider_.get())
           : nullptr,
       base::Bind(&IsPreviewsTypeEnabled),
       std::make_unique<previews::PreviewsLogger>(), GetAllowedPreviews(),
diff --git a/chrome/browser/previews/previews_service.h b/chrome/browser/previews/previews_service.h
index 840eee7..d12077b 100644
--- a/chrome/browser/previews/previews_service.h
+++ b/chrome/browser/previews/previews_service.h
@@ -6,10 +6,13 @@
 #define CHROME_BROWSER_PREVIEWS_PREVIEWS_SERVICE_H_
 
 #include <memory>
+#include <string>
+#include <vector>
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/single_thread_task_runner.h"
+#include "chrome/browser/previews/previews_top_host_provider_impl.h"
 #include "components/blacklist/opt_out_blacklist/opt_out_blacklist_data.h"
 #include "components/keyed_service/core/keyed_service.h"
 
@@ -26,6 +29,7 @@
 }
 
 namespace previews {
+class PreviewsTopHostProviderImpl;
 class PreviewsUIService;
 }
 
@@ -69,6 +73,10 @@
   static blacklist::BlacklistData::AllowedTypesAndVersions GetAllowedPreviews();
 
  private:
+  // The top site provider for use with previews.
+  std::unique_ptr<previews::PreviewsTopHostProviderImpl>
+      previews_top_host_provider_;
+
   // The previews UI thread service.
   std::unique_ptr<previews::PreviewsUIService> previews_ui_service_;
 
diff --git a/chrome/browser/previews/previews_top_host_provider_impl.cc b/chrome/browser/previews/previews_top_host_provider_impl.cc
new file mode 100644
index 0000000..673e7dd7
--- /dev/null
+++ b/chrome/browser/previews/previews_top_host_provider_impl.cc
@@ -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.
+
+#include "chrome/browser/previews/previews_top_host_provider_impl.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "chrome/browser/engagement/site_engagement_details.mojom.h"
+#include "chrome/browser/engagement/site_engagement_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace previews {
+
+PreviewsTopHostProviderImpl::PreviewsTopHostProviderImpl(
+    content::BrowserContext* browser_context)
+    : browser_context_(browser_context) {}
+
+PreviewsTopHostProviderImpl::~PreviewsTopHostProviderImpl() {}
+
+std::vector<std::string> PreviewsTopHostProviderImpl::GetTopHosts(
+    size_t max_sites) const {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(browser_context_);
+
+  std::vector<std::string> top_hosts;
+  top_hosts.reserve(max_sites);
+
+  // Create SiteEngagementService to request site engagement scores.
+  Profile* profile = Profile::FromBrowserContext(browser_context_);
+  SiteEngagementService* engagement_service =
+      SiteEngagementService::Get(profile);
+
+  // Create a vector of the top hosts by engagement score up to |max_sites|
+  // size. Currently utilizes just the first |max_sites| entries.
+  // TODO(crbug.com/932707): Select TOP HTTPS hosts from site engagement.
+  std::vector<mojom::SiteEngagementDetails> engagement_details =
+      engagement_service->GetAllDetails();
+  for (const auto& detail : engagement_details) {
+    if (top_hosts.size() <= max_sites)
+      top_hosts.push_back(detail.origin.host());
+    else
+      break;
+  }
+
+  return top_hosts;
+}
+
+}  // namespace previews
diff --git a/chrome/browser/previews/previews_top_host_provider_impl.h b/chrome/browser/previews/previews_top_host_provider_impl.h
new file mode 100644
index 0000000..c279f5f6
--- /dev/null
+++ b/chrome/browser/previews/previews_top_host_provider_impl.h
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREVIEWS_PREVIEWS_TOP_HOST_PROVIDER_IMPL_H_
+#define CHROME_BROWSER_PREVIEWS_PREVIEWS_TOP_HOST_PROVIDER_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "components/previews/content/previews_top_host_provider.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace previews {
+
+// An implementation of the PreviewTopHostProvider for getting the top sites
+// based on site engagement scores.
+class PreviewsTopHostProviderImpl : public PreviewsTopHostProvider {
+ public:
+  explicit PreviewsTopHostProviderImpl(content::BrowserContext* BrowserContext);
+  ~PreviewsTopHostProviderImpl() override;
+
+  std::vector<std::string> GetTopHosts(size_t max_sites) const override;
+
+ private:
+  // |browser_context_| is used for interaction with the SiteEngagementService
+  // and the embedder should guarantee that it is non-null during the lifetime
+  // of |this|.
+  content::BrowserContext* browser_context_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(PreviewsTopHostProviderImpl);
+};
+
+}  // namespace previews
+
+#endif  // CHROME_BROWSER_PREVIEWS_PREVIEWS_TOP_HOST_PROVIDER_IMPL_H_
diff --git a/chrome/browser/profiles/profile_attributes_storage.cc b/chrome/browser/profiles/profile_attributes_storage.cc
index 615be3a..e4373d7 100644
--- a/chrome/browser/profiles/profile_attributes_storage.cc
+++ b/chrome/browser/profiles/profile_attributes_storage.cc
@@ -195,7 +195,9 @@
     name = l10n_util::GetStringFUTF16(IDS_NEW_NUMBERED_PROFILE_NAME,
                                       base::NumberToString16(name_index));
 #else
-    if (icon_index < profiles::GetGenericAvatarIconCount()) {
+    // TODO(crbug.com/937834): Clean up this code.
+    if (icon_index < profiles::GetGenericAvatarIconCount() ||
+        profiles::IsModernAvatarIconIndex(icon_index)) {
       name = l10n_util::GetStringFUTF16Int(IDS_NUMBERED_PROFILE_NAME,
                                            name_index);
     } else {
diff --git a/chrome/browser/profiles/profile_avatar_icon_util.cc b/chrome/browser/profiles/profile_avatar_icon_util.cc
index ace1174..8774926 100644
--- a/chrome/browser/profiles/profile_avatar_icon_util.cc
+++ b/chrome/browser/profiles/profile_avatar_icon_util.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 #include <vector>
@@ -16,6 +17,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
+#include "build/build_config.h"
 #include "cc/paint/paint_flags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/common/chrome_paths.h"
@@ -38,6 +40,9 @@
 // Helper methods for transforming and drawing avatar icons.
 namespace {
 
+const int kOldAvatarIconWidth = 38;
+const int kOldAvatarIconHeight = 31;
+
 // Determine what the scaled height of the avatar icon should be for a
 // specified width, to preserve the aspect ratio.
 int GetScaledAvatarHeightForWidth(int width, const gfx::ImageSkia& avatar) {
@@ -239,28 +244,33 @@
   int label_id;
 };
 
-const int kAvatarIconWidth = 38;
-const int kAvatarIconHeight = 31;
-const SkColor kAvatarTutorialBackgroundColor = SkColorSetRGB(0x42, 0x85, 0xf4);
-const SkColor kAvatarTutorialContentTextColor = SkColorSetRGB(0xc6, 0xda, 0xfc);
-const SkColor kAvatarBubbleAccountsBackgroundColor =
+constexpr int kAvatarIconSize = 96;
+constexpr SkColor kAvatarTutorialBackgroundColor =
+    SkColorSetRGB(0x42, 0x85, 0xf4);
+constexpr SkColor kAvatarTutorialContentTextColor =
+    SkColorSetRGB(0xc6, 0xda, 0xfc);
+constexpr SkColor kAvatarBubbleAccountsBackgroundColor =
     SkColorSetRGB(0xf3, 0xf3, 0xf3);
-const SkColor kAvatarBubbleGaiaBackgroundColor =
+constexpr SkColor kAvatarBubbleGaiaBackgroundColor =
     SkColorSetRGB(0xf5, 0xf5, 0xf5);
-const SkColor kUserManagerBackgroundColor = SkColorSetRGB(0xee, 0xee, 0xee);
+constexpr SkColor kUserManagerBackgroundColor = SkColorSetRGB(0xee, 0xee, 0xee);
 
-const char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
-const char kGAIAPictureFileName[] = "Google Profile Picture.png";
-const char kHighResAvatarFolderName[] = "Avatars";
+constexpr char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
+constexpr char kGAIAPictureFileName[] = "Google Profile Picture.png";
+constexpr char kHighResAvatarFolderName[] = "Avatars";
 
 // The size of the function-static kDefaultAvatarIconResources array below.
-const size_t kDefaultAvatarIconsCount = 27;
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+constexpr size_t kDefaultAvatarIconsCount = 38;
+#else
+constexpr size_t kDefaultAvatarIconsCount = 27;
+#endif
 
 // The first 8 icons are generic.
-const size_t kGenericAvatarIconsCount = 8;
+constexpr size_t kGenericAvatarIconsCount = 8;
 
 // The avatar used as a placeholder (grey silhouette).
-const size_t kPlaceholderAvatarIndex = 26;
+constexpr size_t kPlaceholderAvatarIndex = 26;
 
 gfx::Image GetSizedAvatarIcon(const gfx::Image& image,
                               bool is_rectangle,
@@ -291,8 +301,8 @@
 
 gfx::Image GetAvatarIconForWebUI(const gfx::Image& image,
                                  bool is_rectangle) {
-  return GetSizedAvatarIcon(image, is_rectangle,
-                            kAvatarIconWidth, kAvatarIconHeight);
+  return GetSizedAvatarIcon(image, is_rectangle, kAvatarIconSize,
+                            kAvatarIconSize);
 }
 
 gfx::Image GetAvatarIconForTitleBar(const gfx::Image& image,
@@ -300,11 +310,10 @@
                                     int dst_width,
                                     int dst_height) {
   // The image requires no border or resizing.
-  if (!is_gaia_image && image.Height() <= kAvatarIconHeight)
+  if (!is_gaia_image && image.Height() <= kAvatarIconSize)
     return image;
 
-  int size = std::min(std::min(kAvatarIconWidth, kAvatarIconHeight),
-                      std::min(dst_width, dst_height));
+  int size = std::min(kAvatarIconSize, std::min(dst_width, dst_height));
   gfx::Size dst_size(dst_width, dst_height);
 
   // Source for a sized icon drawn at the bottom center of the canvas,
@@ -321,16 +330,17 @@
 SkBitmap GetAvatarIconAsSquare(const SkBitmap& source_bitmap,
                                int scale_factor) {
   SkBitmap square_bitmap;
-  if ((source_bitmap.width() == scale_factor * profiles::kAvatarIconWidth) &&
-      (source_bitmap.height() == scale_factor * profiles::kAvatarIconHeight)) {
-    // Shave a couple of columns so the |source_bitmap| is more square. So when
-    // resized to a square aspect ratio it looks pretty.
-    gfx::Rect frame(scale_factor * profiles::kAvatarIconWidth,
-                    scale_factor * profiles::kAvatarIconHeight);
+  if ((source_bitmap.width() == scale_factor * kOldAvatarIconWidth) &&
+      (source_bitmap.height() == scale_factor * kOldAvatarIconHeight)) {
+    // If |source_bitmap| matches the old avatar icon dimensions, i.e. it's an
+    // old avatar icon, shave a couple of columns so the |source_bitmap| is more
+    // square. So when resized to a square aspect ratio it looks pretty.
+    gfx::Rect frame(scale_factor * profiles::kAvatarIconSize,
+                    scale_factor * profiles::kAvatarIconSize);
     frame.Inset(scale_factor * 2, 0, scale_factor * 2, 0);
     source_bitmap.extractSubset(&square_bitmap, gfx::RectToSkIRect(frame));
   } else {
-    // If not the avatar icon's aspect ratio, the image should be square.
+    // If it's not an old avatar icon, the image should be square.
     DCHECK(source_bitmap.width() == source_bitmap.height());
     square_bitmap = source_bitmap;
   }
@@ -350,6 +360,23 @@
   return kPlaceholderAvatarIndex;
 }
 
+size_t GetModernAvatarIconStartIndex() {
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+  return GetPlaceholderAvatarIndex() + 1;
+#else
+  // Only use the placeholder avatar on ChromeOS and Android.
+  // TODO(crbug.com/937834): Clean up code and remove code dependencies from
+  // Android and ChromeOS. Avatar icons from this file are not used on these
+  // platforms.
+  return GetPlaceholderAvatarIndex();
+#endif
+}
+
+bool IsModernAvatarIconIndex(size_t icon_index) {
+  return icon_index >= GetModernAvatarIconStartIndex() &&
+         icon_index < GetDefaultAvatarIconCount();
+}
+
 int GetPlaceholderAvatarIconResourceID() {
   return IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE;
 }
@@ -361,85 +388,74 @@
 const IconResourceInfo* GetDefaultAvatarIconResourceInfo(size_t index) {
   CHECK_LT(index, kDefaultAvatarIconsCount);
   static const IconResourceInfo resource_info[kDefaultAvatarIconsCount] = {
-      {IDR_PROFILE_AVATAR_0,
-       "avatar_generic.png",
-       IDS_DEFAULT_AVATAR_LABEL_0},
-      {IDR_PROFILE_AVATAR_1,
-       "avatar_generic_aqua.png",
-       IDS_DEFAULT_AVATAR_LABEL_1},
-      {IDR_PROFILE_AVATAR_2,
-       "avatar_generic_blue.png",
-       IDS_DEFAULT_AVATAR_LABEL_2},
-      {IDR_PROFILE_AVATAR_3,
-       "avatar_generic_green.png",
-       IDS_DEFAULT_AVATAR_LABEL_3},
-      {IDR_PROFILE_AVATAR_4,
-       "avatar_generic_orange.png",
-       IDS_DEFAULT_AVATAR_LABEL_4},
-      {IDR_PROFILE_AVATAR_5,
-       "avatar_generic_purple.png",
-       IDS_DEFAULT_AVATAR_LABEL_5},
-      {IDR_PROFILE_AVATAR_6,
-       "avatar_generic_red.png",
-       IDS_DEFAULT_AVATAR_LABEL_6},
-      {IDR_PROFILE_AVATAR_7,
-       "avatar_generic_yellow.png",
-       IDS_DEFAULT_AVATAR_LABEL_7},
-      {IDR_PROFILE_AVATAR_8,
-       "avatar_secret_agent.png",
-       IDS_DEFAULT_AVATAR_LABEL_8},
-      {IDR_PROFILE_AVATAR_9,
-       "avatar_superhero.png",
-       IDS_DEFAULT_AVATAR_LABEL_9},
-      {IDR_PROFILE_AVATAR_10,
-       "avatar_volley_ball.png",
-       IDS_DEFAULT_AVATAR_LABEL_10},
-      {IDR_PROFILE_AVATAR_11,
-       "avatar_businessman.png",
-       IDS_DEFAULT_AVATAR_LABEL_11},
-      {IDR_PROFILE_AVATAR_12,
-       "avatar_ninja.png",
-       IDS_DEFAULT_AVATAR_LABEL_12},
-      {IDR_PROFILE_AVATAR_13,
-       "avatar_alien.png",
-       IDS_DEFAULT_AVATAR_LABEL_13},
-      {IDR_PROFILE_AVATAR_14,
-       "avatar_awesome.png",
-       IDS_DEFAULT_AVATAR_LABEL_14},
-      {IDR_PROFILE_AVATAR_15,
-       "avatar_flower.png",
-       IDS_DEFAULT_AVATAR_LABEL_15},
-      {IDR_PROFILE_AVATAR_16,
-       "avatar_pizza.png",
-       IDS_DEFAULT_AVATAR_LABEL_16},
-      {IDR_PROFILE_AVATAR_17,
-       "avatar_soccer.png",
-       IDS_DEFAULT_AVATAR_LABEL_17},
-      {IDR_PROFILE_AVATAR_18,
-       "avatar_burger.png",
-       IDS_DEFAULT_AVATAR_LABEL_18},
-      {IDR_PROFILE_AVATAR_19,
-       "avatar_cat.png",
-       IDS_DEFAULT_AVATAR_LABEL_19},
-      {IDR_PROFILE_AVATAR_20,
-       "avatar_cupcake.png",
-       IDS_DEFAULT_AVATAR_LABEL_20},
-      {IDR_PROFILE_AVATAR_21,
-       "avatar_dog.png",
-       IDS_DEFAULT_AVATAR_LABEL_21},
-      {IDR_PROFILE_AVATAR_22,
-       "avatar_horse.png",
-       IDS_DEFAULT_AVATAR_LABEL_22},
-      {IDR_PROFILE_AVATAR_23,
-       "avatar_margarita.png",
-       IDS_DEFAULT_AVATAR_LABEL_23},
-      {IDR_PROFILE_AVATAR_24,
-       "avatar_note.png",
-       IDS_DEFAULT_AVATAR_LABEL_24},
-      {IDR_PROFILE_AVATAR_25,
-       "avatar_sun_cloud.png",
-       IDS_DEFAULT_AVATAR_LABEL_25},
-      {IDR_PROFILE_AVATAR_26, NULL, -1},
+    // Old avatar icons:
+    {IDR_PROFILE_AVATAR_0, "avatar_generic.png", IDS_DEFAULT_AVATAR_LABEL_0},
+    {IDR_PROFILE_AVATAR_1, "avatar_generic_aqua.png",
+     IDS_DEFAULT_AVATAR_LABEL_1},
+    {IDR_PROFILE_AVATAR_2, "avatar_generic_blue.png",
+     IDS_DEFAULT_AVATAR_LABEL_2},
+    {IDR_PROFILE_AVATAR_3, "avatar_generic_green.png",
+     IDS_DEFAULT_AVATAR_LABEL_3},
+    {IDR_PROFILE_AVATAR_4, "avatar_generic_orange.png",
+     IDS_DEFAULT_AVATAR_LABEL_4},
+    {IDR_PROFILE_AVATAR_5, "avatar_generic_purple.png",
+     IDS_DEFAULT_AVATAR_LABEL_5},
+    {IDR_PROFILE_AVATAR_6, "avatar_generic_red.png",
+     IDS_DEFAULT_AVATAR_LABEL_6},
+    {IDR_PROFILE_AVATAR_7, "avatar_generic_yellow.png",
+     IDS_DEFAULT_AVATAR_LABEL_7},
+    {IDR_PROFILE_AVATAR_8, "avatar_secret_agent.png",
+     IDS_DEFAULT_AVATAR_LABEL_8},
+    {IDR_PROFILE_AVATAR_9, "avatar_superhero.png", IDS_DEFAULT_AVATAR_LABEL_9},
+    {IDR_PROFILE_AVATAR_10, "avatar_volley_ball.png",
+     IDS_DEFAULT_AVATAR_LABEL_10},
+    {IDR_PROFILE_AVATAR_11, "avatar_businessman.png",
+     IDS_DEFAULT_AVATAR_LABEL_11},
+    {IDR_PROFILE_AVATAR_12, "avatar_ninja.png", IDS_DEFAULT_AVATAR_LABEL_12},
+    {IDR_PROFILE_AVATAR_13, "avatar_alien.png", IDS_DEFAULT_AVATAR_LABEL_13},
+    {IDR_PROFILE_AVATAR_14, "avatar_awesome.png", IDS_DEFAULT_AVATAR_LABEL_14},
+    {IDR_PROFILE_AVATAR_15, "avatar_flower.png", IDS_DEFAULT_AVATAR_LABEL_15},
+    {IDR_PROFILE_AVATAR_16, "avatar_pizza.png", IDS_DEFAULT_AVATAR_LABEL_16},
+    {IDR_PROFILE_AVATAR_17, "avatar_soccer.png", IDS_DEFAULT_AVATAR_LABEL_17},
+    {IDR_PROFILE_AVATAR_18, "avatar_burger.png", IDS_DEFAULT_AVATAR_LABEL_18},
+    {IDR_PROFILE_AVATAR_19, "avatar_cat.png", IDS_DEFAULT_AVATAR_LABEL_19},
+    {IDR_PROFILE_AVATAR_20, "avatar_cupcake.png", IDS_DEFAULT_AVATAR_LABEL_20},
+    {IDR_PROFILE_AVATAR_21, "avatar_dog.png", IDS_DEFAULT_AVATAR_LABEL_21},
+    {IDR_PROFILE_AVATAR_22, "avatar_horse.png", IDS_DEFAULT_AVATAR_LABEL_22},
+    {IDR_PROFILE_AVATAR_23, "avatar_margarita.png",
+     IDS_DEFAULT_AVATAR_LABEL_23},
+    {IDR_PROFILE_AVATAR_24, "avatar_note.png", IDS_DEFAULT_AVATAR_LABEL_24},
+    {IDR_PROFILE_AVATAR_25, "avatar_sun_cloud.png",
+     IDS_DEFAULT_AVATAR_LABEL_25},
+
+    // Placeholder avatar icon:
+    {IDR_PROFILE_AVATAR_26, NULL, -1},
+
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
+    // Modern avatar icons:
+    {IDR_PROFILE_AVATAR_27, "avatar_origami_cat.png",
+     IDS_DEFAULT_AVATAR_LABEL_27},
+    {IDR_PROFILE_AVATAR_28, "avatar_origami_corgi.png",
+     IDS_DEFAULT_AVATAR_LABEL_28},
+    {IDR_PROFILE_AVATAR_29, "avatar_origami_dragon.png",
+     IDS_DEFAULT_AVATAR_LABEL_29},
+    {IDR_PROFILE_AVATAR_30, "avatar_origami_elephant.png",
+     IDS_DEFAULT_AVATAR_LABEL_30},
+    {IDR_PROFILE_AVATAR_31, "avatar_origami_fox.png",
+     IDS_DEFAULT_AVATAR_LABEL_31},
+    {IDR_PROFILE_AVATAR_32, "avatar_origami_monkey.png",
+     IDS_DEFAULT_AVATAR_LABEL_32},
+    {IDR_PROFILE_AVATAR_33, "avatar_origami_panda.png",
+     IDS_DEFAULT_AVATAR_LABEL_33},
+    {IDR_PROFILE_AVATAR_34, "avatar_origami_penguin.png",
+     IDS_DEFAULT_AVATAR_LABEL_34},
+    {IDR_PROFILE_AVATAR_35, "avatar_origami_pinkbutterfly.png",
+     IDS_DEFAULT_AVATAR_LABEL_35},
+    {IDR_PROFILE_AVATAR_36, "avatar_origami_rabbit.png",
+     IDS_DEFAULT_AVATAR_LABEL_36},
+    {IDR_PROFILE_AVATAR_37, "avatar_origami_unicorn.png",
+     IDS_DEFAULT_AVATAR_LABEL_37},
+#endif
   };
   return &resource_info[index];
 }
@@ -495,19 +511,20 @@
   return false;
 }
 
-std::unique_ptr<base::ListValue> GetDefaultProfileAvatarIconsAndLabels() {
+std::unique_ptr<base::ListValue> GetDefaultProfileAvatarIconsAndLabels(
+    size_t selected_avatar_idx) {
   std::unique_ptr<base::ListValue> avatars(new base::ListValue());
 
-  const size_t placeholder_avatar_index = profiles::GetPlaceholderAvatarIndex();
-  for (size_t i = 0; i < profiles::GetDefaultAvatarIconCount() &&
-                     i != placeholder_avatar_index;
-       ++i) {
+  for (size_t i = GetModernAvatarIconStartIndex();
+       i < GetDefaultAvatarIconCount(); ++i) {
     std::unique_ptr<base::DictionaryValue> avatar_info(
         new base::DictionaryValue());
     avatar_info->SetString("url", profiles::GetDefaultAvatarIconUrl(i));
     avatar_info->SetString(
         "label", l10n_util::GetStringUTF16(
                      profiles::GetDefaultAvatarLabelResourceIDAtIndex(i)));
+    if (i == selected_avatar_idx)
+      avatar_info->SetBoolean("selected", true);
 
     avatars->Append(std::move(avatar_info));
   }
diff --git a/chrome/browser/profiles/profile_avatar_icon_util.h b/chrome/browser/profiles/profile_avatar_icon_util.h
index dec4541..c2784892 100644
--- a/chrome/browser/profiles/profile_avatar_icon_util.h
+++ b/chrome/browser/profiles/profile_avatar_icon_util.h
@@ -31,8 +31,7 @@
 extern const char kHighResAvatarFolderName[];
 
 // Avatar formatting.
-extern const int kAvatarIconWidth;
-extern const int kAvatarIconHeight;
+extern const int kAvatarIconSize;
 extern const SkColor kAvatarTutorialBackgroundColor;
 extern const SkColor kAvatarTutorialContentTextColor;
 extern const SkColor kAvatarBubbleAccountsBackgroundColor;
@@ -83,6 +82,13 @@
 // Gets the index for the (grey silhouette) avatar used as a placeholder.
 size_t GetPlaceholderAvatarIndex();
 
+// Gets the start index of the modern profile avatar icons.
+size_t GetModernAvatarIconStartIndex();
+
+// Returns whether |icon_index| corresponds to one of the modern profile avatar
+// icons.
+bool IsModernAvatarIconIndex(size_t icon_index);
+
 // Gets the resource ID of the placeholder avatar icon.
 int GetPlaceholderAvatarIconResourceID();
 
@@ -113,8 +119,10 @@
 
 // Returns a list of dictionaries containing the default profile avatar icons as
 // well as avatar labels used for accessibility purposes. The list is ordered
-// according to the avatars' default order.
-std::unique_ptr<base::ListValue> GetDefaultProfileAvatarIconsAndLabels();
+// according to the avatars' default order. If |selected_avatar_idx| is one of
+// the available indices, the corresponding avatar is marked as selected.
+std::unique_ptr<base::ListValue> GetDefaultProfileAvatarIconsAndLabels(
+    size_t selected_avatar_idx = SIZE_MAX);
 
 // This method tries to find a random avatar index that is not in
 // |used_icon_indices|. If there is no such index, a random index is returned.
diff --git a/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc b/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc
index 02655cfa4..1eff1ea 100644
--- a/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc
+++ b/chrome/browser/profiles/profile_avatar_icon_util_unittest.cc
@@ -64,7 +64,7 @@
 
   // Test that a rectangular picture is changed.
   gfx::Image rect_picture(gfx::test::CreateImage());
-  gfx::Size size(profiles::kAvatarIconWidth, profiles::kAvatarIconHeight);
+  gfx::Size size(profiles::kAvatarIconSize, profiles::kAvatarIconSize);
   gfx::Image result2 = profiles::GetAvatarIconForWebUI(rect_picture, true);
 
   VerifyScaling(result2, size);
diff --git a/chrome/browser/profiles/profile_metrics.cc b/chrome/browser/profiles/profile_metrics.cc
index 91b9c758..6b771c5 100644
--- a/chrome/browser/profiles/profile_metrics.cc
+++ b/chrome/browser/profiles/profile_metrics.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_avatar_icon_util.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/chrome_signin_helper.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -236,7 +237,6 @@
 }
 
 void ProfileMetrics::LogProfileAvatarSelection(size_t icon_index) {
-  DCHECK(icon_index < NUM_PROFILE_AVATAR_METRICS);
   ProfileAvatar icon_name = AVATAR_UNKNOWN;
   switch (icon_index) {
     case 0:
@@ -320,11 +320,12 @@
     case 26:
       icon_name = AVATAR_PLACEHOLDER;
       break;
-    case 28:
+    case SIZE_MAX:
       icon_name = AVATAR_GAIA;
       break;
-    default:  // We should never actually get here.
-      NOTREACHED();
+    default:
+      DCHECK(profiles::IsModernAvatarIconIndex(icon_index));
+      // TODO(crbug.com/937834): Log modern avatar selection.
       break;
   }
   UMA_HISTOGRAM_ENUMERATION("Profile.Avatar", icon_name,
@@ -391,7 +392,7 @@
 
 void ProfileMetrics::LogProfileSwitchGaia(ProfileGaia metric) {
   if (metric == GAIA_OPT_IN)
-    LogProfileAvatarSelection(AVATAR_GAIA);
+    LogProfileAvatarSelection(SIZE_MAX);
   UMA_HISTOGRAM_ENUMERATION("Profile.SwitchGaiaPhotoSettings",
                             metric,
                             NUM_PROFILE_GAIA_METRICS);
diff --git a/chrome/browser/profiles/profile_shortcut_manager_win.cc b/chrome/browser/profiles/profile_shortcut_manager_win.cc
index 6466c068..e1fd9ec 100644
--- a/chrome/browser/profiles/profile_shortcut_manager_win.cc
+++ b/chrome/browser/profiles/profile_shortcut_manager_win.cc
@@ -86,10 +86,10 @@
 // Incrementing this number will cause profile icons to be regenerated on
 // profile startup (it should be incremented whenever the product/avatar icons
 // change, etc).
-const int kCurrentProfileIconVersion = 5;
+const int kCurrentProfileIconVersion = 6;
 
-// 2x sized profile avatar icons. Mirrors |kDefaultAvatarIconResources| in
-// profile_info_cache.cc.
+// 2x sized versions of the old profile avatar icons.
+// TODO(crbug.com/937834): Clean this up.
 const int kProfileAvatarIconResources2x[] = {
     IDR_PROFILE_AVATAR_2X_0,  IDR_PROFILE_AVATAR_2X_1,
     IDR_PROFILE_AVATAR_2X_2,  IDR_PROFILE_AVATAR_2X_3,
@@ -807,9 +807,6 @@
 
 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager)
     : profile_manager_(manager) {
-  DCHECK_EQ(base::size(kProfileAvatarIconResources2x),
-            profiles::GetDefaultAvatarIconCount());
-
   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
                  content::NotificationService::AllSources());
 
@@ -999,11 +996,18 @@
       const size_t icon_index = entry->GetAvatarIconIndex();
       const int resource_id_1x =
           profiles::GetDefaultAvatarIconResourceIDAtIndex(icon_index);
-      const int resource_id_2x = kProfileAvatarIconResources2x[icon_index];
-      // Make a copy of the SkBitmaps to ensure that we can safely use the image
-      // data on the thread we post to.
+      // Make a copy of the SkBitmap to ensure that we can safely use the
+      // image data on the thread we post to.
       params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x);
-      params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x);
+
+      if (profiles::IsModernAvatarIconIndex(icon_index)) {
+        // Modern avatars are large(192px) by default, which makes them big
+        // enough for 2x.
+        params.avatar_image_2x = params.avatar_image_1x;
+      } else {
+        const int resource_id_2x = kProfileAvatarIconResources2x[icon_index];
+        params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x);
+      }
     }
   }
   base::CreateCOMSTATaskRunnerWithTraits({base::MayBlock()})
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
index 760c787..d58a115 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
@@ -115,11 +115,6 @@
   cca.state.set('max-wnd', fullWindow);
   cca.state.set('tall', tall);
 
-  var [letterboxW, letterboxH] = this.updatePreviewSize_(fullWindow);
-  var [halfW, halfH] = [letterboxW / 2, letterboxH / 2];
-  var [rightBox, bottomBox, leftBox, topBox] = [halfW, halfH, halfW, halfH];
-
-  // Shift preview to accommodate the shutter in letterbox if applicable.
   var dimens = (shutter) => {
     // These following constants need kept in sync with relevant values in css.
     // preset: button-size + preset-margin + min-margin
@@ -128,18 +123,22 @@
     // baseline: preset-baseline
     return shutter ? [100, 88, 12, 56] : [76, 56, 20, 48];
   };
+
+  var [letterboxW, letterboxH] = this.updatePreviewSize_(fullWindow);
+  var [halfW, halfH] = [letterboxW / 2, letterboxH / 2];
+
+  // Shift preview to accommodate the shutter in letterbox if applicable.
   var accommodate = (measure) => {
     var [, leastShutter] = dimens(true);
     return (measure > leastShutter) && (measure < leastShutter * 2);
   };
-  if (cca.state.set('shift-preview-left',
-      fullWindow && tabletLandscape && accommodate(letterboxW))) {
-    [rightBox, leftBox] = [letterboxW, 0];
-  }
-  if (cca.state.set('shift-preview-top',
-      fullWindow && !tabletLandscape && accommodate(letterboxH))) {
-    [bottomBox, topBox] = [letterboxH, 0];
-  }
+  var cond = fullWindow && tabletLandscape && accommodate(letterboxW);
+  var [rightBox, leftBox] = cond ? [letterboxW, 0] : [halfW, halfW];
+  cca.state.set('shift-preview-left', cond);
+
+  cond = fullWindow && !tabletLandscape && accommodate(letterboxH);
+  var [bottomBox, topBox] = cond ? [letterboxH, 0] : [halfH, halfH];
+  cca.state.set('shift-preview-top', cond);
 
   // Shift buttons' stripes if necessary. Buttons are either fully on letterbox
   // or preview while the shutter/options keep minimum margin to either edges.
@@ -150,11 +149,12 @@
   };
   var shift = (stripe, name, measure, shutter) => {
     var [preset, least, gap, baseline] = dimens(shutter);
-    if (cca.state.set('shift-' + name + '-stripe',
-        measure > gap && measure < preset)) {
+    cond = measure > gap && measure < preset;
+    if (cond) {
       baseline = calc(measure, least);
       stripe.setProperty(name, baseline + 'px');
     }
+    cca.state.set('shift-' + name + '-stripe', cond);
     // Return shutter's baseline in letterbox if applicable.
     return (shutter && baseline < measure) ? baseline : 0;
   };
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
index 35857cd..5cb13f8 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
@@ -228,9 +228,11 @@
   /**
    * @override
    */
-  navigateToRange: function(range, opt_focus, opt_speechProps) {
+  navigateToRange: function(
+      range, opt_focus, opt_speechProps, opt_skipSettingSelection) {
     opt_focus = opt_focus === undefined ? true : opt_focus;
     opt_speechProps = opt_speechProps || {};
+    opt_skipSettingSelection = opt_skipSettingSelection || false;
     var prevRange = this.currentRange_;
 
     // Specialization for math output.
@@ -249,7 +251,8 @@
     var selectedRange;
     var msg;
 
-    if (this.pageSel_ && this.pageSel_.isValid() && range.isValid()) {
+    if (this.pageSel_ && this.pageSel_.isValid() && range.isValid() &&
+        !opt_skipSettingSelection) {
       // Suppress hints.
       o.withoutHints();
 
@@ -291,7 +294,7 @@
         if (this.pageSel_)
           this.pageSel_.select();
       }
-    } else {
+    } else if (!opt_skipSettingSelection) {
       // Ensure we don't select the editable when we first encounter it.
       var lca = null;
       if (range.start.node && prevRange.start.node) {
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
index f711c2a..20df0b4 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
@@ -1922,33 +1922,3 @@
       .replay();
   });
 });
-
-TEST_F('ChromeVoxBackgroundTest', 'AutoCompleteChanged', function() {
-  var mockFeedback = this.createMockFeedback();
-  this.runWithLoadedTree(function() {/*
-    <div><input type="text" aria-autocomplete="off"></input></div>
-    <script>
-      var div = document.querySelector('div');
-      var input = div.firstElementChild;
-      var completion = input.getAttribute('aria-autocomplete');
-      div.addEventListener('click', () => {
-        completion = completion == 'off' ? 'both' : 'off';
-        input.setAttribute('aria-autocomplete', completion);
-      });
-    </script>
-  */}, function(root) {
-    var change = root.firstChild.doDefault.bind(root.firstChild);
-    mockFeedback.expectSpeech('Edit text')
-        .clearPendingOutput()
-        .call(change)
-        .expectSpeech('Edit text')
-        .call(change)
-        .expectSpeech('Edit text')
-        .call(change)
-        .expectNextSpeechUtteranceIsNot(
-            'Press up or down arrow for auto completions.')
-        .expectSpeech('Edit text')
-        .expectSpeech('Press up or down arrow for auto completions.')
-        .replay();
-  });
-});
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/chromevox_state.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/chromevox_state.js
index e635d8b..576336e 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/chromevox_state.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/chromevox_state.js
@@ -78,6 +78,8 @@
    * @param {!cursors.Range} range The new range.
    * @param {boolean=} opt_focus Focus the range; defaults to true.
    * @param {Object=} opt_speechProps Speech properties.
+   * @param {boolean=} opt_skipSettingSelection If true, does not set
+   *     the selection, otherwise it does by default.
    */
   navigateToRange: goog.abstractMethod,
 
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
index 09b80f93..54527b7e 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
@@ -297,6 +297,7 @@
   var skipSync = false;
   var didNavigate = false;
   var tryScrolling = true;
+  var skipSettingSelection = false;
   switch (command) {
     case 'nextCharacter':
       didNavigate = true;
@@ -379,10 +380,12 @@
       dir = Dir.BACKWARD;
       pred = AutomationPredicate.image;
       predErrorMsg = 'no_previous_graphic';
+      skipSettingSelection = true;
       break;
     case 'nextGraphic':
       pred = AutomationPredicate.image;
       predErrorMsg = 'no_next_graphic';
+      skipSettingSelection = true;
       break;
     case 'nextHeading':
       pred = AutomationPredicate.heading;
@@ -1024,8 +1027,10 @@
     }
   }
 
-  if (current)
-    ChromeVoxState.instance.navigateToRange(current, undefined, speechProps);
+  if (current) {
+    ChromeVoxState.instance.navigateToRange(
+        current, undefined, speechProps, skipSettingSelection);
+  }
 
   return false;
 };
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
index 641a4bf2..cd92f93 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
@@ -230,9 +230,10 @@
    * @param {!AutomationEvent} evt
    */
   onAriaAttributeChanged: function(evt) {
-    // Don't report changes on non-focused, richly editables.
-    if (evt.target.state[StateType.RICHLY_EDITABLE] &&
-        !evt.target.state[StateType.FOCUSED])
+    // Don't report changes on editable nodes since they interfere with text
+    // selection changes. Users can query via Search+k for the current state of
+    // the text field (which would also report the entire value).
+    if (evt.target.state[StateType.EDITABLE])
       return;
 
     // Only report attribute changes on some *Option roles if it is selected.
diff --git a/chrome/browser/resources/chromeos/login/apps_menu.js b/chrome/browser/resources/chromeos/login/apps_menu.js
deleted file mode 100644
index cec900db..0000000
--- a/chrome/browser/resources/chromeos/login/apps_menu.js
+++ /dev/null
@@ -1,170 +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.
-
-/**
- * @fileoverview Kiosk apps menu implementation.
- */
-
-cr.define('login', function() {
-  'use strict';
-
-  var Menu = cr.ui.Menu;
-  var MenuButton = cr.ui.MenuButton;
-
-  /**
-   * Creates apps menu button.
-   * @constructor
-   * @extends {cr.ui.MenuButton}
-   */
-  var AppsMenuButton = cr.ui.define('button');
-
-  AppsMenuButton.prototype = {
-    __proto__: MenuButton.prototype,
-
-    /**
-     * Flag of whether to rebuild the menu.
-     * @type {boolean}
-     * @private
-     */
-    needsRebuild_: true,
-
-    /**
-     * Array to hold apps info.
-     * @type {Array}
-     */
-    data_: null,
-    get data() {
-      return this.data_;
-    },
-    set data(data) {
-      this.data_ = data;
-      this.needsRebuild_ = true;
-    },
-
-    /** @override */
-    decorate: function() {
-      MenuButton.prototype.decorate.call(this);
-      this.menu = new Menu;
-      cr.ui.decorate(this.menu, Menu);
-      document.body.appendChild(this.menu);
-
-      this.anchorType = cr.ui.AnchorType.ABOVE;
-      chrome.send('initializeKioskApps');
-    },
-
-    /** @override */
-    showMenu: function(shouldSetFocus) {
-      if (this.needsRebuild_) {
-        this.menu.textContent = '';
-        this.data_.forEach(this.addItem_, this);
-        this.needsRebuild_ = false;
-      }
-
-      if (this.data.length > 0)
-        MenuButton.prototype.showMenu.apply(this, arguments);
-    },
-
-    /**
-     * Invoked when apps menu becomes visible.
-     */
-    didShow: function() {
-      window.setTimeout(function() {
-        if (!$('apps-header-bar-item').hidden)
-          chrome.send('checkKioskAppLaunchError');
-      }, 500);
-    },
-
-    findAndRunAppForTesting: function(id, opt_diagnostic_mode) {
-      for (var i = 0; i < this.data.length; i++) {
-        if (this.data[i].id == id) {
-          this.launchApp_(this.data[i], !!opt_diagnostic_mode);
-          break;
-        }
-      }
-    },
-
-    /**
-     * Launch the app. If |diagnosticMode| is true, ask user to confirm.
-     * @param {Object} app App data.
-     * @param {boolean} diagnosticMode Whether to run the app in diagnostic
-     *     mode.
-     */
-    launchApp_: function(app, diagnosticMode) {
-      if (app.isAndroidApp) {
-        chrome.send('launchArcKioskApp', [app.account_email]);
-        return;
-      }
-      if (!diagnosticMode) {
-        chrome.send('launchKioskApp', [app.id, false]);
-        return;
-      }
-
-      if (!this.confirmDiagnosticMode_) {
-        this.confirmDiagnosticMode_ =
-            new cr.ui.dialogs.ConfirmDialog(document.body);
-        this.confirmDiagnosticMode_.setOkLabel(
-            loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
-        this.confirmDiagnosticMode_.setCancelLabel(
-            loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
-      }
-
-      this.confirmDiagnosticMode_.show(
-          loadTimeData.getStringF(
-              'confirmKioskAppDiagnosticModeFormat', app.label),
-          function() {
-            chrome.send('launchKioskApp', [app.id, true]);
-          });
-    },
-
-    /**
-     * Adds an app to the menu.
-     * @param {Object} app An app info object.
-     * @private
-     */
-    addItem_: function(app) {
-      var menuItem = this.menu.addMenuItem(app);
-      menuItem.classList.add('apps-menu-item');
-      menuItem.addEventListener('activate', function(e) {
-        var diagnosticMode = e.originalEvent && e.originalEvent.ctrlKey;
-        this.launchApp_(app, diagnosticMode);
-      }.bind(this));
-    }
-  };
-
-  /**
-   * Sets apps to be displayed in the apps menu.
-   * @param {!Array<!Object>} apps An array of app info objects.
-   */
-  AppsMenuButton.setApps = function(apps) {
-    $('show-apps-button').data = apps;
-    $('login-header-bar').hasApps =
-        apps.length > 0 || loadTimeData.getBoolean('kioskAppHasLaunchError');
-    chrome.send('kioskAppsLoaded');
-  };
-
-  /**
-   * Shows the given error message.
-   * @param {!string} message Error message to show.
-   */
-  AppsMenuButton.showError = function(message) {
-    /** @const */ var BUBBLE_OFFSET = 25;
-    /** @const */ var BUBBLE_PADDING = 12;
-    $('bubble').showTextForElement(
-        $('show-apps-button'), message, cr.ui.Bubble.Attachment.TOP,
-        BUBBLE_OFFSET, BUBBLE_PADDING);
-  };
-
-
-  /**
-   * Runs app with a given id from the list of loaded apps.
-   * @param {!string} id of an app to run.
-   * @param {boolean=} opt_diagnostic_mode Whether to run the app in diagnostic
-   *     mode.  Default is false.
-   */
-  AppsMenuButton.runAppForTesting = function(id, opt_diagnostic_mode) {
-    $('show-apps-button').findAndRunAppForTesting(id, opt_diagnostic_mode);
-  };
-
-  return {AppsMenuButton: AppsMenuButton};
-});
diff --git a/chrome/browser/resources/chromeos/login/cr_ui.js b/chrome/browser/resources/chromeos/login/cr_ui.js
index a143d1d..0636594 100644
--- a/chrome/browser/resources/chromeos/login/cr_ui.js
+++ b/chrome/browser/resources/chromeos/login/cr_ui.js
@@ -81,34 +81,10 @@
   Oobe.showOobeUI = function(showOobe) {
     if (showOobe) {
       document.body.classList.add('oobe-display');
-
-      // Callback to animate the header bar in.
-      var showHeaderBar = function() {
-        login.HeaderBar.animateIn(false, function() {
-          chrome.send('headerBarVisible');
-        });
-      };
-      // Start asynchronously so the OOBE welcome screen comes in first.
-      window.setTimeout(showHeaderBar, HEADER_BAR_DELAY_MS);
     } else {
       document.body.classList.remove('oobe-display');
       Oobe.getInstance().prepareForLoginDisplay_();
-      // Ensure header bar is visible when switching to Login UI from oobe.
-      if (Oobe.getInstance().displayType == DISPLAY_TYPE.OOBE)
-        login.HeaderBar.animateIn(true);
     }
-
-    Oobe.getInstance().headerHidden = false;
-  };
-
-  /**
-   * When |showShutdown| is set to "true", the shutdown button is shown and the
-   * reboot button hidden. If set to "false", the reboot button is visible and
-   * the shutdown button hidden.
-   */
-  Oobe.showShutdown = function(showShutdown) {
-    $('login-header-bar').showShutdownButton = showShutdown;
-    $('login-header-bar').showRebootButton = !showShutdown;
   };
 
   /**
@@ -381,13 +357,6 @@
   };
 
   /**
-   * Shows/hides login UI control bar with buttons like [Shut down].
-   */
-  Oobe.showControlBar = function(show) {
-    Oobe.getInstance().headerHidden = !show;
-  };
-
-  /**
    * Changes some UI which depends on the virtual keyboard being shown/hidden.
    */
   Oobe.setVirtualKeyboardShown = function(shown) {
diff --git a/chrome/browser/resources/chromeos/login/discover/discover_app.js b/chrome/browser/resources/chromeos/login/discover/discover_app.js
index 7e634d3..2dc0cd6 100644
--- a/chrome/browser/resources/chromeos/login/discover/discover_app.js
+++ b/chrome/browser/resources/chromeos/login/discover/discover_app.js
@@ -33,7 +33,6 @@
     setClientAreaSize: function(data) {},
     showAPIKeysNotice: function(data) {},
     showOobeUI: function(data) {},
-    showShutdown: function(data) {},
     showVersion: function(data) {},
     updateDeviceRequisition: function(data) {},
     updateOobeConfiguration: function(data) {},
diff --git a/chrome/browser/resources/chromeos/login/encryption_migration.css b/chrome/browser/resources/chromeos/login/encryption_migration.css
index 1acb3be..df67332 100644
--- a/chrome/browser/resources/chromeos/login/encryption_migration.css
+++ b/chrome/browser/resources/chromeos/login/encryption_migration.css
@@ -4,7 +4,7 @@
 
 oobe-dialog {
   height: 640px;
-  max-height: calc(100vh - 47px);  /* Subtracting #login-header-bar's height. */
+  max-height: calc(100vh - 56px);  /* Subtracting LoginShelfView's height. */
   max-width: 100vw;
   min-height: 0 !important;  /* Making <oobe-dialog> shrinkable. */
   min-width: 0 !important;  /* Making <oobe-dialog> shrinkable. */
diff --git a/chrome/browser/resources/chromeos/login/md_header_bar.css b/chrome/browser/resources/chromeos/login/md_header_bar.css
deleted file mode 100644
index 73bfaf3..0000000
--- a/chrome/browser/resources/chromeos/login/md_header_bar.css
+++ /dev/null
@@ -1,132 +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. */
-
-#login-header-bar {
-  bottom: 0;
-  left: 0;
-  min-height: 34px;  /* Should be consistent with .header-bar-item's height. */
-  padding-bottom: 6px;
-  position: absolute;
-  right: 0;
-}
-
-#login-header-bar {
-  padding-top: 7px;
-}
-
-#login-header-bar.translucent-background {
-  background-color: rgba(0, 0, 0, 0.9);
-}
-
-html[screen=lock] .login-header-bar-hidden,
-html[screen=oobe] .login-header-bar-hidden {
-  opacity: 0;
-}
-
-html[screen=lock] .login-header-bar-animate-fast,
-html[screen=oobe] .login-header-bar-animate-fast {
-  transition: opacity 200ms ease-out;
-}
-
-html[screen=lock] .login-header-bar-animate-slow,
-html[screen=oobe] .login-header-bar-animate-slow {
-  transition: opacity 2s ease-out;
-}
-
-#login-header-bar button,
-#login-header-bar button:active,
-#login-header-bar button:focus,
-#login-header-bar button:hover {
-  background: transparent none;
-  box-shadow: none;
-  cursor: pointer;
-  height: 34px;
-  margin: 0;
-  min-width: 0;
-  opacity: 0.6;
-  padding: 5px 8px;
-  vertical-align: middle;
-}
-
-#login-header-bar button {
-  color: white !important;
-}
-
-html[full-screen-dialog] #login-header-bar button {
-  color: var(--google-grey-600) !important;
-}
-
-#login-header-bar hd-iron-icon {
-  --iron-icon-fill-color: white;
-  --iron-icon-height: 20px;
-  --iron-icon-width: 20px;
-  padding-inline-end: 8px;
-}
-
-html[full-screen-dialog] #login-header-bar hd-iron-icon {
-  --iron-icon-fill-color: var(--google-grey-600);
-}
-
-#login-header-bar button:not(.button-restricted),
-#login-header-bar button:active:not(.button-restricted),
-#login-header-bar button:focus:not(.button-restricted),
-#login-header-bar button:hover:not(.button-restricted) {
-  opacity: 1 !important;
-}
-
-.header-bar-item {
-  height: 34px;
-  z-index: 3; /* Stays above the scrollable container. */
-}
-
-.add-supervised-user-menu {
-  display: none;
-}
-
-#more-settings-header-bar-item.active button.add-supervised-user-menu {
-  background-color: white;
-  border: 1px solid lightgray;
-  border-radius: 2px;
-  bottom: 0;
-  color: black !important;
-  display: block;
-  font-size: 13px;
-  height: auto;
-  min-height: 34px;
-  padding: 0 16px;
-  position: absolute;
-  text-align: center;
-  white-space: nowrap;
-  z-index: 3;
-}
-
-#more-settings-header-bar-item.active button.add-supervised-user-menu:focus {
-  font-weight: bold;
-}
-
-html[dir=rtl] .header-bar-item {
-  background-position: right center;
-}
-
-#login-header-bar #shutdown-button,
-#login-header-bar #restart-button,
-#login-header-bar #add-user-button,
-#login-header-bar #guest-user-button,
-#login-header-bar #cancel-multiple-sign-in-button {
-  padding: 0 12px;
-}
-
-#login-header-bar #more-settings-button {
-  padding: 0 10px 0 18px;
-}
-
-#login-header-bar #more-settings-header-bar-item {
-  position: relative;
-}
-
-.button-restricted {
-  border: 1px solid transparent;
-  color: white !important;
-  opacity: 0.4 !important;
-}
diff --git a/chrome/browser/resources/chromeos/login/md_header_bar.html b/chrome/browser/resources/chromeos/login/md_header_bar.html
deleted file mode 100644
index 4b17f48..0000000
--- a/chrome/browser/resources/chromeos/login/md_header_bar.html
+++ /dev/null
@@ -1,162 +0,0 @@
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
-
-<iron-iconset-svg name="header-bar-10" size="10">
-  <svg>
-    <defs>
-      <g id="show-apps" fill-rule="evenodd">
-        <path d="M1,3 L3,3 L3,1 L1,1 L1,3 Z M4,9 L6,9 L6,7 L4,7 L4,9 Z M1,9 L3,9 L3,7 L1,7 L1,9 Z M1,6 L3,6 L3,4 L1,4 L1,6 Z M4,6 L6,6 L6,4 L4,4 L4,6 Z M7,1 L7,3 L9,3 L9,1 L7,1 Z M4,3 L6,3 L6,1 L4,1 L4,3 Z M7,6 L9,6 L9,4 L7,4 L7,6 Z M7,9 L9,9 L9,7 L7,7 L7,9 Z">
-        </path>
-      </g>
-    </defs>
-  </svg>
-</iron-iconset-svg>
-
-<iron-iconset-svg name="header-bar-20" size="20">
-  <svg>
-    <defs>
-      <g id="shutdown" fill-rule="evenodd">
-        <path d="M10.834 2.5H9.167v8.334h1.667V2.5zm4.025 1.808l-1.185 1.184C14.99 6.55 15.834 8.175 15.834 10c0 3.225-2.61 5.834-5.834 5.834-3.225 0-5.833-2.61-5.833-5.834 0-1.825.842-3.45 2.15-4.516L5.14 4.31C3.526 5.683 2.5 7.716 2.5 9.998c0 4.141 3.358 7.5 7.5 7.5 4.141 0 7.5-3.358 7.5-7.5 0-2.284-1.025-4.316-2.642-5.69z">
-        </path>
-      </g>
-      <g id="show-apps" fill-rule="evenodd">
-        <path d="M3,7 L7,7 L7,3 L3,3 L3,7 Z M8,17 L12,17 L12,13 L8,13 L8,17 Z M3,17 L7,17 L7,13 L3,13 L3,17 Z M3,12 L7,12 L7,8 L3,8 L3,12 Z M8,12 L12,12 L12,8 L8,8 L8,12 Z M13,3 L13,7 L17,7 L17,3 L13,3 Z M8,7 L12,7 L12,3 L8,3 L8,7 Z M13,12 L17,12 L17,8 L13,8 L13,12 Z M13,17 L17,17 L17,13 L13,13 L13,17 Z">
-        </path>
-      </g>
-      <g id="browse-as-guest" fill-rule="evenodd">
-        <path d="M10 2c-4.416 0-8 3.584-8 8s3.584 8 8 8 8-3.584 8-8-3.584-8-8-8zm0 2.4c1.328 0 2.4 1.072 2.4 2.4 0 1.328-1.072 2.4-2.4 2.4-1.328 0-2.4-1.072-2.4-2.4 0-1.328 1.072-2.4 2.4-2.4zm-5 8.79c.025-1.74 3.334-2.69 5-2.69 1.66 0 4.975.952 5 2.69-1.075 1.693-2.916 2.81-5 2.81-2.083 0-3.925-1.117-5-2.81z">
-        </path>
-      </g>
-      <g id="add-person" fill-rule="evenodd">
-        <path d="M7.6 9.8c-.552 0-1 .448-1 1s.448 1 1 1 1-.448 1-1-.448-1-1-1zm4.8 0c-.552 0-1 .448-1 1s.448 1 1 1 1-.448 1-1-.448-1-1-1zM10 2c-4.416 0-8 3.584-8 8s3.584 8 8 8 8-3.584 8-8-3.584-8-8-8zm0 14.4c-3.528 0-6.4-2.872-6.4-6.4 0-.232.016-.464.04-.688 1.888-.84 3.384-2.384 4.168-4.296C9.256 7.064 11.64 8.4 14.336 8.4c.624 0 1.224-.072 1.8-.208.168.568.264 1.176.264 1.808 0 3.528-2.872 6.4-6.4 6.4z">
-        </path>
-      </g>
-      <g id="more-settings" fill-rule="evenodd">
-        <path d="M10 6c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zm0 6c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zm0 6c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z">
-        </path>
-      </g>
-      <g id="sign-out" fill-rule="evenodd">
-         <path d="M9.5 15l5-5-5-5L8 6.5 10.5 9H2v2h8.5L8 13.5 9.5 15zM4 2c-.987 0-2 1.022-2 2v3.067h2V4h12v12H4v-3H2v3c0 .978 1.013 2 2 2h12c.978 0 2-1.022 2-2V4c0-1-1.022-2-2-2H4z">
-         </path>
-      </g>
-      <g id="cancel" fill-rule="evenodd">
-        <path d="M9.72 5.89c-2.2 0-4.194.88-5.73 2.31L1 5v8h7.474L5.468 9.782c1.154-1.03 2.624-1.67 4.252-1.67 2.94 0 5.44 2.052 6.312 4.888L18 12.307C16.846 8.582 13.582 5.89 9.72 5.89z">
-        </path>
-      </g>
-    </defs>
-  </svg>
-</iron-iconset-svg>
-
-<iron-iconset-svg name="header-bar-40" size="40">
-  <svg>
-    <defs>
-      <g id="shutdown" fill-rule="evenodd">
-        <path d="M21.667 5h-3.334v16.667h3.334V5zm8.05 3.617l-2.367 2.366C29.983 13.1 31.667 16.35 31.667 20c0 6.45-5.217 11.667-11.667 11.667S8.333 26.45 8.333 20c0-3.65 1.684-6.9 4.3-9.033l-2.35-2.35C7.05 11.367 5 15.433 5 20c0 8.283 6.717 15 15 15 8.283 0 15-6.717 15-15 0-4.567-2.05-8.633-5.283-11.383z">
-        </path>
-      </g>
-      <g id="browse-as-guest" fill-rule="evenodd">
-        <path d="M20 4C11.168 4 4 11.168 4 20s7.168 16 16 16 16-7.168 16-16S28.832 4 20 4zm0 4.8c2.656 0 4.8 2.144 4.8 4.8 0 2.656-2.144 4.8-4.8 4.8-2.656 0-4.8-2.144-4.8-4.8 0-2.656 2.144-4.8 4.8-4.8zM10 26.378C10.05 22.903 16.667 21 20 21c3.317 0 9.95 1.903 10 5.378C27.85 29.765 24.167 32 20 32s-7.85-2.235-10-5.622z">
-        </path>
-      </g>
-      <g id="add-person" fill-rule="evenodd">
-        <path d="M15.2 19.6c-1.104 0-2 .896-2 2s.896 2 2 2 2-.896 2-2-.896-2-2-2zm9.6 0c-1.104 0-2 .896-2 2s.896 2 2 2 2-.896 2-2-.896-2-2-2zM20 4C11.168 4 4 11.168 4 20s7.168 16 16 16 16-7.168 16-16S28.832 4 20 4zm0 28.8c-7.056 0-12.8-5.744-12.8-12.8 0-.464.032-.928.08-1.376 3.776-1.68 6.768-4.768 8.336-8.592 2.896 4.096 7.664 6.768 13.056 6.768 1.248 0 2.448-.144 3.6-.416.336 1.136.528 2.352.528 3.616 0 7.056-5.744 12.8-12.8 12.8z">
-        </path>
-      </g>
-      <g id="more-settings" fill-rule="evenodd">
-        <path d="M20 14c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm0 9c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm0 9c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3z">
-        </path>
-      </g>
-      <g id="sign-out" fill-rule="evenodd">
-        <path d="M19.48 29L28 20.5 19.48 12 17 14.5l4.5 4.5H5v3h16.5L17 26.5l2.48 2.5zM8.334 5C6.483 5 5 6.5 5 8.333V15h3V8.006h24v24.02H8V26H5v5.667C5 33.5 6.483 35 8.333 35h23.334C33.5 35 35 33.5 35 31.667V8.333C35 6.5 33.5 5 31.667 5H8.333z">
-        </path>
-      </g>
-      <g id="cancel" fill-rule="evenodd">
-        <path d="M20.44 12.667c-4.4 0-8.388 1.65-11.46 4.333L3 11v15h14.95l-6.014-6.033c2.31-1.934 5.25-3.134 8.504-3.134 5.88 0 10.88 3.85 12.624 9.167L37 24.7c-2.31-6.983-8.836-12.033-16.56-12.033z">
-        </path>
-      </g>
-    </defs>
-  </svg>
-</iron-iconset-svg>
-
-<link rel="stylesheet" href="oobe_flex_layout.css">
-<div id="login-header-bar" hidden
-    class="login-header-bar-hidden layout horizontal">
-  <div id="shutdown-header-bar-item" class="header-bar-item">
-    <button id="shutdown-button" class="custom-appearance layout vertical">
-      <div class="flex layout horizontal center">
-        <hd-iron-icon
-            icon1x="header-bar-20:shutdown" icon2x="header-bar-40:shutdown">
-        </hd-iron-icon>
-        <div i18n-content="shutDown"></div>
-      </div>
-    </button>
-  </div>
-  <div id="restart-header-bar-item" class="header-bar-item">
-    <button id="restart-button" class="custom-appearance layout vertical">
-      <div class="flex layout horizontal center">
-        <hd-iron-icon
-            icon1x="header-bar-20:shutdown" icon2x="header-bar-40:shutdown">
-        </hd-iron-icon>
-        <div i18n-content="shutDown"></div>
-      </div>
-    </button>
-  </div>
-  <div id="apps-header-bar-item" class="header-bar-item" hidden>
-    <button id="show-apps-button" class="custom-appearance layout vertical">
-      <div class="flex layout horizontal center">
-        <hd-iron-icon
-            icon1x="header-bar-10:show-apps" icon2x="header-bar-20:show-apps">
-        </hd-iron-icon>
-        <div i18n-content="showApps"></div>
-      </div>
-    </button>
-  </div>
-  <div id="guest-user-header-bar-item" class="header-bar-item" hidden>
-    <button id="guest-user-button" class="custom-appearance layout vertical">
-      <div class="flex layout horizontal center">
-        <hd-iron-icon
-            icon1x="header-bar-20:browse-as-guest" icon2x="header-bar-40:browse-as-guest">
-        </hd-iron-icon>
-        <div i18n-content="browseAsGuest"></div>
-      </div>
-    </button>
-  </div>
-  <div id="add-user-header-bar-item" class="header-bar-item" hidden>
-    <button id="add-user-button" class="custom-appearance layout vertical">
-      <div class="flex layout horizontal center">
-        <hd-iron-icon
-            icon1x="header-bar-20:add-person" icon2x="header-bar-40:add-person">
-        </hd-iron-icon>
-        <div i18n-content="addUser"></div>
-      </div>
-    </button>
-  </div>
-  <div id="more-settings-header-bar-item" class="header-bar-item">
-    <button id="more-settings-button" class="custom-appearance layout vertical"
-        i18n-values="aria-label:moreOptions">
-      <hd-iron-icon
-          icon1x="header-bar-20:more-settings" icon2x="header-bar-40:more-settings">
-      </hd-iron-icon>
-    </button>
-  </div>
-  <div id="sign-out-user-item" class="header-bar-item" hidden>
-    <button id="sign-out-user-button" class="custom-appearance layout vertical">
-      <div class="flex layout horizontal center">
-        <hd-iron-icon
-            icon1x="header-bar-20:sign-out" icon2x="header-bar-40:sign-out">
-        </hd-iron-icon>
-        <div i18n-content="signOutUser"></div>
-      </div>
-    </button>
-  </div>
-  <div id="cancel-multiple-sign-in-item" class="header-bar-item" hidden>
-    <button id="cancel-multiple-sign-in-button"
-        class="custom-appearance layout vertical">
-      <div class="flex layout horizontal center">
-        <hd-iron-icon
-            icon1x="header-bar-20:cancel" icon2x="header-bar-40:cancel">
-        </hd-iron-icon>
-        <div i18n-content="cancel"></div>
-      </div>
-    </button>
-  </div>
-</div>
diff --git a/chrome/browser/resources/chromeos/login/md_header_bar.js b/chrome/browser/resources/chromeos/login/md_header_bar.js
deleted file mode 100644
index a357252c..0000000
--- a/chrome/browser/resources/chromeos/login/md_header_bar.js
+++ /dev/null
@@ -1,355 +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.
-
-/**
- * @fileoverview Login UI header bar implementation.
- */
-
-cr.define('login', function() {
-  /**
-   * Creates a header bar element.
-   *
-   * @constructor
-   * @extends {HTMLDivElement}
-   */
-  var HeaderBar = cr.ui.define('div');
-
-  HeaderBar.prototype = {
-    __proto__: HTMLDivElement.prototype,
-
-    // Whether guest button should be shown when header bar is in normal mode.
-    showGuest_: false,
-
-    // Whether the reboot button should be shown the when header bar is in
-    // normal mode.
-    showReboot_: false,
-
-    // Whether the shutdown button should be shown when the header bar is in
-    // normal mode.
-    showShutdown_: true,
-
-    // Current UI state of the sign-in screen.
-    signinUIState_: SIGNIN_UI_STATE.HIDDEN,
-
-    // Whether to show kiosk apps menu.
-    hasApps_: false,
-
-    /** @override */
-    decorate: function() {
-      document.addEventListener('click', this.handleClick_.bind(this));
-      $('shutdown-header-bar-item')
-          .addEventListener('click', this.handleShutdownClick_.bind(this));
-      $('shutdown-button')
-          .addEventListener('click', this.handleShutdownClick_.bind(this));
-      $('restart-header-bar-item')
-          .addEventListener('click', this.handleShutdownClick_.bind(this));
-      $('restart-button')
-          .addEventListener('click', this.handleShutdownClick_.bind(this));
-      $('add-user-button').addEventListener('click', this.handleAddUserClick_);
-      $('more-settings-button')
-          .addEventListener('click', this.handleMoreSettingsClick_.bind(this));
-      $('guest-user-header-bar-item')
-          .addEventListener('click', this.handleGuestClick_);
-      $('guest-user-button').addEventListener('click', this.handleGuestClick_);
-      $('sign-out-user-button')
-          .addEventListener('click', this.handleSignoutClick_.bind(this));
-      $('cancel-multiple-sign-in-button')
-          .addEventListener('click', this.handleCancelMultipleSignInClick_);
-      if (Oobe.getInstance().displayType == DISPLAY_TYPE.LOGIN ||
-          Oobe.getInstance().displayType == DISPLAY_TYPE.OOBE) {
-        if (Oobe.getInstance().newKioskUI)
-          chrome.send('initializeKioskApps');
-        else
-          login.AppsMenuButton.decorate($('show-apps-button'));
-      }
-      this.updateUI_();
-    },
-
-    /**
-     * Tab index value for all button elements.
-     *
-     * @type {number}
-     */
-    set buttonsTabIndex(tabIndex) {
-      var buttons = this.getElementsByTagName('button');
-      for (var i = 0, button; button = buttons[i]; ++i) {
-        button.tabIndex = tabIndex;
-      }
-    },
-
-    /**
-     * Disables the header bar and all of its elements.
-     *
-     * @type {boolean}
-     */
-    set disabled(value) {
-      var buttons = this.getElementsByTagName('button');
-      for (var i = 0, button; button = buttons[i]; ++i)
-        if (!button.classList.contains('button-restricted'))
-          button.disabled = value;
-    },
-
-    get getMoreSettingsMenu() {
-      return $('more-settings-header-bar-item');
-    },
-
-    /**
-     * Whether action box button is in active state.
-     * @type {boolean}
-     */
-    get isMoreSettingsActive() {
-      return this.getMoreSettingsMenu.classList.contains('active');
-    },
-    set isMoreSettingsActive(active) {
-      if (active == this.isMoreSettingsActive)
-        return;
-      this.getMoreSettingsMenu.classList.toggle('active', active);
-      $('more-settings-button').tabIndex = active ? -1 : 4;
-    },
-
-    /**
-     * Add user button click handler.
-     *
-     * @private
-     */
-    handleAddUserClick_: function(e) {
-      Oobe.showSigninUI();
-      // Prevent further propagation of click event. Otherwise, the click event
-      // handler of document object will set wallpaper to user's wallpaper when
-      // there is only one existing user. See http://crbug.com/166477
-      e.stopPropagation();
-    },
-
-    handleMoreSettingsClick_: function(e) {
-      this.isMoreSettingsActive = !this.isMoreSettingsActive;
-      e.stopPropagation();
-    },
-
-    handleClick_: function(e) {
-      this.isMoreSettingsActive = false;
-    },
-
-    /**
-     * Cancel add user button click handler.
-     *
-     * @private
-     */
-    handleCancelAddUserClick_: function(e) {
-      // Let screen handle cancel itself if that is capable of doing so.
-      if (Oobe.getInstance().currentScreen &&
-          Oobe.getInstance().currentScreen.cancel) {
-        Oobe.getInstance().currentScreen.cancel();
-        return;
-      }
-
-      Oobe.showUserPods();
-    },
-
-    /**
-     * Guest button click handler.
-     *
-     * @private
-     */
-    handleGuestClick_: function(e) {
-      Oobe.disableSigninUI();
-      chrome.send('launchIncognito');
-      e.stopPropagation();
-    },
-
-    /**
-     * Sign out button click handler.
-     *
-     * @private
-     */
-    handleSignoutClick_: function(e) {
-      this.disabled = true;
-
-      chrome.send('signOutUser');
-      e.stopPropagation();
-    },
-
-    /**
-     * Shutdown button click handler.
-     *
-     * @private
-     */
-    handleShutdownClick_: function(e) {
-      chrome.send('shutdownSystem');
-      e.stopPropagation();
-    },
-
-    /**
-     * Cancel user adding button handler.
-     *
-     * @private
-     */
-    handleCancelMultipleSignInClick_: function(e) {
-      chrome.send('cancelUserAdding');
-      e.stopPropagation();
-    },
-
-    /**
-     * If true then "Browse as Guest" button is shown.
-     *
-     * @type {boolean}
-     */
-    set showGuestButton(value) {
-      this.showGuest_ = value;
-      this.updateUI_();
-    },
-
-    /**
-     * If true the "Restart" button is shown.
-     *
-     * @type {boolean}
-     */
-    set showRebootButton(value) {
-      this.showReboot_ = value;
-      this.updateUI_();
-    },
-
-    /**
-     * If true the "Shutdown" button is shown.
-     *
-     * @type {boolean}
-     */
-    set showShutdownButton(value) {
-      this.showShutdown_ = value;
-      this.updateUI_();
-    },
-
-    /**
-     * Current header bar UI / sign in state.
-     *
-     * @type {number} state Current state of the sign-in screen (see
-     *       SIGNIN_UI_STATE).
-     */
-    get signinUIState() {
-      return this.signinUIState_;
-    },
-    set signinUIState(state) {
-      this.signinUIState_ = state;
-      this.updateUI_();
-    },
-
-    /**
-     * Update whether there are kiosk apps.
-     *
-     * @type {boolean}
-     */
-    set hasApps(value) {
-      this.hasApps_ = value;
-      this.updateUI_();
-    },
-
-    /**
-     * Updates visibility state of action buttons.
-     *
-     * @private
-     */
-    updateUI_: function() {
-      var gaiaIsActive = (this.signinUIState_ == SIGNIN_UI_STATE.GAIA_SIGNIN);
-      var enrollmentIsActive =
-          (this.signinUIState_ == SIGNIN_UI_STATE.ENROLLMENT);
-      var accountPickerIsActive =
-          (this.signinUIState_ == SIGNIN_UI_STATE.ACCOUNT_PICKER);
-      var wrongHWIDWarningIsActive =
-          (this.signinUIState_ == SIGNIN_UI_STATE.WRONG_HWID_WARNING);
-      var isSamlPasswordConfirm =
-          (this.signinUIState_ == SIGNIN_UI_STATE.SAML_PASSWORD_CONFIRM);
-      var isPasswordChangedUI =
-          (this.signinUIState_ == SIGNIN_UI_STATE.PASSWORD_CHANGED);
-      var isMultiProfilesUI =
-          (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING);
-      var isLockScreen = (Oobe.getInstance().displayType == DISPLAY_TYPE.LOCK);
-      var errorScreenIsActive = (this.signinUIState_ == SIGNIN_UI_STATE.ERROR);
-
-      $('add-user-button').hidden = !accountPickerIsActive ||
-          isMultiProfilesUI || isLockScreen || errorScreenIsActive;
-      $('more-settings-header-bar-item').hidden = true;
-      $('guest-user-header-bar-item').hidden = !this.showGuest_ ||
-          isLockScreen || wrongHWIDWarningIsActive || isSamlPasswordConfirm ||
-          isMultiProfilesUI || (gaiaIsActive && $('gaia-signin').closable) ||
-          (enrollmentIsActive && !$('oauth-enrollment').isAtTheBeginning()) ||
-          (gaiaIsActive && !$('gaia-signin').isAtTheBeginning());
-      $('restart-header-bar-item').hidden = !this.showReboot_;
-      $('shutdown-header-bar-item').hidden = !this.showShutdown_;
-      $('sign-out-user-item').hidden = !isLockScreen;
-
-      $('add-user-header-bar-item').hidden = $('add-user-button').hidden;
-      $('apps-header-bar-item').hidden =
-          !this.hasApps_ || (!gaiaIsActive && !accountPickerIsActive);
-      $('cancel-multiple-sign-in-item').hidden = !isMultiProfilesUI;
-
-      if (!Oobe.getInstance().newKioskUI) {
-        if (!$('apps-header-bar-item').hidden)
-          $('show-apps-button').didShow();
-      }
-    },
-
-    /**
-     * Animates Header bar to hide from the screen.
-     *
-     * @param {function()} callback will be called once animation is finished.
-     */
-    animateOut: function(callback) {
-      var launcher = this;
-      launcher.addEventListener('transitionend', function f(e) {
-        launcher.removeEventListener('transitionend', f);
-        callback();
-      });
-      // Guard timer for 2 seconds + 200 ms + epsilon.
-      ensureTransitionEndEvent(launcher, 2250);
-
-      this.classList.remove('login-header-bar-animate-slow');
-      this.classList.add('login-header-bar-animate-fast');
-      this.classList.add('login-header-bar-hidden');
-    },
-
-    /**
-     * Animates Header bar to appear on the screen.
-     *
-     * @param {boolean} fast Whether the animation should complete quickly or
-     *     slowly.
-     * @param {function()} callback will be called once animation is finished.
-     */
-    animateIn: function(fast, callback) {
-      if (callback) {
-        var launcher = this;
-        launcher.addEventListener('transitionend', function f(e) {
-          launcher.removeEventListener('transitionend', f);
-          callback();
-        });
-        // Guard timer for 2 seconds + 200 ms + epsilon.
-        ensureTransitionEndEvent(launcher, 2250);
-      }
-
-      if (fast) {
-        this.classList.remove('login-header-bar-animate-slow');
-        this.classList.add('login-header-bar-animate-fast');
-      } else {
-        this.classList.remove('login-header-bar-animate-fast');
-        this.classList.add('login-header-bar-animate-slow');
-      }
-
-      this.classList.remove('login-header-bar-hidden');
-    },
-  };
-
-  /**
-   * Convenience wrapper of animateOut.
-   */
-  HeaderBar.animateOut = function(callback) {
-    $('login-header-bar').animateOut(callback);
-  };
-
-  /**
-   * Convenience wrapper of animateIn.
-   */
-  HeaderBar.animateIn = function(fast, callback) {
-    $('login-header-bar').animateIn(fast, callback);
-  };
-
-  return {HeaderBar: HeaderBar};
-});
diff --git a/chrome/browser/resources/chromeos/login/md_login.html b/chrome/browser/resources/chromeos/login/md_login.html
index 9ae8e23..8154b02e 100644
--- a/chrome/browser/resources/chromeos/login/md_login.html
+++ b/chrome/browser/resources/chromeos/login/md_login.html
@@ -21,7 +21,6 @@
 <link rel="stylesheet" href="chrome://resources/css/widgets.css">
 <link rel="stylesheet" href="apps_menu.css">
 <link rel="stylesheet" href="../../../../../ui/login/bubble.css">
-<link rel="stylesheet" href="md_header_bar.css">
 <link rel="stylesheet" href="md_top_header_bar.css">
 <link rel="stylesheet" href="../../../../../ui/login/oobe.css">
 <link rel="stylesheet" href="oobe_popup_overlay.css">
diff --git a/chrome/browser/resources/chromeos/login/md_login.js b/chrome/browser/resources/chromeos/login/md_login.js
index bf1f578..a03c337 100644
--- a/chrome/browser/resources/chromeos/login/md_login.js
+++ b/chrome/browser/resources/chromeos/login/md_login.js
@@ -9,10 +9,8 @@
 // <include src="test_util.js">
 // <include src="../../../../../ui/login/screen.js">
 // <include src="screen_context.js">
-// <include src="apps_menu.js">
 // <include src="../../../../../ui/login/bubble.js">
 // <include src="../../../../../ui/login/display_manager.js">
-// <include src="md_header_bar.js">
 // <include src="demo_mode_test_helper.js">
 
 // <include
@@ -113,7 +111,6 @@
       $('bubble-persistent').hideOnKeyPress = false;
 
       cr.ui.Bubble.decorate($('bubble'));
-      login.HeaderBar.decorate($('login-header-bar'));
 
       chrome.send('screenStateInitialize');
 
diff --git a/chrome/browser/resources/chromeos/login/md_screen_container.html b/chrome/browser/resources/chromeos/login/md_screen_container.html
index 069b4e1..3831f8b6 100644
--- a/chrome/browser/resources/chromeos/login/md_screen_container.html
+++ b/chrome/browser/resources/chromeos/login/md_screen_container.html
@@ -17,6 +17,5 @@
 <div id="bubble-persistent" class="bubble faded" hidden></div>
 <div id="bubble" class="bubble faded" hidden></div>
 <include src="md_top_header_bar.html">
-<include src="md_header_bar.html">
 <include src="../../../../../ui/login/account_picker/chromeos_user_pod_template.html">
 <include src="oobe_screen_reset_confirmation_overlay.html">
diff --git a/chrome/browser/resources/chromeos/login/oobe.html b/chrome/browser/resources/chromeos/login/oobe.html
index f7c3c386..8ef0b36 100644
--- a/chrome/browser/resources/chromeos/login/oobe.html
+++ b/chrome/browser/resources/chromeos/login/oobe.html
@@ -23,7 +23,6 @@
 
 <link rel="stylesheet" href="apps_menu.css">
 <link rel="stylesheet" href="../../../../../ui/login/bubble.css">
-<link rel="stylesheet" href="md_header_bar.css">
 <link rel="stylesheet" href="md_top_header_bar.css">
 <link rel="stylesheet" href="../../../../../ui/login/oobe.css">
 <link rel="stylesheet" href="oobe_popup_overlay.css">
diff --git a/chrome/browser/resources/chromeos/login/oobe.js b/chrome/browser/resources/chromeos/login/oobe.js
index 5bebe511e..dfe9328 100644
--- a/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chrome/browser/resources/chromeos/login/oobe.js
@@ -10,10 +10,8 @@
 // <include src="test_util.js">
 // <include src="../../../../../ui/login/screen.js">
 // <include src="screen_context.js">
-// <include src="apps_menu.js">
 // <include src="../../../../../ui/login/bubble.js">
 // <include src="../../../../../ui/login/display_manager.js">
-// <include src="md_header_bar.js">
 // <include src="demo_mode_test_helper.js">
 
 // <include
@@ -142,7 +140,6 @@
       $('bubble-persistent').hideOnKeyPress = false;
 
       cr.ui.Bubble.decorate($('bubble'));
-      login.HeaderBar.decorate($('login-header-bar'));
 
       chrome.send('screenStateInitialize');
     },
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_assistant_optin_flow.js b/chrome/browser/resources/chromeos/login/oobe_screen_assistant_optin_flow.js
index 2cfaebc0..818ec10 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_assistant_optin_flow.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_assistant_optin_flow.js
@@ -16,7 +16,6 @@
 
         /** @Override */
         onBeforeShow: function(data) {
-          Oobe.getInstance().headerHidden = true;
           $('assistant-optin-flow-card').onShow();
         },
 
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_supervision_transition.js b/chrome/browser/resources/chromeos/login/oobe_screen_supervision_transition.js
index 6ed2a1a..efe6064 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_supervision_transition.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_supervision_transition.js
@@ -16,7 +16,6 @@
 
         /** @override */
         onBeforeShow: function(data) {
-          Oobe.getInstance().headerHidden = true;
           $('supervision-transition-md')
               .setIsRemovingSupervision(
                   data['isRemovingSupervision'] ? true : false);
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js b/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js
index bd7a56e..91f6f22 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_terms_of_service.js
@@ -108,13 +108,5 @@
       return $('tos-accept-button').disabled ? $('tos-back-button') :
                                                $('tos-accept-button');
     },
-
-    /**
-     * Event handler that is invoked just before the screen is shown.
-     * @param {object} data Screen init payload.
-     */
-    onBeforeShow: function(data) {
-      Oobe.getInstance().headerHidden = true;
-    }
   };
 });
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js b/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
index 01e59d0..5ad495a7 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
@@ -82,7 +82,6 @@
      * @param {object} data Screen init payload.
      */
     onBeforeShow: function(data) {
-      Oobe.getInstance().headerHidden = true;
       this.loading = true;
       chrome.send('onUserImageScreenShown');
     },
diff --git a/chrome/browser/resources/chromeos/login/screen_active_directory_password_change.js b/chrome/browser/resources/chromeos/login/screen_active_directory_password_change.js
index f0c9849..0bcd53fd 100644
--- a/chrome/browser/resources/chromeos/login/screen_active_directory_password_change.js
+++ b/chrome/browser/resources/chromeos/login/screen_active_directory_password_change.js
@@ -57,7 +57,6 @@
         onBeforeShow: function(data) {
           // Active Directory password change screen is similar to Active
           // Directory login screen. So we restore bottom bar controls.
-          Oobe.getInstance().headerHidden = false;
           this.adPasswordChanged_.reset();
           if ('username' in data)
             this.adPasswordChanged_.username = data.username;
diff --git a/chrome/browser/resources/chromeos/login/screen_app_launch_splash.js b/chrome/browser/resources/chromeos/login/screen_app_launch_splash.js
index 0e714c6..cbd08dd 100644
--- a/chrome/browser/resources/chromeos/login/screen_app_launch_splash.js
+++ b/chrome/browser/resources/chromeos/login/screen_app_launch_splash.js
@@ -53,7 +53,6 @@
 
       $('splash-shortcut-info').hidden = !data['shortcutEnabled'];
 
-      Oobe.getInstance().headerHidden = true;
       Oobe.getInstance().solidBackground = true;
     },
 
diff --git a/chrome/browser/resources/chromeos/login/screen_arc_kiosk_splash.js b/chrome/browser/resources/chromeos/login/screen_arc_kiosk_splash.js
index d7e1284..d8e66b0 100644
--- a/chrome/browser/resources/chromeos/login/screen_arc_kiosk_splash.js
+++ b/chrome/browser/resources/chromeos/login/screen_arc_kiosk_splash.js
@@ -22,7 +22,6 @@
     onBeforeShow: function(data) {
       this.updateApp(data['appInfo']);
 
-      Oobe.getInstance().headerHidden = true;
       Oobe.getInstance().solidBackground = true;
     },
 
diff --git a/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js b/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js
index 7808f0c..0039697 100644
--- a/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js
+++ b/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js
@@ -602,8 +602,6 @@
     onBeforeShow: function(data) {
       this.setLearnMoreHandlers_();
 
-      Oobe.getInstance().headerHidden = true;
-
       this.hideOverlay();
       // ToS content may be loaded before the page is shown. In that case,
       // height of ToS webview is not correctly calculated. Recalculate the
diff --git a/chrome/browser/resources/chromeos/login/screen_discover.js b/chrome/browser/resources/chromeos/login/screen_discover.js
index 9b70d40..48c5f63 100644
--- a/chrome/browser/resources/chromeos/login/screen_discover.js
+++ b/chrome/browser/resources/chromeos/login/screen_discover.js
@@ -20,7 +20,6 @@
      * @param {object} data Screen init payload.
      */
     onBeforeShow: function(data) {
-      Oobe.getInstance().headerHidden = true;
       $('discover-impl').addEventListener('discover-done', function() {
         chrome.send('login.DiscoverScreen.userActed', ['finished']);
       });
diff --git a/chrome/browser/resources/chromeos/login/screen_encryption_migration.js b/chrome/browser/resources/chromeos/login/screen_encryption_migration.js
index 9a66670..1a1a8ad7 100644
--- a/chrome/browser/resources/chromeos/login/screen_encryption_migration.js
+++ b/chrome/browser/resources/chromeos/login/screen_encryption_migration.js
@@ -65,11 +65,6 @@
          */
         setUIState: function(state) {
           $('encryption-migration-element').uiState = state;
-
-          // TODO(qnnguyen): Hide the views login shelf "Shutdown" button during
-          // migration.
-          $('login-header-bar').showShutdownButton =
-              state != EncryptionMigrationUIState.MIGRATING;
         },
 
         /**
diff --git a/chrome/browser/resources/chromeos/login/screen_error_message.js b/chrome/browser/resources/chromeos/login/screen_error_message.js
index b5e5b282..178d9ad5 100644
--- a/chrome/browser/resources/chromeos/login/screen_error_message.js
+++ b/chrome/browser/resources/chromeos/login/screen_error_message.js
@@ -283,12 +283,6 @@
       this.classList.remove(this.ui_state);
       this.ui_state = ui_state;
       this.classList.add(this.ui_state);
-
-      if (ui_state == ERROR_SCREEN_UI_STATE.LOCAL_STATE_ERROR) {
-        // Hide header bar and progress dots, because there are no way
-        // from the error screen about broken local state.
-        Oobe.getInstance().headerHidden = true;
-      }
       this.onContentChange_();
     },
 
diff --git a/chrome/browser/resources/chromeos/login/screen_fatal_error.js b/chrome/browser/resources/chromeos/login/screen_fatal_error.js
index 56f1572..0a0f2d77 100644
--- a/chrome/browser/resources/chromeos/login/screen_fatal_error.js
+++ b/chrome/browser/resources/chromeos/login/screen_fatal_error.js
@@ -33,17 +33,6 @@
       return $('fatal-error-card').submitButton;
     },
 
-    /** @override */
-    onBeforeShow: function() {
-      this.savedUIStates_.headerHidden = Oobe.getInstance().headerHidden;
-      Oobe.getInstance().headerHidden = true;
-    },
-
-    /** @override */
-    onBeforeHide: function() {
-      Oobe.getInstance().headerHidden = this.savedUIStates_.headerHidden;
-    },
-
     /**
      * Invoked when user clicks on the ok button.
      */
diff --git a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
index 4e05050e..a7afdef2 100644
--- a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
@@ -124,6 +124,12 @@
     lastBackMessageValue_: false,
 
     /**
+     * Flag for tests that saml page was loaded.
+     * @type {boolean}
+     */
+    samlInterstitialPageReady: false,
+
+    /**
      * Whether the dialog could be closed.
      * This is being checked in cancel() when user clicks on signin-back-button
      * (normal gaia flow) or the buttons in gaia-navigation (used in enterprise
@@ -163,8 +169,6 @@
           !isWhitelistError && !this.authCompleted_ &&
           this.screenMode_ != ScreenMode.SAML_INTERSTITIAL;
 
-      $('login-header-bar').updateUI_();
-
       let showGuestInOobe = !this.closable && this.isAtTheBeginning();
       chrome.send('showGuestInOobe', [showGuestInOobe]);
     },
@@ -581,10 +585,6 @@
         chrome.send('loginVisible', ['gaia-loading']);
       });
 
-      // Button header is always visible when sign in is presented.
-      // Header is hidden once GAIA reports on successful sign in.
-      Oobe.getInstance().headerHidden = false;
-
       // Re-enable navigation in case it was disabled before refresh.
       this.navigationDisabled_ = false;
 
@@ -711,10 +711,6 @@
       this.authCompleted_ = false;
       this.lastBackMessageValue_ = false;
 
-      $('login-header-bar').showCreateSupervisedButton =
-          data.supervisedUsersCanCreate;
-      $('login-header-bar').showGuestButton = data.guestSignin;
-
       // Reset SAML
       this.classList.toggle('full-width', false);
       $('saml-notice-container').hidden = true;
@@ -782,6 +778,7 @@
           if (this.loading)
             this.loading = false;
           // This event is for the browser tests.
+          this.samlInterstitialPageReady = true;
           $('saml-interstitial').fire('samlInterstitialPageReady');
           break;
       }
@@ -990,7 +987,6 @@
      */
     onAuthConfirmPassword_: function(email, passwordCount) {
       this.loading = true;
-      Oobe.getInstance().headerHidden = false;
 
       if (this.samlPasswordConfirmAttempt_ == 0)
         chrome.send('scrapedPasswordCount', [passwordCount]);
@@ -1112,8 +1108,6 @@
       $('signin-back-button').hidden = true;
       $('signin-frame-dialog').setAttribute('hide-shadow', true);
 
-      // Now that we're in logged in state header should be hidden.
-      Oobe.getInstance().headerHidden = true;
       // Clear any error messages that were shown before login.
       Oobe.clearErrors();
 
@@ -1160,9 +1154,6 @@
       this.gaiaAuthHost_.resetStates();
       if (takeFocus) {
         if (!forceOnline && this.isOffline()) {
-          // Show 'Cancel' button to allow user to return to the main screen
-          // (e.g. this makes sense when connection is back).
-          Oobe.getInstance().headerHidden = false;
           Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.GAIA_SIGNIN);
           // Do nothing, since offline version is reloaded after an error comes.
         } else {
@@ -1192,7 +1183,6 @@
      */
     showErrorBubble: function(loginAttempts, error) {
       if (this.isOffline()) {
-        $('add-user-button').hidden = true;
         // Reload offline version of the sign-in extension, which will show
         // error itself.
         chrome.send('offlineLogin', [this.email]);
@@ -1311,7 +1301,6 @@
       adAuthUI.errorState = errorState;
       this.authCompleted_ = false;
       this.loading = false;
-      Oobe.getInstance().headerHidden = false;
     }
   };
 });
diff --git a/chrome/browser/resources/chromeos/login/screen_marketing_opt_in.js b/chrome/browser/resources/chromeos/login/screen_marketing_opt_in.js
index d336724..ec93d97 100644
--- a/chrome/browser/resources/chromeos/login/screen_marketing_opt_in.js
+++ b/chrome/browser/resources/chromeos/login/screen_marketing_opt_in.js
@@ -14,13 +14,5 @@
     get defaultControl() {
       return $('marketing-opt-in-impl');
     },
-
-    /**
-     * Event handler that is invoked just before the screen is shown.
-     * @param {object} data Screen init payload.
-     */
-    onBeforeShow: function(data) {
-      Oobe.getInstance().headerHidden = true;
-    }
   };
 });
diff --git a/chrome/browser/resources/chromeos/login/screen_password_changed.js b/chrome/browser/resources/chromeos/login/screen_password_changed.js
index 087a0822..0e70231 100644
--- a/chrome/browser/resources/chromeos/login/screen_password_changed.js
+++ b/chrome/browser/resources/chromeos/login/screen_password_changed.js
@@ -19,12 +19,10 @@
           'cancel', this.cancel.bind(this));
 
       this.gaiaPasswordChanged_.addEventListener('passwordEnter', function(e) {
-        $('login-header-bar').disabled = true;
         chrome.send('migrateUserData', [e.detail.password]);
       });
 
       this.gaiaPasswordChanged_.addEventListener('proceedAnyway', function() {
-        $('login-header-bar').disabled = true;
         chrome.send('resyncUserData');
       });
     },
@@ -44,13 +42,6 @@
     },
 
     /**
-     * Event handler that is invoked just before the screen is hidden.
-     */
-    onBeforeHide: function() {
-      $('login-header-bar').disabled = false;
-    },
-
-    /**
      * Show password changed screen.
      * @param {boolean} showError Whether to show the incorrect password error.
      */
@@ -62,10 +53,7 @@
         this.gaiaPasswordChanged_.email = email;
 
       // We'll get here after the successful online authentication.
-      // It assumes session is about to start so hides login screen controls.
-      Oobe.getInstance().headerHidden = false;
       Oobe.showScreen({id: SCREEN_PASSWORD_CHANGED});
-      $('login-header-bar').disabled = false;
       Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.PASSWORD_CHANGED);
     }
   };
diff --git a/chrome/browser/resources/chromeos/login/screen_sync_consent.js b/chrome/browser/resources/chromeos/login/screen_sync_consent.js
index 6526b748..88ffa78 100644
--- a/chrome/browser/resources/chromeos/login/screen_sync_consent.js
+++ b/chrome/browser/resources/chromeos/login/screen_sync_consent.js
@@ -18,20 +18,10 @@
     },
 
     /**
-     * Event handler that is invoked just before the screen is shown.
-     * @param {object} data Screen init payload.
-     */
-    onBeforeShow: function(data) {
-      Oobe.getInstance().headerHidden = true;
-      $('login-header-bar').signinUIState = SIGNIN_UI_STATE.SYNC_CONSENT;
-    },
-
-    /**
      * Event handler that is invoked just before the screen is hidden.
      */
     onBeforeHide: function() {
       this.setThrobberVisible(false /*visible*/);
-      $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
     },
 
     /**
diff --git a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.js b/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.js
index 62ffc3b..2112a3bb 100644
--- a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.js
+++ b/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.js
@@ -36,7 +36,6 @@
         show: function() {
           this.setLoading_(false);
 
-          Oobe.getInstance().headerHidden = true;
           Oobe.showScreen({id: SCREEN_UNRECOVERABLE_CRYPTOHOME_ERROR});
         },
 
diff --git a/chrome/browser/resources/chromeos/login/screen_update_required.js b/chrome/browser/resources/chromeos/login/screen_update_required.js
index e454e32..0495969 100644
--- a/chrome/browser/resources/chromeos/login/screen_update_required.js
+++ b/chrome/browser/resources/chromeos/login/screen_update_required.js
@@ -7,10 +7,5 @@
  */
 
 login.createScreen('UpdateRequiredScreen', 'update-required', function() {
-  return {
-    /** @Override */
-    onBeforeShow: function(data) {
-      Oobe.getInstance().headerHidden = true;
-    }
-  };
+  return {};
 });
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
index e7af165..170f706 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
@@ -69,6 +69,7 @@
   display: -webkit-box;
   height: 31px;
   margin-top: 8px;
+  overflow: hidden;
   pointer-events: auto;
   transition: border-top 130ms ease;
   user-select: none;
@@ -93,6 +94,20 @@
   font-weight: 500;
   line-height: 34px;
   padding: 0 30px;
+  position: relative;
+  transition: all 500ms ease;
+  width: 100%;
+}
+
+.ink {
+  background: rgb(232, 240, 254);
+  border-radius: 100%;
+  position: absolute;
+  transform: scale(0);
+}
+
+.ripple-category-list-item-animation {
+  animation: ripple-category-list-item 500ms linear;
 }
 
 #categories-list > li[selected] > div {
@@ -868,3 +883,9 @@
         transform: scale(1);
       }
 }
+
+@keyframes ripple-category-list-item {
+  100% { opacity: 0;
+         transform: scale(2.5);
+       }
+}
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js b/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js
index 4fd5313..0e0a4c8 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/js/wallpaper_categories_list.js
@@ -70,9 +70,25 @@
         var div = self.ownerDocument.createElement('div');
         div.textContent = entry;
         li.appendChild(div);
-          li.addEventListener('mousedown', e => {
-            e.preventDefault();
-          });
+        div.addEventListener('mousedown', e => {
+          var targetEl = e.target;
+          var inkEl = targetEl.querySelector('.ink');
+          if (inkEl) {
+            inkEl.classList.remove('ripple-category-list-item-animation');
+          } else {
+            inkEl = document.createElement('span');
+            inkEl.classList.add('ink');
+            inkEl.style.width = inkEl.style.height =
+                Math.max(targetEl.offsetWidth, targetEl.offsetHeight) + 'px';
+            targetEl.appendChild(inkEl);
+          }
+          inkEl.style.left = (e.offsetX - 0.5 * inkEl.offsetWidth) + 'px';
+          inkEl.style.top = (e.offsetY - 0.5 * inkEl.offsetHeight) + 'px';
+          inkEl.classList.add('ripple-category-list-item-animation');
+        });
+        li.addEventListener('mousedown', e => {
+          e.preventDefault();
+        });
         return li;
       };
     },
diff --git a/chrome/browser/resources/feed_internals/feed_internals.css b/chrome/browser/resources/feed_internals/feed_internals.css
index e65f136..c69244c 100644
--- a/chrome/browser/resources/feed_internals/feed_internals.css
+++ b/chrome/browser/resources/feed_internals/feed_internals.css
@@ -10,6 +10,7 @@
 }
 
 table {
+  table-layout: fixed;
   width: 100%;
 }
 
@@ -19,9 +20,14 @@
 
 table td {
   width: 50%;
+  word-wrap: break-word;
 }
 
 button {
   display: block;
   min-height: 30px;
 }
+
+#current-content table td:first-child {
+  width: 15%;
+}
diff --git a/chrome/browser/resources/feed_internals/feed_internals.html b/chrome/browser/resources/feed_internals/feed_internals.html
index 9a3c114..c96db826 100644
--- a/chrome/browser/resources/feed_internals/feed_internals.html
+++ b/chrome/browser/resources/feed_internals/feed_internals.html
@@ -23,6 +23,7 @@
 </head>
 
 <body>
+
   <h2>Properties</h2>
   <table>
     <tr>
@@ -30,6 +31,7 @@
       <td id="is-feed-enabled"></td>
     </tr>
   </table>
+
   <h2>User Classifier</h2>
   <table>
     <tr>
@@ -52,6 +54,7 @@
   <button id="clear-cached-data">
     Clear Cache & Refresh Feed
   </button>
+
   <h2>Last Fetch</h2>
   <table>
     <tr>
@@ -67,5 +70,33 @@
       <td id="refresh-suppress-time"></td>
     </tr>
   </table>
+
+  <h2>Current Content</h2>
+  <div id="current-content">
+    <template id="suggestion-template">
+      <details>
+        <summary class="title"></summary>
+        <table>
+          <tr>
+            <td>Publisher Name</td>
+            <td class="publisher"></td>
+          </tr>
+          <tr>
+            <td>URL</td>
+            <td><a class="url"></a></td>
+          </tr>
+          <tr>
+            <td>Favicon URL</td>
+            <td><a class="favicon"></a></td>
+          </tr>
+          <tr>
+            <td>Image URL</td>
+            <td><a class="image"></a></td>
+          </tr>
+        </table>
+      </details>
+    </template>
+  </div>
+
 </body>
 </html>
diff --git a/chrome/browser/resources/feed_internals/feed_internals.js b/chrome/browser/resources/feed_internals/feed_internals.js
index 7e64c0b5..4d3df25 100644
--- a/chrome/browser/resources/feed_internals/feed_internals.js
+++ b/chrome/browser/resources/feed_internals/feed_internals.js
@@ -51,6 +51,48 @@
 }
 
 /**
+ * Get and display last known content.
+ */
+function updatePageWithCurrentContent() {
+  pageHandler.getCurrentContent().then(response => {
+    const before = $('current-content');
+    const after = before.cloneNode(false);
+
+    /** @type {!Array<feedInternals.mojom.Suggestion>} */
+    const suggestions = response.suggestions;
+
+    for (const suggestion of suggestions) {
+      // Create new content item from template.
+      const item = document.importNode($('suggestion-template').content, true);
+
+      // Populate template with text metadata.
+      item.querySelector('.title').textContent = suggestion.title;
+      item.querySelector('.publisher').textContent = suggestion.publisherName;
+
+      // Populate template with link metadata.
+      setLinkNode(item.querySelector('a.url'), suggestion.url);
+      setLinkNode(item.querySelector('a.image'), suggestion.imageUrl);
+      setLinkNode(item.querySelector('a.favicon'), suggestion.faviconUrl);
+
+      after.appendChild(item);
+    }
+
+    before.replaceWith(after);
+  });
+}
+
+/**
+ * Populate <a> node with hyperlinked URL.
+ *
+ * @param {Element} node
+ * @param {string} url
+ */
+function setLinkNode(node, url) {
+  node.textContent = url;
+  node.href = url;
+}
+
+/**
  * Convert time to string for display.
  *
  * @param {feedInternals.mojom.Time|undefined} time
@@ -78,6 +120,7 @@
     // consider adding backend->frontend mojo communication to listen for
     // updates, rather than waiting an arbitrary period of time.
     setTimeout(updatePageWithLastFetchProperties, 1000);
+    setTimeout(updatePageWithCurrentContent, 1000);
   });
 }
 
@@ -88,6 +131,7 @@
   updatePageWithProperties();
   updatePageWithUserClass();
   updatePageWithLastFetchProperties();
+  updatePageWithCurrentContent();
 
   setupEventListeners();
 });
diff --git a/chrome/browser/resources/feedback/js/feedback.js b/chrome/browser/resources/feedback/js/feedback.js
index e3282012..8d095c1 100644
--- a/chrome/browser/resources/feedback/js/feedback.js
+++ b/chrome/browser/resources/feedback/js/feedback.js
@@ -73,12 +73,13 @@
 let isShowingSrtPrompt = false;
 
 /**
- * Regular expression to check for all variants of bluetooth, blutooth, with or
- * without space between the words and for BT when used as an individual word,
- * or as two individual characters. Case insensitive matching.
+ * Regular expression to check for all variants of blu[e]toot[h] with or without
+ * space between the words; for BT when used as an individual word, or as two
+ * individual characters, and for BLE when used as an individual word. Case
+ * insensitive matching.
  * @type {RegExp}
  */
-const btRegEx = new RegExp('[b]lu[e]?[ ]?tooth|\b[b][ ]?[t]\b', 'i');
+const btRegEx = new RegExp('blu[e]?[ ]?toot[h]?|\\bb[ ]?t\\b|\\bble\\b', 'i');
 
 /**
  * Regular expression to check for all strings indicating that a user can't
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index 49935550..e864c86 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -129,7 +129,6 @@
   NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
   NOTIFICATION_MESSAGE: 'mv-msg',
   NTP_CONTENTS: 'ntp-contents',
-  OGB: 'one-google',
   PROMO: 'promo',
   RESTORE_ALL_LINK: 'mv-restore',
   SUGGESTIONS: 'suggestions',
@@ -968,15 +967,32 @@
   if (cmd === 'loaded') {
     tilesAreLoaded = true;
     if (configData.isGooglePage) {
-      // Show search suggestions, promo, and the OGB if they were previously
-      // hidden.
+      // Show search suggestions if they were previously hidden.
       if ($(IDS.SUGGESTIONS)) {
         $(IDS.SUGGESTIONS).style.visibility = 'visible';
       }
-      if ($(IDS.PROMO)) {
-        $(IDS.PROMO).classList.remove(CLASSES.HIDE_PROMO);
+      if (!$('one-google-loader')) {
+        // Load the OneGoogleBar script. It'll create a global variable name
+        // "og" which is a dict corresponding to the native OneGoogleBarData
+        // type. We do this only after all the tiles have loaded, to avoid
+        // slowing down the main page load.
+        var ogScript = document.createElement('script');
+        ogScript.id = 'one-google-loader';
+        ogScript.src = 'chrome-search://local-ntp/one-google.js';
+        document.body.appendChild(ogScript);
+        ogScript.onload = function() {
+          injectOneGoogleBar(og);
+        };
       }
-      $(IDS.OGB).classList.remove('hidden');
+      if (!$('promo-loader')) {
+        var promoScript = document.createElement('script');
+        promoScript.id = 'promo-loader';
+        promoScript.src = 'chrome-search://local-ntp/promo.js';
+        document.body.appendChild(promoScript);
+        promoScript.onload = function() {
+          injectPromo(promo);
+        };
+      }
       $(customBackgrounds.IDS.CUSTOM_LINKS_RESTORE_DEFAULT)
           .classList.toggle(
               customBackgrounds.CLASSES.OPTION_DISABLED,
@@ -1013,15 +1029,11 @@
   }
 }
 
-/**
- * Request data for search suggestions, promo, and the OGB. Insert it into
- * the page once it's available. For search suggestions this should be almost
- * immediately as cached data is always used. Promos and the OGB may need
- * to wait for the asynchronous network request to complete.
- */
-function requestAndInsertGoogleResources() {
+function showSearchSuggestions() {
+  // Inject search suggestions as early as possible to avoid shifting of other
+  // elements.
   if (!$('search-suggestions-loader')) {
-    let ssScript = document.createElement('script');
+    var ssScript = document.createElement('script');
     ssScript.id = 'search-suggestions-loader';
     ssScript.src = 'chrome-search://local-ntp/search-suggestions.js';
     ssScript.async = false;
@@ -1030,26 +1042,6 @@
       injectSearchSuggestions(search_suggestions);
     };
   }
-  if (!$('one-google-loader')) {
-    // Load the OneGoogleBar script. It'll create a global variable |og| which
-    // is a JSON object corresponding to the native OneGoogleBarData type.
-    let ogScript = document.createElement('script');
-    ogScript.id = 'one-google-loader';
-    ogScript.src = 'chrome-search://local-ntp/one-google.js';
-    document.body.appendChild(ogScript);
-    ogScript.onload = function() {
-      injectOneGoogleBar(og);
-    };
-  }
-  if (!$('promo-loader')) {
-    let promoScript = document.createElement('script');
-    promoScript.id = 'promo-loader';
-    promoScript.src = 'chrome-search://local-ntp/promo.js';
-    document.body.appendChild(promoScript);
-    promoScript.onload = function() {
-      injectPromo(promo);
-    };
-  }
 }
 
 
@@ -1115,7 +1107,7 @@
   var searchboxApiHandle = embeddedSearchApiHandle.searchBox;
 
   if (configData.isGooglePage) {
-    requestAndInsertGoogleResources();
+    showSearchSuggestions();
     enableMDIcons();
 
     ntpApiHandle.onaddcustomlinkdone = onAddCustomLinkDone;
@@ -1338,7 +1330,6 @@
   let promoContainer = document.createElement('div');
   promoContainer.id = IDS.PROMO;
   promoContainer.innerHTML += promo.promoHtml;
-  promoContainer.classList.add(CLASSES.HIDE_PROMO);
   $(IDS.NTP_CONTENTS).appendChild(promoContainer);
 
   if (promo.promoLogUrl) {
@@ -1375,10 +1366,6 @@
  * doesn't block the main page load.
  */
 function injectOneGoogleBar(ogb) {
-  if (ogb.barHtml == '') {
-    return;
-  }
-
   var inHeadStyle = document.createElement('style');
   inHeadStyle.type = 'text/css';
   inHeadStyle.appendChild(document.createTextNode(ogb.inHeadStyle));
@@ -1391,8 +1378,9 @@
 
   renderOneGoogleBarTheme();
 
-  var ogElem = $(IDS.OGB);
+  var ogElem = $('one-google');
   ogElem.innerHTML = ogb.barHtml;
+  ogElem.classList.remove('hidden');
 
   var afterBarScript = document.createElement('script');
   afterBarScript.type = 'text/javascript';
diff --git a/chrome/browser/resources/md_user_manager/user_manager.html b/chrome/browser/resources/md_user_manager/user_manager.html
index 43c8ecd4..375a5fe 100644
--- a/chrome/browser/resources/md_user_manager/user_manager.html
+++ b/chrome/browser/resources/md_user_manager/user_manager.html
@@ -2,8 +2,7 @@
 <html build="$i18n{buildType}"
     dir="$i18n{textdirection}"
     lang="$i18n{language}"
-    screen="$i18n{screenType}"
-    $i18n{dark}>
+    screen="$i18n{screenType}">
 <head>
   <meta charset="utf-8">
   <meta name="google" value="notranslate">
@@ -408,6 +407,5 @@
     <include src="../../../../ui/login/account_picker/user_pod_template.html">
   </user-manager-pages>
   <script src="user_manager.js"></script>
-  <link rel="import" href="chrome://resources/html/dark_mode.html">
 </body>
 </html>
diff --git a/chrome/browser/resources/md_user_manager/user_manager.js b/chrome/browser/resources/md_user_manager/user_manager.js
index 620f53d5..7a1d49a 100644
--- a/chrome/browser/resources/md_user_manager/user_manager.js
+++ b/chrome/browser/resources/md_user_manager/user_manager.js
@@ -97,8 +97,7 @@
    *     displayed.
    */
   UserManager.showUserManagerScreen = function(showGuest, showAddPerson) {
-    UserManager.getInstance().showScreen(
-        {id: 'account-picker', data: {disableAddUser: false}});
+    UserManager.getInstance().showScreen({id: 'account-picker', data: {}});
     // Hide control options if the user does not have the right permissions.
     const controlBar = document.querySelector('control-bar');
     controlBar.showGuest = showGuest;
diff --git a/chrome/browser/resources/plugin_metadata/plugins_linux.json b/chrome/browser/resources/plugin_metadata/plugins_linux.json
index 8bcbb06..b427556 100644
--- a/chrome/browser/resources/plugin_metadata/plugins_linux.json
+++ b/chrome/browser/resources/plugin_metadata/plugins_linux.json
@@ -1,5 +1,5 @@
 {
-  "x-version": 37,
+  "x-version": 38,
   "adobe-flash-player": {
     "mime_types": [
       "application/futuresplash",
@@ -10,9 +10,9 @@
     ],
     "versions": [
       {
-        "version": "32.0.0.114",
+        "version": "32.0.0.142",
         "status": "up_to_date",
-        "reference": "https://helpx.adobe.com/security/products/flash-player/apsb19-01.html"
+        "reference": "https://helpx.adobe.com/security/products/flash-player/apsb19-06.html"
       }
     ],
     "lang": "en-US",
diff --git a/chrome/browser/resources/plugin_metadata/plugins_mac.json b/chrome/browser/resources/plugin_metadata/plugins_mac.json
index 3782472..06ce510 100644
--- a/chrome/browser/resources/plugin_metadata/plugins_mac.json
+++ b/chrome/browser/resources/plugin_metadata/plugins_mac.json
@@ -1,5 +1,5 @@
 {
-  "x-version": 43,
+  "x-version": 44,
   "adobe-flash-player": {
     "mime_types": [
       "application/futuresplash",
@@ -7,9 +7,9 @@
     ],
     "versions": [
       {
-        "version": "32.0.0.114",
+        "version": "32.0.0.142",
         "status": "requires_authorization",
-        "reference": "https://helpx.adobe.com/security/products/flash-player/apsb19-01.html"
+        "reference": "https://helpx.adobe.com/security/products/flash-player/apsb19-06.html"
       }
     ],
     "lang": "en-US",
diff --git a/chrome/browser/resources/plugin_metadata/plugins_win.json b/chrome/browser/resources/plugin_metadata/plugins_win.json
index 2c3ec10..2dedd10 100644
--- a/chrome/browser/resources/plugin_metadata/plugins_win.json
+++ b/chrome/browser/resources/plugin_metadata/plugins_win.json
@@ -1,5 +1,5 @@
 {
-  "x-version": 52,
+  "x-version": 53,
   "adobe-flash-player": {
     "mime_types": [
       "application/futuresplash",
@@ -7,9 +7,9 @@
     ],
     "versions": [
       {
-        "version": "32.0.0.114",
+        "version": "32.0.0.142",
         "status": "requires_authorization",
-        "reference": "https://helpx.adobe.com/security/products/flash-player/apsb19-01.html"
+        "reference": "https://helpx.adobe.com/security/products/flash-player/apsb19-06.html"
       }
     ],
     "lang": "en-US",
diff --git a/chrome/browser/resources/print_preview/print_preview_new.html b/chrome/browser/resources/print_preview/print_preview_new.html
index e85a24c1..2917106 100644
--- a/chrome/browser/resources/print_preview/print_preview_new.html
+++ b/chrome/browser/resources/print_preview/print_preview_new.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}" $i18n{dark}>
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
 <head>
   <meta charset="utf-8">
   <style>
@@ -22,6 +22,5 @@
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
   <link rel="import" href="new/app.html">
-  <link rel="import" href="chrome://resources/html/dark_mode.html">
 </body>
 </html>
diff --git a/chrome/browser/resources/safe_browsing/download_file_types.asciipb b/chrome/browser/resources/safe_browsing/download_file_types.asciipb
index 0cb38259..4d608e4 100644
--- a/chrome/browser/resources/safe_browsing/download_file_types.asciipb
+++ b/chrome/browser/resources/safe_browsing/download_file_types.asciipb
@@ -8,7 +8,7 @@
 ##
 ## Top level settings
 ##
-version_id: 31
+version_id: 32
 sampled_ping_probability: 0.01
 max_archived_binaries_to_report: 10
 default_file_type {
@@ -2826,6 +2826,16 @@
   inspection_type: DMG
 }
 file_types {
+  extension: "mobileconfig"
+  uma_value: 356
+  ping_setting: FULL_PING
+  platform_settings {
+    platform: PLATFORM_MAC
+    danger_level: ALLOW_ON_USER_GESTURE
+    auto_open_hint: DISALLOW_AUTO_OPEN
+  }
+}
+file_types {
   extension: "ndif"
   uma_value: 258
   ping_setting: FULL_PING
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
index 3953ab06..28fa3ad2 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
+++ b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
@@ -11,7 +11,17 @@
 
 <dom-module id="settings-appearance-fonts-page">
   <template>
-    <style include="settings-shared"></style>
+    <style include="settings-shared">
+      #minimumSize {
+        align-items: flex-end;
+        display: flex;
+        flex-direction: column;
+      }
+
+      #minimumSizeSample {
+        text-align: end;
+      }
+    </style>
     <div class="settings-box first">
       <div class="start">$i18n{fontSize}</div>
       <settings-slider id="sizeSlider"
@@ -22,20 +32,20 @@
     </div>
     <div class="settings-box">
       <div class="start">$i18n{minimumFont}</div>
-      <div style="
-          font-size:[[computeMinimumFontSize_(
-              prefs.webkit.webprefs.minimum_font_size.value)]]px;
-          font-family:
-              '[[prefs.webkit.webprefs.fonts.standard.Zyyy.value]]';">
-        [[computeMinimumFontSize_(
-                prefs.webkit.webprefs.minimum_font_size.value)]]:
-        $i18n{quickBrownFox}
+      <div id="minimumSize">
+        <settings-slider  pref="{{prefs.webkit.webprefs.minimum_font_size}}"
+            ticks="[[minimumFontSizeRange_]]" label-min="$i18n{tiny}"
+            label-max="$i18n{huge}"></settings-slider>
+        <div id="minimumSizeSample" style="
+            font-size:[[computeMinimumFontSize_(
+                prefs.webkit.webprefs.minimum_font_size.value)]]px;
+            font-family:
+                '[[prefs.webkit.webprefs.fonts.standard.Zyyy.value]]';">
+          [[computeMinimumFontSize_(
+                  prefs.webkit.webprefs.minimum_font_size.value)]]:
+          $i18n{quickBrownFox}
+        </div>
       </div>
-      <settings-slider id="minimumSizeSlider"
-          pref="{{prefs.webkit.webprefs.minimum_font_size}}"
-          ticks="[[minimumFontSizeRange_]]"
-          label-min="$i18n{tiny}" label-max="$i18n{huge}">
-      </settings-slider>
     </div>
     <div class="settings-box">
       <h2>$i18n{standardFont}</h2>
diff --git a/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html b/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
index d8015e55..bb92c4bd 100644
--- a/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
+++ b/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
@@ -38,6 +38,12 @@
         padding: 0 0 0 8px;
       }
 
+      :host-context([dark]) #expired {
+        background-color: unset;
+        font-weight: bold;
+        padding: 0;
+      }
+
       #month {
         width: 70px;
       }
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 494ebd9e..43fec829 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
@@ -101,7 +101,7 @@
                 $i18n{googleAssistantVoiceSettingsDescription}
               </div>
             </div>
-            <controlled-button id="button" class="secondary-button"
+            <controlled-button id="retrainVoiceModel" class="secondary-button"
                 on-click="onRetrainVoiceModelTapped_"
                 label="$i18n{googleAssistantVoiceSettingsRetrainButton}"
                 pref="{{prefs.settings.voice_interaction.hotword.enabled}}">
diff --git a/chrome/browser/resources/settings/people_page/people_page.html b/chrome/browser/resources/settings/people_page/people_page.html
index 0234eae..7696665 100644
--- a/chrome/browser/resources/settings/people_page/people_page.html
+++ b/chrome/browser/resources/settings/people_page/people_page.html
@@ -56,16 +56,25 @@
       }
 
       iron-icon[icon='cr:sync'] {
-       --iron-icon-fill-color: var(--google-green-700);
+        --iron-icon-fill-color: var(--google-green-refresh-700);
+      }
+
+      :host-context([dark]) iron-icon[icon='cr:sync'] {
+        --iron-icon-fill-color: var(--google-green-refresh-300);
       }
 
       #sync-status[actionable] iron-icon[icon='settings:sync-problem'] {
-       --iron-icon-fill-color: var(--settings-error-color);
+        --iron-icon-fill-color: var(--settings-error-color);
       }
 
       #sync-status[actionable].auth-error
           iron-icon[icon='settings:sync-disabled'] {
-       --iron-icon-fill-color: var(--google-blue-500);
+        --iron-icon-fill-color: var(--google-blue-500);
+      }
+
+      :host-context([dark]) #sync-status[actionable].auth-error
+          iron-icon[icon='settings:sync-disabled'] {
+        --iron-icon-fill-color: var(--google-blue-refresh-300);
       }
 
       #sync-status:not([actionable]) .subpage-arrow {
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html
index ee4fa7a5..8c2ebc1 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.html
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html
@@ -51,7 +51,6 @@
       }
 
       #advancedButton {
-        background-color: unset;
         border: none;
         border-radius: initial;
         height: unset;
diff --git a/chrome/browser/resources/settings/settings_shared_css.html b/chrome/browser/resources/settings/settings_shared_css.html
index 52486aca..1d5cff0 100644
--- a/chrome/browser/resources/settings/settings_shared_css.html
+++ b/chrome/browser/resources/settings/settings_shared_css.html
@@ -33,10 +33,6 @@
         flex-shrink: 0;  /* Prevent distortion of icons in cramped UI. */
       }
 
-      iron-icon[icon='cr:check'] {
-       --iron-icon-fill-color: var(--google-green-500);
-      }
-
       iron-icon.policy {
         margin-inline-end: var(--cr-controlled-by-spacing);
       }
@@ -64,11 +60,6 @@
         margin-inline-start: var(--cr-icon-ripple-margin);
       }
 
-      /* For "Advanced" toggle button. */
-      paper-button[toggles][active] {
-        background-color: var(--paper-grey-300);
-      }
-
       /* Special case for buttons inside of toggle-buttons. */
       .settings-box settings-toggle-button paper-button:last-of-type {
         margin-inline-end: 16px;
@@ -115,10 +106,16 @@
       }
 
       cr-radio-group:focus {
+        /* TODO(dbeam): why is this here? It looks weird and I'm not sure how to
+         * actually trigger it with mouse/keyboard/screenreader. */
         background-color: var(--google-grey-300);
         outline: none;
       }
 
+      :host-context([dark]) cr-radio-group:focus {
+        background-color: var(--google-grey-800);
+      }
+
       /* See also: .no-min-width below. */
       .text-elide {
         @apply --cr-text-elide;
diff --git a/chrome/browser/resources/usb_internals/BUILD.gn b/chrome/browser/resources/usb_internals/BUILD.gn
index 0a3f82b..2d9afb3b 100644
--- a/chrome/browser/resources/usb_internals/BUILD.gn
+++ b/chrome/browser/resources/usb_internals/BUILD.gn
@@ -6,19 +6,20 @@
 
 js_type_check("closure_compile") {
   deps = [
+    ":device_page",
     ":usb_internals",
   ]
 }
 
 js_library("usb_internals") {
-  sources = [
-    "usb_internals.js",
-  ]
-
   deps = [
+    ":device_page"
     "//chrome/browser/ui/webui/usb_internals:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:cr",
     "//ui/webui/resources/js:util",
     "//ui/webui/resources/js/cr/ui:tabs",
   ]
 }
+
+js_library("devices_page") {
+}
diff --git a/chrome/browser/resources/usb_internals/devices_page.js b/chrome/browser/resources/usb_internals/devices_page.js
new file mode 100644
index 0000000..134977c
--- /dev/null
+++ b/chrome/browser/resources/usb_internals/devices_page.js
@@ -0,0 +1,65 @@
+// 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.
+
+/**
+ * Javascript for DevicesPage, served from
+ *     chrome://usb-internals/.
+ */
+
+cr.define('devices_page', function() {
+  /**
+   * Sets the device collection for the page's device table.
+   * @param {!Array<!device.mojom.UsbDeviceInfo>} devices
+   */
+  function setDevices(devices) {
+    const tableBody = $('device-list');
+    tableBody.innerHTML = '';
+
+    const rowTemplate = document.querySelector('#device-row');
+
+    for (const device of devices) {
+      const clone = document.importNode(rowTemplate.content, true);
+
+      const td = clone.querySelectorAll('td');
+
+      td[0].textContent = device.busNumber;
+      td[1].textContent = device.portNumber;
+      td[2].textContent = toHex(device.vendorId);
+      td[3].textContent = toHex(device.productId);
+      if (device.manufacturerName) {
+        td[4].textContent = decodeString16(device.manufacturerName.data);
+      }
+      if (device.productName) {
+        td[5].textContent = decodeString16(device.productName.data);
+      }
+      if (device.serialNumber) {
+        td[6].textContent = decodeString16(device.serialNumber.data);
+      }
+
+      tableBody.appendChild(clone);
+    }
+  }
+
+  /**
+   * Parses utf16 coded string.
+   * @param {!mojoBase.mojom.String16} arr
+   * @return {string}
+   */
+  function decodeString16(arr) {
+    return arr.map(ch => String.fromCodePoint(ch)).join('');
+  }
+
+  /**
+   * Parses the decimal number to hex format.
+   * @param {number} num
+   * @return {string}
+   */
+  function toHex(num) {
+    return '0x' + num.toString(16).padStart(4, '0').slice(-4).toUpperCase();
+  }
+
+  return {
+    setDevices: setDevices,
+  };
+});
diff --git a/chrome/browser/resources/usb_internals/usb_internals.html b/chrome/browser/resources/usb_internals/usb_internals.html
index cc025f8d..af7ff97c 100644
--- a/chrome/browser/resources/usb_internals/usb_internals.html
+++ b/chrome/browser/resources/usb_internals/usb_internals.html
@@ -16,18 +16,26 @@
   <script src="chrome://resources/js/cr/ui/tabs.js"></script>
   <script src="chrome://resources/js/mojo_bindings_lite.js"></script>
   <script src="chrome://resources/js/util.js"></script>
-  <script src="url/mojom/url.mojom-lite.js"></script>
-  <script src="device/usb/public/mojom/device_manager_test.mojom-lite.js">
-  </script>
-  <script
-    src="chrome/browser/ui/webui/usb_internals/usb_internals.mojom-lite.js">
-  </script>
+  <script src="chrome://resources/js/big_buffer.mojom-lite.js"></script>
+  <script src="chrome://resources/js/file.mojom-lite.js"></script>
+  <script src="chrome://resources/js/string16.mojom-lite.js"></script>
+
+  <script src="url.mojom-lite.js"></script>
+  <script src="device.mojom-lite.js"></script>
+  <script src="device_manager_client.mojom-lite.js"></script>
+  <script src="device_enumeration_options.mojom-lite.js"></script>
+  <script src="device_manager.mojom-lite.js"></script>
+  <script src="device_manager_test.mojom-lite.js"></script>
+  <script src="usb_internals.mojom-lite.js"></script>
+
+  <script src="devices_page.js"></script>
 </head>
 
 <body>
   <tabbox>
     <tabs>
       <tab>Test Devices</tab>
+      <tab>Devices</tab>
     </tabs>
     <tabpanels>
       <tabpanel>
@@ -82,6 +90,40 @@
           </form>
         </div>
       </tabpanel>
+
+      <tabpanel>
+        <!-- Devices -->
+        <h2>Devices</h2>
+        <table class="styled-table">
+          <thead>
+            <tr>
+              <th>Bus Number</th>
+              <th>Port Number</th>
+              <th>Vendor Id</th>
+              <th>Product Id</th>
+              <th>Manufacturer Name</th>
+              <th>Product Name</th>
+              <th>Serial Number</th>
+              <th>
+            </tr>
+          </thead>
+          <tbody id="device-list"></tbody>
+
+          <template id="device-row">
+            <tr>
+              <td></td>
+              <td></td>
+              <td></td>
+              <td></td>
+              <td></td>
+              <td></td>
+              <td></td>
+              <td><button>Inspect</button></td>
+            </tr>
+          </template>
+
+        </table>
+      </tabpanel>
     </tabpanels>
   </tabbox>
 
diff --git a/chrome/browser/resources/usb_internals/usb_internals.js b/chrome/browser/resources/usb_internals/usb_internals.js
index 09fe786..accc7df 100644
--- a/chrome/browser/resources/usb_internals/usb_internals.js
+++ b/chrome/browser/resources/usb_internals/usb_internals.js
@@ -8,23 +8,36 @@
 cr.define('usb_internals', function() {
   class UsbInternals {
     constructor() {
+      const pageHandler = mojom.UsbInternalsPageHandler.getProxy();
+
       // Connection to the UsbInternalsPageHandler instance running in the
       // browser process.
-      this.usbManagerTest = null;
+      /** @private {device.mojom.UsbDeviceManagerProxy} */
+      this.usbManager_ = new device.mojom.UsbDeviceManagerProxy;
+      pageHandler.bindUsbDeviceManagerInterface(
+          this.usbManager_.$.createRequest());
 
-      const pageHandler = mojom.UsbInternalsPageHandler.getProxy();
-      this.usbManagerTest = new device.mojom.UsbDeviceManagerTestProxy;
-      pageHandler.bindTestInterface(this.usbManagerTest.$.createRequest());
+      /** @private {device.mojom.UsbDeviceManagerTestProxy} */
+      this.usbManagerTest_ = new device.mojom.UsbDeviceManagerTestProxy;
+      pageHandler.bindTestInterface(this.usbManagerTest_.$.createRequest());
 
       cr.ui.decorate('tabbox', cr.ui.TabBox);
+
+      this.refreshDeviceList();
+
       $('add-test-device-form').addEventListener('submit', (event) => {
         this.addTestDevice(event);
       });
-      this.refreshDeviceList();
+      this.refreshTestDeviceList();
     }
 
     async refreshDeviceList() {
-      const response = await this.usbManagerTest.getTestDevices();
+      const response = await this.usbManager_.getDevices();
+      devices_page.setDevices(response.results);
+    }
+
+    async refreshTestDeviceList() {
+      const response = await this.usbManagerTest_.getTestDevices();
 
       const tableBody = $('test-device-list');
       tableBody.innerHTML = '';
@@ -41,8 +54,8 @@
 
         const removeButton = clone.querySelector('button');
         removeButton.addEventListener('click', async () => {
-          await this.usbManagerTest.removeDeviceForTesting(device.guid);
-          this.refreshDeviceList();
+          await this.usbManagerTest_.removeDeviceForTesting(device.guid);
+          this.refreshTestDeviceList();
         });
 
         tableBody.appendChild(clone);
@@ -52,11 +65,11 @@
     async addTestDevice(event) {
       event.preventDefault();
 
-      const response = await this.usbManagerTest.addDeviceForTesting(
+      const response = await this.usbManagerTest_.addDeviceForTesting(
           $('test-device-name').value, $('test-device-serial').value,
           $('test-device-landing-page').value);
       if (response.success) {
-        this.refreshDeviceList();
+        this.refreshTestDeviceList();
       }
 
       $('add-test-device-result').textContent = response.message;
@@ -72,4 +85,4 @@
 
 document.addEventListener('DOMContentLoaded', () => {
   new usb_internals.UsbInternals();
-});
\ No newline at end of file
+});
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 797df9d5..5f4d5103 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -269,6 +269,16 @@
         "telemetry/android/android_telemetry_service.h",
       ]
       deps += [ "//components/safe_browsing/android:safe_browsing_mobile" ]
+    } else if (safe_browsing_mode == 3) {
+      sources += [
+        "../loader/safe_browsing_resource_throttle.cc",
+        "../loader/safe_browsing_resource_throttle.h",
+        "android/services_delegate_android.cc",
+        "android/services_delegate_android.h",
+        "telemetry/android/android_telemetry_service.cc",
+        "telemetry/android/android_telemetry_service.h",
+      ]
+      deps += [ "//components/safe_browsing/db:db" ]
     }
   }
 }
diff --git a/chrome/browser/safe_browsing/android/services_delegate_android.cc b/chrome/browser/safe_browsing/android/services_delegate_android.cc
index cc65b2a..dc8dc71 100644
--- a/chrome/browser/safe_browsing/android/services_delegate_android.cc
+++ b/chrome/browser/safe_browsing/android/services_delegate_android.cc
@@ -7,9 +7,11 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h"
 #include "chrome/browser/safe_browsing/telemetry/telemetry_service.h"
 #include "components/safe_browsing/android/remote_database_manager.h"
+#include "components/safe_browsing/db/v4_local_database_manager.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_manager.h"
@@ -52,10 +54,24 @@
 
 void ServicesDelegateAndroid::Initialize() {
   if (!database_manager_set_for_tests_) {
+#if defined(SAFE_BROWSING_DB_REMOTE)
     database_manager_ =
         base::WrapRefCounted(new RemoteSafeBrowsingDatabaseManager());
+#else
+    database_manager_ = V4LocalDatabaseManager::Create(
+        SafeBrowsingService::GetBaseFilename(),
+        base::BindRepeating(
+            &ServicesDelegateAndroid::GetEstimatedExtendedReportingLevel,
+            base::Unretained(this)));
+#endif
   }
 }
+
+ExtendedReportingLevel
+ServicesDelegateAndroid::GetEstimatedExtendedReportingLevel() const {
+  return safe_browsing_service_->estimated_extended_reporting_by_prefs();
+}
+
 void ServicesDelegateAndroid::SetDatabaseManagerForTest(
     SafeBrowsingDatabaseManager* database_manager) {
   database_manager_set_for_tests_ = true;
diff --git a/chrome/browser/safe_browsing/android/services_delegate_android.h b/chrome/browser/safe_browsing/android/services_delegate_android.h
index f821aa1..b1d8ac6 100644
--- a/chrome/browser/safe_browsing/android/services_delegate_android.h
+++ b/chrome/browser/safe_browsing/android/services_delegate_android.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "chrome/browser/safe_browsing/services_delegate.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 
 namespace safe_browsing {
 
@@ -55,6 +56,14 @@
 
   std::string GetSafetyNetId() const override;
 
+  // Reports the current extended reporting level. Note that this is an
+  // estimation and may not always be correct. It is possible that the
+  // estimation finds both Scout and legacy extended reporting to be enabled.
+  // This can happen, for instance, if one profile has Scout enabled and another
+  // has legacy extended reporting enabled. In such a case, this method reports
+  // LEGACY as the current level.
+  ExtendedReportingLevel GetEstimatedExtendedReportingLevel() const;
+
   SafeBrowsingService* const safe_browsing_service_;
 
   // The telemetry service tied to the current profile.
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
index 682813b..067730b 100644
--- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
@@ -51,6 +51,8 @@
   void OnDownloadUpdated(download::DownloadItem* download) override;
 
  private:
+  friend class AndroidTelemetryServiceTest;
+
   // Whether the ping can be sent, based on empty web_contents, or incognito
   // mode, or extended reporting opt-in status,
   bool CanSendPing(download::DownloadItem* item);
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
new file mode 100644
index 0000000..50b85d5
--- /dev/null
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
@@ -0,0 +1,176 @@
+// 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/safe_browsing/telemetry/android/android_telemetry_service.h"
+
+#include <memory>
+
+#include "base/task/post_task.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/download/public/common/mock_download_item.h"
+#include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
+#include "components/safe_browsing/features.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_item_utils.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+class AndroidTelemetryServiceTest : public testing::Test {
+ public:
+  AndroidTelemetryServiceTest() = default;
+
+ protected:
+  Profile* profile() { return profile_.get(); }
+
+  void SetUp() override {
+    browser_process_ = TestingBrowserProcess::GetGlobal();
+
+    system_request_context_getter_ =
+        base::MakeRefCounted<net::TestURLRequestContextGetter>(
+            base::CreateSingleThreadTaskRunnerWithTraits(
+                {content::BrowserThread::IO}));
+    browser_process_->SetSystemRequestContext(
+        system_request_context_getter_.get());
+    sb_service_ =
+        safe_browsing::SafeBrowsingService::CreateSafeBrowsingService();
+    browser_process_->SetSafeBrowsingService(sb_service_.get());
+    sb_service_->Initialize();
+    base::RunLoop().RunUntilIdle();
+
+    download_item_.reset(new ::testing::NiceMock<download::MockDownloadItem>());
+    profile_.reset(new TestingProfile());
+
+    telemetry_service_ =
+        std::make_unique<AndroidTelemetryService>(sb_service_.get(), profile());
+  }
+
+  void TearDown() override {}
+
+  bool CanSendPing(download::DownloadItem* item) {
+    return telemetry_service_->CanSendPing(item);
+  }
+
+  void SetOffTheRecordProfile() {
+    telemetry_service_->profile_ = profile()->GetOffTheRecordProfile();
+  }
+
+  void ResetProfile() { telemetry_service_->profile_ = profile(); }
+
+ protected:
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+  scoped_refptr<safe_browsing::SafeBrowsingService> sb_service_;
+  std::unique_ptr<download::MockDownloadItem> download_item_;
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<AndroidTelemetryService> telemetry_service_;
+  TestingBrowserProcess* browser_process_;
+  scoped_refptr<net::URLRequestContextGetter> system_request_context_getter_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(AndroidTelemetryServiceTest, CantSendPing_NonApk) {
+  ON_CALL(*download_item_, GetMimeType())
+      .WillByDefault(testing::Return("text/plain"));
+  EXPECT_FALSE(CanSendPing(download_item_.get()));
+}
+
+TEST_F(AndroidTelemetryServiceTest, CantSendPing_SafeBrowsingDisabled) {
+  // Disable Safe Browsing.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, false);
+
+  // Enable Scout Reporting.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled,
+                                    true);
+  // Enable feature.
+  scoped_feature_list_.InitAndEnableFeature(kTelemetryForApkDownloads);
+  // Simulate APK download.
+  ON_CALL(*download_item_, GetMimeType())
+      .WillByDefault(
+          testing::Return("application/vnd.android.package-archive"));
+
+  EXPECT_FALSE(CanSendPing(download_item_.get()));
+}
+
+TEST_F(AndroidTelemetryServiceTest, CantSendPing_IncognitoMode) {
+  // No event is triggered if in incognito mode..
+  SetOffTheRecordProfile();
+
+  // Enable Safe Browsing.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true);
+  // Enable Scout Reporting.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled,
+                                    true);
+  // Enable feature.
+  scoped_feature_list_.InitAndEnableFeature(kTelemetryForApkDownloads);
+  // Simulate APK download.
+  ON_CALL(*download_item_, GetMimeType())
+      .WillByDefault(
+          testing::Return("application/vnd.android.package-archive"));
+
+  EXPECT_FALSE(CanSendPing(download_item_.get()));
+
+  ResetProfile();
+}
+
+TEST_F(AndroidTelemetryServiceTest, CantSendPing_SBERDisabled) {
+  // Disable Scout Reporting.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled,
+                                    false);
+
+  // Enable Safe Browsing.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true);
+  // Enable feature.
+  scoped_feature_list_.InitAndEnableFeature(kTelemetryForApkDownloads);
+  // Simulate APK download.
+  ON_CALL(*download_item_, GetMimeType())
+      .WillByDefault(
+          testing::Return("application/vnd.android.package-archive"));
+
+  EXPECT_FALSE(CanSendPing(download_item_.get()));
+}
+
+TEST_F(AndroidTelemetryServiceTest, CantSendPing_FeatureDisabled) {
+  // Disable feature.
+  scoped_feature_list_.InitAndDisableFeature(kTelemetryForApkDownloads);
+
+  // Enable Safe Browsing.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true);
+  // Enable Scout Reporting.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled,
+                                    true);
+  // Simulate APK download.
+  ON_CALL(*download_item_, GetMimeType())
+      .WillByDefault(
+          testing::Return("application/vnd.android.package-archive"));
+
+  EXPECT_FALSE(CanSendPing(download_item_.get()));
+}
+
+TEST_F(AndroidTelemetryServiceTest, CanSendPing_AllConditionsMet) {
+  // Enable Safe Browsing.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true);
+  // Enable Scout Reporting.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled,
+                                    true);
+  // Enable feature.
+  scoped_feature_list_.InitAndEnableFeature(kTelemetryForApkDownloads);
+  // Simulate APK download.
+  ON_CALL(*download_item_, GetMimeType())
+      .WillByDefault(
+          testing::Return("application/vnd.android.package-archive"));
+
+  // The ping should be sent.
+  EXPECT_TRUE(CanSendPing(download_item_.get()));
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index cc26a4a..d1fbd87 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -367,42 +367,28 @@
   return images;
 }
 
-scoped_refptr<base::RefCountedString> GetOGBString(
-    const base::Optional<OneGoogleBarData>& og) {
-  base::DictionaryValue dict;
-  if (og.has_value()) {
-    dict.SetString("barHtml", og->bar_html);
-    dict.SetString("inHeadScript", og->in_head_script);
-    dict.SetString("inHeadStyle", og->in_head_style);
-    dict.SetString("afterBarScript", og->after_bar_script);
-    dict.SetString("endOfBodyHtml", og->end_of_body_html);
-    dict.SetString("endOfBodyScript", og->end_of_body_script);
-  } else {
-    dict.SetString("barHtml", std::string());
-  }
-
-  std::string js;
-  base::JSONWriter::Write(dict, &js);
-  js = "var og = " + js + ";";
-  return scoped_refptr<base::RefCountedString>(
-      base::RefCountedString::TakeString(&js));
+std::unique_ptr<base::DictionaryValue> ConvertOGBDataToDict(
+    const OneGoogleBarData& og) {
+  auto result = std::make_unique<base::DictionaryValue>();
+  result->SetString("barHtml", og.bar_html);
+  result->SetString("inHeadScript", og.in_head_script);
+  result->SetString("inHeadStyle", og.in_head_style);
+  result->SetString("afterBarScript", og.after_bar_script);
+  result->SetString("endOfBodyHtml", og.end_of_body_html);
+  result->SetString("endOfBodyScript", og.end_of_body_script);
+  return result;
 }
 
-scoped_refptr<base::RefCountedString> GetPromoString(
+std::unique_ptr<base::DictionaryValue> ConvertPromoDataToDict(
     const base::Optional<PromoData>& promo) {
-  base::DictionaryValue dict;
+  auto result = std::make_unique<base::DictionaryValue>();
   if (promo.has_value()) {
-    dict.SetString("promoHtml", promo->promo_html);
-    dict.SetString("promoLogUrl", promo->promo_log_url.spec());
+    result->SetString("promoHtml", promo->promo_html);
+    result->SetString("promoLogUrl", promo->promo_log_url.spec());
   } else {
-    dict.SetString("promoHtml", std::string());
+    result->SetString("promoHtml", std::string());
   }
-
-  std::string js;
-  base::JSONWriter::Write(dict, &js);
-  js = "var promo = " + js + ";";
-  return scoped_refptr<base::RefCountedString>(
-      base::RefCountedString::TakeString(&js));
+  return result;
 }
 
 std::unique_ptr<base::DictionaryValue> ConvertSearchSuggestDataToDict(
@@ -545,10 +531,6 @@
     UpdateConfigData();
   }
 
-  bool DefaultSearchProviderIsGoogle() {
-    return search::DefaultSearchProviderIsGoogle(service_);
-  }
-
   ~SearchConfigurationProvider() override {
     if (service_)
       service_->RemoveObserver(this);
@@ -830,32 +812,40 @@
   if (stripped_path == kOneGoogleBarScriptFilename) {
     if (!one_google_bar_service_) {
       callback.Run(nullptr);
-    } else {
-      ServeOneGoogleBarWhenAvailable(callback);
+      return;
     }
+
+    one_google_bar_requests_.emplace_back(base::TimeTicks::Now(), callback);
+    one_google_bar_service_->Refresh();
+
     return;
   }
 
   if (stripped_path == kPromoScriptFilename) {
     if (!promo_service_) {
       callback.Run(nullptr);
-    } else {
-      ServePromoWhenAvailable(callback);
+      return;
     }
+
+    // TODO(crbug/909931): There's no need to fetch the promo on each load,
+    // we can sometimes use cached data.
+    promo_requests_.emplace_back(base::TimeTicks::Now(), callback);
+    promo_service_->Refresh();
+
     return;
   }
 
-  // Search suggestions always used a cached value, so there is no need to
-  // refresh the data until the old data is used.
   if (stripped_path == kSearchSuggestionsScriptFilename) {
     if (!search_suggest_service_) {
       callback.Run(nullptr);
-    } else {
-      ServeSearchSuggestionsIfAvailable(callback);
-
-      pending_search_suggest_request_ = base::TimeTicks::Now();
-      search_suggest_service_->Refresh();
+      return;
     }
+
+    MaybeServeSearchSuggestions(callback);
+
+    search_suggest_requests_.emplace_back(base::TimeTicks::Now());
+    search_suggest_service_->Refresh();
+
     return;
   }
 
@@ -891,10 +881,6 @@
 #endif  // !defined(GOOGLE_CHROME_BUILD)
 
   if (stripped_path == kMainHtmlFilename) {
-    if (search_config_provider_->DefaultSearchProviderIsGoogle()) {
-      InitiatePromoAndOGBRequests();
-    }
-
     std::string html = ui::ResourceBundle::GetSharedInstance()
                            .GetRawDataResource(IDR_LOCAL_NTP_HTML)
                            .as_string();
@@ -1150,40 +1136,37 @@
 void LocalNtpSource::OnSearchSuggestDataUpdated() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (pending_search_suggest_request_.has_value()) {
-    return;
-  }
-
   SearchSuggestLoader::Status result =
       search_suggest_service_->search_suggest_status();
-  base::TimeDelta delta =
-      base::TimeTicks::Now() - *pending_search_suggest_request_;
-  UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.SearchSuggestions.RequestLatency",
-                             delta);
-  SearchSuggestionsRequestStatus request_status =
-      SearchSuggestionsRequestStatus::UNKNOWN_ERROR;
+  base::TimeTicks now = base::TimeTicks::Now();
+  for (const auto& request : search_suggest_requests_) {
+    base::TimeDelta delta = now - request.start_time;
+    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.SearchSuggestions.RequestLatency",
+                               delta);
+    SearchSuggestionsRequestStatus request_status =
+        SearchSuggestionsRequestStatus::UNKNOWN_ERROR;
 
-  if (result == SearchSuggestLoader::Status::SIGNED_OUT) {
-    request_status = SearchSuggestionsRequestStatus::SIGNED_OUT;
-  } else if (result == SearchSuggestLoader::Status::OPTED_OUT) {
-    request_status = SearchSuggestionsRequestStatus::OPTED_OUT;
-  } else if (result == SearchSuggestLoader::Status::IMPRESSION_CAP) {
-    request_status = SearchSuggestionsRequestStatus::IMPRESSION_CAP;
-  } else if (result == SearchSuggestLoader::Status::REQUESTS_FROZEN) {
-    request_status = SearchSuggestionsRequestStatus::FROZEN;
-  } else if (result == SearchSuggestLoader::Status::OK) {
-    request_status = SearchSuggestionsRequestStatus::SENT;
-    UMA_HISTOGRAM_MEDIUM_TIMES(
-        "NewTabPage.SearchSuggestions.RequestLatency.Success", delta);
-  } else if (result == SearchSuggestLoader::Status::FATAL_ERROR) {
-    request_status = SearchSuggestionsRequestStatus::FATAL_ERROR;
-    UMA_HISTOGRAM_MEDIUM_TIMES(
-        "NewTabPage.SearchSuggestions.RequestLatency.Failure", delta);
+    if (result == SearchSuggestLoader::Status::SIGNED_OUT) {
+      request_status = SearchSuggestionsRequestStatus::SIGNED_OUT;
+    } else if (result == SearchSuggestLoader::Status::OPTED_OUT) {
+      request_status = SearchSuggestionsRequestStatus::OPTED_OUT;
+    } else if (result == SearchSuggestLoader::Status::IMPRESSION_CAP) {
+      request_status = SearchSuggestionsRequestStatus::IMPRESSION_CAP;
+    } else if (result == SearchSuggestLoader::Status::REQUESTS_FROZEN) {
+      request_status = SearchSuggestionsRequestStatus::FROZEN;
+    } else if (result == SearchSuggestLoader::Status::OK) {
+      request_status = SearchSuggestionsRequestStatus::SENT;
+      UMA_HISTOGRAM_MEDIUM_TIMES(
+          "NewTabPage.SearchSuggestions.RequestLatency.Success", delta);
+    } else if (result == SearchSuggestLoader::Status::FATAL_ERROR) {
+      request_status = SearchSuggestionsRequestStatus::FATAL_ERROR;
+      UMA_HISTOGRAM_MEDIUM_TIMES(
+          "NewTabPage.SearchSuggestions.RequestLatency.Failure", delta);
+    }
+    UMA_HISTOGRAM_ENUMERATION("NewTabPage.SearchSuggestions.RequestStatus",
+                              request_status);
   }
-  UMA_HISTOGRAM_ENUMERATION("NewTabPage.SearchSuggestions.RequestStatus",
-                            request_status);
-
-  pending_search_suggest_request_ = base::nullopt;
+  search_suggest_requests_.clear();
 }
 
 void LocalNtpSource::OnSearchSuggestServiceShuttingDown() {
@@ -1192,8 +1175,7 @@
   search_suggest_service_observer_.RemoveAll();
   search_suggest_service_ = nullptr;
 }
-
-void LocalNtpSource::ServeSearchSuggestionsIfAvailable(
+void LocalNtpSource::MaybeServeSearchSuggestions(
     const content::URLDataSource::GotDataCallback& callback) {
   base::Optional<SearchSuggestData> data =
       search_suggest_service_->search_suggest_data();
@@ -1213,89 +1195,59 @@
     const base::Optional<OneGoogleBarData>& data) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (!pending_one_google_bar_request_.has_value()) {
+  if (one_google_bar_requests_.empty())
     return;
-  }
 
   scoped_refptr<base::RefCountedString> result;
   if (data.has_value()) {
-    result = GetOGBString(data);
+    std::string js;
+    base::JSONWriter::Write(*ConvertOGBDataToDict(*data), &js);
+    js = "var og = " + js + ";";
+    result = base::RefCountedString::TakeString(&js);
   }
 
-  base::TimeDelta delta =
-      base::TimeTicks::Now() - *pending_one_google_bar_request_;
-  UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.OneGoogleBar.RequestLatency", delta);
-  if (result) {
-    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.OneGoogleBar.RequestLatency.Success",
-                               delta);
-  } else {
-    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.OneGoogleBar.RequestLatency.Failure",
-                               delta);
+  base::TimeTicks now = base::TimeTicks::Now();
+  for (const auto& request : one_google_bar_requests_) {
+    request.callback.Run(result);
+    base::TimeDelta delta = now - request.start_time;
+    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.OneGoogleBar.RequestLatency", delta);
+    if (result) {
+      UMA_HISTOGRAM_MEDIUM_TIMES(
+          "NewTabPage.OneGoogleBar.RequestLatency.Success", delta);
+    } else {
+      UMA_HISTOGRAM_MEDIUM_TIMES(
+          "NewTabPage.OneGoogleBar.RequestLatency.Failure", delta);
+    }
   }
-  for (const auto& callback : one_google_bar_callbacks_) {
-    callback.Run(result);
-  }
-  pending_one_google_bar_request_ = base::nullopt;
-  one_google_bar_callbacks_.clear();
-}
-
-void LocalNtpSource::ServeOneGoogleBarWhenAvailable(
-    const content::URLDataSource::GotDataCallback& callback) {
-  base::Optional<OneGoogleBarData> data =
-      one_google_bar_service_->one_google_bar_data();
-
-  if (!pending_one_google_bar_request_.has_value()) {
-    callback.Run(GetOGBString(data));
-  } else {
-    one_google_bar_callbacks_.emplace_back(callback);
-  }
+  one_google_bar_requests_.clear();
 }
 
 void LocalNtpSource::ServePromo(const base::Optional<PromoData>& data) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (!pending_promo_request_.has_value()) {
+  if (promo_requests_.empty())
     return;
-  }
 
-  scoped_refptr<base::RefCountedString> result = GetPromoString(data);
+  scoped_refptr<base::RefCountedString> result;
+  std::string js;
+  base::JSONWriter::Write(*ConvertPromoDataToDict(data), &js);
+  js = "var promo = " + js + ";";
+  result = base::RefCountedString::TakeString(&js);
 
-  base::TimeDelta delta = base::TimeTicks::Now() - *pending_promo_request_;
-  UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency", delta);
-  if (result) {
-    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency.Success",
-                               delta);
-  } else {
-    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency.Failure",
-                               delta);
+  base::TimeTicks now = base::TimeTicks::Now();
+  for (const auto& request : promo_requests_) {
+    request.callback.Run(result);
+    base::TimeDelta delta = now - request.start_time;
+    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency", delta);
+    if (result) {
+      UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency.Success",
+                                 delta);
+    } else {
+      UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency.Failure",
+                                 delta);
+    }
   }
-  for (const auto& callback : promo_callbacks_) {
-    callback.Run(result);
-  }
-  pending_promo_request_ = base::nullopt;
-  promo_callbacks_.clear();
-}
-
-void LocalNtpSource::ServePromoWhenAvailable(
-    const content::URLDataSource::GotDataCallback& callback) {
-  base::Optional<PromoData> data = promo_service_->promo_data();
-
-  if (!pending_promo_request_.has_value()) {
-    callback.Run(GetPromoString(data));
-  } else {
-    promo_callbacks_.emplace_back(callback);
-  }
-}
-
-void LocalNtpSource::InitiatePromoAndOGBRequests() {
-  if (one_google_bar_service_) {
-    pending_one_google_bar_request_ = base::TimeTicks::Now();
-    one_google_bar_service_->Refresh();
-  }
-  if (promo_service_) {
-    pending_promo_request_ = base::TimeTicks::Now();
-    promo_service_->Refresh();
-  }
+  promo_requests_.clear();
 }
 
 LocalNtpSource::NtpBackgroundRequest::NtpBackgroundRequest(
@@ -1307,3 +1259,31 @@
     const NtpBackgroundRequest&) = default;
 
 LocalNtpSource::NtpBackgroundRequest::~NtpBackgroundRequest() = default;
+
+LocalNtpSource::OneGoogleBarRequest::OneGoogleBarRequest(
+    base::TimeTicks start_time,
+    const content::URLDataSource::GotDataCallback& callback)
+    : start_time(start_time), callback(callback) {}
+
+LocalNtpSource::OneGoogleBarRequest::OneGoogleBarRequest(
+    const OneGoogleBarRequest&) = default;
+
+LocalNtpSource::OneGoogleBarRequest::~OneGoogleBarRequest() = default;
+
+LocalNtpSource::PromoRequest::PromoRequest(
+    base::TimeTicks start_time,
+    const content::URLDataSource::GotDataCallback& callback)
+    : start_time(start_time), callback(callback) {}
+
+LocalNtpSource::PromoRequest::PromoRequest(const PromoRequest&) = default;
+
+LocalNtpSource::PromoRequest::~PromoRequest() = default;
+
+LocalNtpSource::SearchSuggestRequest::SearchSuggestRequest(
+    base::TimeTicks start_time)
+    : start_time(start_time) {}
+
+LocalNtpSource::SearchSuggestRequest::SearchSuggestRequest(
+    const SearchSuggestRequest&) = default;
+
+LocalNtpSource::SearchSuggestRequest::~SearchSuggestRequest() = default;
diff --git a/chrome/browser/search/local_ntp_source.h b/chrome/browser/search/local_ntp_source.h
index a94d670..7a4811f 100644
--- a/chrome/browser/search/local_ntp_source.h
+++ b/chrome/browser/search/local_ntp_source.h
@@ -69,6 +69,35 @@
     content::URLDataSource::GotDataCallback callback;
   };
 
+  struct OneGoogleBarRequest {
+    OneGoogleBarRequest(
+        base::TimeTicks start_time,
+        const content::URLDataSource::GotDataCallback& callback);
+    OneGoogleBarRequest(const OneGoogleBarRequest&);
+    ~OneGoogleBarRequest();
+
+    base::TimeTicks start_time;
+    content::URLDataSource::GotDataCallback callback;
+  };
+
+  struct PromoRequest {
+    PromoRequest(base::TimeTicks start_time,
+                 const content::URLDataSource::GotDataCallback& callback);
+    PromoRequest(const PromoRequest&);
+    ~PromoRequest();
+
+    base::TimeTicks start_time;
+    content::URLDataSource::GotDataCallback callback;
+  };
+
+  struct SearchSuggestRequest {
+    explicit SearchSuggestRequest(base::TimeTicks start_time);
+    explicit SearchSuggestRequest(const SearchSuggestRequest&);
+    ~SearchSuggestRequest();
+
+    base::TimeTicks start_time;
+  };
+
   // Overridden from content::URLDataSource:
   std::string GetSource() const override;
   void StartDataRequest(
@@ -102,32 +131,13 @@
   void OnSearchSuggestDataUpdated() override;
   void OnSearchSuggestServiceShuttingDown() override;
 
-  // Called when the OGB data is available and serves |data| to any pending
-  // request from the NTP.
   void ServeOneGoogleBar(const base::Optional<OneGoogleBarData>& data);
-  // Called when the page requests OGB data. If the data is available it
-  // is returned immediately, otherwise it is returned when it becomes available
-  // in ServeOneGoogleBar.
-  void ServeOneGoogleBarWhenAvailable(
-      const content::URLDataSource::GotDataCallback& callback);
 
-  // Called when the promo data is available and serves |data| to any pending
-  // requests from the NTP.
   void ServePromo(const base::Optional<PromoData>& data);
-  // Called when the page requests promo data. If the data is available it
-  // is returned immediately, otherwise it is returned when it becomes
-  // available in ServePromo.
-  void ServePromoWhenAvailable(
-      const content::URLDataSource::GotDataCallback& callback);
 
-  // If suggestion data is available return it immediately, otherwise no search
-  // suggestions will be shown on this NTP load.
-  void ServeSearchSuggestionsIfAvailable(
+  void MaybeServeSearchSuggestions(
       const content::URLDataSource::GotDataCallback& callback);
 
-  // Start requests for the promo and OGB.
-  void InitiatePromoAndOGBRequests();
-
   Profile* const profile_;
 
   std::vector<NtpBackgroundRequest> ntp_background_collections_requests_;
@@ -138,23 +148,20 @@
   ScopedObserver<NtpBackgroundService, NtpBackgroundServiceObserver>
       ntp_background_service_observer_;
 
-  base::Optional<base::TimeTicks> pending_one_google_bar_request_;
-  std::vector<content::URLDataSource::GotDataCallback>
-      one_google_bar_callbacks_;
+  std::vector<OneGoogleBarRequest> one_google_bar_requests_;
 
   OneGoogleBarService* one_google_bar_service_;
 
   ScopedObserver<OneGoogleBarService, OneGoogleBarServiceObserver>
       one_google_bar_service_observer_;
 
-  base::Optional<base::TimeTicks> pending_promo_request_;
-  std::vector<content::URLDataSource::GotDataCallback> promo_callbacks_;
+  std::vector<PromoRequest> promo_requests_;
 
   PromoService* promo_service_;
 
   ScopedObserver<PromoService, PromoServiceObserver> promo_service_observer_;
 
-  base::Optional<base::TimeTicks> pending_search_suggest_request_;
+  std::vector<SearchSuggestRequest> search_suggest_requests_;
 
   SearchSuggestService* search_suggest_service_;
 
diff --git a/chrome/browser/signin/identity_test_environment_profile_adaptor.cc b/chrome/browser/signin/identity_test_environment_profile_adaptor.cc
index 5de5dcd5..a390178a 100644
--- a/chrome/browser/signin/identity_test_environment_profile_adaptor.cc
+++ b/chrome/browser/signin/identity_test_environment_profile_adaptor.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "chrome/browser/signin/signin_manager_factory.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
-#include "components/signin/core/browser/test_image_decoder.h"
 
 namespace {
 
diff --git a/chrome/browser/site_isolation_policy.cc b/chrome/browser/site_isolation_policy.cc
new file mode 100644
index 0000000..c173be4
--- /dev/null
+++ b/chrome/browser/site_isolation_policy.cc
@@ -0,0 +1,22 @@
+// 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/site_isolation_policy.h"
+
+#include "chrome/common/chrome_features.h"
+#include "content/public/browser/site_isolation_policy.h"
+
+// static
+bool SiteIsolationPolicy::IsIsolationForPasswordSitesEnabled() {
+  // Ignore attempts to add new isolated origins when site isolation is turned
+  // off, for example via a command-line switch, or via a content/ embedder
+  // that turns site isolation off for low-memory devices.
+  if (!content::SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled())
+    return false;
+
+  // The feature needs to be checked last, because checking the feature
+  // activates the field trial and assigns the client either to a control or an
+  // experiment group - such assignment should be final.
+  return base::FeatureList::IsEnabled(features::kSiteIsolationForPasswordSites);
+}
diff --git a/chrome/browser/site_isolation_policy.h b/chrome/browser/site_isolation_policy.h
new file mode 100644
index 0000000..34f0979
--- /dev/null
+++ b/chrome/browser/site_isolation_policy.h
@@ -0,0 +1,25 @@
+// 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_SITE_ISOLATION_POLICY_H_
+#define CHROME_BROWSER_SITE_ISOLATION_POLICY_H_
+
+#include "base/macros.h"
+
+// A centralized place for making policy decisions about site isolation modes
+// at the chrome/ layer.  This supplements content::SiteIsolationPolicy with
+// features that are specific to chrome/.
+//
+// These methods can be called from any thread.
+class SiteIsolationPolicy {
+ public:
+  // Returns true if the site isolation mode for isolating sites where users
+  // enter passwords is enabled.
+  static bool IsIsolationForPasswordSitesEnabled();
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(SiteIsolationPolicy);
+};
+
+#endif  // CHROME_BROWSER_SITE_ISOLATION_POLICY_H_
diff --git a/chrome/browser/speech/extension_api/tts_extension_api.cc b/chrome/browser/speech/extension_api/tts_extension_api.cc
index d0a967b..b8fa9d2 100644
--- a/chrome/browser/speech/extension_api/tts_extension_api.cc
+++ b/chrome/browser/speech/extension_api/tts_extension_api.cc
@@ -293,7 +293,7 @@
 }
 
 ExtensionFunction::ResponseAction TtsStopSpeakingFunction::Run() {
-  content::TtsController::GetInstance()->Stop();
+  content::TtsController::GetInstance()->Stop(source_url());
   return RespondNow(NoArguments());
 }
 
diff --git a/chrome/browser/ssl/security_state_tab_helper.cc b/chrome/browser/ssl/security_state_tab_helper.cc
index 4c1b8bda..240afa5 100644
--- a/chrome/browser/ssl/security_state_tab_helper.cc
+++ b/chrome/browser/ssl/security_state_tab_helper.cc
@@ -42,7 +42,7 @@
 #include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
 #endif  // defined(OS_CHROMEOS)
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #endif
 
@@ -210,7 +210,7 @@
       case safe_browsing::SB_THREAT_TYPE_URL_UNWANTED:
         return security_state::MALICIOUS_CONTENT_STATUS_UNWANTED_SOFTWARE;
       case safe_browsing::SB_THREAT_TYPE_SIGN_IN_PASSWORD_REUSE:
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
         if (safe_browsing::ChromePasswordProtectionService::
                 ShouldShowPasswordReusePageInfoBubble(
                     web_contents(),
@@ -224,7 +224,7 @@
         return security_state::MALICIOUS_CONTENT_STATUS_SOCIAL_ENGINEERING;
 #endif
       case safe_browsing::SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE:
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
         if (safe_browsing::ChromePasswordProtectionService::
                 ShouldShowPasswordReusePageInfoBubble(
                     web_contents(),
diff --git a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
index cab4239..2a5e625 100644
--- a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
+++ b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
@@ -1752,6 +1752,10 @@
   helper->GetSecurityInfo(&security_info);
   EXPECT_EQ(security_state::DANGEROUS, security_info.security_level);
   EXPECT_EQ(blink::kWebSecurityStyleInsecure, observer.latest_security_style());
+  const content::SecurityStyleExplanations& http_explanation =
+      observer.latest_explanations();
+  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_HTTP_NONSECURE_SUMMARY),
+            http_explanation.summary);
 }
 
 // Visit a valid HTTPS page, then a broken HTTPS page, and then go back,
@@ -2027,6 +2031,10 @@
   // Verify that the security state degrades as expected.
   helper->GetSecurityInfo(&security_info);
   EXPECT_EQ(security_state::DANGEROUS, security_info.security_level);
+  const content::SecurityStyleExplanations& http_explanation =
+      observer.latest_explanations();
+  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_HTTP_NONSECURE_SUMMARY),
+            http_explanation.summary);
 
   // Verify security state stays degraded after same-page navigation.
   ui_test_utils::NavigateToURL(
diff --git a/chrome/browser/subresource_filter/ad_tagging_browsertest.cc b/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
index 58827ca..52d82134 100644
--- a/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
+++ b/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer_test_utils.h"
 #include "components/subresource_filter/core/common/test_ruleset_utils.h"
@@ -387,6 +388,96 @@
                                      1 /* expected_count */);
 }
 
+enum class NavigationInitiationType {
+  kWindowOpen,
+  kSetLocation,
+  kAnchorLinkActivate,
+};
+
+class AdClickNavigationBrowserTest
+    : public AdTaggingBrowserTest,
+      public ::testing::WithParamInterface<
+          std::tuple<NavigationInitiationType, bool /* gesture */>> {
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    // Popups without user gesture is blocked by default. Turn off the switch
+    // here to unblock that, so as to be able to test that the UseCounter not
+    // being recorded was due to the filtering in our recording function, rather
+    // than the default popup blocking.
+    command_line->AppendSwitch(::switches::kDisablePopupBlocking);
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(AdClickNavigationBrowserTest, UseCounter) {
+  NavigationInitiationType type;
+  bool gesture;
+  std::tie(type, gesture) = GetParam();
+  auto web_feature_waiter =
+      std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+          GetWebContents());
+  blink::mojom::WebFeature ad_click_navigation_feature =
+      blink::mojom::WebFeature::kAdClickNavigation;
+  web_feature_waiter->AddWebFeatureExpectation(ad_click_navigation_feature);
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
+
+  ui_test_utils::NavigateToURL(browser(), url);
+  content::WebContents* main_tab = GetWebContents();
+  RenderFrameHost* child = CreateSrcFrame(
+      main_tab, embedded_test_server()->GetURL(
+                    "a.com", "/ad_tagging/frame_factory.html?1&ad=true"));
+
+  std::string script;
+  switch (type) {
+    case NavigationInitiationType::kWindowOpen:
+      script = "window.open('frame_factory.html')";
+      break;
+    case NavigationInitiationType::kSetLocation:
+      script = "location='frame_factory.html'";
+      break;
+    case NavigationInitiationType::kAnchorLinkActivate:
+      script =
+          "var a = document.createElement('a');"
+          "a.setAttribute('href', 'frame_factory.html');"
+          "a.click();";
+      break;
+  }
+
+  if (gesture) {
+    EXPECT_TRUE(ExecJs(child, script));
+    web_feature_waiter->Wait();
+  } else {
+    switch (type) {
+      case NavigationInitiationType::kSetLocation:
+      case NavigationInitiationType::kAnchorLinkActivate: {
+        content::TestNavigationObserver navigation_observer(web_contents());
+        EXPECT_TRUE(ExecuteScriptWithoutUserGesture(child, script));
+        // To report metrics.
+        navigation_observer.Wait();
+        break;
+      }
+      case NavigationInitiationType::kWindowOpen: {
+        EXPECT_TRUE(ExecuteScriptWithoutUserGesture(child, script));
+        // To report metrics.
+        ASSERT_EQ(2, browser()->tab_strip_model()->count());
+        browser()->tab_strip_model()->MoveSelectedTabsTo(0);
+        ui_test_utils::NavigateToURL(browser(), url);
+        break;
+      }
+    }
+    EXPECT_FALSE(
+        web_feature_waiter->DidObserveWebFeature(ad_click_navigation_feature));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    /* no prefix */,
+    AdClickNavigationBrowserTest,
+    ::testing::Combine(
+        ::testing::Values(NavigationInitiationType::kWindowOpen,
+                          NavigationInitiationType::kSetLocation,
+                          NavigationInitiationType::kAnchorLinkActivate),
+        ::testing::Bool()));
+
 class AdTaggingEventFromSubframeBrowserTest
     : public AdTaggingBrowserTest,
       public ::testing::WithParamInterface<
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index 475d418..d38c4555 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -125,6 +125,7 @@
 #include "chrome/browser/chromeos/printing/synced_printers_manager.h"
 #include "chrome/browser/chromeos/printing/synced_printers_manager_factory.h"
 #include "chrome/browser/ui/app_list/arc/arc_package_sync_data_type_controller.h"
+#include "chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h"
 #include "chrome/browser/ui/app_list/arc/arc_package_syncable_service.h"
 #include "components/arc/arc_util.h"
 #endif  // defined(OS_CHROMEOS)
@@ -448,8 +449,16 @@
 #if defined(OS_CHROMEOS)
   if (arc::IsArcAllowedForProfile(profile_) &&
       !arc::IsArcAppSyncFlowDisabled()) {
-    controllers.push_back(std::make_unique<ArcPackageSyncDataTypeController>(
-        syncer::ARC_PACKAGE, dump_stack, sync_service, this, profile_));
+    if (base::FeatureList::IsEnabled(switches::kSyncPseudoUSSArcPackage)) {
+      controllers.push_back(std::make_unique<ArcPackageSyncModelTypeController>(
+          GetModelTypeStoreService()->GetStoreFactory(),
+          base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
+                         base::Unretained(this), syncer::ARC_PACKAGE),
+          dump_stack, sync_service, profile_));
+    } else {
+      controllers.push_back(std::make_unique<ArcPackageSyncDataTypeController>(
+          syncer::ARC_PACKAGE, dump_stack, sync_service, this, profile_));
+    }
   }
 #endif  // defined(OS_CHROMEOS)
 
diff --git a/chrome/browser/sync/test/integration/single_client_arc_package_sync_test.cc b/chrome/browser/sync/test/integration/single_client_arc_package_sync_test.cc
index 5fb85626..fb2ee0a0 100644
--- a/chrome/browser/sync/test/integration/single_client_arc_package_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_arc_package_sync_test.cc
@@ -3,10 +3,12 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/test/integration/feature_toggler.h"
 #include "chrome/browser/sync/test/integration/sync_arc_package_helper.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
 #include "chrome/browser/ui/app_list/arc/arc_package_syncable_service.h"
+#include "components/sync/driver/sync_driver_switches.h"
 
 namespace arc {
 
@@ -19,9 +21,11 @@
 
 }  // namespace
 
-class SingleClientArcPackageSyncTest : public SyncTest {
+class SingleClientArcPackageSyncTest : public FeatureToggler, public SyncTest {
  public:
-  SingleClientArcPackageSyncTest() : SyncTest(SINGLE_CLIENT) {}
+  SingleClientArcPackageSyncTest()
+      : FeatureToggler(switches::kSyncPseudoUSSArcPackage),
+        SyncTest(SINGLE_CLIENT) {}
 
   ~SingleClientArcPackageSyncTest() override {}
 
@@ -29,13 +33,13 @@
   DISALLOW_COPY_AND_ASSIGN(SingleClientArcPackageSyncTest);
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientArcPackageSyncTest, ArcPackageEmpty) {
+IN_PROC_BROWSER_TEST_P(SingleClientArcPackageSyncTest, ArcPackageEmpty) {
   ASSERT_TRUE(SetupSync());
 
   ASSERT_TRUE(AllProfilesHaveSameArcPackageDetails());
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientArcPackageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientArcPackageSyncTest,
                        ArcPackageInstallSomePackages) {
   ASSERT_TRUE(SetupSync());
 
@@ -49,4 +53,8 @@
   ASSERT_TRUE(AllProfilesHaveSameArcPackageDetails());
 }
 
+INSTANTIATE_TEST_SUITE_P(USS,
+                         SingleClientArcPackageSyncTest,
+                         ::testing::Values(false, true));
+
 }  // namespace arc
diff --git a/chrome/browser/sync/test/integration/two_client_arc_package_sync_test.cc b/chrome/browser/sync/test/integration/two_client_arc_package_sync_test.cc
index 528be3d..6eacfb7 100644
--- a/chrome/browser/sync/test/integration/two_client_arc_package_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_arc_package_sync_test.cc
@@ -3,9 +3,11 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/test/integration/feature_toggler.h"
 #include "chrome/browser/sync/test/integration/sync_arc_package_helper.h"
 #include "chrome/browser/sync/test/integration/sync_integration_test_util.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
+#include "components/sync/driver/sync_driver_switches.h"
 
 namespace arc {
 
@@ -18,9 +20,13 @@
 
 }  // namespace
 
-class TwoClientArcPackageSyncTest : public SyncTest {
+class TwoClientArcPackageSyncTest : public FeatureToggler, public SyncTest {
  public:
-  TwoClientArcPackageSyncTest() : SyncTest(TWO_CLIENT) { DisableVerifier(); }
+  TwoClientArcPackageSyncTest()
+      : FeatureToggler(switches::kSyncPseudoUSSArcPackage),
+        SyncTest(TWO_CLIENT) {
+    DisableVerifier();
+  }
 
   ~TwoClientArcPackageSyncTest() override {}
 
@@ -28,13 +34,13 @@
   DISALLOW_COPY_AND_ASSIGN(TwoClientArcPackageSyncTest);
 };
 
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest, StartWithNoPackages) {
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest, StartWithNoPackages) {
   ASSERT_TRUE(SetupSync());
 
   ASSERT_TRUE(AllProfilesHaveSameArcPackageDetails());
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest, StartWithSamePackages) {
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest, StartWithSamePackages) {
   ASSERT_TRUE(SetupClients());
 
   constexpr size_t kNumPackages = 5;
@@ -50,7 +56,7 @@
 
 // In this test, packages are installed before sync started. Client1 will have
 // package 0 to 4 installed while client2 has no package installed.
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest,
                        OneClientHasPackagesAnotherHasNone) {
   ASSERT_TRUE(SetupClients());
 
@@ -68,7 +74,7 @@
 
 // In this test, packages are installed before sync started. Client1 will have
 // package 0 to 9 installed and client2 will have package 0 to 4 installed.
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest,
                        OneClientHasPackagesAnotherHasSubSet) {
   ASSERT_TRUE(SetupClients());
 
@@ -91,7 +97,7 @@
 
 // In this test, packages are installed before sync started. Client1 will have
 // package 0 to 4 installed and client2 will have package 1 to 5 installed.
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest,
                        StartWithDifferentPackages) {
   ASSERT_TRUE(SetupClients());
 
@@ -111,7 +117,7 @@
 }
 
 // Tests package installaton after sync started.
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest, Install) {
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest, Install) {
   ASSERT_TRUE(SetupSync());
   ASSERT_TRUE(AllProfilesHaveSameArcPackageDetails());
 
@@ -122,7 +128,7 @@
 
 // In this test, packages are installed after sync started. Client1 installs
 // package 0 to 4 and client2 installs package 3 to 7.
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest, InstallDifferent) {
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest, InstallDifferent) {
   ASSERT_TRUE(SetupSync());
   ASSERT_TRUE(AllProfilesHaveSameArcPackageDetails());
 
@@ -140,7 +146,7 @@
 
 // Installs package from one client and uninstalls from another after sync
 // started.
-IN_PROC_BROWSER_TEST_F(TwoClientArcPackageSyncTest, Uninstall) {
+IN_PROC_BROWSER_TEST_P(TwoClientArcPackageSyncTest, Uninstall) {
   ASSERT_TRUE(SetupSync());
   ASSERT_TRUE(AllProfilesHaveSameArcPackageDetails());
 
@@ -154,4 +160,8 @@
   EXPECT_TRUE(AllProfilesHaveSameArcPackageDetails());
 }
 
+INSTANTIATE_TEST_SUITE_P(USS,
+                         TwoClientArcPackageSyncTest,
+                         ::testing::Values(false, true));
+
 }  // namespace arc
diff --git a/chrome/browser/themes/theme_properties.cc b/chrome/browser/themes/theme_properties.cc
index b8f6ae2..12214ba 100644
--- a/chrome/browser/themes/theme_properties.cc
+++ b/chrome/browser/themes/theme_properties.cc
@@ -68,8 +68,6 @@
       return gfx::kGoogleGrey600;
     case ThemeProperties::COLOR_CONTROL_BACKGROUND:
       return SK_ColorWHITE;
-    case ThemeProperties::COLOR_BOOKMARK_BAR_INSTRUCTIONS_TEXT:
-      return SkColorSetA(SK_ColorWHITE, 0x8A);
     case ThemeProperties::COLOR_TOOLBAR_CONTENT_AREA_SEPARATOR:
       return SkColorSetRGB(0x28, 0x28, 0x28);
     default:
@@ -251,8 +249,6 @@
       return gfx::kGoogleBlue600;
     case COLOR_CONTROL_BACKGROUND:
       return SK_ColorWHITE;
-    case COLOR_BOOKMARK_BAR_INSTRUCTIONS_TEXT:
-      return SkColorSetRGB(0x64, 0x64, 0x64);
     case COLOR_DETACHED_BOOKMARK_BAR_SEPARATOR:
       // We shouldn't reach this case because the color is calculated from
       // others.
diff --git a/chrome/browser/themes/theme_properties.h b/chrome/browser/themes/theme_properties.h
index 936ca703..35a8f3be 100644
--- a/chrome/browser/themes/theme_properties.h
+++ b/chrome/browser/themes/theme_properties.h
@@ -111,9 +111,6 @@
     // shelf.
     COLOR_TOOLBAR_VERTICAL_SEPARATOR,
 
-    // The color of the "instructions text" in an empty bookmarks bar.
-    COLOR_BOOKMARK_BAR_INSTRUCTIONS_TEXT,
-
     // Colors used for the detached (NTP) bookmark bar.
     COLOR_DETACHED_BOOKMARK_BAR_BACKGROUND,
     COLOR_DETACHED_BOOKMARK_BAR_SEPARATOR,
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc
index 9ee09179..fb026f3 100644
--- a/chrome/browser/themes/theme_service.cc
+++ b/chrome/browser/themes/theme_service.cc
@@ -555,10 +555,6 @@
           GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON, incognito),
           0x4D);
     }
-    case ThemeProperties::COLOR_BOOKMARK_BAR_INSTRUCTIONS_TEXT:
-      if (UsingDefaultTheme())
-        break;
-      return GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT, incognito);
     case ThemeProperties::COLOR_TOOLBAR_CONTENT_AREA_SEPARATOR:
       if (UsingDefaultTheme())
         break;
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 9cddc24..17171ba 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1610,8 +1610,6 @@
       "webui/chromeos/login/gaia_screen_handler.h",
       "webui/chromeos/login/hid_detection_screen_handler.cc",
       "webui/chromeos/login/hid_detection_screen_handler.h",
-      "webui/chromeos/login/kiosk_app_menu_handler.cc",
-      "webui/chromeos/login/kiosk_app_menu_handler.h",
       "webui/chromeos/login/kiosk_autolaunch_screen_handler.cc",
       "webui/chromeos/login/kiosk_autolaunch_screen_handler.h",
       "webui/chromeos/login/kiosk_enable_screen_handler.cc",
@@ -3265,6 +3263,8 @@
         "app_list/arc/arc_fast_app_reinstall_starter.h",
         "app_list/arc/arc_package_sync_data_type_controller.cc",
         "app_list/arc/arc_package_sync_data_type_controller.h",
+        "app_list/arc/arc_package_sync_model_type_controller.cc",
+        "app_list/arc/arc_package_sync_model_type_controller.h",
         "app_list/arc/arc_package_syncable_service.cc",
         "app_list/arc/arc_package_syncable_service.h",
         "app_list/arc/arc_package_syncable_service_factory.cc",
diff --git a/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.cc b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.cc
new file mode 100644
index 0000000..4685a3aa0
--- /dev/null
+++ b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h"
+
+#include <utility>
+
+#include "chrome/browser/chromeos/arc/arc_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/sync/driver/sync_service.h"
+
+ArcPackageSyncModelTypeController::ArcPackageSyncModelTypeController(
+    syncer::OnceModelTypeStoreFactory store_factory,
+    SyncableServiceProvider syncable_service_provider,
+    const base::RepeatingClosure& dump_stack,
+    syncer::SyncService* sync_service,
+    Profile* profile)
+    : syncer::SyncableServiceBasedModelTypeController(
+          syncer::ARC_PACKAGE,
+          std::move(store_factory),
+          std::move(syncable_service_provider),
+          dump_stack),
+      sync_service_(sync_service),
+      profile_(profile) {
+  arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
+  if (arc_session_manager) {
+    arc_session_manager->AddObserver(this);
+  }
+}
+
+ArcPackageSyncModelTypeController::~ArcPackageSyncModelTypeController() {
+  arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
+  if (arc_session_manager) {
+    arc_session_manager->RemoveObserver(this);
+  }
+}
+
+bool ArcPackageSyncModelTypeController::ReadyForStart() const {
+  DCHECK(CalledOnValidThread());
+  return arc::IsArcPlayStoreEnabledForProfile(profile_);
+}
+
+void ArcPackageSyncModelTypeController::OnArcPlayStoreEnabledChanged(
+    bool enabled) {
+  DCHECK(CalledOnValidThread());
+  sync_service_->ReadyForStartChanged(type());
+}
+
+void ArcPackageSyncModelTypeController::OnArcInitialStart() {
+  DCHECK(CalledOnValidThread());
+  sync_service_->ReadyForStartChanged(type());
+}
diff --git a/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h
new file mode 100644
index 0000000..1f47081
--- /dev/null
+++ b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_APP_LIST_ARC_ARC_PACKAGE_SYNC_MODEL_TYPE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_APP_LIST_ARC_ARC_PACKAGE_SYNC_MODEL_TYPE_CONTROLLER_H_
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/arc/arc_session_manager.h"
+#include "components/sync/driver/syncable_service_based_model_type_controller.h"
+
+class Profile;
+
+namespace syncer {
+class SyncService;
+}  // namespace syncer
+
+// A DataTypeController for arc package sync datatypes, which enables or
+// disables these types based on whether ArcAppInstance is ready.
+class ArcPackageSyncModelTypeController
+    : public syncer::SyncableServiceBasedModelTypeController,
+      public arc::ArcSessionManager::Observer {
+ public:
+  // |dump_stack| is called when an unrecoverable error occurs.
+  ArcPackageSyncModelTypeController(
+      syncer::OnceModelTypeStoreFactory store_factory,
+      SyncableServiceProvider syncable_service_provider,
+      const base::RepeatingClosure& dump_stack,
+      syncer::SyncService* sync_service,
+      Profile* profile);
+  ~ArcPackageSyncModelTypeController() override;
+
+  // DataTypeController overrides.
+  bool ReadyForStart() const override;
+
+  // ArcSessionManager::Observer:
+  void OnArcPlayStoreEnabledChanged(bool enabled) override;
+  void OnArcInitialStart() override;
+
+ private:
+  syncer::SyncService* const sync_service_;
+  Profile* const profile_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArcPackageSyncModelTypeController);
+};
+#endif  // CHROME_BROWSER_UI_APP_LIST_ARC_ARC_PACKAGE_SYNC_MODEL_TYPE_CONTROLLER_H_
diff --git a/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc b/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc
index c6e06634..064cfee 100644
--- a/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc
+++ b/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc
@@ -120,6 +120,17 @@
       pending_install_items_.end();
 }
 
+void ArcPackageSyncableService::WaitUntilReadyToSync(base::OnceClosure done) {
+  if (prefs_->package_list_initial_refreshed()) {
+    std::move(done).Run();
+    return;
+  }
+
+  // Wait until the initial list is loaded, handled in
+  // OnPackageListInitialRefreshed().
+  wait_until_ready_to_sync_cb_ = std::move(done);
+}
+
 syncer::SyncMergeResult ArcPackageSyncableService::MergeDataAndStartSyncing(
     syncer::ModelType type,
     const syncer::SyncDataList& initial_sync_data,
@@ -322,6 +333,11 @@
   SendSyncChange(package_info, SyncChange::ACTION_UPDATE);
 }
 
+void ArcPackageSyncableService::OnPackageListInitialRefreshed() {
+  if (wait_until_ready_to_sync_cb_)
+    std::move(wait_until_ready_to_sync_cb_).Run();
+}
+
 void ArcPackageSyncableService::SendSyncChange(
     const mojom::ArcPackageInfo& package_info,
     const syncer::SyncChange::SyncChangeType& sync_change_type) {
diff --git a/chrome/browser/ui/app_list/arc/arc_package_syncable_service.h b/chrome/browser/ui/app_list/arc/arc_package_syncable_service.h
index 1821e6e7..71c91cf3 100644
--- a/chrome/browser/ui/app_list/arc/arc_package_syncable_service.h
+++ b/chrome/browser/ui/app_list/arc/arc_package_syncable_service.h
@@ -55,6 +55,7 @@
   bool IsPackageSyncing(const std::string& package_name) const;
 
   // syncer::SyncableService:
+  void WaitUntilReadyToSync(base::OnceClosure done) override;
   syncer::SyncMergeResult MergeDataAndStartSyncing(
       syncer::ModelType type,
       const syncer::SyncDataList& initial_sync_data,
@@ -79,6 +80,7 @@
   void OnPackageModified(const mojom::ArcPackageInfo& package_info) override;
   void OnPackageRemoved(const std::string& package_name,
                         bool uninstalled) override;
+  void OnPackageListInitialRefreshed() override;
 
   // Sends adds/updates sync change to sync server.
   void SendSyncChange(
@@ -105,6 +107,7 @@
   bool ShouldSyncPackage(const std::string& package_name) const;
 
   Profile* const profile_;
+  base::OnceClosure wait_until_ready_to_sync_cb_;
   std::unique_ptr<syncer::SyncChangeProcessor> sync_processor_;
   std::unique_ptr<syncer::SyncErrorFactory> sync_error_handler_;
 
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc
index 663f06a..e8cadef 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc
@@ -52,11 +52,11 @@
     : AppListModelBuilder(controller, CrostiniAppItem::kItemType) {}
 
 CrostiniAppModelBuilder::~CrostiniAppModelBuilder() {
-  if (crostini_folder_observer_) {
+  if (crostini_folder_observer_)
     model_updater()->RemoveObserver(crostini_folder_observer_.get());
-  }
-  // We don't need to remove ourself from the registry's observer list as these
-  // are both KeyedServices (this class lives on AppListSyncableService).
+
+  crostini::CrostiniRegistryServiceFactory::GetForProfile(profile())
+      ->RemoveObserver(this);
 }
 
 void CrostiniAppModelBuilder::BuildModel() {
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc
index 1b1aceb..8d31670e 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/ui/app_list/app_list_test_util.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
 #include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/testing_profile.h"
@@ -111,17 +110,14 @@
               return std::make_unique<FakeAppListModelUpdater>(profile);
             },
             profile()));
-    controller_ = std::make_unique<test::TestAppListControllerDelegate>();
+    // The AppListSyncableService creates the CrostiniAppModelBuilder.
     sync_service_ = std::make_unique<app_list::AppListSyncableService>(
         profile_.get(), extensions::ExtensionSystem::Get(profile_.get()));
-    builder_ = std::make_unique<CrostiniAppModelBuilder>(controller_.get());
     RemoveNonCrostiniApps(sync_service_.get());
   }
 
   void ResetBuilder() {
-    builder_.reset();
     sync_service_.reset();
-    controller_.reset();
     model_updater_factory_scope_.reset();
   }
 
@@ -133,9 +129,7 @@
     return l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_APP_NAME);
   }
 
-  std::unique_ptr<test::TestAppListControllerDelegate> controller_;
   std::unique_ptr<app_list::AppListSyncableService> sync_service_;
-  std::unique_ptr<CrostiniAppModelBuilder> builder_;
   std::unique_ptr<CrostiniTestHelper> test_helper_;
 
  private:
diff --git a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
index 426df68..69414f4 100644
--- a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
+++ b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
@@ -6,6 +6,7 @@
 #include <string>
 #include <utility>
 
+#include "ash/public/cpp/app_types.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_properties.h"
@@ -21,6 +22,7 @@
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h"
+#include "chrome/browser/ui/ash/session_controller_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -252,21 +254,21 @@
 void CrostiniAppWindowShelfController::RegisterAppWindow(
     aura::Window* window,
     const std::string& shelf_app_id) {
+  window->SetProperty(aura::client::kAppType,
+                      static_cast<int>(ash::AppType::CROSTINI_APP));
   const ash::ShelfID shelf_id(shelf_app_id);
   views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
   aura_window_to_app_window_[window] =
       std::make_unique<AppWindowBase>(shelf_id, widget);
   AppWindowBase* app_window = aura_window_to_app_window_[window].get();
 
-  const AccountId& current_user =
-      user_manager::UserManager::Get()->GetActiveUser()->GetAccountId();
-  const AccountId& window_owner =
-      MultiUserWindowManagerClient::GetInstance()->GetWindowOwner(window);
   // Only add an app to the shelf if it's associated with the currently active
   // user (which should always be the primary user at this time).
-  if (current_user == window_owner) {
-    AddToShelf(window, app_window);
-  }
+  if (SessionControllerClient::IsMultiProfileAvailable() &&
+      user_manager::UserManager::Get()->GetActiveUser()->GetAccountId() !=
+          MultiUserWindowManagerClient::GetInstance()->GetWindowOwner(window))
+    return;
+  AddToShelf(window, app_window);
 }
 
 void CrostiniAppWindowShelfController::OnWindowDestroying(
diff --git a/chrome/browser/ui/ash/test_login_screen.cc b/chrome/browser/ui/ash/test_login_screen.cc
index 9d3064d..9208a6d6 100644
--- a/chrome/browser/ui/ash/test_login_screen.cc
+++ b/chrome/browser/ui/ash/test_login_screen.cc
@@ -113,7 +113,8 @@
     bool show_full_management_disclosure) {}
 
 void TestLoginScreen::SetKioskApps(
-    std::vector<::ash::mojom::KioskAppInfoPtr> kiosk_apps) {}
+    std::vector<::ash::mojom::KioskAppInfoPtr> kiosk_apps,
+    SetKioskAppsCallback callback) {}
 
 void TestLoginScreen::ShowKioskAppError(const std::string& message) {}
 
diff --git a/chrome/browser/ui/ash/test_login_screen.h b/chrome/browser/ui/ash/test_login_screen.h
index 9682606..df917695 100644
--- a/chrome/browser/ui/ash/test_login_screen.h
+++ b/chrome/browser/ui/ash/test_login_screen.h
@@ -74,8 +74,8 @@
       std::vector<::ash::mojom::InputMethodItemPtr> keyboard_layouts) override;
   void SetPublicSessionShowFullManagementDisclosure(
       bool show_full_management_disclosure) override;
-  void SetKioskApps(
-      std::vector<::ash::mojom::KioskAppInfoPtr> kiosk_apps) override;
+  void SetKioskApps(std::vector<::ash::mojom::KioskAppInfoPtr> kiosk_apps,
+                    SetKioskAppsCallback callback) override;
   void ShowKioskAppError(const std::string& message) override;
   void NotifyOobeDialogState(ash::mojom::OobeDialogState state) override;
   void SetAddUserButtonEnabled(bool enable) override;
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index e04a1889..97d8593 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -12,6 +12,7 @@
 #include "base/command_line.h"
 #include "base/debug/debugging_buildflags.h"
 #include "base/debug/profiler.h"
+#include "base/feature_list.h"
 #include "base/macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/stl_util.h"
@@ -43,6 +44,7 @@
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/webui/inspect_ui.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/content_restriction.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
@@ -412,6 +414,7 @@
       break;
     case IDC_RESTORE_TAB:
       RestoreTab(browser_);
+      browser_->window()->OnTabRestored(IDC_RESTORE_TAB);
       break;
     case IDC_SHOW_AS_TAB:
       ConvertPopupToTabbedBrowser(browser_);
@@ -694,10 +697,14 @@
     case IDC_WINDOW_PIN_TAB:
       PinTab(browser_);
       break;
-    case IDC_SHOW_MANAGEMENT_PAGE:
-      ShowSingletonTab(browser_, GURL(kChromeUIManagementURL));
+    case IDC_SHOW_MANAGEMENT_PAGE: {
+      bool link_to_management_page = base::FeatureList::IsEnabled(
+          features::kLinkManagedNoticeToChromeUIManagementURL);
+      ShowSingletonTab(browser_,
+                       GURL(link_to_management_page ? kChromeUIManagementURL
+                                                    : kManagedUiLearnMoreUrl));
       break;
-
+    }
     // Hosted App commands
     case IDC_COPY_URL:
       CopyURL(browser_);
diff --git a/chrome/browser/ui/browser_window.h b/chrome/browser/ui/browser_window.h
index c5e2aa6..fb5ead30 100644
--- a/chrome/browser/ui/browser_window.h
+++ b/chrome/browser/ui/browser_window.h
@@ -209,9 +209,10 @@
   virtual void OnTabDetached(content::WebContents* contents,
                              bool was_active) = 0;
 
-  // Called when the user restores a tab from the recently closed tabs menu.
-  // |command_id| is the menu command associated with the restored tab.
-  virtual void OnTabRestoredFromMenu(int command_id) = 0;
+  // Called when the user restores a tab. |command_id| may be IDC_RESTORE_TAB or
+  // the menu command, depending on whether the tab was restored via keyboard or
+  // main menu.
+  virtual void OnTabRestored(int command_id) = 0;
 
   // Called to force the zoom state to for the active tab to be recalculated.
   // |can_show_bubble| is true when a user presses the zoom up or down keyboard
diff --git a/chrome/browser/ui/cocoa/first_run_dialog_controller.mm b/chrome/browser/ui/cocoa/first_run_dialog_controller.mm
index 48318e0..ed27bed 100644
--- a/chrome/browser/ui/cocoa/first_run_dialog_controller.mm
+++ b/chrome/browser/ui/cocoa/first_run_dialog_controller.mm
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/cocoa/first_run_dialog_controller.h"
 
 #include "base/mac/scoped_nsobject.h"
+#include "base/mac/sdk_forward_declarations.h"
 #include "chrome/browser/ui/cocoa/key_equivalent_constants.h"
 #include "chrome/browser/ui/cocoa/l10n_util.h"
 #include "chrome/grit/chromium_strings.h"
@@ -65,9 +66,24 @@
 }
 
 - (void)loadView {
+  BOOL isDarkMode = NO;
+  if (@available(macOS 10.14, *)) {
+    NSAppearanceName appearance =
+        [[NSApp effectiveAppearance] bestMatchFromAppearancesWithNames:@[
+          NSAppearanceNameAqua, NSAppearanceNameDarkAqua
+        ]];
+    isDarkMode = [appearance isEqual:NSAppearanceNameDarkAqua];
+  }
+  NSColor* topBoxColor = isDarkMode
+                             ? [NSColor colorWithCalibratedRed:0x32 / 255.0
+                                                         green:0x36 / 255.0
+                                                          blue:0x39 / 255.0
+                                                         alpha:1.0]
+                             : [NSColor whiteColor];
+
   NSBox* topBox =
       [[[NSBox alloc] initWithFrame:NSMakeRect(0, 137, 480, 52)] autorelease];
-  [topBox setFillColor:[NSColor whiteColor]];
+  [topBox setFillColor:topBoxColor];
   [topBox setBoxType:NSBoxCustom];
   [topBox setBorderType:NSNoBorder];
   [topBox setContentViewMargins:NSZeroSize];
diff --git a/chrome/browser/ui/cocoa/profiles/profile_menu_controller.mm b/chrome/browser/ui/cocoa/profiles/profile_menu_controller.mm
index 5e903ca..b257392 100644
--- a/chrome/browser/ui/cocoa/profiles/profile_menu_controller.mm
+++ b/chrome/browser/ui/cocoa/profiles/profile_menu_controller.mm
@@ -155,12 +155,10 @@
 
       // The image might be too large and need to be resized (i.e. if this is
       // a signed-in user using the GAIA profile photo).
-      if (itemIcon.Width() > profiles::kAvatarIconWidth ||
-          itemIcon.Height() > profiles::kAvatarIconHeight) {
+      if (itemIcon.Width() > profiles::kAvatarIconSize ||
+          itemIcon.Height() > profiles::kAvatarIconSize) {
         itemIcon = profiles::GetAvatarIconForWebUI(itemIcon, true);
       }
-      DCHECK(itemIcon.Width() <= profiles::kAvatarIconWidth);
-      DCHECK(itemIcon.Height() <= profiles::kAvatarIconHeight);
       [item setImage:itemIcon.ToNSImage()];
       [item setState:itemData.active ? NSOnState : NSOffState];
     }
diff --git a/chrome/browser/ui/libgtkui/gtk_ui.cc b/chrome/browser/ui/libgtkui/gtk_ui.cc
index fcf7940..4fd5da36 100644
--- a/chrome/browser/ui/libgtkui/gtk_ui.cc
+++ b/chrome/browser/ui/libgtkui/gtk_ui.cc
@@ -851,8 +851,6 @@
   colors_[ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON] = tab_text_color;
   colors_[ThemeProperties::COLOR_TAB_TEXT] = tab_text_color;
   colors_[ThemeProperties::COLOR_BOOKMARK_TEXT] = tab_text_color;
-  colors_[ThemeProperties::COLOR_BOOKMARK_BAR_INSTRUCTIONS_TEXT] =
-      tab_text_color;
 
   colors_[ThemeProperties::COLOR_BACKGROUND_TAB] = SK_ColorTRANSPARENT;
   colors_[ThemeProperties::COLOR_BACKGROUND_TAB_INACTIVE] = SK_ColorTRANSPARENT;
diff --git a/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc b/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc
index 849d1c5..76a98bf 100644
--- a/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc
+++ b/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc
@@ -76,7 +76,6 @@
 
 bool AlternateNavInfoBarDelegate::LinkClicked(
     WindowOpenDisposition disposition) {
-  DCHECK(match_);
   history::HistoryService* const history_service =
       HistoryServiceFactory::GetForProfile(profile_,
                                            ServiceAccessType::IMPLICIT_ACCESS);
diff --git a/chrome/browser/ui/page_info/page_info.cc b/chrome/browser/ui/page_info/page_info.cc
index 44cb3ac..f9af799 100644
--- a/chrome/browser/ui/page_info/page_info.cc
+++ b/chrome/browser/ui/page_info/page_info.cc
@@ -89,7 +89,7 @@
 #include "chrome/browser/ui/page_info/page_info_infobar_delegate.h"
 #endif
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #endif
 
@@ -97,10 +97,10 @@
 using base::UTF8ToUTF16;
 using base::UTF16ToUTF8;
 using content::BrowserThread;
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 using PasswordReuseEvent =
     safe_browsing::LoginReputationClientRequest::PasswordReuseEvent;
-#endif  // SAFE_BROWSING_DB_LOCAL
+#endif  // FULL_SAFE_BROWSING
 
 namespace {
 
@@ -345,7 +345,7 @@
       did_revoke_user_ssl_decisions_(false),
       profile_(profile),
       security_level_(security_state::NONE),
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
       password_protection_service_(
           safe_browsing::ChromePasswordProtectionService::
               GetPasswordProtectionService(profile_)),
@@ -555,7 +555,7 @@
 
 void PageInfo::OnChangePasswordButtonPressed(
     content::WebContents* web_contents) {
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   DCHECK(password_protection_service_);
   DCHECK(site_identity_status_ == SITE_IDENTITY_STATUS_SIGN_IN_PASSWORD_REUSE ||
          site_identity_status_ ==
@@ -572,7 +572,7 @@
 
 void PageInfo::OnWhitelistPasswordReuseButtonPressed(
     content::WebContents* web_contents) {
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   DCHECK(password_protection_service_);
   DCHECK(site_identity_status_ == SITE_IDENTITY_STATUS_SIGN_IN_PASSWORD_REUSE ||
          site_identity_status_ ==
@@ -963,7 +963,7 @@
   info.show_ssl_decision_revoke_button = show_ssl_decision_revoke_button_;
   info.show_change_password_buttons = show_change_password_buttons_;
   ui_->SetIdentityInfo(info);
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   if (password_protection_service_ && show_change_password_buttons_) {
     if (site_identity_status_ == SITE_IDENTITY_STATUS_SIGN_IN_PASSWORD_REUSE) {
       safe_browsing::LogWarningAction(
@@ -1027,7 +1027,7 @@
           l10n_util::GetStringUTF16(IDS_PAGE_INFO_UNWANTED_SOFTWARE_DETAILS);
       break;
     case security_state::MALICIOUS_CONTENT_STATUS_SIGN_IN_PASSWORD_REUSE:
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
       *status = PageInfo::SITE_IDENTITY_STATUS_SIGN_IN_PASSWORD_REUSE;
       // |password_protection_service_| may be null in test.
       *details = password_protection_service_
@@ -1037,7 +1037,7 @@
 #endif
       break;
     case security_state::MALICIOUS_CONTENT_STATUS_ENTERPRISE_PASSWORD_REUSE:
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
       *status = PageInfo::SITE_IDENTITY_STATUS_ENTERPRISE_PASSWORD_REUSE;
       // |password_protection_service_| maybe null in test.
       *details = password_protection_service_
diff --git a/chrome/browser/ui/page_info/page_info.h b/chrome/browser/ui/page_info/page_info.h
index 2007d3c3f..7126842d 100644
--- a/chrome/browser/ui/page_info/page_info.h
+++ b/chrome/browser/ui/page_info/page_info.h
@@ -285,7 +285,7 @@
 
   security_state::SecurityLevel security_level_;
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   // Used to handle changing password, and whitelisting site.
   safe_browsing::ChromePasswordProtectionService* password_protection_service_;
 #endif
diff --git a/chrome/browser/ui/page_info/page_info_ui.cc b/chrome/browser/ui/page_info/page_info_ui.cc
index ca2d82f..224a2542 100644
--- a/chrome/browser/ui/page_info/page_info_ui.cc
+++ b/chrome/browser/ui/page_info/page_info_ui.cc
@@ -38,7 +38,7 @@
 #include "ui/gfx/paint_vector_icon.h"
 #endif
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 #include "components/safe_browsing/password_protection/password_protection_service.h"
 #endif
 
@@ -288,12 +288,12 @@
                                        IDS_PAGE_INFO_UNWANTED_SOFTWARE_SUMMARY,
                                        IDS_PAGE_INFO_UNWANTED_SOFTWARE_DETAILS);
     case PageInfo::SITE_IDENTITY_STATUS_SIGN_IN_PASSWORD_REUSE:
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
       return CreateSecurityDescriptionForPasswordReuse(
           /*is_enterprise_password=*/false);
 #endif
     case PageInfo::SITE_IDENTITY_STATUS_ENTERPRISE_PASSWORD_REUSE:
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
       return CreateSecurityDescriptionForPasswordReuse(
           /*is_enterprise_password=*/true);
 #endif
diff --git a/chrome/browser/ui/page_info/page_info_ui.h b/chrome/browser/ui/page_info/page_info_ui.h
index 4c3b503d3..75d32d06 100644
--- a/chrome/browser/ui/page_info/page_info_ui.h
+++ b/chrome/browser/ui/page_info/page_info_ui.h
@@ -232,7 +232,7 @@
   std::unique_ptr<PageInfoUI::SecurityDescription> GetSecurityDescription(
       const IdentityInfo& identity_info) const;
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   // Creates security description for password reuse case.
   virtual std::unique_ptr<PageInfoUI::SecurityDescription>
   CreateSecurityDescriptionForPasswordReuse(
diff --git a/chrome/browser/ui/search/local_ntp_browsertest.cc b/chrome/browser/ui/search/local_ntp_browsertest.cc
index 9acd12e..25693a4 100644
--- a/chrome/browser/ui/search/local_ntp_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_browsertest.cc
@@ -90,7 +90,7 @@
 
   LocalNTPTest()
       : LocalNTPTest(/*enabled_features=*/{features::kUseGoogleLocalNtp},
-                     /*disabled_features=*/{}) {}
+                     /*disabled_features=*/{features::kRemoveNtpFakebox}) {}
 
   void SetUpOnMainThread() override {
     // Some tests depend on the prepopulated most visited tiles coming from
diff --git a/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc b/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
index b4f8918..942a62ce 100644
--- a/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
@@ -254,7 +254,8 @@
  private:
   void SetUp() override {
     feature_list_.InitWithFeatures(
-        {features::kUseGoogleLocalNtp, features::kDoodlesOnLocalNtp}, {});
+        {features::kUseGoogleLocalNtp, features::kDoodlesOnLocalNtp},
+        {features::kRemoveNtpFakebox});
     InProcessBrowserTest::SetUp();
   }
 
diff --git a/chrome/browser/ui/search/local_ntp_uitest.cc b/chrome/browser/ui/search/local_ntp_uitest.cc
index 79031a1..75468df 100644
--- a/chrome/browser/ui/search/local_ntp_uitest.cc
+++ b/chrome/browser/ui/search/local_ntp_uitest.cc
@@ -35,7 +35,9 @@
 
  private:
   void SetUp() override {
-    feature_list_.InitAndEnableFeature(features::kUseGoogleLocalNtp);
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kUseGoogleLocalNtp},
+        /*disabled_features=*/{features::kRemoveNtpFakebox});
     InProcessBrowserTest::SetUp();
   }
 
diff --git a/chrome/browser/ui/search/local_ntp_voice_search_browsertest.cc b/chrome/browser/ui/search/local_ntp_voice_search_browsertest.cc
index 1e8bc483..37315dd3 100644
--- a/chrome/browser/ui/search/local_ntp_voice_search_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_voice_search_browsertest.cc
@@ -31,7 +31,8 @@
 
  private:
   void SetUp() override {
-    feature_list_.InitWithFeatures({features::kUseGoogleLocalNtp}, {});
+    feature_list_.InitWithFeatures({features::kUseGoogleLocalNtp},
+                                   {features::kRemoveNtpFakebox});
     InProcessBrowserTest::SetUp();
   }
 
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
index 5570d720..33d66a8 100644
--- a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
@@ -321,7 +321,7 @@
     }
   }
 
-  browser_->window()->OnTabRestoredFromMenu(command_id);
+  browser_->window()->OnTabRestored(command_id);
 
   UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.OpenRecentTab",
                              menu_opened_timer_.Elapsed());
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
index 4152fa8f..10c680a 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
@@ -361,8 +361,12 @@
     gfx::Insets insets(draggable_region->getBounds().bottom(), 0, 0, 0);
 
     // Invert the draggable regions to determine the additional client areas.
+    // Inversion should be computed for the difference between the inset area
+    // and the draggable_region -- draggable_region->getBounds() could have
+    // smaller width than widget's width.
     SkRegion inverted_region;
-    inverted_region.setRect(draggable_region->getBounds());
+    inverted_region.setRect(0, 0, widget()->GetWindowBoundsInScreen().width(),
+                            draggable_region->getBounds().bottom());
     inverted_region.op(*draggable_region, SkRegion::kDifference_Op);
     std::vector<gfx::Rect> additional_client_regions;
     for (SkRegion::Iterator i(inverted_region); !i.done(); i.next())
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
index 8733a18..ee6ef08 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h"
 #include "chrome/browser/ui/views/chrome_constrained_window_views_client.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/test/view_event_test_base.h"
@@ -426,7 +427,13 @@
   BookmarkBarViewDragTestBase() = default;
   ~BookmarkBarViewDragTestBase() override = default;
 
-  virtual void OnWidgetDragWillStart() = 0;
+  void OnWidgetDragWillStart() {
+    const gfx::Point target = GetDragTargetInScreen();
+    GetDragTaskRunner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(base::IgnoreResult(&ui_controls::SendMouseMove),
+                       target.x(), target.y()));
+  }
 
   virtual void OnWidgetDragComplete() {
     // All drag tests drag node f1a, so at the end of the test, if the node was
@@ -465,6 +472,16 @@
         CreateEventTask(this, &BookmarkBarViewDragTestBase::StartDrag));
   }
 
+  virtual void AfterDragStarted() = 0;
+
+  virtual void OnDragEntered() {
+    // Drop the element.
+    ASSERT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
+        ui_controls::LEFT, ui_controls::UP,
+        CreateEventTask(this,
+                        &BookmarkBarViewDragTestBase::OnWidgetDragComplete)));
+  }
+
   // Called after the drag ends; returns the node the test thinks should be the
   // dropped node.  This is used to verify that the dragged node was dropped in
   // the expected position.
@@ -475,21 +492,22 @@
 
  private:
   void StartDrag() {
-    const gfx::Point target = GetDragTargetInScreen();
+    const views::View* drag_view =
+        bb_view_->GetMenu()->GetSubmenu()->GetMenuItemAt(0);
+    const gfx::Point current_position =
+        ui_test_utils::GetCenterInScreenCoordinates(drag_view);
 #if defined(USE_AURA)
     // TODO: fix this. Aura requires an additional mouse event to trigger drag
     // and drop checking state.
     EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-        target.x() + 10, target.y(),
+        current_position.x() + 10, current_position.y(),
         CreateEventTask(this, &BookmarkBarViewDragTestBase::StartDrag2)));
 #else
     EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-        target.x() + 10, target.y(),
-        CreateEventTask(this,
-                        &BookmarkBarViewDragTestBase::OnWidgetDragWillStart)));
+        current_position.x() + 10, current_position.y(),
+        CreateEventTask(this, &BookmarkBarViewDragTestBase::AfterDragStarted)));
 
-    // See comment above this method as to why we do this.
-    ScheduleMouseMoveInBackground(target.x(), target.y());
+    OnWidgetDragWillStart();
 #endif
   }
 
@@ -497,8 +515,7 @@
     const gfx::Point target = GetDragTargetInScreen();
     EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
         target.x(), target.y(),
-        CreateEventTask(this,
-                        &BookmarkBarViewDragTestBase::OnWidgetDragWillStart)));
+        CreateEventTask(this, &BookmarkBarViewDragTestBase::AfterDragStarted)));
   }
 
   GURL f1a_url_;
@@ -776,32 +793,19 @@
 
 // Tests drag and drop within the same menu.
 class BookmarkBarViewTest5 : public BookmarkBarViewDragTestBase {
- public:
-  // BookmarkBarViewDragTestBase:
-  void OnWidgetDragWillStart() override {
-    // Drop the item so that it's now the second item.
-    views::MenuItemView* target_menu =
-        bb_view_->GetMenu()->GetSubmenu()->GetMenuItemAt(1);
-    gfx::Point loc(1, target_menu->height() - 2);
-    views::View::ConvertPointToScreen(target_menu, &loc);
-    ASSERT_TRUE(ui_controls::SendMouseMove(loc.x(), loc.y()));
-
-    ASSERT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
-        ui_controls::LEFT, ui_controls::UP,
-        CreateEventTask(this, &BookmarkBarViewTest5::OnWidgetDragComplete)));
-  }
-
  protected:
   // BookmarkBarViewDragTestBase:
+  void AfterDragStarted() override { OnDragEntered(); }
+
   const BookmarkNode* GetDroppedNode() const override {
     return model_->bookmark_bar_node()->GetChild(0)->GetChild(1);
   }
 
   gfx::Point GetDragTargetInScreen() const override {
-    views::MenuItemView* target_menu =
+    const views::View* target_view =
         bb_view_->GetMenu()->GetSubmenu()->GetMenuItemAt(1);
-    gfx::Point target(1, target_menu->height() - 1);
-    views::View::ConvertPointToScreen(target_menu, &target);
+    gfx::Point target(target_view->width() / 2, target_view->height() - 1);
+    views::View::ConvertPointToScreen(target_view, &target);
     return target;
   }
 };
@@ -848,17 +852,6 @@
 // Tests drag and drop to different menu.
 class BookmarkBarViewTest7 : public BookmarkBarViewDragTestBase {
  public:
-  // BookmarkBarViewDragTestBase:
-  void OnWidgetDragWillStart() override { OnDropMenuShown(); }
-
-  void OnWidgetDragComplete() override {
-    // The button should be in normal state now.
-    EXPECT_EQ(views::Button::STATE_NORMAL,
-              bb_view_->other_bookmarks_button()->state());
-
-    BookmarkBarViewDragTestBase::OnWidgetDragComplete();
-  }
-
   void OnDropMenuShown() {
     views::MenuItemView* drop_menu = bb_view_->GetDropMenu();
     ASSERT_NE(nullptr, drop_menu);
@@ -869,16 +862,29 @@
     EXPECT_EQ(views::Button::STATE_PRESSED,
               bb_view_->other_bookmarks_button()->state());
 
-    views::MenuItemView* target_menu = drop_submenu->GetMenuItemAt(0);
-    gfx::Point loc(1, 1);
-    views::View::ConvertPointToScreen(target_menu, &loc);
+    const views::View* target_view = drop_submenu->GetMenuItemAt(0);
+
+    // Drag to the top of the target view.
+    gfx::Point target(target_view->width() / 2, 0);
+    views::View::ConvertPointToScreen(target_view, &target);
     ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-        loc.x(), loc.y(),
+        target.x(), target.y(),
         CreateEventTask(this, &BookmarkBarViewTest7::OnDragEntered)));
   }
 
+  // BookmarkBarViewDragTestBase:
+  void OnWidgetDragComplete() override {
+    // The button should be in normal state now.
+    EXPECT_EQ(views::Button::STATE_NORMAL,
+              bb_view_->other_bookmarks_button()->state());
+
+    BookmarkBarViewDragTestBase::OnWidgetDragComplete();
+  }
+
  protected:
   // BookmarkBarViewDragTestBase:
+  void AfterDragStarted() override { OnDropMenuShown(); }
+
   const BookmarkNode* GetDroppedNode() const override {
     return model_->other_node()->GetChild(0);
   }
@@ -887,13 +893,6 @@
     return ui_test_utils::GetCenterInScreenCoordinates(
         bb_view_->other_bookmarks_button());
   }
-
- private:
-  void OnDragEntered() {
-    ASSERT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
-        ui_controls::LEFT, ui_controls::UP,
-        CreateEventTask(this, &BookmarkBarViewTest7::OnWidgetDragComplete)));
-  }
 };
 
 #if !defined(OS_WIN)
@@ -907,37 +906,35 @@
 // original menu.
 class BookmarkBarViewTest8 : public BookmarkBarViewDragTestBase {
  public:
-  // BookmarkBarViewDragTestBase:
-  void OnWidgetDragWillStart() override { OnDropMenuShown(); }
-
   void OnDropMenuShown() {
     views::MenuItemView* drop_menu = bb_view_->GetDropMenu();
     ASSERT_NE(nullptr, drop_menu);
-    ASSERT_TRUE(drop_menu->GetSubmenu()->IsShowing());
-
-    // Now drag back over first menu.
-    views::LabelButton* button = GetBookmarkButton(0);
-    gfx::Point loc = ui_test_utils::GetCenterInScreenCoordinates(button);
-    ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-        loc.x(), loc.y(),
-        CreateEventTask(this, &BookmarkBarViewTest8::OnDragEntered)));
-  }
-
-  void OnDragEntered() {
-    // Drop on folder F11.
-    views::MenuItemView* drop_menu = bb_view_->GetDropMenu();
-    ASSERT_NE(nullptr, drop_menu);
     views::SubmenuView* drop_submenu = drop_menu->GetSubmenu();
     ASSERT_TRUE(drop_submenu->IsShowing());
 
-    views::MenuItemView* target_menu = drop_submenu->GetMenuItemAt(1);
-    ui_test_utils::MoveMouseToCenterAndPress(
-        target_menu, ui_controls::LEFT, ui_controls::UP,
-        CreateEventTask(this, &BookmarkBarViewTest8::OnWidgetDragComplete));
+    const views::View* target_view;
+    base::OnceClosure task;
+    const auto* controller =
+        static_cast<const BookmarkMenuController*>(drop_menu->GetDelegate());
+    if (controller->node() == model_->other_node()) {
+      // Now drag back over first menu.
+      target_view = GetBookmarkButton(0);
+      task = CreateEventTask(this, &BookmarkBarViewTest8::OnDropMenuShown);
+    } else {
+      // Drag to folder F11.
+      target_view = drop_submenu->GetMenuItemAt(1);
+      task = CreateEventTask(this, &BookmarkBarViewTest8::OnDragEntered);
+    }
+    const gfx::Point target =
+        ui_test_utils::GetCenterInScreenCoordinates(target_view);
+    ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(target.x(), target.y(),
+                                                         std::move(task)));
   }
 
  protected:
   // BookmarkBarViewDragTestBase:
+  void AfterDragStarted() override { OnDropMenuShown(); }
+
   const BookmarkNode* GetDroppedNode() const override {
     return model_->bookmark_bar_node()->GetChild(0)->GetChild(0)->GetChild(1);
   }
@@ -1941,29 +1938,29 @@
 class BookmarkBarViewTest22 : public BookmarkBarViewDragTestBase {
  public:
   // BookmarkBarViewDragTestBase:
-  void OnWidgetDragWillStart() override {
-    OnDragEntered();
-    OnWidgetDestroyed();
-  }
-
   void OnWidgetDestroyed() {
-#if defined(OS_CHROMEOS)
-    ASSERT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
-        ui_controls::LEFT, ui_controls::UP,
-        CreateEventTask(this, &BookmarkBarViewTest22::Done)));
-#else
-    // There are no widgets to send the mouse release to.
     Done();
-#endif
   }
 
  protected:
-  void OnDragEntered() {
+  // BookmarkBarViewDragTestBase:
+  void AfterDragStarted() override { OnDragEntered(); }
+
+  void OnDragEntered() override {
+#if defined(OS_CHROMEOS)
+    ASSERT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
+        ui_controls::LEFT, ui_controls::UP,
+        CreateEventTask(this, &BookmarkBarViewTest22::OnWidgetDestroyed)));
+#endif
+
     window_->Close();
-    window_ = NULL;
+    window_ = nullptr;
+
+#if !defined(OS_CHROMEOS)
+    OnWidgetDestroyed();
+#endif
   }
 
-  // BookmarkBarViewDragTestBase:
   const BookmarkNode* GetDroppedNode() const override {
     // This test doesn't check what happens on drop.
     return nullptr;
diff --git a/chrome/browser/ui/views/chrome_typography_provider.cc b/chrome/browser/ui/views/chrome_typography_provider.cc
index 2cd40fc..c2655e4 100644
--- a/chrome/browser/ui/views/chrome_typography_provider.cc
+++ b/chrome/browser/ui/views/chrome_typography_provider.cc
@@ -35,11 +35,11 @@
 #endif
 constexpr char kUnspecifiedTypeface[] = "";
 
-// If the default foreground color from the native theme isn't black, the rest
-// of the Harmony spec isn't going to work. Also skip Harmony if a Windows
-// High Contrast theme is enabled. One of the four standard High Contrast themes
-// in Windows 10 still has black text, but (since the user wants high contrast)
-// the grey text shades in Harmony should not be used.
+// If the default foreground color from the native theme isn't black and dark
+// mode is not on the rest of the Harmony spec isn't going to work. Also skip
+// Harmony if a Windows High Contrast theme is enabled. One of the four standard
+// High Contrast themes in Windows 10 still has black text, but (since the user
+// wants high contrast) the grey text shades in Harmony should not be used.
 bool ShouldIgnoreHarmonySpec(const ui::NativeTheme& theme) {
   // Mac provides users limited ways to customize the UI, including dark and
   // high contrast modes; all these are addressed elsewhere, so there's no need
@@ -50,6 +50,8 @@
 #else
   if (theme.UsesHighContrastColors())
     return true;
+  if (theme.SystemDarkModeEnabled())
+    return false;
 
   // TODO(pbos): Revisit this check. Both GG900 and black are considered
   // "default black" as the common theme uses GG900 as primary color.
diff --git a/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc b/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc
index f86ec57..47da09f 100644
--- a/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc
+++ b/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/metrics/user_metrics.h"
 #include "base/strings/string_number_conversions.h"
@@ -15,6 +16,7 @@
 #include "ui/base/theme_provider.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/text_utils.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/event_monitor.h"
@@ -39,34 +41,31 @@
 FeaturePromoBubbleView::FeaturePromoBubbleView(
     views::View* anchor_view,
     views::BubbleBorder::Arrow arrow,
+    ActivationAction activation_action,
     int string_specifier,
-    ActivationAction activation_action)
-    : FeaturePromoBubbleView(anchor_view,
-                             gfx::Rect(),
-                             arrow,
-                             string_specifier,
-                             activation_action) {}
-
-FeaturePromoBubbleView::FeaturePromoBubbleView(const gfx::Rect& anchor_rect,
-                                               views::BubbleBorder::Arrow arrow,
-                                               int string_specifier)
-    : FeaturePromoBubbleView(nullptr,
-                             anchor_rect,
-                             arrow,
-                             string_specifier,
-                             ActivationAction::ACTIVATE) {}
-
-FeaturePromoBubbleView::FeaturePromoBubbleView(
-    views::View* anchor_view,
-    const gfx::Rect& anchor_rect,
-    views::BubbleBorder::Arrow arrow,
-    int string_specifier,
-    ActivationAction activation_action)
+    base::Optional<int> screenreader_string_specifier,
+    base::Optional<ui::Accelerator> feature_accelerator)
     : BubbleDialogDelegateView(anchor_view, arrow),
       activation_action_(activation_action) {
+  DCHECK(anchor_view);
   UseCompactMargins();
-  if (!anchor_view)
-    SetAnchorRect(anchor_rect);
+
+  const base::string16 body_text = l10n_util::GetStringUTF16(string_specifier);
+
+  // Feature promos are purely informational. We can skip reading the UI
+  // elements inside the bubble and just have the information announced when the
+  // bubble shows. To do so, we change the a11y tree to make this a leaf node
+  // and set the name to the message we want to announce.
+  GetViewAccessibility().OverrideIsLeaf(true);
+  if (!screenreader_string_specifier) {
+    accessible_name_ = body_text;
+  } else if (feature_accelerator) {
+    accessible_name_ = l10n_util::GetStringFUTF16(
+        *screenreader_string_specifier, feature_accelerator->GetShortcutText());
+  } else {
+    accessible_name_ =
+        l10n_util::GetStringUTF16(*screenreader_string_specifier);
+  }
 
   // We get the theme provider from the anchor view since our widget hasn't been
   // created yet.
@@ -86,7 +85,7 @@
       views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
   SetLayoutManager(std::move(box_layout));
 
-  auto* label = new views::Label(l10n_util::GetStringUTF16(string_specifier));
+  auto* label = new views::Label(body_text);
   label->SetBackgroundColor(background_color);
   label->SetEnabledColor(text_color);
   AddChildView(label);
@@ -107,8 +106,7 @@
       ChromeLayoutProvider::Get()->GetCornerRadiusMetric(views::EMPHASIS_HIGH));
 
   widget->Show();
-  if (activation_action == ActivationAction::ACTIVATE)
-    StartAutoCloseTimer(kDelayDefault);
+  StartAutoCloseTimer(kDelayDefault);
 }
 
 FeaturePromoBubbleView::~FeaturePromoBubbleView() = default;
@@ -117,10 +115,13 @@
 FeaturePromoBubbleView* FeaturePromoBubbleView::CreateOwned(
     views::View* anchor_view,
     views::BubbleBorder::Arrow arrow,
+    ActivationAction activation_action,
     int string_specifier,
-    ActivationAction activation_action) {
-  return new FeaturePromoBubbleView(anchor_view, arrow, string_specifier,
-                                    activation_action);
+    base::Optional<int> screenreader_string_specifier,
+    base::Optional<ui::Accelerator> feature_accelerator) {
+  return new FeaturePromoBubbleView(
+      anchor_view, arrow, activation_action, string_specifier,
+      screenreader_string_specifier, feature_accelerator);
 }
 
 void FeaturePromoBubbleView::CloseBubble() {
@@ -156,6 +157,16 @@
   return bounds;
 }
 
+ax::mojom::Role FeaturePromoBubbleView::GetAccessibleWindowRole() const {
+  // Since we don't have any controls for the user to interact with (we're just
+  // an information bubble), override our role to kAlert.
+  return ax::mojom::Role::kAlert;
+}
+
+base::string16 FeaturePromoBubbleView::GetAccessibleWindowTitle() const {
+  return accessible_name_;
+}
+
 void FeaturePromoBubbleView::StartAutoCloseTimer(
     base::TimeDelta auto_close_duration) {
   timer_.Start(FROM_HERE, auto_close_duration, this,
diff --git a/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.h b/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.h
index 15053e6b..6cfe580 100644
--- a/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.h
+++ b/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_FEATURE_PROMOS_FEATURE_PROMO_BUBBLE_VIEW_H_
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/timer/timer.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
@@ -15,6 +16,7 @@
 }
 
 namespace ui {
+class Accelerator;
 class MouseEvent;
 }
 
@@ -31,44 +33,36 @@
   ~FeaturePromoBubbleView() override;
 
   // Creates a promo bubble. The returned pointer is only valid until the widget
-  // is closed. It must not be manually deleted by the caller. |anchor_view| is
-  // the View this bubble is anchored to. |arrow| specifies where on the border
-  // the bubble's arrow is located. |string_specifier| is a string ID that can
-  // be passed to |l10n_util::GetStringUTF16()|. |activation_action| specifies
-  // whether the bubble's widget will be activated.
+  // is closed. It must not be manually deleted by the caller.
+  // * |anchor_view| is the View this bubble is anchored to.
+  // * |arrow| specifies where on the border the bubble's arrow is located.
+  // * |string_specifier| is a string ID that can be passed to
+  // |l10n_util::GetStringUTF16()|.
+  // * |screenreader_string_specifier| is an optional alternate string to be
+  // exposed to screen readers.
+  // * |feature_accelerator| is an optional keyboard accelerator to be announced
+  // by screen readers. If |screenreader_string_specifier| is used and has a
+  // placeholder, |feature_accelerator|'s shortcut text will be filled in.
+  // * |activation_action| specifies whether the bubble's widget will be
+  // activated.
   static FeaturePromoBubbleView* CreateOwned(
       views::View* anchor_view,
       views::BubbleBorder::Arrow arrow,
+      ActivationAction activation_action,
       int string_specifier,
-      ActivationAction activation_action);
+      base::Optional<int> screenreader_string_specifier = base::nullopt,
+      base::Optional<ui::Accelerator> feature_accelerator = base::nullopt);
 
   // Closes the promo bubble.
   void CloseBubble();
 
- protected:
-  // The |anchor_view| is used to anchor the FeaturePromoBubbleView. The |arrow|
-  // sets where the arrow hangs off the bubble and the |string_specifier| is
-  // the text that is displayed on the bubble. The |activation_action| sets if
-  // the bubble is active or not.
-  FeaturePromoBubbleView(views::View* anchor_view,
-                         views::BubbleBorder::Arrow arrow,
-                         int string_specifier,
-                         ActivationAction activation_action);
-
-  // The |anchor_rect| is used to anchor bubble views that have custom
-  // positioning requirements. The |arrow| sets where the arrow hangs off the
-  // bubble and the |string_specifier| is the text that is displayed on the
-  // bubble.
-  FeaturePromoBubbleView(const gfx::Rect& anchor_rect,
-                         views::BubbleBorder::Arrow arrow,
-                         int string_specifier);
-
  private:
   FeaturePromoBubbleView(views::View* anchor_view,
-                         const gfx::Rect& anchor_rect,
                          views::BubbleBorder::Arrow arrow,
+                         ActivationAction activation_action,
                          int string_specifier,
-                         ActivationAction activation_action);
+                         base::Optional<int> screenreader_string_specifier,
+                         base::Optional<ui::Accelerator> feature_accelerator);
 
   // BubbleDialogDelegateView:
   int GetDialogButtons() const override;
@@ -76,6 +70,8 @@
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
   gfx::Rect GetBubbleBounds() override;
+  ax::mojom::Role GetAccessibleWindowRole() const override;
+  base::string16 GetAccessibleWindowTitle() const override;
   void UpdateHighlightedButton(bool highlighted) override {
     // Do nothing: the anchor for promo bubbles should not highlight.
   }
@@ -87,6 +83,8 @@
   base::OneShotTimer timer_;
   const ActivationAction activation_action_;
 
+  base::string16 accessible_name_;
+
   DISALLOW_COPY_AND_ASSIGN(FeaturePromoBubbleView);
 };
 
diff --git a/chrome/browser/ui/views/feature_promos/feature_promo_dialog_browsertest.cc b/chrome/browser/ui/views/feature_promos/feature_promo_dialog_browsertest.cc
index ed7ef5d..3d02108b 100644
--- a/chrome/browser/ui/views/feature_promos/feature_promo_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/feature_promos/feature_promo_dialog_browsertest.cc
@@ -23,8 +23,8 @@
     // currently no infrastructure for test-only string resources.
     int placeholder_string = IDS_NEWTAB_PROMO_0;
     FeaturePromoBubbleView::CreateOwned(
-        app_menu_button, views::BubbleBorder::TOP_RIGHT, placeholder_string,
-        FeaturePromoBubbleView::ActivationAction::ACTIVATE);
+        app_menu_button, views::BubbleBorder::TOP_RIGHT,
+        FeaturePromoBubbleView::ActivationAction::ACTIVATE, placeholder_string);
   }
 };
 
diff --git a/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller.cc b/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller.cc
index ce82d096..f7a6fd4d 100644
--- a/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller.cc
+++ b/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller.cc
@@ -43,9 +43,15 @@
   app_menu_button->AddObserver(this);
   app_menu_button->SetPromoFeature(InProductHelpFeature::kReopenTab);
 
+  // Get keyboard shortcut for reopening last closed tab. This should exist.
+  ui::Accelerator accelerator;
+  bool has_accelerator =
+      browser_view_->GetAccelerator(IDC_RESTORE_TAB, &accelerator);
+  DCHECK(has_accelerator);
   promo_bubble_ = FeaturePromoBubbleView::CreateOwned(
       app_menu_button, views::BubbleBorder::Arrow::TOP_RIGHT,
-      IDS_REOPEN_TAB_PROMO, FeaturePromoBubbleView::ActivationAction::ACTIVATE);
+      FeaturePromoBubbleView::ActivationAction::DO_NOT_ACTIVATE,
+      IDS_REOPEN_TAB_PROMO, IDS_REOPEN_TAB_PROMO_SCREENREADER, accelerator);
   promo_bubble_->set_close_on_deactivate(false);
   promo_bubble_->GetWidget()->AddObserver(this);
 }
@@ -53,8 +59,18 @@
 void ReopenTabPromoController::OnTabReopened(int command_id) {
   iph_service_->TabReopened();
 
-  if (is_showing_ && command_id == AppMenuModel::kMinRecentTabsCommandId) {
-    promo_step_ = StepAtDismissal::kTabReopened;
+  if (!is_showing_)
+    return;
+  if (command_id != AppMenuModel::kMinRecentTabsCommandId &&
+      command_id != IDC_RESTORE_TAB)
+    return;
+
+  promo_step_ = StepAtDismissal::kTabReopened;
+  if (command_id == IDC_RESTORE_TAB) {
+    // If using the keyboard shortcut, we bypass the other steps and so we close
+    // the bubble now.
+    promo_bubble_->GetWidget()->Close();
+    PromoEnded();
   }
 }
 
@@ -62,19 +78,20 @@
   DCHECK(promo_bubble_);
   promo_bubble_ = nullptr;
 
-  // If the menu isn't showing, that means the promo bubble timed out.
-  if (!browser_view_->toolbar()->app_menu_button()->IsMenuShowing())
+  // If we haven't progressed past |StepAtDismissal::kBubbleShown|, the bubble
+  // timed out without the user following our IPH. End it.
+  if (promo_step_ == StepAtDismissal::kBubbleShown)
     PromoEnded();
 }
 
 void ReopenTabPromoController::AppMenuShown() {
+  promo_step_ = StepAtDismissal::kMenuOpened;
+
   // Close the promo bubble since it doesn't automatically close on click.
   promo_bubble_->GetWidget()->Close();
 
   // Stop showing promo on app menu button.
   browser_view_->toolbar()->app_menu_button()->SetPromoFeature(base::nullopt);
-
-  promo_step_ = StepAtDismissal::kMenuOpened;
 }
 
 void ReopenTabPromoController::AppMenuClosed() {
diff --git a/chrome/browser/ui/views/find_bar_view.cc b/chrome/browser/ui/views/find_bar_view.cc
index 64d88fad..f2d3921 100644
--- a/chrome/browser/ui/views/find_bar_view.cc
+++ b/chrome/browser/ui/views/find_bar_view.cc
@@ -512,8 +512,11 @@
                   0xFF);
   auto border = std::make_unique<views::BubbleBorder>(
       views::BubbleBorder::NONE, views::BubbleBorder::SMALL_SHADOW, bg_color);
-  border->SetCornerRadius(
-      ChromeLayoutProvider::Get()->GetCornerRadiusMetric(views::EMPHASIS_HIGH));
+  // TODO(sajadm): Remove when fixing https://crbug.com/822075 and use
+  // EMPHASIS_HIGH metric values from the LayoutProvider to get the
+  // corner radius.
+  border->SetCornerRadius(2);
+
   SetBackground(std::make_unique<views::BubbleBackground>(border.get()));
   SetBorder(std::move(border));
 
diff --git a/chrome/browser/ui/views/frame/browser_frame_mash.cc b/chrome/browser/ui/views/frame/browser_frame_mash.cc
index 0536dcb..92eb1c3 100644
--- a/chrome/browser/ui/views/frame/browser_frame_mash.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_mash.cc
@@ -88,6 +88,9 @@
   std::unique_ptr<views::DesktopWindowTreeHostMus> desktop_window_tree_host =
       std::make_unique<views::DesktopWindowTreeHostMus>(
           std::move(window_tree_host_init_params), browser_frame_, this);
+  // BrowserNonClientFrameViewAsh::OnBoundsChanged() takes care of updating
+  // the insets.
+  desktop_window_tree_host->set_auto_update_client_area(false);
   SetDesktopWindowTreeHost(std::move(desktop_window_tree_host));
   return params;
 }
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 9147b20..1e52423 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -967,7 +967,7 @@
   }
 }
 
-void BrowserView::OnTabRestoredFromMenu(int command_id) {
+void BrowserView::OnTabRestored(int command_id) {
 #if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
   reopen_tab_promo_controller_.OnTabReopened(command_id);
 #endif
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 06aad03..55464d7d 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -316,7 +316,7 @@
                           int index,
                           int reason) override;
   void OnTabDetached(content::WebContents* contents, bool was_active) override;
-  void OnTabRestoredFromMenu(int command_id) override;
+  void OnTabRestored(int command_id) override;
   void ZoomChangedForActiveTab(bool can_show_bubble) override;
   gfx::Rect GetRestoredBounds() const override;
   ui::WindowShowState GetRestoredState() const override;
diff --git a/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos_unittest.cc b/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos_unittest.cc
index 687cf337..e44aa19bb 100644
--- a/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos_unittest.cc
+++ b/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -53,6 +54,23 @@
     return result;
   }
 
+  void set_manual_ack_dispatch_key_event_post_ime_callback(bool manual) {
+    manual_ack_dispatch_key_event_post_ime_callback_ = manual;
+  }
+
+  void AckDispatchKeyEventPostIMECallback() {
+    DCHECK(manual_ack_dispatch_key_event_post_ime_callback_);
+
+    if (!pending_dispatch_key_event_post_ime_callback_) {
+      pending_dispatch_key_event_post_ime_callback_wait_loop_ =
+          std::make_unique<base::RunLoop>();
+      pending_dispatch_key_event_post_ime_callback_wait_loop_->Run();
+      pending_dispatch_key_event_post_ime_callback_wait_loop_.reset();
+    }
+
+    std::move(pending_dispatch_key_event_post_ime_callback_).Run(false, false);
+  }
+
  private:
   void SetCompositionText(const ui::CompositionText& composition) override {
     CompositionEvent ev = {CompositionEventType::SET, composition.text, 0};
@@ -89,6 +107,15 @@
   void DispatchKeyEventPostIME(
       std::unique_ptr<ui::Event> event,
       DispatchKeyEventPostIMECallback callback) override {
+    if (manual_ack_dispatch_key_event_post_ime_callback_) {
+      // Only one pending callback is expected.
+      EXPECT_FALSE(pending_dispatch_key_event_post_ime_callback_);
+      pending_dispatch_key_event_post_ime_callback_ = std::move(callback);
+      if (pending_dispatch_key_event_post_ime_callback_wait_loop_)
+        pending_dispatch_key_event_post_ime_callback_wait_loop_->Quit();
+      return;
+    }
+
     std::move(callback).Run(false, false);
   }
   void EnsureCaretNotInRect(const gfx::Rect& rect) override {}
@@ -103,6 +130,11 @@
   std::unique_ptr<base::RunLoop> run_loop_;
   base::Optional<CompositionEvent> receieved_event_;
 
+  bool manual_ack_dispatch_key_event_post_ime_callback_ = false;
+  DispatchKeyEventPostIMECallback pending_dispatch_key_event_post_ime_callback_;
+  std::unique_ptr<base::RunLoop>
+      pending_dispatch_key_event_post_ime_callback_wait_loop_;
+
   DISALLOW_COPY_AND_ASSIGN(TestTextInputClient);
 };
 
@@ -217,3 +249,27 @@
   EXPECT_FALSE(ProcessKeyEvent(UnicodeKeyPress(ui::VKEY_V, ui::DomCode::US_V,
                                                ui::EF_CONTROL_DOWN, 'V')));
 }
+
+// Test that multiple DispatchKeyEventPostIME calls are handled serially.
+TEST_F(InputMethodBridgeChromeOSTest, SerialDispatchKeyEventPostIME) {
+  client_->set_manual_ack_dispatch_key_event_post_ime_callback(true);
+
+  // Send multiple key events.
+  input_method_->ProcessKeyEvent(
+      std::make_unique<ui::KeyEvent>(ui::ET_KEY_PRESSED, ui::VKEY_A,
+                                     ui::EF_NONE),
+      base::DoNothing());
+  input_method_->ProcessKeyEvent(
+      std::make_unique<ui::KeyEvent>(ui::ET_KEY_RELEASED, ui::VKEY_A,
+                                     ui::EF_NONE),
+      base::DoNothing());
+
+  // TestTextInputClient::DispatchKeyEventPostIME is called via mojo.
+  // RemoteTextInputClient should make the calls serially. Spin message loop to
+  // see whether |client_| complains about more than one call at a time.
+  base::RunLoop().RunUntilIdle();
+
+  // Ack the pending key events.
+  client_->AckDispatchKeyEventPostIMECallback();
+  client_->AckDispatchKeyEventPostIMECallback();
+}
diff --git a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
index a5653fc1..ef5b1599 100644
--- a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
+++ b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
@@ -4,17 +4,29 @@
 
 #include "chrome/browser/ui/views/ime_driver/remote_text_input_client.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "ui/events/event_dispatcher.h"
 
+struct RemoteTextInputClient::QueuedEvent {
+  QueuedEvent(std::unique_ptr<ui::Event> event,
+              DispatchKeyEventPostIMECallback callback)
+      : event(std::move(event)), callback(std::move(callback)) {}
+
+  std::unique_ptr<ui::Event> event;
+  DispatchKeyEventPostIMECallback callback;
+};
+
 RemoteTextInputClient::RemoteTextInputClient(
     ws::mojom::TextInputClientPtr client,
     ws::mojom::SessionDetailsPtr details)
     : remote_client_(std::move(client)), details_(std::move(details)) {}
 
 RemoteTextInputClient::~RemoteTextInputClient() {
-  while (!pending_callbacks_.empty()) {
+  while (!queued_events_.empty()) {
     RunNextPendingCallback(/* handled */ false,
                            /* stopped_propagation */ false);
   }
@@ -38,6 +50,7 @@
     bool handled,
     bool stopped_propagation) {
   RunNextPendingCallback(handled, stopped_propagation);
+  DispatchQueuedEvent();
 }
 
 void RemoteTextInputClient::SetCompositionText(
@@ -201,20 +214,30 @@
 ui::EventDispatchDetails RemoteTextInputClient::DispatchKeyEventPostIME(
     ui::KeyEvent* event,
     DispatchKeyEventPostIMECallback callback) {
-  pending_callbacks_.push(std::move(callback));
+  const bool is_first_event = queued_events_.empty();
+  queued_events_.emplace(ui::Event::Clone(*event), std::move(callback));
+  if (is_first_event)
+    DispatchQueuedEvent();
+  return ui::EventDispatchDetails();
+}
+
+void RemoteTextInputClient::DispatchQueuedEvent() {
+  if (queued_events_.empty())
+    return;
+
+  DCHECK(queued_events_.front().event);
   remote_client_->DispatchKeyEventPostIME(
-      ui::Event::Clone(*event),
+      std::move(queued_events_.front().event),
       base::BindOnce(&RemoteTextInputClient::OnDispatchKeyEventPostIMECompleted,
                      weak_ptr_factory_.GetWeakPtr()));
-  return ui::EventDispatchDetails();
 }
 
 void RemoteTextInputClient::RunNextPendingCallback(bool handled,
                                                    bool stopped_propagation) {
-  DCHECK(!pending_callbacks_.empty());
+  DCHECK(!queued_events_.empty());
   DispatchKeyEventPostIMECallback callback =
-      std::move(pending_callbacks_.front());
-  pending_callbacks_.pop();
+      std::move(queued_events_.front().callback);
+  queued_events_.pop();
   if (callback)
     std::move(callback).Run(handled, stopped_propagation);
 }
diff --git a/chrome/browser/ui/views/ime_driver/remote_text_input_client.h b/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
index e76bfcaf..75127adc 100644
--- a/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
+++ b/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
@@ -28,6 +28,8 @@
   void SetTextInputClientData(ws::mojom::TextInputClientDataPtr data);
 
  private:
+  struct QueuedEvent;
+
   // See |pending_callbacks_| for details.
   void OnDispatchKeyEventPostIMECompleted(bool handled,
                                           bool stopped_propagation);
@@ -70,20 +72,25 @@
       ui::KeyEvent* event,
       DispatchKeyEventPostIMECallback callback) override;
 
-  // Removes the callback at the front of |pending_callbacks_| and runs it with
-  // |handled| and |stopped_propagation| as arguments.
+  // Dispatches the first queued event.
+  void DispatchQueuedEvent();
+
+  // Removes the queue event at the front of |queued_events_| and runs its
+  // callback with |handled| and |stopped_propagation| as arguments.
   void RunNextPendingCallback(bool handled, bool stopped_propagation);
 
   ws::mojom::TextInputClientPtr remote_client_;
   ws::mojom::SessionDetailsPtr details_;
 
-  // Callbacks supplied to DispatchKeyEventPostIME() are added here. When the
-  // response from the remote side is received
-  // (OnDispatchKeyEventPostIMECompleted()), the callback is removed and run.
-  // This is done to ensure if we are destroyed all the callbacks are run.
-  // This is necessary as the callbacks may have originated from a remote
-  // client.
-  base::queue<DispatchKeyEventPostIMECallback> pending_callbacks_;
+  // Events to be dispatched with DispatchKeyEventPostIME(). Only one event is
+  // dispatched at a time and others are queued here until the current one
+  // finished processing, i.e. the response from the remote side is received
+  // (OnDispatchKeyEventPostIMECompleted()), the dispatched event is removed
+  // from the queue and its callback is invoked. This is done to avoid
+  // overlapping of key events processing (https://crbug.com/938808).
+  // Note that when we are destroyed all the callbacks needs to run. This is
+  // necessary as the callbacks may have originated from a remote client.
+  base::queue<QueuedEvent> queued_events_;
 
   base::WeakPtrFactory<RemoteTextInputClient> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/ui/views/location_bar/star_view.cc b/chrome/browser/ui/views/location_bar/star_view.cc
index 29be798dc..1481597 100644
--- a/chrome/browser/ui/views/location_bar/star_view.cc
+++ b/chrome/browser/ui/views/location_bar/star_view.cc
@@ -63,8 +63,8 @@
   FeaturePromoBubbleView* bookmark_promo_bubble =
       FeaturePromoBubbleView::CreateOwned(
           this, views::BubbleBorder::TOP_RIGHT,
-          GetBookmarkPromoStringSpecifier(),
-          FeaturePromoBubbleView::ActivationAction::ACTIVATE);
+          FeaturePromoBubbleView::ActivationAction::ACTIVATE,
+          GetBookmarkPromoStringSpecifier());
   if (!bookmark_promo_observer_.IsObserving(
           bookmark_promo_bubble->GetWidget())) {
     bookmark_promo_observer_.Add(bookmark_promo_bubble->GetWidget());
diff --git a/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc b/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc
index cbaed71..7d747da21 100644
--- a/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc
+++ b/chrome/browser/ui/views/menu_view_drag_and_drop_test.cc
@@ -87,14 +87,14 @@
   // First, fill the parent completely.
   SetBoundsRect(parent()->GetLocalBounds());
 
-  // Then add two draggable views, each 10x2.
+  // Then add two draggable views, each 10x5.
   views::View* first = new TestDragView();
   AddChildView(first);
-  first->SetBounds(2, 2, 10, 2);
+  first->SetBounds(2, 2, 10, 5);
 
   views::View* second = new TestDragView();
   AddChildView(second);
-  second->SetBounds(15, 2, 10, 2);
+  second->SetBounds(15, 2, 10, 5);
 }
 
 bool TestTargetView::GetDropFormats(
@@ -144,6 +144,8 @@
   void BuildMenu(views::MenuItemView* menu) override;
   void DoTestWithMenuOpen() override;
 
+  virtual void OnDragEntered();
+
   TestTargetView* target_view() { return target_view_; }
   bool asked_to_close() const { return asked_to_close_; }
   bool performed_in_menu_drop() const { return performed_in_menu_drop_; }
@@ -201,9 +203,9 @@
   ASSERT_TRUE(submenu);
   ASSERT_TRUE(submenu->IsShowing());
   ASSERT_EQ(3, submenu->GetMenuItemCount());
-  views::View* first_view = submenu->GetMenuItemAt(0);
+  const views::View* first_view = submenu->GetMenuItemAt(0);
   ASSERT_EQ(1, first_view->child_count());
-  views::View* child_view = first_view->child_at(0);
+  const views::View* child_view = first_view->child_at(0);
   EXPECT_EQ(child_view, target_view_);
 
   // We do this here (instead of in BuildMenu()) so that the menu is already
@@ -211,6 +213,15 @@
   target_view_->Init();
 }
 
+void MenuViewDragAndDropTest::OnDragEntered() {
+  // Drop the element.
+  GetDragTaskRunner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(base::IgnoreResult(&ui_controls::SendMouseEvents),
+                     ui_controls::LEFT, ui_controls::UP,
+                     ui_controls::kNoAccelerator));
+}
+
 bool MenuViewDragAndDropTest::GetDropFormats(
     views::MenuItemView* menu,
     int* formats,
@@ -276,15 +287,16 @@
 
  private:
   void StartDrag();
-  void OnDragEntered();
 };
 
 void MenuViewDragAndDropTestTestInMenuDrag::OnWidgetDragWillStart() {
   // Enqueue an event to drag the second menu element to the third element.
-  views::View* drag_view = menu()->GetSubmenu()->GetMenuItemAt(2);
-  gfx::Point loc(1, drag_view->height() - 1);
-  views::View::ConvertPointToScreen(drag_view, &loc);
-  ScheduleMouseMoveInBackground(loc.x(), loc.y());
+  const views::View* drop_target_view = menu()->GetSubmenu()->GetMenuItemAt(2);
+  const gfx::Point target =
+      ui_test_utils::GetCenterInScreenCoordinates(drop_target_view);
+  GetDragTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(base::IgnoreResult(&ui_controls::SendMouseMove),
+                                target.x(), target.y()));
 }
 
 void MenuViewDragAndDropTestTestInMenuDrag::OnWidgetDragComplete() {
@@ -303,8 +315,10 @@
 void MenuViewDragAndDropTestTestInMenuDrag::DoTestWithMenuOpen() {
   MenuViewDragAndDropTest::DoTestWithMenuOpen();
 
+  views::SubmenuView* submenu = menu()->GetSubmenu();
+
   // We're going to drag the second menu element.
-  views::MenuItemView* drag_view = menu()->GetSubmenu()->GetMenuItemAt(1);
+  views::MenuItemView* drag_view = submenu->GetMenuItemAt(1);
   ASSERT_NE(nullptr, drag_view);
   ui_test_utils::MoveMouseToCenterAndPress(
       drag_view, ui_controls::LEFT, ui_controls::DOWN,
@@ -313,30 +327,17 @@
 
 void MenuViewDragAndDropTestTestInMenuDrag::StartDrag() {
   // Begin dragging the second menu element.
-  views::View* drag_view = menu()->GetSubmenu()->GetMenuItemAt(2);
-  gfx::Point loc(1, drag_view->height() - 1);
-  views::View::ConvertPointToScreen(drag_view, &loc);
+  const views::View* drag_view = menu()->GetSubmenu()->GetMenuItemAt(1);
+  const gfx::Point current_position =
+      ui_test_utils::GetCenterInScreenCoordinates(drag_view);
   EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-      loc.x() + 10, loc.y(),
+      current_position.x() + 10, current_position.y(),
       CreateEventTask(this,
                       &MenuViewDragAndDropTestTestInMenuDrag::OnDragEntered)));
 
   OnWidgetDragWillStart();
 }
 
-void MenuViewDragAndDropTestTestInMenuDrag::OnDragEntered() {
-  // Drop the item on the target.
-  views::MenuItemView* drop_target = menu()->GetSubmenu()->GetMenuItemAt(2);
-  gfx::Point loc(1, drop_target->height() - 2);
-  views::View::ConvertPointToScreen(drop_target, &loc);
-  ui_controls::SendMouseMove(loc.x(), loc.y());
-
-  ui_controls::SendMouseEventsNotifyWhenDone(
-      ui_controls::LEFT, ui_controls::UP,
-      CreateEventTask(
-          this, &MenuViewDragAndDropTestTestInMenuDrag::OnWidgetDragComplete));
-}
-
 // Test that an in-menu (i.e., entirely implemented in the menu code) closes the
 // menu automatically once the drag is complete, and does not ask the delegate
 // to stay open.
@@ -356,18 +357,20 @@
  protected:
   // MenuViewDragAndDropTest:
   void DoTestWithMenuOpen() override;
+  void OnDragEntered() override;
 
  private:
   void StartDrag();
-  void OnDragEntered();
 };
 
 void MenuViewDragAndDropTestNestedDrag::OnWidgetDragWillStart() {
   // Enqueue an event to drag the target's first child to its second.
-  views::View* drop_target = target_view()->child_at(1);
-  gfx::Point loc(2, 0);
-  views::View::ConvertPointToScreen(drop_target, &loc);
-  ScheduleMouseMoveInBackground(loc.x(), loc.y());
+  const views::View* drop_target_view = target_view()->child_at(1);
+  const gfx::Point target =
+      ui_test_utils::GetCenterInScreenCoordinates(drop_target_view);
+  GetDragTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(base::IgnoreResult(&ui_controls::SendMouseMove),
+                                target.x(), target.y()));
 }
 
 void MenuViewDragAndDropTestNestedDrag::OnWidgetDragComplete() {
@@ -402,33 +405,24 @@
       CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::StartDrag));
 }
 
-void MenuViewDragAndDropTestNestedDrag::StartDrag() {
-  // Begin dragging the target's first child.
-  views::View* drop_target = target_view()->child_at(1);
-  gfx::Point loc(2, 0);
-  views::View::ConvertPointToScreen(drop_target, &loc);
-  EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-      loc.x() + 10, loc.y(),
-      CreateEventTask(this,
-                      &MenuViewDragAndDropTestNestedDrag::OnDragEntered)));
-
-  OnWidgetDragWillStart();
-}
-
 void MenuViewDragAndDropTestNestedDrag::OnDragEntered() {
   // The view should be dragging now.
   EXPECT_TRUE(target_view()->dragging());
 
-  // Drop the item so that it's now the second item.
-  views::View* drop_target = target_view()->child_at(1);
-  gfx::Point loc(5, 0);
-  views::View::ConvertPointToScreen(drop_target, &loc);
-  ui_controls::SendMouseMove(loc.x(), loc.y());
+  MenuViewDragAndDropTest::OnDragEntered();
+}
 
-  ui_controls::SendMouseEventsNotifyWhenDone(
-      ui_controls::LEFT, ui_controls::UP,
-      CreateEventTask(
-          this, &MenuViewDragAndDropTestNestedDrag::OnWidgetDragComplete));
+void MenuViewDragAndDropTestNestedDrag::StartDrag() {
+  // Begin dragging the target's first child.
+  const views::View* drag_view = target_view()->child_at(0);
+  const gfx::Point current_position =
+      ui_test_utils::GetCenterInScreenCoordinates(drag_view);
+  EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
+      current_position.x() + 10, current_position.y(),
+      CreateEventTask(this,
+                      &MenuViewDragAndDropTestNestedDrag::OnDragEntered)));
+
+  OnWidgetDragWillStart();
 }
 
 // Test that a nested drag (i.e. one via a child view, and not entirely
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
index dfffc41..de918bf 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
@@ -73,7 +73,7 @@
 #include "ui/views/window/dialog_client_view.h"
 #include "url/gurl.h"
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #endif
 
@@ -902,7 +902,7 @@
   permissions_set->AddPaddingColumn(views::GridLayout::kFixedSize, side_margin);
 }
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 std::unique_ptr<PageInfoUI::SecurityDescription>
 PageInfoBubbleView::CreateSecurityDescriptionForPasswordReuse(
     bool is_enterprise_password) const {
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.h b/chrome/browser/ui/views/page_info/page_info_bubble_view.h
index c2ae099b..a39ffec 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.h
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.h
@@ -143,7 +143,7 @@
                                   bool is_list_empty,
                                   int column_id);
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   std::unique_ptr<PageInfoUI::SecurityDescription>
   CreateSecurityDescriptionForPasswordReuse(
       bool is_enterprise_password) const override;
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
index dd6be8a..e77a4d49 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
@@ -38,13 +38,10 @@
       menu_width_(0),
       anchor_button_(anchor_button),
       close_bubble_helper_(this, browser) {
-  // Because the contents are in a ScrollView (see ShowView) they won't be
-  // clipped by the rounded corners like normal. To work around this, we add
-  // extra margins on the top and bottom so the scroll view is entirely
-  // inside the rectangular area of the bubble.
-  const int corner_radius =
-      ChromeLayoutProvider::Get()->GetCornerRadiusMetric(views::EMPHASIS_HIGH);
-  set_margins(gfx::Insets(corner_radius, 0, corner_radius, 0));
+  // TODO(sajadm): Remove when fixing https://crbug.com/822075
+  // The sign in webview will be clipped on the bottom corners without these
+  // margins, see related bug <http://crbug.com/593203>.
+  set_margins(gfx::Insets(0, 0, 2, 0));
   if (anchor_button) {
     anchor_button->AnimateInkDrop(views::InkDropState::ACTIVATED, nullptr);
   } else {
@@ -106,10 +103,7 @@
       display::Screen::GetScreen()
           ->GetDisplayNearestPoint(anchor_rect.CenterPoint())
           .work_area();
-  const int top_margin =
-      ChromeLayoutProvider::Get()->GetCornerRadiusMetric(views::EMPHASIS_HIGH);
-  int available_space =
-      screen_space.bottom() - anchor_rect.bottom() - top_margin;
+  int available_space = screen_space.bottom() - anchor_rect.bottom();
 #if defined(OS_WIN)
   // On Windows the bubble can also be show to the top of the anchor.
   available_space =
diff --git a/chrome/browser/ui/views/tabs/new_tab_button.cc b/chrome/browser/ui/views/tabs/new_tab_button.cc
index b9e52a3..31aca8c 100644
--- a/chrome/browser/ui/views/tabs/new_tab_button.cc
+++ b/chrome/browser/ui/views/tabs/new_tab_button.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/tabs/new_tab_button.h"
 
+#include <memory>
 #include <string>
 
 #include "base/strings/string_number_conversions.h"
@@ -101,8 +102,9 @@
   DCHECK(!new_tab_promo_);
   // Owned by its native widget. Will be destroyed as its widget is destroyed.
   new_tab_promo_ = FeaturePromoBubbleView::CreateOwned(
-      this, views::BubbleBorder::LEFT_CENTER, GetNewTabPromoStringSpecifier(),
-      FeaturePromoBubbleView::ActivationAction::DO_NOT_ACTIVATE);
+      this, views::BubbleBorder::LEFT_CENTER,
+      FeaturePromoBubbleView::ActivationAction::DO_NOT_ACTIVATE,
+      GetNewTabPromoStringSpecifier());
   new_tab_promo_observer_.Add(new_tab_promo_->GetWidget());
   SchedulePaint();
 }
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 1935048..f1b44cb 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -970,6 +970,7 @@
       AddMessageLoopObserver();
   }
 
+  UpdateHoverCard(nullptr, false);
   controller_->CloseTab(model_index, source);
 }
 
@@ -2250,7 +2251,7 @@
 
   const SkColor inactive_fg = GetTabForegroundColor(TAB_INACTIVE, inactive_bg);
   // The contrast ratio for the separator between inactive tabs.
-  constexpr float kTabSeparatorContrast = 3.0f;
+  constexpr float kTabSeparatorContrast = 2.5f;
   const SkAlpha separator_alpha = get_alpha(inactive_fg, kTabSeparatorContrast);
   separator_color_ =
       color_utils::AlphaBlend(inactive_fg, inactive_bg, separator_alpha);
diff --git a/chrome/browser/ui/views/task_manager_view.cc b/chrome/browser/ui/views/task_manager_view.cc
index 9096110d..d9a9d82 100644
--- a/chrome/browser/ui/views/task_manager_view.cc
+++ b/chrome/browser/ui/views/task_manager_view.cc
@@ -39,6 +39,7 @@
 #include "chrome/grit/theme_resources.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/gfx/image/image_skia.h"
 #endif  // defined(OS_CHROMEOS)
 
@@ -102,10 +103,16 @@
   const ash::ShelfID shelf_id(kTaskManagerId);
   window->SetProperty(ash::kShelfIDKey, new std::string(shelf_id.Serialize()));
   window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_DIALOG);
-  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
-  gfx::ImageSkia* icon = rb.GetImageSkiaNamed(IDR_ASH_SHELF_ICON_TASK_MANAGER);
-  // The new gfx::ImageSkia instance is owned by the window itself.
-  window->SetProperty(aura::client::kWindowIconKey, new gfx::ImageSkia(*icon));
+  // For classic Ash, GetWindowIcon() is sufficient. For Mash, the task manager
+  // specifically needs to set a large app icon for the benefit of
+  // ShelfWindowWatcher.
+  if (features::IsUsingWindowService()) {
+    window->GetRootWindow()->SetProperty(
+        aura::client::kAppIconLargeKey,
+        new gfx::ImageSkia(
+            *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+                IDR_ASH_SHELF_ICON_TASK_MANAGER)));
+  }
 #endif
   return g_task_manager_view->table_model_.get();
 }
@@ -183,6 +190,15 @@
   return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_TITLE);
 }
 
+gfx::ImageSkia TaskManagerView::GetWindowIcon() {
+#if defined(OS_CHROMEOS)
+  return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+      IDR_ASH_SHELF_ICON_TASK_MANAGER);
+#else
+  return views::DialogDelegateView::GetWindowIcon();
+#endif
+}
+
 std::string TaskManagerView::GetWindowName() const {
   return prefs::kTaskManagerWindowPlacement;
 }
diff --git a/chrome/browser/ui/views/task_manager_view.h b/chrome/browser/ui/views/task_manager_view.h
index bdd55e3..2213794 100644
--- a/chrome/browser/ui/views/task_manager_view.h
+++ b/chrome/browser/ui/views/task_manager_view.h
@@ -60,6 +60,7 @@
   bool CanMinimize() const override;
   bool ExecuteWindowsCommand(int command_id) override;
   base::string16 GetWindowTitle() const override;
+  gfx::ImageSkia GetWindowIcon() override;
   std::string GetWindowName() const override;
   bool Accept() override;
   bool Close() override;
diff --git a/chrome/browser/ui/views/test/view_event_test_base.cc b/chrome/browser/ui/views/test/view_event_test_base.cc
index 24d2d07..0cc625e 100644
--- a/chrome/browser/ui/views/test/view_event_test_base.cc
+++ b/chrome/browser/ui/views/test/view_event_test_base.cc
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/macros.h"
-#include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/ui/views/test/view_event_test_platform_part.h"
 #include "chrome/test/base/chrome_unit_test_suite.h"
@@ -16,7 +15,6 @@
 #include "mojo/core/embedder/embedder.h"
 #include "ui/base/clipboard/clipboard.h"
 #include "ui/base/ime/input_method_initializer.h"
-#include "ui/base/test/ui_controls.h"
 #include "ui/compositor/test/context_factories_for_test.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
@@ -169,22 +167,17 @@
   run_loop_.Run();
 }
 
-void ViewEventTestBase::ScheduleMouseMoveInBackground(int x, int y) {
+scoped_refptr<base::SingleThreadTaskRunner>
+ViewEventTestBase::GetDragTaskRunner() {
   if (!drag_event_thread_) {
     drag_event_thread_ = std::make_unique<base::Thread>("drag-event-thread");
     drag_event_thread_->Start();
   }
-  drag_event_thread_->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(base::IgnoreResult(&ui_controls::SendMouseMove), x, y));
-}
-
-void ViewEventTestBase::StopBackgroundThread() {
-  drag_event_thread_.reset();
+  return drag_event_thread_->task_runner();
 }
 
 void ViewEventTestBase::RunTestMethod(base::OnceClosure task) {
-  StopBackgroundThread();
+  drag_event_thread_.reset();
 
   std::move(task).Run();
   if (HasFatalFailure())
diff --git a/chrome/browser/ui/views/test/view_event_test_base.h b/chrome/browser/ui/views/test/view_event_test_base.h
index 4831f43..d18bed61 100644
--- a/chrome/browser/ui/views/test/view_event_test_base.h
+++ b/chrome/browser/ui/views/test/view_event_test_base.h
@@ -62,16 +62,9 @@
 //
 // Testing drag and drop is tricky because the mouse move that initiates drag
 // and drop may trigger a nested native event loop that waits for more mouse
-// messages.  Thus code needs an initial mouse move to start the drag, then a
-// mouse move enqueued from a background thread to finish the drag (since tasks
-// on the main thread may be stalled waiting for the nested loop to terminate).
-// You can do this with a pattern like:
-//   // Schedule the mouse move at a location slightly different from where
-//   // you really want to move to to start the drag.
-//   ui_controls::SendMouseMoveNotifyWhenDone(loc.x + 10, loc.y,
-//       base::BindOnce(&YYY, this));
-//   // Then schedule another mouse move to finish it.
-//   ScheduleMouseMoveInBackground(loc.x, loc.y);
+// messages.  Once a drag begins, all UI events until the drag ends must be
+// driven from observer callbacks and posted on the task runner returned by
+// GetDragTaskRunner().
 
 class ViewEventTestBase : public views::WidgetDelegate, public testing::Test {
  public:
@@ -123,15 +116,12 @@
                           base::BindOnce(method, base::Unretained(target)));
   }
 
-  // Spawns a new thread posts a MouseMove in the background.
-  void ScheduleMouseMoveInBackground(int x, int y);
+  // Returns a task runner to use for drag-related mouse events.
+  scoped_refptr<base::SingleThreadTaskRunner> GetDragTaskRunner();
 
   views::Widget* window_;
 
  private:
-  // Stops the thread started by ScheduleMouseMoveInBackground.
-  void StopBackgroundThread();
-
   // Callback from CreateEventTask. Stops the background thread, runs the
   // supplied task and if there are failures invokes Done.
   void RunTestMethod(base::OnceClosure task);
diff --git a/chrome/browser/ui/views/test/view_event_test_platform_part_chromeos.cc b/chrome/browser/ui/views/test/view_event_test_platform_part_chromeos.cc
index 1003a8d..ba0cd3cb 100644
--- a/chrome/browser/ui/views/test/view_event_test_platform_part_chromeos.cc
+++ b/chrome/browser/ui/views/test/view_event_test_platform_part_chromeos.cc
@@ -92,6 +92,7 @@
   chromeos::CrasAudioHandler::Shutdown();
   bluez::BluezDBusManager::Shutdown();
   chromeos::PowerPolicyController::Shutdown();
+  chromeos::PowerManagerClient::Shutdown();
   chromeos::DBusThreadManager::Shutdown();
 }
 
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
index 3534198..6d0b592 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
@@ -79,10 +79,12 @@
  public:
   PulsingInkDropMask(views::View* layer_container,
                      const gfx::Size& layer_size,
+                     const gfx::Insets& margins,
                      float normal_corner_radius,
                      float max_inset)
       : views::InkDropMask(layer_size),
         layer_container_(layer_container),
+        margins_(margins),
         normal_corner_radius_(normal_corner_radius),
         max_inset_(max_inset),
         throb_animation_(this) {
@@ -101,6 +103,7 @@
     ui::PaintRecorder recorder(context, layer()->size());
 
     gfx::RectF bounds(layer()->bounds());
+    bounds.Inset(margins_);
 
     const float current_inset =
         throb_animation_.CurrentValueBetween(0.0f, max_inset_);
@@ -126,6 +129,11 @@
   // our instance.
   views::View* const layer_container_;
 
+  // Margins between the layer bounds and the visible ink drop. We use this
+  // because sometimes the View we're masking is larger than the ink drop we
+  // want to show.
+  const gfx::Insets margins_;
+
   // Normal corner radius of the ink drop without animation. This is also the
   // corner radius at the largest instant of the animation.
   const float normal_corner_radius_;
@@ -394,10 +402,16 @@
     const {
 #if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
   if (promo_feature_) {
-    const float corner_radius = height() / 2.0f;
+    // This gets the latest ink drop insets. |SetTrailingMargin()| is called
+    // whenever our margins change (i.e. due to the window maximizing or
+    // minimizing) and updates our internal padding property accordingly.
+    const gfx::Insets ink_drop_insets =
+        GetToolbarInkDropInsets(this, *GetProperty(views::kInternalPaddingKey));
+    const float corner_radius =
+        (height() - ink_drop_insets.top() - ink_drop_insets.bottom()) / 2.0f;
     return std::make_unique<PulsingInkDropMask>(
-        ink_drop_container(), ink_drop_container()->size(), corner_radius,
-        kFeaturePromoPulseInsetDip);
+        ink_drop_container(), ink_drop_container()->size(), ink_drop_insets,
+        corner_radius, kFeaturePromoPulseInsetDip);
   }
 #endif
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
index 7f09b01..a3b8179 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
@@ -97,7 +97,7 @@
 
 void ToolbarViewInteractiveUITest::OnWidgetDragWillStart() {
   // Enqueue an event to move the mouse to the app menu button, which should
-  // result in calling OnMenuOpened().
+  // result in calling AppMenuShown().
   const gfx::Point target =
       ui_test_utils::GetCenterInScreenCoordinates(GetAppMenuButton());
   task_runner_->PostTask(
@@ -111,10 +111,12 @@
 }
 
 void ToolbarViewInteractiveUITest::StartDrag() {
-  // Move the mouse outside the app button.
-  gfx::Point target =
-      ui_test_utils::GetCenterInScreenCoordinates(GetAppMenuButton());
-  EXPECT_TRUE(ui_controls::SendMouseMove(target.x() + 10, target.y()));
+  // Move the mouse outside the toolbar action.
+  const views::View* toolbar_action =
+      GetBrowserActions()->GetToolbarActionViewAt(0);
+  gfx::Point target(toolbar_action->width() + 1, toolbar_action->height() / 2);
+  views::View::ConvertPointToScreen(toolbar_action, &target);
+  EXPECT_TRUE(ui_controls::SendMouseMove(target.x(), target.y()));
 
   OnWidgetDragWillStart();
 }
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 14811cf7..18a0cd2 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -226,7 +226,7 @@
 #include "extensions/common/manifest.h"
 #endif
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #include "chrome/browser/ui/webui/reset_password/reset_password_ui.h"
 #include "components/safe_browsing/features.h"
@@ -687,7 +687,7 @@
     return &NewWebUI<MediaEngagementUI>;
   }
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   bool enable_reset_password =
       base::FeatureList::IsEnabled(
           safe_browsing::kForceEnableResetPasswordWebUI) ||
diff --git a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
index 85c395c..9e43838 100644
--- a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
@@ -217,7 +217,6 @@
   AddCallback("toggleResetScreen", &CoreOobeHandler::HandleToggleResetScreen);
   AddCallback("toggleEnableDebuggingScreen",
               &CoreOobeHandler::HandleEnableDebuggingScreen);
-  AddCallback("headerBarVisible", &CoreOobeHandler::HandleHeaderBarVisible);
   AddCallback("raiseTabKeyEvent", &CoreOobeHandler::HandleRaiseTabKeyEvent);
   // Note: Used by enterprise_RemoraRequisitionDisplayUsage.py:
   // TODO(felixe): Use chrome.system.display or cros_display_config.mojom,
@@ -303,10 +302,6 @@
   CallJS("cr.ui.Oobe.reloadEulaContent", dictionary);
 }
 
-void CoreOobeHandler::ShowControlBar(bool show) {
-  CallJS("cr.ui.Oobe.showControlBar", show);
-}
-
 void CoreOobeHandler::SetVirtualKeyboardShown(bool shown) {
   CallJS("cr.ui.Oobe.setVirtualKeyboardShown", shown);
 }
@@ -479,11 +474,6 @@
     UpdateOobeUIVisibility();
 }
 
-void CoreOobeHandler::UpdateShutdownAndRebootVisibility(
-    bool reboot_on_shutdown) {
-  CallJS("cr.ui.Oobe.showShutdown", !reboot_on_shutdown);
-}
-
 void CoreOobeHandler::SetLoginUserCount(int user_count) {
   CallJS("cr.ui.Oobe.setLoginUserCount", user_count);
 }
@@ -584,7 +574,6 @@
   if (!features::IsUsingWindowService()) {
     const bool is_keyboard_shown =
         ChromeKeyboardControllerClient::Get()->is_keyboard_visible();
-    ShowControlBar(!is_keyboard_shown);
     SetVirtualKeyboardShown(is_keyboard_shown);
   }
 }
@@ -623,12 +612,6 @@
       static_cast<HelpAppLauncher::HelpTopic>(help_topic_id));
 }
 
-void CoreOobeHandler::HandleHeaderBarVisible() {
-  LoginDisplayHost* login_display_host = LoginDisplayHost::default_host();
-  if (login_display_host)
-    login_display_host->SetStatusAreaVisible(true);
-}
-
 void CoreOobeHandler::HandleRaiseTabKeyEvent(bool reverse) {
   ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_TAB, ui::EF_NONE);
   if (reverse)
diff --git a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h
index 324e0f93..67fe9508 100644
--- a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h
@@ -104,7 +104,6 @@
   void ClearErrors() override;
   void ReloadContent(const base::DictionaryValue& dictionary) override;
   void ReloadEulaContent(const base::DictionaryValue& dictionary) override;
-  void ShowControlBar(bool show) override;
   void SetVirtualKeyboardShown(bool displayed) override;
   void SetClientAreaSize(int width, int height) override;
   void ShowDeviceResetScreen() override;
@@ -140,7 +139,6 @@
   void HandleLaunchHelpApp(double help_topic_id);
   void HandleToggleResetScreen();
   void HandleEnableDebuggingScreen();
-  void HandleHeaderBarVisible();
   void HandleSetOobeBootstrappingSlave();
   void HandleGetPrimaryDisplayNameForTesting(const base::ListValue* args);
   void GetPrimaryDisplayNameCallback(
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 7c5fd013..55f15d3 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -185,14 +185,6 @@
   // nosignup flow if new users are not allowed.
   if (!allow_new_user || is_restrictive_proxy)
     params->SetString("flow", "nosignup");
-
-  params->SetBoolean("supervisedUsersCanCreate", false);
-
-  // Now check whether we're in multi-profiles user adding scenario and
-  // disable GAIA right panel features if that's the case.
-  if (UserAddingScreen::Get()->IsRunning()) {
-    params->SetBoolean("supervisedUsersCanCreate", false);
-  }
 }
 
 void RecordSAMLScrapingVerificationResultInHistogram(bool success) {
@@ -1203,7 +1195,7 @@
 
   LoadAuthExtension(!gaia_silent_load_ /* force */, false /* offline */);
   signin_screen_handler_->UpdateUIState(
-      SigninScreenHandler::UI_STATE_GAIA_SIGNIN, nullptr);
+      SigninScreenHandler::UI_STATE_GAIA_SIGNIN);
   core_oobe_view_->UpdateKeyboardState();
 
   if (gaia_silent_load_) {
diff --git a/chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.cc b/chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.cc
deleted file mode 100644
index f0dee83..0000000
--- a/chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.cc
+++ /dev/null
@@ -1,216 +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 "chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.h"
-
-#include <stddef.h>
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/command_line.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/system/sys_info.h"
-#include "base/values.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
-#include "chrome/browser/chromeos/app_mode/kiosk_app_launch_error.h"
-#include "chrome/browser/chromeos/login/existing_user_controller.h"
-#include "chrome/browser/chromeos/login/screens/network_error.h"
-#include "chrome/grit/generated_resources.h"
-#include "chromeos/constants/chromeos_switches.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/web_ui.h"
-#include "extensions/grit/extensions_browser_resources.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/base/webui/web_ui_util.h"
-
-namespace chromeos {
-
-namespace {
-
-// JS functions that define new and old kiosk UI API.
-const char kKioskSetAppsNewAPI[] = "login.AccountPickerScreen.setApps";
-const char kKioskSetAppsOldAPI[] = "login.AppsMenuButton.setApps";
-const char kKioskShowErrorNewAPI[] = "login.AccountPickerScreen.showAppError";
-const char kKioskShowErrorOldAPI[] = "login.AppsMenuButton.showError";
-
-}  // namespace
-
-KioskAppMenuHandler::KioskAppMenuHandler(
-    const scoped_refptr<NetworkStateInformer>& network_state_informer)
-    : is_webui_initialized_(false),
-      network_state_informer_(network_state_informer),
-      weak_ptr_factory_(this) {
-  KioskAppManager::Get()->AddObserver(this);
-  network_state_informer_->AddObserver(this);
-  ArcKioskAppManager::Get()->AddObserver(this);
-}
-
-KioskAppMenuHandler::~KioskAppMenuHandler() {
-  KioskAppManager::Get()->RemoveObserver(this);
-  network_state_informer_->RemoveObserver(this);
-  ArcKioskAppManager::Get()->RemoveObserver(this);
-}
-
-void KioskAppMenuHandler::GetLocalizedStrings(
-    base::DictionaryValue* localized_strings) {
-  localized_strings->SetString(
-      "showApps",
-      l10n_util::GetStringUTF16(IDS_KIOSK_APPS_BUTTON));
-  localized_strings->SetString(
-      "confirmKioskAppDiagnosticModeFormat",
-      l10n_util::GetStringUTF16(IDS_LOGIN_CONFIRM_KIOSK_DIAGNOSTIC_FORMAT));
-  localized_strings->SetString(
-      "confirmKioskAppDiagnosticModeYes",
-      l10n_util::GetStringUTF16(IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL));
-  localized_strings->SetString(
-      "confirmKioskAppDiagnosticModeNo",
-      l10n_util::GetStringUTF16(IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL));
-  localized_strings->SetBoolean(
-      "kioskAppHasLaunchError",
-      KioskAppLaunchError::Get() != KioskAppLaunchError::NONE);
-}
-
-void KioskAppMenuHandler::RegisterMessages() {
-  web_ui()->RegisterMessageCallback(
-      "initializeKioskApps",
-      base::BindRepeating(&KioskAppMenuHandler::HandleInitializeKioskApps,
-                          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "kioskAppsLoaded",
-      base::BindRepeating(&KioskAppMenuHandler::HandleKioskAppsLoaded,
-                          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "checkKioskAppLaunchError",
-      base::BindRepeating(&KioskAppMenuHandler::HandleCheckKioskAppLaunchError,
-                          base::Unretained(this)));
-}
-
-// static
-bool KioskAppMenuHandler::EnableNewKioskUI() {
-  // Turn off new kiosk UI for M34/35.
-  // TODO(xiyuan, nkostylev): Revist for http://crbug.com/362062.
-  return false;
-}
-
-void KioskAppMenuHandler::SendKioskApps() {
-  if (!is_webui_initialized_)
-    return;
-
-  KioskAppManager::Apps apps;
-  KioskAppManager::Get()->GetApps(&apps);
-
-  base::ListValue apps_list;
-  for (size_t i = 0; i < apps.size(); ++i) {
-    const KioskAppManager::App& app_data = apps[i];
-
-    std::unique_ptr<base::DictionaryValue> app_info(
-        new base::DictionaryValue());
-    app_info->SetBoolean("isApp", true);
-    app_info->SetString("id", app_data.app_id);
-    app_info->SetBoolean("isAndroidApp", false);
-    // Unused for native apps. Added for consistency with Android apps.
-    app_info->SetString("account_email", app_data.account_id.GetUserEmail());
-    app_info->SetString("label", app_data.name);
-
-    std::string icon_url;
-    if (app_data.icon.isNull()) {
-      icon_url =
-          webui::GetBitmapDataUrl(*ui::ResourceBundle::GetSharedInstance()
-                                       .GetImageNamed(IDR_APP_DEFAULT_ICON)
-                                       .ToSkBitmap());
-    } else {
-      icon_url = webui::GetBitmapDataUrl(*app_data.icon.bitmap());
-    }
-    app_info->SetString("iconUrl", icon_url);
-
-    apps_list.Append(std::move(app_info));
-  }
-
-  ArcKioskAppManager::Apps arc_apps;
-  ArcKioskAppManager::Get()->GetAllApps(&arc_apps);
-  for (size_t i = 0; i < arc_apps.size(); ++i) {
-    std::unique_ptr<base::DictionaryValue> app_info(
-        new base::DictionaryValue());
-    app_info->SetBoolean("isApp", true);
-    app_info->SetBoolean("isAndroidApp", true);
-    app_info->SetString("id", arc_apps[i]->app_id());
-    app_info->SetString("account_email",
-                        arc_apps[i]->account_id().GetUserEmail());
-    app_info->SetString("label", arc_apps[i]->name());
-
-    std::string icon_url;
-    if (arc_apps[i]->icon().isNull()) {
-      icon_url =
-          webui::GetBitmapDataUrl(*ui::ResourceBundle::GetSharedInstance()
-                                       .GetImageNamed(IDR_APP_DEFAULT_ICON)
-                                       .ToSkBitmap());
-    } else {
-      icon_url = webui::GetBitmapDataUrl(*arc_apps[i]->icon().bitmap());
-    }
-    app_info->SetString("iconUrl", icon_url);
-
-    apps_list.Append(std::move(app_info));
-  }
-
-  web_ui()->CallJavascriptFunctionUnsafe(
-      EnableNewKioskUI() ? kKioskSetAppsNewAPI : kKioskSetAppsOldAPI,
-      apps_list);
-}
-
-void KioskAppMenuHandler::HandleInitializeKioskApps(
-    const base::ListValue* args) {
-  is_webui_initialized_ = true;
-  SendKioskApps();
-  UpdateState(NetworkError::ERROR_REASON_UPDATE);
-}
-
-void KioskAppMenuHandler::HandleKioskAppsLoaded(
-    const base::ListValue* args) {
-  content::NotificationService::current()->Notify(
-      chrome::NOTIFICATION_KIOSK_APPS_LOADED,
-      content::NotificationService::AllSources(),
-      content::NotificationService::NoDetails());
-}
-
-void KioskAppMenuHandler::HandleCheckKioskAppLaunchError(
-    const base::ListValue* args) {
-  KioskAppLaunchError::Error error = KioskAppLaunchError::Get();
-  if (error == KioskAppLaunchError::NONE)
-    return;
-  KioskAppLaunchError::RecordMetricAndClear();
-
-  const std::string error_message = KioskAppLaunchError::GetErrorMessage(error);
-  bool new_kiosk_ui = EnableNewKioskUI();
-  web_ui()->CallJavascriptFunctionUnsafe(
-      new_kiosk_ui ? kKioskShowErrorNewAPI : kKioskShowErrorOldAPI,
-      base::Value(error_message));
-}
-
-void KioskAppMenuHandler::OnKioskAppsSettingsChanged() {
-  SendKioskApps();
-}
-
-void KioskAppMenuHandler::OnKioskAppDataChanged(const std::string& app_id) {
-  SendKioskApps();
-}
-
-void KioskAppMenuHandler::OnKioskAppDataLoadFailure(const std::string& app_id) {
-  SendKioskApps();
-}
-
-void KioskAppMenuHandler::UpdateState(NetworkError::ErrorReason reason) {
-  if (network_state_informer_->state() == NetworkStateInformer::ONLINE)
-    KioskAppManager::Get()->RetryFailedAppDataFetch();
-}
-
-void KioskAppMenuHandler::OnArcKioskAppsChanged() {
-  SendKioskApps();
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.h b/chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.h
deleted file mode 100644
index ce1e547..0000000
--- a/chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.h
+++ /dev/null
@@ -1,76 +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 CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_KIOSK_APP_MENU_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_KIOSK_APP_MENU_HANDLER_H_
-
-#include <memory>
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
-#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
-#include "chrome/browser/chromeos/app_mode/kiosk_app_manager_observer.h"
-#include "chrome/browser/chromeos/login/screens/network_error.h"
-#include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
-#include "content/public/browser/web_ui_message_handler.h"
-
-namespace chromeos {
-
-// KioskAppMenuHandler supplies kiosk apps data to apps menu on sign-in
-// screen when app mode is enabled and handles "launchKioskApp" request
-// from the apps menu.
-class KioskAppMenuHandler
-    : public content::WebUIMessageHandler,
-      public KioskAppManagerObserver,
-      public NetworkStateInformer::NetworkStateInformerObserver,
-      public ArcKioskAppManager::ArcKioskAppManagerObserver {
- public:
-  explicit KioskAppMenuHandler(
-      const scoped_refptr<NetworkStateInformer>& network_state_informer);
-  ~KioskAppMenuHandler() override;
-
-  void GetLocalizedStrings(base::DictionaryValue* localized_strings);
-
-  // content::WebUIMessageHandler overrides:
-  void RegisterMessages() override;
-
-  // Returns true if new kiosk UI is enabled.
-  static bool EnableNewKioskUI();
-
- private:
-  // Sends all kiosk apps to webui.
-  void SendKioskApps();
-
-  // JS callbacks.
-  void HandleInitializeKioskApps(const base::ListValue* args);
-  void HandleKioskAppsLoaded(const base::ListValue* args);
-  void HandleCheckKioskAppLaunchError(const base::ListValue* args);
-
-  // KioskAppManagerObserver overrides:
-  void OnKioskAppsSettingsChanged() override;
-  void OnKioskAppDataChanged(const std::string& app_id) override;
-  void OnKioskAppDataLoadFailure(const std::string& app_id) override;
-
-  // NetworkStateInformer::NetworkStateInformerObserver overrides:
-  void UpdateState(NetworkError::ErrorReason reason) override;
-
-  // ArcKioskAppManager::ArcKioskAppManagerObserver overrides:
-  void OnArcKioskAppsChanged() override;
-
-  // True when WebUI is initialized. Otherwise don't allow calling JS functions.
-  bool is_webui_initialized_;
-
-  scoped_refptr<NetworkStateInformer> network_state_informer_;
-
-  base::WeakPtrFactory<KioskAppMenuHandler> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(KioskAppMenuHandler);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_KIOSK_APP_MENU_HANDLER_H_
diff --git a/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc b/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc
index b761ce9..46a99135 100644
--- a/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc
+++ b/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc
@@ -75,6 +75,7 @@
 }
 
 L10nUtilTest::~L10nUtilTest() {
+  chromeos::system::StatisticsProvider::GetInstance()->Shutdown();
   chromeos::input_method::Shutdown();
 }
 
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
index 2cbdfee..d703c19 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
@@ -59,7 +59,6 @@
 #include "chrome/browser/ui/webui/chromeos/login/fingerprint_setup_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/hid_detection_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/kiosk_app_menu_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/kiosk_autolaunch_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/kiosk_enable_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h"
@@ -457,12 +456,6 @@
   AddScreenHandler(std::make_unique<MultiDeviceSetupScreenHandler>(
       js_calls_container_.get()));
 
-  // Initialize KioskAppMenuHandler. Note that it is NOT a screen handler.
-  auto kiosk_app_menu_handler =
-      std::make_unique<KioskAppMenuHandler>(network_state_informer_);
-  kiosk_app_menu_handler_ = kiosk_app_menu_handler.get();
-  web_ui()->AddMessageHandler(std::move(kiosk_app_menu_handler));
-
   Profile* profile = Profile::FromWebUI(web_ui());
   // Set up the chrome://theme/ source, for Chrome logo.
   content::URLDataSource::Add(profile, std::make_unique<ThemeSource>(profile));
@@ -681,10 +674,6 @@
   return GetView<NetworkScreenHandler>();
 }
 
-void OobeUI::OnShutdownPolicyChanged(bool reboot_on_shutdown) {
-  core_handler_->UpdateShutdownAndRebootVisibility(reboot_on_shutdown);
-}
-
 AppLaunchSplashScreenView* OobeUI::GetAppLaunchSplashScreenView() {
   return GetView<AppLaunchSplashScreenHandler>();
 }
@@ -702,7 +691,6 @@
     handler->GetLocalizedStrings(localized_strings);
   const std::string& app_locale = g_browser_process->GetApplicationLocale();
   webui::SetLoadTimeDataDefaults(app_locale, localized_strings);
-  kiosk_app_menu_handler_->GetLocalizedStrings(localized_strings);
 
 #if defined(GOOGLE_CHROME_BUILD)
   localized_strings->SetString("buildType", "chrome");
@@ -715,8 +703,6 @@
   localized_strings->SetString("highlightStrength",
                                keyboard_driven_oobe ? "strong" : "normal");
 
-  bool new_kiosk_ui = KioskAppMenuHandler::EnableNewKioskUI();
-  localized_strings->SetString("newKioskUI", new_kiosk_ui ? "on" : "off");
   localized_strings->SetString(
       "showViewsLock", ash::switches::IsUsingViewsLock() ? "on" : "off");
   localized_strings->SetString(
@@ -758,13 +744,6 @@
       ScreenInitialized(handler->oobe_screen());
     }
   }
-
-  // Instantiate the ShutdownPolicyHandler.
-  shutdown_policy_handler_.reset(
-      new ShutdownPolicyHandler(CrosSettings::Get(), this));
-
-  // Trigger an initial update.
-  shutdown_policy_handler_->NotifyDelegateWithShutdownPolicy();
 }
 
 void OobeUI::CurrentScreenChanged(OobeScreen new_screen) {
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
index 9c111fd..ba071a8 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
@@ -15,7 +15,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
 #include "chrome/browser/chromeos/login/oobe_screen.h"
-#include "chrome/browser/chromeos/settings/shutdown_policy_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
@@ -50,7 +49,6 @@
 class FingerprintSetupScreenView;
 class GaiaView;
 class HIDDetectionView;
-class KioskAppMenuHandler;
 class KioskAutolaunchScreenView;
 class KioskEnableScreenView;
 class LoginScreenContext;
@@ -78,8 +76,7 @@
 // - welcome screen (setup language/keyboard/network).
 // - eula screen (CrOS (+ OEM) EULA content/TPM password/crash reporting).
 // - update screen.
-class OobeUI : public ui::MojoWebUIController,
-               public ShutdownPolicyHandler::Delegate {
+class OobeUI : public ui::MojoWebUIController {
  public:
   // List of known types of OobeUI. Type added as path in chrome://oobe url, for
   // example chrome://oobe/user-adding.
@@ -144,9 +141,6 @@
   NetworkScreenView* GetNetworkScreenView();
   MarketingOptInScreenView* GetMarketingOptInScreenView();
 
-  // ShutdownPolicyHandler::Delegate
-  void OnShutdownPolicyChanged(bool reboot_on_shutdown) override;
-
   // Collects localized strings from the owned handlers.
   void GetLocalizedStrings(base::DictionaryValue* localized_strings);
 
@@ -252,9 +246,6 @@
   std::vector<BaseWebUIHandler*> webui_only_handlers_;  // Non-owning pointers.
   std::vector<BaseScreenHandler*> screen_handlers_;     // Non-owning pointers.
 
-  KioskAppMenuHandler* kiosk_app_menu_handler_ =
-      nullptr;  // Non-owning pointers.
-
   std::unique_ptr<ErrorScreen> error_screen_;
 
   // Id of the current oobe/login screen.
@@ -273,9 +264,6 @@
   // List of registered observers.
   base::ObserverList<Observer>::Unchecked observer_list_;
 
-  // Observer of CrosSettings watching the kRebootOnShutdown policy.
-  std::unique_ptr<ShutdownPolicyHandler> shutdown_policy_handler_;
-
   std::unique_ptr<OobeDisplayChooser> oobe_display_chooser_;
 
   // Store the deferred JS calls before the screen handler instance is
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 1f0c887c..713eb50 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -12,8 +12,6 @@
 
 #include "ash/public/cpp/login_constants.h"
 #include "ash/public/cpp/wallpaper_types.h"
-#include "ash/public/interfaces/constants.mojom.h"
-#include "ash/public/interfaces/shutdown.mojom.h"
 #include "ash/public/interfaces/tray_action.mojom.h"
 #include "base/bind.h"
 #include "base/i18n/number_formatting.h"
@@ -102,9 +100,7 @@
 #include "components/version_info/version_info.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/service_manager_connection.h"
 #include "google_apis/gaia/gaia_auth_util.h"
-#include "services/service_manager/public/cpp/connector.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 #include "ui/base/ime/chromeos/ime_keyboard.h"
 #include "ui/base/ime/chromeos/input_method_descriptor.h"
@@ -326,14 +322,6 @@
   builder->Add("submitButtonAccessibleName",
                IDS_LOGIN_POD_SUBMIT_BUTTON_ACCESSIBLE_NAME);
   builder->Add("signedIn", IDS_SCREEN_LOCK_ACTIVE_USER);
-  builder->Add("launchAppButton", IDS_LAUNCH_APP_BUTTON);
-  builder->Add("restart", IDS_ASH_SHELF_RESTART_BUTTON);
-  builder->Add("shutDown", IDS_ASH_SHELF_SHUTDOWN_BUTTON);
-  builder->Add("addUser", IDS_ASH_ADD_USER_BUTTON);
-  builder->Add("browseAsGuest", IDS_ASH_BROWSE_AS_GUEST_BUTTON);
-  builder->Add("moreOptions", IDS_MORE_OPTIONS_BUTTON);
-  builder->Add("cancel", IDS_ASH_SHELF_CANCEL_BUTTON);
-  builder->Add("signOutUser", IDS_ASH_SHELF_SIGN_OUT_BUTTON);
   builder->Add("offlineLogin", IDS_OFFLINE_LOGIN_HTML);
   builder->Add("ownerUserPattern", IDS_LOGIN_POD_OWNER_USER);
   builder->Add("removeUser", IDS_LOGIN_POD_REMOVE_USER);
@@ -460,7 +448,6 @@
               &SigninScreenHandler::HandleLaunchPublicSession);
   AddRawCallback("offlineLogin", &SigninScreenHandler::HandleOfflineLogin);
   AddCallback("rebootSystem", &SigninScreenHandler::HandleRebootSystem);
-  AddCallback("shutdownSystem", &SigninScreenHandler::HandleShutdownSystem);
   AddCallback("removeUser", &SigninScreenHandler::HandleRemoveUser);
   AddCallback("toggleEnrollmentScreen",
               &SigninScreenHandler::HandleToggleEnrollmentScreen);
@@ -470,7 +457,6 @@
               &SigninScreenHandler::HandleToggleKioskEnableScreen);
   AddCallback("accountPickerReady",
               &SigninScreenHandler::HandleAccountPickerReady);
-  AddCallback("signOutUser", &SigninScreenHandler::HandleSignOutUser);
   AddCallback("openInternetDetailDialog",
               &SigninScreenHandler::HandleOpenInternetDetailDialog);
   AddCallback("loginVisible", &SigninScreenHandler::HandleLoginVisible);
@@ -499,12 +485,6 @@
   AddCallback("sendFeedback", &SigninScreenHandler::HandleSendFeedback);
   AddCallback("sendFeedbackAndResyncUserData",
               &SigninScreenHandler::HandleSendFeedbackAndResyncUserData);
-
-  // This message is sent by the kiosk app menu, but is handled here
-  // so we can tell the delegate to launch the app.
-  AddCallback("launchKioskApp", &SigninScreenHandler::HandleLaunchKioskApp);
-  AddCallback("launchArcKioskApp",
-              &SigninScreenHandler::HandleLaunchArcKioskApp);
 }
 
 void SigninScreenHandler::Show(const LoginScreenContext& context,
@@ -592,23 +572,20 @@
         ->GetImeKeyboard()
         ->SetCapsLockEnabled(false);
 
-    base::DictionaryValue params;
-    params.SetBoolean("disableAddUser", AllWhitelistedUsersPresent());
-    UpdateUIState(UI_STATE_ACCOUNT_PICKER, &params);
+    UpdateUIState(UI_STATE_ACCOUNT_PICKER);
   }
 }
 
-void SigninScreenHandler::UpdateUIState(UIState ui_state,
-                                        base::DictionaryValue* params) {
+void SigninScreenHandler::UpdateUIState(UIState ui_state) {
   switch (ui_state) {
     case UI_STATE_GAIA_SIGNIN:
       ui_state_ = UI_STATE_GAIA_SIGNIN;
-      ShowScreenWithData(OobeScreen::SCREEN_GAIA_SIGNIN, params);
+      ShowScreen(OobeScreen::SCREEN_GAIA_SIGNIN);
       break;
     case UI_STATE_ACCOUNT_PICKER:
       ui_state_ = UI_STATE_ACCOUNT_PICKER;
       gaia_screen_handler_->CancelShowGaiaAsync();
-      ShowScreenWithData(OobeScreen::SCREEN_ACCOUNT_PICKER, params);
+      ShowScreen(OobeScreen::SCREEN_ACCOUNT_PICKER);
       break;
     default:
       NOTREACHED();
@@ -967,8 +944,7 @@
     // We need to reload GAIA if UI_STATE_UNKNOWN or the allow new user setting
     // has changed so that reloaded GAIA shows/hides the option to create a new
     // account.
-    UpdateUIState(UI_STATE_ACCOUNT_PICKER, nullptr);
-    UpdateAddButtonStatus();
+    UpdateUIState(UI_STATE_ACCOUNT_PICKER);
   }
 }
 
@@ -1074,11 +1050,6 @@
          is_account_picker_showing_first_time_;
 }
 
-void SigninScreenHandler::UpdateAddButtonStatus() {
-  CallJS("cr.ui.login.DisplayManager.updateAddUserButtonStatus",
-         AllWhitelistedUsersPresent());
-}
-
 void SigninScreenHandler::HandleAuthenticateUser(const AccountId& account_id,
                                                  const std::string& password,
                                                  bool authenticated_by_pin) {
@@ -1178,16 +1149,7 @@
 
   gaia_screen_handler_->set_populated_email(email);
   gaia_screen_handler_->LoadAuthExtension(true /* force */, true /* offline */);
-  UpdateUIState(UI_STATE_GAIA_SIGNIN, nullptr);
-}
-
-void SigninScreenHandler::HandleShutdownSystem() {
-  ash::mojom::ShutdownControllerPtr shutdown_controller;
-  content::ServiceManagerConnection::GetForProcess()
-      ->GetConnector()
-      ->BindInterface(ash::mojom::kServiceName, &shutdown_controller);
-
-  shutdown_controller->RequestShutdownFromLoginScreen();
+  UpdateUIState(UI_STATE_GAIA_SIGNIN);
 }
 
 void SigninScreenHandler::HandleRebootSystem() {
@@ -1207,7 +1169,6 @@
   if (!delegate_)
     return;
   delegate_->RemoveUser(account_id);
-  UpdateAddButtonStatus();
 }
 
 void SigninScreenHandler::HandleToggleEnrollmentScreen() {
@@ -1239,8 +1200,7 @@
 
 void SigninScreenHandler::LoadUsers(const user_manager::UserList& users,
                                     const base::ListValue& users_list) {
-  CallJS("login.AccountPickerScreen.loadUsers", users_list,
-         delegate_->IsShowGuest());
+  CallJS("login.AccountPickerScreen.loadUsers", users_list);
 
   // Enable pin for any users who can use it.
   // TODO(jdufault): Cache pin state in BrowserProcess::local_state() so we
@@ -1299,11 +1259,6 @@
     delegate_->OnSigninScreenReady();
 }
 
-void SigninScreenHandler::HandleSignOutUser() {
-  if (delegate_)
-    delegate_->Signout();
-}
-
 void SigninScreenHandler::HandleOpenInternetDetailDialog() {
   // Empty string opens the internet detail dialog for the default network.
   InternetDetailDialog::ShowDialog("");
@@ -1439,22 +1394,6 @@
          account_id, locale, *keyboard_layouts);
 }
 
-void SigninScreenHandler::HandleLaunchKioskApp(const AccountId& app_account_id,
-                                               bool diagnostic_mode) {
-  UserContext context(user_manager::USER_TYPE_KIOSK_APP, app_account_id);
-  SigninSpecifics specifics;
-  specifics.kiosk_diagnostic_mode = diagnostic_mode;
-  if (delegate_)
-    delegate_->Login(context, specifics);
-}
-
-void SigninScreenHandler::HandleLaunchArcKioskApp(
-    const AccountId& app_account_id) {
-  UserContext context(user_manager::USER_TYPE_ARC_KIOSK_APP, app_account_id);
-  if (delegate_)
-    delegate_->Login(context, SigninSpecifics());
-}
-
 void SigninScreenHandler::HandleGetTabletModeState() {
   CallJS("login.AccountPickerScreen.setTabletModeState",
          TabletModeClient::Get()->tablet_mode_enabled());
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
index ef9db6ff..622c34da 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -47,7 +47,6 @@
 }  // namespace ash
 
 namespace base {
-class DictionaryValue;
 class ListValue;
 }
 
@@ -261,9 +260,8 @@
   void ShowImpl();
 
   // Updates current UI of the signin screen according to |ui_state|
-  // argument.  Optionally it can pass screen initialization data via
-  // |params| argument.
-  void UpdateUIState(UIState ui_state, base::DictionaryValue* params);
+  // argument.
+  void UpdateUIState(UIState ui_state);
 
   void UpdateStateInternal(NetworkError::ErrorReason reason, bool force_update);
   void SetupAndShowOfflineMessage(NetworkStateInformer::State state,
@@ -313,8 +311,6 @@
   // TabletModeClientObserver:
   void OnTabletModeToggled(bool enabled) override;
 
-  void UpdateAddButtonStatus();
-
   // Restore input focus to current user pod.
   void RefocusCurrentPod();
 
@@ -338,7 +334,6 @@
                                  const std::string& locale,
                                  const std::string& input_method);
   void HandleOfflineLogin(const base::ListValue* args);
-  void HandleShutdownSystem();
   void HandleRebootSystem();
   void HandleRemoveUser(const AccountId& account_id);
   void HandleToggleEnrollmentScreen();
@@ -348,7 +343,6 @@
   void HandleToggleResetScreen();
   void HandleToggleKioskAutolaunchScreen();
   void HandleAccountPickerReady();
-  void HandleSignOutUser();
   void HandleOpenInternetDetailDialog();
   void HandleLoginVisible(const std::string& source);
   void HandleCancelPasswordChangedFlow(const AccountId& account_id);
diff --git a/chrome/browser/ui/webui/feed_internals/feed_internals.mojom b/chrome/browser/ui/webui/feed_internals/feed_internals.mojom
index 35f6b28..aa46669 100644
--- a/chrome/browser/ui/webui/feed_internals/feed_internals.mojom
+++ b/chrome/browser/ui/webui/feed_internals/feed_internals.mojom
@@ -35,6 +35,24 @@
   Time? refresh_suppress_time;
 };
 
+// Models a single suggestion in the Feed.
+struct Suggestion {
+  // Title of the suggestion.
+  string title;
+
+  // URL of the suggested page.
+  string url;
+
+  // Name of the content's publisher.
+  string publisher_name;
+
+  // URL of the image associated with the suggestion.
+  string image_url;
+
+  // URL of the suggested page's favicon.
+  string favicon_url;
+};
+
 // Time wrapper to allow for nullable objects.
 struct Time {
   double ms_since_epoch;
@@ -58,4 +76,7 @@
   // Clear all data cached by the Feed library. Also triggers a refresh of the
   // Feed.
   ClearCachedDataAndRefreshFeed();
+
+  // Get the last known content with metadata.
+  GetCurrentContent() => (array<Suggestion> suggestions);
 };
diff --git a/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.cc b/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.cc
index f8075e5..d1601c3 100644
--- a/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.cc
@@ -10,10 +10,12 @@
 #include "base/time/time.h"
 #include "chrome/browser/android/feed/feed_lifecycle_bridge.h"
 #include "components/feed/content/feed_host_service.h"
+#include "components/feed/content/feed_offline_host.h"
 #include "components/feed/core/feed_scheduler_host.h"
 #include "components/feed/core/pref_names.h"
 #include "components/feed/core/user_classifier.h"
 #include "components/feed/feed_feature_list.h"
+#include "components/offline_pages/core/prefetch/suggestions_provider.h"
 #include "components/prefs/pref_service.h"
 
 namespace {
@@ -31,7 +33,9 @@
     PrefService* pref_service)
     : binding_(this, std::move(request)),
       feed_scheduler_host_(feed_host_service->GetSchedulerHost()),
-      pref_service_(pref_service) {}
+      feed_offline_host_(feed_host_service->GetOfflineHost()),
+      pref_service_(pref_service),
+      weak_ptr_factory_(this) {}
 
 FeedInternalsPageHandler::~FeedInternalsPageHandler() = default;
 
@@ -84,3 +88,29 @@
 void FeedInternalsPageHandler::ClearCachedDataAndRefreshFeed() {
   feed::FeedLifecycleBridge::ClearCachedData();
 }
+
+void FeedInternalsPageHandler::GetCurrentContent(
+    GetCurrentContentCallback callback) {
+  feed_offline_host_->GetCurrentArticleSuggestions(base::BindOnce(
+      &FeedInternalsPageHandler::OnGetCurrentArticleSuggestionsDone,
+      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void FeedInternalsPageHandler::OnGetCurrentArticleSuggestionsDone(
+    GetCurrentContentCallback callback,
+    std::vector<offline_pages::PrefetchSuggestion> results) {
+  std::vector<feed_internals::mojom::SuggestionPtr> suggestions;
+
+  for (offline_pages::PrefetchSuggestion result : results) {
+    auto suggestion = feed_internals::mojom::Suggestion::New();
+    suggestion->title = std::move(result.article_title);
+    suggestion->url = result.article_url.spec();
+    suggestion->publisher_name = std::move(result.article_attribution);
+    suggestion->image_url = result.thumbnail_url.spec();
+    suggestion->favicon_url = result.favicon_url.spec();
+
+    suggestions.push_back(std::move(suggestion));
+  }
+
+  std::move(callback).Run(std::move(suggestions));
+}
diff --git a/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.h b/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.h
index e422b8f..277fb4c 100644
--- a/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.h
+++ b/chrome/browser/ui/webui/feed_internals/feed_internals_page_handler.h
@@ -5,7 +5,10 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_FEED_INTERNALS_FEED_INTERNALS_PAGE_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_FEED_INTERNALS_FEED_INTERNALS_PAGE_HANDLER_H_
 
+#include <vector>
+
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/webui/feed_internals/feed_internals.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
@@ -13,9 +16,14 @@
 
 namespace feed {
 class FeedHostService;
+class FeedOfflineHost;
 class FeedSchedulerHost;
 }  // namespace feed
 
+namespace offline_pages {
+struct PrefetchSuggestion;
+}  // namespace offline_pages
+
 // Concrete implementation of feed_internals::mojom::PageHandler.
 class FeedInternalsPageHandler : public feed_internals::mojom::PageHandler {
  public:
@@ -31,15 +39,23 @@
   void ClearUserClassifierProperties() override;
   void GetLastFetchProperties(GetLastFetchPropertiesCallback) override;
   void ClearCachedDataAndRefreshFeed() override;
+  void GetCurrentContent(GetCurrentContentCallback) override;
 
  private:
   // Binding from the mojo interface to concrete implementation.
   mojo::Binding<feed_internals::mojom::PageHandler> binding_;
 
+  void OnGetCurrentArticleSuggestionsDone(
+      GetCurrentContentCallback callback,
+      std::vector<offline_pages::PrefetchSuggestion> suggestions);
+
   // Services that provide the data and functionality.
   feed::FeedSchedulerHost* feed_scheduler_host_;
+  feed::FeedOfflineHost* feed_offline_host_;
   PrefService* pref_service_;
 
+  base::WeakPtrFactory<FeedInternalsPageHandler> weak_ptr_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(FeedInternalsPageHandler);
 };
 
diff --git a/chrome/browser/ui/webui/managed_ui_handler.cc b/chrome/browser/ui/webui/managed_ui_handler.cc
index 41dbfb2..4a69e87 100644
--- a/chrome/browser/ui/webui/managed_ui_handler.cc
+++ b/chrome/browser/ui/webui/managed_ui_handler.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/feature_list.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
@@ -14,7 +15,9 @@
 #include "chrome/browser/policy/profile_policy_connector_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/managed_ui.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/web_ui.h"
@@ -119,10 +122,16 @@
 std::unique_ptr<base::DictionaryValue> ManagedUIHandler::GetDataSourceUpdate()
     const {
   auto update = std::make_unique<base::DictionaryValue>();
+
+  bool link_to_management_page = base::FeatureList::IsEnabled(
+      features::kLinkManagedNoticeToChromeUIManagementURL);
+
   update->SetKey("managedByOrg",
                  base::Value(l10n_util::GetStringFUTF16(
                      IDS_MANAGED_BY_ORG_WITH_HYPERLINK,
-                     base::UTF8ToUTF16(chrome::kChromeUIManagementURL)
+                     base::UTF8ToUTF16(link_to_management_page
+                                           ? chrome::kChromeUIManagementURL
+                                           : chrome::kManagedUiLearnMoreUrl)
 #if defined(OS_CHROMEOS)
                          ,
                      ui::GetChromeOSDeviceName()
diff --git a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
index 1c4fe594..e96e1e9 100644
--- a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
+++ b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
@@ -166,7 +166,8 @@
     offline_page->SetDouble("creationTime", page.creation_time.ToJsTime());
     offline_page->SetDouble("lastAccessTime", page.last_access_time.ToJsTime());
     offline_page->SetInteger("accessCount", page.access_count);
-    offline_page->SetString("originalUrl", page.original_url.spec());
+    offline_page->SetString("originalUrl",
+                            page.original_url_if_different.spec());
     offline_page->SetString("requestOrigin", page.request_origin);
     results.Append(std::move(offline_page));
   }
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index 424212a..5248587f 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/printing/print_preview_data_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/webui/dark_mode_handler.h"
 #include "chrome/browser/ui/webui/localized_string.h"
 #include "chrome/browser/ui/webui/metrics_handler.h"
 #include "chrome/browser/ui/webui/print_preview/print_preview_handler.h"
@@ -472,7 +471,6 @@
   // Set up the chrome://print/ data source.
   Profile* profile = Profile::FromWebUI(web_ui);
   content::WebUIDataSource* source = CreatePrintPreviewUISource(profile);
-  DarkModeHandler::Initialize(web_ui, source);
   content::WebUIDataSource::Add(profile, source);
 
   // Set up the chrome://theme/ source.
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chrome/browser/ui/webui/settings/md_settings_ui.cc
index 291fbdf2..8120165 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_ui.cc
@@ -131,7 +131,7 @@
 #include "chrome/browser/ui/webui/settings/printing_handler.h"
 #endif
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(SAFE_BROWSING_FULL)
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #include "chrome/browser/ui/webui/settings/change_password_handler.h"
 #endif
@@ -268,7 +268,7 @@
 #endif  // OS_WIN && defined(GOOGLE_CHROME_BUILD)
 
   bool password_protection_available = false;
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(SAFE_BROWSING_FULL)
   safe_browsing::ChromePasswordProtectionService* password_protection =
       safe_browsing::ChromePasswordProtectionService::
           GetPasswordProtectionService(profile);
diff --git a/chrome/browser/ui/webui/settings/settings_manage_profile_handler.cc b/chrome/browser/ui/webui/settings/settings_manage_profile_handler.cc
index 71096cd9..fd96a72 100644
--- a/chrome/browser/ui/webui/settings/settings_manage_profile_handler.cc
+++ b/chrome/browser/ui/webui/settings/settings_manage_profile_handler.cc
@@ -121,19 +121,15 @@
 }
 
 std::unique_ptr<base::ListValue> ManageProfileHandler::GetAvailableIcons() {
-  std::unique_ptr<base::ListValue> avatars(
-      profiles::GetDefaultProfileAvatarIconsAndLabels());
-
   PrefService* pref_service = profile_->GetPrefs();
   bool using_gaia = pref_service->GetBoolean(prefs::kProfileUsingGAIAAvatar);
+  size_t selected_avatar_idx =
+      using_gaia ? SIZE_MAX
+                 : pref_service->GetInteger(prefs::kProfileAvatarIndex);
 
-  // Select the avatar from the default set.
-  if (!using_gaia) {
-    size_t index = pref_service->GetInteger(prefs::kProfileAvatarIndex);
-    base::DictionaryValue* avatar = nullptr;
-    if (avatars->GetDictionary(index, &avatar))
-      avatar->SetBoolean("selected", true);
-  }
+  // Obtain a list of the default avatar icons.
+  std::unique_ptr<base::ListValue> avatars(
+      profiles::GetDefaultProfileAvatarIconsAndLabels(selected_avatar_idx));
 
   // Add the GAIA picture to the beginning of the list if it is available.
   ProfileAttributesEntry* entry;
diff --git a/chrome/browser/ui/webui/settings/settings_manage_profile_handler.h b/chrome/browser/ui/webui/settings/settings_manage_profile_handler.h
index 659ef79..32da11d3 100644
--- a/chrome/browser/ui/webui/settings/settings_manage_profile_handler.h
+++ b/chrome/browser/ui/webui/settings/settings_manage_profile_handler.h
@@ -44,6 +44,8 @@
   FRIEND_TEST_ALL_PREFIXES(ManageProfileHandlerTest, HandleSetProfileName);
   FRIEND_TEST_ALL_PREFIXES(ManageProfileHandlerTest, HandleGetAvailableIcons);
   FRIEND_TEST_ALL_PREFIXES(ManageProfileHandlerTest,
+                           HandleGetAvailableIconsOldIconSelected);
+  FRIEND_TEST_ALL_PREFIXES(ManageProfileHandlerTest,
                            HandleGetAvailableIconsGaiaAvatarSelected);
 
   // Callback for the "getAvailableIcons" message.
diff --git a/chrome/browser/ui/webui/settings/settings_manage_profile_handler_unittest.cc b/chrome/browser/ui/webui/settings/settings_manage_profile_handler_unittest.cc
index d981468..d7ee5c1 100644
--- a/chrome/browser/ui/webui/settings/settings_manage_profile_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/settings_manage_profile_handler_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/webui/settings/settings_manage_profile_handler.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/profiles/profile_avatar_icon_util.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
@@ -79,8 +80,10 @@
       const base::DictionaryValue* icon = nullptr;
       EXPECT_TRUE(icons->GetDictionary(i, &icon));
       std::string icon_url;
+      size_t icon_index;
       EXPECT_TRUE(icon->GetString("url", &icon_url));
       EXPECT_FALSE(icon_url.empty());
+      EXPECT_TRUE(profiles::IsDefaultAvatarIconUrl(icon_url, &icon_index));
       std::string icon_label;
       EXPECT_TRUE(icon->GetString("label", &icon_label));
       EXPECT_FALSE(icon_label.empty());
@@ -88,7 +91,7 @@
       bool has_icon_selected = icon->GetBoolean("selected", &icon_selected);
       if (all_not_selected) {
         EXPECT_FALSE(has_icon_selected);
-      } else if (selected_index == i) {
+      } else if (selected_index == icon_index) {
         EXPECT_TRUE(has_icon_selected);
         EXPECT_TRUE(icon_selected);
       }
@@ -126,7 +129,7 @@
 
 TEST_F(ManageProfileHandlerTest, HandleGetAvailableIcons) {
   PrefService* pref_service = profile()->GetPrefs();
-  pref_service->SetInteger(prefs::kProfileAvatarIndex, 7);
+  pref_service->SetInteger(prefs::kProfileAvatarIndex, 27);
 
   base::ListValue list_args_1;
   list_args_1.AppendString("get-icons-callback-id");
@@ -141,12 +144,32 @@
   ASSERT_TRUE(data_1.arg1()->GetAsString(&callback_id_1));
   EXPECT_EQ("get-icons-callback-id", callback_id_1);
 
-  VerifyIconListWithSingleSelection(data_1.arg3(), 7);
+  VerifyIconListWithSingleSelection(data_1.arg3(), 27);
+}
+
+TEST_F(ManageProfileHandlerTest, HandleGetAvailableIconsOldIconSelected) {
+  PrefService* pref_service = profile()->GetPrefs();
+  pref_service->SetInteger(prefs::kProfileAvatarIndex, 7);
+
+  base::ListValue list_args;
+  list_args.AppendString("get-icons-callback-id");
+  handler()->HandleGetAvailableIcons(&list_args);
+
+  EXPECT_EQ(1U, web_ui()->call_data().size());
+
+  const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
+  EXPECT_EQ("cr.webUIResponse", data.function_name());
+
+  std::string callback_id;
+  ASSERT_TRUE(data.arg1()->GetAsString(&callback_id));
+  EXPECT_EQ("get-icons-callback-id", callback_id);
+
+  VerifyIconListWithNoneSelected(data.arg3());
 }
 
 TEST_F(ManageProfileHandlerTest, HandleGetAvailableIconsGaiaAvatarSelected) {
   PrefService* pref_service = profile()->GetPrefs();
-  pref_service->SetInteger(prefs::kProfileAvatarIndex, 7);
+  pref_service->SetInteger(prefs::kProfileAvatarIndex, 27);
   pref_service->SetBoolean(prefs::kProfileUsingGAIAAvatar, true);
 
   base::ListValue list_args;
diff --git a/chrome/browser/ui/webui/signin/md_user_manager_ui.cc b/chrome/browser/ui/webui/signin/md_user_manager_ui.cc
index 7c65f834..c012745 100644
--- a/chrome/browser/ui/webui/signin/md_user_manager_ui.cc
+++ b/chrome/browser/ui/webui/signin/md_user_manager_ui.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_shortcut_manager.h"
 #include "chrome/browser/signin/signin_util.h"
-#include "chrome/browser/ui/webui/dark_mode_handler.h"
 #include "chrome/browser/ui/webui/signin/signin_create_profile_handler.h"
 #include "chrome/browser/ui/webui/signin/signin_utils.h"
 #include "chrome/browser/ui/webui/signin/user_manager_screen_handler.h"
@@ -45,7 +44,6 @@
 
   // Set up the chrome://md-user-manager/ source.
   auto* md_user_source = CreateUIDataSource(localized_strings);
-  DarkModeHandler::Initialize(web_ui, md_user_source);
   content::WebUIDataSource::Add(profile, md_user_source);
 
   // Set up the chrome://theme/ source
diff --git a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
index 2bfcee6c..91ffa695 100644
--- a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
+++ b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
@@ -107,8 +107,8 @@
   // it will be pixelated when displayed in the User Manager, so we should
   // return the placeholder avatar instead.
   gfx::Image avatar_image = entry->GetAvatarIcon();
-  if (avatar_image.Width() <= profiles::kAvatarIconWidth ||
-      avatar_image.Height() <= profiles::kAvatarIconHeight ) {
+  if (avatar_image.Width() <= profiles::kAvatarIconSize ||
+      avatar_image.Height() <= profiles::kAvatarIconSize) {
     avatar_image = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
         profiles::GetPlaceholderAvatarIconResourceID());
   }
@@ -631,8 +631,6 @@
   localized_strings->SetString("cancel", l10n_util::GetStringUTF16(IDS_CANCEL));
   localized_strings->SetString(
       "browseAsGuest", l10n_util::GetStringUTF16(IDS_BROWSE_AS_GUEST_BUTTON));
-  localized_strings->SetString("signOutUser",
-      l10n_util::GetStringUTF16(IDS_SCREEN_LOCK_SIGN_OUT));
   localized_strings->SetString("addSupervisedUser",
       l10n_util::GetStringUTF16(IDS_CREATE_LEGACY_SUPERVISED_USER_MENU_LABEL));
 
@@ -733,7 +731,6 @@
   localized_strings->SetString("publicSessionSelectLanguage", "");
   localized_strings->SetString("publicSessionSelectKeyboard", "");
   localized_strings->SetString("signinBannerText", "");
-  localized_strings->SetString("launchAppButton", "");
   localized_strings->SetString("multiProfilesRestrictedPolicyTitle", "");
   localized_strings->SetString("multiProfilesNotAllowedPolicyMsg", "");
   localized_strings->SetString("multiProfilesPrimaryOnlyPolicyMsg", "");
diff --git a/chrome/browser/ui/webui/usb_internals/BUILD.gn b/chrome/browser/ui/webui/usb_internals/BUILD.gn
index 7c2e8a4..958fa821 100644
--- a/chrome/browser/ui/webui/usb_internals/BUILD.gn
+++ b/chrome/browser/ui/webui/usb_internals/BUILD.gn
@@ -10,6 +10,7 @@
   ]
 
   deps = [
+    "//device/usb/public/mojom:mojom",
     "//device/usb/public/mojom:test",
   ]
 }
diff --git a/chrome/browser/ui/webui/usb_internals/usb_internals.mojom b/chrome/browser/ui/webui/usb_internals/usb_internals.mojom
index fede6b0..4066228 100644
--- a/chrome/browser/ui/webui/usb_internals/usb_internals.mojom
+++ b/chrome/browser/ui/webui/usb_internals/usb_internals.mojom
@@ -4,9 +4,13 @@
 
 module mojom;
 
+import "device/usb/public/mojom/device_manager.mojom";
 import "device/usb/public/mojom/device_manager_test.mojom";
 
 interface UsbInternalsPageHandler {
+  // Bind the UsbDeviceManager interface to get all devices that connected.
+  BindUsbDeviceManagerInterface(device.mojom.UsbDeviceManager& request);
+
   // Simulate the connection of a new device with the given properties.
   BindTestInterface(device.mojom.UsbDeviceManagerTest& request);
 };
diff --git a/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.cc b/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.cc
index 881d85b..3775ba9c 100644
--- a/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.cc
@@ -23,3 +23,11 @@
       ->GetConnector()
       ->BindInterface(device::mojom::kServiceName, std::move(request));
 }
+
+void UsbInternalsPageHandler::BindUsbDeviceManagerInterface(
+    device::mojom::UsbDeviceManagerRequest request) {
+  // Forward the request to the DeviceService.
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->BindInterface(device::mojom::kServiceName, std::move(request));
+}
diff --git a/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.h b/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.h
index cd32dd4..b340c46c 100644
--- a/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.h
+++ b/chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "chrome/browser/ui/webui/usb_internals/usb_internals.mojom.h"
+#include "device/usb/public/mojom/device_manager.mojom.h"
 #include "device/usb/public/mojom/device_manager_test.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
@@ -16,7 +17,9 @@
       mojom::UsbInternalsPageHandlerRequest request);
   ~UsbInternalsPageHandler() override;
 
-  // mojom::UsbInternalsPageHandler overrides:
+  void BindUsbDeviceManagerInterface(
+      device::mojom::UsbDeviceManagerRequest request) override;
+
   void BindTestInterface(
       device::mojom::UsbDeviceManagerTestRequest request) override;
 
diff --git a/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc b/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
index 79e98188..efc72f6 100644
--- a/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
+++ b/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
@@ -16,15 +16,23 @@
   // Set up the chrome://usb-internals source.
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(chrome::kChromeUIUsbInternalsHost);
+
   source->AddResourcePath("usb_internals.css", IDR_USB_INTERNALS_CSS);
   source->AddResourcePath("usb_internals.js", IDR_USB_INTERNALS_JS);
-  source->AddResourcePath(
-      "device/usb/public/mojom/device_manager_test.mojom-lite.js",
-      IDR_USB_DEVICE_MANAGER_TEST_MOJO_JS);
-  source->AddResourcePath(
-      "chrome/browser/ui/webui/usb_internals/usb_internals.mojom-lite.js",
-      IDR_USB_INTERNALS_MOJO_JS);
-  source->AddResourcePath("url/mojom/url.mojom-lite.js", IDR_URL_MOJOM_LITE_JS);
+  source->AddResourcePath("usb_internals.mojom-lite.js",
+                          IDR_USB_INTERNALS_MOJOM_LITE_JS);
+  source->AddResourcePath("devices_page.js", IDR_USB_INTERNALS_DEVICES_PAGE_JS);
+  source->AddResourcePath("device.mojom-lite.js", IDR_USB_DEVICE_MOJOM_LITE_JS);
+  source->AddResourcePath("device_enumeration_options.mojom-lite.js",
+                          IDR_USB_DEVICE_ENUMERATION_OPTIONS_MOJOM_LITE_JS);
+  source->AddResourcePath("device_manager.mojom-lite.js",
+                          IDR_USB_DEVICE_MANAGER_MOJOM_LITE_JS);
+  source->AddResourcePath("device_manager_client.mojom-lite.js",
+                          IDR_USB_DEVICE_MANAGER_CLIENT_MOJOM_LITE_JS);
+  source->AddResourcePath("device_manager_test.mojom-lite.js",
+                          IDR_USB_DEVICE_MANAGER_TEST_MOJOM_LITE_JS);
+  source->AddResourcePath("url.mojom-lite.js", IDR_URL_MOJOM_LITE_JS);
+
   source->SetDefaultResource(IDR_USB_INTERNALS_HTML);
   source->UseGzip();
 
diff --git a/chrome/browser/vr/service/xr_runtime_manager.cc b/chrome/browser/vr/service/xr_runtime_manager.cc
index 86bdc18..a7c69ba 100644
--- a/chrome/browser/vr/service/xr_runtime_manager.cc
+++ b/chrome/browser/vr/service/xr_runtime_manager.cc
@@ -190,8 +190,9 @@
       return orientation_runtime;
     }
 
-    // Otherwise fall back to immersive providers.
-    return GetImmersiveRuntime();
+    // If we don't have an orientation provider, then we don't have an explicit
+    // runtime to back a non-immersive session
+    return nullptr;
   }
 }
 
diff --git a/chrome/browser/web_applications/components/install_finalizer.h b/chrome/browser/web_applications/components/install_finalizer.h
index c02209f82..3b47642 100644
--- a/chrome/browser/web_applications/components/install_finalizer.h
+++ b/chrome/browser/web_applications/components/install_finalizer.h
@@ -41,7 +41,8 @@
   virtual bool CanPinAppToShelf() const = 0;
   virtual void PinAppToShelf(const AppId& app_id) = 0;
 
-  virtual bool CanReparentTab(bool shortcut_created) const = 0;
+  virtual bool CanReparentTab(const AppId& app_id,
+                              bool shortcut_created) const = 0;
   virtual void ReparentTab(const AppId& app_id,
                            content::WebContents* web_contents) = 0;
 
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
index 62210fc..a2fc2a72 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
@@ -116,8 +116,10 @@
   BookmarkAppPinToShelf(app);
 }
 
-bool BookmarkAppInstallFinalizer::CanReparentTab(bool shortcut_created) const {
-  return CanBookmarkAppReparentTab(shortcut_created);
+bool BookmarkAppInstallFinalizer::CanReparentTab(const web_app::AppId& app_id,
+                                                 bool shortcut_created) const {
+  const Extension* app = GetExtensionById(profile_, app_id);
+  return CanBookmarkAppReparentTab(profile_, app, shortcut_created);
 }
 
 void BookmarkAppInstallFinalizer::ReparentTab(
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
index 9a6ea48..9e5ad545 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
@@ -33,7 +33,8 @@
                          CreateOsShortcutsCallback callback) override;
   bool CanPinAppToShelf() const override;
   void PinAppToShelf(const web_app::AppId& app_id) override;
-  bool CanReparentTab(bool shortcut_created) const override;
+  bool CanReparentTab(const web_app::AppId& app_id,
+                      bool shortcut_created) const override;
   void ReparentTab(const web_app::AppId& app_id,
                    content::WebContents* web_contents) override;
   bool CanRevealAppShim() const override;
diff --git a/chrome/browser/web_applications/test/test_install_finalizer.cc b/chrome/browser/web_applications/test/test_install_finalizer.cc
index eedfa1f2..bbe6ea1 100644
--- a/chrome/browser/web_applications/test/test_install_finalizer.cc
+++ b/chrome/browser/web_applications/test/test_install_finalizer.cc
@@ -61,7 +61,8 @@
   ++num_pin_app_to_shelf_calls_;
 }
 
-bool TestInstallFinalizer::CanReparentTab(bool shortcut_created) const {
+bool TestInstallFinalizer::CanReparentTab(const AppId& app_id,
+                                          bool shortcut_created) const {
   return true;
 }
 
diff --git a/chrome/browser/web_applications/test/test_install_finalizer.h b/chrome/browser/web_applications/test/test_install_finalizer.h
index 7725c92..381c77d 100644
--- a/chrome/browser/web_applications/test/test_install_finalizer.h
+++ b/chrome/browser/web_applications/test/test_install_finalizer.h
@@ -28,7 +28,8 @@
                          CreateOsShortcutsCallback callback) override;
   bool CanPinAppToShelf() const override;
   void PinAppToShelf(const AppId& app_id) override;
-  bool CanReparentTab(bool shortcut_created) const override;
+  bool CanReparentTab(const AppId& app_id,
+                      bool shortcut_created) const override;
   void ReparentTab(const AppId& app_id,
                    content::WebContents* web_contents) override;
   bool CanRevealAppShim() const override;
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 93bd380..f4db22c 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -123,7 +123,8 @@
   NOTIMPLEMENTED();
 }
 
-bool WebAppInstallFinalizer::CanReparentTab(bool shortcut_created) const {
+bool WebAppInstallFinalizer::CanReparentTab(const AppId& app_id,
+                                            bool shortcut_created) const {
   // TODO(loyso): Implement it.
   NOTIMPLEMENTED();
   return true;
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.h b/chrome/browser/web_applications/web_app_install_finalizer.h
index 12ec5c8..661fa127 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.h
+++ b/chrome/browser/web_applications/web_app_install_finalizer.h
@@ -33,7 +33,8 @@
                          CreateOsShortcutsCallback callback) override;
   bool CanPinAppToShelf() const override;
   void PinAppToShelf(const AppId& app_id) override;
-  bool CanReparentTab(bool shortcut_created) const override;
+  bool CanReparentTab(const AppId& app_id,
+                      bool shortcut_created) const override;
   void ReparentTab(const AppId& app_id,
                    content::WebContents* web_contents) override;
   bool CanRevealAppShim() const override;
diff --git a/chrome/browser/web_applications/web_app_install_manager.cc b/chrome/browser/web_applications/web_app_install_manager.cc
index 023640c8..c809e59 100644
--- a/chrome/browser/web_applications/web_app_install_manager.cc
+++ b/chrome/browser/web_applications/web_app_install_manager.cc
@@ -245,7 +245,7 @@
 
   // TODO(loyso): Implement |reparent_tab| to skip tab reparenting logic.
   if (web_app_info->open_as_window &&
-      install_finalizer_->CanReparentTab(shortcut_created)) {
+      install_finalizer_->CanReparentTab(app_id, shortcut_created)) {
     install_finalizer_->ReparentTab(app_id, web_contents());
   }
 
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 184cc0e4..23585e76 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -248,7 +248,7 @@
 #if defined(OS_CHROMEOS)
 // Enables event-based status reporting for child accounts in Chrome OS.
 const base::Feature kEventBasedStatusReporting{
-    "EventBasedStatusReporting", base::FEATURE_DISABLED_BY_DEFAULT};
+    "EventBasedStatusReporting", base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
 // An experimental way of showing app banners, which has modal banners and gives
@@ -523,6 +523,10 @@
 #if !defined(OS_ANDROID)
 const base::Feature kShowManagedUi{"ShowManagedUi",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kLinkManagedNoticeToChromeUIManagementURL{
+    "LinkManagedNoticeToChromeUIManagementURL",
+    base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
 #if defined(OS_ANDROID)
@@ -534,6 +538,9 @@
 // Launch bug: https://crbug.com/810843.  This is a //chrome-layer feature to
 // avoid turning on site-per-process by default for *all* //content embedders
 // (e.g. this approach lets ChromeCast avoid site-per-process mode).
+//
+// TODO(alexmos): Move this and the other site isolation features below to
+// browser_features, as they are only used on the browser side.
 const base::Feature kSitePerProcess {
   "site-per-process",
 #if defined(OS_ANDROID)
@@ -543,6 +550,15 @@
 #endif
 };
 
+// Controls a mode for dynamically process-isolating sites where the user has
+// entered a password.  This is intended to be used primarily when full site
+// isolation is turned off.  To check whether this mode is enabled, use
+// ChromeSiteIsolationPolicy::IsIsolationForPasswordSitesEnabled() rather than
+// checking the feature directly, since that decision is influenced by other
+// factors as well.
+const base::Feature kSiteIsolationForPasswordSites{
+    "site-isolation-for-password-sites", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // kSitePerProcessOnlyForHighMemoryClients is checked before kSitePerProcess,
 // and (if enabled) can restrict if kSitePerProcess feature is checked at all -
 // no check will be made on devices with low memory (these devices will have no
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index b773f3d..07dbaefed 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -352,6 +352,8 @@
 
 #if !defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kShowManagedUi;
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kLinkManagedNoticeToChromeUIManagementURL;
 #endif
 
 #if defined(OS_ANDROID)
@@ -364,6 +366,9 @@
 COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kSitePerProcess;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSiteIsolationForPasswordSites;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kSitePerProcessOnlyForHighMemoryClients;
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const char kSitePerProcessOnlyForHighMemoryClientsParamName[];
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 1a48c37..c4f4376 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -153,9 +153,8 @@
 const char kLegacySupervisedUserManagementURL[] =
     "https://www.chrome.com/manage";
 
-// TODO(nicolaso): Replace with a p-link once it's ready. b/117655761
 const char kManagedUiLearnMoreUrl[] =
-    "https://support.google.com/chromebook/answer/1331549";
+    "https://support.google.com/chromebook/?p=is_chrome_managed";
 
 const char kMyActivityUrlInClearBrowsingData[] =
     "https://myactivity.google.com/myactivity/?utm_source=chrome_cbd";
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider_module.cc b/chrome/credential_provider/gaiacp/gaia_credential_provider_module.cc
index d49bc00..fc4215a0 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider_module.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider_module.cc
@@ -8,6 +8,7 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/win/windows_version.h"
 #include "chrome/common/chrome_version.h"
 #include "chrome/credential_provider/eventlog/gcp_eventlog_messages.h"
 #include "chrome/credential_provider/gaiacp/associated_user_validator.h"
@@ -116,7 +117,9 @@
         credential_provider::ConfigureGcpCrashReporting(*cmd_line);
       }
 
-      LOGFN(INFO) << "DllMain(DLL_PROCESS_ATTACH)";
+      LOGFN(INFO) << "DllMain(DLL_PROCESS_ATTACH) Build: "
+                  << base::win::OSInfo::GetInstance()->Kernel32BaseVersion()
+                  << " Version:" << GetWindowsVersion();
       break;
     }
     case DLL_PROCESS_DETACH:
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.cc b/chrome/credential_provider/gaiacp/gcp_utils.cc
index c9cd131b..09adfcd 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.cc
+++ b/chrome/credential_provider/gaiacp/gcp_utils.cc
@@ -246,7 +246,7 @@
 }
 
 // Waits for a process to terminate while capturing output from |output_handle|
-// to the buffer |output_buffer| of size |buffer_size|. The buffer is expected
+// to the buffer |output_buffer| of length |buffer_size|. The buffer is expected
 // to be relatively small.  The exit code of the process is written to
 // |exit_code|.
 HRESULT WaitForProcess(base::win::ScopedHandle::Handle process_handle,
@@ -699,6 +699,18 @@
 #endif
 }
 
+base::string16 GetWindowsVersion() {
+  wchar_t release_id[32];
+  ULONG length = base::size(release_id) * sizeof(release_id[0]);
+  HRESULT hr =
+      GetMachineRegString(L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+                          L"ReleaseId", release_id, &length);
+  if (SUCCEEDED(hr))
+    return release_id;
+
+  return L"Unknown";
+}
+
 FakesForTesting::FakesForTesting() {}
 
 FakesForTesting::~FakesForTesting() {}
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.h b/chrome/credential_provider/gaiacp/gcp_utils.h
index ded5915..4d36a18 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.h
+++ b/chrome/credential_provider/gaiacp/gcp_utils.h
@@ -239,6 +239,11 @@
     const std::unique_ptr<base::DictionaryValue>& dict,
     const char* name);
 
+// Returns the major build version of Windows by reading the registry.
+// See:
+// https://stackoverflow.com/questions/31072543/reliable-way-to-get-windows-version-from-registry
+base::string16 GetWindowsVersion();
+
 class OSUserManager;
 class OSProcessManager;
 
diff --git a/chrome/credential_provider/setup/setup.cc b/chrome/credential_provider/setup/setup.cc
index 3babc53c..7560e73 100644
--- a/chrome/credential_provider/setup/setup.cc
+++ b/chrome/credential_provider/setup/setup.cc
@@ -133,8 +133,10 @@
   LOGFN(INFO) << "Module: " << gcp_setup_exe_path;
   LOGFN(INFO) << "Args: " << lpCmdLine;
   LOGFN(INFO) << "Version: " << TEXT(CHROME_VERSION_STRING);
+
   LOGFN(INFO) << "Windows: "
-              << base::win::OSInfo::GetInstance()->Kernel32BaseVersion();
+              << base::win::OSInfo::GetInstance()->Kernel32BaseVersion()
+              << " Version:" << credential_provider::GetWindowsVersion();
 
   // If running from omaha, make sure machine install is used.
   if (IsPerUserInstallFromGoogleUpdate()) {
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index e86e1f0..245a7102 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -102,8 +102,8 @@
     "sandbox_status_extension_android.h",
     "security_filter_peer.cc",
     "security_filter_peer.h",
-    "ssl/ssl_certificate_error_page_controller.cc",
-    "ssl/ssl_certificate_error_page_controller.h",
+    "security_interstitials/security_interstitial_page_controller.cc",
+    "security_interstitials/security_interstitial_page_controller.h",
     "supervised_user/supervised_user_error_page_controller.cc",
     "supervised_user/supervised_user_error_page_controller.h",
     "tts_dispatcher.cc",
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index d007c01..27f4cff3 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -523,7 +523,7 @@
   new nacl::NaClHelper(render_frame);
 #endif
 
-#if defined(FULL_SAFE_BROWSING) || defined(SAFE_BROWSING_DB_REMOTE)
+#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
   safe_browsing::ThreatDOMDetails::Create(render_frame, registry);
 #endif
 
diff --git a/chrome/renderer/net/net_error_helper.cc b/chrome/renderer/net/net_error_helper.cc
index 0081b5b..2eb6a965 100644
--- a/chrome/renderer/net/net_error_helper.cc
+++ b/chrome/renderer/net/net_error_helper.cc
@@ -24,7 +24,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/render_messages.h"
 #include "chrome/renderer/chrome_render_thread_observer.h"
-#include "chrome/renderer/ssl/ssl_certificate_error_page_controller.h"
+#include "chrome/renderer/security_interstitials/security_interstitial_page_controller.h"
 #include "chrome/renderer/supervised_user/supervised_user_error_page_controller.h"
 #include "components/error_page/common/error.h"
 #include "components/error_page/common/error_page_params.h"
@@ -167,7 +167,7 @@
     : RenderFrameObserver(render_frame),
       content::RenderFrameObserverTracker<NetErrorHelper>(render_frame),
       weak_controller_delegate_factory_(this),
-      weak_ssl_error_controller_delegate_factory_(this),
+      weak_security_interstitial_controller_delegate_factory_(this),
       weak_supervised_user_error_controller_delegate_factory_(this) {
   RenderThread::Get()->AddObserver(this);
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
@@ -324,7 +324,7 @@
   // error page, the controller has not yet been attached, so this won't affect
   // it.
   weak_controller_delegate_factory_.InvalidateWeakPtrs();
-  weak_ssl_error_controller_delegate_factory_.InvalidateWeakPtrs();
+  weak_security_interstitial_controller_delegate_factory_.InvalidateWeakPtrs();
   weak_supervised_user_error_controller_delegate_factory_.InvalidateWeakPtrs();
 
   core_->OnCommitLoad(GetFrameType(render_frame()),
@@ -433,9 +433,10 @@
                                  failed_url, true /* replace_current_item */);
 }
 
-void NetErrorHelper::EnablePageHelperFunctions(net::Error net_error) {
-  SSLCertificateErrorPageController::Install(
-      render_frame(), weak_ssl_error_controller_delegate_factory_.GetWeakPtr());
+void NetErrorHelper::EnablePageHelperFunctions() {
+  SecurityInterstitialPageController::Install(
+      render_frame(),
+      weak_security_interstitial_controller_delegate_factory_.GetWeakPtr());
   NetErrorPageController::Install(
       render_frame(), weak_controller_delegate_factory_.GetWeakPtr());
 
diff --git a/chrome/renderer/net/net_error_helper.h b/chrome/renderer/net/net_error_helper.h
index cf104cf8..0b259ec 100644
--- a/chrome/renderer/net/net_error_helper.h
+++ b/chrome/renderer/net/net_error_helper.h
@@ -18,7 +18,7 @@
 #include "chrome/common/supervised_user_commands.mojom.h"
 #include "chrome/renderer/net/net_error_helper_core.h"
 #include "chrome/renderer/net/net_error_page_controller.h"
-#include "chrome/renderer/ssl/ssl_certificate_error_page_controller.h"
+#include "chrome/renderer/security_interstitials/security_interstitial_page_controller.h"
 #include "chrome/renderer/supervised_user/supervised_user_error_page_controller.h"
 #include "chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h"
 #include "components/error_page/common/net_error_info.h"
@@ -55,7 +55,7 @@
       public content::RenderThreadObserver,
       public NetErrorHelperCore::Delegate,
       public NetErrorPageController::Delegate,
-      public SSLCertificateErrorPageController::Delegate,
+      public SecurityInterstitialPageController::Delegate,
       public SupervisedUserErrorPageControllerDelegate,
       public chrome::mojom::NetworkDiagnosticsClient,
       public chrome::mojom::NavigationCorrector {
@@ -75,7 +75,7 @@
   void UpdateEasterEggHighScore(int high_score) override;
   void ResetEasterEggHighScore() override;
 
-  // SSLCertificateErrorPageController::Delegate implementation
+  // SecurityInterstitialPageController::Delegate implementation
   void SendCommand(
       security_interstitials::SecurityInterstitialCommand command) override;
 
@@ -130,7 +130,7 @@
       bool* auto_fetch_allowed,
       std::string* html) const override;
   void LoadErrorPage(const std::string& html, const GURL& failed_url) override;
-  void EnablePageHelperFunctions(net::Error net_error) override;
+  void EnablePageHelperFunctions() override;
   void UpdateErrorPage(const error_page::Error& error,
                        bool is_failed_post,
                        bool can_use_local_diagnostics_service) override;
@@ -207,8 +207,8 @@
   base::WeakPtrFactory<NetErrorPageController::Delegate>
       weak_controller_delegate_factory_;
 
-  base::WeakPtrFactory<SSLCertificateErrorPageController::Delegate>
-      weak_ssl_error_controller_delegate_factory_;
+  base::WeakPtrFactory<SecurityInterstitialPageController::Delegate>
+      weak_security_interstitial_controller_delegate_factory_;
 
   base::WeakPtrFactory<SupervisedUserErrorPageControllerDelegate>
       weak_supervised_user_error_controller_delegate_factory_;
diff --git a/chrome/renderer/net/net_error_helper_core.cc b/chrome/renderer/net/net_error_helper_core.cc
index 721c1a2..3614c37c 100644
--- a/chrome/renderer/net/net_error_helper_core.cc
+++ b/chrome/renderer/net/net_error_helper_core.cc
@@ -680,8 +680,7 @@
   delegate_->SetIsShowingDownloadButton(
       committed_error_page_info_->download_button_in_page);
 
-  delegate_->EnablePageHelperFunctions(
-      static_cast<net::Error>(committed_error_page_info_->error.reason()));
+  delegate_->EnablePageHelperFunctions();
 
 #if defined(OS_ANDROID)
   if (committed_error_page_info_->offline_content_feature_state ==
diff --git a/chrome/renderer/net/net_error_helper_core.h b/chrome/renderer/net/net_error_helper_core.h
index 14ebf9c..2720c244 100644
--- a/chrome/renderer/net/net_error_helper_core.h
+++ b/chrome/renderer/net/net_error_helper_core.h
@@ -80,7 +80,7 @@
 
     // Create extra Javascript bindings in the error page. Will only be invoked
     // after an error page has finished loading.
-    virtual void EnablePageHelperFunctions(net::Error net_error) = 0;
+    virtual void EnablePageHelperFunctions() = 0;
 
     // Updates the currently displayed error page with a new error code.  The
     // currently displayed error page must have finished loading, and must have
diff --git a/chrome/renderer/net/net_error_helper_core_unittest.cc b/chrome/renderer/net/net_error_helper_core_unittest.cc
index 86a8b86..6847390d 100644
--- a/chrome/renderer/net/net_error_helper_core_unittest.cc
+++ b/chrome/renderer/net/net_error_helper_core_unittest.cc
@@ -390,7 +390,7 @@
     last_error_html_ = html;
   }
 
-  void EnablePageHelperFunctions(net::Error net_error) override {
+  void EnablePageHelperFunctions() override {
     enable_page_helper_functions_count_++;
   }
 
diff --git a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
index ecfe017..5399e46 100644
--- a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
@@ -280,7 +280,7 @@
   dispatch(args);
 });
 
-registerArgumentMassager('fileManagerPrivate.onCrostiniSharedPathsChanged',
+registerArgumentMassager('fileManagerPrivate.onCrostiniChanged',
                          function(args, dispatch) {
   // Convert entries arguments into real Entry objects.
   const entries = args[0].entries;
diff --git a/chrome/renderer/ssl/DEPS b/chrome/renderer/security_interstitials/DEPS
similarity index 100%
rename from chrome/renderer/ssl/DEPS
rename to chrome/renderer/security_interstitials/DEPS
diff --git a/chrome/renderer/ssl/OWNERS b/chrome/renderer/security_interstitials/OWNERS
similarity index 100%
rename from chrome/renderer/ssl/OWNERS
rename to chrome/renderer/security_interstitials/OWNERS
diff --git a/chrome/renderer/security_interstitials/security_interstitial_page_controller.cc b/chrome/renderer/security_interstitials/security_interstitial_page_controller.cc
new file mode 100644
index 0000000..126e46c
--- /dev/null
+++ b/chrome/renderer/security_interstitials/security_interstitial_page_controller.cc
@@ -0,0 +1,146 @@
+// 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 "chrome/renderer/security_interstitials/security_interstitial_page_controller.h"
+
+#include "components/security_interstitials/core/controller_client.h"
+#include "content/public/renderer/render_frame.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+gin::WrapperInfo SecurityInterstitialPageController::kWrapperInfo = {
+    gin::kEmbedderNativeGin};
+
+SecurityInterstitialPageController::Delegate::~Delegate() {}
+
+void SecurityInterstitialPageController::Install(
+    content::RenderFrame* render_frame,
+    base::WeakPtr<Delegate> delegate) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context =
+      render_frame->GetWebFrame()->MainWorldScriptContext();
+  if (context.IsEmpty())
+    return;
+
+  v8::Context::Scope context_scope(context);
+
+  gin::Handle<SecurityInterstitialPageController> controller =
+      gin::CreateHandle(isolate,
+                        new SecurityInterstitialPageController(delegate));
+  if (controller.IsEmpty())
+    return;
+
+  v8::Local<v8::Object> global = context->Global();
+  global->Set(gin::StringToV8(isolate, "certificateErrorPageController"),
+              controller.ToV8());
+}
+
+SecurityInterstitialPageController::SecurityInterstitialPageController(
+    base::WeakPtr<Delegate> delegate)
+    : delegate_(delegate) {}
+
+SecurityInterstitialPageController::~SecurityInterstitialPageController() {}
+
+void SecurityInterstitialPageController::DontProceed() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_DONT_PROCEED);
+}
+
+void SecurityInterstitialPageController::Proceed() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_PROCEED);
+}
+
+void SecurityInterstitialPageController::ShowMoreSection() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_SHOW_MORE_SECTION);
+}
+
+void SecurityInterstitialPageController::OpenHelpCenter() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_OPEN_HELP_CENTER);
+}
+
+void SecurityInterstitialPageController::OpenDiagnostic() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_DIAGNOSTIC);
+}
+
+void SecurityInterstitialPageController::Reload() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_RELOAD);
+}
+
+void SecurityInterstitialPageController::OpenDateSettings() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_OPEN_DATE_SETTINGS);
+}
+
+void SecurityInterstitialPageController::OpenLogin() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_LOGIN);
+}
+
+void SecurityInterstitialPageController::DoReport() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_DO_REPORT);
+}
+
+void SecurityInterstitialPageController::DontReport() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_DONT_REPORT);
+}
+
+void SecurityInterstitialPageController::OpenReportingPrivacy() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_OPEN_REPORTING_PRIVACY);
+}
+
+void SecurityInterstitialPageController::OpenWhitepaper() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_WHITEPAPER);
+}
+
+void SecurityInterstitialPageController::ReportPhishingError() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_REPORT_PHISHING_ERROR);
+}
+
+void SecurityInterstitialPageController::SendCommand(
+    security_interstitials::SecurityInterstitialCommand command) {
+  if (delegate_) {
+    delegate_->SendCommand(command);
+  }
+}
+
+gin::ObjectTemplateBuilder
+SecurityInterstitialPageController::GetObjectTemplateBuilder(
+    v8::Isolate* isolate) {
+  return gin::Wrappable<SecurityInterstitialPageController>::
+      GetObjectTemplateBuilder(isolate)
+          .SetMethod("dontProceed",
+                     &SecurityInterstitialPageController::DontProceed)
+          .SetMethod("proceed", &SecurityInterstitialPageController::Proceed)
+          .SetMethod("showMoreSection",
+                     &SecurityInterstitialPageController::ShowMoreSection)
+          .SetMethod("openHelpCenter",
+                     &SecurityInterstitialPageController::OpenHelpCenter)
+          .SetMethod("openDiagnostic",
+                     &SecurityInterstitialPageController::OpenDiagnostic)
+          .SetMethod("reload", &SecurityInterstitialPageController::Reload)
+          .SetMethod("openDateSettings",
+                     &SecurityInterstitialPageController::OpenDateSettings)
+          .SetMethod("openLogin",
+                     &SecurityInterstitialPageController::OpenLogin)
+          .SetMethod("doReport", &SecurityInterstitialPageController::DoReport)
+          .SetMethod("dontReport",
+                     &SecurityInterstitialPageController::DontReport)
+          .SetMethod("openReportingPrivacy",
+                     &SecurityInterstitialPageController::OpenReportingPrivacy)
+          .SetMethod("openWhitepaper",
+                     &SecurityInterstitialPageController::OpenWhitepaper)
+          .SetMethod("reportPhishingError",
+                     &SecurityInterstitialPageController::ReportPhishingError);
+}
diff --git a/chrome/renderer/ssl/ssl_certificate_error_page_controller.h b/chrome/renderer/security_interstitials/security_interstitial_page_controller.h
similarity index 76%
rename from chrome/renderer/ssl/ssl_certificate_error_page_controller.h
rename to chrome/renderer/security_interstitials/security_interstitial_page_controller.h
index b550222..967a9e7 100644
--- a/chrome/renderer/ssl/ssl_certificate_error_page_controller.h
+++ b/chrome/renderer/security_interstitials/security_interstitial_page_controller.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_RENDERER_SSL_SSL_CERTIFICATE_ERROR_PAGE_CONTROLLER_H_
-#define CHROME_RENDERER_SSL_SSL_CERTIFICATE_ERROR_PAGE_CONTROLLER_H_
+#ifndef CHROME_RENDERER_SECURITY_INTERSTITIALS_SECURITY_INTERSTITIAL_PAGE_CONTROLLER_H_
+#define CHROME_RENDERER_SECURITY_INTERSTITIALS_SECURITY_INTERSTITIAL_PAGE_CONTROLLER_H_
 
 #include "base/memory/weak_ptr.h"
 #include "components/security_interstitials/core/controller_client.h"
@@ -16,8 +16,8 @@
 // This class makes various helper functions available to interstitials
 // when committed interstitials are on. It is bound to the JavaScript
 // window.certificateErrorPageController object.
-class SSLCertificateErrorPageController
-    : public gin::Wrappable<SSLCertificateErrorPageController> {
+class SecurityInterstitialPageController
+    : public gin::Wrappable<SecurityInterstitialPageController> {
  public:
   static gin::WrapperInfo kWrapperInfo;
 
@@ -40,8 +40,8 @@
                       base::WeakPtr<Delegate> delegate);
 
  private:
-  explicit SSLCertificateErrorPageController(base::WeakPtr<Delegate> delegate);
-  ~SSLCertificateErrorPageController() override;
+  explicit SecurityInterstitialPageController(base::WeakPtr<Delegate> delegate);
+  ~SecurityInterstitialPageController() override;
 
   void DontProceed();
   void Proceed();
@@ -65,7 +65,7 @@
 
   base::WeakPtr<Delegate> const delegate_;
 
-  DISALLOW_COPY_AND_ASSIGN(SSLCertificateErrorPageController);
+  DISALLOW_COPY_AND_ASSIGN(SecurityInterstitialPageController);
 };
 
-#endif  // CHROME_RENDERER_SSL_SSL_CERTIFICATE_ERROR_PAGE_CONTROLLER_H_
+#endif  // CHROME_RENDERER_SECURITY_INTERSTITIALS_SECURITY_INTERSTITIAL_PAGE_CONTROLLER_H_
diff --git a/chrome/renderer/ssl/ssl_certificate_error_page_controller.cc b/chrome/renderer/ssl/ssl_certificate_error_page_controller.cc
deleted file mode 100644
index a5516d60..0000000
--- a/chrome/renderer/ssl/ssl_certificate_error_page_controller.cc
+++ /dev/null
@@ -1,144 +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 "chrome/renderer/ssl/ssl_certificate_error_page_controller.h"
-
-#include "components/security_interstitials/core/controller_client.h"
-#include "content/public/renderer/render_frame.h"
-#include "gin/handle.h"
-#include "gin/object_template_builder.h"
-#include "third_party/blink/public/web/blink.h"
-#include "third_party/blink/public/web/web_local_frame.h"
-
-gin::WrapperInfo SSLCertificateErrorPageController::kWrapperInfo = {
-    gin::kEmbedderNativeGin};
-
-SSLCertificateErrorPageController::Delegate::~Delegate() {}
-
-void SSLCertificateErrorPageController::Install(
-    content::RenderFrame* render_frame,
-    base::WeakPtr<Delegate> delegate) {
-  v8::Isolate* isolate = blink::MainThreadIsolate();
-  v8::HandleScope handle_scope(isolate);
-  v8::Local<v8::Context> context =
-      render_frame->GetWebFrame()->MainWorldScriptContext();
-  if (context.IsEmpty())
-    return;
-
-  v8::Context::Scope context_scope(context);
-
-  gin::Handle<SSLCertificateErrorPageController> controller = gin::CreateHandle(
-      isolate, new SSLCertificateErrorPageController(delegate));
-  if (controller.IsEmpty())
-    return;
-
-  v8::Local<v8::Object> global = context->Global();
-  global->Set(gin::StringToV8(isolate, "certificateErrorPageController"),
-              controller.ToV8());
-}
-
-SSLCertificateErrorPageController::SSLCertificateErrorPageController(
-    base::WeakPtr<Delegate> delegate)
-    : delegate_(delegate) {}
-
-SSLCertificateErrorPageController::~SSLCertificateErrorPageController() {}
-
-void SSLCertificateErrorPageController::DontProceed() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_DONT_PROCEED);
-}
-
-void SSLCertificateErrorPageController::Proceed() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_PROCEED);
-}
-
-void SSLCertificateErrorPageController::ShowMoreSection() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_SHOW_MORE_SECTION);
-}
-
-void SSLCertificateErrorPageController::OpenHelpCenter() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_OPEN_HELP_CENTER);
-}
-
-void SSLCertificateErrorPageController::OpenDiagnostic() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_DIAGNOSTIC);
-}
-
-void SSLCertificateErrorPageController::Reload() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_RELOAD);
-}
-
-void SSLCertificateErrorPageController::OpenDateSettings() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_OPEN_DATE_SETTINGS);
-}
-
-void SSLCertificateErrorPageController::OpenLogin() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_LOGIN);
-}
-
-void SSLCertificateErrorPageController::DoReport() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_DO_REPORT);
-}
-
-void SSLCertificateErrorPageController::DontReport() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_DONT_REPORT);
-}
-
-void SSLCertificateErrorPageController::OpenReportingPrivacy() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_OPEN_REPORTING_PRIVACY);
-}
-
-void SSLCertificateErrorPageController::OpenWhitepaper() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_WHITEPAPER);
-}
-
-void SSLCertificateErrorPageController::ReportPhishingError() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_REPORT_PHISHING_ERROR);
-}
-
-void SSLCertificateErrorPageController::SendCommand(
-    security_interstitials::SecurityInterstitialCommand command) {
-  if (delegate_) {
-    delegate_->SendCommand(command);
-  }
-}
-
-gin::ObjectTemplateBuilder
-SSLCertificateErrorPageController::GetObjectTemplateBuilder(
-    v8::Isolate* isolate) {
-  return gin::Wrappable<SSLCertificateErrorPageController>::
-      GetObjectTemplateBuilder(isolate)
-          .SetMethod("dontProceed",
-                     &SSLCertificateErrorPageController::DontProceed)
-          .SetMethod("proceed", &SSLCertificateErrorPageController::Proceed)
-          .SetMethod("showMoreSection",
-                     &SSLCertificateErrorPageController::ShowMoreSection)
-          .SetMethod("openHelpCenter",
-                     &SSLCertificateErrorPageController::OpenHelpCenter)
-          .SetMethod("openDiagnostic",
-                     &SSLCertificateErrorPageController::OpenDiagnostic)
-          .SetMethod("reload", &SSLCertificateErrorPageController::Reload)
-          .SetMethod("openDateSettings",
-                     &SSLCertificateErrorPageController::OpenDateSettings)
-          .SetMethod("openLogin", &SSLCertificateErrorPageController::OpenLogin)
-          .SetMethod("doReport", &SSLCertificateErrorPageController::DoReport)
-          .SetMethod("dontReport",
-                     &SSLCertificateErrorPageController::DontReport)
-          .SetMethod("openReportingPrivacy",
-                     &SSLCertificateErrorPageController::OpenReportingPrivacy)
-          .SetMethod("openWhitepaper",
-                     &SSLCertificateErrorPageController::OpenWhitepaper)
-          .SetMethod("reportPhishingError",
-                     &SSLCertificateErrorPageController::ReportPhishingError);
-}
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2c82510a..6450d4c 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -831,6 +831,7 @@
       "../browser/previews/lazyload_browsertest.cc",
       "../browser/previews/previews_browsertest.cc",
       "../browser/previews/previews_lite_page_browsertest.cc",
+      "../browser/previews/previews_oneplatform_hints_browsertest.cc",
       "../browser/previews/previews_service_browser_test.cc",
       "../browser/previews/resource_loading_hints/resource_loading_hints_browsertest.cc",
       "../browser/process_singleton_browsertest.cc",
@@ -1820,6 +1821,8 @@
         "../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/test/login_screen_tester.cc",
+        "../browser/chromeos/login/test/login_screen_tester.h",
         "../browser/chromeos/login/login_auth_recorder_browsertest.cc",
         "../browser/chromeos/login/login_browsertest.cc",
         "../browser/chromeos/login/login_manager_test.cc",
@@ -3798,6 +3801,7 @@
       "../browser/extensions/api/identity/identity_api_unittest.cc",
       "../browser/extensions/api/identity/identity_mint_queue_unittest.cc",
       "../browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc",
+      "../browser/extensions/api/image_writer_private/image_writer_private_api_unittest.cc",
       "../browser/extensions/api/image_writer_private/operation_manager_unittest.cc",
       "../browser/extensions/api/image_writer_private/operation_unittest.cc",
       "../browser/extensions/api/image_writer_private/removable_storage_provider_chromeos_unittest.cc",
@@ -4242,6 +4246,9 @@
       "//components/safe_browsing/db:v4_test_util",
       "//components/safe_browsing/renderer:websocket_sb_handshake_throttle_unittest",
     ]
+  } else if (safe_browsing_mode == 2) {
+    sources += [ "../browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc" ]
+    deps += []
   }
 
   if (enable_plugins) {
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
index 6ac22f0f..7d3ce88 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
@@ -18,7 +18,7 @@
 import org.chromium.chrome.browser.suggestions.TileSource;
 import org.chromium.chrome.browser.suggestions.TileTitleSource;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.touchless.TouchlessNewTabPage;
+import org.chromium.chrome.browser.touchless.TouchlessDelegate;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.test.util.AccountHolder;
@@ -49,7 +49,7 @@
             public boolean isSatisfied() {
                 if (!tab.isIncognito()) {
                     if (FeatureUtilities.isNoTouchModeEnabled()) {
-                        return tab.getNativePage() instanceof TouchlessNewTabPage;
+                        return TouchlessDelegate.isTouchlessNewTabPage(tab.getNativePage());
                     }
                     // TODO(tedchoc): Make MostVisitedPage also have a isLoaded() concept.
                     if (tab.getNativePage() instanceof NewTabPage) {
diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h
index 0bad1a5..d6ac6e9 100644
--- a/chrome/test/base/test_browser_window.h
+++ b/chrome/test/base/test_browser_window.h
@@ -72,7 +72,7 @@
                           int reason) override {}
   void OnTabDetached(content::WebContents* contents, bool was_active) override {
   }
-  void OnTabRestoredFromMenu(int command_id) override {}
+  void OnTabRestored(int command_id) override {}
   void ZoomChangedForActiveTab(bool can_show_bubble) override {}
   gfx::Rect GetRestoredBounds() const override;
   ui::WindowShowState GetRestoredState() const override;
diff --git a/chrome/test/chromedriver/README.txt b/chrome/test/chromedriver/README.txt
index eaddce6..bf02132 100644
--- a/chrome/test/chromedriver/README.txt
+++ b/chrome/test/chromedriver/README.txt
@@ -1,8 +1,9 @@
 This file contains high-level info about how ChromeDriver works and how to
 contribute.
 
-ChromeDriver is an implementation of the WebDriver standard,
-which allows users to automate testing of their website across browsers.
+ChromeDriver is an implementation of the WebDriver standard
+(https://w3c.github.io/webdriver/), which allows users to automate testing of
+their website across browsers.
 
 See the user site at https://sites.google.com/a/chromium.org/chromedriver/
 
@@ -11,7 +12,10 @@
 create an executable binary in the build folder named
 'chromedriver[.exe]'.
 
-Once built, ChromeDriver can be used interactively with python.
+Once built, ChromeDriver can be used with various third-party libraries that
+support WebDriver protocol, including language bindings provided by Selenium.
+
+For testing purposes, ChromeDriver can be used interactively with python.
 
 $ export PYTHONPATH=<THIS_DIR>/server:<THIS_DIR>/client
 $ python
@@ -25,7 +29,7 @@
 
 ChromeDriver will use the system installed Chrome by default.
 
-To use ChromeDriver2 with Chrome on Android pass the Android package name in the
+To use ChromeDriver with Chrome on Android pass the Android package name in the
 chromeOptions.androidPackage capability when creating the driver. The path to
 adb_commands.py and the adb tool from the Android SDK must be set in PATH. For
 more detailed instructions see the user site.
@@ -79,30 +83,25 @@
 Third party libraries used by chromedriver.
 
 =====Testing=====
-See the ChromeDriver waterfall at:
-    http://build.chromium.org/p/chromium.chromedriver/waterfall
-There are 4 test suites for verifying ChromeDriver's correctness:
+There are several test suites for verifying ChromeDriver's correctness:
 
-1) chromedriver_unittests (chrome/chrome_tests.gypi)
-This is the unittest target, which runs on the main waterfall on win/mac/linux
-and can close the tree. It is also run on the commit queue and try bots by
-default. Tests should take a few milliseconds and be very stable.
+* chromedriver_unittests
+This is the unittest target, which runs on the commit queue on win/mac/linux.
+Tests should take a few milliseconds and be very stable.
 
-2) chromedriver_tests (chrome/chrome_tests.gypi)
-This is a collection of C++ medium sized tests which can be run optionally
-on the trybots.
+* python integration tests (test/run_py_tests.py)
+These tests are maintained by the ChromeDriver team, and are intended to verify
+that ChromeDriver works correctly with Chrome. Run test/run_py_tests.py --help
+for more info. These are run on the commit queue on win/mac/linux.
 
-3) python integration tests
-Run test/run_py_tests.py --help for more info. These are only run on the
-ChromeDriver waterfall.
-
-4) WebDriver Java acceptance tests
+* WebDriver Java acceptance tests (test/run_java_tests.py)
 These are integration tests from the WebDriver open source project which can
-be run via test/run_java_tests.py. They are only run on the ChromeDriver
-bots. Run with --help for more info.
+be run via test/run_java_tests.py. They are not currently run on any bots, but
+will be included in the commit queue in the future. Run with --help for more
+info.
 
 =====Contributing=====
 Find an open issue and submit a patch for review by an individual listed in
 the OWNERS file in this directory. Issues are tracked in chromedriver's issue
 tracker:
-    https://code.google.com/p/chromedriver/issues/list
+    https://bugs.chromium.org/p/chromedriver/issues/list
diff --git a/chrome/test/chromedriver/archive.py b/chrome/test/chromedriver/archive.py
deleted file mode 100644
index e35c7e2..0000000
--- a/chrome/test/chromedriver/archive.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# Copyright (c) 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.
-
-"""Downloads items from the Chromium snapshot archive."""
-
-import json
-import os
-import re
-import urllib
-import urllib2
-
-import util
-
-_SITE = 'http://commondatastorage.googleapis.com'
-GS_GIT_LOG_URL = (
-    'https://chromium.googlesource.com/chromium/src/+/%s?format=json')
-GS_SEARCH_PATTERN = r'Cr-Commit-Position: refs/heads/master@{#(\d+)}'
-CR_REV_URL = 'https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/%s'
-
-
-class Site(object):
-  CHROMIUM_SNAPSHOT = _SITE + '/chromium-browser-snapshots'
-
-
-def GetLatestRevision():
-  """Returns the latest revision (as a string) available for this platform.
-
-  Args:
-    site: the archive site to check against, default to the snapshot one.
-  """
-  url = '%s/%s/LAST_CHANGE' % (GetDownloadSite(), _GetDownloadPlatform())
-  return urllib.urlopen(url).read()
-
-
-def DownloadChrome(revision, dest_dir, site=Site.CHROMIUM_SNAPSHOT):
-  """Downloads the packaged Chrome from the archive to the given directory.
-
-  Args:
-    revision: the revision of Chrome to download.
-    dest_dir: the directory to download Chrome to.
-    site: the archive site to download from, default to the snapshot one.
-
-  Returns:
-    The path to the unzipped Chrome binary.
-  """
-  def GetZipName(revision):
-    if util.IsWindows():
-      return revision + (
-          '/chrome-win.zip' if int(revision) > 591478 else '/chrome-win32.zip')
-    elif util.IsMac():
-      return revision + '/chrome-mac.zip'
-    elif util.IsLinux():
-      return revision + '/chrome-linux.zip'
-
-  def GetDirName(revision):
-    if util.IsWindows():
-      return 'chrome-win' if int(revision) > 591478 else 'chrome-win32'
-    elif util.IsMac():
-      return 'chrome-mac'
-    elif util.IsLinux():
-      return 'chrome-linux'
-
-  def GetChromePathFromPackage():
-    if util.IsWindows():
-      return 'chrome.exe'
-    elif util.IsMac():
-      return 'Chromium.app/Contents/MacOS/Chromium'
-    elif util.IsLinux():
-      return 'chrome-wrapper'
-
-  zip_path = os.path.join(dest_dir, 'chrome-%s.zip' % revision)
-  if not os.path.exists(zip_path):
-    url = site + '/%s/%s' % (_GetDownloadPlatform(), GetZipName(revision))
-    print 'Downloading', url, '...'
-    urllib.urlretrieve(url, zip_path)
-  util.Unzip(zip_path, dest_dir)
-  return os.path.join(dest_dir, GetDirName(revision),
-                      GetChromePathFromPackage())
-
-
-def _GetDownloadPlatform():
-  """Returns the name for this platform on the archive site."""
-  if util.IsWindows():
-    return 'Win'
-  elif util.IsMac():
-    return 'Mac'
-  elif util.IsLinux():
-    return 'Linux_x64'
-
-
-def GetLatestSnapshotPosition():
-  """Returns the latest commit position of snapshot build."""
-  latest_revision = GetLatestRevision()
-  return latest_revision
-
-
-def GetDownloadSite():
-  """Returns the site to download snapshot build according to the platform."""
-  return Site.CHROMIUM_SNAPSHOT
-
-
-def GetCommitPositionFromGitHash(snapshot_hashcode):
-  json_url = GS_GIT_LOG_URL % snapshot_hashcode
-  try:
-    response = urllib2.urlopen(json_url)
-  except urllib2.HTTPError as error:
-    util.PrintAndFlush('HTTP Error %d' % error.getcode())
-    return None
-  except urllib2.URLError as error:
-    util.PrintAndFlush('URL Error %s' % error.message)
-    return None
-  data = json.loads(response.read()[4:])
-  if 'message' in data:
-    message = data['message'].split('\n')
-    message = [line for line in message if line.strip()]
-    search_pattern = re.compile(GS_SEARCH_PATTERN)
-    result = search_pattern.search(message[len(message)-1])
-    if result:
-      return result.group(1)
-  util.PrintAndFlush('Failed to get commit position number for %s' %
-                     snapshot_hashcode)
-  return None
-
-
-def _GetGitHashFromCommitPosition(commit_position):
-  json_url = CR_REV_URL % commit_position
-  try:
-    response = urllib2.urlopen(json_url)
-  except urllib2.HTTPError as error:
-    util.PrintAndFlush('HTTP Error %d' % error.getcode())
-    return None
-  except urllib2.URLError as error:
-    util.PrintAndFlush('URL Error %s' % error.message)
-    return None
-  data = json.loads(response.read())
-  if 'git_sha' in data:
-    return data['git_sha']
-  util.PrintAndFlush('Failed to get git hash for %s' % commit_position)
-  return None
-
-
-def _GetFirstBuildAfterBranch(branch_position):
-  latest_revision = GetLatestSnapshotPosition()
-  for commit_position in range(int(branch_position), int(latest_revision)):
-    git_hash = _GetGitHashFromCommitPosition(commit_position)
-    try:
-      _ = DownloadChrome(git_hash, util.MakeTempDir(), GetDownloadSite())
-      return git_hash
-    except:
-      continue
-  return None
diff --git a/chrome/test/chromedriver/chrome/ui_events.cc b/chrome/test/chromedriver/chrome/ui_events.cc
index 2e7e6db9..c44ebfd 100644
--- a/chrome/test/chromedriver/chrome/ui_events.cc
+++ b/chrome/test/chromedriver/chrome/ui_events.cc
@@ -23,6 +23,7 @@
       modifiers(modifiers),
       buttons(buttons),
       click_count(click_count),
+      origin(kViewPort),
       element_id(std::string()),
       pointer_type(kMouse) {}
 
@@ -34,12 +35,14 @@
     : type(type),
       x(x),
       y(y),
+      origin(kViewPort),
       radiusX(1.0),
       radiusY(1.0),
       rotationAngle(0.0),
       force(1.0),
       id(0),
-      element_id(std::string()) {}
+      element_id(std::string()),
+      dispatch(true) {}
 
 TouchEvent::TouchEvent(const TouchEvent& other) = default;
 
diff --git a/chrome/test/chromedriver/chrome/ui_events.h b/chrome/test/chromedriver/chrome/ui_events.h
index cfa3201e..dc44d43 100644
--- a/chrome/test/chromedriver/chrome/ui_events.h
+++ b/chrome/test/chromedriver/chrome/ui_events.h
@@ -32,6 +32,9 @@
 // Specifies the event's pointer type.
 enum PointerType { kMouse = 0, kPen };
 
+// Specifies the origin of pointer location.
+enum OriginType { kViewPort, kPointer, kElement };
+
 struct MouseEvent {
   MouseEvent(MouseEventType type,
              MouseButton button,
@@ -51,6 +54,7 @@
   int buttons;
   // |click_count| should not be negative.
   int click_count;
+  OriginType origin;
   std::string element_id;
   PointerType pointer_type;
 };
@@ -74,12 +78,14 @@
   TouchEventType type;
   int x;
   int y;
+  OriginType origin;
   double radiusX;
   double radiusY;
   double rotationAngle;
   double force;
   int id;
   std::string element_id;
+  bool dispatch;
 };
 
 // Specifies the type of the keyboard event.
diff --git a/chrome/test/chromedriver/run_buildbot_steps.py b/chrome/test/chromedriver/run_buildbot_steps.py
deleted file mode 100755
index 8844b520..0000000
--- a/chrome/test/chromedriver/run_buildbot_steps.py
+++ /dev/null
@@ -1,302 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 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.
-
-"""Runs all the buildbot steps for ChromeDriver except for update/compile."""
-
-import glob
-import json
-import optparse
-import os
-import sys
-import tempfile
-import time
-
-_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
-GS_CHROMEDRIVER_DATA_BUCKET = 'gs://chromedriver-data'
-GS_CONTINUOUS_URL = GS_CHROMEDRIVER_DATA_BUCKET + '/continuous'
-GS_PREBUILTS_URL = GS_CHROMEDRIVER_DATA_BUCKET + '/prebuilts'
-GS_SERVER_LOGS_URL = GS_CHROMEDRIVER_DATA_BUCKET + '/server_logs'
-SERVER_LOGS_LINK = (
-    'http://chromedriver-data.storage.googleapis.com/server_logs')
-TEST_LOG_FORMAT = '%s_log.json'
-
-SCRIPT_DIR = os.path.join(_THIS_DIR, os.pardir, os.pardir, os.pardir, os.pardir,
-                          os.pardir, os.pardir, os.pardir, 'scripts')
-SITE_CONFIG_DIR = os.path.join(_THIS_DIR, os.pardir, os.pardir, os.pardir,
-                               os.pardir, os.pardir, os.pardir, os.pardir,
-                               'site_config')
-sys.path.append(SCRIPT_DIR)
-sys.path.append(SITE_CONFIG_DIR)
-
-import archive
-import chrome_paths
-from slave import gsutil_download
-from slave import slave_utils
-import util
-
-
-def _ArchivePrebuilts(commit_position):
-  """Uploads the prebuilts to google storage."""
-  util.MarkBuildStepStart('archive prebuilts')
-  zip_path = util.Zip(os.path.join(chrome_paths.GetBuildDir(['chromedriver']),
-                                   'chromedriver'))
-  if slave_utils.GSUtilCopy(
-      zip_path,
-      '%s/%s' % (GS_PREBUILTS_URL, 'r%s.zip' % commit_position)):
-    util.MarkBuildStepError()
-
-
-def _ArchiveServerLogs():
-  """Uploads chromedriver server logs to google storage."""
-  util.MarkBuildStepStart('archive chromedriver server logs')
-  pathname_pattern = os.path.join(tempfile.gettempdir(), 'chromedriver_log_*')
-  print 'archiving logs from: %s' % pathname_pattern
-  for server_log in glob.glob(pathname_pattern):
-    if os.path.isfile(server_log):
-      base_name = os.path.basename(server_log)
-      util.AddLink(base_name, '%s/%s' % (SERVER_LOGS_LINK, base_name))
-      slave_utils.GSUtilCopy(
-          server_log,
-          '%s/%s' % (GS_SERVER_LOGS_URL, base_name),
-          mimetype='text/plain')
-
-
-def _DownloadPrebuilts():
-  """Downloads the most recent prebuilts from google storage."""
-  util.MarkBuildStepStart('Download latest chromedriver')
-
-  zip_path = os.path.join(util.MakeTempDir(), 'build.zip')
-  if gsutil_download.DownloadLatestFile(GS_PREBUILTS_URL,
-                                        GS_PREBUILTS_URL + '/r',
-                                        zip_path):
-    util.MarkBuildStepError()
-
-  util.Unzip(zip_path, chrome_paths.GetBuildDir(['host_forwarder']))
-
-
-def _GetTestResultsLog(platform):
-  """Gets the test results log for the given platform.
-
-  Args:
-    platform: The platform that the test results log is for.
-
-  Returns:
-    A dictionary where the keys are commit positions and the values are booleans
-    indicating whether the tests passed.
-  """
-  (temp_fd, temp_log) = tempfile.mkstemp()
-  os.close(temp_fd)
-  log_name = TEST_LOG_FORMAT % platform
-  result = slave_utils.GSUtilDownloadFile(
-      '%s/%s' % (GS_CHROMEDRIVER_DATA_BUCKET, log_name), temp_log)
-  if result:
-    return {}
-  with open(temp_log, 'rb') as log_file:
-    json_dict = json.load(log_file)
-  # Workaround for json encoding dictionary keys as strings.
-  return dict([(int(v[0]), v[1]) for v in json_dict.items()])
-
-
-def _PutTestResultsLog(platform, test_results_log):
-  """Pushes the given test results log to google storage."""
-  temp_dir = util.MakeTempDir()
-  log_name = TEST_LOG_FORMAT % platform
-  log_path = os.path.join(temp_dir, log_name)
-  with open(log_path, 'wb') as log_file:
-    json.dump(test_results_log, log_file)
-  if slave_utils.GSUtilCopyFile(log_path, GS_CHROMEDRIVER_DATA_BUCKET):
-    raise Exception('Failed to upload test results log to google storage')
-
-
-def _UpdateTestResultsLog(platform, commit_position, passed):
-  """Updates the test results log for the given platform.
-
-  Args:
-    platform: The platform name.
-    commit_position: The commit position number.
-    passed: Boolean indicating whether the tests passed at this commit position.
-  """
-
-  assert commit_position.isdigit(), 'The commit position must be a number'
-  commit_position = int(commit_position)
-  log = _GetTestResultsLog(platform)
-  if len(log) > 500:
-    del log[min(log.keys())]
-  assert commit_position not in log, \
-      'Results already exist for commit position %s' % commit_position
-  log[commit_position] = bool(passed)
-  _PutTestResultsLog(platform, log)
-
-
-def _GetSupportedChromeVersions():
-  """Get the minimum and maximum supported Chrome versions.
-
-  Returns:
-    A tuple of the form (min_version, max_version).
-  """
-  # Minimum supported Chrome version is embedded as:
-  # const int kMinimumSupportedChromeVersion[] = {27, 0, 1453, 0};
-  with open(os.path.join(_THIS_DIR, 'chrome', 'version.cc'), 'r') as f:
-    lines = f.readlines()
-    chrome_min_version_line = [
-        x for x in lines if 'kMinimumSupportedChromeVersion' in x]
-  chrome_min_version = chrome_min_version_line[0].split('{')[1].split(',')[0]
-  with open(os.path.join(chrome_paths.GetSrc(), 'chrome', 'VERSION'), 'r') as f:
-    chrome_max_version = f.readlines()[0].split('=')[1].strip()
-  return (chrome_min_version, chrome_max_version)
-
-
-def _ArchiveGoodBuild(platform, commit_position):
-  """Archive chromedriver binary if the build is green."""
-  assert platform != 'android'
-  util.MarkBuildStepStart('archive build')
-
-  server_name = 'chromedriver'
-  if util.IsWindows():
-    server_name += '.exe'
-  zip_path = util.Zip(os.path.join(chrome_paths.GetBuildDir([server_name]),
-                                   server_name))
-
-  build_name = 'chromedriver_%s_%s.zip' % (
-      platform, commit_position)
-  build_url = '%s/%s' % (GS_CONTINUOUS_URL, build_name)
-  if slave_utils.GSUtilCopy(zip_path, build_url):
-    util.MarkBuildStepError()
-
-  if util.IsWindows():
-    zip_path = util.Zip(os.path.join(
-        chrome_paths.GetBuildDir([server_name + '.pdb']), server_name + '.pdb'))
-    pdb_name = 'chromedriver_%s_pdb_%s.zip' % (
-        platform, commit_position)
-    pdb_url = '%s/%s' % (GS_CONTINUOUS_URL, pdb_name)
-    if slave_utils.GSUtilCopy(zip_path, pdb_url):
-      util.MarkBuildStepError()
-
-  (latest_fd, latest_file) = tempfile.mkstemp()
-  os.write(latest_fd, build_name)
-  os.close(latest_fd)
-  latest_url = '%s/latest_%s' % (GS_CONTINUOUS_URL, platform)
-  if slave_utils.GSUtilCopy(latest_file, latest_url, mimetype='text/plain'):
-    util.MarkBuildStepError()
-  os.remove(latest_file)
-
-
-def _WaitForLatestSnapshot(commit_position):
-  util.MarkBuildStepStart('wait_for_snapshot')
-  for attempt in range(0, 200):
-    snapshot_position = archive.GetLatestSnapshotPosition()
-    if commit_position is not None and snapshot_position is not None:
-      if int(snapshot_position) >= int(commit_position):
-        break
-      util.PrintAndFlush('Waiting for snapshot >= %s, found %s' %
-                         (commit_position, snapshot_position))
-    time.sleep(60)
-  if int(snapshot_position) < int(commit_position):
-    raise Exception('Failed to find a snapshot version >= %s' % commit_position)
-  util.PrintAndFlush('Got snapshot commit position %s' % snapshot_position)
-
-
-def _AddToolsToPath(platform_name):
-  """Add some tools like Ant and Java to PATH for testing steps to use."""
-  paths = []
-  error_message = ''
-  if platform_name == 'win32':
-    paths = [
-        # Path to Ant and Java, required for the java acceptance tests.
-        'C:\\Program Files (x86)\\Java\\ant\\bin',
-        'C:\\Program Files (x86)\\Java\\jre\\bin',
-    ]
-    error_message = ('Java test steps will fail as expected and '
-                     'they can be ignored.\n'
-                     'Ant, Java or others might not be installed on bot.\n'
-                     'Please refer to page "WATERFALL" on site '
-                     'go/chromedriver.')
-  if paths:
-    util.MarkBuildStepStart('Add tools to PATH')
-    path_missing = False
-    for path in paths:
-      if not os.path.isdir(path) or not os.listdir(path):
-        print 'Directory "%s" is not found or empty.' % path
-        path_missing = True
-    if path_missing:
-      print error_message
-      util.MarkBuildStepError()
-      return
-    os.environ['PATH'] += os.pathsep + os.pathsep.join(paths)
-
-
-def main():
-  parser = optparse.OptionParser()
-  parser.add_option(
-      '', '--android-packages',
-      help=('Comma separated list of application package names, '
-            'if running tests on Android.'))
-  parser.add_option(
-      '-r', '--revision', help='Chromium git revision hash')
-  parser.add_option(
-      '', '--update-log', action='store_true',
-      help='Update the test results log (only applicable to Android)')
-  options, _ = parser.parse_args()
-
-  bitness = '32'
-  if util.Is64Bit():
-    bitness = '64'
-  platform = '%s%s' % (util.GetPlatformName(), bitness)
-  if options.android_packages:
-    platform = 'android'
-
-  if not options.revision:
-    commit_position = None
-  else:
-    commit_position = archive.GetCommitPositionFromGitHash(options.revision)
-    if commit_position is None:
-      raise Exception('Failed to convert revision to commit position')
-
-  if platform == 'android':
-    if not options.revision and options.update_log:
-      parser.error('Must supply a --revision with --update-log')
-    _DownloadPrebuilts()
-  else:
-    if not options.revision:
-      parser.error('Must supply a --revision')
-    if platform == 'linux64':
-      _ArchivePrebuilts(commit_position)
-    _WaitForLatestSnapshot(commit_position)
-
-  _AddToolsToPath(platform)
-
-  cmd = [
-      sys.executable,
-      os.path.join(_THIS_DIR, 'test', 'run_all_tests.py'),
-  ]
-  if platform == 'android':
-    cmd.append('--android-packages=' + options.android_packages)
-
-  passed = (util.RunCommand(cmd) == 0)
-
-  _ArchiveServerLogs()
-
-  if platform == 'android':
-    if options.update_log:
-      util.MarkBuildStepStart('update test result log')
-      _UpdateTestResultsLog(platform, commit_position, passed)
-  elif passed:
-    _ArchiveGoodBuild(platform, commit_position)
-
-  if not passed:
-    # Make sure the build is red if there is some uncaught exception during
-    # running run_all_tests.py.
-    util.MarkBuildStepStart('run_all_tests.py')
-    util.MarkBuildStepError()
-
-  # Add a "cleanup" step so that errors from runtest.py or bb_device_steps.py
-  # (which invoke this script) are kept in their own build step.
-  util.MarkBuildStepStart('cleanup')
-
-  return 0 if passed else 1
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/chrome/test/chromedriver/test/run_all_tests.py b/chrome/test/chromedriver/test/run_all_tests.py
deleted file mode 100755
index cca595e..0000000
--- a/chrome/test/chromedriver/test/run_all_tests.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""Runs all ChromeDriver end to end tests."""
-
-import optparse
-import os
-import platform
-import shutil
-import sys
-import tempfile
-import traceback
-
-_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
-_PARENT_DIR = os.path.join(_THIS_DIR, os.pardir)
-sys.path.insert(0, _PARENT_DIR)
-
-import archive
-import chrome_paths
-import util
-
-sys.path.insert(0, os.path.join(chrome_paths.GetSrc(), 'build', 'android'))
-from pylib import constants
-
-
-def _GenerateTestCommand(script,
-                         chromedriver,
-                         chrome=None,
-                         android_package=None,
-                         verbose=False):
-  _, log_path = tempfile.mkstemp(prefix='chromedriver_log_')
-  print 'chromedriver server log: %s' % log_path
-  cmd = [
-      sys.executable,
-      os.path.join(_THIS_DIR, script),
-      '--chromedriver=%s' % chromedriver,
-      '--log-path=%s' % log_path,
-  ]
-
-  if chrome:
-    cmd.append('--chrome=' + chrome)
-
-  if verbose:
-    cmd.append('--verbose')
-
-  if android_package:
-    cmd = ['xvfb-run', '-a'] + cmd
-    cmd.append('--android-package=' + android_package)
-  return cmd
-
-
-def RunReplayTests(chromedriver, chrome):
-  util.MarkBuildStepStart('replay_tests')
-
-  _, log_path = tempfile.mkstemp(prefix='chromedriver_log_')
-  print 'chromedriver server log: %s' % log_path
-  cmd = [
-    sys.executable,
-    os.path.join(_PARENT_DIR, 'log_replay', 'client_replay_test.py'),
-    chromedriver,
-    chrome,
-    '--output-log-path=%s' % log_path
-  ]
-  code = util.RunCommand(cmd)
-
-  if code:
-    util.MarkBuildStepError()
-  return code
-
-
-def RunPythonTests(chromedriver,
-                   chrome=None,
-                   android_package=None):
-  util.MarkBuildStepStart('python_tests')
-  code = util.RunCommand(
-      _GenerateTestCommand('run_py_tests.py',
-                           chromedriver,
-                           chrome=chrome,
-                           android_package=android_package))
-  if code:
-    util.MarkBuildStepError()
-  return code
-
-
-def RunJavaTests(chromedriver, chrome=None,
-                 android_package=None,
-                 verbose=False):
-  util.MarkBuildStepStart('java_tests')
-  code = util.RunCommand(
-      _GenerateTestCommand('run_java_tests.py',
-                           chromedriver,
-                           chrome=chrome,
-                           android_package=android_package,
-                           verbose=verbose))
-  if code:
-    util.MarkBuildStepError()
-  return code
-
-
-def RunCppTests(cpp_tests):
-  util.MarkBuildStepStart('chromedriver_tests')
-  code = util.RunCommand([cpp_tests])
-  if code:
-    util.MarkBuildStepError()
-  return code
-
-
-def DownloadChrome(version_name, revision, download_site):
-  util.MarkBuildStepStart('download %s' % version_name)
-  try:
-    temp_dir = util.MakeTempDir()
-    return (temp_dir, archive.DownloadChrome(revision, temp_dir, download_site))
-  except Exception:
-    traceback.print_exc()
-    util.AddBuildStepText('Skip Java and Python tests')
-    util.MarkBuildStepError()
-    return (None, None)
-
-
-def _KillChromes():
-  chrome_map = {
-      'win': 'chrome.exe',
-      'mac': 'Chromium',
-      'linux': 'chrome',
-  }
-  if util.IsWindows():
-    cmd = ['taskkill', '/F', '/IM']
-  else:
-    cmd = ['killall', '-9']
-  cmd.append(chrome_map[util.GetPlatformName()])
-  util.RunCommand(cmd)
-
-
-def main():
-  parser = optparse.OptionParser()
-  parser.add_option(
-      '', '--android-packages',
-      help='Comma separated list of application package names, '
-           'if running tests on Android.')
-  options, _ = parser.parse_args()
-
-  exe_postfix = ''
-  if util.IsWindows():
-    exe_postfix = '.exe'
-  cpp_tests_name = 'chromedriver_tests' + exe_postfix
-  server_name = 'chromedriver' + exe_postfix
-
-  required_build_outputs = [server_name]
-  if not options.android_packages:
-    required_build_outputs += [cpp_tests_name]
-  try:
-    build_dir = chrome_paths.GetBuildDir(required_build_outputs)
-  except RuntimeError:
-    util.MarkBuildStepStart('check required binaries')
-    traceback.print_exc()
-    util.MarkBuildStepError()
-  constants.SetBuildType(os.path.basename(build_dir))
-  print 'Using build outputs from', build_dir
-
-  chromedriver = os.path.join(build_dir, server_name)
-  platform_name = util.GetPlatformName()
-  if util.IsLinux() and util.Is64Bit():
-    platform_name += '64'
-
-  if options.android_packages:
-    os.environ['PATH'] += os.pathsep + os.path.join(
-        _THIS_DIR, os.pardir, 'chrome')
-    code = 0
-    for package in options.android_packages.split(','):
-      code1 = RunPythonTests(chromedriver,
-                             chrome_version_name=package,
-                             android_package=package)
-      code2 = RunJavaTests(chromedriver,
-                           chrome_version_name=package,
-                           android_package=package,
-                           verbose=True)
-      code = code or code1 or code2
-    return code
-  else:
-    code = 0
-    download_site = archive.GetDownloadSite()
-    revision = archive.GetLatestRevision()
-    temp_dir, chrome_path = DownloadChrome(revision, revision,
-                                             download_site)
-    if not chrome_path:
-      code = 1
-    code1 = RunPythonTests(chromedriver,
-                           chrome=chrome_path)
-    code2 = RunJavaTests(chromedriver,
-                         verbose=True,
-                         chrome=chrome_path)
-    code3 = RunReplayTests(chromedriver,
-                           chrome=chrome_path)
-    code = code or code1 or code2 or code3
-    _KillChromes()
-    shutil.rmtree(temp_dir)
-    cpp_tests = os.path.join(build_dir, cpp_tests_name)
-    return RunCppTests(cpp_tests) or code
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/chrome/test/chromedriver/test/run_java_tests.py b/chrome/test/chromedriver/test/run_java_tests.py
index 53fc7d8..7e94247 100755
--- a/chrome/test/chromedriver/test/run_java_tests.py
+++ b/chrome/test/chromedriver/test/run_java_tests.py
@@ -3,13 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Runs the WebDriver Java acceptance tests.
-
-This script is called from chrome/test/chromedriver/run_all_tests.py and reports
-results using the buildbot annotation scheme.
-
-For ChromeDriver documentation, refer to http://code.google.com/p/chromedriver.
-"""
+"""Runs the WebDriver Java acceptance tests."""
 
 import optparse
 import os
diff --git a/chrome/test/chromedriver/test/waterfall_builder_config_sample.json b/chrome/test/chromedriver/test/waterfall_builder_config_sample.json
deleted file mode 100644
index 1ad85fe..0000000
--- a/chrome/test/chromedriver/test/waterfall_builder_config_sample.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{ 
-  "build_info": {
-    "win7" : {
-      "builder_url" : "http://build.chromium.org/p/chromium.chromedriver/builders/Win7/builds/",
-      "json_url" : "http://build.chromium.org/p/chromium.chromedriver/json/builders/Win7/builds/%d?as_text=1",
-      "builds_url": "http://build.chromium.org/p/chromium.chromedriver/json/builders/Win7/builds/_all?as_text=1"
-    },
-    "mac_10_6" : {
-      "builder_url" : "http://build.chromium.org/p/chromium.chromedriver/builders/Mac%2010.6/builds/",
-      "json_url" : "http://build.chromium.org/p/chromium.chromedriver/json/builders/Mac 10.6/builds/%d?as_text=1",
-      "builds_url": "http://build.chromium.org/p/chromium.chromedriver/json/builders/Mac 10.6/builds/_all?as_text=1"
-    },
-    "linux" : {
-      "builder_url" : "http://build.chromium.org/p/chromium.chromedriver/builders/Linux/builds/",
-      "json_url" : "http://build.chromium.org/p/chromium.chromedriver/json/builders/Linux/builds/%d?as_text=1",
-      "builds_url": "http://build.chromium.org/p/chromium.chromedriver/json/builders/Linux/builds/_all?as_text=1"
-    },
-    "linux32" : {
-      "builder_url" : "http://build.chromium.org/p/chromium.chromedriver/builders/Linux32/builds/",
-      "json_url" : "http://build.chromium.org/p/chromium.chromedriver/json/builders/Linux32/builds/%d?as_text=1",
-      "builds_url": "http://build.chromium.org/p/chromium.chromedriver/json/builders/Linux32/builds/_all?as_text=1"
-    },
-    "android_chromedriver_test" : {
-      "builder_url" : "http://build.chromium.org/p/chromium.fyi/builders/Android%20ChromeDriver%20Tests%20%28dbg%29/builds/",
-      "json_url" : "http://build.chromium.org/p/chromium.fyi/json/builders/Android ChromeDriver Tests (dbg)/builds/%d?as_text=1",
-      "builds_url": "http://build.chromium.org/p/chromium.fyi/json/builders/Android ChromeDriver Tests (dbg)/builds/_all?as_text=1"
-    }
-  }, 
-  "recipient_emails": "pshenoy@chromium.org", 
-  "sender_email": "pshenoy@chromium.org",
-  "old_build_days": 2
-}
diff --git a/chrome/test/chromedriver/test/waterfall_builder_monitor.py b/chrome/test/chromedriver/test/waterfall_builder_monitor.py
deleted file mode 100755
index ba8ec533..0000000
--- a/chrome/test/chromedriver/test/waterfall_builder_monitor.py
+++ /dev/null
@@ -1,226 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2014 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.
-
-"""Waterfall monitoring script.
-  This script checks all builders specified in the config file and sends
-  status email about any step failures in these builders. This also
-  reports a build as failure if the latest build on that builder was built
-  2 days back. (Number of days can be configured in the config file)
-
-  This script can be run as cronjob on a linux machine once a day and
-  get email notification for any waterfall specified in the config file.
-
-  Sample cronjob entry below. This entry will run the script everyday at 9 AM.
-  Include this in the crontab file.
-  0 9 * * *  <Path to script> --config <Path to json file>
-"""
-
-import datetime
-import json
-import optparse
-import sys
-import time
-import traceback
-import urllib
-
-from datetime import timedelta
-from email.mime.text import MIMEText
-from subprocess import Popen, PIPE
-
-
-SUCCESS_SUBJECT = ('[CHROME TESTING]: Builder status %s: PASSED.')
-FAILURE_SUBJECT = ('[CHROME TESTING]: Builder status %s: FAILED %d out of %d')
-EXCEPTION_SUBJECT = ('Exception occurred running waterfall_builder_monitor.py '
-                     'script')
-
-
-def GetTimeDelta(date, days):
-  if isinstance(date, datetime.datetime):
-    return date + timedelta(days)
-
-
-def GetDateFromEpochFormat(epoch_time):
-  last_build_date = time.localtime(epoch_time)
-  last_build_date = datetime.datetime(int(last_build_date.tm_year),
-                                      int(last_build_date.tm_mon),
-                                      int(last_build_date.tm_mday),
-                                      int(last_build_date.tm_hour),
-                                      int(last_build_date.tm_min),
-                                      int(last_build_date.tm_sec))
-  return last_build_date
-
-
-def GetJSONData(json_url):
-  response = urllib.urlopen(json_url)
-  if response.getcode() == 200:
-    try:
-      data = json.loads(response.read())
-    except ValueError:
-      print 'ValueError for JSON URL: %s' % json_url
-      raise
-  else:
-    raise Exception('Error from URL: %s' % json_url)
-  response.close()
-  return data
-
-
-def SendEmailViaSendmailCommand(sender_email, recipient_emails,
-                                subject, email_body):
-  msg = MIMEText(email_body)
-  msg["From"] = sender_email
-  msg["To"] = recipient_emails
-  msg["Subject"] = subject
-  pipe = Popen(["/usr/sbin/sendmail", "-t"], stdin=PIPE)
-  pipe.communicate(msg.as_string())
-
-
-def SendStatusEmailViaSendmailCommand(consolidated_results,
-                                      recipient_emails,
-                                      sender_email):
-  failure_count = 0
-  for result in consolidated_results:
-    if result['error'] != 'passed' and not result['build_too_old']:
-      failure_count += 1
-  today = str(datetime.date.today()).replace('-', '/')[5:]
-  if failure_count == 0:
-    subject = SUCCESS_SUBJECT % today
-  else:
-    subject = FAILURE_SUBJECT % (today,
-                                 failure_count,
-                                 len(consolidated_results))
-
-  email_body = ''
-  for result in consolidated_results:
-    if result['error'] != 'passed' or result['build_too_old']:
-      if result['build_date'] is not None:
-        email_body += result['platform'] + ': ' +\
-                      result['build_link'] + ' ( Build too old: ' +\
-                      result['build_date'] + ' ) ' +'\n\n'
-      else:
-        email_body += result['platform'] + ': ' +\
-                      result['build_link'] + '\n\n'
-
-  SendEmailViaSendmailCommand(sender_email, recipient_emails,
-                              subject, email_body)
-
-
-def SendExceptionEmailViaSendmailCommand(exception_message_lines,
-                                         recipient_emails,
-                                         sender_email):
-  subject = EXCEPTION_SUBJECT
-  email_body = ''
-  email_body = '\n'.join(exception_message_lines)
-
-  SendEmailViaSendmailCommand(sender_email, recipient_emails,
-                              subject, email_body)
-
-
-class OfficialBuilderParser(object):
-  """This class implements basic utility functions on a specified builder."""
-  def __init__(self, builder_type, build_info):
-    self.platform = builder_type
-    self.builder_info = build_info
-    self.builder_url = build_info['builder_url']
-    self.build_json_url = build_info['json_url']
-    self.build = self._GetLatestBuildNumber()
-
-  def _GetLatestBuildNumber(self):
-    json_url = self.builder_info['builds_url']
-    data = GetJSONData(json_url)
-    # Get a sorted list of all the keys in the json data.
-    keys = sorted(map(int, data.keys()))
-    return self._GetLatestCompletedBuild(keys)
-
-  def _GetLatestCompletedBuild(self, keys):
-    reversed_list = keys[::-1]
-    for build in reversed_list:
-      data = self._GetJSONDataForBuild(build)
-      if data is not None:
-        if 'text' in data:
-          return build
-    return None
-
-  def _GetJSONDataForBuild(self, build):
-    if build is None:
-      return build
-    json_url = self.build_json_url % build
-    return GetJSONData(json_url)
-
-
-class GetBuilderStatus(OfficialBuilderParser):
-  def __init__(self, builder_type, build_info):
-    OfficialBuilderParser.__init__(self, builder_type, build_info)
-
-  def CheckForFailedSteps(self, days):
-    if self.build is None:
-      return {}
-    result = {'platform': self.platform,
-              'build_number': self.build,
-              'build_link': self.builder_url + str(self.build),
-              'build_date': None,
-              'build_too_old': False,
-              'error': 'unknown'}
-    data = self._GetJSONDataForBuild(self.build)
-    if data is not None:
-      if 'text' in data:
-        if 'build' in data['text'] and 'successful' in data['text']:
-          result['error'] = 'passed'
-        else:
-          if 'failed' in data['text'] or\
-             'exception' in data['text'] or\
-             'interrupted' in data['text']:
-            result['error'] = 'failed'
-      if 'times' in data:
-        old_date = GetTimeDelta(datetime.datetime.now(), days)
-        last_build_date = GetDateFromEpochFormat(data['times'][0])
-        if last_build_date < old_date:
-          result['build_too_old'] = True
-          result['build_date'] = str(last_build_date).split(' ')[0]
-    else:
-      raise Exception('There was some problem getting JSON data '
-                      'from URL: %s' % result['build_link'])
-    return result
-
-def main():
-  parser = optparse.OptionParser()
-  parser.add_option('--config', type='str',
-                    help='Absolute path to the config file.')
-
-  (options, _) = parser.parse_args()
-  if not options.config:
-    print 'Error: missing required parameter: --config'
-    parser.print_help()
-    return 1
-
-  try:
-    with open(options.config, 'r') as config_file:
-      try:
-        json_data = json.loads(config_file.read())
-      except ValueError:
-        print 'ValueError for loading JSON data from : %s' % options.config
-        raise ValueError
-
-    old_build_days = -2
-    if 'old_build_days' in json_data:
-      old_build_days = - json_data['old_build_days']
-    consolidated_results = []
-    for key in json_data['build_info'].keys():
-      builder_status = GetBuilderStatus(key, json_data['build_info'][key])
-      builder_result = builder_status.CheckForFailedSteps(old_build_days)
-      consolidated_results.append(builder_result)
-
-    SendStatusEmailViaSendmailCommand(consolidated_results,
-                                      json_data['recipient_emails'],
-                                      json_data['sender_email'])
-    return 0
-  except Exception:
-    formatted_lines = traceback.format_exc().splitlines()
-    SendExceptionEmailViaSendmailCommand(formatted_lines,
-                                         json_data['recipient_emails'],
-                                         json_data['sender_email'])
-    return 1
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/chrome/test/chromedriver/third_party/googlecode/LICENSE b/chrome/test/chromedriver/third_party/googlecode/LICENSE
deleted file mode 100644
index 80a4762..0000000
--- a/chrome/test/chromedriver/third_party/googlecode/LICENSE
+++ /dev/null
@@ -1,204 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2007-2009 Google Inc.
-   Copyright 2007-2009 WebDriver committers
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
diff --git a/chrome/test/chromedriver/third_party/googlecode/README.chromium b/chrome/test/chromedriver/third_party/googlecode/README.chromium
deleted file mode 100644
index dd64cfe9..0000000
--- a/chrome/test/chromedriver/third_party/googlecode/README.chromium
+++ /dev/null
@@ -1,18 +0,0 @@
-Name: Google code support upload script
-Short Name: googlecode_upload
-URL: http://support.googlecode.com/svn/trunk/scripts/googlecode_upload.py
-Version: r681
-Security Critical: no
-License: Apache 2
-License File: NOT_SHIPPED
-
-Description:
-A simple script from Google Code support for automating uploads.
-
-Local modifications:
--Added comment describing license. They have a comment about the license, but
-it uses an atypical format that isn't recognized by chromium's checklicense
-script.
--Instead of using the default netrc lookup which requires HOME to be set
-(which normally isn't on Windows), specify the file using os.path.expanduser.
--Fail instead of prompting for username/password if none is given.
diff --git a/chrome/test/chromedriver/third_party/googlecode/googlecode_upload.py b/chrome/test/chromedriver/third_party/googlecode/googlecode_upload.py
deleted file mode 100755
index dec4eed..0000000
--- a/chrome/test/chromedriver/third_party/googlecode/googlecode_upload.py
+++ /dev/null
@@ -1,240 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2006, 2007 Google Inc. All Rights Reserved.
-# Author: danderson@google.com (David Anderson)
-#
-# Script for uploading files to a Google Code project.
-#
-# This is intended to be both a useful script for people who want to
-# streamline project uploads and a reference implementation for
-# uploading files to Google Code projects.
-#
-# To upload a file to Google Code, you need to provide a path to the
-# file on your local machine, a small summary of what the file is, a
-# project name, and a valid account that is a member or owner of that
-# project.  You can optionally provide a list of labels that apply to
-# the file.  The file will be uploaded under the same name that it has
-# in your local filesystem (that is, the "basename" or last path
-# component).  Run the script with '--help' to get the exact syntax
-# and available options.
-#
-# Note that the upload script requests that you enter your
-# googlecode.com password.  This is NOT your Gmail account password!
-# This is the password you use on googlecode.com for committing to
-# Subversion and uploading files.  You can find your password by going
-# to http://code.google.com/hosting/settings when logged in with your
-# Gmail account. If you have already committed to your project's
-# Subversion repository, the script will automatically retrieve your
-# credentials from there (unless disabled, see the output of '--help'
-# for details).
-#
-# If you are looking at this script as a reference for implementing
-# your own Google Code file uploader, then you should take a look at
-# the upload() function, which is the meat of the uploader.  You
-# basically need to build a multipart/form-data POST request with the
-# right fields and send it to https://PROJECT.googlecode.com/files .
-# Authenticate the request using HTTP Basic authentication, as is
-# shown below.
-#
-# Licensed under the terms of the Apache Software License 2.0:
-#  http://www.apache.org/licenses/LICENSE-2.0
-#
-# Questions, comments, feature requests and patches are most welcome.
-# Please direct all of these to the Google Code users group:
-#  http://groups.google.com/group/google-code-hosting
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-
-"""Google Code file uploader script.
-"""
-
-__author__ = 'danderson@google.com (David Anderson)'
-
-import httplib
-import os.path
-import optparse
-import getpass
-import base64
-import sys
-
-
-def upload(file, project_name, user_name, password, summary, labels=None):
-  """Upload a file to a Google Code project's file server.
-
-  Args:
-    file: The local path to the file.
-    project_name: The name of your project on Google Code.
-    user_name: Your Google account name.
-    password: The googlecode.com password for your account.
-              Note that this is NOT your global Google Account password!
-    summary: A small description for the file.
-    labels: an optional list of label strings with which to tag the file.
-
-  Returns: a tuple:
-    http_status: 201 if the upload succeeded, something else if an
-                 error occured.
-    http_reason: The human-readable string associated with http_status
-    file_url: If the upload succeeded, the URL of the file on Google
-              Code, None otherwise.
-  """
-  # The login is the user part of user@gmail.com. If the login provided
-  # is in the full user@domain form, strip it down.
-  if user_name.endswith('@gmail.com'):
-    user_name = user_name[:user_name.index('@gmail.com')]
-
-  form_fields = [('summary', summary)]
-  if labels is not None:
-    form_fields.extend([('label', l.strip()) for l in labels])
-
-  content_type, body = encode_upload_request(form_fields, file)
-
-  upload_host = '%s.googlecode.com' % project_name
-  upload_uri = '/files'
-  auth_token = base64.b64encode('%s:%s'% (user_name, password))
-  headers = {
-    'Authorization': 'Basic %s' % auth_token,
-    'User-Agent': 'Googlecode.com uploader v0.9.4',
-    'Content-Type': content_type,
-    }
-
-  server = httplib.HTTPSConnection(upload_host)
-  server.request('POST', upload_uri, body, headers)
-  resp = server.getresponse()
-  server.close()
-
-  if resp.status == 201:
-    location = resp.getheader('Location', None)
-  else:
-    location = None
-  return resp.status, resp.reason, location
-
-
-def encode_upload_request(fields, file_path):
-  """Encode the given fields and file into a multipart form body.
-
-  fields is a sequence of (name, value) pairs. file is the path of
-  the file to upload. The file will be uploaded to Google Code with
-  the same file name.
-
-  Returns: (content_type, body) ready for httplib.HTTP instance
-  """
-  BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla'
-  CRLF = '\r\n'
-
-  body = []
-
-  # Add the metadata about the upload first
-  for key, value in fields:
-    body.extend(
-      ['--' + BOUNDARY,
-       'Content-Disposition: form-data; name="%s"' % key,
-       '',
-       value,
-       ])
-
-  # Now add the file itself
-  file_name = os.path.basename(file_path)
-  f = open(file_path, 'rb')
-  file_content = f.read()
-  f.close()
-
-  body.extend(
-    ['--' + BOUNDARY,
-     'Content-Disposition: form-data; name="filename"; filename="%s"'
-     % file_name,
-     # The upload server determines the mime-type, no need to set it.
-     'Content-Type: application/octet-stream',
-     '',
-     file_content,
-     ])
-
-  # Finalize the form body
-  body.extend(['--' + BOUNDARY + '--', ''])
-
-  return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body)
-
-
-def upload_find_auth(file_path, project_name, summary, labels=None,
-                     user_name=None, password=None, tries=3):
-  """Find credentials and upload a file to a Google Code project's file server.
-
-  file_path, project_name, summary, and labels are passed as-is to upload.
-
-  Args:
-    file_path: The local path to the file.
-    project_name: The name of your project on Google Code.
-    summary: A small description for the file.
-    labels: an optional list of label strings with which to tag the file.
-    config_dir: Path to Subversion configuration directory, 'none', or None.
-    user_name: Your Google account name.
-    tries: How many attempts to make.
-  """
-  if user_name is None or password is None:
-    from netrc import netrc
-    # Chromium edit: Works on windows without requiring HOME to be set.
-    netrc_path = os.path.join(os.path.expanduser('~'), '.netrc')
-    authenticators = netrc(netrc_path).authenticators("code.google.com")
-    if authenticators:
-      if user_name is None:
-        user_name = authenticators[0]
-      if password is None:
-        password = authenticators[2]
-
-  if user_name is None or password is None:
-    raise RuntimeError('Missing user credentials for upload')
-
-  return upload(file_path, project_name, user_name, password, summary, labels)
-
-
-def main():
-  parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY '
-                                 '-p PROJECT [options] FILE')
-  parser.add_option('-s', '--summary', dest='summary',
-                    help='Short description of the file')
-  parser.add_option('-p', '--project', dest='project',
-                    help='Google Code project name')
-  parser.add_option('-u', '--user', dest='user',
-                    help='Your Google Code username')
-  parser.add_option('-w', '--password', dest='password',
-                    help='Your Google Code password')
-  parser.add_option('-l', '--labels', dest='labels',
-                    help='An optional list of comma-separated labels to attach '
-                    'to the file')
-
-  options, args = parser.parse_args()
-
-  if not options.summary:
-    parser.error('File summary is missing.')
-  elif not options.project:
-    parser.error('Project name is missing.')
-  elif len(args) < 1:
-    parser.error('File to upload not provided.')
-  elif len(args) > 1:
-    parser.error('Only one file may be specified.')
-
-  file_path = args[0]
-
-  if options.labels:
-    labels = options.labels.split(',')
-  else:
-    labels = None
-
-  status, reason, url = upload_find_auth(file_path, options.project,
-                                         options.summary, labels,
-                                         options.user, options.password)
-  if url:
-    print 'The file was uploaded successfully.'
-    print 'URL: %s' % url
-    return 0
-  else:
-    print 'An error occurred. Your file was not uploaded.'
-    print 'Google Code upload server said: %s (%s)' % (reason, status)
-    return 1
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index 31f10966..283deeb8 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -1334,6 +1334,7 @@
       bool has_touch_start = false;
       int buttons = 0;
       std::string button_type;
+      OriginType origin_type = kPointer;
       std::string element_id;
       for (size_t j = 0; j < actions->GetSize(); j++) {
         const base::DictionaryValue* pointer_action;
@@ -1344,10 +1345,18 @@
           pointer_action->GetDouble("x", &x);
           pointer_action->GetDouble("y", &y);
           const base::DictionaryValue* origin_dict;
+          origin_type = kViewPort;
           element_id = "";
-          if (pointer_action->HasKey("origin") &&
-              pointer_action->GetDictionary("origin", &origin_dict)) {
-            origin_dict->GetString(GetElementKey(), &element_id);
+          if (pointer_action->HasKey("origin")) {
+            if (pointer_action->GetDictionary("origin", &origin_dict)) {
+              origin_type = kElement;
+              origin_dict->GetString(GetElementKey(), &element_id);
+            } else {
+              std::string origin;
+              pointer_action->GetString("origin", &origin);
+              if (origin == "pointer")
+                origin_type = kPointer;
+            }
           }
         }
 
@@ -1360,6 +1369,7 @@
           MouseEvent event(StringToMouseEventType(action_type),
                            StringToMouseButton(button_type), x, y, 0, buttons,
                            click_count);
+          event.origin = origin_type;
           event.element_id = element_id;
           event.pointer_type = StringToPointerType(pointer_type);
           mouse_events.push_back(event);
@@ -1373,11 +1383,12 @@
           else if (action_type == "pointerUp")
             has_touch_start = false;
 
-          if (action_type != "pointerMove" || has_touch_start) {
-            TouchEvent event(StringToTouchEventType(action_type), x, y);
-            event.element_id = element_id;
-            touch_events.push_back(event);
-          }
+          TouchEvent event(StringToTouchEventType(action_type), x, y);
+          event.origin = origin_type;
+          event.element_id = element_id;
+          if (action_type == "pointerMove")
+            event.dispatch = has_touch_start;
+          touch_events.push_back(event);
         }
       }
 
@@ -1399,7 +1410,8 @@
   size_t max_list_length =
       std::max({longest_mouse_list_size, longest_touch_list_size,
                 longest_key_list_size, tick_durations.size()});
-  std::map<std::string, gfx::Point> element_center_point;
+  std::vector<gfx::Point> mouse_locations(mouse_events_list.size());
+  std::vector<gfx::Point> touch_locations(touch_events_list.size());
   int key_modifiers = 0;
   for (size_t i = 0; i < max_list_length; i++) {
     std::list<KeyEvent> dispatch_key_events;
@@ -1428,18 +1440,21 @@
       if (i < mouse_events_list[j].size() &&
           mouse_events_list[j][i].type != kPauseMouseEventType) {
         MouseEvent event = mouse_events_list[j][i];
-        if (!event.element_id.empty()) {
-          if (event.type == kMovedMouseEventType ||
-              element_center_point.find(event.element_id) ==
-                  element_center_point.end()) {
+        if (event.type == kMovedMouseEventType) {
+          if (event.origin == kPointer) {
+            event.x += mouse_locations[j].x();
+            event.y += mouse_locations[j].y();
+          } else if (!event.element_id.empty()) {
             int center_x = 0, center_y = 0;
             ElementInViewCenter(session, web_view, event.element_id, &center_x,
                                 &center_y);
-            element_center_point[event.element_id] =
-                gfx::Point(center_x, center_y);
+            event.x += center_x;
+            event.y += center_y;
           }
-          event.x += element_center_point[event.element_id].x();
-          event.y += element_center_point[event.element_id].y();
+          mouse_locations[j] = gfx::Point(event.x, event.y);
+        } else {
+          event.x = mouse_locations[j].x();
+          event.y = mouse_locations[j].y();
         }
         event.modifiers = key_modifiers;
         dispatch_mouse_events.push_back(event);
@@ -1457,20 +1472,24 @@
       if (i < touch_events_list[j].size() &&
           touch_events_list[j][i].type != kPause) {
         TouchEvent event = touch_events_list[j][i];
-        if (!event.element_id.empty()) {
-          if (event.type == kTouchMove ||
-              element_center_point.find(event.element_id) ==
-                  element_center_point.end()) {
+        if (event.type == kTouchMove) {
+          if (event.origin == kPointer) {
+            event.x += touch_locations[j].x();
+            event.y += touch_locations[j].y();
+          } else if (!event.element_id.empty()) {
             int center_x = 0, center_y = 0;
             ElementInViewCenter(session, web_view, event.element_id, &center_x,
                                 &center_y);
-            element_center_point[event.element_id] =
-                gfx::Point(center_x, center_y);
+            event.x += center_x;
+            event.y += center_y;
           }
-          event.x += element_center_point[event.element_id].x();
-          event.y += element_center_point[event.element_id].y();
+          touch_locations[j] = gfx::Point(event.x, event.y);
+        } else {
+          event.x = touch_locations[j].x();
+          event.y = touch_locations[j].y();
         }
-        dispatch_touch_events.push_back(event);
+        if (event.dispatch)
+          dispatch_touch_events.push_back(event);
       }
     }
     if (dispatch_touch_events.size() > 0) {
diff --git a/chrome/test/data/android/render_tests/ExploreSitesPageTest.initial_layout.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ExploreSitesPageTest.initial_layout.Nexus_5-19.png.sha1
index a36c180f..03baa4792 100644
--- a/chrome/test/data/android/render_tests/ExploreSitesPageTest.initial_layout.Nexus_5-19.png.sha1
+++ b/chrome/test/data/android/render_tests/ExploreSitesPageTest.initial_layout.Nexus_5-19.png.sha1
@@ -1 +1 @@
-7847da096d38aa3d3204cdd37726a039b819e2ab
\ No newline at end of file
+9fb8c2552c167fae80da115358dbad6c9a21d43f
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ExploreSitesPageTest.recycler_layout.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ExploreSitesPageTest.recycler_layout.Nexus_5-19.png.sha1
index 1c7a5c77..ff1bb65 100644
--- a/chrome/test/data/android/render_tests/ExploreSitesPageTest.recycler_layout.Nexus_5-19.png.sha1
+++ b/chrome/test/data/android/render_tests/ExploreSitesPageTest.recycler_layout.Nexus_5-19.png.sha1
@@ -1 +1 @@
-1801fe3bce2257fb2143548b99320b652c1bf033
\ No newline at end of file
+8d1722f71ac07ab22c7c126b00e18deff42827f1
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ExploreSitesPageTest.scrolled_to_category_2.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ExploreSitesPageTest.scrolled_to_category_2.Nexus_5-19.png.sha1
index 9ead928..bd17bda 100644
--- a/chrome/test/data/android/render_tests/ExploreSitesPageTest.scrolled_to_category_2.Nexus_5-19.png.sha1
+++ b/chrome/test/data/android/render_tests/ExploreSitesPageTest.scrolled_to_category_2.Nexus_5-19.png.sha1
@@ -1 +1 @@
-41df775d3cc887efad3cc3ed14b010a50a4e6c63
\ No newline at end of file
+abc2f7b73def0c35712eccc030ba6d13df454609
\ No newline at end of file
diff --git a/chrome/test/data/extensions/api_test/image_writer_private/list_devices/test.js b/chrome/test/data/extensions/api_test/image_writer_private/list_devices/test.js
index ec7be06..247fd8b 100644
--- a/chrome/test/data/extensions/api_test/image_writer_private/list_devices/test.js
+++ b/chrome/test/data/extensions/api_test/image_writer_private/list_devices/test.js
@@ -41,4 +41,4 @@
   });
 }
 
-chrome.test.runTests([testDeviceList])
+chrome.test.runTests([testDeviceList]);
diff --git a/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/content_script.js b/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/content_script.js
new file mode 100644
index 0000000..3ef78e1
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/content_script.js
@@ -0,0 +1,11 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+  if (request != 'worker->tab') {
+    chrome.test.sendMessage('FAILURE');
+    return;
+  }
+  sendResponse('worker->tab->worker');
+});
diff --git a/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/manifest.json b/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/manifest.json
new file mode 100644
index 0000000..b222911
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "SW based extension: Messaging_WorkerToTab test",
+  "version": "0.1",
+  "manifest_version": 2,
+  "description": "chrome.tabs.sendMessage from extension SW.",
+  "permissions": ["tabs"],
+  "content_scripts": [{
+    "matches": ["*://127.0.0.1:*/*"],
+    "run_at": "document_start",
+    "js": ["content_script.js"]
+  }],
+  "background": {"service_worker_script": "service_worker_background.js"}
+}
diff --git a/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/service_worker_background.js b/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/service_worker_background.js
new file mode 100644
index 0000000..0396c26f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/messaging/send_message_worker_to_tab/service_worker_background.js
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var TEST_FILE_URL = 'http://127.0.0.1:PORT/extensions/test_file.html'
+
+chrome.test.getConfig((config) => {
+  var createdTabId = undefined;
+  chrome.test.runTests([
+    function createTab() {
+      var testComplete = false;
+      chrome.tabs.onUpdated.addListener(function listener(tabId, changeInfo) {
+        if (!createdTabId || tabId != createdTabId ||
+            changeInfo.status !== 'complete') {
+          return;
+        }
+        chrome.tabs.onUpdated.removeListener(listener);
+        if (!testComplete)
+          chrome.test.succeed();
+      });
+      var testUrl = TEST_FILE_URL.replace(/PORT/, config.testServer.port);
+      chrome.tabs.create({url: testUrl}, function(tab) {
+          createdTabId = tab.id;
+        if (tab.status === 'complete') {
+          testComplete = true;
+          chrome.test.succeed();
+        }
+        // Otherwise tabs.onUpdated will be called.
+      });
+    },
+    function testExternalMessage() {
+      chrome.tabs.sendMessage(createdTabId, 'worker->tab', function(response) {
+        console.log('response = ' + response);
+        chrome.test.assertEq('worker->tab->worker', response);
+        chrome.test.succeed();
+      });
+    },
+  ]);
+});
diff --git a/chrome/test/data/webui/settings/google_assistant_page_test.js b/chrome/test/data/webui/settings/google_assistant_page_test.js
index 5958ac2..f8cf740 100644
--- a/chrome/test/data/webui/settings/google_assistant_page_test.js
+++ b/chrome/test/data/webui/settings/google_assistant_page_test.js
@@ -9,6 +9,7 @@
   constructor() {
     super([
       'showGoogleAssistantSettings',
+      'retrainAssistantVoiceModel',
     ]);
   }
 
@@ -16,6 +17,11 @@
   showGoogleAssistantSettings() {
     this.methodCalled('showGoogleAssistantSettings');
   }
+
+  /** @override */
+  retrainAssistantVoiceModel() {
+    this.methodCalled('retrainAssistantVoiceModel');
+  }
 }
 
 suite('GoogleAssistantHandler', function() {
@@ -26,6 +32,13 @@
   /** @type {?TestGoogleAssistantBrowserProxy} */
   let browserProxy = null;
 
+  suiteSetup(function() {
+    loadTimeData.overrideValues({
+      enableAssistant: true,
+      voiceMatchEnabled: true,
+    });
+  });
+
   setup(function() {
     browserProxy = new TestGoogleAssistantBrowserProxy();
     settings.GoogleAssistantBrowserProxyImpl.instance_ = browserProxy;
@@ -63,6 +76,7 @@
     let button = page.$$('#googleAssistantContextEnable');
     assertFalse(!!button);
     page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.context.enabled', false);
     Polymer.dom.flush();
     button = page.$$('#googleAssistantContextEnable');
     assertTrue(!!button);
@@ -72,6 +86,123 @@
     button.click();
     Polymer.dom.flush();
     assertTrue(button.checked);
+    assertTrue(
+        page.getPref('settings.voice_interaction.context.enabled.value'), true);
+  });
+
+  test('toggleAssistantHotword', function() {
+    let button = page.$$('#googleAssistantHotwordEnable');
+    assertFalse(!!button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
+    Polymer.dom.flush();
+    button = page.$$('#googleAssistantHotwordEnable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    button.click();
+    Polymer.dom.flush();
+    assertTrue(button.checked);
+    assertTrue(
+        page.getPref('settings.voice_interaction.hotword.enabled.value'), true);
+  });
+
+  test('tapOnRetrainVoiceModel', function() {
+    let button = page.$$('#retrainVoiceModel');
+    assertFalse(!!button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    page.setPrefValue(
+        'settings.voice_interaction.activity_control.consent_status',
+        ConsentStatus.kActivityControlAccepted);
+    Polymer.dom.flush();
+    button = page.$$('#retrainVoiceModel');
+    assertTrue(!!button);
+
+    button.click();
+    Polymer.dom.flush();
+    return browserProxy.whenCalled('retrainAssistantVoiceModel');
+  });
+
+  test('retrainButtonVisibility', function() {
+    let button = page.$$('#retrainVoiceModel');
+    assertFalse(!!button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    Polymer.dom.flush();
+    button = page.$$('#retrainVoiceModel');
+    assertFalse(!!button);
+
+    // Hotword enabled.
+    // Activity control consent not granted.
+    // Button should not be shown.
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    page.setPrefValue(
+        'settings.voice_interaction.activity_control.consent_status',
+        ConsentStatus.kUnauthorized);
+    Polymer.dom.flush();
+    button = page.$$('#retrainVoiceModel');
+    assertFalse(!!button);
+
+    // Hotword disabled.
+    // Activity control consent granted.
+    // Button should not be shown.
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', false);
+    page.setPrefValue(
+        'settings.voice_interaction.activity_control.consent_status',
+        ConsentStatus.kActivityControlAccepted);
+    Polymer.dom.flush();
+    button = page.$$('#retrainVoiceModel');
+    assertFalse(!!button);
+
+    // Hotword enabled.
+    // Activity control consent granted.
+    // Button should be shown.
+    page.setPrefValue('settings.voice_interaction.hotword.enabled', true);
+    page.setPrefValue(
+        'settings.voice_interaction.activity_control.consent_status',
+        ConsentStatus.kActivityControlAccepted);
+    Polymer.dom.flush();
+    button = page.$$('#retrainVoiceModel');
+    assertTrue(!!button);
+  });
+
+  test('toggleAssistantNotification', function() {
+    let button = page.$$('#googleAssistantNotificationEnable');
+    assertFalse(!!button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.notification.enabled', false);
+    Polymer.dom.flush();
+    button = page.$$('#googleAssistantNotificationEnable');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    button.click();
+    Polymer.dom.flush();
+    assertTrue(button.checked);
+    assertTrue(
+        page.getPref('settings.voice_interaction.notification.enabled.value'),
+        true);
+  });
+
+  test('toggleAssistantLaunchWithMicOpen', function() {
+    let button = page.$$('#googleAssistantLaunchWithMicOpen');
+    assertFalse(!!button);
+    page.setPrefValue('settings.voice_interaction.enabled', true);
+    page.setPrefValue('settings.voice_interaction.launch_with_mic_open', false);
+    Polymer.dom.flush();
+    button = page.$$('#googleAssistantLaunchWithMicOpen');
+    assertTrue(!!button);
+    assertFalse(button.disabled);
+    assertFalse(button.checked);
+
+    button.click();
+    Polymer.dom.flush();
+    assertTrue(button.checked);
+    assertTrue(
+        page.getPref('settings.voice_interaction.launch_with_mic_open.value'),
+        true);
   });
 
   test('tapOnAssistantSettings', function() {
diff --git a/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js b/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
index f12fd36..476e4d8 100644
--- a/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
+++ b/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
@@ -31,6 +31,12 @@
 });
 var sessionTypeToRequest = sessionTypes.IMMERSIVE;
 
+var referenceSpaceMap = {
+  [sessionTypes.IMMERSIVE]: { type: 'stationary', subtype: 'eye-level' },
+  [sessionTypes.MAGIC_WINDOW]: { type: 'identity' },
+  [sessionTypes.AR]: { type: 'identity' }
+}
+
 class SessionInfo {
   constructor() {
     this.session = null;
@@ -134,7 +140,7 @@
   }
 
   session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
-  session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' })
+  session.requestReferenceSpace(referenceSpaceMap[getSessionType(session)])
       .then( (refSpace) => {
         sessionInfos[getSessionType(session)].currentRefSpace = refSpace;
         session.requestAnimationFrame(onXRFrame);
diff --git a/chromecast/chromecast.gni b/chromecast/chromecast.gni
index e5bf713..e9c0aba 100644
--- a/chromecast/chromecast.gni
+++ b/chromecast/chromecast.gni
@@ -40,6 +40,10 @@
   # still be used to enable multizone.
   supports_multizone = is_cast_audio_only && !is_cast_desktop_build
 
+  # Set to true on devices where the VolumeControl implementation is in the
+  # libcast_avsettings_1.0.so instead of in libcast_media_1.0.so.
+  cast_volume_control_in_avsettings = false
+
   # Set to true for builds targeting ARC.
   is_android_arc = false
 
diff --git a/chromecast/media/BUILD.gn b/chromecast/media/BUILD.gn
index 78d67d1..43547f13 100644
--- a/chromecast/media/BUILD.gn
+++ b/chromecast/media/BUILD.gn
@@ -37,6 +37,10 @@
     deps = [
       "//chromecast/media/cma/backend:libcast_media_1.0",
     ]
+
+    if (cast_volume_control_in_avsettings) {
+      deps += [ "//chromecast/media/avsettings:libcast_avsettings_1.0" ]
+    }
   }
 }
 
diff --git a/chromecast/media/avsettings/BUILD.gn b/chromecast/media/avsettings/BUILD.gn
new file mode 100644
index 0000000..dc01162
--- /dev/null
+++ b/chromecast/media/avsettings/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/buildflag_header.gni")
+import("//chromecast/chromecast.gni")
+
+buildflag_header("avsettings_buildflags") {
+  header = "avsettings_buildflags.h"
+
+  flags = [
+    "VOLUME_CONTROL_IN_AVSETTINGS_SHLIB=$cast_volume_control_in_avsettings",
+  ]
+}
+
+cast_source_set("avsettings_dummy") {
+  sources = [
+    "avsettings_dummy.cc",
+    "avsettings_dummy.h",
+  ]
+
+  deps = [
+    "//chromecast/public",
+  ]
+}
+
+# Target for OEM partners to override avsettings shared library, i.e.
+# libcast_avsettings_1.0.so.
+cast_shared_library("libcast_avsettings_1.0") {
+  sources = [
+    "avsettings_shlib.cc",
+  ]
+
+  deps = [
+    ":avsettings_buildflags",
+    ":avsettings_dummy",
+    "//chromecast/public",
+  ]
+}
diff --git a/chromecast/media/avsettings/avsettings_dummy.cc b/chromecast/media/avsettings/avsettings_dummy.cc
new file mode 100644
index 0000000..6c94092
--- /dev/null
+++ b/chromecast/media/avsettings/avsettings_dummy.cc
@@ -0,0 +1,108 @@
+// 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 "chromecast/media/avsettings/avsettings_dummy.h"
+
+namespace chromecast {
+
+AvSettingsDummy::AvSettingsDummy() : delegate_(nullptr) {}
+
+AvSettingsDummy::~AvSettingsDummy() = default;
+
+void AvSettingsDummy::Initialize(Delegate* delegate) {
+  delegate_ = delegate;
+}
+
+void AvSettingsDummy::Finalize() {
+  delegate_ = nullptr;
+}
+
+AvSettings::ActiveState AvSettingsDummy::GetActiveState() {
+  return ActiveState::UNKNOWN;
+}
+
+bool AvSettingsDummy::TurnActive(bool switch_to_cast) {
+  return false;
+}
+
+bool AvSettingsDummy::TurnStandby() {
+  return false;
+}
+
+bool AvSettingsDummy::KeepSystemAwake(int time_millis) {
+  return false;
+}
+
+AvSettings::AudioVolumeControlType
+AvSettingsDummy::GetAudioVolumeControlType() {
+  return MASTER_VOLUME;
+}
+
+bool AvSettingsDummy::GetAudioVolumeStepInterval(float* step_interval) {
+  return false;  // Use default intervals per control type
+}
+
+int AvSettingsDummy::GetAudioCodecsSupported() {
+  return 0;
+}
+
+int AvSettingsDummy::GetMaxAudioChannels(AudioCodec codec) {
+  return 0;
+}
+
+bool AvSettingsDummy::GetScreenResolution(int* width, int* height) {
+  return false;
+}
+
+int AvSettingsDummy::GetHDCPVersion() {
+  return 0;
+}
+
+int AvSettingsDummy::GetSupportedEotfs() {
+  return 0;
+}
+
+int AvSettingsDummy::GetDolbyVisionFlags() {
+  return 0;
+}
+
+int AvSettingsDummy::GetScreenWidthMm() {
+  return 0;
+}
+
+int AvSettingsDummy::GetScreenHeightMm() {
+  return 0;
+}
+
+bool AvSettingsDummy::GetOutputRestrictions(OutputRestrictions* restrictions) {
+  return false;
+}
+
+void AvSettingsDummy::ApplyOutputRestrictions(
+    const OutputRestrictions& restrictions) {}
+
+AvSettings::WakeOnCastStatus AvSettingsDummy::GetWakeOnCastStatus() {
+  return WAKE_ON_CAST_NOT_SUPPORTED;
+}
+
+bool AvSettingsDummy::EnableWakeOnCast(bool enabled) {
+  return false;
+}
+
+AvSettings::HdrOutputType AvSettingsDummy::GetHdrOutputType() {
+  return HDR_OUTPUT_SDR;
+}
+
+bool AvSettingsDummy::SetHdmiVideoMode(bool allow_4k,
+                                       int optimize_for_fps,
+                                       AvSettings::HdrOutputType output_type) {
+  return false;
+}
+
+bool AvSettingsDummy::IsHdrOutputSupportedByCurrentHdmiVideoMode(
+    HdrOutputType output_type) {
+  return false;
+}
+
+}  // namespace chromecast
diff --git a/chromecast/media/avsettings/avsettings_dummy.h b/chromecast/media/avsettings/avsettings_dummy.h
new file mode 100644
index 0000000..ffe7b6d
--- /dev/null
+++ b/chromecast/media/avsettings/avsettings_dummy.h
@@ -0,0 +1,56 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_AVSETTINGS_AVSETTINGS_DUMMY_H_
+#define CHROMECAST_MEDIA_AVSETTINGS_AVSETTINGS_DUMMY_H_
+
+#include "chromecast/public/avsettings.h"
+
+namespace chromecast {
+
+// Dummy implementation of AvSettings.
+class AvSettingsDummy : public AvSettings {
+ public:
+  AvSettingsDummy();
+  ~AvSettingsDummy() override;
+
+  // AvSettings implementation:
+  void Initialize(Delegate* delegate) override;
+  void Finalize() override;
+  ActiveState GetActiveState() override;
+  bool TurnActive(bool switch_to_cast) override;
+  bool TurnStandby() override;
+  bool KeepSystemAwake(int time_millis) override;
+  AudioVolumeControlType GetAudioVolumeControlType() override;
+  bool GetAudioVolumeStepInterval(float* step_interval) override;
+  int GetAudioCodecsSupported() override;
+  int GetMaxAudioChannels(AudioCodec codec) override;
+  bool GetScreenResolution(int* width, int* height) override;
+  int GetHDCPVersion() override;
+  int GetSupportedEotfs() override;
+  int GetDolbyVisionFlags() override;
+  int GetScreenWidthMm() override;
+  int GetScreenHeightMm() override;
+  bool GetOutputRestrictions(OutputRestrictions* restrictions) override;
+  void ApplyOutputRestrictions(const OutputRestrictions& restrictions) override;
+  WakeOnCastStatus GetWakeOnCastStatus() override;
+  bool EnableWakeOnCast(bool enabled) override;
+  HdrOutputType GetHdrOutputType() override;
+  bool SetHdmiVideoMode(bool allow_4k,
+                        int optimize_for_fps,
+                        HdrOutputType output_type) override;
+  bool IsHdrOutputSupportedByCurrentHdmiVideoMode(
+      HdrOutputType output_type) override;
+
+ private:
+  Delegate* delegate_;
+
+  // Disallow copy and assign.
+  AvSettingsDummy(const AvSettingsDummy&) = delete;
+  AvSettingsDummy& operator=(const AvSettingsDummy&) = delete;
+};
+
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_AVSETTINGS_AVSETTINGS_DUMMY_H_
diff --git a/chromecast/media/avsettings/avsettings_shlib.cc b/chromecast/media/avsettings/avsettings_shlib.cc
new file mode 100644
index 0000000..9942302
--- /dev/null
+++ b/chromecast/media/avsettings/avsettings_shlib.cc
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "chromecast/media/avsettings/avsettings_buildflags.h"
+#include "chromecast/media/avsettings/avsettings_dummy.h"
+#include "chromecast/public/avsettings.h"
+#include "chromecast/public/volume_control.h"
+
+namespace chromecast {
+
+// static
+AvSettings* AvSettingsShlib::Create(const std::vector<std::string>& argv) {
+  return new AvSettingsDummy();
+}
+
+#if BUILDFLAG(VOLUME_CONTROL_IN_AVSETTINGS_SHLIB)
+namespace media {
+
+void VolumeControl::Initialize(const std::vector<std::string>& argv) {}
+void VolumeControl::Finalize() {}
+void VolumeControl::AddVolumeObserver(VolumeObserver* observer) {}
+void VolumeControl::RemoveVolumeObserver(VolumeObserver* observer) {}
+
+float VolumeControl::GetVolume(AudioContentType type) {
+  return 0.0f;
+}
+
+void VolumeControl::SetVolume(VolumeChangeSource source,
+                              AudioContentType type,
+                              float level) {}
+
+bool VolumeControl::IsMuted(AudioContentType type) {
+  return false;
+}
+
+void VolumeControl::SetMuted(VolumeChangeSource source,
+                             AudioContentType type,
+                             bool muted) {}
+
+void VolumeControl::SetOutputLimit(AudioContentType type, float limit) {}
+
+float VolumeControl::VolumeToDbFS(float volume) {
+  return 0.0f;
+}
+
+float VolumeControl::DbFSToVolume(float db) {
+  return 0.0f;
+}
+
+void VolumeControl::SetPowerSaveMode(bool power_save_on) {}
+
+}  // namespace media
+#endif  // BUILDFLAG(VOLUME_CONTROL_IN_AVSETTINGS_SHLIB)
+
+}  // namespace chromecast
diff --git a/chromecast/media/cma/backend/BUILD.gn b/chromecast/media/cma/backend/BUILD.gn
index 0841728..f433fae 100644
--- a/chromecast/media/cma/backend/BUILD.gn
+++ b/chromecast/media/cma/backend/BUILD.gn
@@ -82,16 +82,20 @@
   ]
 
   deps = [
+    ":audio_buildflags",
     "//chromecast/public",
   ]
 }
 
+volume_control_in_media_shlib = !cast_volume_control_in_avsettings
+
 buildflag_header("audio_buildflags") {
   header = "audio_buildflags.h"
 
   flags = [
     "MEDIA_CLOCK_MONOTONIC_RAW=$media_clock_monotonic_raw",
     "SYSTEM_OWNS_VOLUME=$system_owns_volume",
+    "VOLUME_CONTROL_IN_MEDIA_SHLIB=$volume_control_in_media_shlib",
   ]
 }
 
diff --git a/chromecast/media/cma/backend/android/volume_control_android.cc b/chromecast/media/cma/backend/android/volume_control_android.cc
index 95f0443..35a0898 100644
--- a/chromecast/media/cma/backend/android/volume_control_android.cc
+++ b/chromecast/media/cma/backend/android/volume_control_android.cc
@@ -394,10 +394,5 @@
   return GetVolumeControl().DbFSToVolumeCached(AudioContentType::kMedia, db);
 }
 
-// static
-void VolumeControl::SetPowerSaveMode(bool power_save_on) {
-  // Ignored.
-}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/audio_output_redirector.cc b/chromecast/media/cma/backend/audio_output_redirector.cc
index c2243d53..85ab26f 100644
--- a/chromecast/media/cma/backend/audio_output_redirector.cc
+++ b/chromecast/media/cma/backend/audio_output_redirector.cc
@@ -118,11 +118,14 @@
 void AudioOutputRedirector::AddInput(MixerInput* mixer_input) {
   if (ApplyToInput(mixer_input)) {
     inputs_[mixer_input] = std::make_unique<InputImpl>(this, mixer_input);
+  } else {
+    non_redirected_inputs_.insert(mixer_input);
   }
 }
 
 void AudioOutputRedirector::RemoveInput(MixerInput* mixer_input) {
   inputs_.erase(mixer_input);
+  non_redirected_inputs_.erase(mixer_input);
 }
 
 bool AudioOutputRedirector::ApplyToInput(MixerInput* mixer_input) {
@@ -140,6 +143,33 @@
   return false;
 }
 
+void AudioOutputRedirector::UpdatePatterns(
+    std::vector<std::pair<AudioContentType, std::string>> patterns) {
+  config_.stream_match_patterns = std::move(patterns);
+  // Remove streams that no longer match.
+  for (auto it = inputs_.begin(); it != inputs_.end();) {
+    MixerInput* mixer_input = it->first;
+    if (!ApplyToInput(mixer_input)) {
+      non_redirected_inputs_.insert(mixer_input);
+      it = inputs_.erase(it);
+    } else {
+      ++it;
+    }
+  }
+
+  // Add streams that previously didn't match.
+  for (auto it = non_redirected_inputs_.begin();
+       it != non_redirected_inputs_.end();) {
+    MixerInput* mixer_input = *it;
+    if (ApplyToInput(mixer_input)) {
+      inputs_[mixer_input] = std::make_unique<InputImpl>(this, mixer_input);
+      it = non_redirected_inputs_.erase(it);
+    } else {
+      ++it;
+    }
+  }
+}
+
 void AudioOutputRedirector::Start(int output_samples_per_second) {
   output_->Start(output_samples_per_second);
 }
@@ -221,8 +251,7 @@
 AudioOutputRedirectorToken* CastMediaShlib::AddAudioOutputRedirection(
     const AudioOutputRedirectionConfig& config,
     std::unique_ptr<RedirectedAudioOutput> output) {
-  if (!output || config.num_output_channels <= 0 ||
-      config.stream_match_patterns.empty()) {
+  if (!output || config.num_output_channels <= 0) {
     return nullptr;
   }
 
@@ -243,5 +272,18 @@
   }
 }
 
+// static
+void CastMediaShlib::ModifyAudioOutputRedirection(
+    AudioOutputRedirectorToken* token,
+    std::vector<std::pair<AudioContentType, std::string>>
+        stream_match_patterns) {
+  AudioOutputRedirector* redirector =
+      static_cast<AudioOutputRedirector*>(token);
+  if (redirector) {
+    StreamMixer::Get()->ModifyAudioOutputRedirection(
+        redirector, std::move(stream_match_patterns));
+  }
+}
+
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/audio_output_redirector.h b/chromecast/media/cma/backend/audio_output_redirector.h
index 4d1b48d..c2ab65f 100644
--- a/chromecast/media/cma/backend/audio_output_redirector.h
+++ b/chromecast/media/cma/backend/audio_output_redirector.h
@@ -8,6 +8,7 @@
 #include <cstdint>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/containers/flat_map.h"
@@ -60,6 +61,12 @@
   void AddInput(MixerInput* mixer_input);
   void RemoveInput(MixerInput* mixer_input);
 
+  // Updates the set of patterns used to determine which inputs should be
+  // redirected by this AudioOutputRedirector. Any inputs which no longer match
+  // will stop being redirected.
+  void UpdatePatterns(
+      std::vector<std::pair<AudioContentType, std::string>> patterns);
+
   // Indicates that mixer output is starting at the given sample rate of
   // |output_samples_per_second|.
   void Start(int output_samples_per_second);
@@ -88,7 +95,7 @@
 
   bool ApplyToInput(MixerInput* mixer_input);
 
-  const AudioOutputRedirectionConfig config_;
+  AudioOutputRedirectionConfig config_;
   const std::unique_ptr<RedirectedAudioOutput> output_;
 
   int next_num_frames_ = 0;
@@ -99,6 +106,7 @@
   std::vector<float*> channel_data_;
 
   base::flat_map<MixerInput*, std::unique_ptr<InputImpl>> inputs_;
+  base::flat_set<MixerInput*> non_redirected_inputs_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioOutputRedirector);
 };
diff --git a/chromecast/media/cma/backend/cast_media_android_dummy.cc b/chromecast/media/cma/backend/cast_media_android_dummy.cc
index 46339609..d2b7b54 100644
--- a/chromecast/media/cma/backend/cast_media_android_dummy.cc
+++ b/chromecast/media/cma/backend/cast_media_android_dummy.cc
@@ -90,7 +90,5 @@
   return 0.0f;
 }
 
-void VolumeControl::SetPowerSaveMode(bool power_save_on) {}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/cast_media_dummy.cc b/chromecast/media/cma/backend/cast_media_dummy.cc
index 0d51f32..f27a180 100644
--- a/chromecast/media/cma/backend/cast_media_dummy.cc
+++ b/chromecast/media/cma/backend/cast_media_dummy.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chromecast/media/cma/backend/audio_buildflags.h"
 #include "chromecast/public/cast_media_shlib.h"
 #include "chromecast/public/media/media_capabilities_shlib.h"
 #include "chromecast/public/volume_control.h"
@@ -52,6 +53,8 @@
          config.codec == kCodecPCM || config.codec == kCodecVorbis;
 }
 
+#if BUILDFLAG(VOLUME_CONTROL_IN_MEDIA_SHLIB)
+
 void VolumeControl::Initialize(const std::vector<std::string>& argv) {}
 void VolumeControl::Finalize() {}
 void VolumeControl::AddVolumeObserver(VolumeObserver* observer) {}
@@ -85,5 +88,7 @@
 
 void VolumeControl::SetPowerSaveMode(bool power_save_on) {}
 
+#endif  // BUILDFLAG(VOLUME_CONTROL_IN_MEDIA_SHLIB)
+
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/desktop/volume_control_desktop.cc b/chromecast/media/cma/backend/desktop/volume_control_desktop.cc
index 640a0bd4..a4c505c 100644
--- a/chromecast/media/cma/backend/desktop/volume_control_desktop.cc
+++ b/chromecast/media/cma/backend/desktop/volume_control_desktop.cc
@@ -215,8 +215,5 @@
   return (db - kMinVolumeDbfs) / (kMaxVolumeDbfs - kMinVolumeDbfs);
 }
 
-// static
-void VolumeControl::SetPowerSaveMode(bool power_save_on) {}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_manager.cc b/chromecast/media/cma/backend/media_pipeline_backend_manager.cc
index 53f7cbb..ac7c6fd6 100644
--- a/chromecast/media/cma/backend/media_pipeline_backend_manager.cc
+++ b/chromecast/media/cma/backend/media_pipeline_backend_manager.cc
@@ -144,9 +144,11 @@
                             &MediaPipelineBackendManager::EnterPowerSaveMode);
   } else if (!had_playing_audio_streams && new_playing_audio_streams > 0) {
     power_save_timer_.Stop();
-    metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
-        "Cast.Platform.VolumeControl.PowerSaveOff");
-    VolumeControl::SetPowerSaveMode(false);
+    if (VolumeControl::SetPowerSaveMode) {
+      metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
+          "Cast.Platform.VolumeControl.PowerSaveOff");
+      VolumeControl::SetPowerSaveMode(false);
+    }
   }
 
   if (sfx) {
@@ -185,8 +187,7 @@
 
 void MediaPipelineBackendManager::EnterPowerSaveMode() {
   DCHECK_EQ(TotalPlayingAudioStreamsCount(), 0);
-  DCHECK(VolumeControl::SetPowerSaveMode);
-  if (!power_save_enabled_) {
+  if (!VolumeControl::SetPowerSaveMode || !power_save_enabled_) {
     return;
   }
   metrics::CastMetricsHelper::GetInstance()->RecordSimpleAction(
@@ -264,7 +265,7 @@
 void MediaPipelineBackendManager::SetPowerSaveEnabled(bool power_save_enabled) {
   MAKE_SURE_MEDIA_THREAD(SetPowerSaveEnabled, power_save_enabled);
   power_save_enabled_ = power_save_enabled;
-  if (!power_save_enabled_) {
+  if (VolumeControl::SetPowerSaveMode && !power_save_enabled_) {
     VolumeControl::SetPowerSaveMode(false);
   }
 }
diff --git a/chromecast/media/cma/backend/stream_mixer.cc b/chromecast/media/cma/backend/stream_mixer.cc
index 7efc0b3..728d790c 100644
--- a/chromecast/media/cma/backend/stream_mixer.cc
+++ b/chromecast/media/cma/backend/stream_mixer.cc
@@ -780,6 +780,24 @@
   audio_output_redirectors_.erase(redirector);
 }
 
+void StreamMixer::ModifyAudioOutputRedirection(
+    AudioOutputRedirector* redirector,
+    std::vector<std::pair<AudioContentType, std::string>>
+        stream_match_patterns) {
+  POST_THROUGH_INPUT_THREAD(&StreamMixer::ModifyAudioOutputRedirectionOnThread,
+                            redirector, std::move(stream_match_patterns));
+}
+
+void StreamMixer::ModifyAudioOutputRedirectionOnThread(
+    AudioOutputRedirector* redirector,
+    std::vector<std::pair<AudioContentType, std::string>>
+        stream_match_patterns) {
+  auto it = audio_output_redirectors_.find(redirector);
+  if (it != audio_output_redirectors_.end()) {
+    it->second->UpdatePatterns(std::move(stream_match_patterns));
+  }
+}
+
 void StreamMixer::PostLoopbackData(int64_t expected_playback_time,
                                    SampleFormat format,
                                    int sample_rate,
diff --git a/chromecast/media/cma/backend/stream_mixer.h b/chromecast/media/cma/backend/stream_mixer.h
index 4fe927e..19111ef 100644
--- a/chromecast/media/cma/backend/stream_mixer.h
+++ b/chromecast/media/cma/backend/stream_mixer.h
@@ -9,6 +9,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/containers/flat_map.h"
@@ -100,6 +101,10 @@
   void AddAudioOutputRedirector(
       std::unique_ptr<AudioOutputRedirector> redirector);
   void RemoveAudioOutputRedirector(AudioOutputRedirector* redirector);
+  void ModifyAudioOutputRedirection(
+      AudioOutputRedirector* redirector,
+      std::vector<std::pair<AudioContentType, std::string>>
+          stream_match_patterns);
 
   // Sets the volume multiplier for the given content |type|.
   void SetVolume(AudioContentType type, float level);
@@ -194,6 +199,10 @@
   void AddAudioOutputRedirectorOnThread(
       std::unique_ptr<AudioOutputRedirector> redirector);
   void RemoveAudioOutputRedirectorOnThread(AudioOutputRedirector* redirector);
+  void ModifyAudioOutputRedirectionOnThread(
+      AudioOutputRedirector* redirector,
+      std::vector<std::pair<AudioContentType, std::string>>
+          stream_match_patterns);
 
   void PostLoopbackData(int64_t expected_playback_time,
                         SampleFormat sample_format,
diff --git a/chromecast/media/cma/backend/stream_mixer_unittest.cc b/chromecast/media/cma/backend/stream_mixer_unittest.cc
index 286b93c..a703af6 100644
--- a/chromecast/media/cma/backend/stream_mixer_unittest.cc
+++ b/chromecast/media/cma/backend/stream_mixer_unittest.cc
@@ -385,12 +385,16 @@
   }
 
   MockRedirectedAudioOutput* AddOutputRedirector(
-      const AudioOutputRedirectionConfig& config) {
+      const AudioOutputRedirectionConfig& config,
+      AudioOutputRedirector** redirector_ptr = nullptr) {
     auto redirected_output =
         std::make_unique<MockRedirectedAudioOutput>(kNumChannels);
     MockRedirectedAudioOutput* redirected_output_ptr = redirected_output.get();
     auto redirector = std::make_unique<AudioOutputRedirector>(
         config, std::move(redirected_output));
+    if (redirector_ptr) {
+      *redirector_ptr = redirector.get();
+    }
     mixer_->AddAudioOutputRedirector(std::move(redirector));
     return redirected_output_ptr;
   }
@@ -1332,6 +1336,64 @@
   mixer_.reset();
 }
 
+TEST_F(StreamMixerTest, ModifyOutputRedirection) {
+  std::vector<std::unique_ptr<MockMixerSource>> inputs;
+  inputs.push_back(
+      std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "matches"));
+  inputs.push_back(
+      std::make_unique<MockMixerSource>(kTestSamplesPerSecond, "asdf"));
+
+  for (size_t i = 0; i < inputs.size(); ++i) {
+    mixer_->AddInput(inputs[i].get());
+  }
+  WaitForMixer();
+  mock_output_->ClearData();
+
+  AudioOutputRedirectionConfig config;
+  config.stream_match_patterns.push_back({AudioContentType::kMedia, "*match*"});
+  AudioOutputRedirector* redirector_ptr = nullptr;
+  MockRedirectedAudioOutput* redirected_output_ptr =
+      AddOutputRedirector(config, &redirector_ptr);
+  CHECK(redirector_ptr);
+  WaitForMixer();
+
+  const int kNumFrames = 32;
+  for (size_t i = 0; i < inputs.size(); ++i) {
+    inputs[i]->SetData(GetTestData(i));
+  }
+  PlaybackOnce();
+  mock_output_->ClearData();
+  for (size_t i = 0; i < inputs.size(); ++i) {
+    inputs[i]->SetData(GetTestData(i));
+  }
+  PlaybackOnce();
+
+  CheckRedirectorOutput(redirected_output_ptr, {inputs[1].get()},
+                        {inputs[0].get()}, kNumFrames);
+
+  std::vector<std::pair<AudioContentType, std::string>> new_match_patterns = {
+      {AudioContentType::kMedia, "*asdf*"}};
+  mixer_->ModifyAudioOutputRedirection(redirector_ptr,
+                                       std::move(new_match_patterns));
+  WaitForMixer();
+  mock_output_->ClearData();
+
+  for (size_t i = 0; i < inputs.size(); ++i) {
+    inputs[i]->SetData(GetTestData(i));
+  }
+  PlaybackOnce();
+  mock_output_->ClearData();
+  for (size_t i = 0; i < inputs.size(); ++i) {
+    inputs[i]->SetData(GetTestData(i));
+  }
+  PlaybackOnce();
+
+  CheckRedirectorOutput(redirected_output_ptr, {inputs[0].get()},
+                        {inputs[1].get()}, kNumFrames);
+
+  mixer_.reset();
+}
+
 #if GTEST_HAS_DEATH_TEST
 
 using StreamMixerDeathTest = StreamMixerTest;
diff --git a/chromecast/public/avsettings.h b/chromecast/public/avsettings.h
index 09e9b30..28c0217 100644
--- a/chromecast/public/avsettings.h
+++ b/chromecast/public/avsettings.h
@@ -7,6 +7,10 @@
 
 #include <stdint.h>
 
+#include <string>
+#include <vector>
+
+#include "chromecast_export.h"
 #include "output_restrictions.h"
 #include "task_runner.h"
 
@@ -353,6 +357,12 @@
       HdrOutputType output_type) = 0;
 };
 
+// Entrypoint for overridable AvSettings shared library.
+class CHROMECAST_EXPORT AvSettingsShlib {
+ public:
+  static AvSettings* Create(const std::vector<std::string>& argv);
+};
+
 }  // namespace chromecast
 
 #endif  // CHROMECAST_PUBLIC_AVSETTINGS_H_
diff --git a/chromecast/public/cast_media_shlib.h b/chromecast/public/cast_media_shlib.h
index 6255274..6242076 100644
--- a/chromecast/public/cast_media_shlib.h
+++ b/chromecast/public/cast_media_shlib.h
@@ -10,9 +10,11 @@
 #include <functional>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "chromecast_export.h"
+#include "volume_control.h"
 
 namespace chromecast {
 namespace media {
@@ -176,6 +178,12 @@
   // AddAudioOutputRedirection().
   static void RemoveAudioOutputRedirection(AudioOutputRedirectorToken* token)
       __attribute__((__weak__));
+
+  // Updates the set of streams that an audio output redirector should apply to.
+  static void ModifyAudioOutputRedirection(
+      AudioOutputRedirectorToken* token,
+      std::vector<std::pair<AudioContentType, std::string /* device ID */>>
+          stream_match_patterns) __attribute__((__weak__));
 };
 
 }  // namespace media
diff --git a/chromecast/public/media/redirected_audio_output.h b/chromecast/public/media/redirected_audio_output.h
index 694742ac..15b624e4 100644
--- a/chromecast/public/media/redirected_audio_output.h
+++ b/chromecast/public/media/redirected_audio_output.h
@@ -34,7 +34,7 @@
   bool apply_volume = false;
 
   // Any extra delay to apply to the timestamps sent to the redirected output.
-  // Note that the dealyed timestamp will be used internally for AV sync.
+  // Note that the delayed timestamp will be used internally for AV sync.
   int64_t extra_delay_microseconds = 0;
 
   // Patterns to determine which audio streams should be redirected. If a stream
diff --git a/chromecast/public/volume_control.h b/chromecast/public/volume_control.h
index 63d4669..042a0b69 100644
--- a/chromecast/public/volume_control.h
+++ b/chromecast/public/volume_control.h
@@ -102,7 +102,9 @@
   // Called to enable power save mode when no audio is being played
   // (|power_save_on| will be true in this case), and to disable power save mode
   // when audio playback resumes (|power_save_on| will be false).
-  static void SetPowerSaveMode(bool power_save_on);
+  // NOTE: This is optional (therefore a weak symbol) because most platforms
+  // do not have any need to implement it.
+  static void SetPowerSaveMode(bool power_save_on) __attribute__((weak));
 
   // Converts a volume level in the range [0.0, 1.0] to/from a volume in dB.
   // The volume in dB should be full-scale (so a volume level of 1.0 would be
diff --git a/chromeos/components/drivefs/drivefs_host_unittest.cc b/chromeos/components/drivefs/drivefs_host_unittest.cc
index 28c8a4b..acc0c61 100644
--- a/chromeos/components/drivefs/drivefs_host_unittest.cc
+++ b/chromeos/components/drivefs/drivefs_host_unittest.cc
@@ -364,7 +364,7 @@
         .WillOnce(RunQuitClosure(&quit_closure));
     // Eventually we must attempt unmount.
     EXPECT_CALL(*disk_manager_, UnmountPath("/media/drivefsroot/salt-g-ID",
-                                            chromeos::UNMOUNT_OPTIONS_NONE, _));
+                                            chromeos::UNMOUNT_OPTIONS_LAZY, _));
     SendOnMounted();
     run_loop.Run();
     ASSERT_TRUE(host_->IsMounted());
@@ -500,7 +500,7 @@
   auto token = StartMount();
   DispatchMountSuccessEvent(token);
   EXPECT_CALL(*disk_manager_, UnmountPath("/media/drivefsroot/salt-g-ID",
-                                          chromeos::UNMOUNT_OPTIONS_NONE, _));
+                                          chromeos::UNMOUNT_OPTIONS_LAZY, _));
 
   host_.reset();
   EXPECT_FALSE(PendingConnectionManager::Get().OpenIpcChannel(token, {}));
diff --git a/chromeos/components/drivefs/drivefs_session.cc b/chromeos/components/drivefs/drivefs_session.cc
index 581cbd8..2de44b8 100644
--- a/chromeos/components/drivefs/drivefs_session.cc
+++ b/chromeos/components/drivefs/drivefs_session.cc
@@ -55,7 +55,7 @@
     disk_mount_manager_->RemoveObserver(this);
     if (!mount_path_.empty()) {
       disk_mount_manager_->UnmountPath(mount_path_.value(),
-                                       chromeos::UNMOUNT_OPTIONS_NONE, {});
+                                       chromeos::UNMOUNT_OPTIONS_LAZY, {});
       mount_path_.clear();
     }
   }
diff --git a/chromeos/components/drivefs/drivefs_session_unittest.cc b/chromeos/components/drivefs/drivefs_session_unittest.cc
index c69d32aa..b7b6906 100644
--- a/chromeos/components/drivefs/drivefs_session_unittest.cc
+++ b/chromeos/components/drivefs/drivefs_session_unittest.cc
@@ -76,7 +76,7 @@
                       chromeos::MOUNT_TYPE_NETWORK_STORAGE,
                       {}});
   EXPECT_CALL(disk_manager_, UnmountPath(kExpectedMountPath,
-                                         chromeos::UNMOUNT_OPTIONS_NONE, _));
+                                         chromeos::UNMOUNT_OPTIONS_LAZY, _));
   mounter.reset();
 }
 
@@ -91,7 +91,7 @@
                       chromeos::MOUNT_TYPE_NETWORK_STORAGE,
                       {}});
   EXPECT_CALL(disk_manager_, UnmountPath(kExpectedMountPath,
-                                         chromeos::UNMOUNT_OPTIONS_NONE, _));
+                                         chromeos::UNMOUNT_OPTIONS_LAZY, _));
 }
 
 TEST_F(DriveFsDiskMounterTest, DestroyBeforeMounted) {
diff --git a/chromeos/components/proximity_auth/e2e_test/cros.py b/chromeos/components/proximity_auth/e2e_test/cros.py
deleted file mode 100644
index 1209e896..0000000
--- a/chromeos/components/proximity_auth/e2e_test/cros.py
+++ /dev/null
@@ -1,584 +0,0 @@
-# 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.
-
-import logging
-import os
-import subprocess
-import sys
-import time
-
-# Add the telemetry directory to Python's search paths.
-current_directory = os.path.dirname(os.path.realpath(__file__))
-perf_dir = os.path.realpath(
-    os.path.join(current_directory, '..', '..', '..', 'tools', 'perf'))
-if perf_dir not in sys.path:
-  sys.path.append(perf_dir)
-from chrome_telemetry_build import chromium_config
-telemetry_dir = chromium_config.GetTelemetryDir()
-if telemetry_dir not in sys.path:
-  sys.path.append(telemetry_dir)
-
-from telemetry.internal.browser import browser_options
-from telemetry.internal.browser import browser_finder
-from telemetry.core import exceptions
-from telemetry.core import util
-from telemetry.core import cros_interface
-from telemetry.internal.browser import extension_to_load
-
-logger = logging.getLogger('proximity_auth.%s' % __name__)
-
-class AccountPickerScreen(object):
-  """ Wrapper for the ChromeOS account picker screen.
-
-  The account picker screen is the WebContents page used for both the lock
-  screen and signin screen.
-
-  Note: This class assumes the account picker screen only has one user. If there
-  are multiple user pods, the first one will be used.
-  """
-
-  class AuthType:
-    """ The authentication type expected for a user pod. """
-    OFFLINE_PASSWORD = 0
-    ONLINE_SIGN_IN = 1
-    NUMERIC_PIN = 2
-    USER_CLICK = 3
-    EXPAND_THEN_USER_CLICK = 4
-    FORCE_OFFLINE_PASSWORD = 5
-
-  class SmartLockState:
-    """ The state of the Smart Lock icon on a user pod.
-    """
-    NOT_SHOWN = 'not_shown'
-    AUTHENTICATED = 'authenticated'
-    LOCKED = 'locked'
-    HARD_LOCKED = 'hardlocked'
-    TO_BE_ACTIVATED = 'to_be_activated'
-    SPINNER = 'spinner'
-
-  # JavaScript expression for getting the user pod on the page
-  _GET_POD_JS = 'document.getElementById("pod-row").pods[0]'
-
-  def __init__(self, oobe, chromeos):
-    """
-    Args:
-      oobe: Inspector page of the OOBE WebContents.
-      chromeos: The parent Chrome wrapper.
-    """
-    self._oobe = oobe
-    self._chromeos = chromeos
-
-  @property
-  def is_lockscreen(self):
-    return self._oobe.EvaluateJavaScript(
-        '!document.getElementById("sign-out-user-item").hidden')
-
-  @property
-  def auth_type(self):
-    return self._oobe.EvaluateJavaScript(
-        '{{ @pod }}.authType', pod=self._GET_POD_JS)
-
-  @property
-  def smart_lock_state(self):
-    icon_shown = self._oobe.EvaluateJavaScript(
-        '!{{ @pod }}.customIconElement.hidden', pod=self._GET_POD_JS)
-    if not icon_shown:
-      return self.SmartLockState.NOT_SHOWN
-    class_list_dict = self._oobe.EvaluateJavaScript(
-        '{{ @pod }}.customIconElement.querySelector(".custom-icon").classList',
-        pod=self._GET_POD_JS)
-    class_list = [v for k,v in class_list_dict.items() if k != 'length']
-
-    if 'custom-icon-unlocked' in class_list:
-      return self.SmartLockState.AUTHENTICATED
-    if 'custom-icon-locked' in class_list:
-      return self.SmartLockState.LOCKED
-    if 'custom-icon-hardlocked' in class_list:
-      return self.SmartLockState.HARD_LOCKED
-    if 'custom-icon-locked-to-be-activated' in class_list:
-      return self.SmartLockState.TO_BE_ACTIVATED
-    if 'custom-icon-spinner' in class_list:
-      return self.SmartLockState.SPINNER
-
-  def WaitForSmartLockState(self, state, wait_time_secs=60):
-    """ Waits for the Smart Lock icon to reach the given state.
-
-    Args:
-      state: A value in AccountPickerScreen.SmartLockState
-      wait_time_secs: The time to wait
-    Returns:
-      True if the state is reached within the wait time, else False.
-    """
-    try:
-      util.WaitFor(lambda: self.smart_lock_state == state, wait_time_secs)
-      return True
-    except exceptions.TimeoutException:
-      return False
-
-  def EnterPassword(self):
-    """ Enters the password to unlock or sign-in.
-
-    Raises:
-      TimeoutException: entering the password fails to enter/resume the user
-      session.
-    """
-    assert(self.auth_type == self.AuthType.OFFLINE_PASSWORD or
-           self.auth_type == self.AuthType.FORCE_OFFLINE_PASSWORD)
-    oobe = self._oobe
-    oobe.EvaluateJavaScript(
-        '{{ @pod }}.passwordElement.value = {{ password }}',
-        pod=self._GET_POD_JS, password=self._chromeos.password)
-    oobe.EvaluateJavaScript(
-        '{{ @pod }}.activate()', pod=self._GET_POD_JS)
-    util.WaitFor(lambda: (self._chromeos.session_state ==
-                          ChromeOS.SessionState.IN_SESSION),
-                 5)
-
-  def UnlockWithClick(self):
-    """ Clicks the user pod to unlock or sign-in. """
-    assert(self.auth_type == self.AuthType.USER_CLICK)
-    self._oobe.EvaluateJavaScript(
-        '{{ @pod }}.activate()', pod=self._GET_POD_JS)
-
-
-class SmartLockSettings(object):
-  """ Wrapper for the Smart Lock settings in chromeos://settings.
-  """
-  def __init__(self, tab, chromeos):
-    """
-    Args:
-      tab: Inspector page of the chromeos://settings tag.
-      chromeos: The parent Chrome wrapper.
-    """
-    self._tab = tab
-    self._chromeos = chromeos
-
-  @property
-  def is_smart_lock_enabled(self):
-    ''' Returns true if the settings show that Smart Lock is enabled. '''
-    return self._tab.EvaluateJavaScript(
-        '!document.getElementById("easy-unlock-enabled").hidden')
-
-  def TurnOffSmartLock(self):
-    """ Turns off Smart Lock.
-
-    Smart Lock is turned off by clicking the turn-off button and navigating
-    through the resulting overlay.
-
-    Raises:
-      TimeoutException: Timed out waiting for Smart Lock to be turned off.
-    """
-    assert(self.is_smart_lock_enabled)
-    tab = self._tab
-    tab.EvaluateJavaScript(
-        'document.getElementById("easy-unlock-turn-off-button").click()')
-    tab.WaitForJavaScriptCondition(
-        '!document.getElementById("easy-unlock-turn-off-overlay").hidden && '
-        'document.getElementById("easy-unlock-turn-off-confirm") != null',
-        timeout=10)
-    tab.EvaluateJavaScript(
-        'document.getElementById("easy-unlock-turn-off-confirm").click()')
-    tab.WaitForJavaScriptCondition(
-        '!document.getElementById("easy-unlock-disabled").hidden', timeout=15)
-
-  def StartSetup(self):
-    """ Starts the Smart Lock setup flow by clicking the button.
-    """
-    assert(not self.is_smart_lock_enabled)
-    self._tab.EvaluateJavaScript(
-        'document.getElementById("easy-unlock-setup-button").click()')
-
-  def StartSetupAndReturnApp(self):
-    """ Runs the setup and returns the wrapper to the setup app.
-
-    After clicking the setup button in the settings page, enter the password to
-    reauthenticate the user before the app launches.
-
-    Returns:
-      A SmartLockApp object of the app that was launched.
-
-    Raises:
-      TimeoutException: Timed out waiting for app.
-    """
-    self.StartSetup()
-    util.WaitFor(lambda: (self._chromeos.session_state ==
-                          ChromeOS.SessionState.LOCK_SCREEN),
-                 5)
-    lock_screen = self._chromeos.GetAccountPickerScreen()
-    lock_screen.EnterPassword()
-    util.WaitFor(lambda: self._chromeos.GetSmartLockApp() is not None, 10)
-    return self._chromeos.GetSmartLockApp()
-
-
-class SmartLockApp(object):
-  """ Wrapper for the Smart Lock setup dialog.
-
-  Note: This does not include the app's background page.
-  """
-
-  class PairingState:
-    """ The current state of the setup flow. """
-    SCAN = 'scan'
-    PAIR = 'pair'
-    CLICK_FOR_TRIAL_RUN = 'click_for_trial_run'
-    TRIAL_RUN_COMPLETED = 'trial_run_completed'
-    PROMOTE_SMARTLOCK_FOR_ANDROID = 'promote-smart-lock-for-android'
-
-  def __init__(self, app_page, chromeos):
-    """
-    Args:
-      app_page: Inspector page of the app window.
-      chromeos: The parent Chrome wrapper.
-    """
-    self._app_page = app_page
-    self._chromeos = chromeos
-
-  @property
-  def pairing_state(self):
-    ''' Returns the state the app is currently in.
-
-    Raises:
-      ValueError: The current state is unknown.
-    '''
-    state = self._app_page.EvaluateJavaScript(
-        'document.body.getAttribute("step")')
-    if state == 'scan':
-      return SmartLockApp.PairingState.SCAN
-    elif state == 'pair':
-      return SmartLockApp.PairingState.PAIR
-    elif state == 'promote-smart-lock-for-android':
-      return SmartLockApp.PairingState.PROMOTE_SMARTLOCK_FOR_ANDROID
-    elif state == 'complete':
-      button_text = self._app_page.EvaluateJavaScript(
-          'document.getElementById("pairing-button").textContent')
-      button_text = button_text.strip().lower()
-      if button_text == 'try it out':
-        return SmartLockApp.PairingState.CLICK_FOR_TRIAL_RUN
-      elif button_text == 'done':
-        return SmartLockApp.PairingState.TRIAL_RUN_COMPLETED
-      else:
-        raise ValueError('Unknown button text: %s', button_text)
-    else:
-      raise ValueError('Unknown pairing state: %s' % state)
-
-  def FindPhone(self, retries=3):
-    """ Starts the initial step to find nearby phones.
-
-    The app must be in the SCAN state.
-
-    Args:
-      retries: The number of times to retry if no phones are found.
-    Returns:
-      True if a phone is found, else False.
-    """
-    assert(self.pairing_state == self.PairingState.SCAN)
-    for _ in xrange(retries):
-      self._ClickPairingButton()
-      if self.pairing_state == self.PairingState.PAIR:
-        return True
-      # Wait a few seconds before retrying.
-      time.sleep(10)
-    return False
-
-  def PairPhone(self):
-    """ Starts the step of finding nearby phones.
-
-    The app must be in the PAIR state.
-
-    Returns:
-      True if pairing succeeded, else False.
-    """
-    assert(self.pairing_state == self.PairingState.PAIR)
-    self._ClickPairingButton()
-    if self.pairing_state == self.PairingState.PROMOTE_SMARTLOCK_FOR_ANDROID:
-      self._ClickPairingButton()
-    return self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN
-
-  def StartTrialRun(self):
-    """ Starts the trial run.
-
-    The app must be in the CLICK_FOR_TRIAL_RUN state.
-
-    Raises:
-      TimeoutException: Timed out starting the trial run.
-    """
-    assert(self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN)
-    self._app_page.EvaluateJavaScript(
-        'document.getElementById("pairing-button").click()')
-    util.WaitFor(lambda: (self._chromeos.session_state ==
-                          ChromeOS.SessionState.LOCK_SCREEN),
-                 10)
-
-  def DismissApp(self):
-    """ Dismisses the app after setup is completed.
-
-    The app must be in the TRIAL_RUN_COMPLETED state.
-    """
-    assert(self.pairing_state == self.PairingState.TRIAL_RUN_COMPLETED)
-    self._app_page.EvaluateJavaScript(
-        'document.getElementById("pairing-button").click()')
-
-  def _ClickPairingButton(self):
-    # Waits are needed because the clicks occur before the button label changes.
-    time.sleep(1)
-    self._app_page.EvaluateJavaScript(
-        'document.getElementById("pairing-button").click()')
-    time.sleep(1)
-    self._app_page.WaitForJavaScriptCondition(
-        '!document.getElementById("pairing-button").disabled', timeout=60)
-    time.sleep(1)
-    self._app_page.WaitForJavaScriptCondition(
-        '!document.getElementById("pairing-button-title")'
-        '.classList.contains("animated-fade-out")', timeout=5)
-    self._app_page.WaitForJavaScriptCondition(
-        '!document.getElementById("pairing-button-title")'
-        '.classList.contains("animated-fade-in")', timeout=5)
-
-
-class ChromeOS(object):
-  """ Wrapper for a remote ChromeOS device.
-
-  Operations performed through this wrapper are sent through the network to
-  Chrome using the Chrome DevTools API. Therefore, any function may throw an
-  exception if the communication to the remote device is severed.
-  """
-
-  class SessionState:
-    """ The state of the user session.
-    """
-    SIGNIN_SCREEN = 'signin_screen'
-    IN_SESSION = 'in_session'
-    LOCK_SCREEN = 'lock_screen'
-
-  _SMART_LOCK_SETTINGS_URL = 'chrome://settings/search#Smart%20Lock'
-
-  def __init__(self, remote_address, username, password, ssh_port=None):
-    """
-    Args:
-      remote_address: The remote address of the cros device.
-      username: The username of the account to test.
-      password: The password of the account to test.
-      ssh_port: The ssh port to connect to.
-    """
-    self._remote_address = remote_address
-    self._username = username
-    self._password = password
-    self._ssh_port = ssh_port
-    self._browser_to_create = None
-    self._browser = None
-    self._cros_interface = None
-    self._background_page = None
-    self._processes = []
-
-  @property
-  def username(self):
-    ''' Returns the username of the user to login. '''
-    return self._username
-
-  @property
-  def password(self):
-    ''' Returns the password of the user to login. '''
-    return self._password
-
-  @property
-  def session_state(self):
-    ''' Returns the state of the user session. '''
-    assert(self._browser is not None)
-    if self._browser.oobe_exists:
-      if self._cros_interface.IsCryptohomeMounted(self.username, False):
-        return self.SessionState.LOCK_SCREEN
-      else:
-        return self.SessionState.SIGNIN_SCREEN
-    else:
-      return self.SessionState.IN_SESSION;
-
-  @property
-  def cryptauth_access_token(self):
-    try:
-      self._background_page.WaitForJavaScriptCondition(
-            'var __token = __token || null; '
-            'chrome.identity.getAuthToken(function(token) {'
-            '  __token = token;'
-            '}); '
-            '__token != null', timeout=5)
-      return self._background_page.EvaluateJavaScript('__token');
-    except exceptions.TimeoutException:
-      logger.error('Failed to get access token.');
-      return ''
-
-  def __enter__(self):
-    return self
-
-  def __exit__(self, *args):
-    if self._browser is not None:
-      self._browser.Close()
-    if self._browser_to_create is not None:
-      self._browser_to_create.CleanUpEnvironment()
-    if self._cros_interface is not None:
-      self._cros_interface.CloseConnection()
-    for process in self._processes:
-      process.terminate()
-
-  def Start(self, local_app_path=None):
-    """ Connects to the ChromeOS device and logs in.
-    Args:
-      local_app_path: A path on the local device containing the Smart Lock app
-                      to use instead of the app on the ChromeOS device.
-    Return:
-      |self| for using in a "with" statement.
-    """
-    assert(self._browser is None)
-
-    finder_opts = browser_options.BrowserFinderOptions('cros-chrome')
-    finder_opts.CreateParser().parse_args(args=[])
-    finder_opts.cros_remote = self._remote_address
-    if self._ssh_port is not None:
-      finder_opts.cros_remote_ssh_port = self._ssh_port
-    finder_opts.verbosity = 1
-
-    browser_opts = finder_opts.browser_options
-    browser_opts.create_browser_with_oobe = True
-    browser_opts.disable_component_extensions_with_background_pages = False
-    browser_opts.gaia_login = True
-    browser_opts.username = self._username
-    browser_opts.password = self._password
-    browser_opts.auto_login = True
-
-    self._cros_interface = cros_interface.CrOSInterface(
-        finder_opts.cros_remote,
-        finder_opts.cros_remote_ssh_port,
-        finder_opts.cros_ssh_identity)
-
-    browser_opts.disable_default_apps = local_app_path is not None
-    if local_app_path is not None:
-      easy_unlock_app = extension_to_load.ExtensionToLoad(
-          path=local_app_path,
-          browser_type='cros-chrome',
-          is_component=True)
-      finder_opts.extensions_to_load.append(easy_unlock_app)
-
-    self._browser_to_create = browser_finder.FindBrowser(finder_opts)
-    self._browser_to_create.SetUpEnvironment(browser_opts)
-
-    retries = 3
-    while self._browser is not None or retries > 0:
-      try:
-        self._browser = self._browser_to_create.Create()
-        break;
-      except (exceptions.LoginException) as e:
-        logger.error('Timed out logging in: %s' % e);
-        if retries == 1:
-          raise
-
-    bg_page_path = '/_generated_background_page.html'
-    util.WaitFor(
-        lambda: self._FindSmartLockAppPage(bg_page_path) is not None,
-        10);
-    self._background_page = self._FindSmartLockAppPage(bg_page_path)
-    return self
-
-  def GetAccountPickerScreen(self):
-    """ Returns the wrapper for the lock screen or sign-in screen.
-
-    Return:
-      An instance of AccountPickerScreen.
-    Raises:
-      TimeoutException: Timed out waiting for account picker screen to load.
-    """
-    assert(self._browser is not None)
-    assert(self.session_state == self.SessionState.LOCK_SCREEN or
-           self.session_state == self.SessionState.SIGNIN_SCREEN)
-    oobe = self._browser.oobe
-    def IsLockScreenResponsive():
-      return (oobe.EvaluateJavaScript("typeof Oobe == 'function'") and
-              oobe.EvaluateJavaScript(
-                  "typeof Oobe.authenticateForTesting == 'function'"))
-    util.WaitFor(IsLockScreenResponsive, 10)
-    oobe.WaitForJavaScriptCondition(
-        'document.getElementById("pod-row") && '
-        'document.getElementById("pod-row").pods && '
-        'document.getElementById("pod-row").pods.length > 0', timeout=10)
-    return AccountPickerScreen(oobe, self)
-
-  def GetSmartLockSettings(self):
-    """ Returns the wrapper for the Smart Lock settings.
-    A tab will be navigated to chrome://settings if it does not exist.
-
-    Return:
-      An instance of SmartLockSettings.
-    Raises:
-      TimeoutException: Timed out waiting for settings page.
-    """
-    if not len(self._browser.tabs):
-      self._browser.New()
-    tab = self._browser.tabs[0]
-    url = tab.EvaluateJavaScript('document.location.href')
-    if url != self._SMART_LOCK_SETTINGS_URL:
-      tab.Navigate(self._SMART_LOCK_SETTINGS_URL)
-
-    # Wait for settings page to be responsive.
-    tab.WaitForJavaScriptCondition(
-        'document.getElementById("easy-unlock-disabled") && '
-        'document.getElementById("easy-unlock-enabled") && '
-        '(!document.getElementById("easy-unlock-disabled").hidden || '
-        ' !document.getElementById("easy-unlock-enabled").hidden)',
-        timeout=10)
-    settings = SmartLockSettings(tab, self)
-    logger.info('Started Smart Lock settings: enabled=%s' %
-                 settings.is_smart_lock_enabled)
-    return settings
-
-  def GetSmartLockApp(self):
-    """ Returns the wrapper for the Smart Lock setup app.
-
-    Return:
-      An instance of SmartLockApp or None if the app window does not exist.
-    """
-    app_page = self._FindSmartLockAppPage('/pairing.html')
-    if app_page is not None:
-      # Wait for app window to be responsive.
-      tab.WaitForJavaScriptCondition(
-            'document.getElementById("pairing-button") != null', timeout=10)
-      return SmartLockApp(app_page, self)
-    return None
-
-  def SetCryptAuthStaging(self, cryptauth_staging_url):
-    logger.info('Setting CryptAuth to Staging')
-    try:
-      self._background_page.ExecuteJavaScript("""
-          var key = app.CryptAuthClient.GOOGLE_API_URL_OVERRIDE_;
-          var __complete = false;
-          chrome.storage.local.set({key: {{ url }}}, function() {
-              __complete = true;
-          });""",
-          url=cryptauth_staging_url)
-      self._background_page.WaitForJavaScriptCondition(
-          '__complete == true', timeout=10)
-    except exceptions.TimeoutException:
-      logger.error('Failed to override CryptAuth to staging url.')
-
-  def RunBtmon(self):
-    """ Runs the btmon command.
-    Return:
-      A subprocess.Popen object of the btmon process.
-    """
-    assert(self._cros_interface)
-    cmd = self._cros_interface.FormSSHCommandLine(['btmon'])
-    process = subprocess.Popen(args=cmd, stdout=subprocess.PIPE,
-                               stderr=subprocess.PIPE)
-    self._processes.append(process)
-    return process
-
-  def _FindSmartLockAppPage(self, page_name):
-    try:
-      extensions = self._browser.extensions.GetByExtensionId(
-          'mkaemigholebcgchlkbankmihknojeak')
-    except KeyError:
-      return None
-    for extension_page in extensions:
-      pathname = extension_page.EvaluateJavaScript(
-          'document.location.pathname')
-      if pathname == page_name:
-        return extension_page
-    return None
diff --git a/chromeos/components/proximity_auth/e2e_test/cryptauth.py b/chromeos/components/proximity_auth/e2e_test/cryptauth.py
deleted file mode 100644
index 7373fa9..0000000
--- a/chromeos/components/proximity_auth/e2e_test/cryptauth.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# 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.
-
-import httplib
-import json
-import logging
-import pprint
-import time
-
-logger = logging.getLogger('proximity_auth.%s' % __name__)
-
-_GOOGLE_APIS_URL = 'www.googleapis.com'
-_REQUEST_PATH = '/cryptauth/v1/%s?alt=JSON'
-
-class CryptAuthClient(object):
-  """ A client for making blocking CryptAuth API calls. """
-
-  def __init__(self, access_token, google_apis_url=_GOOGLE_APIS_URL):
-    self._access_token = access_token
-    self._google_apis_url = google_apis_url
-
-  def GetMyDevices(self):
-    """ Invokes the GetMyDevices API.
-
-    Returns:
-      A list of devices or None if the API call fails.
-      Each device is a dictionary of the deserialized JSON returned by
-      CryptAuth.
-    """
-    request_data = {
-      'approvedForUnlockRequired': False,
-      'allowStaleRead': False,
-      'invocationReason': 13 # REASON_MANUAL
-    }
-    response = self._SendRequest('deviceSync/getmydevices', request_data)
-    return response['devices'] if response is not None else None
-
-  def GetUnlockKey(self):
-    """
-    Returns:
-      The unlock key registered with CryptAuth if it exists or None.
-      The device is a dictionary of the deserialized JSON returned by CryptAuth.
-    """
-    devices = self.GetMyDevices()
-    if devices is None:
-      return None
-
-    for device in devices:
-      if device['unlockKey']:
-        return device
-    return None
-
-  def ToggleEasyUnlock(self, enable, public_key=''):
-    """ Calls the ToggleEasyUnlock API.
-    Args:
-      enable: True to designate the device specified by |public_key| as an
-          unlock key.
-      public_key: The public key of the device to toggle. Ignored if |enable| is
-          False, which toggles all unlock keys off.
-    Returns:
-      True upon success, else False.
-    """
-    request_data = { 'enable': enable, }
-    if not enable:
-      request_data['applyToAll'] = True
-    else:
-      request_data['publicKey'] = public_key
-    response = self._SendRequest('deviceSync/toggleeasyunlock', request_data)
-    return response is not None
-
-  def FindEligibleUnlockDevices(self, time_delta_millis=None):
-    """ Finds devices eligible to be an unlock key.
-    Args:
-      time_delta_millis: If specified, then only return eligible devices that
-          have contacted CryptAuth in the last time delta.
-    Returns:
-      A tuple containing two lists, one of eligible devices and the other of
-      ineligible devices.
-      Each device is a dictionary of the deserialized JSON returned by
-      CryptAuth.
-    """
-    request_data = {}
-    if time_delta_millis is not None:
-      request_data['maxLastUpdateTimeDeltaMillis'] = time_delta_millis * 1000;
-
-    response = self._SendRequest(
-        'deviceSync/findeligibleunlockdevices', request_data)
-    if response is None:
-      return None
-    eligibleDevices = (
-        response['eligibleDevices'] if 'eligibleDevices' in response else [])
-    ineligibleDevices = (
-        response['ineligibleDevices'] if (
-            'ineligibleDevices' in response) else [])
-    return eligibleDevices, ineligibleDevices
-
-  def PingPhones(self, timeout_secs=10):
-    """ Asks CryptAuth to ping registered phones and determine their status.
-    Args:
-      timeout_secs: The number of seconds to wait for phones to respond.
-    Returns:
-      A tuple containing two lists, one of eligible devices and the other of
-      ineligible devices.
-      Each device is a dictionary of the deserialized JSON returned by
-      CryptAuth.
-    """
-    response = self._SendRequest(
-        'deviceSync/senddevicesynctickle',
-        { 'tickleType': 'updateEnrollment' })
-    if response is None:
-      return None
-    # We wait for phones to update their status with CryptAuth.
-    logger.info('Waiting for %s seconds for phone status...' % timeout_secs)
-    time.sleep(timeout_secs)
-    return self.FindEligibleUnlockDevices(time_delta_millis=timeout_secs)
-
-  def _SendRequest(self, function_path, request_data):
-    """ Sends an HTTP request to CryptAuth and returns the deserialized
-        response.
-    """
-    conn = httplib.HTTPSConnection(self._google_apis_url)
-    path = _REQUEST_PATH % function_path
-
-    headers = {
-      'authorization': 'Bearer ' + self._access_token,
-      'Content-Type': 'application/json'
-    }
-    body = json.dumps(request_data)
-    logger.info('Making request to %s with body:\n%s' % (
-                path, pprint.pformat(request_data)))
-    conn.request('POST', path, body, headers)
-
-    response = conn.getresponse()
-    if response.status == 204:
-      return {}
-    if response.status != 200:
-      logger.warning('Request to %s failed: %s' % (path, response.status))
-      return None
-    return json.loads(response.read())
diff --git a/chromeos/components/proximity_auth/e2e_test/setup_test.py b/chromeos/components/proximity_auth/e2e_test/setup_test.py
deleted file mode 100644
index 9d7cf785..0000000
--- a/chromeos/components/proximity_auth/e2e_test/setup_test.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# 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.
-
-""" Script that exercises the Smart Lock setup flow, testing that a nearby phone
-can be found and used to unlock a Chromebook.
-
-Note: This script does not currently automate Android phones, so make sure that
-a phone is properly configured and online before starting the test.
-
-Usage:
-  python setup_test.py --remote_address REMOTE_ADDRESS
-                       --username USERNAME
-                       --password PASSWORD
-                       [--app_path APP_PATH]
-                       [--ssh_port SSH_PORT]
-                       [--cryptauth_staging_url STAGING_URL]
-  If |--app_path| is provided, then a copy of the Smart Lock app on the local
-  machine will be used instead of the app on the ChromeOS device.
-"""
-
-import argparse
-import cros
-import cryptauth
-import logging
-import os
-import subprocess
-import sys
-import tempfile
-
-logger = logging.getLogger('proximity_auth.%s' % __name__)
-
-class SmartLockSetupError(Exception):
-  pass
-
-def pingable_address(address):
-  try:
-    subprocess.check_output(['ping', '-c', '1', '-W', '1', address])
-  except subprocess.CalledProcessError:
-    raise argparse.ArgumentError('%s cannot be reached.' % address)
-  return address
-
-def email(arg):
-  tokens = arg.lower().split('@')
-  if len(tokens) != 2 or '.' not in tokens[1]:
-    raise argparse.ArgumentError('%s is not a valid email address' % arg)
-  name, domain = tokens
-  if domain == 'gmail.com':
-    name = name.replace('.', '')
-  return '@'.join([name, domain])
-
-def directory(path):
-  if not os.path.isdir(path):
-    raise argparse.ArgumentError('%s is not a directory' % path)
-  return path
-
-def ParseArgs():
-  parser = argparse.ArgumentParser(prog='python setup_test.py')
-  parser.add_argument('--remote_address', required=True, type=pingable_address)
-  parser.add_argument('--username', required=True, type=email)
-  parser.add_argument('--password', required=True)
-  parser.add_argument('--ssh_port', type=int)
-  parser.add_argument('--app_path', type=directory)
-  parser.add_argument('--cryptauth_staging_url', type=str)
-  args = parser.parse_args()
-  return args
-
-def CheckCryptAuthState(access_token):
-  cryptauth_client = cryptauth.CryptAuthClient(access_token)
-
-  # Check if we can make CryptAuth requests.
-  if cryptauth_client.GetMyDevices() is None:
-    logger.error('Cannot reach CryptAuth on test machine.')
-    return False
-
-  if cryptauth_client.GetUnlockKey() is not None:
-    logger.info('Smart Lock currently enabled, turning off on Cryptauth...')
-    if not cryptauth_client.ToggleEasyUnlock(False):
-      logger.error('ToggleEasyUnlock request failed.')
-      return False
-
-  result = cryptauth_client.FindEligibleUnlockDevices()
-  if result is None:
-    logger.error('FindEligibleUnlockDevices request failed')
-    return False
-  eligibleDevices, _ = result
-  if len(eligibleDevices) == 0:
-    logger.warn('No eligible phones found, trying to ping phones...')
-    result = cryptauth_client.PingPhones()
-    if result is None or not len(result[0]):
-      logger.error('Pinging phones failed :(')
-      return False
-    else:
-      logger.info('Pinging phones succeeded!')
-  else:
-    logger.info('Found eligible device: %s' % (
-                eligibleDevices[0]['friendlyDeviceName']))
-  return True
-
-def _NavigateSetupDialog(chromeos, app):
-  logger.info('Scanning for nearby phones...')
-  btmon = chromeos.RunBtmon()
-  find_phone_success = app.FindPhone()
-  btmon.terminate()
-
-  if not find_phone_success:
-    fd, filepath = tempfile.mkstemp(prefix='btmon-')
-    os.write(fd, btmon.stdout.read())
-    os.close(fd)
-    logger.info('Logs for btmon can be found at %s' % filepath)
-    raise SmartLockSetupError("Failed to find nearby phone.")
-
-  logger.info('Phone found! Starting pairing...')
-  if not app.PairPhone():
-    raise SmartLockSetupError("Failed to pair with phone.")
-  logger.info('Pairing success! Starting trial run...')
-  app.StartTrialRun()
-
-  logger.info('Unlocking for trial run...')
-  lock_screen = chromeos.GetAccountPickerScreen()
-  lock_screen.WaitForSmartLockState(
-      lock_screen.SmartLockState.AUTHENTICATED)
-  lock_screen.UnlockWithClick()
-
-  logger.info('Trial run success! Dismissing app...')
-  app.DismissApp()
-
-def RunSetupTest(args):
-  logger.info('Starting test for %s at %s' % (
-              args.username, args.remote_address))
-  if args.app_path is not None:
-    logger.info('Replacing Smart Lock app with %s' % args.app_path)
-
-  chromeos = cros.ChromeOS(
-      args.remote_address, args.username, args.password, ssh_port=args.ssh_port)
-  with chromeos.Start(local_app_path=args.app_path):
-    logger.info('Chrome initialized')
-
-    # TODO(tengs): The access token is currently fetched from the Smart Lock
-    # app's background page. To be more robust, we should instead mint the
-    # access token ourselves.
-    if not CheckCryptAuthState(chromeos.cryptauth_access_token):
-      raise SmartLockSetupError('Failed to check CryptAuth state')
-
-    logger.info('Opening Smart Lock settings...')
-    settings = chromeos.GetSmartLockSettings()
-    assert(not settings.is_smart_lock_enabled)
-
-    if args.cryptauth_staging_url is not None:
-      chromeos.SetCryptAuthStaging(args.cryptauth_staging_url)
-
-    logger.info('Starting Smart Lock setup flow...')
-    app = settings.StartSetupAndReturnApp()
-
-    if app is None:
-      raise SmartLockSetupError('Failed to obtain set up app window')
-
-    _NavigateSetupDialog(chromeos, app)
-
-def main():
-  logging.basicConfig()
-  logging.getLogger('proximity_auth').setLevel(logging.INFO)
-  args = ParseArgs()
-  RunSetupTest(args)
-
-if __name__ == '__main__':
-  main()
diff --git a/chromeos/services/device_sync/cryptauth_enrollment_result.cc b/chromeos/services/device_sync/cryptauth_enrollment_result.cc
index dfcc7f9f..0c85864 100644
--- a/chromeos/services/device_sync/cryptauth_enrollment_result.cc
+++ b/chromeos/services/device_sync/cryptauth_enrollment_result.cc
@@ -116,7 +116,11 @@
       stream << "[Error: KeyActions do not specify an active key]";
       break;
     case ResultCode::kErrorKeyCreationKeyTypeNotSupported:
-      stream << "[Error: KeyCreation instructions specify unsupported KeyType]";
+      stream << "[Error: Key-creation instructions specify unsupported "
+             << "KeyType]";
+      break;
+    case ResultCode::kErrorUserKeyPairCreationInstructionsInvalid:
+      stream << "[Error: Key-creation instructions for user key pair invalid]";
       break;
     case ResultCode::kErrorSymmetricKeyCreationMissingServerDiffieHellman:
       stream << "[Error: Cannot create symmetric key; missing server "
diff --git a/chromeos/services/device_sync/cryptauth_enrollment_result.h b/chromeos/services/device_sync/cryptauth_enrollment_result.h
index f093af12..0213753 100644
--- a/chromeos/services/device_sync/cryptauth_enrollment_result.h
+++ b/chromeos/services/device_sync/cryptauth_enrollment_result.h
@@ -79,6 +79,9 @@
     kErrorKeyActionsDoNotSpecifyAnActiveKey,
     // KeyCreation instructions specify an unsupported KeyType.
     kErrorKeyCreationKeyTypeNotSupported,
+    // Invalid key-creation instructions for user key pair. It must be P256 and
+    // active.
+    kErrorUserKeyPairCreationInstructionsInvalid,
     // Cannot create a symmetric key without the server's Diffie-Hellman key.
     kErrorSymmetricKeyCreationMissingServerDiffieHellman,
     // Failed to compute at least one key proof.
diff --git a/chromeos/services/device_sync/cryptauth_key_bundle.cc b/chromeos/services/device_sync/cryptauth_key_bundle.cc
index 62f0ac1..d37c762 100644
--- a/chromeos/services/device_sync/cryptauth_key_bundle.cc
+++ b/chromeos/services/device_sync/cryptauth_key_bundle.cc
@@ -152,8 +152,12 @@
 }
 
 void CryptAuthKeyBundle::AddKey(const CryptAuthKey& key) {
+  DCHECK(name_ != Name::kUserKeyPair ||
+         key.handle() == kCryptAuthFixedUserKeyPairHandle);
+
   if (key.status() == CryptAuthKey::Status::kActive)
     DeactivateKeys();
+
   handle_to_key_map_.insert_or_assign(key.handle(), key);
 }
 
diff --git a/chromeos/services/device_sync/cryptauth_key_bundle.h b/chromeos/services/device_sync/cryptauth_key_bundle.h
index 296d2b5..cff51ee 100644
--- a/chromeos/services/device_sync/cryptauth_key_bundle.h
+++ b/chromeos/services/device_sync/cryptauth_key_bundle.h
@@ -66,6 +66,8 @@
   // If the key being added is active, all other keys in the bundle will be
   // deactivated. If the handle of the input key matches one in the bundle, the
   // existing key will be overwritten.
+  // Note: All keys added to the bundle kUserKeyPair must have the handle
+  // kCryptAuthFixedUserKeyPairHandle.
   void AddKey(const CryptAuthKey& key);
 
   // Activates the key corresponding to |handle| in the bundle and deactivates
diff --git a/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc b/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc
index 4d27af8..3b0af62b 100644
--- a/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chromeos/services/device_sync/cryptauth_key_bundle.h"
+#include "chromeos/services/device_sync/cryptauth_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -20,14 +21,14 @@
 }  // namespace
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, CreateKeyBundle) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
-  EXPECT_EQ(bundle.name(), CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
+  EXPECT_EQ(bundle.name(), CryptAuthKeyBundle::Name::kLegacyMasterKey);
   EXPECT_TRUE(bundle.handle_to_key_map().empty());
   EXPECT_FALSE(bundle.key_directive());
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, SetKeyDirective) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   cryptauthv2::KeyDirective key_directive;
   bundle.set_key_directive(key_directive);
   ASSERT_TRUE(bundle.key_directive());
@@ -36,20 +37,35 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
-  CryptAuthKey key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
-                   cryptauthv2::KeyType::RAW256, kFakeSymmetricKeyHandle);
-  bundle.AddKey(key);
-  EXPECT_TRUE(bundle.handle_to_key_map().size() == 1);
+  CryptAuthKeyBundle bundle_legacy(CryptAuthKeyBundle::Name::kLegacyMasterKey);
+  CryptAuthKey sym_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
+                       cryptauthv2::KeyType::RAW256, kFakeSymmetricKeyHandle);
+  bundle_legacy.AddKey(sym_key);
+  EXPECT_TRUE(bundle_legacy.handle_to_key_map().size() == 1);
+  const auto& legacy_it =
+      bundle_legacy.handle_to_key_map().find(kFakeSymmetricKeyHandle);
+  ASSERT_TRUE(legacy_it != bundle_legacy.handle_to_key_map().end());
+  EXPECT_EQ(legacy_it->first, sym_key.handle());
+  EXPECT_EQ(legacy_it->second, sym_key);
 
-  const auto& it = bundle.handle_to_key_map().find(kFakeSymmetricKeyHandle);
-  ASSERT_TRUE(it != bundle.handle_to_key_map().end());
-  EXPECT_EQ(it->first, key.handle());
-  EXPECT_EQ(it->second, key);
+  // Note: Handles for kUserKeyPair must be kCryptAuthFixedUserKeyPairHandle.
+  CryptAuthKeyBundle bundle_user_key_pair(
+      CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKey asym_key(
+      kFakePublicKey, kFakePrivateKey, CryptAuthKey::Status::kActive,
+      cryptauthv2::KeyType::P256, kCryptAuthFixedUserKeyPairHandle);
+  bundle_user_key_pair.AddKey(asym_key);
+  EXPECT_TRUE(bundle_user_key_pair.handle_to_key_map().size() == 1);
+  const auto& user_key_pair_it = bundle_user_key_pair.handle_to_key_map().find(
+      kCryptAuthFixedUserKeyPairHandle);
+  ASSERT_TRUE(user_key_pair_it !=
+              bundle_user_key_pair.handle_to_key_map().end());
+  EXPECT_EQ(user_key_pair_it->first, asym_key.handle());
+  EXPECT_EQ(user_key_pair_it->second, asym_key);
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey_Inactive) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
@@ -72,7 +88,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey_ActiveKeyDeactivatesOthers) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
@@ -95,7 +111,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey_ReplaceKeyWithSameHandle) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256, "same-handle");
   bundle.AddKey(symmetric_key);
@@ -111,7 +127,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, GetActiveKey_DoesNotExist) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   EXPECT_FALSE(bundle.GetActiveKey());
 
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
@@ -122,7 +138,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, GetActiveKey_Exists) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
@@ -137,7 +153,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, SetActiveKey_InactiveToActive) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
@@ -159,7 +175,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, SetActiveKey_ActiveToActive) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
@@ -181,7 +197,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, DeactivateKeys) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
@@ -203,7 +219,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, DeleteKey) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
@@ -215,7 +231,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, ToAndFromDictionary_Trivial) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   base::Optional<CryptAuthKeyBundle> bundle_from_dict =
       CryptAuthKeyBundle::FromDictionary(bundle.AsDictionary());
   ASSERT_TRUE(bundle_from_dict);
@@ -223,7 +239,7 @@
 }
 
 TEST(DeviceSyncCryptAuthKeyBundleTest, ToAndFromDictionary) {
-  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
                              kFakeSymmetricKeyHandle);
diff --git a/chromeos/services/device_sync/cryptauth_key_creator.cc b/chromeos/services/device_sync/cryptauth_key_creator.cc
index 30c70ce..2716f5a 100644
--- a/chromeos/services/device_sync/cryptauth_key_creator.cc
+++ b/chromeos/services/device_sync/cryptauth_key_creator.cc
@@ -14,6 +14,22 @@
     base::Optional<std::string> handle)
     : status(status), type(type), handle(handle) {}
 
+CryptAuthKeyCreator::CreateKeyData::CreateKeyData(
+    CryptAuthKey::Status status,
+    cryptauthv2::KeyType type,
+    const std::string& handle,
+    const std::string& public_key,
+    const std::string& private_key)
+    : status(status),
+      type(type),
+      handle(handle),
+      public_key(public_key),
+      private_key(private_key) {
+  DCHECK(!handle.empty());
+  DCHECK(!public_key.empty());
+  DCHECK(!private_key.empty());
+}
+
 CryptAuthKeyCreator::CreateKeyData::~CreateKeyData() = default;
 
 CryptAuthKeyCreator::CreateKeyData::CreateKeyData(const CreateKeyData&) =
diff --git a/chromeos/services/device_sync/cryptauth_key_creator.h b/chromeos/services/device_sync/cryptauth_key_creator.h
index 243868d..d0ec0072 100644
--- a/chromeos/services/device_sync/cryptauth_key_creator.h
+++ b/chromeos/services/device_sync/cryptauth_key_creator.h
@@ -38,7 +38,7 @@
 //   the CryptAuth server. Specifically,
 //
 //   |derived_key| =
-//       Hkdf(|secret|, salt="CryptAuth Enrollment", info=|derived_key_handle|).
+//       Hkdf(|secret|, salt="CryptAuth Enrollment", info=|key_bundle_name|).
 //
 //   The CryptAuth server's Diffie-Hellman key is passed into CreateKeys(),
 //   CreateKeys() generates the client side of the Diffie-Hellman handshake, and
@@ -49,12 +49,27 @@
     CreateKeyData(CryptAuthKey::Status status,
                   cryptauthv2::KeyType type,
                   base::Optional<std::string> handle = base::nullopt);
+
+    // Special constructor needed to handle existing user key pair. The input
+    // strings cannot be empty.
+    CreateKeyData(CryptAuthKey::Status status,
+                  cryptauthv2::KeyType type,
+                  const std::string& handle,
+                  const std::string& public_key,
+                  const std::string& private_key);
+
     ~CreateKeyData();
     CreateKeyData(const CreateKeyData&);
 
     CryptAuthKey::Status status;
     cryptauthv2::KeyType type;
     base::Optional<std::string> handle;
+    // Special data needed to handle existing user key pair. If these are both
+    // non-empty strings and the key type is asymmetric, then the key creator
+    // will bypass the standard key creation and simply return
+    // CryptAuthKey(|public_key|, |private_key|, |status|, |type|, |handle|).
+    base::Optional<std::string> public_key;
+    base::Optional<std::string> private_key;
   };
 
   CryptAuthKeyCreator();
diff --git a/chromeos/services/device_sync/cryptauth_key_creator_impl.cc b/chromeos/services/device_sync/cryptauth_key_creator_impl.cc
index c12f83a..1298e9c 100644
--- a/chromeos/services/device_sync/cryptauth_key_creator_impl.cc
+++ b/chromeos/services/device_sync/cryptauth_key_creator_impl.cc
@@ -4,24 +4,14 @@
 
 #include "chromeos/services/device_sync/cryptauth_key_creator_impl.h"
 
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "base/base64.h"
 #include "base/bind.h"
-#include "base/containers/flat_map.h"
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
-#include "base/optional.h"
 #include "base/strings/string_util.h"
-#include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/components/multidevice/secure_message_delegate_impl.h"
 #include "chromeos/services/device_sync/cryptauth_constants.h"
-#include "chromeos/services/device_sync/cryptauth_key.h"
 #include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
 #include "crypto/hkdf.h"
-#include "crypto/random.h"
 
 namespace chromeos {
 
@@ -65,17 +55,6 @@
   }
 }
 
-// Creates a handle by base64-encoding 32 random bytes.
-std::string CreateRandomHandle() {
-  std::string bytes(32, '\0');
-  crypto::RandBytes(base::WriteInto(&bytes, bytes.size()), bytes.size());
-
-  std::string handle;
-  base::Base64Encode(bytes, &handle);
-
-  return handle;
-}
-
 }  // namespace
 
 // static
@@ -116,9 +95,10 @@
   DCHECK(!keys_to_create.empty());
 
   // Fail if CreateKeys() has already been called.
-  DCHECK(keys_to_create_.empty() && new_keys_.empty() &&
+  DCHECK(num_keys_to_create_ == 0 && new_keys_.empty() &&
          !create_keys_callback_);
 
+  num_keys_to_create_ = keys_to_create.size();
   keys_to_create_ = keys_to_create;
   server_ephemeral_dh_ = server_ephemeral_dh;
   create_keys_callback_ = std::move(create_keys_callback);
@@ -165,29 +145,39 @@
 
 void CryptAuthKeyCreatorImpl::StartKeyCreation() {
   for (const auto& key_to_create : keys_to_create_) {
+    const CryptAuthKeyBundle::Name& bundle_name = key_to_create.first;
+    const CreateKeyData& key_data = key_to_create.second;
+
     // If the key to create is symmetric, derive a symmetric key from the
     // Diffie-Hellman handshake secrect using HKDF. The CryptAuth v2
     // Enrollment protocol specifies that the salt should be "CryptAuth
     // Enrollment" and the info should be the key handle. This process is
     // synchronous, unlike SecureMessageDelegate calls.
-    if (IsValidSymmetricKeyType(key_to_create.second.type)) {
-      std::string handle = key_to_create.second.handle.has_value()
-                               ? *key_to_create.second.handle
-                               : CreateRandomHandle();
+    if (IsValidSymmetricKeyType(key_data.type)) {
       std::string derived_symmetric_key_material = crypto::HkdfSha256(
           dh_handshake_secret_->symmetric_key(),
-          kCryptAuthSymmetricKeyDerivationSalt, handle,
-          NumBytesForSymmetricKeyType(key_to_create.second.type));
+          kCryptAuthSymmetricKeyDerivationSalt,
+          CryptAuthKeyBundle::KeyBundleNameEnumToString(bundle_name),
+          NumBytesForSymmetricKeyType(key_data.type));
 
-      OnSymmetricKeyDerived(key_to_create.first, derived_symmetric_key_material,
-                            handle);
-    } else {
-      DCHECK(IsValidAsymmetricKeyType(key_to_create.second.type));
+      OnSymmetricKeyDerived(bundle_name, derived_symmetric_key_material);
 
-      secure_message_delegate_->GenerateKeyPair(
-          base::Bind(&CryptAuthKeyCreatorImpl::OnAsymmetricKeyPairGenerated,
-                     base::Unretained(this), key_to_create.first));
+      continue;
     }
+
+    DCHECK(IsValidAsymmetricKeyType(key_data.type));
+
+    // If the key material was explicitly set in CreateKeyData, bypass the
+    // standard key creation.
+    if (key_data.public_key && key_data.private_key) {
+      OnAsymmetricKeyPairGenerated(bundle_name, *key_data.public_key,
+                                   *key_data.private_key);
+      continue;
+    }
+
+    secure_message_delegate_->GenerateKeyPair(
+        base::Bind(&CryptAuthKeyCreatorImpl::OnAsymmetricKeyPairGenerated,
+                   base::Unretained(this), key_to_create.first));
   }
 }
 
@@ -195,7 +185,7 @@
     CryptAuthKeyBundle::Name bundle_name,
     const std::string& public_key,
     const std::string& private_key) {
-  DCHECK(keys_to_create_.size() > 0);
+  DCHECK(num_keys_to_create_ > 0);
   DCHECK(!public_key.empty() && !private_key.empty());
 
   const CryptAuthKeyCreator::CreateKeyData& create_key_data =
@@ -205,26 +195,25 @@
                         create_key_data.status, create_key_data.type,
                         create_key_data.handle);
 
-  keys_to_create_.erase(bundle_name);
-  if (keys_to_create_.empty())
+  --num_keys_to_create_;
+  if (num_keys_to_create_ == 0)
     std::move(create_keys_callback_).Run(new_keys_, client_ephemeral_dh_);
 }
 
 void CryptAuthKeyCreatorImpl::OnSymmetricKeyDerived(
     CryptAuthKeyBundle::Name bundle_name,
-    const std::string& symmetric_key,
-    const std::string& handle) {
-  DCHECK(keys_to_create_.size() > 0);
+    const std::string& symmetric_key) {
+  DCHECK(num_keys_to_create_ > 0);
   DCHECK(!symmetric_key.empty());
 
   const CryptAuthKeyCreator::CreateKeyData& create_key_data =
       keys_to_create_.find(bundle_name)->second;
 
   new_keys_.try_emplace(bundle_name, symmetric_key, create_key_data.status,
-                        create_key_data.type, handle);
+                        create_key_data.type, create_key_data.handle);
 
-  keys_to_create_.erase(bundle_name);
-  if (keys_to_create_.empty())
+  --num_keys_to_create_;
+  if (num_keys_to_create_ == 0)
     std::move(create_keys_callback_).Run(new_keys_, client_ephemeral_dh_);
 }
 
diff --git a/chromeos/services/device_sync/cryptauth_key_creator_impl.h b/chromeos/services/device_sync/cryptauth_key_creator_impl.h
index 17eb0a5..8f263b7 100644
--- a/chromeos/services/device_sync/cryptauth_key_creator_impl.h
+++ b/chromeos/services/device_sync/cryptauth_key_creator_impl.h
@@ -5,8 +5,6 @@
 #ifndef CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_KEY_CREATOR_IMPL_H_
 #define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_KEY_CREATOR_IMPL_H_
 
-#include "chromeos/services/device_sync/cryptauth_key_creator.h"
-
 #include <memory>
 #include <string>
 #include <utility>
@@ -18,6 +16,7 @@
 #include "base/optional.h"
 #include "chromeos/services/device_sync/cryptauth_key.h"
 #include "chromeos/services/device_sync/cryptauth_key_bundle.h"
+#include "chromeos/services/device_sync/cryptauth_key_creator.h"
 
 namespace chromeos {
 
@@ -60,9 +59,9 @@
                                     const std::string& public_key,
                                     const std::string& private_key);
   void OnSymmetricKeyDerived(CryptAuthKeyBundle::Name bundle_name,
-                             const std::string& symmetric_key,
-                             const std::string& handle);
+                             const std::string& symmetric_key);
 
+  size_t num_keys_to_create_ = 0;
   base::flat_map<CryptAuthKeyBundle::Name, CreateKeyData> keys_to_create_;
   base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey> new_keys_;
   base::Optional<CryptAuthKey> server_ephemeral_dh_;
diff --git a/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc
index c22eea1..ab35c46 100644
--- a/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromeos/services/device_sync/cryptauth_key_creator_impl.h"
-
 #include <memory>
 #include <string>
 #include <utility>
@@ -18,6 +16,7 @@
 #include "chromeos/services/device_sync/cryptauth_key.h"
 #include "chromeos/services/device_sync/cryptauth_key_bundle.h"
 #include "chromeos/services/device_sync/cryptauth_key_creator.h"
+#include "chromeos/services/device_sync/cryptauth_key_creator_impl.h"
 #include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
 #include "crypto/hkdf.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -33,15 +32,19 @@
 const char kFakeAsymmetricKeyHandle[] = "asymmetric_key_handle";
 const char kFakePublicKeyMaterial[] = "public_key";
 const char kFakeSymmetricKeyHandle[] = "symmetric_key_handle";
+const char kFakeProvidedPublicKeyMaterial[] = "provided_public_key";
+const char kFakeProvidedPrivateKeyMaterial[] = "provided_private_key";
 
-void VerifyCreatedKeysCallback(
-    const base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>&
-        expected_new_keys,
-    const base::Optional<CryptAuthKey>& expected_client_ephemeral_dh,
-    const base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>& new_keys,
-    const base::Optional<CryptAuthKey>& client_ephemeral_dh) {
-  EXPECT_EQ(expected_client_ephemeral_dh, client_ephemeral_dh);
-  EXPECT_EQ(expected_new_keys, new_keys);
+size_t NumBytesForSymmetricKeyType(cryptauthv2::KeyType key_type) {
+  switch (key_type) {
+    case cryptauthv2::KeyType::RAW128:
+      return 16u;
+    case cryptauthv2::KeyType::RAW256:
+      return 32u;
+    default:
+      NOTREACHED();
+      return 0u;
+  }
 }
 
 class FakeSecureMessageDelegateFactory
@@ -71,12 +74,19 @@
 
 class DeviceSyncCryptAuthKeyCreatorImplTest : public testing::Test {
  protected:
-  DeviceSyncCryptAuthKeyCreatorImplTest() = default;
+  DeviceSyncCryptAuthKeyCreatorImplTest()
+      : fake_secure_message_delegate_factory_(
+            std::make_unique<FakeSecureMessageDelegateFactory>()),
+        fake_server_ephemeral_dh_(CryptAuthKey(
+            kFakeServerEphemeralDhPublicKeyMaterial,
+            fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
+                kFakeServerEphemeralDhPublicKeyMaterial),
+            CryptAuthKey::Status::kActive,
+            cryptauthv2::KeyType::P256)) {}
+
   ~DeviceSyncCryptAuthKeyCreatorImplTest() override = default;
 
   void SetUp() override {
-    fake_secure_message_delegate_factory_ =
-        std::make_unique<FakeSecureMessageDelegateFactory>();
     multidevice::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
         fake_secure_message_delegate_factory_.get());
 
@@ -88,31 +98,81 @@
         nullptr);
   }
 
-  CryptAuthKey DeriveSecret(
-      const base::Optional<CryptAuthKey>& server_ephemeral_dh,
-      const base::Optional<CryptAuthKey>& client_ephemeral_dh) {
+  void CallCreateKeys(
+      const base::flat_map<CryptAuthKeyBundle::Name,
+                           CryptAuthKeyCreator::CreateKeyData>& keys_to_create,
+      const base::Optional<CryptAuthKey>& server_ephemeral_dh) {
+    key_creator_->CreateKeys(
+        keys_to_create, server_ephemeral_dh,
+        base::BindOnce(&DeviceSyncCryptAuthKeyCreatorImplTest::OnKeysCreated,
+                       base::Unretained(this)));
+  }
+
+  void VerifyKeyCreation(
+      const base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>&
+          expected_new_keys,
+      const base::Optional<CryptAuthKey>& expected_client_ephemeral_dh) {
+    EXPECT_TRUE(new_keys_);
+    EXPECT_TRUE(client_ephemeral_dh_);
+    EXPECT_EQ(expected_new_keys, *new_keys_);
+    EXPECT_EQ(expected_client_ephemeral_dh, *client_ephemeral_dh_);
+  }
+
+  std::string DeriveSecret(const CryptAuthKey& server_ephemeral_dh,
+                           const CryptAuthKey& client_ephemeral_dh) {
     std::string derived_key;
     fake_secure_message_delegate()->DeriveKey(
-        client_ephemeral_dh->private_key(), server_ephemeral_dh->public_key(),
+        client_ephemeral_dh.private_key(), server_ephemeral_dh.public_key(),
         base::Bind([](std::string* derived_key,
                       const std::string& key) { *derived_key = key; },
                    &derived_key));
-    return CryptAuthKey(derived_key, CryptAuthKey::Status::kActive,
-                        cryptauthv2::KeyType::RAW256);
+    return derived_key;
   }
 
-  CryptAuthKeyCreator* key_creator() { return key_creator_.get(); }
+  CryptAuthKey DeriveSymmetricKey(
+      const CryptAuthKeyBundle::Name& bundle_name,
+      const CryptAuthKeyCreator::CreateKeyData& key_to_create,
+      const CryptAuthKey& client_ephemeral_dh) {
+    std::string expected_handshake_secret =
+        DeriveSecret(fake_server_ephemeral_dh_, client_ephemeral_dh);
+
+    std::string expected_symmetric_key_material = crypto::HkdfSha256(
+        expected_handshake_secret, kCryptAuthSymmetricKeyDerivationSalt,
+        CryptAuthKeyBundle::KeyBundleNameEnumToString(bundle_name),
+        NumBytesForSymmetricKeyType(key_to_create.type));
+
+    return CryptAuthKey(expected_symmetric_key_material, key_to_create.status,
+                        key_to_create.type, key_to_create.handle);
+  }
 
   multidevice::FakeSecureMessageDelegate* fake_secure_message_delegate() {
     return fake_secure_message_delegate_factory_->instance();
   }
 
+  const CryptAuthKey& fake_server_ephemeral_dh() {
+    return fake_server_ephemeral_dh_;
+  }
+
  private:
+  void OnKeysCreated(
+      const base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>& new_keys,
+      const base::Optional<CryptAuthKey>& client_ephemeral_dh) {
+    new_keys_ = new_keys;
+    client_ephemeral_dh_ = client_ephemeral_dh;
+  }
+
   std::unique_ptr<FakeSecureMessageDelegateFactory>
       fake_secure_message_delegate_factory_;
-
   std::unique_ptr<CryptAuthKeyCreator> key_creator_;
 
+  CryptAuthKey fake_server_ephemeral_dh_;
+
+  // A null value (for the outermost Optional) indicates that OnKeysCreated()
+  // was not called.
+  base::Optional<base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>>
+      new_keys_;
+  base::Optional<base::Optional<CryptAuthKey>> client_ephemeral_dh_;
+
   DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthKeyCreatorImplTest);
 };
 
@@ -136,53 +196,82 @@
 
   fake_secure_message_delegate()->set_next_public_key(kFakePublicKeyMaterial);
 
-  key_creator()->CreateKeys(
-      keys_to_create, base::nullopt /* fake_server_ephemeral_dh */,
-      base::BindOnce(VerifyCreatedKeysCallback, expected_new_keys,
-                     base::nullopt /* expected_client_ephemeral_dh */));
+  CallCreateKeys(keys_to_create, base::nullopt /* fake_server_ephemeral_dh */);
+  VerifyKeyCreation(expected_new_keys,
+                    base::nullopt /* expected_client_ephemeral_dh */);
 }
 
 TEST_F(DeviceSyncCryptAuthKeyCreatorImplTest, SymmetricKeyCreation) {
+  CryptAuthKeyCreator::CreateKeyData symmetric_key_to_create(
+      CryptAuthKey::Status::kActive, cryptauthv2::KeyType::RAW256,
+      kFakeSymmetricKeyHandle);
+
   base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKeyCreator::CreateKeyData>
-      keys_to_create = {
-          {CryptAuthKeyBundle::Name::kUserKeyPair,
-           CryptAuthKeyCreator::CreateKeyData(CryptAuthKey::Status::kActive,
-                                              cryptauthv2::KeyType::RAW256,
-                                              kFakeSymmetricKeyHandle)}};
+      keys_to_create = {{CryptAuthKeyBundle::Name::kLegacyMasterKey,
+                         symmetric_key_to_create}};
 
-  base::Optional<CryptAuthKey> fake_server_ephemeral_dh =
-      CryptAuthKey(kFakeServerEphemeralDhPublicKeyMaterial,
-                   fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
-                       kFakeServerEphemeralDhPublicKeyMaterial),
-                   CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256);
+  CryptAuthKey expected_client_ephemeral_dh(
+      kFakeClientEphemeralDhPublicKeyMaterial,
+      fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
+          kFakeClientEphemeralDhPublicKeyMaterial),
+      CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256);
 
-  base::Optional<CryptAuthKey> expected_client_ephemeral_dh =
-      CryptAuthKey(kFakeClientEphemeralDhPublicKeyMaterial,
-                   fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
-                       kFakeClientEphemeralDhPublicKeyMaterial),
-                   CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256);
-
-  CryptAuthKey expected_handshake_secret =
-      DeriveSecret(fake_server_ephemeral_dh, expected_client_ephemeral_dh);
-  std::string expected_symmetric_key_material =
-      crypto::HkdfSha256(expected_handshake_secret.symmetric_key(),
-                         kCryptAuthSymmetricKeyDerivationSalt,
-                         kFakeSymmetricKeyHandle, 32u /* derived_key_size */);
-
-  CryptAuthKey expected_symmetric_key(
-      expected_symmetric_key_material, CryptAuthKey::Status::kActive,
-      cryptauthv2::KeyType::RAW256, kFakeSymmetricKeyHandle);
+  CryptAuthKey expected_symmetric_key =
+      DeriveSymmetricKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
+                         symmetric_key_to_create, expected_client_ephemeral_dh);
 
   base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey> expected_new_keys = {
-      {CryptAuthKeyBundle::Name::kUserKeyPair, expected_symmetric_key}};
+      {CryptAuthKeyBundle::Name::kLegacyMasterKey, expected_symmetric_key}};
 
   fake_secure_message_delegate()->set_next_public_key(
       kFakeClientEphemeralDhPublicKeyMaterial);
 
-  key_creator()->CreateKeys(
-      keys_to_create, fake_server_ephemeral_dh,
-      base::BindOnce(VerifyCreatedKeysCallback, expected_new_keys,
-                     expected_client_ephemeral_dh));
+  CallCreateKeys(keys_to_create, fake_server_ephemeral_dh());
+  VerifyKeyCreation(expected_new_keys, expected_client_ephemeral_dh);
+}
+
+TEST_F(DeviceSyncCryptAuthKeyCreatorImplTest,
+       MultipleKeyCreation_KeyMaterialProvidedForAsymmetricKey) {
+  CryptAuthKeyCreator::CreateKeyData symmetric_key_to_create(
+      CryptAuthKey::Status::kActive, cryptauthv2::KeyType::RAW256,
+      kFakeSymmetricKeyHandle);
+
+  base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKeyCreator::CreateKeyData>
+      keys_to_create = {
+          {CryptAuthKeyBundle::Name::kUserKeyPair,
+           CryptAuthKeyCreator::CreateKeyData(
+               CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256,
+               kFakeAsymmetricKeyHandle, kFakeProvidedPublicKeyMaterial,
+               kFakeProvidedPrivateKeyMaterial)},
+          {CryptAuthKeyBundle::Name::kLegacyMasterKey,
+           symmetric_key_to_create}};
+
+  CryptAuthKey expected_client_ephemeral_dh(
+      kFakeClientEphemeralDhPublicKeyMaterial,
+      fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
+          kFakeClientEphemeralDhPublicKeyMaterial),
+      CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256);
+
+  CryptAuthKey expected_asymmetric_key(
+      kFakeProvidedPublicKeyMaterial, kFakeProvidedPrivateKeyMaterial,
+      CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256,
+      kFakeAsymmetricKeyHandle);
+
+  CryptAuthKey expected_symmetric_key =
+      DeriveSymmetricKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
+                         symmetric_key_to_create, expected_client_ephemeral_dh);
+
+  base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey> expected_new_keys = {
+      {CryptAuthKeyBundle::Name::kUserKeyPair, expected_asymmetric_key},
+      {CryptAuthKeyBundle::Name::kLegacyMasterKey, expected_symmetric_key}};
+
+  // There is no need to generate an asymmetric key for kUserKeyPair since we
+  // passed in the key material to CreateKeyData.
+  fake_secure_message_delegate()->set_next_public_key(
+      kFakeClientEphemeralDhPublicKeyMaterial);
+
+  CallCreateKeys(keys_to_create, fake_server_ephemeral_dh());
+  VerifyKeyCreation(expected_new_keys, expected_client_ephemeral_dh);
 }
 
 }  // namespace device_sync
diff --git a/chromeos/services/device_sync/cryptauth_key_proof_computer.h b/chromeos/services/device_sync/cryptauth_key_proof_computer.h
index 456570a0e..c389783 100644
--- a/chromeos/services/device_sync/cryptauth_key_proof_computer.h
+++ b/chromeos/services/device_sync/cryptauth_key_proof_computer.h
@@ -25,7 +25,7 @@
 //   Symmetric keys: The HMAC-SHA256 of the payload, using a key derived from
 //                   the input symmetric key. Schematically,
 //
-//       HMAC(HKDF(|symmetric_key|, |salt|, info=|key_handle|), |payload|)
+//       HMAC(HKDF(|symmetric_key|, |salt|, |info|), |payload|)
 //
 //   Asymmetric keys: A DER-encoded ECDSA signature (RFC 3279) of the
 //                    concatenation of the salt and payload strings.
@@ -33,10 +33,12 @@
 //
 //       Sign(|private_key|, |salt| + |payload|)
 //
-// The CryptAuth v2 Enrollment protocol states that the
-// SyncKeysResponse::random_session_id, sent by the CryptAuth server, be used as
-// the payload for key proofs. In the future, key crossproofs might be employed,
-// where the payload will consist of other key proofs.
+// Specifically, the CryptAuth v2 Enrollment protocol states that
+//   1) |payload| = SyncKeysResponse::random_session_id,
+//   2)    |salt| = "CryptAuth Key Proof",
+//   3)    |info| = key bundle name (needed for symmetric keys only).
+// In the future, key crossproofs might be employed, where the payload will
+// consist of other key proofs.
 //
 // Requirements:
 //   - Currently, the only supported key types are RAW128 and RAW256 for
@@ -47,10 +49,13 @@
   virtual ~CryptAuthKeyProofComputer() = default;
 
   // Returns null if key proof computation failed.
+  // Note: The parameter |info| must be non-null for symmetric keys, but it is
+  // not used for asymmetric keys.
   virtual base::Optional<std::string> ComputeKeyProof(
       const CryptAuthKey& key,
       const std::string& payload,
-      const std::string& salt) = 0;
+      const std::string& salt,
+      const base::Optional<std::string>& info) = 0;
 
   DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyProofComputer);
 };
diff --git a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.cc b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.cc
index 275a9219..d3f81e7 100644
--- a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.cc
+++ b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.cc
@@ -83,21 +83,24 @@
 base::Optional<std::string> CryptAuthKeyProofComputerImpl::ComputeKeyProof(
     const CryptAuthKey& key,
     const std::string& payload,
-    const std::string& salt) {
-  if (key.IsSymmetricKey())
-    return ComputeSymmetricKeyProof(key, payload, salt);
+    const std::string& salt,
+    const base::Optional<std::string>& info) {
+  if (key.IsAsymmetricKey())
+    return ComputeAsymmetricKeyProof(key, payload, salt);
 
-  return ComputeAsymmetricKeyProof(key, payload, salt);
+  DCHECK(info);
+  return ComputeSymmetricKeyProof(key, payload, salt, *info);
 }
 
 base::Optional<std::string>
 CryptAuthKeyProofComputerImpl::ComputeSymmetricKeyProof(
     const CryptAuthKey& symmetric_key,
     const std::string& payload,
-    const std::string& salt) {
-  std::string derived_symmetric_key_material = crypto::HkdfSha256(
-      symmetric_key.symmetric_key(), salt, symmetric_key.handle(),
-      NumBytesForSymmetricKeyType(symmetric_key.type()));
+    const std::string& salt,
+    const std::string& info) {
+  std::string derived_symmetric_key_material =
+      crypto::HkdfSha256(symmetric_key.symmetric_key(), salt, info,
+                         NumBytesForSymmetricKeyType(symmetric_key.type()));
 
   crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
   std::vector<unsigned char> signed_payload(hmac.DigestLength());
diff --git a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.h b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.h
index 710b0f62..1ed9afd 100644
--- a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.h
+++ b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl.h
@@ -34,9 +34,11 @@
   ~CryptAuthKeyProofComputerImpl() override;
 
   // CryptAuthKeyProofComputer:
-  base::Optional<std::string> ComputeKeyProof(const CryptAuthKey& key,
-                                              const std::string& payload,
-                                              const std::string& salt) override;
+  base::Optional<std::string> ComputeKeyProof(
+      const CryptAuthKey& key,
+      const std::string& payload,
+      const std::string& salt,
+      const base::Optional<std::string>& info) override;
 
  private:
   CryptAuthKeyProofComputerImpl();
@@ -44,7 +46,8 @@
   base::Optional<std::string> ComputeSymmetricKeyProof(
       const CryptAuthKey& symmetric_key,
       const std::string& payload,
-      const std::string& salt);
+      const std::string& salt,
+      const std::string& info);
   base::Optional<std::string> ComputeAsymmetricKeyProof(
       const CryptAuthKey& asymmetric_key,
       const std::string& payload,
diff --git a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc
index 02519a6..59e9dd8 100644
--- a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc
@@ -107,7 +107,8 @@
   base::Optional<std::string> key_proof =
       CryptAuthKeyProofComputerImpl::Factory::Get()
           ->BuildInstance()
-          ->ComputeKeyProof(key, kTestPayload, kAsymmetricTestSalt);
+          ->ComputeKeyProof(key, kTestPayload, kAsymmetricTestSalt,
+                            base::nullopt /* info */);
   EXPECT_TRUE(key_proof);
 
   // Verify the key proof which should be of the form:
@@ -126,18 +127,19 @@
 TEST(DeviceSyncCryptAuthKeyProofComputerImplTest,
      Symmetric256KeyProofComputation_Success) {
   CryptAuthKey key(ByteVectorToString(kTestSymmetricKeyBytes),
-                   CryptAuthKey::Status::kActive, cryptauthv2::KeyType::RAW256,
-                   ByteVectorToString(kSymmetricTestInfoBytes));
+                   CryptAuthKey::Status::kActive, cryptauthv2::KeyType::RAW256);
 
   base::Optional<std::string> key_proof =
       CryptAuthKeyProofComputerImpl::Factory::Get()
           ->BuildInstance()
           ->ComputeKeyProof(key, kTestPayload,
-                            ByteVectorToString(kSymmetricTestSaltBytes));
+                            ByteVectorToString(kSymmetricTestSaltBytes),
+                            ByteVectorToString(kSymmetricTestInfoBytes));
+
   EXPECT_TRUE(key_proof);
 
   // Verify the key proof which should be of the form:
-  //     HMAC(HKDF(|key|, |salt|, info=<key handle>), |payload|)
+  //     HMAC(HKDF(|key|, |salt|, |info|), |payload|)
   crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
   EXPECT_TRUE(
       hmac.Init(ByteVectorToString(kExpectedDerivedSymmetricKey32Bytes)));
@@ -147,14 +149,14 @@
 TEST(DeviceSyncCryptAuthKeyProofComputerImplTest,
      Symmetric128KeyProofComputation_Success) {
   CryptAuthKey key(ByteVectorToString(kTestSymmetricKeyBytes),
-                   CryptAuthKey::Status::kActive, cryptauthv2::KeyType::RAW128,
-                   ByteVectorToString(kSymmetricTestInfoBytes));
+                   CryptAuthKey::Status::kActive, cryptauthv2::KeyType::RAW128);
 
   base::Optional<std::string> key_proof =
       CryptAuthKeyProofComputerImpl::Factory::Get()
           ->BuildInstance()
           ->ComputeKeyProof(key, kTestPayload,
-                            ByteVectorToString(kSymmetricTestSaltBytes));
+                            ByteVectorToString(kSymmetricTestSaltBytes),
+                            ByteVectorToString(kSymmetricTestInfoBytes));
   EXPECT_TRUE(key_proof);
 
   crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
@@ -171,7 +173,8 @@
   base::Optional<std::string> key_proof =
       CryptAuthKeyProofComputerImpl::Factory::Get()
           ->BuildInstance()
-          ->ComputeKeyProof(key, kTestPayload, kAsymmetricTestSalt);
+          ->ComputeKeyProof(key, kTestPayload, kAsymmetricTestSalt,
+                            base::nullopt /* info */);
 
   EXPECT_FALSE(key_proof);
 }
diff --git a/chromeos/services/device_sync/cryptauth_key_registry.h b/chromeos/services/device_sync/cryptauth_key_registry.h
index ee05b3f6..c8f83a9 100644
--- a/chromeos/services/device_sync/cryptauth_key_registry.h
+++ b/chromeos/services/device_sync/cryptauth_key_registry.h
@@ -37,6 +37,8 @@
   // Adds |key| to the key bundle with |name|. If the key being added is active,
   // all other keys in the bundle will be deactivated. If the handle of the
   // input key matches one in the bundle, the existing key will be overwritten.
+  // Note: All keys added to the bundle kUserKeyPair must have the handle
+  // kCryptAuthFixedUserKeyPairHandle.
   virtual void AddEnrolledKey(CryptAuthKeyBundle::Name name,
                               const CryptAuthKey& key);
 
diff --git a/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc
index 5bfa752..45efd13 100644
--- a/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromeos/services/device_sync/cryptauth_key_registry_impl.h"
 
 #include "base/stl_util.h"
+#include "chromeos/services/device_sync/cryptauth_constants.h"
 #include "chromeos/services/device_sync/pref_names.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -48,11 +49,11 @@
 TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, GetActiveKey_NoActiveKey) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  sym_key);
 
   EXPECT_FALSE(
-      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair));
+      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kLegacyMasterKey));
 }
 
 TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, GetActiveKey) {
@@ -61,13 +62,13 @@
   CryptAuthKey asym_key("public-key", "private-key",
                         CryptAuthKey::Status::kActive,
                         cryptauthv2::KeyType::P256, "asym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  sym_key);
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  asym_key);
 
   const CryptAuthKey* key =
-      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair);
+      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   ASSERT_TRUE(key);
   EXPECT_EQ(asym_key, *key);
 }
@@ -75,18 +76,19 @@
 TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, AddKey) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kActive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  sym_key);
   const CryptAuthKeyBundle* key_bundle =
-      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   ASSERT_TRUE(key_bundle);
 
   const CryptAuthKey* active_key =
-      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair);
+      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   ASSERT_TRUE(active_key);
   EXPECT_EQ(sym_key, *active_key);
 
-  CryptAuthKeyBundle expected_bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle expected_bundle(
+      CryptAuthKeyBundle::Name::kLegacyMasterKey);
   expected_bundle.AddKey(sym_key);
   EXPECT_EQ(expected_bundle, *key_bundle);
 
@@ -100,14 +102,14 @@
   CryptAuthKey asym_key("public-key", "private-key",
                         CryptAuthKey::Status::kActive,
                         cryptauthv2::KeyType::P256, "asym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  asym_key);
 
   expected_bundle.AddKey(asym_key);
   EXPECT_EQ(expected_bundle, *key_bundle);
 
   active_key =
-      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair);
+      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   ASSERT_TRUE(active_key);
   EXPECT_EQ(asym_key, *active_key);
 
@@ -123,22 +125,23 @@
   CryptAuthKey asym_key("public-key", "private-key",
                         CryptAuthKey::Status::kActive,
                         cryptauthv2::KeyType::P256, "asym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  sym_key);
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  asym_key);
 
-  key_registry()->SetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->SetActiveKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                "sym-handle");
 
   const CryptAuthKey* key =
-      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair);
+      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   EXPECT_TRUE(key);
 
   sym_key.set_status(CryptAuthKey::Status::kActive);
   EXPECT_EQ(sym_key, *key);
 
-  CryptAuthKeyBundle expected_bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle expected_bundle(
+      CryptAuthKeyBundle::Name::kLegacyMasterKey);
   expected_bundle.AddKey(sym_key);
   asym_key.set_status(CryptAuthKey::Status::kInactive);
   expected_bundle.AddKey(asym_key);
@@ -155,17 +158,18 @@
   CryptAuthKey asym_key("public-key", "private-key",
                         CryptAuthKey::Status::kActive,
                         cryptauthv2::KeyType::P256, "asym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  sym_key);
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  asym_key);
 
-  key_registry()->DeactivateKeys(CryptAuthKeyBundle::Name::kUserKeyPair);
+  key_registry()->DeactivateKeys(CryptAuthKeyBundle::Name::kLegacyMasterKey);
 
   EXPECT_FALSE(
-      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair));
+      key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kLegacyMasterKey));
 
-  CryptAuthKeyBundle expected_bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle expected_bundle(
+      CryptAuthKeyBundle::Name::kLegacyMasterKey);
   expected_bundle.AddKey(sym_key);
   asym_key.set_status(CryptAuthKey::Status::kInactive);
   expected_bundle.AddKey(asym_key);
@@ -182,16 +186,16 @@
   CryptAuthKey asym_key("public-key", "private-key",
                         CryptAuthKey::Status::kActive,
                         cryptauthv2::KeyType::P256, "asym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  sym_key);
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  asym_key);
 
-  key_registry()->DeleteKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->DeleteKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                             "sym-handle");
 
   const CryptAuthKeyBundle* key_bundle =
-      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   ASSERT_TRUE(key_bundle);
 
   EXPECT_FALSE(
@@ -199,7 +203,8 @@
   EXPECT_TRUE(
       base::ContainsKey(key_bundle->handle_to_key_map(), "asym-handle"));
 
-  CryptAuthKeyBundle expected_bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle expected_bundle(
+      CryptAuthKeyBundle::Name::kLegacyMasterKey);
   expected_bundle.AddKey(asym_key);
   base::Value expected_dict(base::Value::Type::DICTIONARY);
   expected_dict.SetKey(
@@ -211,23 +216,24 @@
 TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, SetKeyDirective) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  sym_key);
 
   cryptauthv2::KeyDirective key_directive;
   key_directive.set_enroll_time_millis(1000);
-  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                   key_directive);
 
   const CryptAuthKeyBundle* key_bundle =
-      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
   ASSERT_TRUE(key_bundle);
 
   EXPECT_TRUE(key_bundle->key_directive());
   EXPECT_EQ(key_directive.SerializeAsString(),
             key_bundle->key_directive()->SerializeAsString());
 
-  CryptAuthKeyBundle expected_bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
+  CryptAuthKeyBundle expected_bundle(
+      CryptAuthKeyBundle::Name::kLegacyMasterKey);
   expected_bundle.AddKey(sym_key);
   expected_bundle.set_key_directive(key_directive);
   base::Value expected_dict(base::Value::Type::DICTIONARY);
@@ -239,18 +245,19 @@
 
 TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest,
        ConstructorPopulatesBundlesUsingPref) {
-  CryptAuthKey asym_key("public-key", "private", CryptAuthKey::Status::kActive,
-                        cryptauthv2::KeyType::P256, "asym-handle");
-  CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kActive,
-                       cryptauthv2::KeyType::RAW256, "sym-handle");
+  CryptAuthKey asym_key(
+      "public-key", "private-key", CryptAuthKey::Status::kActive,
+      cryptauthv2::KeyType::P256, kCryptAuthFixedUserKeyPairHandle);
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
                                  asym_key);
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
-                                 sym_key);
 
+  CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kActive,
+                       cryptauthv2::KeyType::RAW256, "sym-handle");
   cryptauthv2::KeyDirective key_directive;
   key_directive.set_enroll_time_millis(1000);
-  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
+                                 sym_key);
+  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                   key_directive);
 
   // A new registry using the same pref service that was just written.
@@ -262,19 +269,18 @@
   const CryptAuthKeyBundle* key_bundle_user_key_pair =
       key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   ASSERT_TRUE(key_bundle_user_key_pair);
-
-  const CryptAuthKeyBundle* key_bundle_legacy_master_key =
-      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
-
   CryptAuthKeyBundle expected_bundle_user_key_pair(
       CryptAuthKeyBundle::Name::kUserKeyPair);
   expected_bundle_user_key_pair.AddKey(asym_key);
-  expected_bundle_user_key_pair.set_key_directive(key_directive);
   EXPECT_EQ(expected_bundle_user_key_pair, *key_bundle_user_key_pair);
 
+  const CryptAuthKeyBundle* key_bundle_legacy_master_key =
+      key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kLegacyMasterKey);
+  ASSERT_TRUE(key_bundle_legacy_master_key);
   CryptAuthKeyBundle expected_bundle_legacy_master_key(
       CryptAuthKeyBundle::Name::kLegacyMasterKey);
   expected_bundle_legacy_master_key.AddKey(sym_key);
+  expected_bundle_legacy_master_key.set_key_directive(key_directive);
   EXPECT_EQ(expected_bundle_legacy_master_key, *key_bundle_legacy_master_key);
 }
 
diff --git a/chromeos/services/device_sync/cryptauth_v2_enroller_impl.cc b/chromeos/services/device_sync/cryptauth_v2_enroller_impl.cc
index 4b812da..a55688d 100644
--- a/chromeos/services/device_sync/cryptauth_v2_enroller_impl.cc
+++ b/chromeos/services/device_sync/cryptauth_v2_enroller_impl.cc
@@ -254,48 +254,59 @@
          key_type == cryptauthv2::KeyType::P256;
 }
 
+// The key bundle kUserKeyPair has special standing in order to 1) accommodate
+// any existing key from v1 Enrollment and 2) enforce that the key is not
+// rotated. As such, only one user key pair should exist in the key bundle, and
+// it should be an active, P-256 key with handle
+// kCryptAuthFixedUserKeyPairHandle.
+//
+// It is possible that CryptAuth could request the creation of a new user key
+// pair even if the client sends information about an existing key in the
+// SyncKeysRequest. If this happens, the client should re-use the existing user
+// key pair key material when creating a new key. At the end of the enrollment
+// flow, the existing key will be replaced with this new key that has the same
+// public/private keys.
+//
 // Returns an error code if the key-creation instructions are invalid and null
 // otherwise.
 base::Optional<CryptAuthEnrollmentResult::ResultCode>
-ProcessKeyCreationInstructions(
-    const CryptAuthKeyBundle::Name& bundle_name,
-    const SyncSingleKeyResponse& single_key_response,
-    const std::string& server_ephemeral_dh,
-    base::Optional<CryptAuthKeyCreator::CreateKeyData>* new_key_to_create,
-    base::Optional<cryptauthv2::KeyDirective>* new_key_directive) {
-  if (single_key_response.key_creation() == SyncSingleKeyResponse::NONE)
+ProcessNewUserKeyPairInstructions(
+    CryptAuthKey::Status status,
+    cryptauthv2::KeyType type,
+    const CryptAuthKey* current_active_key,
+    base::Optional<CryptAuthKeyCreator::CreateKeyData>* new_key_to_create) {
+  if (type != cryptauthv2::KeyType::P256) {
+    PA_LOG(ERROR) << "User key pair must have KeyType P256.";
+    return CryptAuthEnrollmentResult::ResultCode::
+        kErrorUserKeyPairCreationInstructionsInvalid;
+  }
+
+  // Because no more than one user key pair can exist in the bundle, the newly
+  // created key must be active.
+  if (status != CryptAuthKey::Status::kActive) {
+    PA_LOG(ERROR) << "New user key pair must be active.";
+    return CryptAuthEnrollmentResult::ResultCode::
+        kErrorUserKeyPairCreationInstructionsInvalid;
+  }
+
+  // If a user key pair already exists in the registry, reuse the same key data.
+  if (current_active_key && current_active_key->IsAsymmetricKey() &&
+      !current_active_key->private_key().empty()) {
+    PA_LOG(WARNING) << "Received request to create new user key pair while one "
+                    << "already exists in the key registry. Reusing existing "
+                    << "key material.";
+
+    *new_key_to_create = CryptAuthKeyCreator::CreateKeyData(
+        status, type, kCryptAuthFixedUserKeyPairHandle,
+        current_active_key->public_key(), current_active_key->private_key());
+
     return base::nullopt;
-
-  if (!IsSupportedKeyType(single_key_response.key_type())) {
-    PA_LOG(ERROR) << "KeyType " << single_key_response.key_type() << " "
-                  << "not supported.";
-    return CryptAuthEnrollmentResult::ResultCode::
-        kErrorKeyCreationKeyTypeNotSupported;
   }
 
-  // Symmetric keys cannot be created without the server's Diffie-Hellman key.
-  if (server_ephemeral_dh.empty() &&
-      (single_key_response.key_type() == cryptauthv2::KeyType::RAW128 ||
-       single_key_response.key_type() == cryptauthv2::KeyType::RAW256)) {
-    PA_LOG(ERROR)
-        << "Missing server's Diffie-Hellman key. Cannot create symmetric keys.";
-    return CryptAuthEnrollmentResult::ResultCode::
-        kErrorSymmetricKeyCreationMissingServerDiffieHellman;
-  }
-
-  // CryptAuth demands that the key in the kUserKeyPair bundle has a fixed
-  // handle name. For other key bundles, do not specify a handle name; let
-  // CryptAuthKey generate a handle for us.
-  base::Optional<std::string> new_key_handle;
-  if (bundle_name == CryptAuthKeyBundle::Name::kUserKeyPair)
-    new_key_handle = kCryptAuthFixedUserKeyPairHandle;
-
+  // If there is no user key pair in the registry, then the user has never
+  // successfully enrolled via v1 or v2 Enrollment. Generate a new key pair.
   *new_key_to_create = CryptAuthKeyCreator::CreateKeyData(
-      ConvertKeyCreationToKeyStatus(single_key_response.key_creation()),
-      single_key_response.key_type(), new_key_handle);
-
-  if (single_key_response.has_key_directive())
-    *new_key_directive = single_key_response.key_directive();
+      status, type, kCryptAuthFixedUserKeyPairHandle);
 
   return base::nullopt;
 }
@@ -620,6 +631,51 @@
   return error_code;
 }
 
+base::Optional<CryptAuthEnrollmentResult::ResultCode>
+CryptAuthV2EnrollerImpl::ProcessKeyCreationInstructions(
+    const CryptAuthKeyBundle::Name& bundle_name,
+    const SyncSingleKeyResponse& single_key_response,
+    const std::string& server_ephemeral_dh,
+    base::Optional<CryptAuthKeyCreator::CreateKeyData>* new_key_to_create,
+    base::Optional<cryptauthv2::KeyDirective>* new_key_directive) {
+  if (single_key_response.key_creation() == SyncSingleKeyResponse::NONE)
+    return base::nullopt;
+
+  CryptAuthKey::Status status =
+      ConvertKeyCreationToKeyStatus(single_key_response.key_creation());
+  cryptauthv2::KeyType type = single_key_response.key_type();
+
+  if (!IsSupportedKeyType(type)) {
+    PA_LOG(ERROR) << "KeyType " << type << " not supported.";
+    return CryptAuthEnrollmentResult::ResultCode::
+        kErrorKeyCreationKeyTypeNotSupported;
+  }
+
+  // Symmetric keys cannot be created without the server's Diffie-Hellman key.
+  if (server_ephemeral_dh.empty() && (type == cryptauthv2::KeyType::RAW128 ||
+                                      type == cryptauthv2::KeyType::RAW256)) {
+    PA_LOG(ERROR)
+        << "Missing server's Diffie-Hellman key. Cannot create symmetric keys.";
+    return CryptAuthEnrollmentResult::ResultCode::
+        kErrorSymmetricKeyCreationMissingServerDiffieHellman;
+  }
+
+  if (single_key_response.has_key_directive())
+    *new_key_directive = single_key_response.key_directive();
+
+  // Handle the user key pair special case separately below.
+  if (bundle_name != CryptAuthKeyBundle::Name::kUserKeyPair) {
+    *new_key_to_create = CryptAuthKeyCreator::CreateKeyData(status, type);
+
+    return base::nullopt;
+  }
+
+  DCHECK(bundle_name == CryptAuthKeyBundle::Name::kUserKeyPair);
+  return ProcessNewUserKeyPairInstructions(
+      status, type, key_registry_->GetActiveKey(bundle_name),
+      new_key_to_create);
+}
+
 void CryptAuthV2EnrollerImpl::OnSyncKeysFailure(NetworkRequestError error) {
   FinishAttempt(SyncKeysNetworkRequestErrorToResultCode(error));
 }
@@ -645,10 +701,12 @@
     const CryptAuthKeyBundle::Name& bundle_name = name_key_pair.first;
     const CryptAuthKey& new_key = name_key_pair.second;
 
+    std::string bundle_name_str =
+        CryptAuthKeyBundle::KeyBundleNameEnumToString(bundle_name);
+
     EnrollSingleKeyRequest* single_key_request =
         request.add_enroll_single_key_requests();
-    single_key_request->set_key_name(
-        CryptAuthKeyBundle::KeyBundleNameEnumToString(bundle_name));
+    single_key_request->set_key_name(bundle_name_str);
     single_key_request->set_new_key_handle(new_key.handle());
     if (new_key.IsAsymmetricKey())
       single_key_request->set_key_material(new_key.public_key());
@@ -657,7 +715,7 @@
     // SyncKeysResponse as the payload and the particular salt specified by the
     // v2 Enrollment protocol.
     base::Optional<std::string> key_proof = key_proof_computer->ComputeKeyProof(
-        new_key, session_id, kCryptAuthKeyProofSalt);
+        new_key, session_id, kCryptAuthKeyProofSalt, bundle_name_str);
     if (!key_proof || key_proof->empty()) {
       FinishAttempt(CryptAuthEnrollmentResult::ResultCode::
                         kErrorKeyProofComputationFailed);
diff --git a/chromeos/services/device_sync/cryptauth_v2_enroller_impl.h b/chromeos/services/device_sync/cryptauth_v2_enroller_impl.h
index 5b3e01b..ba8012fd 100644
--- a/chromeos/services/device_sync/cryptauth_v2_enroller_impl.h
+++ b/chromeos/services/device_sync/cryptauth_v2_enroller_impl.h
@@ -120,6 +120,17 @@
       base::flat_map<CryptAuthKeyBundle::Name, cryptauthv2::KeyDirective>*
           new_key_directives);
 
+  // A function to help ProcessSingleKeyResponse() handle the key-creation
+  // instructions.
+  base::Optional<CryptAuthEnrollmentResult::ResultCode>
+  ProcessKeyCreationInstructions(
+      const CryptAuthKeyBundle::Name& bundle_name,
+      const cryptauthv2::SyncKeysResponse::SyncSingleKeyResponse&
+          single_key_response,
+      const std::string& server_ephemeral_dh,
+      base::Optional<CryptAuthKeyCreator::CreateKeyData>* new_key_to_create,
+      base::Optional<cryptauthv2::KeyDirective>* new_key_directive);
+
   void OnSyncKeysFailure(NetworkRequestError error);
 
   void OnKeysCreated(
diff --git a/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc
index 26948b4..816c73b 100644
--- a/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc
@@ -75,21 +75,13 @@
 
 const char kOldActivePublicKey[] = "old_active_public_key";
 const char kOldActivePrivateKey[] = "old_active_private_key";
-const char kOldActiveAsymmetricKeyHandle[] = "old_active_handle";
+
+// User key pair active handle must be kCryptAuthFixedUserKeyPairHandle.
 const CryptAuthKey kOldActiveAsymmetricKey(kOldActivePublicKey,
                                            kOldActivePrivateKey,
                                            CryptAuthKey::Status::kActive,
                                            KeyType::P256,
-                                           kOldActiveAsymmetricKeyHandle);
-
-const char kOldInactivePublicKey[] = "old_inactive_public_key";
-const char kOldInactivePrivateKey[] = "old_inactive_private_key";
-const char kOldInactiveAsymmetricKeyHandle[] = "old_inactive_handle";
-const CryptAuthKey kOldInactiveAsymmetricKey(kOldInactivePublicKey,
-                                             kOldInactivePrivateKey,
-                                             CryptAuthKey::Status::kInactive,
-                                             KeyType::P256,
-                                             kOldInactiveAsymmetricKeyHandle);
+                                           kCryptAuthFixedUserKeyPairHandle);
 
 const char kOldActiveSymmetricKeyMaterial[] = "old_active_symmetric_key";
 const char kOldActiveSymmetricKeyHandle[] = "old_active_symmetric_key_handle";
@@ -108,7 +100,6 @@
 
 const char kNewPublicKey[] = "new_public_key";
 const char kNewPrivateKey[] = "new_private_key";
-const char kFixedUserKeyPairHandle[] = "device_key";
 
 const char kNewSymmetricKey[] = "new_symmetric_key";
 const char kNewSymmetricKeyHandle[] = "new_symmetric_key_handle";
@@ -504,8 +495,26 @@
 
       EXPECT_EQ(key.status(), create_key_data.status);
       EXPECT_EQ(key.type(), create_key_data.type);
-      if (bundle_name == CryptAuthKeyBundle::Name::kUserKeyPair)
+
+      // Special handling for user key pair.
+      if (bundle_name == CryptAuthKeyBundle::Name::kUserKeyPair) {
         EXPECT_EQ(key.handle(), create_key_data.handle);
+        const CryptAuthKey* current_active_user_key_pair =
+            key_registry()->GetActiveKey(
+                CryptAuthKeyBundle::Name::kUserKeyPair);
+        if (current_active_user_key_pair) {
+          EXPECT_EQ(key.public_key(), create_key_data.public_key);
+          EXPECT_EQ(current_active_user_key_pair->public_key(),
+                    create_key_data.public_key);
+
+          EXPECT_EQ(key.private_key(), create_key_data.private_key);
+          EXPECT_EQ(current_active_user_key_pair->private_key(),
+                    create_key_data.private_key);
+
+          EXPECT_EQ(KeyType::P256, create_key_data.type);
+          EXPECT_EQ(CryptAuthKey::Status::kActive, create_key_data.status);
+        }
+      }
     }
 
     ASSERT_TRUE(key_creator()->server_ephemeral_dh()->IsAsymmetricKey());
@@ -514,6 +523,31 @@
     EXPECT_EQ(KeyType::P256, key_creator()->server_ephemeral_dh()->type());
   }
 
+  void VerifyEnrollSingleKeyRequest(const CryptAuthKeyBundle::Name& bundle_name,
+                                    const CryptAuthKey& new_key) {
+    const EnrollSingleKeyRequest& single_request_user_key_pair =
+        enroll_keys_request()->enroll_single_key_requests(
+            GetKeyBundleIndex(bundle_name));
+
+    std::string bundle_name_str =
+        CryptAuthKeyBundle::KeyBundleNameEnumToString(bundle_name);
+
+    EXPECT_EQ(bundle_name_str, single_request_user_key_pair.key_name());
+
+    EXPECT_EQ(new_key.handle(), single_request_user_key_pair.new_key_handle());
+
+    // No private or symmetric keys should be sent to CryptAuth, so key_material
+    // should only ever be populated with a public key.
+    EXPECT_EQ(new_key.IsAsymmetricKey() ? new_key.public_key() : std::string(),
+              single_request_user_key_pair.key_material());
+
+    EXPECT_EQ(CryptAuthKeyProofComputerImpl::Factory::Get()
+                  ->BuildInstance()
+                  ->ComputeKeyProof(new_key, kRandomSessionId,
+                                    kCryptAuthKeyProofSalt, bundle_name_str),
+              single_request_user_key_pair.key_proof());
+  }
+
   CryptAuthV2Enroller* enroller() { return enroller_.get(); }
 
   CryptAuthKeyRegistry* key_registry() { return key_registry_.get(); }
@@ -577,8 +611,6 @@
   // Seed key registry.
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
                                  kOldActiveAsymmetricKey);
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
-                                 kOldInactiveAsymmetricKey);
   key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kUserKeyPair,
                                   GetSampleOldKeyDirective());
   CryptAuthKeyBundle expected_key_bundle_user_key_pair(
@@ -601,29 +633,27 @@
   ClientDirective expected_new_client_directive = GetSampleNewClientDirective();
   KeyDirective expected_new_key_directive = GetSampleNewKeyDirective();
 
-  // For kUserKeyPair:
+  // For kUserKeyPair (special case):
+  //   - active --> temporarily active during key creation
+  //   - new --> same handle so overwrites active key with same material
+  // For kMasterLegacyKey:
   //   - active --> deleted
   //   - inactive --> temporarily active during key creation
   //   - new --> active after created
-  // For kMasterLegacyKey:
-  //   - active --> active
-  //   - inactive --> inactive
-  //   - new --> inactive
   std::vector<SyncSingleKeyResponseData> sync_single_key_responses_data = {
       SyncSingleKeyResponseData(
           CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
-          {{kOldActiveAsymmetricKeyHandle, SyncSingleKeyResponse::DELETE},
-           {kOldInactiveAsymmetricKeyHandle,
+          {{kCryptAuthFixedUserKeyPairHandle,
             SyncSingleKeyResponse::ACTIVATE}} /* handle_to_action_map */,
           SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
           KeyType::P256 /* new_key_type */,
           expected_new_key_directive /* new_key_directive */),
       SyncSingleKeyResponseData(
           CryptAuthKeyBundle::Name::kLegacyMasterKey, key_registry(),
-          {{kOldActiveSymmetricKeyHandle, SyncSingleKeyResponse::ACTIVATE},
+          {{kOldActiveSymmetricKeyHandle, SyncSingleKeyResponse::DELETE},
            {kOldInactiveSymmetricKeyHandle,
-            SyncSingleKeyResponse::DEACTIVATE}} /* handle_to_action_map */,
-          SyncSingleKeyResponse::INACTIVE /* new_key_creation */,
+            SyncSingleKeyResponse::ACTIVATE}} /* handle_to_action_map */,
+          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
           KeyType::RAW256 /* new_key_type */,
           expected_new_key_directive /* new_key_directive */)};
 
@@ -635,25 +665,30 @@
   SendSyncKeysResponse(sync_keys_response);
 
   // Verify that the key actions were applied. (Note: New keys not created yet.)
-  expected_key_bundle_user_key_pair.DeleteKey(kOldActiveAsymmetricKeyHandle);
-  expected_key_bundle_user_key_pair.SetActiveKey(
-      kOldInactiveAsymmetricKeyHandle);
+  // No key actions expected for kUserKeyPair.
   EXPECT_EQ(
       expected_key_bundle_user_key_pair,
       *key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair));
 
+  // In kLegacyMasterKey bundle, former active key should have been deleted and
+  // former inactive key should now be active.
+  expected_key_bundle_legacy_master_key.DeleteKey(kOldActiveSymmetricKeyHandle);
+  expected_key_bundle_legacy_master_key.SetActiveKey(
+      kOldInactiveSymmetricKeyHandle);
   EXPECT_EQ(expected_key_bundle_legacy_master_key,
             *key_registry()->GetKeyBundle(
                 CryptAuthKeyBundle::Name::kLegacyMasterKey));
 
   // Verify the key creation data, and assume successful key creation.
+  // Note: Since an active user key pair already exists, the same key material
+  // should re-used.
   base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey> expected_new_keys = {
       {CryptAuthKeyBundle::Name::kUserKeyPair,
-       CryptAuthKey(kNewPublicKey, kNewPrivateKey,
+       CryptAuthKey(kOldActivePublicKey, kOldActivePrivateKey,
                     CryptAuthKey::Status::kActive, KeyType::P256,
-                    kFixedUserKeyPairHandle)},
+                    kCryptAuthFixedUserKeyPairHandle)},
       {CryptAuthKeyBundle::Name::kLegacyMasterKey,
-       CryptAuthKey(kNewSymmetricKey, CryptAuthKey::Status::kInactive,
+       CryptAuthKey(kNewSymmetricKey, CryptAuthKey::Status::kActive,
                     KeyType::RAW256, kNewSymmetricKeyHandle)}};
 
   VerifyKeyCreatorInputs(
@@ -666,40 +701,13 @@
   EXPECT_EQ(kRandomSessionId, enroll_keys_request()->random_session_id());
   EXPECT_EQ(kClientDhPublicKey, enroll_keys_request()->client_ephemeral_dh());
   EXPECT_EQ(2, enroll_keys_request()->enroll_single_key_requests_size());
-
-  std::unique_ptr<CryptAuthKeyProofComputer> key_proof_computer =
-      CryptAuthKeyProofComputerImpl::Factory::Get()->BuildInstance();
-
-  const EnrollSingleKeyRequest& single_request_user_key_pair =
-      enroll_keys_request()->enroll_single_key_requests(
-          GetKeyBundleIndex(CryptAuthKeyBundle::Name::kUserKeyPair));
-  EXPECT_EQ(CryptAuthKeyBundle::KeyBundleNameEnumToString(
-                CryptAuthKeyBundle::Name::kUserKeyPair),
-            single_request_user_key_pair.key_name());
-  EXPECT_EQ(kFixedUserKeyPairHandle,
-            single_request_user_key_pair.new_key_handle());
-  EXPECT_EQ(kNewPublicKey, single_request_user_key_pair.key_material());
-  EXPECT_EQ(key_proof_computer->ComputeKeyProof(
-                expected_new_keys.find(CryptAuthKeyBundle::Name::kUserKeyPair)
-                    ->second,
-                kRandomSessionId, kCryptAuthKeyProofSalt),
-            single_request_user_key_pair.key_proof());
-
-  const EnrollSingleKeyRequest& single_request_legacy_master_key =
-      enroll_keys_request()->enroll_single_key_requests(
-          GetKeyBundleIndex(CryptAuthKeyBundle::Name::kLegacyMasterKey));
-  EXPECT_EQ(CryptAuthKeyBundle::KeyBundleNameEnumToString(
-                CryptAuthKeyBundle::Name::kLegacyMasterKey),
-            single_request_legacy_master_key.key_name());
-  EXPECT_EQ(kNewSymmetricKeyHandle,
-            single_request_legacy_master_key.new_key_handle());
-  EXPECT_TRUE(single_request_legacy_master_key.key_material().empty());
-  EXPECT_EQ(
-      key_proof_computer->ComputeKeyProof(
-          expected_new_keys.find(CryptAuthKeyBundle::Name::kLegacyMasterKey)
-              ->second,
-          kRandomSessionId, kCryptAuthKeyProofSalt),
-      single_request_legacy_master_key.key_proof());
+  VerifyEnrollSingleKeyRequest(
+      CryptAuthKeyBundle::Name::kUserKeyPair,
+      expected_new_keys.find(CryptAuthKeyBundle::Name::kUserKeyPair)->second);
+  VerifyEnrollSingleKeyRequest(
+      CryptAuthKeyBundle::Name::kLegacyMasterKey,
+      expected_new_keys.find(CryptAuthKeyBundle::Name::kLegacyMasterKey)
+          ->second);
 
   // Assume a successful EnrollKeys() call.
   // Note: No parameters in EnrollKeysResponse are processed by the enroller
@@ -732,15 +740,70 @@
 }
 
 TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
-       SuccessfulEnrollment_NoKeysCreated) {
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
-                                 kOldActiveAsymmetricKey);
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
-                                 kOldInactiveAsymmetricKey);
-  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kUserKeyPair,
-                                  GetSampleOldKeyDirective());
+       SuccessfulEnrollment_CreateUserKeyPair_NoKeyInRegistry) {
+  CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
+             GetSamplePreviousClientDirectivePolicyReference());
+
+  ClientDirective expected_new_client_directive = GetSampleNewClientDirective();
+  KeyDirective expected_new_key_directive = GetSampleNewKeyDirective();
+
+  SyncKeysResponse sync_keys_response = BuildSyncKeysResponse(
+      {SyncSingleKeyResponseData(
+          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
+          {} /* handle_to_action_map */,
+          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
+          KeyType::P256 /* new_key_type */,
+          expected_new_key_directive /* new_key_directive */)},
+      kRandomSessionId, kServerEphemeralDh, expected_new_client_directive);
+
+  SendSyncKeysResponse(sync_keys_response);
+
+  // Note: Because there is not an existing kUserKeyPair key in registry, a new
+  // key should be generated. (If there was an existing key, its key material
+  // would be reused because the kUserKeyPair key should not be rotated.)
+  base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey> expected_new_keys = {
+      {CryptAuthKeyBundle::Name::kUserKeyPair,
+       CryptAuthKey(kNewPublicKey, kNewPrivateKey,
+                    CryptAuthKey::Status::kActive, KeyType::P256,
+                    kCryptAuthFixedUserKeyPairHandle)}};
+
+  VerifyKeyCreatorInputs(
+      expected_new_keys,
+      kServerEphemeralDh /* expected_server_ephemeral_dh_public_key */);
+
+  RunKeyCreator(expected_new_keys, kClientEphemeralDh);
+
+  EXPECT_EQ(1, enroll_keys_request()->enroll_single_key_requests_size());
+  VerifyEnrollSingleKeyRequest(
+      CryptAuthKeyBundle::Name::kUserKeyPair,
+      expected_new_keys.find(CryptAuthKeyBundle::Name::kUserKeyPair)->second);
+
+  SendEnrollKeysResponse(EnrollKeysResponse());
+
+  EXPECT_EQ(CryptAuthEnrollmentResult(
+                CryptAuthEnrollmentResult::ResultCode::kSuccessNewKeysEnrolled,
+                expected_new_client_directive),
+            enrollment_result());
+
   CryptAuthKeyBundle expected_key_bundle(
-      *key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair));
+      CryptAuthKeyBundle::Name::kUserKeyPair);
+  expected_key_bundle.AddKey(
+      expected_new_keys.find(CryptAuthKeyBundle::Name::kUserKeyPair)->second);
+  expected_key_bundle.set_key_directive(expected_new_key_directive);
+  EXPECT_EQ(expected_key_bundle, *key_registry()->GetKeyBundle(
+                                     CryptAuthKeyBundle::Name::kUserKeyPair));
+}
+
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
+       SuccessfulEnrollment_NoKeysCreated) {
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
+                                 kOldActiveSymmetricKey);
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
+                                 kOldInactiveSymmetricKey);
+  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kLegacyMasterKey,
+                                  GetSampleOldKeyDirective());
+  CryptAuthKeyBundle expected_key_bundle(*key_registry()->GetKeyBundle(
+      CryptAuthKeyBundle::Name::kLegacyMasterKey));
 
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
@@ -749,18 +812,19 @@
   // but not create any new keys.
   SyncKeysResponse sync_keys_response =
       BuildSyncKeysResponse({SyncSingleKeyResponseData(
-          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
-          {{kOldActiveAsymmetricKeyHandle, SyncSingleKeyResponse::DEACTIVATE},
-           {kOldInactiveAsymmetricKeyHandle,
+          CryptAuthKeyBundle::Name::kLegacyMasterKey, key_registry(),
+          {{kOldActiveSymmetricKeyHandle, SyncSingleKeyResponse::DEACTIVATE},
+           {kOldInactiveSymmetricKeyHandle,
             SyncSingleKeyResponse::ACTIVATE}} /* handle_to_action_map */,
           SyncSingleKeyResponse::NONE /* new_key_creation */,
           base::nullopt /* new_key_type */,
           base::nullopt /* new_key_directive */)});
   SendSyncKeysResponse(sync_keys_response);
 
-  expected_key_bundle.SetActiveKey(kOldInactiveAsymmetricKeyHandle);
-  EXPECT_EQ(expected_key_bundle, *key_registry()->GetKeyBundle(
-                                     CryptAuthKeyBundle::Name::kUserKeyPair));
+  expected_key_bundle.SetActiveKey(kOldInactiveSymmetricKeyHandle);
+  EXPECT_EQ(expected_key_bundle,
+            *key_registry()->GetKeyBundle(
+                CryptAuthKeyBundle::Name::kLegacyMasterKey));
 
   EXPECT_EQ(CryptAuthEnrollmentResult(
                 CryptAuthEnrollmentResult::ResultCode::kSuccessNoNewKeysNeeded,
@@ -853,7 +917,7 @@
 
 TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
        Failure_InvalidKeyActions_NoActiveKey) {
-  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+  key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kLegacyMasterKey,
                                  kOldActiveAsymmetricKey);
 
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
@@ -862,8 +926,8 @@
   // Try to deactivate the only active key.
   SyncKeysResponse sync_keys_response =
       BuildSyncKeysResponse({SyncSingleKeyResponseData(
-          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
-          {{kOldActiveAsymmetricKeyHandle,
+          CryptAuthKeyBundle::Name::kLegacyMasterKey, key_registry(),
+          {{kOldActiveSymmetricKeyHandle,
             SyncSingleKeyResponse::DEACTIVATE}} /* handle_to_action_map */,
           SyncSingleKeyResponse::NONE /* new_key_creation */,
           base::nullopt /* new_key_type */,
@@ -899,13 +963,59 @@
 }
 
 TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
+       Failure_InvalidKeyCreationInstructions_UnsupportedUserKeyPairKeyType) {
+  CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
+             GetSamplePreviousClientDirectivePolicyReference());
+
+  // Instruct client to create a symmetric user key pair. The user key pair is
+  // heavily protected against anything other than P256.
+  SyncKeysResponse sync_keys_response =
+      BuildSyncKeysResponse({SyncSingleKeyResponseData(
+          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
+          {} /* handle_to_action_map */,
+          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
+          KeyType::RAW256 /* new_key_type */,
+          base::nullopt /* new_key_directive */)});
+  SendSyncKeysResponse(sync_keys_response);
+
+  EXPECT_EQ(CryptAuthEnrollmentResult(
+                CryptAuthEnrollmentResult::ResultCode::
+                    kErrorUserKeyPairCreationInstructionsInvalid,
+                sync_keys_response.client_directive()),
+            enrollment_result());
+}
+
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
+       Failure_InvalidKeyCreationInstructions_NewUserKeyPairMustBeActive) {
+  CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
+             GetSamplePreviousClientDirectivePolicyReference());
+
+  // Instruct client to create a new, inactive user key pair. Since there can
+  // only be one user key pair in the bundle, a new one must be active.
+  SyncKeysResponse sync_keys_response =
+      BuildSyncKeysResponse({SyncSingleKeyResponseData(
+          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
+          {} /* handle_to_action_map */,
+          SyncSingleKeyResponse::INACTIVE /* new_key_creation */,
+          KeyType::P256 /* new_key_type */,
+          base::nullopt /* new_key_directive */)});
+  SendSyncKeysResponse(sync_keys_response);
+
+  EXPECT_EQ(CryptAuthEnrollmentResult(
+                CryptAuthEnrollmentResult::ResultCode::
+                    kErrorUserKeyPairCreationInstructionsInvalid,
+                sync_keys_response.client_directive()),
+            enrollment_result());
+}
+
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
        Failure_InvalidKeyCreationInstructions_NoServerDiffieHellman) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
   SyncKeysResponse sync_keys_response =
       BuildSyncKeysResponse({SyncSingleKeyResponseData(
-          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
+          CryptAuthKeyBundle::Name::kLegacyMasterKey, key_registry(),
           {} /* handle_to_action_map */,
           SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
           KeyType::RAW256 /* new_key_type */,
@@ -941,7 +1051,7 @@
       {CryptAuthKeyBundle::Name::kUserKeyPair,
        CryptAuthKey(kNewPublicKey, kNewPrivateKey,
                     CryptAuthKey::Status::kActive, KeyType::P256,
-                    kFixedUserKeyPairHandle)}};
+                    kCryptAuthFixedUserKeyPairHandle)}};
   RunKeyCreator(expected_new_keys, kClientEphemeralDh);
 
   EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
@@ -980,7 +1090,7 @@
       {CryptAuthKeyBundle::Name::kUserKeyPair,
        CryptAuthKey(kNewPublicKey, kNewPrivateKey,
                     CryptAuthKey::Status::kActive, KeyType::P256,
-                    kFixedUserKeyPairHandle)}};
+                    kCryptAuthFixedUserKeyPairHandle)}};
   RunKeyCreator(expected_new_keys, kClientEphemeralDh);
 
   FailEnrollKeysRequest(NetworkRequestError::kBadRequest);
@@ -1049,7 +1159,7 @@
       {CryptAuthKeyBundle::Name::kUserKeyPair,
        CryptAuthKey(kNewPublicKey, kNewPrivateKey,
                     CryptAuthKey::Status::kActive, KeyType::P256,
-                    kFixedUserKeyPairHandle)}};
+                    kCryptAuthFixedUserKeyPairHandle)}};
   RunKeyCreator(expected_new_keys, kClientEphemeralDh);
 
   // Timeout waiting for EnrollKeysResponse.
diff --git a/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.cc b/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.cc
index 225b4b1..58fed3c 100644
--- a/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.cc
+++ b/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.cc
@@ -24,12 +24,13 @@
 base::Optional<std::string> FakeCryptAuthKeyProofComputer::ComputeKeyProof(
     const CryptAuthKey& key,
     const std::string& payload,
-    const std::string& salt) {
+    const std::string& salt,
+    const base::Optional<std::string>& info) {
   if (should_return_null_)
     return base::nullopt;
 
-  return kFakeKeyProofPrefix + std::string("_") + key.handle() +
-         std::string("_") + payload + std::string("_") + salt;
+  return kFakeKeyProofPrefix + std::string("_") + std::string("_") + payload +
+         std::string("_") + salt + (info ? "_" + *info : "");
 }
 
 }  // namespace device_sync
diff --git a/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.h b/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.h
index bd7c017..02a2dd9 100644
--- a/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.h
+++ b/chromeos/services/device_sync/fake_cryptauth_key_proof_computer.h
@@ -8,6 +8,8 @@
 #include <string>
 
 #include "base/macros.h"
+#include "base/optional.h"
+#include "chromeos/services/device_sync/cryptauth_key_bundle.h"
 #include "chromeos/services/device_sync/cryptauth_key_proof_computer.h"
 
 namespace chromeos {
@@ -22,10 +24,12 @@
   ~FakeCryptAuthKeyProofComputer() override;
 
   // CryptAuthKeyProofComputer:
-  // Returns "fake_key_proof_<key handle>_<payload>_<salt>".
-  base::Optional<std::string> ComputeKeyProof(const CryptAuthKey& key,
-                                              const std::string& payload,
-                                              const std::string& salt) override;
+  // Returns "fake_key_proof_|payload|>_<|salt|_|info (if not null)|".
+  base::Optional<std::string> ComputeKeyProof(
+      const CryptAuthKey& key,
+      const std::string& payload,
+      const std::string& salt,
+      const base::Optional<std::string>& info) override;
 
   void set_should_return_null(bool should_return_null) {
     should_return_null_ = should_return_null;
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 68fcbda..4dbc6a7 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -369,8 +369,7 @@
   if (enable_print_preview) {
     deps += [ "//components/pwg_encoder:unit_tests" ]
   }
-
-  if (safe_browsing_mode == 1) {
+  if (safe_browsing_mode == 1 || safe_browsing_mode == 3) {
     deps += [ "//components/safe_browsing/db:unit_tests_desktop" ]
   } else if (safe_browsing_mode == 2) {
     deps += [ "//components/safe_browsing/android:unit_tests_mobile" ]
diff --git a/components/autofill/content/renderer/page_form_analyser_logger.cc b/components/autofill/content/renderer/page_form_analyser_logger.cc
index 40676ca..88821bf 100644
--- a/components/autofill/content/renderer/page_form_analyser_logger.cc
+++ b/components/autofill/content/renderer/page_form_analyser_logger.cc
@@ -6,7 +6,11 @@
 
 #include <utility>
 
+#include "base/strings/string_util.h"
 #include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_input_element.h"
+#include "third_party/blink/public/web/web_node.h"
 
 namespace autofill {
 
@@ -38,11 +42,31 @@
       text.clear();
       text += "[DOM] ";
       text += entry.message;
-      for (unsigned i = 0; i < entry.nodes.size(); ++i)
-        text += " %o";
+
+      std::vector<blink::WebNode> nodesToLog;
+      for (unsigned i = 0; i < entry.nodes.size(); ++i) {
+        if (entry.nodes[i].IsElementNode()) {
+          const blink::WebElement element =
+              entry.nodes[i].ToConst<blink::WebElement>();
+          const blink::WebInputElement* webInputElement =
+              blink::ToWebInputElement(&element);
+
+          // Filter out password inputs with values from being logged, as their
+          // values are also logged.
+          const bool shouldObfuscate =
+              webInputElement &&
+              webInputElement->IsPasswordFieldForAutofill() &&
+              !webInputElement->Value().IsEmpty();
+
+          if (!shouldObfuscate) {
+            text += " %o";
+            nodesToLog.push_back(element);
+          }
+        }
+      }
 
       blink::WebConsoleMessage message(level, blink::WebString::FromUTF8(text));
-      message.nodes = std::move(entry.nodes);  // avoids copying node vectors.
+      message.nodes = std::move(nodesToLog);  // avoids copying node vectors.
       frame_->AddMessageToConsole(message);
     }
   }
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 9b95c9fa..fea04db 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -129,6 +129,7 @@
     "form_structure.h",
     "form_types.cc",
     "form_types.h",
+    "label_formatter.cc",
     "label_formatter.h",
     "label_formatter_utils.cc",
     "label_formatter_utils.h",
diff --git a/components/autofill/core/browser/address_form_label_formatter.cc b/components/autofill/core/browser/address_form_label_formatter.cc
index 1cd38ac6..604dd78 100644
--- a/components/autofill/core/browser/address_form_label_formatter.cc
+++ b/components/autofill/core/browser/address_form_label_formatter.cc
@@ -10,13 +10,11 @@
     const std::string& app_locale,
     ServerFieldType focused_field_type,
     const std::vector<ServerFieldType>& field_types)
-    : app_locale_(app_locale),
-      focused_field_type_(focused_field_type),
-      field_types_(field_types) {
+    : LabelFormatter(app_locale, focused_field_type, field_types) {
   for (const ServerFieldType& type : field_types) {
-    if ((type != ADDRESS_HOME_COUNTRY) && (type != ADDRESS_BILLING_COUNTRY) &&
-        (type != focused_field_type_)) {
-      filtered_field_types_.push_back(type);
+    if (type != focused_field_type && type != ADDRESS_HOME_COUNTRY &&
+        type != ADDRESS_BILLING_COUNTRY) {
+      field_types_for_labels_.push_back(type);
     }
   }
 }
diff --git a/components/autofill/core/browser/address_form_label_formatter.h b/components/autofill/core/browser/address_form_label_formatter.h
index e08a45d..1f0c485 100644
--- a/components/autofill/core/browser/address_form_label_formatter.h
+++ b/components/autofill/core/browser/address_form_label_formatter.h
@@ -19,33 +19,19 @@
 // with name and address fields and without email or phone fields.
 class AddressFormLabelFormatter : public LabelFormatter {
  public:
-  explicit AddressFormLabelFormatter(
-      const std::string& app_locale,
-      ServerFieldType focused_field_type,
-      const std::vector<ServerFieldType>& field_types);
+  AddressFormLabelFormatter(const std::string& app_locale,
+                            ServerFieldType focused_field_type,
+                            const std::vector<ServerFieldType>& field_types);
+
   ~AddressFormLabelFormatter() override;
 
   std::vector<base::string16> GetLabels(
       const std::vector<AutofillProfile*>& profiles) const override;
 
  private:
-  // The locale for which to generate labels. This reflects the language and
-  // country for which the application is translated, e.g. en_AU for Austalian
-  // English.
-  std::string app_locale_;
-
-  // The field on which the user is currently focused.
-  ServerFieldType focused_field_type_;
-
-  // A collection of meaningful field types in the form with which the user is
-  // interacting. The NO_SERVER_DATA and UNKNOWN_TYPE field types are not
-  // considered meaningful.
-  std::vector<ServerFieldType> field_types_;
-
-  // A collection of meaningful field types excluding the focused_field_type_
-  // and ADDRESS_HOME_COUNTRY and ADDRESS_BILLING_COUNTRY. These types are used
-  // to construct the labels.
-  std::vector<ServerFieldType> filtered_field_types_;
+  // A collection of field types that can be used to make labels. This
+  // collection excludes the focused_field_type_ and address countries.
+  std::vector<ServerFieldType> field_types_for_labels_;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 5803d9f7..39c2314 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -114,6 +114,8 @@
     // All the required conditions were satisfied even though the form is
     // unfocused after the user entered information into it.
     UPLOAD_OFFERED_FROM_NON_FOCUSABLE_FIELD = 1 << 15,
+    // The card does not satisfy any of the ranges of supported BIN ranges.
+    UPLOAD_NOT_OFFERED_UNSUPPORTED_BIN_RANGE = 1 << 16,
     // Update |kNumCardUploadDecisionMetrics| when adding new enum here.
   };
 
@@ -1235,7 +1237,7 @@
  private:
   static void Log(AutocompleteEvent event);
 
-  static const int kNumCardUploadDecisionMetrics = 16;
+  static const int kNumCardUploadDecisionMetrics = 17;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(AutofillMetrics);
 };
diff --git a/components/autofill/core/browser/contact_form_label_formatter.cc b/components/autofill/core/browser/contact_form_label_formatter.cc
index 7db04dd..47701675 100644
--- a/components/autofill/core/browser/contact_form_label_formatter.cc
+++ b/components/autofill/core/browser/contact_form_label_formatter.cc
@@ -9,15 +9,19 @@
 ContactFormLabelFormatter::ContactFormLabelFormatter(
     const std::string& app_locale,
     ServerFieldType focused_field_type,
-    const std::vector<ServerFieldType>& field_types)
-    : app_locale_(app_locale),
-      focused_field_type_(focused_field_type),
-      field_types_(field_types) {
+    const std::vector<ServerFieldType>& field_types,
+    const std::set<FieldTypeGroup>& field_type_groups)
+    : LabelFormatter(app_locale, focused_field_type, field_types),
+      field_type_groups_(field_type_groups),
+      filtered_field_type_groups_(field_type_groups) {
   for (const ServerFieldType& type : field_types) {
-    if (type != focused_field_type_) {
-      filtered_field_types_.push_back(type);
+    if (type != focused_field_type) {
+      field_types_for_labels_.push_back(type);
     }
   }
+  const FieldTypeGroup group =
+      AutofillType(AutofillType(focused_field_type).GetStorableType()).group();
+  filtered_field_type_groups_.erase(group);
 }
 
 ContactFormLabelFormatter::~ContactFormLabelFormatter() {}
diff --git a/components/autofill/core/browser/contact_form_label_formatter.h b/components/autofill/core/browser/contact_form_label_formatter.h
index 47fd10d..3868497 100644
--- a/components/autofill/core/browser/contact_form_label_formatter.h
+++ b/components/autofill/core/browser/contact_form_label_formatter.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_CONTACT_FORM_LABEL_FORMATTER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_CONTACT_FORM_LABEL_FORMATTER_H_
 
+#include <set>
 #include <string>
 #include <vector>
 
@@ -19,32 +20,28 @@
 // containing name and phone or email fields.
 class ContactFormLabelFormatter : public LabelFormatter {
  public:
-  explicit ContactFormLabelFormatter(
-      const std::string& app_locale,
-      ServerFieldType focused_field_type,
-      const std::vector<ServerFieldType>& field_types);
+  ContactFormLabelFormatter(const std::string& app_locale,
+                            ServerFieldType focused_field_type,
+                            const std::vector<ServerFieldType>& field_types,
+                            const std::set<FieldTypeGroup>& field_type_groups);
+
   ~ContactFormLabelFormatter() override;
 
   std::vector<base::string16> GetLabels(
       const std::vector<AutofillProfile*>& profiles) const override;
 
  private:
-  // The locale for which to generate labels. This reflects the language and
-  // country for which the application is translated, e.g. en_AU for Austalian
-  // English.
-  std::string app_locale_;
+  // A collection of field types that can be used to make labels. This
+  // collection excludes the focused_field_type_.
+  std::vector<ServerFieldType> field_types_for_labels_;
 
-  // The field on which the user is currently focused.
-  ServerFieldType focused_field_type_;
+  // A collection of meaningful FieldTypeGroups in the form with which the user
+  // is interacting.
+  std::set<FieldTypeGroup> field_type_groups_;
 
-  // A collection of meaningful field types in the form with which the user is
-  // interacting. The NO_SERVER_DATA and UNKNOWN_TYPE field types are not
-  // considered meaningful.
-  std::vector<ServerFieldType> field_types_;
-
-  // A collection of meaningful field types excluding the focused_field_type_.
-  // These types are used to construct the labels.
-  std::vector<ServerFieldType> filtered_field_types_;
+  // A collection of meaningful FieldTypeGroups in the form with which the user
+  // is interacting minus the focused field's corresponding FieldTypeGroup.
+  std::set<FieldTypeGroup> filtered_field_type_groups_;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/credit_card_save_manager.cc b/components/autofill/core/browser/credit_card_save_manager.cc
index 84e8922..6aa41e8e 100644
--- a/components/autofill/core/browser/credit_card_save_manager.cc
+++ b/components/autofill/core/browser/credit_card_save_manager.cc
@@ -420,8 +420,8 @@
   show_save_prompt_ =
       num_strikes < kMaxStrikesToPreventPoppingUpOfferToSavePrompt;
 
-  // Only offer upload once both Payments and the Autofill LegacyStrikeDatabase
-  // have returned their decisions. Use population of
+  // Only offer upload once both Payments and the Autofill
+  // LegacyStrikeDatabase have returned their decisions. Use population of
   // |upload_request_.context_token| as an indicator of the Payments call
   // returning successfully.
   if (!upload_request_.context_token.empty())
@@ -431,12 +431,24 @@
 void CreditCardSaveManager::OnDidGetUploadDetails(
     AutofillClient::PaymentsRpcResult result,
     const base::string16& context_token,
-    std::unique_ptr<base::Value> legal_message) {
+    std::unique_ptr<base::Value> legal_message,
+    std::vector<std::pair<int, int>> supported_card_bin_ranges) {
   if (observer_for_testing_)
     observer_for_testing_->OnReceivedGetUploadDetailsResponse();
   if (result == AutofillClient::SUCCESS) {
     // Do *not* call payments_client_->Prepare() here. We shouldn't send
     // credentials until the user has explicitly accepted a prompt to upload.
+    if (base::FeatureList::IsEnabled(
+            features::kAutofillDoNotUploadSaveUnsupportedCards) &&
+        !supported_card_bin_ranges.empty() &&
+        !IsCreditCardSupported(supported_card_bin_ranges)) {
+      AttemptToOfferCardLocalSave(has_non_focusable_field_,
+                                  upload_request_.card);
+      upload_decision_metrics_ |=
+          AutofillMetrics::UPLOAD_NOT_OFFERED_UNSUPPORTED_BIN_RANGE;
+      LogCardUploadDecisions(upload_decision_metrics_);
+      return;
+    }
     upload_request_.context_token = context_token;
     legal_message_ = base::DictionaryValue::From(std::move(legal_message));
 
@@ -1057,4 +1069,28 @@
   }
 }
 
+bool CreditCardSaveManager::IsCreditCardSupported(
+    std::vector<std::pair<int, int>> supported_card_bin_ranges) {
+  base::string16 stripped_number =
+      CreditCard::StripSeparators(upload_request_.card.number());
+  for (auto& bin_range : supported_card_bin_ranges) {
+    unsigned long range_num_of_digits =
+        base::NumberToString(bin_range.first).size();
+    DCHECK_EQ(range_num_of_digits,
+              base::NumberToString(bin_range.second).size());
+    // The first n digits of credit card number, where n is the number of
+    // digits in range's starting/ending number.
+    int first_digits_start, first_digits_end;
+    base::StringToInt(stripped_number.substr(0, range_num_of_digits),
+                      &first_digits_start);
+    base::StringToInt(stripped_number.substr(0, range_num_of_digits),
+                      &first_digits_end);
+    if (first_digits_start >= bin_range.first &&
+        first_digits_end <= bin_range.second) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/credit_card_save_manager.h b/components/autofill/core/browser/credit_card_save_manager.h
index f6fd234..5cffff6 100644
--- a/components/autofill/core/browser/credit_card_save_manager.h
+++ b/components/autofill/core/browser/credit_card_save_manager.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/optional.h"
@@ -153,10 +154,14 @@
 
   // Returns the legal message retrieved from Payments. On failure or not
   // meeting Payments's conditions for upload, |legal_message| will contain
-  // nullptr.
-  void OnDidGetUploadDetails(AutofillClient::PaymentsRpcResult result,
-                             const base::string16& context_token,
-                             std::unique_ptr<base::Value> legal_message);
+  // nullptr. |supported_card_bin_ranges| is a list of BIN prefix ranges which
+  // are supoorted, with the first and second number in the pair being the start
+  // and end of the range.
+  void OnDidGetUploadDetails(
+      AutofillClient::PaymentsRpcResult result,
+      const base::string16& context_token,
+      std::unique_ptr<base::Value> legal_message,
+      std::vector<std::pair<int, int>> supported_card_bin_ranges);
 
   // Logs the number of strikes that a card had when save succeeded.
   void LogStrikesPresentWhenCardSaved(bool is_local, const int num_strikes);
@@ -259,6 +264,13 @@
   // Logs the reason why expiration date was explicitly requested.
   void LogSaveCardRequestExpirationDateReasonMetric();
 
+  // Checks if credit card matches one of the ranges in
+  // |supported_card_bin_ranges|, inclusive of the start and end boundaries.
+  // For example, if the range consists of std::pair<34, 36>, then all cards
+  // with first two digits of 34, 35 and 36 are supported.
+  bool IsCreditCardSupported(
+      std::vector<std::pair<int, int>> supported_card_bin_ranges);
+
   // For testing.
   void SetEventObserverForTesting(ObserverForTest* observer) {
     observer_for_testing_ = observer;
@@ -335,6 +347,8 @@
   std::unique_ptr<LocalCardMigrationStrikeDatabase>
       local_card_migration_strike_database_;
 
+  std::unique_ptr<CreditCardSaveStrikeDatabase> strike_database_;
+
   // May be null.
   ObserverForTest* observer_for_testing_ = nullptr;
 
diff --git a/components/autofill/core/browser/credit_card_save_manager_unittest.cc b/components/autofill/core/browser/credit_card_save_manager_unittest.cc
index af7d56d8..ec4e91e3 100644
--- a/components/autofill/core/browser/credit_card_save_manager_unittest.cc
+++ b/components/autofill/core/browser/credit_card_save_manager_unittest.cc
@@ -5509,4 +5509,61 @@
   EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 3);
 }
 
+// Tests that if a card doesn't fall in any of the supported bin ranges, local
+// save is offered rather than upload save.
+TEST_F(CreditCardSaveManagerTest, UploadSaveNotOfferedForUnsupportedCard) {
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillDoNotUploadSaveUnsupportedCards);
+  std::vector<std::pair<int, int>> supported_card_bin_ranges{
+      std::make_pair(4111, 4113), std::make_pair(34, 34),
+      std::make_pair(300, 305)};
+  payments_client_->SetSupportedBINRanges(supported_card_bin_ranges);
+  // Set up our credit card form data.
+  FormData credit_card_form;
+  CreateTestCreditCardFormData(&credit_card_form, true, false);
+  FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+  // Edit the data, and submit.
+  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
+  credit_card_form.fields[1].value = ASCIIToUTF16("5454545454545454");
+  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
+  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
+  credit_card_form.fields[4].value = ASCIIToUTF16("123");
+
+  // Since card isn't in any of the supported ranges, local save should be
+  // offered and upload save should not.
+  FormSubmitted(credit_card_form);
+  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
+  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
+}
+
+// Tests that if a card falls in one of the supported bin ranges, upload save
+// is offered.
+TEST_F(CreditCardSaveManagerTest, UploadSaveOfferedForSupportedCard) {
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillDoNotUploadSaveUnsupportedCards);
+  // Set supported BIN ranges.
+  std::vector<std::pair<int, int>> supported_card_bin_ranges{
+      std::make_pair(4111, 4113)};
+  payments_client_->SetSupportedBINRanges(supported_card_bin_ranges);
+
+  // Set up our credit card form data.
+  FormData credit_card_form;
+  CreateTestCreditCardFormData(&credit_card_form, true, false);
+  FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+  // Edit the data, and submit.
+  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
+  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
+  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
+  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
+  credit_card_form.fields[4].value = ASCIIToUTF16("123");
+
+  // Since card is in one of the supported ranges(4111-4113), upload save should
+  // be offered.
+  FormSubmitted(credit_card_form);
+  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
+  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/label_formatter.cc b/components/autofill/core/browser/label_formatter.cc
new file mode 100644
index 0000000..32426dd
--- /dev/null
+++ b/components/autofill/core/browser/label_formatter.cc
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/label_formatter.h"
+
+namespace autofill {
+
+LabelFormatter::LabelFormatter(const std::string& app_locale,
+                               ServerFieldType focused_field_type,
+                               const std::vector<ServerFieldType>& field_types)
+    : app_locale_(app_locale),
+      focused_field_type_(focused_field_type),
+      field_types_(field_types) {}
+LabelFormatter::~LabelFormatter() = default;
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/label_formatter.h b/components/autofill/core/browser/label_formatter.h
index a4061809..d4364010 100644
--- a/components/autofill/core/browser/label_formatter.h
+++ b/components/autofill/core/browser/label_formatter.h
@@ -17,11 +17,35 @@
 // Handles the creation of Suggestions' disambiguating labels.
 class LabelFormatter {
  public:
-  virtual ~LabelFormatter() = default;
+  LabelFormatter(const std::string& app_locale,
+                 ServerFieldType focused_field_type,
+                 const std::vector<ServerFieldType>& field_types);
+  virtual ~LabelFormatter();
+
   // Returns a collection of |labels| formed by extracting useful disambiguating
   // information from a collection of |profiles|.
   virtual std::vector<base::string16> GetLabels(
       const std::vector<AutofillProfile*>& profiles) const = 0;
+
+ protected:
+  const std::string& app_locale() const { return app_locale_; }
+  ServerFieldType focused_field_type() const { return focused_field_type_; }
+  const std::vector<ServerFieldType>& field_types() const {
+    return field_types_;
+  }
+
+ private:
+  // The locale for which to generate labels. This reflects the language and
+  // country for which the application is translated, e.g. en_AU for Austalian
+  // English.
+  std::string app_locale_;
+
+  // The field on which the user is currently focused.
+  ServerFieldType focused_field_type_;
+
+  // A collection of meaningful field types in the form with which the user is
+  // interacting.
+  std::vector<ServerFieldType> field_types_;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/label_formatter_utils.cc b/components/autofill/core/browser/label_formatter_utils.cc
index aae4a090..53d8eab 100644
--- a/components/autofill/core/browser/label_formatter_utils.cc
+++ b/components/autofill/core/browser/label_formatter_utils.cc
@@ -7,22 +7,64 @@
 #include <memory>
 #include <set>
 
+#include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/browser/address_form_label_formatter.h"
+#include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/contact_form_label_formatter.h"
+#include "components/autofill/core/browser/validation.h"
 
 namespace autofill {
 namespace {
 
-// Returns true if |type| belongs to the NAME FieldTypeGroup. Note that billing
-// ServerFieldTypes are converted to their non-billing types.
-bool FieldTypeIsName(const ServerFieldType& type) {
-  FieldTypeGroup focused_field_group =
-      AutofillType(AutofillType(type).GetStorableType()).group();
-  return focused_field_group == NAME;
+bool ContainsName(uint32_t groups) {
+  return groups & label_formatter_groups::kName;
+}
+
+bool ContainsAddress(uint32_t groups) {
+  return groups & label_formatter_groups::kAddress;
+}
+
+bool ContainsEmail(uint32_t groups) {
+  return groups & label_formatter_groups::kEmail;
+}
+
+bool ContainsPhone(uint32_t groups) {
+  return groups & label_formatter_groups::kPhone;
+}
+
+std::set<FieldTypeGroup> GetFieldTypeGroups(uint32_t groups) {
+  std::set<FieldTypeGroup> field_type_groups;
+  if (ContainsName(groups)) {
+    field_type_groups.insert(NAME);
+  }
+  if (ContainsAddress(groups)) {
+    field_type_groups.insert(ADDRESS_HOME);
+  }
+  if (ContainsEmail(groups)) {
+    field_type_groups.insert(EMAIL);
+  }
+  if (ContainsPhone(groups)) {
+    field_type_groups.insert(PHONE_HOME);
+  }
+  return field_type_groups;
 }
 
 }  // namespace
 
+std::vector<ServerFieldType> FilterFieldTypes(
+    const std::vector<ServerFieldType>& field_types) {
+  std::vector<ServerFieldType> filtered_field_types;
+  for (const ServerFieldType& field_type : field_types) {
+    const FieldTypeGroup group =
+        AutofillType(AutofillType(field_type).GetStorableType()).group();
+    if (group == NAME || group == ADDRESS_HOME || group == EMAIL ||
+        group == PHONE_HOME) {
+      filtered_field_types.push_back(field_type);
+    }
+  }
+  return filtered_field_types;
+}
+
 uint32_t DetermineGroups(const std::vector<ServerFieldType>& field_types) {
   uint32_t group_bitmask = 0;
   for (const ServerFieldType& type : field_types) {
@@ -42,7 +84,7 @@
         group_bitmask |= label_formatter_groups::kPhone;
         break;
       default:
-        group_bitmask |= label_formatter_groups::kUnsupported;
+        break;
     }
   }
   return group_bitmask;
@@ -52,34 +94,22 @@
     const std::string& app_locale,
     ServerFieldType focused_field_type,
     const std::vector<ServerFieldType>& field_types) {
-  if (!FieldTypeIsName(focused_field_type)) {
+  const uint32_t groups = DetermineGroups(field_types);
+
+  if (!ContainsName(groups)) {
     return nullptr;
   }
-
-  std::vector<ServerFieldType> filtered_field_types;
-  for (const ServerFieldType& field_type : field_types) {
-    // NO_SERVER_DATA fields are frequently found in the collection of field
-    // types sent from the frontend. UKNOWN_TYPE fields represent various form
-    // elements, e.g. checkboxes. Neither field type is useful to the formatter,
-    // so they are excluded from its collection of field types.
-    if (field_type != NO_SERVER_DATA && field_type != UNKNOWN_TYPE) {
-      filtered_field_types.push_back(field_type);
-    }
-  }
-
-  const uint32_t groups = DetermineGroups(filtered_field_types);
-  if (groups ==
-      (label_formatter_groups::kName | label_formatter_groups::kAddress)) {
+  if (ContainsAddress(groups) && !ContainsEmail(groups) &&
+      !ContainsPhone(groups)) {
     return std::make_unique<AddressFormLabelFormatter>(
-        app_locale, focused_field_type, filtered_field_types);
+        app_locale, focused_field_type, FilterFieldTypes(field_types));
   }
-
-  if (groups ==
-      (label_formatter_groups::kName | label_formatter_groups::kPhone |
-       label_formatter_groups::kEmail)) {
+  if (ContainsEmail(groups) || ContainsPhone(groups)) {
     return std::make_unique<ContactFormLabelFormatter>(
-        app_locale, focused_field_type, filtered_field_types);
+        app_locale, focused_field_type, FilterFieldTypes(field_types),
+        GetFieldTypeGroups(groups));
   }
   return nullptr;
 }
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/label_formatter_utils.h b/components/autofill/core/browser/label_formatter_utils.h
index f4c12f4b..80b2194 100644
--- a/components/autofill/core/browser/label_formatter_utils.h
+++ b/components/autofill/core/browser/label_formatter_utils.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_LABEL_FORMATTER_UTILS_H_
 
 #include <memory>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -16,30 +17,37 @@
 namespace label_formatter_groups {
 
 // Bits for FieldTypeGroup options.
-// The form contains an unsupported field.
-constexpr uint32_t kUnsupported = 1 << 0;
 // The form contains at least one field associated with the NAME_HOME or
 // NAME_BILLING FieldTypeGroups.
-constexpr uint32_t kName = 1 << 1;
+constexpr uint32_t kName = 1 << 0;
 // The form contains at least one field associated with the ADDRESS_HOME or
 // ADDRESS_BILLING FieldTypeGroups.
-constexpr uint32_t kAddress = 1 << 2;
+constexpr uint32_t kAddress = 1 << 1;
 // The form contains at least one field associated with the EMAIL
 // FieldTypeGroup.
-constexpr uint32_t kEmail = 1 << 3;
+constexpr uint32_t kEmail = 1 << 2;
 // The form contains at least one field associated with the PHONE_HOME or
 // PHONE_BILLING FieldTypeGroup.
-constexpr uint32_t kPhone = 1 << 4;
+constexpr uint32_t kPhone = 1 << 3;
 
 }  // namespace label_formatter_groups
 
-// Returns a bitmask indicating the FieldTypeGroups associated with the given
-// |field_types|.
+// Returns a subset of meaningful ServerFieldTypes found in |field_types|.
+// ServerFieldTypes like NO_SERVER_DATA and UNKNOWN_TYPE are frequently found in
+// the collection of types sent from the frontend. Other types, e.g.
+// COMPANY_NAME and PHONE_FAX_WHOLE_NUMBER, are also sometimes present. These
+// types are not useful to LabelFormatters, so only types related to names,
+// addresses, emails, and phone numbers are in the results.
+std::vector<ServerFieldType> FilterFieldTypes(
+    const std::vector<ServerFieldType>& field_types);
+
+// Returns a bitmask indicating whether the NAME, ADDRESS_HOME, EMAIL, and
+// PHONE_HOME FieldTypeGroups are associated with the given |field_types|.
 uint32_t DetermineGroups(const std::vector<ServerFieldType>& field_types);
 
 // Creates a form-specific LabelFormatter according to |field_types|. If the
-// given |focused_field_type| and |field_types| do not correspond to a
-// LabelFormatter, then nullptr will be returned.
+// given |field_types| do not correspond to a LabelFormatter, then nullptr will
+// be returned.
 std::unique_ptr<LabelFormatter> Create(
     const std::string& app_locale,
     ServerFieldType focused_field_type,
diff --git a/components/autofill/core/browser/label_formatter_utils_unittest.cc b/components/autofill/core/browser/label_formatter_utils_unittest.cc
index 0542d2c..660cb02 100644
--- a/components/autofill/core/browser/label_formatter_utils_unittest.cc
+++ b/components/autofill/core/browser/label_formatter_utils_unittest.cc
@@ -13,9 +13,35 @@
 using label_formatter_groups::kEmail;
 using label_formatter_groups::kName;
 using label_formatter_groups::kPhone;
-using label_formatter_groups::kUnsupported;
+
 }  // namespace
 
+TEST(LabelFormatterUtilsTest, FilterFieldTypesNoFiltering) {
+  const std::vector<ServerFieldType> field_types{
+      NAME_LAST,           NAME_BILLING_LAST, ADDRESS_HOME_ZIP,
+      ADDRESS_BILLING_ZIP, EMAIL_ADDRESS,     PHONE_HOME_NUMBER,
+      PHONE_BILLING_NUMBER};
+  const std::vector<ServerFieldType> filtered_field_types =
+      FilterFieldTypes(field_types);
+  EXPECT_EQ(field_types, filtered_field_types);
+}
+
+TEST(LabelFormatterUtilsTest, FilterFieldTypesFilterCompany) {
+  const std::vector<ServerFieldType> field_types{NAME_LAST, COMPANY_NAME};
+  const std::vector<ServerFieldType> expected_filtered_field_types{NAME_LAST};
+  const std::vector<ServerFieldType> filtered_field_types =
+      FilterFieldTypes(field_types);
+  EXPECT_EQ(expected_filtered_field_types, filtered_field_types);
+}
+
+TEST(LabelFormatterUtilsTest, FilterFieldTypesForNoGivenFieldTypes) {
+  const std::vector<ServerFieldType> field_types =
+      std::vector<ServerFieldType>();
+  const std::vector<ServerFieldType> filtered_field_types =
+      FilterFieldTypes(field_types);
+  EXPECT_EQ(field_types, filtered_field_types);
+}
+
 TEST(LabelFormatterUtilsTest, DetermineGroupsForHomeNameAndAddress) {
   const std::vector<ServerFieldType> field_types{
       NAME_FIRST,        NAME_LAST,          ADDRESS_HOME_LINE1,
@@ -36,7 +62,7 @@
   EXPECT_EQ(expected_group_bitmask, group_bitmask);
 }
 
-TEST(LabelFormatterUtilsTest, DetermineGroupsForNameHomePhoneAndEmail) {
+TEST(LabelFormatterUtilsTest, DetermineGroupsForHomeNamePhoneAndEmail) {
   const std::vector<ServerFieldType> field_types{
       NAME_FULL, PHONE_HOME_CITY_AND_NUMBER, EMAIL_ADDRESS};
 
@@ -45,7 +71,7 @@
   EXPECT_EQ(expected_group_bitmask, group_bitmask);
 }
 
-TEST(LabelFormatterUtilsTest, DetermineGroupsForNameBillingPhoneAndEmail) {
+TEST(LabelFormatterUtilsTest, DetermineGroupsForBillingNamePhoneAndEmail) {
   const std::vector<ServerFieldType> field_types{
       NAME_BILLING_FULL, PHONE_BILLING_WHOLE_NUMBER, EMAIL_ADDRESS};
 
@@ -58,7 +84,7 @@
   const std::vector<ServerFieldType> field_types{UNKNOWN_TYPE, NAME_FULL,
                                                  ADDRESS_HOME_ZIP};
 
-  const uint32_t expected_group_bitmask = kName | kAddress | kUnsupported;
+  const uint32_t expected_group_bitmask = kName | kAddress;
   const uint32_t group_bitmask = DetermineGroups(field_types);
   EXPECT_EQ(expected_group_bitmask, group_bitmask);
 }
@@ -71,4 +97,4 @@
   EXPECT_EQ(expected_group_bitmask, group_bitmask);
 }
 
-}  // namespace autofill
+}  // namespace autofill
\ No newline at end of file
diff --git a/components/autofill/core/browser/local_card_migration_manager.cc b/components/autofill/core/browser/local_card_migration_manager.cc
index ab4fb69..e82bb52 100644
--- a/components/autofill/core/browser/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/local_card_migration_manager.cc
@@ -232,7 +232,8 @@
     bool is_from_settings_page,
     AutofillClient::PaymentsRpcResult result,
     const base::string16& context_token,
-    std::unique_ptr<base::Value> legal_message) {
+    std::unique_ptr<base::Value> legal_message,
+    std::vector<std::pair<int, int>> supported_card_bin_ranges) {
   if (observer_for_testing_)
     observer_for_testing_->OnReceivedGetUploadDetailsResponse();
 
diff --git a/components/autofill/core/browser/local_card_migration_manager.h b/components/autofill/core/browser/local_card_migration_manager.h
index a040d1f..02365d3 100644
--- a/components/autofill/core/browser/local_card_migration_manager.h
+++ b/components/autofill/core/browser/local_card_migration_manager.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/strings/string16.h"
@@ -134,7 +135,8 @@
       bool is_from_settings_page,
       AutofillClient::PaymentsRpcResult result,
       const base::string16& context_token,
-      std::unique_ptr<base::Value> legal_message);
+      std::unique_ptr<base::Value> legal_message,
+      std::vector<std::pair<int, int>> supported_card_bin_ranges);
 
   // Callback after successfully getting the migration save results. Map
   // migration save result to each card depending on the |save_result|. Will
diff --git a/components/autofill/core/browser/payments/payments_client.cc b/components/autofill/core/browser/payments/payments_client.cc
index fe22763..3fda27d8 100644
--- a/components/autofill/core/browser/payments/payments_client.cc
+++ b/components/autofill/core/browser/payments/payments_client.cc
@@ -343,7 +343,8 @@
       const std::string& app_locale,
       base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
                               const base::string16&,
-                              std::unique_ptr<base::Value>)> callback,
+                              std::unique_ptr<base::Value>,
+                              std::vector<std::pair<int, int>>)> callback,
       const int billable_service_number,
       PaymentsClient::UploadCardSource upload_card_source)
       : addresses_(addresses),
@@ -442,6 +443,16 @@
     base::Value* dictionary_value = response.FindKey("legal_message");
     if (dictionary_value)
       legal_message_ = std::make_unique<base::Value>(dictionary_value->Clone());
+
+    base::Value* list_ptr = response.FindKey("supported_card_bin_ranges");
+    if (list_ptr && list_ptr->is_list()) {
+      for (base::Value& result : list_ptr->GetList()) {
+        DCHECK(result.is_dict());
+        base::Optional<int> start = response.FindIntKey("start");
+        base::Optional<int> end = response.FindIntKey("end");
+        supported_card_bin_ranges_.push_back(std::make_pair(*start, *end));
+      }
+    }
   }
 
   bool IsResponseComplete() override {
@@ -449,7 +460,8 @@
   }
 
   void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override {
-    std::move(callback_).Run(result, context_token_, std::move(legal_message_));
+    std::move(callback_).Run(result, context_token_, std::move(legal_message_),
+                             supported_card_bin_ranges_);
   }
 
  private:
@@ -460,10 +472,12 @@
   std::string app_locale_;
   base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
                           const base::string16&,
-                          std::unique_ptr<base::Value>)>
+                          std::unique_ptr<base::Value>,
+                          std::vector<std::pair<int, int>>)>
       callback_;
   base::string16 context_token_;
   std::unique_ptr<base::Value> legal_message_;
+  std::vector<std::pair<int, int>> supported_card_bin_ranges_;
   const int billable_service_number_;
   PaymentsClient::UploadCardSource upload_card_source_;
 };
@@ -771,7 +785,8 @@
     const std::string& app_locale,
     base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
                             const base::string16&,
-                            std::unique_ptr<base::Value>)> callback,
+                            std::unique_ptr<base::Value>,
+                            std::vector<std::pair<int, int>>)> callback,
     const int billable_service_number,
     UploadCardSource upload_card_source) {
   IssueRequest(
diff --git a/components/autofill/core/browser/payments/payments_client.h b/components/autofill/core/browser/payments/payments_client.h
index be0bc53..ad6c075 100644
--- a/components/autofill/core/browser/payments/payments_client.h
+++ b/components/autofill/core/browser/payments/payments_client.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_CLIENT_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_CLIENT_H_
 
+#include <utility>
+
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
@@ -178,7 +180,8 @@
       const std::string& app_locale,
       base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
                               const base::string16&,
-                              std::unique_ptr<base::Value>)> callback,
+                              std::unique_ptr<base::Value>,
+                              std::vector<std::pair<int, int>>)> callback,
       const int billable_service_number,
       UploadCardSource upload_card_source =
           UploadCardSource::UNKNOWN_UPLOAD_CARD_SOURCE);
diff --git a/components/autofill/core/browser/payments/payments_client_unittest.cc b/components/autofill/core/browser/payments/payments_client_unittest.cc
index 2d3e5350..6f9edce 100644
--- a/components/autofill/core/browser/payments/payments_client_unittest.cc
+++ b/components/autofill/core/browser/payments/payments_client_unittest.cc
@@ -119,11 +119,14 @@
     real_pan_ = real_pan;
   }
 
-  void OnDidGetUploadDetails(AutofillClient::PaymentsRpcResult result,
-                             const base::string16& context_token,
-                             std::unique_ptr<base::Value> legal_message) {
+  void OnDidGetUploadDetails(
+      AutofillClient::PaymentsRpcResult result,
+      const base::string16& context_token,
+      std::unique_ptr<base::Value> legal_message,
+      std::vector<std::pair<int, int>> supported_card_bin_ranges) {
     result_ = result;
     legal_message_ = std::move(legal_message);
+    supported_card_bin_ranges_ = supported_card_bin_ranges;
   }
 
   void OnDidUploadCard(AutofillClient::PaymentsRpcResult result,
@@ -237,6 +240,7 @@
   std::string server_id_;
   std::string real_pan_;
   std::unique_ptr<base::Value> legal_message_;
+  std::vector<std::pair<int, int>> supported_card_bin_ranges_;
   std::vector<MigratableCreditCard> migratable_credit_cards_;
   std::unique_ptr<std::unordered_map<std::string, std::string>> save_result_;
   std::string display_text_;
diff --git a/components/autofill/core/browser/payments/test_payments_client.cc b/components/autofill/core/browser/payments/test_payments_client.cc
index 8bf0f86a..e58fe67 100644
--- a/components/autofill/core/browser/payments/test_payments_client.cc
+++ b/components/autofill/core/browser/payments/test_payments_client.cc
@@ -30,7 +30,8 @@
     const std::string& app_locale,
     base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
                             const base::string16&,
-                            std::unique_ptr<base::Value>)> callback,
+                            std::unique_ptr<base::Value>,
+                            std::vector<std::pair<int, int>>)> callback,
     const int billable_service_number,
     PaymentsClient::UploadCardSource upload_card_source) {
   upload_details_addresses_ = addresses;
@@ -38,11 +39,11 @@
   active_experiments_ = active_experiments;
   billable_service_number_ = billable_service_number;
   upload_card_source_ = upload_card_source;
-  std::move(callback).Run(app_locale == "en-US"
-                              ? AutofillClient::SUCCESS
-                              : AutofillClient::PERMANENT_FAILURE,
-                          base::ASCIIToUTF16("this is a context token"),
-                          std::unique_ptr<base::Value>(nullptr));
+  std::move(callback).Run(
+      app_locale == "en-US" ? AutofillClient::SUCCESS
+                            : AutofillClient::PERMANENT_FAILURE,
+      base::ASCIIToUTF16("this is a context token"),
+      std::unique_ptr<base::Value>(nullptr), supported_card_bin_ranges_);
 }
 
 void TestPaymentsClient::UploadCard(
@@ -71,5 +72,10 @@
   save_result_ = std::move(save_result);
 }
 
+void TestPaymentsClient::SetSupportedBINRanges(
+    std::vector<std::pair<int, int>> bin_ranges) {
+  supported_card_bin_ranges_ = bin_ranges;
+}
+
 }  // namespace payments
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/test_payments_client.h b/components/autofill/core/browser/payments/test_payments_client.h
index 01e9d00..e7f6c928 100644
--- a/components/autofill/core/browser/payments/test_payments_client.h
+++ b/components/autofill/core/browser/payments/test_payments_client.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_PAYMENTS_CLIENT_H_
 
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "components/autofill/core/browser/payments/payments_client.h"
@@ -34,7 +35,8 @@
       const std::string& app_locale,
       base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
                               const base::string16&,
-                              std::unique_ptr<base::Value>)> callback,
+                              std::unique_ptr<base::Value>,
+                              std::vector<std::pair<int, int>>)> callback,
       const int billable_service_number,
       UploadCardSource upload_card_source =
           UploadCardSource::UNKNOWN_UPLOAD_CARD_SOURCE) override;
@@ -55,6 +57,8 @@
       std::unique_ptr<std::unordered_map<std::string, std::string>>
           save_result);
 
+  void SetSupportedBINRanges(std::vector<std::pair<int, int>> bin_ranges);
+
   int detected_values_in_upload_details() const { return detected_values_; }
   const std::vector<AutofillProfile>& addresses_in_upload_details() const {
     return upload_details_addresses_;
@@ -74,6 +78,7 @@
 
  private:
   std::string server_id_;
+  std::vector<std::pair<int, int>> supported_card_bin_ranges_;
   std::vector<AutofillProfile> upload_details_addresses_;
   std::vector<AutofillProfile> upload_card_addresses_;
   int detected_values_;
diff --git a/components/autofill/core/browser/test_local_card_migration_manager.cc b/components/autofill/core/browser/test_local_card_migration_manager.cc
index 0438e64..b9808476 100644
--- a/components/autofill/core/browser/test_local_card_migration_manager.cc
+++ b/components/autofill/core/browser/test_local_card_migration_manager.cc
@@ -76,11 +76,13 @@
     bool is_from_settings_page,
     AutofillClient::PaymentsRpcResult result,
     const base::string16& context_token,
-    std::unique_ptr<base::Value> legal_message) {
+    std::unique_ptr<base::Value> legal_message,
+    std::vector<std::pair<int, int>> supported_bin_ranges) {
   if (result == AutofillClient::SUCCESS) {
     local_card_migration_was_triggered_ = true;
     LocalCardMigrationManager::OnDidGetUploadDetails(
-        is_from_settings_page, result, context_token, std::move(legal_message));
+        is_from_settings_page, result, context_token, std::move(legal_message),
+        supported_bin_ranges);
   }
 }
 
diff --git a/components/autofill/core/browser/test_local_card_migration_manager.h b/components/autofill/core/browser/test_local_card_migration_manager.h
index fe5e6f8a..722d366 100644
--- a/components/autofill/core/browser/test_local_card_migration_manager.h
+++ b/components/autofill/core/browser/test_local_card_migration_manager.h
@@ -5,7 +5,10 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_MANAGER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_MANAGER_H_
 
+#include <memory>
 #include <string>
+#include <utility>
+#include <vector>
 
 #include "components/autofill/core/browser/local_card_migration_manager.h"
 #include "components/autofill/core/browser/sync_utils.h"
@@ -60,7 +63,8 @@
       bool is_from_settings_page,
       AutofillClient::PaymentsRpcResult result,
       const base::string16& context_token,
-      std::unique_ptr<base::Value> legal_message) override;
+      std::unique_ptr<base::Value> legal_message,
+      std::vector<std::pair<int, int>> supported_bin_ranges) override;
 
   bool local_card_migration_was_triggered_ = false;
 
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 5bed4234..4783ffa 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -67,6 +67,10 @@
 const base::Feature kAutofillDeleteDisusedCreditCards{
     "AutofillDeleteDisusedCreditCards", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kAutofillDoNotUploadSaveUnsupportedCards{
+    "AutofillDoNotUploadSaveUnsupportedCards",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether the credit card downstream keyboard accessory shows
 // the Google Pay logo animation on iOS.
 const base::Feature kAutofillDownstreamUseGooglePayBrandingOniOS{
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index a7a832b..492f662a 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -33,6 +33,7 @@
 extern const base::Feature kAutofillCreditCardLocalCardMigration;
 extern const base::Feature kAutofillDeleteDisusedAddresses;
 extern const base::Feature kAutofillDeleteDisusedCreditCards;
+extern const base::Feature kAutofillDoNotUploadSaveUnsupportedCards;
 extern const base::Feature kAutofillDownstreamUseGooglePayBrandingOniOS;
 extern const base::Feature kAutofillDynamicForms;
 extern const base::Feature kAutofillEnableAccountWalletStorage;
diff --git a/components/autofill/ios/browser/resources/autofill_controller.js b/components/autofill/ios/browser/resources/autofill_controller.js
index ec5477b5..2172b64 100644
--- a/components/autofill/ios/browser/resources/autofill_controller.js
+++ b/components/autofill/ios/browser/resources/autofill_controller.js
@@ -256,7 +256,7 @@
   if (!__gCrWeb.autofill.styleInjected) {
     var style = document.createElement('style');
     style.textContent = '[chrome-autofilled] {' +
-        'background-color:#FAFFBD !important;' +
+        'background-color:#E8F0FE !important;' +
         'background-image:none !important;' +
         'color:#000000 !important;' +
         '}';
diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn
index 0bffb91..145f7d0f 100644
--- a/components/autofill_assistant/browser/BUILD.gn
+++ b/components/autofill_assistant/browser/BUILD.gn
@@ -23,6 +23,7 @@
   sources = [
     "metrics.h",
     "overlay_state.h",
+    "payment_request.h",
   ]
 }
 
diff --git a/components/autofill_assistant/browser/actions/get_payment_information_action.cc b/components/autofill_assistant/browser/actions/get_payment_information_action.cc
index 7b838dd1..dff48e8 100644
--- a/components/autofill_assistant/browser/actions/get_payment_information_action.cc
+++ b/components/autofill_assistant/browser/actions/get_payment_information_action.cc
@@ -119,7 +119,8 @@
     }
     processed_action_proto_->mutable_payment_details()
         ->set_is_terms_and_conditions_accepted(
-            payment_information->is_terms_and_conditions_accepted);
+            payment_information->terms_and_conditions ==
+            TermsAndConditionsState::ACCEPTED);
     processed_action_proto_->mutable_payment_details()->set_payer_email(
         payment_information->payer_email);
   }
diff --git a/components/autofill_assistant/browser/chip.h b/components/autofill_assistant/browser/chip.h
index 941b5a410..c030529 100644
--- a/components/autofill_assistant/browser/chip.h
+++ b/components/autofill_assistant/browser/chip.h
@@ -20,13 +20,16 @@
   Chip(Chip&&);
   Chip& operator=(Chip&&);
 
-  ChipType type;
+  ChipType type = UNKNOWN_CHIP_TYPE;
 
   // Localized string to display.
   std::string text;
 
   // Callback triggered when the chip is tapped.
   base::OnceClosure callback;
+
+  // Whether this chip is disabled.
+  bool disabled = false;
 };
 
 // Guarantees that the Chip.type of all chips is set to a sensible value.
diff --git a/components/autofill_assistant/browser/controller.cc b/components/autofill_assistant/browser/controller.cc
index b0df135..4b98e1f 100644
--- a/components/autofill_assistant/browser/controller.cc
+++ b/components/autofill_assistant/browser/controller.cc
@@ -210,8 +210,6 @@
     return;
   }
 
-  // If the button clicked is not the Cancel button, then we clear the
-  // current chips and run the callback.
   auto callback = std::move((*chips)[chip_index].callback);
   SetChips(nullptr);
   std::move(callback).Run();
@@ -600,14 +598,105 @@
   return payment_request_options_.get();
 }
 
-void Controller::SetPaymentInformation(
-    std::unique_ptr<PaymentInformation> payment_information) {
-  if (!payment_request_options_)
+void Controller::OnPaymentRequestContinueButtonClicked() {
+  if (!payment_request_options_ || !payment_request_info_)
     return;
 
   auto callback = std::move(payment_request_options_->callback);
+  auto payment_request_info = std::move(payment_request_info_);
+
+  // TODO(crbug.com/806868): succeed is currently always true, but we might want
+  // to set it to false and propagate the result to GetPaymentInformationAction
+  // when the user clicks "Cancel" during that action.
+  payment_request_info->succeed = true;
+
   SetPaymentRequestOptions(nullptr);
-  std::move(callback).Run(std::move(payment_information));
+  std::move(callback).Run(std::move(payment_request_info));
+}
+
+void Controller::SetShippingAddress(
+    std::unique_ptr<autofill::AutofillProfile> address) {
+  if (!payment_request_info_)
+    return;
+
+  payment_request_info_->shipping_address = std::move(address);
+  UpdatePaymentRequestActions();
+}
+
+void Controller::SetBillingAddress(
+    std::unique_ptr<autofill::AutofillProfile> address) {
+  if (!payment_request_info_)
+    return;
+
+  payment_request_info_->billing_address = std::move(address);
+  UpdatePaymentRequestActions();
+}
+
+void Controller::SetContactInfo(std::string name,
+                                std::string phone,
+                                std::string email) {
+  if (!payment_request_info_)
+    return;
+
+  payment_request_info_->payer_name = name;
+  payment_request_info_->payer_phone = phone;
+  payment_request_info_->payer_email = email;
+  UpdatePaymentRequestActions();
+}
+
+void Controller::SetCreditCard(std::unique_ptr<autofill::CreditCard> card) {
+  if (!payment_request_info_)
+    return;
+
+  payment_request_info_->card = std::move(card);
+  UpdatePaymentRequestActions();
+}
+
+void Controller::SetTermsAndConditions(
+    TermsAndConditionsState terms_and_conditions) {
+  if (!payment_request_info_)
+    return;
+
+  payment_request_info_->terms_and_conditions = terms_and_conditions;
+  UpdatePaymentRequestActions();
+}
+
+void Controller::UpdatePaymentRequestActions() {
+  // TODO(crbug.com/806868): This method uses #SetChips(), which means that
+  // updating the PR actions will also clear the suggestions. We should update
+  // the actions only if there are use cases of PR + suggestions.
+  if (!payment_request_options_ || !payment_request_info_) {
+    return;
+  }
+
+  bool contact_info_ok = (!payment_request_options_->request_payer_name ||
+                          !payment_request_info_->payer_name.empty()) &&
+                         (!payment_request_options_->request_payer_email ||
+                          !payment_request_info_->payer_email.empty()) &&
+                         (!payment_request_options_->request_payer_phone ||
+                          !payment_request_info_->payer_phone.empty());
+
+  bool shipping_address_ok = !payment_request_options_->request_shipping ||
+                             payment_request_info_->shipping_address;
+
+  bool terms_ok = payment_request_info_->terms_and_conditions != NOT_SELECTED;
+
+  bool continue_button_enabled = contact_info_ok && shipping_address_ok &&
+                                 payment_request_info_->card && terms_ok;
+
+  auto chips = std::make_unique<std::vector<Chip>>();
+  chips->emplace_back();
+  chips->back().text =
+      l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM);
+  chips->back().type = HIGHLIGHTED_ACTION;
+  chips->back().disabled = !continue_button_enabled;
+  if (continue_button_enabled) {
+    chips->back().callback =
+        base::BindOnce(&Controller::OnPaymentRequestContinueButtonClicked,
+                       weak_ptr_factory_.GetWeakPtr());
+  }
+
+  SetChips(std::move(chips));
 }
 
 void Controller::GetTouchableArea(std::vector<RectF>* area) const {
@@ -767,7 +856,12 @@
   if (payment_request_options_ == nullptr && options == nullptr)
     return;
 
+  if (options) {
+    payment_request_info_ = std::make_unique<PaymentInformation>();
+  }
+
   payment_request_options_ = std::move(options);
+  UpdatePaymentRequestActions();
   GetUiController()->OnPaymentRequestChanged(payment_request_options_.get());
 }
 
diff --git a/components/autofill_assistant/browser/controller.h b/components/autofill_assistant/browser/controller.h
index ba8e478..cbb55e9 100644
--- a/components/autofill_assistant/browser/controller.h
+++ b/components/autofill_assistant/browser/controller.h
@@ -111,8 +111,16 @@
   void SelectAction(int index) override;
   std::string GetDebugContext() override;
   const PaymentRequestOptions* GetPaymentRequestOptions() const override;
-  void SetPaymentInformation(
-      std::unique_ptr<PaymentInformation> payment_information) override;
+  void SetShippingAddress(
+      std::unique_ptr<autofill::AutofillProfile> address) override;
+  void SetBillingAddress(
+      std::unique_ptr<autofill::AutofillProfile> address) override;
+  void SetContactInfo(std::string name,
+                      std::string phone,
+                      std::string email) override;
+  void SetCreditCard(std::unique_ptr<autofill::CreditCard> card) override;
+  void SetTermsAndConditions(
+      TermsAndConditionsState terms_and_conditions) override;
   void GetTouchableArea(std::vector<RectF>* area) const override;
   void OnFatalError(const std::string& error_message,
                     Metrics::DropOutReason reason) override;
@@ -164,6 +172,9 @@
   // Called when a script is selected.
   void OnScriptSelected(const std::string& script_path);
 
+  void UpdatePaymentRequestActions();
+  void OnPaymentRequestContinueButtonClicked();
+
   // Overrides ScriptTracker::Listener:
   void OnNoRunnableScripts() override;
   void OnRunnableScriptsChanged(
@@ -258,6 +269,7 @@
   bool will_shutdown_ = false;
 
   std::unique_ptr<PaymentRequestOptions> payment_request_options_;
+  std::unique_ptr<PaymentInformation> payment_request_info_;
 
   // Tracks scripts and script execution. It's kept at the end, as it tend to
   // depend on everything the controller support, through script and script
diff --git a/components/autofill_assistant/browser/payment_request.h b/components/autofill_assistant/browser/payment_request.h
index 77fa420..db9faa4ed 100644
--- a/components/autofill_assistant/browser/payment_request.h
+++ b/components/autofill_assistant/browser/payment_request.h
@@ -18,6 +18,15 @@
 
 namespace autofill_assistant {
 
+// GENERATED_JAVA_ENUM_PACKAGE: (
+// org.chromium.chrome.browser.autofill_assistant.payment)
+// GENERATED_JAVA_CLASS_NAME_OVERRIDE: AssistantTermsAndConditionsState
+enum TermsAndConditionsState {
+  NOT_SELECTED = 0,
+  ACCEPTED = 1,
+  REQUIRES_REVIEW = 2,
+};
+
 // Struct for holding the payment information data.
 struct PaymentInformation {
   PaymentInformation();
@@ -30,7 +39,7 @@
   std::string payer_name;
   std::string payer_phone;
   std::string payer_email;
-  bool is_terms_and_conditions_accepted = false;
+  TermsAndConditionsState terms_and_conditions = NOT_SELECTED;
 };
 
 // Struct for holding the payment request options.
diff --git a/components/autofill_assistant/browser/ui_delegate.h b/components/autofill_assistant/browser/ui_delegate.h
index b9283a4..f9409c8 100644
--- a/components/autofill_assistant/browser/ui_delegate.h
+++ b/components/autofill_assistant/browser/ui_delegate.h
@@ -64,10 +64,26 @@
   // field contains a non-null options describing the request.
   virtual const PaymentRequestOptions* GetPaymentRequestOptions() const = 0;
 
-  // Sets payment information, in response to the current payment request
+  // Sets shipping address, in response to the current payment request options.
+  virtual void SetShippingAddress(
+      std::unique_ptr<autofill::AutofillProfile> address) = 0;
+
+  // Sets billing address, in response to the current payment request options.
+  virtual void SetBillingAddress(
+      std::unique_ptr<autofill::AutofillProfile> address) = 0;
+
+  // Sets contact info, in response to the current payment request options.
+  virtual void SetContactInfo(std::string name,
+                              std::string phone,
+                              std::string email) = 0;
+
+  // Sets credit card, in response to the current payment request options.
+  virtual void SetCreditCard(std::unique_ptr<autofill::CreditCard> card) = 0;
+
+  // Sets terms and conditions, in response to the current payment request
   // options.
-  virtual void SetPaymentInformation(
-      std::unique_ptr<PaymentInformation> payment_information) = 0;
+  virtual void SetTermsAndConditions(
+      TermsAndConditionsState terms_and_conditions) = 0;
 
   // Adds the rectangles that correspond to the current touchable area to the
   // given vector.
diff --git a/components/autofill_assistant_strings.grdp b/components/autofill_assistant_strings.grdp
index daadd1daff..52494ec 100644
--- a/components/autofill_assistant_strings.grdp
+++ b/components/autofill_assistant_strings.grdp
@@ -15,6 +15,9 @@
     <message name="IDS_AUTOFILL_ASSISTANT_LOADING" desc="Text label that is shown during the loading of the first page, right after being triggered.">
       Opening <ph name="SITE_NAME">$1<ex>google.com</ex></ph>…
     </message>
+    <message name="IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM" desc="Text on the payment request primary button to confirm payment information [CHAR-LIMIT=32]">
+      Continue
+    </message>
     <message name="IDS_AUTOFILL_ASSISTANT_DETAILS_DIFFER" desc="Shown as Status Message when details differ.">
       The screening is different from what you selected. Continue?
     </message>
diff --git a/components/component_updater/README.md b/components/component_updater/README.md
index bf3fc31..a72b22f 100644
--- a/components/component_updater/README.md
+++ b/components/component_updater/README.md
@@ -37,7 +37,7 @@
 ### Create a CRX Package Signing Key & Manifest (Non-Google)
 All components are delivered as CRX files (signed ZIP archives). You need to
 create a signing key. If you are a Googler, follow the instructions at
-http://go/newchromecomponents for maximum key security. Otherwise, you can
+http://go/newchromecomponent for maximum key security. Otherwise, you can
 create an RSA key pair using `openssl` or a similar tool.
 
 You will additionally need to create a manifest.json file. If nothing else, the
diff --git a/components/cronet/stale_host_resolver.cc b/components/cronet/stale_host_resolver.cc
index 303e7728..91b5724 100644
--- a/components/cronet/stale_host_resolver.cc
+++ b/components/cronet/stale_host_resolver.cc
@@ -17,6 +17,7 @@
 #include "base/values.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/net_errors.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/dns_util.h"
 #include "net/dns/host_resolver_source.h"
 #include "net/log/net_log_with_source.h"
@@ -422,7 +423,7 @@
       use_stale_on_name_not_resolved(false) {}
 
 StaleHostResolver::StaleHostResolver(
-    std::unique_ptr<net::HostResolverImpl> inner_resolver,
+    std::unique_ptr<net::ContextHostResolver> inner_resolver,
     const StaleOptions& stale_options)
     : inner_resolver_(std::move(inner_resolver)),
       options_(stale_options),
diff --git a/components/cronet/stale_host_resolver.h b/components/cronet/stale_host_resolver.h
index 3f82a9d..11834d9a 100644
--- a/components/cronet/stale_host_resolver.h
+++ b/components/cronet/stale_host_resolver.h
@@ -12,18 +12,21 @@
 #include "base/time/default_tick_clock.h"
 #include "net/base/completion_once_callback.h"
 #include "net/dns/host_resolver.h"
-#include "net/dns/host_resolver_impl.h"
 
 namespace base {
 class TickClock;
 }  // namespace base
 
+namespace net {
+class ContextHostResolver;
+}  // namespace net
+
 namespace cronet {
 namespace {
 class StaleHostResolverTest;
 }  // namespace
 
-// A HostResolver that wraps a HostResolverImpl and uses it to make requests,
+// A HostResolver that wraps a ContextHostResolver and uses it to make requests,
 // but "impatiently" returns stale data (if available and usable) after a delay,
 // to reduce DNS latency at the expense of accuracy.
 class StaleHostResolver : public net::HostResolver {
@@ -60,7 +63,7 @@
   // Creates a StaleHostResolver that uses |inner_resolver| for actual
   // resolution, but potentially returns stale data according to
   // |stale_options|.
-  StaleHostResolver(std::unique_ptr<net::HostResolverImpl> inner_resolver,
+  StaleHostResolver(std::unique_ptr<net::ContextHostResolver> inner_resolver,
                     const StaleOptions& stale_options);
 
   ~StaleHostResolver() override;
@@ -107,9 +110,9 @@
   // Set |tick_clock_| for testing. Must be set before issuing any requests.
   void SetTickClockForTesting(const base::TickClock* tick_clock);
 
-  // The underlying HostResolverImpl that will be used to make cache and network
-  // requests.
-  std::unique_ptr<net::HostResolverImpl> inner_resolver_;
+  // The underlying ContextHostResolver that will be used to make cache and
+  // network requests.
+  std::unique_ptr<net::ContextHostResolver> inner_resolver_;
 
   // Shared instance of tick clock, overridden for testing.
   const base::TickClock* tick_clock_ = base::DefaultTickClock::GetInstance();
diff --git a/components/cronet/stale_host_resolver_unittest.cc b/components/cronet/stale_host_resolver_unittest.cc
index faa2e152..b5a2543 100644
--- a/components/cronet/stale_host_resolver_unittest.cc
+++ b/components/cronet/stale_host_resolver_unittest.cc
@@ -30,6 +30,7 @@
 #include "net/base/net_errors.h"
 #include "net/base/network_change_notifier.h"
 #include "net/cert/cert_verifier.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_hosts.h"
 #include "net/dns/dns_test_util.h"
@@ -171,14 +172,15 @@
     mock_proc_ = new MockHostResolverProc(result);
   }
 
-  std::unique_ptr<net::HostResolverImpl> CreateMockInnerResolverWithDnsClient(
+  std::unique_ptr<net::ContextHostResolver>
+  CreateMockInnerResolverWithDnsClient(
       std::unique_ptr<net::DnsClient> dns_client) {
-    std::unique_ptr<net::HostResolverImpl> inner_resolver(
+    std::unique_ptr<net::ContextHostResolver> inner_resolver(
         net::HostResolver::CreateDefaultResolverImpl(nullptr));
 
-    net::HostResolverImpl::ProcTaskParams proc_params(mock_proc_.get(), 1u);
-    inner_resolver->set_proc_params_for_test(proc_params);
-    inner_resolver->SetDnsClient(std::move(dns_client));
+    net::ProcTaskParams proc_params(mock_proc_.get(), 1u);
+    inner_resolver->SetProcParamsForTesting(proc_params);
+    inner_resolver->SetDnsClientForTesting(std::move(dns_client));
     return inner_resolver;
   }
 
diff --git a/components/cronet/url_request_context_config.cc b/components/cronet/url_request_context_config.cc
index b02d87e..4b71d49 100644
--- a/components/cronet/url_request_context_config.cc
+++ b/components/cronet/url_request_context_config.cc
@@ -24,6 +24,7 @@
 #include "net/cert/ct_policy_status.h"
 #include "net/cert/do_nothing_ct_verifier.h"
 #include "net/cert/multi_threaded_cert_verifier.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/host_resolver.h"
 #include "net/dns/mapped_host_resolver.h"
 #include "net/http/http_network_session.h"
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
index 941b3c8..1aee8f7 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
@@ -534,6 +534,23 @@
     std::string encoded_config;
     base::Base64Encode(config_data, &encoded_config);
     config_storer_.Run(encoded_config);
+
+    // Record timing metrics on successful requests only.
+    const network::ResourceResponseHead* info = url_loader_->ResponseInfo();
+    base::TimeDelta http_request_rtt =
+        info->response_start - info->request_start;
+    UMA_HISTOGRAM_TIMES("DataReductionProxy.ConfigService.HttpRequestRTT",
+                        http_request_rtt);
+
+    if (info->load_timing.connect_timing.connect_end > base::TimeTicks() &&
+        info->load_timing.connect_timing.connect_start > base::TimeTicks()) {
+      base::TimeDelta connection_setup =
+          info->load_timing.connect_timing.connect_end -
+          info->load_timing.connect_timing.connect_start;
+      UMA_HISTOGRAM_TIMES(
+          "DataReductionProxy.ConfigService.ConnectionSetupTime",
+          connection_setup);
+    }
   } else {
     ++failed_attempts_before_success_;
   }
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc
index f73a903d..a04d6c5 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc
@@ -555,6 +555,8 @@
 // Tests that the config is read successfully on the first attempt.
 TEST_F(DataReductionProxyConfigServiceClientTest, RemoteConfigSuccess) {
   Init(true);
+  base::HistogramTester histogram_tester;
+
   AddMockSuccess();
   SetDataReductionProxyEnabled(true, true);
   EXPECT_TRUE(configurator()->GetProxyConfig().proxy_rules().empty());
@@ -562,6 +564,8 @@
   config_client()->RetrieveConfig();
   RunUntilIdle();
   VerifyRemoteSuccess(true);
+  histogram_tester.ExpectTotalCount(
+      "DataReductionProxy.ConfigService.HttpRequestRTT", 1);
   EXPECT_FALSE(configurator()->GetProxyConfig().proxy_rules().empty());
 #if defined(OS_ANDROID)
   EXPECT_FALSE(config_client()->foreground_fetch_pending());
@@ -650,6 +654,8 @@
   histogram_tester.ExpectUniqueSample(
       "DataReductionProxy.ConfigService.FetchFailedAttemptsBeforeSuccess", 1,
       1);
+  histogram_tester.ExpectTotalCount(
+      "DataReductionProxy.ConfigService.HttpRequestRTT", 1);
 }
 
 // Verifies that the config is fetched successfully after IP address changes.
diff --git a/components/download/internal/common/download_stats.cc b/components/download/internal/common/download_stats.cc
index 80a7b22..f9ec2f1d 100644
--- a/components/download/internal/common/download_stats.cc
+++ b/components/download/internal/common/download_stats.cc
@@ -401,43 +401,44 @@
     FILE_PATH_LITERAL(".settingcontent-ms"),
     FILE_PATH_LITERAL(".oxt"),  // 317
     FILE_PATH_LITERAL(".pyd"),
-    FILE_PATH_LITERAL(".pyo"),      // 319
-    FILE_PATH_LITERAL(".desktop"),  // 320
-    FILE_PATH_LITERAL(".cpi"),      // 321
-    FILE_PATH_LITERAL(".jpg"),      // 322
-    FILE_PATH_LITERAL(".jpeg"),     // 323
-    FILE_PATH_LITERAL(".mp3"),      // 324
-    FILE_PATH_LITERAL(".mp4"),      // 325
-    FILE_PATH_LITERAL(".png"),      // 326
-    FILE_PATH_LITERAL(".xls"),      // 327
-    FILE_PATH_LITERAL(".doc"),      // 328
-    FILE_PATH_LITERAL(".pptx"),     // 329
-    FILE_PATH_LITERAL(".csv"),      // 330
-    FILE_PATH_LITERAL(".ica"),      // 331
-    FILE_PATH_LITERAL(".ppt"),      // 332
-    FILE_PATH_LITERAL(".gif"),      // 333
-    FILE_PATH_LITERAL(".txt"),      // 334
-    FILE_PATH_LITERAL(".package"),  // 335
-    FILE_PATH_LITERAL(".tif"),      // 336
-    FILE_PATH_LITERAL(".rtf"),      // 337
-    FILE_PATH_LITERAL(".webp"),     // 338
-    FILE_PATH_LITERAL(".mkv"),      // 339
-    FILE_PATH_LITERAL(".wav"),      // 340
-    FILE_PATH_LITERAL(".mov"),      // 341
-    FILE_PATH_LITERAL(".dot"),      // 342
-    FILE_PATH_LITERAL(".dotx"),     // 343
-    FILE_PATH_LITERAL(".xlsb"),     // 344
-    FILE_PATH_LITERAL(".xlt"),      // 345
-    FILE_PATH_LITERAL(".xlm"),      // 346
-    FILE_PATH_LITERAL(".xldm"),     // 347
-    FILE_PATH_LITERAL(".xla"),      // 348
-    FILE_PATH_LITERAL(".xlam"),     // 349
-    FILE_PATH_LITERAL(".xll"),      // 350
-    FILE_PATH_LITERAL(".xlw"),      // 351
-    FILE_PATH_LITERAL(".pot"),      // 352
-    FILE_PATH_LITERAL(".potm"),     // 353
-    FILE_PATH_LITERAL(".ppsm"),     // 354
-    FILE_PATH_LITERAL(".pps"),      // 355
+    FILE_PATH_LITERAL(".pyo"),           // 319
+    FILE_PATH_LITERAL(".desktop"),       // 320
+    FILE_PATH_LITERAL(".cpi"),           // 321
+    FILE_PATH_LITERAL(".jpg"),           // 322
+    FILE_PATH_LITERAL(".jpeg"),          // 323
+    FILE_PATH_LITERAL(".mp3"),           // 324
+    FILE_PATH_LITERAL(".mp4"),           // 325
+    FILE_PATH_LITERAL(".png"),           // 326
+    FILE_PATH_LITERAL(".xls"),           // 327
+    FILE_PATH_LITERAL(".doc"),           // 328
+    FILE_PATH_LITERAL(".pptx"),          // 329
+    FILE_PATH_LITERAL(".csv"),           // 330
+    FILE_PATH_LITERAL(".ica"),           // 331
+    FILE_PATH_LITERAL(".ppt"),           // 332
+    FILE_PATH_LITERAL(".gif"),           // 333
+    FILE_PATH_LITERAL(".txt"),           // 334
+    FILE_PATH_LITERAL(".package"),       // 335
+    FILE_PATH_LITERAL(".tif"),           // 336
+    FILE_PATH_LITERAL(".rtf"),           // 337
+    FILE_PATH_LITERAL(".webp"),          // 338
+    FILE_PATH_LITERAL(".mkv"),           // 339
+    FILE_PATH_LITERAL(".wav"),           // 340
+    FILE_PATH_LITERAL(".mov"),           // 341
+    FILE_PATH_LITERAL(".dot"),           // 342
+    FILE_PATH_LITERAL(".dotx"),          // 343
+    FILE_PATH_LITERAL(".xlsb"),          // 344
+    FILE_PATH_LITERAL(".xlt"),           // 345
+    FILE_PATH_LITERAL(".xlm"),           // 346
+    FILE_PATH_LITERAL(".xldm"),          // 347
+    FILE_PATH_LITERAL(".xla"),           // 348
+    FILE_PATH_LITERAL(".xlam"),          // 349
+    FILE_PATH_LITERAL(".xll"),           // 350
+    FILE_PATH_LITERAL(".xlw"),           // 351
+    FILE_PATH_LITERAL(".pot"),           // 352
+    FILE_PATH_LITERAL(".potm"),          // 353
+    FILE_PATH_LITERAL(".ppsm"),          // 354
+    FILE_PATH_LITERAL(".pps"),           // 355
+    FILE_PATH_LITERAL(".mobileconfig"),  // 356
     // NOTE! When you add a type here, please add the UMA value as a comment.
     // These must all match DownloadItem.DangerousFileType in
     // enums.xml. From 263 onward, they should also match
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 184287f..cb691a5 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -542,10 +542,13 @@
     const gfx::Rect& window_bounds,
     int bounds_change) {
   // 1) Do no update the bounds unless we have geometry from client.
-  // 2) Do not update the bounds if window is minimized.
+  // 2) Do not update the bounds if window is minimized unless it
+  // exiting the minimzied state.
   // The bounds will be provided by client when unminimized.
   if (!geometry().IsEmpty() && !window_bounds.IsEmpty() &&
-      !widget_->IsMinimized() && bounds_changed_callback_) {
+      (!widget_->IsMinimized() ||
+       requested_state != ash::mojom::WindowStateType::MINIMIZED) &&
+      bounds_changed_callback_) {
     // Sends the client bounds, which matches the geometry
     // when frame is enabled.
     ash::NonClientFrameViewAsh* frame_view = GetFrameView();
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index 15b50bde..84e8fbc 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -1850,6 +1850,12 @@
                                      ash::mojom::WindowStateType::MINIMIZED, 0,
                                      gfx::Rect(0, 0, 100, 100), 0);
   ASSERT_EQ(1, bounds_change_count());
+
+  // Send bounds change when exiting minmized.
+  shell_surface->OnBoundsChangeEvent(ash::mojom::WindowStateType::MINIMIZED,
+                                     ash::mojom::WindowStateType::NORMAL, 0,
+                                     gfx::Rect(0, 0, 100, 100), 0);
+  ASSERT_EQ(2, bounds_change_count());
 }
 
 TEST_F(ClientControlledShellSurfaceTest, SetPipWindowBoundsAnimates) {
diff --git a/components/feed/content/feed_offline_host.cc b/components/feed/content/feed_offline_host.cc
index b47ebda..d1b0dfd 100644
--- a/components/feed/content/feed_offline_host.cc
+++ b/components/feed/content/feed_offline_host.cc
@@ -25,12 +25,6 @@
 
 namespace {
 
-// |url| is always set. Sometimes |original_url| is set. If |original_url| is
-// set it is returned by this method, otherwise fall back to |url|.
-const GURL& PreferOriginal(const OfflinePageItem& item) {
-  return item.original_url.is_empty() ? item.url : item.original_url;
-}
-
 // Aggregates multiple callbacks from OfflinePageModel, storing the offline url.
 // When all callbacks have been invoked, tracked by ref counting, then
 // |on_completeion_| is finally invoked, sending all results together.
@@ -255,7 +249,7 @@
 void FeedOfflineHost::OfflinePageAdded(OfflinePageModel* model,
                                        const OfflinePageItem& added_page) {
   DCHECK(!notify_status_change_.is_null());
-  const std::string& url = PreferOriginal(added_page).spec();
+  const std::string& url = added_page.GetOriginalUrl().spec();
   CacheOfflinePageUrlAndId(url, added_page.offline_id);
   notify_status_change_.Run(url, true);
 }
diff --git a/components/feed/content/feed_offline_host_unittest.cc b/components/feed/content/feed_offline_host_unittest.cc
index 6d03c13..d2e052f 100644
--- a/components/feed/content/feed_offline_host_unittest.cc
+++ b/components/feed/content/feed_offline_host_unittest.cc
@@ -57,7 +57,7 @@
                        std::string name_space) {
     OfflinePageItem item;
     item.url = GURL(url);
-    item.original_url = GURL(original_url);
+    item.original_url_if_different = GURL(original_url);
     item.offline_id = offline_id;
     item.creation_time = creation_time;
     item.client_id = offline_pages::ClientId(name_space, "");
@@ -373,7 +373,7 @@
 TEST_F(FeedOfflineHostTest, OfflinePageAdded) {
   OfflinePageItem added_page;
   added_page.url = GURL(kUrl1);
-  added_page.original_url = GURL(kUrl2);
+  added_page.original_url_if_different = GURL(kUrl2);
   added_page.offline_id = 4;
 
   host()->OfflinePageAdded(nullptr, added_page);
diff --git a/components/feed/core/feed_logging_metrics.cc b/components/feed/core/feed_logging_metrics.cc
index 1d9eaeec..29f2f72 100644
--- a/components/feed/core/feed_logging_metrics.cc
+++ b/components/feed/core/feed_logging_metrics.cc
@@ -28,7 +28,11 @@
 // identical bucket sizes and names with Zine is for comparing Feed with Zine
 // easily. After Zine is deprecated, we can change the values if we needed.
 
+// Constants used as max sample sizes for histograms.
+const int kMaxContentCount = 50;
+const int kMaxFailureCount = 10;
 const int kMaxSuggestionsTotal = 50;
+const int kMaxTokenCount = 10;
 
 // Keep in sync with MAX_SUGGESTIONS_PER_SECTION in NewTabPageUma.java.
 const int kMaxSuggestionsForArticle = 20;
@@ -335,6 +339,70 @@
   }
 }
 
+void FeedLoggingMetrics::OnInternalError(int internal_error) {
+  // TODO(https://crbug.com/935602): The max value here is fragile, figure out
+  // some way to test the @IntDef size.
+  UMA_HISTOGRAM_ENUMERATION("ContentSuggestions.Feed.InternalError",
+                            internal_error, 10);
+}
+
+void FeedLoggingMetrics::OnTokenCompleted(bool was_synthetic,
+                                          int content_count,
+                                          int token_count) {
+  if (was_synthetic) {
+    UMA_HISTOGRAM_EXACT_LINEAR(
+        "ContentSuggestions.Feed.TokenCompleted.ContentCount.Synthetic",
+        content_count, kMaxFailureCount);
+    UMA_HISTOGRAM_EXACT_LINEAR(
+        "ContentSuggestions.Feed.TokenCompleted.TokenCount.Synthetic",
+        token_count, kMaxTokenCount);
+  } else {
+    UMA_HISTOGRAM_EXACT_LINEAR(
+        "ContentSuggestions.Feed.TokenCompleted.ContentCount.Nonsynthetic",
+        content_count, kMaxContentCount);
+    UMA_HISTOGRAM_EXACT_LINEAR(
+        "ContentSuggestions.Feed.TokenCompleted.TokenCount.Nonsynthetic",
+        token_count, kMaxTokenCount);
+  }
+}
+
+void FeedLoggingMetrics::OnTokenFailedToComplete(bool was_synthetic,
+                                                 int failure_count) {
+  if (was_synthetic) {
+    UMA_HISTOGRAM_EXACT_LINEAR(
+        "ContentSuggestions.Feed.TokenFailedToCompleted.Synthetic",
+        failure_count, kMaxFailureCount);
+  } else {
+    UMA_HISTOGRAM_EXACT_LINEAR(
+        "ContentSuggestions.Feed.TokenFailedToCompleted.Nonsynthetic",
+        failure_count, kMaxFailureCount);
+  }
+}
+
+void FeedLoggingMetrics::OnServerRequest(int request_reason) {
+  // TODO(https://crbug.com/935602): The max value here is fragile, figure out
+  // some way to test the @IntDef size.
+  UMA_HISTOGRAM_ENUMERATION("ContentSuggestions.Feed.ServerRequest.Reason",
+                            request_reason, 8);
+}
+
+void FeedLoggingMetrics::OnZeroStateShown(int zero_state_show_reason) {
+  // TODO(https://crbug.com/935602): The max value here is fragile, figure out
+  // some way to test the @IntDef size.
+  UMA_HISTOGRAM_ENUMERATION("ContentSuggestions.Feed.ZeroStateShown.Reason",
+                            zero_state_show_reason, 3);
+}
+
+void FeedLoggingMetrics::OnZeroStateRefreshCompleted(int new_content_count,
+                                                     int new_token_count) {
+  UMA_HISTOGRAM_EXACT_LINEAR(
+      "ContentSuggestions.Feed.ZeroStateRefreshCompleted.ContentCount",
+      new_content_count, kMaxContentCount);
+  UMA_HISTOGRAM_EXACT_LINEAR(
+      "ContentSuggestions.Feed.ZeroStateRefreshCompleted.TokenCount",
+      new_token_count, kMaxTokenCount);
+}
+
 void FeedLoggingMetrics::ReportScrolledAfterOpen() {
   base::RecordAction(base::UserMetricsAction("Suggestions.ScrolledAfterOpen"));
 }
diff --git a/components/feed/core/feed_logging_metrics.h b/components/feed/core/feed_logging_metrics.h
index 975d9212..2b1984cc 100644
--- a/components/feed/core/feed_logging_metrics.h
+++ b/components/feed/core/feed_logging_metrics.h
@@ -89,6 +89,18 @@
 
   void OnPietFrameRenderingEvent(std::vector<int> piet_error_codes);
 
+  void OnInternalError(int internal_error);
+
+  void OnTokenCompleted(bool was_synthetic, int content_count, int token_count);
+
+  void OnTokenFailedToComplete(bool was_synthetic, int failure_count);
+
+  void OnServerRequest(int request_reason);
+
+  void OnZeroStateShown(int zero_state_show_reason);
+
+  void OnZeroStateRefreshCompleted(int new_content_count, int new_token_count);
+
   void ReportScrolledAfterOpen();
 
  private:
diff --git a/components/feed/core/feed_scheduler_host.h b/components/feed/core/feed_scheduler_host.h
index eb43b69..d2455de 100644
--- a/components/feed/core/feed_scheduler_host.h
+++ b/components/feed/core/feed_scheduler_host.h
@@ -226,7 +226,7 @@
       throttlers_;
 
   // Status of the last fetch for debugging.
-  int last_fetch_status_;
+  int last_fetch_status_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(FeedSchedulerHost);
 };
diff --git a/components/gwp_asan/client/guarded_page_allocator.cc b/components/gwp_asan/client/guarded_page_allocator.cc
index 13fc2bf..483c425 100644
--- a/components/gwp_asan/client/guarded_page_allocator.cc
+++ b/components/gwp_asan/client/guarded_page_allocator.cc
@@ -76,8 +76,8 @@
                         std::next(free_slot_ring_buffer_.begin(), total_pages));
   }
 
-  slots_ = std::make_unique<AllocatorState::SlotMetadata[]>(total_pages);
-  state_.slot_metadata = reinterpret_cast<uintptr_t>(slots_.get());
+  metadata_ = std::make_unique<AllocatorState::SlotMetadata[]>(total_pages);
+  state_.metadata_addr = reinterpret_cast<uintptr_t>(metadata_.get());
 }
 
 GuardedPageAllocator::~GuardedPageAllocator() {
@@ -128,13 +128,13 @@
 
   // Check for a call to free() with an incorrect pointer (e.g. the pointer does
   // not match the allocated pointer.)
-  if (addr != slots_[slot].alloc_ptr) {
+  if (addr != metadata_[slot].alloc_ptr) {
     state_.free_invalid_address = addr;
     __builtin_trap();
   }
 
   // Check for double free.
-  if (slots_[slot].deallocation_occurred.exchange(true)) {
+  if (metadata_[slot].deallocation_occurred.exchange(true)) {
     state_.double_free_address = addr;
     // TODO(https://crbug.com/925447): The other thread may not be done writing
     // a stack trace so we could spin here until it's read; however, it's also
@@ -156,8 +156,8 @@
   CHECK(PointerIsMine(ptr));
   const uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
   size_t slot = state_.AddrToSlot(state_.GetPageAddr(addr));
-  DCHECK_EQ(addr, slots_[slot].alloc_ptr);
-  return slots_[slot].alloc_size;
+  DCHECK_EQ(addr, metadata_[slot].alloc_ptr);
+  return metadata_[slot].alloc_size;
 }
 
 size_t GuardedPageAllocator::RegionSize() const {
@@ -194,34 +194,35 @@
 void GuardedPageAllocator::RecordAllocationMetadata(size_t slot,
                                                     size_t size,
                                                     void* ptr) {
-  slots_[slot].alloc_size = size;
-  slots_[slot].alloc_ptr = reinterpret_cast<uintptr_t>(ptr);
+  metadata_[slot].alloc_size = size;
+  metadata_[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 = ReportTid();
-  slots_[slot].alloc.trace_collected = true;
+  metadata_[slot].alloc.trace_len =
+      Pack(reinterpret_cast<uintptr_t*>(trace), len,
+           metadata_[slot].alloc.packed_trace,
+           sizeof(metadata_[slot].alloc.packed_trace));
+  metadata_[slot].alloc.tid = ReportTid();
+  metadata_[slot].alloc.trace_collected = true;
 
-  slots_[slot].dealloc.tid = base::kInvalidThreadId;
-  slots_[slot].dealloc.trace_len = 0;
-  slots_[slot].dealloc.trace_collected = false;
-  slots_[slot].deallocation_occurred = false;
+  metadata_[slot].dealloc.tid = base::kInvalidThreadId;
+  metadata_[slot].dealloc.trace_len = 0;
+  metadata_[slot].dealloc.trace_collected = false;
+  metadata_[slot].deallocation_occurred = false;
 }
 
 void GuardedPageAllocator::RecordDeallocationMetadata(size_t slot) {
   void* trace[AllocatorState::kMaxStackFrames];
   size_t len =
       base::debug::CollectStackTrace(trace, AllocatorState::kMaxStackFrames);
-  slots_[slot].dealloc.trace_len =
+  metadata_[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 = ReportTid();
-  slots_[slot].dealloc.trace_collected = true;
+           metadata_[slot].dealloc.packed_trace,
+           sizeof(metadata_[slot].dealloc.packed_trace));
+  metadata_[slot].dealloc.tid = ReportTid();
+  metadata_[slot].dealloc.trace_collected = true;
 }
 
 uintptr_t GuardedPageAllocator::GetCrashKeyAddress() const {
diff --git a/components/gwp_asan/client/guarded_page_allocator.h b/components/gwp_asan/client/guarded_page_allocator.h
index a4ace98..4ca13eb8 100644
--- a/components/gwp_asan/client/guarded_page_allocator.h
+++ b/components/gwp_asan/client/guarded_page_allocator.h
@@ -124,7 +124,7 @@
 
   // We dynamically allocate the SlotMetadata array to avoid allocating
   // extraneous memory for when total_pages < kGpaMaxPages.
-  std::unique_ptr<AllocatorState::SlotMetadata[]> slots_;
+  std::unique_ptr<AllocatorState::SlotMetadata[]> metadata_;
 
   // Required for a singleton to access the constructor.
   friend base::NoDestructor<GuardedPageAllocator>;
diff --git a/components/gwp_asan/client/gwp_asan.cc b/components/gwp_asan/client/gwp_asan.cc
index e5eb6e2..1f64ea3 100644
--- a/components/gwp_asan/client/gwp_asan.cc
+++ b/components/gwp_asan/client/gwp_asan.cc
@@ -23,26 +23,35 @@
 namespace internal {
 namespace {
 
+constexpr int kDefaultMaxAllocations = 7;
+constexpr int kDefaultTotalPages = 30;
+constexpr int kDefaultAllocationSamplingFrequency = 1000;
+constexpr double kDefaultProcessSamplingProbability = 1.0;
+constexpr int kDefaultIncreasedMemoryMultiplier = 4;
+
 const base::Feature kGwpAsan{"GwpAsanMalloc",
                              base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::FeatureParam<int> kMaxAllocationsParam{&kGwpAsan, "MaxAllocations",
-                                                   7};
+                                                   kDefaultMaxAllocations};
 
-const base::FeatureParam<int> kTotalPagesParam{&kGwpAsan, "TotalPages", 30};
+const base::FeatureParam<int> kTotalPagesParam{&kGwpAsan, "TotalPages",
+                                               kDefaultTotalPages};
 
 const base::FeatureParam<int> kAllocationSamplingParam{
-    &kGwpAsan, "AllocationSamplingFrequency", 1000};
+    &kGwpAsan, "AllocationSamplingFrequency",
+    kDefaultAllocationSamplingFrequency};
 
 const base::FeatureParam<double> kProcessSamplingParam{
-    &kGwpAsan, "ProcessSamplingProbability", 1.0};
+    &kGwpAsan, "ProcessSamplingProbability",
+    kDefaultProcessSamplingProbability};
 
 // The multiplier to increase MaxAllocations/TotalPages in scenarios where we
 // want to perform additional testing (e.g. on canary/dev builds or in the
 // browser process.) The multiplier increase is cumulative when multiple
 // conditions apply.
 const base::FeatureParam<int> kIncreasedMemoryMultiplierParam{
-    &kGwpAsan, "IncreasedMemoryMultiplier", 4};
+    &kGwpAsan, "IncreasedMemoryMultiplier", kDefaultIncreasedMemoryMultiplier};
 
 bool EnableForMalloc(bool is_canary_dev, bool is_browser_process) {
   if (!base::FeatureList::IsEnabled(kGwpAsan))
diff --git a/components/gwp_asan/common/allocator_state.cc b/components/gwp_asan/common/allocator_state.cc
index 8aa839f..937b1bb 100644
--- a/components/gwp_asan/common/allocator_state.cc
+++ b/components/gwp_asan/common/allocator_state.cc
@@ -31,7 +31,7 @@
   if (slot_idx >= kGpaMaxPages)
     return GetMetadataReturnType::kErrorBadSlot;
 
-  *slot_address = slot_metadata + (slot_idx * sizeof(SlotMetadata));
+  *slot_address = metadata_addr + (slot_idx * sizeof(SlotMetadata));
   return GetMetadataReturnType::kGwpAsanCrash;
 }
 
@@ -53,7 +53,7 @@
       pages_end_addr - pages_base_addr != page_size * (total_pages * 2 + 1))
     return false;
 
-  if (!slot_metadata)
+  if (!metadata_addr)
     return false;
 
   return true;
@@ -91,12 +91,12 @@
 AllocatorState::ErrorType AllocatorState::GetErrorType(uintptr_t addr,
                                                        bool allocated,
                                                        bool deallocated) const {
+  if (free_invalid_address)
+    return ErrorType::kFreeInvalidAddress;
   if (!allocated)
     return ErrorType::kUnknown;
   if (double_free_address)
     return ErrorType::kDoubleFree;
-  if (free_invalid_address)
-    return ErrorType::kFreeInvalidAddress;
   if (deallocated)
     return ErrorType::kUseAfterFree;
   if (addr < first_page_addr)
diff --git a/components/gwp_asan/common/allocator_state.h b/components/gwp_asan/common/allocator_state.h
index 3bc19eb8..74de37d 100644
--- a/components/gwp_asan/common/allocator_state.h
+++ b/components/gwp_asan/common/allocator_state.h
@@ -140,7 +140,7 @@
   // Pointer to an array of metadata about every allocation, including its size,
   // offset, and pointers to the allocation/deallocation stack traces (if
   // present.)
-  uintptr_t slot_metadata = 0;
+  uintptr_t metadata_addr = 0;
 
   // Set to the address of a double freed allocation if a double free occurred.
   uintptr_t double_free_address = 0;
diff --git a/components/gwp_asan/common/allocator_state_unittest.cc b/components/gwp_asan/common/allocator_state_unittest.cc
index 1d029115f..44cff48e 100644
--- a/components/gwp_asan/common/allocator_state_unittest.cc
+++ b/components/gwp_asan/common/allocator_state_unittest.cc
@@ -33,7 +33,7 @@
         end_addr_offset + base + page_size * (total_pages * 2 + 1);
 
     // An invalid address, but it's never dereferenced in AllocatorState.
-    state_.slot_metadata = 0x1234;
+    state_.metadata_addr = 0x1234;
   }
 
   AllocatorState state_;
@@ -110,5 +110,16 @@
             AllocatorState::ErrorType::kBufferOverflow);
 }
 
+// Correctly handle the edge case when a free() occurs on a page that has never
+// been allocated.
+TEST_F(AllocatorStateTest, GetErrorTypeFreeInvalidAddressEdgeCase) {
+  InitializeState(base::GetPageSize(), kGpaMaxPages);
+  EXPECT_TRUE(state_.IsValid());
+
+  state_.free_invalid_address = state_.first_page_addr;
+  EXPECT_EQ(state_.GetErrorType(state_.first_page_addr, false, false),
+            AllocatorState::ErrorType::kFreeInvalidAddress);
+}
+
 }  // namespace internal
 }  // namespace gwp_asan
diff --git a/components/ntp_snippets/remote/prefetched_pages_tracker_impl.cc b/components/ntp_snippets/remote/prefetched_pages_tracker_impl.cc
index d70dc34..f9b5807 100644
--- a/components/ntp_snippets/remote/prefetched_pages_tracker_impl.cc
+++ b/components/ntp_snippets/remote/prefetched_pages_tracker_impl.cc
@@ -19,12 +19,6 @@
          offline_pages::kSuggestedArticlesNamespace;
 }
 
-const GURL& GetOfflinePageUrl(const OfflinePageItem& offline_page_item) {
-  return offline_page_item.original_url != GURL()
-             ? offline_page_item.original_url
-             : offline_page_item.url;
-}
-
 }  // namespace
 
 PrefetchedPagesTrackerImpl::PrefetchedPagesTrackerImpl(
@@ -119,7 +113,7 @@
 
 void PrefetchedPagesTrackerImpl::AddOfflinePage(
     const OfflinePageItem& offline_page_item) {
-  const GURL& url = GetOfflinePageUrl(offline_page_item);
+  const GURL& url = offline_page_item.GetOriginalUrl();
   DCHECK(prefetched_url_counts_.count(url) == 0 ||
          prefetched_url_counts_.find(url)->second > 0);
   ++prefetched_url_counts_[url];
diff --git a/components/ntp_snippets/remote/prefetched_pages_tracker_impl_unittest.cc b/components/ntp_snippets/remote/prefetched_pages_tracker_impl_unittest.cc
index d76ea9d..f58eaea 100644
--- a/components/ntp_snippets/remote/prefetched_pages_tracker_impl_unittest.cc
+++ b/components/ntp_snippets/remote/prefetched_pages_tracker_impl_unittest.cc
@@ -140,7 +140,7 @@
       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
   tracker.OfflinePageDeleted(offline_pages::OfflinePageModel::DeletedPageInfo(
       item.offline_id, kSystemDownloadId, item.client_id,
-      /*request_origin=*/"", item.original_url));
+      /*request_origin=*/"", item.original_url_if_different));
   EXPECT_FALSE(
       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
 }
@@ -163,7 +163,8 @@
   tracker.OfflinePageDeleted(offline_pages::OfflinePageModel::DeletedPageInfo(
       manually_downloaded_item.offline_id, kSystemDownloadId,
       manually_downloaded_item.client_id,
-      /*request_origin=*/"", manually_downloaded_item.original_url));
+      /*request_origin=*/"",
+      manually_downloaded_item.original_url_if_different));
   EXPECT_TRUE(
       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
 }
@@ -274,7 +275,7 @@
 
   tracker.OfflinePageDeleted(offline_pages::OfflinePageModel::DeletedPageInfo(
       first_item.offline_id, kSystemDownloadId, first_item.client_id,
-      /*request_origin=*/"", first_item.original_url));
+      /*request_origin=*/"", first_item.original_url_if_different));
 
   // Only one offline page (out of two) has been removed, the remaining one
   // should be reported here.
@@ -299,14 +300,14 @@
 
   tracker.OfflinePageDeleted(offline_pages::OfflinePageModel::DeletedPageInfo(
       first_item.offline_id, kSystemDownloadId, first_item.client_id,
-      /*request_origin=*/"", first_item.original_url));
+      /*request_origin=*/"", first_item.original_url_if_different));
 
   ASSERT_TRUE(
       tracker.PrefetchedOfflinePageExists(GURL("http://prefetched.com")));
 
   tracker.OfflinePageDeleted(offline_pages::OfflinePageModel::DeletedPageInfo(
       second_item.offline_id, kSystemDownloadId, second_item.client_id,
-      /*request_origin=*/"", second_item.original_url));
+      /*request_origin=*/"", second_item.original_url_if_different));
 
   // All offline pages have been removed, their absence should be reported here.
   EXPECT_FALSE(
diff --git a/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc b/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc
index c8dc249..4de15b8 100644
--- a/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc
+++ b/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc
@@ -158,7 +158,7 @@
       if (page.second.client_id.id == guid) {
         DeletedPageInfo info(page.second.offline_id, kSystemDownloadId,
                              page.second.client_id, page.second.request_origin,
-                             page.second.original_url);
+                             page.second.original_url_if_different);
         observer_->OfflinePageDeleted(info);
         pages.erase(page.first);
         return;
diff --git a/components/offline_pages/core/downloads/offline_item_conversions.cc b/components/offline_pages/core/downloads/offline_item_conversions.cc
index 33ebda4..7223f834 100644
--- a/components/offline_pages/core/downloads/offline_item_conversions.cc
+++ b/components/offline_pages/core/downloads/offline_item_conversions.cc
@@ -57,7 +57,7 @@
   item.file_path = page.file_path;
   item.mime_type = GetMimeType();
   item.page_url = page.url;
-  item.original_url = page.original_url;
+  item.original_url = page.original_url_if_different;
   item.progress.value = 100;
   item.progress.max = 100;
   item.progress.unit = OfflineItemProgressUnit::PERCENTAGE;
diff --git a/components/offline_pages/core/downloads/offline_item_conversions_unittest.cc b/components/offline_pages/core/downloads/offline_item_conversions_unittest.cc
index ed972349..3aa47c8e 100644
--- a/components/offline_pages/core/downloads/offline_item_conversions_unittest.cc
+++ b/components/offline_pages/core/downloads/offline_item_conversions_unittest.cc
@@ -39,7 +39,7 @@
 
   OfflinePageItem offline_page_item(kTestUrl, offline_id, client_id, file_path,
                                     file_size, creation_time);
-  offline_page_item.original_url = kTestOriginalUrl;
+  offline_page_item.original_url_if_different = kTestOriginalUrl;
   offline_page_item.title = base::UTF8ToUTF16(title);
   offline_page_item.last_access_time = last_access_time;
   offline_page_item.file_missing_time = base::Time::Now();
diff --git a/components/offline_pages/core/model/add_page_task.cc b/components/offline_pages/core/model/add_page_task.cc
index f8f601a0..cd4e73e 100644
--- a/components/offline_pages/core/model/add_page_task.cc
+++ b/components/offline_pages/core/model/add_page_task.cc
@@ -57,7 +57,7 @@
   statement.BindInt64(7, store_utils::ToDatabaseTime(item.last_access_time));
   statement.BindInt(8, item.access_count);
   statement.BindString16(9, item.title);
-  statement.BindString(10, item.original_url.spec());
+  statement.BindString(10, item.original_url_if_different.spec());
   statement.BindString(11, item.request_origin);
   statement.BindInt64(12, item.system_download_id);
   statement.BindInt64(13, store_utils::ToDatabaseTime(item.file_missing_time));
diff --git a/components/offline_pages/core/model/add_page_task_unittest.cc b/components/offline_pages/core/model/add_page_task_unittest.cc
index bca504c..dde4408 100644
--- a/components/offline_pages/core/model/add_page_task_unittest.cc
+++ b/components/offline_pages/core/model/add_page_task_unittest.cc
@@ -95,7 +95,7 @@
                        kTestFilePath, kTestFileSize, base::Time::Now(),
                        kTestOrigin);
   page.title = kTestTitle;
-  page.original_url = kTestUrl2;
+  page.original_url_if_different = kTestUrl2;
   page.system_download_id = kTestDownloadId;
   page.file_missing_time = base::Time::Now();
   page.digest = kTestDigest;
diff --git a/components/offline_pages/core/model/cleanup_thumbnails_task_unittest.cc b/components/offline_pages/core/model/cleanup_thumbnails_task_unittest.cc
index 68c618c1..2c0a49f45 100644
--- a/components/offline_pages/core/model/cleanup_thumbnails_task_unittest.cc
+++ b/components/offline_pages/core/model/cleanup_thumbnails_task_unittest.cc
@@ -42,7 +42,8 @@
 TEST_F(CleanupThumbnailsTaskTest, DbConnectionIsNull) {
   base::MockCallback<StoreThumbnailTask::CompleteCallback> callback;
   EXPECT_CALL(callback, Run(false)).Times(1);
-  store()->SetStateForTesting(StoreState::FAILED_LOADING, true);
+  store()->SetInitializationStatusForTesting(
+      SqlStoreBase::InitializationStatus::kFailure, true);
   RunTask(std::make_unique<CleanupThumbnailsTask>(
       store(), store_utils::FromDatabaseTime(1000), callback.Get()));
 }
diff --git a/components/offline_pages/core/model/delete_page_task.cc b/components/offline_pages/core/model/delete_page_task.cc
index 4784e4d..159bed38 100644
--- a/components/offline_pages/core/model/delete_page_task.cc
+++ b/components/offline_pages/core/model/delete_page_task.cc
@@ -4,6 +4,8 @@
 
 #include "components/offline_pages/core/model/delete_page_task.h"
 
+#include <iterator>
+
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -41,14 +43,18 @@
 // in the SQL query and the result of it can be simply fetched by calling
 // statement.Column*(INFO_WRAPPER_COUNT), as it's the last column. For example,
 // please take a look at GetCachedDeletedPageInfoWrappersByUrlPredicateSync.
-#define INFO_WRAPPER_FIELDS                                                  \
-  "offline_id, system_download_id, client_namespace, client_id, file_path, " \
-  "request_origin, access_count, creation_time, online_url"
-#define INFO_WRAPPER_FIELD_COUNT 8
+#define INFO_WRAPPER_FIELDS                                             \
+  "offline_id,system_download_id,client_namespace,client_id,file_path," \
+  "request_origin,access_count,creation_time,online_url,original_url"
+#define INFO_WRAPPER_FIELD_COUNT 10
 
 struct DeletedPageInfoWrapper {
-  DeletedPageInfoWrapper();
-  DeletedPageInfoWrapper(const DeletedPageInfoWrapper& other);
+  DeletedPageInfoWrapper() = default;
+  // Move-only to avoid copies.
+  DeletedPageInfoWrapper(const DeletedPageInfoWrapper& other) = delete;
+  DeletedPageInfoWrapper(DeletedPageInfoWrapper&& other) = default;
+  DeletedPageInfoWrapper& operator=(DeletedPageInfoWrapper&& other) = default;
+
   int64_t offline_id;
   int64_t system_download_id;
   ClientId client_id;
@@ -58,8 +64,22 @@
   int access_count;
   base::Time creation_time;
   GURL url;
+  GURL original_url_if_different;
 };
 
+// Consumes |wrapper| and returns an |OfflinePageModel::DeletePageInfo|.
+OfflinePageModel::DeletedPageInfo ExtractPageInfo(
+    DeletedPageInfoWrapper&& wrapper) {
+  OfflinePageModel::DeletedPageInfo info;
+  info.offline_id = wrapper.offline_id;
+  info.system_download_id = wrapper.system_download_id;
+  info.client_id = std::move(wrapper.client_id);
+  info.request_origin = std::move(wrapper.request_origin);
+  info.url = std::move(wrapper.url);
+  info.original_url_if_different = std::move(wrapper.original_url_if_different);
+  return info;
+}
+
 DeletedPageInfoWrapper CreateInfoWrapper(const sql::Statement& statement) {
   DeletedPageInfoWrapper info_wrapper;
   info_wrapper.offline_id = statement.ColumnInt64(0);
@@ -73,13 +93,10 @@
   info_wrapper.creation_time =
       store_utils::FromDatabaseTime(statement.ColumnInt64(7));
   info_wrapper.url = GURL(statement.ColumnString(8));
+  info_wrapper.original_url_if_different = GURL(statement.ColumnString(9));
   return info_wrapper;
 }
 
-DeletedPageInfoWrapper::DeletedPageInfoWrapper() = default;
-DeletedPageInfoWrapper::DeletedPageInfoWrapper(
-    const DeletedPageInfoWrapper& other) = default;
-
 void ReportDeletePageHistograms(
     const std::vector<DeletedPageInfoWrapper>& info_wrappers) {
   const int max_minutes = base::TimeDelta::FromDays(365).InMinutes();
@@ -121,7 +138,7 @@
 // file is deleted successfully, there will be no such issue.
 DeletePageTaskResult DeletePagesByDeletedPageInfoWrappersSync(
     sql::Database* db,
-    const std::vector<DeletedPageInfoWrapper>& info_wrappers) {
+    std::vector<DeletedPageInfoWrapper> info_wrappers) {
   std::vector<OfflinePageModel::DeletedPageInfo> deleted_page_infos;
 
   // If there's no page to delete, return an empty list with SUCCESS.
@@ -131,14 +148,11 @@
   ReportDeletePageHistograms(info_wrappers);
 
   bool any_archive_deleted = false;
-  for (const auto& info_wrapper : info_wrappers) {
+  for (auto& info_wrapper : info_wrappers) {
     if (DeleteArchiveSync(info_wrapper.file_path)) {
       any_archive_deleted = true;
       if (DeletePageEntryByOfflineIdSync(db, info_wrapper.offline_id)) {
-        deleted_page_infos.emplace_back(
-            info_wrapper.offline_id, info_wrapper.system_download_id,
-            info_wrapper.client_id, info_wrapper.request_origin,
-            info_wrapper.url);
+        deleted_page_infos.push_back(ExtractPageInfo(std::move(info_wrapper)));
       }
     }
   }
@@ -185,10 +199,10 @@
   for (int64_t offline_id : offline_ids) {
     DeletedPageInfoWrapper info;
     if (GetDeletedPageInfoWrapperByOfflineIdSync(db, offline_id, &info))
-      infos.push_back(info);
+      infos.push_back(std::move(info));
   }
   DeletePageTaskResult result =
-      DeletePagesByDeletedPageInfoWrappersSync(db, infos);
+      DeletePagesByDeletedPageInfoWrappersSync(db, std::move(infos));
 
   if (!transaction.Commit())
     return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
@@ -231,11 +245,12 @@
   for (ClientId client_id : client_ids) {
     std::vector<DeletedPageInfoWrapper> temp_infos =
         GetDeletedPageInfoWrappersByClientIdSync(db, client_id);
-    infos.insert(infos.end(), temp_infos.begin(), temp_infos.end());
+    infos.insert(infos.end(), std::make_move_iterator(temp_infos.begin()),
+                 std::make_move_iterator(temp_infos.end()));
   }
 
   DeletePageTaskResult result =
-      DeletePagesByDeletedPageInfoWrappersSync(db, infos);
+      DeletePagesByDeletedPageInfoWrappersSync(db, std::move(infos));
 
   if (!transaction.Commit())
     return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
@@ -282,11 +297,12 @@
     std::vector<DeletedPageInfoWrapper> temp_infos =
         GetDeletedPageInfoWrappersByClientIdAndOriginSync(db, client_id,
                                                           origin);
-    infos.insert(infos.end(), temp_infos.begin(), temp_infos.end());
+    infos.insert(infos.end(), std::make_move_iterator(temp_infos.begin()),
+                 std::make_move_iterator(temp_infos.end()));
   }
 
   DeletePageTaskResult result =
-      DeletePagesByDeletedPageInfoWrappersSync(db, infos);
+      DeletePagesByDeletedPageInfoWrappersSync(db, std::move(infos));
 
   if (!transaction.Commit())
     return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
@@ -315,7 +331,7 @@
               GURL(statement.ColumnString(INFO_WRAPPER_FIELD_COUNT))))
         continue;
       DeletedPageInfoWrapper info_wrapper = CreateInfoWrapper(statement);
-      info_wrappers.push_back(info_wrapper);
+      info_wrappers.push_back(std::move(info_wrapper));
     }
   }
   return info_wrappers;
@@ -331,11 +347,11 @@
   if (!transaction.Begin())
     return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
 
-  const std::vector<DeletedPageInfoWrapper>& infos =
+  std::vector<DeletedPageInfoWrapper> infos =
       GetCachedDeletedPageInfoWrappersByUrlPredicateSync(db, namespaces,
                                                          predicate);
   DeletePageTaskResult result =
-      DeletePagesByDeletedPageInfoWrappersSync(db, infos);
+      DeletePagesByDeletedPageInfoWrappersSync(db, std::move(infos));
 
   if (!transaction.Commit())
     return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
@@ -363,7 +379,7 @@
 
   while (statement.Step()) {
     DeletedPageInfoWrapper info_wrapper = CreateInfoWrapper(statement);
-    info_wrappers.push_back(info_wrapper);
+    info_wrappers.push_back(std::move(info_wrapper));
   }
 
   // Since the page information was selected by ascending order of last access
@@ -389,11 +405,11 @@
   if (!transaction.Begin())
     return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
 
-  const std::vector<DeletedPageInfoWrapper>& infos =
+  std::vector<DeletedPageInfoWrapper> infos =
       GetDeletedPageInfoWrappersForPageLimitDeletion(db, url, name_space,
                                                      limit);
   DeletePageTaskResult result =
-      DeletePagesByDeletedPageInfoWrappersSync(db, infos);
+      DeletePagesByDeletedPageInfoWrappersSync(db, std::move(infos));
 
   if (!transaction.Commit())
     return DeletePageTaskResult(DeletePageResult::STORE_FAILURE, {});
diff --git a/components/offline_pages/core/model/delete_page_task_unittest.cc b/components/offline_pages/core/model/delete_page_task_unittest.cc
index 90bc107..09a787a3 100644
--- a/components/offline_pages/core/model/delete_page_task_unittest.cc
+++ b/components/offline_pages/core/model/delete_page_task_unittest.cc
@@ -35,6 +35,10 @@
 const int64_t kTestOfflineIdNoMatch = 20170905LL;
 const ClientId kTestClientIdNoMatch(kTestNamespace, "20170905");
 
+GURL OriginalUrl() {
+  return GURL("http://original.com");
+}
+
 }  // namespace
 
 class DeletePageTaskTest : public ModelTaskTestBase {
@@ -93,6 +97,36 @@
   return !base::PathExists(page.file_path) && !stored_page;
 }
 
+// Delete a page and verify all the information in DeletedPageInfo is accurate.
+TEST_F(DeletePageTaskTest, DeletedPageInfoIsPopulated) {
+  generator()->SetNamespace(kTestNamespace);
+  OfflinePageItem page1 = generator()->CreateItemWithTempFile();
+  page1.url = kTestUrl1;
+  page1.original_url_if_different = OriginalUrl();
+  page1.request_origin = "test-origin";
+  page1.system_download_id = 1234;
+  store_test_util()->InsertItem(page1);
+
+  // Run DeletePageTask for to delete the page.
+  std::vector<int64_t> offline_ids({page1.offline_id});
+  auto task = DeletePageTask::CreateTaskMatchingOfflineIds(
+      store(), delete_page_callback(), offline_ids);
+  RunTask(std::move(task));
+
+  EXPECT_EQ(DeletePageResult::SUCCESS, last_delete_page_result());
+  EXPECT_EQ(1UL, last_deleted_page_infos().size());
+
+  // Verify original_url is returned via DeletedPageInfo.
+  const DeletedPageInfo& info = last_deleted_page_infos()[0];
+  EXPECT_EQ(page1.url, info.url);
+  EXPECT_EQ(page1.client_id, info.client_id);
+  EXPECT_EQ(page1.request_origin, info.request_origin);
+  EXPECT_EQ(page1.system_download_id, info.system_download_id);
+  EXPECT_EQ(page1.offline_id, info.offline_id);
+  EXPECT_EQ(OriginalUrl(), info.original_url_if_different);
+  EXPECT_EQ(OriginalUrl(), info.GetOriginalUrl());
+}
+
 TEST_F(DeletePageTaskTest, DeletePageByOfflineId) {
   // Add 3 pages and try to delete 2 of them using offline id.
   generator()->SetNamespace(kTestNamespace);
diff --git a/components/offline_pages/core/model/get_pages_task.cc b/components/offline_pages/core/model/get_pages_task.cc
index 0d47018ba..61325c5 100644
--- a/components/offline_pages/core/model/get_pages_task.cc
+++ b/components/offline_pages/core/model/get_pages_task.cc
@@ -54,7 +54,7 @@
   item.last_access_time = last_access_time;
   item.access_count = access_count;
   item.title = title;
-  item.original_url = original_url;
+  item.original_url_if_different = original_url;
   item.request_origin = request_origin;
   item.system_download_id = system_download_id;
   item.file_missing_time = file_missing_time;
@@ -187,8 +187,8 @@
   while (statement.Step()) {
     OfflinePageItem temp_item = MakeOfflinePageItem(&statement);
     if (temp_item.url.ReplaceComponents(remove_fragment) == url_to_match ||
-        temp_item.original_url.ReplaceComponents(remove_fragment) ==
-            url_to_match) {
+        temp_item.original_url_if_different.ReplaceComponents(
+            remove_fragment) == url_to_match) {
       result.pages.push_back(temp_item);
     }
   }
diff --git a/components/offline_pages/core/model/get_thumbnail_task_unittest.cc b/components/offline_pages/core/model/get_thumbnail_task_unittest.cc
index 448f7226..a546dff8 100644
--- a/components/offline_pages/core/model/get_thumbnail_task_unittest.cc
+++ b/components/offline_pages/core/model/get_thumbnail_task_unittest.cc
@@ -91,7 +91,8 @@
         called = true;
         EXPECT_FALSE(result);
       });
-  store()->SetStateForTesting(StoreState::FAILED_LOADING, true);
+  store()->SetInitializationStatusForTesting(
+      SqlStoreBase::InitializationStatus::kFailure, true);
 
   RunTask(std::make_unique<GetThumbnailTask>(store(), 1, std::move(callback)));
 
diff --git a/components/offline_pages/core/model/has_thumbnail_task_unittest.cc b/components/offline_pages/core/model/has_thumbnail_task_unittest.cc
index f7fe69a..0684225ba 100644
--- a/components/offline_pages/core/model/has_thumbnail_task_unittest.cc
+++ b/components/offline_pages/core/model/has_thumbnail_task_unittest.cc
@@ -50,7 +50,8 @@
   RunTask(
       std::make_unique<StoreThumbnailTask>(store(), thumb, base::DoNothing()));
 
-  store()->SetStateForTesting(StoreState::FAILED_LOADING, true);
+  store()->SetInitializationStatusForTesting(
+      SqlStoreBase::InitializationStatus::kFailure, true);
   base::MockCallback<ThumbnailExistsCallback> exists_callback;
   EXPECT_CALL(exists_callback, Run(false));
   RunTask(std::make_unique<HasThumbnailTask>(store(), thumb.offline_id,
diff --git a/components/offline_pages/core/model/offline_page_item_generator.cc b/components/offline_pages/core/model/offline_page_item_generator.cc
index aee905b..619b017b 100644
--- a/components/offline_pages/core/model/offline_page_item_generator.cc
+++ b/components/offline_pages/core/model/offline_page_item_generator.cc
@@ -26,7 +26,7 @@
     item.client_id.id = id_;
   item.request_origin = request_origin_;
   item.url = url_;
-  item.original_url = original_url_;
+  item.original_url_if_different = original_url_;
   item.file_size = file_size_;
   item.creation_time = creation_time_;
   item.last_access_time = last_access_time_;
diff --git a/components/offline_pages/core/model/offline_page_model_taskified.cc b/components/offline_pages/core/model/offline_page_model_taskified.cc
index 2e3f29f..9e04bba3 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.cc
+++ b/components/offline_pages/core/model/offline_page_model_taskified.cc
@@ -517,7 +517,7 @@
   // the completion of certain action, i.e., authentication, in the middle.
   if (skip_clearing_original_url_for_testing_ ||
       save_page_params.original_url != offline_page.url) {
-    offline_page.original_url = save_page_params.original_url;
+    offline_page.original_url_if_different = save_page_params.original_url;
   }
 
   if (policy_controller_->IsUserRequestedDownload(
diff --git a/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc b/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc
index 3f5faa14..a927485 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc
+++ b/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc
@@ -378,7 +378,7 @@
   EXPECT_EQ(0, saved_page_ptr->access_count);
   EXPECT_EQ(0, saved_page_ptr->flags);
   EXPECT_EQ(kTestTitle, saved_page_ptr->title);
-  EXPECT_EQ(kTestUrl2, saved_page_ptr->original_url);
+  EXPECT_EQ(kTestUrl2, saved_page_ptr->original_url_if_different);
   EXPECT_EQ("", saved_page_ptr->request_origin);
   EXPECT_EQ(kTestDigest, saved_page_ptr->digest);
 
@@ -439,7 +439,7 @@
 
   EXPECT_EQ(kTestUrl, saved_page_ptr->url);
   // The original URL should be empty.
-  EXPECT_TRUE(saved_page_ptr->original_url.is_empty());
+  EXPECT_TRUE(saved_page_ptr->original_url_if_different.is_empty());
 
   histogram_tester()->ExpectUniqueSample(
       "OfflinePages.SavePageCount",
@@ -479,7 +479,7 @@
   EXPECT_EQ(0, saved_page_ptr->access_count);
   EXPECT_EQ(0, saved_page_ptr->flags);
   EXPECT_EQ(kTestTitle, saved_page_ptr->title);
-  EXPECT_EQ(kTestUrl2, saved_page_ptr->original_url);
+  EXPECT_EQ(kTestUrl2, saved_page_ptr->original_url_if_different);
   EXPECT_EQ(kTestRequestOrigin, saved_page_ptr->request_origin);
 
   histogram_tester()->ExpectUniqueSample(
diff --git a/components/offline_pages/core/model/store_thumbnail_task_unittest.cc b/components/offline_pages/core/model/store_thumbnail_task_unittest.cc
index 7dc95df..41527e0a 100644
--- a/components/offline_pages/core/model/store_thumbnail_task_unittest.cc
+++ b/components/offline_pages/core/model/store_thumbnail_task_unittest.cc
@@ -69,7 +69,8 @@
 }
 
 TEST_F(StoreThumbnailTaskTest, DbConnectionIsNull) {
-  store()->SetStateForTesting(StoreState::FAILED_LOADING, true);
+  store()->SetInitializationStatusForTesting(
+      SqlStoreBase::InitializationStatus::kFailure, true);
   OfflinePageThumbnail thumb;
   thumb.offline_id = 1;
   thumb.expiration = store_utils::FromDatabaseTime(1234);
diff --git a/components/offline_pages/core/offline_page_item.cc b/components/offline_pages/core/offline_page_item.cc
index e32196f1..09cad91e 100644
--- a/components/offline_pages/core/offline_page_item.cc
+++ b/components/offline_pages/core/offline_page_item.cc
@@ -77,7 +77,8 @@
          file_size == other.file_size && creation_time == other.creation_time &&
          last_access_time == other.last_access_time &&
          access_count == other.access_count && title == other.title &&
-         flags == other.flags && original_url == other.original_url &&
+         flags == other.flags &&
+         original_url_if_different == other.original_url_if_different &&
          request_origin == other.request_origin &&
          system_download_id == other.system_download_id &&
          file_missing_time == other.file_missing_time &&
diff --git a/components/offline_pages/core/offline_page_item.h b/components/offline_pages/core/offline_page_item.h
index dcf7c02..7107e8e 100644
--- a/components/offline_pages/core/offline_page_item.h
+++ b/components/offline_pages/core/offline_page_item.h
@@ -49,6 +49,11 @@
   bool operator==(const OfflinePageItem& other) const;
   bool operator<(const OfflinePageItem& other) const;
 
+  const GURL& GetOriginalUrl() const {
+    return original_url_if_different.is_empty() ? url
+                                                : original_url_if_different;
+  }
+
   // The URL of the page. This is the last committed URL. In the case that
   // redirects occur, access |original_url| for the original URL.
   GURL url;
@@ -76,7 +81,7 @@
   Flags flags;
   // The original URL of the page if not empty. Otherwise, this is set to empty
   // and |url| should be accessed instead.
-  GURL original_url;
+  GURL original_url_if_different;
   // The app, if any, that the item was saved on behalf of.
   // Empty string implies Chrome.
   std::string request_origin;
diff --git a/components/offline_pages/core/offline_page_metadata_store.cc b/components/offline_pages/core/offline_page_metadata_store.cc
index 708b6f52..94b3720 100644
--- a/components/offline_pages/core/offline_page_metadata_store.cc
+++ b/components/offline_pages/core/offline_page_metadata_store.cc
@@ -4,6 +4,8 @@
 
 #include "components/offline_pages/core/offline_page_metadata_store.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -310,177 +312,100 @@
   }
 }
 
-bool PrepareDirectory(const base::FilePath& path) {
-  base::File::Error error = base::File::FILE_OK;
-  if (!base::DirectoryExists(path.DirName())) {
-    if (!base::CreateDirectoryAndGetError(path.DirName(), &error)) {
-      LOG(ERROR) << "Failed to create offline pages db directory: "
-                 << base::File::ErrorToString(error);
-      return false;
-    }
+StoreState InitializationStatusToStoreState(
+    SqlStoreBase::InitializationStatus status) {
+  switch (status) {
+    case SqlStoreBase::InitializationStatus::kNotInitialized:
+      return StoreState::NOT_LOADED;
+    case SqlStoreBase::InitializationStatus::kInProgress:
+      return StoreState::INITIALIZING;
+    case SqlStoreBase::InitializationStatus::kSuccess:
+      return StoreState::LOADED;
+    case SqlStoreBase::InitializationStatus::kFailure:
+      return StoreState::FAILED_LOADING;
   }
-
-  UMA_HISTOGRAM_ENUMERATION("OfflinePages.SQLStorage.CreateDirectoryResult",
-                            -error, -base::File::FILE_ERROR_MAX);
-
-  return true;
-}
-
-bool InitDatabase(sql::Database* db,
-                  const base::FilePath& path,
-                  bool in_memory) {
-  db->set_page_size(4096);
-  db->set_cache_size(500);
-  db->set_histogram_tag("OfflinePageMetadata");
-  db->set_exclusive_locking();
-
-  if (!in_memory && !PrepareDirectory(path))
-    return false;
-
-  bool open_db_result = false;
-  if (in_memory)
-    open_db_result = db->OpenInMemory();
-  else
-    open_db_result = db->Open(path);
-
-  if (!open_db_result) {
-    LOG(ERROR) << "Failed to open database, in memory: " << in_memory;
-    return false;
-  }
-  db->Preload();
-
-  return CreateSchema(db);
-}
-
-void CloseDatabaseSync(
-    sql::Database* db,
-    scoped_refptr<base::SingleThreadTaskRunner> callback_runner,
-    base::OnceClosure callback) {
-  if (db)
-    db->Close();
-  callback_runner->PostTask(FROM_HERE, std::move(callback));
 }
 
 }  // anonymous namespace
 
-// static
-constexpr base::TimeDelta OfflinePageMetadataStore::kClosingDelay;
-
 OfflinePageMetadataStore::OfflinePageMetadataStore(
     scoped_refptr<base::SequencedTaskRunner> background_task_runner)
-    : background_task_runner_(std::move(background_task_runner)),
-      in_memory_(true),
-      state_(StoreState::NOT_LOADED),
-      weak_ptr_factory_(this),
-      closing_weak_ptr_factory_(this) {}
+    : SqlStoreBase("OfflinePageMetadata",
+                   std::move(background_task_runner),
+                   base::FilePath()) {}
 
 OfflinePageMetadataStore::OfflinePageMetadataStore(
     scoped_refptr<base::SequencedTaskRunner> background_task_runner,
     const base::FilePath& path)
-    : background_task_runner_(std::move(background_task_runner)),
-      in_memory_(false),
-      db_file_path_(path.AppendASCII("OfflinePages.db")),
-      state_(StoreState::NOT_LOADED),
-      weak_ptr_factory_(this),
-      closing_weak_ptr_factory_(this) {}
+    : SqlStoreBase("OfflinePageMetadata",
+                   std::move(background_task_runner),
+                   path.AppendASCII("OfflinePages.db")) {}
 
-OfflinePageMetadataStore::~OfflinePageMetadataStore() {
-  if (db_.get() &&
-      !background_task_runner_->DeleteSoon(FROM_HERE, db_.release())) {
-    DLOG(WARNING) << "SQL database will not be deleted.";
-  }
+OfflinePageMetadataStore::~OfflinePageMetadataStore() = default;
+
+base::OnceCallback<bool(sql::Database* db)>
+OfflinePageMetadataStore::GetSchemaInitializationFunction() {
+  return base::BindOnce(&CreateSchema);
 }
 
 StoreState OfflinePageMetadataStore::GetStateForTesting() const {
-  return state_;
+  return InitializationStatusToStoreState(initialization_status_for_testing());
 }
 
-void OfflinePageMetadataStore::SetStateForTesting(StoreState state,
-                                                  bool reset_db) {
-  state_ = state;
-  if (reset_db)
-    db_.reset(nullptr);
-}
-
-void OfflinePageMetadataStore::InitializeInternal(
-    base::OnceClosure pending_command) {
+void OfflinePageMetadataStore::OnOpenStart(base::TimeTicks last_closing_time) {
   TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Metadata Store", this, "is reopen",
-                           !last_closing_time_.is_null());
-  DCHECK_EQ(state_, StoreState::NOT_LOADED);
-
-  if (!last_closing_time_.is_null()) {
+                           !last_closing_time.is_null());
+  if (!last_closing_time.is_null()) {
     ReportStoreEvent(OfflinePagesStoreEvent::kReopened);
     UMA_HISTOGRAM_CUSTOM_TIMES("OfflinePages.SQLStorage.TimeFromCloseToOpen",
-                               base::TimeTicks::Now() - last_closing_time_,
+                               base::TimeTicks::Now() - last_closing_time,
                                base::TimeDelta::FromMilliseconds(10),
                                base::TimeDelta::FromMinutes(10),
                                50 /* buckets */);
   } else {
     ReportStoreEvent(OfflinePagesStoreEvent::kOpenedFirstTime);
   }
-
-  state_ = StoreState::INITIALIZING;
-  db_.reset(new sql::Database());
-  base::PostTaskAndReplyWithResult(
-      background_task_runner_.get(), FROM_HERE,
-      base::BindOnce(&InitDatabase, db_.get(), db_file_path_, in_memory_),
-      base::BindOnce(&OfflinePageMetadataStore::OnInitializeInternalDone,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     std::move(pending_command)));
 }
 
-void OfflinePageMetadataStore::OnInitializeInternalDone(
-    base::OnceClosure pending_command,
-    bool success) {
+void OfflinePageMetadataStore::OnOpenDone(bool success) {
   TRACE_EVENT_ASYNC_STEP_PAST1("offline_pages", "Metadata Store", this,
                                "Initializing", "succeeded", success);
-  // TODO(fgorski): DCHECK initialization is in progress, once we have a
-  // relevant value for the store state.
-  if (success) {
-    state_ = StoreState::LOADED;
-  } else {
-    state_ = StoreState::FAILED_LOADING;
-    db_.reset();
+  if (!success) {
     TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store", this);
   }
-
-  CHECK(!pending_command.is_null());
-  std::move(pending_command).Run();
-
-  // Execute other pending commands.
-  for (auto command_iter = pending_commands_.begin();
-       command_iter != pending_commands_.end();) {
-    std::move(*command_iter++).Run();
-  }
-
-  pending_commands_.clear();
-
-  if (state_ == StoreState::FAILED_LOADING)
-    state_ = StoreState::NOT_LOADED;
 }
 
-void OfflinePageMetadataStore::CloseInternal() {
-  if (state_ != StoreState::LOADED) {
+void OfflinePageMetadataStore::OnTaskBegin(bool is_initialized) {
+  TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Metadata Store: task execution",
+                           this, "is store loaded", is_initialized);
+}
+
+void OfflinePageMetadataStore::OnTaskRunComplete() {
+  // Note: the time recorded for this trace step will include thread hop wait
+  // times to the background thread and back.
+  TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages",
+                               "Metadata Store: task execution", this, "Task");
+}
+
+void OfflinePageMetadataStore::OnTaskReturnComplete() {
+  TRACE_EVENT_ASYNC_STEP_PAST0(
+      "offline_pages", "Metadata Store: task execution", this, "Callback");
+  TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store: task execution",
+                         this);
+}
+
+void OfflinePageMetadataStore::OnCloseStart(
+    InitializationStatus status_before_close) {
+  if (status_before_close != InitializationStatus::kSuccess) {
     ReportStoreEvent(OfflinePagesStoreEvent::kCloseSkipped);
     return;
   }
   TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Metadata Store", this, "Open");
 
-  last_closing_time_ = base::TimeTicks::Now();
   ReportStoreEvent(OfflinePagesStoreEvent::kClosed);
-
-  state_ = StoreState::NOT_LOADED;
-  background_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &CloseDatabaseSync, db_.get(), base::ThreadTaskRunnerHandle::Get(),
-          base::BindOnce(&OfflinePageMetadataStore::CloseInternalDone,
-                         weak_ptr_factory_.GetWeakPtr(), std::move(db_))));
 }
 
-void OfflinePageMetadataStore::CloseInternalDone(
-    std::unique_ptr<sql::Database> db) {
-  db.reset();
+void OfflinePageMetadataStore::OnCloseComplete() {
   TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Metadata Store", this,
                                "Closing");
   TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store", this);
diff --git a/components/offline_pages/core/offline_page_metadata_store.h b/components/offline_pages/core/offline_page_metadata_store.h
index e1df0b8..ebfb0d4f 100644
--- a/components/offline_pages/core/offline_page_metadata_store.h
+++ b/components/offline_pages/core/offline_page_metadata_store.h
@@ -19,6 +19,7 @@
 #include "base/trace_event/trace_event.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_store_types.h"
+#include "components/offline_pages/task/sql_store_base.h"
 
 namespace base {
 class SequencedTaskRunner;
@@ -64,32 +65,8 @@
 //   |UpgradeFrom54|.
 // * Upgrade should use |UpgradeWithQuery| and simply specify SQL command to
 //   move data from old table (prefixed by temp_) to the new one.
-class OfflinePageMetadataStore {
+class OfflinePageMetadataStore : public SqlStoreBase {
  public:
-  // This enum is used in an UMA histogram. Hence the entries here shouldn't
-  // be deleted or re-ordered and new ones should be added to the end.
-  enum LoadStatus {
-    LOAD_SUCCEEDED,
-    STORE_INIT_FAILED,
-    STORE_LOAD_FAILED,
-    DATA_PARSING_FAILED,
-
-    // NOTE: always keep this entry at the end.
-    LOAD_STATUS_COUNT
-  };
-
-  typedef base::RepeatingCallback<void(bool /* success */)> ResetCallback;
-
-  // Definition of the callback that is going to run the core of the command in
-  // the |Execute| method.
-  template <typename T>
-  using RunCallback = base::OnceCallback<T(sql::Database*)>;
-
-  // Definition of the callback used to pass the result back to the caller of
-  // |Execute| method.
-  template <typename T>
-  using ResultCallback = base::OnceCallback<void(T)>;
-
   // This is the first version saved in the meta table, which was introduced in
   // the store in M65. It is set once a legacy upgrade is run successfully for
   // the last time in |UpgradeFromLegacyVersion|.
@@ -97,11 +74,6 @@
   static const int kCurrentVersion = 3;
   static const int kCompatibleVersion = kFirstPostLegacyVersion;
 
-  // Defines inactivity time of DB after which it is going to be closed.
-  // TODO(fgorski): Derive appropriate value in a scientific way.
-  static constexpr base::TimeDelta kClosingDelay =
-      base::TimeDelta::FromSeconds(20);
-
   // TODO(fgorski): Move to private and expose ForTest factory.
   // Applies in PrefetchStore as well.
   // Creates the store in memory. Should only be used for testing.
@@ -113,126 +85,22 @@
       scoped_refptr<base::SequencedTaskRunner> background_task_runner,
       const base::FilePath& database_dir);
 
-  ~OfflinePageMetadataStore();
-
-  // Executes a |run_callback| on SQL store on the blocking thread, and posts
-  // its result back to calling thread through |result_callback|.
-  // Calling |Execute| when store is NOT_LOADED will cause the store
-  // initialization to start.
-  // Store state needs to be LOADED for |run_callback| to run.
-  // If initialization fails, |result_callback| is invoked with |default_value|.
-  template <typename T>
-  void Execute(RunCallback<T> run_callback,
-               ResultCallback<T> result_callback,
-               T default_value) {
-    // TODO(fgorski): Add a proper state indicating in progress initialization
-    // and CHECK that state.
-
-    if (state_ == StoreState::NOT_LOADED) {
-      InitializeInternal(base::BindOnce(
-          &OfflinePageMetadataStore::Execute<T>, weak_ptr_factory_.GetWeakPtr(),
-          std::move(run_callback), std::move(result_callback),
-          std::move(default_value)));
-      return;
-    }
-
-    TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Metadata Store: task execution",
-                             this, "is store loaded",
-                             state_ == StoreState::LOADED);
-    // This if allows to run commands later, after store was given a chance to
-    // initialize. They would be failing immediately otherwise.
-    if (state_ == StoreState::INITIALIZING) {
-      pending_commands_.push_back(base::BindOnce(
-          &OfflinePageMetadataStore::Execute<T>, weak_ptr_factory_.GetWeakPtr(),
-          std::move(run_callback), std::move(result_callback),
-          std::move(default_value)));
-      TRACE_EVENT_ASYNC_END1("offline_pages", "Metadata Store: task execution",
-                             this, "postponed", true);
-      return;
-    }
-
-    // Ensure that any scheduled close operations are canceled.
-    closing_weak_ptr_factory_.InvalidateWeakPtrs();
-
-    sql::Database* db = state_ == StoreState::LOADED ? db_.get() : nullptr;
-    if (!db) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(result_callback), std::move(default_value)));
-      return;
-    }
-    base::PostTaskAndReplyWithResult(
-        background_task_runner_.get(), FROM_HERE,
-        base::BindOnce(std::move(run_callback), db),
-        base::BindOnce(&OfflinePageMetadataStore::RescheduleClosing<T>,
-                       weak_ptr_factory_.GetWeakPtr(),
-                       std::move(result_callback)));
-  }
+  ~OfflinePageMetadataStore() override;
 
   // Helper function used to force incorrect state for testing purposes.
-  void SetStateForTesting(StoreState state, bool reset_db);
   StoreState GetStateForTesting() const;
 
- private:
-  // Initializes database and calls callback.
-  void InitializeInternal(base::OnceClosure pending_command);
-
-  // Used to conclude opening/resetting DB connection.
-  void OnInitializeInternalDone(base::OnceClosure pending_command,
-                                bool success);
-
-  // Reschedules the closing with a delay. Ensures that |result_callback| is
-  // called.
-  template <typename T>
-  void RescheduleClosing(ResultCallback<T> result_callback, T result) {
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&OfflinePageMetadataStore::CloseInternal,
-                       closing_weak_ptr_factory_.GetWeakPtr()),
-        kClosingDelay);
-
-    // Note: the time recorded for this trace step will include thread hop wait
-    // times to the background thread and back.
-    TRACE_EVENT_ASYNC_STEP_PAST0(
-        "offline_pages", "Metadata Store: task execution", this, "Task");
-    std::move(result_callback).Run(std::move(result));
-    TRACE_EVENT_ASYNC_STEP_PAST0(
-        "offline_pages", "Metadata Store: task execution", this, "Callback");
-    TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store: task execution",
-                           this);
-  }
-
-  // Internal function initiating the closing.
-  void CloseInternal();
-
-  // Completes the closing. Main purpose is to destroy the db pointer.
-  void CloseInternalDone(std::unique_ptr<sql::Database> db);
-
-  // Background thread where all SQL access should be run.
-  scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
-
-  // Whether store is opened in memory (for testing) or using a file.
-  bool in_memory_;
-
-  // Path to the database on disk.
-  base::FilePath db_file_path_;
-
-  // Database connection.
-  std::unique_ptr<sql::Database> db_;
-
-  // State of the store.
-  StoreState state_;
-
-  // Pending commands.
-  std::vector<base::OnceClosure> pending_commands_;
-
-  // Time of the last time the store was closed. Kept for metrics reporting.
-  base::TimeTicks last_closing_time_;
-
-  base::WeakPtrFactory<OfflinePageMetadataStore> weak_ptr_factory_;
-  base::WeakPtrFactory<OfflinePageMetadataStore> closing_weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(OfflinePageMetadataStore);
+ protected:
+  // SqlStoreBase:
+  base::OnceCallback<bool(sql::Database* db)> GetSchemaInitializationFunction()
+      override;
+  void OnOpenStart(base::TimeTicks last_open_time) override;
+  void OnOpenDone(bool success) override;
+  void OnTaskBegin(bool is_initialized) override;
+  void OnTaskRunComplete() override;
+  void OnTaskReturnComplete() override;
+  void OnCloseStart(InitializationStatus status_before_close) override;
+  void OnCloseComplete() override;
 };
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/offline_page_metadata_store_unittest.cc b/components/offline_pages/core/offline_page_metadata_store_unittest.cc
index 4eea0ca..6c3d4dbea 100644
--- a/components/offline_pages/core/offline_page_metadata_store_unittest.cc
+++ b/components/offline_pages/core/offline_page_metadata_store_unittest.cc
@@ -34,6 +34,7 @@
 namespace offline_pages {
 
 namespace {
+using InitializationStatus = SqlStoreBase::InitializationStatus;
 
 #define OFFLINE_PAGES_TABLE_V1 "offlinepages_v1"
 
@@ -390,7 +391,7 @@
   statement.BindString(7, item.url.spec());
   statement.BindString(8, store_utils::ToDatabaseFilePath(item.file_path));
   statement.BindString16(9, item.title);
-  statement.BindString(10, item.original_url.spec());
+  statement.BindString(10, item.original_url_if_different.spec());
   statement.BindString(11, item.request_origin);
   statement.BindInt64(12, item.system_download_id);
   statement.BindInt(13, store_utils::ToDatabaseTime(item.file_missing_time));
@@ -508,7 +509,7 @@
   item.last_access_time = last_access_time;
   item.access_count = access_count;
   item.title = title;
-  item.original_url = original_url;
+  item.original_url_if_different = original_url;
   item.request_origin = request_origin;
   item.system_download_id = system_download_id;
   item.file_missing_time = file_missing_time;
@@ -576,7 +577,7 @@
     OfflinePageItem offline_page(GURL(kTestURL), 1234LL, kTestClientId1,
                                  base::FilePath(kFilePath), kFileSize);
     offline_page.title = base::UTF8ToUTF16("a title");
-    offline_page.original_url = GURL(kOriginalTestURL);
+    offline_page.original_url_if_different = GURL(kOriginalTestURL);
     offline_page.system_download_id = kTestSystemDownloadId;
     offline_page.digest = kTestDigest;
 
@@ -712,7 +713,7 @@
                           store_utils::ToDatabaseTime(item.last_access_time));
       statement.BindInt(8, item.access_count);
       statement.BindString16(9, item.title);
-      statement.BindString(10, item.original_url.spec());
+      statement.BindString(10, item.original_url_if_different.spec());
       statement.BindString(11, item.request_origin);
       statement.BindInt64(12, item.system_download_id);
       statement.BindInt64(13,
@@ -789,28 +790,28 @@
 
   // Because execute method is self-healing this part of the test expects a
   // positive results now.
-  store->SetStateForTesting(StoreState::NOT_LOADED, false);
+  store->SetInitializationStatusForTesting(
+      InitializationStatus::kNotInitialized, false);
   EXPECT_EQ(0UL, GetOfflinePages(store.get()).size());
-  EXPECT_EQ(StoreState::LOADED, store->GetStateForTesting());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store->initialization_status_for_testing());
 
-  store->SetStateForTesting(StoreState::FAILED_LOADING, false);
+  store->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                           false);
   EXPECT_EQ(0UL, GetOfflinePages(store.get()).size());
-  EXPECT_EQ(StoreState::FAILED_LOADING, store->GetStateForTesting());
+  EXPECT_EQ(InitializationStatus::kFailure,
+            store->initialization_status_for_testing());
 
-  store->SetStateForTesting(StoreState::FAILED_RESET, false);
-  EXPECT_EQ(0UL, GetOfflinePages(store.get()).size());
-  EXPECT_EQ(StoreState::FAILED_RESET, store->GetStateForTesting());
-
-  store->SetStateForTesting(StoreState::LOADED, true);
+  store->SetInitializationStatusForTesting(InitializationStatus::kSuccess,
+                                           true);
   EXPECT_EQ(0UL, GetOfflinePages(store.get()).size());
 
-  store->SetStateForTesting(StoreState::NOT_LOADED, true);
+  store->SetInitializationStatusForTesting(
+      InitializationStatus::kNotInitialized, true);
   EXPECT_EQ(0UL, GetOfflinePages(store.get()).size());
 
-  store->SetStateForTesting(StoreState::FAILED_LOADING, false);
-  EXPECT_EQ(0UL, GetOfflinePages(store.get()).size());
-
-  store->SetStateForTesting(StoreState::FAILED_RESET, false);
+  store->SetInitializationStatusForTesting(InitializationStatus::kFailure,
+                                           false);
   EXPECT_EQ(0UL, GetOfflinePages(store.get()).size());
 }
 
@@ -896,7 +897,7 @@
   OfflinePageItem offline_page_2(GURL("https://other.page.com"), 5678LL,
                                  kTestClientId2, file_path_2, 12345,
                                  OfflineTimeNow(), kTestRequestOrigin);
-  offline_page_2.original_url = GURL("https://example.com/bar");
+  offline_page_2.original_url_if_different = GURL("https://example.com/bar");
   offline_page_2.system_download_id = kTestSystemDownloadId;
   offline_page_2.digest = kTestDigest;
 
diff --git a/components/offline_pages/core/offline_page_model.h b/components/offline_pages/core/offline_page_model.h
index cdd0ded2..8f8b5f5 100644
--- a/components/offline_pages/core/offline_page_model.h
+++ b/components/offline_pages/core/offline_page_model.h
@@ -71,6 +71,12 @@
                     const ClientId& client_id,
                     const std::string& request_origin,
                     const GURL& url);
+
+    const GURL& GetOriginalUrl() const {
+      return original_url_if_different.is_empty() ? url
+                                                  : original_url_if_different;
+    }
+
     // The ID of the deleted page.
     int64_t offline_id;
     // The system download manager id of the deleted page.  This will be 0 if
@@ -82,6 +88,9 @@
     std::string request_origin;
     // URL of the page that was deleted.
     GURL url;
+    // Originally request URL of the page that was deleted. If this is empty,
+    // the final URL is not different than the original URL.
+    GURL original_url_if_different;
   };
 
   // Observer of the OfflinePageModel.
diff --git a/components/offline_pages/core/prefetch/prefetch_importer_impl.cc b/components/offline_pages/core/prefetch/prefetch_importer_impl.cc
index 069b3a0c..ed0923b 100644
--- a/components/offline_pages/core/prefetch/prefetch_importer_impl.cc
+++ b/components/offline_pages/core/prefetch/prefetch_importer_impl.cc
@@ -102,7 +102,7 @@
   }
   OfflinePageItem offline_page(url, archive.offline_id, archive.client_id,
                                dest_path, archive.file_size, base::Time::Now());
-  offline_page.original_url = original_url;
+  offline_page.original_url_if_different = original_url;
   offline_page.title = archive.title;
 
   outstanding_import_offline_ids_.emplace(archive.offline_id);
diff --git a/components/offline_pages/core/prefetch/prefetch_importer_impl_unittest.cc b/components/offline_pages/core/prefetch/prefetch_importer_impl_unittest.cc
index ab0997e..80baf72c 100644
--- a/components/offline_pages/core/prefetch/prefetch_importer_impl_unittest.cc
+++ b/components/offline_pages/core/prefetch/prefetch_importer_impl_unittest.cc
@@ -110,7 +110,8 @@
   EXPECT_EQ(kTestOfflineID, offline_page_model()->last_added_page().offline_id);
   EXPECT_EQ(kTestClientID, offline_page_model()->last_added_page().client_id);
   EXPECT_EQ(kTestFinalURL, offline_page_model()->last_added_page().url);
-  EXPECT_EQ(kTestURL, offline_page_model()->last_added_page().original_url);
+  EXPECT_EQ(kTestURL,
+            offline_page_model()->last_added_page().original_url_if_different);
   EXPECT_EQ(kTestTitle, offline_page_model()->last_added_page().title);
   EXPECT_EQ(kTestFileSize, offline_page_model()->last_added_page().file_size);
 }
diff --git a/components/offline_pages/core/prefetch/store/prefetch_store.cc b/components/offline_pages/core/prefetch/store/prefetch_store.cc
index d28e193d..064e3b65 100644
--- a/components/offline_pages/core/prefetch/store/prefetch_store.cc
+++ b/components/offline_pages/core/prefetch/store/prefetch_store.cc
@@ -4,6 +4,8 @@
 
 #include "components/offline_pages/core/prefetch/store/prefetch_store.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -23,164 +25,86 @@
 
 const char kPrefetchStoreFileName[] = "PrefetchStore.db";
 
-bool PrepareDirectory(const base::FilePath& path) {
-  base::File::Error error = base::File::FILE_OK;
-  if (!base::DirectoryExists(path.DirName())) {
-    if (!base::CreateDirectoryAndGetError(path.DirName(), &error)) {
-      LOG(ERROR) << "Failed to create prefetch db directory: "
-                 << base::File::ErrorToString(error);
-      return false;
-    }
-  }
-  return true;
-}
-
-// TODO(fgorski): This function and this part of the system in general could
-// benefit from a better status code reportable through UMA to better capture
-// the reason for failure, aiding the process of repeated attempts to
-// open/initialize the database.
-bool InitializeSync(sql::Database* db,
-                    const base::FilePath& path,
-                    bool in_memory) {
-  // These values are default.
-  db->set_page_size(4096);
-  db->set_cache_size(500);
-  db->set_histogram_tag("PrefetchStore");
-  db->set_exclusive_locking();
-
-  if (!in_memory && !PrepareDirectory(path))
-    return false;
-
-  bool open_db_result = false;
-  if (in_memory)
-    open_db_result = db->OpenInMemory();
-  else
-    open_db_result = db->Open(path);
-
-  if (!open_db_result) {
-    LOG(ERROR) << "Failed to open database, in memory: " << in_memory;
-    return false;
-  }
-  db->Preload();
-
-  return PrefetchStoreSchema::CreateOrUpgradeIfNeeded(db);
-}
-
-void CloseDatabaseSync(
-    sql::Database* db,
-    scoped_refptr<base::SingleThreadTaskRunner> callback_runner,
-    base::OnceClosure callback) {
-  if (db)
-    db->Close();
-  callback_runner->PostTask(FROM_HERE, std::move(callback));
-}
-
 void ReportStoreEvent(OfflinePagesStoreEvent event) {
   UMA_HISTOGRAM_ENUMERATION("OfflinePages.PrefetchStore.StoreEvent", event);
 }
 
 }  // namespace
 
-// static
-constexpr base::TimeDelta PrefetchStore::kClosingDelay;
-
 PrefetchStore::PrefetchStore(
     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
-    : blocking_task_runner_(std::move(blocking_task_runner)),
-      in_memory_(true),
-      db_(nullptr, base::OnTaskRunnerDeleter(blocking_task_runner_)),
-      initialization_status_(InitializationStatus::NOT_INITIALIZED),
-      weak_ptr_factory_(this),
-      closing_weak_ptr_factory_(this) {}
+    : SqlStoreBase("PrefetchStore",
+                   std::move(blocking_task_runner),
+                   base::FilePath()) {}
 
 PrefetchStore::PrefetchStore(
     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
     const base::FilePath& path)
-    : blocking_task_runner_(std::move(blocking_task_runner)),
-      db_file_path_(path.AppendASCII(kPrefetchStoreFileName)),
-      in_memory_(false),
-      db_(nullptr, base::OnTaskRunnerDeleter(blocking_task_runner_)),
-      initialization_status_(InitializationStatus::NOT_INITIALIZED),
-      weak_ptr_factory_(this),
-      closing_weak_ptr_factory_(this) {}
+    : SqlStoreBase("PrefetchStore",
+                   std::move(blocking_task_runner),
+                   path.AppendASCII(kPrefetchStoreFileName)) {}
 
-PrefetchStore::~PrefetchStore() {}
+PrefetchStore::~PrefetchStore() = default;
 
-void PrefetchStore::Initialize(base::OnceClosure pending_command) {
+base::OnceCallback<bool(sql::Database* db)>
+PrefetchStore::GetSchemaInitializationFunction() {
+  return base::BindOnce(&PrefetchStoreSchema::CreateOrUpgradeIfNeeded);
+}
+
+void PrefetchStore::OnOpenStart(base::TimeTicks last_closing_time) {
   TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Prefetch Store", this, "is reopen",
-                           !last_closing_time_.is_null());
-  DCHECK_EQ(initialization_status_, InitializationStatus::NOT_INITIALIZED);
-  initialization_status_ = InitializationStatus::INITIALIZING;
-
-  if (!last_closing_time_.is_null()) {
+                           !last_closing_time.is_null());
+  if (!last_closing_time.is_null()) {
     ReportStoreEvent(OfflinePagesStoreEvent::kReopened);
     UMA_HISTOGRAM_CUSTOM_TIMES("OfflinePages.PrefetchStore.TimeFromCloseToOpen",
-                               base::TimeTicks::Now() - last_closing_time_,
+                               base::TimeTicks::Now() - last_closing_time,
                                base::TimeDelta::FromMilliseconds(10),
                                base::TimeDelta::FromMinutes(10),
                                50 /* buckets */);
   } else {
     ReportStoreEvent(OfflinePagesStoreEvent::kOpenedFirstTime);
   }
-
-  // This is how we reset a pointer and provide deleter. This is necessary to
-  // ensure that we can close the store more than once.
-  db_ = DatabaseUniquePtr(new sql::Database,
-                          base::OnTaskRunnerDeleter(blocking_task_runner_));
-
-  base::PostTaskAndReplyWithResult(
-      blocking_task_runner_.get(), FROM_HERE,
-      base::BindOnce(&InitializeSync, db_.get(), db_file_path_, in_memory_),
-      base::BindOnce(&PrefetchStore::OnInitializeDone,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     std::move(pending_command)));
 }
 
-void PrefetchStore::OnInitializeDone(base::OnceClosure pending_command,
-                                     bool success) {
+void PrefetchStore::OnOpenDone(bool success) {
   // TODO(carlosk): Add initializing error reporting here.
   TRACE_EVENT_ASYNC_STEP_PAST1("offline_pages", "Prefetch Store", this,
                                "Initializing", "succeeded", success);
-  DCHECK_EQ(initialization_status_, InitializationStatus::INITIALIZING);
-  if (success) {
-    initialization_status_ = InitializationStatus::SUCCESS;
-  } else {
-    initialization_status_ = InitializationStatus::FAILURE;
-    db_.reset();
+  if (!success) {
     TRACE_EVENT_ASYNC_END0("offline_pages", "Prefetch Store", this);
   }
-
-  CHECK(!pending_command.is_null());
-  std::move(pending_command).Run();
-
-  // Once pending commands are empty, we get back to NOT_INITIALIZED state, to
-  // make it possible to retry initialization next time a DB operation is
-  // attempted.
-  if (initialization_status_ == InitializationStatus::FAILURE)
-    initialization_status_ = InitializationStatus::NOT_INITIALIZED;
 }
 
-void PrefetchStore::CloseInternal() {
-  if (initialization_status_ != InitializationStatus::SUCCESS) {
+void PrefetchStore::OnTaskBegin(bool is_initialized) {
+  TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Prefetch Store: task execution",
+                           this, "is store loaded", is_initialized);
+}
+
+void PrefetchStore::OnTaskRunComplete() {
+  // Note: the time recorded for this trace step will include thread hop wait
+  // times to the background thread and back.
+  TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages",
+                               "Prefetch Store: task execution", this, "Task");
+}
+
+void PrefetchStore::OnTaskReturnComplete() {
+  TRACE_EVENT_ASYNC_STEP_PAST0(
+      "offline_pages", "Prefetch Store: task execution", this, "Callback");
+  TRACE_EVENT_ASYNC_END0("offline_pages", "Prefetch Store: task execution",
+                         this);
+}
+
+void PrefetchStore::OnCloseStart(InitializationStatus initialization_status) {
+  if (initialization_status != InitializationStatus::kSuccess) {
     ReportStoreEvent(OfflinePagesStoreEvent::kCloseSkipped);
     return;
   }
   TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Prefetch Store", this, "Open");
 
-  last_closing_time_ = base::TimeTicks::Now();
   ReportStoreEvent(OfflinePagesStoreEvent::kClosed);
-
-  initialization_status_ = InitializationStatus::NOT_INITIALIZED;
-  blocking_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &CloseDatabaseSync, db_.get(), base::ThreadTaskRunnerHandle::Get(),
-          base::BindOnce(&PrefetchStore::CloseInternalDone,
-                         weak_ptr_factory_.GetWeakPtr(), std::move(db_))));
 }
 
-void PrefetchStore::CloseInternalDone(DatabaseUniquePtr db) {
-  db.reset();
+void PrefetchStore::OnCloseComplete() {
   TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Prefetch Store", this,
                                "Closing");
   TRACE_EVENT_ASYNC_END0("offline_pages", "Prefetch Store", this);
diff --git a/components/offline_pages/core/prefetch/store/prefetch_store.h b/components/offline_pages/core/prefetch/store/prefetch_store.h
index b9bcecb..5d80e18 100644
--- a/components/offline_pages/core/prefetch/store/prefetch_store.h
+++ b/components/offline_pages/core/prefetch/store/prefetch_store.h
@@ -16,6 +16,7 @@
 #include "base/task_runner_util.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
+#include "components/offline_pages/task/sql_store_base.h"
 
 namespace sql {
 class Database;
@@ -23,13 +24,6 @@
 
 namespace offline_pages {
 
-enum class InitializationStatus {
-  NOT_INITIALIZED,
-  INITIALIZING,
-  SUCCESS,
-  FAILURE,
-};
-
 // PrefetchStore is a front end to SQLite store hosting prefetch related
 // items.
 //
@@ -39,23 +33,8 @@
 //
 // Store has a set of auxiliary functions meant to be used on the blocking
 // thread. They can be found in prefetch_store_sql_utils file.
-class PrefetchStore {
+class PrefetchStore : public SqlStoreBase {
  public:
-  // Definition of the callback that is going to run the core of the command in
-  // the |Execute| method.
-  template <typename T>
-  using RunCallback = base::OnceCallback<T(sql::Database*)>;
-
-  // Definition of the callback used to pass the result back to the caller of
-  // |Execute| method.
-  template <typename T>
-  using ResultCallback = base::OnceCallback<void(T)>;
-
-  // Defines inactivity time of DB after which it is going to be closed.
-  // TODO(fgorski): Derive appropriate value in a scientific way.
-  static constexpr base::TimeDelta kClosingDelay =
-      base::TimeDelta::FromSeconds(20);
-
   // Creates an instance of |PrefetchStore| with an in-memory SQLite database.
   explicit PrefetchStore(
       scoped_refptr<base::SequencedTaskRunner> blocking_task_runner);
@@ -65,122 +44,24 @@
   PrefetchStore(scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
                 const base::FilePath& database_dir);
 
-  ~PrefetchStore();
-
-  // Executes a |run_callback| on SQL store on the blocking thread, and posts
-  // its result back to calling thread through |result_callback|.
-  // Calling |Execute| when store is NOT_INITIALIZED will cause the store
-  // initialization to start.
-  // Store initialization status needs to be SUCCESS for run_callback to run.
-  // If initialization fails, |result_callback| is invoked with |default_value|.
-  template <typename T>
-  void Execute(RunCallback<T> run_callback,
-               ResultCallback<T> result_callback,
-               T default_value) {
-    CHECK_NE(initialization_status_, InitializationStatus::INITIALIZING);
-
-    if (initialization_status_ == InitializationStatus::NOT_INITIALIZED) {
-      Initialize(base::BindOnce(
-          &PrefetchStore::Execute<T>, weak_ptr_factory_.GetWeakPtr(),
-          std::move(run_callback), std::move(result_callback),
-          std::move(default_value)));
-      return;
-    }
-
-    TRACE_EVENT_ASYNC_BEGIN1(
-        "offline_pages", "Prefetch Store: task execution", this,
-        "is store loaded",
-        initialization_status_ == InitializationStatus::SUCCESS);
-    // Ensure that any scheduled close operations are canceled.
-    closing_weak_ptr_factory_.InvalidateWeakPtrs();
-
-    sql::Database* db = initialization_status_ == InitializationStatus::SUCCESS
-                            ? db_.get()
-                            : nullptr;
-    if (!db) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(result_callback), std::move(default_value)));
-    } else {
-      base::PostTaskAndReplyWithResult(
-          blocking_task_runner_.get(), FROM_HERE,
-          base::BindOnce(std::move(run_callback), db),
-          base::BindOnce(&PrefetchStore::RescheduleClosing<T>,
-                         weak_ptr_factory_.GetWeakPtr(),
-                         std::move(result_callback)));
-    }
-  }
-
-  // Gets the initialization status of the store.
-  InitializationStatus initialization_status() const {
-    return initialization_status_;
-  }
+  ~PrefetchStore() override;
 
   static const char* GetTableCreationSqlForTesting();
 
+ protected:
+  // SqlStoreBase:
+  base::OnceCallback<bool(sql::Database* db)> GetSchemaInitializationFunction()
+      override;
+  void OnOpenStart(base::TimeTicks last_open_time) override;
+  void OnOpenDone(bool success) override;
+  void OnTaskBegin(bool is_initialized) override;
+  void OnTaskRunComplete() override;
+  void OnTaskReturnComplete() override;
+  void OnCloseStart(InitializationStatus status_before_close) override;
+  void OnCloseComplete() override;
+
  private:
   friend class PrefetchStoreTestUtil;
-
-  using DatabaseUniquePtr =
-      std::unique_ptr<sql::Database, base::OnTaskRunnerDeleter>;
-
-  // Used internally to initialize connection.
-  void Initialize(base::OnceClosure pending_command);
-
-  // Used to conclude opening/resetting DB connection.
-  void OnInitializeDone(base::OnceClosure pending_command, bool success);
-
-  // Reschedules the closing with a delay. Ensures that |result_callback| is
-  // called.
-  template <typename T>
-  void RescheduleClosing(ResultCallback<T> result_callback, T result) {
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&PrefetchStore::CloseInternal,
-                       closing_weak_ptr_factory_.GetWeakPtr()),
-        kClosingDelay);
-
-    // Note: the time recorded for this trace step will include thread hop wait
-    // times to the background thread and back.
-    TRACE_EVENT_ASYNC_STEP_PAST0(
-        "offline_pages", "Prefetch Store: task execution", this, "Task");
-    std::move(result_callback).Run(std::move(result));
-    TRACE_EVENT_ASYNC_STEP_PAST0(
-        "offline_pages", "Prefetch Store: task execution", this, "Callback");
-    TRACE_EVENT_ASYNC_END0("offline_pages", "Prefetch Store: task execution",
-                           this);
-  }
-
-  // Internal function initiating the closing.
-  void CloseInternal();
-
-  // Completes the closing. Main purpose is to destroy the db pointer.
-  void CloseInternalDone(DatabaseUniquePtr db);
-
-  // Background thread where all SQL access should be run.
-  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
-
-  // Path to the database on disk.
-  base::FilePath db_file_path_;
-
-  // Only open the store in memory. Used for testing.
-  bool in_memory_;
-
-  // Database connection.
-  std::unique_ptr<sql::Database, base::OnTaskRunnerDeleter> db_;
-
-  // Initialization status of the store.
-  InitializationStatus initialization_status_;
-
-  // Time of the last time the store was closed. Kept for metrics reporting.
-  base::TimeTicks last_closing_time_;
-
-  // Weak pointer to control the callback.
-  base::WeakPtrFactory<PrefetchStore> weak_ptr_factory_;
-  // Weak pointer to cancel closing of the store.
-  base::WeakPtrFactory<PrefetchStore> closing_weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrefetchStore);
 };
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc b/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc
index 1f72096..f0247ff 100644
--- a/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc
+++ b/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc
@@ -344,7 +344,8 @@
 }
 
 void PrefetchStoreTestUtil::SimulateInitializationError() {
-  store_->initialization_status_ = InitializationStatus::FAILURE;
+  store_->SetInitializationStatusForTesting(
+      SqlStoreBase::InitializationStatus::kFailure, false);
 }
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/store/prefetch_store_unittest.cc b/components/offline_pages/core/prefetch/store/prefetch_store_unittest.cc
index a44a80c2..fd44031 100644
--- a/components/offline_pages/core/prefetch/store/prefetch_store_unittest.cc
+++ b/components/offline_pages/core/prefetch/store/prefetch_store_unittest.cc
@@ -17,6 +17,8 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace offline_pages {
+namespace {
+using InitializationStatus = SqlStoreBase::InitializationStatus;
 
 class PrefetchStoreTest : public testing::Test {
  public:
@@ -88,17 +90,19 @@
   PrefetchItem item1(
       item_generator()->CreateItem(PrefetchItemState::NEW_REQUEST));
   EXPECT_TRUE(store_util()->InsertPrefetchItem(item1));
-  EXPECT_EQ(InitializationStatus::SUCCESS, store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store()->initialization_status_for_testing());
 
   task_runner()->FastForwardBy(PrefetchStore::kClosingDelay);
-  EXPECT_EQ(InitializationStatus::NOT_INITIALIZED,
-            store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kNotInitialized,
+            store()->initialization_status_for_testing());
 
   // Should initialize the store again.
   PrefetchItem item2(
       item_generator()->CreateItem(PrefetchItemState::NEW_REQUEST));
   EXPECT_TRUE(store_util()->InsertPrefetchItem(item2));
-  EXPECT_EQ(InitializationStatus::SUCCESS, store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store()->initialization_status_for_testing());
 }
 
 TEST_F(PrefetchStoreTest, CloseStorePostponed) {
@@ -106,30 +110,35 @@
   PrefetchItem item1(
       item_generator()->CreateItem(PrefetchItemState::NEW_REQUEST));
   EXPECT_TRUE(store_util()->InsertPrefetchItem(item1));
-  EXPECT_EQ(InitializationStatus::SUCCESS, store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store()->initialization_status_for_testing());
 
   task_runner()->FastForwardBy(PrefetchStore::kClosingDelay / 2);
-  EXPECT_EQ(InitializationStatus::SUCCESS, store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store()->initialization_status_for_testing());
 
   // Should postpone closing.
   PrefetchItem item2(
       item_generator()->CreateItem(PrefetchItemState::NEW_REQUEST));
   EXPECT_TRUE(store_util()->InsertPrefetchItem(item2));
-  EXPECT_EQ(InitializationStatus::SUCCESS, store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store()->initialization_status_for_testing());
 
   // This adds up to more than kClosingDelay after the first call, which means
   // the closing would trigger, it does not however, since second call caused it
   // to be postponed.
   task_runner()->FastForwardBy(2 * PrefetchStore::kClosingDelay / 3);
   // Store should still be initialized.
-  EXPECT_EQ(InitializationStatus::SUCCESS, store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kSuccess,
+            store()->initialization_status_for_testing());
   // There is still a pending task to close the store.
   EXPECT_TRUE(task_runner()->HasPendingTask());
 
   // After this step the store should be closed.
   task_runner()->FastForwardBy(PrefetchStore::kClosingDelay);
-  EXPECT_EQ(InitializationStatus::NOT_INITIALIZED,
-            store()->initialization_status());
+  EXPECT_EQ(InitializationStatus::kNotInitialized,
+            store()->initialization_status_for_testing());
 }
 
+}  // namespace
 }  // namespace offline_pages
diff --git a/components/offline_pages/task/BUILD.gn b/components/offline_pages/task/BUILD.gn
index fa6a891..14e59c2 100644
--- a/components/offline_pages/task/BUILD.gn
+++ b/components/offline_pages/task/BUILD.gn
@@ -10,6 +10,8 @@
   sources = [
     "closure_task.cc",
     "closure_task.h",
+    "sql_store_base.cc",
+    "sql_store_base.h",
     "task.cc",
     "task.h",
     "task_queue.cc",
@@ -18,6 +20,7 @@
 
   deps = [
     "//base",
+    "//sql",
   ]
 }
 
diff --git a/components/offline_pages/task/DEPS b/components/offline_pages/task/DEPS
new file mode 100644
index 0000000..6fff87d
--- /dev/null
+++ b/components/offline_pages/task/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+sql",
+]
diff --git a/components/offline_pages/task/sql_store_base.cc b/components/offline_pages/task/sql_store_base.cc
new file mode 100644
index 0000000..d1552d4
--- /dev/null
+++ b/components/offline_pages/task/sql_store_base.cc
@@ -0,0 +1,201 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/offline_pages/task/sql_store_base.h"
+
+#include <iterator>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/trace_event/trace_event.h"
+
+namespace offline_pages {
+namespace {
+
+bool PrepareDirectory(const base::FilePath& path) {
+  base::File::Error error = base::File::FILE_OK;
+  if (!base::DirectoryExists(path.DirName())) {
+    if (!base::CreateDirectoryAndGetError(path.DirName(), &error)) {
+      DLOG(ERROR) << "Failed to create prefetch db directory: "
+                  << base::File::ErrorToString(error);
+      return false;
+    }
+  }
+  return true;
+}
+
+// TODO(fgorski): This function and this part of the system in general could
+// benefit from a better status code reportable through UMA to better capture
+// the reason for failure, aiding the process of repeated attempts to
+// open/initialize the database.
+bool InitializeSync(
+    sql::Database* db,
+    const base::FilePath& path,
+    const std::string& histogram_tag,
+    base::OnceCallback<bool(sql::Database*)> initialize_schema) {
+  // These values are default.
+  db->set_page_size(4096);
+  db->set_cache_size(500);
+  db->set_histogram_tag(histogram_tag);
+  db->set_exclusive_locking();
+  const bool in_memory = path.empty();
+  if (!in_memory && !PrepareDirectory(path))
+    return false;
+
+  bool open_db_result = false;
+  if (in_memory)
+    open_db_result = db->OpenInMemory();
+  else
+    open_db_result = db->Open(path);
+
+  if (!open_db_result) {
+    DLOG(ERROR) << "Failed to open database, in memory: " << in_memory;
+    return false;
+  }
+  db->Preload();
+
+  return std::move(initialize_schema).Run(db);
+}
+
+void CloseDatabaseSync(
+    sql::Database* db,
+    scoped_refptr<base::SingleThreadTaskRunner> callback_runner,
+    base::OnceClosure callback) {
+  if (db)
+    db->Close();
+  callback_runner->PostTask(FROM_HERE, std::move(callback));
+}
+
+}  // namespace
+
+// static
+constexpr base::TimeDelta SqlStoreBase::kClosingDelay;
+
+SqlStoreBase::SqlStoreBase(
+    const std::string& histogram_tag,
+    scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+    const base::FilePath& file_path)
+    : background_task_runner_(background_task_runner),
+      histogram_tag_(histogram_tag),
+      db_file_path_(file_path),
+      db_(nullptr, base::OnTaskRunnerDeleter(background_task_runner_)),
+      weak_ptr_factory_(this),
+      closing_weak_ptr_factory_(this) {}
+
+SqlStoreBase::~SqlStoreBase() = default;
+
+void SqlStoreBase::SetInitializationStatusForTesting(
+    InitializationStatus initialization_status,
+    bool reset_db) {
+  initialization_status_ = initialization_status;
+  if (reset_db)
+    db_.reset(nullptr);
+}
+
+void SqlStoreBase::Initialize(base::OnceClosure pending_command) {
+  OnOpenStart(last_closing_time_);
+
+  DCHECK_EQ(initialization_status_, InitializationStatus::kNotInitialized);
+  initialization_status_ = InitializationStatus::kInProgress;
+
+  // This is how we reset a pointer and provide deleter. This is necessary to
+  // ensure that we can close the store more than once.
+  db_ = DatabaseUniquePtr(new sql::Database,
+                          base::OnTaskRunnerDeleter(background_task_runner_));
+
+  base::PostTaskAndReplyWithResult(
+      background_task_runner_.get(), FROM_HERE,
+      base::BindOnce(&InitializeSync, db_.get(), db_file_path_, histogram_tag_,
+                     GetSchemaInitializationFunction()),
+      base::BindOnce(&SqlStoreBase::InitializeDone,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(pending_command)));
+}
+
+void SqlStoreBase::InitializeDone(base::OnceClosure pending_command,
+                                  bool success) {
+  DCHECK_EQ(initialization_status_, InitializationStatus::kInProgress);
+  if (success) {
+    initialization_status_ = InitializationStatus::kSuccess;
+  } else {
+    initialization_status_ = InitializationStatus::kFailure;
+    db_.reset();
+  }
+
+  CHECK(!pending_command.is_null());
+  std::move(pending_command).Run();
+  for (auto command_iter = std::make_move_iterator(pending_commands_.begin());
+       command_iter != std::make_move_iterator(pending_commands_.end());
+       ++command_iter) {
+    (*command_iter).Run();
+  }
+  pending_commands_.clear();
+
+  // Once pending commands are empty, we get back to kNotInitialized state, to
+  // make it possible to retry initialization next time a DB operation is
+  // attempted.
+  if (initialization_status_ == InitializationStatus::kFailure)
+    initialization_status_ = InitializationStatus::kNotInitialized;
+
+  OnOpenDone(success);
+}
+
+void SqlStoreBase::ExecuteInternal(base::OnceClosure command) {
+  if (initialization_status_ == InitializationStatus::kInProgress) {
+    pending_commands_.push_back(std::move(command));
+    return;
+  }
+
+  if (initialization_status_ == InitializationStatus::kNotInitialized) {
+    Initialize(std::move(command));
+    return;
+  }
+
+  std::move(command).Run();
+}
+
+sql::Database* SqlStoreBase::ExecuteBegin() {
+  OnTaskBegin(initialization_status_ == InitializationStatus::kSuccess);
+  // Ensure that any scheduled close operations are canceled.
+  closing_weak_ptr_factory_.InvalidateWeakPtrs();
+
+  return initialization_status_ == InitializationStatus::kSuccess ? db_.get()
+                                                                  : nullptr;
+}
+
+void SqlStoreBase::CloseInternal() {
+  OnCloseStart(initialization_status_);
+
+  last_closing_time_ = base::TimeTicks::Now();
+
+  initialization_status_ = InitializationStatus::kNotInitialized;
+  background_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &CloseDatabaseSync, db_.get(), base::ThreadTaskRunnerHandle::Get(),
+          base::BindOnce(&SqlStoreBase::CloseInternalDone,
+                         weak_ptr_factory_.GetWeakPtr(), std::move(db_))));
+}
+
+void SqlStoreBase::RescheduleClosingBefore() {
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&SqlStoreBase::CloseInternal,
+                     closing_weak_ptr_factory_.GetWeakPtr()),
+      kClosingDelay);
+
+  // Note: the time recorded for this trace step will include thread hop wait
+  // times to the background thread and back.
+  OnTaskRunComplete();
+}
+
+void SqlStoreBase::CloseInternalDone(DatabaseUniquePtr db) {
+  db.reset();
+  OnCloseComplete();
+}
+
+}  // namespace offline_pages
diff --git a/components/offline_pages/task/sql_store_base.h b/components/offline_pages/task/sql_store_base.h
new file mode 100644
index 0000000..18ea55e
--- /dev/null
+++ b/components/offline_pages/task/sql_store_base.h
@@ -0,0 +1,190 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OFFLINE_PAGES_TASK_SQL_STORE_BASE_H_
+#define COMPONENTS_OFFLINE_PAGES_TASK_SQL_STORE_BASE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+#include "sql/database.h"
+
+namespace offline_pages {
+
+// Maintains an SQLite database and permits safe access.
+// Opens the database only when queried. Automatically closes the database
+// if it's not being used.
+// This is a base class, and must be overridden to configure the database
+// schema.
+class SqlStoreBase {
+ public:
+  enum class InitializationStatus {
+    kNotInitialized,
+    kInProgress,
+    kSuccess,
+    kFailure,
+  };
+
+  // Definition of the callback that is going to run the core of the command in
+  // the |Execute| method.
+  template <typename T>
+  using RunCallback = base::OnceCallback<T(sql::Database*)>;
+
+  // Definition of the callback used to pass the result back to the caller of
+  // |Execute| method.
+  template <typename T>
+  using ResultCallback = base::OnceCallback<void(T)>;
+
+  // Defines inactivity time of DB after which it is going to be closed.
+  // TODO(crbug.com/933369): Derive appropriate value in a scientific way.
+  static constexpr base::TimeDelta kClosingDelay =
+      base::TimeDelta::FromSeconds(20);
+
+  // If |file_path| is empty, this constructs an in-memory database.
+  SqlStoreBase(const std::string& histogram_tag,
+               scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+               const base::FilePath& file_path);
+  virtual ~SqlStoreBase();
+
+  // Gets the initialization status of the store.
+  InitializationStatus initialization_status_for_testing() const {
+    return initialization_status_;
+  }
+
+  // Executes a |run_callback| on SQL store on the blocking sequence, and posts
+  // its result back to calling thread through |result_callback|.
+  // Calling |Execute| when store is kNotInitialized will cause the store
+  // initialization to start.
+  // Store initialization status needs to be kSuccess for run_callback to run.
+  // If initialization fails, |result_callback| is invoked with |default_value|.
+  template <typename T>
+  void Execute(RunCallback<T> run_callback,
+               ResultCallback<T> result_callback,
+               T default_value) {
+    ExecuteInternal(
+        base::BindOnce(&SqlStoreBase::ExecuteAfterInitialized<T>,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(run_callback),
+                       std::move(result_callback), std::move(default_value)));
+  }
+
+  void SetInitializationStatusForTesting(
+      InitializationStatus initialization_status,
+      bool reset_db);
+
+ protected:
+  // Returns a function that installs the latest schema to |db| (if necessary),
+  // and returns whether the database was successfully verified to have the
+  // current schema. |GetSchemaInitializationFunction| is called on the main
+  // thread, but the returned function is executed on the blocking sequence.
+  virtual base::OnceCallback<bool(sql::Database* db)>
+  GetSchemaInitializationFunction() = 0;
+
+  // Optional tracing methods. The derived class may implement tracing events
+  // with these methods.
+
+  // Called on attempt to open the database. |last_closing_time| is the time
+  // since the last time the database was closed, or null if the database was
+  // not previously opened since creation of this.
+  virtual void OnOpenStart(base::TimeTicks last_closing_time) {}
+  // Called when done attempting to open the database.
+  virtual void OnOpenDone(bool success) {}
+  // Called before a task is executed through |Execute()|.
+  virtual void OnTaskBegin(bool is_initialized) {}
+  // Called after calling the |run_callback| in |Execute()|.
+  virtual void OnTaskRunComplete() {}
+  // Called after calling the |result_callback| in |Execute()|.
+  virtual void OnTaskReturnComplete() {}
+  // Called when starting to close the database.
+  virtual void OnCloseStart(InitializationStatus status_before_close) {}
+  // Called after closing the database.
+  virtual void OnCloseComplete() {}
+
+ private:
+  using DatabaseUniquePtr =
+      std::unique_ptr<sql::Database, base::OnTaskRunnerDeleter>;
+
+  void Initialize(base::OnceClosure pending_command);
+  void InitializeDone(base::OnceClosure pending_command, bool success);
+
+  void ExecuteInternal(base::OnceClosure command);
+  sql::Database* ExecuteBegin();
+
+  template <typename T>
+  void ExecuteAfterInitialized(RunCallback<T> run_callback,
+                               ResultCallback<T> result_callback,
+                               T default_value) {
+    DCHECK_NE(initialization_status_, InitializationStatus::kNotInitialized);
+    DCHECK_NE(initialization_status_, InitializationStatus::kInProgress);
+    sql::Database* db = ExecuteBegin();
+    if (!db) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(result_callback), std::move(default_value)));
+      return;
+    }
+    base::PostTaskAndReplyWithResult(
+        background_task_runner_.get(), FROM_HERE,
+        base::BindOnce(std::move(run_callback), db),
+        base::BindOnce(&SqlStoreBase::RescheduleClosing<T>,
+                       weak_ptr_factory_.GetWeakPtr(),
+                       std::move(result_callback)));
+  }
+
+  // Reschedules the closing with a delay. Ensures that |result_callback| is
+  // called.
+  template <typename T>
+  void RescheduleClosing(ResultCallback<T> result_callback, T result) {
+    RescheduleClosingBefore();
+    std::move(result_callback).Run(std::move(result));
+    OnTaskReturnComplete();
+  }
+
+  void RescheduleClosingBefore();
+
+  // Internal function initiating the closing.
+  void CloseInternal();
+
+  // Completes the closing. Main purpose is to destroy the db pointer.
+  void CloseInternalDone(DatabaseUniquePtr db);
+
+  // Background thread where all SQL access should be run.
+  scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+  // Histogram tag for the sqlite database.
+  std::string histogram_tag_;
+
+  // Path to the database on disk. If empty, the database is in memory only.
+  base::FilePath db_file_path_;
+
+  // Database connection.
+  DatabaseUniquePtr db_;
+
+  // Pending commands.
+  std::vector<base::OnceClosure> pending_commands_;
+
+  // State of initialization.
+  InitializationStatus initialization_status_ =
+      InitializationStatus::kNotInitialized;
+
+  // Time of the last time the store was closed. Kept for metrics reporting.
+  base::TimeTicks last_closing_time_;
+
+  base::WeakPtrFactory<SqlStoreBase> weak_ptr_factory_;
+  base::WeakPtrFactory<SqlStoreBase> closing_weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SqlStoreBase);
+};
+
+}  // namespace offline_pages
+
+#endif  // COMPONENTS_OFFLINE_PAGES_TASK_SQL_STORE_BASE_H_
diff --git a/components/optimization_guide/proto/hints.proto b/components/optimization_guide/proto/hints.proto
index 63c3f350..b896e5d9 100644
--- a/components/optimization_guide/proto/hints.proto
+++ b/components/optimization_guide/proto/hints.proto
@@ -7,6 +7,103 @@
 
 package optimization_guide.proto;
 
+// Information about the hint that the client already has for a host.
+message MatchedHintInfo {
+  // Describes the granularity of the key field.
+  optional KeyRepresentation key_representation = 1;
+  // The key of the hint currently used for the host.
+  optional string key = 2;
+  // The version of the hint for this key already stored on the client.
+  optional int64 version = 3;
+}
+
+message HostInfo {
+  // Host that the client is requesting information for.
+  optional string host = 1;
+  // Information about the hint that the client already has for the host.
+  optional MatchedHintInfo matched_hint = 2;
+}
+
+// Request to return a set of hints that guide what optimizations to perform
+// on those hosts.
+message GetHintsRequest {
+  // Information about the set of hosts to retrieve hints for.
+  repeated HostInfo hosts = 1;
+
+  // The set of optimization types that the requesting client can support
+  // and perform.
+  //
+  // It is guaranteed that the response will only contain hints for
+  // optimizations present in this set.
+  repeated OptimizationType supported_optimizations = 2;
+
+  // Context in which this request is made.
+  optional RequestContext context = 3;
+}
+
+// Response to the GetHints request.
+message GetHintsResponse {
+  // An ordered list containing hints for key/optimization combinations.
+  //
+  // It is guaranteed that there will only be a single hint per key and key
+  // representation combination. These hints are intended to apply to a full
+  // page. It is expected that the client will use the Hint record with the
+  // most specific key that matches the main frame URL.
+  //
+  // Note, this list may contain multiple hints that apply to a page. For
+  // example, if there are hints for (HOST_SUFFIX,cnn.com) and
+  // (HOST_SUFFIX,sports.cnn.com), these may both apply to
+  // sports.cnn.com/foo. In this case, the client is expected to use the
+  // hints from (HOST_SUFFIX,sports.cnn.com).
+  repeated Hint hints = 1;
+
+  // The maximum duration in which the hints provided in this response should
+  // be retained in the client cache.
+  optional Duration max_cache_duration = 2;
+
+  // A set of hint keys to remove from the client cache.
+  //
+  // It is guaranteed that all entries in this list were provided by the client
+  // in the corresponding request's |hosts.matched_hint| fields.
+  //
+  // It is expected for the client to immediately stop using all hints contained
+  // in this field. Hints that are not present in |hints| or in this field
+  // should adhere to the client cache's existing expiration policy.
+  repeated MatchedHintInfo hints_to_remove = 3;
+}
+
+// A Duration represents a signed, fixed-length span of time represented
+// as a count of seconds and fractions of seconds at nanosecond
+// resolution. It is independent of any calendar and concepts like "day"
+// or "month". It is related to Timestamp in that the difference between
+// two Timestamp values is a Duration and it can be added or subtracted
+// from a Timestamp. Range is approximately +-10,000 years.
+// This is local definition matching server side duration.proto definition.
+message Duration {
+  // Signed seconds of the span of time. Must be from -315,576,000,000
+  // to +315,576,000,000 inclusive.
+  optional int64 seconds = 1;
+
+  // Signed fractions of a second at nanosecond resolution of the span
+  // of time. Durations less than one second are represented with a 0
+  // `seconds` field and a positive or negative `nanos` field. For durations
+  // of one second or more, a non-zero value for the `nanos` field must be
+  // of the same sign as the `seconds` field. Must be from -999,999,999
+  // to +999,999,999 inclusive.
+  optional int32 nanos = 2;
+}
+
+// Context in which the hints are requested.
+enum RequestContext {
+  reserved 1;
+  // Context not specified.
+  CONTEXT_UNSPECIFIED = 0;
+  // Requesting hints on page navigation.
+  CONTEXT_PAGE_NAVIGATION = 2;
+  // Requesting hints as part of a batch update.
+  CONTEXT_BATCH_UPDATE = 3;
+}
+
 enum OptimizationType {
   TYPE_UNSPECIFIED = 0;
   // This optimization blocks JavaScript on the page.
diff --git a/components/password_manager/core/browser/password_manager_client.h b/components/password_manager/core/browser/password_manager_client.h
index 53a5dee..898adccc 100644
--- a/components/password_manager/core/browser/password_manager_client.h
+++ b/components/password_manager/core/browser/password_manager_client.h
@@ -30,7 +30,7 @@
 
 class GURL;
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 namespace safe_browsing {
 class PasswordProtectionService;
 }
@@ -224,7 +224,7 @@
   // Returns the current best guess as to the page's display language.
   virtual std::string GetPageLanguage() const;
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   // Return the PasswordProtectionService associated with this instance.
   virtual safe_browsing::PasswordProtectionService*
   GetPasswordProtectionService() const = 0;
diff --git a/components/password_manager/core/browser/password_reuse_detection_manager.cc b/components/password_manager/core/browser/password_reuse_detection_manager.cc
index b5c2f4ddf..342c665 100644
--- a/components/password_manager/core/browser/password_reuse_detection_manager.cc
+++ b/components/password_manager/core/browser/password_reuse_detection_manager.cc
@@ -110,7 +110,7 @@
   metrics_util::LogPasswordReuse(password_length, saved_passwords,
                                  matching_domains.size(),
                                  password_field_detected, reused_password_type);
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   if (reused_password_type == metrics_util::PasswordType::SYNC_PASSWORD)
     client_->LogPasswordReuseDetectedEvent();
 
diff --git a/components/password_manager/core/browser/stub_password_manager_client.cc b/components/password_manager/core/browser/stub_password_manager_client.cc
index ee3df8f..71bd57c 100644
--- a/components/password_manager/core/browser/stub_password_manager_client.cc
+++ b/components/password_manager/core/browser/stub_password_manager_client.cc
@@ -72,7 +72,7 @@
   return &log_manager_;
 }
 
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
 safe_browsing::PasswordProtectionService*
 StubPasswordManagerClient::GetPasswordProtectionService() const {
   return nullptr;
diff --git a/components/password_manager/core/browser/stub_password_manager_client.h b/components/password_manager/core/browser/stub_password_manager_client.h
index 88c5ea4..b2ec574 100644
--- a/components/password_manager/core/browser/stub_password_manager_client.h
+++ b/components/password_manager/core/browser/stub_password_manager_client.h
@@ -51,7 +51,7 @@
   const GURL& GetLastCommittedEntryURL() const override;
   const CredentialsFilter* GetStoreResultFilter() const override;
   const LogManager* GetLogManager() const override;
-#if defined(SAFE_BROWSING_DB_LOCAL)
+#if defined(FULL_SAFE_BROWSING)
   safe_browsing::PasswordProtectionService* GetPasswordProtectionService()
       const override;
   void CheckSafeBrowsingReputation(const GURL& form_action,
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 846721a..58d9eaa 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -11089,7 +11089,7 @@
       'name': 'PacHttpsUrlStrippingEnabled',
       'type': 'main',
       'schema': { 'type': 'boolean' },
-      'supported_on': [ 'chrome.*:52-74', 'chrome_os:52-74' ],
+      'supported_on': [ 'chrome.*:52-75', 'chrome_os:52-75' ],
       'features': {
         'dynamic_refresh': False,
         'per_profile': False,
diff --git a/components/previews/content/BUILD.gn b/components/previews/content/BUILD.gn
index 8595b0a..fb3f705 100644
--- a/components/previews/content/BUILD.gn
+++ b/components/previews/content/BUILD.gn
@@ -9,6 +9,8 @@
     "hint_cache_leveldb_store.cc",
     "hint_cache_leveldb_store.h",
     "hint_cache_store.h",
+    "hints_fetcher.cc",
+    "hints_fetcher.h",
     "previews_decider_impl.cc",
     "previews_decider_impl.h",
     "previews_hints.cc",
@@ -17,6 +19,7 @@
     "previews_hints_util.h",
     "previews_optimization_guide.cc",
     "previews_optimization_guide.h",
+    "previews_top_host_provider.h",
     "previews_ui_service.cc",
     "previews_ui_service.h",
     "previews_user_data.cc",
diff --git a/components/previews/content/hints_fetcher.cc b/components/previews/content/hints_fetcher.cc
new file mode 100644
index 0000000..c6b81fe
--- /dev/null
+++ b/components/previews/content/hints_fetcher.cc
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/previews/content/hints_fetcher.h"
+
+#include "base/feature_list.h"
+#include "base/metrics/histogram_macros.h"
+#include "components/optimization_guide/proto/hints.pb.h"
+#include "components/previews/content/hint_cache.h"
+#include "components/previews/core/previews_experiments.h"
+
+namespace previews {
+
+HintsFetcher::HintsFetcher(HintCache* hint_cache) : hint_cache_(hint_cache) {}
+
+HintsFetcher::~HintsFetcher() {}
+
+void HintsFetcher::FetchHintsForHosts(const std::vector<std::string>& hosts) {
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  GetRemoteHints(hosts);
+}
+
+void HintsFetcher::GetRemoteHints(const std::vector<std::string>& hosts) {
+  get_hints_request_ =
+      std::make_unique<optimization_guide::proto::GetHintsRequest>();
+
+  // Add all the optimizations supported by the current version of Chrome,
+  // regardless of whether the session is in holdback for either of them.
+  get_hints_request_->add_supported_optimizations(
+      optimization_guide::proto::NOSCRIPT);
+  get_hints_request_->add_supported_optimizations(
+      optimization_guide::proto::RESOURCE_LOADING);
+  static_assert(static_cast<int>(PreviewsType::LITE_PAGE_REDIRECT) + 1 ==
+                    static_cast<int>(PreviewsType::LAST),
+                "PreviewsType has been updated, make sure OnePlatform hints "
+                "are not affected");
+
+  get_hints_request_->set_context(
+      optimization_guide::proto::CONTEXT_BATCH_UPDATE);
+
+  for (const auto& host : hosts) {
+    optimization_guide::proto::HostInfo* host_info =
+        get_hints_request_->add_hosts();
+    host_info->set_host(host);
+  }
+}
+
+void HintsFetcher::HandleResponse(const std::string& config_data,
+                                  int status,
+                                  int response_code) {}
+
+void HintsFetcher::OnURLLoadComplete(
+    std::unique_ptr<std::string> response_body) {}
+
+bool HintsFetcher::ParseGetHintsResponseAndApplyHints(
+    const optimization_guide::proto::GetHintsResponse& get_hints_response) {
+  ALLOW_UNUSED_LOCAL(hint_cache_);
+  return false;
+}
+
+}  // namespace previews
diff --git a/components/previews/content/hints_fetcher.h b/components/previews/content/hints_fetcher.h
new file mode 100644
index 0000000..37c4923
--- /dev/null
+++ b/components/previews/content/hints_fetcher.h
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREVIEWS_CONTENT_HINTS_FETCHER_H_
+#define COMPONENTS_PREVIEWS_CONTENT_HINTS_FETCHER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "components/optimization_guide/proto/hints.pb.h"
+#include "components/previews/core/previews_experiments.h"
+#include "url/gurl.h"
+
+namespace previews {
+
+class HintCache;
+
+// A class to handle OnePlatform client requests for optimization hints from
+// the remote Optimization Guide Service.
+//
+// When Chrome starts up, if the client's OnePlatform hints have not been
+// updated in over day, this class will be triggered to fetch new hints from the
+// remote Optimization Guide Service. Owner must ensure that |hint_cache|
+// remains alive for the lifetime of |HintsFetcher|.
+class HintsFetcher {
+ public:
+  explicit HintsFetcher(HintCache* hint_cache);
+  ~HintsFetcher();
+
+  // Handles any configuration or checking needed and then
+  // initiates the fetch of OnePlatform Hints.
+  void FetchHintsForHosts(const std::vector<std::string>& hosts);
+
+ private:
+  // Creates the GetHintsResponse proto and executes the SimpleURLLoader request
+  // to the remote Optimization Guide Service with the |get_hints_request_|.
+  void GetRemoteHints(const std::vector<std::string>& hosts);
+
+  // Handles the response from the remote Optimization Guide Service.
+  // |response| is the response body, |status| is the
+  // |net::Error| of the response, and response_code is the HTTP
+  // response code (if available).
+  void HandleResponse(const std::string& response,
+                      int status,
+                      int response_code);
+
+  // URL loader completion callback.
+  void OnURLLoadComplete(std::unique_ptr<std::string> response_body);
+
+  // Parses the hints component of |get_hints_response| and applies it to
+  // |hints_|. Returns true if |get_hints_response| was successfully
+  // parsed and applied.
+  bool ParseGetHintsResponseAndApplyHints(
+      const optimization_guide::proto::GetHintsResponse& get_hints_response);
+
+  // Used to hold the GetHintsRequest being constructed and sent as a remote
+  // request.
+  std::unique_ptr<optimization_guide::proto::GetHintsRequest>
+      get_hints_request_;
+
+  // The URL for the remote Optimization Guide Service.
+  const GURL oneplatform_service_url_;
+
+  // The caller must ensure that the |hints_| outlives this instance.
+  HintCache* hint_cache_ = nullptr;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(HintsFetcher);
+};
+
+}  // namespace previews
+
+#endif  // COMPONENTS_PREVIEWS_CONTENT_HINTS_FETCHER_H_
diff --git a/components/previews/content/previews_decider_impl.cc b/components/previews/content/previews_decider_impl.cc
index d5de54e..62109e3 100644
--- a/components/previews/content/previews_decider_impl.cc
+++ b/components/previews/content/previews_decider_impl.cc
@@ -184,9 +184,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::Time time =
       previews_black_list_->AddPreviewNavigation(url, opt_out, type);
-  if (opt_out) {
-    last_opt_out_time_ = time;
-  }
   LogPreviewNavigation(url, opt_out, type, time, page_id);
 }
 
@@ -253,29 +250,20 @@
   if (url.has_username() || url.has_password())
     return PreviewsEligibilityReason::URL_HAS_BASIC_AUTH;
 
-  // Trigger the USER_RECENTLY_OPTED_OUT rule when a reload on a preview has
-  // occurred recently.
-  if (recent_preview_reload_time_ &&
-      recent_preview_reload_time_.value() + params::SingleOptOutDuration() >
-          clock_->Now()) {
-    return PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT;
-  }
+  // Skip blacklist checks if the blacklist is ignored.
+  if (!blacklist_ignored_) {
+    if (!previews_black_list_)
+      return PreviewsEligibilityReason::BLACKLIST_UNAVAILABLE;
+    passed_reasons->push_back(PreviewsEligibilityReason::BLACKLIST_UNAVAILABLE);
 
-  // In the case that the user has chosen to ignore the normal blacklist rules
-  // (flags or interventions-internals), a preview should still not be served
-  // for 5 seconds after the last opt out. This allows "show original" to
-  // function correctly as the start of that navigation will be within 5 seconds
-  // (we don't yet re-evaluate on redirects, so this is sufficient).
-  if (blacklist_ignored_) {
-    if (clock_->Now() < last_opt_out_time_ + base::TimeDelta::FromSeconds(5)) {
+    // Trigger the USER_RECENTLY_OPTED_OUT rule when a reload on a preview has
+    // occurred recently. No need to push_back the eligibility reason as it will
+    // be added in IsLoadedAndAllowed as the first check.
+    if (recent_preview_reload_time_ &&
+        recent_preview_reload_time_.value() + params::SingleOptOutDuration() >
+            clock_->Now()) {
       return PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT;
     }
-    passed_reasons->push_back(
-        PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT);
-  } else if (!previews_black_list_) {
-    return PreviewsEligibilityReason::BLACKLIST_UNAVAILABLE;
-  } else {
-    passed_reasons->push_back(PreviewsEligibilityReason::BLACKLIST_UNAVAILABLE);
 
     // The blacklist will disallow certain hosts for periods of time based on
     // user's opting out of the preview.
diff --git a/components/previews/content/previews_decider_impl.h b/components/previews/content/previews_decider_impl.h
index 89581818..11595db 100644
--- a/components/previews/content/previews_decider_impl.h
+++ b/components/previews/content/previews_decider_impl.h
@@ -185,10 +185,6 @@
 
   std::unique_ptr<PreviewsBlackList> previews_black_list_;
 
-  // Only used when the blacklist has been disabled to allow "Show Original" to
-  // function as expected. The time of the most recent opt out event.
-  base::Time last_opt_out_time_;
-
   // Holds optimization guidance from the server.
   std::unique_ptr<PreviewsOptimizationGuide> previews_opt_guide_;
 
diff --git a/components/previews/content/previews_decider_impl_unittest.cc b/components/previews/content/previews_decider_impl_unittest.cc
index 8e811df..169906d 100644
--- a/components/previews/content/previews_decider_impl_unittest.cc
+++ b/components/previews/content/previews_decider_impl_unittest.cc
@@ -36,6 +36,7 @@
 #include "components/blacklist/opt_out_blacklist/opt_out_blacklist_item.h"
 #include "components/blacklist/opt_out_blacklist/opt_out_store.h"
 #include "components/optimization_guide/optimization_guide_service.h"
+#include "components/previews/content/previews_top_host_provider.h"
 #include "components/previews/content/previews_ui_service.h"
 #include "components/previews/content/previews_user_data.h"
 #include "components/previews/core/previews_black_list.h"
@@ -129,6 +130,17 @@
   PreviewsEligibilityReason status_;
 };
 
+// A test class implementation to enable testing of previews_decider_impl.
+class TestPreviewsTopHostProvider : public PreviewsTopHostProvider {
+ public:
+  TestPreviewsTopHostProvider() {}
+  ~TestPreviewsTopHostProvider() override {}
+
+  std::vector<std::string> GetTopHosts(size_t max_sites) const override {
+    return std::vector<std::string>();
+  }
+};
+
 // Stub class of PreviewsOptimizationGuide to control IsWhitelisted and
 // IsBlacklisted outcomes when testing PreviewsDeciderImpl.
 class TestPreviewsOptimizationGuide : public PreviewsOptimizationGuide {
@@ -136,10 +148,12 @@
   TestPreviewsOptimizationGuide(
       optimization_guide::OptimizationGuideService* optimization_guide_service,
       const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
-      const base::FilePath& test_path)
+      const base::FilePath& test_path,
+      PreviewsTopHostProvider* previews_top_host_provider)
       : PreviewsOptimizationGuide(optimization_guide_service,
                                   ui_task_runner,
-                                  test_path) {}
+                                  test_path,
+                                  previews_top_host_provider) {}
   ~TestPreviewsOptimizationGuide() override {}
 
   // PreviewsOptimizationGuide:
@@ -383,7 +397,7 @@
         std::make_unique<TestPreviewsOptimizationGuide>(
             &optimization_guide_service_,
             scoped_task_environment_.GetMainThreadTaskRunner(),
-            temp_dir_.GetPath()),
+            temp_dir_.GetPath(), &previews_top_host_provider_),
         base::BindRepeating(&IsPreviewFieldTrialEnabled),
         std::make_unique<PreviewsLogger>(), std::move(allowed_types),
         &network_quality_tracker_));
@@ -416,6 +430,7 @@
   base::FieldTrialList field_trial_list_;
   TestPreviewsDeciderImpl* previews_decider_impl_;
   optimization_guide::OptimizationGuideService optimization_guide_service_;
+  TestPreviewsTopHostProvider previews_top_host_provider_;
   std::unique_ptr<TestPreviewsUIService> ui_service_;
   network::TestNetworkQualityTracker network_quality_tracker_;
 };
@@ -1501,7 +1516,7 @@
   }
 }
 
-TEST_F(PreviewsDeciderImplTest, IgnoreFlagStillHasFiveSecondRule) {
+TEST_F(PreviewsDeciderImplTest, IgnoreFlagDoesNotCheckBlacklist) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       {features::kPreviews, features::kClientLoFi}, {});
@@ -1514,21 +1529,10 @@
   EXPECT_TRUE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
       &user_data, GURL("https://www.google.com"), false, PreviewsType::LOFI));
 
-  previews_decider_impl()->AddPreviewNavigation(
-      GURL("http://wwww.somedomain.com"), true, PreviewsType::LOFI, 1);
-
-  EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
-      &user_data, GURL("https://www.google.com"), false, PreviewsType::LOFI));
-  EXPECT_EQ(PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT,
-            ui_service()->decision_reasons().back());
-
-  clock_.Advance(base::TimeDelta::FromSeconds(6));
+  previews_decider_impl()->AddPreviewReload();
 
   EXPECT_TRUE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
       &user_data, GURL("https://www.google.com"), false, PreviewsType::LOFI));
-  EXPECT_THAT(
-      ui_service()->decision_passed_reasons().back(),
-      ::testing::Contains(PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT));
 }
 
 TEST_F(PreviewsDeciderImplTest, ReloadsTriggerFiveMinuteRule) {
diff --git a/components/previews/content/previews_optimization_guide.cc b/components/previews/content/previews_optimization_guide.cc
index b9da086..5d0dd359 100644
--- a/components/previews/content/previews_optimization_guide.cc
+++ b/components/previews/content/previews_optimization_guide.cc
@@ -8,15 +8,17 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_macros_local.h"
 #include "base/task/post_task.h"
 #include "base/task_runner_util.h"
 #include "components/optimization_guide/hints_component_info.h"
 #include "components/optimization_guide/optimization_guide_service.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/previews/content/hint_cache_leveldb_store.h"
+#include "components/previews/content/hints_fetcher.h"
 #include "components/previews/content/previews_hints.h"
 #include "components/previews/content/previews_hints_util.h"
+#include "components/previews/content/previews_top_host_provider.h"
 #include "components/previews/content/previews_user_data.h"
 #include "components/previews/core/previews_constants.h"
 #include "components/previews/core/previews_switches.h"
@@ -81,7 +83,8 @@
 PreviewsOptimizationGuide::PreviewsOptimizationGuide(
     optimization_guide::OptimizationGuideService* optimization_guide_service,
     const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
-    const base::FilePath& profile_path)
+    const base::FilePath& profile_path,
+    PreviewsTopHostProvider* previews_top_host_provider)
     : optimization_guide_service_(optimization_guide_service),
       ui_task_runner_(ui_task_runner),
       background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
@@ -89,6 +92,7 @@
       hint_cache_(std::make_unique<HintCache>(
           std::make_unique<HintCacheLevelDBStore>(profile_path,
                                                   background_task_runner_))),
+      previews_top_host_provider_(previews_top_host_provider),
       ui_weak_ptr_factory_(this) {
   DCHECK(optimization_guide_service_);
   hint_cache_->Initialize(
@@ -216,6 +220,21 @@
                     hint_cache_->MaybeCreateComponentUpdateData(
                         base::Version(kManualConfigComponentVersion))));
   }
+
+  // If user is eligible for platform hints, currently controlled by a feature
+  // flag |kPreviewsOnePlatformHints|, start the OnePlatform client request.
+  // TODO(mcrouse): Add a check for user specific state in addition to the
+  // feature state:
+  // (1) Data saver should be enabled
+  // (2) Infobar notification does not need to be shown to the user.
+
+  if (previews::params::IsOnePlatformHintsEnabled()) {
+    // TODO(mcrouse): We will likely need to an async call and likely
+    // within a timer that will call GetOnePlatformClientHints().
+    // This is a temporary call for testing.
+    GetOnePlatformClientHints();
+  }
+
   // Register as an observer regardless of hint proto override usage. This is
   // needed as a signal during testing.
   optimization_guide_service_->AddObserver(this);
@@ -248,6 +267,28 @@
                      std::move(next_update_closure_)));
 }
 
+void PreviewsOptimizationGuide::GetOnePlatformClientHints() {
+  std::vector<std::string> top_hosts = previews_top_host_provider_->GetTopHosts(
+      previews::params::MaxOnePlatformUpdateHosts());
+  DCHECK_GE(previews::params::MaxOnePlatformUpdateHosts(), top_hosts.size());
+
+  LOCAL_HISTOGRAM_COUNTS_100("Previews.HintsFetcher.GetHintsRequest.HostCount",
+                             top_hosts.size());
+
+  if (!hintsfetcher_) {
+    hintsfetcher_ = std::make_unique<HintsFetcher>(hint_cache_.get());
+  }
+
+  hintsfetcher_->FetchHintsForHosts(top_hosts);
+
+  // TODO(mcrouse) to build SimpleURLLoader to perform request from service
+  // for per-user client hints.
+  // Pass callback for when URLLoader request is successful to call
+  // PreviewOptimizationGuide::OnOnePlatformClientHintsReceived().
+
+  OnOnePlatformHintsReceived();
+}
+
 void PreviewsOptimizationGuide::UpdateHints(
     base::OnceClosure update_closure,
     std::unique_ptr<PreviewsHints> hints) {
@@ -284,4 +325,9 @@
   next_update_closure_ = std::move(next_update_closure);
 }
 
+void PreviewsOptimizationGuide::OnOnePlatformHintsReceived() {
+  // TODO(mcrouse):  Once hints reseponse received from server, will need to
+  // update the cache and store.
+}
+
 }  // namespace previews
diff --git a/components/previews/content/previews_optimization_guide.h b/components/previews/content/previews_optimization_guide.h
index d001eed..22c6861 100644
--- a/components/previews/content/previews_optimization_guide.h
+++ b/components/previews/content/previews_optimization_guide.h
@@ -33,7 +33,9 @@
 
 namespace previews {
 
+class HintsFetcher;
 class PreviewsHints;
+class PreviewsTopHostProvider;
 class PreviewsUserData;
 
 // A Previews optimization guide that makes decisions guided by hints received
@@ -42,10 +44,12 @@
     : public optimization_guide::OptimizationGuideServiceObserver {
  public:
   // The embedder guarantees |optimization_guide_service| outlives |this|.
+  // The embedder guarantees that |previews_top_host_provider_| outlives |this|.
   PreviewsOptimizationGuide(
       optimization_guide::OptimizationGuideService* optimization_guide_service,
       const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
-      const base::FilePath& profile_path);
+      const base::FilePath& profile_path,
+      PreviewsTopHostProvider* previews_top_host_provider);
 
   ~PreviewsOptimizationGuide() override;
 
@@ -116,6 +120,15 @@
                     const GURL& document_url,
                     const optimization_guide::proto::Hint* loaded_hint) const;
 
+  // Method to request OnePlatform client hints for user's sites with top
+  // engagement scores and creates a remote request using |hints_fetcher_| On
+  // request success OnOnePlatformHintsReceived callback will be called.
+  void GetOnePlatformClientHints();
+
+  // Called when the response from the OnePlatform Guide Service is handled and
+  // stored by the |hints_fetcher_|. received.
+  void OnOnePlatformHintsReceived();
+
   // The OptimizationGuideService that this guide is listening to. Not owned.
   optimization_guide::OptimizationGuideService* optimization_guide_service_;
 
@@ -137,6 +150,13 @@
   // Used in testing to subscribe to an update event in this class.
   base::OnceClosure next_update_closure_;
 
+  // HintsFetcher handles the request to update Hints from OnePlatform Guide
+  // Service.
+  std::unique_ptr<HintsFetcher> hintsfetcher_;
+
+  // TopHostProvider that this guide can query. Not owned.
+  PreviewsTopHostProvider* previews_top_host_provider_ = nullptr;
+
   // Used to get |weak_ptr_| to self on the UI thread.
   base::WeakPtrFactory<PreviewsOptimizationGuide> ui_weak_ptr_factory_;
 
diff --git a/components/previews/content/previews_optimization_guide_unittest.cc b/components/previews/content/previews_optimization_guide_unittest.cc
index f43f55c7..9cff687 100644
--- a/components/previews/content/previews_optimization_guide_unittest.cc
+++ b/components/previews/content/previews_optimization_guide_unittest.cc
@@ -24,6 +24,7 @@
 #include "components/optimization_guide/hints_component_info.h"
 #include "components/optimization_guide/optimization_guide_service.h"
 #include "components/optimization_guide/proto/hints.pb.h"
+#include "components/previews/content/previews_top_host_provider.h"
 #include "components/previews/content/previews_user_data.h"
 #include "components/previews/core/bloom_filter.h"
 #include "components/previews/core/previews_experiments.h"
@@ -66,6 +67,17 @@
   bool remove_observer_called_;
 };
 
+// A test class implementation for unit testing previews_optimization_guide.
+class TestPreviewsTopHostProvider : public PreviewsTopHostProvider {
+ public:
+  TestPreviewsTopHostProvider() {}
+  ~TestPreviewsTopHostProvider() override {}
+
+  std::vector<std::string> GetTopHosts(size_t max_sites) const override {
+    return std::vector<std::string>();
+  }
+};
+
 class PreviewsOptimizationGuideTest : public testing::Test {
  public:
   PreviewsOptimizationGuideTest() {}
@@ -105,7 +117,9 @@
             scoped_task_environment_.GetMainThreadTaskRunner());
     guide_ = std::make_unique<PreviewsOptimizationGuide>(
         optimization_guide_service_.get(),
-        scoped_task_environment_.GetMainThreadTaskRunner(), temp_dir());
+        scoped_task_environment_.GetMainThreadTaskRunner(), temp_dir(),
+        previews_top_host_provider_.get());
+
     // Add observer is called after the HintCache is fully initialized,
     // indicating that the PreviewsOptimizationGuide is ready to process hints.
     while (!optimization_guide_service_->AddObserverCalled()) {
@@ -177,6 +191,7 @@
 
   std::unique_ptr<PreviewsOptimizationGuide> guide_;
   std::unique_ptr<TestOptimizationGuideService> optimization_guide_service_;
+  std::unique_ptr<TestPreviewsTopHostProvider> previews_top_host_provider_;
 
   // Flag set when the OnLoadOptimizationHints callback runs. This indicates
   // that MaybeLoadOptimizationHints() has completed its processing.
diff --git a/components/previews/content/previews_top_host_provider.h b/components/previews/content/previews_top_host_provider.h
new file mode 100644
index 0000000..d4c20c02
--- /dev/null
+++ b/components/previews/content/previews_top_host_provider.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREVIEWS_CONTENT_PREVIEWS_TOP_HOST_PROVIDER_H_
+#define COMPONENTS_PREVIEWS_CONTENT_PREVIEWS_TOP_HOST_PROVIDER_H_
+
+#include <string>
+#include <vector>
+
+namespace previews {
+
+// A class to handle querying for the top hosts for a user.
+class PreviewsTopHostProvider {
+ public:
+  // Returns a vector of at most |max_sites| top hosts, the order of hosts is
+  // not guaranteed.
+  virtual std::vector<std::string> GetTopHosts(size_t max_sites) const = 0;
+
+ protected:
+  PreviewsTopHostProvider() {}
+  virtual ~PreviewsTopHostProvider() {}
+};
+
+}  // namespace previews
+
+#endif  // COMPONENTS_PREVIEWS_CONTENT_PREVIEWS_TOP_HOST_PROVIDER_H_
diff --git a/components/previews/core/previews_experiments.cc b/components/previews/core/previews_experiments.cc
index b31b9bf..cc909ec 100644
--- a/components/previews/core/previews_experiments.cc
+++ b/components/previews/core/previews_experiments.cc
@@ -111,6 +111,11 @@
                               "max_hosts_in_blacklist", 100);
 }
 
+size_t MaxOnePlatformUpdateHosts() {
+  return GetFieldTrialParamByFeatureAsInt(features::kPreviewsOnePlatformHints,
+                                          "max_oneplatform_update_hosts", 30);
+}
+
 int PerHostBlackListOptOutThreshold() {
   return GetParamValueAsInt(kClientSidePreviewsFieldTrial,
                             "per_host_opt_out_threshold", 2);
@@ -327,6 +332,10 @@
   return base::FeatureList::IsEnabled(features::kOptimizationHints);
 }
 
+bool IsOnePlatformHintsEnabled() {
+  return base::FeatureList::IsEnabled(features::kPreviewsOnePlatformHints);
+}
+
 int NoScriptPreviewsInflationPercent() {
   // The default value was determined from lab experiment data of whitelisted
   // URLs. It may be improved once there is enough UKM live experiment data
diff --git a/components/previews/core/previews_experiments.h b/components/previews/core/previews_experiments.h
index 406233b08..378e7abd 100644
--- a/components/previews/core/previews_experiments.h
+++ b/components/previews/core/previews_experiments.h
@@ -68,6 +68,10 @@
 // The maximum number of hosts allowed in the in memory black list.
 size_t MaxInMemoryHostsInBlackList();
 
+// The maximum number of hosts requested by the client to the OnePlatform
+// Service.
+size_t MaxOnePlatformUpdateHosts();
+
 // The number of recent navigations that were opted out of for a given host that
 // would trigger that host to be blacklisted.
 int PerHostBlackListOptOutThreshold();
@@ -167,6 +171,10 @@
 // Whether server optimization hints are enabled.
 bool IsOptimizationHintsEnabled();
 
+// Returns true if the feature to fetch user-specific hints using
+// the OnePlatform API is enabled.
+bool IsOnePlatformHintsEnabled();
+
 // For estimating NoScript data savings, this is the percentage factor to
 // multiple by the network bytes for inflating the original_bytes count.
 int NoScriptPreviewsInflationPercent();
diff --git a/components/previews/core/previews_features.cc b/components/previews/core/previews_features.cc
index fb9f57c9..4e8ce0e 100644
--- a/components/previews/core/previews_features.cc
+++ b/components/previews/core/previews_features.cc
@@ -110,5 +110,9 @@
 const base::Feature kPreviewsReloadsAreSoftOptOuts{
     "PreviewsReloadsAreSoftOptOuts", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables using the OnePlatform Client Hints requests.
+const base::Feature kPreviewsOnePlatformHints{
+    "PreviewsOnePlatformHints", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace previews
diff --git a/components/previews/core/previews_features.h b/components/previews/core/previews_features.h
index 9a7a266..7da9ed7 100644
--- a/components/previews/core/previews_features.h
+++ b/components/previews/core/previews_features.h
@@ -25,6 +25,7 @@
 extern const base::Feature kHTTPSServerPreviewsUsingURLLoader;
 extern const base::Feature kDataSaverLiteModeRebranding;
 extern const base::Feature kPreviewsReloadsAreSoftOptOuts;
+extern const base::Feature kPreviewsOnePlatformHints;
 
 }  // namespace features
 }  // namespace previews
diff --git a/components/safe_browsing/db/v4_update_protocol_manager.cc b/components/safe_browsing/db/v4_update_protocol_manager.cc
index 5067d1f..f4f951b6 100644
--- a/components/safe_browsing/db/v4_update_protocol_manager.cc
+++ b/components/safe_browsing/db/v4_update_protocol_manager.cc
@@ -21,6 +21,10 @@
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 
+#if !defined(FULL_SAFE_BROWSING)
+#include "base/system/sys_info.h"
+#endif
+
 using base::Time;
 using base::TimeDelta;
 
@@ -78,6 +82,15 @@
 // Maximum time, in seconds, to wait for a response to an update request.
 static const int kV4TimerUpdateWaitSecMax = 15 * 60;  // 15 minutes
 
+// The default number of entries, per threat type, in the safe browsing
+// database on low end (low RAM) devices. This value is also used as the default
+// for GMS Safe Browsing on low end devices.
+static const int kLowEndDefaultDBEntryCount = 1 << 10;
+
+// Malware threat DB coverage drops off too much below 4096 entries, so we use
+// this values instead of the default above.
+static const int kLowEndMalwareDBEntryCount = 1 << 12;
+
 ChromeClientInfo::SafeBrowsingReportingPopulation GetReportingLevelProtoValue(
     ExtendedReportingLevel reporting_level) {
   switch (reporting_level) {
@@ -243,6 +256,13 @@
     list_update_request->mutable_constraints()->add_supported_compressions(RAW);
     list_update_request->mutable_constraints()->add_supported_compressions(
         RICE);
+
+#if !defined(FULL_SAFE_BROWSING)
+    if (base::SysInfo::IsLowEndDevice()) {
+      list_update_request->mutable_constraints()->set_max_database_entries(
+          GetLowEndDBEntryCount(list_update_request->threat_type()));
+    }
+#endif
   }
 
   if (!extended_reporting_level_callback_.is_null()) {
@@ -261,6 +281,15 @@
   return req_base64;
 }
 
+int V4UpdateProtocolManager::GetLowEndDBEntryCount(ThreatType threat_type) {
+  switch (threat_type) {
+    case ThreatType::MALWARE_THREAT:
+      return kLowEndMalwareDBEntryCount;
+    default:
+      return kLowEndDefaultDBEntryCount;
+  }
+}
+
 bool V4UpdateProtocolManager::ParseUpdateResponse(
     const std::string& data,
     ParsedServerResponse* parsed_server_response) {
diff --git a/components/safe_browsing/db/v4_update_protocol_manager.h b/components/safe_browsing/db/v4_update_protocol_manager.h
index dc54a89..b0eba38 100644
--- a/components/safe_browsing/db/v4_update_protocol_manager.h
+++ b/components/safe_browsing/db/v4_update_protocol_manager.h
@@ -153,6 +153,10 @@
   // Get the next update interval, considering whether we are in backoff.
   base::TimeDelta GetNextUpdateInterval(bool back_off);
 
+  // Returns the entry count to be used for the DB for the threat type. Should
+  // only be used on low end devices (with very little RAM).
+  int GetLowEndDBEntryCount(ThreatType threat_type);
+
   // The factory that controls the creation of V4UpdateProtocolManager.
   // This is used by tests.
   static V4UpdateProtocolManagerFactory* factory_;
diff --git a/components/safe_browsing/db/v4_update_protocol_manager_unittest.cc b/components/safe_browsing/db/v4_update_protocol_manager_unittest.cc
index 55c967e3..07445051 100644
--- a/components/safe_browsing/db/v4_update_protocol_manager_unittest.cc
+++ b/components/safe_browsing/db/v4_update_protocol_manager_unittest.cc
@@ -282,8 +282,15 @@
 
   std::string encoded_request_with_minus =
       pm->GetBase64SerializedUpdateRequestProto();
-  EXPECT_EQ("Cg8KCHVuaXR0ZXN0EgMxLjAaGAgBEAIaCmg4eGZZcVk-OlIiBCABIAIoASICCAE=",
-            encoded_request_with_minus);
+
+  const std::string expected =
+#if defined(FULL_SAFE_BROWSING)
+      "Cg8KCHVuaXR0ZXN0EgMxLjAaGAgBEAIaCmg4eGZZcVk-OlIiBCABIAIoASICCAE=";
+#else
+      "Cg8KCHVuaXR0ZXN0EgMxLjAaGwgBEAIaCmg4eGZZcVk-OlIiBxCAICABIAIoASICCAE=";
+#endif
+
+  EXPECT_EQ(expected, encoded_request_with_minus);
 
   // TODO(vakh): Add a similar test for underscore for completeness, although
   // the '-' case is sufficient to prove that we are using URL encoding.
@@ -346,7 +353,12 @@
   store_state_map_->clear();
   (*store_state_map_)[ListIdentifier(LINUX_PLATFORM, URL, MALWARE_THREAT)] =
       "state";
-  std::string base = "Cg8KCHVuaXR0ZXN0EgMxLjAaEwgBEAIaBXN0YXRlIgQgASACKAEiAgg";
+  const std::string base =
+#if defined(FULL_SAFE_BROWSING)
+      "Cg8KCHVuaXR0ZXN0EgMxLjAaEwgBEAIaBXN0YXRlIgQgASACKAEiAgg";
+#else
+      "Cg8KCHVuaXR0ZXN0EgMxLjAaFggBEAIaBXN0YXRlIgcQgCAgASACKAEiAgg";
+#endif
 
   std::unique_ptr<V4UpdateProtocolManager> pm_with_off(CreateProtocolManager(
       std::vector<ListUpdateResponse>({}), false, SBER_LEVEL_OFF));
diff --git a/components/security_state/content/content_utils.cc b/components/security_state/content/content_utils.cc
index dfda31fb..55878368 100644
--- a/components/security_state/content/content_utils.cc
+++ b/components/security_state/content/content_utils.cc
@@ -64,7 +64,7 @@
   if (security_info.security_level == security_state::DANGEROUS &&
       !security_info.scheme_is_cryptographic) {
     security_style_explanations->summary =
-        l10n_util::GetStringUTF8(IDS_EDITED_NONSECURE_SUMMARY);
+        l10n_util::GetStringUTF8(IDS_HTTP_NONSECURE_SUMMARY);
     if (security_info.insecure_input_events.insecure_field_edited) {
       security_style_explanations->insecure_explanations.push_back(
           content::SecurityStyleExplanation(
diff --git a/components/security_state_strings.grdp b/components/security_state_strings.grdp
index 75f1f9f..86284919 100644
--- a/components/security_state_strings.grdp
+++ b/components/security_state_strings.grdp
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
   <!-- Strings describing Chrome security policy for DevTools security panel -->
-  <message name="IDS_EDITED_NONSECURE_SUMMARY" desc="Main summary for an HTTP page where a user has entered data in a form field." translateable="false">
+  <message name="IDS_HTTP_NONSECURE_SUMMARY" desc="Main summary for where the site is non-secure HTTP." translateable="false">
     This page is insecure (unencrypted HTTP).
   </message>
   <message name="IDS_EDITED_NONSECURE" desc="Summary phrase for a security problem where the site is non-secure (HTTP) and user has entered data in a form field." translateable="false">
diff --git a/components/send_tab_to_self/OWNERS b/components/send_tab_to_self/OWNERS
index 3f65592..cbbc50e6 100644
--- a/components/send_tab_to_self/OWNERS
+++ b/components/send_tab_to_self/OWNERS
@@ -1,7 +1,5 @@
 hansberry@chromium.org
 jeffreycohen@chromium.org
 sebsg@chromium.org
-tgupta@chromium.org
-tinazwang@chromium.org
 
 # COMPONENT: UI>Browser>Sharing
diff --git a/components/services/heap_profiling/public/cpp/settings.cc b/components/services/heap_profiling/public/cpp/settings.cc
index 6a9d01f..9a454a4 100644
--- a/components/services/heap_profiling/public/cpp/settings.cc
+++ b/components/services/heap_profiling/public/cpp/settings.cc
@@ -30,10 +30,6 @@
 const bool kDefaultInProcessMode = false;
 
 bool RecordAllAllocationsForStartup() {
-  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  if (cmdline->HasSwitch(kMemlogSampling))
-    return false;
-
   return !base::GetFieldTrialParamByFeatureAsBool(
       kOOPHeapProfilingFeature, kOOPHeapProfilingFeatureSampling,
       kDefaultShouldSample);
diff --git a/components/services/heap_profiling/public/cpp/switches.cc b/components/services/heap_profiling/public/cpp/switches.cc
index 1e9a82c7..0079282 100644
--- a/components/services/heap_profiling/public/cpp/switches.cc
+++ b/components/services/heap_profiling/public/cpp/switches.cc
@@ -18,7 +18,6 @@
 const char kMemlogModeRendererSampling[] = "renderer-sampling";
 const char kMemlogModeUtilityAndBrowser[] = "utility-and-browser";
 const char kMemlogModeUtilitySampling[] = "utility-sampling";
-const char kMemlogSampling[] = "memlog-sampling";
 const char kMemlogSamplingRate[] = "memlog-sampling-rate";
 const char kMemlogStackMode[] = "memlog-stack-mode";
 const char kMemlogStackModeMixed[] = "mixed";
diff --git a/components/services/heap_profiling/public/cpp/switches.h b/components/services/heap_profiling/public/cpp/switches.h
index e304ffb9..a61a089f 100644
--- a/components/services/heap_profiling/public/cpp/switches.h
+++ b/components/services/heap_profiling/public/cpp/switches.h
@@ -19,7 +19,6 @@
 extern const char kMemlogModeRendererSampling[];
 extern const char kMemlogModeUtilityAndBrowser[];
 extern const char kMemlogModeUtilitySampling[];
-extern const char kMemlogSampling[];
 extern const char kMemlogSamplingRate[];
 extern const char kMemlogStackMode[];
 extern const char kMemlogStackModeMixed[];
diff --git a/components/signin/core/browser/BUILD.gn b/components/signin/core/browser/BUILD.gn
index c6ea7639..e5dedd5 100644
--- a/components/signin/core/browser/BUILD.gn
+++ b/components/signin/core/browser/BUILD.gn
@@ -220,8 +220,6 @@
   sources = [
     "fake_profile_oauth2_token_service.cc",
     "fake_profile_oauth2_token_service.h",
-    "test_image_decoder.cc",
-    "test_image_decoder.h",
 
     # TODO(https://crbug.com/907782): Move list_accounts_test_utils to
     # //services/identity/public/cpp once FakeGCMS no longer depends on it.
@@ -233,10 +231,8 @@
 
   deps = [
     "//base/test:test_support",
-    "//components/image_fetcher/core",
     "//components/prefs",
     "//google_apis:test_support",
-    "//ui/gfx:test_support",
   ]
 
   public_deps = [
diff --git a/components/signin/core/browser/account_tracker_service_unittest.cc b/components/signin/core/browser/account_tracker_service_unittest.cc
index 885ab88..a9e42f19 100644
--- a/components/signin/core/browser/account_tracker_service_unittest.cc
+++ b/components/signin/core/browser/account_tracker_service_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
+#include "components/image_fetcher/core/fake_image_decoder.h"
 #include "components/image_fetcher/core/image_data_fetcher.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/scoped_user_pref_update.h"
@@ -22,7 +23,6 @@
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/avatar_icon_util.h"
 #include "components/signin/core/browser/signin_pref_names.h"
-#include "components/signin/core/browser/test_image_decoder.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "google_apis/gaia/fake_oauth2_token_service.h"
 #include "google_apis/gaia/gaia_oauth_client.h"
@@ -356,9 +356,9 @@
     account_tracker_->AddObserver(&observer_);
 
     account_tracker_->Initialize(&pref_service_, std::move(path));
-    account_fetcher_->Initialize(signin_client(), token_service(),
-                                 account_tracker_.get(),
-                                 std::make_unique<TestImageDecoder>());
+    account_fetcher_->Initialize(
+        signin_client(), token_service(), account_tracker_.get(),
+        std::make_unique<image_fetcher::FakeImageDecoder>());
     if (network_enabled) {
       account_fetcher_->EnableNetworkFetchesForTest();
     }
diff --git a/components/signin/core/browser/oauth2_token_service_delegate_android.cc b/components/signin/core/browser/oauth2_token_service_delegate_android.cc
index b242742f..4f1df012 100644
--- a/components/signin/core/browser/oauth2_token_service_delegate_android.cc
+++ b/components/signin/core/browser/oauth2_token_service_delegate_android.cc
@@ -485,7 +485,8 @@
     const std::string& primary_account_id) {
   DCHECK_EQ(LOAD_CREDENTIALS_NOT_STARTED, load_credentials_state());
   set_load_credentials_state(LOAD_CREDENTIALS_IN_PROGRESS);
-  if (primary_account_id.empty()) {
+  if (primary_account_id.empty() &&
+      !base::FeatureList::IsEnabled(signin::kMiceFeature)) {
     FireRefreshTokensLoaded();
     return;
   }
diff --git a/components/signin/core/browser/signin_manager_unittest.cc b/components/signin/core/browser/signin_manager_unittest.cc
index 1751a43d..255182d 100644
--- a/components/signin/core/browser/signin_manager_unittest.cc
+++ b/components/signin/core/browser/signin_manager_unittest.cc
@@ -14,17 +14,18 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
+#include "components/image_fetcher/core/fake_image_decoder.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/signin/core/browser/account_consistency_method.h"
+#include "components/signin/core/browser/account_fetcher_service.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/device_id_helper.h"
 #include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_pref_names.h"
-#include "components/signin/core/browser/test_image_decoder.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "google_apis/gaia/fake_oauth2_token_service_delegate.h"
@@ -79,9 +80,9 @@
     SigninManagerBase::RegisterProfilePrefs(user_prefs_.registry());
     SigninManagerBase::RegisterPrefs(local_state_.registry());
     account_tracker_.Initialize(&user_prefs_, base::FilePath());
-    account_fetcher_.Initialize(&test_signin_client_, &token_service_,
-                                &account_tracker_,
-                                std::make_unique<TestImageDecoder>());
+    account_fetcher_.Initialize(
+        &test_signin_client_, &token_service_, &account_tracker_,
+        std::make_unique<image_fetcher::FakeImageDecoder>());
   }
 
   ~SigninManagerTest() override {
diff --git a/components/signin/core/browser/test_image_decoder.cc b/components/signin/core/browser/test_image_decoder.cc
deleted file mode 100644
index 225b413..0000000
--- a/components/signin/core/browser/test_image_decoder.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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.
-
-#include "components/signin/core/browser/test_image_decoder.h"
-
-#include "base/values.h"
-#include "build/build_config.h"
-#include "components/signin/core/browser/account_tracker_service.h"
-#include "components/signin/core/browser/profile_oauth2_token_service.h"
-#include "ui/gfx/image/image_unittest_util.h"
-
-TestImageDecoder::TestImageDecoder() = default;
-
-TestImageDecoder::~TestImageDecoder() = default;
-
-void TestImageDecoder::DecodeImage(
-    const std::string& image_data,
-    const gfx::Size& desired_image_frame_size,
-    const image_fetcher::ImageDecodedCallback& callback) {
-  callback.Run(image_data.empty() ? gfx::Image()
-                                  : gfx::test::CreateImage(64, 64));
-}
diff --git a/components/signin/core/browser/test_image_decoder.h b/components/signin/core/browser/test_image_decoder.h
deleted file mode 100644
index 311099d..0000000
--- a/components/signin/core/browser/test_image_decoder.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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.
-
-#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_TEST_IMAGE_DECODER_H_
-#define COMPONENTS_SIGNIN_CORE_BROWSER_TEST_IMAGE_DECODER_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "build/build_config.h"
-#include "components/image_fetcher/core/image_decoder.h"
-#include "components/signin/core/browser/account_fetcher_service.h"
-
-// This dummy class implements |image_fetcher::ImageDecoder|, and is passed
-// as an argument to |AccountFetcherService::Initialize|.
-class TestImageDecoder : public image_fetcher::ImageDecoder {
- public:
-  TestImageDecoder();
-  ~TestImageDecoder() override;
-
-  // image_fetcher::Decoder implementation:
-
-  // If |image_data| is non-empty, a blank 64x64 image is passed to callback.
-  // Otherwise an empty image is passed.
-  void DecodeImage(
-      const std::string& image_data,
-      const gfx::Size& desired_image_frame_size,
-      const image_fetcher::ImageDecodedCallback& callback) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TestImageDecoder);
-};
-
-#endif  // COMPONENTS_SIGNIN_CORE_BROWSER_TEST_IMAGE_DECODER_H_
diff --git a/components/subresource_filter/content/browser/BUILD.gn b/components/subresource_filter/content/browser/BUILD.gn
index 712c2fa..2b7aa3b 100644
--- a/components/subresource_filter/content/browser/BUILD.gn
+++ b/components/subresource_filter/content/browser/BUILD.gn
@@ -44,7 +44,6 @@
     "//components/safe_browsing/db:database_manager",
     "//components/safe_browsing/db:util",
     "//components/subresource_filter/content/common",
-    "//components/subresource_filter/content/mojom",
     "//components/subresource_filter/core/browser",
     "//components/subresource_filter/core/common",
     "//components/subresource_filter/core/mojom",
@@ -56,6 +55,7 @@
     "//url",
   ]
   public_deps = [
+    "//components/subresource_filter/content/mojom",
     "//third_party/flatbuffers:flatbuffers",
     "//ui/base",
   ]
@@ -106,7 +106,6 @@
     "//components/prefs:test_support",
     "//components/safe_browsing/db:util",
     "//components/subresource_filter/content/common",
-    "//components/subresource_filter/content/mojom",
     "//components/subresource_filter/core/browser",
     "//components/subresource_filter/core/browser:test_support",
     "//components/subresource_filter/core/common",
@@ -118,4 +117,7 @@
     "//ipc:test_support",
     "//testing/gtest",
   ]
+  public_deps = [
+    "//components/subresource_filter/content/mojom",
+  ]
 }
diff --git a/components/sync/driver/data_type_manager_impl.cc b/components/sync/driver/data_type_manager_impl.cc
index f1adf21..9dc92262 100644
--- a/components/sync/driver/data_type_manager_impl.cc
+++ b/components/sync/driver/data_type_manager_impl.cc
@@ -100,17 +100,19 @@
 }
 
 void DataTypeManagerImpl::ReadyForStartChanged(ModelType type) {
-  const auto& dtc_iter = controllers_->find(type);
-  if (dtc_iter == controllers_->end())
+  if (!UpdateUnreadyTypeError(type)) {
+    // Nothing changed.
     return;
+  }
 
-  if (dtc_iter->second->ReadyForStart()) {
-    ForceReconfiguration();
-  } else {
+  if (data_type_status_table_.GetUnreadyErrorTypes().Has(type)) {
     model_association_manager_.StopDatatype(
         type, DISABLE_SYNC,
         SyncError(FROM_HERE, syncer::SyncError::UNREADY_ERROR,
                   "Data type is unready.", type));
+  } else if (last_requested_types_.Has(type)) {
+    // Only reconfigure if the type is both ready and desired.
+    ForceReconfiguration();
   }
 }
 
@@ -347,28 +349,36 @@
 void DataTypeManagerImpl::UpdateUnreadyTypeErrors(
     const ModelTypeSet& desired_types) {
   for (ModelType type : desired_types) {
-    const auto& iter = controllers_->find(type);
-    if (iter == controllers_->end())
-      continue;
-    const DataTypeController* dtc = iter->second.get();
-    bool unready_status =
-        data_type_status_table_.GetUnreadyErrorTypes().Has(type);
-    if (dtc->ReadyForStart() != (unready_status == false)) {
-      // Adjust data_type_status_table_ if unready state in it doesn't match
-      // DataTypeController::ReadyForStart().
-      if (dtc->ReadyForStart()) {
-        data_type_status_table_.ResetUnreadyErrorFor(type);
-      } else {
-        SyncError error(FROM_HERE, SyncError::UNREADY_ERROR,
-                        "Datatype not ready at config time.", type);
-        std::map<ModelType, SyncError> errors;
-        errors[type] = error;
-        data_type_status_table_.UpdateFailedDataTypes(errors);
-      }
-    }
+    UpdateUnreadyTypeError(type);
   }
 }
 
+bool DataTypeManagerImpl::UpdateUnreadyTypeError(ModelType type) {
+  const auto& iter = controllers_->find(type);
+  if (iter == controllers_->end())
+    return false;
+
+  const DataTypeController* dtc = iter->second.get();
+  bool unready_status =
+      data_type_status_table_.GetUnreadyErrorTypes().Has(type);
+  if (dtc->ReadyForStart() == (unready_status == false))
+    return false;
+
+  // Adjust data_type_status_table_ if unready state in it doesn't match
+  // DataTypeController::ReadyForStart().
+  if (dtc->ReadyForStart()) {
+    data_type_status_table_.ResetUnreadyErrorFor(type);
+  } else {
+    SyncError error(FROM_HERE, SyncError::UNREADY_ERROR,
+                    "Datatype not ready at config time.", type);
+    std::map<ModelType, SyncError> errors;
+    errors[type] = error;
+    data_type_status_table_.UpdateFailedDataTypes(errors);
+  }
+
+  return true;
+}
+
 void DataTypeManagerImpl::ProcessReconfigure() {
   // This may have been called asynchronously; no-op if it is no longer needed.
   if (!needs_reconfigure_) {
diff --git a/components/sync/driver/data_type_manager_impl.h b/components/sync/driver/data_type_manager_impl.h
index d4be3742..e39ad589 100644
--- a/components/sync/driver/data_type_manager_impl.h
+++ b/components/sync/driver/data_type_manager_impl.h
@@ -141,6 +141,11 @@
   // DataTypeController::ReadyForStart().
   void UpdateUnreadyTypeErrors(const ModelTypeSet& desired_types);
 
+  // Update unready state for |type|, such that data_type_status_table_ matches
+  // DataTypeController::ReadyForStart(). Returns true if there was an actual
+  // change.
+  bool UpdateUnreadyTypeError(ModelType type);
+
   // Post a task to reconfigure when no downloading or association are running.
   void ProcessReconfigure();
 
diff --git a/components/sync/driver/sync_driver_switches.cc b/components/sync/driver/sync_driver_switches.cc
index c71f4d0..b9bebb28d 100644
--- a/components/sync/driver/sync_driver_switches.cc
+++ b/components/sync/driver/sync_driver_switches.cc
@@ -48,6 +48,8 @@
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kSyncPseudoUSSApps{"SyncPseudoUSSApps",
                                        base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kSyncPseudoUSSArcPackage{"SyncPseudoUSSArcPackage",
+                                             base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kSyncPseudoUSSDictionary{"SyncPseudoUSSDictionary",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kSyncPseudoUSSExtensionSettings{
diff --git a/components/sync/driver/sync_driver_switches.h b/components/sync/driver/sync_driver_switches.h
index fc6f979..186a6c27 100644
--- a/components/sync/driver/sync_driver_switches.h
+++ b/components/sync/driver/sync_driver_switches.h
@@ -24,6 +24,7 @@
     kSyncAllowWalletDataInTransportModeWithCustomPassphrase;
 extern const base::Feature kSyncPseudoUSSAppList;
 extern const base::Feature kSyncPseudoUSSApps;
+extern const base::Feature kSyncPseudoUSSArcPackage;
 extern const base::Feature kSyncPseudoUSSDictionary;
 extern const base::Feature kSyncPseudoUSSExtensionSettings;
 extern const base::Feature kSyncPseudoUSSExtensions;
diff --git a/components/sync/model/syncable_service.cc b/components/sync/model/syncable_service.cc
index d4b33c2d..91ee623 100644
--- a/components/sync/model/syncable_service.cc
+++ b/components/sync/model/syncable_service.cc
@@ -4,10 +4,16 @@
 
 #include "components/sync/model/syncable_service.h"
 
+#include <utility>
+
 namespace syncer {
 
 SyncableService::SyncableService() {}
 
 SyncableService::~SyncableService() {}
 
+void SyncableService::WaitUntilReadyToSync(base::OnceClosure done) {
+  std::move(done).Run();
+}
+
 }  // namespace syncer
diff --git a/components/sync/model/syncable_service.h b/components/sync/model/syncable_service.h
index 8baaef1..b7b68cf 100644
--- a/components/sync/model/syncable_service.h
+++ b/components/sync/model/syncable_service.h
@@ -43,6 +43,12 @@
   // make pptimizations or tradeoffs by type, etc.
   using StartSyncFlare = base::Callback<void(ModelType)>;
 
+  // Allows the SyncableService to delay sync events (all below) until the model
+  // becomes ready to sync.
+  // TODO(crbug.com/939329): Make this pure to enforce discussion on all
+  // subclasses.
+  virtual void WaitUntilReadyToSync(base::OnceClosure done);
+
   // Informs the service to begin syncing the specified synced datatype |type|.
   // The service should then merge |initial_sync_data| into it's local data,
   // calling |sync_processor|'s ProcessSyncChanges as necessary to reconcile the
diff --git a/components/sync/model_impl/syncable_service_based_bridge.cc b/components/sync/model_impl/syncable_service_based_bridge.cc
index 6fb89ab1..ef44409 100644
--- a/components/sync/model_impl/syncable_service_based_bridge.cc
+++ b/components/sync/model_impl/syncable_service_based_bridge.cc
@@ -319,10 +319,9 @@
     return;
   }
 
-  std::move(store_factory_)
-      .Run(type_, base::BindOnce(&SyncableServiceBasedBridge::OnStoreCreated,
-                                 weak_ptr_factory_.GetWeakPtr()));
-  DCHECK(!store_factory_);
+  syncable_service_->WaitUntilReadyToSync(
+      base::BindOnce(&SyncableServiceBasedBridge::OnSyncableServiceReady,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData(
@@ -456,6 +455,15 @@
       other);
 }
 
+void SyncableServiceBasedBridge::OnSyncableServiceReady() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  std::move(store_factory_)
+      .Run(type_, base::BindOnce(&SyncableServiceBasedBridge::OnStoreCreated,
+                                 weak_ptr_factory_.GetWeakPtr()));
+  DCHECK(!store_factory_);
+}
+
 void SyncableServiceBasedBridge::OnStoreCreated(
     const base::Optional<ModelError>& error,
     std::unique_ptr<ModelTypeStore> store) {
diff --git a/components/sync/model_impl/syncable_service_based_bridge.h b/components/sync/model_impl/syncable_service_based_bridge.h
index 1576aab8..6b2ab93 100644
--- a/components/sync/model_impl/syncable_service_based_bridge.h
+++ b/components/sync/model_impl/syncable_service_based_bridge.h
@@ -76,6 +76,7 @@
                                        ModelTypeChangeProcessor* other);
 
  private:
+  void OnSyncableServiceReady();
   void OnStoreCreated(const base::Optional<ModelError>& error,
                       std::unique_ptr<ModelTypeStore> store);
   void OnReadAllDataForInit(std::unique_ptr<InMemoryStore> in_memory_store,
diff --git a/components/sync/model_impl/syncable_service_based_bridge_unittest.cc b/components/sync/model_impl/syncable_service_based_bridge_unittest.cc
index 54b4fa1..3fa2e26 100644
--- a/components/sync/model_impl/syncable_service_based_bridge_unittest.cc
+++ b/components/sync/model_impl/syncable_service_based_bridge_unittest.cc
@@ -30,14 +30,16 @@
 namespace syncer {
 namespace {
 
+using testing::_;
 using testing::DoAll;
 using testing::ElementsAre;
+using testing::Invoke;
 using testing::IsEmpty;
+using testing::IsNull;
 using testing::NotNull;
 using testing::Pair;
 using testing::Return;
 using testing::SaveArg;
-using testing::_;
 
 const ModelType kModelType = PREFERENCES;
 
@@ -65,6 +67,7 @@
 
 class MockSyncableService : public SyncableService {
  public:
+  MOCK_METHOD1(WaitUntilReadyToSync, void(base::OnceClosure done));
   MOCK_METHOD4(
       MergeDataAndStartSyncing,
       SyncMergeResult(ModelType type,
@@ -82,6 +85,9 @@
  protected:
   SyncableServiceBasedBridgeTest()
       : store_(ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()) {
+    ON_CALL(syncable_service_, WaitUntilReadyToSync(_))
+        .WillByDefault(
+            Invoke([](base::OnceClosure done) { std::move(done).Run(); }));
     ON_CALL(syncable_service_, MergeDataAndStartSyncing(_, _, _, _))
         .WillByDefault(
             [&](ModelType type, const SyncDataList& initial_sync_data,
@@ -113,15 +119,18 @@
     real_processor_.reset();
   }
 
-  void StartSyncing() {
+  syncer::DataTypeActivationRequest GetTestActivationRequest() {
     syncer::DataTypeActivationRequest request;
     request.error_handler = mock_error_handler_.Get();
     request.cache_guid = "TestCacheGuid";
     request.authenticated_account_id = "SomeAccountId";
+    return request;
+  }
 
+  void StartSyncing() {
     base::RunLoop loop;
     real_processor_->OnSyncStarting(
-        request,
+        GetTestActivationRequest(),
         base::BindLambdaForTesting(
             [&](std::unique_ptr<syncer::DataTypeActivationResponse> response) {
               worker_ = std::make_unique<MockModelTypeWorker>(
@@ -204,6 +213,38 @@
   EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash, _)));
 }
 
+TEST_F(SyncableServiceBasedBridgeTest, ShouldWaitUntilModelReadyToSync) {
+  base::OnceClosure syncable_service_ready_cb;
+  ON_CALL(syncable_service_, WaitUntilReadyToSync(_))
+      .WillByDefault(Invoke([&](base::OnceClosure done) {
+        syncable_service_ready_cb = std::move(done);
+      }));
+
+  EXPECT_CALL(mock_processor_, ModelReadyToSync(_)).Times(0);
+  EXPECT_CALL(syncable_service_, WaitUntilReadyToSync(_)).Times(0);
+  EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing(_, _, _, _)).Times(0);
+
+  // Bridge initialization alone, without sync itself starting, should not
+  // issue calls to the syncable service.
+  InitializeBridge();
+  EXPECT_FALSE(syncable_service_ready_cb);
+
+  // Sync itself starting should wait until the syncable service becomes ready,
+  // before issuing any other call (e.g. MergeDataAndStartSyncing()).
+  EXPECT_CALL(syncable_service_, WaitUntilReadyToSync(_));
+  real_processor_->OnSyncStarting(GetTestActivationRequest(),
+                                  base::DoNothing());
+  ASSERT_TRUE(syncable_service_ready_cb);
+
+  // When the SyncableService gets ready, the bridge should propagate this
+  // information to the processor.
+  EXPECT_CALL(mock_processor_, ModelReadyToSync(_));
+  std::move(syncable_service_ready_cb).Run();
+
+  // Required to initialize the store.
+  base::RunLoop().RunUntilIdle();
+}
+
 TEST_F(SyncableServiceBasedBridgeTest,
        ShouldStopSyncableServiceIfPreviouslyStarted) {
   InitializeBridge();
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-25.png.sha1
new file mode 100644
index 0000000..ca20a2e
--- /dev/null
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-25.png.sha1
@@ -0,0 +1 @@
+0f9b558ecaac382f144cae2647579a597152b8a3
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-26.png.sha1
new file mode 100644
index 0000000..fb7c2f1
--- /dev/null
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-26.png.sha1
@@ -0,0 +1 @@
+e5fa46dc5e243ac8f164a8b12a077c0109a25c3e
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-25.png.sha1
new file mode 100644
index 0000000..1c202ad
--- /dev/null
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-25.png.sha1
@@ -0,0 +1 @@
+67d5565b078f625bfcc6fa4d951529bd638922b2
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-26.png.sha1
new file mode 100644
index 0000000..65ac77f
--- /dev/null
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-26.png.sha1
@@ -0,0 +1 @@
+0dfae90e612afa2537605da7fb73e09fbea6149c
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-25.png.sha1
new file mode 100644
index 0000000..59a7316
--- /dev/null
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-25.png.sha1
@@ -0,0 +1 @@
+a22df8a3582ec5f241ab5b2864cfad7385f4c3f6
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-26.png.sha1
new file mode 100644
index 0000000..0e284c0
--- /dev/null
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-26.png.sha1
@@ -0,0 +1 @@
+ed308dcd89ed718b2f4c607f5c8776fa9e3b9c0c
\ No newline at end of file
diff --git a/components/viz/common/BUILD.gn b/components/viz/common/BUILD.gn
index 43efd68..24e6c23 100644
--- a/components/viz/common/BUILD.gn
+++ b/components/viz/common/BUILD.gn
@@ -214,7 +214,6 @@
     "//gpu/command_buffer/client:gles2_interface",
     "//gpu/command_buffer/client:raster",
     "//gpu/command_buffer/client:raster_interface",
-    "//gpu/config",
     "//gpu/vulkan:buildflags",
     "//mojo/public/cpp/system",
     "//third_party/libyuv",
@@ -235,6 +234,10 @@
     deps += [ "//ui/base" ]
   }
 
+  if (is_android) {
+    deps += [ "//gpu/config" ]
+  }
+
   if (is_chromecast) {
     defines += [ "IS_CHROMECAST" ]
   }
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 8e024e5a..52fe758 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -7,7 +7,10 @@
 #include "base/command_line.h"
 #include "build/build_config.h"
 #include "components/viz/common/switches.h"
-#include "gpu/config/gpu_finch_features.h"
+
+#if defined(OS_ANDROID)
+#include "gpu/config/gpu_finch_features.h"  // nogncheck
+#endif
 
 namespace features {
 
diff --git a/components/viz/common/skia_helper.cc b/components/viz/common/skia_helper.cc
index 2228ca37..f6413733 100644
--- a/components/viz/common/skia_helper.cc
+++ b/components/viz/common/skia_helper.cc
@@ -6,6 +6,7 @@
 #include "cc/base/math_util.h"
 #include "third_party/skia/include/effects/SkOverdrawColorFilter.h"
 #include "third_party/skia/include/gpu/GrBackendSurface.h"
+#include "third_party/skia/include/gpu/GrContext.h"
 #include "ui/gfx/skia_util.h"
 
 namespace viz {
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index aa0f9661..490ef4e 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -156,16 +156,22 @@
       const size_t number_of_textures = (is_i420 ? 3 : 2) + (has_alpha ? 1 : 0);
       std::vector<ResourceMetadata> metadatas;
       metadatas.reserve(number_of_textures);
+      // metadata.size is overridden because it is always the same size for
+      // all planes. Trust the size in |quad| instead. See
+      // https://crbug.com/939362
       auto y_metadata = skia_renderer->lock_set_for_external_use_->LockResource(
           quad->y_plane_resource_id());
+      y_metadata.size = quad->ya_tex_size;
       metadatas.push_back(std::move(y_metadata));
       auto u_metadata = skia_renderer->lock_set_for_external_use_->LockResource(
           quad->u_plane_resource_id());
+      u_metadata.size = quad->uv_tex_size;
       metadatas.push_back(std::move(u_metadata));
       if (is_i420) {
         auto v_metadata =
             skia_renderer->lock_set_for_external_use_->LockResource(
                 quad->v_plane_resource_id());
+        v_metadata.size = quad->uv_tex_size;
         metadatas.push_back(std::move(v_metadata));
       }
 
@@ -173,6 +179,7 @@
         auto a_metadata =
             skia_renderer->lock_set_for_external_use_->LockResource(
                 quad->a_plane_resource_id());
+        a_metadata.size = quad->ya_tex_size;
         metadatas.push_back(std::move(a_metadata));
       }
 
diff --git a/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc b/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc
index 15b40f4..9d726a1 100644
--- a/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc
+++ b/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc
@@ -4,6 +4,7 @@
 
 #include "components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.h"
 
+#include "components/viz/service/display/overlay_strategy_fullscreen.h"
 #include "components/viz/service/display/overlay_strategy_single_on_top.h"
 #include "components/viz/service/display/overlay_strategy_underlay.h"
 #include "components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.h"
@@ -19,6 +20,9 @@
   ~OverlayCandidateValidatorImpl() override = default;
 
   void GetStrategies(OverlayProcessor::StrategyList* strategies) override {
+    // Added in priority order, most to least desirable.
+    strategies->push_back(std::make_unique<OverlayStrategyFullscreen>(this));
+    strategies->push_back(std::make_unique<OverlayStrategySingleOnTop>(this));
     strategies->push_back(std::make_unique<OverlayStrategyUnderlay>(
         this, OverlayStrategyUnderlay::OpaqueMode::AllowTransparentCandidates));
   }
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 7cb357c..b4640f7 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -76,6 +76,13 @@
   DCHECK(!added_frame_observer_);
 }
 
+CompositorFrameSinkSupport::PresentationFeedbackMap
+CompositorFrameSinkSupport::TakePresentationFeedbacks() {
+  PresentationFeedbackMap map;
+  map.swap(presentation_feedbacks_);
+  return map;
+}
+
 void CompositorFrameSinkSupport::SetUpHitTest(
     LatestLocalSurfaceIdLookupDelegate* local_surface_id_lookup_delegate) {
   DCHECK(is_root_);
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index 28a67051..d62d8a38 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -56,6 +56,8 @@
                                    const gfx::Size& frame_size_in_pixels,
                                    const gfx::Rect& damage_rect,
                                    base::TimeTicks expected_display_time)>;
+  using PresentationFeedbackMap =
+      base::flat_map<uint32_t, gfx::PresentationFeedback>;
 
   static const uint64_t kFrameIndexStart = 2;
 
@@ -80,11 +82,12 @@
 
   FrameSinkManagerImpl* frame_sink_manager() { return frame_sink_manager_; }
 
-  const base::flat_map<uint32_t, gfx::PresentationFeedback>&
-  presentation_feedbacks() {
+  const PresentationFeedbackMap& presentation_feedbacks() {
     return presentation_feedbacks_;
   }
 
+  PresentationFeedbackMap TakePresentationFeedbacks() WARN_UNUSED_RESULT;
+
   // Viz hit-test setup is only called when |is_root_| is true (except on
   // android webview).
   void SetUpHitTest(
@@ -304,7 +307,7 @@
   bool callback_received_receive_ack_ = true;
   uint32_t trace_sequence_ = 0;
 
-  base::flat_map<uint32_t, gfx::PresentationFeedback> presentation_feedbacks_;
+  PresentationFeedbackMap presentation_feedbacks_;
 
   LocalSurfaceId::ParentComponent last_evicted_parent_component_;
 
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
index 48f14281..cd0e3c1 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -548,6 +548,8 @@
         dirty_rect_, gfx::Vector2d(source_size.width(), source_size.height()),
         gfx::Vector2d(content_rect.width(), content_rect.height()));
     update_rect.Offset(content_rect.OffsetFromOrigin());
+    if (pixel_format_ == media::PIXEL_FORMAT_I420)
+      update_rect = ExpandRectToI420SubsampleBoundaries(update_rect);
   }
   metadata->SetRect(media::VideoFrameMetadata::CAPTURE_UPDATE_RECT,
                     update_rect);
@@ -814,6 +816,16 @@
   return result;
 }
 
+// static
+gfx::Rect FrameSinkVideoCapturerImpl::ExpandRectToI420SubsampleBoundaries(
+    const gfx::Rect& rect) {
+  const int x = rect.x() & ~1;
+  const int y = rect.y() & ~1;
+  const int r = rect.right() + (rect.right() & 1);
+  const int b = rect.bottom() + (rect.bottom() & 1);
+  return gfx::Rect(x, y, r - x, b - y);
+}
+
 FrameSinkVideoCapturerImpl::CapturedFrame::CapturedFrame(
     int64_t capture_frame_number,
     OracleFrameNumber oracle_frame_number,
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
index 15f79e5..3c5ddc07 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
@@ -214,6 +214,10 @@
   // I420 format, ensures that every dimension is even and at least 2.
   gfx::Size AdjustSizeForPixelFormat(const gfx::Size& size);
 
+  // Expands |rect| such that its x, y, right, and bottom values are even
+  // numbers.
+  static gfx::Rect ExpandRectToI420SubsampleBoundaries(const gfx::Rect& rect);
+
   // Owner/Manager of this instance.
   FrameSinkVideoCapturerManager* const frame_sink_manager_;
 
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
index 073681e..68b0214 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
@@ -43,6 +43,11 @@
 namespace viz {
 namespace {
 
+bool AlignsWithI420SubsamplingBoundaries(const gfx::Rect& update_rect) {
+  return (update_rect.x() % 2 == 0) && (update_rect.y() % 2 == 0) &&
+         (update_rect.width() % 2 == 0) && (update_rect.height() % 2 == 0);
+}
+
 // Returns true if |frame|'s device scale factor, page scale factor and root
 // scroll offset are equal to the expected values.
 bool CompareVarsInCompositorFrameMetadata(
@@ -473,6 +478,11 @@
     PropagateMojoTasks();
   }
 
+  gfx::Rect ExpandRectToI420SubsampleBoundaries(const gfx::Rect& rect) {
+    return FrameSinkVideoCapturerImpl::ExpandRectToI420SubsampleBoundaries(
+        rect);
+  }
+
  protected:
   SizeSet size_set_;
   scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
@@ -1048,6 +1058,10 @@
                     size_set().expected_content_rect.height()));
   expected_frame_update_rect.Offset(
       size_set().expected_content_rect.OffsetFromOrigin());
+  EXPECT_FALSE(AlignsWithI420SubsamplingBoundaries(expected_frame_update_rect));
+  expected_frame_update_rect =
+      ExpandRectToI420SubsampleBoundaries(expected_frame_update_rect);
+  EXPECT_TRUE(AlignsWithI420SubsamplingBoundaries(expected_frame_update_rect));
 
   // Notify frame damage with custom damage rect, and expect that the refresh
   // frame is delivered to the consumer with a corresponding |update_rect|.
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index d743ffc..e90f38a4 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1274,8 +1274,11 @@
     "notifications/notification_id_generator.h",
     "notifications/notification_storage.cc",
     "notifications/notification_storage.h",
+    "notifications/notification_trigger_constants.h",
     "notifications/platform_notification_context_impl.cc",
     "notifications/platform_notification_context_impl.h",
+    "notifications/platform_notification_service_proxy.cc",
+    "notifications/platform_notification_service_proxy.h",
     "payments/payment_app_context_impl.cc",
     "payments/payment_app_context_impl.h",
     "payments/payment_app_database.cc",
diff --git a/content/browser/accessibility/OWNERS b/content/browser/accessibility/OWNERS
index 2c85844e..1ff46368 100644
--- a/content/browser/accessibility/OWNERS
+++ b/content/browser/accessibility/OWNERS
@@ -1,4 +1,7 @@
 file://ui/accessibility/OWNERS
 
+# For ATK / AuraLinux
+mrobinson@igalia.com
+
 # TEAM: chromium-accessibility@chromium.org
 # COMPONENT: UI>Accessibility
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 6dcefa2a7..962bbcd 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -18,6 +18,7 @@
 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
 #include "content/common/accessibility_messages.h"
 #include "content/public/common/content_client.h"
+#include "ui/accessibility/ax_node_position.h"
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/accessibility/ax_text_utils.h"
 #include "ui/accessibility/platform/ax_unique_id.h"
@@ -1059,6 +1060,15 @@
     return *empty_data;
 }
 
+ui::AXNodePosition::AXPositionInstance
+BrowserAccessibility::CreateTextPositionAt(
+    int offset,
+    ax::mojom::TextAffinity affinity) const {
+  DCHECK(manager_);
+  return ui::AXNodePosition::CreateTextPosition(manager_->ax_tree_id(), GetId(),
+                                                offset, affinity);
+}
+
 gfx::NativeViewAccessible BrowserAccessibility::GetNSWindow() {
   NOTREACHED();
   return nullptr;
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index 0959288..2e0c652 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -21,8 +21,10 @@
 #include "content/browser/accessibility/browser_accessibility_position.h"
 #include "content/common/content_export.h"
 #include "third_party/blink/public/web/web_ax_enums.h"
+#include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_position.h"
 #include "ui/accessibility/ax_range.h"
 #include "ui/accessibility/ax_text_utils.h"
 #include "ui/accessibility/platform/ax_platform_node.h"
@@ -346,6 +348,10 @@
   // AXPlatformNodeDelegate.
   const ui::AXNodeData& GetData() const override;
   const ui::AXTreeData& GetTreeData() const override;
+  ui::AXNodePosition::AXPositionInstance CreateTextPositionAt(
+      int offset,
+      ax::mojom::TextAffinity affinity =
+          ax::mojom::TextAffinity::kDownstream) const override;
   gfx::NativeViewAccessible GetNSWindow() override;
   gfx::NativeViewAccessible GetParent() override;
   int GetChildCount() override;
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index d96a0347..a82025b4 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -16,18 +16,14 @@
 #include "content/browser/accessibility/browser_accessibility.h"
 #include "content/common/accessibility_messages.h"
 #include "content/public/common/use_zoom_for_dsf_policy.h"
+#include "ui/accessibility/ax_node_position.h"
 #include "ui/accessibility/ax_tree_data.h"
+#include "ui/accessibility/ax_tree_manager_map.h"
 #include "ui/accessibility/ax_tree_serializer.h"
 
 namespace content {
 
 namespace {
-
-// Map from AXTreeID to BrowserAccessibilityManager
-using AXTreeIDMap = std::map<ui::AXTreeID, BrowserAccessibilityManager*>;
-base::LazyInstance<AXTreeIDMap>::Leaky g_ax_tree_id_map =
-    LAZY_INSTANCE_INITIALIZER;
-
 // A function to call when focus changes, for testing only.
 base::LazyInstance<base::Closure>::DestructorAtExit
     g_focus_change_callback_for_testing = LAZY_INSTANCE_INITIALIZER;
@@ -148,9 +144,8 @@
 // static
 BrowserAccessibilityManager* BrowserAccessibilityManager::FromID(
     ui::AXTreeID ax_tree_id) {
-  AXTreeIDMap& ax_tree_id_map = g_ax_tree_id_map.Get();
-  AXTreeIDMap::iterator iter = ax_tree_id_map.find(ax_tree_id);
-  return iter == ax_tree_id_map.end() ? nullptr : iter->second;
+  return static_cast<BrowserAccessibilityManager*>(
+      ui::AXTreeManagerMap::GetInstance().GetManager(ax_tree_id));
 }
 
 BrowserAccessibilityManager::BrowserAccessibilityManager(
@@ -191,7 +186,7 @@
 BrowserAccessibilityManager::~BrowserAccessibilityManager() {
   tree_.reset(nullptr);
   event_generator_.ReleaseTree();
-  g_ax_tree_id_map.Get().erase(ax_tree_id_);
+  ui::AXTreeManagerMap::GetInstance().RemoveTreeManager(ax_tree_id_);
 }
 
 void BrowserAccessibilityManager::Initialize(
@@ -1169,9 +1164,9 @@
   bool ax_tree_id_changed = false;
   if (GetTreeData().tree_id != ui::AXTreeIDUnknown() &&
       GetTreeData().tree_id != ax_tree_id_) {
-    g_ax_tree_id_map.Get().erase(ax_tree_id_);
+    ui::AXTreeManagerMap::GetInstance().RemoveTreeManager(ax_tree_id_);
     ax_tree_id_ = GetTreeData().tree_id;
-    g_ax_tree_id_map.Get().insert(std::make_pair(ax_tree_id_, this));
+    ui::AXTreeManagerMap::GetInstance().AddTreeManager(ax_tree_id_, this);
     ax_tree_id_changed = true;
   }
 
@@ -1189,6 +1184,19 @@
   }
 }
 
+ui::AXNode* BrowserAccessibilityManager::GetNodeFromTree(ui::AXTreeID tree_id,
+                                                         int32_t node_id) {
+  auto* manager = BrowserAccessibilityManager::FromID(tree_id);
+  if (!manager)
+    return nullptr;
+
+  BrowserAccessibility* wrapper = manager->GetFromID(node_id);
+  if (wrapper)
+    return wrapper->node();
+
+  return nullptr;
+}
+
 BrowserAccessibilityManager* BrowserAccessibilityManager::GetRootManager() {
   BrowserAccessibility* parent = GetParentNodeFromParentTree();
   if (!parent)
diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h
index 6dc23ac..0a2c167 100644
--- a/content/browser/accessibility/browser_accessibility_manager.h
+++ b/content/browser/accessibility/browser_accessibility_manager.h
@@ -26,6 +26,7 @@
 #include "ui/accessibility/ax_range.h"
 #include "ui/accessibility/ax_serializable_tree.h"
 #include "ui/accessibility/ax_tree_id_registry.h"
+#include "ui/accessibility/ax_tree_manager.h"
 #include "ui/accessibility/ax_tree_observer.h"
 #include "ui/accessibility/ax_tree_update.h"
 #include "ui/gfx/native_widget_types.h"
@@ -112,7 +113,8 @@
 };
 
 // Manages a tree of BrowserAccessibility objects.
-class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeObserver {
+class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeObserver,
+                                                   public ui::AXTreeManager {
  protected:
   using BrowserAccessibilityPositionInstance =
       BrowserAccessibilityPosition::AXPositionInstance;
@@ -361,6 +363,9 @@
       bool root_changed,
       const std::vector<ui::AXTreeObserver::Change>& changes) override;
 
+  // AXTreeManager implementation.
+  ui::AXNode* GetNodeFromTree(ui::AXTreeID tree_id, int32_t node_id) override;
+
   BrowserAccessibilityDelegate* delegate() const { return delegate_; }
 
   // If this BrowserAccessibilityManager is a child frame or guest frame,
diff --git a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
index 01dff2c9..c5c088d6 100644
--- a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
+++ b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
@@ -132,9 +132,8 @@
   return false;
 }
 
-// Marked flaky per http://crbug.com/101984
 IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
-                       DISABLED_WebpageAccessibility) {
+                       WebpageAccessibility) {
   // Create a data url and load it.
   const char url_str[] =
       "data:text/html,"
@@ -161,7 +160,7 @@
   // Check properties of the BODY element.
   ASSERT_EQ(1, root->child_count());
   const ui::AXNode* body = root->ChildAtIndex(0);
-  EXPECT_EQ(ax::mojom::Role::kGroup, body->data().role);
+  EXPECT_EQ(ax::mojom::Role::kGenericContainer, body->data().role);
   EXPECT_STREQ("body",
                GetAttr(body, ax::mojom::StringAttribute::kHtmlTag).c_str());
   EXPECT_STREQ("block",
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc
index 82db89bb..daf4c948 100644
--- a/content/browser/browsing_instance.cc
+++ b/content/browser/browsing_instance.cc
@@ -8,13 +8,19 @@
 #include "base/logging.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_or_resource_context.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/site_isolation_policy.h"
+#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 
 namespace content {
 
+namespace {
+const char* const kDefaultInstanceSiteURL = "http://unisolated.invalid";
+}  // namespace
+
 // Start the BrowsingInstance ID counter from 1 to avoid a conflict with the
 // invalid BrowsingInstanceId value, which is 0 in its underlying IdType32.
 int BrowsingInstance::next_browsing_instance_id_ = 1;
@@ -39,10 +45,17 @@
 
 void BrowsingInstance::SetDefaultProcess(RenderProcessHost* default_process) {
   DCHECK(!default_process_);
+  DCHECK(!default_site_instance_);
   default_process_ = default_process;
   default_process_->AddObserver(this);
 }
 
+bool BrowsingInstance::IsDefaultSiteInstance(
+    const SiteInstanceImpl* site_instance) const {
+  return site_instance != nullptr &&
+         site_instance == default_site_instance_.get();
+}
+
 bool BrowsingInstance::HasSiteInstance(const GURL& url) {
   std::string site =
       SiteInstanceImpl::GetSiteForURL(browser_context_, isolation_context_, url)
@@ -52,14 +65,13 @@
 }
 
 scoped_refptr<SiteInstanceImpl> BrowsingInstance::GetSiteInstanceForURL(
-    const GURL& url) {
-  std::string site =
-      SiteInstanceImpl::GetSiteForURL(browser_context_, isolation_context_, url)
-          .possibly_invalid_spec();
+    const GURL& url,
+    bool allow_default_instance) {
+  scoped_refptr<SiteInstanceImpl> site_instance =
+      GetSiteInstanceForURLHelper(url, allow_default_instance);
 
-  auto i = site_instance_map_.find(site);
-  if (i != site_instance_map_.end())
-    return i->second;
+  if (site_instance)
+    return site_instance;
 
   // No current SiteInstance for this site, so let's create one.
   scoped_refptr<SiteInstanceImpl> instance = new SiteInstanceImpl(this);
@@ -69,10 +81,68 @@
   return instance;
 }
 
+void BrowsingInstance::GetSiteAndLockForURL(const GURL& url,
+                                            bool allow_default_instance,
+                                            GURL* site_url,
+                                            GURL* lock_url) {
+  scoped_refptr<SiteInstanceImpl> site_instance =
+      GetSiteInstanceForURLHelper(url, allow_default_instance);
+
+  if (site_instance) {
+    *site_url = site_instance->GetSiteURL();
+    *lock_url = site_instance->lock_url();
+    return;
+  }
+
+  BrowserOrResourceContext context(browser_context_);
+  *site_url = SiteInstanceImpl::GetSiteForURL(
+      context, isolation_context_, url, true /* should_use_effective_urls */);
+  *lock_url = SiteInstanceImpl::DetermineProcessLockURL(
+      context, isolation_context_, url);
+}
+
+scoped_refptr<SiteInstanceImpl> BrowsingInstance::GetSiteInstanceForURLHelper(
+    const GURL& url,
+    bool allow_default_instance) {
+  std::string site =
+      SiteInstanceImpl::GetSiteForURL(browser_context_, isolation_context_, url)
+          .possibly_invalid_spec();
+
+  auto i = site_instance_map_.find(site);
+  if (i != site_instance_map_.end())
+    return i->second;
+
+  // Check to see if we can use the default SiteInstance for sites that don't
+  // need to be isolated in their own process. The default instance allows us to
+  // have multiple unisolated sites share a process. We don't use the default
+  // instance when kProcessSharingWithStrictSiteInstances is enabled because in
+  // that case we want each site to have their own SiteInstance object and logic
+  // elsewhere ensures that those SiteInstances share a process.
+  if (allow_default_instance &&
+      !base::FeatureList::IsEnabled(
+          features::kProcessSharingWithStrictSiteInstances) &&
+      !SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
+          browser_context_, isolation_context_, url)) {
+    DCHECK(!default_process_);
+    if (!default_site_instance_) {
+      default_site_instance_ = new SiteInstanceImpl(this);
+      default_site_instance_->SetSite(GURL(kDefaultInstanceSiteURL));
+    }
+    return default_site_instance_;
+  }
+
+  return nullptr;
+}
+
 void BrowsingInstance::RegisterSiteInstance(SiteInstanceImpl* site_instance) {
   DCHECK(site_instance->browsing_instance_.get() == this);
   DCHECK(site_instance->HasSite());
 
+  // Explicitly prevent the |default_site_instance_| from being added since
+  // the map is only supposed to contain instances that map to a single site.
+  if (site_instance == default_site_instance_.get())
+    return;
+
   std::string site = site_instance->GetSiteURL().possibly_invalid_spec();
 
   // Only register if we don't have a SiteInstance for this site already.
diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h
index 474039c..e780c0f 100644
--- a/content/browser/browsing_instance.h
+++ b/content/browser/browsing_instance.h
@@ -104,7 +104,37 @@
   // Get the SiteInstance responsible for rendering the given URL.  Should
   // create a new one if necessary, but should not create more than one
   // SiteInstance per site.
-  scoped_refptr<SiteInstanceImpl> GetSiteInstanceForURL(const GURL& url);
+  //
+  // |allow_default_instance| should be set to true in cases where the caller
+  // is ok with |url| sharing a process with other sites that do not require
+  // a dedicated process. Note that setting this to true means that the
+  // SiteInstanceImpl you get back may return "http://unisolated.invalid" for
+  // GetSiteURL() and lock_url() calls because the default instance is not
+  // bound to a single site.
+  scoped_refptr<SiteInstanceImpl> GetSiteInstanceForURL(
+      const GURL& url,
+      bool allow_default_instance);
+
+  // Gets site and lock URLs for |url| that are identical with what these
+  // values would be if we called GetSiteInstanceForURL() with the same
+  // |url| and |allow_default_instance|. This method is used when we need this
+  // information, but do not want to create a SiteInstance yet.
+  void GetSiteAndLockForURL(const GURL& url,
+                            bool allow_default_instance,
+                            GURL* site_url,
+                            GURL* lock_url);
+
+  // Helper function used by GetSiteInstanceForURL() and GetSiteAndLockForURL()
+  // that returns an existing SiteInstance from |site_instance_map_| or
+  // returns |default_site_instance_| if |allow_default_instance| is true and
+  // other conditions are met. If there is no existing SiteInstance that is
+  // appropriate for |url|, |allow_default_instance| combination, then a nullptr
+  // is returned.
+  //
+  // Note: This method is not intended to be called by code outside this object.
+  scoped_refptr<SiteInstanceImpl> GetSiteInstanceForURLHelper(
+      const GURL& url,
+      bool allow_default_instance);
 
   // Adds the given SiteInstance to our map, to ensure that we do not create
   // another SiteInstance for the same site.
@@ -129,6 +159,8 @@
   void SetDefaultProcess(RenderProcessHost* default_process);
   RenderProcessHost* default_process() const { return default_process_; }
 
+  bool IsDefaultSiteInstance(const SiteInstanceImpl* site_instance) const;
+
   // Map of site to SiteInstance, to ensure we only have one SiteInstance per
   // site.
   typedef std::unordered_map<std::string, SiteInstanceImpl*> SiteInstanceMap;
@@ -151,6 +183,9 @@
   // SiteInstances can be assigned to the same site.  This is ok in rare cases.
   // It also does not contain SiteInstances which have not yet been assigned a
   // site, such as about:blank.  See NavigatorImpl::ShouldAssignSiteForURL.
+  // This map only contains instances that map to a single site. The
+  // |default_site_instance_|, which associates multiple sites with a single
+  // instance, is not contained in this map.
   SiteInstanceMap site_instance_map_;
 
   // Number of WebContentses currently using this BrowsingInstance.
@@ -160,6 +195,13 @@
   // doesn't require a dedicated process.
   RenderProcessHost* default_process_;
 
+  // SiteInstance to use if a URL does not correspond to an instance in
+  // |site_instance_map_| and it does not require a dedicated process.
+  // This field and |default_process_| are mutually exclusive and this field
+  // should only be set if kProcessSharingWithStrictSiteInstances is not
+  // enabled.
+  scoped_refptr<SiteInstanceImpl> default_site_instance_;
+
   DISALLOW_COPY_AND_ASSIGN(BrowsingInstance);
 };
 
diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc
index e4fe19f7..f48d0b3 100644
--- a/content/browser/child_process_launcher.cc
+++ b/content/browser/child_process_launcher.cc
@@ -161,6 +161,15 @@
   ChildProcessLauncherHelper::ResetRegisteredFilesForTesting();
 }
 
+#if defined(OS_ANDROID)
+void ChildProcessLauncher::DumpProcessStack() {
+  base::Process to_pass = process_.process.Duplicate();
+  GetProcessLauncherTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&ChildProcessLauncherHelper::DumpProcessStack,
+                                helper_, std::move(to_pass)));
+}
+#endif
+
 ChildProcessLauncher::Client* ChildProcessLauncher::ReplaceClientForTest(
     Client* client) {
   Client* ret = client_;
diff --git a/content/browser/child_process_launcher.h b/content/browser/child_process_launcher.h
index 5ecd927..407bfc4 100644
--- a/content/browser/child_process_launcher.h
+++ b/content/browser/child_process_launcher.h
@@ -210,6 +210,10 @@
   // support multiple shell context creation in unit_tests.
   static void ResetRegisteredFilesForTesting();
 
+#if defined(OS_ANDROID)
+  // Dumps the stack of the child process without crashing it.
+  void DumpProcessStack();
+#endif
  private:
   friend class internal::ChildProcessLauncherHelper;
 
diff --git a/content/browser/child_process_launcher_helper.h b/content/browser/child_process_launcher_helper.h
index 9b93e1f..301b8443 100644
--- a/content/browser/child_process_launcher_helper.h
+++ b/content/browser/child_process_launcher_helper.h
@@ -190,6 +190,9 @@
   void OnChildProcessStarted(JNIEnv* env,
                              const base::android::JavaParamRef<jobject>& obj,
                              jint handle);
+
+  // Dumps the stack of the child process without crashing it.
+  void DumpProcessStack(const base::Process& process);
 #endif  // OS_ANDROID
 
  private:
diff --git a/content/browser/child_process_launcher_helper_android.cc b/content/browser/child_process_launcher_helper_android.cc
index de95aba..adc3cc9 100644
--- a/content/browser/child_process_launcher_helper_android.cc
+++ b/content/browser/child_process_launcher_helper_android.cc
@@ -250,6 +250,14 @@
   return base::File(base::android::OpenApkAsset(path.value(), region));
 }
 
+void ChildProcessLauncherHelper::DumpProcessStack(
+    const base::Process& process) {
+  JNIEnv* env = AttachCurrentThread();
+  DCHECK(env);
+  return Java_ChildProcessLauncherHelperImpl_dumpProcessStack(env, java_peer_,
+                                                              process.Handle());
+}
+
 // Called from ChildProcessLauncher.java when the ChildProcess was started.
 // |handle| is the processID of the child process as originated in Java, 0 if
 // the ChildProcess could not be created.
diff --git a/content/browser/dom_storage/session_storage_context_mojo.cc b/content/browser/dom_storage/session_storage_context_mojo.cc
index d90d3da5..50cc0dca 100644
--- a/content/browser/dom_storage/session_storage_context_mojo.cc
+++ b/content/browser/dom_storage/session_storage_context_mojo.cc
@@ -237,12 +237,14 @@
     bool should_persist) {
   auto namespace_it = namespaces_.find(namespace_id);
   // If the namespace has pending clones, do the clone now before destroying it.
-  if (namespace_it->second->HasNamespacesWaitingForClone()) {
-    namespace_it->second->CloneAllNamespacesWaitingForClone();
+  if (namespace_it != namespaces_.end()) {
+    if (namespace_it->second->HasNamespacesWaitingForClone())
+      namespace_it->second->CloneAllNamespacesWaitingForClone();
+
+    // The object hierarchy uses iterators bound to the metadata object, so
+    // make sure to delete the object hierarchy first.
+    namespaces_.erase(namespace_it);
   }
-  // The object hierarchy uses iterators bound to the metadata object, so make
-  // sure to delete the object hierarchy first.
-  namespaces_.erase(namespace_it);
 
   if (!has_scavenged_ && should_persist)
     protected_namespaces_from_scavenge_.insert(namespace_id);
@@ -339,6 +341,12 @@
 void SessionStorageContextMojo::ShutdownAndDelete() {
   DCHECK_NE(connection_state_, CONNECTION_SHUTDOWN);
 
+  // The namespaces will DCHECK if they are destructed with pending clones. It
+  // is valid for to drop these on shutdown.
+  for (auto& namespace_pair : namespaces_) {
+    namespace_pair.second->ClearNamespacesWaitingForClone();
+  }
+
   // Nothing to do if no connection to the database was ever finished.
   if (connection_state_ != CONNECTION_FINISHED) {
     connection_state_ = CONNECTION_SHUTDOWN;
diff --git a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
index 1a6c235..727afbb 100644
--- a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
+++ b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
@@ -72,6 +72,8 @@
   }
 
   void TearDown() override {
+    if (context_)
+      ShutdownContext();
     ChildProcessSecurityPolicyImpl::GetInstance()->Remove(kTestProcessId);
 
     mojo::core::SetDefaultProcessErrorCallback(
diff --git a/content/browser/dom_storage/session_storage_namespace_impl_mojo.cc b/content/browser/dom_storage/session_storage_namespace_impl_mojo.cc
index 62ba8210..813792d 100644
--- a/content/browser/dom_storage/session_storage_namespace_impl_mojo.cc
+++ b/content/browser/dom_storage/session_storage_namespace_impl_mojo.cc
@@ -92,6 +92,7 @@
   populated_ = false;
   origin_areas_.clear();
   bindings_.CloseAllBindings();
+  namespaces_waiting_for_clone_call_.clear();
 }
 
 void SessionStorageNamespaceImplMojo::Bind(
diff --git a/content/browser/dom_storage/session_storage_namespace_impl_mojo.h b/content/browser/dom_storage/session_storage_namespace_impl_mojo.h
index d0e2b12..38f8579 100644
--- a/content/browser/dom_storage/session_storage_namespace_impl_mojo.h
+++ b/content/browser/dom_storage/session_storage_namespace_impl_mojo.h
@@ -108,7 +108,10 @@
       const OriginAreas& areas_to_clone);
 
   // Resets to a pre-populated and pre-bound state. Used when the owner needs to
-  // delete & recreate the database.
+  // delete & recreate the database. This call should happen on every namespace
+  // at once, and the logic relies on that.
+  // TODO(dmurph): It's unclear if we need this or not - we might just want to
+  // destruct the object instead of having this method.
   void Reset();
 
   SessionStorageMetadata::NamespaceEntry namespace_entry() {
@@ -158,6 +161,13 @@
   }
   void CloneAllNamespacesWaitingForClone();
 
+  // This is only used on shutdown to avoid the DCHECK in the destructor.
+  // We are fine to drop clone calls during shutdown, even if this loses some
+  // data.
+  void ClearNamespacesWaitingForClone() {
+    namespaces_waiting_for_clone_call_.clear();
+  }
+
  private:
   FRIEND_TEST_ALL_PREFIXES(SessionStorageContextMojoTest,
                            PurgeMemoryDoesNotCrashOrHang);
diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc
index e8201d40..0767a19 100644
--- a/content/browser/frame_host/frame_tree_node.cc
+++ b/content/browser/frame_host/frame_tree_node.cc
@@ -18,6 +18,7 @@
 #include "base/strings/string_util.h"
 #include "content/browser/devtools/devtools_instrumentation.h"
 #include "content/browser/frame_host/frame_tree.h"
+#include "content/browser/frame_host/navigation_controller_impl.h"
 #include "content/browser/frame_host/navigation_request.h"
 #include "content/browser/frame_host/navigator.h"
 #include "content/browser/frame_host/render_frame_host_impl.h"
@@ -579,6 +580,9 @@
     }
   }
 
+  static_cast<NavigationControllerImpl*>(navigator()->GetController())
+      ->NotifyUserActivation();
+
   return true;
 }
 
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 71a0a5f..90cfe80 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -423,6 +423,23 @@
 }
 #endif  // DCHECK_IS_ON()
 
+// Resets |should_skip_on_back_forward_ui| flag for |entry| if it has a frame
+// entry for |root_frame| with the same document sequence number as
+// |document_sequence_number|.
+bool ResetSkippableForSameDocumentEntry(FrameTreeNode* root_frame,
+                                        int64_t& document_sequence_number,
+                                        NavigationEntryImpl* entry) {
+  if (entry && entry->should_skip_on_back_forward_ui()) {
+    auto* frame_entry = entry->GetFrameEntry(root_frame);
+    if (frame_entry &&
+        frame_entry->document_sequence_number() == document_sequence_number) {
+      entry->set_should_skip_on_back_forward_ui(false);
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace
 
 // NavigationControllerImpl ----------------------------------------------------
@@ -2075,6 +2092,44 @@
 }
 #endif
 
+void NavigationControllerImpl::NotifyUserActivation() {
+  // When a user activation occurs, ensure that all adjacent entries for the
+  // same document clear their skippable bit, so that the history manipulation
+  // intervention does not apply to them.
+  auto* last_committed_entry = GetLastCommittedEntry();
+  if (!last_committed_entry)
+    return;
+  int last_committed_entry_index = GetLastCommittedEntryIndex();
+
+  auto* root_frame = delegate_->GetFrameTree()->root();
+  auto* frame_entry = last_committed_entry->GetFrameEntry(root_frame);
+  if (!frame_entry)
+    return;
+
+  int64_t document_sequence_number = frame_entry->document_sequence_number();
+
+  // |last_committed_entry| should not be skippable because it is the current
+  // entry and in case the skippable bit was earlier set then on re-navigation
+  // it would have been reset.
+  DCHECK(!last_committed_entry->should_skip_on_back_forward_ui());
+
+  for (int index = last_committed_entry_index - 1; index >= 0; index--) {
+    auto* entry = GetEntryAtIndex(index);
+    if (!ResetSkippableForSameDocumentEntry(root_frame,
+                                            document_sequence_number, entry)) {
+      break;
+    }
+  }
+  for (int index = last_committed_entry_index + 1; index < GetEntryCount();
+       index++) {
+    auto* entry = GetEntryAtIndex(index);
+    if (!ResetSkippableForSameDocumentEntry(root_frame,
+                                            document_sequence_number, entry)) {
+      break;
+    }
+  }
+}
+
 bool NavigationControllerImpl::StartHistoryNavigationInNewSubframe(
     RenderFrameHostImpl* render_frame_host,
     const GURL& default_url) {
diff --git a/content/browser/frame_host/navigation_controller_impl.h b/content/browser/frame_host/navigation_controller_impl.h
index d891162c3..32f6207e 100644
--- a/content/browser/frame_host/navigation_controller_impl.h
+++ b/content/browser/frame_host/navigation_controller_impl.h
@@ -238,6 +238,10 @@
       const scoped_refptr<const base::RefCountedString>& data_url_as_string);
 #endif
 
+  // Invoked when a user activation occurs within the page, so that relevant
+  // entries can be updated as needed.
+  void NotifyUserActivation();
+
  private:
   friend class RestoreHelper;
 
diff --git a/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
index 3304971a..af81884 100644
--- a/content/browser/frame_host/navigation_controller_impl_browsertest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
@@ -8734,6 +8734,166 @@
       "Navigation.BackForward.SetShouldSkipOnBackForwardUI", false, 1);
 }
 
+// Tests that if a navigation entry is marked as skippable due to pushState then
+// the flag should be reset if there is a user gesture on this document. All of
+// the adjacent entries belonging to the same document will have their skippable
+// bits reset.
+IN_PROC_BROWSER_TEST_F(NavigationControllerHistoryInterventionBrowserTest,
+                       OnUserGestureResetSameDocumentEntriesSkipFlag) {
+  GURL skippable_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), skippable_url));
+
+  // It is safe to obtain the root frame tree node here, as it doesn't change.
+  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+                            ->GetFrameTree()
+                            ->root();
+
+  EXPECT_FALSE(root->HasBeenActivated());
+  EXPECT_FALSE(root->HasTransientUserActivation());
+
+  NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
+      shell()->web_contents()->GetController());
+
+  // Redirect to another page without a user gesture.
+  GURL redirected_url(embedded_test_server()->GetURL("/empty.html"));
+  EXPECT_TRUE(
+      NavigateToURLFromRendererWithoutUserGesture(shell(), redirected_url));
+  // Last entry should have been marked as skippable.
+  EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
+
+  // Use the pushState API to add another entry without user gesture.
+  GURL push_state_url1(embedded_test_server()->GetURL("/title1.html"));
+  std::string script("history.pushState('', '','" + push_state_url1.spec() +
+                     "');");
+  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
+                                                       script));
+
+  // Use the pushState API to add another entry without user gesture.
+  GURL push_state_url2(embedded_test_server()->GetURL("/title2.html"));
+  script = "history.pushState('', '','" + push_state_url2.spec() + "');";
+  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
+                                                       script));
+
+  EXPECT_EQ(3, controller.GetCurrentEntryIndex());
+  EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
+
+  // We now have
+  // [skippable_url(skip), redirected_url(skip), push_state_url1(skip),
+  // push_state_url2*]
+  // Last 2 entries should have been marked as skippable.
+  EXPECT_TRUE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
+  EXPECT_TRUE(controller.GetEntryAtIndex(2)->should_skip_on_back_forward_ui());
+  EXPECT_FALSE(
+      controller.GetLastCommittedEntry()->should_skip_on_back_forward_ui());
+
+  EXPECT_EQ(skippable_url, controller.GetEntryAtIndex(0)->GetURL());
+  EXPECT_EQ(redirected_url, controller.GetEntryAtIndex(1)->GetURL());
+  EXPECT_EQ(push_state_url1, controller.GetEntryAtIndex(2)->GetURL());
+  EXPECT_EQ(push_state_url2, controller.GetEntryAtIndex(3)->GetURL());
+
+  // Do another pushState so push_state_url2's entry also becomes skippable.
+  GURL push_state_url3(embedded_test_server()->GetURL("/title3.html"));
+  script = "history.pushState('', '','" + push_state_url3.spec() + "');";
+  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
+                                                       script));
+  EXPECT_TRUE(controller.GetEntryAtIndex(3)->should_skip_on_back_forward_ui());
+  // We now have
+  // [skippable_url(skip), redirected_url(skip), push_state_url1(skip),
+  // push_state_url2(skip), push_state_url3*]
+
+  // Go to index 2.
+  TestNavigationObserver load_observer(shell()->web_contents());
+  controller.GoToIndex(2);
+  load_observer.Wait();
+  EXPECT_EQ(push_state_url1, controller.GetLastCommittedEntry()->GetURL());
+
+  // We now have (Before user gesture)
+  // [skippable_url(skip), redirected_url(skip), push_state_url1*,
+  // push_state_url2(skip), push_state_url3]
+  EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
+  EXPECT_TRUE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
+  EXPECT_FALSE(controller.GetEntryAtIndex(2)->should_skip_on_back_forward_ui());
+  EXPECT_TRUE(controller.GetEntryAtIndex(3)->should_skip_on_back_forward_ui());
+  EXPECT_FALSE(controller.GetEntryAtIndex(4)->should_skip_on_back_forward_ui());
+
+  // Simulate a user gesture.
+  root->UpdateUserActivationState(
+      blink::UserActivationUpdateType::kNotifyActivation);
+
+  // We now have (After user gesture)
+  // [skippable_url(skip), redirected_url, push_state_url1*, push_state_url2,
+  // push_state_url3]
+  // All the navigations that refer to the same document should have their
+  // skippable bit reset.
+  EXPECT_FALSE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
+  EXPECT_FALSE(controller.GetEntryAtIndex(2)->should_skip_on_back_forward_ui());
+  EXPECT_FALSE(controller.GetEntryAtIndex(3)->should_skip_on_back_forward_ui());
+  EXPECT_FALSE(controller.GetEntryAtIndex(4)->should_skip_on_back_forward_ui());
+  // The first entry is not the same document and its bit should not be reset.
+  EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
+
+  // goBack should now navigate to entry at index 1.
+  TestNavigationObserver back_load_observer(shell()->web_contents());
+  controller.GoBack();
+  back_load_observer.Wait();
+  EXPECT_EQ(redirected_url, controller.GetLastCommittedEntry()->GetURL());
+
+  // Do another pushState without user gesture.
+  GURL push_state_url4(embedded_test_server()->GetURL("/title3.html"));
+  script = "history.pushState('', '','" + push_state_url3.spec() + "');";
+  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
+                                                       script));
+  // We now have
+  // [skippable_url(skip), redirected_url, push_state_url4*]
+  EXPECT_EQ(3, controller.GetEntryCount());
+  EXPECT_EQ(skippable_url, controller.GetEntryAtIndex(0)->GetURL());
+  EXPECT_EQ(redirected_url, controller.GetEntryAtIndex(1)->GetURL());
+  EXPECT_EQ(push_state_url4, controller.GetEntryAtIndex(2)->GetURL());
+  // The skippable flag will still be unset since this page has seen a user
+  // gesture once.
+  EXPECT_FALSE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
+}
+
+// Tests that if a navigation entry is marked as skippable due to redirect to a
+// new document then the flag should not be reset if there is a user gesture on
+// the new document.
+IN_PROC_BROWSER_TEST_F(NavigationControllerHistoryInterventionBrowserTest,
+                       OnUserGestureDoNotResetDifferentDocumentEntrySkipFlag) {
+  GURL skippable_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), skippable_url));
+
+  // It is safe to obtain the root frame tree node here, as it doesn't change.
+  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+                            ->GetFrameTree()
+                            ->root();
+
+  EXPECT_FALSE(root->HasBeenActivated());
+  EXPECT_FALSE(root->HasTransientUserActivation());
+
+  // Navigate to a new same-site document from the renderer without a user
+  // gesture.
+  GURL redirected_url(embedded_test_server()->GetURL("/title1.html"));
+  EXPECT_TRUE(
+      NavigateToURLFromRendererWithoutUserGesture(shell(), redirected_url));
+
+  NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
+      shell()->web_contents()->GetController());
+  EXPECT_EQ(1, controller.GetCurrentEntryIndex());
+  EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+
+  EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
+  EXPECT_FALSE(
+      controller.GetLastCommittedEntry()->should_skip_on_back_forward_ui());
+
+  // Simulate a user gesture.
+  root->UpdateUserActivationState(
+      blink::UserActivationUpdateType::kNotifyActivation);
+
+  // Since the last navigations refer to a different document, a user gesture
+  // here should not reset the skippable bit in the previous entries.
+  EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
+}
+
 // Tests that the navigation entry is not marked as skippable on back/forward
 // button if it does a renderer initiated navigation after getting a user
 // activation.
diff --git a/content/browser/frame_host/navigator_impl_unittest.cc b/content/browser/frame_host/navigator_impl_unittest.cc
index cc26ba06..8e0460d 100644
--- a/content/browser/frame_host/navigator_impl_unittest.cc
+++ b/content/browser/frame_host/navigator_impl_unittest.cc
@@ -1009,14 +1009,22 @@
     SiteInstanceDescriptor descriptor(browser_context(), kUrlSameSiteAs2,
                                       SiteInstanceRelation::RELATED);
     related_instance = ConvertToSiteInstance(rfhm, descriptor, nullptr);
-    // Should return a new instance, related to the current, set to the new site
-    // URL.
+    // If kUrlSameSiteAs2 requires a dedicated process on this platform, this
+    // should return a new instance, related to the current and set to the new
+    // site URL.
+    // Otherwise, this should return the default site instance
     EXPECT_TRUE(
         current_instance->IsRelatedSiteInstance(related_instance.get()));
     EXPECT_NE(current_instance, related_instance.get());
     EXPECT_NE(unrelated_instance.get(), related_instance.get());
-    EXPECT_EQ(SiteInstance::GetSiteForURL(browser_context(), kUrlSameSiteAs2),
-              related_instance->GetSiteURL());
+
+    if (AreAllSitesIsolatedForTesting()) {
+      EXPECT_EQ(SiteInstance::GetSiteForURL(browser_context(), kUrlSameSiteAs2),
+                related_instance->GetSiteURL());
+    } else {
+      EXPECT_TRUE(static_cast<SiteInstanceImpl*>(related_instance.get())
+                      ->IsDefaultSiteInstance());
+    }
   }
 
   // 5) Convert a descriptor of an unrelated instance with the same site as the
diff --git a/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
index 022a16c..9b7945e 100644
--- a/content/browser/frame_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
@@ -4654,9 +4654,17 @@
     EXPECT_EQ(test_url, child1->current_frame_host()->GetLastCommittedURL());
     EXPECT_EQ(url::Origin::Create(test_url),
               child1->current_frame_host()->GetLastCommittedOrigin());
-    GURL c_site_url =
-        child1->current_frame_host()->GetSiteInstance()->GetSiteURL();
-    EXPECT_EQ("c.com", c_site_url.host());
+
+    SiteInstanceImpl* child1_site_instance =
+        child1->current_frame_host()->GetSiteInstance();
+
+    GURL c_site_url = child1_site_instance->GetSiteURL();
+    if (AreAllSitesIsolatedForTesting()) {
+      EXPECT_EQ("c.com", c_site_url.host());
+      EXPECT_EQ(test_url.host(), c_site_url.host());
+    } else {
+      EXPECT_TRUE(child1_site_instance->IsDefaultSiteInstance());
+    }
     EXPECT_NE(a_site_url, c_site_url);
     EXPECT_NE(b_site_url, c_site_url);
   }
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 0a7c48d..0b81d93 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -1208,7 +1208,7 @@
   EXPECT_EQ(expected_to_isolate, IsIsolatedOrigin(GURL("https://bar.com/")));
 }
 
-// This is a regresion test for https://crbug.com/793350 - the long list of
+// This is a regression test for https://crbug.com/793350 - the long list of
 // origins to isolate used to be unnecessarily propagated to the renderer
 // process, trigerring a crash due to exceeding kZygoteMaxMessageLength.
 class IsolatedOriginLongListTest : public IsolatedOriginTestBase {
@@ -1442,6 +1442,74 @@
   EXPECT_TRUE(IsIsolatedOrigin(isolated_url));
 }
 
+// Verify that main frame's origin isolation still keeps all same-origin frames
+// in the same process.  When allocating processes for a(b(c),d(c)), we should
+// ensure that "c" frames are in the same process.
+//
+// This is a regression test for https://crbug.com/787576.
+IN_PROC_BROWSER_TEST_F(IsolatedOriginNoFlagOverrideTest,
+                       SameOriginSubframesProcessSharing) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "isolated.foo.com", "/cross_site_iframe_factory.html?a(b(c),d(c))"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  RenderFrameHost* a = root->current_frame_host();
+  RenderFrameHost* b = root->child_at(0)->current_frame_host();
+  RenderFrameHost* c1 = root->child_at(0)->child_at(0)->current_frame_host();
+  RenderFrameHost* d = root->child_at(1)->current_frame_host();
+  RenderFrameHost* c2 = root->child_at(1)->child_at(0)->current_frame_host();
+
+  // Sanity check that the test works with the right frame tree.
+  EXPECT_TRUE(IsIsolatedOrigin(a->GetLastCommittedOrigin()));
+  EXPECT_FALSE(IsIsolatedOrigin(b->GetLastCommittedOrigin()));
+  EXPECT_FALSE(IsIsolatedOrigin(d->GetLastCommittedOrigin()));
+  EXPECT_FALSE(IsIsolatedOrigin(c1->GetLastCommittedOrigin()));
+  EXPECT_FALSE(IsIsolatedOrigin(c2->GetLastCommittedOrigin()));
+  EXPECT_EQ("b.com", b->GetLastCommittedURL().host());
+  EXPECT_EQ("d.com", d->GetLastCommittedURL().host());
+  EXPECT_EQ("c.com", c1->GetLastCommittedURL().host());
+  EXPECT_EQ("c.com", c2->GetLastCommittedURL().host());
+
+  // Verify that the isolated site is indeed isolated.
+  EXPECT_NE(a->GetProcess()->GetID(), c1->GetProcess()->GetID());
+  EXPECT_NE(a->GetProcess()->GetID(), c2->GetProcess()->GetID());
+  EXPECT_NE(a->GetProcess()->GetID(), b->GetProcess()->GetID());
+  EXPECT_NE(a->GetProcess()->GetID(), d->GetProcess()->GetID());
+
+  // Verify that same-origin c1 and c2 frames share a process.  This is
+  // necessary for correctness - otherwise c1 and c2 wouldn't be able to
+  // synchronously script each other.
+  EXPECT_EQ(c1->GetProcess()->GetID(), c2->GetProcess()->GetID());
+
+  // Verify that same-origin c1 and c2 frames can script each other.
+  EXPECT_TRUE(ExecuteScript(c1, "window.name = 'c1';"));
+  EXPECT_TRUE(ExecuteScript(c2, R"(
+      c1 = window.open('', 'c1');
+      c1.cross_frame_property_test = 'hello from c2'; )"));
+  std::string actual_property_value;
+  EXPECT_TRUE(ExecuteScriptAndExtractString(
+      c1, "domAutomationController.send(window.cross_frame_property_test);",
+      &actual_property_value));
+  EXPECT_EQ("hello from c2", actual_property_value);
+
+  // The test assertions below are not strictly necessary - they just document
+  // the current behavior and might be tweaked if needed.  In particular,
+  // consolidating b,c,d sites into the same process is not necessary for
+  // correctness.  Consolidation might be desirable if we want to limit the
+  // number of renderer processes.  OTOH, consolidation might be undesirable
+  // if we desire smaller renderer processes (even if it means more processes).
+  if (!AreAllSitesIsolatedForTesting()) {
+    EXPECT_EQ(b->GetProcess()->GetID(), c1->GetProcess()->GetID());
+    EXPECT_EQ(b->GetProcess()->GetID(), c2->GetProcess()->GetID());
+    EXPECT_EQ(b->GetProcess()->GetID(), d->GetProcess()->GetID());
+  } else {
+    EXPECT_NE(b->GetProcess()->GetID(), c1->GetProcess()->GetID());
+    EXPECT_NE(b->GetProcess()->GetID(), c2->GetProcess()->GetID());
+    EXPECT_NE(b->GetProcess()->GetID(), d->GetProcess()->GetID());
+    EXPECT_EQ(c1->GetProcess()->GetID(), c2->GetProcess()->GetID());
+  }
+}
+
 // Helper class for testing dynamically-added isolated origins.  Tests that use
 // this run without full --site-per-process, but with two isolated origins that
 // are configured at startup (isolated.foo.com and isolated.bar.com).
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 161ab06..2ee25ad 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -494,10 +494,8 @@
           resource_context_, url_request_context_getter->GetURLRequestContext(),
           upload_file_system_context, *request_info_,
           std::move(navigation_ui_data_), std::move(url_loader_client),
-          std::move(url_loader),
-          nullptr /* service_worker_navigation_handle_core */,
-          appcache_handle_core, options, resource_request_->priority,
-          global_request_id_);
+          std::move(url_loader), appcache_handle_core, options,
+          resource_request_->priority, global_request_id_);
     }
 
     // TODO(arthursonzogni): Detect when the ResourceDispatcherHost didn't
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index 6a0b176..2247abad 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -56,8 +56,6 @@
 #include "content/browser/loader/throttling_resource_handler.h"
 #include "content/browser/loader/upload_data_stream_builder.h"
 #include "content/browser/resource_context_impl.h"
-#include "content/browser/service_worker/service_worker_context_wrapper.h"
-#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
 #include "content/browser/service_worker/service_worker_request_handler.h"
 #include "content/browser/streams/stream.h"
 #include "content/browser/streams/stream_context.h"
@@ -1502,7 +1500,6 @@
     std::unique_ptr<NavigationUIData> navigation_ui_data,
     network::mojom::URLLoaderClientPtr url_loader_client,
     network::mojom::URLLoaderRequest url_loader_request,
-    ServiceWorkerNavigationHandleCore* service_worker_handle_core,
     AppCacheNavigationHandleCore* appcache_handle_core,
     uint32_t url_loader_options,
     net::RequestPriority net_priority,
@@ -1586,10 +1583,7 @@
   // TODO(davidben): Associate the request with the FrameTreeNode and/or tab so
   // that IO thread -> UI thread hops will work.
   ResourceRequestInfoImpl* extra_info = new ResourceRequestInfoImpl(
-      ResourceRequesterInfo::CreateForBrowserSideNavigation(
-          service_worker_handle_core
-              ? service_worker_handle_core->context_wrapper()
-              : scoped_refptr<ServiceWorkerContextWrapper>()),
+      ResourceRequesterInfo::CreateForBrowserSideNavigation(),
       -1,  // route_id
       info.frame_tree_node_id,
       ChildProcessHost::kInvalidUniqueID,  // plugin_child_id
@@ -1632,16 +1626,6 @@
         blob_context->GetBlobDataFromPublicURL(new_request->url()));
   }
 
-  network::mojom::RequestContextFrameType frame_type =
-      info.is_main_frame ? network::mojom::RequestContextFrameType::kTopLevel
-                         : network::mojom::RequestContextFrameType::kNested;
-  ServiceWorkerRequestHandler::InitializeForNavigation(
-      new_request.get(), service_worker_handle_core, blob_context,
-      info.begin_params->skip_service_worker, resource_type,
-      info.begin_params->request_context_type, frame_type,
-      info.are_ancestors_secure, info.common_params.post_data,
-      extra_info->GetWebContentsGetterForRequest());
-
   // Have the appcache associate its extra info with the request.
   if (appcache_handle_core) {
     AppCacheInterceptor::SetExtraRequestInfoForHost(
diff --git a/content/browser/loader/resource_dispatcher_host_impl.h b/content/browser/loader/resource_dispatcher_host_impl.h
index 3bb8c72f..dfeee02 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.h
+++ b/content/browser/loader/resource_dispatcher_host_impl.h
@@ -77,7 +77,6 @@
 class ResourceMessageDelegate;
 class ResourceRequesterInfo;
 class ResourceRequestInfoImpl;
-class ServiceWorkerNavigationHandleCore;
 struct NavigationRequestInfo;
 struct Referrer;
 struct ResourceRequest;
@@ -235,7 +234,6 @@
       std::unique_ptr<NavigationUIData> navigation_ui_data,
       network::mojom::URLLoaderClientPtr url_loader_client,
       network::mojom::URLLoaderRequest url_loader_request,
-      ServiceWorkerNavigationHandleCore* service_worker_handle_core,
       AppCacheNavigationHandleCore* appcache_handle_core,
       uint32_t url_loader_options,
       net::RequestPriority net_priority,
diff --git a/content/browser/loader/resource_requester_info.cc b/content/browser/loader/resource_requester_info.cc
index 17c8b70..b16ba18 100644
--- a/content/browser/loader/resource_requester_info.cc
+++ b/content/browser/loader/resource_requester_info.cc
@@ -89,13 +89,12 @@
 }
 
 scoped_refptr<ResourceRequesterInfo>
-ResourceRequesterInfo::CreateForBrowserSideNavigation(
-    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context) {
+ResourceRequesterInfo::CreateForBrowserSideNavigation() {
   return scoped_refptr<ResourceRequesterInfo>(new ResourceRequesterInfo(
       RequesterType::BROWSER_SIDE_NAVIGATION,
       ChildProcessHost::kInvalidUniqueID, nullptr /* appcache_service */,
       nullptr /* blob_storage_context */, nullptr /* file_system_context */,
-      service_worker_context.get(), GetContextsCallback()));
+      nullptr /* service_worker_context */, GetContextsCallback()));
 }
 
 scoped_refptr<ResourceRequesterInfo>
diff --git a/content/browser/loader/resource_requester_info.h b/content/browser/loader/resource_requester_info.h
index 762feb7..24027c6 100644
--- a/content/browser/loader/resource_requester_info.h
+++ b/content/browser/loader/resource_requester_info.h
@@ -60,8 +60,7 @@
 
   // Creates a ResourceRequesterInfo for a requester that requests resources
   // within the browser process for browser side navigation (aka PlzNavigate).
-  static scoped_refptr<ResourceRequesterInfo> CreateForBrowserSideNavigation(
-      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context);
+  static scoped_refptr<ResourceRequesterInfo> CreateForBrowserSideNavigation();
 
   // Creates a ResourceRequesterInfo for a requester that requests resources for
   // download or page save.
diff --git a/content/browser/media/android/browser_gpu_video_accelerator_factories.cc b/content/browser/media/android/browser_gpu_video_accelerator_factories.cc
index a38b48a..fddfec8 100644
--- a/content/browser/media/android/browser_gpu_video_accelerator_factories.cc
+++ b/content/browser/media/android/browser_gpu_video_accelerator_factories.cc
@@ -106,8 +106,7 @@
 std::unique_ptr<media::VideoDecoder>
 BrowserGpuVideoAcceleratorFactories::CreateVideoDecoder(
     media::MediaLog* media_log,
-    const media::RequestOverlayInfoCB& request_overlay_info_cb,
-    const gfx::ColorSpace& target_color_space) {
+    const media::RequestOverlayInfoCB& request_overlay_info_cb) {
   return nullptr;
 }
 
diff --git a/content/browser/media/android/browser_gpu_video_accelerator_factories.h b/content/browser/media/android/browser_gpu_video_accelerator_factories.h
index 7e54de7..119c857 100644
--- a/content/browser/media/android/browser_gpu_video_accelerator_factories.h
+++ b/content/browser/media/android/browser_gpu_video_accelerator_factories.h
@@ -28,8 +28,7 @@
       const media::VideoDecoderConfig& config) override;
   std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
       media::MediaLog* media_log,
-      const media::RequestOverlayInfoCB& request_overlay_info_cb,
-      const gfx::ColorSpace& target_color_space) override;
+      const media::RequestOverlayInfoCB& request_overlay_info_cb) override;
   std::unique_ptr<media::VideoDecodeAccelerator> CreateVideoDecodeAccelerator()
       override;
   std::unique_ptr<media::VideoEncodeAccelerator> CreateVideoEncodeAccelerator()
diff --git a/content/browser/media/media_interface_proxy.cc b/content/browser/media/media_interface_proxy.cc
index a77cfb30..e1ff9b00 100644
--- a/content/browser/media/media_interface_proxy.cc
+++ b/content/browser/media/media_interface_proxy.cc
@@ -152,15 +152,6 @@
     factory->CreateVideoDecoder(std::move(request));
 }
 
-// TODO(https://crbug.com/936528) : remove this method.
-void MediaInterfaceProxy::CreateRenderer(
-    media::mojom::HostedRendererType type,
-    const std::string& audio_device_id,
-    media::mojom::RendererRequest renderer) {
-  DCHECK_EQ(type, media::mojom::HostedRendererType::kDefault);
-  CreateDefaultRenderer(audio_device_id, std::move(renderer));
-}
-
 void MediaInterfaceProxy::CreateDefaultRenderer(
     const std::string& audio_device_id,
     media::mojom::RendererRequest request) {
diff --git a/content/browser/media/media_interface_proxy.h b/content/browser/media/media_interface_proxy.h
index 60ecd18..f0c6bd5 100644
--- a/content/browser/media/media_interface_proxy.h
+++ b/content/browser/media/media_interface_proxy.h
@@ -49,10 +49,6 @@
   void CreateVideoDecoder(media::mojom::VideoDecoderRequest request) final;
   void CreateDefaultRenderer(const std::string& audio_device_id,
                              media::mojom::RendererRequest request) final;
-  // TODO(https://crbug.com/936528) : remove this method.
-  void CreateRenderer(media::mojom::HostedRendererType type,
-                      const std::string& audio_device_id,
-                      media::mojom::RendererRequest renderer) final;
 #if defined(OS_ANDROID)
   void CreateFlingingRenderer(const std::string& presentation_id,
                               media::mojom::RendererRequest request) final;
diff --git a/content/browser/media/video_decoder_proxy.cc b/content/browser/media/video_decoder_proxy.cc
index d588e3f..0df8b46 100644
--- a/content/browser/media/video_decoder_proxy.cc
+++ b/content/browser/media/video_decoder_proxy.cc
@@ -45,11 +45,6 @@
     const std::string& audio_device_id,
     media::mojom::RendererRequest request) {}
 
-// TODO(https://crbug.com/936528): remove this method.
-void VideoDecoderProxy::CreateRenderer(media::mojom::HostedRendererType type,
-                                       const std::string& audio_device_id,
-                                       media::mojom::RendererRequest request) {}
-
 #if defined(OS_ANDROID)
 void VideoDecoderProxy::CreateFlingingRenderer(
     const std::string& audio_device_id,
diff --git a/content/browser/media/video_decoder_proxy.h b/content/browser/media/video_decoder_proxy.h
index 76708a96..23bad96 100644
--- a/content/browser/media/video_decoder_proxy.h
+++ b/content/browser/media/video_decoder_proxy.h
@@ -31,10 +31,6 @@
   void CreateVideoDecoder(media::mojom::VideoDecoderRequest request) final;
   void CreateDefaultRenderer(const std::string& audio_device_id,
                              media::mojom::RendererRequest request) final;
-  // TODO(https://crbug.com/936528) : remove this method.
-  void CreateRenderer(media::mojom::HostedRendererType type,
-                      const std::string& audio_device_id,
-                      media::mojom::RendererRequest request) final;
 #if defined(OS_ANDROID)
   void CreateMediaPlayerRenderer(media::mojom::RendererRequest request) final;
   void CreateFlingingRenderer(const std::string& presentation_id,
diff --git a/content/browser/notifications/blink_notification_service_impl.cc b/content/browser/notifications/blink_notification_service_impl.cc
index 22b3836..f251139f 100644
--- a/content/browser/notifications/blink_notification_service_impl.cc
+++ b/content/browser/notifications/blink_notification_service_impl.cc
@@ -57,7 +57,7 @@
   if (include_triggered)
     return true;
   // Notifications without a trigger always match.
-  if (!database_data.notification_data.show_trigger_timestamp.has_value())
+  if (!database_data.notification_data.show_trigger_timestamp)
     return true;
   // Otherwise it has to be triggered already.
   return database_data.has_triggered;
@@ -211,8 +211,12 @@
   // TODO(https://crbug.com/870258): Validate resources are not too big (either
   // here or in the mojo struct traits).
 
-  if (platform_notification_data.show_trigger_timestamp.has_value())
+  if (platform_notification_data.show_trigger_timestamp &&
+      base::FeatureList::IsEnabled(features::kNotificationTriggers)) {
+    // TODO(knollr): Let PlatformNotificationContext display all notifications,
+    // even non scheduled ones and always set resources here.
     database_data.notification_resources = notification_resources;
+  }
 
   notification_context_->WriteNotificationData(
       next_persistent_id, service_worker_registration_id, origin_.GetURL(),
@@ -237,6 +241,16 @@
     return;
   }
 
+  if (platform_notification_data.show_trigger_timestamp &&
+      base::FeatureList::IsEnabled(features::kNotificationTriggers)) {
+    // This notification will be handled by the |notification_context_| because
+    // it has to be scheduled rather than displayed immediately.
+    // TODO(knollr): Let PlatformNotificationContext display all notifications,
+    // even non scheduled ones to make this code path go away.
+    std::move(callback).Run(PersistentNotificationError::NONE);
+    return;
+  }
+
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
       base::BindOnce(
diff --git a/content/browser/notifications/blink_notification_service_impl_unittest.cc b/content/browser/notifications/blink_notification_service_impl_unittest.cc
index e0958af4..a2fb2bb 100644
--- a/content/browser/notifications/blink_notification_service_impl_unittest.cc
+++ b/content/browser/notifications/blink_notification_service_impl_unittest.cc
@@ -778,6 +778,9 @@
 }
 
 TEST_F(BlinkNotificationServiceImplTest, GetTriggeredNotificationsWithFilter) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kNotificationTriggers);
+
   SetPermissionStatus(blink::mojom::PermissionStatus::GRANTED);
 
   scoped_refptr<ServiceWorkerRegistration> registration;
@@ -815,6 +818,9 @@
 }
 
 TEST_F(BlinkNotificationServiceImplTest, ResourcesStoredForTriggered) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kNotificationTriggers);
+
   SetPermissionStatus(blink::mojom::PermissionStatus::GRANTED);
 
   scoped_refptr<ServiceWorkerRegistration> registration;
@@ -862,4 +868,27 @@
   EXPECT_FALSE(stored_resources_b.has_value());
 }
 
+TEST_F(BlinkNotificationServiceImplTest, NotCallingDisplayForTriggered) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kNotificationTriggers);
+
+  SetPermissionStatus(blink::mojom::PermissionStatus::GRANTED);
+
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  RegisterServiceWorker(&registration);
+
+  base::Time timestamp = base::Time::Now() + base::TimeDelta::FromSeconds(10);
+  blink::PlatformNotificationData scheduled_notification_data;
+  scheduled_notification_data.show_trigger_timestamp = timestamp;
+  blink::NotificationResources resources;
+
+  DisplayPersistentNotificationSync(registration->id(),
+                                    scheduled_notification_data, resources);
+
+  // Wait for service to receive all the Display calls.
+  RunAllTasksUntilIdle();
+
+  EXPECT_EQ(0u, GetDisplayedNotifications().size());
+}
+
 }  // namespace content
diff --git a/content/browser/notifications/notification_trigger_constants.h b/content/browser/notifications/notification_trigger_constants.h
new file mode 100644
index 0000000..3d5a1e0
--- /dev/null
+++ b/content/browser/notifications/notification_trigger_constants.h
@@ -0,0 +1,15 @@
+// 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_BROWSER_NOTIFICATIONS_NOTIFICATION_TRIGGER_CONSTANTS_H_
+#define CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_TRIGGER_CONSTANTS_H_
+
+namespace content {
+
+// Maximum number of currently scheduled notifications per origin.
+constexpr int kMaximumScheduledNotificationsPerOrigin = 30;
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_TRIGGER_CONSTANTS_H_
diff --git a/content/browser/notifications/platform_notification_context_impl.cc b/content/browser/notifications/platform_notification_context_impl.cc
index 693aa19..9d43b4e 100644
--- a/content/browser/notifications/platform_notification_context_impl.cc
+++ b/content/browser/notifications/platform_notification_context_impl.cc
@@ -8,12 +8,15 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "base/task/post_task.h"
 #include "content/browser/notifications/blink_notification_service_impl.h"
 #include "content/browser/notifications/notification_database.h"
+#include "content/browser/notifications/notification_trigger_constants.h"
+#include "content/browser/notifications/platform_notification_service_proxy.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -21,6 +24,7 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/notification_database_data.h"
 #include "content/public/browser/platform_notification_service.h"
+#include "content/public/common/content_features.h"
 #include "third_party/blink/public/common/notifications/notification_resources.h"
 
 namespace content {
@@ -31,6 +35,13 @@
 const base::FilePath::CharType kPlatformNotificationsDirectory[] =
     FILE_PATH_LITERAL("Platform Notifications");
 
+// Checks if this notification can trigger in the future.
+bool CanTrigger(const NotificationDatabaseData& data) {
+  if (!base::FeatureList::IsEnabled(features::kNotificationTriggers))
+    return false;
+  return data.notification_data.show_trigger_timestamp && !data.has_triggered;
+}
+
 }  // namespace
 
 PlatformNotificationContextImpl::PlatformNotificationContextImpl(
@@ -56,6 +67,9 @@
 
 void PlatformNotificationContextImpl::Initialize() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  service_proxy_ = std::make_unique<PlatformNotificationServiceProxy>(
+      service_worker_context_, browser_context_);
+
   PlatformNotificationService* service =
       GetContentClient()->browser()->GetPlatformNotificationService();
   if (!service) {
@@ -84,10 +98,11 @@
   // because flakiness may cause a platform to inform Chrome of a notification
   // that has since been closed, or because the platform does not support
   // notifications that exceed the lifetime of the browser process.
-  if (supports_synchronization) {
-    LazyInitialize(
-        base::BindOnce(&PlatformNotificationContextImpl::DoSyncNotificationData,
-                       this, std::move(displayed_notifications)));
+  if (supports_synchronization ||
+      base::FeatureList::IsEnabled(features::kNotificationTriggers)) {
+    LazyInitialize(base::BindOnce(
+        &PlatformNotificationContextImpl::DoSyncNotificationData, this,
+        supports_synchronization, std::move(displayed_notifications)));
   }
 
   // |service_worker_context_| may be NULL in tests.
@@ -96,28 +111,57 @@
 }
 
 void PlatformNotificationContextImpl::DoSyncNotificationData(
+    bool supports_synchronization,
     std::set<std::string> displayed_notifications,
     bool initialized) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   if (!initialized)
     return;
 
-  // Iterate over all notifications and delete all expired ones. Passing |this|
-  // as Unretained is safe as this is synchronous.
+  // Reset |next_trigger_| to keep track of the next trigger timestamp.
+  next_trigger_ = base::nullopt;
+
+  // Iterate over all notifications and delete all expired ones.
   NotificationDatabase::Status status =
       database_->ForEachNotificationData(base::BindRepeating(
-          &PlatformNotificationContextImpl::DoHandleSyncNotification,
-          base::Unretained(this), displayed_notifications));
+          &PlatformNotificationContextImpl::DoHandleSyncNotification, this,
+          supports_synchronization, displayed_notifications));
 
   // Blow away the database if reading data failed due to corruption.
   if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
     DestroyDatabase();
+
+  // Schedule the next trigger timestamp.
+  if (next_trigger_) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+        base::BindOnce(&PlatformNotificationContextImpl::ScheduleTrigger, this,
+                       next_trigger_.value()));
+  }
 }
 
 void PlatformNotificationContextImpl::DoHandleSyncNotification(
+    bool supports_synchronization,
     const std::set<std::string>& displayed_notifications,
     const NotificationDatabaseData& data) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  // Handle pending notifications.
+  if (CanTrigger(data)) {
+    base::Time timestamp =
+        data.notification_data.show_trigger_timestamp.value();
+    // Check if we should display this notification.
+    if (timestamp <= base::Time::Now())
+      DoTriggerNotification(data);
+    else if (!next_trigger_ || next_trigger_.value() > timestamp)
+      next_trigger_ = timestamp;
+    return;
+  }
+
+  // Do not delete notifications if the platform does not support syncing them.
+  if (!supports_synchronization)
+    return;
+
+  // Delete notifications that are not on screen anymore.
   if (!displayed_notifications.count(data.notification_id))
     database_->DeleteNotificationData(data.notification_id, data.origin);
 }
@@ -125,6 +169,7 @@
 void PlatformNotificationContextImpl::Shutdown() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
+  service_proxy_.reset();
   services_.clear();
 
   // |service_worker_context_| may be NULL in tests.
@@ -203,6 +248,61 @@
                      NotificationDatabaseData()));
 }
 
+void PlatformNotificationContextImpl::ScheduleTrigger(base::Time timestamp) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  base::TimeDelta delay = timestamp - base::Time::Now();
+  if (delay.InMicroseconds() < 0)
+    delay = base::TimeDelta();
+
+  if (trigger_timer_.IsRunning() && trigger_timer_.GetCurrentDelay() <= delay)
+    return;
+
+  trigger_timer_.Start(FROM_HERE, delay, this,
+                       &PlatformNotificationContextImpl::TriggerNotifications);
+}
+
+void PlatformNotificationContextImpl::TriggerNotifications() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  std::set<std::string> displayed_notifications;
+  LazyInitialize(base::BindOnce(
+      &PlatformNotificationContextImpl::DoSyncNotificationData, this,
+      /* supports_synchronization= */ false,
+      std::move(displayed_notifications)));
+}
+
+void PlatformNotificationContextImpl::DoTriggerNotification(
+    const NotificationDatabaseData& database_data) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  // Bail out in case we can not display the notification after Shutdown.
+  if (!service_proxy_)
+    return;
+
+  blink::NotificationResources resources;
+  NotificationDatabase::Status status = database_->ReadNotificationResources(
+      database_data.notification_id, database_data.origin, &resources);
+
+  if (status != NotificationDatabase::STATUS_OK) {
+    // TODO(knollr): add UMA for this
+    resources = blink::NotificationResources();
+  }
+
+  // Create a copy of the |database_data| to store the |has_triggered| flag.
+  NotificationDatabaseData write_database_data = database_data;
+  write_database_data.has_triggered = true;
+  status = database_->WriteNotificationData(write_database_data.origin,
+                                            write_database_data);
+
+  if (status != NotificationDatabase::STATUS_OK) {
+    database_->DeleteNotificationData(write_database_data.notification_id,
+                                      write_database_data.origin);
+    return;
+  }
+
+  write_database_data.notification_resources = std::move(resources);
+  service_proxy_->DisplayNotification(std::move(write_database_data),
+                                      base::DoNothing());
+}
+
 void PlatformNotificationContextImpl::ReadNotificationResources(
     const std::string& notification_id,
     const GURL& origin,
@@ -329,7 +429,8 @@
         // The database is only used for persistent notifications.
         DCHECK(NotificationIdGenerator::IsPersistentNotification(
             it->notification_id));
-        if (displayed_notifications.count(it->notification_id)) {
+        if (displayed_notifications.count(it->notification_id) ||
+            CanTrigger(*it)) {
           ++it;
         } else {
           obsolete_notifications.push_back(it->notification_id);
@@ -372,6 +473,28 @@
       database_data, std::move(callback)));
 }
 
+bool PlatformNotificationContextImpl::DoCheckNotificationTriggerQuota(
+    const GURL& origin) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  int notification_count = 0;
+  // Iterate over all notifications and count all scheduled notifications for
+  // |origin|.
+  NotificationDatabase::Status status =
+      database_->ForEachNotificationData(base::BindRepeating(
+          [](const GURL& expected_origin, int* count,
+             const NotificationDatabaseData& data) {
+            if (CanTrigger(data) && data.origin == expected_origin)
+              *count = *count + 1;
+          },
+          origin, &notification_count));
+
+  // Blow away the database if reading data failed due to corruption.
+  if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
+    DestroyDatabase();
+
+  return notification_count < kMaximumScheduledNotificationsPerOrigin;
+}
+
 void PlatformNotificationContextImpl::DoWriteNotificationData(
     int64_t service_worker_registration_id,
     int64_t persistent_notification_id,
@@ -389,6 +512,12 @@
     return;
   }
 
+  bool replaces_existing = false;
+  std::string notification_id =
+      notification_id_generator_.GenerateForPersistentNotification(
+          origin, database_data.notification_data.tag,
+          persistent_notification_id);
+
   // Eagerly delete data for replaced notifications from the database.
   if (!database_data.notification_data.tag.empty()) {
     std::set<std::string> deleted_notification_ids;
@@ -397,6 +526,8 @@
             origin, database_data.notification_data.tag,
             &deleted_notification_ids);
 
+    replaces_existing = deleted_notification_ids.count(notification_id) != 0;
+
     UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DeleteBeforeWriteResult",
                               delete_status,
                               NotificationDatabase::STATUS_COUNT);
@@ -417,10 +548,18 @@
 
   // Create a copy of the |database_data| to store a generated notification ID.
   NotificationDatabaseData write_database_data = database_data;
-  write_database_data.notification_id =
-      notification_id_generator_.GenerateForPersistentNotification(
-          origin, database_data.notification_data.tag,
-          persistent_notification_id);
+  write_database_data.notification_id = notification_id;
+  write_database_data.origin = origin;
+
+  if (CanTrigger(write_database_data) &&
+      !DoCheckNotificationTriggerQuota(origin)) {
+    // TODO(knollr): Reply with a custom error so developers can handle this.
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+        base::BindOnce(std::move(callback), /* success= */ false,
+                       /* notification_id= */ ""));
+    return;
+  }
 
   NotificationDatabase::Status status =
       database_->WriteNotificationData(origin, write_database_data);
@@ -429,6 +568,18 @@
                             NotificationDatabase::STATUS_COUNT);
 
   if (status == NotificationDatabase::STATUS_OK) {
+    if (CanTrigger(write_database_data)) {
+      if (replaces_existing)
+        service_proxy_->CloseNotification(notification_id);
+      // Schedule notification to be shown.
+      base::PostTaskWithTraits(
+          FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+          base::BindOnce(&PlatformNotificationContextImpl::ScheduleTrigger,
+                         this,
+                         write_database_data.notification_data
+                             .show_trigger_timestamp.value()));
+    }
+
     base::PostTaskWithTraits(
         FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
         base::BindOnce(std::move(callback), /* success= */ true,
diff --git a/content/browser/notifications/platform_notification_context_impl.h b/content/browser/notifications/platform_notification_context_impl.h
index 268e9ff..c7c9bc8f 100644
--- a/content/browser/notifications/platform_notification_context_impl.h
+++ b/content/browser/notifications/platform_notification_context_impl.h
@@ -16,6 +16,9 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "content/browser/notifications/notification_database.h"
 #include "content/browser/notifications/notification_id_generator.h"
 #include "content/browser/service_worker/service_worker_context_core_observer.h"
@@ -39,6 +42,7 @@
 class BlinkNotificationServiceImpl;
 class BrowserContext;
 struct NotificationDatabaseData;
+class PlatformNotificationServiceProxy;
 class ServiceWorkerContextWrapper;
 
 // Implementation of the Web Notification storage context. The public methods
@@ -103,6 +107,7 @@
 
  private:
   friend class PlatformNotificationContextTest;
+  friend class PlatformNotificationContextTriggerTest;
 
   ~PlatformNotificationContextImpl() override;
 
@@ -116,6 +121,15 @@
   // called with true, otherwise it will be called with false.
   void LazyInitialize(InitializeResultCallback callback);
 
+  // Schedules a job to run at |timestamp| and call TriggerNotifications.
+  void ScheduleTrigger(base::Time timestamp);
+
+  // Trigger all pending notifications.
+  void TriggerNotifications();
+
+  // Marks this notification as shown and displays it.
+  void DoTriggerNotification(const NotificationDatabaseData& database_data);
+
   // Opens the database. Must be called on the |task_runner_| thread. |callback|
   // will be invoked on the |task_runner_| thread. When the database has been
   // successfully opened, |callback| will be called with true, otherwise it will
@@ -141,12 +155,14 @@
 
   // Synchronize displayed notifications. This removes all non-displayed
   // notifications from the database.
-  void DoSyncNotificationData(std::set<std::string> displayed_notifications,
+  void DoSyncNotificationData(bool supports_synchronization,
+                              std::set<std::string> displayed_notifications,
                               bool initialized);
 
   // Checks if the given notification is still valid, otherwise deletes it from
   // the database.
   void DoHandleSyncNotification(
+      bool supports_synchronization,
       const std::set<std::string>& displayed_notifications,
       const NotificationDatabaseData& data);
 
@@ -170,6 +186,10 @@
       bool supports_synchronization,
       bool initialized);
 
+  // Checks if the number of notifications scheduled for |origin| does not
+  // exceed the quota.
+  bool DoCheckNotificationTriggerQuota(const GURL& origin);
+
   // Actually writes the notification database to the database. Must only be
   // called on the |task_runner_| thread. |callback| will be invoked on the
   // UI thread when the operation has completed.
@@ -219,6 +239,15 @@
 
   NotificationIdGenerator notification_id_generator_;
 
+  // Triggers pending notifications, set by ScheduleTrigger.
+  base::OneShotTimer trigger_timer_;
+
+  // Keeps track of the next trigger timestamp.
+  base::Optional<base::Time> next_trigger_;
+
+  // Calls through to PlatformNotificationService methods.
+  std::unique_ptr<PlatformNotificationServiceProxy> service_proxy_;
+
   // The notification services are owned by the platform context, and will be
   // removed when either this class is destroyed or the Mojo pipe disconnects.
   std::vector<std::unique_ptr<BlinkNotificationServiceImpl>> services_;
diff --git a/content/browser/notifications/platform_notification_context_trigger_unittest.cc b/content/browser/notifications/platform_notification_context_trigger_unittest.cc
new file mode 100644
index 0000000..9d43d36c
--- /dev/null
+++ b/content/browser/notifications/platform_notification_context_trigger_unittest.cc
@@ -0,0 +1,252 @@
+// 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 <stdint.h>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/time/time.h"
+#include "content/browser/notifications/notification_trigger_constants.h"
+#include "content/browser/notifications/platform_notification_context_impl.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/public/browser/notification_database_data.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/test/mock_platform_notification_service.h"
+#include "content/test/test_content_browser_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
+#include "url/gurl.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace content {
+
+// Fake Service Worker registration id to use in tests requiring one.
+const int64_t kFakeServiceWorkerRegistrationId = 42;
+
+class NotificationBrowserClient : public TestContentBrowserClient {
+ public:
+  NotificationBrowserClient()
+      : platform_notification_service_(
+            std::make_unique<MockPlatformNotificationService>()) {}
+
+  PlatformNotificationService* GetPlatformNotificationService() override {
+    return platform_notification_service_.get();
+  }
+
+ private:
+  std::unique_ptr<PlatformNotificationService> platform_notification_service_;
+};
+
+class PlatformNotificationContextTriggerTest : public ::testing::Test {
+ public:
+  PlatformNotificationContextTriggerTest()
+      : thread_bundle_(
+            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
+            base::test::ScopedTaskEnvironment::NowSource::
+                MAIN_THREAD_MOCK_TIME),
+        success_(false) {}
+
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(features::kNotificationTriggers);
+    SetBrowserClientForTesting(&notification_browser_client_);
+    platform_notification_service_ =
+        notification_browser_client_.GetPlatformNotificationService();
+    platform_notification_context_ =
+        base::MakeRefCounted<PlatformNotificationContextImpl>(
+            base::FilePath(), &browser_context_, nullptr);
+    platform_notification_context_->SetTaskRunnerForTesting(
+        base::ThreadTaskRunnerHandle::Get());
+    platform_notification_context_->Initialize();
+    base::RunLoop().RunUntilIdle();
+  }
+
+  // Callback to provide when writing a notification to the database.
+  void DidWriteNotificationData(bool success,
+                                const std::string& notification_id) {
+    success_ = success;
+  }
+
+  // Callback to provide when deleting notification data from the database.
+  void DidDeleteNotificationData(bool success) { success_ = success; }
+
+  // Callback to provide when getting displayed notifications from the
+  // PlatformNotificationService.
+  void DidGetDisplayedNotifications(std::set<std::string> notification_ids,
+                                    bool supports_synchronization) {
+    displayed_notification_ids_ = notification_ids;
+  }
+
+ protected:
+  void WriteNotificationData(const std::string& tag,
+                             base::Optional<base::Time> timestamp) {
+    ASSERT_TRUE(
+        TryWriteNotificationData("https://example.com", tag, timestamp));
+  }
+
+  bool TryWriteNotificationData(const std::string& url,
+                                const std::string& tag,
+                                base::Optional<base::Time> timestamp) {
+    GURL origin(url);
+    NotificationDatabaseData notification_database_data;
+    notification_database_data.origin = origin;
+    notification_database_data.service_worker_registration_id =
+        kFakeServiceWorkerRegistrationId;
+    notification_database_data.notification_data.show_trigger_timestamp =
+        timestamp;
+    notification_database_data.notification_data.tag = tag;
+
+    platform_notification_context_->WriteNotificationData(
+        next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
+        origin, notification_database_data,
+        base::BindOnce(
+            &PlatformNotificationContextTriggerTest::DidWriteNotificationData,
+            base::Unretained(this)));
+    base::RunLoop().RunUntilIdle();
+    return success_;
+  }
+
+  std::set<std::string> GetDisplayedNotifications() {
+    platform_notification_service_->GetDisplayedNotifications(
+        &browser_context_,
+        base::BindOnce(&PlatformNotificationContextTriggerTest::
+                           DidGetDisplayedNotifications,
+                       base::Unretained(this)));
+    base::RunLoop().RunUntilIdle();
+    return displayed_notification_ids_;
+  }
+
+  TestBrowserThreadBundle thread_bundle_;  // Must be first member
+  TestBrowserContext browser_context_;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  NotificationBrowserClient notification_browser_client_;
+  PlatformNotificationService* platform_notification_service_;
+  scoped_refptr<PlatformNotificationContextImpl> platform_notification_context_;
+
+  // Returns the next persistent notification id for tests.
+  int64_t next_persistent_notification_id() {
+    return next_persistent_notification_id_++;
+  }
+
+  bool success_;
+  NotificationDatabaseData database_data_;
+  std::string notification_id_;
+  int64_t next_persistent_notification_id_ = 1;
+  std::set<std::string> displayed_notification_ids_;
+};
+
+TEST_F(PlatformNotificationContextTriggerTest, TriggerInFuture) {
+  WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
+  ASSERT_EQ(0u, GetDisplayedNotifications().size());
+
+  // Wait until the trigger timestamp is reached.
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(10));
+  ASSERT_EQ(1u, GetDisplayedNotifications().size());
+}
+
+TEST_F(PlatformNotificationContextTriggerTest, TriggerInPast) {
+  // Trigger timestamp in the past should immediately trigger.
+  WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
+  ASSERT_EQ(1u, GetDisplayedNotifications().size());
+}
+
+TEST_F(PlatformNotificationContextTriggerTest,
+       OverwriteExistingTriggerInFuture) {
+  WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
+  ASSERT_EQ(0u, GetDisplayedNotifications().size());
+
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
+  ASSERT_EQ(0u, GetDisplayedNotifications().size());
+
+  // Overwrites the scheduled notifications with a new trigger timestamp.
+  WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
+
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
+  ASSERT_EQ(0u, GetDisplayedNotifications().size());
+
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
+  ASSERT_EQ(1u, GetDisplayedNotifications().size());
+}
+
+TEST_F(PlatformNotificationContextTriggerTest, OverwriteExistingTriggerToPast) {
+  WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
+  ASSERT_EQ(0u, GetDisplayedNotifications().size());
+
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
+
+  // Overwrites the scheduled notifications with a new trigger timestamp.
+  WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
+  ASSERT_EQ(1u, GetDisplayedNotifications().size());
+}
+
+TEST_F(PlatformNotificationContextTriggerTest,
+       OverwriteDisplayedNotificationToPast) {
+  WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(10));
+
+  // Overwrites a displayed notification with a trigger timestamp in the past.
+  WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
+  ASSERT_EQ(1u, GetDisplayedNotifications().size());
+}
+
+TEST_F(PlatformNotificationContextTriggerTest,
+       OverwriteDisplayedNotificationToFuture) {
+  WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(10));
+
+  // Overwrites a displayed notification which hides it until the trigger
+  // timestamp is reached.
+  WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
+
+  ASSERT_EQ(0u, GetDisplayedNotifications().size());
+
+  thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(10));
+  ASSERT_EQ(1u, GetDisplayedNotifications().size());
+}
+
+TEST_F(PlatformNotificationContextTriggerTest,
+       LimitsNumberOfScheduledNotificationsPerOrigin) {
+  for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
+    WriteNotificationData(std::to_string(i),
+                          Time::Now() + TimeDelta::FromSeconds(i));
+  }
+
+  ASSERT_FALSE(TryWriteNotificationData(
+      "https://example.com",
+      std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
+      Time::Now() +
+          TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
+
+  ASSERT_TRUE(TryWriteNotificationData(
+      "https://example2.com",
+      std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
+      Time::Now() +
+          TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
+}
+
+TEST_F(PlatformNotificationContextTriggerTest, EnforcesLimitOnUpdate) {
+  for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
+    WriteNotificationData(std::to_string(i),
+                          Time::Now() + TimeDelta::FromSeconds(i));
+  }
+
+  ASSERT_TRUE(TryWriteNotificationData(
+      "https://example.com",
+      std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
+      base::nullopt));
+
+  ASSERT_FALSE(TryWriteNotificationData(
+      "https://example.com",
+      std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
+      Time::Now() +
+          TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
+}
+
+}  // namespace content
diff --git a/content/browser/notifications/platform_notification_service_proxy.cc b/content/browser/notifications/platform_notification_service_proxy.cc
new file mode 100644
index 0000000..8ce2ed287
--- /dev/null
+++ b/content/browser/notifications/platform_notification_service_proxy.cc
@@ -0,0 +1,100 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/notifications/platform_notification_service_proxy.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/task/post_task.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_database_data.h"
+#include "content/public/browser/platform_notification_service.h"
+
+namespace content {
+
+PlatformNotificationServiceProxy::PlatformNotificationServiceProxy(
+    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+    BrowserContext* browser_context)
+    : service_worker_context_(service_worker_context),
+      browser_context_(browser_context),
+      notification_service_(
+          GetContentClient()->browser()->GetPlatformNotificationService()),
+      weak_ptr_factory_(this) {}
+
+PlatformNotificationServiceProxy::~PlatformNotificationServiceProxy() = default;
+
+void PlatformNotificationServiceProxy::DoDisplayNotification(
+    const NotificationDatabaseData& data,
+    const GURL& service_worker_scope,
+    DisplayResultCallback callback) {
+  if (notification_service_) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+        base::BindOnce(
+            &PlatformNotificationService::DisplayPersistentNotification,
+            base::Unretained(notification_service_), browser_context_,
+            data.notification_id, service_worker_scope, data.origin,
+            data.notification_data,
+            data.notification_resources.value_or(
+                blink::NotificationResources())));
+  }
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+      base::BindOnce(std::move(callback), /* success= */ true,
+                     data.notification_id));
+}
+
+void PlatformNotificationServiceProxy::VerifyServiceWorkerScope(
+    const NotificationDatabaseData& data,
+    DisplayResultCallback callback,
+    blink::ServiceWorkerStatusCode status,
+    scoped_refptr<ServiceWorkerRegistration> registration) {
+  if (status == blink::ServiceWorkerStatusCode::kOk &&
+      registration->scope().GetOrigin() == data.origin) {
+    DoDisplayNotification(data, registration->scope(), std::move(callback));
+  } else {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+        base::BindOnce(std::move(callback), /* success= */ false,
+                       /* notification_id= */ ""));
+  }
+}
+
+void PlatformNotificationServiceProxy::DisplayNotification(
+    const NotificationDatabaseData& data,
+    DisplayResultCallback callback) {
+  if (!service_worker_context_) {
+    DoDisplayNotification(data, GURL(), std::move(callback));
+    return;
+  }
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::IO, base::TaskPriority::USER_VISIBLE},
+      base::BindOnce(
+          &ServiceWorkerContextWrapper::FindReadyRegistrationForId,
+          service_worker_context_, data.service_worker_registration_id,
+          data.origin,
+          base::BindOnce(
+              &PlatformNotificationServiceProxy::VerifyServiceWorkerScope,
+              weak_ptr_factory_.GetWeakPtr(), data, std::move(callback))));
+}
+
+void PlatformNotificationServiceProxy::CloseNotification(
+    const std::string& notification_id) {
+  if (!notification_service_)
+    return;
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+      base::BindOnce(&PlatformNotificationService::ClosePersistentNotification,
+                     base::Unretained(notification_service_), browser_context_,
+                     notification_id));
+}
+
+}  // namespace content
diff --git a/content/browser/notifications/platform_notification_service_proxy.h b/content/browser/notifications/platform_notification_service_proxy.h
new file mode 100644
index 0000000..85f59099d
--- /dev/null
+++ b/content/browser/notifications/platform_notification_service_proxy.h
@@ -0,0 +1,77 @@
+// 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_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_SERVICE_PROXY_H_
+#define CONTENT_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_SERVICE_PROXY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+
+class GURL;
+
+namespace blink {
+enum class ServiceWorkerStatusCode;
+}  // namespace blink
+
+namespace content {
+
+class BrowserContext;
+struct NotificationDatabaseData;
+class PlatformNotificationService;
+class ServiceWorkerContextWrapper;
+class ServiceWorkerRegistration;
+
+class CONTENT_EXPORT PlatformNotificationServiceProxy {
+ public:
+  using DisplayResultCallback =
+      base::OnceCallback<void(bool /* success */,
+                              const std::string& /* notification_id */)>;
+
+  PlatformNotificationServiceProxy(
+      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+      BrowserContext* browser_context);
+
+  ~PlatformNotificationServiceProxy();
+
+  // Displays a notification with |data| and calls |callback| with the result.
+  // This will verify against the given |service_worker_context_| if available.
+  void DisplayNotification(const NotificationDatabaseData& data,
+                           DisplayResultCallback callback);
+
+  // Closes the notification with |notification_id|.
+  void CloseNotification(const std::string& notification_id);
+
+ private:
+  // Actually calls |notification_service_| to display the notification after
+  // verifying the |service_worker_scope|.
+  void DoDisplayNotification(const NotificationDatabaseData& data,
+                             const GURL& service_worker_scope,
+                             DisplayResultCallback callback);
+
+  // Verifies that the service worker exists and is valid for the given
+  // notification origin.
+  void VerifyServiceWorkerScope(
+      const NotificationDatabaseData& data,
+      DisplayResultCallback callback,
+      blink::ServiceWorkerStatusCode status,
+      scoped_refptr<ServiceWorkerRegistration> registration);
+
+  scoped_refptr<ServiceWorkerContextWrapper> service_worker_context_;
+  BrowserContext* browser_context_;
+  PlatformNotificationService* notification_service_;
+
+  base::WeakPtrFactory<PlatformNotificationServiceProxy> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(PlatformNotificationServiceProxy);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_SERVICE_PROXY_H_
diff --git a/content/browser/renderer_host/accessibility_tree_linkage_win_browsertest.cc b/content/browser/renderer_host/accessibility_tree_linkage_win_browsertest.cc
new file mode 100644
index 0000000..6800517
--- /dev/null
+++ b/content/browser/renderer_host/accessibility_tree_linkage_win_browsertest.cc
@@ -0,0 +1,129 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/accessibility/browser_accessibility.h"
+#include "content/browser/renderer_host/legacy_render_widget_host_win.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_aura.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "ui/accessibility/accessibility_switches.h"
+#include "ui/accessibility/platform/ax_fragment_root_win.h"
+#include "ui/accessibility/platform/ax_platform_node.h"
+#include "ui/aura/client/aura_constants.h"
+
+namespace content {
+
+struct AccessibilityLinkageTestParams {
+  bool is_uia_enabled;
+  bool is_legacy_window_disabled;
+} const kTestParameters[] = {{false, false},
+                             {false, true},
+                             {true, false},
+                             {true, true}};
+
+class AccessibilityTreeLinkageWinBrowserTest
+    : public ContentBrowserTest,
+      public ::testing::WithParamInterface<AccessibilityLinkageTestParams> {
+ public:
+  AccessibilityTreeLinkageWinBrowserTest() {
+    dummy_ax_platform_node_ = ui::AXPlatformNode::Create(&dummy_ax_node_);
+  }
+
+  ~AccessibilityTreeLinkageWinBrowserTest() override {
+    dummy_ax_platform_node_->Destroy();
+    dummy_ax_platform_node_ = nullptr;
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    if (GetParam().is_uia_enabled)
+      base::CommandLine::ForCurrentProcess()->AppendSwitch(
+          ::switches::kEnableExperimentalUIAutomation);
+    if (GetParam().is_legacy_window_disabled)
+      base::CommandLine::ForCurrentProcess()->AppendSwitch(
+          ::switches::kDisableLegacyIntermediateWindow);
+  }
+
+  RenderWidgetHostViewAura* GetView() {
+    return static_cast<RenderWidgetHostViewAura*>(
+        shell()->web_contents()->GetRenderWidgetHostView());
+  }
+
+  gfx::NativeWindow GetParentWindow() { return GetView()->window()->parent(); }
+
+  LegacyRenderWidgetHostHWND* GetLegacyRenderWidgetHostHWND() {
+    return GetView()->legacy_render_widget_host_HWND_;
+  }
+
+ protected:
+  ui::AXPlatformNodeDelegateBase dummy_ax_node_;
+  ui::AXPlatformNode* dummy_ax_platform_node_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AccessibilityTreeLinkageWinBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(AccessibilityTreeLinkageWinBrowserTest, Linkage) {
+  NavigateToURL(shell(), GURL(url::kAboutBlankURL));
+
+  GetParentWindow()->SetProperty(
+      aura::client::kParentNativeViewAccessibleKey,
+      dummy_ax_platform_node_->GetNativeViewAccessible());
+
+  if (GetParam().is_legacy_window_disabled)
+    ASSERT_EQ(GetLegacyRenderWidgetHostHWND(), nullptr);
+  else
+    ASSERT_NE(GetLegacyRenderWidgetHostHWND(), nullptr);
+
+  // Used by WebView to splice in the web content root accessible as a child of
+  // the WebView's parent
+  gfx::NativeViewAccessible native_view_accessible =
+      GetView()->GetNativeViewAccessible();
+  if (GetParam().is_uia_enabled && !GetParam().is_legacy_window_disabled) {
+    EXPECT_EQ(native_view_accessible,
+              ui::AXFragmentRootWin::GetForAcceleratedWidget(
+                  GetView()->AccessibilityGetAcceleratedWidget())
+                  ->GetNativeViewAccessible());
+  } else {
+    EXPECT_EQ(native_view_accessible, GetView()
+                                          ->host()
+                                          ->GetRootBrowserAccessibilityManager()
+                                          ->GetRoot()
+                                          ->GetNativeViewAccessible());
+  }
+
+  // Used by LegacyRenderWidgetHostHWND to find the parent of the UIA fragment
+  // root for web content
+  gfx::NativeViewAccessible parent_native_view_accessible =
+      GetView()->GetParentNativeViewAccessible();
+  EXPECT_EQ(parent_native_view_accessible,
+            dummy_ax_platform_node_->GetNativeViewAccessible());
+
+  // Used by BrowserAccessibilityManager to find the parent of the web content
+  // root accessible
+  gfx::NativeViewAccessible accessibility_native_view_accessible =
+      GetView()->AccessibilityGetNativeViewAccessible();
+  if (GetParam().is_legacy_window_disabled) {
+    EXPECT_EQ(accessibility_native_view_accessible,
+              dummy_ax_platform_node_->GetNativeViewAccessible());
+  } else {
+    if (GetParam().is_uia_enabled) {
+      EXPECT_EQ(accessibility_native_view_accessible,
+                ui::AXFragmentRootWin::GetForAcceleratedWidget(
+                    GetView()->AccessibilityGetAcceleratedWidget())
+                    ->GetNativeViewAccessible());
+    } else {
+      EXPECT_EQ(accessibility_native_view_accessible,
+                GetLegacyRenderWidgetHostHWND()->window_accessible());
+    }
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         AccessibilityTreeLinkageWinBrowserTest,
+                         testing::ValuesIn(kTestParameters));
+
+}  // namespace content
diff --git a/content/browser/renderer_host/legacy_render_widget_host_win.cc b/content/browser/renderer_host/legacy_render_widget_host_win.cc
index 330cb33..a99ca77 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.cc
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.cc
@@ -248,25 +248,11 @@
       BrowserAccessibilityStateImpl::GetInstance()->EnableAccessibility();
     }
 
-    RenderWidgetHostImpl* rwhi =
-        RenderWidgetHostImpl::From(host_->GetRenderWidgetHost());
-    if (!rwhi)
+    gfx::NativeViewAccessible root = GetOrCreateWindowRootAccessible();
+    if (root == nullptr)
       return static_cast<LRESULT>(0L);
 
-    BrowserAccessibilityManagerWin* manager =
-        static_cast<BrowserAccessibilityManagerWin*>(
-            rwhi->GetRootBrowserAccessibilityManager());
-    if (!manager || !manager->GetRoot())
-      return static_cast<LRESULT>(0L);
-
-    BrowserAccessibilityComWin* root =
-        ToBrowserAccessibilityWin(manager->GetRoot())->GetCOM();
-
     if (is_uia_request) {
-      if (!ax_fragment_root_)
-        ax_fragment_root_ =
-            std::make_unique<ui::AXFragmentRootWin>(hwnd(), root);
-
       Microsoft::WRL::ComPtr<IRawElementProviderSimple> root_uia;
       ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
           IID_PPV_ARGS(&root_uia));
@@ -554,6 +540,34 @@
     DestroyAnimationObserver();
 }
 
+gfx::NativeViewAccessible
+LegacyRenderWidgetHostHWND::GetOrCreateWindowRootAccessible() {
+  RenderWidgetHostImpl* rwhi =
+      RenderWidgetHostImpl::From(host_->GetRenderWidgetHost());
+  if (!rwhi)
+    return nullptr;
+
+  BrowserAccessibilityManagerWin* manager =
+      static_cast<BrowserAccessibilityManagerWin*>(
+          rwhi->GetOrCreateRootBrowserAccessibilityManager());
+  if (!manager || !manager->GetRoot())
+    return nullptr;
+
+  BrowserAccessibilityComWin* root =
+      ToBrowserAccessibilityWin(manager->GetRoot())->GetCOM();
+
+  if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled()) {
+    if (!ax_fragment_root_) {
+      ax_fragment_root_ = std::make_unique<ui::AXFragmentRootWin>(hwnd(), root);
+      ax_fragment_root_->SetParent(host_->GetParentNativeViewAccessible());
+    }
+
+    return ax_fragment_root_->GetNativeViewAccessible();
+  }
+
+  return root->GetNativeViewAccessible();
+}
+
 void LegacyRenderWidgetHostHWND::CreateAnimationObserver() {
   DCHECK(!compositor_animation_observer_);
   DCHECK(host_);
diff --git a/content/browser/renderer_host/legacy_render_widget_host_win.h b/content/browser/renderer_host/legacy_render_widget_host_win.h
index 3be08b1..ec2ec68 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.h
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.h
@@ -16,6 +16,7 @@
 #include "content/common/content_export.h"
 #include "ui/compositor/compositor_animation_observer.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/native_widget_types.h"
 
 namespace ui {
 class AXFragmentRootWin;
@@ -128,6 +129,9 @@
   // gesturing on touchpad.
   void PollForNextEvent();
 
+  // Return the root accessible object for either MSAA or UI Automation.
+  gfx::NativeViewAccessible GetOrCreateWindowRootAccessible();
+
  protected:
   void OnFinalMessage(HWND hwnd) override;
 
diff --git a/content/browser/renderer_host/render_process_host_browsertest.cc b/content/browser/renderer_host/render_process_host_browsertest.cc
index 4caf7d5..7c551b5c 100644
--- a/content/browser/renderer_host/render_process_host_browsertest.cc
+++ b/content/browser/renderer_host/render_process_host_browsertest.cc
@@ -416,7 +416,9 @@
   GURL test_url = embedded_test_server()->GetURL("/simple_page.html");
   BrowserContext* browser_context =
       ShellContentBrowserClient::Get()->browser_context();
-  GURL test_site = SiteInstance::GetSiteForURL(browser_context, test_url);
+  scoped_refptr<SiteInstance> test_site_instance =
+      SiteInstance::Create(browser_context)->GetRelatedSiteInstance(test_url);
+  GURL test_site = test_site_instance->GetSiteURL();
   CustomStoragePartitionForSomeSites modified_client(test_site);
   ContentBrowserClient* old_client =
       SetBrowserClientForTesting(&modified_client);
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index a8185e63..92a6bcde 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -1608,13 +1608,6 @@
       << "RenderProcessHostImpl is destroyed by something other than itself";
 #endif
 
-  if (render_frame_message_filter_) {
-    base::PostTaskWithTraits(
-        FROM_HERE, {BrowserThread::IO},
-        base::BindOnce(&RenderFrameMessageFilter::ClearResourceContext,
-                       render_frame_message_filter_));
-  }
-
   // Make sure to clean up the in-process renderer before the channel, otherwise
   // it may still run and have its IPCs fail, causing asserts.
   in_process_renderer_.reset();
@@ -2647,6 +2640,11 @@
 ChildProcessImportance RenderProcessHostImpl::GetEffectiveImportance() {
   return effective_importance_;
 }
+
+void RenderProcessHostImpl::DumpProcessStack() {
+  if (child_process_launcher_)
+    child_process_launcher_->DumpProcessStack();
+}
 #endif
 
 RendererAudioOutputStreamFactoryContext*
@@ -2844,7 +2842,7 @@
   if (!SiteInstanceImpl::IsOriginLockASite(lock_url))
     return;
 
-  GetRendererInterface()->SetIsLockedToSite();
+  GetRendererInterface()->SetIsLockedToSite(lock_url);
 }
 
 bool RenderProcessHostImpl::IsForGuestsOnly() {
@@ -3493,6 +3491,19 @@
   base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
   deleting_soon_ = true;
 
+  if (render_frame_message_filter_) {
+    // RenderFrameMessageFilter is refcounted and can outlive the
+    // ResourceContext. If the BrowserContext is shutting down, after
+    // RenderProcessHostImpl cleanup a task will be posted to the IO thread
+    // that destroys the ResourceContext. Therefore the ClearResourceContext
+    // task must be posted now to ensure it gets ahead of the destruction of
+    // the ResourceContext in the IOThread sequence.
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::IO},
+        base::BindOnce(&RenderFrameMessageFilter::ClearResourceContext,
+                       render_frame_message_filter_));
+  }
+
   // Destroy all mojo bindings and IPC channels that can cause calls to this
   // object, to avoid method invocations that trigger usages of profile.
   ResetIPC();
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index 5b1f0474..a746e1d 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -185,6 +185,7 @@
   void RemovePriorityClient(PriorityClient* priority_client) override;
 #if defined(OS_ANDROID)
   ChildProcessImportance GetEffectiveImportance() override;
+  void DumpProcessStack() override;
 #endif
   void SetSuddenTerminationAllowed(bool enabled) override;
   bool SuddenTerminationAllowed() override;
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 c272f6a..cbee55a 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -569,10 +569,15 @@
   aura::WindowTreeHost* window_host = window_->GetHost();
   if (!window_host)
     return static_cast<gfx::NativeViewAccessible>(NULL);
+
+  if (legacy_render_widget_host_HWND_)
+    return legacy_render_widget_host_HWND_->GetOrCreateWindowRootAccessible();
+
   BrowserAccessibilityManager* manager =
       host()->GetOrCreateRootBrowserAccessibilityManager();
   if (manager)
     return ToBrowserAccessibilityWin(manager->GetRoot())->GetCOM();
+
 #elif defined(USE_X11)
   BrowserAccessibilityManager* manager =
       host()->GetOrCreateRootBrowserAccessibilityManager();
@@ -880,6 +885,16 @@
   legacy_render_widget_host_HWND_ = nullptr;
   legacy_window_destroyed_ = true;
 }
+
+gfx::NativeViewAccessible
+RenderWidgetHostViewAura::GetParentNativeViewAccessible() {
+  if (window_->parent()) {
+    return window_->parent()->GetProperty(
+        aura::client::kParentNativeViewAccessibleKey);
+  }
+
+  return nullptr;
+}
 #endif
 
 void RenderWidgetHostViewAura::DidCreateNewRendererCompositorFrameSink(
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 126be50..a2dedc2 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -307,6 +307,8 @@
 
   // Notification that the LegacyRenderWidgetHostHWND was destroyed.
   void OnLegacyWindowDestroyed();
+
+  gfx::NativeViewAccessible GetParentNativeViewAccessible();
 #endif
 
   // Method to indicate if this instance is shutting down or closing.
@@ -371,12 +373,14 @@
 
  private:
   friend class DelegatedFrameHostClientAura;
+  friend class FakeRenderWidgetHostViewAura;
   friend class InputMethodAuraTestBase;
   friend class RenderWidgetHostViewAuraTest;
   friend class RenderWidgetHostViewAuraBrowserTest;
   friend class RenderWidgetHostViewAuraCopyRequestTest;
   friend class TestInputMethodObserver;
 #if defined(OS_WIN)
+  friend class AccessibilityTreeLinkageWinBrowserTest;
   friend class DirectManipulationBrowserTest;
 #endif
   FRIEND_TEST_ALL_PREFIXES(InputMethodResultAuraTest,
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index 5281c7b..4afd0dbb 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -383,8 +383,9 @@
 // This is a test for crbug.com/312016. It tries to create two RenderWidgetHosts
 // with the same process and routing ids, which causes a collision. It is almost
 // identical to the AttemptDuplicateRenderViewHost test case.
+// Crashes on all platforms.  http://crbug.com/939338
 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
-                       AttemptDuplicateRenderWidgetHost) {
+                       DISABLED_AttemptDuplicateRenderWidgetHost) {
   int duplicate_routing_id = MSG_ROUTING_NONE;
   RenderFrameHostImpl* pending_rfh =
       PrepareToDuplicateHosts(shell(), &duplicate_routing_id);
diff --git a/content/browser/service_worker/embedded_worker_test_helper.cc b/content/browser/service_worker/embedded_worker_test_helper.cc
index 0fbce527..fa59552 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.cc
+++ b/content/browser/service_worker/embedded_worker_test_helper.cc
@@ -132,7 +132,7 @@
   }
   void SetSchedulerKeepActive(bool keep_active) override { NOTREACHED(); }
   void ProcessPurgeAndSuspend() override { NOTREACHED(); }
-  void SetIsLockedToSite() override { NOTREACHED(); }
+  void SetIsLockedToSite(const GURL& lock_url) override { NOTREACHED(); }
   void EnableV8LowMemoryMode() override { NOTREACHED(); }
 
   EmbeddedWorkerTestHelper* helper_;
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index 1836ca82..09e5c2a 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -77,7 +77,6 @@
 #include "net/cert/cert_status_flags.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/http/http_response_info.h"
-#include "net/log/net_log_with_source.h"
 #include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
@@ -939,8 +938,7 @@
     request->method = "GET";
     fetch_dispatcher_ = std::make_unique<ServiceWorkerFetchDispatcher>(
         std::move(request), resource_type, std::string() /* client_id */,
-        version_, net::NetLogWithSource(), std::move(prepare_callback),
-        std::move(fetch_callback));
+        version_, std::move(prepare_callback), std::move(fetch_callback));
     fetch_dispatcher_->Run();
   }
 
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.cc b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
index e6785f1..f307891 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.cc
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
@@ -43,8 +43,6 @@
 #include "net/base/request_priority.h"
 #include "net/http/http_util.h"
 #include "net/log/net_log.h"
-#include "net/log/net_log_capture_mode.h"
-#include "net/log/net_log_event_type.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_request.h"
 #include "services/network/public/cpp/features.h"
@@ -239,34 +237,6 @@
   }
 }
 
-std::unique_ptr<base::Value> NetLogServiceWorkerStatusCallback(
-    blink::ServiceWorkerStatusCode status,
-    net::NetLogCaptureMode) {
-  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
-  dict->SetString("status", blink::ServiceWorkerStatusToString(status));
-  return std::move(dict);
-}
-
-std::unique_ptr<base::Value> NetLogFetchEventCallback(
-    blink::ServiceWorkerStatusCode status,
-    ServiceWorkerFetchDispatcher::FetchEventResult result,
-    net::NetLogCaptureMode) {
-  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
-  dict->SetString("status", blink::ServiceWorkerStatusToString(status));
-  dict->SetBoolean(
-      "has_response",
-      result == ServiceWorkerFetchDispatcher::FetchEventResult::kGotResponse);
-  return std::move(dict);
-}
-
-void EndNetLogEventWithServiceWorkerStatus(
-    const net::NetLogWithSource& net_log,
-    net::NetLogEventType type,
-    blink::ServiceWorkerStatusCode status) {
-  net_log.EndEvent(type,
-                   base::Bind(&NetLogServiceWorkerStatusCallback, status));
-}
-
 const net::NetworkTrafficAnnotationTag kNavigationPreloadTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("service_worker_navigation_preload",
                                         R"(
@@ -495,14 +465,12 @@
     ResourceType resource_type,
     const std::string& client_id,
     scoped_refptr<ServiceWorkerVersion> version,
-    const net::NetLogWithSource& net_log,
     base::OnceClosure prepare_callback,
     FetchCallback fetch_callback)
     : request_(std::move(request)),
       client_id_(client_id),
       version_(std::move(version)),
       resource_type_(resource_type),
-      net_log_(net_log),
       prepare_callback_(std::move(prepare_callback)),
       fetch_callback_(std::move(fetch_callback)),
       did_complete_(false),
@@ -511,17 +479,9 @@
   if (blink::ServiceWorkerUtils::IsServicificationEnabled())
     DCHECK(!request_->blob);
 #endif  // DCHECK_IS_ON()
-  net_log_.BeginEvent(net::NetLogEventType::SERVICE_WORKER_DISPATCH_FETCH_EVENT,
-                      net::NetLog::StringCallback(
-                          "event_type", ServiceWorkerMetrics::EventTypeToString(
-                                            GetEventType())));
 }
 
-ServiceWorkerFetchDispatcher::~ServiceWorkerFetchDispatcher() {
-  if (!did_complete_)
-    net_log_.EndEvent(
-        net::NetLogEventType::SERVICE_WORKER_DISPATCH_FETCH_EVENT);
-}
+ServiceWorkerFetchDispatcher::~ServiceWorkerFetchDispatcher() = default;
 
 void ServiceWorkerFetchDispatcher::Run() {
   DCHECK(version_->status() == ServiceWorkerVersion::ACTIVATING ||
@@ -529,8 +489,6 @@
       << version_->status();
 
   if (version_->status() == ServiceWorkerVersion::ACTIVATING) {
-    net_log_.BeginEvent(
-        net::NetLogEventType::SERVICE_WORKER_WAIT_FOR_ACTIVATION);
     version_->RegisterStatusChangeCallback(
         base::BindOnce(&ServiceWorkerFetchDispatcher::DidWaitForActivation,
                        weak_factory_.GetWeakPtr()));
@@ -540,7 +498,6 @@
 }
 
 void ServiceWorkerFetchDispatcher::DidWaitForActivation() {
-  net_log_.EndEvent(net::NetLogEventType::SERVICE_WORKER_WAIT_FOR_ACTIVATION);
   StartWorker();
 }
 
@@ -558,7 +515,6 @@
     return;
   }
 
-  net_log_.BeginEvent(net::NetLogEventType::SERVICE_WORKER_START_WORKER);
   version_->RunAfterStartWorker(
       GetEventType(),
       base::BindOnce(&ServiceWorkerFetchDispatcher::DidStartWorker,
@@ -568,12 +524,9 @@
 void ServiceWorkerFetchDispatcher::DidStartWorker(
     blink::ServiceWorkerStatusCode status) {
   if (status != blink::ServiceWorkerStatusCode::kOk) {
-    EndNetLogEventWithServiceWorkerStatus(
-        net_log_, net::NetLogEventType::SERVICE_WORKER_START_WORKER, status);
     DidFail(status);
     return;
   }
-  net_log_.EndEvent(net::NetLogEventType::SERVICE_WORKER_START_WORKER);
   DispatchFetchEvent();
 }
 
@@ -590,7 +543,6 @@
   // Run callback to say that the fetch event will be dispatched.
   DCHECK(prepare_callback_);
   std::move(prepare_callback_).Run();
-  net_log_.BeginEvent(net::NetLogEventType::SERVICE_WORKER_FETCH_EVENT);
 
   // Set up for receiving the response.
   blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_ptr;
@@ -636,8 +588,6 @@
 void ServiceWorkerFetchDispatcher::DidFailToDispatch(
     std::unique_ptr<ResponseCallback> response_callback,
     blink::ServiceWorkerStatusCode status) {
-  EndNetLogEventWithServiceWorkerStatus(
-      net_log_, net::NetLogEventType::SERVICE_WORKER_FETCH_EVENT, status);
   DidFail(status);
 }
 
@@ -655,7 +605,6 @@
     blink::mojom::FetchAPIResponsePtr response,
     blink::mojom::ServiceWorkerStreamHandlePtr body_as_stream,
     blink::mojom::ServiceWorkerFetchEventTimingPtr timing) {
-  net_log_.EndEvent(net::NetLogEventType::SERVICE_WORKER_FETCH_EVENT);
   Complete(blink::ServiceWorkerStatusCode::kOk, fetch_result,
            std::move(response), std::move(body_as_stream), std::move(timing));
 }
@@ -669,10 +618,6 @@
   DCHECK(fetch_callback_);
 
   did_complete_ = true;
-  net_log_.EndEvent(
-      net::NetLogEventType::SERVICE_WORKER_DISPATCH_FETCH_EVENT,
-      base::Bind(&NetLogFetchEventCallback, status, fetch_result));
-
   std::move(fetch_callback_)
       .Run(status, fetch_result, std::move(response), std::move(body_as_stream),
            std::move(timing), version_);
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.h b/content/browser/service_worker/service_worker_fetch_dispatcher.h
index 9c169b10..a5745ab0 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.h
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.h
@@ -18,7 +18,6 @@
 #include "content/common/content_export.h"
 #include "content/public/common/resource_type.h"
 #include "mojo/public/cpp/system/data_pipe.h"
-#include "net/log/net_log_with_source.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
@@ -59,7 +58,6 @@
                                ResourceType resource_type,
                                const std::string& client_id,
                                scoped_refptr<ServiceWorkerVersion> version,
-                               const net::NetLogWithSource& net_log,
                                base::OnceClosure prepare_callback,
                                FetchCallback fetch_callback);
   ~ServiceWorkerFetchDispatcher();
@@ -122,7 +120,6 @@
   std::string client_id_;
   scoped_refptr<ServiceWorkerVersion> version_;
   const ResourceType resource_type_;
-  net::NetLogWithSource net_log_;
   base::OnceClosure prepare_callback_;
   FetchCallback fetch_callback_;
   bool did_complete_;
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index ca894b5..a505f5d34 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -243,7 +243,6 @@
       blink::mojom::FetchAPIRequest::From(resource_request_),
       static_cast<ResourceType>(resource_request_.resource_type),
       provider_host_->client_uuid(), active_worker,
-      net::NetLogWithSource() /* TODO(scottmg): net log? */,
       base::BindOnce(&ServiceWorkerNavigationLoader::DidPrepareFetchEvent,
                      weak_factory_.GetWeakPtr(),
                      base::WrapRefCounted(active_worker),
diff --git a/content/browser/service_worker/service_worker_request_handler.cc b/content/browser/service_worker/service_worker_request_handler.cc
index 77d7c45..05ebba7 100644
--- a/content/browser/service_worker/service_worker_request_handler.cc
+++ b/content/browser/service_worker/service_worker_request_handler.cc
@@ -26,7 +26,6 @@
 #include "ipc/ipc_message.h"
 #include "net/base/url_util.h"
 #include "net/url_request/url_request.h"
-#include "net/url_request/url_request_interceptor.h"
 #include "services/network/public/cpp/resource_request_body.h"
 #include "storage/browser/blob/blob_storage_context.h"
 #include "third_party/blink/public/common/service_worker/service_worker_utils.h"
@@ -35,28 +34,6 @@
 
 namespace {
 
-class ServiceWorkerRequestInterceptor
-    : public net::URLRequestInterceptor {
- public:
-  explicit ServiceWorkerRequestInterceptor(ResourceContext* resource_context)
-      : resource_context_(resource_context) {}
-  ~ServiceWorkerRequestInterceptor() override {}
-  net::URLRequestJob* MaybeInterceptRequest(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) const override {
-    ServiceWorkerRequestHandler* handler =
-        ServiceWorkerRequestHandler::GetHandler(request);
-    if (!handler)
-      return nullptr;
-    return handler->MaybeCreateJob(
-        request, network_delegate, resource_context_);
-  }
-
- private:
-  ResourceContext* resource_context_;
-  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRequestInterceptor);
-};
-
 bool SchemeMaySupportRedirectingToHTTPS(const GURL& url) {
 #if defined(OS_CHROMEOS)
   return url.SchemeIs(kExternalFileScheme);
@@ -71,69 +48,6 @@
 int ServiceWorkerRequestHandler::user_data_key_;
 
 // static
-void ServiceWorkerRequestHandler::InitializeForNavigation(
-    net::URLRequest* request,
-    ServiceWorkerNavigationHandleCore* navigation_handle_core,
-    storage::BlobStorageContext* blob_storage_context,
-    bool skip_service_worker,
-    ResourceType resource_type,
-    blink::mojom::RequestContextType request_context_type,
-    network::mojom::RequestContextFrameType frame_type,
-    bool is_parent_frame_secure,
-    scoped_refptr<network::ResourceRequestBody> body,
-    base::RepeatingCallback<WebContents*()> web_contents_getter) {
-  // Only create a handler when there is a ServiceWorkerNavigationHandlerCore
-  // to take ownership of a pre-created SeviceWorkerProviderHost.
-  if (!navigation_handle_core)
-    return;
-
-  // This is the legacy path used by non-NetworkService and
-  // non-S13nServiceWorker. The NetworkService/S13nServiceWorker path is
-  // InitializeForNavigationNetworkService().
-  //
-  // This function can still be called with a null navigation_handle_core by
-  // ResourceDispatcherHostImpl::BeginNavigationRequest when S13nSW is on and
-  // NetworkService is off, so this DCHECK must be after the null check above.
-  DCHECK(!blink::ServiceWorkerUtils::IsServicificationEnabled());
-
-  // Create the handler even for insecure HTTP since it's used in the
-  // case of redirect to HTTPS.
-  if (!request->url().SchemeIsHTTPOrHTTPS() &&
-      !OriginCanAccessServiceWorkers(request->url()) &&
-      !SchemeMaySupportRedirectingToHTTPS(request->url())) {
-    return;
-  }
-
-  if (!navigation_handle_core->context_wrapper())
-    return;
-  ServiceWorkerContextCore* context =
-      navigation_handle_core->context_wrapper()->context();
-  if (!context)
-    return;
-
-  // Initialize the SWProviderHost.
-  base::WeakPtr<ServiceWorkerProviderHost> provider_host =
-      ServiceWorkerProviderHost::PreCreateNavigationHost(
-          context->AsWeakPtr(), is_parent_frame_secure,
-          std::move(web_contents_getter));
-
-  std::unique_ptr<ServiceWorkerRequestHandler> handler(
-      provider_host->CreateRequestHandler(
-          network::mojom::FetchRequestMode::kNavigate,
-          network::mojom::FetchCredentialsMode::kInclude,
-          network::mojom::FetchRedirectMode::kManual,
-          std::string() /* integrity */, false /* keepalive */, resource_type,
-          request_context_type, frame_type, blob_storage_context->AsWeakPtr(),
-          body, skip_service_worker));
-  if (handler)
-    request->SetUserData(&user_data_key_, std::move(handler));
-
-  navigation_handle_core->DidPreCreateProviderHost(
-      provider_host->provider_id());
-}
-
-// S13nServiceWorker:
-// static
 std::unique_ptr<NavigationLoaderInterceptor>
 ServiceWorkerRequestHandler::InitializeForNavigationNetworkService(
     const GURL& url,
@@ -279,14 +193,6 @@
 }
 
 // static
-std::unique_ptr<net::URLRequestInterceptor>
-ServiceWorkerRequestHandler::CreateInterceptor(
-    ResourceContext* resource_context) {
-  return std::unique_ptr<net::URLRequestInterceptor>(
-      new ServiceWorkerRequestInterceptor(resource_context));
-}
-
-// static
 bool ServiceWorkerRequestHandler::IsControlledByServiceWorker(
     const net::URLRequest* request) {
   ServiceWorkerRequestHandler* handler = GetHandler(request);
diff --git a/content/browser/service_worker/service_worker_request_handler.h b/content/browser/service_worker/service_worker_request_handler.h
index 4d100aa..9e723d58 100644
--- a/content/browser/service_worker/service_worker_request_handler.h
+++ b/content/browser/service_worker/service_worker_request_handler.h
@@ -25,7 +25,6 @@
 namespace net {
 class NetworkDelegate;
 class URLRequest;
-class URLRequestInterceptor;
 }
 
 namespace network {
@@ -51,24 +50,10 @@
     : public base::SupportsUserData::Data,
       public NavigationLoaderInterceptor {
  public:
-  // PlzNavigate
-  // Attaches a newly created handler if the given |request| needs to be handled
-  // by ServiceWorker.
-  static void InitializeForNavigation(
-      net::URLRequest* request,
-      ServiceWorkerNavigationHandleCore* navigation_handle_core,
-      storage::BlobStorageContext* blob_storage_context,
-      bool skip_service_worker,
-      ResourceType resource_type,
-      blink::mojom::RequestContextType request_context_type,
-      network::mojom::RequestContextFrameType frame_type,
-      bool is_parent_frame_secure,
-      scoped_refptr<network::ResourceRequestBody> body,
-      base::RepeatingCallback<WebContents*()> web_contents_getter);
-
-  // S13nServiceWorker:
-  // Same as InitializeForNavigation() but instead of attaching to a URLRequest,
-  // just creates a NavigationLoaderInterceptor and returns it.
+  // Returns a loader interceptor for a navigation. May return nullptr
+  // if the navigation cannot use service workers.
+  // TODO(falken): Rename to InitializeForNavigation since this also is called
+  // when NetworkService is disabled.
   static std::unique_ptr<NavigationLoaderInterceptor>
   InitializeForNavigationNetworkService(
       const GURL& url,
@@ -116,10 +101,6 @@
   static ServiceWorkerRequestHandler* GetHandler(
       const net::URLRequest* request);
 
-  // Creates a protocol interceptor for ServiceWorker.
-  static std::unique_ptr<net::URLRequestInterceptor> CreateInterceptor(
-      ResourceContext* resource_context);
-
   // Returns true if the request falls into the scope of a ServiceWorker.
   // It's only reliable after the ServiceWorkerRequestHandler MaybeCreateJob
   // method runs to completion for this request. The AppCache handler uses
diff --git a/content/browser/service_worker/service_worker_request_handler_unittest.cc b/content/browser/service_worker/service_worker_request_handler_unittest.cc
index a942079..ceac781f 100644
--- a/content/browser/service_worker/service_worker_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_request_handler_unittest.cc
@@ -132,36 +132,6 @@
 
   void InitializeHandlerForNavigationSimpleTest(const std::string& url,
                                                 bool expected_handler_created) {
-    bool handler_created = false;
-    if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
-      handler_created = InitializeHandlerForNavigationNetworkService(
-          url, expected_handler_created);
-    } else {
-      handler_created = InitializeHandlerForNavigationNonNetworkService(
-          url, expected_handler_created);
-    }
-    EXPECT_EQ(expected_handler_created, handler_created);
-  }
-
-  bool InitializeHandlerForNavigationNonNetworkService(
-      const std::string& url,
-      bool expected_handler_created) {
-    std::unique_ptr<ServiceWorkerNavigationHandleCore> navigation_handle_core =
-        CreateNavigationHandleCore(helper_->context_wrapper());
-    std::unique_ptr<net::URLRequest> request = CreateRequest(url, "GET");
-    ServiceWorkerRequestHandler::InitializeForNavigation(
-        request.get(), navigation_handle_core.get(), &blob_storage_context_,
-        false /* skip_service_worker */, RESOURCE_TYPE_MAIN_FRAME,
-        blink::mojom::RequestContextType::HYPERLINK,
-        network::mojom::RequestContextFrameType::kTopLevel,
-        true /* is_parent_frame_secure */, nullptr /* body */,
-        base::RepeatingCallback<WebContents*(void)>());
-    return !!GetHandler(request.get());
-  }
-
-  bool InitializeHandlerForNavigationNetworkService(
-      const std::string& url,
-      bool expected_handler_created) {
     std::unique_ptr<ServiceWorkerNavigationHandleCore> navigation_handle_core =
         CreateNavigationHandleCore(helper_->context_wrapper());
     base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host;
@@ -175,7 +145,7 @@
             true /* is_parent_frame_secure */, nullptr /* body */,
             base::RepeatingCallback<WebContents*(void)>(),
             &service_worker_provider_host);
-    return !!interceptor.get();
+    EXPECT_EQ(expected_handler_created, !!interceptor.get());
   }
 
   TestBrowserThreadBundle browser_thread_bundle_;
diff --git a/content/browser/service_worker/service_worker_url_request_job.cc b/content/browser/service_worker/service_worker_url_request_job.cc
index be01af49..31f10bda 100644
--- a/content/browser/service_worker/service_worker_url_request_job.cc
+++ b/content/browser/service_worker/service_worker_url_request_job.cc
@@ -852,7 +852,6 @@
   fetch_dispatcher_ = std::make_unique<ServiceWorkerFetchDispatcher>(
       std::move(fetch_api_request), resource_type_,
       provider_host_->client_uuid(), base::WrapRefCounted(active_worker),
-      request()->net_log(),
       base::BindOnce(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent,
                      weak_factory_.GetWeakPtr(),
                      base::WrapRefCounted(active_worker)),
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index bbeebe73..9dcff48c 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -13,6 +13,7 @@
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/frame_host/debug_urls.h"
 #include "content/browser/frame_host/frame_tree_node.h"
+#include "content/browser/isolated_origin_util.h"
 #include "content/browser/isolation_context.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/storage_partition_impl.h"
@@ -75,7 +76,8 @@
   // This will create a new SiteInstance and BrowsingInstance.
   scoped_refptr<BrowsingInstance> instance(
       new BrowsingInstance(browser_context));
-  return instance->GetSiteInstanceForURL(url);
+  return instance->GetSiteInstanceForURL(url,
+                                         /* allow_default_instance */ false);
 }
 
 // static
@@ -113,6 +115,10 @@
   return browsing_instance_->default_process();
 }
 
+bool SiteInstanceImpl::IsDefaultSiteInstance() {
+  return browsing_instance_->IsDefaultSiteInstance(this);
+}
+
 void SiteInstanceImpl::MaybeSetBrowsingInstanceDefaultProcess() {
   if (!base::FeatureList::IsEnabled(
           features::kProcessSharingWithStrictSiteInstances)) {
@@ -225,12 +231,9 @@
   // URL is invalid.
   has_site_ = true;
   BrowserContext* browser_context = browsing_instance_->browser_context();
-  site_ = GetSiteForURL(BrowserOrResourceContext(browser_context),
-                        GetIsolationContext(), url,
-                        true /* should_use_effective_urls */);
   original_url_ = url;
-  lock_url_ = DetermineProcessLockURL(BrowserOrResourceContext(browser_context),
-                                      GetIsolationContext(), url);
+  browsing_instance_->GetSiteAndLockForURL(
+      url, /* allow_default_instance */ false, &site_, &lock_url_);
 
   // Now that we have a site, register it with the BrowsingInstance.  This
   // ensures that we won't create another SiteInstance for this site within
@@ -271,7 +274,8 @@
 
 scoped_refptr<SiteInstance> SiteInstanceImpl::GetRelatedSiteInstance(
     const GURL& url) {
-  return browsing_instance_->GetSiteInstanceForURL(url);
+  return browsing_instance_->GetSiteInstanceForURL(
+      url, /* allow_default_instance */ true);
 }
 
 bool SiteInstanceImpl::IsRelatedSiteInstance(const SiteInstance* instance) {
@@ -310,11 +314,14 @@
 
   // If the site URL is an extension (e.g., for hosted apps or WebUI) but the
   // process is not (or vice versa), make sure we notice and fix it.
-  GURL site_url = SiteInstanceImpl::GetSiteForURL(
-      browsing_instance_->browser_context(), GetIsolationContext(), url);
-  GURL origin_lock = DetermineProcessLockURL(
-      BrowserOrResourceContext(browsing_instance_->browser_context()),
-      GetIsolationContext(), url);
+  GURL site_url;
+  GURL origin_lock;
+
+  // Note: This call must return information that is identical to what
+  // would be reported in the SiteInstance returned by
+  // GetRelatedSiteInstance(url).
+  browsing_instance_->GetSiteAndLockForURL(
+      url, /* allow_default_instance */ true, &site_url, &origin_lock);
   return !RenderProcessHostImpl::IsSuitableHost(
       GetProcess(), browsing_instance_->browser_context(),
       GetIsolationContext(), site_url, origin_lock);
@@ -810,4 +817,27 @@
   }
 }
 
+// static
+void SiteInstance::StartIsolatingSite(BrowserContext* context,
+                                      const GURL& url) {
+  if (!SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled())
+    return;
+
+  // Ignore attempts to isolate origins that are not supported.  Do this here
+  // instead of relying on AddIsolatedOrigins()'s internal validation, to avoid
+  // the runtime warning generated by the latter.
+  url::Origin origin(url::Origin::Create(url));
+  if (!IsolatedOriginUtil::IsValidIsolatedOrigin(origin))
+    return;
+
+  // Convert |url| to a site, to avoid breaking document.domain.  Note that
+  // this doesn't use effective URL resolution or other special cases from
+  // GetSiteForURL() and simply converts |origin| to a scheme and eTLD+1.
+  GURL site(SiteInstanceImpl::GetSiteForOrigin(origin));
+
+  ChildProcessSecurityPolicyImpl* policy =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+  policy->AddIsolatedOrigins({url::Origin::Create(site)}, context);
+}
+
 }  // namespace content
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index 1acc3ef..5889208a 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -283,6 +283,9 @@
   // the BrowsingInstance's default process.
   RenderProcessHost* GetDefaultProcessIfUsable();
 
+  // Returns true if this object was constructed as a default site instance.
+  bool IsDefaultSiteInstance();
+
  private:
   friend class BrowsingInstance;
   friend class SiteInstanceTestBrowserClient;
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index 33f16dd..14ae9f0 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -470,9 +470,14 @@
                                        GURL("https://bar.com/"));
     scoped_refptr<SiteInstance> site_instance =
         bar_site_instance->GetRelatedSiteInstance(test_url);
-    EXPECT_EQ(expected_app_site_url, site_instance->GetSiteURL());
-    EXPECT_EQ(nonapp_site_url,
-              static_cast<SiteInstanceImpl*>(site_instance.get())->lock_url());
+    auto* site_instance_impl =
+        static_cast<SiteInstanceImpl*>(site_instance.get());
+    if (AreAllSitesIsolatedForTesting()) {
+      EXPECT_EQ(expected_app_site_url, site_instance->GetSiteURL());
+      EXPECT_EQ(nonapp_site_url, site_instance_impl->lock_url());
+    } else {
+      EXPECT_TRUE(site_instance_impl->IsDefaultSiteInstance());
+    }
   }
 
   // New SiteInstance with a lazily assigned site URL.
@@ -566,14 +571,14 @@
 
   const GURL url_a1("http://www.google.com/1.html");
   scoped_refptr<SiteInstanceImpl> site_instance_a1(
-      browsing_instance->GetSiteInstanceForURL(url_a1));
+      browsing_instance->GetSiteInstanceForURL(url_a1, false));
   EXPECT_TRUE(site_instance_a1.get() != nullptr);
 
   // A separate site should create a separate SiteInstance.
   const GURL url_b1("http://www.yahoo.com/");
   scoped_refptr<SiteInstanceImpl> site_instance_b1(
 
-      browsing_instance->GetSiteInstanceForURL(url_b1));
+      browsing_instance->GetSiteInstanceForURL(url_b1, false));
   EXPECT_NE(site_instance_a1.get(), site_instance_b1.get());
   EXPECT_TRUE(site_instance_a1->IsRelatedSiteInstance(site_instance_b1.get()));
 
@@ -585,7 +590,7 @@
   // A second visit to the original site should return the same SiteInstance.
   const GURL url_a2("http://www.google.com/2.html");
   EXPECT_EQ(site_instance_a1.get(),
-            browsing_instance->GetSiteInstanceForURL(url_a2));
+            browsing_instance->GetSiteInstanceForURL(url_a2, false));
   EXPECT_EQ(site_instance_a1.get(),
             site_instance_a1->GetRelatedSiteInstance(url_a2));
 
@@ -595,7 +600,7 @@
       new BrowsingInstance(browser_context.get());
   // Ensure the new SiteInstance is ref counted so that it gets deleted.
   scoped_refptr<SiteInstanceImpl> site_instance_a2_2(
-      browsing_instance2->GetSiteInstanceForURL(url_a2));
+      browsing_instance2->GetSiteInstanceForURL(url_a2, false));
   EXPECT_NE(site_instance_a1.get(), site_instance_a2_2.get());
   EXPECT_FALSE(
       site_instance_a1->IsRelatedSiteInstance(site_instance_a2_2.get()));
@@ -638,14 +643,14 @@
 
   const GURL url_a1("http://www.google.com/1.html");
   scoped_refptr<SiteInstanceImpl> site_instance_a1(
-      browsing_instance->GetSiteInstanceForURL(url_a1));
+      browsing_instance->GetSiteInstanceForURL(url_a1, false));
   EXPECT_TRUE(site_instance_a1.get() != nullptr);
   std::unique_ptr<RenderProcessHost> process_a1(site_instance_a1->GetProcess());
 
   // A separate site should create a separate SiteInstance.
   const GURL url_b1("http://www.yahoo.com/");
   scoped_refptr<SiteInstanceImpl> site_instance_b1(
-      browsing_instance->GetSiteInstanceForURL(url_b1));
+      browsing_instance->GetSiteInstanceForURL(url_b1, false));
   EXPECT_NE(site_instance_a1.get(), site_instance_b1.get());
   EXPECT_TRUE(site_instance_a1->IsRelatedSiteInstance(site_instance_b1.get()));
 
@@ -657,7 +662,7 @@
   // A second visit to the original site should return the same SiteInstance.
   const GURL url_a2("http://www.google.com/2.html");
   EXPECT_EQ(site_instance_a1.get(),
-            browsing_instance->GetSiteInstanceForURL(url_a2));
+            browsing_instance->GetSiteInstanceForURL(url_a2, false));
   EXPECT_EQ(site_instance_a1.get(),
             site_instance_a1->GetRelatedSiteInstance(url_a2));
 
@@ -666,7 +671,7 @@
   BrowsingInstance* browsing_instance2 =
       new BrowsingInstance(browser_context.get());
   scoped_refptr<SiteInstanceImpl> site_instance_a1_2(
-      browsing_instance2->GetSiteInstanceForURL(url_a1));
+      browsing_instance2->GetSiteInstanceForURL(url_a1, false));
   EXPECT_TRUE(site_instance_a1.get() != nullptr);
   EXPECT_NE(site_instance_a1.get(), site_instance_a1_2.get());
   EXPECT_EQ(process_a1.get(), site_instance_a1_2->GetProcess());
@@ -678,7 +683,7 @@
   BrowsingInstance* browsing_instance3 =
       new BrowsingInstance(browser_context2.get());
   scoped_refptr<SiteInstanceImpl> site_instance_a2_3(
-      browsing_instance3->GetSiteInstanceForURL(url_a2));
+      browsing_instance3->GetSiteInstanceForURL(url_a2, false));
   EXPECT_TRUE(site_instance_a2_3.get() != nullptr);
   std::unique_ptr<RenderProcessHost> process_a2_3(
       site_instance_a2_3->GetProcess());
@@ -1230,10 +1235,14 @@
                                        GURL("https://bar.com/"));
     scoped_refptr<SiteInstance> site_instance =
         bar_site_instance->GetRelatedSiteInstance(original_url);
-    EXPECT_EQ(expected_site_url, site_instance->GetSiteURL());
-    EXPECT_EQ(
-        original_url,
-        static_cast<SiteInstanceImpl*>(site_instance.get())->original_url());
+    auto* site_instance_impl =
+        static_cast<SiteInstanceImpl*>(site_instance.get());
+    if (AreAllSitesIsolatedForTesting()) {
+      EXPECT_EQ(expected_site_url, site_instance->GetSiteURL());
+      EXPECT_EQ(original_url, site_instance_impl->original_url());
+    } else {
+      EXPECT_TRUE(site_instance_impl->IsDefaultSiteInstance());
+    }
   }
 
   // New SiteInstance with a lazily assigned site URL.
@@ -1263,4 +1272,30 @@
       GURL("http://user:pass@google.com:99/foo;bar?q=a#ref")));
 }
 
+TEST_F(SiteInstanceTest, StartIsolatingSite) {
+  IsolationContext isolation_context(context());
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+
+  // StartIsolatingSite() should convert the URL to a site before isolating it.
+  SiteInstance::StartIsolatingSite(context(),
+                                   GURL("http://bar.foo.com/foo/bar.html"));
+  EXPECT_TRUE(IsIsolatedOrigin(GURL("http://foo.com")));
+  SiteInstance::StartIsolatingSite(context(), GURL("https://a.b.c.com:8000/"));
+  EXPECT_TRUE(IsIsolatedOrigin(GURL("https://c.com")));
+  SiteInstance::StartIsolatingSite(context(),
+                                   GURL("http://bar.com/foo/bar.html"));
+  EXPECT_TRUE(IsIsolatedOrigin(GURL("http://bar.com")));
+
+  // Attempts to isolate an unsupported isolated origin should be ignored.
+  GURL data_url("data:,");
+  GURL blank_url(url::kAboutBlankURL);
+  SiteInstance::StartIsolatingSite(context(), data_url);
+  SiteInstance::StartIsolatingSite(context(), blank_url);
+  EXPECT_FALSE(IsIsolatedOrigin(data_url));
+  EXPECT_FALSE(IsIsolatedOrigin(blank_url));
+
+  // Cleanup.
+  policy->RemoveIsolatedOriginsForBrowserContext(*context());
+}
+
 }  // namespace content
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index ca6a967..84e8ebd2 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -3573,6 +3573,125 @@
   EXPECT_TRUE(interceptor->Capturing());
 }
 
+IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
+                       CrossProcessMousePointerCapture) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "/frame_tree/page_with_iframe_in_div.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  RenderFrameSubmissionObserver render_frame_submission_observer(
+      shell()->web_contents());
+
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  FrameTreeNode* child_node = root->child_at(0);
+  ASSERT_EQ(
+      " Site A ------------ proxies for B\n"
+      "   +--Site B ------- proxies for A\n"
+      "Where A = http://127.0.0.1/\n"
+      "      B = http://bar.com/",
+      DepictFrameTree(root));
+
+  ASSERT_TRUE(ExecuteScript(root,
+                            " document.addEventListener('pointerdown', (e) => {"
+                            "  e.target.setPointerCapture(e.pointerId);"
+                            "});"));
+
+  // Create listeners for mouse events.
+  RenderWidgetHostMouseEventMonitor main_frame_monitor(
+      root->current_frame_host()->GetRenderWidgetHost());
+  RenderWidgetHostMouseEventMonitor child_frame_monitor(
+      child_node->current_frame_host()->GetRenderWidgetHost());
+
+  WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
+
+  RenderWidgetHostInputEventRouter* router =
+      web_contents()->GetInputEventRouter();
+
+  RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
+      root->current_frame_host()->GetRenderWidgetHost()->GetView());
+  RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>(
+      child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
+
+  scoped_refptr<SetMouseCaptureInterceptor> root_interceptor =
+      new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>(
+          root->current_frame_host()->GetRenderWidgetHost()));
+
+  // Target MouseDown to main frame.
+  blink::WebMouseEvent mouse_event(
+      blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
+      blink::WebInputEvent::GetStaticTimeStampForTests());
+  mouse_event.button = blink::WebPointerProperties::Button::kLeft;
+  mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
+  mouse_event.pointer_type = blink::WebPointerProperties::PointerType::kMouse;
+  SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view);
+  mouse_event.click_count = 1;
+  main_frame_monitor.ResetEventReceived();
+  child_frame_monitor.ResetEventReceived();
+  RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
+                                      &mouse_event);
+
+  EXPECT_TRUE(main_frame_monitor.EventWasReceived());
+  EXPECT_FALSE(child_frame_monitor.EventWasReceived());
+  // Wait for the mouse capture message.
+  root_interceptor->Wait();
+  EXPECT_TRUE(root_interceptor->Capturing());
+  base::RunLoop().RunUntilIdle();
+
+  // Target MouseMove at child frame. The main frame is now capturing input,
+  // so it should receive the event instead.
+  float scale_factor =
+      render_frame_submission_observer.LastRenderFrameMetadata()
+          .page_scale_factor;
+  gfx::Rect bounds = child_view->GetViewBounds();
+  int child_frame_target_x = gfx::ToCeiledInt(
+      (bounds.x() - root_view->GetViewBounds().x() + 5) * scale_factor);
+  int child_frame_target_y = gfx::ToCeiledInt(
+      (bounds.y() - root_view->GetViewBounds().y() + 5) * scale_factor);
+  mouse_event.SetType(blink::WebInputEvent::kMouseMove);
+  mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
+
+  SetWebEventPositions(&mouse_event,
+                       gfx::Point(child_frame_target_x, child_frame_target_y),
+                       root_view);
+
+  main_frame_monitor.ResetEventReceived();
+  child_frame_monitor.ResetEventReceived();
+  RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
+                                      &mouse_event);
+
+  EXPECT_TRUE(main_frame_monitor.EventWasReceived());
+  EXPECT_FALSE(child_frame_monitor.EventWasReceived());
+
+  // Add script to release capture and send a mouse move to triger it.
+  ASSERT_TRUE(ExecuteScript(root,
+                            " document.addEventListener('pointermove', (e) => {"
+                            "  e.target.releasePointerCapture(e.pointerId);"
+                            "});"));
+  main_frame_monitor.ResetEventReceived();
+  child_frame_monitor.ResetEventReceived();
+  RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
+                                      &mouse_event);
+
+  EXPECT_TRUE(main_frame_monitor.EventWasReceived());
+  EXPECT_FALSE(child_frame_monitor.EventWasReceived());
+
+  // Mouse capture should be released now.
+  root_interceptor->Wait();
+  EXPECT_FALSE(root_interceptor->Capturing());
+
+  // Next move event should route to child frame.
+  RouteMouseEventAndWaitUntilDispatch(router, root_view, child_view,
+                                      &mouse_event);
+  // Dispatch twice because the router generates an extra MouseLeave for the
+  // main frame.
+  main_frame_monitor.ResetEventReceived();
+  child_frame_monitor.ResetEventReceived();
+  RouteMouseEventAndWaitUntilDispatch(router, root_view, child_view,
+                                      &mouse_event);
+  EXPECT_FALSE(main_frame_monitor.EventWasReceived());
+  EXPECT_TRUE(child_frame_monitor.EventWasReceived());
+}
+
 // There are no cursors on Android.
 #if !defined(OS_ANDROID)
 class CursorMessageFilter : public content::BrowserMessageFilter {
diff --git a/content/browser/speech/mock_tts_controller.cc b/content/browser/speech/mock_tts_controller.cc
index ac6fdd6..2d7c0dc 100644
--- a/content/browser/speech/mock_tts_controller.cc
+++ b/content/browser/speech/mock_tts_controller.cc
@@ -30,6 +30,8 @@
 
   void Stop() override {}
 
+  void Stop(const GURL& source_url) override {}
+
   void Pause() override {}
 
   void Resume() override {}
diff --git a/content/browser/speech/tts_controller_impl.cc b/content/browser/speech/tts_controller_impl.cc
index c47dac1f..554a2129 100644
--- a/content/browser/speech/tts_controller_impl.cc
+++ b/content/browser/speech/tts_controller_impl.cc
@@ -108,9 +108,18 @@
 }
 
 void TtsControllerImpl::Stop() {
+  Stop(GURL());
+}
+
+void TtsControllerImpl::Stop(const GURL& source_url) {
   base::RecordAction(base::UserMetricsAction("TextToSpeech.Stop"));
 
   paused_ = false;
+
+  if (!source_url.is_empty() && current_utterance_ &&
+      current_utterance_->GetSrcUrl().GetOrigin() != source_url.GetOrigin())
+    return;
+
   if (current_utterance_ && !current_utterance_->GetEngineId().empty()) {
     if (GetTtsControllerDelegate()->GetTtsEngineDelegate())
       GetTtsControllerDelegate()->GetTtsEngineDelegate()->Stop(
diff --git a/content/browser/speech/tts_controller_impl.h b/content/browser/speech/tts_controller_impl.h
index d959a6c..93abbdb 100644
--- a/content/browser/speech/tts_controller_impl.h
+++ b/content/browser/speech/tts_controller_impl.h
@@ -42,6 +42,7 @@
   bool IsSpeaking() override;
   void SpeakOrEnqueue(TtsUtterance* utterance) override;
   void Stop() override;
+  void Stop(const GURL& source_url) override;
   void Pause() override;
   void Resume() override;
   void OnTtsEvent(int utterance_id,
diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc
index ac79b6f..4d42ad98 100644
--- a/content/browser/storage_partition_impl_map.cc
+++ b/content/browser/storage_partition_impl_map.cc
@@ -34,7 +34,6 @@
 #include "content/browser/loader/prefetch_url_loader_service.h"
 #include "content/browser/loader/resource_request_info_impl.h"
 #include "content/browser/resource_context_impl.h"
-#include "content/browser/service_worker/service_worker_request_handler.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/streams/stream.h"
 #include "content/browser/streams/stream_context.h"
@@ -419,8 +418,6 @@
       DevToolsURLRequestInterceptor::MaybeCreate(browser_context_);
   if (devtools_interceptor)
     request_interceptors.push_back(std::move(devtools_interceptor));
-  request_interceptors.push_back(ServiceWorkerRequestHandler::CreateInterceptor(
-      browser_context_->GetResourceContext()));
   request_interceptors.push_back(std::make_unique<AppCacheInterceptor>());
 
   bool create_request_context = true;
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 58c6678..9ad7688 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1299,11 +1299,20 @@
 }
 
 void WebContentsImpl::EnableWebContentsOnlyAccessibilityMode() {
-  if (!GetAccessibilityMode().is_mode_off()) {
+  // If accessibility is already enabled, we'll need to force a reset
+  // in order to ensure new observers of accessibility events get the
+  // full accessibility tree from scratch.
+  bool need_reset = GetAccessibilityMode().has_mode(ui::AXMode::kWebContents);
+
+  ui::AXMode desired_mode =
+      GetContentClient()->browser()->GetAXModeForBrowserContext(
+          GetBrowserContext());
+  desired_mode |= ui::kAXModeWebContentsOnly;
+  AddAccessibilityMode(desired_mode);
+
+  if (need_reset) {
     for (RenderFrameHost* rfh : GetAllFrames())
       ResetAccessibility(rfh);
-  } else {
-    AddAccessibilityMode(ui::kAXModeWebContentsOnly);
   }
 }
 
diff --git a/content/browser/webauth/authenticator_impl.cc b/content/browser/webauth/authenticator_impl.cc
index 0c22c16..0ae9433 100644
--- a/content/browser/webauth/authenticator_impl.cc
+++ b/content/browser/webauth/authenticator_impl.cc
@@ -102,6 +102,21 @@
   kMaxValue = kAbandoned,
 };
 
+// The following enums correspond to UMA histograms and should not be
+// reassigned.
+enum class RelyingPartySecurityCheckFailure {
+  kOpaqueOrNonSecureOrigin = 0,
+  kRelyingPartyIdInvalid = 1,
+  kAppIdExtensionInvalid = 2,
+  kAppIdExtensionDomainMismatch = 3,
+  kMaxValue = kAppIdExtensionDomainMismatch,
+};
+
+void ReportSecurityCheckFailure(RelyingPartySecurityCheckFailure error) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "WebAuthentication.RelyingPartySecurityCheckFailure", error);
+}
+
 bool OriginIsCryptoTokenExtension(const url::Origin& origin) {
   auto cryptotoken_origin = url::Origin::Create(GURL(kCryptotokenOrigin));
   return cryptotoken_origin == origin;
@@ -213,6 +228,8 @@
   GURL appid_url = GURL(appid);
   if (!appid_url.is_valid() || appid_url.scheme() != url::kHttpsScheme ||
       appid_url.scheme_piece() != origin.scheme()) {
+    ReportSecurityCheckFailure(
+        RelyingPartySecurityCheckFailure::kAppIdExtensionInvalid);
     return base::nullopt;
   }
 
@@ -247,6 +264,9 @@
     return appid;
   }
 
+  ReportSecurityCheckFailure(
+      RelyingPartySecurityCheckFailure::kAppIdExtensionDomainMismatch);
+
   return base::nullopt;
 }
 
@@ -615,6 +635,8 @@
   relying_party_id_ = options->relying_party->id;
 
   if (!HasValidEffectiveDomain(caller_origin_)) {
+    ReportSecurityCheckFailure(
+        RelyingPartySecurityCheckFailure::kOpaqueOrNonSecureOrigin);
     bad_message::ReceivedBadMessage(render_frame_host_->GetProcess(),
                                     bad_message::AUTH_INVALID_EFFECTIVE_DOMAIN);
     InvokeCallbackAndCleanup(std::move(callback),
@@ -624,6 +646,8 @@
   }
 
   if (!IsRelyingPartyIdValid(relying_party_id_, caller_origin_)) {
+    ReportSecurityCheckFailure(
+        RelyingPartySecurityCheckFailure::kRelyingPartyIdInvalid);
     bad_message::ReceivedBadMessage(render_frame_host_->GetProcess(),
                                     bad_message::AUTH_INVALID_RELYING_PARTY);
     InvokeCallbackAndCleanup(std::move(callback),
@@ -670,6 +694,10 @@
         std::move(options->challenge));
   }
 
+  UMA_HISTOGRAM_COUNTS_100(
+      "WebAuthentication.MakeCredentialExcludeCredentialsCount",
+      options->exclude_credentials.size());
+
   // U2F requests proxied from the cryptotoken extension are limited to USB
   // devices.
   const auto transports =
@@ -775,6 +803,8 @@
   }
 
   if (!HasValidEffectiveDomain(caller_origin_)) {
+    ReportSecurityCheckFailure(
+        RelyingPartySecurityCheckFailure::kOpaqueOrNonSecureOrigin);
     bad_message::ReceivedBadMessage(render_frame_host_->GetProcess(),
                                     bad_message::AUTH_INVALID_EFFECTIVE_DOMAIN);
     InvokeCallbackAndCleanup(std::move(callback),
@@ -784,6 +814,8 @@
   }
 
   if (!IsRelyingPartyIdValid(options->relying_party_id, caller_origin_)) {
+    ReportSecurityCheckFailure(
+        RelyingPartySecurityCheckFailure::kRelyingPartyIdInvalid);
     bad_message::ReceivedBadMessage(render_frame_host_->GetProcess(),
                                     bad_message::AUTH_INVALID_RELYING_PARTY);
     InvokeCallbackAndCleanup(std::move(callback),
@@ -818,6 +850,10 @@
                 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice})
           : transports_;
 
+  UMA_HISTOGRAM_COUNTS_100(
+      "WebAuthentication.CredentialRequestAllowCredentialsCount",
+      options->allow_credentials.size());
+
   DCHECK(get_assertion_response_callback_.is_null());
   get_assertion_response_callback_ = std::move(callback);
 
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 98977d2a..12f1bca6 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -227,7 +227,8 @@
 
   WebRuntimeFeatures::EnableFeatureFromString(
       "BlinkGenPropertyTrees",
-      base::FeatureList::IsEnabled(blink::features::kBlinkGenPropertyTrees));
+      base::FeatureList::IsEnabled(blink::features::kBlinkGenPropertyTrees) ||
+          enable_experimental_web_platform_features);
 
   WebRuntimeFeatures::EnablePassiveDocumentEventListeners(
       base::FeatureList::IsEnabled(features::kPassiveDocumentEventListeners));
diff --git a/content/public/android/java/src/org/chromium/content_public/common/ContentSwitches.java b/content/common/android/java_templates/ContentSwitches.java.tmpl
similarity index 83%
rename from content/public/android/java/src/org/chromium/content_public/common/ContentSwitches.java
rename to content/common/android/java_templates/ContentSwitches.java.tmpl
index 9a47ed3..334fad3d 100644
--- a/content/public/android/java/src/org/chromium/content_public/common/ContentSwitches.java
+++ b/content/common/android/java_templates/ContentSwitches.java.tmpl
@@ -8,7 +8,7 @@
  * Contains all of the command line switches that are specific to the content/
  * portion of Chromium on Android.
  */
-public final class ContentSwitches {
+public final class ContentSwitches {{
     // Tell Java to use the official command line, loaded from the
     // official-command-line.xml files.  WARNING this is not done
     // immediately on startup, so early running Java code will not see
@@ -27,9 +27,6 @@
     // Change the url of the JavaScript that gets injected when accessibility mode is enabled.
     public static final String ACCESSIBILITY_JAVASCRIPT_URL = "accessibility-js-url";
 
-    // Sets the ISO country code that will be used for phone number detection.
-    public static final String NETWORK_COUNTRY_ISO = "network-country-iso";
-
     // How much of the browser controls need to be shown before they will auto show.
     public static final String TOP_CONTROLS_SHOW_THRESHOLD = "top-controls-show-threshold";
 
@@ -39,16 +36,9 @@
     // Native switch - chrome_switches::kDisablePopupBlocking
     public static final String DISABLE_POPUP_BLOCKING = "disable-popup-blocking";
 
-    // Native switch kDisableGestureRequirementForPresentation
-    public static final String DISABLE_GESTURE_REQUIREMENT_FOR_PRESENTATION =
-            "disable-gesture-requirement-for-presentation";
-
     // Native switch kRendererProcessLimit
     public static final String RENDER_PROCESS_LIMIT = "renderer-process-limit";
 
-    // Native switch kInProcessGPU
-    public static final String IN_PROCESS_GPU = "in-process-gpu";
-
     // Native switch kProcessType
     public static final String SWITCH_PROCESS_TYPE = "type";
 
@@ -74,6 +64,8 @@
     // Native switch value kNetworkSandbox
     public static final String NETWORK_SANDBOX_TYPE = "network";
 
+{NATIVE_STRINGS}
+
     // Prevent instantiation.
-    private ContentSwitches() {}
-}
+    private ContentSwitches() {{}}
+}}
diff --git a/content/common/renderer.mojom b/content/common/renderer.mojom
index 3760989..fb914afb 100644
--- a/content/common/renderer.mojom
+++ b/content/common/renderer.mojom
@@ -15,6 +15,7 @@
 import "third_party/blink/public/mojom/service_worker/embedded_worker.mojom";
 import "third_party/blink/public/mojom/user_agent/user_agent_metadata.mojom";
 import "ui/gfx/geometry/mojo/geometry.mojom";
+import "url/mojom/url.mojom";
 
 struct CreateViewParams {
   // Renderer-wide preferences.
@@ -266,7 +267,9 @@
   // Tells the renderer process that it has been locked to a site (i.e., a
   // scheme plus eTLD+1, such as https://google.com), or to a more specific
   // origin.
-  SetIsLockedToSite();
+  // TODO(nasko): Remove |lock_url| after we've gathered enough information to
+  // debug issues with browser-side security checks. https://crbug.com/931895.
+  SetIsLockedToSite(url.mojom.Url lock_url);
 
   // Tells the renderer to enable V8's memory saving mode when possible.
   // This is only used when site-per-process is enabled. If the process
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index f92fcb33..b4217dc 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -93,6 +93,7 @@
     ":generate_sandboxed_service_srcjar",
     ":is_ready_to_pay_service_aidl",
     ":content_public_android_java_enums_srcjar",
+    ":content_public_android_java_switches_srcjar",
     "//content/browser/accessibility:content_browser_accessibility_java_enums_srcjar",
     "//ui/touch_selection:ui_touch_selection_enums_srcjar",
     "//ui/touch_selection:ui_touch_handle_orientation_srcjar",
@@ -284,7 +285,6 @@
     "java/src/org/chromium/content_public/browser/WebContentsObserver.java",
     "java/src/org/chromium/content_public/browser/WebContentsStatics.java",
     "java/src/org/chromium/content_public/common/ContentProcessInfo.java",
-    "java/src/org/chromium/content_public/common/ContentSwitches.java",
     "java/src/org/chromium/content_public/common/ContentUrlConstants.java",
     "java/src/org/chromium/content_public/common/Referrer.java",
     "java/src/org/chromium/content_public/common/ResourceRequestBody.java",
@@ -368,6 +368,13 @@
   ]
 }
 
+java_cpp_strings("content_public_android_java_switches_srcjar") {
+  sources = [
+    "//content/public/common/content_switches.cc",
+  ]
+  template = "//content/common/android/java_templates/ContentSwitches.java.tmpl"
+}
+
 generate_jar_jni("jar_jni") {
   jni_package = "content"
   classes = [
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
index 6cc28a1..bf93723 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
@@ -520,6 +520,20 @@
         }
     }
 
+    /**
+     * Dumps the stack of the child process with |pid|  without crashing it.
+     * @param pid Process id of the child process.
+     */
+    @CalledByNative
+    private void dumpProcessStack(int pid) {
+        assert LauncherThread.runningOnLauncherThread();
+        ChildProcessLauncherHelperImpl launcher = getByPid(pid);
+        if (launcher != null) {
+            ChildProcessConnection connection = launcher.mLauncher.getConnection();
+            connection.dumpProcessStack();
+        }
+    }
+
     // Can be called on a number of threads, including launcher, and binder.
     private static native void nativeOnChildProcessStarted(
             long nativeChildProcessLauncherHelper, int pid);
diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h
index 19063d33..7463db06 100644
--- a/content/public/browser/render_process_host.h
+++ b/content/public/browser/render_process_host.h
@@ -276,6 +276,9 @@
 #if defined(OS_ANDROID)
   // Return the highest importance of all widgets in this process.
   virtual ChildProcessImportance GetEffectiveImportance() = 0;
+
+  // Dumps the stack of this render process without crashing it.
+  virtual void DumpProcessStack() = 0;
 #endif
 
   // Sets a flag indicating that the process can be abnormally terminated.
diff --git a/content/public/browser/site_instance.h b/content/public/browser/site_instance.h
index 3b4c1cf..050707a 100644
--- a/content/public/browser/site_instance.h
+++ b/content/public/browser/site_instance.h
@@ -175,6 +175,20 @@
   // ContentBrowserClient::GetEffectiveURL().
   static GURL GetSiteForURL(BrowserContext* context, const GURL& url);
 
+  // Starts requiring a dedicated process for |url|'s site.  On platforms where
+  // strict site isolation is disabled, this may be used as a runtime signal
+  // that a certain site should become process-isolated, because its security
+  // is important to the user (e.g., if the user has typed a password on that
+  // site).  The site will be determined from |url|'s scheme and eTLD+1. If
+  // |context| is non-null, the site will be isolated only within that
+  // BrowserContext; if |context| is null, the site will be isolated globally
+  // for all BrowserContexts.
+  //
+  // Note that this has no effect if site isolation is turned off, such as via
+  // the kDisableSiteIsolation cmdline flag or enterprise policy -- see also
+  // SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled().
+  static void StartIsolatingSite(BrowserContext* context, const GURL& url);
+
  protected:
   friend class base::RefCounted<SiteInstance>;
 
diff --git a/content/public/browser/site_isolation_policy.cc b/content/public/browser/site_isolation_policy.cc
index ba22edf7..0f23b9c 100644
--- a/content/public/browser/site_isolation_policy.cc
+++ b/content/public/browser/site_isolation_policy.cc
@@ -116,6 +116,11 @@
 }
 
 // static
+bool SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled() {
+  return !IsSiteIsolationDisabled();
+}
+
+// static
 std::vector<url::Origin>
 SiteIsolationPolicy::GetIsolatedOriginsFromEnvironment() {
   std::string cmdline_arg =
diff --git a/content/public/browser/site_isolation_policy.h b/content/public/browser/site_isolation_policy.h
index e3348681..d236aa4c 100644
--- a/content/public/browser/site_isolation_policy.h
+++ b/content/public/browser/site_isolation_policy.h
@@ -48,6 +48,11 @@
   // process iframes (OOPIF's) to print properly.
   static bool ShouldPdfCompositorBeEnabledForOopifs();
 
+  // Returns true if isolated origins may be added at runtime in response
+  // to hints such as users typing in a password or (in the future) an origin
+  // opting itself into isolation via a header.
+  static bool AreDynamicIsolatedOriginsEnabled();
+
   // Returns the origins to isolate.  See also AreIsolatedOriginsEnabled.
   // This list applies globally to the whole browser in all profiles.
   static std::vector<url::Origin> GetIsolatedOrigins();
diff --git a/content/public/browser/tts_controller.h b/content/public/browser/tts_controller.h
index 548ede8..82f0ede 100644
--- a/content/public/browser/tts_controller.h
+++ b/content/public/browser/tts_controller.h
@@ -97,6 +97,9 @@
   // as well.
   virtual void Stop() = 0;
 
+  // Stops the current utterance if it matches the given |source_url|.
+  virtual void Stop(const GURL& source_url) = 0;
+
   // Pause the speech queue. Some engines may support pausing in the middle
   // of an utterance.
   virtual void Pause() = 0;
diff --git a/content/public/test/mock_render_process_host.cc b/content/public/test/mock_render_process_host.cc
index 0da619e..48b6543 100644
--- a/content/public/test/mock_render_process_host.cc
+++ b/content/public/test/mock_render_process_host.cc
@@ -306,6 +306,8 @@
   NOTIMPLEMENTED();
   return ChildProcessImportance::NORMAL;
 }
+
+void MockRenderProcessHost::DumpProcessStack() {}
 #endif
 
 void MockRenderProcessHost::SetSuddenTerminationAllowed(bool allowed) {
diff --git a/content/public/test/mock_render_process_host.h b/content/public/test/mock_render_process_host.h
index a963922..b6d205c 100644
--- a/content/public/test/mock_render_process_host.h
+++ b/content/public/test/mock_render_process_host.h
@@ -110,6 +110,7 @@
   void RemovePriorityClient(PriorityClient* priority_client) override;
 #if defined(OS_ANDROID)
   ChildProcessImportance GetEffectiveImportance() override;
+  void DumpProcessStack() override;
 #endif
   void SetSuddenTerminationAllowed(bool allowed) override;
   bool SuddenTerminationAllowed() override;
diff --git a/content/renderer/input/widget_input_handler_manager.cc b/content/renderer/input/widget_input_handler_manager.cc
index 590c97d..863149f 100644
--- a/content/renderer/input/widget_input_handler_manager.cc
+++ b/content/renderer/input/widget_input_handler_manager.cc
@@ -403,9 +403,17 @@
   WidgetInputHandlerManager* manager =
       render_widget->widget_input_handler_manager();
 
+  // TODO(bokan): The synchronous compositor doesn't support the
+  // RequestPresentation API yet so just ACK the gesture immediately so it
+  // doesn't hang forever. https://crbug.com/938956.
+  bool sync_compositing = false;
+#if defined(OS_ANDROID)
+  sync_compositing = GetContentClient()->UsingSynchronousCompositing();
+#endif
+
   // If the RenderWidget is hidden, we won't produce compositor frames for it
   // so just ACK the input to prevent blocking the browser indefinitely.
-  if (render_widget->is_hidden()) {
+  if (render_widget->is_hidden() || sync_compositing) {
     manager->InvokeInputProcessedCallback();
     return;
   }
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
index 43c31144..a655afb54 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
@@ -209,8 +209,7 @@
 std::unique_ptr<media::VideoDecoder>
 GpuVideoAcceleratorFactoriesImpl::CreateVideoDecoder(
     media::MediaLog* media_log,
-    const media::RequestOverlayInfoCB& request_overlay_info_cb,
-    const gfx::ColorSpace& target_color_space) {
+    const media::RequestOverlayInfoCB& request_overlay_info_cb) {
   DCHECK(video_accelerator_enabled_);
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(interface_factory_.is_bound());
@@ -223,12 +222,12 @@
     interface_factory_->CreateVideoDecoder(mojo::MakeRequest(&video_decoder));
     return std::make_unique<media::MojoVideoDecoder>(
         task_runner_, this, media_log, std::move(video_decoder),
-        request_overlay_info_cb, target_color_space);
+        request_overlay_info_cb, rendering_color_space_);
   }
 #endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
 
   return std::make_unique<media::GpuVideoDecoder>(
-      this, request_overlay_info_cb, target_color_space, media_log);
+      this, request_overlay_info_cb, rendering_color_space_, media_log);
 }
 
 std::unique_ptr<media::VideoDecodeAccelerator>
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
index faa69c6..08a215bd 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
@@ -71,8 +71,7 @@
   int32_t GetCommandBufferRouteId() override;
   std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
       media::MediaLog* media_log,
-      const media::RequestOverlayInfoCB& request_overlay_info_cb,
-      const gfx::ColorSpace& target_color_space) override;
+      const media::RequestOverlayInfoCB& request_overlay_info_cb) override;
   bool IsDecoderConfigSupported(
       const media::VideoDecoderConfig& config) override;
   std::unique_ptr<media::VideoDecodeAccelerator> CreateVideoDecodeAccelerator()
diff --git a/content/renderer/media/media_interface_factory.cc b/content/renderer/media/media_interface_factory.cc
index 4797c54..54695766 100644
--- a/content/renderer/media/media_interface_factory.cc
+++ b/content/renderer/media/media_interface_factory.cc
@@ -51,15 +51,6 @@
   GetMediaInterfaceFactory()->CreateVideoDecoder(std::move(request));
 }
 
-// TODO(https://crbug.com/936528) : remove this method.
-void MediaInterfaceFactory::CreateRenderer(
-    media::mojom::HostedRendererType type,
-    const std::string& audio_device_id,
-    media::mojom::RendererRequest renderer) {
-  DCHECK_EQ(type, media::mojom::HostedRendererType::kDefault);
-  CreateDefaultRenderer(audio_device_id, std::move(renderer));
-}
-
 void MediaInterfaceFactory::CreateDefaultRenderer(
     const std::string& audio_device_id,
     media::mojom::RendererRequest request) {
diff --git a/content/renderer/media/media_interface_factory.h b/content/renderer/media/media_interface_factory.h
index aa05860..67b2d19 100644
--- a/content/renderer/media/media_interface_factory.h
+++ b/content/renderer/media/media_interface_factory.h
@@ -35,10 +35,6 @@
   void CreateVideoDecoder(media::mojom::VideoDecoderRequest request) final;
   void CreateDefaultRenderer(const std::string& audio_device_id,
                              media::mojom::RendererRequest request) final;
-  // TODO(https://crbug.com/936528) : remove this method.
-  void CreateRenderer(media::mojom::HostedRendererType type,
-                      const std::string& audio_device_id,
-                      media::mojom::RendererRequest renderer) final;
 #if defined(OS_ANDROID)
   void CreateFlingingRenderer(const std::string& presentation_id,
                               media::mojom::RendererRequest request) final;
diff --git a/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc b/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
index 6b51dd2..9c4d2799 100644
--- a/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
+++ b/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <functional>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -22,10 +23,10 @@
 #include "build/build_config.h"
 #include "content/renderer/media/render_media_log.h"
 #include "content/renderer/media/webrtc/webrtc_video_frame_adapter.h"
+#include "content/renderer/media/webrtc/webrtc_video_utils.h"
 #include "media/base/media_log.h"
 #include "media/base/media_util.h"
 #include "media/base/overlay_info.h"
-#include "media/base/video_decoder_config.h"
 #include "media/base/video_types.h"
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "third_party/webrtc/api/video/video_frame.h"
@@ -159,7 +160,8 @@
 
   // Synchronously verify that the decoder can be initialized.
   std::unique_ptr<RTCVideoDecoderAdapter> rtc_video_decoder_adapter =
-      base::WrapUnique(new RTCVideoDecoderAdapter(gpu_factories, format));
+      base::WrapUnique(
+          new RTCVideoDecoderAdapter(gpu_factories, config, format));
   if (!rtc_video_decoder_adapter->InitializeSync(config)) {
     gpu_factories->GetTaskRunner()->DeleteSoon(
         FROM_HERE, std::move(rtc_video_decoder_adapter));
@@ -171,10 +173,12 @@
 
 RTCVideoDecoderAdapter::RTCVideoDecoderAdapter(
     media::GpuVideoAcceleratorFactories* gpu_factories,
+    const media::VideoDecoderConfig& config,
     const webrtc::SdpVideoFormat& format)
     : media_task_runner_(gpu_factories->GetTaskRunner()),
       gpu_factories_(gpu_factories),
       format_(format),
+      config_(config),
       weak_this_factory_(this) {
   DVLOG(1) << __func__;
   DETACH_FROM_THREAD(decoding_thread_checker_);
@@ -189,7 +193,8 @@
 bool RTCVideoDecoderAdapter::InitializeSync(
     const media::VideoDecoderConfig& config) {
   DVLOG(3) << __func__;
-  DCHECK_CALLED_ON_VALID_THREAD(worker_thread_checker_);
+  // Can be called on |worker_thread_| or |decoding_thread_|.
+  DCHECK(!media_task_runner_->BelongsToCurrentThread());
 
   bool result = false;
   base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::MANUAL,
@@ -249,6 +254,15 @@
   buffer->set_timestamp(
       base::TimeDelta::FromMicroseconds(input_image.Timestamp()));
 
+  if (ShouldReinitializeForSettingHDRColorSpace(input_image)) {
+    config_.set_color_space_info(
+        WebRtcToMediaVideoColorSpace(*input_image.ColorSpace()));
+    if (!ReinitializeSync(config_))
+      return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+    if (input_image._frameType != webrtc::kVideoFrameKey)
+      return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
   // Queue for decoding.
   {
     base::AutoLock auto_lock(lock_);
@@ -306,17 +320,20 @@
   DVLOG(3) << __func__;
   DCHECK(media_task_runner_->BelongsToCurrentThread());
 
-  // TODO(sandersd): Plumb a real log sink here so that we can contribute to the
-  // media-internals UI. The current log just discards all messages.
-  media_log_ = std::make_unique<media::NullMediaLog>();
-
-  video_decoder_ = gpu_factories_->CreateVideoDecoder(
-      media_log_.get(), base::BindRepeating(&OnRequestOverlayInfo),
-      gfx::ColorSpace());
+  // On ReinitializeSync() calls, |video_decoder_| may already be set.
   if (!video_decoder_) {
-    media_task_runner_->PostTask(FROM_HERE,
-                                 base::BindRepeating(init_cb, false));
-    return;
+    // TODO(sandersd): Plumb a real log sink here so that we can contribute to
+    // the media-internals UI. The current log just discards all messages.
+    media_log_ = std::make_unique<media::NullMediaLog>();
+
+    video_decoder_ = gpu_factories_->CreateVideoDecoder(
+        media_log_.get(), base::BindRepeating(&OnRequestOverlayInfo));
+
+    if (!video_decoder_) {
+      media_task_runner_->PostTask(FROM_HERE,
+                                   base::BindRepeating(init_cb, false));
+      return;
+    }
   }
 
   // In practice this is ignored by hardware decoders.
@@ -407,4 +424,67 @@
   consecutive_error_count_ = 0;
 }
 
+bool RTCVideoDecoderAdapter::ShouldReinitializeForSettingHDRColorSpace(
+    const webrtc::EncodedImage& input_image) const {
+  DCHECK_CALLED_ON_VALID_THREAD(decoding_thread_checker_);
+
+  if (config_.profile() == media::VP9PROFILE_PROFILE2 &&
+      input_image.ColorSpace()) {
+    const media::VideoColorSpace& new_color_space =
+        WebRtcToMediaVideoColorSpace(*input_image.ColorSpace());
+    if (!config_.color_space_info().IsSpecified() ||
+        new_color_space != config_.color_space_info()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool RTCVideoDecoderAdapter::ReinitializeSync(
+    const media::VideoDecoderConfig& config) {
+  DCHECK_CALLED_ON_VALID_THREAD(decoding_thread_checker_);
+
+  bool result = false;
+  base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::MANUAL,
+                             base::WaitableEvent::InitialState::NOT_SIGNALED);
+  media::VideoDecoder::InitCB init_cb =
+      base::BindRepeating(&FinishWait, &waiter, &result);
+  FlushDoneCB flush_success_cb =
+      base::BindOnce(&RTCVideoDecoderAdapter::InitializeOnMediaThread,
+                     weak_this_, std::cref(config), std::cref(init_cb));
+  FlushDoneCB flush_fail_cb =
+      base::BindOnce(&FinishWait, &waiter, &result, false);
+  if (media_task_runner_->PostTask(
+          FROM_HERE, base::BindOnce(&RTCVideoDecoderAdapter::FlushOnMediaThread,
+                                    weak_this_, std::move(flush_success_cb),
+                                    std::move(flush_fail_cb)))) {
+    waiter.Wait();
+  }
+  return result;
+}
+
+void RTCVideoDecoderAdapter::FlushOnMediaThread(FlushDoneCB flush_success_cb,
+                                                FlushDoneCB flush_fail_cb) {
+  DCHECK(media_task_runner_->BelongsToCurrentThread());
+
+  // Remove any pending tasks.
+  {
+    base::AutoLock auto_lock(lock_);
+    pending_buffers_.clear();
+  }
+
+  // Send EOS frame for flush.
+  video_decoder_->Decode(
+      media::DecoderBuffer::CreateEOSBuffer(),
+      base::BindRepeating(
+          [](FlushDoneCB flush_success, FlushDoneCB flush_fail,
+             media::DecodeStatus status) {
+            if (status == media::DecodeStatus::OK)
+              std::move(flush_success).Run();
+            else
+              std::move(flush_fail).Run();
+          },
+          base::Passed(&flush_success_cb), base::Passed(&flush_fail_cb)));
+}
+
 }  // namespace content
diff --git a/content/renderer/media/webrtc/rtc_video_decoder_adapter.h b/content/renderer/media/webrtc/rtc_video_decoder_adapter.h
index 048962ad..f4d3d3ce 100644
--- a/content/renderer/media/webrtc/rtc_video_decoder_adapter.h
+++ b/content/renderer/media/webrtc/rtc_video_decoder_adapter.h
@@ -18,6 +18,7 @@
 #include "media/base/decode_status.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_decoder.h"
+#include "media/base/video_decoder_config.h"
 #include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
 #include "third_party/webrtc/modules/video_coding/include/video_codec_interface.h"
 #include "ui/gfx/geometry/size.h"
@@ -30,7 +31,6 @@
 class DecoderBuffer;
 class GpuVideoAcceleratorFactories;
 class MediaLog;
-class VideoDecoderConfig;
 class VideoFrame;
 }  // namespace media
 
@@ -81,9 +81,11 @@
   using CreateVideoDecoderCB =
       base::RepeatingCallback<std::unique_ptr<media::VideoDecoder>(
           media::MediaLog*)>;
+  using FlushDoneCB = base::OnceCallback<void()>;
 
   // Called on the worker thread.
   RTCVideoDecoderAdapter(media::GpuVideoAcceleratorFactories* gpu_factories,
+                         const media::VideoDecoderConfig& config,
                          const webrtc::SdpVideoFormat& format);
 
   bool InitializeSync(const media::VideoDecoderConfig& config);
@@ -93,10 +95,17 @@
   void OnDecodeDone(media::DecodeStatus status);
   void OnOutput(const scoped_refptr<media::VideoFrame>& frame);
 
+  bool ShouldReinitializeForSettingHDRColorSpace(
+      const webrtc::EncodedImage& input_image) const;
+  bool ReinitializeSync(const media::VideoDecoderConfig& config);
+  void FlushOnMediaThread(FlushDoneCB flush_success_cb,
+                          FlushDoneCB flush_fail_cb);
+
   // Construction parameters.
   scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
   media::GpuVideoAcceleratorFactories* gpu_factories_;
   webrtc::SdpVideoFormat format_;
+  media::VideoDecoderConfig config_;
 
   // Media thread members.
   // |media_log_| must outlive |video_decoder_| because it is passed as a raw
diff --git a/content/renderer/media/webrtc/rtc_video_decoder_adapter_unittest.cc b/content/renderer/media/webrtc/rtc_video_decoder_adapter_unittest.cc
index 49b09c8..5332faa4 100644
--- a/content/renderer/media/webrtc/rtc_video_decoder_adapter_unittest.cc
+++ b/content/renderer/media/webrtc/rtc_video_decoder_adapter_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include <stdint.h>
@@ -30,6 +31,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/webrtc/api/video_codecs/video_codec.h"
+#include "third_party/webrtc/media/base/vp9_profile.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -91,6 +93,8 @@
   RTCVideoDecoderAdapterTest()
       : media_thread_("Media Thread"),
         gpu_factories_(nullptr),
+        sdp_format_(webrtc::SdpVideoFormat(
+            webrtc::CodecTypeToPayloadString(webrtc::kVideoCodecVP9))),
         decoded_image_callback_(decoded_cb_.Get()) {
     media_thread_.Start();
 
@@ -105,15 +109,14 @@
         .WillByDefault(Return(true));
     EXPECT_CALL(gpu_factories_, IsDecoderConfigSupported(_)).Times(AtLeast(0));
 
-    ON_CALL(gpu_factories_, CreateVideoDecoder(_, _, _))
+    ON_CALL(gpu_factories_, CreateVideoDecoder(_, _))
         .WillByDefault(
             [this](media::MediaLog* media_log,
-                   const media::RequestOverlayInfoCB& request_overlay_info_cb,
-                   const gfx::ColorSpace& target_color_space) {
+                   const media::RequestOverlayInfoCB& request_overlay_info_cb) {
               DCHECK(this->owned_video_decoder_);
               return std::move(this->owned_video_decoder_);
             });
-    EXPECT_CALL(gpu_factories_, CreateVideoDecoder(_, _, _)).Times(AtLeast(0));
+    EXPECT_CALL(gpu_factories_, CreateVideoDecoder(_, _)).Times(AtLeast(0));
   }
 
   ~RTCVideoDecoderAdapterTest() {
@@ -144,12 +147,10 @@
 
   bool CreateAndInitialize(bool init_cb_result = true) {
     EXPECT_CALL(*video_decoder_, Initialize(_, _, _, _, _, _))
-        .WillOnce(DoAll(SaveArg<4>(&output_cb_),
+        .WillOnce(DoAll(SaveArg<0>(&vda_config_), SaveArg<4>(&output_cb_),
                         media::RunCallback<3>(init_cb_result)));
-    rtc_video_decoder_adapter_ = RTCVideoDecoderAdapter::Create(
-        &gpu_factories_,
-        webrtc::SdpVideoFormat(
-            webrtc::CodecTypeToPayloadString(webrtc::kVideoCodecVP9)));
+    rtc_video_decoder_adapter_ =
+        RTCVideoDecoderAdapter::Create(&gpu_factories_, sdp_format_);
     return !!rtc_video_decoder_adapter_;
   }
 
@@ -194,6 +195,25 @@
 
   int32_t Release() { return rtc_video_decoder_adapter_->Release(); }
 
+  webrtc::EncodedImage GetEncodedImageWithColorSpace(uint8_t* buf,
+                                                     uint32_t timestamp) {
+    webrtc::EncodedImage input_image(buf, 1, 1);
+    input_image._completeFrame = true;
+    input_image._frameType = webrtc::kVideoFrameKey;
+    input_image.SetTimestamp(timestamp);
+    webrtc::ColorSpace webrtc_color_space;
+    webrtc_color_space.set_primaries_from_uint8(1);
+    webrtc_color_space.set_transfer_from_uint8(1);
+    webrtc_color_space.set_matrix_from_uint8(1);
+    webrtc_color_space.set_range_from_uint8(1);
+    input_image.SetColorSpace(webrtc_color_space);
+    return input_image;
+  }
+
+  void SetSdpFormat(const webrtc::SdpVideoFormat& sdp_format) {
+    sdp_format_ = sdp_format;
+  }
+
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   base::Thread media_thread_;
 
@@ -205,9 +225,11 @@
       decoded_cb_;
 
   StrictMock<media::MockGpuVideoAcceleratorFactories> gpu_factories_;
+  media::VideoDecoderConfig vda_config_;
   std::unique_ptr<RTCVideoDecoderAdapter> rtc_video_decoder_adapter_;
 
  private:
+  webrtc::SdpVideoFormat sdp_format_;
   std::unique_ptr<StrictMock<MockVideoDecoder>> owned_video_decoder_;
   DecodedImageCallback decoded_image_callback_;
   media::VideoDecoder::OutputCB output_cb_;
@@ -301,4 +323,80 @@
   FAIL();
 }
 
+TEST_F(RTCVideoDecoderAdapterTest, ReinitializesForHDRColorSpaceInitially) {
+  SetSdpFormat(webrtc::SdpVideoFormat(
+      "VP9", {{webrtc::kVP9FmtpProfileId,
+               webrtc::VP9ProfileToString(webrtc::VP9Profile::kProfile2)}}));
+  ASSERT_TRUE(BasicSetup());
+  EXPECT_EQ(media::VP9PROFILE_PROFILE2, vda_config_.profile());
+  EXPECT_FALSE(vda_config_.color_space_info().IsSpecified());
+  uint8_t buf[] = {0};
+
+  // Decode() is expected to be called for EOS flush as well.
+  EXPECT_CALL(*video_decoder_, Decode(_, _))
+      .Times(3)
+      .WillRepeatedly(media::RunCallback<1>(media::DecodeStatus::OK));
+  EXPECT_CALL(decoded_cb_, Run(_)).Times(2);
+
+  // First Decode() should cause a reinitialize as new color space is given.
+  EXPECT_CALL(*video_decoder_, Initialize(_, _, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<0>(&vda_config_), media::RunCallback<3>(true)));
+  webrtc::EncodedImage first_input_image =
+      GetEncodedImageWithColorSpace(&buf[0], 0);
+  ASSERT_EQ(
+      rtc_video_decoder_adapter_->Decode(first_input_image, false, nullptr, 0),
+      WEBRTC_VIDEO_CODEC_OK);
+  media_thread_.FlushForTesting();
+  EXPECT_TRUE(vda_config_.color_space_info().IsSpecified());
+  FinishDecode(0);
+  media_thread_.FlushForTesting();
+
+  // Second Decode() with same params should happen normally.
+  webrtc::EncodedImage second_input_image =
+      GetEncodedImageWithColorSpace(&buf[0], 1);
+  ASSERT_EQ(
+      rtc_video_decoder_adapter_->Decode(second_input_image, false, nullptr, 0),
+      WEBRTC_VIDEO_CODEC_OK);
+  FinishDecode(1);
+  media_thread_.FlushForTesting();
+}
+
+TEST_F(RTCVideoDecoderAdapterTest, HandlesReinitializeFailure) {
+  SetSdpFormat(webrtc::SdpVideoFormat(
+      "VP9", {{webrtc::kVP9FmtpProfileId,
+               webrtc::VP9ProfileToString(webrtc::VP9Profile::kProfile2)}}));
+  ASSERT_TRUE(BasicSetup());
+  EXPECT_EQ(media::VP9PROFILE_PROFILE2, vda_config_.profile());
+  EXPECT_FALSE(vda_config_.color_space_info().IsSpecified());
+  uint8_t buf[] = {0};
+  webrtc::EncodedImage input_image = GetEncodedImageWithColorSpace(&buf[0], 0);
+
+  // Decode() is expected to be called for EOS flush as well.
+  EXPECT_CALL(*video_decoder_, Decode(_, _))
+      .WillOnce(media::RunCallback<1>(media::DecodeStatus::OK));
+
+  // Set Initialize() to fail.
+  EXPECT_CALL(*video_decoder_, Initialize(_, _, _, _, _, _))
+      .WillOnce(media::RunCallback<3>(false));
+  ASSERT_EQ(rtc_video_decoder_adapter_->Decode(input_image, false, nullptr, 0),
+            WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE);
+}
+
+TEST_F(RTCVideoDecoderAdapterTest, HandlesFlushFailure) {
+  SetSdpFormat(webrtc::SdpVideoFormat(
+      "VP9", {{webrtc::kVP9FmtpProfileId,
+               webrtc::VP9ProfileToString(webrtc::VP9Profile::kProfile2)}}));
+  ASSERT_TRUE(BasicSetup());
+  EXPECT_EQ(media::VP9PROFILE_PROFILE2, vda_config_.profile());
+  EXPECT_FALSE(vda_config_.color_space_info().IsSpecified());
+  uint8_t buf[] = {0};
+  webrtc::EncodedImage input_image = GetEncodedImageWithColorSpace(&buf[0], 0);
+
+  // Decode() is expected to be called for EOS flush, set to fail.
+  EXPECT_CALL(*video_decoder_, Decode(_, _))
+      .WillOnce(media::RunCallback<1>(media::DecodeStatus::ABORTED));
+  ASSERT_EQ(rtc_video_decoder_adapter_->Decode(input_image, false, nullptr, 0),
+            WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE);
+}
+
 }  // namespace content
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 1c4ae023..b82743e 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2863,7 +2863,8 @@
   frame_->EnableViewSourceMode(false);
 
   auto navigation_params = WebNavigationParams::CreateForErrorPage(
-      document_loader, error_html, GURL(kUnreachableWebDataURL), error.url());
+      document_loader, error_html, GURL(kUnreachableWebDataURL), error.url(),
+      error.reason());
   std::unique_ptr<DocumentState> document_state;
 
   if (inherit_document_state) {
@@ -3600,6 +3601,7 @@
   FillNavigationParamsRequest(common_params, commit_params,
                               navigation_params.get());
   navigation_params->url = GURL(kUnreachableWebDataURL);
+  navigation_params->error_code = error_code;
 
   if (!ShouldDisplayErrorPageForFailedLoad(error_code, common_params.url)) {
     // The browser expects this frame to be loading an error page. Inform it
@@ -6022,15 +6024,51 @@
   DCHECK(!(was_within_same_document && interface_params));
   UpdateStateForCommit(item, commit_type, transition);
 
+  auto params = MakeDidCommitProvisionalLoadParams(commit_type, transition);
+
+  // If this is a regular commit, not an error page, the URL that was just
+  // committed must match the process lock, if there is one. Verify it here, to
+  // get a stack trace for a bug where this seems to be occurring.
+  // TODO(nasko): Remove this check after we've gathered enough information to
+  // debug issues with browser-side security checks. https://crbug.com/931895.
+  RenderThreadImpl* render_thread = RenderThreadImpl::current();
+  const GURL* lock_url =
+      render_thread ? render_thread->site_lock_url() : nullptr;
+  if (frame_->GetDocumentLoader()->ErrorCode() != net::ERR_BLOCKED_BY_CLIENT &&
+      lock_url && lock_url->scheme() == params->url.scheme() &&
+      lock_url->SchemeIsHTTPOrHTTPS()) {
+    std::string lock_domain =
+        net::registry_controlled_domains::GetDomainAndRegistry(
+            lock_url->host(),
+            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+    std::string commit_domain =
+        net::registry_controlled_domains::GetDomainAndRegistry(
+            params->url.host(),
+            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+    if (lock_domain != commit_domain) {
+      base::debug::SetCrashKeyString(
+          base::debug::AllocateCrashKeyString(
+              "lock_domain", base::debug::CrashKeySize::Size64),
+          lock_domain);
+      base::debug::SetCrashKeyString(
+          base::debug::AllocateCrashKeyString(
+              "commit_domain", base::debug::CrashKeySize::Size64),
+          commit_domain);
+      base::debug::SetCrashKeyString(
+          base::debug::AllocateCrashKeyString(
+              "is_subframe", base::debug::CrashKeySize::Size32),
+          is_main_frame_ ? "true" : "false");
+      CHECK(false);
+    }
+  }
+
   // This invocation must precede any calls to allowScripts(), allowImages(), or
   // allowPlugins() for the new page. This ensures that when these functions
   // send ViewHostMsg_ContentBlocked messages, those arrive after the browser
   // process has already been informed of the provisional load committing.
   if (was_within_same_document) {
-    GetFrameHost()->DidCommitSameDocumentNavigation(
-        MakeDidCommitProvisionalLoadParams(commit_type, transition));
+    GetFrameHost()->DidCommitSameDocumentNavigation(std::move(params));
   } else {
-    auto params = MakeDidCommitProvisionalLoadParams(commit_type, transition);
     NavigationState* navigation_state =
         NavigationState::FromDocumentLoader(frame_->GetDocumentLoader());
     if (navigation_state->uses_per_navigation_mojo_interface()) {
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 44d96379..9d029d4 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1711,9 +1711,10 @@
       base::TimeDelta::FromMinutes(90));
 }
 
-void RenderThreadImpl::SetIsLockedToSite() {
+void RenderThreadImpl::SetIsLockedToSite(const GURL& lock_url) {
   DCHECK(blink_platform_impl_);
   blink_platform_impl_->SetIsLockedToSite();
+  site_lock_url_ = std::make_unique<GURL>(lock_url);
 }
 
 void RenderThreadImpl::EnableV8LowMemoryMode() {
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index afa96183..11d7639 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -494,6 +494,10 @@
     video_frame_compositor_task_runner_ = task_runner;
   }
 
+  // TODO(nasko): Remove after we've gathered enough information to debug issues
+  // with browser-side security checks. https://crbug.com/931895.
+  const GURL* site_lock_url() { return site_lock_url_.get(); }
+
  private:
   friend class RenderThreadImplBrowserTest;
 
@@ -551,7 +555,7 @@
   void SetProcessState(mojom::RenderProcessState process_state) override;
   void SetSchedulerKeepActive(bool keep_active) override;
   void ProcessPurgeAndSuspend() override;
-  void SetIsLockedToSite() override;
+  void SetIsLockedToSite(const GURL& lock_url) override;
   void EnableV8LowMemoryMode() override;
 
   void OnMemoryPressure(
@@ -752,6 +756,11 @@
   mojo::Binding<viz::mojom::CompositingModeWatcher>
       compositing_mode_watcher_binding_;
 
+  // TODO(nasko): Temporary diagnostic member, holding the site URL this process
+  // is locked to. Remove after we've gathered enough information to
+  // debug issues with browser-side security checks. https://crbug.com/931895.
+  std::unique_ptr<GURL> site_lock_url_;
+
   base::WeakPtrFactory<RenderThreadImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(RenderThreadImpl);
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index 0c503a61..0e48435 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -517,6 +517,11 @@
     v8::Local<v8::Context> context) {
   CHECK(worker_task_runner_->RunsTasksInCurrentSequence());
   RecordDebugLog("WillDestroyWorkerContext");
+  if (dispatching_fetch_event_) {
+    CrashWithDebugLog("WDWC_DFE");
+    return;
+  }
+
   // At this point WillStopCurrentWorkerThread is already called, so
   // worker_task_runner_->RunsTasksInCurrentSequence() returns false
   // (while we're still on the worker thread).
@@ -1481,9 +1486,10 @@
     DispatchFetchEventCallback callback) {
   CHECK(worker_task_runner_->RunsTasksInCurrentSequence());
   if (!context_) {
-    CrashWithDebugLog("DFE");
+    CrashWithDebugLog("DFE1");
     return;
   }
+  dispatching_fetch_event_ = true;
   int event_id = context_->timeout_timer->StartEvent(
       CreateAbortCallback(&context_->fetch_event_callbacks));
   context_->fetch_event_callbacks.emplace(event_id, std::move(callback));
@@ -1509,7 +1515,12 @@
   blink::WebServiceWorkerRequest web_request;
   ToWebServiceWorkerRequestForFetchEvent(std::move(params->request),
                                          params->client_id, &web_request);
+  if (!context_) {
+    CrashWithDebugLog("DFE2");
+    return;
+  }
   proxy_->DispatchFetchEvent(event_id, web_request, navigation_preload_sent);
+  dispatching_fetch_event_ = false;
 }
 
 void ServiceWorkerContextClient::DispatchNotificationClickEvent(
diff --git a/content/renderer/service_worker/service_worker_context_client.h b/content/renderer/service_worker/service_worker_context_client.h
index be31fe2..175b509 100644
--- a/content/renderer/service_worker/service_worker_context_client.h
+++ b/content/renderer/service_worker/service_worker_context_client.h
@@ -435,6 +435,7 @@
   bool report_debug_log_ = true;
   base::Lock debug_log_lock_;
   std::deque<std::string> debug_log_ GUARDED_BY(debug_log_lock_);
+  bool dispatching_fetch_event_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerContextClient);
 };
diff --git a/content/renderer/service_worker/service_worker_network_provider_for_frame.cc b/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
index dec7a84..0cdced7 100644
--- a/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
+++ b/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
@@ -19,21 +19,6 @@
 
 namespace content {
 
-namespace {
-// Returns whether it's possible for a document whose frame is a descendant of
-// |frame| to be a secure context, not considering scheme exceptions (since any
-// document can be a secure context if it has a scheme exception). See
-// Document::isSecureContextImpl for more details.
-bool IsFrameSecure(blink::WebFrame* frame) {
-  while (frame) {
-    if (!frame->GetSecurityOrigin().IsPotentiallyTrustworthy())
-      return false;
-    frame = frame->Parent();
-  }
-  return true;
-}
-}  // namespace
-
 class ServiceWorkerNetworkProviderForFrame::NewDocumentObserver
     : public RenderFrameObserver {
  public:
@@ -78,21 +63,13 @@
     scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
   DCHECK(ServiceWorkerUtils::IsBrowserAssignedProviderId(provider_id));
 
-  // Ideally Document::IsSecureContext would be called here, but the document is
-  // not created yet, and due to redirects the URL may change. So pass
-  // is_parent_frame_secure to the browser process, so it can determine the
-  // context security when deciding whether to allow a service worker to control
-  // the document.
-  const bool is_parent_frame_secure =
-      IsFrameSecure(frame->GetWebFrame()->Parent());
-
   auto provider =
       base::WrapUnique(new ServiceWorkerNetworkProviderForFrame(frame));
 
   auto host_info = blink::mojom::ServiceWorkerProviderHostInfo::New(
       provider_id, frame->GetRoutingID(),
       blink::mojom::ServiceWorkerProviderType::kForWindow,
-      is_parent_frame_secure, nullptr /* host_request */,
+      false /* is_parent_frame_secure */, nullptr /* host_request */,
       nullptr /* client_ptr_info */);
   blink::mojom::ServiceWorkerContainerAssociatedRequest client_request =
       mojo::MakeRequest(&host_info->client_ptr_info);
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 33a601f8..523d1cb3 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1139,6 +1139,7 @@
     sources += [
       "../browser/accessibility/accessibility_win_browsertest.cc",
       "../browser/accessibility/ax_platform_node_win_browsertest.cc",
+      "../browser/renderer_host/accessibility_tree_linkage_win_browsertest.cc",
       "../browser/renderer_host/direct_manipulation_browsertest.cc",
     ]
 
@@ -1560,6 +1561,7 @@
     "../browser/notifications/notification_event_dispatcher_impl_unittest.cc",
     "../browser/notifications/notification_id_generator_unittest.cc",
     "../browser/notifications/notification_storage_unittest.cc",
+    "../browser/notifications/platform_notification_context_trigger_unittest.cc",
     "../browser/notifications/platform_notification_context_unittest.cc",
     "../browser/payments/payment_app_content_unittest_base.cc",
     "../browser/payments/payment_app_content_unittest_base.h",
diff --git a/content/test/gpu/gpu_tests/pixel_expectations.py b/content/test/gpu/gpu_tests/pixel_expectations.py
index 4b1ad04..0c8f85b 100644
--- a/content/test/gpu/gpu_tests/pixel_expectations.py
+++ b/content/test/gpu/gpu_tests/pixel_expectations.py
@@ -152,3 +152,7 @@
               bug=927901)
     self.Skip('Pixel_Video_VP9_DXVA', ['linux', 'android', 'mac', 'chromeos'],
               bug=927901)
+
+    # Complex overlays test is flaky on Nvidia probably due to its small size.
+    self.Flaky('Pixel_DirectComposition_ComplexOverlays', ['win', 'nvidia'],
+               bug=929425)
diff --git a/content/test/gpu/gpu_tests/trace_test_expectations.py b/content/test/gpu/gpu_tests/trace_test_expectations.py
index 559f5a3..de36ffb 100644
--- a/content/test/gpu/gpu_tests/trace_test_expectations.py
+++ b/content/test/gpu/gpu_tests/trace_test_expectations.py
@@ -31,3 +31,7 @@
     self.Fail('VideoPathTraceTest_DirectComposition_Video_VP9_Fullsize',
         ['win', 'intel'], bug=930343)
 
+    # Complex overlays test is flaky on Nvidia probably due to its small size.
+    self.Flaky('VideoPathTraceTest_DirectComposition_ComplexOverlays',
+        ['win', 'nvidia'], bug=937545)
+
diff --git a/device/usb/public/mojom/BUILD.gn b/device/usb/public/mojom/BUILD.gn
index 23a8a97..1de3791 100644
--- a/device/usb/public/mojom/BUILD.gn
+++ b/device/usb/public/mojom/BUILD.gn
@@ -34,6 +34,7 @@
     "//components/arc/common:common_blink",
     "//third_party/blink/public/mojom/usb:usb_blink",
     "//third_party/blink/renderer/modules/webusb",
+    "//chrome/browser/ui/webui/usb_internals:mojo_bindings_blink",
   ]
 }
 
diff --git a/device/vr/android/gvr/gvr_device.cc b/device/vr/android/gvr/gvr_device.cc
index daf2c813..8c1b2a4b 100644
--- a/device/vr/android/gvr/gvr_device.cc
+++ b/device/vr/android/gvr/gvr_device.cc
@@ -227,18 +227,6 @@
   exclusive_controller_binding_.Close();
 }
 
-void GvrDevice::OnGetInlineFrameData(
-    mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
-  if (!gvr_api_) {
-    std::move(callback).Run(nullptr);
-    return;
-  }
-  mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
-  frame_data->pose =
-      GvrDelegate::GetVRPosePtrWithNeckModel(gvr_api_.get(), nullptr);
-  std::move(callback).Run(std::move(frame_data));
-}
-
 void GvrDevice::OnListeningForActivate(bool listening) {
   GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
   if (!delegate_provider)
@@ -365,13 +353,7 @@
     return;
   }
 
-  if (!options->immersive) {
-    // TODO(https://crbug.com/695937): This should be NOTREACHED() once we no
-    // longer need the hacked GVR non-immersive mode.  This should now only be
-    // hit if orientation devices are disabled by flag.
-    ReturnNonImmersiveSession(std::move(pending_request_session_callback_));
-    return;
-  }
+  DCHECK(options->immersive);
 
   // StartWebXRPresentation is async as we may trigger a DON (Device ON) flow
   // that pauses Chrome.
diff --git a/device/vr/android/gvr/gvr_device.h b/device/vr/android/gvr/gvr_device.h
index da05cef..875a272 100644
--- a/device/vr/android/gvr/gvr_device.h
+++ b/device/vr/android/gvr/gvr_device.h
@@ -45,8 +45,6 @@
  private:
   // VRDeviceBase
   void OnListeningForActivate(bool listening) override;
-  void OnGetInlineFrameData(
-      mojom::XRFrameDataProvider::GetFrameDataCallback callback) override;
 
   void OnStartPresentResult(mojom::XRSessionPtr session);
 
diff --git a/device/vr/oculus/oculus_device.cc b/device/vr/oculus/oculus_device.cc
index 8a37118..495af89 100644
--- a/device/vr/oculus/oculus_device.cc
+++ b/device/vr/oculus/oculus_device.cc
@@ -133,10 +133,7 @@
     return;
   }
 
-  if (!options->immersive) {
-    ReturnNonImmersiveSession(std::move(callback));
-    return;
-  }
+  DCHECK(options->immersive);
 
   StopOvrSession();
 
@@ -285,20 +282,6 @@
   }
 }
 
-void OculusDevice::OnGetInlineFrameData(
-    mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
-  if (!session_) {
-    std::move(callback).Run(nullptr);
-    return;
-  }
-
-  ovrTrackingState state = ovr_GetTrackingState(session_, 0, false);
-
-  mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
-  frame_data->pose = mojo::ConvertTo<mojom::VRPosePtr>(state.HeadPose.ThePose);
-  std::move(callback).Run(std::move(frame_data));
-}
-
 void OculusDevice::GetIsolatedXRGamepadProvider(
     mojom::IsolatedXRGamepadProviderRequest provider_request) {
   // We bind the provider_request on the render loop thread, so gamepad data is
diff --git a/device/vr/oculus/oculus_device.h b/device/vr/oculus/oculus_device.h
index a825d2d1..5134c3bf 100644
--- a/device/vr/oculus/oculus_device.h
+++ b/device/vr/oculus/oculus_device.h
@@ -37,8 +37,6 @@
   void EnsureInitialized(int render_process_id,
                          int render_frame_id,
                          EnsureInitializedCallback callback) override;
-  void OnGetInlineFrameData(
-      mojom::XRFrameDataProvider::GetFrameDataCallback callback) override;
   void OnRequestSessionResult(mojom::XRRuntime::RequestSessionCallback callback,
                               bool result,
                               mojom::XRSessionPtr session);
diff --git a/device/vr/openvr/openvr_device.cc b/device/vr/openvr/openvr_device.cc
index 7d2bdb8..a1b02aee 100644
--- a/device/vr/openvr/openvr_device.cc
+++ b/device/vr/openvr/openvr_device.cc
@@ -181,10 +181,7 @@
     return;
   }
 
-  if (!options->immersive) {
-    ReturnNonImmersiveSession(std::move(callback));
-    return;
-  }
+  DCHECK(options->immersive);
 
   if (!render_loop_->IsRunning()) {
     render_loop_->Start();
@@ -334,23 +331,6 @@
   exclusive_controller_binding_.Close();
 }
 
-void OpenVRDevice::OnGetInlineFrameData(
-    mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
-  if (!openvr_) {
-    std::move(callback).Run(nullptr);
-    return;
-  }
-  const float kPredictionTimeSeconds = 0.03f;
-  vr::TrackedDevicePose_t rendering_poses[vr::k_unMaxTrackedDeviceCount];
-  openvr_->GetSystem()->GetDeviceToAbsoluteTrackingPose(
-      vr::TrackingUniverseSeated, kPredictionTimeSeconds, rendering_poses,
-      vr::k_unMaxTrackedDeviceCount);
-  mojom::XRFrameDataPtr data = mojom::XRFrameData::New();
-  data->pose = mojo::ConvertTo<mojom::VRPosePtr>(
-      rendering_poses[vr::k_unTrackedDeviceIndex_Hmd]);
-  std::move(callback).Run(std::move(data));
-}
-
 // Only deal with events that will cause displayInfo changes for now.
 void OpenVRDevice::OnPollingEvents() {
   main_thread_task_runner_->PostDelayedTask(
diff --git a/device/vr/openvr/openvr_device.h b/device/vr/openvr/openvr_device.h
index cbec7eb..fed1fca8 100644
--- a/device/vr/openvr/openvr_device.h
+++ b/device/vr/openvr/openvr_device.h
@@ -52,10 +52,6 @@
   mojom::XRCompositorHostPtr BindCompositorHost();
 
  private:
-  // VRDeviceBase
-  void OnGetInlineFrameData(
-      mojom::XRFrameDataProvider::GetFrameDataCallback callback) override;
-
   // XRSessionController
   void SetFrameDataRestricted(bool restricted) override;
 
diff --git a/device/vr/windows_mixed_reality/mixed_reality_device.cc b/device/vr/windows_mixed_reality/mixed_reality_device.cc
index 7f8c7d0..cd792a7 100644
--- a/device/vr/windows_mixed_reality/mixed_reality_device.cc
+++ b/device/vr/windows_mixed_reality/mixed_reality_device.cc
@@ -88,10 +88,7 @@
 void MixedRealityDevice::RequestSession(
     mojom::XRRuntimeSessionOptionsPtr options,
     mojom::XRRuntime::RequestSessionCallback callback) {
-  if (!options->immersive) {
-    ReturnNonImmersiveSession(std::move(callback));
-    return;
-  }
+  DCHECK(options->immersive);
 
   if (!render_loop_)
     CreateRenderLoop();
diff --git a/docs/android_accessing_cpp_switches_in_java.md b/docs/android_accessing_cpp_switches_in_java.md
new file mode 100644
index 0000000..b9f12f2
--- /dev/null
+++ b/docs/android_accessing_cpp_switches_in_java.md
@@ -0,0 +1,93 @@
+# Accessing C++ Switches In Java
+
+[TOC]
+
+## Introduction
+
+Accessing C++ switches in Java is implemented via a Python script which analyzes
+the C++ switches file and spits out the corresponding Java class. The generated
+class name will be based upon the switch file name, and the path must be
+specified in a comment within the switch file itself.
+
+## Usage
+
+1. Add directives to your C++ switch file
+
+    ```cpp
+    // GENERATED_JAVA_PACKAGE: org.chromium.chrome
+
+    // ...snip...
+
+    // Documentation for the following switch.
+    const char kSomeSwitch[] = "some-switch";
+
+    // ...snip...
+    ```
+
+2. Create a template file
+   ```java
+    // Copyright {YEAR} 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.
+
+    // This file is autogenerated by
+    //     {SCRIPT_NAME}
+    // From
+    //     {SOURCE_PATH}, and
+    //     {TEMPLATE_PATH}
+
+    package my.java.package
+
+    // Be sure to escape any curly braces in your template by doubling as
+    // follows.
+    public abstract class MySwitches {{
+
+    {NATIVE_SWITCHES}
+
+    }}
+   ```
+
+3. Add a new build target
+
+    ```gn
+    import("//build/config/android/rules.gni")
+
+    java_cpp_strings("foo_generated_switch") {
+      sources = [
+        "//base/android/native_foo_switches.cc",
+      ]
+      template = "//base/android/java_templates/MySwitches.java.tmpl"
+    }
+    ```
+
+5. Add the new target to the desired android_library targets srcjar_deps:
+
+    ```gn
+    android_library("base_java") {
+      srcjar_deps = [
+        ":foo_generated_switches",
+      ]
+    }
+    ```
+
+5. The generated file `org/chromium/chrome/NativeFooSwitches.java` would contain:
+
+    ```java
+    package org.chromium.chrome;
+
+    public final class NativeFooSwitches {
+        // ...snip...
+
+        public static final String SOME_SWITCH = "some-switch";
+
+        // ...snip...
+    }
+    ```
+
+## Code
+* [Generator
+code](https://cs.chromium.org/chromium/src/build/android/gyp/java_cpp_strings.py?dr=C&sq=package:chromium)
+and
+[Tests](https://cs.chromium.org/chromium/src/build/android/gyp/java_cpp_strings_tests.py?dr=C&sq=package:chromium)
+* [GN
+template](https://cs.chromium.org/chromium/src/build/config/android/rules.gni?sq=package:chromium&dr=C)
diff --git a/docs/memory/debugging_memory_issues.md b/docs/memory/debugging_memory_issues.md
index 83cab2e..f7738a72 100644
--- a/docs/memory/debugging_memory_issues.md
+++ b/docs/memory/debugging_memory_issues.md
@@ -118,9 +118,14 @@
 
 * `#memlog` controls which processes are profiled. It's also possible to
   manually specify the process via the interface at `chrome://memory-internals`.
-* `#memlog-sampling` will greatly reduce the overhead of the heap profiler, at
-  the expense of inaccuracy in small or infrequent allocations. Unless
-  performance is a concern, leave it disabled.
+* `#memlog-in-process` makes the profiling service to be run within the
+  Chrome browser process. Defaults to run the service as a separate dedicated
+  process.
+* `#memlog-sampling-rate` specifies the sampling interval in bytes. The lower
+  the interval, the more precise is the profile. However it comes at the cost of
+  performance. Default value is 100KB, that is enough to observe allocation
+  sites that make allocations >500KB total, where total equals to a single
+  allocation size times the number of such allocations at the same call site.
 * `#memlog-stack-mode` describes the type of metadata recorded for each
   allocation. `native` stacks provide the most utility. The only time the other
   options should be considered is for Android official builds, most of which do
diff --git a/extensions/browser/api/messaging/BUILD.gn b/extensions/browser/api/messaging/BUILD.gn
index a5adc40b..d67b942 100644
--- a/extensions/browser/api/messaging/BUILD.gn
+++ b/extensions/browser/api/messaging/BUILD.gn
@@ -9,6 +9,8 @@
 
 source_set("messaging") {
   sources = [
+    "channel_endpoint.cc",
+    "channel_endpoint.h",
     "extension_message_port.cc",
     "extension_message_port.h",
     "message_port.cc",
diff --git a/extensions/browser/api/messaging/channel_endpoint.cc b/extensions/browser/api/messaging/channel_endpoint.cc
new file mode 100644
index 0000000..e64d3a4
--- /dev/null
+++ b/extensions/browser/api/messaging/channel_endpoint.cc
@@ -0,0 +1,72 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/messaging/channel_endpoint.h"
+
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/child_process_host.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/constants.h"
+
+namespace extensions {
+
+ChannelEndpoint::ChannelEndpoint(content::BrowserContext* browser_context,
+                                 int render_process_id,
+                                 const PortContext& port_context)
+    : browser_context_(browser_context),
+      render_process_id_(render_process_id),
+      port_context_(port_context) {
+  // Context must be exclusive to render frame or worker.
+  DCHECK_NE(port_context.is_for_service_worker(),
+            port_context.is_for_render_frame());
+}
+
+// For native message endpoint.
+ChannelEndpoint::ChannelEndpoint(content::BrowserContext* browser_context)
+    : browser_context_(browser_context),
+      render_process_id_(content::ChildProcessHost::kInvalidUniqueID) {
+  DCHECK(!port_context_.is_for_render_frame() &&
+         !port_context_.is_for_service_worker());
+}
+
+bool ChannelEndpoint::is_for_service_worker() const {
+  return port_context_.is_for_service_worker();
+}
+
+bool ChannelEndpoint::is_for_render_frame() const {
+  return port_context_.frame.has_value();
+}
+
+bool ChannelEndpoint::is_for_native_host() const {
+  return !port_context_.is_for_render_frame() &&
+         !port_context_.is_for_service_worker();
+}
+
+content::RenderFrameHost* ChannelEndpoint::GetRenderFrameHost() const {
+  DCHECK(port_context_.is_for_render_frame());
+  return content::RenderFrameHost::FromID(render_process_id_,
+                                          port_context_.frame->routing_id);
+}
+
+WorkerId ChannelEndpoint::GetWorkerId() const {
+  DCHECK(port_context_.is_for_service_worker());
+  return {port_context_.worker->extension_id, render_process_id_,
+          port_context_.worker->version_id, port_context_.worker->thread_id};
+}
+
+bool ChannelEndpoint::IsValid() const {
+  if (is_for_service_worker()) {
+    return ProcessManager::Get(browser_context())
+        ->HasServiceWorker(GetWorkerId());
+  }
+
+  if (is_for_render_frame())
+    return GetRenderFrameHost() != nullptr;
+
+  DCHECK(is_for_native_host());
+  return true;
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/api/messaging/channel_endpoint.h b/extensions/browser/api/messaging/channel_endpoint.h
new file mode 100644
index 0000000..62d6e87
--- /dev/null
+++ b/extensions/browser/api/messaging/channel_endpoint.h
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_MESSAGING_CHANNEL_ENDPOINT_H_
+#define EXTENSIONS_BROWSER_API_MESSAGING_CHANNEL_ENDPOINT_H_
+
+#include "extensions/browser/service_worker/worker_id.h"
+
+#include "extensions/common/api/messaging/port_context.h"
+
+namespace content {
+class BrowserContext;
+class RenderFrameHost;
+}  // namespace content
+
+namespace extensions {
+
+// Represents an endpoint (tab, frame or worker) of a message channel in a
+// render process.
+// TODO(crbug.com/939594): Consolidate all classes/structs around extension
+// message ports.
+class ChannelEndpoint {
+ public:
+  // An endpoint for a PortContext.
+  ChannelEndpoint(content::BrowserContext* browser_context,
+                  int render_process_id,
+                  const PortContext& port_context);
+  // An endpoint for a native message host.
+  ChannelEndpoint(content::BrowserContext* browser_context);
+
+  content::BrowserContext* browser_context() const { return browser_context_; }
+  int render_process_id() const { return render_process_id_; }
+  const PortContext& port_context() const { return port_context_; }
+
+  bool is_for_service_worker() const;
+  bool is_for_render_frame() const;
+  bool is_for_native_host() const;
+
+  // If the endpoint is a Service Worker, returns its worker id.
+  // Only valid for worker endpoint.
+  WorkerId GetWorkerId() const;
+
+  // Returns the render frame if this endpoint points to a frame. Returns
+  // nullptr if the frame is no longer alive.
+  // Only valid for frame endpoint.
+  content::RenderFrameHost* GetRenderFrameHost() const;
+
+  // Returns whether the endpoint is currently live.
+  bool IsValid() const;
+
+ private:
+  content::BrowserContext* const browser_context_;
+  const int render_process_id_;
+  const PortContext port_context_;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_API_MESSAGING_CHANNEL_ENDPOINT_H_
diff --git a/extensions/browser/api/messaging/extension_message_port.cc b/extensions/browser/api/messaging/extension_message_port.cc
index b57e4b70..a74181f 100644
--- a/extensions/browser/api/messaging/extension_message_port.cc
+++ b/extensions/browser/api/messaging/extension_message_port.cc
@@ -17,6 +17,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/api/messaging/channel_endpoint.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_manager_observer.h"
@@ -186,6 +187,35 @@
   }
 }
 
+ExtensionMessagePort::ExtensionMessagePort(
+    base::WeakPtr<ChannelDelegate> channel_delegate,
+    const PortId& port_id,
+    content::BrowserContext* browser_context)
+    : weak_channel_delegate_(channel_delegate),
+      port_id_(port_id),
+      browser_context_(browser_context) {}
+
+// static
+std::unique_ptr<ExtensionMessagePort> ExtensionMessagePort::CreateForEndpoint(
+    base::WeakPtr<ChannelDelegate> channel_delegate,
+    const PortId& port_id,
+    const std::string& extension_id,
+    const ChannelEndpoint& endpoint,
+    bool include_child_frames) {
+  if (endpoint.is_for_render_frame()) {
+    return std::make_unique<ExtensionMessagePort>(
+        channel_delegate, port_id, extension_id, endpoint.GetRenderFrameHost(),
+        include_child_frames);
+  }
+  DCHECK(!include_child_frames);
+  // NOTE: We don't want all the workers within the extension, so we cannot
+  // reuse other constructor from above.
+  std::unique_ptr<ExtensionMessagePort> port(new ExtensionMessagePort(
+      channel_delegate, port_id, endpoint.browser_context()));
+  port->RegisterWorker(endpoint.GetWorkerId());
+  return port;
+}
+
 ExtensionMessagePort::~ExtensionMessagePort() {}
 
 void ExtensionMessagePort::RemoveCommonFrames(const MessagePort& port) {
diff --git a/extensions/browser/api/messaging/extension_message_port.h b/extensions/browser/api/messaging/extension_message_port.h
index cbaf656..a747f5cf 100644
--- a/extensions/browser/api/messaging/extension_message_port.h
+++ b/extensions/browser/api/messaging/extension_message_port.h
@@ -30,6 +30,7 @@
 
 namespace extensions {
 class ExtensionHost;
+class ChannelEndpoint;
 struct PortContext;
 
 // A port that manages communication with an extension.
@@ -49,6 +50,16 @@
                        const PortId& port_id,
                        const std::string& extension_id,
                        content::RenderProcessHost* extension_process);
+
+  // Creates a port for any ChannelEndpoint which can be for a render frame or
+  // Service Worker.
+  static std::unique_ptr<ExtensionMessagePort> CreateForEndpoint(
+      base::WeakPtr<ChannelDelegate> channel_delegate,
+      const PortId& port_id,
+      const std::string& extension_id,
+      const ChannelEndpoint& endpoint,
+      bool include_child_frames);
+
   ~ExtensionMessagePort() override;
 
   // MessagePort:
@@ -75,6 +86,10 @@
   class FrameTracker;
   struct IPCTarget;
 
+  ExtensionMessagePort(base::WeakPtr<ChannelDelegate> channel_delegate,
+                       const PortId& port_id,
+                       content::BrowserContext* browser_context);
+
   // Registers a frame as a receiver / sender.
   void RegisterFrame(content::RenderFrameHost* rfh);
 
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc
index 621334e..59237f2 100644
--- a/extensions/browser/api/messaging/message_service.cc
+++ b/extensions/browser/api/messaging/message_service.cc
@@ -27,6 +27,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/child_process_host.h"
 #include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/messaging/channel_endpoint.h"
 #include "extensions/browser/api/messaging/extension_message_port.h"
 #include "extensions/browser/api/messaging/message_port.h"
 #include "extensions/browser/api/messaging/messaging_delegate.h"
@@ -85,8 +86,7 @@
 };
 
 struct MessageService::OpenChannelParams {
-  int source_process_id;
-  int source_routing_id;
+  ChannelEndpoint source;
   std::unique_ptr<base::DictionaryValue> source_tab;
   int source_frame_id;
   std::unique_ptr<MessagePort> receiver;
@@ -99,8 +99,7 @@
   bool include_guest_process_info;
 
   // Takes ownership of receiver.
-  OpenChannelParams(int source_process_id,
-                    int source_routing_id,
+  OpenChannelParams(const ChannelEndpoint& source,
                     std::unique_ptr<base::DictionaryValue> source_tab,
                     int source_frame_id,
                     MessagePort* receiver,
@@ -111,8 +110,7 @@
                     const GURL& source_url,
                     const std::string& channel_name,
                     bool include_guest_process_info)
-      : source_process_id(source_process_id),
-        source_routing_id(source_routing_id),
+      : source(source),
         source_tab(std::move(source_tab)),
         source_frame_id(source_frame_id),
         receiver(receiver),
@@ -171,8 +169,7 @@
 }
 
 void MessageService::OpenChannelToExtension(
-    int source_process_id,
-    int source_routing_id,
+    const ChannelEndpoint& source,
     const PortId& source_port_id,
     const MessagingEndpoint& source_endpoint,
     std::unique_ptr<MessagePort> opener_port,
@@ -187,15 +184,17 @@
          source_endpoint.type == MessagingEndpoint::Type::kNativeApp);
   DCHECK_EQ(source_endpoint.native_app_name.has_value(),
             source_endpoint.type == MessagingEndpoint::Type::kNativeApp);
+  int source_process_id = source.render_process_id();
   DCHECK_EQ(source_process_id == content::ChildProcessHost::kInvalidUniqueID,
             source_endpoint.type == MessagingEndpoint::Type::kNativeApp);
+  const PortContext& source_context = source.port_context();
+  DCHECK(!source_context.is_for_service_worker())
+      << "Service worker to extension messaging isn't supported yet.";
 
   content::RenderFrameHost* source_render_frame_host = nullptr;
   BrowserContext* context = context_;
   if (source_process_id != content::ChildProcessHost::kInvalidUniqueID) {
-    DCHECK_NE(source_routing_id, MSG_ROUTING_NONE);
-    source_render_frame_host =
-        content::RenderFrameHost::FromID(source_process_id, source_routing_id);
+    source_render_frame_host = source.GetRenderFrameHost();
     if (!source_render_frame_host)
       return;
     context = source_render_frame_host->GetProcess()->GetBrowserContext();
@@ -291,10 +290,10 @@
   }
 
   std::unique_ptr<OpenChannelParams> params(new OpenChannelParams(
-      source_process_id, source_routing_id, std::move(source_tab),
-      source_frame_id, nullptr, source_port_id.GetOppositePortId(),
-      source_endpoint, std::move(opener_port), target_extension_id, source_url,
-      channel_name, include_guest_process_info));
+      source, std::move(source_tab), source_frame_id, nullptr,
+      source_port_id.GetOppositePortId(), source_endpoint,
+      std::move(opener_port), target_extension_id, source_url, channel_name,
+      include_guest_process_info));
 
   pending_incognito_channels_[params->receiver_port_id.GetChannelId()] =
       PendingMessagesQueue();
@@ -427,8 +426,7 @@
 #endif  // !(defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX))
 }
 
-void MessageService::OpenChannelToTab(int source_process_id,
-                                      int source_routing_id,
+void MessageService::OpenChannelToTab(const ChannelEndpoint& source,
                                       const PortId& source_port_id,
                                       int tab_id,
                                       int frame_id,
@@ -438,18 +436,18 @@
   DCHECK_GE(frame_id, -1);
   DCHECK(source_port_id.is_opener);
 
-  content::RenderFrameHost* source =
-      content::RenderFrameHost::FromID(source_process_id, source_routing_id);
-  if (!source)
+  // RenderFrameHost or the worker thread might be gone.
+  if (!source.IsValid())
     return;
 
-  auto opener_port = std::make_unique<ExtensionMessagePort>(
-      weak_factory_.GetWeakPtr(), source_port_id, extension_id, source,
-      false /* include_child_frames */);
+  std::unique_ptr<ExtensionMessagePort> opener_port =
+      ExtensionMessagePort::CreateForEndpoint(
+          weak_factory_.GetWeakPtr(), source_port_id, extension_id, source,
+          false /* include_child_frames */);
   if (!opener_port->IsValidPort())
     return;
 
-  BrowserContext* source_context = source->GetProcess()->GetBrowserContext();
+  BrowserContext* source_context = source.browser_context();
   DCHECK(
       ExtensionsBrowserClient::Get()->IsSameContext(source_context, context_));
   content::WebContents* receiver_contents =
@@ -484,7 +482,7 @@
   DCHECK(ExtensionsBrowserClient::Get()->IsSameContext(receiver_context,
                                                        context_));
   std::unique_ptr<OpenChannelParams> params(new OpenChannelParams(
-      source_process_id, source_routing_id,
+      source,
       std::unique_ptr<base::DictionaryValue>(),  // Source tab doesn't make
                                                  // sense
                                                  // for opening to tabs.
@@ -509,14 +507,9 @@
   DCHECK_EQ(target_extension != nullptr, !params->target_extension_id.empty());
 
   // Check whether the source got closed while in flight.
-  content::RenderFrameHost* source = nullptr;
-  if (params->source_process_id !=
-      content::ChildProcessHost::kInvalidUniqueID) {
-    source = content::RenderFrameHost::FromID(params->source_process_id,
-                                              params->source_routing_id);
-    if (!source)
-      return;
-  }
+  const ChannelEndpoint& source = params->source;
+  if (!source.IsValid())
+    return;  // Closed while in flight.
   if (!params->opener_port->IsValidPort())
     return;
 
@@ -525,11 +518,8 @@
     return;
   }
 
-  // TODO(crbug.com/925918): Implement opening channel from from a Service
-  // Worker context.
-  params->opener_port->OpenPort(
-      params->source_process_id,
-      PortContext::ForFrame(params->source_routing_id));
+  const PortContext& port_context = source.port_context();
+  params->opener_port->OpenPort(source.render_process_id(), port_context);
   params->opener_port->RevalidatePort();
 
   params->receiver->RemoveCommonFrames(*params->opener_port);
@@ -547,12 +537,15 @@
 
   int guest_process_id = content::ChildProcessHost::kInvalidUniqueID;
   int guest_render_frame_routing_id = MSG_ROUTING_NONE;
-  if (params->include_guest_process_info) {
-    guest_process_id = params->source_process_id;
-    guest_render_frame_routing_id = params->source_routing_id;
+  if (params->include_guest_process_info &&
+      // TODO(lazyboy): Investigate <webview> SW messaging.
+      source.is_for_render_frame()) {
+    guest_process_id = params->source.render_process_id();
+    DCHECK(port_context.frame);
+    guest_render_frame_routing_id = port_context.frame->routing_id;
 
     DCHECK(WebViewGuest::FromWebContents(
-            WebContents::FromRenderFrameHost(source)));
+        WebContents::FromRenderFrameHost(source.GetRenderFrameHost())));
   }
 
   // Send the connect event to the receiver.  Give it the opener's port ID (the
@@ -792,11 +785,10 @@
 
   ChannelId channel_id = (*params)->receiver_port_id.GetChannelId();
   pending_lazy_context_channels_.emplace(channel_id, context_id);
-  int source_id = (*params)->source_process_id;
   task_queue->AddPendingTask(
-      context_id, base::BindOnce(&MessageService::PendingLazyContextOpenChannel,
-                                 weak_factory_.GetWeakPtr(),
-                                 base::Passed(params), source_id));
+      context_id,
+      base::BindOnce(&MessageService::PendingLazyContextOpenChannel,
+                     weak_factory_.GetWeakPtr(), std::move(*params)));
 
   for (const PendingMessage& message : pending_messages) {
     EnqueuePendingMessageForLazyBackgroundLoad(message.first, channel_id,
@@ -822,15 +814,11 @@
   pending_incognito_channels_.erase(pending_for_incognito);
 
   // Check whether the source got closed while in flight.
-  content::RenderFrameHost* source = nullptr;
-  if (params->source_process_id !=
-      content::ChildProcessHost::kInvalidUniqueID) {
-    DCHECK_NE(params->source_routing_id, MSG_ROUTING_NONE);
-    source = content::RenderFrameHost::FromID(params->source_process_id,
-                                              params->source_routing_id);
-    if (!source)
-      return;
-  }
+  const ChannelEndpoint& source = params->source;
+  // Re-lookup the source process since it may no longer be valid.
+  if (!source.IsValid())
+    return;
+
   if (!params->opener_port->IsValidPort())
     return;
 
@@ -839,10 +827,7 @@
     return;
   }
 
-  content::RenderProcessHost* source_process =
-      source ? source->GetProcess() : nullptr;
-  BrowserContext* context =
-      source_process ? source_process->GetBrowserContext() : context_;
+  BrowserContext* context = source.browser_context();
   DCHECK(ExtensionsBrowserClient::Get()->IsSameContext(context, context_));
 
   // Note: we use the source's profile here. If the source is an incognito
@@ -879,7 +864,6 @@
 
 void MessageService::PendingLazyContextOpenChannel(
     std::unique_ptr<OpenChannelParams> params,
-    int source_process_id,
     std::unique_ptr<LazyContextTaskQueue::ContextInfo> context_info) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
diff --git a/extensions/browser/api/messaging/message_service.h b/extensions/browser/api/messaging/message_service.h
index 5106df8..280b9b7 100644
--- a/extensions/browser/api/messaging/message_service.h
+++ b/extensions/browser/api/messaging/message_service.h
@@ -30,6 +30,7 @@
 }
 
 namespace extensions {
+class ChannelEndpoint;
 class Extension;
 class ExtensionHost;
 class MessagingDelegate;
@@ -81,8 +82,7 @@
   // and every listening context owned by that extension. |channel_name| is
   // an optional identifier for use by extension developers. |opener_port| is an
   // optional pre-opened port that should be attached to the opened channel.
-  void OpenChannelToExtension(int source_process_id,
-                              int source_routing_id,
+  void OpenChannelToExtension(const ChannelEndpoint& source,
                               const PortId& source_port_id,
                               const MessagingEndpoint& source_endpoint,
                               std::unique_ptr<MessagePort> opener_port,
@@ -93,8 +93,7 @@
   // Same as above, but opens a channel to the tab with the given ID.  Messages
   // are restricted to that tab, so if there are multiple tabs in that process,
   // only the targeted tab will receive messages.
-  void OpenChannelToTab(int source_process_id,
-                        int source_routing_id,
+  void OpenChannelToTab(const ChannelEndpoint& source,
                         const PortId& source_port_id,
                         int tab_id,
                         int frame_id,
@@ -206,7 +205,6 @@
   // use that argument.
   void PendingLazyContextOpenChannel(
       std::unique_ptr<OpenChannelParams> params,
-      int source_process_id,
       std::unique_ptr<LazyContextTaskQueue::ContextInfo> context_info);
   void PendingLazyContextClosePort(
       const PortId& port_id,
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
index 24039e6..4aad402 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/debug/alias.h"
 #include "base/debug/dump_without_crashing.h"
+#include "base/feature_list.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -19,7 +20,9 @@
 #include "extensions/browser/extension_navigation_ui_data.h"
 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
 #include "net/http/http_util.h"
+#include "services/network/public/cpp/features.h"
 #include "third_party/blink/public/platform/resource_request_blocked_reason.h"
+#include "url/origin.h"
 
 namespace extensions {
 
@@ -36,6 +39,7 @@
     network::mojom::URLLoaderClientPtr client)
     : factory_(factory),
       request_(request),
+      original_initiator_(request.request_initiator),
       is_download_(is_download),
       request_id_(request_id),
       network_service_request_id_(network_service_request_id),
@@ -79,11 +83,16 @@
   request_completed_ = false;
   // Derive a new WebRequestInfo value any time |Restart()| is called, because
   // the details in |request_| may have changed e.g. if we've been redirected.
+  // |request_initiator| can be modified on redirects, but we keep the original
+  // for |initiator| in the event. See also
+  // https://developer.chrome.com/extensions/webRequest#event-onBeforeRequest.
+  network::ResourceRequest request_for_info = request_;
+  request_for_info.request_initiator = original_initiator_;
   info_.emplace(
       request_id_, factory_->render_process_id_, request_.render_frame_id,
       factory_->navigation_ui_data_ ? factory_->navigation_ui_data_->DeepCopy()
                                     : nullptr,
-      routing_id_, factory_->resource_context_, request_, is_download_,
+      routing_id_, factory_->resource_context_, request_for_info, is_download_,
       !(options_ & network::mojom::kURLLoadOptionSynchronous));
 
   current_request_uses_header_client_ =
@@ -359,19 +368,37 @@
       "Location: %s\n"
       "Non-Authoritative-Reason: WebRequest API\n\n",
       kInternalRedirectStatusCode, redirect_url_.spec().c_str());
-  std::string http_origin;
-  if (request_.headers.GetHeader("Origin", &http_origin)) {
+
+  if (base::FeatureList::IsEnabled(network::features::kOutOfBlinkCors)) {
+    // Cross-origin requests need to modify the Origin header to 'null'. Since
+    // CorsURLLoader sets |request_initiator| to the Origin request header in
+    // NetworkService, we need to modify |request_initiator| here to craft the
+    // Origin header indirectly.
+    // Following checks implement the step 10 of "4.4. HTTP-redirect fetch",
+    // https://fetch.spec.whatwg.org/#http-redirect-fetch
+    if (request_.request_initiator &&
+        (!url::Origin::Create(redirect_url_)
+              .IsSameOriginWith(url::Origin::Create(request_.url)) &&
+         !request_.request_initiator->IsSameOriginWith(
+             url::Origin::Create(request_.url)))) {
+      // Reset the initiator to pretend tainted origin flag of the spec is set.
+      request_.request_initiator = url::Origin();
+    }
+  } else {
     // If this redirect is used in a cross-origin request, add CORS headers to
-    // make sure that the redirect gets through. Note that the destination URL
-    // is still subject to the usual CORS policy, i.e. the resource will only
-    // be available to web pages if the server serves the response with the
-    // required CORS response headers.
-    // Matches the behavior in url_request_redirect_job.cc.
-    headers += base::StringPrintf(
-        "\n"
-        "Access-Control-Allow-Origin: %s\n"
-        "Access-Control-Allow-Credentials: true",
-        http_origin.c_str());
+    // make sure that the redirect gets through the Blink CORS. Note that the
+    // destination URL is still subject to the usual CORS policy, i.e. the
+    // resource will only be available to web pages if the server serves the
+    // response with the required CORS response headers. Matches the behavior in
+    // url_request_redirect_job.cc.
+    std::string http_origin;
+    if (request_.headers.GetHeader("Origin", &http_origin)) {
+      headers += base::StringPrintf(
+          "\n"
+          "Access-Control-Allow-Origin: %s\n"
+          "Access-Control-Allow-Credentials: true",
+          http_origin.c_str());
+    }
   }
   head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
       net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.length()));
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
index d38dfe4..89477505 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
@@ -124,6 +124,7 @@
 
     WebRequestProxyingURLLoaderFactory* const factory_;
     network::ResourceRequest request_;
+    const base::Optional<url::Origin> original_initiator_;
     const bool is_download_;
     const uint64_t request_id_;
     const int32_t network_service_request_id_;
diff --git a/extensions/browser/extension_host.cc b/extensions/browser/extension_host.cc
index 0714c6335..8cc9503 100644
--- a/extensions/browser/extension_host.cc
+++ b/extensions/browser/extension_host.cc
@@ -336,24 +336,30 @@
   // events for other extensions have been acked.  Make sure that the event id
   // sent by the renderer is one that this ExtensionHost expects to receive.
   // This way if a renderer _is_ compromised, it can really only affect itself.
-  const auto it = unacked_messages_.find(event_id);
-  if (!is_background_page || it == unacked_messages_.end()) {
+  if (!is_background_page) {
     // Kill this renderer.
     DCHECK(render_process_host());
-    if (!is_background_page) {
-      LOG(ERROR) << "Killing renderer for extension " << extension_id()
-                 << " for sending an EventAck without a lazy background page.";
-    } else {
-      // We have received an unexpected event id from the renderer.  It might
-      // be compromised or it might have some other issue.
-      LOG(ERROR) << "Killing renderer for extension " << extension_id()
-                 << " for sending an EventAck message with a bad event id.";
-    }
+    LOG(ERROR) << "Killing renderer for extension " << extension_id()
+               << " for sending an EventAck without a lazy background page.";
     bad_message::ReceivedBadMessage(render_process_host(),
                                     bad_message::EH_BAD_EVENT_ID);
     return;
   }
 
+  const auto it = unacked_messages_.find(event_id);
+  if (it == unacked_messages_.end()) {
+    // Ideally, we'd be able to kill the renderer in the case of it sending an
+    // ack for an event that we haven't seen. However, https://crbug.com/939279
+    // demonstrates that there are cases in which this can happen in other
+    // situations. We should track those down and fix them, but for now
+    // log and gracefully exit.
+    // bad_message::ReceivedBadMessage(render_process_host(),
+    //                                 bad_message::EH_BAD_EVENT_ID);
+    LOG(ERROR) << "Received EventAck for extension " << extension_id()
+               << " for an unknown event.";
+    return;
+  }
+
   EventRouter* router = EventRouter::Get(browser_context_);
   if (router)
     router->OnEventAck(browser_context_, extension_id(), it->second);
diff --git a/extensions/browser/extension_message_filter.cc b/extensions/browser/extension_message_filter.cc
index 4e8ac3a..21d39287 100644
--- a/extensions/browser/extension_message_filter.cc
+++ b/extensions/browser/extension_message_filter.cc
@@ -10,6 +10,7 @@
 #include "components/crx_file/id_util.h"
 #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
 #include "content/public/browser/render_process_host.h"
+#include "extensions/browser/api/messaging/channel_endpoint.h"
 #include "extensions/browser/api/messaging/message_service.h"
 #include "extensions/browser/bad_message.h"
 #include "extensions/browser/blob_holder.h"
@@ -400,11 +401,12 @@
   // TODO(crbug.com/925918): Support messages from Service Worker.
   DCHECK(source_context.is_for_render_frame());
   if (browser_context_) {
+    ChannelEndpoint source_endpoint(browser_context_, render_process_id_,
+                                    source_context);
     MessageService::Get(browser_context_)
-        ->OpenChannelToExtension(
-            render_process_id_, source_context.frame->routing_id, port_id,
-            info.source_endpoint, nullptr /* opener_port */, info.target_id,
-            info.source_url, channel_name);
+        ->OpenChannelToExtension(source_endpoint, port_id, info.source_endpoint,
+                                 nullptr /* opener_port */, info.target_id,
+                                 info.source_url, channel_name);
   }
 }
 
@@ -431,15 +433,14 @@
     const std::string& channel_name,
     const PortId& port_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  // TODO(crbug.com/925918): Support messages from Service Worker.
-  DCHECK(source_context.is_for_render_frame());
   if (!browser_context_)
     return;
 
+  ChannelEndpoint source_endpoint(browser_context_, render_process_id_,
+                                  source_context);
   MessageService::Get(browser_context_)
-      ->OpenChannelToTab(render_process_id_, source_context.frame->routing_id,
-                         port_id, info.tab_id, info.frame_id, extension_id,
-                         channel_name);
+      ->OpenChannelToTab(source_endpoint, port_id, info.tab_id, info.frame_id,
+                         extension_id, channel_name);
 }
 
 void ExtensionMessageFilter::OnOpenMessagePort(const PortContext& source,
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index bdd7848..f787d076 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -936,6 +936,7 @@
 void Dispatcher::OnDeliverMessage(int worker_thread_id,
                                   const PortId& target_port_id,
                                   const Message& message) {
+  DCHECK_EQ(kMainThreadId, worker_thread_id);
   bindings_system_->GetMessagingService()->DeliverMessage(
       script_context_set_.get(), target_port_id, message,
       NULL);  // All render frames.
diff --git a/extensions/renderer/ipc_message_sender.cc b/extensions/renderer/ipc_message_sender.cc
index 81a11aeb..04f71f8 100644
--- a/extensions/renderer/ipc_message_sender.cc
+++ b/extensions/renderer/ipc_message_sender.cc
@@ -364,9 +364,16 @@
             PortContextForCurrentWorker(), info, channel_name, port_id));
         break;
       }
-      case MessageTarget::TAB:
-        NOTIMPLEMENTED() << "https://crbug.com/925918.";
+      case MessageTarget::TAB: {
+        DCHECK(extension);
+        ExtensionMsg_TabTargetConnectionInfo info;
+        info.tab_id = *target.tab_id;
+        info.frame_id = *target.frame_id;
+        dispatcher_->Send(new ExtensionHostMsg_OpenChannelToTab(
+            PortContextForCurrentWorker(), info, extension->id(), channel_name,
+            port_id));
         break;
+      }
       case MessageTarget::NATIVE_APP:
         NOTIMPLEMENTED() << "https://crbug.com/925918.";
         break;
diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc
index 7cba7369..ca80343 100644
--- a/extensions/renderer/messaging_bindings.cc
+++ b/extensions/renderer/messaging_bindings.cc
@@ -23,13 +23,17 @@
 #include "extensions/common/api/messaging/port_context.h"
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/extension_messages.h"
+#include "extensions/renderer/extension_bindings_system.h"
 #include "extensions/renderer/extension_frame_helper.h"
 #include "extensions/renderer/extension_port.h"
 #include "extensions/renderer/gc_callback.h"
+#include "extensions/renderer/ipc_message_sender.h"
+#include "extensions/renderer/message_target.h"
 #include "extensions/renderer/messaging_util.h"
 #include "extensions/renderer/script_context.h"
 #include "extensions/renderer/script_context_set.h"
 #include "extensions/renderer/v8_helpers.h"
+#include "extensions/renderer/worker_thread_dispatcher.h"
 #include "extensions/renderer/worker_thread_util.h"
 #include "gin/converter.h"
 #include "third_party/blink/public/web/web_user_gesture_indicator.h"
@@ -53,6 +57,11 @@
 base::LazyInstance<std::map<ScriptContext*, MessagingBindings*>>::
     DestructorAtExit g_messaging_map = LAZY_INSTANCE_INITIALIZER;
 
+IPCMessageSender* GetWorkerThreadIPCMessageSender() {
+  DCHECK(worker_thread_util::IsWorkerThread());
+  return WorkerThreadDispatcher::GetBindingsSystem()->GetIPCMessageSender();
+}
+
 }  // namespace
 
 MessagingBindings::MessagingBindings(ScriptContext* context)
@@ -261,11 +270,10 @@
 
 void MessagingBindings::OpenChannelToTab(
     const v8::FunctionCallbackInfo<v8::Value>& args) {
-  // TODO(crbug.com/925918): Support Service worker to tab messaging.
-  DCHECK(!worker_thread_util::IsWorkerThread());
-
   content::RenderFrame* render_frame = context()->GetRenderFrame();
-  if (!render_frame)
+  bool is_for_service_worker = false;
+  if (!render_frame &&
+      !(is_for_service_worker = worker_thread_util::IsWorkerThread()))
     return;
 
   DCHECK_NE(context()->context_type(), Feature::CONTENT_SCRIPT_CONTEXT);
@@ -295,14 +303,23 @@
   std::string extension_id = *v8::String::Utf8Value(isolate, args[2]);
   std::string channel_name = *v8::String::Utf8Value(isolate, args[3]);
 
-  ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
-  DCHECK(frame_helper);
+  if (!is_for_service_worker) {
+    ExtensionFrameHelper* frame_helper =
+        ExtensionFrameHelper::Get(render_frame);
+    DCHECK(frame_helper);
+  }
 
   {
     SCOPED_UMA_HISTOGRAM_TIMER("Extensions.Messaging.SetPortIdTime.Tab");
-    render_frame->Send(new ExtensionHostMsg_OpenChannelToTab(
-        PortContext::ForFrame(render_frame->GetRoutingID()), info, extension_id,
-        channel_name, port_id));
+    if (is_for_service_worker) {
+      GetWorkerThreadIPCMessageSender()->SendOpenMessageChannel(
+          context(), port_id, MessageTarget::ForTab(info.tab_id, info.frame_id),
+          channel_name, false /* include_tls_channel_id */);
+    } else {
+      render_frame->Send(new ExtensionHostMsg_OpenChannelToTab(
+          PortContext::ForFrame(render_frame->GetRoutingID()), info,
+          extension_id, channel_name, port_id));
+    }
   }
 
   args.GetReturnValue().Set(static_cast<int32_t>(js_id));
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index 89290eb..9b6ab9f 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -292,6 +292,7 @@
     sources += [ "command_buffer/service/shared_image_backing_factory_ahardwarebuffer_unittest.cc" ]
   } else if (is_mac) {
     libs += [ "IOSurface.framework" ]
+    sources += [ "command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc" ]
   } else if (is_win) {
     deps += [
       "//ui/platform_window",
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index 129446b6..832dad31 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -29,7 +29,6 @@
 #include "base/trace_event/process_memory_dump.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/paint/decode_stashing_image_provider.h"
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/paint_cache.h"
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index 143cfdc..8c6b378 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -12,7 +12,6 @@
 #include <vector>
 
 #include "base/logging.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/paint/decode_stashing_image_provider.h"
 #include "cc/paint/display_item_list.h"  // nogncheck
 #include "cc/paint/paint_op_buffer_serializer.h"
diff --git a/gpu/command_buffer/client/raster_implementation_gles_unittest.cc b/gpu/command_buffer/client/raster_implementation_gles_unittest.cc
index 324fc46..6e0ac73 100644
--- a/gpu/command_buffer/client/raster_implementation_gles_unittest.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles_unittest.cc
@@ -13,7 +13,6 @@
 #include <vector>
 
 #include "base/containers/flat_map.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/image_provider.h"
 #include "components/viz/common/resources/resource_format_utils.h"
diff --git a/gpu/command_buffer/service/BUILD.gn b/gpu/command_buffer/service/BUILD.gn
index c33ac92..6955858 100644
--- a/gpu/command_buffer/service/BUILD.gn
+++ b/gpu/command_buffer/service/BUILD.gn
@@ -332,8 +332,14 @@
   }
 
   if (is_mac) {
+    sources += [
+      "shared_image_backing_factory_iosurface.h",
+      "shared_image_backing_factory_iosurface.mm",
+    ]
+
     # Required by gles2_cmd_decoder.cc on Mac.
     libs = [
+      "Cocoa.framework",
       "IOSurface.framework",
       "OpenGL.framework",
     ]
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
index f11190a6..e692207b 100644
--- a/gpu/command_buffer/service/feature_info.cc
+++ b/gpu/command_buffer/service/feature_info.cc
@@ -262,15 +262,6 @@
   disallowed_features_ = disallowed_features;
   context_type_ = context_type;
   is_passthrough_cmd_decoder_ = is_passthrough_cmd_decoder;
-  switch (context_type) {
-    case CONTEXT_TYPE_WEBGL1:
-    case CONTEXT_TYPE_OPENGLES2:
-      break;
-    default:
-      // https://crbug.com/826509
-      workarounds_.use_client_side_arrays_for_stream_buffers = false;
-      break;
-  }
   InitializeFeatures();
   initialized_ = true;
 }
diff --git a/gpu/command_buffer/service/feature_info_unittest.cc b/gpu/command_buffer/service/feature_info_unittest.cc
index f3409215e..ca61ad0c 100644
--- a/gpu/command_buffer/service/feature_info_unittest.cc
+++ b/gpu/command_buffer/service/feature_info_unittest.cc
@@ -1440,23 +1440,6 @@
   EXPECT_TRUE(info_->validators()->index_type.IsValid(GL_UNSIGNED_INT));
 }
 
-TEST_P(FeatureInfoTest, InitializeVAOsWithClientSideArrays) {
-  gpu::GpuDriverBugWorkarounds workarounds;
-  workarounds.use_client_side_arrays_for_stream_buffers = true;
-  SetupInitExpectationsWithWorkarounds("GL_OES_vertex_array_object",
-                                       workarounds);
-  if (GetContextType() == CONTEXT_TYPE_OPENGLES2) {
-    EXPECT_TRUE(info_->workarounds().use_client_side_arrays_for_stream_buffers);
-    EXPECT_FALSE(info_->feature_flags().native_vertex_array_object);
-  } else {  // CONTEXT_TYPE_OPENGLES3
-    // We only turn on use_client_side_arrays_for_stream_buffers on ES2
-    // contexts. See https://crbug.com/826509.
-    EXPECT_FALSE(
-        info_->workarounds().use_client_side_arrays_for_stream_buffers);
-    EXPECT_TRUE(info_->feature_flags().native_vertex_array_object);
-  }
-}
-
 TEST_P(FeatureInfoTest, InitializeEXT_blend_minmax) {
   SetupInitExpectations("GL_EXT_blend_minmax");
   EXPECT_TRUE(gfx::HasExtension(info_->extensions(), "GL_EXT_blend_minmax"));
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index 164b75a..21f408fb 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -23,7 +23,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "cc/paint/paint_cache.h"
 #include "cc/paint/paint_op_buffer.h"
 #include "cc/paint/transfer_cache_entry.h"
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
index 310413e3..067fbaf 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
@@ -38,15 +38,16 @@
 
 namespace {
 
+using UnpackStateAttribs =
+    SharedImageBackingFactoryGLTexture::UnpackStateAttribs;
+
 class ScopedResetAndRestoreUnpackState {
  public:
   ScopedResetAndRestoreUnpackState(gl::GLApi* api,
-                                   bool es3_capable,
-                                   bool desktop_gl,
-                                   bool supports_unpack_subimage,
+                                   const UnpackStateAttribs& attribs,
                                    bool uploading_data)
       : api_(api) {
-    if (es3_capable) {
+    if (attribs.es3_capable) {
       // Need to unbind any GL_PIXEL_UNPACK_BUFFER for the nullptr in
       // glTexImage2D to mean "no pixels" (as opposed to offset 0 in the
       // buffer).
@@ -59,7 +60,7 @@
       if (unpack_alignment_ != 4)
         api_->glPixelStoreiFn(GL_UNPACK_ALIGNMENT, 4);
 
-      if (es3_capable || supports_unpack_subimage) {
+      if (attribs.es3_capable || attribs.supports_unpack_subimage) {
         api_->glGetIntegervFn(GL_UNPACK_ROW_LENGTH, &unpack_row_length_);
         if (unpack_row_length_)
           api_->glPixelStoreiFn(GL_UNPACK_ROW_LENGTH, 0);
@@ -71,7 +72,7 @@
           api_->glPixelStoreiFn(GL_UNPACK_SKIP_PIXELS, 0);
       }
 
-      if (es3_capable) {
+      if (attribs.es3_capable) {
         api_->glGetIntegervFn(GL_UNPACK_SKIP_IMAGES, &unpack_skip_images_);
         if (unpack_skip_images_)
           api_->glPixelStoreiFn(GL_UNPACK_SKIP_IMAGES, 0);
@@ -80,7 +81,7 @@
           api_->glPixelStoreiFn(GL_UNPACK_IMAGE_HEIGHT, 0);
       }
 
-      if (desktop_gl) {
+      if (attribs.desktop_gl) {
         api->glGetBooleanvFn(GL_UNPACK_SWAP_BYTES, &unpack_swap_bytes_);
         if (unpack_swap_bytes_)
           api->glPixelStoreiFn(GL_UNPACK_SWAP_BYTES, GL_FALSE);
@@ -231,11 +232,30 @@
   scoped_refptr<gles2::TexturePassthrough> texture_passthrough_;
 };
 
+class SharedImageBackingWithReadAccess : public SharedImageBacking {
+ public:
+  SharedImageBackingWithReadAccess(const Mailbox& mailbox,
+                                   viz::ResourceFormat format,
+                                   const gfx::Size& size,
+                                   const gfx::ColorSpace& color_space,
+                                   uint32_t usage,
+                                   size_t estimated_size)
+      : SharedImageBacking(mailbox,
+                           format,
+                           size,
+                           color_space,
+                           usage,
+                           estimated_size) {}
+  ~SharedImageBackingWithReadAccess() override = default;
+
+  virtual void BeginReadAccess() = 0;
+};
+
 class SharedImageRepresentationSkiaImpl : public SharedImageRepresentationSkia {
  public:
   SharedImageRepresentationSkiaImpl(
       SharedImageManager* manager,
-      SharedImageBacking* backing,
+      SharedImageBackingWithReadAccess* backing,
       sk_sp<SkPromiseImageTexture> cached_promise_texture,
       MemoryTypeTracker* tracker,
       GLenum target,
@@ -286,6 +306,8 @@
 
   sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
     CheckContext();
+    static_cast<SharedImageBackingWithReadAccess*>(backing())
+        ->BeginReadAccess();
     return promise_texture_;
   }
 
@@ -312,21 +334,23 @@
 
 // Implementation of SharedImageBacking that creates a GL Texture and stores it
 // as a gles2::Texture. Can be used with the legacy mailbox implementation.
-class SharedImageBackingGLTexture : public SharedImageBacking {
+class SharedImageBackingGLTexture : public SharedImageBackingWithReadAccess {
  public:
   SharedImageBackingGLTexture(const Mailbox& mailbox,
                               viz::ResourceFormat format,
                               const gfx::Size& size,
                               const gfx::ColorSpace& color_space,
                               uint32_t usage,
-                              gles2::Texture* texture)
-      : SharedImageBacking(mailbox,
-                           format,
-                           size,
-                           color_space,
-                           usage,
-                           texture->estimated_size()),
-        texture_(texture) {
+                              gles2::Texture* texture,
+                              const UnpackStateAttribs& attribs)
+      : SharedImageBackingWithReadAccess(mailbox,
+                                         format,
+                                         size,
+                                         color_space,
+                                         usage,
+                                         texture->estimated_size()),
+        texture_(texture),
+        attribs_(attribs) {
     DCHECK(texture_);
   }
 
@@ -401,6 +425,29 @@
     texture_->DumpLevelMemory(pmd, client_tracing_id, dump_name);
   }
 
+  void BeginReadAccess() override {
+    GLenum target = texture_->target();
+    gles2::Texture::ImageState old_state = gles2::Texture::UNBOUND;
+    gl::GLImage* image = texture_->GetLevelImage(target, 0, &old_state);
+    if (image && old_state == gpu::gles2::Texture::UNBOUND) {
+      gl::GLApi* api = gl::g_current_gl_context;
+      ScopedRestoreTexture scoped_restore(api, target);
+      api->glBindTextureFn(target, texture_->service_id());
+      gles2::Texture::ImageState new_state = gles2::Texture::UNBOUND;
+      if (image->ShouldBindOrCopy() == gl::GLImage::BIND) {
+        if (image->BindTexImage(target))
+          new_state = gles2::Texture::BOUND;
+      } else {
+        ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs_,
+                                                             /*upload=*/true);
+        if (image->CopyTexImage(target))
+          new_state = gles2::Texture::COPIED;
+      }
+      if (old_state != new_state)
+        texture_->SetLevelImage(target, 0, image, new_state);
+    }
+  }
+
  protected:
   std::unique_ptr<SharedImageRepresentationGLTexture> ProduceGLTexture(
       SharedImageManager* manager,
@@ -473,12 +520,14 @@
   gles2::Texture* texture_ = nullptr;
   gles2::Texture* rgb_emulation_texture_ = nullptr;
   sk_sp<SkPromiseImageTexture> cached_promise_texture_;
+  const UnpackStateAttribs attribs_;
 };
 
 // Implementation of SharedImageBacking that creates a GL Texture and stores it
 // as a gles2::TexturePassthrough. Can be used with the legacy mailbox
 // implementation.
-class SharedImageBackingPassthroughGLTexture : public SharedImageBacking {
+class SharedImageBackingPassthroughGLTexture
+    : public SharedImageBackingWithReadAccess {
  public:
   SharedImageBackingPassthroughGLTexture(
       const Mailbox& mailbox,
@@ -488,12 +537,12 @@
       uint32_t usage,
       scoped_refptr<gles2::TexturePassthrough> passthrough_texture,
       bool is_cleared)
-      : SharedImageBacking(mailbox,
-                           format,
-                           size,
-                           color_space,
-                           usage,
-                           passthrough_texture->estimated_size()),
+      : SharedImageBackingWithReadAccess(mailbox,
+                                         format,
+                                         size,
+                                         color_space,
+                                         usage,
+                                         passthrough_texture->estimated_size()),
         texture_passthrough_(std::move(passthrough_texture)),
         is_cleared_(is_cleared) {
     DCHECK(texture_passthrough_);
@@ -555,6 +604,8 @@
       gl_image->OnMemoryDump(pmd, client_tracing_id, dump_name);
   }
 
+  void BeginReadAccess() override {}
+
  protected:
   std::unique_ptr<SharedImageRepresentationGLTexturePassthrough>
   ProduceGLTexturePassthrough(SharedImageManager* manager,
@@ -610,12 +661,12 @@
   gpu_memory_buffer_formats_ =
       feature_info->feature_flags().gpu_memory_buffer_formats;
   texture_usage_angle_ = feature_info->feature_flags().angle_texture_usage;
-  es3_capable_ = feature_info->IsES3Capable();
-  desktop_gl_ = !feature_info->gl_version_info().is_es;
+  attribs.es3_capable = feature_info->IsES3Capable();
+  attribs.desktop_gl = !feature_info->gl_version_info().is_es;
   // Can't use the value from feature_info, as we unconditionally enable this
   // extension, and assume it can't be used if PBOs are not used (which isn't
   // true for Skia used direclty against GL).
-  supports_unpack_subimage_ =
+  attribs.supports_unpack_subimage =
       gl::g_current_gl_driver->ext.b_GL_EXT_unpack_subimage;
   bool enable_texture_storage =
       feature_info->feature_flags().ext_texture_storage;
@@ -811,16 +862,14 @@
                              size.width(), size.height());
     needs_subimage_upload = !pixel_data.empty();
   } else if (format_info.is_compressed) {
-    ScopedResetAndRestoreUnpackState scoped_unpack_state(
-        api, es3_capable_, desktop_gl_, supports_unpack_subimage_,
-        !pixel_data.empty());
+    ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs,
+                                                         !pixel_data.empty());
     api->glCompressedTexImage2DFn(target, 0, format_info.image_internal_format,
                                   size.width(), size.height(), 0,
                                   pixel_data.size(), pixel_data.data());
   } else {
-    ScopedResetAndRestoreUnpackState scoped_unpack_state(
-        api, es3_capable_, desktop_gl_, supports_unpack_subimage_,
-        !pixel_data.empty());
+    ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs,
+                                                         !pixel_data.empty());
     api->glTexImage2DFn(target, 0, format_info.image_internal_format,
                         size.width(), size.height(), 0,
                         format_info.adjusted_format, format_info.gl_type,
@@ -830,19 +879,19 @@
   // If we are using a buffer or TexStorage API but have data to upload, do so
   // now via TexSubImage2D.
   if (needs_subimage_upload) {
-    ScopedResetAndRestoreUnpackState scoped_unpack_state(
-        api, es3_capable_, desktop_gl_, supports_unpack_subimage_,
-        !pixel_data.empty());
+    ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs,
+                                                         !pixel_data.empty());
     api->glTexSubImage2DFn(target, 0, 0, 0, size.width(), size.height(),
                            format_info.adjusted_format, format_info.gl_type,
                            pixel_data.data());
   }
 
-  return MakeBacking(
-      use_passthrough_, mailbox, target, service_id, image,
-      gles2::Texture::BOUND, level_info_internal_format, format_info.gl_format,
-      format_info.gl_type, format_info.swizzle,
-      pixel_data.empty() ? is_cleared : true, format, size, color_space, usage);
+  return MakeBacking(use_passthrough_, mailbox, target, service_id, image,
+                     gles2::Texture::BOUND, level_info_internal_format,
+                     format_info.gl_format, format_info.gl_type,
+                     format_info.swizzle,
+                     pixel_data.empty() ? is_cleared : true, format, size,
+                     color_space, usage, attribs);
 }
 
 std::unique_ptr<SharedImageBacking>
@@ -928,7 +977,7 @@
 
   return MakeBacking(use_passthrough_, mailbox, target, service_id, image,
                      image_state, internal_format, gl_format, gl_type, nullptr,
-                     true, format, size, color_space, usage);
+                     true, format, size, color_space, usage, attribs);
 }
 
 std::unique_ptr<SharedImageBacking>
@@ -944,7 +993,7 @@
                      gles2::Texture::UNBOUND, viz::GLInternalFormat(format),
                      viz::GLDataFormat(format), viz::GLDataType(format),
                      nullptr, is_cleared, format, size, gfx::ColorSpace(),
-                     usage);
+                     usage, UnpackStateAttribs());
 }
 
 scoped_refptr<gl::GLImage> SharedImageBackingFactoryGLTexture::MakeGLImage(
@@ -988,7 +1037,8 @@
     viz::ResourceFormat format,
     const gfx::Size& size,
     const gfx::ColorSpace& color_space,
-    uint32_t usage) {
+    uint32_t usage,
+    const UnpackStateAttribs& attribs) {
   if (passthrough) {
     scoped_refptr<gles2::TexturePassthrough> passthrough_texture =
         base::MakeRefCounted<gles2::TexturePassthrough>(service_id, target);
@@ -1023,7 +1073,7 @@
     texture->SetImmutable(true);
 
     return std::make_unique<SharedImageBackingGLTexture>(
-        mailbox, format, size, color_space, usage, texture);
+        mailbox, format, size, color_space, usage, texture, attribs);
   }
 }
 
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h
index 31818fa..43c609f2 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.h
@@ -35,6 +35,12 @@
 class GPU_GLES2_EXPORT SharedImageBackingFactoryGLTexture
     : public SharedImageBackingFactory {
  public:
+  struct UnpackStateAttribs {
+    bool es3_capable = false;
+    bool desktop_gl = false;
+    bool supports_unpack_subimage = false;
+  };
+
   SharedImageBackingFactoryGLTexture(const GpuPreferences& gpu_preferences,
                                      const GpuDriverBugWorkarounds& workarounds,
                                      const GpuFeatureInfo& gpu_feature_info,
@@ -95,7 +101,8 @@
       viz::ResourceFormat format,
       const gfx::Size& size,
       const gfx::ColorSpace& color_space,
-      uint32_t usage);
+      uint32_t usage,
+      const UnpackStateAttribs& attribs);
 
   struct FormatInfo {
     FormatInfo();
@@ -146,9 +153,7 @@
   GpuMemoryBufferFormatSet gpu_memory_buffer_formats_;
   int32_t max_texture_size_ = 0;
   bool texture_usage_angle_ = false;
-  bool es3_capable_ = false;
-  bool desktop_gl_ = false;
-  bool supports_unpack_subimage_ = false;
+  UnpackStateAttribs attribs;
 };
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.h b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.h
new file mode 100644
index 0000000..003f059
--- /dev/null
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.h
@@ -0,0 +1,68 @@
+// 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 GPU_COMMAND_BUFFER_SERVICE_SHARED_IMAGE_BACKING_FACTORY_IOSURFACE_H_
+#define GPU_COMMAND_BUFFER_SERVICE_SHARED_IMAGE_BACKING_FACTORY_IOSURFACE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "components/viz/common/resources/resource_format.h"
+#include "gpu/command_buffer/service/shared_image_backing_factory.h"
+#include "gpu/gpu_gles2_export.h"
+#include "ui/gl/gl_bindings.h"
+
+namespace gfx {
+class Size;
+class ColorSpace;
+}  // namespace gfx
+
+namespace gpu {
+class GpuDriverBugWorkarounds;
+struct GpuFeatureInfo;
+struct Mailbox;
+class SharedImageBacking;
+
+// Implementation of SharedImageBackingFactory that produce IOSurface backed
+// SharedImages. This is meant to be used on macOS only.
+class GPU_GLES2_EXPORT SharedImageBackingFactoryIOSurface
+    : public SharedImageBackingFactory {
+ public:
+  SharedImageBackingFactoryIOSurface(const GpuDriverBugWorkarounds& workarounds,
+                                     const GpuFeatureInfo& gpu_feature_info);
+  ~SharedImageBackingFactoryIOSurface() override;
+
+  // SharedImageBackingFactory implementation.
+  std::unique_ptr<SharedImageBacking> CreateSharedImage(
+      const Mailbox& mailbox,
+      viz::ResourceFormat format,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      uint32_t usage) override;
+  std::unique_ptr<SharedImageBacking> CreateSharedImage(
+      const Mailbox& mailbox,
+      viz::ResourceFormat format,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      uint32_t usage,
+      base::span<const uint8_t> pixel_data) override;
+  std::unique_ptr<SharedImageBacking> CreateSharedImage(
+      const Mailbox& mailbox,
+      int client_id,
+      gfx::GpuMemoryBufferHandle handle,
+      gfx::BufferFormat format,
+      SurfaceHandle surface_handle,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      uint32_t usage) override;
+
+ private:
+  bool format_supported_by_gl_[viz::RESOURCE_FORMAT_MAX + 1];
+
+  DISALLOW_COPY_AND_ASSIGN(SharedImageBackingFactoryIOSurface);
+};
+
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_SERVICE_SHARED_IMAGE_BACKING_FACTORY_IOSURFACE_H_
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
new file mode 100644
index 0000000..c02c4ab
--- /dev/null
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface.mm
@@ -0,0 +1,411 @@
+// 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 "gpu/command_buffer/service/shared_image_backing_factory_iosurface.h"
+
+#include "base/mac/scoped_cftyperef.h"
+#include "components/viz/common/resources/resource_format_utils.h"
+#include "components/viz/common/resources/resource_sizes.h"
+#include "gpu/command_buffer/service/mailbox_manager.h"
+#include "gpu/command_buffer/service/shared_image_backing.h"
+#include "gpu/command_buffer/service/shared_image_representation.h"
+#include "gpu/command_buffer/service/skia_utils.h"
+#include "gpu/command_buffer/service/texture_manager.h"
+#include "third_party/skia/include/core/SkPromiseImageTexture.h"
+#include "ui/gfx/mac/io_surface.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/gl_image_io_surface.h"
+
+namespace gpu {
+
+namespace {
+
+struct GLFormatInfo {
+  bool supported = false;
+
+  // GL internal_format/format/type triplet.
+  GLuint internal_format = 0;
+  GLenum format = 0;
+  GLenum type = 0;
+};
+
+// Get GL format triplets and modify them to match the logic in
+// gl_image_iosurface.mm
+GLFormatInfo GetGLFormatInfo(viz::ResourceFormat format) {
+  GLFormatInfo info = {
+      true,
+      viz::GLInternalFormat(format),
+      viz::GLDataFormat(format),
+      viz::GLDataType(format),
+  };
+
+  if (info.internal_format == GL_ZERO || info.format == GL_ZERO ||
+      info.type == GL_ZERO) {
+    return {false, GL_ZERO, GL_ZERO, GL_ZERO};
+  }
+
+  switch (format) {
+    case viz::BGRA_8888:
+      info.format = GL_RGBA;
+      info.internal_format = GL_RGBA;
+      break;
+
+      // Technically we should use GL_RGB but CGLTexImageIOSurface2D() (and
+      // OpenGL ES 3.0, for the case) support only GL_RGBA (the hardware ignores
+      // the alpha channel anyway), see https://crbug.com/797347.
+    case viz::BGRX_1010102:
+      info.format = GL_RGBA;
+      info.internal_format = GL_RGBA;
+      break;
+
+    default:
+      break;
+  }
+
+  return info;
+}
+
+void FlushIOSurfaceGLOperations() {
+  // The CGLTexImageIOSurface2D documentation says that we need to call
+  // glFlush, otherwise there is the risk of a race between different
+  // graphics contexts.
+  gl::GLApi* api = gl::g_current_gl_context;
+  api->glFlushFn();
+}
+
+}  // anonymous namespace
+
+// Representation of a SharedImageBackingIOSurface as a GL Texture.
+class SharedImageRepresentationGLTextureIOSurface
+    : public SharedImageRepresentationGLTexture {
+ public:
+  SharedImageRepresentationGLTextureIOSurface(SharedImageManager* manager,
+                                              SharedImageBacking* backing,
+                                              MemoryTypeTracker* tracker,
+                                              gles2::Texture* texture)
+      : SharedImageRepresentationGLTexture(manager, backing, tracker),
+        texture_(texture) {
+    DCHECK(texture_);
+  }
+
+  ~SharedImageRepresentationGLTextureIOSurface() override {
+    texture_->RemoveLightweightRef(has_context());
+  }
+
+  gles2::Texture* GetTexture() override { return texture_; }
+
+  bool BeginAccess(GLenum mode) override { return true; }
+
+  void EndAccess() override { FlushIOSurfaceGLOperations(); }
+
+ private:
+  gles2::Texture* texture_;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedImageRepresentationGLTextureIOSurface);
+};
+
+// Representation of a SharedImageBackingIOSurface as a Skia Texture.
+class SharedImageRepresentationSkiaIOSurface
+    : public SharedImageRepresentationSkia {
+ public:
+  SharedImageRepresentationSkiaIOSurface(
+      SharedImageManager* manager,
+      SharedImageBacking* backing,
+      sk_sp<SkPromiseImageTexture> promise_texture,
+      MemoryTypeTracker* tracker,
+      gles2::Texture* texture)
+      : SharedImageRepresentationSkia(manager, backing, tracker),
+        promise_texture_(std::move(promise_texture)),
+        texture_(texture) {
+    DCHECK(texture_);
+    DCHECK(promise_texture_);
+  }
+
+  ~SharedImageRepresentationSkiaIOSurface() override {
+    texture_->RemoveLightweightRef(has_context());
+  }
+
+  sk_sp<SkSurface> BeginWriteAccess(
+      GrContext* gr_context,
+      int final_msaa_count,
+      const SkSurfaceProps& surface_props) override {
+    SkColorType sk_color_type = viz::ResourceFormatToClosestSkColorType(
+        /*gpu_compositing=*/true, format());
+
+    return SkSurface::MakeFromBackendTextureAsRenderTarget(
+        gr_context, promise_texture_->backendTexture(),
+        kTopLeft_GrSurfaceOrigin, final_msaa_count, sk_color_type,
+        backing()->color_space().ToSkColorSpace(), &surface_props);
+  }
+
+  void EndWriteAccess(sk_sp<SkSurface> surface) override {
+    FlushIOSurfaceGLOperations();
+
+    if (texture_->IsLevelCleared(texture_->target(), 0)) {
+      backing()->SetCleared();
+    }
+  }
+
+  sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
+    return promise_texture_;
+  }
+
+  void EndReadAccess() override { FlushIOSurfaceGLOperations(); }
+
+ private:
+  sk_sp<SkPromiseImageTexture> promise_texture_;
+  gles2::Texture* texture_;
+};
+
+// Implementation of SharedImageBacking by wrapping IOSurfaces
+class SharedImageBackingIOSurface : public SharedImageBacking {
+ public:
+  SharedImageBackingIOSurface(const Mailbox& mailbox,
+                              viz::ResourceFormat format,
+                              const gfx::Size& size,
+                              const gfx::ColorSpace& color_space,
+                              uint32_t usage,
+                              base::ScopedCFTypeRef<IOSurfaceRef> io_surface,
+                              size_t estimated_size)
+      : SharedImageBacking(mailbox,
+                           format,
+                           size,
+                           color_space,
+                           usage,
+                           estimated_size),
+        io_surface_(std::move(io_surface)) {
+    DCHECK(io_surface_);
+  }
+  ~SharedImageBackingIOSurface() final { DCHECK(!io_surface_); }
+
+  bool IsCleared() const final { return is_cleared_; }
+  void SetCleared() final {
+    if (legacy_texture_) {
+      legacy_texture_->SetLevelCleared(legacy_texture_->target(), 0, true);
+    }
+
+    is_cleared_ = true;
+  }
+
+  void Update() final {}
+
+  bool ProduceLegacyMailbox(MailboxManager* mailbox_manager) final {
+    DCHECK(io_surface_);
+
+    legacy_texture_ = GenGLTexture();
+    if (!legacy_texture_) {
+      return false;
+    }
+
+    mailbox_manager->ProduceTexture(mailbox(), legacy_texture_);
+    return true;
+  }
+  void Destroy() final {
+    DCHECK(io_surface_);
+
+    if (legacy_texture_) {
+      legacy_texture_->RemoveLightweightRef(have_context());
+      legacy_texture_ = nullptr;
+    }
+
+    io_surface_.reset();
+  }
+
+ protected:
+  std::unique_ptr<SharedImageRepresentationGLTexture> ProduceGLTexture(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker) final {
+    gles2::Texture* texture = GenGLTexture();
+    if (!texture) {
+      return nullptr;
+    }
+
+    return std::make_unique<SharedImageRepresentationGLTextureIOSurface>(
+        manager, this, tracker, texture);
+  }
+
+  std::unique_ptr<SharedImageRepresentationSkia> ProduceSkia(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker) override {
+    gles2::Texture* texture = GenGLTexture();
+    if (!texture) {
+      return nullptr;
+    }
+
+    GrBackendTexture backend_texture;
+    GetGrBackendTexture(gl::GLContext::GetCurrent()->GetVersionInfo(),
+                        texture->target(), size(), texture->service_id(),
+                        format(), &backend_texture);
+    sk_sp<SkPromiseImageTexture> promise_texture =
+        SkPromiseImageTexture::Make(backend_texture);
+    return std::make_unique<SharedImageRepresentationSkiaIOSurface>(
+        manager, this, promise_texture, tracker, texture);
+  }
+
+ private:
+  gles2::Texture* GenGLTexture() {
+    GLFormatInfo gl_info = GetGLFormatInfo(format());
+    DCHECK(gl_info.supported);
+
+    // Wrap the IOSurface in a GLImageIOSurface
+    scoped_refptr<gl::GLImageIOSurface> image(
+        gl::GLImageIOSurface::Create(size(), gl_info.internal_format));
+    if (!image->Initialize(io_surface_, gfx::GenericSharedMemoryId(),
+                           viz::BufferFormat(format()))) {
+      LOG(ERROR) << "Failed to create GLImageIOSurface";
+      return nullptr;
+    }
+
+    gl::GLApi* api = gl::g_current_gl_context;
+
+    // Save the currently bound rectangle texture to reset it once we are done.
+    GLint old_texture_binding = 0;
+    api->glGetIntegervFn(GL_TEXTURE_BINDING_RECTANGLE, &old_texture_binding);
+
+    // Create a gles2 rectangle texture to bind to the IOSurface.
+    GLuint service_id = 0;
+    api->glGenTexturesFn(1, &service_id);
+    api->glBindTextureFn(GL_TEXTURE_RECTANGLE, service_id);
+    api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER,
+                           GL_LINEAR);
+    api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER,
+                           GL_LINEAR);
+    api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S,
+                           GL_CLAMP_TO_EDGE);
+    api->glTexParameteriFn(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T,
+                           GL_CLAMP_TO_EDGE);
+
+    // Bind the GLImageIOSurface to our texture
+    if (!image->BindTexImage(GL_TEXTURE_RECTANGLE)) {
+      LOG(ERROR) << "Failed to bind GLImageIOSurface";
+      api->glBindTextureFn(GL_TEXTURE_RECTANGLE, old_texture_binding);
+      api->glDeleteTexturesFn(1, &service_id);
+      return nullptr;
+    }
+
+    // If the backing is already cleared, no need to clear it again.
+    gfx::Rect cleared_rect;
+    if (is_cleared_) {
+      cleared_rect = gfx::Rect(size());
+    }
+
+    // Manually create a gles2::Texture wrapping our driver texture.
+    gles2::Texture* texture = new gles2::Texture(service_id);
+    texture->SetLightweightRef();
+    texture->SetTarget(GL_TEXTURE_RECTANGLE, 1);
+    texture->sampler_state_.min_filter = GL_LINEAR;
+    texture->sampler_state_.mag_filter = GL_LINEAR;
+    texture->sampler_state_.wrap_t = GL_CLAMP_TO_EDGE;
+    texture->sampler_state_.wrap_s = GL_CLAMP_TO_EDGE;
+    texture->SetLevelInfo(GL_TEXTURE_RECTANGLE, 0, gl_info.internal_format,
+                          size().width(), size().height(), 1, 0, gl_info.format,
+                          gl_info.type, cleared_rect);
+    texture->SetLevelImage(GL_TEXTURE_RECTANGLE, 0, image.get(),
+                           gles2::Texture::BOUND);
+    texture->SetImmutable(true);
+
+    DCHECK_EQ(image->GetInternalFormat(), gl_info.format);
+
+    api->glBindTextureFn(GL_TEXTURE_RECTANGLE, old_texture_binding);
+    return texture;
+  }
+
+  base::ScopedCFTypeRef<IOSurfaceRef> io_surface_;
+  bool is_cleared_ = false;
+
+  // A texture for the associated legacy mailbox.
+  gles2::Texture* legacy_texture_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedImageBackingIOSurface);
+};
+
+// Implementation of SharedImageBackingFactoryIOSurface that creates
+// SharedImageBackings wrapping IOSurfaces.
+SharedImageBackingFactoryIOSurface::SharedImageBackingFactoryIOSurface(
+    const GpuDriverBugWorkarounds& workarounds,
+    const GpuFeatureInfo& gpu_feature_info) {
+  scoped_refptr<gles2::FeatureInfo> feature_info =
+      new gles2::FeatureInfo(workarounds, gpu_feature_info);
+  feature_info->Initialize(ContextType::CONTEXT_TYPE_OPENGLES2, false,
+                           gles2::DisallowedFeatures());
+  const gles2::Validators* validators = feature_info->validators();
+
+  // Precompute for each format if we can use it with GL.
+  for (int i = 0; i <= viz::RESOURCE_FORMAT_MAX; ++i) {
+    viz::ResourceFormat format = static_cast<viz::ResourceFormat>(i);
+    GLFormatInfo gl_info = GetGLFormatInfo(format);
+
+    format_supported_by_gl_[i] =
+        gl_info.supported &&
+        validators->texture_internal_format.IsValid(gl_info.internal_format) &&
+        validators->texture_format.IsValid(gl_info.format) &&
+        validators->pixel_type.IsValid(gl_info.type);
+  }
+}
+
+SharedImageBackingFactoryIOSurface::~SharedImageBackingFactoryIOSurface() =
+    default;
+
+std::unique_ptr<SharedImageBacking>
+SharedImageBackingFactoryIOSurface::CreateSharedImage(
+    const Mailbox& mailbox,
+    viz::ResourceFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    uint32_t usage) {
+  // Check the format is supported and for simplicity always require it to be
+  // supported for GL.
+  if (!format_supported_by_gl_[format]) {
+    LOG(ERROR) << "viz::ResourceFormat " << format
+               << " not supported by IOSurfaces";
+    return nullptr;
+  }
+
+  // Calculate SharedImage size in bytes.
+  size_t estimated_size;
+  if (!viz::ResourceSizes::MaybeSizeInBytes(size, format, &estimated_size)) {
+    LOG(ERROR) << "Failed to calculate SharedImage size";
+    return nullptr;
+  }
+
+  base::ScopedCFTypeRef<IOSurfaceRef> io_surface(
+      gfx::CreateIOSurface(size, viz::BufferFormat(format), false));
+  if (!io_surface) {
+    LOG(ERROR) << "Failed to allocate IOSurface.";
+    return nullptr;
+  }
+
+  gfx::IOSurfaceSetColorSpace(io_surface, color_space);
+
+  return std::make_unique<SharedImageBackingIOSurface>(
+      mailbox, format, size, color_space, usage, std::move(io_surface),
+      estimated_size);
+}
+
+std::unique_ptr<SharedImageBacking>
+SharedImageBackingFactoryIOSurface::CreateSharedImage(
+    const Mailbox& mailbox,
+    viz::ResourceFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    uint32_t usage,
+    base::span<const uint8_t> pixel_data) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+std::unique_ptr<SharedImageBacking>
+SharedImageBackingFactoryIOSurface::CreateSharedImage(
+    const Mailbox& mailbox,
+    int client_id,
+    gfx::GpuMemoryBufferHandle handle,
+    gfx::BufferFormat format,
+    SurfaceHandle surface_handle,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    uint32_t usage) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+}  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
new file mode 100644
index 0000000..aeb672a
--- /dev/null
+++ b/gpu/command_buffer/service/shared_image_backing_factory_iosurface_unittest.cc
@@ -0,0 +1,237 @@
+// 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 "gpu/command_buffer/service/shared_image_backing_factory_iosurface.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind_helpers.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
+#include "gpu/command_buffer/service/mailbox_manager_impl.h"
+#include "gpu/command_buffer/service/shared_context_state.h"
+#include "gpu/command_buffer/service/shared_image_factory.h"
+#include "gpu/command_buffer/service/shared_image_manager.h"
+#include "gpu/command_buffer/service/shared_image_representation.h"
+#include "gpu/config/gpu_driver_bug_workarounds.h"
+#include "gpu/config/gpu_feature_info.h"
+#include "gpu/config/gpu_preferences.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkPromiseImageTexture.h"
+#include "third_party/skia/include/core/SkSurface.h"
+#include "third_party/skia/include/gpu/GrBackendSurface.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/gl_surface.h"
+#include "ui/gl/init/gl_factory.h"
+
+namespace gpu {
+namespace {
+
+class SharedImageBackingFactoryIOSurfaceTest : public testing::Test {
+ public:
+  void SetUp() override {
+    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    ASSERT_TRUE(surface_);
+    context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
+                                         gl::GLContextAttribs());
+    ASSERT_TRUE(context_);
+    bool result = context_->MakeCurrent(surface_.get());
+    ASSERT_TRUE(result);
+
+    GpuDriverBugWorkarounds workarounds;
+    scoped_refptr<gl::GLShareGroup> share_group = new gl::GLShareGroup();
+    context_state_ = base::MakeRefCounted<SharedContextState>(
+        std::move(share_group), surface_, context_,
+        false /* use_virtualized_gl_contexts */, base::DoNothing());
+    context_state_->InitializeGrContext(workarounds, nullptr);
+    auto feature_info =
+        base::MakeRefCounted<gles2::FeatureInfo>(workarounds, GpuFeatureInfo());
+    context_state_->InitializeGL(GpuPreferences(), std::move(feature_info));
+
+    backing_factory_ = std::make_unique<SharedImageBackingFactoryIOSurface>(
+        workarounds, GpuFeatureInfo());
+
+    memory_type_tracker_ = std::make_unique<MemoryTypeTracker>(nullptr);
+    shared_image_representation_factory_ =
+        std::make_unique<SharedImageRepresentationFactory>(
+            &shared_image_manager_, nullptr);
+  }
+
+  GrContext* gr_context() { return context_state_->gr_context(); }
+
+ protected:
+  scoped_refptr<gl::GLSurface> surface_;
+  scoped_refptr<gl::GLContext> context_;
+  scoped_refptr<SharedContextState> context_state_;
+  std::unique_ptr<SharedImageBackingFactoryIOSurface> backing_factory_;
+  gles2::MailboxManagerImpl mailbox_manager_;
+  SharedImageManager shared_image_manager_;
+  std::unique_ptr<MemoryTypeTracker> memory_type_tracker_;
+  std::unique_ptr<SharedImageRepresentationFactory>
+      shared_image_representation_factory_;
+};
+
+// Basic test to check creation and deletion of IOSurface backed shared image.
+TEST_F(SharedImageBackingFactoryIOSurfaceTest, Basic) {
+  Mailbox mailbox = Mailbox::GenerateForSharedImage();
+  viz::ResourceFormat format = viz::ResourceFormat::RGBA_8888;
+  gfx::Size size(256, 256);
+  gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB();
+  uint32_t usage = SHARED_IMAGE_USAGE_GLES2 | SHARED_IMAGE_USAGE_DISPLAY;
+
+  auto backing = backing_factory_->CreateSharedImage(mailbox, format, size,
+                                                     color_space, usage);
+  EXPECT_TRUE(backing);
+
+  // Check clearing.
+  if (!backing->IsCleared()) {
+    backing->SetCleared();
+    EXPECT_TRUE(backing->IsCleared());
+  }
+
+  // First, validate via a legacy mailbox.
+  GLenum expected_target = GL_TEXTURE_RECTANGLE;
+  EXPECT_TRUE(backing->ProduceLegacyMailbox(&mailbox_manager_));
+  TextureBase* texture_base = mailbox_manager_.ConsumeTexture(mailbox);
+
+  // Currently there is no support for passthrough texture on Mac and hence
+  // in IOSurface backing. So the TextureBase* should be pointing to a Texture
+  // object.
+  auto* texture = gles2::Texture::CheckedCast(texture_base);
+  ASSERT_TRUE(texture);
+  EXPECT_EQ(texture->target(), expected_target);
+  EXPECT_TRUE(texture->IsImmutable());
+  int width, height, depth;
+  bool has_level =
+      texture->GetLevelSize(GL_TEXTURE_2D, 0, &width, &height, &depth);
+  EXPECT_TRUE(has_level);
+  EXPECT_EQ(width, size.width());
+  EXPECT_EQ(height, size.height());
+
+  // Next validate via a SharedImageRepresentationGLTexture.
+  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
+      shared_image_manager_.Register(std::move(backing),
+                                     memory_type_tracker_.get());
+  auto gl_representation =
+      shared_image_representation_factory_->ProduceGLTexture(mailbox);
+  EXPECT_TRUE(gl_representation);
+  EXPECT_TRUE(gl_representation->GetTexture()->service_id());
+  EXPECT_EQ(expected_target, gl_representation->GetTexture()->target());
+  EXPECT_EQ(size, gl_representation->size());
+  EXPECT_EQ(format, gl_representation->format());
+  EXPECT_EQ(color_space, gl_representation->color_space());
+  EXPECT_EQ(usage, gl_representation->usage());
+  gl_representation.reset();
+
+  // Finally, validate a SharedImageRepresentationSkia.
+  auto skia_representation =
+      shared_image_representation_factory_->ProduceSkia(mailbox);
+  EXPECT_TRUE(skia_representation);
+  auto surface = skia_representation->BeginWriteAccess(
+      gr_context(), 0, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
+  EXPECT_TRUE(surface);
+  EXPECT_EQ(size.width(), surface->width());
+  EXPECT_EQ(size.height(), surface->height());
+  skia_representation->EndWriteAccess(std::move(surface));
+  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  EXPECT_TRUE(promise_texture);
+  if (promise_texture) {
+    GrBackendTexture backend_texture = promise_texture->backendTexture();
+    EXPECT_TRUE(backend_texture.isValid());
+    EXPECT_EQ(size.width(), backend_texture.width());
+    EXPECT_EQ(size.height(), backend_texture.height());
+  }
+  skia_representation->EndReadAccess();
+  skia_representation.reset();
+
+  factory_ref.reset();
+  EXPECT_FALSE(mailbox_manager_.ConsumeTexture(mailbox));
+}
+
+// Test to check interaction between Gl and skia GL representations.
+// We write to a GL texture using gl representation and then read from skia
+// representation.
+TEST_F(SharedImageBackingFactoryIOSurfaceTest, GLSkiaGL) {
+  // Create a backing using mailbox.
+  auto mailbox = Mailbox::GenerateForSharedImage();
+  auto format = viz::ResourceFormat::RGBA_8888;
+  gfx::Size size(1, 1);
+  auto color_space = gfx::ColorSpace::CreateSRGB();
+  uint32_t usage = SHARED_IMAGE_USAGE_GLES2 | SHARED_IMAGE_USAGE_DISPLAY;
+  auto backing = backing_factory_->CreateSharedImage(mailbox, format, size,
+                                                     color_space, usage);
+  EXPECT_TRUE(backing);
+
+  GLenum expected_target = GL_TEXTURE_RECTANGLE;
+  std::unique_ptr<SharedImageRepresentationFactoryRef> factory_ref =
+      shared_image_manager_.Register(std::move(backing),
+                                     memory_type_tracker_.get());
+
+  // Create a SharedImageRepresentationGLTexture.
+  auto gl_representation =
+      shared_image_representation_factory_->ProduceGLTexture(mailbox);
+  EXPECT_TRUE(gl_representation);
+  EXPECT_EQ(expected_target, gl_representation->GetTexture()->target());
+
+  // Create an FBO.
+  GLuint fbo = 0;
+  gl::GLApi* api = gl::g_current_gl_context;
+  api->glGenFramebuffersEXTFn(1, &fbo);
+  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);
+
+  // Attach the texture to FBO.
+  api->glFramebufferTexture2DEXTFn(
+      GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+      gl_representation->GetTexture()->target(),
+      gl_representation->GetTexture()->service_id(), 0);
+
+  // Set the clear color to green.
+  api->glClearColorFn(0.0f, 1.0f, 0.0f, 1.0f);
+  api->glClearFn(GL_COLOR_BUFFER_BIT);
+  gl_representation.reset();
+
+  // Next create a SharedImageRepresentationSkia to read back the texture data.
+  auto skia_representation =
+      shared_image_representation_factory_->ProduceSkia(mailbox);
+  EXPECT_TRUE(skia_representation);
+  auto promise_texture = skia_representation->BeginReadAccess(nullptr);
+  EXPECT_TRUE(promise_texture);
+  if (promise_texture) {
+    GrBackendTexture backend_texture = promise_texture->backendTexture();
+    EXPECT_TRUE(backend_texture.isValid());
+    EXPECT_EQ(size.width(), backend_texture.width());
+    EXPECT_EQ(size.height(), backend_texture.height());
+  }
+
+  // Create an Sk Image from GrBackendTexture.
+  auto sk_image = SkImage::MakeFromTexture(
+      gr_context(), promise_texture->backendTexture(), kTopLeft_GrSurfaceOrigin,
+      kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr);
+
+  SkImageInfo dst_info =
+      SkImageInfo::Make(size.width(), size.height(), kRGBA_8888_SkColorType,
+                        kOpaque_SkAlphaType, nullptr);
+
+  const int num_pixels = size.width() * size.height();
+  std::unique_ptr<uint8_t[]> dst_pixels(new uint8_t[num_pixels * 4]());
+
+  // Read back pixels from Sk Image.
+  EXPECT_TRUE(sk_image->readPixels(dst_info, dst_pixels.get(),
+                                   dst_info.minRowBytes(), 0, 0));
+  skia_representation->EndReadAccess();
+
+  // Compare the pixel values.
+  EXPECT_EQ(dst_pixels[0], 0);
+  EXPECT_EQ(dst_pixels[1], 255);
+  EXPECT_EQ(dst_pixels[2], 0);
+  EXPECT_EQ(dst_pixels[3], 255);
+
+  skia_representation.reset();
+  factory_ref.reset();
+  EXPECT_FALSE(mailbox_manager_.ConsumeTexture(mailbox));
+}
+
+}  // anonymous namespace
+}  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image_factory.cc b/gpu/command_buffer/service/shared_image_factory.cc
index 8605158..c53291a 100644
--- a/gpu/command_buffer/service/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image_factory.cc
@@ -30,6 +30,8 @@
 #include "gpu/command_buffer/service/external_vk_image_factory.h"
 #elif defined(OS_ANDROID) && BUILDFLAG(ENABLE_VULKAN)
 #include "gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.h"
+#elif defined(OS_MACOSX)
+#include "gpu/command_buffer/service/shared_image_backing_factory_iosurface.h"
 #endif
 
 namespace gpu {
@@ -77,6 +79,11 @@
           std::make_unique<SharedImageBackingFactoryAHB>(workarounds,
                                                          gpu_feature_info,
                                                          context_state)),
+#elif defined(OS_MACOSX)
+      interop_backing_factory_(
+          std::make_unique<SharedImageBackingFactoryIOSurface>(
+              workarounds,
+              gpu_feature_info)),
 #endif
       wrapped_sk_image_factory_(
           gpu_preferences.enable_raster_to_sk_image
diff --git a/gpu/command_buffer/service/texture_manager.h b/gpu/command_buffer/service/texture_manager.h
index 993270b..1f66199e 100644
--- a/gpu/command_buffer/service/texture_manager.h
+++ b/gpu/command_buffer/service/texture_manager.h
@@ -43,6 +43,9 @@
 class SharedImageRepresentationGLTexture;
 class SharedImageRepresentationGLTextureAHB;
 class SharedImageRepresentationSkiaGLAHB;
+class SharedImageBackingIOSurface;
+class SharedImageRepresentationGLTextureIOSurface;
+class SharedImageRepresentationSkiaIOSurface;
 
 namespace gles2 {
 class GLStreamTextureImage;
@@ -375,6 +378,9 @@
   friend class gpu::SharedImageBackingAHB;
   friend class gpu::SharedImageRepresentationGLTextureAHB;
   friend class gpu::SharedImageRepresentationSkiaGLAHB;
+  friend class gpu::SharedImageBackingIOSurface;
+  friend class gpu::SharedImageRepresentationGLTextureIOSurface;
+  friend class gpu::SharedImageRepresentationSkiaIOSurface;
   friend class TextureDefinition;
   friend class TextureManager;
   friend class TextureRef;
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index e96ecfc4..fea08b2 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -9,6 +9,11 @@
         "type": "android"
       },
       "gl_vendor": "Imagination.*",
+      "gl_type": "gles",
+      "gl_version": {
+        "op": "<",
+        "value": "3.0"
+      },
       "features": [
         "use_client_side_arrays_for_stream_buffers"
       ]
@@ -21,6 +26,11 @@
         "type": "android"
       },
       "gl_vendor": "ARM.*",
+      "gl_type": "gles",
+      "gl_version": {
+        "op": "<",
+        "value": "3.0"
+      },
       "features": [
         "use_client_side_arrays_for_stream_buffers"
       ]
@@ -3207,6 +3217,18 @@
       "features": [
         "rely_on_implicit_sync_for_swap_buffers"
       ]
+    },
+    {
+      "id": 297,
+      "cr_bugs": [938678],
+      "description": "Needed to pass dEQP-EGL.functional.robustness.reset_context.shaders.infinite_loop.*",
+      "os": {
+        "type" : "chromeos"
+      },
+      "vendor_id": "0x1002",
+      "features": [
+        "exit_on_context_lost"
+      ]
     }
   ]
 }
diff --git a/gpu/config/gpu_driver_bug_list_unittest.cc b/gpu/config/gpu_driver_bug_list_unittest.cc
index 88646b3..58739a41 100644
--- a/gpu/config/gpu_driver_bug_list_unittest.cc
+++ b/gpu/config/gpu_driver_bug_list_unittest.cc
@@ -24,6 +24,7 @@
   GPUInfo gpu_info;
   gpu_info.gl_vendor = "ARM";
   gpu_info.gl_renderer = "MALi_T604";
+  gpu_info.gl_version = "OpenGL ES 2.0";
   std::set<int> bugs = list->MakeDecision(
       GpuControlList::kOsAndroid, "4.1", gpu_info);
   EXPECT_EQ(1u, bugs.count(USE_CLIENT_SIDE_ARRAYS_FOR_STREAM_BUFFERS));
@@ -34,6 +35,7 @@
   GPUInfo gpu_info;
   gpu_info.gl_vendor = "Imagination Technologies";
   gpu_info.gl_renderer = "PowerVR SGX 540";
+  gpu_info.gl_version = "OpenGL ES 2.0";
   std::set<int> bugs = list->MakeDecision(
       GpuControlList::kOsAndroid, "4.1", gpu_info);
   EXPECT_EQ(1u, bugs.count(USE_CLIENT_SIDE_ARRAYS_FOR_STREAM_BUFFERS));
diff --git a/gpu/ipc/client/DEPS b/gpu/ipc/client/DEPS
index 3333b39..7a79c6ac 100644
--- a/gpu/ipc/client/DEPS
+++ b/gpu/ipc/client/DEPS
@@ -14,7 +14,6 @@
     "+media/filters/jpeg_parser.h",
   ],
   "raster_in_process_context_tests.cc": [
-    "+cc/paint/color_space_transfer_cache_entry.h",
     "+components/viz/common/resources/resource_format.h",
     "+components/viz/common/resources/resource_format_utils.h",
     "+components/viz/test/test_gpu_memory_buffer_manager.h",
diff --git a/gpu/ipc/client/raster_in_process_context_tests.cc b/gpu/ipc/client/raster_in_process_context_tests.cc
index 9d357d4..520066d 100644
--- a/gpu/ipc/client/raster_in_process_context_tests.cc
+++ b/gpu/ipc/client/raster_in_process_context_tests.cc
@@ -5,7 +5,6 @@
 #include <memory>
 
 #include "build/build_config.h"
-#include "cc/paint/color_space_transfer_cache_entry.h"
 #include "components/viz/common/resources/resource_format.h"
 #include "components/viz/test/test_gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/client/raster_implementation.h"
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 84f1854f..1daa26e 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -1844,10 +1844,6 @@
       mixins: "win-gpu-fyi-ci"
     }
     builders {
-      name: "GPU FYI Win Clang Builder (dbg)"
-      mixins: "win-gpu-fyi-ci"
-    }
-    builders {
       name: "GPU FYI Win dEQP Builder"
       mixins: "win-gpu-fyi-ci"
     }
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index fa1894b..ffdc316f 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -3051,11 +3051,6 @@
     short_name: "x64"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/GPU FYI Win Clang Builder (dbg)"
-    category: "Windows|Builder|Debug"
-    short_name: "clg"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/Win10 FYI Debug (NVIDIA)"
     category: "Windows|10|Nvidia"
     short_name: "dbg"
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index 87ffee2..d25de57 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -152,7 +152,6 @@
   triggers: "GPU FYI Mac dEQP Builder"
   triggers: "GPU FYI Win Builder (dbg)"
   triggers: "GPU FYI Win Builder"
-  triggers: "GPU FYI Win Clang Builder (dbg)"
   triggers: "GPU FYI Win dEQP Builder"
   triggers: "GPU FYI Win x64 Builder (dbg)"
   triggers: "GPU FYI Win x64 Builder"
@@ -2543,16 +2542,6 @@
 }
 
 job {
-  id: "GPU FYI Win Clang Builder (dbg)"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "GPU FYI Win Clang Builder (dbg)"
-  }
-}
-
-job {
   id: "GPU FYI Win dEQP Builder"
   acl_sets: "default"
   buildbucket: {
diff --git a/ios/build/bots/chromium.mac/ios-slimnav.json b/ios/build/bots/chromium.mac/ios-slimnav.json
index 94827d46..3c55bc9 100644
--- a/ios/build/bots/chromium.mac/ios-slimnav.json
+++ b/ios/build/bots/chromium.mac/ios-slimnav.json
@@ -17,7 +17,7 @@
     {
       "include": "screen_size_dependent_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone 6s Plus",
       "os": "12.1",
@@ -28,7 +28,7 @@
     {
       "include": "screen_size_dependent_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone 6s",
       "os": "12.1",
@@ -39,7 +39,7 @@
     {
       "include": "common_tests.json",
             "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone 6s",
       "os": "12.1",
@@ -50,7 +50,7 @@
     {
       "include": "screen_size_dependent_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "12.1",
@@ -61,7 +61,7 @@
     {
       "include": "screen_size_dependent_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone 6s Plus",
       "os": "11.4",
@@ -72,7 +72,7 @@
     {
       "include": "screen_size_dependent_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone 6s",
       "os": "11.4",
@@ -83,7 +83,7 @@
     {
       "include": "common_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone 6s",
       "os": "11.4",
@@ -94,7 +94,7 @@
     {
       "include": "screen_size_dependent_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -105,7 +105,7 @@
     {
       "include": "eg_cq_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone 6s",
       "os": "11.4",
@@ -116,7 +116,7 @@
     {
       "app": "ios_chrome_bookmarks_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -129,7 +129,7 @@
     {
       "app": "ios_chrome_manual_fill_egtests",
       "test args": [
-        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManager"
+        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -141,7 +141,7 @@
     {
       "app": "ios_chrome_web_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -153,7 +153,7 @@
     {
       "app": "ios_chrome_settings_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -165,7 +165,7 @@
     {
       "app": "ios_chrome_reading_list_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -177,7 +177,7 @@
     {
       "app": "ios_showcase_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -189,7 +189,7 @@
     {
       "app": "ios_chrome_bookmarks_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "shard size": 2,
       "device type": "iPad Air 2",
@@ -202,7 +202,7 @@
     {
       "app": "ios_chrome_manual_fill_egtests",
       "test args": [
-        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManager"
+        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManagerr,OfflineVersionWithoutNativeContent"
       ],
       "xctest": true,
       "device type": "iPad Air 2",
@@ -214,7 +214,7 @@
     {
       "app": "ios_chrome_web_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "12.1",
@@ -226,7 +226,7 @@
     {
       "app": "ios_chrome_settings_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "12.1",
@@ -238,7 +238,7 @@
     {
       "app": "ios_chrome_reading_list_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "12.1",
@@ -250,7 +250,7 @@
     {
       "app": "ios_showcase_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "12.1",
@@ -262,7 +262,7 @@
     {
       "app": "ios_chrome_bookmarks_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "11.4",
@@ -275,7 +275,7 @@
     {
       "app": "ios_chrome_manual_fill_egtests",
       "test args": [
-        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManager"
+        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManagerr,OfflineVersionWithoutNativeContent"
       ],
       "xctest": true,
       "device type": "iPhone X",
@@ -287,7 +287,7 @@
     {
       "app": "ios_chrome_web_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "11.4",
@@ -299,7 +299,7 @@
     {
       "app": "ios_chrome_settings_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "11.4",
@@ -311,7 +311,7 @@
     {
       "app": "ios_chrome_reading_list_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "11.4",
@@ -323,7 +323,7 @@
     {
       "app": "ios_showcase_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "11.4",
@@ -335,7 +335,7 @@
     {
       "app": "ios_chrome_bookmarks_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "shard size": 2,
       "device type": "iPhone X",
@@ -348,7 +348,7 @@
     {
       "app": "ios_chrome_manual_fill_egtests",
       "test args": [
-        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManager"
+        "--enable-features=AutofillManualFallback,AutofillManualFallbackPhaseTwo,WebFrameMessaging,SlimNavigationManagerr,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "12.1",
@@ -360,7 +360,7 @@
     {
       "app": "ios_chrome_web_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "12.1",
@@ -372,7 +372,7 @@
     {
       "app": "ios_chrome_settings_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "12.1",
@@ -384,7 +384,7 @@
     {
       "app": "ios_chrome_reading_list_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "12.1",
@@ -396,7 +396,7 @@
     {
       "app": "ios_showcase_egtests",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "12.1",
@@ -408,7 +408,7 @@
     {
       "include": "eg_cq_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "11.4",
@@ -419,7 +419,7 @@
     {
       "include": "eg_cq_tests.json",
       "test args": [
-        "--enable-features=SlimNavigationManager"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "12.1",
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index 56a37c9..4217b33 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -313,6 +313,12 @@
     {"autofill-dynamic-forms", flag_descriptions::kAutofillDynamicFormsName,
      flag_descriptions::kAutofillDynamicFormsDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(autofill::features::kAutofillDynamicForms)},
+    {"autofill-no-local-save-on-upload-success",
+     flag_descriptions::kAutofillNoLocalSaveOnUploadSuccessName,
+     flag_descriptions::kAutofillNoLocalSaveOnUploadSuccessDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillNoLocalSaveOnUploadSuccess)},
     {"autofill-prefilled-fields",
      flag_descriptions::kAutofillPrefilledFieldsName,
      flag_descriptions::kAutofillPrefilledFieldsDescription, flags_ui::kOsIos,
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
index d9d3d6ea..46e5205c 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
@@ -142,6 +142,13 @@
 const char kAutofillDynamicFormsDescription[] =
     "Refills forms that dynamically change after an initial fill";
 
+const char kAutofillNoLocalSaveOnUploadSuccessName[] =
+    "Disable saving local copy of uploaded card when credit card upload "
+    "succeeds";
+const char kAutofillNoLocalSaveOnUploadSuccessDescription[] =
+    "When enabled, no local copy of server card will be saved when credit card "
+    "upload succeeds.";
+
 const char kAutofillPrefilledFieldsName[] = "Autofill prefilled forms";
 const char kAutofillPrefilledFieldsDescription[] =
     "Fills forms that contain a programmatically filled value.";
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.h b/ios/chrome/browser/ios_chrome_flag_descriptions.h
index 00a2007c..1d28b4a 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.h
@@ -111,6 +111,11 @@
 extern const char kAutofillDynamicFormsName[];
 extern const char kAutofillDynamicFormsDescription[];
 
+// Title and description for the flag to control saving FULL_SERVER_CARDS upon
+// success of credit card upload.
+extern const char kAutofillNoLocalSaveOnUploadSuccessName[];
+extern const char kAutofillNoLocalSaveOnUploadSuccessDescription[];
+
 // Title and description for the flag to control the dynamic autofill.
 extern const char kAutofillPrefilledFieldsName[];
 extern const char kAutofillPrefilledFieldsDescription[];
diff --git a/ios/chrome/browser/reading_list/offline_page_tab_helper.mm b/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
index 4a511b2..148f2cf28 100644
--- a/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
+++ b/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
@@ -144,10 +144,6 @@
     return;
   }
   offline_navigation_triggered_ = GURL::EmptyGURL();
-  if (!reading_list_model_->loaded()) {
-    initial_navigation_url_ = GURL::EmptyGURL();
-    return;
-  }
   initial_navigation_url_ = context->GetUrl();
   loading_slow_or_failed_ = false;
   navigation_committed_ = false;
@@ -194,7 +190,9 @@
     PresentOfflinePageForOnlineUrl(url);
     return;
   }
-  if (!url.is_valid() || !reading_list_model_->GetEntryByURL(url)) {
+
+  if (!url.is_valid() || !reading_list_model_->loaded() ||
+      !reading_list_model_->GetEntryByURL(url)) {
     return;
   }
   reading_list_model_->SetReadStatus(url, true);
@@ -209,8 +207,9 @@
 
 void OfflinePageTabHelper::ReadingListModelLoaded(
     const ReadingListModel* model) {
-  // There is no need to do anything. If the navigation is in progress then
-  // CheckLoadingProgress might still present distilled version of the page.
+  if (navigation_committed_ && loading_slow_or_failed_) {
+    PresentOfflinePageForOnlineUrl(initial_navigation_url_);
+  }
 }
 
 void OfflinePageTabHelper::ReadingListModelBeingDeleted(
diff --git a/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm b/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm
index a4070f7..2cefd46a 100644
--- a/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm
+++ b/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm
@@ -28,19 +28,130 @@
 const char kTestDistilledPath[] = "distilled.html";
 const char kTestDistilledURL[] = "http://foo.bar/distilled";
 const char kTestDirectory[] = "ios/testing/data/";
+
+// A simple implementation of ReadingListModel that only support functions
+// needed to load an offline page.
+class FakeReadingListModel : public ReadingListModel {
+ public:
+  ~FakeReadingListModel() override {}
+  bool loaded() const override { return loaded_; }
+
+  syncer::ModelTypeSyncBridge* GetModelTypeSyncBridge() override {
+    NOTREACHED();
+    return nullptr;
+  }
+
+  const std::vector<GURL> Keys() const override {
+    NOTREACHED();
+    return std::vector<GURL>();
+  }
+
+  size_t size() const override {
+    DCHECK(loaded_);
+    return 0;
+  }
+
+  size_t unread_size() const override {
+    NOTREACHED();
+    return 0;
+  }
+
+  size_t unseen_size() const override {
+    NOTREACHED();
+    return 0;
+  }
+
+  void MarkAllSeen() override { NOTREACHED(); }
+
+  bool DeleteAllEntries() override {
+    NOTREACHED();
+    return false;
+  }
+
+  bool GetLocalUnseenFlag() const override {
+    NOTREACHED();
+    return false;
+  }
+
+  void ResetLocalUnseenFlag() override { NOTREACHED(); }
+
+  const ReadingListEntry* GetEntryByURL(const GURL& gurl) const override {
+    DCHECK(loaded_);
+    if (entry_->URL() == gurl) {
+      return entry_;
+    }
+    return nullptr;
+  }
+
+  const ReadingListEntry* GetFirstUnreadEntry(bool distilled) const override {
+    NOTREACHED();
+    return nullptr;
+  }
+
+  const ReadingListEntry& AddEntry(const GURL& url,
+                                   const std::string& title,
+                                   reading_list::EntrySource source) override {
+    NOTREACHED();
+    return *entry_;
+  }
+
+  void RemoveEntryByURL(const GURL& url) override { NOTREACHED(); }
+
+  void SetReadStatus(const GURL& url, bool read) override {
+    if (entry_->URL() == url) {
+      entry_->SetRead(true, base::Time());
+    }
+  }
+
+  void SetEntryTitle(const GURL& url, const std::string& title) override {
+    NOTREACHED();
+  }
+
+  void SetEntryDistilledState(
+      const GURL& url,
+      ReadingListEntry::DistillationState state) override {
+    NOTREACHED();
+  }
+
+  void SetEntryDistilledInfo(const GURL& url,
+                             const base::FilePath& distilled_path,
+                             const GURL& distilled_url,
+                             int64_t distilation_size,
+                             const base::Time& distilation_time) override {
+    NOTREACHED();
+  }
+
+  void SetContentSuggestionsExtra(
+      const GURL& url,
+      const reading_list::ContentSuggestionsExtra& extra) override {
+    NOTREACHED();
+  }
+
+  void SetEntry(ReadingListEntry* entry) { entry_ = entry; }
+  void SetLoaded() {
+    loaded_ = true;
+    for (auto& observer : observers_) {
+      observer.ReadingListModelLoaded(this);
+    }
+  }
+
+ private:
+  ReadingListEntry* entry_ = nullptr;
+  bool loaded_ = false;
+};
 }
 
 // Test fixture to test loading of Reading list offline pages.
 class OfflinePageTabHelperTest : public web::WebTest {
  public:
   void SetUp() override {
+    web::WebTest::SetUp();
     TestChromeBrowserState::Builder test_cbs_builder;
     base::FilePath test_data_dir;
     ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
     test_data_dir = test_data_dir.AppendASCII(kTestDirectory);
     test_cbs_builder.SetPath(test_data_dir);
     chrome_browser_state_ = test_cbs_builder.Build();
-
     test_web_state_.SetBrowserState(chrome_browser_state_.get());
     reading_list_model_ = std::make_unique<ReadingListModelImpl>(
         /*storage_layer*/ nullptr, /*pref_service*/ nullptr,
@@ -57,6 +168,37 @@
   web::TestWebState test_web_state_;
 };
 
+// Test fixture to test loading of Reading list offline pages with a delayed
+// ReadingListModel.
+class OfflinePageTabHelperDelayedModelTest : public web::WebTest {
+  void SetUp() override {
+    web::WebTest::SetUp();
+    TestChromeBrowserState::Builder test_cbs_builder;
+    base::FilePath test_data_dir;
+    ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
+    test_data_dir = test_data_dir.AppendASCII(kTestDirectory);
+    test_cbs_builder.SetPath(test_data_dir);
+    chrome_browser_state_ = test_cbs_builder.Build();
+    test_web_state_.SetBrowserState(chrome_browser_state_.get());
+    fake_reading_list_model_ = std::make_unique<FakeReadingListModel>();
+    GURL url(kTestURL);
+    entry_ = std::make_unique<ReadingListEntry>(url, kTestTitle, base::Time());
+    std::string distilled_path = kTestDistilledPath;
+    entry_->SetDistilledInfo(base::FilePath(distilled_path),
+                             GURL(kTestDistilledURL), 50,
+                             base::Time::FromTimeT(100));
+    fake_reading_list_model_->SetEntry(entry_.get());
+    OfflinePageTabHelper::CreateForWebState(&test_web_state_,
+                                            fake_reading_list_model_.get());
+  }
+
+ protected:
+  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
+  std::unique_ptr<FakeReadingListModel> fake_reading_list_model_;
+  web::TestWebState test_web_state_;
+  std::unique_ptr<ReadingListEntry> entry_;
+};
+
 // Tests that loading an online version does mark it read.
 TEST_F(OfflinePageTabHelperTest, TestLoadReadingListSuccess) {
   GURL url(kTestURL);
@@ -173,3 +315,34 @@
       base::Time::FromTimeT(100));
   EXPECT_TRUE(offline_page_tab_helper->HasDistilledVersionForOnlineUrl(url));
 }
+
+// Tests that OfflinePageTabHelper correctly shows Offline page if model takes
+// a long time to load.
+TEST_F(OfflinePageTabHelperDelayedModelTest, TestLateReadingListModelLoading) {
+  OfflinePageTabHelper* offline_page_tab_helper =
+      OfflinePageTabHelper::FromWebState(&test_web_state_);
+  GURL url(kTestURL);
+  EXPECT_FALSE(offline_page_tab_helper->HasDistilledVersionForOnlineUrl(url));
+  web::FakeNavigationContext context;
+
+  context.SetHasCommitted(true);
+  context.SetUrl(url);
+  test_web_state_.OnNavigationStarted(&context);
+  test_web_state_.OnNavigationFinished(&context);
+  test_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);
+  EXPECT_FALSE(base::test::ios::WaitUntilConditionOrTimeout(
+      base::test::ios::kWaitForFileOperationTimeout, ^bool {
+        base::RunLoop().RunUntilIdle();
+        return test_web_state_.GetLastLoadedData();
+      }));
+  EXPECT_FALSE(entry_->IsRead());
+  EXPECT_FALSE(offline_page_tab_helper->presenting_offline_page());
+  fake_reading_list_model_->SetLoaded();
+  EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
+      base::test::ios::kWaitForFileOperationTimeout, ^bool {
+        base::RunLoop().RunUntilIdle();
+        return test_web_state_.GetLastLoadedData();
+      }));
+  EXPECT_TRUE(entry_->IsRead());
+  EXPECT_TRUE(offline_page_tab_helper->presenting_offline_page());
+}
diff --git a/ios/chrome/browser/signin/BUILD.gn b/ios/chrome/browser/signin/BUILD.gn
index 7d87fab..af9142f 100644
--- a/ios/chrome/browser/signin/BUILD.gn
+++ b/ios/chrome/browser/signin/BUILD.gn
@@ -123,6 +123,7 @@
   deps = [
     ":signin",
     "//base",
+    "//components/image_fetcher/core:test_support",
     "//components/keyed_service/core",
     "//components/signin/core/browser:internals_test_support",
     "//components/signin/ios/browser",
diff --git a/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc b/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc
index ab13af6..4c8cc81 100644
--- a/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc
+++ b/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
-#include "components/signin/core/browser/test_image_decoder.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm b/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
index ee084d1..663d272 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
@@ -570,10 +570,6 @@
 // appearing, and that the Reading List entry is present in the Reading List.
 // Loads online version by tapping on entry.
 - (void)testSavingToReadingListAndLoadNormal {
-  // TODO(crbug.com/874649): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   auto network_change_disabler =
       std::make_unique<net::NetworkChangeNotifier::DisableForTest>();
   auto wifi_network = std::make_unique<WifiNetworkChangeNotifier>();
@@ -611,14 +607,6 @@
 // appearing, and that the Reading List entry is present in the Reading List.
 // Loads offline version by tapping on entry without web server.
 - (void)testSavingToReadingListAndLoadNoNetwork {
-  // TODO(crbug.com/874649): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
-  // TODO(crbug.com/936773): re-enable this test.
-  if (reading_list::IsOfflinePageWithoutNativeContentEnabled())
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   auto network_change_disabler =
       std::make_unique<net::NetworkChangeNotifier::DisableForTest>();
   auto wifi_network = std::make_unique<WifiNetworkChangeNotifier>();
@@ -666,14 +654,6 @@
 // appearing, and that the Reading List entry is present in the Reading List.
 // Loads offline version by tapping on entry with delayed web server.
 - (void)testSavingToReadingListAndLoadBadNetwork {
-  // TODO(crbug.com/905839): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
-  // TODO(crbug.com/936773): re-enable this test.
-  if (reading_list::IsOfflinePageWithoutNativeContentEnabled())
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   auto network_change_disabler =
       std::make_unique<net::NetworkChangeNotifier::DisableForTest>();
   auto wifi_network = std::make_unique<WifiNetworkChangeNotifier>();
diff --git a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
index 1897cbf..c41dfca 100644
--- a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
@@ -551,10 +551,14 @@
   NSString* continueButtonTitle =
       l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
   if ([self authService] -> IsAuthenticatedIdentityManaged()) {
-    std::string hosted_domain =
-        IdentityManagerFactory::GetForBrowserState(_browserState)
-            ->GetPrimaryAccountInfo()
-            .hosted_domain;
+    identity::IdentityManager* identityManager =
+        IdentityManagerFactory::GetForBrowserState(_browserState);
+    base::Optional<AccountInfo> accountInfo =
+        identityManager->FindExtendedAccountInfoForAccount(
+            identityManager->GetPrimaryAccountInfo());
+    std::string hosted_domain = accountInfo.has_value()
+                                    ? accountInfo.value().hosted_domain
+                                    : std::string();
     if (unified_consent::IsUnifiedConsentFeatureEnabled()) {
       title =
           l10n_util::GetNSString(IDS_IOS_MANAGED_DISCONNECT_DIALOG_TITLE_UNITY);
diff --git a/ios/chrome/browser/ui/translate/translate_infobar_view.mm b/ios/chrome/browser/ui/translate/translate_infobar_view.mm
index e06061b7a..461e80e3 100644
--- a/ios/chrome/browser/ui/translate/translate_infobar_view.mm
+++ b/ios/chrome/browser/ui/translate/translate_infobar_view.mm
@@ -95,9 +95,16 @@
   if (!self.superview)
     return;
 
-  [NamedGuide guideWithName:kTranslateInfobarOptionsGuide
-                       view:self.optionsButton]
-      .constrainedView = self.optionsButton;
+  // Constrain the options button named guide to its corresponding view. Reset
+  // the named guide's existing constrained view beforehand. Otherwise this will
+  // be a no-op if the new constrained view is the same as the existing one,
+  // even though the existing constraints are invalid (e.g., when the infobar is
+  // removed from the view hierarchy and added again after a tab switch).
+  NamedGuide* namedGuide =
+      [NamedGuide guideWithName:kTranslateInfobarOptionsGuide
+                           view:self.optionsButton];
+  [namedGuide resetConstraints];
+  namedGuide.constrainedView = self.optionsButton;
 
   // The initial bottom padding should be the current height of the secondary
   // toolbar or the bottom safe area inset, whichever is greater.
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn
index fffd472..35ab14e 100644
--- a/ios/chrome/test/earl_grey2/BUILD.gn
+++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -51,11 +51,6 @@
   public_deps = [
     ":shared_helper_headers",
   ]
-
-  include_dirs = [
-    "//ios/third_party/earl_grey2/src",
-    "//ios/third_party/edo/src",
-  ]
 }
 
 group("eg_test_support+eg2") {
@@ -76,8 +71,6 @@
     "smoke_egtest.mm",
   ]
 
-  include_dirs = [ "//ios/third_party/edo/src" ]
-
   deps = [
     ":eg_test_support+eg2",
     "//ios/third_party/earl_grey2:test_lib",
diff --git a/ios/testing/DEPS b/ios/testing/DEPS
index d9bd356..616b5038 100644
--- a/ios/testing/DEPS
+++ b/ios/testing/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+ios/third_party/earl_grey2/src",
   "+net",
   "+third_party/google_toolbox_for_mac",
   "+third_party/ocmock",
diff --git a/ios/testing/earl_grey/BUILD.gn b/ios/testing/earl_grey/BUILD.gn
index 86694a5a..1818cd25 100644
--- a/ios/testing/earl_grey/BUILD.gn
+++ b/ios/testing/earl_grey/BUILD.gn
@@ -2,11 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-config("earl_grey_support_config") {
-  include_dirs = [ "." ]
-  visibility = [ ":earl_grey_support" ]
-}
-
 source_set("earl_grey_support") {
   defines = [ "CHROME_EARL_GREY_1" ]
   configs += [ "//build/config/compiler:enable_arc" ]
@@ -19,12 +14,11 @@
 
   sources = [
     "disabled_test_macros.h",
+    "earl_grey_app.h",
+    "earl_grey_test.h",
     "matchers.h",
     "matchers.mm",
   ]
-
-  public_configs = [ ":earl_grey_support_config" ]
-  configs += [ ":earl_grey_support_config" ]
 }
 
 source_set("eg_app_support+eg2") {
@@ -38,12 +32,10 @@
   ]
 
   sources = [
+    "earl_grey_app.h",
     "matchers.h",
     "matchers.mm",
   ]
-
-  public_configs = [ ":earl_grey_support_config" ]
-  configs += [ ":earl_grey_support_config" ]
 }
 
 source_set("eg_test_support+eg2") {
@@ -52,5 +44,10 @@
 
   sources = [
     "disabled_test_macros.h",
+    "earl_grey_test.h",
+  ]
+
+  deps = [
+    "//ios/third_party/earl_grey2:test_lib",
   ]
 }
diff --git a/ios/testing/earl_grey/earl_grey_app.h b/ios/testing/earl_grey/earl_grey_app.h
new file mode 100644
index 0000000..4f9facc
--- /dev/null
+++ b/ios/testing/earl_grey/earl_grey_app.h
@@ -0,0 +1,28 @@
+// 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 IOS_TESTING_EARL_GREY_EARL_GREY_APP_H_
+#define IOS_TESTING_EARL_GREY_EARL_GREY_APP_H_
+
+// Contains includes and typedefs to allow code to compile under both EarlGrey1
+// and EarlGrey2 (App Process).
+
+#if defined(CHROME_EARL_GREY_1)
+
+#import <EarlGrey/EarlGrey.h>
+
+typedef DescribeToBlock GREYDescribeToBlock;
+typedef MatchesBlock GREYMatchesBlock;
+
+#elif defined(CHROME_EARL_GREY_2)
+
+#import <AppFramework/Action/GREYActionsShorthand.h>
+#import <AppFramework/EarlGreyApp.h>
+#import <AppFramework/Matcher/GREYMatchersShorthand.h>
+
+#else
+#error Must define either CHROME_EARL_GREY_1 or CHROME_EARL_GREY_2.
+#endif
+
+#endif  // IOS_TESTING_EARL_GREY_EARL_GREY_APP_H_
diff --git a/ios/testing/earl_grey/earl_grey_test.h b/ios/testing/earl_grey/earl_grey_test.h
new file mode 100644
index 0000000..5255743c
--- /dev/null
+++ b/ios/testing/earl_grey/earl_grey_test.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_TESTING_EARL_GREY_EARL_GREY_TEST_H_
+#define IOS_TESTING_EARL_GREY_EARL_GREY_TEST_H_
+
+// Contains includes and typedefs to allow code to compile under both EarlGrey1
+// and EarlGrey2 (Test Process).
+
+#if defined(CHROME_EARL_GREY_1)
+
+#import <EarlGrey/EarlGrey.h>
+
+typedef DescribeToBlock GREYDescribeToBlock;
+typedef MatchesBlock GREYMatchesBlock;
+
+#elif defined(CHROME_EARL_GREY_2)
+
+#import "ios/third_party/earl_grey2/src/TestLib/EarlGreyImpl/EarlGrey.h"  // nogncheck
+
+#else
+#error Must define either CHROME_EARL_GREY_1 or CHROME_EARL_GREY_2.
+#endif
+
+#endif  // IOS_TESTING_EARL_GREY_EARL_GREY_TEST_H_
diff --git a/ios/testing/earl_grey/matchers.mm b/ios/testing/earl_grey/matchers.mm
index 7903f03..35c2c67 100644
--- a/ios/testing/earl_grey/matchers.mm
+++ b/ios/testing/earl_grey/matchers.mm
@@ -3,20 +3,12 @@
 // found in the LICENSE file.
 
 #import "ios/testing/earl_grey/matchers.h"
+#include "ios/testing/earl_grey/earl_grey_app.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-#if defined(CHROME_EARL_GREY_1)
-#import <EarlGrey/EarlGrey.h>
-#endif
-
-#if defined(CHROME_EARL_GREY_2)
-#import <AppFramework/EarlGreyApp.h>
-#import <AppFramework/Matcher/GREYMatchersShorthand.h>
-#endif
-
 namespace testing {
 
 id<GREYMatcher> ButtonWithAccessibilityLabel(NSString* label) {
diff --git a/ios/third_party/earl_grey2/BUILD.gn b/ios/third_party/earl_grey2/BUILD.gn
index 52aeeb6..551c92b 100644
--- a/ios/third_party/earl_grey2/BUILD.gn
+++ b/ios/third_party/earl_grey2/BUILD.gn
@@ -6,7 +6,10 @@
 import("//build/config/ios/rules.gni")
 
 config("config") {
-  include_dirs = [ "//ios/third_party/earl_grey2/src" ]
+  include_dirs = [
+    "//ios/third_party/earl_grey2/src",
+    "//ios/third_party/edo/src",
+  ]
 }
 
 group("earl_grey2") {
diff --git a/ios/third_party/material_components_ios/BUILD.gn b/ios/third_party/material_components_ios/BUILD.gn
index 1ed62261..85c03fb 100644
--- a/ios/third_party/material_components_ios/BUILD.gn
+++ b/ios/third_party/material_components_ios/BUILD.gn
@@ -297,27 +297,27 @@
     "src/components/ShadowLayer/src/MDCShadowLayer.h",
     "src/components/ShadowLayer/src/MDCShadowLayer.m",
     "src/components/ShadowLayer/src/MaterialShadowLayer.h",
+    "src/components/ShapeLibrary/src/MDCCornerTreatment+CornerTypeInitalizer.h",
     "src/components/ShapeLibrary/src/MDCCornerTreatment+CornerTypeInitalizer.m",
-    "src/components/ShapeLibrary/src/MDCCornerTreatment+CornerTypeInitalizerNew.h",
+    "src/components/ShapeLibrary/src/MDCCurvedCornerTreatment.h",
     "src/components/ShapeLibrary/src/MDCCurvedCornerTreatment.m",
-    "src/components/ShapeLibrary/src/MDCCurvedCornerTreatmentNew.h",
+    "src/components/ShapeLibrary/src/MDCCutCornerTreatment.h",
     "src/components/ShapeLibrary/src/MDCCutCornerTreatment.m",
-    "src/components/ShapeLibrary/src/MDCCutCornerTreatmentNew.h",
+    "src/components/ShapeLibrary/src/MDCRoundedCornerTreatment.h",
     "src/components/ShapeLibrary/src/MDCRoundedCornerTreatment.m",
-    "src/components/ShapeLibrary/src/MDCRoundedCornerTreatmentNew.h",
+    "src/components/Shapes/src/MDCCornerTreatment.h",
     "src/components/Shapes/src/MDCCornerTreatment.m",
-    "src/components/Shapes/src/MDCCornerTreatmentNew.h",
+    "src/components/Shapes/src/MDCEdgeTreatment.h",
     "src/components/Shapes/src/MDCEdgeTreatment.m",
-    "src/components/Shapes/src/MDCEdgeTreatmentNew.h",
+    "src/components/Shapes/src/MDCPathGenerator.h",
     "src/components/Shapes/src/MDCPathGenerator.m",
-    "src/components/Shapes/src/MDCPathGeneratorNew.h",
+    "src/components/Shapes/src/MDCRectangleShapeGenerator.h",
     "src/components/Shapes/src/MDCRectangleShapeGenerator.m",
-    "src/components/Shapes/src/MDCRectangleShapeGeneratorNew.h",
+    "src/components/Shapes/src/MDCShapedShadowLayer.h",
     "src/components/Shapes/src/MDCShapedShadowLayer.m",
-    "src/components/Shapes/src/MDCShapedShadowLayerNew.h",
+    "src/components/Shapes/src/MDCShapedView.h",
     "src/components/Shapes/src/MDCShapedView.m",
-    "src/components/Shapes/src/MDCShapedViewNew.h",
-    "src/components/Shapes/src/MaterialShapesNew.h",
+    "src/components/Shapes/src/MaterialShapes.h",
     "src/components/Snackbar/src/MDCSnackbarManager.h",
     "src/components/Snackbar/src/MDCSnackbarManager.m",
     "src/components/Snackbar/src/MDCSnackbarMessage.h",
diff --git a/ios/web/navigation/legacy_navigation_manager_impl.mm b/ios/web/navigation/legacy_navigation_manager_impl.mm
index 0b6fccdbe..a51f21c 100644
--- a/ios/web/navigation/legacy_navigation_manager_impl.mm
+++ b/ios/web/navigation/legacy_navigation_manager_impl.mm
@@ -158,15 +158,7 @@
 }
 
 int LegacyNavigationManagerImpl::GetPendingItemIndex() const {
-  if (GetPendingItem()) {
-    if ([session_controller_ pendingItemIndex] != -1) {
-      return [session_controller_ pendingItemIndex];
-    }
-    // TODO(crbug.com/665189): understand why last committed item index is
-    // returned here.
-    return GetLastCommittedItemIndex();
-  }
-  return -1;
+  return [session_controller_ pendingItemIndex];
 }
 
 int LegacyNavigationManagerImpl::
@@ -358,7 +350,7 @@
 }
 
 void LegacyNavigationManagerImpl::SetPendingItemIndex(int index) {
-  NOTREACHED();
+  session_controller_.pendingItemIndex = index;
 }
 
 }  // namespace web
diff --git a/ios/web/navigation/navigation_manager_impl_unittest.mm b/ios/web/navigation/navigation_manager_impl_unittest.mm
index ebc6f75..530d791 100644
--- a/ios/web/navigation/navigation_manager_impl_unittest.mm
+++ b/ios/web/navigation/navigation_manager_impl_unittest.mm
@@ -236,8 +236,7 @@
   EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
 }
 
-// Tests that GetPendingItemIndex() returns current item index if there is a
-// pending entry.
+// Tests that GetPendingItemIndex() returns -1 if there is a pending item.
 TEST_P(NavigationManagerTest, GetPendingItemIndexWithPendingEntry) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
@@ -251,6 +250,19 @@
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
       web::NavigationInitiationType::BROWSER_INITIATED,
       web::NavigationManager::UserAgentOverrideOption::INHERIT);
+  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
+}
+
+// Tests that setting and getting PendingItemIndex.
+TEST_P(NavigationManagerTest, SetAndGetPendingItemIndex) {
+  navigation_manager()->AddPendingItem(
+      GURL("http://www.url.test"), Referrer(), ui::PAGE_TRANSITION_TYPED,
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      web::NavigationManager::UserAgentOverrideOption::INHERIT);
+  [mock_wk_list_ setCurrentURL:@"http://www.url.test"];
+  navigation_manager()->CommitPendingItem();
+
+  navigation_manager()->SetPendingItemIndex(0);
   EXPECT_EQ(0, navigation_manager()->GetPendingItemIndex());
 }
 
diff --git a/ios/web/navigation/wk_based_navigation_manager_impl.mm b/ios/web/navigation/wk_based_navigation_manager_impl.mm
index 3ab5ec5..c4f53648 100644
--- a/ios/web/navigation/wk_based_navigation_manager_impl.mm
+++ b/ios/web/navigation/wk_based_navigation_manager_impl.mm
@@ -372,15 +372,9 @@
 }
 
 int WKBasedNavigationManagerImpl::GetPendingItemIndex() const {
-  if (GetPendingItem()) {
-    if (pending_item_index_ != -1) {
-      return pending_item_index_;
-    }
-    // TODO(crbug.com/665189): understand why last committed item index is
-    // returned here.
-    return GetLastCommittedItemIndex();
-  }
-  return -1;
+  if (is_restore_session_in_progress_)
+    return -1;
+  return pending_item_index_;
 }
 
 bool WKBasedNavigationManagerImpl::RemoveItemAtIndex(int index) {
diff --git a/ios/web/shell/test/BUILD.gn b/ios/web/shell/test/BUILD.gn
index e05de3f..a52d68d 100644
--- a/ios/web/shell/test/BUILD.gn
+++ b/ios/web/shell/test/BUILD.gn
@@ -124,8 +124,6 @@
     "earl_grey/web_shell_test_case.mm",
   ]
 
-  include_dirs = [ "//ios/third_party/edo/src" ]
-
   deps = [
     "//ios/testing/earl_grey:eg_test_support+eg2",
     "//ios/third_party/earl_grey2:test_lib",
@@ -144,8 +142,6 @@
     "web_shell_sample_egtest.mm",
   ]
 
-  include_dirs = [ "//ios/third_party/edo/src" ]
-
   deps = [
     ":eg_test_support+eg2",
     "//ios/third_party/earl_grey2:test_lib",
diff --git a/ios/web/shell/test/earl_grey/web_shell_test_case.mm b/ios/web/shell/test/earl_grey/web_shell_test_case.mm
index bf79531..8827aee 100644
--- a/ios/web/shell/test/earl_grey/web_shell_test_case.mm
+++ b/ios/web/shell/test/earl_grey/web_shell_test_case.mm
@@ -4,17 +4,13 @@
 
 #import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 
+#import "ios/testing/earl_grey/earl_grey_test.h"
 #import "ios/web/public/test/http_server/http_server.h"
 
 #if defined(CHROME_EARL_GREY_1)
-#import <EarlGrey/EarlGrey.h>           // nogncheck
 #include "testing/coverage_util_ios.h"  // nogncheck
 #endif
 
-#if defined(CHROME_EARL_GREY_2)
-#import "ios/third_party/earl_grey2/src/TestLib/EarlGreyImpl/EarlGrey.h"  // nogncheck
-#endif
-
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
diff --git a/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm b/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm
index b65fde8..8fd166a89 100644
--- a/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm
+++ b/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm
@@ -54,7 +54,7 @@
       WebViewBrowserState::FromBrowserState(context);
   auto local_device_info_provider =
       std::make_unique<syncer::LocalDeviceInfoProviderImpl>(
-          version_info::Channel::UNKNOWN, version_info::GetVersionNumber(),
+          version_info::Channel::STABLE, version_info::GetVersionNumber(),
           ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET,
           /*signin_scoped_device_id_callback=*/
           base::BindRepeating(&signin::GetSigninScopedDeviceId,
diff --git a/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm b/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm
index f9e79001..ebf00175 100644
--- a/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm
+++ b/ios/web_view/internal/sync/web_view_gcm_profile_service_factory.mm
@@ -98,7 +98,7 @@
       base::BindRepeating(&RequestProxyResolvingSocketFactory, context),
       browser_state->GetSharedURLLoaderFactory(),
       ApplicationContext::GetInstance()->GetNetworkConnectionTracker(),
-      version_info::Channel::UNKNOWN, GetProductCategoryForSubtypes(),
+      version_info::Channel::STABLE, GetProductCategoryForSubtypes(),
       WebViewIdentityManagerFactory::GetForBrowserState(browser_state),
       base::WrapUnique(new gcm::GCMClientFactory),
       base::CreateSingleThreadTaskRunnerWithTraits({web::WebThread::UI}),
diff --git a/ios/web_view/internal/sync/web_view_sync_client.mm b/ios/web_view/internal/sync/web_view_sync_client.mm
index 35c88a3..33003c7 100644
--- a/ios/web_view/internal/sync/web_view_sync_client.mm
+++ b/ios/web_view/internal/sync/web_view_sync_client.mm
@@ -81,8 +81,7 @@
       browser_state_, ServiceAccessType::IMPLICIT_ACCESS);
 
   component_factory_.reset(new browser_sync::ProfileSyncComponentsFactoryImpl(
-      this, version_info::Channel::UNKNOWN,
-      prefs::kSavingBrowserHistoryDisabled,
+      this, version_info::Channel::STABLE, prefs::kSavingBrowserHistoryDisabled,
       base::CreateSingleThreadTaskRunnerWithTraits({web::WebThread::UI}),
       db_thread_, profile_web_data_service_, account_web_data_service_,
       password_store_,
diff --git a/media/base/android/BUILD.gn b/media/base/android/BUILD.gn
index d7f7763..2e10ae7f 100644
--- a/media/base/android/BUILD.gn
+++ b/media/base/android/BUILD.gn
@@ -154,6 +154,13 @@
     ]
   }
 
+  java_cpp_strings("java_switches") {
+    sources = [
+      "//media/base/media_switches.cc",
+    ]
+    template = "//media/base/android/java_templates/MediaSwitches.java.tmpl"
+  }
+
   android_resources("media_java_resources") {
     custom_package = "org.chromium.media"
     resource_dirs = [ "java/res" ]
@@ -167,6 +174,7 @@
     ]
     srcjar_deps = [
       ":java_enums",
+      ":java_switches",
       "//media/base:java_enums",
     ]
     java_files = [
@@ -187,7 +195,6 @@
       "java/src/org/chromium/media/MediaPlayerBridge.java",
       "java/src/org/chromium/media/MediaPlayerListener.java",
       "java/src/org/chromium/media/MediaServerCrashListener.java",
-      "java/src/org/chromium/media/MediaSwitches.java",
     ]
   }
 
diff --git a/media/base/android/java/src/org/chromium/media/MediaSwitches.java b/media/base/android/java_templates/MediaSwitches.java.tmpl
similarity index 86%
rename from media/base/android/java/src/org/chromium/media/MediaSwitches.java
rename to media/base/android/java_templates/MediaSwitches.java.tmpl
index 4f11b45..bac4c74 100644
--- a/media/base/android/java/src/org/chromium/media/MediaSwitches.java
+++ b/media/base/android/java_templates/MediaSwitches.java.tmpl
@@ -7,7 +7,7 @@
 /**
  * Contains command line switches that are specific to the media layer.
  */
-public abstract class MediaSwitches {
+public abstract class MediaSwitches {{
     // Set the autoplay policy to ignore user gesture requirements
     public static final String AUTOPLAY_NO_GESTURE_REQUIRED_POLICY =
             "autoplay-policy=no-user-gesture-required";
@@ -15,6 +15,8 @@
     // TODO(819383): Remove this and its usage.
     public static final String USE_MODERN_MEDIA_CONTROLS = "UseModernMediaControls";
 
+{NATIVE_STRINGS}
+
     // Prevents instantiation.
-    private MediaSwitches() {}
-}
+    private MediaSwitches() {{}}
+}}
diff --git a/media/base/video_decoder_config.cc b/media/base/video_decoder_config.cc
index aff9a78b..77670d0 100644
--- a/media/base/video_decoder_config.cc
+++ b/media/base/video_decoder_config.cc
@@ -83,6 +83,11 @@
 
 VideoDecoderConfig::~VideoDecoderConfig() = default;
 
+void VideoDecoderConfig::set_color_space_info(
+    const VideoColorSpace& color_space) {
+  color_space_info_ = color_space;
+}
+
 const VideoColorSpace& VideoDecoderConfig::color_space_info() const {
   return color_space_info_;
 }
diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h
index fa52c39..bd65211 100644
--- a/media/base/video_decoder_config.h
+++ b/media/base/video_decoder_config.h
@@ -137,6 +137,7 @@
   }
 
   // Color space of the image data.
+  void set_color_space_info(const VideoColorSpace& color_space);
   const VideoColorSpace& color_space_info() const;
 
   // Dynamic range of the image data.
diff --git a/media/blink/multibuffer_data_source.cc b/media/blink/multibuffer_data_source.cc
index f02d270..f5e776f9 100644
--- a/media/blink/multibuffer_data_source.cc
+++ b/media/blink/multibuffer_data_source.cc
@@ -289,6 +289,10 @@
   return url_data_->is_cors_cross_origin();
 }
 
+bool MultibufferDataSource::HasAccessControl() const {
+  return url_data_->has_access_control();
+}
+
 UrlData::CorsMode MultibufferDataSource::cors_mode() const {
   return url_data_->cors_mode();
 }
diff --git a/media/blink/multibuffer_data_source.h b/media/blink/multibuffer_data_source.h
index 51b3cb4c..9e3a38c0 100644
--- a/media/blink/multibuffer_data_source.h
+++ b/media/blink/multibuffer_data_source.h
@@ -82,6 +82,10 @@
   // This must be called after the response arrives.
   bool IsCorsCrossOrigin() const;
 
+  // Returns true if the response includes an Access-Control-Allow-Origin
+  // header (that is not "null").
+  bool HasAccessControl() const;
+
   // Returns the CorsMode of the underlying UrlData.
   UrlData::CorsMode cors_mode() const;
 
diff --git a/media/blink/resource_multibuffer_data_provider.cc b/media/blink/resource_multibuffer_data_provider.cc
index b79632c2..c455cdc 100644
--- a/media/blink/resource_multibuffer_data_provider.cc
+++ b/media/blink/resource_multibuffer_data_provider.cc
@@ -331,6 +331,18 @@
   destination_url_data->set_is_cors_cross_origin(
       network::cors::IsCorsCrossOriginResponseType(response_type));
 
+  // Only used for metrics.
+  {
+    WebString access_control =
+        response.HttpHeaderField("Access-Control-Allow-Origin");
+    if (!access_control.IsEmpty() && !access_control.Equals("null")) {
+      // Note: When |access_control| is not *, we should verify that it matches
+      // the requesting origin. Instead we just assume that it matches, which is
+      // probably accurate enough for metrics.
+      destination_url_data->set_has_access_control();
+    }
+  }
+
   if (destination_url_data != url_data_) {
     // At this point, we've encountered a redirect, or found a better url data
     // instance for the data that we're about to download.
diff --git a/media/blink/url_index.cc b/media/blink/url_index.cc
index d3c381a..fb940a4 100644
--- a/media/blink/url_index.cc
+++ b/media/blink/url_index.cc
@@ -49,6 +49,7 @@
     : url_(url),
       have_data_origin_(false),
       cors_mode_(cors_mode),
+      has_access_control_(false),
       url_index_(url_index),
       length_(kPositionNotSpecified),
       range_supported_(false),
@@ -90,6 +91,7 @@
     bytes_read_from_cache_ += other->bytes_read_from_cache_;
     // is_cors_corss_origin_ will not relax from true to false.
     set_is_cors_cross_origin(other->is_cors_cross_origin_);
+    has_access_control_ |= other->has_access_control_;
     multibuffer()->MergeFrom(other->multibuffer());
   }
 }
@@ -112,6 +114,10 @@
   is_cors_cross_origin_ = is_cors_cross_origin;
 }
 
+void UrlData::set_has_access_control() {
+  has_access_control_ = true;
+}
+
 void UrlData::RedirectTo(const scoped_refptr<UrlData>& url_data) {
   DCHECK(thread_checker_.CalledOnValidThread());
   // Copy any cached data over to the new location.
diff --git a/media/blink/url_index.h b/media/blink/url_index.h
index 1c933968..797b9e82 100644
--- a/media/blink/url_index.h
+++ b/media/blink/url_index.h
@@ -67,6 +67,8 @@
   // Cross-origin access mode
   CorsMode cors_mode() const { return cors_mode_; }
 
+  bool has_access_control() const { return has_access_control_; }
+
   // Are HTTP range requests supported?
   bool range_supported() const { return range_supported_; }
 
@@ -122,6 +124,7 @@
   void set_last_modified(base::Time last_modified);
   void set_etag(const std::string& etag);
   void set_is_cors_cross_origin(bool is_cors_cross_origin);
+  void set_has_access_control();
 
   // A redirect has occured (or we've found a better UrlData for the same
   // resource).
@@ -182,6 +185,7 @@
 
   // Cross-origin access mode.
   const CorsMode cors_mode_;
+  bool has_access_control_;
 
   UrlIndex* const url_index_;
 
@@ -197,7 +201,7 @@
   // Does the server support ranges?
   bool range_supported_;
 
-  // Set to false if we have reason to beleive the chrome disk cache
+  // Set to false if we have reason to believe the chrome disk cache
   // will not cache this url.
   bool cacheable_;
 
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index dc85f7e..e0c9a7e 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -1622,11 +1622,17 @@
   // URL, since MediaPlayer doesn't support data:// URLs, fail playback now.
   const bool found_hls = status == PipelineStatus::DEMUXER_ERROR_DETECTED_HLS;
   if (found_hls && mb_data_source_) {
+    demuxer_found_hls_ = true;
+
     UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin",
                           mb_data_source_->IsCorsCrossOrigin());
-    // Note: Does not consider the full redirect chain. Redirecting through
-    // another origin will set WouldTaintOrigin() though, assuming that the
-    // crossorigin attribute is not set.
+    if (mb_data_source_->IsCorsCrossOrigin()) {
+      UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.HasAccessControl",
+                            mb_data_source_->HasAccessControl());
+    }
+
+    // Note: Does not consider the full redirect chain, which could contain
+    // undetected mixed content.
     bool frame_url_is_cryptographic = url::Origin(frame_->GetSecurityOrigin())
                                           .GetURL()
                                           .SchemeIsCryptographic();
@@ -1636,10 +1642,6 @@
     UMA_HISTOGRAM_BOOLEAN(
         "Media.WebMediaPlayerImpl.HLS.IsMixedContent",
         frame_url_is_cryptographic && !manifest_url_is_cryptographic);
-    UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.WouldTaintOrigin",
-                          WouldTaintOrigin());
-    // Note: Affects WouldTaintOrigin().
-    demuxer_found_hls_ = true;
 
     renderer_factory_selector_->SetUseMediaPlayer(true);
 
diff --git a/media/gpu/android/avda_shared_state.cc b/media/gpu/android/avda_shared_state.cc
index ea79328..2d5e1c1b 100644
--- a/media/gpu/android/avda_shared_state.cc
+++ b/media/gpu/android/avda_shared_state.cc
@@ -48,6 +48,8 @@
 
 void AVDASharedState::UpdateTexImage() {
   texture_owner()->UpdateTexImage();
+  if (!texture_owner()->binds_texture_on_update())
+    texture_owner()->EnsureTexImageBound();
   // Helpfully, this is already column major.
   texture_owner()->GetTransformMatrix(gl_matrix_);
 }
diff --git a/media/gpu/android/codec_image.cc b/media/gpu/android/codec_image.cc
index 5cf2987..ba5dfb4 100644
--- a/media/gpu/android/codec_image.cc
+++ b/media/gpu/android/codec_image.cc
@@ -96,7 +96,7 @@
   if (bound_service_id != static_cast<GLint>(texture_owner_->GetTextureId()))
     return false;
 
-  RenderToTextureOwnerFrontBuffer(BindingsMode::kDontRestore);
+  RenderToTextureOwnerFrontBuffer(BindingsMode::kEnsureTexImageBound);
   return true;
 }
 
@@ -155,7 +155,7 @@
   // The matrix is available after we render to the front buffer. If that fails
   // we'll return the matrix from the previous frame, which is more likely to be
   // correct than the identity matrix anyway.
-  RenderToTextureOwnerFrontBuffer(BindingsMode::kDontRestore);
+  RenderToTextureOwnerFrontBuffer(BindingsMode::kDontRestoreIfBound);
   texture_owner_->GetTransformMatrix(matrix);
   YInvertMatrix(matrix);
 }
@@ -176,8 +176,10 @@
 }
 
 bool CodecImage::RenderToFrontBuffer() {
+  // This code is used to trigger early rendering of the image before it is used
+  // for compositing, there is no need to bind the image.
   return texture_owner_
-             ? RenderToTextureOwnerFrontBuffer(BindingsMode::kRestore)
+             ? RenderToTextureOwnerFrontBuffer(BindingsMode::kRestoreIfBound)
              : RenderToOverlay();
 }
 
@@ -202,13 +204,13 @@
   return true;
 }
 
-bool CodecImage::RenderToTextureOwnerFrontBuffer(BindingsMode bindings_mode,
-                                                 bool bind_egl_image) {
+bool CodecImage::RenderToTextureOwnerFrontBuffer(BindingsMode bindings_mode) {
   DCHECK(texture_owner_);
-  DCHECK(bind_egl_image);
 
-  if (phase_ == Phase::kInFrontBuffer)
+  if (phase_ == Phase::kInFrontBuffer) {
+    EnsureBoundIfNeeded(bindings_mode);
     return true;
+  }
   if (phase_ == Phase::kInvalidated)
     return false;
 
@@ -223,23 +225,31 @@
 
   std::unique_ptr<ui::ScopedMakeCurrent> scoped_make_current =
       MakeCurrentIfNeeded(texture_owner_.get());
-  // If we have to switch contexts, then we always want to restore the
-  // bindings. Also if bind_egl_image is set to false, we do no need to restore
-  // any bindings since UpdateTexImage will not bind any egl image to the
-  // texture target.
+  // If updating the image will implicitly update the texture bindings then
+  // restore if requested or the update needed a context switch.
   bool should_restore_bindings =
-      (bindings_mode == BindingsMode::kRestore || !!scoped_make_current) &&
-      bind_egl_image;
+      texture_owner_->binds_texture_on_update() &&
+      (bindings_mode == BindingsMode::kRestoreIfBound || !!scoped_make_current);
 
   GLint bound_service_id = 0;
   if (should_restore_bindings)
     glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &bound_service_id);
-  texture_owner_->UpdateTexImage(bind_egl_image);
+  texture_owner_->UpdateTexImage();
+  EnsureBoundIfNeeded(bindings_mode);
   if (should_restore_bindings)
     glBindTexture(GL_TEXTURE_EXTERNAL_OES, bound_service_id);
   return true;
 }
 
+void CodecImage::EnsureBoundIfNeeded(BindingsMode mode) {
+  DCHECK(texture_owner_);
+
+  if (texture_owner_->binds_texture_on_update() ||
+      mode != BindingsMode::kEnsureTexImageBound)
+    return;
+  texture_owner_->EnsureTexImageBound();
+}
+
 bool CodecImage::RenderToOverlay() {
   if (phase_ == Phase::kInFrontBuffer)
     return true;
@@ -263,7 +273,7 @@
 CodecImage::GetAHardwareBuffer() {
   DCHECK(texture_owner_);
 
-  RenderToTextureOwnerFrontBuffer(BindingsMode::kDontRestore);
+  RenderToTextureOwnerFrontBuffer(BindingsMode::kDontRestoreIfBound);
   return texture_owner_->GetAHardwareBuffer();
 }
 
diff --git a/media/gpu/android/codec_image.h b/media/gpu/android/codec_image.h
index f435e1b..afbcf6e 100644
--- a/media/gpu/android/codec_image.h
+++ b/media/gpu/android/codec_image.h
@@ -106,17 +106,22 @@
 
   // Renders this image to the texture owner front buffer by first rendering
   // it to the back buffer if it's not already there, and then waiting for the
-  // frame available event before calling UpdateTexImage(). Passing
-  // BindingsMode::kDontRestore skips the work of restoring the current texture
-  // bindings if the texture owner's context is already current. Otherwise,
-  // this switches contexts and preserves the texture bindings. Setting
-  // |bind_egl_image| = false skips doing the egl bindings to the texture target
-  // during texture update. |bind_egl_image| must always be true when using a
-  // SurfaceTexture backed CodecImage(TextureOwner). Returns true if the buffer
-  // is in the front buffer. Returns false if the buffer was invalidated.
-  enum class BindingsMode { kRestore, kDontRestore };
-  bool RenderToTextureOwnerFrontBuffer(BindingsMode bindings_mode,
-                                       bool bind_egl_image = true);
+  // frame available event before calling UpdateTexImage().
+  enum class BindingsMode {
+    // Ensures that the TextureOwner's texture is bound to the latest image, if
+    // it requires explicit binding.
+    kEnsureTexImageBound,
+
+    // Updates the current image but does not bind it. If updating the image
+    // implicitly binds the texture, the current bindings will be restored.
+    kRestoreIfBound,
+
+    // Updates the current image but does not bind it. If updating the image
+    // implicitly binds the texture, the current bindings will not be restored.
+    kDontRestoreIfBound
+  };
+  bool RenderToTextureOwnerFrontBuffer(BindingsMode bindings_mode);
+  void EnsureBoundIfNeeded(BindingsMode mode);
 
   // Renders this image to the overlay. Returns true if the buffer is in the
   // overlay front buffer. Returns false if the buffer was invalidated.
diff --git a/media/gpu/android/codec_image_unittest.cc b/media/gpu/android/codec_image_unittest.cc
index a0f9027..42794da 100644
--- a/media/gpu/android/codec_image_unittest.cc
+++ b/media/gpu/android/codec_image_unittest.cc
@@ -61,8 +61,8 @@
     // The tests rely on this texture being bound.
     glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id_);
 
-    texture_owner_ = new NiceMock<MockTextureOwner>(texture_id_, context_.get(),
-                                                    surface_.get());
+    texture_owner_ = new NiceMock<MockTextureOwner>(
+        texture_id_, context_.get(), surface_.get(), BindsTextureOnUpdate());
   }
 
   void TearDown() override {
@@ -90,6 +90,8 @@
     return image;
   }
 
+  virtual bool BindsTextureOnUpdate() { return true; }
+
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   NiceMock<MockMediaCodecBridge>* codec_;
   std::unique_ptr<CodecWrapper> wrapper_;
@@ -107,6 +109,10 @@
   PromotionHintReceiver promotion_hint_receiver_;
 };
 
+class CodecImageTestExplicitBind : public CodecImageTest {
+  bool BindsTextureOnUpdate() override { return false; }
+};
+
 TEST_F(CodecImageTest, DestructionCbRuns) {
   base::MockCallback<CodecImage::DestructionCb> cb;
   auto i = NewImage(kOverlay, cb.Get());
@@ -156,7 +162,19 @@
   InSequence s;
   EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true));
   EXPECT_CALL(*texture_owner_, WaitForFrameAvailable());
-  EXPECT_CALL(*texture_owner_, UpdateTexImage(true));
+  EXPECT_CALL(*texture_owner_, UpdateTexImage());
+  i->CopyTexImage(GL_TEXTURE_EXTERNAL_OES);
+  ASSERT_TRUE(i->was_rendered_to_front_buffer());
+}
+
+TEST_F(CodecImageTestExplicitBind, CopyTexImageTriggersFrontBufferRendering) {
+  auto i = NewImage(kTextureOwner);
+  // Verify that the release comes before the wait.
+  InSequence s;
+  EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true));
+  EXPECT_CALL(*texture_owner_, WaitForFrameAvailable());
+  EXPECT_CALL(*texture_owner_, UpdateTexImage());
+  EXPECT_CALL(*texture_owner_, EnsureTexImageBound());
   i->CopyTexImage(GL_TEXTURE_EXTERNAL_OES);
   ASSERT_TRUE(i->was_rendered_to_front_buffer());
 }
@@ -166,7 +184,23 @@
   InSequence s;
   EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true));
   EXPECT_CALL(*texture_owner_, WaitForFrameAvailable());
-  EXPECT_CALL(*texture_owner_, UpdateTexImage(true));
+  EXPECT_CALL(*texture_owner_, UpdateTexImage());
+  EXPECT_CALL(*texture_owner_, GetTransformMatrix(_));
+  float matrix[16];
+  i->GetTextureMatrix(matrix);
+  ASSERT_TRUE(i->was_rendered_to_front_buffer());
+}
+
+TEST_F(CodecImageTestExplicitBind,
+       GetTextureMatrixTriggersFrontBufferRendering) {
+  // GetTextureMatrix should not bind the image.
+  texture_owner_->expect_update_tex_image = false;
+
+  auto i = NewImage(kTextureOwner);
+  InSequence s;
+  EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true));
+  EXPECT_CALL(*texture_owner_, WaitForFrameAvailable());
+  EXPECT_CALL(*texture_owner_, UpdateTexImage());
   EXPECT_CALL(*texture_owner_, GetTransformMatrix(_));
   float matrix[16];
   i->GetTextureMatrix(matrix);
@@ -245,7 +279,21 @@
   glGenTextures(1, &pre_bound_texture);
   glBindTexture(GL_TEXTURE_EXTERNAL_OES, pre_bound_texture);
   auto i = NewImage(kTextureOwner);
-  EXPECT_CALL(*texture_owner_, UpdateTexImage(true));
+  EXPECT_CALL(*texture_owner_, UpdateTexImage());
+  i->RenderToFrontBuffer();
+  GLint post_bound_texture = 0;
+  glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &post_bound_texture);
+  ASSERT_EQ(pre_bound_texture, static_cast<GLuint>(post_bound_texture));
+}
+
+TEST_F(CodecImageTestExplicitBind, RenderToFrontBufferDoesNotBindTexture) {
+  texture_owner_->expect_update_tex_image = false;
+
+  GLuint pre_bound_texture = 0;
+  glGenTextures(1, &pre_bound_texture);
+  glBindTexture(GL_TEXTURE_EXTERNAL_OES, pre_bound_texture);
+  auto i = NewImage(kTextureOwner);
+  EXPECT_CALL(*texture_owner_, UpdateTexImage());
   i->RenderToFrontBuffer();
   GLint post_bound_texture = 0;
   glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &post_bound_texture);
@@ -264,7 +312,7 @@
 
   auto i = NewImage(kTextureOwner);
   // Our context should not be current when UpdateTexImage() is called.
-  EXPECT_CALL(*texture_owner_, UpdateTexImage(true)).WillOnce(Invoke([&](bool) {
+  EXPECT_CALL(*texture_owner_, UpdateTexImage()).WillOnce(Invoke([&]() {
     ASSERT_FALSE(context->IsCurrent(surface.get()));
   }));
   i->RenderToFrontBuffer();
@@ -299,7 +347,7 @@
   EXPECT_EQ(texture_owner_->get_a_hardware_buffer_count, 0);
   EXPECT_FALSE(i->was_rendered_to_front_buffer());
 
-  EXPECT_CALL(*texture_owner_, UpdateTexImage(true));
+  EXPECT_CALL(*texture_owner_, UpdateTexImage());
   i->GetAHardwareBuffer();
   EXPECT_EQ(texture_owner_->get_a_hardware_buffer_count, 1);
   EXPECT_TRUE(i->was_rendered_to_front_buffer());
diff --git a/media/gpu/android/image_reader_gl_owner.cc b/media/gpu/android/image_reader_gl_owner.cc
index abc0418..5faf959 100644
--- a/media/gpu/android/image_reader_gl_owner.cc
+++ b/media/gpu/android/image_reader_gl_owner.cc
@@ -77,7 +77,8 @@
 ImageReaderGLOwner::ImageReaderGLOwner(
     std::unique_ptr<gpu::gles2::AbstractTexture> texture,
     Mode mode)
-    : TextureOwner(std::move(texture)),
+    : TextureOwner(false /* binds_texture_on_image_update */,
+                   std::move(texture)),
       current_image_(nullptr),
       loader_(base::android::AndroidImageReader::GetInstance()),
       context_(gl::GLContext::GetCurrent()),
@@ -185,7 +186,7 @@
   return gl::ScopedJavaSurface::AcquireExternalSurface(j_surface);
 }
 
-void ImageReaderGLOwner::UpdateTexImage(bool bind_egl_image) {
+void ImageReaderGLOwner::UpdateTexImage() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   // If we've lost the texture, then do nothing.
@@ -250,13 +251,6 @@
   current_image_ = image;
   current_image_fence_ = std::move(scoped_acquire_fence_fd);
   current_image_bound_ = false;
-
-  // Skip generating and binding egl image if bind_egl_image is false.
-  if (bind_egl_image) {
-    // TODO(khushalsagar): This should be on the public API so that we only bind
-    // the texture if we were going to render it without an overlay.
-    EnsureTexImageBound();
-  }
 }
 
 void ImageReaderGLOwner::EnsureTexImageBound() {
diff --git a/media/gpu/android/image_reader_gl_owner.h b/media/gpu/android/image_reader_gl_owner.h
index d52f73f..4ad5a35 100644
--- a/media/gpu/android/image_reader_gl_owner.h
+++ b/media/gpu/android/image_reader_gl_owner.h
@@ -34,7 +34,8 @@
   gl::GLContext* GetContext() const override;
   gl::GLSurface* GetSurface() const override;
   gl::ScopedJavaSurface CreateJavaSurface() const override;
-  void UpdateTexImage(bool bind_egl_image) override;
+  void UpdateTexImage() override;
+  void EnsureTexImageBound() override;
   void GetTransformMatrix(float mtx[16]) override;
   void ReleaseBackBuffers() override;
   void SetReleaseTimeToNow() override;
@@ -62,8 +63,6 @@
   // error.
   bool MaybeDeleteCurrentImage();
 
-  void EnsureTexImageBound();
-
   // Releases an external ref on the image, with the fence that must be signaled
   // before the |image| can be resued by the AImageReader.
   void ReleaseRefOnImage(AImage* image, base::ScopedFD fence_fd);
diff --git a/media/gpu/android/mock_texture_owner.cc b/media/gpu/android/mock_texture_owner.cc
index 9cb0b23..037ccd2 100644
--- a/media/gpu/android/mock_texture_owner.cc
+++ b/media/gpu/android/mock_texture_owner.cc
@@ -13,11 +13,14 @@
 
 MockTextureOwner::MockTextureOwner(GLuint fake_texture_id,
                                    gl::GLContext* fake_context,
-                                   gl::GLSurface* fake_surface)
-    : TextureOwner(std::make_unique<MockAbstractTexture>(fake_texture_id)),
+                                   gl::GLSurface* fake_surface,
+                                   bool binds_texture_on_update)
+    : TextureOwner(binds_texture_on_update,
+                   std::make_unique<MockAbstractTexture>(fake_texture_id)),
       fake_context(fake_context),
       fake_surface(fake_surface),
-      expecting_frame_available(false) {
+      expecting_frame_available(false),
+      expect_update_tex_image(!binds_texture_on_update) {
   ON_CALL(*this, GetTextureId()).WillByDefault(Return(fake_texture_id));
   ON_CALL(*this, GetContext()).WillByDefault(Return(fake_context));
   ON_CALL(*this, GetSurface()).WillByDefault(Return(fake_surface));
@@ -31,6 +34,9 @@
   ON_CALL(*this, WaitForFrameAvailable())
       .WillByDefault(
           Invoke(this, &MockTextureOwner::FakeWaitForFrameAvailable));
+  ON_CALL(*this, EnsureTexImageBound()).WillByDefault(Invoke([this] {
+    CHECK(expect_update_tex_image);
+  }));
 }
 
 MockTextureOwner::~MockTextureOwner() {
diff --git a/media/gpu/android/mock_texture_owner.h b/media/gpu/android/mock_texture_owner.h
index adcff47..c5f06c0b 100644
--- a/media/gpu/android/mock_texture_owner.h
+++ b/media/gpu/android/mock_texture_owner.h
@@ -22,13 +22,15 @@
  public:
   MockTextureOwner(GLuint fake_texture_id,
                    gl::GLContext* fake_context,
-                   gl::GLSurface* fake_surface);
+                   gl::GLSurface* fake_surface,
+                   bool binds_texture_on_update = false);
 
   MOCK_CONST_METHOD0(GetTextureId, GLuint());
   MOCK_CONST_METHOD0(GetContext, gl::GLContext*());
   MOCK_CONST_METHOD0(GetSurface, gl::GLSurface*());
   MOCK_CONST_METHOD0(CreateJavaSurface, gl::ScopedJavaSurface());
-  MOCK_METHOD1(UpdateTexImage, void(bool bind_egl_image));
+  MOCK_METHOD0(UpdateTexImage, void());
+  MOCK_METHOD0(EnsureTexImageBound, void());
   MOCK_METHOD1(GetTransformMatrix, void(float mtx[16]));
   MOCK_METHOD0(ReleaseBackBuffers, void());
   MOCK_METHOD0(SetReleaseTimeToNow, void());
@@ -53,6 +55,7 @@
   gl::GLSurface* fake_surface;
   bool expecting_frame_available;
   int get_a_hardware_buffer_count = 0;
+  bool expect_update_tex_image;
 
  protected:
   ~MockTextureOwner();
diff --git a/media/gpu/android/surface_texture_gl_owner.cc b/media/gpu/android/surface_texture_gl_owner.cc
index af0da9b..f19f2ad4 100644
--- a/media/gpu/android/surface_texture_gl_owner.cc
+++ b/media/gpu/android/surface_texture_gl_owner.cc
@@ -37,7 +37,7 @@
 
 SurfaceTextureGLOwner::SurfaceTextureGLOwner(
     std::unique_ptr<gpu::gles2::AbstractTexture> texture)
-    : TextureOwner(std::move(texture)),
+    : TextureOwner(true /*binds_texture_on_update */, std::move(texture)),
       surface_texture_(gl::SurfaceTexture::Create(GetTextureId())),
       context_(gl::GLContext::GetCurrent()),
       surface_(gl::GLSurface::GetCurrent()),
@@ -68,15 +68,16 @@
   return gl::ScopedJavaSurface(surface_texture_.get());
 }
 
-// bind_egl_image is a no-op for surface texture since it always binds the egl
-// image under the hood.
-void SurfaceTextureGLOwner::UpdateTexImage(bool bind_egl_image) {
+void SurfaceTextureGLOwner::UpdateTexImage() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(bind_egl_image);
   if (surface_texture_)
     surface_texture_->UpdateTexImage();
 }
 
+void SurfaceTextureGLOwner::EnsureTexImageBound() {
+  NOTREACHED();
+}
+
 void SurfaceTextureGLOwner::GetTransformMatrix(float mtx[]) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // If we don't have a SurfaceTexture, then the matrix doesn't matter.  We
diff --git a/media/gpu/android/surface_texture_gl_owner.h b/media/gpu/android/surface_texture_gl_owner.h
index a65c48e..f2634aaac 100644
--- a/media/gpu/android/surface_texture_gl_owner.h
+++ b/media/gpu/android/surface_texture_gl_owner.h
@@ -33,7 +33,8 @@
   gl::GLContext* GetContext() const override;
   gl::GLSurface* GetSurface() const override;
   gl::ScopedJavaSurface CreateJavaSurface() const override;
-  void UpdateTexImage(bool bind_egl_image) override;
+  void UpdateTexImage() override;
+  void EnsureTexImageBound() override;
   void GetTransformMatrix(float mtx[16]) override;
   void ReleaseBackBuffers() override;
   void SetReleaseTimeToNow() override;
diff --git a/media/gpu/android/texture_owner.cc b/media/gpu/android/texture_owner.cc
index bf2f4ee8..60b57ffe 100644
--- a/media/gpu/android/texture_owner.cc
+++ b/media/gpu/android/texture_owner.cc
@@ -15,9 +15,11 @@
 
 namespace media {
 
-TextureOwner::TextureOwner(std::unique_ptr<gpu::gles2::AbstractTexture> texture)
+TextureOwner::TextureOwner(bool binds_texture_on_update,
+                           std::unique_ptr<gpu::gles2::AbstractTexture> texture)
     : base::RefCountedDeleteOnSequence<TextureOwner>(
           base::ThreadTaskRunnerHandle::Get()),
+      binds_texture_on_update_(binds_texture_on_update),
       texture_(std::move(texture)),
       task_runner_(base::ThreadTaskRunnerHandle::Get()) {
   // Notify the subclass when the texture is destroyed.
diff --git a/media/gpu/android/texture_owner.h b/media/gpu/android/texture_owner.h
index 329ccd1..c8d59ad2 100644
--- a/media/gpu/android/texture_owner.h
+++ b/media/gpu/android/texture_owner.h
@@ -73,9 +73,12 @@
   virtual gl::ScopedJavaSurface CreateJavaSurface() const = 0;
 
   // Update the texture image using the latest available image data.
-  // |bind_egl_image| hints the underlying implementation whether an egl image
-  // should be bound to the texture target or not.
-  virtual void UpdateTexImage(bool bind_egl_image = true) = 0;
+  virtual void UpdateTexImage() = 0;
+
+  // Ensures that the latest texture image is bound to the texture target.
+  // Should only be used if the TextureOwner requires explicit binding of the
+  // image after an update.
+  virtual void EnsureTexImageBound() = 0;
 
   // Transformation matrix if any associated with the texture image.
   virtual void GetTransformMatrix(float mtx[16]) = 0;
@@ -106,12 +109,15 @@
   virtual std::unique_ptr<base::android::ScopedHardwareBufferFenceSync>
   GetAHardwareBuffer() = 0;
 
+  bool binds_texture_on_update() const { return binds_texture_on_update_; }
+
  protected:
   friend class base::RefCountedDeleteOnSequence<TextureOwner>;
   friend class base::DeleteHelper<TextureOwner>;
 
   // |texture| is the texture that we'll own.
-  TextureOwner(std::unique_ptr<gpu::gles2::AbstractTexture> texture);
+  TextureOwner(bool binds_texture_on_update,
+               std::unique_ptr<gpu::gles2::AbstractTexture> texture);
   virtual ~TextureOwner();
 
   // Drop |texture_| immediately.  Will call OnTextureDestroyed immediately if
@@ -128,6 +134,10 @@
   gpu::gles2::AbstractTexture* texture() const { return texture_.get(); }
 
  private:
+  // Set to true if the updating the image for this owner will automatically
+  // bind it to the texture target.
+  const bool binds_texture_on_update_;
+
   std::unique_ptr<gpu::gles2::AbstractTexture> texture_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
diff --git a/media/gpu/ipc/service/vda_video_decoder.cc b/media/gpu/ipc/service/vda_video_decoder.cc
index 2b94759..4daed39 100644
--- a/media/gpu/ipc/service/vda_video_decoder.cc
+++ b/media/gpu/ipc/service/vda_video_decoder.cc
@@ -277,11 +277,17 @@
       false;
 #endif
 
+  // Hardware decoders require ColorSpace to be set beforehand to provide
+  // correct HDR output.
+  const bool is_hdr_color_space_change =
+      config_.profile() == media::VP9PROFILE_PROFILE2 &&
+      config_.color_space_info() != config.color_space_info();
+
   // The configuration is supported.
   config_ = config;
 
   if (reinitializing) {
-    if (is_profile_change) {
+    if (is_profile_change || is_hdr_color_space_change) {
       MEDIA_LOG(INFO, media_log_) << "Reinitializing video decode accelerator "
                                   << "for profile change";
       gpu_task_runner_->PostTask(
diff --git a/media/gpu/video_decode_accelerator_tests.cc b/media/gpu/video_decode_accelerator_tests.cc
index 137d1b43..421fe56 100644
--- a/media/gpu/video_decode_accelerator_tests.cc
+++ b/media/gpu/video_decode_accelerator_tests.cc
@@ -98,28 +98,6 @@
   EXPECT_TRUE(tvp->WaitForFrameProcessors());
 }
 
-// Flush the decoder immediately after doing a mid-stream reset, without waiting
-// for a kResetDone event.
-TEST_F(VideoDecoderTest, FlushBeforeResetDone) {
-  auto tvp = CreateVideoPlayer(g_env->video_);
-
-  tvp->Play();
-  EXPECT_TRUE(tvp->WaitForFrameDecoded(g_env->video_->NumFrames() / 2));
-  tvp->Reset();
-  tvp->Flush();
-  EXPECT_TRUE(tvp->WaitForResetDone());
-  EXPECT_TRUE(tvp->WaitForFlushDone());
-
-  // As flush doesn't cancel reset, we should have received a single kResetDone
-  // and kFlushDone event. We didn't decode the entire video, but more frames
-  // might be decoded by the time we called reset, so we can only check whether
-  // the decoded frame count is <= the total number of frames.
-  EXPECT_EQ(tvp->GetResetDoneCount(), 1u);
-  EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
-  EXPECT_LE(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
-  EXPECT_TRUE(tvp->WaitForFrameProcessors());
-}
-
 // Reset the decoder immediately after initialization.
 TEST_F(VideoDecoderTest, ResetAfterInitialize) {
   auto tvp = CreateVideoPlayer(g_env->video_);
diff --git a/media/learning/common/learning_task_controller.h b/media/learning/common/learning_task_controller.h
index 7af3f63b..65cd91f6 100644
--- a/media/learning/common/learning_task_controller.h
+++ b/media/learning/common/learning_task_controller.h
@@ -14,6 +14,21 @@
 namespace media {
 namespace learning {
 
+// Wrapper struct for completing an observation via LearningTaskController.
+// Most callers will just send in a TargetValue, so this lets us provide a
+// default weight.  Further, a few callers will add optional data, like the UKM
+// SourceId, which most callers don't care about.
+struct ObservationCompletion {
+  ObservationCompletion() = default;
+  /* implicit */ ObservationCompletion(const TargetValue& target)
+      : target_value(target) {}
+  ObservationCompletion(const TargetValue& target, WeightType w)
+      : target_value(target), weight(w) {}
+
+  TargetValue target_value;
+  WeightType weight = 1u;
+};
+
 // Client for a single learning task.  Intended to be the primary API for client
 // code that generates FeatureVectors / requests predictions for a single task.
 // The API supports sending in an observed FeatureVector without a target value,
@@ -25,10 +40,12 @@
   LearningTaskController() = default;
   virtual ~LearningTaskController() = default;
 
-  // Used to set the target value and example weight.
-  using SetTargetValueCB = base::OnceCallback<void(TargetValue, WeightType)>;
+  // TODO(liberato): what is the scope of this id?  can it be local to whoever
+  // owns the LTC?  otherwise, consider making it an unguessable token.
+  // TODO(liberato): consider making a special id that means "i will not send a
+  // target value", to save a call to CancelObservation.
+  using ObservationId = int32_t;
 
-  // Record a FeatureVector that may be used for prediction and / or adding a
   // new example.  Call this at the time one would try to predict the
   // TargetValue.  This lets the framework snapshot any framework-provided
   // feature values at prediction time.  Later, if you want to turn these
@@ -39,7 +56,15 @@
   // TODO(liberato): See if this ends up generating smaller code with pass-by-
   // value or with |FeatureVector&&|, once we have callers that can actually
   // benefit from it.
-  virtual SetTargetValueCB BeginObservation(const FeatureVector& features) = 0;
+  virtual void BeginObservation(ObservationId id,
+                                const FeatureVector& features) = 0;
+
+  // Complete an observation by sending a completion.
+  virtual void CompleteObservation(ObservationId id,
+                                   const ObservationCompletion& completion) = 0;
+
+  // Notify the LTC that no completion will be sent.
+  virtual void CancelObservation(ObservationId id) = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LearningTaskController);
diff --git a/media/learning/impl/learning_session_impl.cc b/media/learning/impl/learning_session_impl.cc
index 176cb00..4ad7de4 100644
--- a/media/learning/impl/learning_session_impl.cc
+++ b/media/learning/impl/learning_session_impl.cc
@@ -37,8 +37,10 @@
   if (iter != task_map_.end()) {
     // TODO(liberato): We shouldn't be adding examples.  We should provide the
     // LearningTaskController instead, although ownership gets a bit weird.
-    iter->second->BeginObservation(example.features)
-        .Run(example.target_value, example.weight);
+    LearningTaskController::ObservationId id = 1;
+    iter->second->BeginObservation(id, example.features);
+    iter->second->CompleteObservation(
+        id, ObservationCompletion(example.target_value, example.weight));
   }
 }
 
diff --git a/media/learning/impl/learning_session_impl_unittest.cc b/media/learning/impl/learning_session_impl_unittest.cc
index 5cc7ac03..5c55d66 100644
--- a/media/learning/impl/learning_session_impl_unittest.cc
+++ b/media/learning/impl/learning_session_impl_unittest.cc
@@ -32,20 +32,25 @@
       }
     }
 
-    SetTargetValueCB BeginObservation(const FeatureVector& features) override {
-      return base::BindOnce(&FakeLearningTaskController::AddExample,
-                            base::Unretained(this), features);
+    void BeginObservation(ObservationId id,
+                          const FeatureVector& features) override {
+      id_ = id;
+      features_ = features;
     }
 
-    void AddExample(FeatureVector features,
-                    TargetValue target,
-                    WeightType weight) {
-      example_.features = std::move(features);
-      example_.target_value = target;
-      example_.weight = weight;
+    void CompleteObservation(ObservationId id,
+                             const ObservationCompletion& completion) override {
+      EXPECT_EQ(id_, id);
+      example_.features = std::move(features_);
+      example_.target_value = completion.target_value;
+      example_.weight = completion.weight;
     }
 
+    void CancelObservation(ObservationId id) override { ASSERT_TRUE(false); }
+
     SequenceBoundFeatureProvider feature_provider_;
+    ObservationId id_ = 0;
+    FeatureVector features_;
     LabelledExample example_;
   };
 
diff --git a/media/learning/impl/learning_task_controller_helper.cc b/media/learning/impl/learning_task_controller_helper.cc
index dc82675b..d4d57b4 100644
--- a/media/learning/impl/learning_task_controller_helper.cc
+++ b/media/learning/impl/learning_task_controller_helper.cc
@@ -13,28 +13,6 @@
 namespace media {
 namespace learning {
 
-class RunOnDelete {
- public:
-  RunOnDelete(scoped_refptr<base::SequencedTaskRunner> task_runner,
-              base::OnceClosure cb)
-      : task_runner_(std::move(task_runner)), cb_(std::move(cb)) {}
-
-  ~RunOnDelete() {
-    if (task_runner_)
-      task_runner_->PostTask(FROM_HERE, std::move(cb_));
-  }
-
-  // Don't do anything on delete.
-  void Cancel() {
-    task_runner_ = nullptr;
-    cb_ = base::OnceClosure();
-  }
-
- private:
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  base::OnceClosure cb_;
-};
-
 LearningTaskControllerHelper::LearningTaskControllerHelper(
     const LearningTask& task,
     AddExampleCB add_example_cb,
@@ -46,52 +24,36 @@
 
 LearningTaskControllerHelper::~LearningTaskControllerHelper() = default;
 
-LearningTaskController::SetTargetValueCB
-LearningTaskControllerHelper::BeginObservation(FeatureVector features) {
-  int64_t example_id = next_example_id_++;
-  auto& pending_example = pending_examples_[example_id];
+void LearningTaskControllerHelper::BeginObservation(ObservationId id,
+                                                    FeatureVector features) {
+  auto& pending_example = pending_examples_[id];
 
   // Start feature prediction, so that we capture the current values.
   if (!feature_provider_.is_null()) {
     feature_provider_.Post(
         FROM_HERE, &FeatureProvider::AddFeatures, std::move(features),
         base::BindOnce(&LearningTaskControllerHelper::OnFeaturesReadyTrampoline,
-                       task_runner_, AsWeakPtr(), example_id));
+                       task_runner_, AsWeakPtr(), id));
   } else {
     pending_example.example.features = std::move(features);
     pending_example.features_done = true;
   }
-
-  return base::BindOnce(
-      &LearningTaskControllerHelper::OnTargetValue, AsWeakPtr(), example_id,
-      std::make_unique<RunOnDelete>(
-          task_runner_,
-          base::BindOnce(
-              &LearningTaskControllerHelper::OnLabelCallbackDestroyed,
-              AsWeakPtr(), example_id)));
 }
 
-void LearningTaskControllerHelper::OnTargetValue(
-    int64_t example_id,
-    std::unique_ptr<RunOnDelete> run_on_delete,
-    TargetValue target_value,
-    WeightType weight) {
-  // Don't bother to run the callback when |run_on_delete| is destroyed.
-  run_on_delete->Cancel();
-  run_on_delete = nullptr;
-
-  auto iter = pending_examples_.find(example_id);
+void LearningTaskControllerHelper::CompleteObservation(
+    ObservationId id,
+    const ObservationCompletion& completion) {
+  auto iter = pending_examples_.find(id);
   DCHECK(iter != pending_examples_.end());
 
-  iter->second.example.target_value = target_value;
-  iter->second.example.weight = weight;
+  iter->second.example.target_value = completion.target_value;
+  iter->second.example.weight = completion.weight;
   iter->second.target_done = true;
   ProcessExampleIfFinished(std::move(iter));
 }
 
-void LearningTaskControllerHelper::OnLabelCallbackDestroyed(
-    int64_t example_id) {
-  auto iter = pending_examples_.find(example_id);
+void LearningTaskControllerHelper::CancelObservation(ObservationId id) {
+  auto iter = pending_examples_.find(id);
   // If the example has already been completed, then we shouldn't be called.
   DCHECK(iter != pending_examples_.end());
 
@@ -104,11 +66,11 @@
 void LearningTaskControllerHelper::OnFeaturesReadyTrampoline(
     scoped_refptr<base::SequencedTaskRunner> task_runner,
     base::WeakPtr<LearningTaskControllerHelper> weak_this,
-    int64_t example_id,
+    ObservationId id,
     FeatureVector features) {
-  auto cb =
-      base::BindOnce(&LearningTaskControllerHelper::OnFeaturesReady,
-                     std::move(weak_this), example_id, std::move(features));
+  // TODO(liberato): this would benefit from promises / deferred data.
+  auto cb = base::BindOnce(&LearningTaskControllerHelper::OnFeaturesReady,
+                           std::move(weak_this), id, std::move(features));
   if (!task_runner->RunsTasksInCurrentSequence()) {
     task_runner->PostTask(FROM_HERE, std::move(cb));
   } else {
@@ -116,9 +78,9 @@
   }
 }
 
-void LearningTaskControllerHelper::OnFeaturesReady(int64_t example_id,
+void LearningTaskControllerHelper::OnFeaturesReady(ObservationId id,
                                                    FeatureVector features) {
-  PendingExampleMap::iterator iter = pending_examples_.find(example_id);
+  PendingExampleMap::iterator iter = pending_examples_.find(id);
   // It's possible that OnLabelCallbackDestroyed has already run.  That's okay
   // since we don't support prediction right now.
   if (iter == pending_examples_.end())
diff --git a/media/learning/impl/learning_task_controller_helper.h b/media/learning/impl/learning_task_controller_helper.h
index 0321f242..9370536 100644
--- a/media/learning/impl/learning_task_controller_helper.h
+++ b/media/learning/impl/learning_task_controller_helper.h
@@ -21,7 +21,6 @@
 namespace learning {
 
 class LearningTaskControllerHelperTest;
-class RunOnDelete;
 
 // Helper class for managing LabelledExamples that are constructed
 // incrementally.  Keeps track of in-flight examples as they're added via
@@ -38,6 +37,9 @@
   // Callback to add labelled examples as training data.
   using AddExampleCB = base::RepeatingCallback<void(LabelledExample)>;
 
+  // Convenience.
+  using ObservationId = LearningTaskController::ObservationId;
+
   // TODO(liberato): Consider making the FP not optional.
   LearningTaskControllerHelper(const LearningTask& task,
                                AddExampleCB add_example_cb,
@@ -46,8 +48,10 @@
   virtual ~LearningTaskControllerHelper();
 
   // See LearningTaskController::BeginObservation.
-  LearningTaskController::SetTargetValueCB BeginObservation(
-      FeatureVector features);
+  void BeginObservation(ObservationId id, FeatureVector features);
+  void CompleteObservation(ObservationId id,
+                           const ObservationCompletion& completion);
+  void CancelObservation(ObservationId id);
 
  private:
   // Record of an example that has been started by RecordObservedFeatures, but
@@ -63,32 +67,19 @@
   };
 
   // [non-repeating int] = example
-  using PendingExampleMap = std::map<int64_t, PendingExample>;
-
-  // Called by the SetTargetCB for a particular example.  |example_id| is the
-  // PendingExample id.  |run_on_delete| is an RAII class that will notify us
-  // when it's deleted, so that we can clear the example out of the map.
-  // |target_value| is the observed target for the example.
-  // TODO(liberato): This should take a weight, too.
-  void OnTargetValue(int64_t example_id,
-                     std::unique_ptr<RunOnDelete> run_on_delete,
-                     TargetValue target_value,
-                     WeightType weight);
-
-  // Called when the SetTargetValueCB is destroyed.
-  void OnLabelCallbackDestroyed(int64_t example_id);
+  using PendingExampleMap = std::map<ObservationId, PendingExample>;
 
   // Called on any sequence when features are ready.  Will call OnFeatureReady
   // if called on |task_runner|, or will post to |task_runner|.
   static void OnFeaturesReadyTrampoline(
       scoped_refptr<base::SequencedTaskRunner> task_runner,
       base::WeakPtr<LearningTaskControllerHelper> weak_this,
-      int64_t example_id,
+      ObservationId id,
       FeatureVector features);
 
   // Called when a new feature vector has been finished by |feature_provider_|,
   // if needed, to actually add the example.
-  void OnFeaturesReady(int64_t example_id, FeatureVector features);
+  void OnFeaturesReady(ObservationId example_id, FeatureVector features);
 
   // If |example| is finished, then send it to the LearningSession and remove it
   // from the map.  Otherwise, do nothing.
@@ -100,9 +91,6 @@
   // Optional feature provider.
   SequenceBoundFeatureProvider feature_provider_;
 
-  // Next arbitrary integer to be used in the map.
-  int64_t next_example_id_ = 1;
-
   // All outstanding PendingExamples.
   PendingExampleMap pending_examples_;
 
diff --git a/media/learning/impl/learning_task_controller_helper_unittest.cc b/media/learning/impl/learning_task_controller_helper_unittest.cc
index 4e22895..806c43c0 100644
--- a/media/learning/impl/learning_task_controller_helper_unittest.cc
+++ b/media/learning/impl/learning_task_controller_helper_unittest.cc
@@ -87,6 +87,8 @@
 
   LearningTask task_;
 
+  LearningTaskController::ObservationId id_ = 1;
+
   LabelledExample example_;
 };
 
@@ -94,10 +96,10 @@
   // A helper that doesn't use a FeatureProvider should forward examples as soon
   // as they're done.
   CreateClient(false);
-  auto cb = helper_->BeginObservation(example_.features);
+  helper_->BeginObservation(id_, example_.features);
   EXPECT_EQ(pending_example_count(), 1u);
-  EXPECT_TRUE(cb);
-  std::move(cb).Run(example_.target_value, example_.weight);
+  helper_->CompleteObservation(
+      id_, ObservationCompletion(example_.target_value, example_.weight));
   EXPECT_TRUE(most_recent_example_);
   EXPECT_EQ(*most_recent_example_, example_);
   EXPECT_EQ(most_recent_example_->weight, example_.weight);
@@ -107,11 +109,9 @@
 TEST_F(LearningTaskControllerHelperTest, DropTargetValueWithoutFPWorks) {
   // Verify that we can drop an example without labelling it.
   CreateClient(false);
-  auto cb = helper_->BeginObservation(example_.features);
-  EXPECT_TRUE(cb);
+  helper_->BeginObservation(id_, example_.features);
   EXPECT_EQ(pending_example_count(), 1u);
-  // Drop the callback, which should forget the pending example.
-  cb.Reset();
+  helper_->CancelObservation(id_);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_FALSE(most_recent_example_);
   EXPECT_EQ(pending_example_count(), 0u);
@@ -120,15 +120,15 @@
 TEST_F(LearningTaskControllerHelperTest, AddTargetValueBeforeFP) {
   // Verify that an example is added if the target value arrives first.
   CreateClient(true);
-  auto cb = helper_->BeginObservation(example_.features);
+  helper_->BeginObservation(id_, example_.features);
   EXPECT_EQ(pending_example_count(), 1u);
-  EXPECT_TRUE(cb);
   scoped_task_environment_.RunUntilIdle();
   // The feature provider should know about the example.
   EXPECT_EQ(fp_features_, example_.features);
 
   // Add the targe value and verify that the example wasn't added yet.
-  std::move(cb).Run(example_.target_value, example_.weight);
+  helper_->CompleteObservation(
+      id_, ObservationCompletion(example_.target_value, example_.weight));
   EXPECT_FALSE(most_recent_example_);
   EXPECT_EQ(pending_example_count(), 1u);
 
@@ -145,15 +145,14 @@
 TEST_F(LearningTaskControllerHelperTest, DropTargetValueBeforeFP) {
   // Verify that an example is correctly dropped before the FP adds features.
   CreateClient(true);
-  auto cb = helper_->BeginObservation(example_.features);
+  helper_->BeginObservation(id_, example_.features);
   EXPECT_EQ(pending_example_count(), 1u);
-  EXPECT_TRUE(cb);
   scoped_task_environment_.RunUntilIdle();
   // The feature provider should know about the example.
   EXPECT_EQ(fp_features_, example_.features);
 
-  // Drop the TargetValue cb.
-  cb.Reset();
+  // Cancel the observation.
+  helper_->CancelObservation(id_);
   // We don't care if the example is still queued or not, only that we can
   // add features and have it be zero by then.
 
@@ -169,9 +168,8 @@
 TEST_F(LearningTaskControllerHelperTest, AddTargetValueAfterFP) {
   // Verify that an example is added if the target value arrives second.
   CreateClient(true);
-  auto cb = helper_->BeginObservation(example_.features);
+  helper_->BeginObservation(id_, example_.features);
   EXPECT_EQ(pending_example_count(), 1u);
-  EXPECT_TRUE(cb);
   scoped_task_environment_.RunUntilIdle();
   // The feature provider should know about the example.
   EXPECT_EQ(fp_features_, example_.features);
@@ -185,7 +183,8 @@
   EXPECT_EQ(pending_example_count(), 1u);
 
   // Add the targe value and verify that the example is added.
-  std::move(cb).Run(example_.target_value, example_.weight);
+  helper_->CompleteObservation(
+      id_, ObservationCompletion(example_.target_value, example_.weight));
   EXPECT_TRUE(most_recent_example_);
   EXPECT_EQ(*most_recent_example_, example_);
   EXPECT_EQ(most_recent_example_->weight, example_.weight);
@@ -193,11 +192,10 @@
 }
 
 TEST_F(LearningTaskControllerHelperTest, DropTargetValueAfterFP) {
-  // Verify that we can drop the TargetValue cb after sending features.
+  // Verify that we can cancel the observationc after sending features.
   CreateClient(true);
-  auto cb = helper_->BeginObservation(example_.features);
+  helper_->BeginObservation(id_, example_.features);
   EXPECT_EQ(pending_example_count(), 1u);
-  EXPECT_TRUE(cb);
   scoped_task_environment_.RunUntilIdle();
   // The feature provider should know about the example.
   EXPECT_EQ(fp_features_, example_.features);
@@ -212,9 +210,9 @@
   EXPECT_FALSE(most_recent_example_);
   EXPECT_EQ(pending_example_count(), 1u);
 
-  // Delete the SetTargetValueCB, and verify that the pending example has been
+  // Cancel the observation, and verify that the pending example has been
   // removed, and no example was sent to us.
-  cb.Reset();
+  helper_->CancelObservation(id_);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_FALSE(most_recent_example_);
   EXPECT_EQ(pending_example_count(), 0u);
diff --git a/media/learning/impl/learning_task_controller_impl.cc b/media/learning/impl/learning_task_controller_impl.cc
index a38a633..84092c93 100644
--- a/media/learning/impl/learning_task_controller_impl.cc
+++ b/media/learning/impl/learning_task_controller_impl.cc
@@ -38,9 +38,20 @@
 
 LearningTaskControllerImpl::~LearningTaskControllerImpl() = default;
 
-LearningTaskController::SetTargetValueCB
-LearningTaskControllerImpl::BeginObservation(const FeatureVector& features) {
-  return helper_->BeginObservation(features);
+void LearningTaskControllerImpl::BeginObservation(
+    ObservationId id,
+    const FeatureVector& features) {
+  helper_->BeginObservation(id, features);
+}
+
+void LearningTaskControllerImpl::CompleteObservation(
+    ObservationId id,
+    const ObservationCompletion& completion) {
+  helper_->CompleteObservation(id, completion);
+}
+
+void LearningTaskControllerImpl::CancelObservation(ObservationId id) {
+  helper_->CancelObservation(id);
 }
 
 void LearningTaskControllerImpl::AddFinishedExample(LabelledExample example) {
diff --git a/media/learning/impl/learning_task_controller_impl.h b/media/learning/impl/learning_task_controller_impl.h
index 74b27897..2866da1 100644
--- a/media/learning/impl/learning_task_controller_impl.h
+++ b/media/learning/impl/learning_task_controller_impl.h
@@ -44,7 +44,11 @@
   ~LearningTaskControllerImpl() override;
 
   // LearningTaskController
-  SetTargetValueCB BeginObservation(const FeatureVector& features) override;
+  void BeginObservation(ObservationId id,
+                        const FeatureVector& features) override;
+  void CompleteObservation(ObservationId id,
+                           const ObservationCompletion& completion) override;
+  void CancelObservation(ObservationId id) override;
 
  private:
   // Add |example| to the training data, and process it.
diff --git a/media/learning/impl/learning_task_controller_impl_unittest.cc b/media/learning/impl/learning_task_controller_impl_unittest.cc
index c9e8a1e..7433919 100644
--- a/media/learning/impl/learning_task_controller_impl_unittest.cc
+++ b/media/learning/impl/learning_task_controller_impl_unittest.cc
@@ -113,8 +113,10 @@
   }
 
   void AddExample(const LabelledExample& example) {
-    std::move(controller_->BeginObservation(example.features))
-        .Run(example.target_value, example.weight);
+    LearningTaskController::ObservationId id = 1;
+    controller_->BeginObservation(id, example.features);
+    controller_->CompleteObservation(
+        id, ObservationCompletion(example.target_value, example.weight));
   }
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
diff --git a/media/mojo/interfaces/interface_factory.mojom b/media/mojo/interfaces/interface_factory.mojom
index 4bd229a..86beb52 100644
--- a/media/mojo/interfaces/interface_factory.mojom
+++ b/media/mojo/interfaces/interface_factory.mojom
@@ -12,36 +12,12 @@
 import "media/mojo/interfaces/video_decoder.mojom";
 import "mojo/public/mojom/base/token.mojom";
 
-// DEPRECATED: This will be cleaned up as part of https://crbug.com/936528.
-// It is currently left in to prevent breaking the ChromeCast build, and will
-// be removed before M74 branch.
-// Defines the types of renderers that can be hosted by a mojo Renderer.
-enum HostedRendererType {
-  // media::DefaultRenderer: Used to offload normal rendering scenarios to a
-  // different process, for stability or performance reasons.
-  kDefault,
-
-  // DEPRECATED: Use CreateMediaPlayerRenderer() instead.
-  [EnableIf=is_android]
-  kMediaPlayer,
-
-  // DEPRECATED: Use CreateFlingingRenderer() instead.
-  [EnableIf=is_android]
-  kFlinging,
-};
-
 // A factory for creating media mojo interfaces. Renderers can only access
 // ContentDecryptionModules created with the same factory.
 interface InterfaceFactory {
   CreateAudioDecoder(AudioDecoder& audio_decoder);
   CreateVideoDecoder(VideoDecoder& video_decoder);
 
-  // DEPRECATED: This will be cleaned up as part of https://crbug.com/936528.
-  // It is currently left in to prevent breaking the ChromeCast build, and will
-  // be removed before M74 branch.
-  CreateRenderer(HostedRendererType type, string audio_device_id,
-                 Renderer& renderer);
-
   // Creates a regular media::Renderer (DefaultRendererFactory).
   CreateDefaultRenderer(string audio_device_id, Renderer& renderer);
 
diff --git a/media/mojo/services/interface_factory_impl.cc b/media/mojo/services/interface_factory_impl.cc
index 8e5bf0b..f4353b1 100644
--- a/media/mojo/services/interface_factory_impl.cc
+++ b/media/mojo/services/interface_factory_impl.cc
@@ -95,15 +95,6 @@
 #endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
 }
 
-// TODO(https://crbug.com/936528) : remove this method.
-void InterfaceFactoryImpl::CreateRenderer(
-    mojom::HostedRendererType type,
-    const std::string& audio_device_id,
-    media::mojom::RendererRequest renderer) {
-  DCHECK_EQ(type, mojom::HostedRendererType::kDefault);
-  CreateDefaultRenderer(audio_device_id, std::move(renderer));
-}
-
 void InterfaceFactoryImpl::CreateDefaultRenderer(
     const std::string& audio_device_id,
     mojo::InterfaceRequest<mojom::Renderer> request) {
diff --git a/media/mojo/services/interface_factory_impl.h b/media/mojo/services/interface_factory_impl.h
index 5e91daf..06e7672 100644
--- a/media/mojo/services/interface_factory_impl.h
+++ b/media/mojo/services/interface_factory_impl.h
@@ -42,10 +42,6 @@
   void CreateVideoDecoder(mojom::VideoDecoderRequest request) final;
   void CreateDefaultRenderer(const std::string& audio_device_id,
                              mojom::RendererRequest request) final;
-  // TODO(https://crbug.com/936528) : remove this method.
-  void CreateRenderer(mojom::HostedRendererType type,
-                      const std::string& audio_device_id,
-                      media::mojom::RendererRequest renderer) final;
 #if defined(OS_ANDROID)
   void CreateMediaPlayerRenderer(mojom::RendererRequest request) final;
   void CreateFlingingRenderer(const std::string& presentation_id,
diff --git a/media/video/gpu_video_accelerator_factories.h b/media/video/gpu_video_accelerator_factories.h
index 8825417..1f0cb303 100644
--- a/media/video/gpu_video_accelerator_factories.h
+++ b/media/video/gpu_video_accelerator_factories.h
@@ -89,8 +89,7 @@
 
   virtual std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
       MediaLog* media_log,
-      const RequestOverlayInfoCB& request_overlay_info_cb,
-      const gfx::ColorSpace& target_color_space) = 0;
+      const RequestOverlayInfoCB& request_overlay_info_cb) = 0;
 
   // Caller owns returned pointer, but should call Destroy() on it (instead of
   // directly deleting) for proper destruction, as per the
diff --git a/media/video/mock_gpu_video_accelerator_factories.h b/media/video/mock_gpu_video_accelerator_factories.h
index 5c8d3fc..1c8850d 100644
--- a/media/video/mock_gpu_video_accelerator_factories.h
+++ b/media/video/mock_gpu_video_accelerator_factories.h
@@ -37,10 +37,10 @@
   MOCK_METHOD0(GetCommandBufferRouteId, int32_t());
 
   MOCK_METHOD1(IsDecoderConfigSupported, bool(const VideoDecoderConfig&));
-  MOCK_METHOD3(CreateVideoDecoder,
-               std::unique_ptr<media::VideoDecoder>(MediaLog*,
-                                                    const RequestOverlayInfoCB&,
-                                                    const gfx::ColorSpace&));
+  MOCK_METHOD2(
+      CreateVideoDecoder,
+      std::unique_ptr<media::VideoDecoder>(MediaLog*,
+                                           const RequestOverlayInfoCB&));
 
   // CreateVideo{Decode,Encode}Accelerator returns scoped_ptr, which the mocking
   // framework does not want.  Trampoline them.
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
index 4f58b83..13093151 100644
--- a/mojo/public/interfaces/bindings/tests/BUILD.gn
+++ b/mojo/public/interfaces/bindings/tests/BUILD.gn
@@ -488,8 +488,22 @@
 # These could probably be merged with "test_interfaces" at some point.
 mojom("other_test_interfaces") {
   testonly = true
-  cpp_only = true
   sources = [
     "new_endpoint_types.test-mojom",
   ]
 }
+
+# Ensure that some target forces JS and Java bindings generation when all
+# targets are built. This provides a basic generation smoke test for new
+# endpoint types in mojom.
+group("test_generation") {
+  testonly = true
+  deps = [
+    ":other_test_interfaces",
+    ":other_test_interfaces_js",
+  ]
+
+  if (is_android) {
+    deps += [ ":other_test_interfaces_java" ]
+  }
+}
diff --git a/mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl b/mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl
index a91260e..e8a7460 100644
--- a/mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl
@@ -95,10 +95,12 @@
 {%- endmacro -%}
 
 {%- macro generate_or_mutate_interface(obj, operation, kind, name) -%}
-{%-   if kind|is_interface_request_kind -%}
+{%-   if kind|is_interface_request_kind or kind|is_pending_receiver_kind -%}
 {{build_call(obj, operation, 'InterfaceRequest', name, '"' ~ kind.kind.module.namespace ~ '.' ~ kind.kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
 {%-   elif kind|is_interface_kind -%}
 {{build_call(obj, operation, 'Interface', name, '"' ~ kind.module.namespace ~ '.' ~ kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
+{%-   elif kind|is_pending_remote_kind -%}
+{{build_call(obj, operation, 'Interface', name, '"' ~ kind.kind.module.namespace ~ '.' ~ kind.kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
 {%-   elif kind|is_associated_interface_request_kind -%}
 {{build_call(obj, operation, 'AssociatedInterfaceRequest', name, '"' ~ kind.kind.module.namespace ~ '.' ~ kind.kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
 {%-   elif kind|is_associated_interface_kind -%}
diff --git a/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
index 0e9b8931..de19055 100644
--- a/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
@@ -20,11 +20,12 @@
 // validate {{name}}
 err = messageValidator.validateMapPointer({{offset}}, {{field|validate_map_params}});
 {{_check_err()}}
-{%-   elif field.kind|is_interface_kind %}
+{%-   elif field.kind|is_interface_kind or field.kind|is_pending_remote_kind %}
 // validate {{name}}
 err = messageValidator.validateInterface({{offset}}, {{field|validate_nullable_params}});
 {{_check_err()}}
-{%-   elif field.kind|is_interface_request_kind %}
+{%-   elif field.kind|is_interface_request_kind or
+           field.kind|is_pending_receiver_kind %}
 // validate {{name}}
 err = messageValidator.validateInterfaceRequest({{offset}}, {{field|validate_nullable_params}})
 {{_check_err()}}
diff --git a/mojo/public/tools/bindings/generators/mojom_java_generator.py b/mojo/public/tools/bindings/generators/mojom_java_generator.py
index 0f2b618..8097af3 100644
--- a/mojo/public/tools/bindings/generators/mojom_java_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_java_generator.py
@@ -134,7 +134,9 @@
     if name in _java_reserved_types:
       return name + '_'
     return name
-  if mojom.IsInterfaceRequestKind(element) or mojom.IsAssociatedKind(element):
+  if (mojom.IsInterfaceRequestKind(element) or
+      mojom.IsAssociatedKind(element) or mojom.IsPendingRemoteKind(element) or
+      mojom.IsPendingReceiverKind(element)):
     return GetNameForElement(element.kind)
   if isinstance(element, (mojom.Method,
                           mojom.Parameter,
@@ -199,8 +201,12 @@
     params.append(GetArrayExpectedLength(kind))
   if mojom.IsInterfaceKind(kind):
     params.append('%s.MANAGER' % GetJavaType(context, kind))
+  if mojom.IsPendingRemoteKind(kind):
+    params.append('%s.MANAGER' % GetJavaType(context, kind.kind))
   if mojom.IsArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
     params.append('%s.MANAGER' % GetJavaType(context, kind.kind))
+  if mojom.IsArrayKind(kind) and mojom.IsPendingRemoteKind(kind.kind):
+    params.append('%s.MANAGER' % GetJavaType(context, kind.kind.kind))
   return params
 
 
@@ -211,9 +217,9 @@
       return _DecodeMethodName(kind.kind) + 's'
     if mojom.IsEnumKind(kind):
       return _DecodeMethodName(mojom.INT32)
-    if mojom.IsInterfaceRequestKind(kind):
+    if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
       return 'readInterfaceRequest'
-    if mojom.IsInterfaceKind(kind):
+    if mojom.IsInterfaceKind(kind) or mojom.IsPendingRemoteKind(kind):
       return 'readServiceInterface'
     if mojom.IsAssociatedInterfaceRequestKind(kind):
       return 'readAssociatedInterfaceRequestNotSupported'
@@ -271,7 +277,9 @@
       mojom.IsInterfaceKind(kind) or
       mojom.IsUnionKind(kind)):
     return GetNameForKind(context, kind)
-  if mojom.IsInterfaceRequestKind(kind):
+  if mojom.IsPendingRemoteKind(kind):
+    return GetNameForKind(context, kind.kind)
+  if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
     return ('org.chromium.mojo.bindings.InterfaceRequest<%s>' %
             GetNameForKind(context, kind.kind))
   if mojom.IsAssociatedInterfaceKind(kind):
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
index 9eb5f2d..bc8bc63 100644
--- a/mojo/public/tools/bindings/generators/mojom_js_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -289,7 +289,9 @@
       "is_any_handle_kind": mojom.IsAnyHandleKind,
       "is_any_interface_kind": mojom.IsAnyInterfaceKind,
       "is_interface_kind": mojom.IsInterfaceKind,
+      "is_pending_remote_kind": mojom.IsPendingRemoteKind,
       "is_interface_request_kind": mojom.IsInterfaceRequestKind,
+      "is_pending_receiver_kind": mojom.IsPendingReceiverKind,
       "is_map_kind": mojom.IsMapKind,
       "is_object_kind": mojom.IsObjectKind,
       "is_reference_kind": mojom.IsReferenceKind,
@@ -381,6 +383,8 @@
       return _kind_to_closure_type[kind]
     if mojom.IsInterfaceKind(kind):
       return kind.module.namespace + "." + kind.name + "Ptr"
+    if mojom.IsPendingRemoteKind(kind):
+      return kind.kind.module.namespace + "." + kind.kind.name + "Ptr"
     if (mojom.IsStructKind(kind) or
         mojom.IsEnumKind(kind)):
       return kind.module.namespace + "." + kind.name
@@ -392,7 +396,7 @@
     if mojom.IsMapKind(kind):
       return "Map<%s, %s>" % (
           self._ClosureType(kind.key_kind), self._ClosureType(kind.value_kind))
-    if mojom.IsInterfaceRequestKind(kind):
+    if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
       return "mojo.InterfaceRequest"
     # TODO(calamity): Support associated interfaces properly.
     if mojom.IsAssociatedInterfaceKind(kind):
@@ -426,7 +430,8 @@
           self._LiteClosureTypeWithNullability(kind.key_kind),
           self._LiteClosureTypeWithNullability(kind.value_kind))
 
-    if mojom.IsAssociatedKind(kind) or mojom.IsInterfaceRequestKind(kind):
+    if (mojom.IsAssociatedKind(kind) or mojom.IsInterfaceRequestKind(kind) or
+        mojom.IsPendingRemoteKind(kind) or mojom.IsPendingReceiverKind(kind)):
       named_kind = kind.kind
     else:
       named_kind = kind
@@ -442,9 +447,9 @@
     if (mojom.IsStructKind(kind) or mojom.IsUnionKind(kind) or
         mojom.IsEnumKind(kind)):
       return name
-    if mojom.IsInterfaceKind(kind):
+    if mojom.IsInterfaceKind(kind) or mojom.IsPendingRemoteKind(kind):
       return name + "Proxy"
-    if mojom.IsInterfaceRequestKind(kind):
+    if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
       return name + "Request"
     # TODO(calamity): Support associated interfaces properly.
     if mojom.IsAssociatedInterfaceKind(kind):
@@ -517,7 +522,8 @@
           self._LiteJavaScriptType(kind.value_kind),
           "true" if mojom.IsNullableKind(kind.value_kind) else "false")
 
-    if mojom.IsAssociatedKind(kind) or mojom.IsInterfaceRequestKind(kind):
+    if (mojom.IsAssociatedKind(kind) or mojom.IsInterfaceRequestKind(kind) or
+        mojom.IsPendingRemoteKind(kind) or mojom.IsPendingReceiverKind(kind)):
       named_kind = kind.kind
     else:
       named_kind = kind
@@ -533,9 +539,9 @@
     if (mojom.IsStructKind(kind) or mojom.IsUnionKind(kind) or
         mojom.IsEnumKind(kind)):
       return "%sSpec.$" % name
-    if mojom.IsInterfaceKind(kind):
+    if mojom.IsInterfaceKind(kind) or mojom.IsPendingRemoteKind(kind):
       return "mojo.internal.InterfaceProxy(%sProxy)" % name
-    if mojom.IsInterfaceRequestKind(kind):
+    if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
       return "mojo.internal.InterfaceRequest(%sRequest)" % name
     if mojom.IsAssociatedInterfaceKind(kind):
       # TODO(rockot): Implement associated interfaces.
@@ -564,7 +570,10 @@
       return "null"
     if mojom.IsInterfaceKind(field.kind):
       return "new %sPtr()" % self._JavaScriptType(field.kind)
-    if mojom.IsInterfaceRequestKind(field.kind):
+    if mojom.IsPendingRemoteKind(field.kind):
+      return "new %sPtr()" % self._JavaScriptType(field.kind.kind)
+    if (mojom.IsInterfaceRequestKind(field.kind) or
+        mojom.IsPendingReceiverKind(field.kind)):
       return "new bindings.InterfaceRequest()"
     if mojom.IsAssociatedInterfaceKind(field.kind):
       return "new associatedBindings.AssociatedInterfacePtrInfo()"
@@ -605,7 +614,11 @@
       return "new codec.%s(%sPtr)" % (
           "NullableInterface" if mojom.IsNullableKind(kind) else "Interface",
           self._JavaScriptType(kind))
-    if mojom.IsInterfaceRequestKind(kind):
+    if mojom.IsPendingRemoteKind(kind):
+      return "new codec.%s(%sPtr)" % (
+          "NullableInterface" if mojom.IsNullableKind(kind) else "Interface",
+          self._JavaScriptType(kind.kind))
+    if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
       return "codec.%s" % (
           "NullableInterfaceRequest" if mojom.IsNullableKind(kind)
                                      else "InterfaceRequest")
@@ -771,12 +784,15 @@
     return result
 
   def _FuzzHandleName(self, kind):
-    if mojom.IsInterfaceRequestKind(kind):
+    if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
       return '{0}.{1}Request'.format(kind.kind.module.namespace,
                                      kind.kind.name)
     elif mojom.IsInterfaceKind(kind):
       return '{0}.{1}Ptr'.format(kind.module.namespace,
                                  kind.name)
+    elif mojom.IsPendingRemoteKind(kind):
+      return '{0}.{1}Ptr'.format(kind.kind.module.namespace,
+                                 kind.kind.name)
     elif mojom.IsAssociatedInterfaceRequestKind(kind):
       return '{0}.{1}AssociatedRequest'.format(kind.kind.module.namespace,
                                                kind.kind.name)
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 6838a451..ac6024ea 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -3061,6 +3061,7 @@
       "third_party/quic/core/quic_epoll_connection_helper.h",
       "third_party/quic/core/quic_packet_reader.cc",
       "third_party/quic/core/quic_packet_reader.h",
+      "third_party/quic/platform/api/quic_default_buffer_allocator.h",
       "third_party/quic/platform/api/quic_default_proof_providers.h",
       "third_party/quic/platform/api/quic_system_event_loop.h",
       "third_party/quic/platform/impl/batch_writer/quic_batch_writer_base.cc",
@@ -3071,6 +3072,7 @@
       "third_party/quic/platform/impl/batch_writer/quic_gso_batch_writer.h",
       "third_party/quic/platform/impl/batch_writer/quic_sendmmsg_batch_writer.cc",
       "third_party/quic/platform/impl/batch_writer/quic_sendmmsg_batch_writer.h",
+      "third_party/quic/platform/impl/quic_default_buffer_allocator_impl.h",
       "third_party/quic/platform/impl/quic_default_proof_providers_impl.cc",
       "third_party/quic/platform/impl/quic_default_proof_providers_impl.h",
       "third_party/quic/platform/impl/quic_epoll_clock.cc",
@@ -3343,8 +3345,6 @@
     sources += [
       "third_party/quic/test_tools/bad_packet_writer.cc",
       "third_party/quic/test_tools/bad_packet_writer.h",
-      "third_party/quic/test_tools/fake_epoll_server.cc",
-      "third_party/quic/test_tools/fake_epoll_server.h",
       "third_party/quic/test_tools/limited_mtu_test_writer.cc",
       "third_party/quic/test_tools/limited_mtu_test_writer.h",
       "third_party/quic/test_tools/packet_dropping_test_writer.cc",
@@ -3361,6 +3361,8 @@
       "third_party/quic/test_tools/quic_test_server.h",
       "third_party/quic/test_tools/server_thread.cc",
       "third_party/quic/test_tools/server_thread.h",
+      "tools/epoll_server/fake_epoll_server.cc",
+      "tools/epoll_server/fake_epoll_server.h",
     ]
     deps += [
       ":epoll_quic_tools",
diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn
index 989877f..0e71ba1 100644
--- a/net/dns/BUILD.gn
+++ b/net/dns/BUILD.gn
@@ -31,6 +31,8 @@
     sources += [
       "address_sorter.h",
       "address_sorter_win.cc",
+      "context_host_resolver.cc",
+      "context_host_resolver.h",
       "dns_config.cc",
       "dns_config_overrides.cc",
       "dns_config_service.cc",
@@ -433,8 +435,8 @@
 source_set("fuzzer_test_support") {
   testonly = true
   sources = [
-    "fuzzed_host_resolver.cc",
-    "fuzzed_host_resolver.h",
+    "fuzzed_context_host_resolver.cc",
+    "fuzzed_context_host_resolver.h",
   ]
   deps = [
     "//base",
diff --git a/net/dns/context_host_resolver.cc b/net/dns/context_host_resolver.cc
new file mode 100644
index 0000000..f7b74aa7
--- /dev/null
+++ b/net/dns/context_host_resolver.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/dns/context_host_resolver.h"
+
+#include <utility>
+
+#include "base/strings/string_piece.h"
+#include "base/time/tick_clock.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_config.h"
+#include "net/dns/host_resolver_impl.h"
+#include "net/dns/host_resolver_proc.h"
+
+namespace net {
+
+ContextHostResolver::ContextHostResolver(std::unique_ptr<HostResolverImpl> impl)
+    : impl_(std::move(impl)) {}
+
+ContextHostResolver::~ContextHostResolver() = default;
+
+std::unique_ptr<HostResolver::ResolveHostRequest>
+ContextHostResolver::CreateRequest(
+    const HostPortPair& host,
+    const NetLogWithSource& source_net_log,
+    const base::Optional<ResolveHostParameters>& optional_parameters) {
+  return impl_->CreateRequest(host, source_net_log, optional_parameters);
+}
+
+std::unique_ptr<HostResolver::MdnsListener>
+ContextHostResolver::CreateMdnsListener(const HostPortPair& host,
+                                        DnsQueryType query_type) {
+  return impl_->CreateMdnsListener(host, query_type);
+}
+
+void ContextHostResolver::SetDnsClientEnabled(bool enabled) {
+  impl_->SetDnsClientEnabled(enabled);
+}
+
+HostCache* ContextHostResolver::GetHostCache() {
+  return impl_->GetHostCache();
+}
+
+bool ContextHostResolver::HasCached(base::StringPiece hostname,
+                                    HostCache::Entry::Source* source_out,
+                                    HostCache::EntryStaleness* stale_out,
+                                    bool* secure_out) const {
+  return impl_->HasCached(hostname, source_out, stale_out, secure_out);
+}
+
+std::unique_ptr<base::Value> ContextHostResolver::GetDnsConfigAsValue() const {
+  return impl_->GetDnsConfigAsValue();
+}
+
+void ContextHostResolver::SetNoIPv6OnWifi(bool no_ipv6_on_wifi) {
+  impl_->SetNoIPv6OnWifi(no_ipv6_on_wifi);
+}
+
+bool ContextHostResolver::GetNoIPv6OnWifi() {
+  return impl_->GetNoIPv6OnWifi();
+}
+
+void ContextHostResolver::SetDnsConfigOverrides(
+    const DnsConfigOverrides& overrides) {
+  impl_->SetDnsConfigOverrides(overrides);
+}
+
+void ContextHostResolver::SetRequestContext(
+    URLRequestContext* request_context) {
+  impl_->SetRequestContext(request_context);
+}
+
+const std::vector<DnsConfig::DnsOverHttpsServerConfig>*
+ContextHostResolver::GetDnsOverHttpsServersForTesting() const {
+  return impl_->GetDnsOverHttpsServersForTesting();
+}
+
+size_t ContextHostResolver::LastRestoredCacheSize() const {
+  return impl_->LastRestoredCacheSize();
+}
+
+size_t ContextHostResolver::CacheSize() const {
+  return impl_->CacheSize();
+}
+
+void ContextHostResolver::SetProcParamsForTesting(
+    const ProcTaskParams& proc_params) {
+  impl_->set_proc_params_for_test(proc_params);
+}
+
+void ContextHostResolver::SetDnsClientForTesting(
+    std::unique_ptr<DnsClient> dns_client) {
+  impl_->SetDnsClient(std::move(dns_client));
+}
+
+void ContextHostResolver::SetBaseDnsConfigForTesting(
+    const DnsConfig& base_config) {
+  impl_->SetBaseDnsConfigForTesting(base_config);
+}
+
+void ContextHostResolver::SetTickClockForTesting(
+    const base::TickClock* tick_clock) {
+  impl_->SetTickClockForTesting(tick_clock);
+}
+
+}  // namespace net
diff --git a/net/dns/context_host_resolver.h b/net/dns/context_host_resolver.h
new file mode 100644
index 0000000..c200ded
--- /dev/null
+++ b/net/dns/context_host_resolver.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_DNS_CONTEXT_HOST_RESOLVER_H_
+#define NET_DNS_CONTEXT_HOST_RESOLVER_H_
+
+#include <memory>
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "net/dns/host_resolver.h"
+
+namespace base {
+class TickClock;
+}  // namespace base
+
+namespace net {
+
+class DnsClient;
+struct DnsConfig;
+class HostResolverImpl;
+struct ProcTaskParams;
+
+// Wrapper for HostResolverImpl that sets per-context parameters for created
+// requests. Except for tests, typically only interacted with through the
+// HostResolver interface.
+//
+// See HostResolver::Create[...]() methods for construction.
+//
+// TODO(crbug.com/934402): Construct individually for each URLRequestContext
+// rather than using this as the singleton shared resolver.
+class NET_EXPORT ContextHostResolver : public HostResolver {
+ public:
+  // Creates a ContextHostResolver that forwards all of its requests through
+  // |impl|.
+  explicit ContextHostResolver(std::unique_ptr<HostResolverImpl> impl);
+  ~ContextHostResolver() override;
+
+  // HostResolver methods:
+  std::unique_ptr<ResolveHostRequest> CreateRequest(
+      const HostPortPair& host,
+      const NetLogWithSource& net_log,
+      const base::Optional<ResolveHostParameters>& optional_parameters)
+      override;
+  std::unique_ptr<MdnsListener> CreateMdnsListener(
+      const HostPortPair& host,
+      DnsQueryType query_type) override;
+  void SetDnsClientEnabled(bool enabled) override;
+  HostCache* GetHostCache() override;
+  bool HasCached(base::StringPiece hostname,
+                 HostCache::Entry::Source* source_out,
+                 HostCache::EntryStaleness* stale_out,
+                 bool* secure_out) const override;
+  std::unique_ptr<base::Value> GetDnsConfigAsValue() const override;
+  void SetNoIPv6OnWifi(bool no_ipv6_on_wifi) override;
+  bool GetNoIPv6OnWifi() override;
+  void SetDnsConfigOverrides(const DnsConfigOverrides& overrides) override;
+  void SetRequestContext(URLRequestContext* request_context) override;
+  const std::vector<DnsConfig::DnsOverHttpsServerConfig>*
+  GetDnsOverHttpsServersForTesting() const override;
+
+  // Returns the number of host cache entries that were restored, or 0 if there
+  // is no cache.
+  size_t LastRestoredCacheSize() const;
+  // Returns the number of entries in the host cache, or 0 if there is no cache.
+  size_t CacheSize() const;
+
+  void SetProcParamsForTesting(const ProcTaskParams& proc_params);
+  void SetDnsClientForTesting(std::unique_ptr<DnsClient> dns_client);
+  void SetBaseDnsConfigForTesting(const DnsConfig& base_config);
+  void SetTickClockForTesting(const base::TickClock* tick_clock);
+
+ private:
+  // TODO(crbug.com/934402): Make this a non-owned pointer to the singleton
+  // resolver.
+  std::unique_ptr<HostResolverImpl> impl_;
+};
+
+}  // namespace net
+
+#endif  // NET_DNS_CONTEXT_HOST_RESOLVER_H_
diff --git a/net/dns/fuzzed_host_resolver.cc b/net/dns/fuzzed_context_host_resolver.cc
similarity index 84%
rename from net/dns/fuzzed_host_resolver.cc
rename to net/dns/fuzzed_context_host_resolver.cc
index 702bff01..933f626 100644
--- a/net/dns/fuzzed_host_resolver.cc
+++ b/net/dns/fuzzed_context_host_resolver.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 "net/dns/fuzzed_host_resolver.h"
+#include "net/dns/fuzzed_context_host_resolver.h"
 
 #include <stdint.h>
 #include <algorithm>
@@ -29,8 +29,11 @@
 #include "net/dns/dns_client.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_hosts.h"
+#include "net/dns/host_resolver_impl.h"
+#include "net/dns/host_resolver_proc.h"
 #include "net/dns/mdns_client.h"
 #include "net/dns/public/util.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_with_source.h"
 #include "net/socket/datagram_server_socket.h"
 
@@ -294,33 +297,69 @@
   base::FuzzedDataProvider* const data_provider_;
 };
 
+class FuzzedHostResolverImpl : public HostResolverImpl {
+ public:
+  // |data_provider| and |net_log| must outlive the FuzzedHostResolver.
+  FuzzedHostResolverImpl(const Options& options,
+                         NetLog* net_log,
+                         base::FuzzedDataProvider* data_provider)
+      : HostResolverImpl(options, net_log),
+        data_provider_(data_provider),
+        is_ipv6_reachable_(data_provider->ConsumeBool()),
+        data_provider_weak_factory_(data_provider) {
+    ProcTaskParams proc_task_params(
+        new FuzzedHostResolverProc(data_provider_weak_factory_.GetWeakPtr()),
+        // Retries are only used when the original request hangs, which this
+        // class currently can't simulate.
+        0 /* max_retry_attempts */);
+    set_proc_params_for_test(proc_task_params);
+    SetTaskRunnerForTesting(base::SequencedTaskRunnerHandle::Get());
+    SetMdnsSocketFactoryForTesting(
+        std::make_unique<FuzzedMdnsSocketFactory>(data_provider_));
+  }
+
+  ~FuzzedHostResolverImpl() override = default;
+
+ private:
+  // HostResolverImpl implementation:
+  bool IsGloballyReachable(const IPAddress& dest,
+                           const NetLogWithSource& net_log) override {
+    return is_ipv6_reachable_;
+  }
+
+  void RunLoopbackProbeJob() override {
+    SetHaveOnlyLoopbackAddresses(data_provider_->ConsumeBool());
+  }
+
+  base::FuzzedDataProvider* const data_provider_;
+
+  // Fixed value to be returned by IsIPv6Reachable.
+  const bool is_ipv6_reachable_;
+
+  base::WeakPtrFactory<base::FuzzedDataProvider> data_provider_weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(FuzzedHostResolverImpl);
+};
+
 }  // namespace
 
-FuzzedHostResolver::FuzzedHostResolver(const Options& options,
-                                       NetLog* net_log,
-                                       base::FuzzedDataProvider* data_provider)
-    : HostResolverImpl(options, net_log),
+FuzzedContextHostResolver::FuzzedContextHostResolver(
+    const Options& options,
+    NetLog* net_log,
+    base::FuzzedDataProvider* data_provider)
+    : ContextHostResolver(
+          std::make_unique<FuzzedHostResolverImpl>(options,
+                                                   net_log,
+                                                   data_provider)),
       data_provider_(data_provider),
       socket_factory_(data_provider),
-      is_ipv6_reachable_(data_provider->ConsumeBool()),
-      net_log_(net_log),
-      data_provider_weak_factory_(data_provider) {
-  HostResolverImpl::ProcTaskParams proc_task_params(
-      new FuzzedHostResolverProc(data_provider_weak_factory_.GetWeakPtr()),
-      // Retries are only used when the original request hangs, which this class
-      // currently can't simulate.
-      0 /* max_retry_attempts */);
-  set_proc_params_for_test(proc_task_params);
-  SetTaskRunnerForTesting(base::SequencedTaskRunnerHandle::Get());
-  SetMdnsSocketFactoryForTesting(
-      std::make_unique<FuzzedMdnsSocketFactory>(data_provider_));
-}
+      net_log_(net_log) {}
 
-FuzzedHostResolver::~FuzzedHostResolver() = default;
+FuzzedContextHostResolver::~FuzzedContextHostResolver() = default;
 
-void FuzzedHostResolver::SetDnsClientEnabled(bool enabled) {
+void FuzzedContextHostResolver::SetDnsClientEnabled(bool enabled) {
   if (!enabled) {
-    HostResolverImpl::SetDnsClientEnabled(false);
+    ContextHostResolver::SetDnsClientEnabled(false);
     return;
   }
 
@@ -382,16 +421,7 @@
       base::Bind(&base::FuzzedDataProvider::ConsumeIntegralInRange<int32_t>,
                  base::Unretained(data_provider_)));
   dns_client->SetConfig(config);
-  SetDnsClient(std::move(dns_client));
-}
-
-bool FuzzedHostResolver::IsGloballyReachable(const IPAddress& dest,
-                                             const NetLogWithSource& net_log) {
-  return is_ipv6_reachable_;
-}
-
-void FuzzedHostResolver::RunLoopbackProbeJob() {
-  SetHaveOnlyLoopbackAddresses(data_provider_->ConsumeBool());
+  SetDnsClientForTesting(std::move(dns_client));
 }
 
 }  // namespace net
diff --git a/net/dns/fuzzed_context_host_resolver.h b/net/dns/fuzzed_context_host_resolver.h
new file mode 100644
index 0000000..26bad94a
--- /dev/null
+++ b/net/dns/fuzzed_context_host_resolver.h
@@ -0,0 +1,66 @@
+// 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 NET_DNS_FUZZED_CONTEXT_HOST_RESOLVER_H_
+#define NET_DNS_FUZZED_CONTEXT_HOST_RESOLVER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "net/dns/context_host_resolver.h"
+#include "net/dns/host_resolver.h"
+#include "net/socket/fuzzed_socket_factory.h"
+
+namespace base {
+class FuzzedDataProvider;
+}
+
+namespace net {
+
+class NetLog;
+
+// HostResolver that uses a fuzzer to determine what results to return. It
+// inherits from ContextHostResolver, unlike MockHostResolver, so more closely
+// matches real behavior.
+//
+// By default uses a mocked out system resolver, though can be configured to
+// use the built-in async resolver (Built in DNS stub resolver) with a fuzzed
+// set of UDP/TCP sockets.
+//
+// To make behavior most deterministic, does not use the WorkerPool to run its
+// simulated platform host resolver calls, instead runs them on the thread it is
+// created on.
+//
+// Note that it does not attempt to sort the resulting AddressList when using
+// the mock system resolver path.
+//
+// The async DNS client can make system calls in AddressSorterPosix, but other
+// methods that make system calls are stubbed out.
+class FuzzedContextHostResolver : public ContextHostResolver {
+ public:
+  FuzzedContextHostResolver(const Options& options,
+                            NetLog* net_log,
+                            base::FuzzedDataProvider* data_provider);
+  ~FuzzedContextHostResolver() override;
+
+  // Enable / disable the async resolver. When enabled, installs a
+  // DnsClient with fuzzed UDP and TCP sockets.
+  void SetDnsClientEnabled(bool enabled) override;
+
+ private:
+  base::FuzzedDataProvider* const data_provider_;
+
+  // Used for UDP and TCP sockets if the async resolver is enabled.
+  FuzzedSocketFactory socket_factory_;
+
+  NetLog* const net_log_;
+
+  DISALLOW_COPY_AND_ASSIGN(FuzzedContextHostResolver);
+};
+
+}  // namespace net
+
+#endif  // NET_DNS_FUZZED_CONTEXT_HOST_RESOLVER_H_
diff --git a/net/dns/fuzzed_host_resolver.h b/net/dns/fuzzed_host_resolver.h
deleted file mode 100644
index 7cb1b51..0000000
--- a/net/dns/fuzzed_host_resolver.h
+++ /dev/null
@@ -1,82 +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 NET_DNS_FUZZED_HOST_RESOLVER_H_
-#define NET_DNS_FUZZED_HOST_RESOLVER_H_
-
-#include <stdint.h>
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "net/base/address_family.h"
-#include "net/dns/host_resolver.h"
-#include "net/dns/host_resolver_impl.h"
-#include "net/socket/fuzzed_socket_factory.h"
-
-namespace base {
-class FuzzedDataProvider;
-}
-
-namespace net {
-
-class AddressList;
-class DnsClient;
-class NetLog;
-
-// HostResolver that uses a fuzzer to determine what results to return. It
-// inherits from HostResolverImpl, unlike MockHostResolver, so more closely
-// matches real behavior.
-//
-// By default uses a mocked out system resolver, though can be configured to
-// use the built-in async resolver (Built in DNS stub resolver) with a fuzzed
-// set of UDP/TCP sockets.
-//
-// To make behavior most deterministic, does not use the WorkerPool to run its
-// simulated platform host resolver calls, instead runs them on the thread it is
-// created on.
-//
-// Note that it does not attempt to sort the resulting AddressList when using
-// the mock system resolver path.
-//
-// The async DNS client can make system calls in AddressSorterPosix, but other
-// methods that make system calls are stubbed out.
-class FuzzedHostResolver : public HostResolverImpl {
- public:
-  // |data_provider| and |net_log| must outlive the FuzzedHostResolver.
-  FuzzedHostResolver(const Options& options,
-                     NetLog* net_log,
-                     base::FuzzedDataProvider* data_provider);
-  ~FuzzedHostResolver() override;
-
-  // Enable / disable the async resolver. When enabled, installs a
-  // DnsClient with fuzzed UDP and TCP sockets. Overrides
-  // HostResolverImpl method of the same name.
-  void SetDnsClientEnabled(bool enabled) override;
-
- private:
-  // HostResolverImpl implementation:
-  bool IsGloballyReachable(const IPAddress& dest,
-                           const NetLogWithSource& net_log) override;
-  void RunLoopbackProbeJob() override;
-
-  base::FuzzedDataProvider* data_provider_;
-
-  // Used for UDP and TCP sockets if the async resolver is enabled.
-  FuzzedSocketFactory socket_factory_;
-
-  // Fixed value to be returned by IsIPv6Reachable.
-  const bool is_ipv6_reachable_;
-
-  NetLog* net_log_;
-
-  base::WeakPtrFactory<base::FuzzedDataProvider> data_provider_weak_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(FuzzedHostResolver);
-};
-
-}  // namespace net
-
-#endif  // NET_DNS_FUZZED_HOST_RESOLVER_H_
diff --git a/net/dns/host_resolver.cc b/net/dns/host_resolver.cc
index 5313e96..2f13cc4 100644
--- a/net/dns/host_resolver.cc
+++ b/net/dns/host_resolver.cc
@@ -15,6 +15,7 @@
 #include "base/values.h"
 #include "net/base/address_list.h"
 #include "net/base/net_errors.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/dns_client.h"
 #include "net/dns/dns_util.h"
 #include "net/dns/host_cache.h"
@@ -144,15 +145,15 @@
 std::unique_ptr<HostResolver> HostResolver::CreateSystemResolver(
     const Options& options,
     NetLog* net_log) {
-  return std::unique_ptr<HostResolver>(
-      CreateSystemResolverImpl(options, net_log).release());
+  return CreateSystemResolverImpl(options, net_log);
 }
 
 // static
-std::unique_ptr<HostResolverImpl> HostResolver::CreateSystemResolverImpl(
+std::unique_ptr<ContextHostResolver> HostResolver::CreateSystemResolverImpl(
     const Options& options,
     NetLog* net_log) {
-  return std::make_unique<HostResolverImpl>(options, net_log);
+  return std::make_unique<ContextHostResolver>(
+      std::make_unique<HostResolverImpl>(options, net_log));
 }
 
 // static
@@ -162,7 +163,7 @@
 }
 
 // static
-std::unique_ptr<HostResolverImpl> HostResolver::CreateDefaultResolverImpl(
+std::unique_ptr<ContextHostResolver> HostResolver::CreateDefaultResolverImpl(
     NetLog* net_log) {
   return CreateSystemResolverImpl(Options(), net_log);
 }
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h
index 72a1f8a..7fd5cef 100644
--- a/net/dns/host_resolver.h
+++ b/net/dns/host_resolver.h
@@ -30,22 +30,21 @@
 namespace net {
 
 class AddressList;
+class ContextHostResolver;
 class DnsClient;
 struct DnsConfigOverrides;
-class HostResolverImpl;
 class NetLog;
 class NetLogWithSource;
 class URLRequestContext;
 
 // This class represents the task of resolving hostnames (or IP address
-// literal) to an AddressList object.
+// literal) to an AddressList object (or other DNS-style results).
 //
-// HostResolver can handle multiple requests at a time, so when cancelling a
-// request the RequestHandle that was returned by Resolve() needs to be
-// given.  A simpler alternative for consumers that only have 1 outstanding
-// request at a time is to create a SingleRequestHostResolver wrapper around
-// HostResolver (which will automatically cancel the single request when it
-// goes out of scope).
+// Typically implemented by ContextHostResolver or wrappers thereof. See
+// HostResolver::Create[...]() methods for construction or URLRequestContext for
+// retrieval.
+//
+// See mock_host_resolver.h for test implementations.
 class NET_EXPORT HostResolver {
  public:
   // Handler for an individual host resolution request. Created by
@@ -284,6 +283,8 @@
   // read from the system for DnsClient resolution.
   virtual void SetDnsConfigOverrides(const DnsConfigOverrides& overrides);
 
+  // Sets the URLRequestContext to be used for underlying requests made at the
+  // HTTP level (e.g. DNS over HTTPS requests).
   virtual void SetRequestContext(URLRequestContext* request_context) {}
 
   // Returns the currently configured DNS over HTTPS servers. Returns nullptr if
@@ -297,27 +298,27 @@
   static std::unique_ptr<HostResolver> CreateSystemResolver(
       const Options& options,
       NetLog* net_log);
-  // Same, but explicitly returns the HostResolverImpl. Only used by
-  // StaleHostResolver in cronet.
-  static std::unique_ptr<HostResolverImpl> CreateSystemResolverImpl(
+  // Same, but explicitly returns the implementing ContextHostResolver. Only
+  // used by tests.
+  static std::unique_ptr<ContextHostResolver> CreateSystemResolverImpl(
       const Options& options,
       NetLog* net_log);
 
   // As above, but uses default parameters.
   static std::unique_ptr<HostResolver> CreateDefaultResolver(NetLog* net_log);
-  // Same, but explicitly returns the HostResolverImpl. Only used by
-  // StaleHostResolver in cronet.
-  static std::unique_ptr<HostResolverImpl> CreateDefaultResolverImpl(
+  // Same, but explicitly returns the implementing ContextHostResolver. Only
+  // used by tests and by StaleHostResolver in Cronet.
+  static std::unique_ptr<ContextHostResolver> CreateDefaultResolverImpl(
       NetLog* net_log);
 
- protected:
-  HostResolver();
-
   // Helpers for interacting with HostCache and ProcResolver.
   static AddressFamily DnsQueryTypeToAddressFamily(DnsQueryType query_type);
   static HostResolverFlags ParametersToHostResolverFlags(
       const ResolveHostParameters& parameters);
 
+ protected:
+  HostResolver();
+
  private:
   DISALLOW_COPY_AND_ASSIGN(HostResolver);
 };
diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc
index d0c75fa..7e95f8c6 100644
--- a/net/dns/host_resolver_impl.cc
+++ b/net/dns/host_resolver_impl.cc
@@ -100,10 +100,6 @@
 
 namespace {
 
-// Default delay between calls to the system resolver for the same hostname.
-// (Can be overridden by field trial.)
-const int64_t kDnsDefaultUnresponsiveDelayMs = 6000;
-
 // Limit the size of hostnames that will be resolved to combat issues in
 // some platform's resolvers.
 const size_t kMaxHostLength = 4096;
@@ -509,7 +505,8 @@
         request_host_(request_host),
         parameters_(optional_parameters ? optional_parameters.value()
                                         : ResolveHostParameters()),
-        host_resolver_flags_(ParametersToHostResolverFlags(parameters_)),
+        host_resolver_flags_(
+            HostResolver::ParametersToHostResolverFlags(parameters_)),
         priority_(parameters_.initial_priority),
         job_(nullptr),
         resolver_(resolver),
@@ -791,7 +788,8 @@
     AddressList results;
     int os_error = 0;
     int error = resolver_proc->Resolve(
-        key.hostname, DnsQueryTypeToAddressFamily(key.dns_query_type),
+        key.hostname,
+        HostResolver::DnsQueryTypeToAddressFamily(key.dns_query_type),
         key.host_resolver_flags, &results, &os_error);
 
     network_task_runner->PostTask(
@@ -2124,25 +2122,6 @@
 
 //-----------------------------------------------------------------------------
 
-HostResolverImpl::ProcTaskParams::ProcTaskParams(
-    HostResolverProc* resolver_proc,
-    size_t max_retry_attempts)
-    : resolver_proc(resolver_proc),
-      max_retry_attempts(max_retry_attempts),
-      unresponsive_delay(
-          base::TimeDelta::FromMilliseconds(kDnsDefaultUnresponsiveDelayMs)),
-      retry_factor(2) {
-  // Maximum of 4 retry attempts for host resolution.
-  static const size_t kDefaultMaxRetryAttempts = 4u;
-  if (max_retry_attempts == HostResolver::kDefaultRetryAttempts)
-    max_retry_attempts = kDefaultMaxRetryAttempts;
-}
-
-HostResolverImpl::ProcTaskParams::ProcTaskParams(const ProcTaskParams& other) =
-    default;
-
-HostResolverImpl::ProcTaskParams::~ProcTaskParams() = default;
-
 HostResolverImpl::HostResolverImpl(const Options& options, NetLog* net_log)
     : max_queued_jobs_(0),
       proc_params_(NULL, options.max_retry_attempts),
@@ -2660,7 +2639,7 @@
     //   to lack of detected IPv6 support. (See SystemHostResolverCall for
     //   rationale).
     if (key.dns_query_type == DnsQueryType::UNSPECIFIED ||
-        DnsQueryTypeToAddressFamily(key.dns_query_type) ==
+        HostResolver::DnsQueryTypeToAddressFamily(key.dns_query_type) ==
             address.GetFamily() ||
         (address.GetFamily() == ADDRESS_FAMILY_IPV6 &&
          key.dns_query_type == DnsQueryType::A &&
@@ -2909,8 +2888,7 @@
   proc_params_.unresponsive_delay =
       GetTimeDeltaForConnectionTypeFromFieldTrialOrDefault(
           "DnsUnresponsiveDelayMsByConnectionType",
-          base::TimeDelta::FromMilliseconds(kDnsDefaultUnresponsiveDelayMs),
-          type);
+          ProcTaskParams::kDnsDefaultUnresponsiveDelay, type);
 }
 
 void HostResolverImpl::OnInitialDNSConfigRead() {
diff --git a/net/dns/host_resolver_impl.h b/net/dns/host_resolver_impl.h
index 893aeceb..24380e6 100644
--- a/net/dns/host_resolver_impl.h
+++ b/net/dns/host_resolver_impl.h
@@ -43,6 +43,12 @@
 class NetLog;
 class NetLogWithSource;
 
+// Scheduler and controller of host resolution requests. Because of the global
+// nature of host resolutions, this class is generally expected to be singleton
+// within the browser and only be interacted with through per-context
+// ContextHostResolver objects (which are themselves generally interacted with
+// though the HostResolver interface).
+//
 // For each hostname that is requested, HostResolver creates a
 // HostResolverImpl::Job. When this job gets dispatched it creates a task
 // (ProcTask for the system resolver or DnsTask for the async resolver) which
@@ -72,48 +78,14 @@
 //
 // Jobs are ordered in the queue based on their priority and order of arrival.
 class NET_EXPORT HostResolverImpl
-    : public HostResolver,
-      public NetworkChangeNotifier::IPAddressObserver,
+    : public NetworkChangeNotifier::IPAddressObserver,
       public NetworkChangeNotifier::ConnectionTypeObserver,
       public NetworkChangeNotifier::DNSObserver {
  public:
-  // Parameters for ProcTask which resolves hostnames using HostResolveProc.
-  //
-  // |resolver_proc| is used to perform the actual resolves; it must be
-  // thread-safe since it may be run from multiple worker threads. If
-  // |resolver_proc| is NULL then the default host resolver procedure is
-  // used (which is SystemHostResolverProc except if overridden).
-  //
-  // For each attempt, we could start another attempt if host is not resolved
-  // within |unresponsive_delay| time. We keep attempting to resolve the host
-  // for |max_retry_attempts|. For every retry attempt, we grow the
-  // |unresponsive_delay| by the |retry_factor| amount (that is retry interval
-  // is multiplied by the retry factor each time). Once we have retried
-  // |max_retry_attempts|, we give up on additional attempts.
-  //
-  struct NET_EXPORT_PRIVATE ProcTaskParams {
-    // Sets up defaults.
-    ProcTaskParams(HostResolverProc* resolver_proc, size_t max_retry_attempts);
-
-    ProcTaskParams(const ProcTaskParams& other);
-
-    ~ProcTaskParams();
-
-    // The procedure to use for resolving host names. This will be NULL, except
-    // in the case of unit-tests which inject custom host resolving behaviors.
-    scoped_refptr<HostResolverProc> resolver_proc;
-
-    // Maximum number retry attempts to resolve the hostname.
-    // Pass HostResolver::kDefaultRetryAttempts to choose a default value.
-    size_t max_retry_attempts;
-
-    // This is the limit after which we make another attempt to resolve the host
-    // if the worker thread has not responded yet.
-    base::TimeDelta unresponsive_delay;
-
-    // Factor to grow |unresponsive_delay| when we re-re-try.
-    uint32_t retry_factor;
-  };
+  using MdnsListener = HostResolver::MdnsListener;
+  using Options = HostResolver::Options;
+  using ResolveHostRequest = HostResolver::ResolveHostRequest;
+  using ResolveHostParameters = HostResolver::ResolveHostParameters;
 
   // Creates a HostResolver as specified by |options|. Blocking tasks are run in
   // TaskScheduler.
@@ -143,20 +115,18 @@
   std::unique_ptr<ResolveHostRequest> CreateRequest(
       const HostPortPair& host,
       const NetLogWithSource& net_log,
-      const base::Optional<ResolveHostParameters>& optional_parameters)
-      override;
-  std::unique_ptr<MdnsListener> CreateMdnsListener(
-      const HostPortPair& host,
-      DnsQueryType query_type) override;
-  void SetDnsClientEnabled(bool enabled) override;
+      const base::Optional<ResolveHostParameters>& optional_parameters);
+  std::unique_ptr<MdnsListener> CreateMdnsListener(const HostPortPair& host,
+                                                   DnsQueryType query_type);
+  void SetDnsClientEnabled(bool enabled);
 
-  HostCache* GetHostCache() override;
+  HostCache* GetHostCache();
   bool HasCached(base::StringPiece hostname,
                  HostCache::Entry::Source* source_out,
                  HostCache::EntryStaleness* stale_out,
-                 bool* secure_out) const override;
+                 bool* secure_out) const;
 
-  std::unique_ptr<base::Value> GetDnsConfigAsValue() const override;
+  std::unique_ptr<base::Value> GetDnsConfigAsValue() const;
 
   // Returns the number of host cache entries that were restored, or 0 if there
   // is no cache.
@@ -164,14 +134,14 @@
   // Returns the number of entries in the host cache, or 0 if there is no cache.
   size_t CacheSize() const;
 
-  void SetNoIPv6OnWifi(bool no_ipv6_on_wifi) override;
-  bool GetNoIPv6OnWifi() override;
+  void SetNoIPv6OnWifi(bool no_ipv6_on_wifi);
+  bool GetNoIPv6OnWifi();
 
-  void SetDnsConfigOverrides(const DnsConfigOverrides& overrides) override;
+  void SetDnsConfigOverrides(const DnsConfigOverrides& overrides);
 
-  void SetRequestContext(URLRequestContext* request_context) override;
+  void SetRequestContext(URLRequestContext* request_context);
   const std::vector<DnsConfig::DnsOverHttpsServerConfig>*
-  GetDnsOverHttpsServersForTesting() const override;
+  GetDnsOverHttpsServersForTesting() const;
 
   void set_proc_params_for_test(const ProcTaskParams& proc_params) {
     proc_params_ = proc_params;
diff --git a/net/dns/host_resolver_impl_fuzzer.cc b/net/dns/host_resolver_impl_fuzzer.cc
index a16bebc4..20f33e4a 100644
--- a/net/dns/host_resolver_impl_fuzzer.cc
+++ b/net/dns/host_resolver_impl_fuzzer.cc
@@ -17,10 +17,11 @@
 #include "net/base/address_list.h"
 #include "net/base/net_errors.h"
 #include "net/base/request_priority.h"
-#include "net/dns/fuzzed_host_resolver.h"
+#include "net/dns/fuzzed_context_host_resolver.h"
 #include "net/dns/host_resolver.h"
 #include "net/log/net_log_with_source.h"
 #include "net/log/test_net_log.h"
+#include "net/net_buildflags.h"
 
 namespace {
 
@@ -206,7 +207,8 @@
     options.max_concurrent_resolves =
         data_provider.ConsumeIntegralInRange(1, 8);
     options.enable_caching = data_provider.ConsumeBool();
-    net::FuzzedHostResolver host_resolver(options, &net_log, &data_provider);
+    net::FuzzedContextHostResolver host_resolver(options, &net_log,
+                                                 &data_provider);
     host_resolver.SetDnsClientEnabled(data_provider.ConsumeBool());
 
     std::vector<std::unique_ptr<DnsRequest>> dns_requests;
diff --git a/net/dns/host_resolver_impl_unittest.cc b/net/dns/host_resolver_impl_unittest.cc
index 1238156..19f8ef7 100644
--- a/net/dns/host_resolver_impl_unittest.cc
+++ b/net/dns/host_resolver_impl_unittest.cc
@@ -82,9 +82,8 @@
   return options;
 }
 
-HostResolverImpl::ProcTaskParams DefaultParams(
-    HostResolverProc* resolver_proc) {
-  return HostResolverImpl::ProcTaskParams(resolver_proc, kMaxRetryAttempts);
+ProcTaskParams DefaultParams(HostResolverProc* resolver_proc) {
+  return ProcTaskParams(resolver_proc, kMaxRetryAttempts);
 }
 
 // A HostResolverProc that pushes each host mapped into a list and allows
@@ -479,7 +478,7 @@
   // This HostResolverImpl will only allow 1 outstanding resolve at a time and
   // perform no retries.
   void CreateSerialResolver() {
-    HostResolverImpl::ProcTaskParams params = DefaultParams(proc_.get());
+    ProcTaskParams params = DefaultParams(proc_.get());
     params.max_retry_attempts = 0u;
     CreateResolverWithLimitsAndParams(1u, params, true /* ipv6_reachable */);
   }
@@ -494,10 +493,9 @@
     EXPECT_FALSE(proc_->HasBlockedRequests());
   }
 
-  virtual void CreateResolverWithLimitsAndParams(
-      size_t max_concurrent_resolves,
-      const HostResolverImpl::ProcTaskParams& params,
-      bool ipv6_reachable) {
+  virtual void CreateResolverWithLimitsAndParams(size_t max_concurrent_resolves,
+                                                 const ProcTaskParams& params,
+                                                 bool ipv6_reachable) {
     HostResolverImpl::Options options = DefaultOptions();
     options.max_concurrent_resolves = max_concurrent_resolves;
     resolver_.reset(new TestHostResolverImpl(options, NULL, ipv6_reachable));
@@ -1775,7 +1773,7 @@
       new LookupAttemptHostResolverProc(NULL, kAttemptNumberToResolve,
                                         kTotalAttempts));
 
-  HostResolverImpl::ProcTaskParams params = DefaultParams(resolver_proc.get());
+  ProcTaskParams params = DefaultParams(resolver_proc.get());
   base::TimeDelta unresponsive_delay = params.unresponsive_delay;
   int retry_factor = params.retry_factor;
 
@@ -2816,10 +2814,9 @@
   }
 
   // HostResolverImplTest implementation:
-  void CreateResolverWithLimitsAndParams(
-      size_t max_concurrent_resolves,
-      const HostResolverImpl::ProcTaskParams& params,
-      bool ipv6_reachable) override {
+  void CreateResolverWithLimitsAndParams(size_t max_concurrent_resolves,
+                                         const ProcTaskParams& params,
+                                         bool ipv6_reachable) override {
     HostResolverImpl::Options options = DefaultOptions();
     options.max_concurrent_resolves = max_concurrent_resolves;
     resolver_.reset(new TestHostResolverImpl(options, NULL, ipv6_reachable));
diff --git a/net/dns/host_resolver_proc.cc b/net/dns/host_resolver_proc.cc
index 94fcaaa..ab5e16f 100644
--- a/net/dns/host_resolver_proc.cc
+++ b/net/dns/host_resolver_proc.cc
@@ -14,6 +14,7 @@
 #include "net/base/sys_addrinfo.h"
 #include "net/dns/dns_reloader.h"
 #include "net/dns/dns_util.h"
+#include "net/dns/host_resolver.h"
 
 #if defined(OS_OPENBSD)
 #define AI_ADDRCONFIG 0
@@ -275,4 +276,23 @@
 
 SystemHostResolverProc::~SystemHostResolverProc() = default;
 
+const base::TimeDelta ProcTaskParams::kDnsDefaultUnresponsiveDelay =
+    base::TimeDelta::FromSeconds(6);
+
+ProcTaskParams::ProcTaskParams(HostResolverProc* resolver_proc,
+                               size_t max_retry_attempts)
+    : resolver_proc(resolver_proc),
+      max_retry_attempts(max_retry_attempts),
+      unresponsive_delay(kDnsDefaultUnresponsiveDelay),
+      retry_factor(2) {
+  // Maximum of 4 retry attempts for host resolution.
+  static const size_t kDefaultMaxRetryAttempts = 4u;
+  if (max_retry_attempts == HostResolver::kDefaultRetryAttempts)
+    max_retry_attempts = kDefaultMaxRetryAttempts;
+}
+
+ProcTaskParams::ProcTaskParams(const ProcTaskParams& other) = default;
+
+ProcTaskParams::~ProcTaskParams() = default;
+
 }  // namespace net
diff --git a/net/dns/host_resolver_proc.h b/net/dns/host_resolver_proc.h
index 182298b7f..36bdb1f 100644
--- a/net/dns/host_resolver_proc.h
+++ b/net/dns/host_resolver_proc.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/time/time.h"
 #include "net/base/address_family.h"
 #include "net/base/net_export.h"
 
@@ -108,6 +109,48 @@
   DISALLOW_COPY_AND_ASSIGN(SystemHostResolverProc);
 };
 
+// Parameters for customizing HostResolverProc behavior in HostResolvers.
+//
+// |resolver_proc| is used to perform the actual resolves; it must be
+// thread-safe since it may be run from multiple worker threads. If
+// |resolver_proc| is NULL then the default host resolver procedure is
+// used (which is SystemHostResolverProc except if overridden).
+//
+// For each attempt, we could start another attempt if host is not resolved
+// within |unresponsive_delay| time. We keep attempting to resolve the host
+// for |max_retry_attempts|. For every retry attempt, we grow the
+// |unresponsive_delay| by the |retry_factor| amount (that is retry interval
+// is multiplied by the retry factor each time). Once we have retried
+// |max_retry_attempts|, we give up on additional attempts.
+//
+struct NET_EXPORT_PRIVATE ProcTaskParams {
+  // Default delay between calls to the system resolver for the same hostname.
+  // (Can be overridden by field trial.)
+  static const base::TimeDelta kDnsDefaultUnresponsiveDelay;
+
+  // Sets up defaults.
+  ProcTaskParams(HostResolverProc* resolver_proc, size_t max_retry_attempts);
+
+  ProcTaskParams(const ProcTaskParams& other);
+
+  ~ProcTaskParams();
+
+  // The procedure to use for resolving host names. This will be NULL, except
+  // in the case of unit-tests which inject custom host resolving behaviors.
+  scoped_refptr<HostResolverProc> resolver_proc;
+
+  // Maximum number retry attempts to resolve the hostname.
+  // Pass HostResolver::kDefaultRetryAttempts to choose a default value.
+  size_t max_retry_attempts;
+
+  // This is the limit after which we make another attempt to resolve the host
+  // if the worker thread has not responded yet.
+  base::TimeDelta unresponsive_delay;
+
+  // Factor to grow |unresponsive_delay| when we re-re-try.
+  uint32_t retry_factor;
+};
+
 }  // namespace net
 
 #endif  // NET_DNS_HOST_RESOLVER_PROC_H_
diff --git a/net/http/http_proxy_client_socket_wrapper.cc b/net/http/http_proxy_client_socket_wrapper.cc
index 2829cb4a..46139820 100644
--- a/net/http/http_proxy_client_socket_wrapper.cc
+++ b/net/http/http_proxy_client_socket_wrapper.cc
@@ -40,9 +40,10 @@
 HttpProxyClientSocketWrapper::HttpProxyClientSocketWrapper(
     const OnProxyAuthChallengeCallback& on_proxy_auth_callback,
     RequestPriority priority,
+    const SocketTag& socket_tag,
     base::TimeDelta connect_timeout_duration,
     base::TimeDelta proxy_negotiation_timeout_duration,
-    const CommonConnectJobParams& common_connect_job_params,
+    const CommonConnectJobParams* common_connect_job_params,
     const scoped_refptr<TransportSocketParams>& transport_params,
     const scoped_refptr<SSLSocketParams>& ssl_params,
     quic::QuicTransportVersion quic_version,
@@ -59,6 +60,7 @@
     : on_proxy_auth_callback_(on_proxy_auth_callback),
       next_state_(STATE_NONE),
       priority_(priority),
+      socket_tag_(socket_tag),
       connect_timeout_duration_(connect_timeout_duration),
       proxy_negotiation_timeout_duration_(proxy_negotiation_timeout_duration),
       transport_params_(transport_params),
@@ -81,7 +83,7 @@
                             GetDestination().ToString()),
                        http_auth_cache,
                        http_auth_handler_factory,
-                       common_connect_job_params_.host_resolver)
+                       common_connect_job_params_->host_resolver)
                  : nullptr),
       net_log_(NetLogWithSource::Make(
           net_log.net_log(),
@@ -527,8 +529,8 @@
 int HttpProxyClientSocketWrapper::DoTransportConnect() {
   next_state_ = STATE_TCP_CONNECT_COMPLETE;
   nested_connect_job_ = TransportConnectJob::CreateTransportConnectJob(
-      transport_params_, priority_, common_connect_job_params_, this,
-      &net_log_);
+      transport_params_, priority_, socket_tag_, common_connect_job_params_,
+      this, &net_log_);
   return nested_connect_job_->Connect();
 }
 
@@ -561,8 +563,7 @@
   if (tunnel_) {
     SpdySessionKey key(ssl_params_->GetDirectConnectionParams()->destination(),
                        ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
-                       SpdySessionKey::IsProxySession::kTrue,
-                       common_connect_job_params_.socket_tag);
+                       SpdySessionKey::IsProxySession::kTrue, socket_tag_);
     if (spdy_session_pool_->FindAvailableSession(
             key, /* enable_ip_based_pooling = */ true,
             /* is_websocket = */ false, net_log_)) {
@@ -573,7 +574,8 @@
   }
   next_state_ = STATE_SSL_CONNECT_COMPLETE;
   nested_connect_job_ = std::make_unique<SSLConnectJob>(
-      priority_, common_connect_job_params_, ssl_params_, this, &net_log_);
+      priority_, socket_tag_, common_connect_job_params_, ssl_params_, this,
+      &net_log_);
   return nested_connect_job_->Connect();
 }
 
@@ -650,12 +652,13 @@
 
   // Add a HttpProxy connection on top of the tcp socket.
   transport_socket_ =
-      common_connect_job_params_.client_socket_factory->CreateProxyClientSocket(
-          nested_connect_job_->PassSocket(), user_agent_, endpoint_,
-          ProxyServer(GetProxyServerScheme(), GetDestination()),
-          http_auth_controller_.get(), tunnel_, using_spdy_,
-          negotiated_protocol_, common_connect_job_params_.proxy_delegate,
-          ssl_params_.get() != nullptr, traffic_annotation_);
+      common_connect_job_params_->client_socket_factory
+          ->CreateProxyClientSocket(
+              nested_connect_job_->PassSocket(), user_agent_, endpoint_,
+              ProxyServer(GetProxyServerScheme(), GetDestination()),
+              http_auth_controller_.get(), tunnel_, using_spdy_,
+              negotiated_protocol_, common_connect_job_params_->proxy_delegate,
+              ssl_params_.get() != nullptr, traffic_annotation_);
   nested_connect_job_.reset();
   return transport_socket_->Connect(base::Bind(
       &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this)));
@@ -683,8 +686,7 @@
   DCHECK(ssl_params_);
   SpdySessionKey key(ssl_params_->GetDirectConnectionParams()->destination(),
                      ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
-                     SpdySessionKey::IsProxySession::kTrue,
-                     common_connect_job_params_.socket_tag);
+                     SpdySessionKey::IsProxySession::kTrue, socket_tag_);
   base::WeakPtr<SpdySession> spdy_session =
       spdy_session_pool_->FindAvailableSession(
           key, /* enable_ip_based_pooling = */ true,
@@ -705,8 +707,8 @@
   spdy_stream_request_ = std::make_unique<SpdyStreamRequest>();
   return spdy_stream_request_->StartRequest(
       SPDY_BIDIRECTIONAL_STREAM, spdy_session,
-      GURL("https://" + endpoint_.ToString()), priority_,
-      common_connect_job_params_.socket_tag, spdy_session->net_log(),
+      GURL("https://" + endpoint_.ToString()), priority_, socket_tag_,
+      spdy_session->net_log(),
       base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete,
                  base::Unretained(this)),
       traffic_annotation_);
@@ -739,8 +741,7 @@
       std::make_unique<QuicStreamRequest>(quic_stream_factory_);
   return quic_stream_request_->Request(
       proxy_server, quic_version_, ssl_params_->privacy_mode(), priority_,
-      common_connect_job_params_.socket_tag,
-      ssl_params_->ssl_config().GetCertVerifyFlags(),
+      socket_tag_, ssl_params_->ssl_config().GetCertVerifyFlags(),
       GURL("https://" + proxy_server.ToString()), net_log_,
       &quic_net_error_details_,
       /*failed_on_default_network_callback=*/CompletionOnceCallback(),
diff --git a/net/http/http_proxy_client_socket_wrapper.h b/net/http/http_proxy_client_socket_wrapper.h
index 2284743e..2882f00 100644
--- a/net/http/http_proxy_client_socket_wrapper.h
+++ b/net/http/http_proxy_client_socket_wrapper.h
@@ -27,6 +27,7 @@
 #include "net/quic/quic_stream_factory.h"
 #include "net/socket/connect_job.h"
 #include "net/socket/next_proto.h"
+#include "net/socket/socket_tag.h"
 #include "net/socket/ssl_client_socket.h"
 #include "net/spdy/spdy_session.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -64,9 +65,10 @@
   HttpProxyClientSocketWrapper(
       const OnProxyAuthChallengeCallback& on_proxy_auth_callback,
       RequestPriority priority,
+      const SocketTag& socket_tag,
       base::TimeDelta connect_timeout_duration,
       base::TimeDelta proxy_negotiation_timeout_duration,
-      const CommonConnectJobParams& common_connect_job_params,
+      const CommonConnectJobParams* common_connect_job_params,
       const scoped_refptr<TransportSocketParams>& transport_params,
       const scoped_refptr<SSLSocketParams>& ssl_params,
       quic::QuicTransportVersion quic_version,
@@ -205,6 +207,7 @@
   State next_state_;
 
   RequestPriority priority_;
+  const SocketTag socket_tag_;
   const base::TimeDelta connect_timeout_duration_;
   const base::TimeDelta proxy_negotiation_timeout_duration_;
 
@@ -220,7 +223,7 @@
   bool has_restarted_;
   const bool tunnel_;
 
-  const CommonConnectJobParams common_connect_job_params_;
+  const CommonConnectJobParams* common_connect_job_params_;
 
   bool using_spdy_;
   bool is_trusted_proxy_;
diff --git a/net/http/http_proxy_connect_job.cc b/net/http/http_proxy_connect_job.cc
index de7c208..6e1ed3d4 100644
--- a/net/http/http_proxy_connect_job.cc
+++ b/net/http/http_proxy_connect_job.cc
@@ -166,11 +166,13 @@
 
 HttpProxyConnectJob::HttpProxyConnectJob(
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     const scoped_refptr<HttpProxySocketParams>& params,
     Delegate* delegate,
     const NetLogWithSource* net_log)
     : ConnectJob(priority,
+                 socket_tag,
                  base::TimeDelta() /* The socket takes care of timeouts */,
                  common_connect_job_params,
                  delegate,
@@ -181,9 +183,10 @@
           base::BindRepeating(&HttpProxyConnectJob::OnNeedsProxyAuth,
                               base::Unretained(this)),
           priority,
+          socket_tag,
           ConnectionTimeout(
               *params,
-              common_connect_job_params.network_quality_estimator),
+              common_connect_job_params->network_quality_estimator),
           kHttpProxyConnectJobTunnelTimeout,
           common_connect_job_params,
           params->transport_params(),
diff --git a/net/http/http_proxy_connect_job.h b/net/http/http_proxy_connect_job.h
index ba3eece..6082729 100644
--- a/net/http/http_proxy_connect_job.h
+++ b/net/http/http_proxy_connect_job.h
@@ -27,6 +27,7 @@
 class HttpAuthHandlerFactory;
 class HttpProxyClientSocketWrapper;
 class NetworkQualityEstimator;
+class SocketTag;
 class SpdySessionPool;
 class SSLSocketParams;
 class TransportSocketParams;
@@ -102,7 +103,8 @@
 class NET_EXPORT_PRIVATE HttpProxyConnectJob : public ConnectJob {
  public:
   HttpProxyConnectJob(RequestPriority priority,
-                      const CommonConnectJobParams& common_connect_job_params,
+                      const SocketTag& socket_tag,
+                      const CommonConnectJobParams* common_connect_job_params,
                       const scoped_refptr<HttpProxySocketParams>& params,
                       Delegate* delegate,
                       const NetLogWithSource* net_log);
diff --git a/net/http/http_proxy_connect_job_unittest.cc b/net/http/http_proxy_connect_job_unittest.cc
index e69a00b..4000f8a 100644
--- a/net/http/http_proxy_connect_job_unittest.cc
+++ b/net/http/http_proxy_connect_job_unittest.cc
@@ -70,6 +70,7 @@
     network_quality_estimator_ =
         std::make_unique<TestNetworkQualityEstimator>();
     session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+    InitCommonConnectJobParams();
   }
 
   virtual ~HttpProxyConnectJobTest() {
@@ -158,32 +159,38 @@
       ConnectJob::Delegate* delegate,
       RequestPriority priority) {
     return std::make_unique<HttpProxyConnectJob>(
-        priority,
-        CommonConnectJobParams(
-            "group_name", SocketTag(), &socket_factory_,
-            session_deps_.host_resolver.get(), proxy_delegate_.get(),
-            SSLClientSocketContext(
-                session_deps_.cert_verifier.get(),
-                session_deps_.channel_id_service.get(),
-                session_deps_.transport_security_state.get(),
-                session_deps_.cert_transparency_verifier.get(),
-                session_deps_.ct_policy_enforcer.get(),
-                nullptr /* ssl_session_cache_arg */),
-            SSLClientSocketContext(
-                session_deps_.cert_verifier.get(),
-                session_deps_.channel_id_service.get(),
-                session_deps_.transport_security_state.get(),
-                session_deps_.cert_transparency_verifier.get(),
-                session_deps_.ct_policy_enforcer.get(),
-                nullptr /* ssl_session_cache_arg */),
-            nullptr /* socket_performance_watcher_factory */,
-            network_quality_estimator_.get(), nullptr /* net_log */,
-            nullptr /* websocket_endpoint_lock_manager */),
+        priority, SocketTag(), common_connect_job_params_.get(),
         std::move(http_proxy_socket_params), delegate, nullptr /* net_log */);
   }
 
+  // This may only be called at the start of the test, before any ConnectJobs
+  // have been created.
   void InitProxyDelegate() {
     proxy_delegate_ = std::make_unique<TestProxyDelegate>();
+    InitCommonConnectJobParams();
+  }
+
+  // This may only be called at the start of the test, before any ConnectJobs
+  // have been created.
+  void InitCommonConnectJobParams() {
+    common_connect_job_params_ = std::make_unique<CommonConnectJobParams>(
+        &socket_factory_, session_deps_.host_resolver.get(),
+        proxy_delegate_.get(),
+        SSLClientSocketContext(session_deps_.cert_verifier.get(),
+                               session_deps_.channel_id_service.get(),
+                               session_deps_.transport_security_state.get(),
+                               session_deps_.cert_transparency_verifier.get(),
+                               session_deps_.ct_policy_enforcer.get(),
+                               nullptr /* ssl_session_cache_arg */),
+        SSLClientSocketContext(session_deps_.cert_verifier.get(),
+                               session_deps_.channel_id_service.get(),
+                               session_deps_.transport_security_state.get(),
+                               session_deps_.cert_transparency_verifier.get(),
+                               session_deps_.ct_policy_enforcer.get(),
+                               nullptr /* ssl_session_cache_arg */),
+        nullptr /* socket_performance_watcher_factory */,
+        network_quality_estimator_.get(), nullptr /* net_log */,
+        nullptr /* websocket_endpoint_lock_manager */);
   }
 
   void Initialize(base::span<const MockRead> reads,
@@ -242,6 +249,8 @@
   SpdyTestUtil spdy_util_;
 
   TestCompletionCallback callback_;
+
+  std::unique_ptr<CommonConnectJobParams> common_connect_job_params_;
 };
 
 // All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY)
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index c2531a3f..0688dd0 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -7,6 +7,7 @@
 // The following line silences a presubmit warning that would otherwise be
 // triggered by this:
 // no-include-guard-because-multiply-included
+// NOLINT(build/header_guard)
 
 // In the event of a failure, a many end events will have a |net_error|
 // parameter with the integer error code associated with the failure.  Most
@@ -785,8 +786,13 @@
 //   }
 EVENT_TYPE(TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS)
 
-// A backup connect job is created due to slow connect.
-EVENT_TYPE(BACKUP_CONNECT_JOB_CREATED)
+// A connect job is created by a socket pool. Its parameters are:
+//   {
+//     "backup_job": <Whether this is a backup job created because the other
+//                    ConnectJob was taking too long>,
+//     "group_name": <The group name for the socket request>,
+//   }
+EVENT_TYPE(SOCKET_POOL_CONNECT_JOB_CREATED)
 
 // This event is sent when a connect job is eventually bound to a request
 // (because of late binding and socket backup jobs, we don't assign the job to
diff --git a/net/network_error_logging/network_error_logging_service.cc b/net/network_error_logging/network_error_logging_service.cc
index 78bc90a8..4607d9f 100644
--- a/net/network_error_logging/network_error_logging_service.cc
+++ b/net/network_error_logging/network_error_logging_service.cc
@@ -207,17 +207,20 @@
     OriginPolicy policy;
     policy.origin = origin;
     policy.received_ip_address = received_ip_address;
+    policy.last_used = clock_->Now();
     HeaderOutcome outcome = ParseHeader(value, clock_->Now(), &policy);
     RecordHeaderOutcome(outcome);
     if (outcome != HeaderOutcome::SET && outcome != HeaderOutcome::REMOVED)
       return;
 
+    // If a policy for |origin| already existed, remove the old poliicy.
     auto it = policies_.find(origin);
-    if (it != policies_.end()) {
-      MaybeRemoveWildcardPolicy(origin, &it->second);
-      policies_.erase(it);
-    }
+    if (it != policies_.end())
+      RemovePolicy(it);
 
+    // A policy's |expires| field is set to a null time if the max_age was 0.
+    // Having a max_age of 0 means that the policy should be removed, so return
+    // here instead of continuing on to inserting the policy.
     if (policy.expires.is_null())
       return;
 
@@ -225,6 +228,14 @@
     auto inserted = policies_.insert(std::make_pair(origin, policy));
     DCHECK(inserted.second);
     MaybeAddWildcardPolicy(origin, &inserted.first->second);
+
+    // Evict policies if the policy limit is exceeded.
+    if (policies_.size() > kMaxPolicies) {
+      RemoveAllExpiredPolicies();
+      while (policies_.size() > kMaxPolicies) {
+        EvictStalestPolicy();
+      }
+    }
   }
 
   void OnRequest(RequestDetails details) override {
@@ -246,6 +257,9 @@
       return;
     }
 
+    // Mark the policy used.
+    policy->last_used = clock_->Now();
+
     Error type = details.type;
     // It is expected for Reporting uploads to terminate with ERR_ABORTED, since
     // the ReportingUploader cancels them after receiving the response code and
@@ -338,6 +352,10 @@
           RequestOutcome::kDiscardedNoOriginPolicy);
       return;
     }
+
+    // Mark the policy used.
+    policy->last_used = clock_->Now();
+
     if (IsMismatchingSubdomainReport(*policy, report_origin)) {
       RecordSignedExchangeRequestOutcome(
           RequestOutcome::kDiscardedNonDNSSubdomainReport);
@@ -371,17 +389,14 @@
 
   void RemoveBrowsingData(const base::RepeatingCallback<bool(const GURL&)>&
                               origin_filter) override {
-    std::vector<url::Origin> origins_to_remove;
-
-    for (auto it = policies_.begin(); it != policies_.end(); ++it) {
-      if (origin_filter.Run(it->first.GetURL()))
-        origins_to_remove.push_back(it->first);
-    }
-
-    for (auto it = origins_to_remove.begin(); it != origins_to_remove.end();
-         ++it) {
-      MaybeRemoveWildcardPolicy(*it, &policies_[*it]);
-      policies_.erase(*it);
+    for (auto it = policies_.begin(); it != policies_.end();) {
+      const url::Origin& origin = it->first;
+      // Remove policies matching the filter.
+      if (origin_filter.Run(origin.GetURL())) {
+        it = RemovePolicy(it);
+      } else {
+        ++it;
+      }
     }
   }
 
@@ -437,6 +452,10 @@
     double success_fraction;
     double failure_fraction;
     bool include_subdomains;
+
+    // Last time the policy was accessed to create a report, even if no report
+    // ends up being queued. Also updated when the policy is first set.
+    mutable base::Time last_used;
   };
 
   // Map from origin to origin's (owned) policy.
@@ -524,7 +543,6 @@
   }
 
   const OriginPolicy* FindPolicyForOrigin(const url::Origin& origin) const {
-    // TODO(juliatuttle): Clean out expired policies sometime/somewhere.
     auto it = policies_.find(origin);
     if (it != policies_.end() && clock_->Now() < it->second.expires)
       return &it->second;
@@ -576,14 +594,24 @@
     DCHECK(inserted.second);
   }
 
-  void MaybeRemoveWildcardPolicy(const url::Origin& origin,
-                                 const OriginPolicy* policy) {
+  // Removes the policy pointed to by |policy_it|. Invalidates |policy_it|.
+  // Returns the iterator to the next element.
+  PolicyMap::iterator RemovePolicy(PolicyMap::iterator policy_it) {
+    DCHECK(policy_it != policies_.end());
+    OriginPolicy* policy = &policy_it->second;
+    MaybeRemoveWildcardPolicy(policy);
+    return policies_.erase(policy_it);
+  }
+
+  void MaybeRemoveWildcardPolicy(const OriginPolicy* policy) {
     DCHECK(policy);
-    DCHECK_EQ(policy, &policies_[origin]);
 
     if (!policy->include_subdomains)
       return;
 
+    const url::Origin& origin = policy->origin;
+    DCHECK_EQ(policy, &policies_[origin]);
+
     auto wildcard_it = wildcard_policies_.find(origin.host());
     DCHECK(wildcard_it != wildcard_policies_.end());
 
@@ -593,6 +621,30 @@
       wildcard_policies_.erase(wildcard_it);
   }
 
+  void RemoveAllExpiredPolicies() {
+    for (auto it = policies_.begin(); it != policies_.end();) {
+      if (it->second.expires < clock_->Now()) {
+        it = RemovePolicy(it);
+      } else {
+        ++it;
+      }
+    }
+  }
+
+  void EvictStalestPolicy() {
+    PolicyMap::iterator stalest_it = policies_.begin();
+    for (auto it = policies_.begin(); it != policies_.end(); ++it) {
+      if (it->second.last_used < stalest_it->second.last_used)
+        stalest_it = it;
+    }
+
+    // This should only be called if we have hit the max policy limit, so there
+    // should be at least one policy.
+    DCHECK(stalest_it != policies_.end());
+
+    RemovePolicy(stalest_it);
+  }
+
   std::unique_ptr<const base::Value> CreateReportBody(
       const std::string& phase,
       const std::string& type,
@@ -715,6 +767,9 @@
 const char NetworkErrorLoggingService::kInnerUrlKey[] = "inner_url";
 const char NetworkErrorLoggingService::kCertUrlKey[] = "cert_url";
 
+// See also: max number of Reporting endpoints specified in ReportingPolicy.
+const size_t NetworkErrorLoggingService::kMaxPolicies = 1000u;
+
 // static
 void NetworkErrorLoggingService::
     RecordHeaderDiscardedForNoNetworkErrorLoggingService() {
diff --git a/net/network_error_logging/network_error_logging_service.h b/net/network_error_logging/network_error_logging_service.h
index 812d8b9..7527ff1b 100644
--- a/net/network_error_logging/network_error_logging_service.h
+++ b/net/network_error_logging/network_error_logging_service.h
@@ -115,6 +115,9 @@
   static const char kInnerUrlKey[];
   static const char kCertUrlKey[];
 
+  // Maximum number of NEL policies to store before evicting.
+  static const size_t kMaxPolicies;
+
   // Histograms.  These are mainly used in test cases to verify that interesting
   // events occurred.
 
diff --git a/net/network_error_logging/network_error_logging_service_unittest.cc b/net/network_error_logging/network_error_logging_service_unittest.cc
index 8ff36e0..89eae7da 100644
--- a/net/network_error_logging/network_error_logging_service_unittest.cc
+++ b/net/network_error_logging/network_error_logging_service_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/simple_test_clock.h"
 #include "base/test/values_test_util.h"
 #include "base/time/time.h"
@@ -185,6 +186,21 @@
     return reporting_service_->reports();
   }
 
+  const url::Origin MakeOrigin(size_t index) {
+    GURL url(base::StringPrintf("https://example%zd.com/", index));
+    return url::Origin::Create(url);
+  }
+
+  // Returns whether the NetworkErrorLoggingService has a policy corresponding
+  // to |origin|. Returns true if so, even if the policy is expired.
+  bool HasPolicyForOrigin(const url::Origin& origin) {
+    std::set<url::Origin> all_policy_origins =
+        service_->GetPolicyOriginsForTesting();
+    return all_policy_origins.find(origin) != all_policy_origins.end();
+  }
+
+  size_t PolicyCount() { return service_->GetPolicyOriginsForTesting().size(); }
+
   const GURL kUrl_ = GURL("https://example.com/path");
   const GURL kUrlDifferentPort_ = GURL("https://example.com:4433/path");
   const GURL kUrlSubdomain_ = GURL("https://subdomain.example.com/path");
@@ -585,8 +601,11 @@
 
 TEST_F(NetworkErrorLoggingServiceTest, MaxAge0) {
   service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+  EXPECT_EQ(1u, PolicyCount());
 
+  // Max_age of 0 removes the policy.
   service()->OnHeader(kOrigin_, kServerIP_, kHeaderMaxAge0_);
+  EXPECT_EQ(0u, PolicyCount());
 
   service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
 
@@ -775,22 +794,31 @@
 
 TEST_F(NetworkErrorLoggingServiceTest, RemoveAllBrowsingData) {
   service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+  EXPECT_EQ(1u, PolicyCount());
+  EXPECT_TRUE(HasPolicyForOrigin(kOrigin_));
 
   service()->RemoveAllBrowsingData();
 
   service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
 
+  EXPECT_EQ(0u, PolicyCount());
+  EXPECT_FALSE(HasPolicyForOrigin(kOrigin_));
   EXPECT_TRUE(reports().empty());
 }
 
 TEST_F(NetworkErrorLoggingServiceTest, RemoveSomeBrowsingData) {
   service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
   service()->OnHeader(kOriginDifferentHost_, kServerIP_, kHeader_);
+  EXPECT_EQ(2u, PolicyCount());
 
+  // Remove policy for kOrigin_ but not kOriginDifferentHost_
   service()->RemoveBrowsingData(
       base::BindRepeating([](const GURL& origin) -> bool {
         return origin.host() == "example.com";
       }));
+  EXPECT_EQ(1u, PolicyCount());
+  EXPECT_TRUE(HasPolicyForOrigin(kOriginDifferentHost_));
+  EXPECT_FALSE(HasPolicyForOrigin(kOrigin_));
 
   service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
 
@@ -1034,5 +1062,86 @@
   EXPECT_TRUE(reports().empty());
 }
 
+// When the max number of policies is exceeded, first try to remove expired
+// policies before evicting the least recently used unexpired policy.
+TEST_F(NetworkErrorLoggingServiceTest, EvictAllExpiredPoliciesFirst) {
+  base::SimpleTestClock clock;
+  service()->SetClockForTesting(&clock);
+
+  // Add 100 policies then make them expired.
+  for (size_t i = 0; i < 100; ++i) {
+    service()->OnHeader(MakeOrigin(i), kServerIP_, kHeader_);
+  }
+  EXPECT_EQ(100u, PolicyCount());
+  clock.Advance(base::TimeDelta::FromSeconds(86401));  // max_age is 86400 sec
+  // Expired policies are allowed to linger before hitting the policy limit.
+  EXPECT_EQ(100u, PolicyCount());
+
+  // Reach the max policy limit.
+  for (size_t i = 100; i < NetworkErrorLoggingService::kMaxPolicies; ++i) {
+    service()->OnHeader(MakeOrigin(i), kServerIP_, kHeader_);
+  }
+  EXPECT_EQ(NetworkErrorLoggingService::kMaxPolicies, PolicyCount());
+
+  // Add one more policy to trigger eviction of only the expired policies.
+  service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+  EXPECT_EQ(NetworkErrorLoggingService::kMaxPolicies - 100 + 1, PolicyCount());
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, EvictLeastRecentlyUsedPolicy) {
+  base::SimpleTestClock clock;
+  service()->SetClockForTesting(&clock);
+
+  // A policy's |last_used| is updated when it is added
+  for (size_t i = 0; i < NetworkErrorLoggingService::kMaxPolicies; ++i) {
+    service()->OnHeader(MakeOrigin(i), kServerIP_, kHeader_);
+    clock.Advance(base::TimeDelta::FromSeconds(1));
+  }
+
+  EXPECT_EQ(PolicyCount(), NetworkErrorLoggingService::kMaxPolicies);
+
+  // Set another policy which triggers eviction. None of the policies have
+  // expired, so the least recently used (i.e. least recently added) policy
+  // should be evicted.
+  service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+  clock.Advance(base::TimeDelta::FromSeconds(1));
+  EXPECT_EQ(PolicyCount(), NetworkErrorLoggingService::kMaxPolicies);
+
+  EXPECT_FALSE(HasPolicyForOrigin(MakeOrigin(0)));  // evicted
+  std::set<url::Origin> all_policy_origins =
+      service()->GetPolicyOriginsForTesting();
+  for (size_t i = 1; i < NetworkErrorLoggingService::kMaxPolicies; ++i) {
+    // Avoid n calls to HasPolicyForOrigin(), which would be O(n^2).
+    EXPECT_EQ(1u, all_policy_origins.count(MakeOrigin(i)));
+  }
+  EXPECT_TRUE(HasPolicyForOrigin(kOrigin_));
+
+  // Now use the policies in reverse order starting with kOrigin_, then add
+  // another policy to trigger eviction, to check that the stalest policy is
+  // identified correctly.
+  service()->OnRequest(
+      MakeRequestDetails(kOrigin_.GetURL(), ERR_CONNECTION_REFUSED));
+  clock.Advance(base::TimeDelta::FromSeconds(1));
+  for (size_t i = NetworkErrorLoggingService::kMaxPolicies - 1; i >= 1; --i) {
+    service()->OnRequest(
+        MakeRequestDetails(MakeOrigin(i).GetURL(), ERR_CONNECTION_REFUSED));
+    clock.Advance(base::TimeDelta::FromSeconds(1));
+  }
+  service()->OnHeader(kOriginSubdomain_, kServerIP_, kHeader_);
+  EXPECT_EQ(PolicyCount(), NetworkErrorLoggingService::kMaxPolicies);
+
+  EXPECT_FALSE(HasPolicyForOrigin(kOrigin_));  // evicted
+  all_policy_origins = service()->GetPolicyOriginsForTesting();
+  for (size_t i = NetworkErrorLoggingService::kMaxPolicies - 1; i >= 1; --i) {
+    // Avoid n calls to HasPolicyForOrigin(), which would be O(n^2).
+    EXPECT_EQ(1u, all_policy_origins.count(MakeOrigin(i)));
+  }
+  EXPECT_TRUE(HasPolicyForOrigin(kOriginSubdomain_));  // most recently added
+
+  // Note: This test advances the clock by ~2000 seconds, which is below the
+  // specified max_age of 86400 seconds, so none of the policies expire during
+  // this test.
+}
+
 }  // namespace
 }  // namespace net
diff --git a/net/nqe/network_quality_estimator_util_unittest.cc b/net/nqe/network_quality_estimator_util_unittest.cc
index c907e910..83aeb01 100644
--- a/net/nqe/network_quality_estimator_util_unittest.cc
+++ b/net/nqe/network_quality_estimator_util_unittest.cc
@@ -11,8 +11,8 @@
 #include "net/base/host_port_pair.h"
 #include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/host_resolver.h"
-#include "net/dns/host_resolver_impl.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/log/test_net_log.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -119,20 +119,20 @@
       std::make_unique<BoundTestNetLog>();
   BoundTestNetLog* net_log_ptr = net_log.get();
 
-  net::HostResolver::Options options;
-  // Use HostResolverImpl since MockCachingHostResolver does not determine the
-  // correct answer for localhosts.
-  HostResolverImpl resolver(options, net_log_ptr->bound().net_log());
+  // Use actual HostResolver since MockCachingHostResolver does not determine
+  // the correct answer for localhosts.
+  std::unique_ptr<ContextHostResolver> resolver =
+      HostResolver::CreateDefaultResolverImpl(net_log_ptr->bound().net_log());
 
   scoped_refptr<net::RuleBasedHostResolverProc> rules(
       new net::RuleBasedHostResolverProc(nullptr));
 
-  EXPECT_TRUE(IsPrivateHost(&resolver, HostPortPair("localhost", 443)));
-  EXPECT_TRUE(IsPrivateHost(&resolver, HostPortPair("localhost6", 443)));
-  EXPECT_TRUE(IsPrivateHost(&resolver, HostPortPair("127.0.0.1", 80)));
-  EXPECT_TRUE(IsPrivateHost(&resolver, HostPortPair("0.0.0.0", 80)));
-  EXPECT_TRUE(IsPrivateHost(&resolver, HostPortPair("::1", 80)));
-  EXPECT_FALSE(IsPrivateHost(&resolver, HostPortPair("google.com", 80)));
+  EXPECT_TRUE(IsPrivateHost(resolver.get(), HostPortPair("localhost", 443)));
+  EXPECT_TRUE(IsPrivateHost(resolver.get(), HostPortPair("localhost6", 443)));
+  EXPECT_TRUE(IsPrivateHost(resolver.get(), HostPortPair("127.0.0.1", 80)));
+  EXPECT_TRUE(IsPrivateHost(resolver.get(), HostPortPair("0.0.0.0", 80)));
+  EXPECT_TRUE(IsPrivateHost(resolver.get(), HostPortPair("::1", 80)));
+  EXPECT_FALSE(IsPrivateHost(resolver.get(), HostPortPair("google.com", 80)));
 }
 
 }  // namespace
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index 174a004..ac1a62a 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -239,13 +239,6 @@
           FLAGS_quic_reloadable_flag_quic_log_cert_name_for_empty_sct,
           true)
 
-// If true, close connection with INVALID_STOP_WAITING if received a
-// STOP_WAITING with least_unacked 0.
-QUIC_FLAG(
-    bool,
-    FLAGS_quic_reloadable_flag_quic_close_connection_with_zero_least_unacked_stop_waiting,
-    true)
-
 // If true, QuicCryptoServerConfig will correctly rotate configs based on
 // primary time.
 QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_fix_config_rotation, true)
@@ -295,3 +288,15 @@
 
 // If true, enforce that QUIC CHLOs fit in one packet.
 QUIC_FLAG(bool, FLAGS_quic_enforce_single_packet_chlo, true)
+
+// If both this flag and gfe2_reloadable_flag_quic_deprecate_ack_bundling_mode
+// are true, QuicReceivedPacketManager decides when to send ACKs.
+QUIC_FLAG(bool,
+          FLAGS_quic_reloadable_flag_quic_rpm_decides_when_to_send_acks,
+          false)
+
+// If true, instead of send encryption none termination packets, send stateless
+// reset in reponse to short headers.
+QUIC_FLAG(bool,
+          FLAGS_quic_reloadable_flag_quic_always_reset_short_header_packets,
+          false)
diff --git a/net/quic/quic_stream_factory_fuzzer.cc b/net/quic/quic_stream_factory_fuzzer.cc
index 8835927..05ddcfa 100644
--- a/net/quic/quic_stream_factory_fuzzer.cc
+++ b/net/quic/quic_stream_factory_fuzzer.cc
@@ -12,7 +12,7 @@
 #include "net/cert/do_nothing_ct_verifier.h"
 #include "net/cert/mock_cert_verifier.h"
 #include "net/cert/x509_certificate.h"
-#include "net/dns/fuzzed_host_resolver.h"
+#include "net/dns/fuzzed_context_host_resolver.h"
 #include "net/http/http_server_properties_impl.h"
 #include "net/http/transport_security_state.h"
 #include "net/quic/mock_crypto_client_stream_factory.h"
@@ -83,8 +83,8 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   base::FuzzedDataProvider data_provider(data, size);
 
-  FuzzedHostResolver host_resolver(HostResolver::Options(), nullptr,
-                                   &data_provider);
+  FuzzedContextHostResolver host_resolver(HostResolver::Options(), nullptr,
+                                          &data_provider);
   FuzzedSocketFactory socket_factory(&data_provider);
 
   // Initialize this on each loop since some options mutate this.
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index 52ae592b..9d279e9 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -79,7 +79,8 @@
 
   quic::QuicFramer framer(quic::test::SupportedVersions(quic::ParsedQuicVersion(
                               quic::PROTOCOL_QUIC_CRYPTO, version_)),
-                          clock_->Now(), perspective_);
+                          clock_->Now(), perspective_,
+                          quic::kQuicDefaultConnectionIdLength);
   size_t max_plaintext_size =
       framer.GetMaxPlaintextSize(quic::kDefaultMaxPacketSize);
   char buffer[quic::kDefaultMaxPacketSize];
@@ -691,7 +692,8 @@
   }
   quic::QuicFramer framer(quic::test::SupportedVersions(quic::ParsedQuicVersion(
                               quic::PROTOCOL_QUIC_CRYPTO, version_)),
-                          clock_->Now(), perspective_);
+                          clock_->Now(), perspective_,
+                          quic::kQuicDefaultConnectionIdLength);
   quic::QuicFrames frames;
   quic::QuicFrame ack_frame(&ack);
   frames.push_back(ack_frame);
@@ -1187,7 +1189,8 @@
     quic::QuicStreamFrameDataProducer* data_producer) {
   quic::QuicFramer framer(quic::test::SupportedVersions(quic::ParsedQuicVersion(
                               quic::PROTOCOL_QUIC_CRYPTO, version_)),
-                          clock_->Now(), perspective_);
+                          clock_->Now(), perspective_,
+                          quic::kQuicDefaultConnectionIdLength);
   if (data_producer != nullptr) {
     framer.set_data_producer(data_producer);
   }
diff --git a/net/socket/client_socket_pool_base.cc b/net/socket/client_socket_pool_base.cc
index ad5e522..caae809 100644
--- a/net/socket/client_socket_pool_base.cc
+++ b/net/socket/client_socket_pool_base.cc
@@ -36,6 +36,16 @@
 // after a certain timeout has passed without receiving an ACK.
 bool g_connect_backup_jobs_enabled = true;
 
+std::unique_ptr<base::Value> NetLogCreateConnectJobCallback(
+    bool backup_job,
+    const std::string* group_name,
+    net::NetLogCaptureMode capture_mode) {
+  auto dict = std::make_unique<base::DictionaryValue>();
+  dict->SetBoolean("backup_job", backup_job);
+  dict->SetString("group_name", *group_name);
+  return std::move(dict);
+}
+
 }  // namespace
 
 namespace internal {
@@ -322,8 +332,22 @@
 
   // We couldn't find a socket to reuse, and there's space to allocate one,
   // so allocate and connect a new one.
-  std::unique_ptr<ConnectJob> connect_job(
-      connect_job_factory_->NewConnectJob(group_name, request, this));
+  group = GetOrCreateGroup(group_name);
+  connecting_socket_count_++;
+  std::unique_ptr<ConnectJob> owned_connect_job(
+      connect_job_factory_->NewConnectJob(request, group));
+  owned_connect_job->net_log().AddEvent(
+      NetLogEventType::SOCKET_POOL_CONNECT_JOB_CREATED,
+      base::BindRepeating(&NetLogCreateConnectJobCallback,
+                          false /* backup_job */, &group_name));
+  ConnectJob* connect_job = owned_connect_job.get();
+  bool was_group_empty = group->IsEmpty();
+  // Need to add the ConnectJob to the group before connecting, to ensure
+  // |group| is not empty.  Otherwise, if the ConnectJob calls back into the
+  // socket pool with a new socket request (Like for DNS over HTTPS), the pool
+  // would then notice the group is empty, and delete it. That would result in a
+  // UAF when group is referenced later in this function.
+  group->AddJob(std::move(owned_connect_job), preconnecting);
 
   int rv = connect_job->Connect();
   if (rv == OK) {
@@ -331,22 +355,17 @@
     if (!preconnecting) {
       HandOutSocket(connect_job->PassSocket(), ClientSocketHandle::UNUSED,
                     connect_job->connect_timing(), handle, base::TimeDelta(),
-                    GetOrCreateGroup(group_name), request.net_log());
+                    group, request.net_log());
     } else {
-      AddIdleSocket(connect_job->PassSocket(), GetOrCreateGroup(group_name));
+      AddIdleSocket(connect_job->PassSocket(), group);
     }
+    RemoveConnectJob(connect_job, group);
   } else if (rv == ERR_IO_PENDING) {
-    // If we don't have any sockets in this group, set a timer for potentially
+    // If we didn't have any sockets in this group, set a timer for potentially
     // creating a new one.  If the SYN is lost, this backup socket may complete
     // before the slow socket, improving end user latency.
-    Group* group = GetOrCreateGroup(group_name);
-    if (connect_backup_jobs_enabled_ && group->IsEmpty()) {
-      group->StartBackupJobTimer(group_name, this);
-    }
-
-    connecting_socket_count_++;
-
-    group->AddJob(std::move(connect_job), preconnecting);
+    if (connect_backup_jobs_enabled_ && was_group_empty)
+      group->StartBackupJobTimer(group_name);
   } else {
     LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
     std::unique_ptr<StreamSocket> error_socket;
@@ -355,14 +374,14 @@
       connect_job->GetAdditionalErrorState(handle);
       error_socket = connect_job->PassSocket();
     }
-    Group* group = GetOrCreateGroup(group_name);
     if (error_socket) {
       HandOutSocket(std::move(error_socket), ClientSocketHandle::UNUSED,
                     connect_job->connect_timing(), handle, base::TimeDelta(),
                     group, request.net_log());
-    } else if (group->IsEmpty()) {
-      RemoveGroup(group_name);
     }
+    RemoveConnectJob(connect_job, group);
+    if (group->IsEmpty())
+      RemoveGroup(group_name);
   }
 
   return rv;
@@ -703,7 +722,7 @@
   auto it = group_map_.find(group_name);
   if (it != group_map_.end())
     return it->second;
-  Group* group = new Group;
+  Group* group = new Group(group_name, this);
   group_map_[group_name] = group;
   return group;
 }
@@ -834,122 +853,6 @@
   return has_stalled_group;
 }
 
-void ClientSocketPoolBaseHelper::OnConnectJobComplete(
-    int result, ConnectJob* job) {
-  DCHECK_NE(ERR_IO_PENDING, result);
-  const std::string group_name = job->group_name();
-  auto group_it = group_map_.find(group_name);
-  CHECK(group_it != group_map_.end());
-  Group* group = group_it->second;
-
-  std::unique_ptr<StreamSocket> socket = job->PassSocket();
-
-  // Copies of these are needed because |job| may be deleted before they are
-  // accessed.
-  NetLogWithSource job_log = job->net_log();
-  LoadTimingInfo::ConnectTiming connect_timing = job->connect_timing();
-
-  // Check if the ConnectJob is already bound to a Request. If so, complete the
-  // request.
-  //
-  // TODO(mmenke) this logic resembles the case where the job is assigned to a
-  // request below. Look into merging the logic.
-  int pending_result;
-  std::unique_ptr<Request> request =
-      group->FindAndRemoveBoundRequestForConnectJob(job, &pending_result);
-  if (request) {
-    --connecting_socket_count_;
-    bool handed_out_socket = false;
-    if (pending_result != OK) {
-      result = pending_result;
-    } else {
-      if (socket) {
-        handed_out_socket = true;
-        HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
-                      connect_timing, request->handle(), base::TimeDelta(),
-                      group, request->net_log());
-      }
-    }
-    request->net_log().EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL,
-                                                result);
-    InvokeUserCallbackLater(request->handle(), request->release_callback(),
-                            result, request->socket_tag());
-    if (!handed_out_socket) {
-      OnAvailableSocketSlot(group_name, group);
-      CheckForStalledSocketGroups();
-    }
-    return;
-  }
-
-  // RemoveConnectJob(job, _) must be called by all branches below;
-  // otherwise, |job| will be leaked.
-
-  if (result == OK) {
-    DCHECK(socket.get());
-    request = group->PopNextUnboundRequest();
-    RemoveConnectJob(job, group);
-    if (request) {
-      LogBoundConnectJobToRequest(job_log.source(), *request);
-      HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
-                    connect_timing, request->handle(), base::TimeDelta(), group,
-                    request->net_log());
-      request->net_log().EndEvent(NetLogEventType::SOCKET_POOL);
-      InvokeUserCallbackLater(request->handle(), request->release_callback(),
-                              result, request->socket_tag());
-    } else {
-      AddIdleSocket(std::move(socket), group);
-      OnAvailableSocketSlot(group_name, group);
-      CheckForStalledSocketGroups();
-    }
-  } else {
-    // If we got a socket, it must contain error information so pass that
-    // up so that the caller can retrieve it.
-    bool handed_out_socket = false;
-    std::unique_ptr<Request> request = group->PopNextUnboundRequest();
-    if (request) {
-      LogBoundConnectJobToRequest(job_log.source(), *request);
-      job->GetAdditionalErrorState(request->handle());
-      RemoveConnectJob(job, group);
-      if (socket.get()) {
-        handed_out_socket = true;
-        HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
-                      connect_timing, request->handle(), base::TimeDelta(),
-                      group, request->net_log());
-      }
-      request->net_log().EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL,
-                                                  result);
-      InvokeUserCallbackLater(request->handle(), request->release_callback(),
-                              result, request->socket_tag());
-    } else {
-      RemoveConnectJob(job, group);
-    }
-    if (!handed_out_socket) {
-      OnAvailableSocketSlot(group_name, group);
-      CheckForStalledSocketGroups();
-    }
-  }
-}
-
-void ClientSocketPoolBaseHelper::OnNeedsProxyAuth(
-    const HttpResponseInfo& response,
-    HttpAuthController* auth_controller,
-    base::OnceClosure restart_with_auth_callback,
-    ConnectJob* job) {
-  auto group_it = group_map_.find(job->group_name());
-  CHECK(group_it != group_map_.end());
-
-  const Request* request = group_it->second->BindRequestToConnectJob(job);
-  // If can't bind the ConnectJob to a request, treat this as a ConnectJob
-  // failure.
-  if (!request) {
-    OnConnectJobComplete(ERR_PROXY_AUTH_REQUESTED, job);
-    return;
-  }
-
-  request->proxy_auth_callback().Run(response, auth_controller,
-                                     std::move(restart_with_auth_callback));
-}
-
 void ClientSocketPoolBaseHelper::OnIPAddressChanged() {
   FlushWithError(ERR_NETWORK_CHANGED);
 }
@@ -1153,6 +1056,122 @@
   return false;
 }
 
+void ClientSocketPoolBaseHelper::OnConnectJobComplete(Group* group,
+                                                      int result,
+                                                      ConnectJob* job) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK(group_map_.find(group->group_name()) != group_map_.end());
+  DCHECK_EQ(group, group_map_[group->group_name()]);
+
+  std::unique_ptr<StreamSocket> socket = job->PassSocket();
+
+  // Copies of these are needed because |job| may be deleted before they are
+  // accessed.
+  NetLogWithSource job_log = job->net_log();
+  LoadTimingInfo::ConnectTiming connect_timing = job->connect_timing();
+
+  // Check if the ConnectJob is already bound to a Request. If so, complete the
+  // request.
+  //
+  // TODO(mmenke) this logic resembles the case where the job is assigned to a
+  // request below. Look into merging the logic.
+  int pending_result;
+  std::unique_ptr<Request> request =
+      group->FindAndRemoveBoundRequestForConnectJob(job, &pending_result);
+  if (request) {
+    --connecting_socket_count_;
+    bool handed_out_socket = false;
+    if (pending_result != OK) {
+      result = pending_result;
+    } else {
+      if (socket) {
+        handed_out_socket = true;
+        HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
+                      connect_timing, request->handle(), base::TimeDelta(),
+                      group, request->net_log());
+      }
+    }
+    request->net_log().EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL,
+                                                result);
+    InvokeUserCallbackLater(request->handle(), request->release_callback(),
+                            result, request->socket_tag());
+    if (!handed_out_socket) {
+      OnAvailableSocketSlot(group->group_name(), group);
+      CheckForStalledSocketGroups();
+    }
+    return;
+  }
+
+  // RemoveConnectJob(job, _) must be called by all branches below;
+  // otherwise, |job| will be leaked.
+
+  if (result == OK) {
+    DCHECK(socket.get());
+    request = group->PopNextUnboundRequest();
+    RemoveConnectJob(job, group);
+    if (request) {
+      LogBoundConnectJobToRequest(job_log.source(), *request);
+      HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
+                    connect_timing, request->handle(), base::TimeDelta(), group,
+                    request->net_log());
+      request->net_log().EndEvent(NetLogEventType::SOCKET_POOL);
+      InvokeUserCallbackLater(request->handle(), request->release_callback(),
+                              result, request->socket_tag());
+    } else {
+      AddIdleSocket(std::move(socket), group);
+      OnAvailableSocketSlot(group->group_name(), group);
+      CheckForStalledSocketGroups();
+    }
+  } else {
+    // If we got a socket, it must contain error information so pass that
+    // up so that the caller can retrieve it.
+    bool handed_out_socket = false;
+    std::unique_ptr<Request> request = group->PopNextUnboundRequest();
+    if (request) {
+      LogBoundConnectJobToRequest(job_log.source(), *request);
+      job->GetAdditionalErrorState(request->handle());
+      RemoveConnectJob(job, group);
+      if (socket.get()) {
+        handed_out_socket = true;
+        HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
+                      connect_timing, request->handle(), base::TimeDelta(),
+                      group, request->net_log());
+      }
+      request->net_log().EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL,
+                                                  result);
+      InvokeUserCallbackLater(request->handle(), request->release_callback(),
+                              result, request->socket_tag());
+    } else {
+      RemoveConnectJob(job, group);
+    }
+    if (!handed_out_socket) {
+      OnAvailableSocketSlot(group->group_name(), group);
+      CheckForStalledSocketGroups();
+    }
+  }
+}
+
+void ClientSocketPoolBaseHelper::OnNeedsProxyAuth(
+    Group* group,
+    const HttpResponseInfo& response,
+    HttpAuthController* auth_controller,
+    base::OnceClosure restart_with_auth_callback,
+    ConnectJob* job) {
+  DCHECK(group_map_.find(group->group_name()) != group_map_.end());
+  DCHECK_EQ(group, group_map_[group->group_name()]);
+
+  const Request* request = group->BindRequestToConnectJob(job);
+  // If can't bind the ConnectJob to a request, treat this as a ConnectJob
+  // failure.
+  if (!request) {
+    OnConnectJobComplete(group, ERR_PROXY_AUTH_REQUESTED, job);
+    return;
+  }
+
+  request->proxy_auth_callback().Run(response, auth_controller,
+                                     std::move(restart_with_auth_callback));
+}
+
 void ClientSocketPoolBaseHelper::InvokeUserCallbackLater(
     ClientSocketHandle* handle,
     CompletionOnceCallback callback,
@@ -1192,8 +1211,12 @@
   }
 }
 
-ClientSocketPoolBaseHelper::Group::Group()
-    : never_assigned_job_count_(0),
+ClientSocketPoolBaseHelper::Group::Group(
+    const std::string& group_name,
+    ClientSocketPoolBaseHelper* client_socket_pool_base_helper)
+    : group_name_(group_name),
+      client_socket_pool_base_helper_(client_socket_pool_base_helper),
+      never_assigned_job_count_(0),
       unbound_requests_(NUM_PRIORITIES),
       active_socket_count_(0) {}
 
@@ -1205,9 +1228,24 @@
   DCHECK(bound_requests_.empty());
 }
 
+void ClientSocketPoolBaseHelper::Group::OnConnectJobComplete(int result,
+                                                             ConnectJob* job) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  client_socket_pool_base_helper_->OnConnectJobComplete(this, result, job);
+}
+
+void ClientSocketPoolBaseHelper::Group::OnNeedsProxyAuth(
+    const HttpResponseInfo& response,
+    HttpAuthController* auth_controller,
+    base::OnceClosure restart_with_auth_callback,
+    ConnectJob* job) {
+  client_socket_pool_base_helper_->OnNeedsProxyAuth(
+      this, response, auth_controller, std::move(restart_with_auth_callback),
+      job);
+}
+
 void ClientSocketPoolBaseHelper::Group::StartBackupJobTimer(
-    const std::string& group_name,
-    ClientSocketPoolBaseHelper* pool) {
+    const std::string& group_name) {
   // Only allow one timer to run at a time.
   if (BackupJobTimerIsRunning())
     return;
@@ -1215,9 +1253,9 @@
   // Unretained here is okay because |backup_job_timer_| is
   // automatically cancelled when it's destroyed.
   backup_job_timer_.Start(
-      FROM_HERE, pool->ConnectRetryInterval(),
+      FROM_HERE, client_socket_pool_base_helper_->ConnectRetryInterval(),
       base::Bind(&Group::OnBackupJobTimerFired, base::Unretained(this),
-                 group_name, pool));
+                 group_name));
 }
 
 bool ClientSocketPoolBaseHelper::Group::BackupJobTimerIsRunning() const {
@@ -1289,8 +1327,7 @@
 }
 
 void ClientSocketPoolBaseHelper::Group::OnBackupJobTimerFired(
-    std::string group_name,
-    ClientSocketPoolBaseHelper* pool) {
+    std::string group_name) {
   // If there are no more jobs pending, there is no work to do.
   // If we've done our cleanups correctly, this should not happen.
   if (jobs_.empty()) {
@@ -1314,26 +1351,31 @@
 
   // If our old job is waiting on DNS, or if we can't create any sockets
   // right now due to limits, just reset the timer.
-  if (pool->ReachedMaxSocketsLimit() ||
-      !HasAvailableSocketSlot(pool->max_sockets_per_group_) ||
+  if (client_socket_pool_base_helper_->ReachedMaxSocketsLimit() ||
+      !HasAvailableSocketSlot(
+          client_socket_pool_base_helper_->max_sockets_per_group_) ||
       (*jobs_.begin())->GetLoadState() == LOAD_STATE_RESOLVING_HOST) {
-    StartBackupJobTimer(group_name, pool);
+    StartBackupJobTimer(group_name);
     return;
   }
 
   if (unbound_requests_.empty())
     return;
 
-  std::unique_ptr<ConnectJob> backup_job =
-      pool->connect_job_factory_->NewConnectJob(
-          group_name, *unbound_requests_.FirstMax().value(), pool);
-  backup_job->net_log().AddEvent(NetLogEventType::BACKUP_CONNECT_JOB_CREATED);
+  std::unique_ptr<ConnectJob> owned_backup_job =
+      client_socket_pool_base_helper_->connect_job_factory_->NewConnectJob(
+          *unbound_requests_.FirstMax().value(), this);
+  owned_backup_job->net_log().AddEvent(
+      NetLogEventType::SOCKET_POOL_CONNECT_JOB_CREATED,
+      base::BindRepeating(&NetLogCreateConnectJobCallback,
+                          true /* backup_job */, &group_name_));
+  ConnectJob* backup_job = owned_backup_job.get();
+  AddJob(std::move(owned_backup_job), false);
+  client_socket_pool_base_helper_->connecting_socket_count_++;
   int rv = backup_job->Connect();
-  pool->connecting_socket_count_++;
-  ConnectJob* raw_backup_job = backup_job.get();
-  AddJob(std::move(backup_job), false);
-  if (rv != ERR_IO_PENDING)
-    pool->OnConnectJobComplete(rv, raw_backup_job);
+  if (rv != ERR_IO_PENDING) {
+    client_socket_pool_base_helper_->OnConnectJobComplete(this, rv, backup_job);
+  }
 }
 
 void ClientSocketPoolBaseHelper::Group::SanityCheck() const {
diff --git a/net/socket/client_socket_pool_base.h b/net/socket/client_socket_pool_base.h
index 86b13e0..774b5a55 100644
--- a/net/socket/client_socket_pool_base.h
+++ b/net/socket/client_socket_pool_base.h
@@ -73,8 +73,7 @@
 // ClientSocketPoolBaseHelper.  This class is not for external use, please use
 // ClientSocketPoolBase instead.
 class NET_EXPORT_PRIVATE ClientSocketPoolBaseHelper
-    : public ConnectJob::Delegate,
-      public NetworkChangeNotifier::IPAddressObserver {
+    : public NetworkChangeNotifier::IPAddressObserver {
  public:
   using Flags = uint32_t;
 
@@ -155,7 +154,6 @@
     virtual ~ConnectJobFactory() {}
 
     virtual std::unique_ptr<ConnectJob> NewConnectJob(
-        const std::string& group_name,
         const Request& request,
         ConnectJob::Delegate* delegate) const = 0;
 
@@ -292,13 +290,6 @@
 
   void EnableConnectBackupJobs();
 
-  // ConnectJob::Delegate methods:
-  void OnConnectJobComplete(int result, ConnectJob* job) override;
-  void OnNeedsProxyAuth(const HttpResponseInfo& response,
-                        HttpAuthController* auth_controller,
-                        base::OnceClosure restart_with_auth_callback,
-                        ConnectJob* job) override;
-
   // NetworkChangeNotifier::IPAddressObserver methods:
   void OnIPAddressChanged() override;
 
@@ -347,12 +338,20 @@
   // SanityCheck() will always be true, except during the invocation of a
   // method.  So all public methods expect the Group to pass SanityCheck() when
   // invoked.
-  class NET_EXPORT_PRIVATE Group {
+  class NET_EXPORT_PRIVATE Group : public ConnectJob::Delegate {
    public:
     using JobList = std::list<std::unique_ptr<ConnectJob>>;
 
-    Group();
-    ~Group();
+    Group(const std::string& group_name,
+          ClientSocketPoolBaseHelper* client_socket_pool_base_helper);
+    ~Group() override;
+
+    // ConnectJob::Delegate methods:
+    void OnConnectJobComplete(int result, ConnectJob* job) override;
+    void OnNeedsProxyAuth(const HttpResponseInfo& response,
+                          HttpAuthController* auth_controller,
+                          base::OnceClosure restart_with_auth_callback,
+                          ConnectJob* job) override;
 
     bool IsEmpty() const {
       return active_socket_count_ == 0 && idle_sockets_.empty() &&
@@ -389,8 +388,7 @@
 
     // Set a timer to create a backup job if it takes too long to
     // create one and if a timer isn't already running.
-    void StartBackupJobTimer(const std::string& group_name,
-                             ClientSocketPoolBaseHelper* pool);
+    void StartBackupJobTimer(const std::string& group_name);
 
     bool BackupJobTimerIsRunning() const;
 
@@ -471,6 +469,7 @@
     bool RequestWithHandleHasJobForTesting(
         const ClientSocketHandle* handle) const;
 
+    const std::string& group_name() { return group_name_; }
     size_t unassigned_job_count() const { return unassigned_jobs_.size(); }
     const JobList& jobs() const { return jobs_; }
     const std::list<IdleSocket>& idle_sockets() const { return idle_sockets_; }
@@ -545,9 +544,7 @@
     void TransferJobBetweenRequests(Request* source, Request* dest);
 
     // Called when the backup socket timer fires.
-    void OnBackupJobTimerFired(
-        std::string group_name,
-        ClientSocketPoolBaseHelper* pool);
+    void OnBackupJobTimerFired(std::string group_name);
 
     // Checks that:
     //  - |unassigned_jobs_| is empty iff there are at least as many requests
@@ -561,6 +558,9 @@
     //  - There are no duplicate entries in |unassigned_jobs_|.
     void SanityCheck() const;
 
+    const std::string group_name_;
+    ClientSocketPoolBaseHelper* const client_socket_pool_base_helper_;
+
     // Total number of ConnectJobs that have never been assigned to a Request.
     // Since jobs use late binding to requests, which ConnectJobs have or have
     // not been assigned to a request are not tracked.  This is incremented on
@@ -687,6 +687,15 @@
                                int rv,
                                const SocketTag& socket_tag);
 
+  // These correspond to ConnectJob::Delegate methods, and are invoked by the
+  // Group a ConnectJob belongs to.
+  void OnConnectJobComplete(Group* group, int result, ConnectJob* job);
+  void OnNeedsProxyAuth(Group* group,
+                        const HttpResponseInfo& response,
+                        HttpAuthController* auth_controller,
+                        base::OnceClosure restart_with_auth_callback,
+                        ConnectJob* job);
+
   // Invokes the user callback for |handle|.  By the time this task has run,
   // it's possible that the request has been cancelled, so |handle| may not
   // exist in |pending_callback_map_|.  We look up the callback and result code
@@ -780,7 +789,6 @@
     virtual ~ConnectJobFactory() {}
 
     virtual std::unique_ptr<ConnectJob> NewConnectJob(
-        const std::string& group_name,
         const Request& request,
         ConnectJob::Delegate* delegate) const = 0;
 
@@ -893,10 +901,6 @@
     return helper_.DumpMemoryStats(pmd, parent_dump_absolute_name);
   }
 
-  virtual void OnConnectJobComplete(int result, ConnectJob* job) {
-    return helper_.OnConnectJobComplete(result, job);
-  }
-
   size_t NumNeverAssignedConnectJobsInGroup(
       const std::string& group_name) const {
     return helper_.NumNeverAssignedConnectJobsInGroup(group_name);
@@ -955,12 +959,10 @@
     ~ConnectJobFactoryAdaptor() override {}
 
     std::unique_ptr<ConnectJob> NewConnectJob(
-        const std::string& group_name,
         const internal::ClientSocketPoolBaseHelper::Request& request,
         ConnectJob::Delegate* delegate) const override {
       const Request& casted_request = static_cast<const Request&>(request);
-      return connect_job_factory_->NewConnectJob(
-          group_name, casted_request, delegate);
+      return connect_job_factory_->NewConnectJob(casted_request, delegate);
     }
 
     const std::unique_ptr<ConnectJobFactory> connect_job_factory_;
diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc
index 07af0295..b753e4c 100644
--- a/net/socket/client_socket_pool_base_unittest.cc
+++ b/net/socket/client_socket_pool_base_unittest.cc
@@ -306,26 +306,15 @@
   static const int kPendingConnectDelay = 2;
 
   TestConnectJob(JobType job_type,
-                 const std::string& group_name,
                  const TestClientSocketPoolBase::Request& request,
                  base::TimeDelta timeout_duration,
+                 const CommonConnectJobParams* common_connect_job_params,
                  ConnectJob::Delegate* delegate,
-                 MockClientSocketFactory* client_socket_factory,
-                 NetLog* net_log)
+                 MockClientSocketFactory* client_socket_factory)
       : ConnectJob(request.priority(),
+                   request.socket_tag(),
                    timeout_duration,
-                   CommonConnectJobParams(
-                       group_name,
-                       request.socket_tag(),
-                       nullptr /* client_socket_factory */,
-                       nullptr /* host_resolver */,
-                       nullptr /* proxy_delegate */,
-                       SSLClientSocketContext(),
-                       SSLClientSocketContext(),
-                       nullptr /* socket_performance_watcher_factory */,
-                       nullptr /* network_quality_estimator */,
-                       net_log,
-                       nullptr /* websocket_endpoint_lock_manager */),
+                   common_connect_job_params,
                    delegate,
                    nullptr /* net_log */,
                    NetLogSourceType::TRANSPORT_CONNECT_JOB,
@@ -536,10 +525,19 @@
  public:
   TestConnectJobFactory(MockClientSocketFactory* client_socket_factory,
                         NetLog* net_log)
-      : job_type_(TestConnectJob::kMockJob),
+      : common_connect_job_params_(
+            nullptr /* client_socket_factory */,
+            nullptr /* host_resolver */,
+            nullptr /* proxy_delegate */,
+            SSLClientSocketContext(),
+            SSLClientSocketContext(),
+            nullptr /* socket_performance_watcher_factory */,
+            nullptr /* network_quality_estimator */,
+            net_log,
+            nullptr /* websocket_endpoint_lock_manager */),
+        job_type_(TestConnectJob::kMockJob),
         job_types_(NULL),
-        client_socket_factory_(client_socket_factory),
-        net_log_(net_log) {}
+        client_socket_factory_(client_socket_factory) {}
 
   ~TestConnectJobFactory() override = default;
 
@@ -557,7 +555,6 @@
   // ConnectJobFactory implementation.
 
   std::unique_ptr<ConnectJob> NewConnectJob(
-      const std::string& group_name,
       const TestClientSocketPoolBase::Request& request,
       ConnectJob::Delegate* delegate) const override {
     EXPECT_TRUE(!job_types_ || !job_types_->empty());
@@ -566,17 +563,17 @@
       job_type = job_types_->front();
       job_types_->pop_front();
     }
-    return std::unique_ptr<ConnectJob>(
-        new TestConnectJob(job_type, group_name, request, timeout_duration_,
-                           delegate, client_socket_factory_, net_log_));
+    return std::make_unique<TestConnectJob>(
+        job_type, request, timeout_duration_, &common_connect_job_params_,
+        delegate, client_socket_factory_);
   }
 
  private:
+  const CommonConnectJobParams common_connect_job_params_;
   TestConnectJob::JobType job_type_;
   std::list<TestConnectJob::JobType>* job_types_;
   base::TimeDelta timeout_duration_;
   MockClientSocketFactory* const client_socket_factory_;
-  NetLog* net_log_;
 
   DISALLOW_COPY_AND_ASSIGN(TestConnectJobFactory);
 };
diff --git a/net/socket/client_socket_pool_manager_impl.cc b/net/socket/client_socket_pool_manager_impl.cc
index 65ff148..c0de62a 100644
--- a/net/socket/client_socket_pool_manager_impl.cc
+++ b/net/socket/client_socket_pool_manager_impl.cc
@@ -107,7 +107,6 @@
         ssl_client_session_cache_privacy_mode_, ssl_config_service_,
         network_quality_estimator_, websocket_endpoint_lock_manager_, net_log_);
   } else {
-    // TODO(mmenke): Can the SOCKS check be removed?
     new_pool = std::make_unique<TransportClientSocketPool>(
         sockets_per_proxy_server, sockets_per_group,
         unused_idle_socket_timeout(pool_type_), socket_factory_, host_resolver_,
@@ -115,8 +114,8 @@
         transport_security_state_, cert_transparency_verifier_,
         ct_policy_enforcer_, ssl_client_session_cache_,
         ssl_client_session_cache_privacy_mode_, ssl_config_service_,
-        proxy_server.is_socks() ? nullptr : socket_performance_watcher_factory_,
-        network_quality_estimator_, net_log_);
+        socket_performance_watcher_factory_, network_quality_estimator_,
+        net_log_);
   }
 
   std::pair<TransportSocketPoolMap::iterator, bool> ret =
diff --git a/net/socket/connect_job.cc b/net/socket/connect_job.cc
index d87bb38..10ba19b 100644
--- a/net/socket/connect_job.cc
+++ b/net/socket/connect_job.cc
@@ -18,8 +18,6 @@
 namespace net {
 
 CommonConnectJobParams::CommonConnectJobParams(
-    const std::string& group_name,
-    const SocketTag& socket_tag,
     ClientSocketFactory* client_socket_factory,
     HostResolver* host_resolver,
     ProxyDelegate* proxy_delegate,
@@ -29,9 +27,7 @@
     NetworkQualityEstimator* network_quality_estimator,
     NetLog* net_log,
     WebSocketEndpointLockManager* websocket_endpoint_lock_manager)
-    : group_name(group_name),
-      socket_tag(socket_tag),
-      client_socket_factory(client_socket_factory),
+    : client_socket_factory(client_socket_factory),
       host_resolver(host_resolver),
       proxy_delegate(proxy_delegate),
       ssl_client_socket_context(ssl_client_socket_context),
@@ -40,9 +36,7 @@
       socket_performance_watcher_factory(socket_performance_watcher_factory),
       network_quality_estimator(network_quality_estimator),
       net_log(net_log),
-      websocket_endpoint_lock_manager(websocket_endpoint_lock_manager) {
-  DCHECK(!group_name.empty());
-}
+      websocket_endpoint_lock_manager(websocket_endpoint_lock_manager) {}
 
 CommonConnectJobParams::CommonConnectJobParams(
     const CommonConnectJobParams& other) = default;
@@ -53,29 +47,27 @@
     const CommonConnectJobParams& other) = default;
 
 ConnectJob::ConnectJob(RequestPriority priority,
+                       const SocketTag& socket_tag,
                        base::TimeDelta timeout_duration,
-                       const CommonConnectJobParams& common_connect_job_params,
+                       const CommonConnectJobParams* common_connect_job_params,
                        Delegate* delegate,
                        const NetLogWithSource* net_log,
                        NetLogSourceType net_log_source_type,
                        NetLogEventType net_log_connect_event_type)
     : timeout_duration_(timeout_duration),
       priority_(priority),
+      socket_tag_(socket_tag),
       common_connect_job_params_(common_connect_job_params),
       delegate_(delegate),
       top_level_job_(net_log == nullptr),
       net_log_(net_log
                    ? *net_log
-                   : NetLogWithSource::Make(common_connect_job_params.net_log,
+                   : NetLogWithSource::Make(common_connect_job_params->net_log,
                                             net_log_source_type)),
       net_log_connect_event_type_(net_log_connect_event_type) {
   DCHECK(delegate);
-  if (top_level_job_) {
-    net_log_.BeginEvent(
-        NetLogEventType::CONNECT_JOB,
-        NetLog::StringCallback("group_name",
-                               &common_connect_job_params.group_name));
-  }
+  if (top_level_job_)
+    net_log_.BeginEvent(NetLogEventType::CONNECT_JOB);
 }
 
 ConnectJob::~ConnectJob() {
diff --git a/net/socket/connect_job.h b/net/socket/connect_job.h
index 7e54215..49dedfbf 100644
--- a/net/socket/connect_job.h
+++ b/net/socket/connect_job.h
@@ -43,8 +43,6 @@
 // those.
 struct NET_EXPORT_PRIVATE CommonConnectJobParams {
   CommonConnectJobParams(
-      const std::string& group_name,
-      const SocketTag& socket_tag,
       ClientSocketFactory* client_socket_factory,
       HostResolver* host_resolver,
       ProxyDelegate* proxy_delegate,
@@ -59,14 +57,6 @@
 
   CommonConnectJobParams& operator=(const CommonConnectJobParams& other);
 
-  // Socket pool group name, used for logging and identying the group in a
-  // socket pool.
-  // TODO(mmenke): Remove the latter use.
-  std::string group_name;
-
-  // Tag applied to any created socket.
-  SocketTag socket_tag;
-
   ClientSocketFactory* client_socket_factory;
   HostResolver* host_resolver;
   ProxyDelegate* proxy_delegate;
@@ -125,8 +115,9 @@
   // |net_log_connect_event_type| is the NetLog event type logged on Connect()
   // and connect completion.
   ConnectJob(RequestPriority priority,
+             const SocketTag& socket_tag,
              base::TimeDelta timeout_duration,
-             const CommonConnectJobParams& common_connect_job_params,
+             const CommonConnectJobParams* common_connect_job_params,
              Delegate* delegate,
              const NetLogWithSource* net_log,
              NetLogSourceType net_log_source_type,
@@ -134,9 +125,6 @@
   virtual ~ConnectJob();
 
   // Accessors
-  const std::string& group_name() const {
-    return common_connect_job_params_.group_name;
-  }
   const NetLogWithSource& net_log() { return net_log_; }
   RequestPriority priority() const { return priority_; }
 
@@ -193,31 +181,29 @@
   const NetLogWithSource& net_log() const { return net_log_; }
 
  protected:
-  const SocketTag& socket_tag() const {
-    return common_connect_job_params_.socket_tag;
-  }
+  const SocketTag& socket_tag() const { return socket_tag_; }
   ClientSocketFactory* client_socket_factory() {
-    return common_connect_job_params_.client_socket_factory;
+    return common_connect_job_params_->client_socket_factory;
   }
   HostResolver* host_resolver() {
-    return common_connect_job_params_.host_resolver;
+    return common_connect_job_params_->host_resolver;
   }
   const SSLClientSocketContext& ssl_client_socket_context() {
-    return common_connect_job_params_.ssl_client_socket_context;
+    return common_connect_job_params_->ssl_client_socket_context;
   }
   const SSLClientSocketContext& ssl_client_socket_context_privacy_mode() {
-    return common_connect_job_params_.ssl_client_socket_context_privacy_mode;
+    return common_connect_job_params_->ssl_client_socket_context_privacy_mode;
   }
   SocketPerformanceWatcherFactory* socket_performance_watcher_factory() {
-    return common_connect_job_params_.socket_performance_watcher_factory;
+    return common_connect_job_params_->socket_performance_watcher_factory;
   }
   NetworkQualityEstimator* network_quality_estimator() {
-    return common_connect_job_params_.network_quality_estimator;
+    return common_connect_job_params_->network_quality_estimator;
   }
   WebSocketEndpointLockManager* websocket_endpoint_lock_manager() {
-    return common_connect_job_params_.websocket_endpoint_lock_manager;
+    return common_connect_job_params_->websocket_endpoint_lock_manager;
   }
-  const CommonConnectJobParams& common_connect_job_params() {
+  const CommonConnectJobParams* common_connect_job_params() {
     return common_connect_job_params_;
   }
 
@@ -245,7 +231,8 @@
 
   const base::TimeDelta timeout_duration_;
   RequestPriority priority_;
-  const CommonConnectJobParams common_connect_job_params_;
+  const SocketTag socket_tag_;
+  const CommonConnectJobParams* common_connect_job_params_;
   // Timer to abort jobs that take too long.
   base::OneShotTimer timer_;
   Delegate* delegate_;
diff --git a/net/socket/connect_job_unittest.cc b/net/socket/connect_job_unittest.cc
index 43ef541..915d02b 100644
--- a/net/socket/connect_job_unittest.cc
+++ b/net/socket/connect_job_unittest.cc
@@ -34,22 +34,12 @@
 
   TestConnectJob(JobType job_type,
                  base::TimeDelta timeout_duration,
-                 ConnectJob::Delegate* delegate,
-                 NetLog* net_log)
+                 const CommonConnectJobParams* common_connect_job_params,
+                 ConnectJob::Delegate* delegate)
       : ConnectJob(DEFAULT_PRIORITY,
+                   SocketTag(),
                    timeout_duration,
-                   CommonConnectJobParams(
-                       "group_name",
-                       SocketTag(),
-                       nullptr /* client_socket_factory */,
-                       nullptr /* host_resolver */,
-                       nullptr /* proxy_delegate */,
-                       SSLClientSocketContext(),
-                       SSLClientSocketContext(),
-                       nullptr /* socket_performance_watcher_factory */,
-                       nullptr /* network_quality_estimator */,
-                       net_log,
-                       nullptr /* websocket_endpoint_lock_manager */),
+                   common_connect_job_params,
                    delegate,
                    nullptr /* net_log */,
                    NetLogSourceType::TRANSPORT_CONNECT_JOB,
@@ -88,7 +78,7 @@
   // The priority seen during the most recent call to ChangePriorityInternal().
   RequestPriority last_seen_priority() const { return last_seen_priority_; }
 
- private:
+ protected:
   const JobType job_type_;
   StaticSocketDataProvider socket_data_provider_;
   RequestPriority last_seen_priority_;
@@ -100,11 +90,23 @@
  public:
   ConnectJobTest()
       : scoped_task_environment_(
-            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {}
+            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
+        common_connect_job_params_(
+            nullptr /* client_socket_factory */,
+            nullptr /* host_resolver */,
+            nullptr /* proxy_delegate */,
+            SSLClientSocketContext(),
+            SSLClientSocketContext(),
+            nullptr /* socket_performance_watcher_factory */,
+            nullptr /* network_quality_estimator */,
+            &net_log_,
+            nullptr /* websocket_endpoint_lock_manager */) {}
   ~ConnectJobTest() override = default;
 
  protected:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
+  TestNetLog net_log_;
+  const CommonConnectJobParams common_connect_job_params_;
   TestConnectJobDelegate delegate_;
 };
 
@@ -112,8 +114,8 @@
 // completion.
 TEST_F(ConnectJobTest, NoTimeoutOnSyncCompletion) {
   TestConnectJob job(TestConnectJob::JobType::kSyncSuccess,
-                     base::TimeDelta::FromMicroseconds(1), &delegate_,
-                     nullptr /* net_log */);
+                     base::TimeDelta::FromMicroseconds(1),
+                     &common_connect_job_params_, &delegate_);
   EXPECT_THAT(job.Connect(), test::IsOk());
 }
 
@@ -121,8 +123,8 @@
 // completion.
 TEST_F(ConnectJobTest, NoTimeoutOnAsyncCompletion) {
   TestConnectJob job(TestConnectJob::JobType::kAsyncSuccess,
-                     base::TimeDelta::FromMinutes(1), &delegate_,
-                     nullptr /* net_log */);
+                     base::TimeDelta::FromMinutes(1),
+                     &common_connect_job_params_, &delegate_);
   ASSERT_THAT(job.Connect(), test::IsError(ERR_IO_PENDING));
   EXPECT_THAT(delegate_.WaitForResult(), test::IsOk());
 }
@@ -130,7 +132,7 @@
 // Job shouldn't timeout when passed a TimeDelta of zero.
 TEST_F(ConnectJobTest, NoTimeoutWithNoTimeDelta) {
   TestConnectJob job(TestConnectJob::JobType::kHung, base::TimeDelta(),
-                     &delegate_, nullptr /* net_log */);
+                     &common_connect_job_params_, &delegate_);
   ASSERT_THAT(job.Connect(), test::IsError(ERR_IO_PENDING));
   scoped_task_environment_.RunUntilIdle();
   EXPECT_FALSE(delegate_.has_result());
@@ -140,8 +142,8 @@
 // subclasses during the SetPriorityInternal call.
 TEST_F(ConnectJobTest, SetPriority) {
   TestConnectJob job(TestConnectJob::JobType::kAsyncSuccess,
-                     base::TimeDelta::FromMicroseconds(1), &delegate_,
-                     nullptr /* net_log */);
+                     base::TimeDelta::FromMicroseconds(1),
+                     &common_connect_job_params_, &delegate_);
   ASSERT_THAT(job.Connect(), test::IsError(ERR_IO_PENDING));
 
   job.ChangePriority(HIGHEST);
@@ -157,10 +159,10 @@
 
 TEST_F(ConnectJobTest, TimedOut) {
   const base::TimeDelta kTimeout = base::TimeDelta::FromHours(1);
-  TestNetLog log;
 
-  std::unique_ptr<TestConnectJob> job = std::make_unique<TestConnectJob>(
-      TestConnectJob::JobType::kHung, kTimeout, &delegate_, &log);
+  std::unique_ptr<TestConnectJob> job =
+      std::make_unique<TestConnectJob>(TestConnectJob::JobType::kHung, kTimeout,
+                                       &common_connect_job_params_, &delegate_);
   ASSERT_THAT(job->Connect(), test::IsError(ERR_IO_PENDING));
 
   // Nothing should happen before the specified time.
@@ -177,7 +179,7 @@
   job.reset();
 
   TestNetLogEntry::List entries;
-  log.GetEntries(&entries);
+  net_log_.GetEntries(&entries);
 
   EXPECT_EQ(6u, entries.size());
   EXPECT_TRUE(LogContainsBeginEvent(entries, 0, NetLogEventType::CONNECT_JOB));
@@ -197,8 +199,8 @@
 TEST_F(ConnectJobTest, TimedOutWithRestartedTimer) {
   const base::TimeDelta kTimeout = base::TimeDelta::FromHours(1);
 
-  TestConnectJob job(TestConnectJob::JobType::kHung, kTimeout, &delegate_,
-                     nullptr /* net_log */);
+  TestConnectJob job(TestConnectJob::JobType::kHung, kTimeout,
+                     &common_connect_job_params_, &delegate_);
   ASSERT_THAT(job.Connect(), test::IsError(ERR_IO_PENDING));
 
   // Nothing should happen before the specified time.
diff --git a/net/socket/socks_connect_job.cc b/net/socket/socks_connect_job.cc
index 60efaec..6f52016 100644
--- a/net/socket/socks_connect_job.cc
+++ b/net/socket/socks_connect_job.cc
@@ -36,11 +36,13 @@
 
 SOCKSConnectJob::SOCKSConnectJob(
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     const scoped_refptr<SOCKSSocketParams>& socks_params,
     ConnectJob::Delegate* delegate,
     const NetLogWithSource* net_log)
     : ConnectJob(priority,
+                 socket_tag,
                  ConnectionTimeout(),
                  common_connect_job_params,
                  delegate,
@@ -138,7 +140,7 @@
 
   next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
   transport_connect_job_ = TransportConnectJob::CreateTransportConnectJob(
-      socks_params_->transport_params(), priority(),
+      socks_params_->transport_params(), priority(), socket_tag(),
       common_connect_job_params(), this, &net_log());
   return transport_connect_job_->Connect();
 }
diff --git a/net/socket/socks_connect_job.h b/net/socket/socks_connect_job.h
index 210a930..f043500 100644
--- a/net/socket/socks_connect_job.h
+++ b/net/socket/socks_connect_job.h
@@ -20,6 +20,7 @@
 
 namespace net {
 
+class SocketTag;
 class StreamSocket;
 class TransportSocketParams;
 
@@ -62,7 +63,8 @@
                                            public ConnectJob::Delegate {
  public:
   SOCKSConnectJob(RequestPriority priority,
-                  const CommonConnectJobParams& common_connect_job_params,
+                  const SocketTag& socket_tag,
+                  const CommonConnectJobParams* common_connect_job_params,
                   const scoped_refptr<SOCKSSocketParams>& socks_params,
                   ConnectJob::Delegate* delegate,
                   const NetLogWithSource* net_log);
diff --git a/net/socket/socks_connect_job_unittest.cc b/net/socket/socks_connect_job_unittest.cc
index 2a1062ee..12d6d3a 100644
--- a/net/socket/socks_connect_job_unittest.cc
+++ b/net/socket/socks_connect_job_unittest.cc
@@ -49,7 +49,17 @@
       : WithScopedTaskEnvironment(
             base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
             base::test::ScopedTaskEnvironment::NowSource::
-                MAIN_THREAD_MOCK_TIME) {
+                MAIN_THREAD_MOCK_TIME),
+        common_connect_job_params_(
+            &client_socket_factory_,
+            &host_resolver_,
+            nullptr /* proxy_delegate */,
+            SSLClientSocketContext(),
+            SSLClientSocketContext(),
+            nullptr /* socket_performance_watcher_factory */,
+            nullptr /* network_quality_estimator */,
+            &net_log_,
+            nullptr /* websocket_endpoint_lock_manager */) {
     // Set an initial delay to ensure that the first call to TimeTicks::Now()
     // before incrementing the counter does not return a null value.
     FastForwardBy(base::TimeDelta::FromSeconds(1));
@@ -70,21 +80,11 @@
         TRAFFIC_ANNOTATION_FOR_TESTS);
   }
 
-  CommonConnectJobParams CreateCommonParams() {
-    // Group name doesn't matter.
-    return CommonConnectJobParams(
-        "group_name", SocketTag(), &client_socket_factory_, &host_resolver_,
-        nullptr /* proxy_delegate */, SSLClientSocketContext(),
-        SSLClientSocketContext(),
-        nullptr /* socket_performance_watcher_factory */,
-        nullptr /* network_quality_estimator */, &net_log_,
-        nullptr /* websocket_endpoint_lock_manager */);
-  }
-
  protected:
   NetLog net_log_;
   MockHostResolver host_resolver_;
   MockTaggingClientSocketFactory client_socket_factory_;
+  const CommonConnectJobParams common_connect_job_params_;
 };
 
 TEST_F(SOCKSConnectJobTest, HostResolutionFailure) {
@@ -93,7 +93,8 @@
   for (bool failure_synchronous : {false, true}) {
     host_resolver_.set_synchronous_mode(failure_synchronous);
     TestConnectJobDelegate test_delegate;
-    SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+    SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                      &common_connect_job_params_,
                                       CreateSOCKSParams(SOCKSVersion::V5),
                                       &test_delegate, nullptr /* net_log */);
     test_delegate.StartJobExpectingResult(
@@ -121,7 +122,8 @@
       client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data);
 
       TestConnectJobDelegate test_delegate;
-      SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+      SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                        &common_connect_job_params_,
                                         CreateSOCKSParams(SOCKSVersion::V5),
                                         &test_delegate, nullptr /* net_log */);
       test_delegate.StartJobExpectingResult(
@@ -154,7 +156,8 @@
       client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data);
 
       TestConnectJobDelegate test_delegate;
-      SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+      SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                        &common_connect_job_params_,
                                         CreateSOCKSParams(SOCKSVersion::V4),
                                         &test_delegate, nullptr /* net_log */);
       test_delegate.StartJobExpectingResult(
@@ -190,7 +193,8 @@
       client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data);
 
       TestConnectJobDelegate test_delegate;
-      SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+      SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                        &common_connect_job_params_,
                                         CreateSOCKSParams(SOCKSVersion::V5),
                                         &test_delegate, nullptr /* net_log */);
       test_delegate.StartJobExpectingResult(
@@ -217,7 +221,8 @@
   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data);
 
   TestConnectJobDelegate test_delegate;
-  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                    &common_connect_job_params_,
                                     CreateSOCKSParams(SOCKSVersion::V4),
                                     &test_delegate, nullptr /* net_log */);
   socks_connect_job.Connect();
@@ -247,7 +252,8 @@
   host_resolver_.set_ondemand_mode(true);
 
   TestConnectJobDelegate test_delegate;
-  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                    &common_connect_job_params_,
                                     CreateSOCKSParams(SOCKSVersion::V5),
                                     &test_delegate, nullptr /* net_log */);
   socks_connect_job.Connect();
@@ -284,7 +290,8 @@
   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data);
 
   TestConnectJobDelegate test_delegate;
-  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                    &common_connect_job_params_,
                                     CreateSOCKSParams(SOCKSVersion::V5),
                                     &test_delegate, nullptr /* net_log */);
   socks_connect_job.Connect();
@@ -329,9 +336,9 @@
         continue;
       TestConnectJobDelegate test_delegate;
       SOCKSConnectJob socks_connect_job(
-          static_cast<RequestPriority>(initial_priority), CreateCommonParams(),
-          CreateSOCKSParams(SOCKSVersion::V4), &test_delegate,
-          nullptr /* net_log */);
+          static_cast<RequestPriority>(initial_priority), SocketTag(),
+          &common_connect_job_params_, CreateSOCKSParams(SOCKSVersion::V4),
+          &test_delegate, nullptr /* net_log */);
       ASSERT_THAT(socks_connect_job.Connect(), test::IsError(ERR_IO_PENDING));
       ASSERT_TRUE(host_resolver_.has_pending_requests());
       int request_id = host_resolver_.num_resolve();
@@ -374,7 +381,8 @@
   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data);
 
   TestConnectJobDelegate test_delegate;
-  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, CreateCommonParams(),
+  SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(),
+                                    &common_connect_job_params_,
                                     CreateSOCKSParams(SOCKSVersion::V5),
                                     &test_delegate, nullptr /* net_log */);
   base::TimeTicks start = base::TimeTicks::Now();
@@ -413,7 +421,8 @@
 
   TestConnectJobDelegate test_delegate;
   std::unique_ptr<SOCKSConnectJob> socks_connect_job =
-      std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, CreateCommonParams(),
+      std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, SocketTag(),
+                                        &common_connect_job_params_,
                                         CreateSOCKSParams(SOCKSVersion::V5),
                                         &test_delegate, nullptr /* net_log */);
   socks_connect_job->Connect();
@@ -436,7 +445,8 @@
 
   TestConnectJobDelegate test_delegate;
   std::unique_ptr<SOCKSConnectJob> socks_connect_job =
-      std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, CreateCommonParams(),
+      std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, SocketTag(),
+                                        &common_connect_job_params_,
                                         CreateSOCKSParams(SOCKSVersion::V5),
                                         &test_delegate, nullptr /* net_log */);
   socks_connect_job->Connect();
@@ -464,7 +474,8 @@
 
   TestConnectJobDelegate test_delegate;
   std::unique_ptr<SOCKSConnectJob> socks_connect_job =
-      std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, CreateCommonParams(),
+      std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, SocketTag(),
+                                        &common_connect_job_params_,
                                         CreateSOCKSParams(SOCKSVersion::V5),
                                         &test_delegate, nullptr /* net_log */);
   socks_connect_job->Connect();
diff --git a/net/socket/ssl_connect_job.cc b/net/socket/ssl_connect_job.cc
index d1e8888..87ab689 100644
--- a/net/socket/ssl_connect_job.cc
+++ b/net/socket/ssl_connect_job.cc
@@ -92,14 +92,16 @@
 
 SSLConnectJob::SSLConnectJob(
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     const scoped_refptr<SSLSocketParams>& params,
     ConnectJob::Delegate* delegate,
     const NetLogWithSource* net_log)
     : ConnectJob(priority,
+                 socket_tag,
                  ConnectionTimeout(
                      *params,
-                     common_connect_job_params.network_quality_estimator),
+                     common_connect_job_params->network_quality_estimator),
                  common_connect_job_params,
                  delegate,
                  net_log,
@@ -268,7 +270,7 @@
 
   next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
   nested_connect_job_ = TransportConnectJob::CreateTransportConnectJob(
-      params_->GetDirectConnectionParams(), priority(),
+      params_->GetDirectConnectionParams(), priority(), socket_tag(),
       common_connect_job_params(), this, &net_log());
   return nested_connect_job_->Connect();
 }
@@ -296,7 +298,7 @@
 
   next_state_ = STATE_SOCKS_CONNECT_COMPLETE;
   nested_connect_job_ = std::make_unique<SOCKSConnectJob>(
-      priority(), common_connect_job_params(),
+      priority(), socket_tag(), common_connect_job_params(),
       params_->GetSocksProxyConnectionParams(), this, &net_log());
   return nested_connect_job_->Connect();
 }
@@ -318,7 +320,7 @@
   scoped_refptr<HttpProxySocketParams> http_proxy_params =
       params_->GetHttpProxyConnectionParams();
   nested_connect_job_ = std::make_unique<HttpProxyConnectJob>(
-      priority(), common_connect_job_params(),
+      priority(), socket_tag(), common_connect_job_params(),
       params_->GetHttpProxyConnectionParams(), this, &net_log());
   return nested_connect_job_->Connect();
 }
diff --git a/net/socket/ssl_connect_job.h b/net/socket/ssl_connect_job.h
index d305e38..523f1f8 100644
--- a/net/socket/ssl_connect_job.h
+++ b/net/socket/ssl_connect_job.h
@@ -26,6 +26,7 @@
 class ClientSocketHandle;
 class HostPortPair;
 class HttpProxySocketParams;
+class SocketTag;
 class SOCKSSocketParams;
 class TransportSocketParams;
 
@@ -82,7 +83,8 @@
   // Note: the SSLConnectJob does not own |messenger| so it must outlive the
   // job.
   SSLConnectJob(RequestPriority priority,
-                const CommonConnectJobParams& common_connect_job_params,
+                const SocketTag& socket_tag,
+                const CommonConnectJobParams* common_connect_job_params,
                 const scoped_refptr<SSLSocketParams>& params,
                 ConnectJob::Delegate* delegate,
                 const NetLogWithSource* net_log);
diff --git a/net/socket/ssl_connect_job_unittest.cc b/net/socket/ssl_connect_job_unittest.cc
index eb684db0..7c31bcd1 100644
--- a/net/socket/ssl_connect_job_unittest.cc
+++ b/net/socket/ssl_connect_job_unittest.cc
@@ -53,7 +53,6 @@
 const int kMaxSocketsPerGroup = 6;
 constexpr base::TimeDelta kUnusedIdleSocketTimeout =
     base::TimeDelta::FromSeconds(10);
-const char kGroupName[] = "a";
 
 // Just check that all connect times are set to base::TimeTicks::Now(), for
 // tests that don't update the mocked out time.
@@ -140,7 +139,17 @@
             nullptr /* ssl_config_service */,
             nullptr /* socket_performance_watcher_factory */,
             nullptr /* network_quality_estimator */,
-            nullptr /* net_log */) {
+            nullptr /* net_log */),
+        common_connect_job_params_(
+            &socket_factory_,
+            &host_resolver_,
+            nullptr /* proxy_delegate */,
+            ssl_client_socket_context_,
+            ssl_client_socket_context_,
+            nullptr /* socket_performance_watcher */,
+            nullptr /* network_quality_estimator */,
+            nullptr /* net_log */,
+            nullptr /* websocket_lock_endpoint_manager */) {
     ssl_config_service_->GetSSLConfig(&ssl_config_);
 
     // Set an initial delay to ensure that the first call to TimeTicks::Now()
@@ -155,14 +164,7 @@
       ProxyServer::Scheme proxy_scheme = ProxyServer::SCHEME_DIRECT,
       RequestPriority priority = DEFAULT_PRIORITY) {
     return std::make_unique<SSLConnectJob>(
-        priority,
-        CommonConnectJobParams(
-            kGroupName, SocketTag(), &socket_factory_, &host_resolver_,
-            nullptr /* proxy_delegate */, ssl_client_socket_context_,
-            ssl_client_socket_context_,
-            nullptr /* socket_performance_watcher */,
-            nullptr /* network_quality_estimator */, nullptr /* net_log */,
-            nullptr /* websocket_lock_endpoint_manager */),
+        priority, SocketTag(), &common_connect_job_params_,
         SSLParams(proxy_scheme), test_delegate, nullptr /* net_log */);
   }
 
@@ -223,6 +225,7 @@
   TransportClientSocketPool http_proxy_socket_pool_;
 
   SSLConfig ssl_config_;
+  const CommonConnectJobParams common_connect_job_params_;
 };
 
 TEST_F(SSLConnectJobTest, TCPFail) {
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc
index 023d121..2c00e22c 100644
--- a/net/socket/transport_client_socket_pool.cc
+++ b/net/socket/transport_client_socket_pool.cc
@@ -36,41 +36,45 @@
 std::unique_ptr<ConnectJob> CreateTransportConnectJob(
     scoped_refptr<TransportSocketParams> transport_socket_params,
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     ConnectJob::Delegate* delegate) {
   return TransportConnectJob::CreateTransportConnectJob(
-      std::move(transport_socket_params), priority, common_connect_job_params,
-      delegate, nullptr /* net_log */);
+      std::move(transport_socket_params), priority, socket_tag,
+      common_connect_job_params, delegate, nullptr /* net_log */);
 }
 
 std::unique_ptr<ConnectJob> CreateSOCKSConnectJob(
     scoped_refptr<SOCKSSocketParams> socks_socket_params,
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     ConnectJob::Delegate* delegate) {
-  return std::make_unique<SOCKSConnectJob>(priority, common_connect_job_params,
-                                           std::move(socks_socket_params),
-                                           delegate, nullptr /* net_log */);
+  return std::make_unique<SOCKSConnectJob>(
+      priority, socket_tag, common_connect_job_params,
+      std::move(socks_socket_params), delegate, nullptr /* net_log */);
 }
 
 std::unique_ptr<ConnectJob> CreateSSLConnectJob(
     scoped_refptr<SSLSocketParams> ssl_socket_params,
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     ConnectJob::Delegate* delegate) {
-  return std::make_unique<SSLConnectJob>(priority, common_connect_job_params,
-                                         std::move(ssl_socket_params), delegate,
-                                         nullptr /* net_log */);
+  return std::make_unique<SSLConnectJob>(
+      priority, socket_tag, common_connect_job_params,
+      std::move(ssl_socket_params), delegate, nullptr /* net_log */);
 }
 
 std::unique_ptr<ConnectJob> CreateHttpProxyConnectJob(
     scoped_refptr<HttpProxySocketParams> http_proxy_socket_params,
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     ConnectJob::Delegate* delegate) {
   return std::make_unique<HttpProxyConnectJob>(
-      priority, common_connect_job_params, std::move(http_proxy_socket_params),
-      delegate, nullptr /* net_log */);
+      priority, socket_tag, common_connect_job_params,
+      std::move(http_proxy_socket_params), delegate, nullptr /* net_log */);
 }
 
 }  // namespace
@@ -123,32 +127,26 @@
         SocketPerformanceWatcherFactory* socket_performance_watcher_factory,
         NetworkQualityEstimator* network_quality_estimator,
         NetLog* net_log)
-    : client_socket_factory_(client_socket_factory),
-      host_resolver_(host_resolver),
-      proxy_delegate_(proxy_delegate),
-      ssl_client_socket_context_(ssl_client_socket_context),
-      ssl_client_socket_context_privacy_mode_(
-          ssl_client_socket_context_privacy_mode),
-      socket_performance_watcher_factory_(socket_performance_watcher_factory),
-      network_quality_estimator_(network_quality_estimator),
-      net_log_(net_log) {}
+    : common_connect_job_params_(
+          client_socket_factory,
+          host_resolver,
+          proxy_delegate,
+          ssl_client_socket_context,
+          ssl_client_socket_context_privacy_mode,
+          socket_performance_watcher_factory,
+          network_quality_estimator,
+          net_log,
+          nullptr /* websocket_endpoint_lock_manager */) {}
 
 TransportClientSocketPool::TransportConnectJobFactory::
     ~TransportConnectJobFactory() = default;
 
 std::unique_ptr<ConnectJob>
 TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob(
-    const std::string& group_name,
     const PoolBase::Request& request,
     ConnectJob::Delegate* delegate) const {
   return request.params()->create_connect_job_callback().Run(
-      request.priority(),
-      CommonConnectJobParams(
-          group_name, request.socket_tag(),
-          client_socket_factory_, host_resolver_, proxy_delegate_,
-          ssl_client_socket_context_, ssl_client_socket_context_privacy_mode_,
-          socket_performance_watcher_factory_, network_quality_estimator_,
-          net_log_, nullptr /* websocket_endpoint_lock_manager */),
+      request.priority(), request.socket_tag(), &common_connect_job_params_,
       delegate);
 }
 
diff --git a/net/socket/transport_client_socket_pool.h b/net/socket/transport_client_socket_pool.h
index 7197460..e8aa4873 100644
--- a/net/socket/transport_client_socket_pool.h
+++ b/net/socket/transport_client_socket_pool.h
@@ -14,6 +14,7 @@
 #include "net/base/net_export.h"
 #include "net/socket/client_socket_pool.h"
 #include "net/socket/client_socket_pool_base.h"
+#include "net/socket/connect_job.h"
 #include "net/socket/connection_attempts.h"
 #include "net/socket/socket_tag.h"
 #include "net/socket/ssl_client_socket.h"
@@ -50,7 +51,8 @@
   using CreateConnectJobCallback =
       base::RepeatingCallback<std::unique_ptr<ConnectJob>(
           RequestPriority priority,
-          const CommonConnectJobParams& common_connect_job_params,
+          const SocketTag& socket_tag,
+          const CommonConnectJobParams* common_connect_job_params,
           ConnectJob::Delegate* delegate)>;
 
   // "Parameters" that own a single callback for creating a ConnectJob that can
@@ -181,19 +183,11 @@
     // ClientSocketPoolBase::ConnectJobFactory methods.
 
     std::unique_ptr<ConnectJob> NewConnectJob(
-        const std::string& group_name,
         const PoolBase::Request& request,
         ConnectJob::Delegate* delegate) const override;
 
    private:
-    ClientSocketFactory* const client_socket_factory_;
-    HostResolver* const host_resolver_;
-    ProxyDelegate* const proxy_delegate_;
-    const SSLClientSocketContext ssl_client_socket_context_;
-    const SSLClientSocketContext ssl_client_socket_context_privacy_mode_;
-    SocketPerformanceWatcherFactory* const socket_performance_watcher_factory_;
-    NetworkQualityEstimator* const network_quality_estimator_;
-    NetLog* const net_log_;
+    const CommonConnectJobParams common_connect_job_params_;
 
     DISALLOW_COPY_AND_ASSIGN(TransportConnectJobFactory);
   };
diff --git a/net/socket/transport_connect_job.cc b/net/socket/transport_connect_job.cc
index d2d612e..1b6d525 100644
--- a/net/socket/transport_connect_job.cc
+++ b/net/socket/transport_connect_job.cc
@@ -70,27 +70,30 @@
 std::unique_ptr<ConnectJob> TransportConnectJob::CreateTransportConnectJob(
     scoped_refptr<TransportSocketParams> transport_client_params,
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     ConnectJob::Delegate* delegate,
     const NetLogWithSource* net_log) {
-  if (!common_connect_job_params.websocket_endpoint_lock_manager) {
+  if (!common_connect_job_params->websocket_endpoint_lock_manager) {
     return std::make_unique<TransportConnectJob>(
-        priority, common_connect_job_params, transport_client_params, delegate,
-        net_log);
+        priority, socket_tag, common_connect_job_params,
+        transport_client_params, delegate, net_log);
   }
 
   return std::make_unique<WebSocketTransportConnectJob>(
-      priority, common_connect_job_params, transport_client_params, delegate,
-      net_log);
+      priority, socket_tag, common_connect_job_params, transport_client_params,
+      delegate, net_log);
 }
 
 TransportConnectJob::TransportConnectJob(
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     const scoped_refptr<TransportSocketParams>& params,
     Delegate* delegate,
     const NetLogWithSource* net_log)
     : ConnectJob(priority,
+                 socket_tag,
                  ConnectionTimeout(),
                  common_connect_job_params,
                  delegate,
@@ -101,7 +104,7 @@
       next_state_(STATE_NONE),
       resolve_result_(OK) {
   // This is only set for WebSockets.
-  DCHECK(!common_connect_job_params.websocket_endpoint_lock_manager);
+  DCHECK(!common_connect_job_params->websocket_endpoint_lock_manager);
 }
 
 TransportConnectJob::~TransportConnectJob() {
diff --git a/net/socket/transport_connect_job.h b/net/socket/transport_connect_job.h
index ff8af775..f6bc4fc 100644
--- a/net/socket/transport_connect_job.h
+++ b/net/socket/transport_connect_job.h
@@ -22,6 +22,7 @@
 namespace net {
 
 class NetLogWithSource;
+class SocketTag;
 
 typedef base::RepeatingCallback<int(const AddressList&,
                                     const NetLogWithSource& net_log)>
@@ -90,12 +91,14 @@
   static std::unique_ptr<ConnectJob> CreateTransportConnectJob(
       scoped_refptr<TransportSocketParams> transport_client_params,
       RequestPriority priority,
-      const CommonConnectJobParams& common_connect_job_params,
+      const SocketTag& socket_tag,
+      const CommonConnectJobParams* common_connect_job_params,
       ConnectJob::Delegate* delegate,
       const NetLogWithSource* net_log);
 
   TransportConnectJob(RequestPriority priority,
-                      const CommonConnectJobParams& common_connect_job_params,
+                      const SocketTag& socket_tag,
+                      const CommonConnectJobParams* common_connect_job_params,
                       const scoped_refptr<TransportSocketParams>& params,
                       Delegate* delegate,
                       const NetLogWithSource* net_log);
diff --git a/net/socket/transport_connect_job_unittest.cc b/net/socket/transport_connect_job_unittest.cc
index 8cb2aa76..0fbc0a6 100644
--- a/net/socket/transport_connect_job_unittest.cc
+++ b/net/socket/transport_connect_job_unittest.cc
@@ -31,7 +31,18 @@
 
 class TransportConnectJobTest : public TestWithScopedTaskEnvironment {
  public:
-  TransportConnectJobTest() : client_socket_factory_(&net_log_) {}
+  TransportConnectJobTest()
+      : client_socket_factory_(&net_log_),
+        common_connect_job_params_(
+            &client_socket_factory_,
+            &host_resolver_,
+            nullptr /* proxy_delegate */,
+            SSLClientSocketContext(),
+            SSLClientSocketContext(),
+            nullptr /* socket_performance_watcher_factory */,
+            nullptr /* network_quality_estimator */,
+            &net_log_,
+            nullptr /* websocket_endpoint_lock_manager */) {}
 
   ~TransportConnectJobTest() override {}
 
@@ -40,20 +51,11 @@
         HostPortPair(kHostName, 80), false, OnHostResolutionCallback());
   }
 
-  CommonConnectJobParams DefaultCommonConnectJobParams() {
-    return CommonConnectJobParams(
-        kHostName /* group_name */, SocketTag(), &client_socket_factory_,
-        &host_resolver_, nullptr /* proxy_delegate */, SSLClientSocketContext(),
-        SSLClientSocketContext(),
-        nullptr /* socket_performance_watcher_factory */,
-        nullptr /* network_quality_estimator */, &net_log_,
-        nullptr /* websocket_endpoint_lock_manager */);
-  }
-
  protected:
   TestNetLog net_log_;
   MockHostResolver host_resolver_;
   MockTransportClientSocketFactory client_socket_factory_;
+  const CommonConnectJobParams common_connect_job_params_;
 };
 
 TEST_F(TransportConnectJobTest, MakeAddrListStartWithIPv4) {
@@ -133,8 +135,8 @@
     host_resolver_.set_synchronous_mode(host_resolution_synchronous);
     TestConnectJobDelegate test_delegate;
     TransportConnectJob transport_conect_job(
-        DEFAULT_PRIORITY, DefaultCommonConnectJobParams(), DefaultParams(),
-        &test_delegate, nullptr /* net_log */);
+        DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
+        DefaultParams(), &test_delegate, nullptr /* net_log */);
     test_delegate.StartJobExpectingResult(&transport_conect_job,
                                           ERR_NAME_NOT_RESOLVED,
                                           host_resolution_synchronous);
@@ -153,8 +155,8 @@
       ClientSocketHandle handle;
       TestConnectJobDelegate test_delegate;
       TransportConnectJob transport_conect_job(
-          DEFAULT_PRIORITY, DefaultCommonConnectJobParams(), DefaultParams(),
-          &test_delegate, nullptr /* net_log */);
+          DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
+          DefaultParams(), &test_delegate, nullptr /* net_log */);
       test_delegate.StartJobExpectingResult(
           &transport_conect_job, ERR_CONNECTION_FAILED,
           host_resolution_synchronous && connection_synchronous);
@@ -173,8 +175,8 @@
       ClientSocketHandle handle;
       TestConnectJobDelegate test_delegate;
       TransportConnectJob transport_conect_job(
-          DEFAULT_PRIORITY, DefaultCommonConnectJobParams(), DefaultParams(),
-          &test_delegate, nullptr /* net_log */);
+          DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
+          DefaultParams(), &test_delegate, nullptr /* net_log */);
       test_delegate.StartJobExpectingResult(
           &transport_conect_job, OK,
           host_resolution_synchronous && connection_synchronous);
@@ -200,8 +202,8 @@
 
   TestConnectJobDelegate test_delegate;
   TransportConnectJob transport_conect_job(
-      DEFAULT_PRIORITY, DefaultCommonConnectJobParams(), DefaultParams(),
-      &test_delegate, nullptr /* net_log */);
+      DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
+      DefaultParams(), &test_delegate, nullptr /* net_log */);
   test_delegate.StartJobExpectingResult(&transport_conect_job, OK,
                                         false /* expect_sync_result */);
 
@@ -240,8 +242,8 @@
 
   TestConnectJobDelegate test_delegate;
   TransportConnectJob transport_conect_job(
-      DEFAULT_PRIORITY, DefaultCommonConnectJobParams(), DefaultParams(),
-      &test_delegate, nullptr /* net_log */);
+      DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
+      DefaultParams(), &test_delegate, nullptr /* net_log */);
   test_delegate.StartJobExpectingResult(&transport_conect_job, OK,
                                         false /* expect_sync_result */);
 
@@ -270,8 +272,8 @@
 
   TestConnectJobDelegate test_delegate;
   TransportConnectJob transport_conect_job(
-      DEFAULT_PRIORITY, DefaultCommonConnectJobParams(), DefaultParams(),
-      &test_delegate, nullptr /* net_log */);
+      DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
+      DefaultParams(), &test_delegate, nullptr /* net_log */);
   test_delegate.StartJobExpectingResult(&transport_conect_job, OK,
                                         false /* expect_sync_result */);
 
@@ -293,8 +295,8 @@
 
   TestConnectJobDelegate test_delegate;
   TransportConnectJob transport_conect_job(
-      DEFAULT_PRIORITY, DefaultCommonConnectJobParams(), DefaultParams(),
-      &test_delegate, nullptr /* net_log */);
+      DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
+      DefaultParams(), &test_delegate, nullptr /* net_log */);
   test_delegate.StartJobExpectingResult(&transport_conect_job, OK,
                                         false /* expect_sync_result */);
 
diff --git a/net/socket/websocket_transport_client_socket_pool.cc b/net/socket/websocket_transport_client_socket_pool.cc
index 28001117..4a5c6d0f 100644
--- a/net/socket/websocket_transport_client_socket_pool.cc
+++ b/net/socket/websocket_transport_client_socket_pool.cc
@@ -62,25 +62,26 @@
           nullptr /* socket_performance_watcher_factory */,
           network_quality_estimator,
           net_log),
-      pool_net_log_(net_log),
-      client_socket_factory_(client_socket_factory),
-      host_resolver_(host_resolver),
-      proxy_delegate_(proxy_delegate),
-      ssl_client_socket_context_(cert_verifier,
+      common_connect_job_params_(
+          client_socket_factory,
+          host_resolver,
+          proxy_delegate,
+          SSLClientSocketContext(cert_verifier,
                                  channel_id_service,
                                  transport_security_state,
                                  cert_transparency_verifier,
                                  ct_policy_enforcer,
                                  ssl_client_session_cache),
-      ssl_client_socket_context_privacy_mode_(
-          cert_verifier,
-          channel_id_service,
-          transport_security_state,
-          cert_transparency_verifier,
-          ct_policy_enforcer,
-          ssl_client_session_cache_privacy_mode),
-      network_quality_estimator_(network_quality_estimator),
-      websocket_endpoint_lock_manager_(websocket_endpoint_lock_manager),
+          SSLClientSocketContext(cert_verifier,
+                                 channel_id_service,
+                                 transport_security_state,
+                                 cert_transparency_verifier,
+                                 ct_policy_enforcer,
+                                 ssl_client_session_cache_privacy_mode),
+          nullptr /* SocketPerformanceWatcherFactory */,
+          network_quality_estimator,
+          net_log,
+          websocket_endpoint_lock_manager),
       max_sockets_(max_sockets),
       handed_out_socket_count_(0),
       flushing_(false),
@@ -156,14 +157,7 @@
   // pool types on top of a standard TransportClientSocketPool.
   std::unique_ptr<ConnectJob> connect_job =
       casted_params->create_connect_job_callback().Run(
-          priority,
-          CommonConnectJobParams(group_name, SocketTag(),
-                                 client_socket_factory_, host_resolver_,
-                                 proxy_delegate_, ssl_client_socket_context_,
-                                 ssl_client_socket_context_privacy_mode_,
-                                 nullptr /* SocketPerformanceWatcherFactory */,
-                                 network_quality_estimator_, pool_net_log_,
-                                 websocket_endpoint_lock_manager_),
+          priority, SocketTag(), &common_connect_job_params_,
           connect_job_delegate.get());
 
   int result = connect_job_delegate->Connect(std::move(connect_job));
diff --git a/net/socket/websocket_transport_client_socket_pool.h b/net/socket/websocket_transport_client_socket_pool.h
index a740386..4fe0252 100644
--- a/net/socket/websocket_transport_client_socket_pool.h
+++ b/net/socket/websocket_transport_client_socket_pool.h
@@ -21,6 +21,7 @@
 #include "net/log/net_log_with_source.h"
 #include "net/socket/client_socket_pool.h"
 #include "net/socket/client_socket_pool_base.h"
+#include "net/socket/connect_job.h"
 #include "net/socket/ssl_client_socket.h"
 #include "net/socket/transport_client_socket_pool.h"
 
@@ -200,18 +201,11 @@
   void ActivateStalledRequest();
   bool DeleteStalledRequest(ClientSocketHandle* handle);
 
+  const CommonConnectJobParams common_connect_job_params_;
   std::set<const ClientSocketHandle*> pending_callbacks_;
   PendingConnectsMap pending_connects_;
   StalledRequestQueue stalled_request_queue_;
   StalledRequestMap stalled_request_map_;
-  NetLog* const pool_net_log_;
-  ClientSocketFactory* const client_socket_factory_;
-  HostResolver* const host_resolver_;
-  ProxyDelegate* const proxy_delegate_;
-  const SSLClientSocketContext ssl_client_socket_context_;
-  const SSLClientSocketContext ssl_client_socket_context_privacy_mode_;
-  NetworkQualityEstimator* const network_quality_estimator_;
-  WebSocketEndpointLockManager* websocket_endpoint_lock_manager_;
   const int max_sockets_;
   int handed_out_socket_count_;
   bool flushing_;
diff --git a/net/socket/websocket_transport_connect_job.cc b/net/socket/websocket_transport_connect_job.cc
index 4f9e9be..0c15fb7 100644
--- a/net/socket/websocket_transport_connect_job.cc
+++ b/net/socket/websocket_transport_connect_job.cc
@@ -23,11 +23,13 @@
 
 WebSocketTransportConnectJob::WebSocketTransportConnectJob(
     RequestPriority priority,
-    const CommonConnectJobParams& common_connect_job_params,
+    const SocketTag& socket_tag,
+    const CommonConnectJobParams* common_connect_job_params,
     const scoped_refptr<TransportSocketParams>& params,
     Delegate* delegate,
     const NetLogWithSource* net_log)
     : ConnectJob(priority,
+                 socket_tag,
                  TransportConnectJob::ConnectionTimeout(),
                  common_connect_job_params,
                  delegate,
@@ -39,7 +41,7 @@
       race_result_(TransportConnectJob::RACE_UNKNOWN),
       had_ipv4_(false),
       had_ipv6_(false) {
-  DCHECK(common_connect_job_params.websocket_endpoint_lock_manager);
+  DCHECK(common_connect_job_params->websocket_endpoint_lock_manager);
 }
 
 WebSocketTransportConnectJob::~WebSocketTransportConnectJob() = default;
diff --git a/net/socket/websocket_transport_connect_job.h b/net/socket/websocket_transport_connect_job.h
index 57a7660f..1ebf709d 100644
--- a/net/socket/websocket_transport_connect_job.h
+++ b/net/socket/websocket_transport_connect_job.h
@@ -20,6 +20,7 @@
 
 namespace net {
 
+class SocketTag;
 class WebSocketTransportConnectSubJob;
 
 // WebSocketTransportConnectJob handles the host resolution necessary for socket
@@ -39,7 +40,8 @@
  public:
   WebSocketTransportConnectJob(
       RequestPriority priority,
-      const CommonConnectJobParams& common_connect_job_params,
+      const SocketTag& socket_tag,
+      const CommonConnectJobParams* common_connect_job_params,
       const scoped_refptr<TransportSocketParams>& params,
       Delegate* delegate,
       const NetLogWithSource* net_log);
diff --git a/net/spdy/spdy_proxy_client_socket_unittest.cc b/net/spdy/spdy_proxy_client_socket_unittest.cc
index 6a6171e..7115c81 100644
--- a/net/spdy/spdy_proxy_client_socket_unittest.cc
+++ b/net/spdy/spdy_proxy_client_socket_unittest.cc
@@ -86,7 +86,8 @@
 base::WeakPtr<SpdySession> CreateSpdyProxySession(
     HttpNetworkSession* http_session,
     SpdySessionDependencies* session_deps,
-    const SpdySessionKey& key) {
+    const SpdySessionKey& key,
+    const CommonConnectJobParams* common_connect_job_params) {
   EXPECT_FALSE(http_session->spdy_session_pool()->FindAvailableSession(
       key, true /* enable_ip_based_pooling */, false /* is_websocket */,
       NetLogWithSource()));
@@ -100,27 +101,9 @@
       transport_params, nullptr, nullptr, key.host_port_pair(), ssl_config,
       key.privacy_mode());
   TestConnectJobDelegate connect_job_delegate;
-  SSLConnectJob connect_job(
-      MEDIUM,
-      CommonConnectJobParams(
-          "group_name", SocketTag(), session_deps->socket_factory.get(),
-          session_deps->host_resolver.get(), nullptr /* proxy_delegate */,
-          SSLClientSocketContext(session_deps->cert_verifier.get(),
-                                 session_deps->channel_id_service.get(),
-                                 session_deps->transport_security_state.get(),
-                                 session_deps->cert_transparency_verifier.get(),
-                                 session_deps->ct_policy_enforcer.get(),
-                                 nullptr /* ssl_client_session_cache_arg */),
-          SSLClientSocketContext(session_deps->cert_verifier.get(),
-                                 session_deps->channel_id_service.get(),
-                                 session_deps->transport_security_state.get(),
-                                 session_deps->cert_transparency_verifier.get(),
-                                 session_deps->ct_policy_enforcer.get(),
-                                 nullptr /* ssl_client_session_cache_arg */),
-          nullptr /* socket_performance_watcher_factory */,
-          nullptr /* network_quality_estimator */, session_deps->net_log,
-          nullptr /* websocket_endpoint_lock_manager */),
-      ssl_params, &connect_job_delegate, nullptr /* net_log */);
+  SSLConnectJob connect_job(MEDIUM, SocketTag(), common_connect_job_params,
+                            ssl_params, &connect_job_delegate,
+                            nullptr /* net_log */);
   connect_job_delegate.StartJobExpectingResult(&connect_job, OK,
                                                false /* expect_sync_result */);
 
@@ -216,6 +199,7 @@
   HostPortPair endpoint_host_port_pair_;
   ProxyServer proxy_;
   SpdySessionKey endpoint_spdy_session_key_;
+  const CommonConnectJobParams common_connect_job_params_;
 
   DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketTest);
 };
@@ -232,7 +216,27 @@
                                  proxy_,
                                  PRIVACY_MODE_DISABLED,
                                  SpdySessionKey::IsProxySession::kFalse,
-                                 SocketTag()) {
+                                 SocketTag()),
+      common_connect_job_params_(
+          session_deps_.socket_factory.get(),
+          session_deps_.host_resolver.get(),
+          nullptr /* proxy_delegate */,
+          SSLClientSocketContext(session_deps_.cert_verifier.get(),
+                                 session_deps_.channel_id_service.get(),
+                                 session_deps_.transport_security_state.get(),
+                                 session_deps_.cert_transparency_verifier.get(),
+                                 session_deps_.ct_policy_enforcer.get(),
+                                 nullptr /* ssl_client_session_cache_arg */),
+          SSLClientSocketContext(session_deps_.cert_verifier.get(),
+                                 session_deps_.channel_id_service.get(),
+                                 session_deps_.transport_security_state.get(),
+                                 session_deps_.cert_transparency_verifier.get(),
+                                 session_deps_.ct_policy_enforcer.get(),
+                                 nullptr /* ssl_client_session_cache_arg */),
+          nullptr /* socket_performance_watcher_factory */,
+          nullptr /* network_quality_estimator */,
+          net_log_.bound().net_log(),
+          nullptr /* websocket_endpoint_lock_manager */) {
   session_deps_.net_log = net_log_.bound().net_log();
 }
 
@@ -268,7 +272,8 @@
 
   // Creates the SPDY session and stream.
   spdy_session_ = CreateSpdyProxySession(session_.get(), &session_deps_,
-                                         endpoint_spdy_session_key_);
+                                         endpoint_spdy_session_key_,
+                                         &common_connect_job_params_);
 
   base::WeakPtr<SpdyStream> spdy_stream(
       CreateStreamSynchronously(
diff --git a/net/third_party/quic/core/chlo_extractor.cc b/net/third_party/quic/core/chlo_extractor.cc
index 1c81946f..cd7fed4 100644
--- a/net/third_party/quic/core/chlo_extractor.cc
+++ b/net/third_party/quic/core/chlo_extractor.cc
@@ -294,8 +294,10 @@
 bool ChloExtractor::Extract(const QuicEncryptedPacket& packet,
                             const ParsedQuicVersionVector& versions,
                             const QuicTagVector& create_session_tag_indicators,
-                            Delegate* delegate) {
-  QuicFramer framer(versions, QuicTime::Zero(), Perspective::IS_SERVER);
+                            Delegate* delegate,
+                            uint8_t connection_id_length) {
+  QuicFramer framer(versions, QuicTime::Zero(), Perspective::IS_SERVER,
+                    connection_id_length);
   ChloFramerVisitor visitor(&framer, create_session_tag_indicators, delegate);
   framer.set_visitor(&visitor);
   if (!framer.ProcessPacket(packet)) {
diff --git a/net/third_party/quic/core/chlo_extractor.h b/net/third_party/quic/core/chlo_extractor.h
index 8b07107..e46a646 100644
--- a/net/third_party/quic/core/chlo_extractor.h
+++ b/net/third_party/quic/core/chlo_extractor.h
@@ -33,7 +33,8 @@
   static bool Extract(const QuicEncryptedPacket& packet,
                       const ParsedQuicVersionVector& versions,
                       const QuicTagVector& create_session_tag_indicators,
-                      Delegate* delegate);
+                      Delegate* delegate,
+                      uint8_t connection_id_length);
 
   ChloExtractor(const ChloExtractor&) = delete;
   ChloExtractor operator=(const ChloExtractor&) = delete;
diff --git a/net/third_party/quic/core/chlo_extractor_test.cc b/net/third_party/quic/core/chlo_extractor_test.cc
index c183f1f05..e96b2ca 100644
--- a/net/third_party/quic/core/chlo_extractor_test.cc
+++ b/net/third_party/quic/core/chlo_extractor_test.cc
@@ -69,7 +69,7 @@
       offset++;
     }
     QuicFramer framer(SupportedVersions(header_.version), QuicTime::Zero(),
-                      Perspective::IS_CLIENT);
+                      Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength);
     if (version.transport_version < QUIC_VERSION_47 || munge_stream_id) {
       QuicStreamId stream_id =
           QuicUtils::GetCryptoStreamId(version.transport_version);
@@ -121,7 +121,8 @@
     }
     MakePacket(version, client_hello_str, /*munge_offset*/ false,
                /*munge_stream_id*/ false);
-    EXPECT_TRUE(ChloExtractor::Extract(*packet_, versions, {}, &delegate_))
+    EXPECT_TRUE(ChloExtractor::Extract(*packet_, versions, {}, &delegate_,
+                                       kQuicDefaultConnectionIdLength))
         << ParsedQuicVersionToString(version);
     EXPECT_EQ(version.transport_version, delegate_.transport_version());
     EXPECT_EQ(header_.destination_connection_id, delegate_.connection_id());
@@ -137,8 +138,9 @@
   QuicString client_hello_str(client_hello.GetSerialized().AsStringPiece());
   MakePacket(AllSupportedVersions()[0], client_hello_str,
              /*munge_offset*/ false, /*munge_stream_id*/ true);
-  EXPECT_FALSE(
-      ChloExtractor::Extract(*packet_, AllSupportedVersions(), {}, &delegate_));
+  EXPECT_FALSE(ChloExtractor::Extract(*packet_, AllSupportedVersions(), {},
+                                      &delegate_,
+                                      kQuicDefaultConnectionIdLength));
 }
 
 TEST_F(ChloExtractorTest, DoesNotFindValidChloOnWrongOffset) {
@@ -148,15 +150,17 @@
   QuicString client_hello_str(client_hello.GetSerialized().AsStringPiece());
   MakePacket(AllSupportedVersions()[0], client_hello_str, /*munge_offset*/ true,
              /*munge_stream_id*/ false);
-  EXPECT_FALSE(
-      ChloExtractor::Extract(*packet_, AllSupportedVersions(), {}, &delegate_));
+  EXPECT_FALSE(ChloExtractor::Extract(*packet_, AllSupportedVersions(), {},
+                                      &delegate_,
+                                      kQuicDefaultConnectionIdLength));
 }
 
 TEST_F(ChloExtractorTest, DoesNotFindInvalidChlo) {
   MakePacket(AllSupportedVersions()[0], "foo", /*munge_offset*/ false,
              /*munge_stream_id*/ true);
-  EXPECT_FALSE(
-      ChloExtractor::Extract(*packet_, AllSupportedVersions(), {}, &delegate_));
+  EXPECT_FALSE(ChloExtractor::Extract(*packet_, AllSupportedVersions(), {},
+                                      &delegate_,
+                                      kQuicDefaultConnectionIdLength));
 }
 
 }  // namespace
diff --git a/net/third_party/quic/core/congestion_control/bbr_sender_test.cc b/net/third_party/quic/core/congestion_control/bbr_sender_test.cc
index 313725d..e80a35a8b 100644
--- a/net/third_party/quic/core/congestion_control/bbr_sender_test.cc
+++ b/net/third_party/quic/core/congestion_control/bbr_sender_test.cc
@@ -256,8 +256,7 @@
 // Test a simple long data transfer in the default setup.
 TEST_F(BbrSenderTest, SimpleTransfer) {
   // Disable Ack Decimation on the receiver, because it can increase srtt.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
   CreateDefaultSetup();
 
   // At startup make sure we are at the default.
@@ -306,8 +305,7 @@
   // congestion_window_ in GetCongestionWindow().
   SetConnectionOption(kBBS1);
   // Disable Ack Decimation on the receiver, because it can increase srtt.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
   CreateDefaultSetup();
 
   // At startup make sure we are at the default.
@@ -377,7 +375,7 @@
   QuicConnectionPeer::SetSendAlgorithm(bbr_sender_.connection(), sender_);
   // Enable Ack Decimation on the receiver.
   QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::ACK_DECIMATION);
+                                 AckMode::ACK_DECIMATION);
   CreateDefaultSetup();
 
   // Transfer 12MB.
@@ -402,8 +400,7 @@
 // Test a simple long data transfer with 2 rtts of aggregation.
 TEST_F(BbrSenderTest, SimpleTransfer2RTTAggregationBytes20RTTWindow) {
   // Disable Ack Decimation on the receiver, because it can increase srtt.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
   CreateDefaultSetup();
   SetConnectionOption(kBBR4);
   // 2 RTTs of aggregation, with a max of 10kb.
@@ -430,8 +427,7 @@
 // Test a simple long data transfer with 2 rtts of aggregation.
 TEST_F(BbrSenderTest, SimpleTransfer2RTTAggregationBytes40RTTWindow) {
   // Disable Ack Decimation on the receiver, because it can increase srtt.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
   CreateDefaultSetup();
   SetConnectionOption(kBBR5);
   // 2 RTTs of aggregation, with a max of 10kb.
@@ -546,8 +542,7 @@
 // Verify that the DRAIN phase works correctly.
 TEST_F(BbrSenderTest, Drain) {
   // Disable Ack Decimation on the receiver, because it can increase srtt.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
   CreateDefaultSetup();
   const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10);
   // Get the queue at the bottleneck, which is the outgoing queue at the port to
@@ -610,8 +605,7 @@
 TEST_F(BbrSenderTest, ShallowDrain) {
   SetQuicReloadableFlag(quic_bbr_slower_startup3, true);
   // Disable Ack Decimation on the receiver, because it can increase srtt.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
 
   CreateDefaultSetup();
   // BBQ4 increases the pacing gain in DRAIN to 0.75
@@ -816,8 +810,7 @@
 // connection will exit low gain early if the number of bytes in flight is low.
 TEST_F(BbrSenderTest, InFlightAwareGainCycling) {
   // Disable Ack Decimation on the receiver, because it can increase srtt.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
   CreateDefaultSetup();
   DriveOutOfStartup();
 
@@ -1222,8 +1215,7 @@
   SetQuicReloadableFlag(quic_bbr_slower_startup3, true);
   // Disable Ack Decimation on the receiver to avoid loss and make results
   // consistent.
-  QuicConnectionPeer::SetAckMode(receiver_.connection(),
-                                 QuicConnection::AckMode::TCP_ACKING);
+  QuicConnectionPeer::SetAckMode(receiver_.connection(), AckMode::TCP_ACKING);
   CreateDefaultSetup();
 
   SetConnectionOption(kBBQ3);
diff --git a/net/third_party/quic/core/crypto/common_cert_set.cc b/net/third_party/quic/core/crypto/common_cert_set.cc
index e81d111b..365c3b5 100644
--- a/net/third_party/quic/core/crypto/common_cert_set.cc
+++ b/net/third_party/quic/core/crypto/common_cert_set.cc
@@ -9,7 +9,6 @@
 #include "base/macros.h"
 #include "net/third_party/quic/core/quic_utils.h"
 #include "net/third_party/quic/platform/api/quic_arraysize.h"
-#include "net/third_party/quic/platform/api/quic_singleton.h"
 
 namespace quic {
 
@@ -142,17 +141,10 @@
     return false;
   }
 
-  static CommonCertSetsQUIC* GetInstance() {
-    return QuicSingleton<CommonCertSetsQUIC>::get();
-  }
-
- private:
   CommonCertSetsQUIC() {}
   CommonCertSetsQUIC(const CommonCertSetsQUIC&) = delete;
   CommonCertSetsQUIC& operator=(const CommonCertSetsQUIC&) = delete;
   ~CommonCertSetsQUIC() override {}
-
-  friend QuicSingletonFriend<CommonCertSetsQUIC>;
 };
 
 }  // anonymous namespace
@@ -161,7 +153,8 @@
 
 // static
 const CommonCertSets* CommonCertSets::GetInstanceQUIC() {
-  return CommonCertSetsQUIC::GetInstance();
+  static CommonCertSetsQUIC* certs = new CommonCertSetsQUIC();
+  return certs;
 }
 
 }  // namespace quic
diff --git a/net/third_party/quic/core/crypto/crypto_protocol.h b/net/third_party/quic/core/crypto/crypto_protocol.h
index 43499d2..e8df065 100644
--- a/net/third_party/quic/core/crypto/crypto_protocol.h
+++ b/net/third_party/quic/core/crypto/crypto_protocol.h
@@ -128,7 +128,6 @@
 const QuicTag kNTLP = TAG('N', 'T', 'L', 'P');   // No tail loss probe
 const QuicTag k1TLP = TAG('1', 'T', 'L', 'P');   // 1 tail loss probe
 const QuicTag k1RTO = TAG('1', 'R', 'T', 'O');   // Send 1 packet upon RTO
-const QuicTag kNCON = TAG('N', 'C', 'O', 'N');   // N Connection Congestion Ctrl
 const QuicTag kNRTO = TAG('N', 'R', 'T', 'O');   // CWND reduction on loss
 const QuicTag kTIME = TAG('T', 'I', 'M', 'E');   // Time based loss detection
 const QuicTag kATIM = TAG('A', 'T', 'I', 'M');   // Adaptive time loss detection
diff --git a/net/third_party/quic/core/crypto/quic_random.cc b/net/third_party/quic/core/crypto/quic_random.cc
index ff19d9c..0a4bd4a 100644
--- a/net/third_party/quic/core/crypto/quic_random.cc
+++ b/net/third_party/quic/core/crypto/quic_random.cc
@@ -6,7 +6,6 @@
 
 #include "base/macros.h"
 #include "net/third_party/quic/platform/api/quic_bug_tracker.h"
-#include "net/third_party/quic/platform/api/quic_singleton.h"
 #include "third_party/boringssl/src/include/openssl/rand.h"
 
 namespace quic {
@@ -15,25 +14,16 @@
 
 class DefaultRandom : public QuicRandom {
  public:
-  static DefaultRandom* GetInstance();
-
-  // QuicRandom implementation
-  void RandBytes(void* data, size_t len) override;
-  uint64_t RandUint64() override;
-
- private:
   DefaultRandom() {}
   DefaultRandom(const DefaultRandom&) = delete;
   DefaultRandom& operator=(const DefaultRandom&) = delete;
   ~DefaultRandom() override {}
 
-  friend QuicSingletonFriend<DefaultRandom>;
+  // QuicRandom implementation
+  void RandBytes(void* data, size_t len) override;
+  uint64_t RandUint64() override;
 };
 
-DefaultRandom* DefaultRandom::GetInstance() {
-  return QuicSingleton<DefaultRandom>::get();
-}
-
 void DefaultRandom::RandBytes(void* data, size_t len) {
   RAND_bytes(reinterpret_cast<uint8_t*>(data), len);
 }
@@ -48,7 +38,8 @@
 
 // static
 QuicRandom* QuicRandom::GetInstance() {
-  return DefaultRandom::GetInstance();
+  static DefaultRandom* random = new DefaultRandom();
+  return random;
 }
 
 }  // namespace quic
diff --git a/net/third_party/quic/core/http/end_to_end_test.cc b/net/third_party/quic/core/http/end_to_end_test.cc
index 6cc4a659..882aa1f0 100644
--- a/net/third_party/quic/core/http/end_to_end_test.cc
+++ b/net/third_party/quic/core/http/end_to_end_test.cc
@@ -450,9 +450,13 @@
     SetQuicReloadableFlag(quic_use_cheap_stateless_rejects,
                           GetParam().use_cheap_stateless_reject);
 
-    auto* test_server = new QuicTestServer(
-        crypto_test_utils::ProofSourceForTesting(), server_config_,
-        server_supported_versions_, &memory_cache_backend_);
+    uint8_t connection_id_length = override_connection_id_ != nullptr
+                                       ? override_connection_id_->length()
+                                       : kQuicDefaultConnectionIdLength;
+    auto* test_server =
+        new QuicTestServer(crypto_test_utils::ProofSourceForTesting(),
+                           server_config_, server_supported_versions_,
+                           &memory_cache_backend_, connection_id_length);
     server_thread_ = QuicMakeUnique<ServerThread>(test_server, server_address_);
     if (chlo_multiplier_ != 0) {
       server_thread_->server()->SetChloMultiplier(chlo_multiplier_);
@@ -786,6 +790,18 @@
   EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
 }
 
+TEST_P(EndToEndTest, MultipleRequestResponseZeroConnectionID) {
+  QuicConnectionId connection_id = QuicUtils::CreateZeroConnectionId(
+      GetParam().negotiated_version.transport_version);
+  override_connection_id_ = &connection_id;
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
 TEST_P(EndToEndTestWithTls, MultipleStreams) {
   // Verifies quic_test_client can track responses of all active streams.
   ASSERT_TRUE(Initialize());
@@ -2231,7 +2247,7 @@
   QuicPublicResetPacket header;
   header.connection_id = connection_id;
   QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
-                    Perspective::IS_SERVER);
+                    Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
   std::unique_ptr<QuicEncryptedPacket> packet;
   if (client_connection->transport_version() > QUIC_VERSION_43) {
     packet = framer.BuildIetfStatelessResetPacket(connection_id,
@@ -2277,7 +2293,7 @@
   QuicPublicResetPacket header;
   header.connection_id = incorrect_connection_id;
   QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
-                    Perspective::IS_SERVER);
+                    Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
   std::unique_ptr<QuicEncryptedPacket> packet;
   testing::NiceMock<MockQuicConnectionDebugVisitor> visitor;
   client_->client()->client_session()->connection()->set_debug_visitor(
@@ -2328,7 +2344,7 @@
   QuicPublicResetPacket header;
   header.connection_id = incorrect_connection_id;
   QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
-                    Perspective::IS_CLIENT);
+                    Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength);
   std::unique_ptr<QuicEncryptedPacket> packet(
       framer.BuildPublicResetPacket(header));
   client_writer_->WritePacket(
diff --git a/net/third_party/quic/core/quic_connection.cc b/net/third_party/quic/core/quic_connection.cc
index 848231b9..c411c5c 100644
--- a/net/third_party/quic/core/quic_connection.cc
+++ b/net/third_party/quic/core/quic_connection.cc
@@ -223,7 +223,8 @@
     const ParsedQuicVersionVector& supported_versions)
     : framer_(supported_versions,
               helper->GetClock()->ApproximateNow(),
-              perspective),
+              perspective,
+              connection_id.length()),
       current_packet_content_(NO_FRAMES_RECEIVED),
       is_current_packet_connectivity_probing_(false),
       current_effective_peer_migration_type_(NO_CHANGE),
@@ -350,6 +351,9 @@
   if (packet_generator_.deprecate_ack_bundling_mode()) {
     QUIC_RELOADABLE_FLAG_COUNT(quic_deprecate_ack_bundling_mode);
   }
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_rpm_decides_when_to_send_acks);
+  }
   QUIC_DLOG(INFO) << ENDPOINT
                   << "Created connection with connection_id: " << connection_id
                   << " and version: "
@@ -431,29 +435,33 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnSetFromConfig(config);
   }
-  if (GetQuicReloadableFlag(quic_enable_ack_decimation) &&
-      config.HasClientSentConnectionOption(kACD0, perspective_)) {
-    ack_mode_ = TCP_ACKING;
-  }
-  if (config.HasClientSentConnectionOption(kACKD, perspective_)) {
-    ack_mode_ = ACK_DECIMATION;
-  }
-  if (config.HasClientSentConnectionOption(kAKD2, perspective_)) {
-    ack_mode_ = ACK_DECIMATION_WITH_REORDERING;
-  }
-  if (config.HasClientSentConnectionOption(kAKD3, perspective_)) {
-    ack_mode_ = ACK_DECIMATION;
-    ack_decimation_delay_ = kShortAckDecimationDelay;
-  }
-  if (config.HasClientSentConnectionOption(kAKD4, perspective_)) {
-    ack_mode_ = ACK_DECIMATION_WITH_REORDERING;
-    ack_decimation_delay_ = kShortAckDecimationDelay;
-  }
-  if (config.HasClientSentConnectionOption(kAKDU, perspective_)) {
-    unlimited_ack_decimation_ = true;
-  }
-  if (config.HasClientSentConnectionOption(kACKQ, perspective_)) {
-    fast_ack_after_quiescence_ = true;
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    received_packet_manager_.SetFromConfig(config, perspective_);
+  } else {
+    if (GetQuicReloadableFlag(quic_enable_ack_decimation) &&
+        config.HasClientSentConnectionOption(kACD0, perspective_)) {
+      ack_mode_ = TCP_ACKING;
+    }
+    if (config.HasClientSentConnectionOption(kACKD, perspective_)) {
+      ack_mode_ = ACK_DECIMATION;
+    }
+    if (config.HasClientSentConnectionOption(kAKD2, perspective_)) {
+      ack_mode_ = ACK_DECIMATION_WITH_REORDERING;
+    }
+    if (config.HasClientSentConnectionOption(kAKD3, perspective_)) {
+      ack_mode_ = ACK_DECIMATION;
+      ack_decimation_delay_ = kShortAckDecimationDelay;
+    }
+    if (config.HasClientSentConnectionOption(kAKD4, perspective_)) {
+      ack_mode_ = ACK_DECIMATION_WITH_REORDERING;
+      ack_decimation_delay_ = kShortAckDecimationDelay;
+    }
+    if (config.HasClientSentConnectionOption(kAKDU, perspective_)) {
+      unlimited_ack_decimation_ = true;
+    }
+    if (config.HasClientSentConnectionOption(kACKQ, perspective_)) {
+      fast_ack_after_quiescence_ = true;
+    }
   }
   if (config.HasClientSentConnectionOption(k5RTO, perspective_)) {
     close_connection_after_five_rtos_ = true;
@@ -515,10 +523,6 @@
   return sent_packet_manager_.MaxPacingRate();
 }
 
-void QuicConnection::SetNumOpenStreams(size_t num_streams) {
-  sent_packet_manager_.SetNumOpenStreams(num_streams);
-}
-
 bool QuicConnection::SelectMutualVersion(
     const ParsedQuicVersionVector& available_versions) {
   // Try to find the highest mutual version by iterating over supported
@@ -1431,9 +1435,15 @@
   const bool was_missing =
       should_last_packet_instigate_acks_ && was_last_packet_missing_;
 
-  // It's possible the ack frame was sent along with response data, so it
-  // no longer needs to be sent.
-  if (ack_frame_updated()) {
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    received_packet_manager_.MaybeUpdateAckTimeout(
+        should_last_packet_instigate_acks_, last_header_.packet_number,
+        time_of_last_received_packet_, clock_->ApproximateNow(),
+        sent_packet_manager_.GetRttStats(),
+        sent_packet_manager_.delayed_ack_time());
+  } else if (ack_frame_updated()) {
+    // It's possible the ack frame was sent along with response data, so it
+    // no longer needs to be sent.
     MaybeQueueAck(was_missing);
   }
 
@@ -1457,6 +1467,7 @@
 }
 
 void QuicConnection::MaybeQueueAck(bool was_missing) {
+  DCHECK(!received_packet_manager_.decide_when_to_send_acks());
   ++num_packets_received_since_last_ack_sent_;
   // Determine whether the newly received packet was missing before recording
   // the received packet.
@@ -1883,7 +1894,16 @@
   ScopedPacketFlusher flusher(this, NO_ACK);
 
   WriteQueuedPackets();
-  if (send_ack_when_on_can_write_) {
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    const QuicTime ack_timeout = received_packet_manager_.ack_timeout();
+    if (ack_timeout.IsInitialized() &&
+        ack_timeout <= clock_->ApproximateNow()) {
+      // Send an ACK now because either 1) we were write blocked when we last
+      // tried to send an ACK, or 2) both ack alarm and send alarm were set to
+      // go off together.
+      SendAck();
+    }
+  } else if (send_ack_when_on_can_write_) {
     // Send an ACK now because either 1) we were write blocked when we last
     // tried to send an ACK, or 2) both ack alarm and send alarm were set to go
     // off together.
@@ -2164,7 +2184,13 @@
 const QuicFrames QuicConnection::MaybeBundleAckOpportunistically() {
   DCHECK(packet_generator_.deprecate_ack_bundling_mode());
   QuicFrames frames;
-  if (!ack_alarm_->IsSet() && stop_waiting_count_ <= 1) {
+  bool has_pending_ack = false;
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    has_pending_ack = received_packet_manager_.ack_timeout().IsInitialized();
+  } else {
+    has_pending_ack = ack_alarm_->IsSet();
+  }
+  if (!has_pending_ack && stop_waiting_count_ <= 1) {
     // No need to send an ACK.
     return frames;
   }
@@ -2597,7 +2623,11 @@
 }
 
 void QuicConnection::SendAck() {
-  ResetAckStates();
+  if (!received_packet_manager_.decide_when_to_send_acks()) {
+    // When received_packet_manager decides when to send ack, delaying
+    // ResetAckStates until ACK is successfully flushed.
+    ResetAckStates();
+  }
 
   if (packet_generator_.deprecate_ack_bundling_mode()) {
     QUIC_DVLOG(1) << ENDPOINT << "Sending an ACK proactively";
@@ -2608,7 +2638,14 @@
       PopulateStopWaitingFrame(&stop_waiting);
       frames.push_back(QuicFrame(stop_waiting));
     }
-    send_ack_when_on_can_write_ = !packet_generator_.FlushAckFrame(frames);
+    if (received_packet_manager_.decide_when_to_send_acks()) {
+      if (!packet_generator_.FlushAckFrame(frames)) {
+        return;
+      }
+      ResetAckStates();
+    } else {
+      send_ack_when_on_can_write_ = !packet_generator_.FlushAckFrame(frames);
+    }
   } else {
     packet_generator_.SetShouldSendAck(!no_stop_waiting_frames_);
   }
@@ -3139,21 +3176,39 @@
   }
 
   if (flush_and_set_pending_retransmission_alarm_on_delete_) {
-    if (connection_->packet_generator_.deprecate_ack_bundling_mode() &&
-        connection_->ack_alarm_->IsSet() &&
-        connection_->ack_alarm_->deadline() <=
-            connection_->clock_->ApproximateNow()) {
-      // An ACK needs to be sent right now. This ACK did not get bundled
-      // because either there was no data to write or packets were marked as
-      // received after frames were queued in the generator.
-      if (connection_->send_alarm_->IsSet() &&
-          connection_->send_alarm_->deadline() <=
+    if (connection_->packet_generator_.deprecate_ack_bundling_mode()) {
+      if (connection_->received_packet_manager_.decide_when_to_send_acks()) {
+        const QuicTime ack_timeout =
+            connection_->received_packet_manager_.ack_timeout();
+        if (ack_timeout.IsInitialized()) {
+          if (ack_timeout <= connection_->clock_->ApproximateNow() &&
+              !connection_->CanWrite(NO_RETRANSMITTABLE_DATA)) {
+            // Cancel ACK alarm if connection is write blocked, and ACK will be
+            // sent when connection gets unblocked.
+            connection_->ack_alarm_->Cancel();
+          } else {
+            connection_->MaybeSetAckAlarmTo(ack_timeout);
+          }
+        }
+      }
+      if (connection_->ack_alarm_->IsSet() &&
+          connection_->ack_alarm_->deadline() <=
               connection_->clock_->ApproximateNow()) {
-        // If send alarm will go off soon, let send alarm send the ACK.
-        connection_->ack_alarm_->Cancel();
-        connection_->send_ack_when_on_can_write_ = true;
-      } else {
-        connection_->SendAck();
+        // An ACK needs to be sent right now. This ACK did not get bundled
+        // because either there was no data to write or packets were marked as
+        // received after frames were queued in the generator.
+        if (connection_->send_alarm_->IsSet() &&
+            connection_->send_alarm_->deadline() <=
+                connection_->clock_->ApproximateNow()) {
+          // If send alarm will go off soon, let send alarm send the ACK.
+          connection_->ack_alarm_->Cancel();
+          if (!connection_->received_packet_manager_
+                   .decide_when_to_send_acks()) {
+            connection_->send_ack_when_on_can_write_ = true;
+          }
+        } else {
+          connection_->SendAck();
+        }
       }
     }
     connection_->packet_generator_.Flush();
@@ -3659,6 +3714,9 @@
   stop_waiting_count_ = 0;
   num_retransmittable_packets_received_since_last_ack_sent_ = 0;
   num_packets_received_since_last_ack_sent_ = 0;
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    received_packet_manager_.ResetAckStates();
+  }
 }
 
 MessageStatus QuicConnection::SendMessage(QuicMessageId message_id,
@@ -3722,5 +3780,36 @@
   return ENCRYPTION_NONE;
 }
 
+size_t QuicConnection::min_received_before_ack_decimation() const {
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    return received_packet_manager_.min_received_before_ack_decimation();
+  }
+  return min_received_before_ack_decimation_;
+}
+
+void QuicConnection::set_min_received_before_ack_decimation(size_t new_value) {
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    received_packet_manager_.set_min_received_before_ack_decimation(new_value);
+  } else {
+    min_received_before_ack_decimation_ = new_value;
+  }
+}
+
+size_t QuicConnection::ack_frequency_before_ack_decimation() const {
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    return received_packet_manager_.ack_frequency_before_ack_decimation();
+  }
+  return ack_frequency_before_ack_decimation_;
+}
+
+void QuicConnection::set_ack_frequency_before_ack_decimation(size_t new_value) {
+  DCHECK_GT(new_value, 0u);
+  if (received_packet_manager_.decide_when_to_send_acks()) {
+    received_packet_manager_.set_ack_frequency_before_ack_decimation(new_value);
+  } else {
+    ack_frequency_before_ack_decimation_ = new_value;
+  }
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/net/third_party/quic/core/quic_connection.h b/net/third_party/quic/core/quic_connection.h
index 6b730a91..667fac97 100644
--- a/net/third_party/quic/core/quic_connection.h
+++ b/net/third_party/quic/core/quic_connection.h
@@ -336,8 +336,6 @@
     NO_ACK,
   };
 
-  enum AckMode { TCP_ACKING, ACK_DECIMATION, ACK_DECIMATION_WITH_REORDERING };
-
   // Constructs a new QuicConnection for |connection_id| and
   // |initial_peer_address| using |writer| to write packets. |owns_writer|
   // specifies whether the connection takes ownership of |writer|. |helper| must
@@ -380,9 +378,6 @@
   // Returns the max pacing rate for the connection.
   virtual QuicBandwidth MaxPacingRate() const;
 
-  // Sets the number of active streams on the connection for congestion control.
-  void SetNumOpenStreams(size_t num_streams);
-
   // Sends crypto handshake messages of length |write_length| to the peer in as
   // few packets as possible. Returns the number of bytes consumed from the
   // data.
@@ -820,20 +815,11 @@
     return packet_generator_.fully_pad_crypto_handshake_packets();
   }
 
-  size_t min_received_before_ack_decimation() const {
-    return min_received_before_ack_decimation_;
-  }
-  void set_min_received_before_ack_decimation(size_t new_value) {
-    min_received_before_ack_decimation_ = new_value;
-  }
+  size_t min_received_before_ack_decimation() const;
+  void set_min_received_before_ack_decimation(size_t new_value);
 
-  size_t ack_frequency_before_ack_decimation() const {
-    return ack_frequency_before_ack_decimation_;
-  }
-  void set_ack_frequency_before_ack_decimation(size_t new_value) {
-    DCHECK_GT(new_value, 0u);
-    ack_frequency_before_ack_decimation_ = new_value;
-  }
+  size_t ack_frequency_before_ack_decimation() const;
+  void set_ack_frequency_before_ack_decimation(size_t new_value);
 
   // If |defer| is true, configures the connection to defer sending packets in
   // response to an ACK to the SendAlarm. If |defer| is false, packets may be
@@ -1172,6 +1158,8 @@
   QuicPacketHeader last_header_;
   bool should_last_packet_instigate_acks_;
   // Whether the most recent packet was missing before it was received.
+  // TODO(fayang): Remove was_last_packet_missing_ when deprecating
+  // quic_rpm_decides_when_to_send_acks.
   bool was_last_packet_missing_;
 
   // Track some peer state so we can do less bookkeeping
@@ -1234,12 +1222,18 @@
   // quic_deprecate_ack_bundling_mode.
   bool ack_queued_;
   // How many retransmittable packets have arrived without sending an ack.
+  // TODO(fayang): Remove
+  // num_retransmittable_packets_received_since_last_ack_sent_ when deprecating
+  // quic_rpm_decides_when_to_send_acks.
   QuicPacketCount num_retransmittable_packets_received_since_last_ack_sent_;
   // How many consecutive packets have arrived without sending an ack.
   QuicPacketCount num_packets_received_since_last_ack_sent_;
   // Indicates how many consecutive times an ack has arrived which indicates
   // the peer needs to stop waiting for some packets.
   int stop_waiting_count_;
+  // TODO(fayang): Remove ack_mode_, ack_decimation_delay_,
+  // unlimited_ack_decimation_, fast_ack_after_quiescence_ when deprecating
+  // quic_rpm_decides_when_to_send_acks.
   // Indicates the current ack mode, defaults to acking every 2 packets.
   AckMode ack_mode_;
   // The max delay in fraction of min_rtt to use when sending decimated acks.
@@ -1311,6 +1305,8 @@
   QuicTime time_of_last_received_packet_;
 
   // The time the previous ack-instigating packet was received and processed.
+  // TODO(fayang): Remove time_of_previous_received_packet_ when deprecating
+  // quic_rpm_decides_when_to_send_acks.
   QuicTime time_of_previous_received_packet_;
 
   // Sent packet manager which tracks the status of packets sent by this
@@ -1394,9 +1390,13 @@
   size_t max_consecutive_num_packets_with_no_retransmittable_frames_;
 
   // Ack decimation will start happening after this many packets are received.
+  // TODO(fayang): Remove min_received_before_ack_decimation_ when deprecating
+  // quic_rpm_decides_when_to_send_acks.
   size_t min_received_before_ack_decimation_;
 
   // Before ack decimation starts (if enabled), we ack every n-th packet.
+  // TODO(fayang): Remove ack_frequency_before_ack_decimation_ when deprecating
+  // quic_rpm_decides_when_to_send_acks.
   size_t ack_frequency_before_ack_decimation_;
 
   // If true, the connection will fill up the pipe with extra data whenever the
diff --git a/net/third_party/quic/core/quic_connection_test.cc b/net/third_party/quic/core/quic_connection_test.cc
index 1203b71..a47f992 100644
--- a/net/third_party/quic/core/quic_connection_test.cc
+++ b/net/third_party/quic/core/quic_connection_test.cc
@@ -830,14 +830,16 @@
       : connection_id_(TestConnectionId()),
         framer_(SupportedVersions(version()),
                 QuicTime::Zero(),
-                Perspective::IS_CLIENT),
+                Perspective::IS_CLIENT,
+                connection_id_.length()),
         send_algorithm_(new StrictMock<MockSendAlgorithm>),
         loss_algorithm_(new MockLossAlgorithm()),
         helper_(new TestConnectionHelper(&clock_, &random_generator_)),
         alarm_factory_(new TestAlarmFactory()),
         peer_framer_(SupportedVersions(version()),
                      QuicTime::Zero(),
-                     Perspective::IS_SERVER),
+                     Perspective::IS_SERVER,
+                     connection_id_.length()),
         peer_creator_(connection_id_,
                       &peer_framer_,
                       /*delegate=*/nullptr),
@@ -2346,8 +2348,7 @@
                        QuicTime::Delta::Zero(), QuicTime::Zero());
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
 
-  QuicConnectionPeer::SetAckMode(
-      &connection_, QuicConnection::ACK_DECIMATION_WITH_REORDERING);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION_WITH_REORDERING);
 
   // Start ack decimation from 10th packet.
   connection_.set_min_received_before_ack_decimation(10);
@@ -5155,7 +5156,7 @@
 
 TEST_P(QuicConnectionTest, SendDelayedAckDecimation) {
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
-  QuicConnectionPeer::SetAckMode(&connection_, QuicConnection::ACK_DECIMATION);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION);
 
   const size_t kMinRttMs = 40;
   RttStats* rtt_stats = const_cast<RttStats*>(manager_->GetRttStats());
@@ -5213,7 +5214,7 @@
 
 TEST_P(QuicConnectionTest, SendDelayedAckAckDecimationAfterQuiescence) {
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
-  QuicConnectionPeer::SetAckMode(&connection_, QuicConnection::ACK_DECIMATION);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION);
   QuicConnectionPeer::SetFastAckAfterQuiescence(&connection_, true);
 
   const size_t kMinRttMs = 40;
@@ -5399,7 +5400,7 @@
 
 TEST_P(QuicConnectionTest, SendDelayedAckDecimationEighthRtt) {
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
-  QuicConnectionPeer::SetAckMode(&connection_, QuicConnection::ACK_DECIMATION);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION);
   QuicConnectionPeer::SetAckDecimationDelay(&connection_, 0.125);
 
   const size_t kMinRttMs = 40;
@@ -5458,8 +5459,7 @@
 
 TEST_P(QuicConnectionTest, SendDelayedAckDecimationWithReordering) {
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
-  QuicConnectionPeer::SetAckMode(
-      &connection_, QuicConnection::ACK_DECIMATION_WITH_REORDERING);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION_WITH_REORDERING);
 
   const size_t kMinRttMs = 40;
   RttStats* rtt_stats = const_cast<RttStats*>(manager_->GetRttStats());
@@ -5523,8 +5523,7 @@
 
 TEST_P(QuicConnectionTest, SendDelayedAckDecimationWithLargeReordering) {
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
-  QuicConnectionPeer::SetAckMode(
-      &connection_, QuicConnection::ACK_DECIMATION_WITH_REORDERING);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION_WITH_REORDERING);
 
   const size_t kMinRttMs = 40;
   RttStats* rtt_stats = const_cast<RttStats*>(manager_->GetRttStats());
@@ -5607,8 +5606,7 @@
 
 TEST_P(QuicConnectionTest, SendDelayedAckDecimationWithReorderingEighthRtt) {
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
-  QuicConnectionPeer::SetAckMode(
-      &connection_, QuicConnection::ACK_DECIMATION_WITH_REORDERING);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION_WITH_REORDERING);
   QuicConnectionPeer::SetAckDecimationDelay(&connection_, 0.125);
 
   const size_t kMinRttMs = 40;
@@ -5676,8 +5674,7 @@
 TEST_P(QuicConnectionTest,
        SendDelayedAckDecimationWithLargeReorderingEighthRtt) {
   EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(AnyNumber());
-  QuicConnectionPeer::SetAckMode(
-      &connection_, QuicConnection::ACK_DECIMATION_WITH_REORDERING);
+  QuicConnectionPeer::SetAckMode(&connection_, ACK_DECIMATION_WITH_REORDERING);
   QuicConnectionPeer::SetAckDecimationDelay(&connection_, 0.125);
 
   const size_t kMinRttMs = 40;
diff --git a/net/third_party/quic/core/quic_constants.h b/net/third_party/quic/core/quic_constants.h
index f17b6d2..571af03 100644
--- a/net/third_party/quic/core/quic_constants.h
+++ b/net/third_party/quic/core/quic_constants.h
@@ -214,10 +214,6 @@
 // Number of bytes reserved for connection ID length.
 const size_t kConnectionIdLengthSize = 1;
 
-// Length of an encoded variable length connection ID, in bytes.
-// TODO(dschinazi) b/120240679 - remove kQuicConnectionIdLength
-const size_t kQuicConnectionIdLength = 8;
-
 // Minimum length of random bytes in IETF stateless reset packet.
 const size_t kMinRandomBytesLengthInStatelessReset = 24;
 
diff --git a/net/third_party/quic/core/quic_dispatcher.cc b/net/third_party/quic/core/quic_dispatcher.cc
index 536aac74..32e56d3 100644
--- a/net/third_party/quic/core/quic_dispatcher.cc
+++ b/net/third_party/quic/core/quic_dispatcher.cc
@@ -277,7 +277,8 @@
     QuicVersionManager* version_manager,
     std::unique_ptr<QuicConnectionHelperInterface> helper,
     std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
-    std::unique_ptr<QuicAlarmFactory> alarm_factory)
+    std::unique_ptr<QuicAlarmFactory> alarm_factory,
+    uint8_t expected_connection_id_length)
     : config_(config),
       crypto_config_(crypto_config),
       compressed_certs_cache_(
@@ -292,7 +293,8 @@
       version_manager_(version_manager),
       framer_(GetSupportedVersions(),
               /*unused*/ QuicTime::Zero(),
-              Perspective::IS_SERVER),
+              Perspective::IS_SERVER,
+              expected_connection_id_length),
       last_error_(QUIC_NO_ERROR),
       new_sessions_allowed_per_event_loop_(0u),
       accept_new_connections_(true) {
@@ -1173,7 +1175,7 @@
     if (FLAGS_quic_allow_chlo_buffering &&
         !ChloExtractor::Extract(*current_packet_, GetSupportedVersions(),
                                 config_->create_session_tag_indicators(),
-                                &alpn_extractor)) {
+                                &alpn_extractor, connection_id.length())) {
       // Buffer non-CHLO packets.
       ProcessUnauthenticatedHeaderFate(kFateBuffer, connection_id, form,
                                        version);
@@ -1195,7 +1197,7 @@
                           rejector.get());
   if (!ChloExtractor::Extract(*current_packet_, GetSupportedVersions(),
                               config_->create_session_tag_indicators(),
-                              &validator)) {
+                              &validator, connection_id.length())) {
     ProcessUnauthenticatedHeaderFate(kFateBuffer, connection_id, form, version);
     return;
   }
diff --git a/net/third_party/quic/core/quic_dispatcher.h b/net/third_party/quic/core/quic_dispatcher.h
index 99d3025..2f7e78c 100644
--- a/net/third_party/quic/core/quic_dispatcher.h
+++ b/net/third_party/quic/core/quic_dispatcher.h
@@ -49,7 +49,8 @@
                  QuicVersionManager* version_manager,
                  std::unique_ptr<QuicConnectionHelperInterface> helper,
                  std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
-                 std::unique_ptr<QuicAlarmFactory> alarm_factory);
+                 std::unique_ptr<QuicAlarmFactory> alarm_factory,
+                 uint8_t expected_connection_id_length);
   QuicDispatcher(const QuicDispatcher&) = delete;
   QuicDispatcher& operator=(const QuicDispatcher&) = delete;
 
diff --git a/net/third_party/quic/core/quic_dispatcher_test.cc b/net/third_party/quic/core/quic_dispatcher_test.cc
index 2c2343b..fa8b4a5 100644
--- a/net/third_party/quic/core/quic_dispatcher_test.cc
+++ b/net/third_party/quic/core/quic_dispatcher_test.cc
@@ -125,7 +125,8 @@
                        std::unique_ptr<QuicCryptoServerStream::Helper>(
                            new QuicSimpleCryptoServerStreamHelper(
                                QuicRandom::GetInstance())),
-                       QuicMakeUnique<MockAlarmFactory>()) {}
+                       QuicMakeUnique<MockAlarmFactory>(),
+                       kQuicDefaultConnectionIdLength) {}
 
   MOCK_METHOD4(CreateQuicSession,
                QuicServerSessionBase*(QuicConnectionId connection_id,
@@ -292,7 +293,8 @@
     std::unique_ptr<QuicReceivedPacket> received_packet(
         ConstructReceivedPacket(*packet, mock_helper_.GetClock()->Now()));
 
-    if (ChloExtractor::Extract(*packet, versions, {}, nullptr)) {
+    if (ChloExtractor::Extract(*packet, versions, {}, nullptr,
+                               connection_id.length())) {
       // Add CHLO packet to the beginning to be verified first, because it is
       // also processed first by new session.
       data_connection_map_[connection_id].push_front(
diff --git a/net/third_party/quic/core/quic_epoll_alarm_factory.cc b/net/third_party/quic/core/quic_epoll_alarm_factory.cc
index 3f6f3df3..bd4b6b2 100644
--- a/net/third_party/quic/core/quic_epoll_alarm_factory.cc
+++ b/net/third_party/quic/core/quic_epoll_alarm_factory.cc
@@ -4,6 +4,8 @@
 
 #include "net/third_party/quic/core/quic_epoll_alarm_factory.h"
 
+#include <type_traits>
+
 #include "net/third_party/quic/core/quic_arena_scoped_ptr.h"
 
 namespace quic {
@@ -42,10 +44,12 @@
  private:
   class EpollAlarmImpl : public QuicEpollAlarmBase {
    public:
+    using int64_epoll = decltype(QuicEpollAlarmBase().OnAlarm());
+
     explicit EpollAlarmImpl(QuicEpollAlarm* alarm) : alarm_(alarm) {}
 
     // Use the same integer type as the base class.
-    int64_t /* allow-non-std-int */ OnAlarm() override {
+    int64_epoll OnAlarm() override {
       QuicEpollAlarmBase::OnAlarm();
       alarm_->Fire();
       // Fire will take care of registering the alarm, if needed.
@@ -78,8 +82,8 @@
   if (arena != nullptr) {
     return arena->New<QuicEpollAlarm>(epoll_server_, std::move(delegate));
   }
-    return QuicArenaScopedPtr<QuicAlarm>(
-        new QuicEpollAlarm(epoll_server_, std::move(delegate)));
+  return QuicArenaScopedPtr<QuicAlarm>(
+      new QuicEpollAlarm(epoll_server_, std::move(delegate)));
 }
 
 }  // namespace quic
diff --git a/net/third_party/quic/core/quic_epoll_connection_helper.h b/net/third_party/quic/core/quic_epoll_connection_helper.h
index 95d5545..739b570 100644
--- a/net/third_party/quic/core/quic_epoll_connection_helper.h
+++ b/net/third_party/quic/core/quic_epoll_connection_helper.h
@@ -17,6 +17,7 @@
 #include "net/third_party/quic/core/quic_packets.h"
 #include "net/third_party/quic/core/quic_simple_buffer_allocator.h"
 #include "net/third_party/quic/core/quic_time.h"
+#include "net/third_party/quic/platform/api/quic_default_buffer_allocator.h"
 #include "net/third_party/quic/platform/api/quic_epoll.h"
 #include "net/third_party/quic/platform/impl/quic_epoll_clock.h"
 
@@ -24,8 +25,6 @@
 
 class QuicRandom;
 
-using QuicStreamBufferAllocator = SimpleBufferAllocator;
-
 enum class QuicAllocator { SIMPLE, BUFFER_POOL };
 
 class QuicEpollConnectionHelper : public QuicConnectionHelperInterface {
@@ -46,7 +45,7 @@
   QuicRandom* random_generator_;
   // Set up allocators.  They take up minimal memory before use.
   // Allocator for stream send buffers.
-  QuicStreamBufferAllocator stream_buffer_allocator_;
+  QuicDefaultBufferAllocator stream_buffer_allocator_;
   SimpleBufferAllocator simple_buffer_allocator_;
   QuicAllocator allocator_type_;
 };
diff --git a/net/third_party/quic/core/quic_framer.cc b/net/third_party/quic/core/quic_framer.cc
index d4b93ab..4b877b7e 100644
--- a/net/third_party/quic/core/quic_framer.cc
+++ b/net/third_party/quic/core/quic_framer.cc
@@ -17,6 +17,7 @@
 #include "net/third_party/quic/core/crypto/quic_encrypter.h"
 #include "net/third_party/quic/core/crypto/quic_random.h"
 #include "net/third_party/quic/core/quic_connection_id.h"
+#include "net/third_party/quic/core/quic_constants.h"
 #include "net/third_party/quic/core/quic_data_reader.h"
 #include "net/third_party/quic/core/quic_data_writer.h"
 #include "net/third_party/quic/core/quic_socket_address_coder.h"
@@ -452,7 +453,8 @@
 
 QuicFramer::QuicFramer(const ParsedQuicVersionVector& supported_versions,
                        QuicTime creation_time,
-                       Perspective perspective)
+                       Perspective perspective,
+                       uint8_t expected_connection_id_length)
     : visitor_(nullptr),
       error_(QUIC_NO_ERROR),
       last_serialized_connection_id_(EmptyQuicConnectionId()),
@@ -470,7 +472,8 @@
       first_sending_packet_number_(FirstSendingPacketNumber()),
       data_producer_(nullptr),
       infer_packet_header_type_from_version_(perspective ==
-                                             Perspective::IS_CLIENT) {
+                                             Perspective::IS_CLIENT),
+      expected_connection_id_length_(expected_connection_id_length) {
   DCHECK(!supported_versions.empty());
   version_ = supported_versions_[0];
   decrypter_ = QuicMakeUnique<NullDecrypter>(perspective);
@@ -764,7 +767,7 @@
     const QuicNewConnectionIdFrame& frame) {
   return kQuicFrameTypeSize +
          QuicDataWriter::GetVarInt62Len(frame.sequence_number) +
-         kConnectionIdLengthSize + kQuicConnectionIdLength +
+         kConnectionIdLengthSize + frame.connection_id.length() +
          sizeof(frame.stateless_reset_token);
 }
 
@@ -2543,11 +2546,11 @@
 
   uint8_t destination_connection_id_length =
       header->destination_connection_id_included == CONNECTION_ID_PRESENT
-          ? kQuicDefaultConnectionIdLength
+          ? expected_connection_id_length_
           : 0;
   uint8_t source_connection_id_length =
       header->source_connection_id_included == CONNECTION_ID_PRESENT
-          ? kQuicDefaultConnectionIdLength
+          ? expected_connection_id_length_
           : 0;
   if (header->form == IETF_QUIC_LONG_HEADER_PACKET) {
     // Read and validate connection ID length.
@@ -2567,9 +2570,6 @@
     }
     if (dcil != destination_connection_id_length ||
         scil != source_connection_id_length) {
-      // Long header packets received by client must include 8-byte source
-      // connection ID, and those received by server must include 8-byte
-      // destination connection ID.
       QUIC_DVLOG(1) << "dcil: " << static_cast<uint32_t>(dcil)
                     << ", scil: " << static_cast<uint32_t>(scil);
       set_detailed_error("Invalid ConnectionId length.");
@@ -3666,10 +3666,7 @@
     set_detailed_error("Unable to read least unacked delta.");
     return false;
   }
-  if (header.packet_number.ToUint64() < least_unacked_delta ||
-      (GetQuicReloadableFlag(
-           quic_close_connection_with_zero_least_unacked_stop_waiting) &&
-       header.packet_number.ToUint64() == least_unacked_delta)) {
+  if (header.packet_number.ToUint64() <= least_unacked_delta) {
     set_detailed_error("Invalid unacked delta.");
     return false;
   }
@@ -5566,7 +5563,7 @@
     set_detailed_error("Can not write New Connection ID sequence number");
     return false;
   }
-  if (!writer->WriteUInt8(kQuicConnectionIdLength)) {
+  if (!writer->WriteUInt8(frame.connection_id.length())) {
     set_detailed_error(
         "Can not write New Connection ID frame connection ID Length");
     return false;
@@ -5600,8 +5597,7 @@
     return false;
   }
 
-  // TODO(dschinazi) b/120240679 - remove this check
-  if (connection_id_length != kQuicConnectionIdLength) {
+  if (connection_id_length != kQuicDefaultConnectionIdLength) {
     set_detailed_error("Invalid new connection ID length.");
     return false;
   }
diff --git a/net/third_party/quic/core/quic_framer.h b/net/third_party/quic/core/quic_framer.h
index 8d45a4a0..ac1efa4 100644
--- a/net/third_party/quic/core/quic_framer.h
+++ b/net/third_party/quic/core/quic_framer.h
@@ -224,7 +224,8 @@
   // version in |supported_versions|.
   QuicFramer(const ParsedQuicVersionVector& supported_versions,
              QuicTime creation_time,
-             Perspective perspective);
+             Perspective perspective,
+             uint8_t expected_connection_id_length);
   QuicFramer(const QuicFramer&) = delete;
   QuicFramer& operator=(const QuicFramer&) = delete;
 
@@ -915,6 +916,12 @@
   // Otherwise, framer infers packet header type from first byte of a received
   // packet.
   bool infer_packet_header_type_from_version_;
+
+  // IETF short headers contain a destination connection ID but do not
+  // encode its length. This variable contains the length we expect to read.
+  // This is also used to validate the long header connection ID lengths in
+  // older versions of QUIC.
+  const uint8_t expected_connection_id_length_;
 };
 
 }  // namespace quic
diff --git a/net/third_party/quic/core/quic_framer_test.cc b/net/third_party/quic/core/quic_framer_test.cc
index dc283ee8..cdf565c5 100644
--- a/net/third_party/quic/core/quic_framer_test.cc
+++ b/net/third_party/quic/core/quic_framer_test.cc
@@ -14,6 +14,7 @@
 #include "net/third_party/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quic/core/crypto/quic_decrypter.h"
 #include "net/third_party/quic/core/crypto/quic_encrypter.h"
+#include "net/third_party/quic/core/quic_connection_id.h"
 #include "net/third_party/quic/core/quic_packets.h"
 #include "net/third_party/quic/core/quic_types.h"
 #include "net/third_party/quic/core/quic_utils.h"
@@ -434,7 +435,8 @@
         start_(QuicTime::Zero() + QuicTime::Delta::FromMicroseconds(0x10)),
         framer_(AllSupportedVersionsIncludingTls(),
                 start_,
-                Perspective::IS_SERVER) {
+                Perspective::IS_SERVER,
+                kQuicDefaultConnectionIdLength) {
     SetQuicFlag(&FLAGS_quic_supports_tls_handshake, true);
     framer_.set_version(version_);
     framer_.SetDecrypter(ENCRYPTION_NONE,
@@ -9695,7 +9697,7 @@
                         size_t size,
                         const ParsedQuicVersion& version) {
   QuicFramer framer(AllSupportedVersions(), QuicTime::Zero(),
-                    Perspective::IS_SERVER);
+                    Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
   ASSERT_EQ(GetQuicFlag(FLAGS_quic_supports_tls_handshake), true);
   const char* const packet_bytes = reinterpret_cast<const char*>(data);
 
@@ -12882,6 +12884,46 @@
   ASSERT_EQ(visitor_.coalesced_packets_.size(), 0u);
 }
 
+TEST_P(QuicFramerTest, PacketHeaderWithVariableLengthConnectionId) {
+  if (framer_.transport_version() < QUIC_VERSION_46) {
+    return;
+  }
+  char connection_id_bytes[9] = {0xFE, 0xDC, 0xBA, 0x98, 0x76,
+                                 0x54, 0x32, 0x10, 0x42};
+  QuicConnectionId connection_id(connection_id_bytes,
+                                 sizeof(connection_id_bytes));
+  QuicFramerPeer::SetLargestPacketNumber(&framer_, kPacketNumber - 2);
+  QuicFramerPeer::SetExpectedConnectionIDLength(&framer_,
+                                                connection_id.length());
+
+  // clang-format off
+  PacketFragments packet = {
+      // type (8 byte connection_id and 1 byte packet number)
+      {"Unable to read type.",
+       {0x40}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0x42}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x78}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(connection_id, visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_FALSE(visitor_.header_->version_flag);
+  EXPECT_EQ(PACKET_1BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_PACKET_HEADER);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/net/third_party/quic/core/quic_ietf_framer_test.cc b/net/third_party/quic/core/quic_ietf_framer_test.cc
index 802dc71..7111c86e 100644
--- a/net/third_party/quic/core/quic_ietf_framer_test.cc
+++ b/net/third_party/quic/core/quic_ietf_framer_test.cc
@@ -210,7 +210,10 @@
  public:
   QuicIetfFramerTest()
       : start_(QuicTime::Zero() + QuicTime::Delta::FromMicroseconds(0x10)),
-        framer_(AllSupportedVersions(), start_, Perspective::IS_SERVER) {
+        framer_(AllSupportedVersions(),
+                start_,
+                Perspective::IS_SERVER,
+                kQuicDefaultConnectionIdLength) {
     framer_.set_visitor(&visitor_);
   }
 
diff --git a/net/third_party/quic/core/quic_packet_creator_test.cc b/net/third_party/quic/core/quic_packet_creator_test.cc
index 1ec5056..de214f0 100644
--- a/net/third_party/quic/core/quic_packet_creator_test.cc
+++ b/net/third_party/quic/core/quic_packet_creator_test.cc
@@ -135,13 +135,15 @@
 
  protected:
   QuicPacketCreatorTest()
-      : server_framer_(SupportedVersions(GetParam().version),
+      : connection_id_(TestConnectionId(2)),
+        server_framer_(SupportedVersions(GetParam().version),
                        QuicTime::Zero(),
-                       Perspective::IS_SERVER),
+                       Perspective::IS_SERVER,
+                       connection_id_.length()),
         client_framer_(SupportedVersions(GetParam().version),
                        QuicTime::Zero(),
-                       Perspective::IS_CLIENT),
-        connection_id_(TestConnectionId(2)),
+                       Perspective::IS_CLIENT,
+                       connection_id_.length()),
         data_("foo"),
         creator_(connection_id_, &client_framer_, &delegate_, &producer_),
         serialized_packet_(creator_.NoPacket()) {
@@ -245,12 +247,12 @@
   static const QuicStreamOffset kOffset = 0u;
 
   char buffer_[kMaxPacketSize];
+  QuicConnectionId connection_id_;
   QuicFrames frames_;
   QuicFramer server_framer_;
   QuicFramer client_framer_;
   StrictMock<MockFramerVisitor> framer_visitor_;
   StrictMock<MockPacketCreatorDelegate> delegate_;
-  QuicConnectionId connection_id_;
   QuicString data_;
   struct iovec iov_;
   TestPacketCreator creator_;
diff --git a/net/third_party/quic/core/quic_packet_generator_test.cc b/net/third_party/quic/core/quic_packet_generator_test.cc
index c99d0b4..dc4cac7 100644
--- a/net/third_party/quic/core/quic_packet_generator_test.cc
+++ b/net/third_party/quic/core/quic_packet_generator_test.cc
@@ -201,7 +201,8 @@
   QuicPacketGeneratorTest()
       : framer_(AllSupportedVersions(),
                 QuicTime::Zero(),
-                Perspective::IS_CLIENT),
+                Perspective::IS_CLIENT,
+                kQuicDefaultConnectionIdLength),
         generator_(TestConnectionId(),
                    &framer_,
                    &random_generator_,
diff --git a/net/third_party/quic/core/quic_packet_reader.cc b/net/third_party/quic/core/quic_packet_reader.cc
index 6dbe6d2..129f6dd 100644
--- a/net/third_party/quic/core/quic_packet_reader.cc
+++ b/net/third_party/quic/core/quic_packet_reader.cc
@@ -65,7 +65,7 @@
     const QuicClock& clock,
     ProcessPacketInterface* processor,
     QuicPacketCount* packets_dropped) {
-#if MMSG_MORE
+#if MMSG_MORE_NO_ANDROID
   return ReadAndDispatchManyPackets(fd, port, clock, processor,
                                     packets_dropped);
 #else
@@ -80,7 +80,7 @@
     const QuicClock& clock,
     ProcessPacketInterface* processor,
     QuicPacketCount* packets_dropped) {
-#if MMSG_MORE
+#if MMSG_MORE_NO_ANDROID
   // Re-set the length fields in case recvmmsg has changed them.
   for (int i = 0; i < kNumPacketsPerReadMmsgCall; ++i) {
     DCHECK_LE(kMaxPacketSize, packets_[i].iov.iov_len);
diff --git a/net/third_party/quic/core/quic_packet_reader.h b/net/third_party/quic/core/quic_packet_reader.h
index 577e63e..5b018aa 100644
--- a/net/third_party/quic/core/quic_packet_reader.h
+++ b/net/third_party/quic/core/quic_packet_reader.h
@@ -19,8 +19,6 @@
 #include "net/third_party/quic/platform/api/quic_socket_address.h"
 #include "net/third_party/quic/platform/impl/quic_socket_utils.h"
 
-#define MMSG_MORE 0
-
 namespace quic {
 
 #if MMSG_MORE
diff --git a/net/third_party/quic/core/quic_received_packet_manager.cc b/net/third_party/quic/core/quic_received_packet_manager.cc
index 8bf9a65..5d63eef6 100644
--- a/net/third_party/quic/core/quic_received_packet_manager.cc
+++ b/net/third_party/quic/core/quic_received_packet_manager.cc
@@ -8,6 +8,7 @@
 #include <limits>
 #include <utility>
 
+#include "net/third_party/quic/core/congestion_control/rtt_stats.h"
 #include "net/third_party/quic/core/crypto/crypto_protocol.h"
 #include "net/third_party/quic/core/quic_connection_stats.h"
 #include "net/third_party/quic/platform/api/quic_bug_tracker.h"
@@ -23,6 +24,19 @@
 // Set to the number of nacks needed for fast retransmit plus one for protection
 // against an ack loss
 const size_t kMaxPacketsAfterNewMissing = 4;
+
+// Maximum number of retransmittable packets received before sending an ack.
+const QuicPacketCount kDefaultRetransmittablePacketsBeforeAck = 2;
+// Minimum number of packets received before ack decimation is enabled.
+// This intends to avoid the beginning of slow start, when CWNDs may be
+// rapidly increasing.
+const QuicPacketCount kMinReceivedBeforeAckDecimation = 100;
+// Wait for up to 10 retransmittable packets before sending an ack.
+const QuicPacketCount kMaxRetransmittablePacketsBeforeAck = 10;
+// One quarter RTT delay when doing ack decimation.
+const float kAckDecimationDelay = 0.25;
+// One eighth RTT delay when doing ack decimation.
+const float kShortAckDecimationDelay = 0.125;
 }  // namespace
 
 QuicReceivedPacketManager::QuicReceivedPacketManager(QuicConnectionStats* stats)
@@ -30,15 +44,63 @@
       max_ack_ranges_(0),
       time_largest_observed_(QuicTime::Zero()),
       save_timestamps_(false),
-      stats_(stats) {}
+      stats_(stats),
+      ack_mode_(GetQuicReloadableFlag(quic_enable_ack_decimation)
+                    ? ACK_DECIMATION
+                    : TCP_ACKING),
+      num_retransmittable_packets_received_since_last_ack_sent_(0),
+      min_received_before_ack_decimation_(kMinReceivedBeforeAckDecimation),
+      ack_frequency_before_ack_decimation_(
+          kDefaultRetransmittablePacketsBeforeAck),
+      ack_decimation_delay_(kAckDecimationDelay),
+      unlimited_ack_decimation_(false),
+      fast_ack_after_quiescence_(false),
+      ack_timeout_(QuicTime::Zero()),
+      time_of_previous_received_packet_(QuicTime::Zero()),
+      was_last_packet_missing_(false),
+      decide_when_to_send_acks_(
+          GetQuicReloadableFlag(quic_deprecate_ack_bundling_mode) &&
+          GetQuicReloadableFlag(quic_rpm_decides_when_to_send_acks)) {}
 
 QuicReceivedPacketManager::~QuicReceivedPacketManager() {}
 
+void QuicReceivedPacketManager::SetFromConfig(const QuicConfig& config,
+                                              Perspective perspective) {
+  DCHECK(decide_when_to_send_acks_);
+  if (GetQuicReloadableFlag(quic_enable_ack_decimation) &&
+      config.HasClientSentConnectionOption(kACD0, perspective)) {
+    ack_mode_ = TCP_ACKING;
+  }
+  if (config.HasClientSentConnectionOption(kACKD, perspective)) {
+    ack_mode_ = ACK_DECIMATION;
+  }
+  if (config.HasClientSentConnectionOption(kAKD2, perspective)) {
+    ack_mode_ = ACK_DECIMATION_WITH_REORDERING;
+  }
+  if (config.HasClientSentConnectionOption(kAKD3, perspective)) {
+    ack_mode_ = ACK_DECIMATION;
+    ack_decimation_delay_ = kShortAckDecimationDelay;
+  }
+  if (config.HasClientSentConnectionOption(kAKD4, perspective)) {
+    ack_mode_ = ACK_DECIMATION_WITH_REORDERING;
+    ack_decimation_delay_ = kShortAckDecimationDelay;
+  }
+  if (config.HasClientSentConnectionOption(kAKDU, perspective)) {
+    unlimited_ack_decimation_ = true;
+  }
+  if (config.HasClientSentConnectionOption(kACKQ, perspective)) {
+    fast_ack_after_quiescence_ = true;
+  }
+}
+
 void QuicReceivedPacketManager::RecordPacketReceived(
     const QuicPacketHeader& header,
     QuicTime receipt_time) {
   const QuicPacketNumber packet_number = header.packet_number;
   DCHECK(IsAwaitingPacket(packet_number)) << " packet_number:" << packet_number;
+  if (decide_when_to_send_acks_) {
+    was_last_packet_missing_ = IsMissing(packet_number);
+  }
   if (!ack_frame_updated_) {
     ack_frame_.received_packet_times.clear();
   }
@@ -99,7 +161,9 @@
 
 const QuicFrame QuicReceivedPacketManager::GetUpdatedAckFrame(
     QuicTime approximate_now) {
-  ack_frame_updated_ = false;
+  if (!decide_when_to_send_acks_) {
+    ack_frame_updated_ = false;
+  }
   if (time_largest_observed_ == QuicTime::Zero()) {
     // We have received no packets.
     ack_frame_.ack_delay_time = QuicTime::Delta::Infinite();
@@ -151,6 +215,102 @@
          ack_frame_.packets.Min() >= peer_least_packet_awaiting_ack_);
 }
 
+void QuicReceivedPacketManager::MaybeUpdateAckTimeout(
+    bool should_last_packet_instigate_acks,
+    QuicPacketNumber last_received_packet_number,
+    QuicTime time_of_last_received_packet,
+    QuicTime now,
+    const RttStats* rtt_stats,
+    QuicTime::Delta delayed_ack_time) {
+  DCHECK(decide_when_to_send_acks_);
+  if (!ack_frame_updated_) {
+    // ACK frame has not been updated, nothing to do.
+    return;
+  }
+
+  if (was_last_packet_missing_ && last_sent_largest_acked_.IsInitialized() &&
+      last_received_packet_number < last_sent_largest_acked_) {
+    // Only ack immediately if an ACK frame was sent with a larger largest acked
+    // than the newly received packet number.
+    ack_timeout_ = now;
+    return;
+  }
+
+  if (!should_last_packet_instigate_acks) {
+    return;
+  }
+
+  ++num_retransmittable_packets_received_since_last_ack_sent_;
+  if (ack_mode_ != TCP_ACKING &&
+      last_received_packet_number >= PeerFirstSendingPacketNumber() +
+                                         min_received_before_ack_decimation_) {
+    // Ack up to 10 packets at once unless ack decimation is unlimited.
+    if (!unlimited_ack_decimation_ &&
+        num_retransmittable_packets_received_since_last_ack_sent_ >=
+            kMaxRetransmittablePacketsBeforeAck) {
+      ack_timeout_ = now;
+      return;
+    }
+    // Wait for the minimum of the ack decimation delay or the delayed ack time
+    // before sending an ack.
+    QuicTime::Delta ack_delay = std::min(
+        delayed_ack_time, rtt_stats->min_rtt() * ack_decimation_delay_);
+    if (fast_ack_after_quiescence_ && now - time_of_previous_received_packet_ >
+                                          rtt_stats->SmoothedOrInitialRtt()) {
+      // Ack the first packet out of queiscence faster, because QUIC does
+      // not pace the first few packets and commonly these may be handshake
+      // or TLP packets, which we'd like to acknowledge quickly.
+      ack_delay = QuicTime::Delta::FromMilliseconds(1);
+    }
+    MaybeUpdateAckTimeoutTo(now + ack_delay);
+  } else {
+    // Ack with a timer or every 2 packets by default.
+    if (num_retransmittable_packets_received_since_last_ack_sent_ >=
+        ack_frequency_before_ack_decimation_) {
+      ack_timeout_ = now;
+    } else if (fast_ack_after_quiescence_ &&
+               (now - time_of_previous_received_packet_) >
+                   rtt_stats->SmoothedOrInitialRtt()) {
+      // Ack the first packet out of queiscence faster, because QUIC does
+      // not pace the first few packets and commonly these may be handshake
+      // or TLP packets, which we'd like to acknowledge quickly.
+      MaybeUpdateAckTimeoutTo(now + QuicTime::Delta::FromMilliseconds(1));
+    } else {
+      MaybeUpdateAckTimeoutTo(now + delayed_ack_time);
+    }
+  }
+
+  // If there are new missing packets to report, send an ack immediately.
+  if (HasNewMissingPackets()) {
+    if (ack_mode_ == ACK_DECIMATION_WITH_REORDERING) {
+      // Wait the minimum of an eighth min_rtt and the existing ack time.
+      QuicTime ack_time = now + kShortAckDecimationDelay * rtt_stats->min_rtt();
+      MaybeUpdateAckTimeoutTo(ack_time);
+    } else {
+      ack_timeout_ = now;
+    }
+  }
+
+  if (fast_ack_after_quiescence_) {
+    time_of_previous_received_packet_ = time_of_last_received_packet;
+  }
+}
+
+void QuicReceivedPacketManager::ResetAckStates() {
+  DCHECK(decide_when_to_send_acks_);
+  ack_frame_updated_ = false;
+  ack_timeout_ = QuicTime::Zero();
+  num_retransmittable_packets_received_since_last_ack_sent_ = 0;
+  last_sent_largest_acked_ = LargestAcked(ack_frame_);
+}
+
+void QuicReceivedPacketManager::MaybeUpdateAckTimeoutTo(QuicTime time) {
+  DCHECK(decide_when_to_send_acks_);
+  if (!ack_timeout_.IsInitialized() || ack_timeout_ > time) {
+    ack_timeout_ = time;
+  }
+}
+
 bool QuicReceivedPacketManager::HasMissingPackets() const {
   if (ack_frame_.packets.Empty()) {
     return false;
diff --git a/net/third_party/quic/core/quic_received_packet_manager.h b/net/third_party/quic/core/quic_received_packet_manager.h
index f438702..099d6ca 100644
--- a/net/third_party/quic/core/quic_received_packet_manager.h
+++ b/net/third_party/quic/core/quic_received_packet_manager.h
@@ -13,8 +13,11 @@
 
 namespace quic {
 
+class RttStats;
+
 namespace test {
 class QuicConnectionPeer;
+class QuicReceivedPacketManagerPeer;
 }  // namespace test
 
 struct QuicConnectionStats;
@@ -28,6 +31,8 @@
       delete;
   virtual ~QuicReceivedPacketManager();
 
+  void SetFromConfig(const QuicConfig& config, Perspective perspective);
+
   // Updates the internal state concerning which packets have been received.
   // header: the packet header.
   // timestamp: the arrival time of the packet.
@@ -50,6 +55,20 @@
   // received after this call.
   void DontWaitForPacketsBefore(QuicPacketNumber least_unacked);
 
+  // Called to update ack_timeout_ to the time when an ACK needs to be sent. A
+  // caller can decide whether and when to send an ACK by retrieving
+  // ack_timeout_. If ack_timeout_ is not initialized, no ACK needs to be sent.
+  // Otherwise, ACK needs to be sent by the specified time.
+  void MaybeUpdateAckTimeout(bool should_last_packet_instigate_acks,
+                             QuicPacketNumber last_received_packet_number,
+                             QuicTime time_of_last_received_packet,
+                             QuicTime now,
+                             const RttStats* rtt_stats,
+                             QuicTime::Delta delayed_ack_time);
+
+  // Resets ACK related states, called after an ACK is successfully sent.
+  void ResetAckStates();
+
   // Returns true if there are any missing packets.
   bool HasMissingPackets() const;
 
@@ -83,8 +102,31 @@
     save_timestamps_ = save_timestamps;
   }
 
+  size_t min_received_before_ack_decimation() const {
+    return min_received_before_ack_decimation_;
+  }
+  void set_min_received_before_ack_decimation(size_t new_value) {
+    min_received_before_ack_decimation_ = new_value;
+  }
+
+  size_t ack_frequency_before_ack_decimation() const {
+    return ack_frequency_before_ack_decimation_;
+  }
+  void set_ack_frequency_before_ack_decimation(size_t new_value) {
+    DCHECK_GT(new_value, 0u);
+    ack_frequency_before_ack_decimation_ = new_value;
+  }
+
+  QuicTime ack_timeout() const { return ack_timeout_; }
+
+  bool decide_when_to_send_acks() const { return decide_when_to_send_acks_; }
+
  private:
   friend class test::QuicConnectionPeer;
+  friend class test::QuicReceivedPacketManagerPeer;
+
+  // Sets ack_timeout_ to |time| if ack_timeout_ is not initialized or > time.
+  void MaybeUpdateAckTimeoutTo(QuicTime time);
 
   // Least packet number of the the packet sent by the peer for which it
   // hasn't received an ack.
@@ -112,6 +154,38 @@
   QuicPacketNumber least_received_packet_number_;
 
   QuicConnectionStats* stats_;
+
+  AckMode ack_mode_;
+  // How many retransmittable packets have arrived without sending an ack.
+  QuicPacketCount num_retransmittable_packets_received_since_last_ack_sent_;
+  // Ack decimation will start happening after this many packets are received.
+  size_t min_received_before_ack_decimation_;
+  // Before ack decimation starts (if enabled), we ack every n-th packet.
+  size_t ack_frequency_before_ack_decimation_;
+  // The max delay in fraction of min_rtt to use when sending decimated acks.
+  float ack_decimation_delay_;
+  // When true, removes ack decimation's max number of packets(10) before
+  // sending an ack.
+  bool unlimited_ack_decimation_;
+  // When true, use a 1ms delayed ack timer if it's been an SRTT since a packet
+  // was received.
+  bool fast_ack_after_quiescence_;
+
+  // Time that an ACK needs to be sent. 0 means no ACK is pending. Used when
+  // decide_when_to_send_acks_ is true.
+  QuicTime ack_timeout_;
+
+  // The time the previous ack-instigating packet was received and processed.
+  QuicTime time_of_previous_received_packet_;
+  // Whether the most recent packet was missing before it was received.
+  bool was_last_packet_missing_;
+
+  // Last sent largest acked, which gets updated when ACK was successfully sent.
+  QuicPacketNumber last_sent_largest_acked_;
+
+  // Latched value of quic_deprecate_ack_bundling_mode and
+  // quic_rpm_decides_when_to_send_acks.
+  const bool decide_when_to_send_acks_;
 };
 
 }  // namespace quic
diff --git a/net/third_party/quic/core/quic_received_packet_manager_test.cc b/net/third_party/quic/core/quic_received_packet_manager_test.cc
index e68f864..18d5627 100644
--- a/net/third_party/quic/core/quic_received_packet_manager_test.cc
+++ b/net/third_party/quic/core/quic_received_packet_manager_test.cc
@@ -8,14 +8,40 @@
 #include <ostream>
 #include <vector>
 
+#include "net/third_party/quic/core/congestion_control/rtt_stats.h"
+#include "net/third_party/quic/core/crypto/crypto_protocol.h"
 #include "net/third_party/quic/core/quic_connection_stats.h"
 #include "net/third_party/quic/platform/api/quic_expect_bug.h"
 #include "net/third_party/quic/platform/api/quic_test.h"
+#include "net/third_party/quic/test_tools/mock_clock.h"
 
 namespace quic {
 namespace test {
+
+class QuicReceivedPacketManagerPeer {
+ public:
+  static void SetAckMode(QuicReceivedPacketManager* manager, AckMode ack_mode) {
+    manager->ack_mode_ = ack_mode;
+  }
+
+  static void SetFastAckAfterQuiescence(QuicReceivedPacketManager* manager,
+                                        bool fast_ack_after_quiescence) {
+    manager->fast_ack_after_quiescence_ = fast_ack_after_quiescence;
+  }
+
+  static void SetAckDecimationDelay(QuicReceivedPacketManager* manager,
+                                    float ack_decimation_delay) {
+    manager->ack_decimation_delay_ = ack_decimation_delay;
+  }
+};
+
 namespace {
 
+const bool kInstigateAck = true;
+const QuicTime::Delta kMinRttMs = QuicTime::Delta::FromMilliseconds(40);
+const QuicTime::Delta kDelayedAckTime =
+    QuicTime::Delta::FromMilliseconds(kDefaultDelayedAckTimeMs);
+
 struct TestParams {
   explicit TestParams(QuicTransportVersion version) : version(version) {}
 
@@ -40,6 +66,8 @@
 class QuicReceivedPacketManagerTest : public QuicTestWithParam<TestParams> {
  protected:
   QuicReceivedPacketManagerTest() : received_manager_(&stats_) {
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    rtt_stats_.UpdateRtt(kMinRttMs, QuicTime::Delta::Zero(), QuicTime::Zero());
     received_manager_.set_save_timestamps(true);
   }
 
@@ -53,6 +81,31 @@
     received_manager_.RecordPacketReceived(header, receipt_time);
   }
 
+  bool HasPendingAck() {
+    DCHECK(received_manager_.decide_when_to_send_acks());
+    return received_manager_.ack_timeout().IsInitialized();
+  }
+
+  void MaybeUpdateAckTimeout(bool should_last_packet_instigate_acks,
+                             uint64_t last_received_packet_number) {
+    DCHECK(received_manager_.decide_when_to_send_acks());
+    received_manager_.MaybeUpdateAckTimeout(
+        should_last_packet_instigate_acks,
+        QuicPacketNumber(last_received_packet_number), clock_.ApproximateNow(),
+        clock_.ApproximateNow(), &rtt_stats_, kDelayedAckTime);
+  }
+
+  void CheckAckTimeout(QuicTime time) {
+    DCHECK(HasPendingAck() && received_manager_.ack_timeout() == time);
+    if (time <= clock_.ApproximateNow()) {
+      // ACK timeout expires, send an ACK.
+      received_manager_.ResetAckStates();
+      DCHECK(!HasPendingAck());
+    }
+  }
+
+  MockClock clock_;
+  RttStats rtt_stats_;
   QuicConnectionStats stats_;
   QuicReceivedPacketManager received_manager_;
 };
@@ -83,6 +136,9 @@
   EXPECT_TRUE(received_manager_.ack_frame_updated());
 
   QuicFrame ack = received_manager_.GetUpdatedAckFrame(QuicTime::Zero());
+  if (received_manager_.decide_when_to_send_acks()) {
+    received_manager_.ResetAckStates();
+  }
   EXPECT_FALSE(received_manager_.ack_frame_updated());
   // When UpdateReceivedPacketInfo with a time earlier than the time of the
   // largest observed packet, make sure that the delta is 0, not negative.
@@ -91,6 +147,9 @@
 
   QuicTime four_ms = QuicTime::Zero() + QuicTime::Delta::FromMilliseconds(4);
   ack = received_manager_.GetUpdatedAckFrame(four_ms);
+  if (received_manager_.decide_when_to_send_acks()) {
+    received_manager_.ResetAckStates();
+  }
   EXPECT_FALSE(received_manager_.ack_frame_updated());
   // When UpdateReceivedPacketInfo after not having received a new packet,
   // the delta should still be accurate.
@@ -107,6 +166,9 @@
   received_manager_.RecordPacketReceived(header, two_ms);
   EXPECT_TRUE(received_manager_.ack_frame_updated());
   ack = received_manager_.GetUpdatedAckFrame(two_ms);
+  if (received_manager_.decide_when_to_send_acks()) {
+    received_manager_.ResetAckStates();
+  }
   EXPECT_FALSE(received_manager_.ack_frame_updated());
   // UpdateReceivedPacketInfo should discard any times which can't be
   // expressed on the wire.
@@ -198,6 +260,548 @@
   EXPECT_FALSE(received_manager_.HasMissingPackets());
 }
 
+TEST_P(QuicReceivedPacketManagerTest, OutOfOrderReceiptCausesAckSent) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+
+  RecordPacketReceipt(3, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 3);
+  if (GetQuicRestartFlag(quic_enable_accept_random_ipn)) {
+    // Delayed ack is scheduled.
+    CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+  } else {
+    // Should ack immediately since we have missing packets.
+    CheckAckTimeout(clock_.ApproximateNow());
+  }
+
+  RecordPacketReceipt(2, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 2);
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  RecordPacketReceipt(1, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 1);
+  // Should ack immediately, since this fills the last hole.
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  RecordPacketReceipt(4, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 4);
+  // Delayed ack is scheduled.
+  CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+}
+
+TEST_P(QuicReceivedPacketManagerTest, OutOfOrderAckReceiptCausesNoAck) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+
+  RecordPacketReceipt(2, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(!kInstigateAck, 2);
+  EXPECT_FALSE(HasPendingAck());
+
+  RecordPacketReceipt(1, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(!kInstigateAck, 1);
+  EXPECT_FALSE(HasPendingAck());
+}
+
+TEST_P(QuicReceivedPacketManagerTest, AckReceiptCausesAckSend) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+
+  RecordPacketReceipt(1, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(!kInstigateAck, 1);
+  EXPECT_FALSE(HasPendingAck());
+
+  RecordPacketReceipt(2, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(!kInstigateAck, 2);
+  EXPECT_FALSE(HasPendingAck());
+
+  RecordPacketReceipt(3, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 3);
+  // Delayed ack is scheduled.
+  CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+  clock_.AdvanceTime(kDelayedAckTime);
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  RecordPacketReceipt(4, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(!kInstigateAck, 4);
+  EXPECT_FALSE(HasPendingAck());
+
+  RecordPacketReceipt(5, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(!kInstigateAck, 5);
+  EXPECT_FALSE(HasPendingAck());
+}
+
+TEST_P(QuicReceivedPacketManagerTest, AckSentEveryNthPacket) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  received_manager_.set_ack_frequency_before_ack_decimation(3);
+
+  // Receives packets 1 - 39.
+  for (size_t i = 1; i <= 39; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 3 == 0) {
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+}
+
+TEST_P(QuicReceivedPacketManagerTest, AckDecimationReducesAcks) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_,
+                                            ACK_DECIMATION_WITH_REORDERING);
+
+  // Start ack decimation from 10th packet.
+  received_manager_.set_min_received_before_ack_decimation(10);
+
+  // Receives packets 1 - 29.
+  for (size_t i = 1; i <= 29; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i <= 10) {
+      // For packets 1-10, ack every 2 packets.
+      if (i % 2 == 0) {
+        CheckAckTimeout(clock_.ApproximateNow());
+      } else {
+        CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+      }
+      continue;
+    }
+    // ack at 20.
+    if (i == 20) {
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kMinRttMs * 0.25);
+    }
+  }
+
+  // We now receive the 30th packet, and so we send an ack.
+  RecordPacketReceipt(30, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 30);
+  CheckAckTimeout(clock_.ApproximateNow());
+}
+
+TEST_P(QuicReceivedPacketManagerTest, SendDelayedAfterQuiescence) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetFastAckAfterQuiescence(&received_manager_,
+                                                           true);
+  // The beginning of the connection counts as quiescence.
+  QuicTime ack_time =
+      clock_.ApproximateNow() + QuicTime::Delta::FromMilliseconds(1);
+
+  RecordPacketReceipt(1, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 1);
+  CheckAckTimeout(ack_time);
+  // Simulate delayed ack alarm firing.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  // Process another packet immediately after sending the ack and expect the
+  // ack timeout to be set delayed ack time in the future.
+  ack_time = clock_.ApproximateNow() + kDelayedAckTime;
+  RecordPacketReceipt(2, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 2);
+  CheckAckTimeout(ack_time);
+  // Simulate delayed ack alarm firing.
+  clock_.AdvanceTime(kDelayedAckTime);
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  // Wait 1 second and enesure the ack timeout is set to 1ms in the future.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  ack_time = clock_.ApproximateNow() + QuicTime::Delta::FromMilliseconds(1);
+  RecordPacketReceipt(3, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 3);
+  CheckAckTimeout(ack_time);
+}
+
+TEST_P(QuicReceivedPacketManagerTest, SendDelayedAckDecimation) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_, ACK_DECIMATION);
+  // The ack time should be based on min_rtt * 1/4, since it's less than the
+  // default delayed ack time.
+  QuicTime ack_time = clock_.ApproximateNow() + kMinRttMs * 0.25;
+
+  // Process all the packets in order so there aren't missing packets.
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 1; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+
+  RecordPacketReceipt(kFirstDecimatedPacket, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket);
+  CheckAckTimeout(ack_time);
+
+  // The 10th received packet causes an ack to be sent.
+  for (uint64_t i = 1; i < 10; ++i) {
+    RecordPacketReceipt(kFirstDecimatedPacket + i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + i);
+  }
+  CheckAckTimeout(clock_.ApproximateNow());
+}
+
+TEST_P(QuicReceivedPacketManagerTest,
+       SendDelayedAckAckDecimationAfterQuiescence) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_, ACK_DECIMATION);
+  QuicReceivedPacketManagerPeer::SetFastAckAfterQuiescence(&received_manager_,
+                                                           true);
+  // The beginning of the connection counts as quiescence.
+  QuicTime ack_time =
+      clock_.ApproximateNow() + QuicTime::Delta::FromMilliseconds(1);
+  RecordPacketReceipt(1, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 1);
+  CheckAckTimeout(ack_time);
+  // Simulate delayed ack alarm firing.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  // Process another packet immedately after sending the ack and expect the
+  // ack timeout to be set delayed ack time in the future.
+  ack_time = clock_.ApproximateNow() + kDelayedAckTime;
+  RecordPacketReceipt(2, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 2);
+  CheckAckTimeout(ack_time);
+  // Simulate delayed ack alarm firing.
+  clock_.AdvanceTime(kDelayedAckTime);
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  // Wait 1 second and enesure the ack timeout is set to 1ms in the future.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  ack_time = clock_.ApproximateNow() + QuicTime::Delta::FromMilliseconds(1);
+  RecordPacketReceipt(3, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, 3);
+  CheckAckTimeout(ack_time);
+  // Process enough packets to get into ack decimation behavior.
+  // The ack time should be based on min_rtt/4, since it's less than the
+  // default delayed ack time.
+  ack_time = clock_.ApproximateNow() + kMinRttMs * 0.25;
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 4; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+  EXPECT_FALSE(HasPendingAck());
+  RecordPacketReceipt(kFirstDecimatedPacket, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket);
+  CheckAckTimeout(ack_time);
+
+  // The 10th received packet causes an ack to be sent.
+  for (uint64_t i = 1; i < 10; ++i) {
+    RecordPacketReceipt(kFirstDecimatedPacket + i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + i);
+  }
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  // Wait 1 second and enesure the ack timeout is set to 1ms in the future.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  ack_time = clock_.ApproximateNow() + QuicTime::Delta::FromMilliseconds(1);
+  RecordPacketReceipt(kFirstDecimatedPacket + 10, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + 10);
+  CheckAckTimeout(ack_time);
+}
+
+TEST_P(QuicReceivedPacketManagerTest,
+       SendDelayedAckDecimationUnlimitedAggregation) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicConfig config;
+  QuicTagVector connection_options;
+  connection_options.push_back(kACKD);
+  // No limit on the number of packets received before sending an ack.
+  connection_options.push_back(kAKDU);
+  config.SetConnectionOptionsToSend(connection_options);
+  received_manager_.SetFromConfig(config, Perspective::IS_CLIENT);
+
+  // The ack time should be based on min_rtt/4, since it's less than the
+  // default delayed ack time.
+  QuicTime ack_time = clock_.ApproximateNow() + kMinRttMs * 0.25;
+
+  // Process all the initial packets in order so there aren't missing packets.
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 1; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+
+  RecordPacketReceipt(kFirstDecimatedPacket, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket);
+  CheckAckTimeout(ack_time);
+
+  // 18 packets will not cause an ack to be sent.  19 will because when
+  // stop waiting frames are in use, we ack every 20 packets no matter what.
+  for (int i = 1; i <= 18; ++i) {
+    RecordPacketReceipt(kFirstDecimatedPacket + i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + i);
+  }
+  CheckAckTimeout(ack_time);
+}
+
+TEST_P(QuicReceivedPacketManagerTest, SendDelayedAckDecimationEighthRtt) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_, ACK_DECIMATION);
+  QuicReceivedPacketManagerPeer::SetAckDecimationDelay(&received_manager_,
+                                                       0.125);
+
+  // The ack time should be based on min_rtt/8, since it's less than the
+  // default delayed ack time.
+  QuicTime ack_time = clock_.ApproximateNow() + kMinRttMs * 0.125;
+
+  // Process all the packets in order so there aren't missing packets.
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 1; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+
+  RecordPacketReceipt(kFirstDecimatedPacket, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket);
+  CheckAckTimeout(ack_time);
+
+  // The 10th received packet causes an ack to be sent.
+  for (uint64_t i = 1; i < 10; ++i) {
+    RecordPacketReceipt(kFirstDecimatedPacket + i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + i);
+  }
+  CheckAckTimeout(clock_.ApproximateNow());
+}
+
+TEST_P(QuicReceivedPacketManagerTest, SendDelayedAckDecimationWithReordering) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_,
+                                            ACK_DECIMATION_WITH_REORDERING);
+
+  // The ack time should be based on min_rtt/4, since it's less than the
+  // default delayed ack time.
+  QuicTime ack_time = clock_.ApproximateNow() + kMinRttMs * 0.25;
+  // Process all the packets in order so there aren't missing packets.
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 1; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+
+  // Receive one packet out of order and then the rest in order.
+  // The loop leaves a one packet gap between acks sent to simulate some loss.
+  for (int j = 0; j < 3; ++j) {
+    // Process packet 10 first and ensure the timeout is one eighth min_rtt.
+    RecordPacketReceipt(kFirstDecimatedPacket + 9 + (j * 11),
+                        clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + 9 + (j * 11));
+    ack_time = clock_.ApproximateNow() + QuicTime::Delta::FromMilliseconds(5);
+    CheckAckTimeout(ack_time);
+
+    // The 10th received packet causes an ack to be sent.
+    for (int i = 0; i < 9; ++i) {
+      RecordPacketReceipt(kFirstDecimatedPacket + i + (j * 11),
+                          clock_.ApproximateNow());
+      MaybeUpdateAckTimeout(kInstigateAck,
+                            kFirstDecimatedPacket + i + (j * 11));
+    }
+    CheckAckTimeout(clock_.ApproximateNow());
+  }
+}
+
+TEST_P(QuicReceivedPacketManagerTest,
+       SendDelayedAckDecimationWithLargeReordering) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_,
+                                            ACK_DECIMATION_WITH_REORDERING);
+  // The ack time should be based on min_rtt/4, since it's less than the
+  // default delayed ack time.
+  QuicTime ack_time = clock_.ApproximateNow() + kMinRttMs * 0.25;
+
+  // Process all the packets in order so there aren't missing packets.
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 1; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+
+  RecordPacketReceipt(kFirstDecimatedPacket, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket);
+  CheckAckTimeout(ack_time);
+
+  RecordPacketReceipt(kFirstDecimatedPacket + 19, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + 19);
+  ack_time = clock_.ApproximateNow() + kMinRttMs * 0.125;
+  CheckAckTimeout(ack_time);
+
+  // The 10th received packet causes an ack to be sent.
+  for (int i = 1; i < 9; ++i) {
+    RecordPacketReceipt(kFirstDecimatedPacket + i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + i);
+  }
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  // The next packet received in order will cause an immediate ack, because it
+  // fills a hole.
+  RecordPacketReceipt(kFirstDecimatedPacket + 10, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + 10);
+  CheckAckTimeout(clock_.ApproximateNow());
+}
+
+TEST_P(QuicReceivedPacketManagerTest,
+       SendDelayedAckDecimationWithReorderingEighthRtt) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_,
+                                            ACK_DECIMATION_WITH_REORDERING);
+  QuicReceivedPacketManagerPeer::SetAckDecimationDelay(&received_manager_,
+                                                       0.125);
+  // The ack time should be based on min_rtt/8, since it's less than the
+  // default delayed ack time.
+  QuicTime ack_time = clock_.ApproximateNow() + kMinRttMs * 0.125;
+
+  // Process all the packets in order so there aren't missing packets.
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 1; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+
+  RecordPacketReceipt(kFirstDecimatedPacket, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket);
+  CheckAckTimeout(ack_time);
+
+  // Process packet 10 first and ensure the timeout is one eighth min_rtt.
+  RecordPacketReceipt(kFirstDecimatedPacket + 9, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + 9);
+  CheckAckTimeout(ack_time);
+
+  // The 10th received packet causes an ack to be sent.
+  for (int i = 1; i < 9; ++i) {
+    RecordPacketReceipt(kFirstDecimatedPacket + i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck + i, kFirstDecimatedPacket);
+  }
+  CheckAckTimeout(clock_.ApproximateNow());
+}
+
+TEST_P(QuicReceivedPacketManagerTest,
+       SendDelayedAckDecimationWithLargeReorderingEighthRtt) {
+  if (!received_manager_.decide_when_to_send_acks()) {
+    return;
+  }
+  EXPECT_FALSE(HasPendingAck());
+  QuicReceivedPacketManagerPeer::SetAckMode(&received_manager_,
+                                            ACK_DECIMATION_WITH_REORDERING);
+  QuicReceivedPacketManagerPeer::SetAckDecimationDelay(&received_manager_,
+                                                       0.125);
+
+  // The ack time should be based on min_rtt/8, since it's less than the
+  // default delayed ack time.
+  QuicTime ack_time = clock_.ApproximateNow() + kMinRttMs * 0.125;
+  // Process all the packets in order so there aren't missing packets.
+  uint64_t kFirstDecimatedPacket = 101;
+  for (uint64_t i = 1; i < kFirstDecimatedPacket; ++i) {
+    RecordPacketReceipt(i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, i);
+    if (i % 2 == 0) {
+      // Ack every 2 packets by default.
+      CheckAckTimeout(clock_.ApproximateNow());
+    } else {
+      CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+    }
+  }
+
+  RecordPacketReceipt(kFirstDecimatedPacket, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket);
+  CheckAckTimeout(ack_time);
+
+  RecordPacketReceipt(kFirstDecimatedPacket + 19, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + 19);
+  CheckAckTimeout(ack_time);
+
+  // The 10th received packet causes an ack to be sent.
+  for (int i = 1; i < 9; ++i) {
+    RecordPacketReceipt(kFirstDecimatedPacket + i, clock_.ApproximateNow());
+    MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + i);
+  }
+  CheckAckTimeout(clock_.ApproximateNow());
+
+  // The next packet received in order will cause an immediate ack, because it
+  // fills a hole.
+  RecordPacketReceipt(kFirstDecimatedPacket + 10, clock_.ApproximateNow());
+  MaybeUpdateAckTimeout(kInstigateAck, kFirstDecimatedPacket + 10);
+  CheckAckTimeout(clock_.ApproximateNow());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/net/third_party/quic/core/quic_sent_packet_manager.cc b/net/third_party/quic/core/quic_sent_packet_manager.cc
index 46047f0ae..a20a7490c 100644
--- a/net/third_party/quic/core/quic_sent_packet_manager.cc
+++ b/net/third_party/quic/core/quic_sent_packet_manager.cc
@@ -90,7 +90,6 @@
                     &general_loss_algorithm_)),
       general_loss_algorithm_(loss_type),
       uber_loss_algorithm_(loss_type),
-      n_connection_simulation_(false),
       consecutive_rto_count_(0),
       consecutive_tlp_count_(0),
       consecutive_crypto_retransmission_count_(0),
@@ -189,9 +188,6 @@
   if (config.HasClientSentConnectionOption(k1CON, perspective)) {
     send_algorithm_->SetNumEmulatedConnections(1);
   }
-  if (config.HasClientSentConnectionOption(kNCON, perspective)) {
-    n_connection_simulation_ = true;
-  }
   if (config.HasClientSentConnectionOption(kNTLP, perspective)) {
     max_tail_loss_probes_ = 0;
   }
@@ -262,14 +258,6 @@
   }
 }
 
-void QuicSentPacketManager::SetNumOpenStreams(size_t num_streams) {
-  if (n_connection_simulation_) {
-    // Ensure the number of connections is between 1 and 5.
-    send_algorithm_->SetNumEmulatedConnections(
-        std::min<size_t>(5, std::max<size_t>(1, num_streams)));
-  }
-}
-
 void QuicSentPacketManager::SetHandshakeConfirmed() {
   handshake_confirmed_ = true;
   if (unacked_packets_.use_uber_loss_algorithm()) {
diff --git a/net/third_party/quic/core/quic_sent_packet_manager.h b/net/third_party/quic/core/quic_sent_packet_manager.h
index 2bbc1e4d..8e34977 100644
--- a/net/third_party/quic/core/quic_sent_packet_manager.h
+++ b/net/third_party/quic/core/quic_sent_packet_manager.h
@@ -104,8 +104,6 @@
       const CachedNetworkParameters& cached_network_params,
       bool max_bandwidth_resumption);
 
-  void SetNumOpenStreams(size_t num_streams);
-
   void SetMaxPacingRate(QuicBandwidth max_pacing_rate) {
     pacing_sender_.set_max_pacing_rate(max_pacing_rate);
   }
@@ -511,7 +509,6 @@
   // quic_use_uber_loss_algorithm.
   GeneralLossAlgorithm general_loss_algorithm_;
   UberLossAlgorithm uber_loss_algorithm_;
-  bool n_connection_simulation_;
 
   // Tracks the first RTO packet.  If any packet before that packet gets acked,
   // it indicates the RTO was spurious and should be reversed(F-RTO).
diff --git a/net/third_party/quic/core/quic_sent_packet_manager_test.cc b/net/third_party/quic/core/quic_sent_packet_manager_test.cc
index 9e0fcea88..141f77b 100644
--- a/net/third_party/quic/core/quic_sent_packet_manager_test.cc
+++ b/net/third_party/quic/core/quic_sent_packet_manager_test.cc
@@ -2029,23 +2029,6 @@
   manager_.SetFromConfig(client_config);
 }
 
-TEST_P(QuicSentPacketManagerTest, NegotiateNConnectionFromOptions) {
-  // By default, changing the number of open streams does nothing.
-  manager_.SetNumOpenStreams(5);
-
-  QuicConfig config;
-  QuicTagVector options;
-
-  options.push_back(kNCON);
-  QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
-  EXPECT_CALL(*network_change_visitor_, OnCongestionChange());
-  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
-  manager_.SetFromConfig(config);
-
-  EXPECT_CALL(*send_algorithm_, SetNumEmulatedConnections(5));
-  manager_.SetNumOpenStreams(5);
-}
-
 TEST_P(QuicSentPacketManagerTest, NegotiateNoMinTLPFromOptionsAtServer) {
   QuicConfig config;
   QuicTagVector options;
diff --git a/net/third_party/quic/core/quic_session.cc b/net/third_party/quic/core/quic_session.cc
index 281e28c9..85492e2 100644
--- a/net/third_party/quic/core/quic_session.cc
+++ b/net/third_party/quic/core/quic_session.cc
@@ -717,8 +717,6 @@
   }
 
   stream->OnClose();
-  // Decrease the number of streams being emulated when a new one is opened.
-  connection_->SetNumOpenStreams(dynamic_stream_map_.size());
 
   if (!stream_was_draining && !IsIncomingStream(stream_id) && had_fin_or_rst &&
       connection_->transport_version() != QUIC_VERSION_99) {
@@ -750,8 +748,6 @@
     v99_streamid_manager_.OnStreamClosed(stream_id);
   }
 
-  // Decrease the number of streams being emulated when a new one is opened.
-  connection_->SetNumOpenStreams(dynamic_stream_map_.size());
   OnCanCreateNewOutgoingStream();
 }
 
@@ -1020,9 +1016,6 @@
   if (IsIncomingStream(stream_id)) {
     ++num_dynamic_incoming_streams_;
   }
-
-  // Increase the number of streams being emulated when a new one is opened.
-  connection_->SetNumOpenStreams(dynamic_stream_map_.size());
 }
 
 QuicStreamId QuicSession::GetNextOutgoingBidirectionalStreamId() {
diff --git a/net/third_party/quic/core/quic_session_test.cc b/net/third_party/quic/core/quic_session_test.cc
index 830ab0e..0fac9f4 100644
--- a/net/third_party/quic/core/quic_session_test.cc
+++ b/net/third_party/quic/core/quic_session_test.cc
@@ -439,7 +439,8 @@
         path_frame_buffer2_({8, 9, 10, 11, 12, 13, 14, 15}),
         client_framer_(SupportedVersions(GetParam()),
                        QuicTime::Zero(),
-                       Perspective::IS_CLIENT) {
+                       Perspective::IS_CLIENT,
+                       kQuicDefaultConnectionIdLength) {
     client_framer_.set_visitor(&framer_visitor_);
   }
 
diff --git a/net/third_party/quic/core/quic_time_wait_list_manager.cc b/net/third_party/quic/core/quic_time_wait_list_manager.cc
index 8c5be034..6dddb705 100644
--- a/net/third_party/quic/core/quic_time_wait_list_manager.cc
+++ b/net/third_party/quic/core/quic_time_wait_list_manager.cc
@@ -162,6 +162,17 @@
           if (connection_data->encryption_level == ENCRYPTION_NONE) {
             QUIC_CODE_COUNT(
                 quic_encryption_none_termination_packets_for_short_header);
+            if (GetQuicReloadableFlag(quic_always_reset_short_header_packets)) {
+              QUIC_RELOADABLE_FLAG_COUNT(
+                  quic_always_reset_short_header_packets);
+              // Send stateless reset in response to short header packets,
+              // because ENCRYPTION_NONE termination packets will not be
+              // processed by clients.
+              SendPublicReset(self_address, peer_address, connection_id,
+                              connection_data->ietf_quic,
+                              std::move(packet_context));
+              return;
+            }
           } else if (connection_data->encryption_level == ENCRYPTION_ZERO_RTT) {
             QUIC_CODE_COUNT(quic_zero_rtt_termination_packets_for_short_header);
           }
diff --git a/net/third_party/quic/core/quic_time_wait_list_manager_test.cc b/net/third_party/quic/core/quic_time_wait_list_manager_test.cc
index 7e7190be..b0343a7 100644
--- a/net/third_party/quic/core/quic_time_wait_list_manager_test.cc
+++ b/net/third_party/quic/core/quic_time_wait_list_manager_test.cc
@@ -202,7 +202,7 @@
     const testing::tuple<const char*, int>& packet_buffer) {
   FramerVisitorCapturingPublicReset visitor(expected_connection_id);
   QuicFramer framer(AllSupportedVersions(), QuicTime::Zero(),
-                    Perspective::IS_CLIENT);
+                    Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength);
   framer.set_visitor(&visitor);
   QuicEncryptedPacket encrypted(testing::get<0>(packet_buffer),
                                 testing::get<1>(packet_buffer));
@@ -542,6 +542,42 @@
   }
 }
 
+// Regression test for b/116200989.
+TEST_F(QuicTimeWaitListManagerTest,
+       SendStatelessResetInResponseToShortHeaders) {
+  // This test mimics a scenario where an ENCRYPTION_NONE connection close is
+  // added as termination packet for an IETF connection ID. However, a short
+  // header packet is received later.
+  const size_t kConnectionCloseLength = 100;
+  EXPECT_CALL(visitor_, OnConnectionAddedToTimeWaitList(connection_id_));
+  std::vector<std::unique_ptr<QuicEncryptedPacket>> termination_packets;
+  termination_packets.push_back(
+      std::unique_ptr<QuicEncryptedPacket>(new QuicEncryptedPacket(
+          new char[kConnectionCloseLength], kConnectionCloseLength, true)));
+  // Add an ENCRYPTION_NONE termination packet.
+  time_wait_list_manager_.AddConnectionIdToTimeWait(
+      connection_id_, /*ietf_quic=*/true,
+      QuicTimeWaitListManager::SEND_TERMINATION_PACKETS, ENCRYPTION_NONE,
+      &termination_packets);
+
+  if (GetQuicReloadableFlag(quic_always_reset_short_header_packets)) {
+    // Termination packet is not encrypted, instead, send stateless reset.
+    EXPECT_CALL(writer_,
+                WritePacket(_, _, self_address_.host(), peer_address_, _))
+        .With(Args<0, 1>(PublicResetPacketEq(connection_id_)))
+        .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
+  } else {
+    // An unprocessable connection close is sent to peer.
+    EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength,
+                                     self_address_.host(), peer_address_, _))
+        .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
+  }
+  // Processes IETF short header packet.
+  time_wait_list_manager_.ProcessPacket(
+      self_address_, peer_address_, connection_id_,
+      IETF_QUIC_SHORT_HEADER_PACKET, QuicMakeUnique<QuicPerPacketContext>());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/net/third_party/quic/core/quic_types.h b/net/third_party/quic/core/quic_types.h
index 1b51124..6d35146 100644
--- a/net/third_party/quic/core/quic_types.h
+++ b/net/third_party/quic/core/quic_types.h
@@ -575,6 +575,8 @@
   NUM_PACKET_NUMBER_SPACES,
 };
 
+enum AckMode { TCP_ACKING, ACK_DECIMATION, ACK_DECIMATION_WITH_REORDERING };
+
 }  // namespace quic
 
 #endif  // NET_THIRD_PARTY_QUIC_CORE_QUIC_TYPES_H_
diff --git a/net/third_party/quic/core/quic_utils.cc b/net/third_party/quic/core/quic_utils.cc
index a8f4689..583a1fa0 100644
--- a/net/third_party/quic/core/quic_utils.cc
+++ b/net/third_party/quic/core/quic_utils.cc
@@ -357,10 +357,7 @@
 
 // static
 bool QuicUtils::IsIetfPacketShortHeader(uint8_t first_byte) {
-  if (first_byte & FLAGS_LONG_HEADER) {
-    return false;
-  }
-  return !(first_byte & FLAGS_DEMULTIPLEXING_BIT);
+  return IsIetfPacketHeader(first_byte) && !(first_byte & FLAGS_LONG_HEADER);
 }
 
 // static
diff --git a/net/third_party/quic/core/quic_version_manager.h b/net/third_party/quic/core/quic_version_manager.h
index 5b00f07..e7a6b6f 100644
--- a/net/third_party/quic/core/quic_version_manager.h
+++ b/net/third_party/quic/core/quic_version_manager.h
@@ -13,6 +13,8 @@
 // Used to generate filtered supported versions based on flags.
 class QUIC_EXPORT_PRIVATE QuicVersionManager {
  public:
+  // |supported_versions| should be sorted in the order of preference (typically
+  // highest supported version to the lowest supported version).
   explicit QuicVersionManager(ParsedQuicVersionVector supported_versions);
   virtual ~QuicVersionManager();
 
@@ -20,7 +22,8 @@
   // TODO(nharper): Remove this method once it is unused.
   const QuicTransportVersionVector& GetSupportedTransportVersions();
 
-  // Returns currently supported QUIC versions.
+  // Returns currently supported QUIC versions. This vector has the same order
+  // as the versions passed to the constructor.
   const ParsedQuicVersionVector& GetSupportedVersions();
 
  protected:
diff --git a/net/third_party/quic/core/tls_handshaker.cc b/net/third_party/quic/core/tls_handshaker.cc
index df0c07a7..120c678 100644
--- a/net/third_party/quic/core/tls_handshaker.cc
+++ b/net/third_party/quic/core/tls_handshaker.cc
@@ -8,7 +8,6 @@
 #include "net/third_party/quic/core/tls_client_handshaker.h"
 #include "net/third_party/quic/platform/api/quic_arraysize.h"
 #include "net/third_party/quic/platform/api/quic_bug_tracker.h"
-#include "net/third_party/quic/platform/api/quic_singleton.h"
 #include "third_party/boringssl/src/include/openssl/crypto.h"
 #include "third_party/boringssl/src/include/openssl/ssl.h"
 
@@ -19,7 +18,8 @@
 class SslIndexSingleton {
  public:
   static SslIndexSingleton* GetInstance() {
-    return QuicSingleton<SslIndexSingleton>::get();
+    static SslIndexSingleton* instance = new SslIndexSingleton();
+    return instance;
   }
 
   int HandshakerIndex() const { return ssl_ex_data_index_handshaker_; }
@@ -35,8 +35,6 @@
   SslIndexSingleton(const SslIndexSingleton&) = delete;
   SslIndexSingleton& operator=(const SslIndexSingleton&) = delete;
 
-  friend QuicSingletonFriend<SslIndexSingleton>;
-
   int ssl_ex_data_index_handshaker_;
 };
 
diff --git a/net/third_party/quic/platform/api/quic_default_buffer_allocator.h b/net/third_party/quic/platform/api/quic_default_buffer_allocator.h
new file mode 100644
index 0000000..abd37de
--- /dev/null
+++ b/net/third_party/quic/platform/api/quic_default_buffer_allocator.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_DEFAULT_BUFFER_ALLOCATOR_H_
+#define NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_DEFAULT_BUFFER_ALLOCATOR_H_
+
+#include "net/third_party/quic/platform/impl/quic_default_buffer_allocator_impl.h"
+
+namespace quic {
+
+using QuicDefaultBufferAllocator = QuicStreamBufferAllocatorImpl;
+
+}
+
+#endif  // NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_DEFAULT_BUFFER_ALLOCATOR_H_
diff --git a/net/third_party/quic/platform/api/quic_system_event_loop.h b/net/third_party/quic/platform/api/quic_system_event_loop.h
index 3a3c6f4..24ae4f8b 100644
--- a/net/third_party/quic/platform/api/quic_system_event_loop.h
+++ b/net/third_party/quic/platform/api/quic_system_event_loop.h
@@ -11,4 +11,6 @@
   QuicRunSystemEventLoopIterationImpl();
 }
 
+using QuicSystemEventLoop = QuicSystemEventLoopImpl;
+
 #endif  // NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_SYSTEM_EVENT_LOOP_H_
diff --git a/net/third_party/quic/platform/impl/quic_default_buffer_allocator_impl.h b/net/third_party/quic/platform/impl/quic_default_buffer_allocator_impl.h
new file mode 100644
index 0000000..aa9170af
--- /dev/null
+++ b/net/third_party/quic/platform/impl/quic_default_buffer_allocator_impl.h
@@ -0,0 +1,13 @@
+#ifndef NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_DEFAULT_BUFFER_ALLOCATOR_IMPL_H_
+#define NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_DEFAULT_BUFFER_ALLOCATOR_IMPL_H_
+
+#include "net/third_party/quic/core/quic_simple_buffer_allocator.h"
+
+namespace quic {
+
+using QuicStreamBufferAllocatorImpl = SimpleBufferAllocator;
+
+}  // namespace quic
+
+#endif /* NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_DEFAULT_BUFFER_ALLOCATOR_IMPL_H_ \
+        */
diff --git a/net/third_party/quic/platform/impl/quic_epoll_clock_test.cc b/net/third_party/quic/platform/impl/quic_epoll_clock_test.cc
index 83ab721..22f1b5e 100644
--- a/net/third_party/quic/platform/impl/quic_epoll_clock_test.cc
+++ b/net/third_party/quic/platform/impl/quic_epoll_clock_test.cc
@@ -6,7 +6,7 @@
 
 #include "net/third_party/quic/platform/api/quic_flags.h"
 #include "net/third_party/quic/platform/api/quic_test.h"
-#include "net/third_party/quic/test_tools/fake_epoll_server.h"
+#include "net/tools/epoll_server/fake_epoll_server.h"
 
 namespace quic {
 namespace test {
diff --git a/net/third_party/quic/platform/impl/quic_epoll_test_tools_impl.h b/net/third_party/quic/platform/impl/quic_epoll_test_tools_impl.h
index 2670140..02b49832 100644
--- a/net/third_party/quic/platform/impl/quic_epoll_test_tools_impl.h
+++ b/net/third_party/quic/platform/impl/quic_epoll_test_tools_impl.h
@@ -5,7 +5,7 @@
 #ifndef NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_EPOLL_TEST_TOOLS_IMPL_H_
 #define NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_EPOLL_TEST_TOOLS_IMPL_H_
 
-#include "net/third_party/quic/test_tools/fake_epoll_server.h"
+#include "net/tools/epoll_server/fake_epoll_server.h"
 
 using QuicFakeEpollServerImpl = quic::test::FakeEpollServer;
 
diff --git a/net/third_party/quic/platform/impl/quic_socket_address_impl.cc b/net/third_party/quic/platform/impl/quic_socket_address_impl.cc
index ea710bf..34f0bdbf 100644
--- a/net/third_party/quic/platform/impl/quic_socket_address_impl.cc
+++ b/net/third_party/quic/platform/impl/quic_socket_address_impl.cc
@@ -31,8 +31,11 @@
 }
 
 QuicSocketAddressImpl::QuicSocketAddressImpl(const struct sockaddr& saddr) {
-  QUIC_BUG << "QuicSocketAddressImpl(const struct sockaddr& saddr) is not "
-              "implemented.";
+  if (saddr.sa_family == AF_INET) {
+    CHECK(socket_address_.FromSockAddr(&saddr, sizeof(struct sockaddr_in)));
+  } else if (saddr.sa_family == AF_INET6) {
+    CHECK(socket_address_.FromSockAddr(&saddr, sizeof(struct sockaddr_in6)));
+  }
 }
 
 bool operator==(const QuicSocketAddressImpl& lhs,
diff --git a/net/third_party/quic/platform/impl/quic_socket_utils.h b/net/third_party/quic/platform/impl/quic_socket_utils.h
index 51809b2..2169589 100644
--- a/net/third_party/quic/platform/impl/quic_socket_utils.h
+++ b/net/third_party/quic/platform/impl/quic_socket_utils.h
@@ -19,6 +19,9 @@
 #include "net/third_party/quic/core/quic_bandwidth.h"
 #include "net/third_party/quic/core/quic_types.h"
 
+#define MMSG_MORE 0
+#define MMSG_MORE_NO_ANDROID 0
+
 namespace quic {
 class QuicIpAddress;
 class QuicSocketAddress;
diff --git a/net/third_party/quic/platform/impl/quic_system_event_loop_impl.h b/net/third_party/quic/platform/impl/quic_system_event_loop_impl.h
index 0d35bf9..654a48b8d 100644
--- a/net/third_party/quic/platform/impl/quic_system_event_loop_impl.h
+++ b/net/third_party/quic/platform/impl/quic_system_event_loop_impl.h
@@ -5,10 +5,22 @@
 #ifndef NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_SYSTEM_EVENT_LOOP_IMPL_H_
 #define NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_SYSTEM_EVENT_LOOP_IMPL_H_
 
+#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/task/task_scheduler/task_scheduler.h"
 
 inline void QuicRunSystemEventLoopIterationImpl() {
   base::RunLoop().RunUntilIdle();
 }
 
+class QuicSystemEventLoopImpl {
+ public:
+  QuicSystemEventLoopImpl(std::string context_name) {
+    base::TaskScheduler::CreateAndStartWithDefaultParams(context_name);
+  }
+
+ private:
+  base::MessageLoopForIO message_loop_;
+};
+
 #endif  // NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_SYSTEM_EVENT_LOOP_IMPL_H_
diff --git a/net/third_party/quic/quartc/quartc_dispatcher.cc b/net/third_party/quic/quartc/quartc_dispatcher.cc
index fa7b0ac..587395d5 100644
--- a/net/third_party/quic/quartc/quartc_dispatcher.cc
+++ b/net/third_party/quic/quartc/quartc_dispatcher.cc
@@ -4,6 +4,7 @@
 
 #include "net/third_party/quic/quartc/quartc_dispatcher.h"
 
+#include "net/third_party/quic/core/quic_utils.h"
 #include "net/third_party/quic/core/quic_versions.h"
 #include "net/third_party/quic/platform/api/quic_ptr_util.h"
 #include "net/third_party/quic/quartc/quartc_factory.h"
@@ -25,7 +26,8 @@
                      version_manager,
                      std::move(helper),
                      std::move(session_helper),
-                     std::move(alarm_factory)),
+                     std::move(alarm_factory),
+                     kQuicDefaultConnectionIdLength),
       owned_quic_config_(std::move(config)),
       owned_crypto_config_(std::move(crypto_config)),
       crypto_config_(crypto_config_serialized),
diff --git a/net/third_party/quic/quartc/quartc_endpoint.h b/net/third_party/quic/quartc/quartc_endpoint.h
index 0cedad2..647c326a 100644
--- a/net/third_party/quic/quartc/quartc_endpoint.h
+++ b/net/third_party/quic/quartc/quartc_endpoint.h
@@ -148,6 +148,10 @@
     return crypto_config_.serialized_crypto_config;
   }
 
+  const std::vector<ParsedQuicVersion> GetSupportedQuicVersions() const {
+    return version_manager_->GetSupportedVersions();
+  }
+
  private:
   // Implementation of QuicAlarmFactory used by this endpoint.  Unowned.
   QuicAlarmFactory* alarm_factory_;
diff --git a/net/third_party/quic/quartc/quartc_endpoint_test.cc b/net/third_party/quic/quartc/quartc_endpoint_test.cc
index 4a1aaa18..b557712 100644
--- a/net/third_party/quic/quartc/quartc_endpoint_test.cc
+++ b/net/third_party/quic/quartc/quartc_endpoint_test.cc
@@ -80,7 +80,8 @@
 
 // Tests that the server can negotiate for an older QUIC version if the client
 // attempts to connect using a newer version.
-TEST_F(QuartcEndpointTest, DISABLED_ServerNegotiatesForOldVersion) {
+TEST_F(QuartcEndpointTest,
+       QUIC_TEST_DISABLED_IN_CHROME(ServerNegotiatesForOldVersion)) {
   // Note: for this test, we need support for two versions.  Which two shouldn't
   // matter, but they must be enabled so that the version manager doesn't filter
   // them out.
@@ -124,7 +125,8 @@
 
 // Tests that the server can accept connections from clients that use older
 // QUIC versions.
-TEST_F(QuartcEndpointTest, DISABLED_ServerAcceptsOldVersion) {
+TEST_F(QuartcEndpointTest,
+       QUIC_TEST_DISABLED_IN_CHROME(ServerAcceptsOldVersion)) {
   // Note: for this test, we need support for two versions.  Which two shouldn't
   // matter, but they must be enabled so that the version manager doesn't filter
   // them out.
@@ -168,7 +170,7 @@
 
 // Tests that version negotiation fails when the client and server support
 // completely disjoint sets of versions.
-TEST_F(QuartcEndpointTest, VersionNegotiationWithDisjointVersions) {
+TEST_F(QuartcEndpointTest, DISABLED_VersionNegotiationWithDisjointVersions) {
   // Note: for this test, we need support for two versions.  Which two shouldn't
   // matter, but they must be enabled so that the version manager doesn't filter
   // them out.
diff --git a/net/third_party/quic/quartc/quartc_factory.cc b/net/third_party/quic/quartc/quartc_factory.cc
index e0f148939..010527d4 100644
--- a/net/third_party/quic/quartc/quartc_factory.cc
+++ b/net/third_party/quic/quartc/quartc_factory.cc
@@ -48,13 +48,7 @@
       server_crypto_config);
 }
 
-QuicConfig CreateQuicConfig(const QuartcSessionConfig& quartc_session_config) {
-  // TODO(b/124398962): Figure out a better way to initialize QUIC flags.
-  // Creating a config shouldn't have global side-effects on flags.  However,
-  // this has the advantage of ensuring that flag values stay in sync with the
-  // options requested by configs, so simply splitting the config and flag
-  // settings doesn't seem preferable.
-
+void ConfigureGlobalQuicSettings() {
   // Fixes behavior of StopReading() with level-triggered stream sequencers.
   SetQuicReloadableFlag(quic_stop_reading_when_level_triggered, true);
 
@@ -82,16 +76,43 @@
   SetQuicRestartFlag(quic_no_server_conn_ver_negotiation2, false);
   SetQuicReloadableFlag(quic_no_client_conn_ver_negotiation, false);
 
-  QuicTagVector copt;
-  copt.push_back(kNSTP);
-
   // Enable and request QUIC to include receive timestamps in ACK frames.
   SetQuicReloadableFlag(quic_send_timestamps, true);
-  copt.push_back(kSTMP);
 
   // Enable ACK_DECIMATION_WITH_REORDERING. It requires ack_decimation to be
   // false.
   SetQuicReloadableFlag(quic_enable_ack_decimation, false);
+
+  // Note: flag settings have no effect for Exoblaze builds since
+  // SetQuicReloadableFlag() gets stubbed out.
+  SetQuicReloadableFlag(quic_bbr_less_probe_rtt, true);   // Enable BBR6,7,8.
+  SetQuicReloadableFlag(quic_unified_iw_options, true);   // Enable IWXX opts.
+  SetQuicReloadableFlag(quic_bbr_slower_startup3, true);  // Enable BBQX opts.
+  SetQuicReloadableFlag(quic_bbr_flexible_app_limited, true);  // Enable BBR9.
+}
+
+QuicConfig CreateQuicConfig(const QuartcSessionConfig& quartc_session_config) {
+  // TODO(b/124398962): Figure out a better way to initialize QUIC flags.
+  // Creating a config shouldn't have global side-effects on flags.  However,
+  // this has the advantage of ensuring that flag values stay in sync with the
+  // options requested by configs, so simply splitting the config and flag
+  // settings doesn't seem preferable.
+  ConfigureGlobalQuicSettings();
+
+  // In exoblaze this may return false. DCHECK to avoid problems caused by
+  // incorrect flags configuration.
+  DCHECK(GetQuicReloadableFlag(quic_enable_version_46))
+      << "Your build does not support quic reloadable flags and shouldn't "
+         "place Quartc calls";
+
+  QuicTagVector copt;
+  copt.push_back(kNSTP);
+
+  // Enable and request QUIC to include receive timestamps in ACK frames.
+  copt.push_back(kSTMP);
+
+  // Enable ACK_DECIMATION_WITH_REORDERING. It requires ack_decimation to be
+  // false.
   copt.push_back(kAKD2);
 
   // Use unlimited decimation in order to reduce number of unbundled ACKs.
@@ -100,12 +121,6 @@
   // Enable time-based loss detection.
   copt.push_back(kTIME);
 
-  // Note: flag settings have no effect for Exoblaze builds since
-  // SetQuicReloadableFlag() gets stubbed out.
-  SetQuicReloadableFlag(quic_bbr_less_probe_rtt, true);   // Enable BBR6,7,8.
-  SetQuicReloadableFlag(quic_unified_iw_options, true);   // Enable IWXX opts.
-  SetQuicReloadableFlag(quic_bbr_slower_startup3, true);  // Enable BBQX opts.
-  SetQuicReloadableFlag(quic_bbr_flexible_app_limited, true);  // Enable BBR9.
   copt.push_back(kBBR3);  // Stay in low-gain until in-flight < BDP.
   copt.push_back(kBBR5);  // 40 RTT ack aggregation.
   copt.push_back(kBBR6);  // Use a 0.75 * BDP cwnd during PROBE_RTT.
diff --git a/net/third_party/quic/quartc/quartc_factory.h b/net/third_party/quic/quartc/quartc_factory.h
index 87acf91..bd00286f 100644
--- a/net/third_party/quic/quartc/quartc_factory.h
+++ b/net/third_party/quic/quartc/quartc_factory.h
@@ -85,6 +85,11 @@
   std::unique_ptr<QuicCryptoServerStream::Helper> stream_helper_;
 };
 
+// Configures global settings, such as supported quic versions.
+// Must execute on QUIC thread.
+void ConfigureGlobalQuicSettings();
+
+// Must execute on QUIC thread.
 QuicConfig CreateQuicConfig(const QuartcSessionConfig& quartc_session_config);
 
 std::unique_ptr<QuicConnection> CreateQuicConnection(
diff --git a/net/third_party/quic/test_tools/mock_quic_dispatcher.cc b/net/third_party/quic/test_tools/mock_quic_dispatcher.cc
index 7a6bf87..0db624aa 100644
--- a/net/third_party/quic/test_tools/mock_quic_dispatcher.cc
+++ b/net/third_party/quic/test_tools/mock_quic_dispatcher.cc
@@ -23,7 +23,8 @@
                            std::move(helper),
                            std::move(session_helper),
                            std::move(alarm_factory),
-                           quic_simple_server_backend) {}
+                           quic_simple_server_backend,
+                           kQuicDefaultConnectionIdLength) {}
 
 MockQuicDispatcher::~MockQuicDispatcher() {}
 
diff --git a/net/third_party/quic/test_tools/quic_connection_peer.cc b/net/third_party/quic/test_tools/quic_connection_peer.cc
index 2443523e..fa9c0ce 100644
--- a/net/third_party/quic/test_tools/quic_connection_peer.cc
+++ b/net/third_party/quic/test_tools/quic_connection_peer.cc
@@ -257,21 +257,35 @@
 
 // static
 void QuicConnectionPeer::SetAckMode(QuicConnection* connection,
-                                    QuicConnection::AckMode ack_mode) {
-  connection->ack_mode_ = ack_mode;
+                                    AckMode ack_mode) {
+  if (connection->received_packet_manager_.decide_when_to_send_acks()) {
+    connection->received_packet_manager_.ack_mode_ = ack_mode;
+  } else {
+    connection->ack_mode_ = ack_mode;
+  }
 }
 
 // static
 void QuicConnectionPeer::SetFastAckAfterQuiescence(
     QuicConnection* connection,
     bool fast_ack_after_quiescence) {
-  connection->fast_ack_after_quiescence_ = fast_ack_after_quiescence;
+  if (connection->received_packet_manager_.decide_when_to_send_acks()) {
+    connection->received_packet_manager_.fast_ack_after_quiescence_ =
+        fast_ack_after_quiescence;
+  } else {
+    connection->fast_ack_after_quiescence_ = fast_ack_after_quiescence;
+  }
 }
 
 // static
 void QuicConnectionPeer::SetAckDecimationDelay(QuicConnection* connection,
                                                float ack_decimation_delay) {
-  connection->ack_decimation_delay_ = ack_decimation_delay;
+  if (connection->received_packet_manager_.decide_when_to_send_acks()) {
+    connection->received_packet_manager_.ack_decimation_delay_ =
+        ack_decimation_delay;
+  } else {
+    connection->ack_decimation_delay_ = ack_decimation_delay;
+  }
 }
 
 // static
diff --git a/net/third_party/quic/test_tools/quic_connection_peer.h b/net/third_party/quic/test_tools/quic_connection_peer.h
index 6aede1d..c7b06b5 100644
--- a/net/third_party/quic/test_tools/quic_connection_peer.h
+++ b/net/third_party/quic/test_tools/quic_connection_peer.h
@@ -116,8 +116,7 @@
                                          QuicPacketCount packets);
   static void SetNextMtuProbeAt(QuicConnection* connection,
                                 QuicPacketNumber number);
-  static void SetAckMode(QuicConnection* connection,
-                         QuicConnection::AckMode ack_mode);
+  static void SetAckMode(QuicConnection* connection, AckMode ack_mode);
   static void SetFastAckAfterQuiescence(QuicConnection* connection,
                                         bool fast_ack_after_quiescence);
   static void SetAckDecimationDelay(QuicConnection* connection,
diff --git a/net/third_party/quic/test_tools/quic_framer_peer.cc b/net/third_party/quic/test_tools/quic_framer_peer.cc
index e6d7798..817e3587 100644
--- a/net/third_party/quic/test_tools/quic_framer_peer.cc
+++ b/net/third_party/quic/test_tools/quic_framer_peer.cc
@@ -347,5 +347,13 @@
       QuicPacketNumber(packet_number);
 }
 
+// static
+void QuicFramerPeer::SetExpectedConnectionIDLength(
+    QuicFramer* framer,
+    uint8_t expected_connection_id_length) {
+  *const_cast<uint8_t*>(&framer->expected_connection_id_length_) =
+      expected_connection_id_length;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/net/third_party/quic/test_tools/quic_framer_peer.h b/net/third_party/quic/test_tools/quic_framer_peer.h
index 24934cdc..8b909157 100644
--- a/net/third_party/quic/test_tools/quic_framer_peer.h
+++ b/net/third_party/quic/test_tools/quic_framer_peer.h
@@ -165,6 +165,9 @@
                                    QuicPacketNumberLength packet_number_length);
   static void SetFirstSendingPacketNumber(QuicFramer* framer,
                                           uint64_t packet_number);
+  static void SetExpectedConnectionIDLength(
+      QuicFramer* framer,
+      uint8_t expected_connection_id_length);
 };
 
 }  // namespace test
diff --git a/net/third_party/quic/test_tools/quic_test_server.cc b/net/third_party/quic/test_tools/quic_test_server.cc
index e75fd0b..ce30d9a 100644
--- a/net/third_party/quic/test_tools/quic_test_server.cc
+++ b/net/third_party/quic/test_tools/quic_test_server.cc
@@ -76,14 +76,16 @@
       std::unique_ptr<QuicConnectionHelperInterface> helper,
       std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
       std::unique_ptr<QuicAlarmFactory> alarm_factory,
-      QuicSimpleServerBackend* quic_simple_server_backend)
+      QuicSimpleServerBackend* quic_simple_server_backend,
+      uint8_t expected_connection_id_length)
       : QuicSimpleDispatcher(config,
                              crypto_config,
                              version_manager,
                              std::move(helper),
                              std::move(session_helper),
                              std::move(alarm_factory),
-                             quic_simple_server_backend),
+                             quic_simple_server_backend,
+                             expected_connection_id_length),
         session_factory_(nullptr),
         stream_factory_(nullptr),
         crypto_stream_factory_(nullptr) {}
@@ -157,11 +159,24 @@
     const QuicConfig& config,
     const ParsedQuicVersionVector& supported_versions,
     QuicSimpleServerBackend* quic_simple_server_backend)
+    : QuicTestServer(std::move(proof_source),
+                     config,
+                     supported_versions,
+                     quic_simple_server_backend,
+                     kQuicDefaultConnectionIdLength) {}
+
+QuicTestServer::QuicTestServer(
+    std::unique_ptr<ProofSource> proof_source,
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions,
+    QuicSimpleServerBackend* quic_simple_server_backend,
+    uint8_t expected_connection_id_length)
     : QuicServer(std::move(proof_source),
                  config,
                  QuicCryptoServerConfig::ConfigOptions(),
                  supported_versions,
-                 quic_simple_server_backend) {}
+                 quic_simple_server_backend,
+                 expected_connection_id_length) {}
 
 QuicDispatcher* QuicTestServer::CreateQuicDispatcher() {
   return new QuicTestDispatcher(
@@ -170,7 +185,8 @@
                                                 QuicAllocator::BUFFER_POOL),
       std::unique_ptr<QuicCryptoServerStream::Helper>(
           new QuicSimpleCryptoServerStreamHelper(QuicRandom::GetInstance())),
-      QuicMakeUnique<QuicEpollAlarmFactory>(epoll_server()), server_backend());
+      QuicMakeUnique<QuicEpollAlarmFactory>(epoll_server()), server_backend(),
+      expected_connection_id_length());
 }
 
 void QuicTestServer::SetSessionFactory(SessionFactory* factory) {
diff --git a/net/third_party/quic/test_tools/quic_test_server.h b/net/third_party/quic/test_tools/quic_test_server.h
index 1282ef9..3faf7ed 100644
--- a/net/third_party/quic/test_tools/quic_test_server.h
+++ b/net/third_party/quic/test_tools/quic_test_server.h
@@ -65,6 +65,11 @@
                  const QuicConfig& config,
                  const ParsedQuicVersionVector& supported_versions,
                  QuicSimpleServerBackend* quic_simple_server_backend);
+  QuicTestServer(std::unique_ptr<ProofSource> proof_source,
+                 const QuicConfig& config,
+                 const ParsedQuicVersionVector& supported_versions,
+                 QuicSimpleServerBackend* quic_simple_server_backend,
+                 uint8_t expected_connection_id_length);
 
   // Create a custom dispatcher which creates custom sessions.
   QuicDispatcher* CreateQuicDispatcher() override;
diff --git a/net/third_party/quic/test_tools/quic_test_utils.cc b/net/third_party/quic/test_tools/quic_test_utils.cc
index 0c7151e5..12aadd8 100644
--- a/net/third_party/quic/test_tools/quic_test_utils.cc
+++ b/net/third_party/quic/test_tools/quic_test_utils.cc
@@ -855,7 +855,8 @@
   }
 
   QuicFrames frames;
-  QuicFramer framer(*versions, QuicTime::Zero(), perspective);
+  QuicFramer framer(*versions, QuicTime::Zero(), perspective,
+                    kQuicDefaultConnectionIdLength);
   if ((*versions)[0].transport_version < QUIC_VERSION_47) {
     QuicFrame frame(QuicStreamFrame(
         QuicUtils::GetCryptoStreamId((*versions)[0].transport_version), false,
@@ -913,7 +914,8 @@
   QuicFrames frames;
   frames.push_back(frame);
   QuicFramer framer(versions != nullptr ? *versions : AllSupportedVersions(),
-                    QuicTime::Zero(), perspective);
+                    QuicTime::Zero(), perspective,
+                    kQuicDefaultConnectionIdLength);
 
   std::unique_ptr<QuicPacket> packet(
       BuildUnsizedDataPacket(&framer, header, frames));
diff --git a/net/third_party/quic/test_tools/simple_quic_framer.cc b/net/third_party/quic/test_tools/simple_quic_framer.cc
index 2718eeda..fa11fa94 100644
--- a/net/third_party/quic/test_tools/simple_quic_framer.cc
+++ b/net/third_party/quic/test_tools/simple_quic_framer.cc
@@ -292,16 +292,23 @@
 SimpleQuicFramer::SimpleQuicFramer()
     : framer_(AllSupportedVersions(),
               QuicTime::Zero(),
-              Perspective::IS_SERVER) {}
+              Perspective::IS_SERVER,
+              kQuicDefaultConnectionIdLength) {}
 
 SimpleQuicFramer::SimpleQuicFramer(
     const ParsedQuicVersionVector& supported_versions)
-    : framer_(supported_versions, QuicTime::Zero(), Perspective::IS_SERVER) {}
+    : framer_(supported_versions,
+              QuicTime::Zero(),
+              Perspective::IS_SERVER,
+              kQuicDefaultConnectionIdLength) {}
 
 SimpleQuicFramer::SimpleQuicFramer(
     const ParsedQuicVersionVector& supported_versions,
     Perspective perspective)
-    : framer_(supported_versions, QuicTime::Zero(), perspective) {}
+    : framer_(supported_versions,
+              QuicTime::Zero(),
+              perspective,
+              kQuicDefaultConnectionIdLength) {}
 
 SimpleQuicFramer::~SimpleQuicFramer() {}
 
diff --git a/net/third_party/quic/test_tools/simulator/actor.cc b/net/third_party/quic/test_tools/simulator/actor.cc
index 98aaca6..a217531b 100644
--- a/net/third_party/quic/test_tools/simulator/actor.cc
+++ b/net/third_party/quic/test_tools/simulator/actor.cc
@@ -12,10 +12,12 @@
     : simulator_(simulator),
       clock_(simulator->GetClock()),
       name_(std::move(name)) {
-  simulator->AddActor(this);
+  simulator_->AddActor(this);
 }
 
-Actor::~Actor() {}
+Actor::~Actor() {
+  simulator_->RemoveActor(this);
+}
 
 void Actor::Schedule(QuicTime next_tick) {
   simulator_->Schedule(this, next_tick);
diff --git a/net/third_party/quic/test_tools/simulator/simulator.cc b/net/third_party/quic/test_tools/simulator/simulator.cc
index 5a5154c..74c56b1d 100644
--- a/net/third_party/quic/test_tools/simulator/simulator.cc
+++ b/net/third_party/quic/test_tools/simulator/simulator.cc
@@ -19,7 +19,11 @@
       alarm_factory_.CreateAlarm(new RunForDelegate(&run_for_should_stop_)));
 }
 
-Simulator::~Simulator() {}
+Simulator::~Simulator() {
+  // Ensure that Actor under run_for_alarm_ is removed before Simulator data
+  // structures are destructed.
+  run_for_alarm_.reset();
+}
 
 Simulator::Clock::Clock() : now_(kStartTime) {}
 
@@ -46,6 +50,21 @@
   DCHECK(emplace_names_result.second);
 }
 
+void Simulator::RemoveActor(Actor* actor) {
+  auto scheduled_time_it = scheduled_times_.find(actor);
+  auto actor_names_it = actor_names_.find(actor->name());
+  DCHECK(scheduled_time_it != scheduled_times_.end());
+  DCHECK(actor_names_it != actor_names_.end());
+
+  QuicTime scheduled_time = scheduled_time_it->second;
+  if (scheduled_time != QuicTime::Infinite()) {
+    Unschedule(actor);
+  }
+
+  scheduled_times_.erase(scheduled_time_it);
+  actor_names_.erase(actor_names_it);
+}
+
 void Simulator::Schedule(Actor* actor, QuicTime new_time) {
   auto scheduled_time_it = scheduled_times_.find(actor);
   DCHECK(scheduled_time_it != scheduled_times_.end());
diff --git a/net/third_party/quic/test_tools/simulator/simulator.h b/net/third_party/quic/test_tools/simulator/simulator.h
index 74ed03a..3ea2b438 100644
--- a/net/third_party/quic/test_tools/simulator/simulator.h
+++ b/net/third_party/quic/test_tools/simulator/simulator.h
@@ -26,10 +26,6 @@
   Simulator& operator=(const Simulator&) = delete;
   ~Simulator() override;
 
-  // Register an actor with the simulator.  Returns a handle which the actor can
-  // use to schedule and unschedule itself.
-  void AddActor(Actor* actor);
-
   // Schedule the specified actor.  This method will ensure that |actor| is
   // called at |new_time| at latest.  If Schedule() is called multiple times
   // before the Actor is called, Act() is called exactly once, at the earliest
@@ -70,6 +66,8 @@
   void RunFor(QuicTime::Delta time_span);
 
  private:
+  friend class Actor;
+
   class Clock : public QuicClock {
    public:
     // Do not start at zero as certain code can treat zero as an invalid
@@ -97,6 +95,12 @@
     bool* run_for_should_stop_;
   };
 
+  // Register an actor with the simulator. Invoked by Actor constructor.
+  void AddActor(Actor* actor);
+
+  // Unregister an actor with the simulator. Invoked by Actor destructor.
+  void RemoveActor(Actor* actor);
+
   // Finds the next scheduled actor, advances time to the schedule time and
   // notifies the actor.
   void HandleNextScheduledActor();
diff --git a/net/third_party/quic/test_tools/simulator/simulator_test.cc b/net/third_party/quic/test_tools/simulator/simulator_test.cc
index fdda91cb..92f329d 100644
--- a/net/third_party/quic/test_tools/simulator/simulator_test.cc
+++ b/net/third_party/quic/test_tools/simulator/simulator_test.cc
@@ -48,19 +48,22 @@
 
 class SimulatorTest : public QuicTest {};
 
-// Test that the basic event handling works.
+// Test that the basic event handling works, and that Actors can be created and
+// destroyed mid-simulation.
 TEST_F(SimulatorTest, Counters) {
   Simulator simulator;
-  Counter fast_counter(&simulator, "fast_counter",
-                       QuicTime::Delta::FromSeconds(3));
-  Counter slow_counter(&simulator, "slow_counter",
-                       QuicTime::Delta::FromSeconds(10));
+  for (int i = 0; i < 2; ++i) {
+    Counter fast_counter(&simulator, "fast_counter",
+                         QuicTime::Delta::FromSeconds(3));
+    Counter slow_counter(&simulator, "slow_counter",
+                         QuicTime::Delta::FromSeconds(10));
 
-  simulator.RunUntil(
-      [&slow_counter]() { return slow_counter.get_value() >= 10; });
+    simulator.RunUntil(
+        [&slow_counter]() { return slow_counter.get_value() >= 10; });
 
-  EXPECT_EQ(10, slow_counter.get_value());
-  EXPECT_EQ(10 * 10 / 3, fast_counter.get_value());
+    EXPECT_EQ(10, slow_counter.get_value());
+    EXPECT_EQ(10 * 10 / 3, fast_counter.get_value());
+  }
 }
 
 // A port which counts the number of packets received on it, both total and
diff --git a/net/third_party/quic/tools/quic_client_bin.cc b/net/third_party/quic/tools/quic_client_bin.cc
index 86bc7ad5..1d14fda 100644
--- a/net/third_party/quic/tools/quic_client_bin.cc
+++ b/net/third_party/quic/tools/quic_client_bin.cc
@@ -8,100 +8,143 @@
 //
 // Some usage examples:
 //
-//   TODO(rtenneti): make --host optional by getting IP Address of URL's host.
-//
-//   Get IP address of the www.google.com
-//   IP=`dig www.google.com +short | head -1`
-//
 // Standard request/response:
-//   quic_client http://www.google.com  --host=${IP}
-//   quic_client http://www.google.com --quiet  --host=${IP}
-//   quic_client https://www.google.com --port=443  --host=${IP}
+//   quic_client www.google.com
+//   quic_client www.google.com --quiet
+//   quic_client www.google.com --port=443
 //
 // Use a specific version:
-//   quic_client http://www.google.com --quic_version=23  --host=${IP}
+//   quic_client www.google.com --quic_version=23
 //
 // Send a POST instead of a GET:
-//   quic_client http://www.google.com --body="this is a POST body" --host=${IP}
+//   quic_client www.google.com --body="this is a POST body"
 //
 // Append additional headers to the request:
-//   quic_client http://www.google.com  --host=${IP}
-//               --headers="Header-A: 1234; Header-B: 5678"
+//   quic_client www.google.com --headers="Header-A: 1234; Header-B: 5678"
 //
 // Connect to a host different to the URL being requested:
-//   Get IP address of the www.google.com
+//   quic_client mail.google.com --host=www.google.com
+//
+// Connect to a specific IP:
 //   IP=`dig www.google.com +short | head -1`
-//   quic_client mail.google.com --host=${IP}
+//   quic_client www.google.com --host=${IP}
 //
 // Send repeated requests and change ephemeral port between requests
 //   quic_client www.google.com --num_requests=10
 //
 // Try to connect to a host which does not speak QUIC:
-//   Get IP address of the www.example.com
-//   IP=`dig www.example.com +short | head -1`
-//   quic_client http://www.example.com --host=${IP}
+//   quic_client www.example.com
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
 
 #include <iostream>
+#include <memory>
 #include <vector>
 
-#include "base/at_exit.h"
-#include "base/command_line.h"
-#include "base/message_loop/message_loop.h"
-#include "base/task/task_scheduler/task_scheduler.h"
-#include "net/base/net_errors.h"
-#include "net/base/privacy_mode.h"
 #include "net/third_party/quic/core/quic_packets.h"
 #include "net/third_party/quic/core/quic_server_id.h"
+#include "net/third_party/quic/core/quic_utils.h"
 #include "net/third_party/quic/platform/api/quic_default_proof_providers.h"
-#include "net/third_party/quic/platform/api/quic_flags.h"
 #include "net/third_party/quic/platform/api/quic_ptr_util.h"
 #include "net/third_party/quic/platform/api/quic_socket_address.h"
 #include "net/third_party/quic/platform/api/quic_str_cat.h"
 #include "net/third_party/quic/platform/api/quic_string.h"
 #include "net/third_party/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quic/platform/api/quic_system_event_loop.h"
 #include "net/third_party/quic/platform/api/quic_text_utils.h"
 #include "net/third_party/quic/tools/quic_client.h"
 #include "net/third_party/quic/tools/quic_url.h"
-#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
-#include "net/tools/epoll_server/epoll_server.h"
-#include "net/tools/quic/synchronous_host_resolver.h"
 
-using quic::ProofVerifier;
+namespace {
+
+using quic::QuicSocketAddress;
 using quic::QuicString;
 using quic::QuicStringPiece;
 using quic::QuicTextUtils;
 using quic::QuicUrl;
-using spdy::SpdyHeaderBlock;
-using std::cerr;
-using std::cout;
-using std::endl;
-using std::string;
+
+class FakeProofVerifier : public quic::ProofVerifier {
+ public:
+  ~FakeProofVerifier() override {}
+  quic::QuicAsyncStatus VerifyProof(
+      const std::string& /*hostname*/,
+      const uint16_t /*port*/,
+      const std::string& /*server_config*/,
+      quic::QuicTransportVersion /*quic_version*/,
+      quic::QuicStringPiece /*chlo_hash*/,
+      const std::vector<std::string>& /*certs*/,
+      const std::string& /*cert_sct*/,
+      const std::string& /*signature*/,
+      const quic::ProofVerifyContext* /*context*/,
+      std::string* /*error_details*/,
+      std::unique_ptr<quic::ProofVerifyDetails>* /*details*/,
+      std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override {
+    return quic::QUIC_SUCCESS;
+  }
+  quic::QuicAsyncStatus VerifyCertChain(
+      const std::string& /*hostname*/,
+      const std::vector<std::string>& /*certs*/,
+      const quic::ProofVerifyContext* /*context*/,
+      std::string* /*error_details*/,
+      std::unique_ptr<quic::ProofVerifyDetails>* /*details*/,
+      std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override {
+    return quic::QUIC_SUCCESS;
+  }
+  std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override {
+    return nullptr;
+  }
+};
+
+QuicSocketAddress LookupAddress(QuicString host, QuicString port) {
+  addrinfo hint;
+  memset(&hint, 0, sizeof(hint));
+  hint.ai_protocol = IPPROTO_UDP;
+
+  addrinfo* info_list = nullptr;
+  int result = getaddrinfo(host.c_str(), port.c_str(), &hint, &info_list);
+  if (result != 0) {
+    QUIC_LOG(ERROR) << "Failed to look up " << host << ": "
+                    << gai_strerror(result);
+    return QuicSocketAddress();
+  }
+
+  CHECK(info_list != nullptr);
+  std::unique_ptr<addrinfo, void (*)(addrinfo*)> info_list_owned(info_list,
+                                                                 freeaddrinfo);
+  return QuicSocketAddress(*info_list->ai_addr);
+}
+
+}  // namespace
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(
-    string,
+    std::string,
     host,
     "",
-    "The IP or hostname the quic client will connect to.");
+    "The IP or hostname to connect to. If not provided, the host "
+    "will be derived from the provided URL.");
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
 
-DEFINE_QUIC_COMMAND_LINE_FLAG(string,
+DEFINE_QUIC_COMMAND_LINE_FLAG(std::string,
                               body,
                               "",
                               "If set, send a POST with this body.");
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(
-    string,
+    std::string,
     body_hex,
     "",
-    "If set, contents are converted from hex to ascii, before sending as body "
-    "of a POST. e.g. --body_hex=\"68656c6c6f\"");
+    "If set, contents are converted from hex to ascii, before "
+    "sending as body of a POST. e.g. --body_hex=\"68656c6c6f\"");
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(
-    string,
+    std::string,
     headers,
     "",
-    "A semicolon separated list of key:value pairs to add to request headers.");
+    "A semicolon separated list of key:value pairs to "
+    "add to request headers.");
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
                               quiet,
@@ -112,23 +155,23 @@
     int32_t,
     quic_version,
     -1,
-    "QUIC version to speak, e.g. 21. If not set, then all available versions "
-    "are offered in the handshake.");
+    "QUIC version to speak, e.g. 21. If not set, then all available "
+    "versions are offered in the handshake.");
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(
     bool,
     version_mismatch_ok,
     false,
-    "If true, a version mismatch in the handshake is not considered a failure. "
-    "Useful for probing a server to determine if it speaks any version of "
-    "QUIC.");
+    "If true, a version mismatch in the handshake is not considered a "
+    "failure. Useful for probing a server to determine if it speaks "
+    "any version of QUIC.");
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(
     bool,
     redirect_is_success,
     true,
-    "If true, an HTTP response code of 3xx is considered to be a successful "
-    "response, otherwise a failure.");
+    "If true, an HTTP response code of 3xx is considered to be a "
+    "successful response, otherwise a failure.");
 
 DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t,
                               initial_mtu,
@@ -141,84 +184,31 @@
     1,
     "How many sequential requests to make on a single connection.");
 
+DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
+                              disable_certificate_verification,
+                              false,
+                              "If true, don't verify the server certificate.");
+
 DEFINE_QUIC_COMMAND_LINE_FLAG(
     bool,
     drop_response_body,
     false,
     "If true, drop response body immediately after it is received.");
 
-DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
-                              disable_certificate_verification,
-                              false,
-                              "If true, do not verify certificates.");
-
-class FakeProofVerifier : public ProofVerifier {
- public:
-  quic::QuicAsyncStatus VerifyProof(
-      const string& /*hostname*/,
-      const uint16_t /*port*/,
-      const string& /*server_config*/,
-      quic::QuicTransportVersion /*quic_version*/,
-      QuicStringPiece /*chlo_hash*/,
-      const std::vector<string>& /*certs*/,
-      const string& /*cert_sct*/,
-      const string& /*signature*/,
-      const quic::ProofVerifyContext* /*context*/,
-      string* /*error_details*/,
-      std::unique_ptr<quic::ProofVerifyDetails>* /*details*/,
-      std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override {
-    return quic::QUIC_SUCCESS;
-  }
-
-  quic::QuicAsyncStatus VerifyCertChain(
-      const std::string& /*hostname*/,
-      const std::vector<std::string>& /*certs*/,
-      const quic::ProofVerifyContext* /*verify_context*/,
-      std::string* /*error_details*/,
-      std::unique_ptr<quic::ProofVerifyDetails>* /*verify_details*/,
-      std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override {
-    return quic::QUIC_SUCCESS;
-  }
-  std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override {
-    return nullptr;
-  }
-};
-
 int main(int argc, char* argv[]) {
-  base::TaskScheduler::CreateAndStartWithDefaultParams("quic_client");
-  const char* usage =
-      "Usage: epoll_quic_client [options] <url>\n"
-      "\n"
-      "<url> with scheme must be provided (e.g. http://www.google.com)\n";
+  QuicSystemEventLoop event_loop("quic_client");
+  const char* usage = "Usage: quic_client [options] <url>";
+
+  // All non-flag arguments should be interpreted as URLs to fetch.
   std::vector<QuicString> urls =
       quic::QuicParseCommandLineFlags(usage, argc, argv);
-  if (urls.empty()) {
+  if (urls.size() != 1) {
     quic::QuicPrintCommandLineFlagHelp(usage);
     exit(0);
   }
 
-  logging::LoggingSettings settings;
-  settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
-  CHECK(logging::InitLogging(settings));
-
-  VLOG(1) << "server host: " << GetQuicFlag(FLAGS_host)
-          << " port: " << GetQuicFlag(FLAGS_port)
-          << " body: " << GetQuicFlag(FLAGS_body)
-          << " headers: " << GetQuicFlag(FLAGS_headers)
-          << " quiet: " << GetQuicFlag(FLAGS_quiet)
-          << " quic-version: " << GetQuicFlag(FLAGS_quic_version)
-          << " version_mismatch_ok: " << GetQuicFlag(FLAGS_version_mismatch_ok)
-          << " redirect_is_success: " << GetQuicFlag(FLAGS_redirect_is_success)
-          << " initial_mtu: " << GetQuicFlag(FLAGS_initial_mtu);
-
-  base::AtExitManager exit_manager;
-  base::MessageLoopForIO message_loop;
-
-  // Determine IP address to connect to from supplied hostname.
-  quic::QuicIpAddress ip_addr;
-
   QuicUrl url(urls[0], "https");
-  string host = GetQuicFlag(FLAGS_host);
+  std::string host = GetQuicFlag(FLAGS_host);
   if (host.empty()) {
     host = url.host();
   }
@@ -226,20 +216,14 @@
   if (port == 0) {
     port = url.port();
   }
-  if (!ip_addr.FromString(host)) {
-    net::AddressList addresses;
-    int rv = net::SynchronousHostResolver::Resolve(host, &addresses);
-    if (rv != net::OK) {
-      LOG(ERROR) << "Unable to resolve '" << host
-                 << "' : " << net::ErrorToShortString(rv);
-      return 1;
-    }
-    ip_addr =
-        quic::QuicIpAddress(quic::QuicIpAddressImpl(addresses[0].address()));
-  }
 
-  string host_port = quic::QuicStrCat(ip_addr.ToString(), ":", port);
-  VLOG(1) << "Resolved " << host << " to " << host_port << endl;
+  // Determine IP address to connect to from supplied hostname.
+  QuicSocketAddress addr = LookupAddress(host, quic::QuicStrCat(port));
+  if (!addr.IsInitialized()) {
+    return 1;
+  }
+  std::cerr << "Resolved " << url.ToString() << " to " << addr.ToString()
+            << std::endl;
 
   // Build the client, and try to connect.
   quic::QuicEpollServer epoll_server;
@@ -251,42 +235,41 @@
         quic::PROTOCOL_QUIC_CRYPTO, static_cast<quic::QuicTransportVersion>(
                                         GetQuicFlag(FLAGS_quic_version))));
   }
-  const int32_t num_requests = GetQuicFlag(FLAGS_num_requests);
-  // For secure QUIC we need to verify the cert chain.
-  std::unique_ptr<ProofVerifier> proof_verifier;
+  const int32_t num_requests(GetQuicFlag(FLAGS_num_requests));
+  std::unique_ptr<quic::ProofVerifier> proof_verifier;
   if (GetQuicFlag(FLAGS_disable_certificate_verification)) {
     proof_verifier = quic::QuicMakeUnique<FakeProofVerifier>();
   } else {
     proof_verifier = quic::CreateDefaultProofVerifier();
   }
-  quic::QuicClient client(quic::QuicSocketAddress(ip_addr, port), server_id,
-                          versions, &epoll_server, std::move(proof_verifier));
-  client.set_initial_max_packet_length(GetQuicFlag(FLAGS_initial_mtu) != 0
-                                           ? GetQuicFlag(FLAGS_initial_mtu)
-                                           : quic::kDefaultMaxPacketSize);
+  quic::QuicClient client(addr, server_id, versions, &epoll_server,
+                          std::move(proof_verifier));
+  int32_t initial_mtu = GetQuicFlag(FLAGS_initial_mtu);
+  client.set_initial_max_packet_length(
+      initial_mtu != 0 ? initial_mtu : quic::kDefaultMaxPacketSize);
   client.set_drop_response_body(GetQuicFlag(FLAGS_drop_response_body));
   if (!client.Initialize()) {
-    cerr << "Failed to initialize client." << endl;
+    std::cerr << "Failed to initialize client." << std::endl;
     return 1;
   }
   if (!client.Connect()) {
     quic::QuicErrorCode error = client.session()->error();
     if (error == quic::QUIC_INVALID_VERSION) {
-      cout << "Server talks QUIC, but none of the versions supported by "
-           << "this client: " << ParsedQuicVersionVectorToString(versions)
-           << endl;
+      std::cerr << "Server talks QUIC, but none of the versions supported by "
+                << "this client: " << ParsedQuicVersionVectorToString(versions)
+                << std::endl;
       // 0: No error.
       // 20: Failed to connect due to QUIC_INVALID_VERSION.
       return GetQuicFlag(FLAGS_version_mismatch_ok) ? 0 : 20;
     }
-    cerr << "Failed to connect to " << host_port
-         << ". Error: " << quic::QuicErrorCodeToString(error) << endl;
+    std::cerr << "Failed to connect to " << addr.ToString()
+              << ". Error: " << quic::QuicErrorCodeToString(error) << std::endl;
     return 1;
   }
-  cout << "Connected to " << host_port << endl;
+  std::cerr << "Connected to " << addr.ToString() << std::endl;
 
   // Construct the string body from flags, if provided.
-  string body = GetQuicFlag(FLAGS_body);
+  std::string body = GetQuicFlag(FLAGS_body);
   if (!GetQuicFlag(FLAGS_body_hex).empty()) {
     DCHECK(GetQuicFlag(FLAGS_body).empty())
         << "Only set one of --body and --body_hex.";
@@ -322,62 +305,67 @@
 
     // Print request and response details.
     if (!GetQuicFlag(FLAGS_quiet)) {
-      cout << "Request:" << endl;
-      cout << "headers:" << header_block.DebugString();
+      std::cout << "Request:" << std::endl;
+      std::cout << "headers:" << header_block.DebugString();
       if (!GetQuicFlag(FLAGS_body_hex).empty()) {
         // Print the user provided hex, rather than binary body.
-        cout << "body:\n"
-             << QuicTextUtils::HexDump(
-                    QuicTextUtils::HexDecode(GetQuicFlag(FLAGS_body_hex)))
-             << endl;
+        std::cout << "body:\n"
+                  << QuicTextUtils::HexDump(
+                         QuicTextUtils::HexDecode(GetQuicFlag(FLAGS_body_hex)))
+                  << std::endl;
       } else {
-        cout << "body: " << body << endl;
+        std::cout << "body: " << body << std::endl;
       }
-      cout << endl;
+      std::cout << std::endl;
 
       if (!client.preliminary_response_headers().empty()) {
-        cout << "Preliminary response headers: "
-             << client.preliminary_response_headers() << endl;
-        cout << endl;
+        std::cout << "Preliminary response headers: "
+                  << client.preliminary_response_headers() << std::endl;
+        std::cout << std::endl;
       }
 
-      cout << "Response:" << endl;
-      cout << "headers: " << client.latest_response_headers() << endl;
-      string response_body = client.latest_response_body();
+      std::cout << "Response:" << std::endl;
+      std::cout << "headers: " << client.latest_response_headers() << std::endl;
+      std::string response_body = client.latest_response_body();
       if (!GetQuicFlag(FLAGS_body_hex).empty()) {
         // Assume response is binary data.
-        cout << "body:\n" << QuicTextUtils::HexDump(response_body) << endl;
+        std::cout << "body:\n"
+                  << QuicTextUtils::HexDump(response_body) << std::endl;
       } else {
-        cout << "body: " << response_body << endl;
+        std::cout << "body: " << response_body << std::endl;
       }
-      cout << "trailers: " << client.latest_response_trailers() << endl;
+      std::cout << "trailers: " << client.latest_response_trailers()
+                << std::endl;
     }
 
     if (!client.connected()) {
-      cerr << "Request caused connection failure. Error: "
-           << quic::QuicErrorCodeToString(client.session()->error()) << endl;
+      std::cerr << "Request caused connection failure. Error: "
+                << quic::QuicErrorCodeToString(client.session()->error())
+                << std::endl;
       return 1;
     }
 
     size_t response_code = client.latest_response_code();
     if (response_code >= 200 && response_code < 300) {
-      cout << "Request succeeded (" << response_code << ")." << endl;
+      std::cerr << "Request succeeded (" << response_code << ")." << std::endl;
     } else if (response_code >= 300 && response_code < 400) {
       if (GetQuicFlag(FLAGS_redirect_is_success)) {
-        cout << "Request succeeded (redirect " << response_code << ")." << endl;
+        std::cerr << "Request succeeded (redirect " << response_code << ")."
+                  << std::endl;
       } else {
-        cout << "Request failed (redirect " << response_code << ")." << endl;
+        std::cerr << "Request failed (redirect " << response_code << ")."
+                  << std::endl;
         return 1;
       }
     } else {
-      cerr << "Request failed (" << response_code << ")." << endl;
+      std::cerr << "Request failed (" << response_code << ")." << std::endl;
       return 1;
     }
 
     // Change the ephemeral port if there are more requests to do.
     if (i + 1 < num_requests) {
       if (!client.ChangeEphemeralPort()) {
-        cout << "Failed to change ephemeral port." << endl;
+        std::cerr << "Failed to change ephemeral port." << std::endl;
         return 1;
       }
     }
diff --git a/net/third_party/quic/tools/quic_packet_printer_bin.cc b/net/third_party/quic/tools/quic_packet_printer_bin.cc
index ef3cde47..b6ba7a9 100644
--- a/net/third_party/quic/tools/quic_packet_printer_bin.cc
+++ b/net/third_party/quic/tools/quic_packet_printer_bin.cc
@@ -12,48 +12,31 @@
 // Usage: quic_packet_printer server|client <hex dump of packet>
 //
 // Example input:
-// quic_packet_printer server 0c6b810308320f24c004a939a38a2e3fd6ca589917f200400
-// 201b80b0100501c0700060003023d0000001c00556e656e637279707465642073747265616d2
-// 064617461207365656e
+// quic_packet_printer server 0c6b810308320f24c004a939a38a2e3fd6ca589917f200400201b80b0100501c0700060003023d0000001c00556e656e637279707465642073747265616d2064617461207365656e
 //
 // Example output:
 // OnPacket
 // OnUnauthenticatedPublicHeader
-// OnUnauthenticatedHeader: { connection_id: 13845207862000976235,
-// connection_id_length:8, packet_number_length:1, multipath_flag: 0,
-// reset_flag: 0, version_flag: 0, path_id: , packet_number: 4}
+// OnUnauthenticatedHeader: { connection_id: 13845207862000976235, connection_id_length:8, packet_number_length:1, multipath_flag: 0, reset_flag: 0, version_flag: 0, path_id: , packet_number: 4 }
 // OnDecryptedPacket
 // OnPacketHeader
-// OnAckFrame:  largest_observed: 1 ack_delay_time: 3000
-// missing_packets: [  ] is_truncated: 0 received_packets: [ 1 at 466016  ]
+// OnAckFrame:  largest_observed: 1 ack_delay_time: 3000 missing_packets: [  ] is_truncated: 0 received_packets: [ 1 at 466016  ]
 // OnStopWaitingFrame
-// OnConnectionCloseFrame: error_code { 61 } error_details { Unencrypted stream
-// data seen }
+// OnConnectionCloseFrame: error_code { 61 } error_details { Unencrypted stream data seen }
 
 // clang-format on
 
 #include <iostream>
-#include <string>
 
-#include "base/command_line.h"
-#include "base/strings/utf_string_conversions.h"
 #include "net/third_party/quic/core/quic_framer.h"
 #include "net/third_party/quic/core/quic_utils.h"
+#include "net/third_party/quic/platform/api/quic_flags.h"
 #include "net/third_party/quic/platform/api/quic_text_utils.h"
 
-// If set, specify the QUIC version to use.
-quic::QuicString FLAGS_quic_version = "";
-
-namespace {
-
-quic::QuicString ArgToString(base::CommandLine::StringType arg) {
-#if defined(OS_WIN)
-  return base::UTF16ToASCII(arg);
-#else
-  return arg;
-#endif
-}
-}  // namespace
+DEFINE_QUIC_COMMAND_LINE_FLAG(std::string,
+                              quic_version,
+                              "",
+                              "If set, specify the QUIC version to use.");
 
 namespace quic {
 
@@ -224,40 +207,36 @@
 }  // namespace quic
 
 int main(int argc, char* argv[]) {
-  base::CommandLine::Init(argc, argv);
-  base::CommandLine* line = base::CommandLine::ForCurrentProcess();
-  const base::CommandLine::StringVector& args = line->GetArgs();
+  const char* usage = "Usage: quic_packet_printer client|server <hex>";
+  std::vector<quic::QuicString> args =
+      quic::QuicParseCommandLineFlags(usage, argc, argv);
 
-  if (args.size() != 3) {
-    std::cerr << "Missing argument " << argc << ". (Usage: " << argv[0]
-              << " client|server <hex>\n";
+  if (args.size() < 2) {
+    quic::QuicPrintCommandLineFlagHelp(usage);
     return 1;
   }
 
-  if (line->HasSwitch("quic_version")) {
-    FLAGS_quic_version = line->GetSwitchValueASCII("quic_version");
-  }
-
-  quic::QuicString perspective_string = ArgToString(args[0]);
+  quic::QuicString perspective_string = args[0];
   quic::Perspective perspective;
   if (perspective_string == "client") {
     perspective = quic::Perspective::IS_CLIENT;
   } else if (perspective_string == "server") {
     perspective = quic::Perspective::IS_SERVER;
   } else {
-    std::cerr << "Invalid perspective. " << perspective_string
-              << " Usage: " << args[0] << " client|server <hex>\n";
+    std::cerr << "Invalid perspective" << std::endl;
+    quic::QuicPrintCommandLineFlagHelp(usage);
     return 1;
   }
-  quic::QuicString hex = quic::QuicTextUtils::HexDecode(argv[2]);
+  quic::QuicString hex = quic::QuicTextUtils::HexDecode(args[1]);
   quic::ParsedQuicVersionVector versions = quic::AllSupportedVersions();
   // Fake a time since we're not actually generating acks.
   quic::QuicTime start(quic::QuicTime::Zero());
-  quic::QuicFramer framer(versions, start, perspective);
-  if (!FLAGS_quic_version.empty()) {
+  quic::QuicFramer framer(versions, start, perspective,
+                          quic::kQuicDefaultConnectionIdLength);
+  if (!GetQuicFlag(FLAGS_quic_version).empty()) {
     for (quic::ParsedQuicVersion version : versions) {
       if (quic::QuicVersionToString(version.transport_version) ==
-          FLAGS_quic_version) {
+          GetQuicFlag(FLAGS_quic_version)) {
         framer.set_version(version);
       }
     }
diff --git a/net/third_party/quic/tools/quic_reject_reason_decoder_bin.cc b/net/third_party/quic/tools/quic_reject_reason_decoder_bin.cc
index 9eaf8f19..c55c071 100644
--- a/net/third_party/quic/tools/quic_reject_reason_decoder_bin.cc
+++ b/net/third_party/quic/tools/quic_reject_reason_decoder_bin.cc
@@ -7,28 +7,27 @@
 
 #include <iostream>
 
-#include "base/command_line.h"
-#include "base/strings/string_number_conversions.h"
 #include "net/third_party/quic/core/crypto/crypto_handshake.h"
 #include "net/third_party/quic/core/crypto/crypto_utils.h"
+#include "net/third_party/quic/platform/api/quic_flags.h"
+#include "net/third_party/quic/platform/api/quic_text_utils.h"
 
-using base::CommandLine;
-using quic::HandshakeFailureReason;
 using quic::CryptoUtils;
+using quic::HandshakeFailureReason;
 using quic::MAX_FAILURE_REASON;
 
 int main(int argc, char* argv[]) {
-  CommandLine::Init(argc, argv);
-  CommandLine* line = CommandLine::ForCurrentProcess();
-  const CommandLine::StringVector& args = line->GetArgs();
+  const char* usage = "Usage: quic_reject_reason_decoder <packed_reason>";
+  std::vector<quic::QuicString> args =
+      quic::QuicParseCommandLineFlags(usage, argc, argv);
 
   if (args.size() != 1) {
-    std::cerr << "Missing argument (Usage: " << argv[0] << " <packed_reason>\n";
+    std::cerr << usage << std::endl;
     return 1;
   }
 
   uint32_t packed_error = 0;
-  if (!base::StringToUint(args[0], &packed_error)) {
+  if (!quic::QuicTextUtils::StringToUint32(args[0], &packed_error)) {
     std::cerr << "Unable to parse: " << args[0] << "\n";
     return 2;
   }
diff --git a/net/third_party/quic/tools/quic_server.cc b/net/third_party/quic/tools/quic_server.cc
index 141b1a4..e51310c 100644
--- a/net/third_party/quic/tools/quic_server.cc
+++ b/net/third_party/quic/tools/quic_server.cc
@@ -55,14 +55,16 @@
                  QuicConfig(),
                  QuicCryptoServerConfig::ConfigOptions(),
                  AllSupportedVersions(),
-                 quic_simple_server_backend) {}
+                 quic_simple_server_backend,
+                 kQuicDefaultConnectionIdLength) {}
 
 QuicServer::QuicServer(
     std::unique_ptr<ProofSource> proof_source,
     const QuicConfig& config,
     const QuicCryptoServerConfig::ConfigOptions& crypto_config_options,
     const ParsedQuicVersionVector& supported_versions,
-    QuicSimpleServerBackend* quic_simple_server_backend)
+    QuicSimpleServerBackend* quic_simple_server_backend,
+    uint8_t expected_connection_id_length)
     : port_(0),
       fd_(-1),
       packets_dropped_(0),
@@ -77,7 +79,8 @@
       crypto_config_options_(crypto_config_options),
       version_manager_(supported_versions),
       packet_reader_(new QuicPacketReader()),
-      quic_simple_server_backend_(quic_simple_server_backend) {
+      quic_simple_server_backend_(quic_simple_server_backend),
+      expected_connection_id_length_(expected_connection_id_length) {
   Initialize();
 }
 
@@ -155,7 +158,7 @@
           new QuicSimpleCryptoServerStreamHelper(QuicRandom::GetInstance())),
       std::unique_ptr<QuicEpollAlarmFactory>(
           new QuicEpollAlarmFactory(&epoll_server_)),
-      quic_simple_server_backend_);
+      quic_simple_server_backend_, expected_connection_id_length_);
 }
 
 void QuicServer::WaitForEvents() {
diff --git a/net/third_party/quic/tools/quic_server.h b/net/third_party/quic/tools/quic_server.h
index 3f2be0c..caddf20 100644
--- a/net/third_party/quic/tools/quic_server.h
+++ b/net/third_party/quic/tools/quic_server.h
@@ -41,7 +41,8 @@
              const QuicConfig& config,
              const QuicCryptoServerConfig::ConfigOptions& server_config_options,
              const ParsedQuicVersionVector& supported_versions,
-             QuicSimpleServerBackend* quic_simple_server_backend);
+             QuicSimpleServerBackend* quic_simple_server_backend,
+             uint8_t expected_connection_id_length);
   QuicServer(const QuicServer&) = delete;
   QuicServer& operator=(const QuicServer&) = delete;
 
@@ -99,6 +100,10 @@
 
   void set_silent_close(bool value) { silent_close_ = value; }
 
+  uint8_t expected_connection_id_length() {
+    return expected_connection_id_length_;
+  }
+
  private:
   friend class quic::test::QuicServerPeer;
 
@@ -145,6 +150,9 @@
   std::unique_ptr<QuicPacketReader> packet_reader_;
 
   QuicSimpleServerBackend* quic_simple_server_backend_;  // unowned.
+
+  // Connection ID length expected to be read on incoming IETF short headers.
+  uint8_t expected_connection_id_length_;
 };
 
 }  // namespace quic
diff --git a/net/third_party/quic/tools/quic_server_bin.cc b/net/third_party/quic/tools/quic_server_bin.cc
index a9f6133..7117e94b 100644
--- a/net/third_party/quic/tools/quic_server_bin.cc
+++ b/net/third_party/quic/tools/quic_server_bin.cc
@@ -98,7 +98,8 @@
       CreateProofSource(base::FilePath(GetQuicFlag(FLAGS_certificate_file)),
                         base::FilePath(GetQuicFlag(FLAGS_key_file))),
       config, quic::QuicCryptoServerConfig::ConfigOptions(),
-      quic::AllSupportedVersions(), &memory_cache_backend);
+      quic::AllSupportedVersions(), &memory_cache_backend,
+      quic::kQuicDefaultConnectionIdLength);
 
   int rc = server.CreateUDPSocketAndListen(quic::QuicSocketAddress(
       quic::QuicIpAddress::Any6(), GetQuicFlag(FLAGS_port)));
diff --git a/net/third_party/quic/tools/quic_server_test.cc b/net/third_party/quic/tools/quic_server_test.cc
index c086d17..15f40d81 100644
--- a/net/third_party/quic/tools/quic_server_test.cc
+++ b/net/third_party/quic/tools/quic_server_test.cc
@@ -45,7 +45,8 @@
                              std::move(helper),
                              std::move(session_helper),
                              std::move(alarm_factory),
-                             quic_simple_server_backend) {}
+                             quic_simple_server_backend,
+                             kQuicDefaultConnectionIdLength) {}
   ~MockQuicSimpleDispatcher() override = default;
 
   MOCK_METHOD0(OnCanWrite, void());
diff --git a/net/third_party/quic/tools/quic_simple_dispatcher.cc b/net/third_party/quic/tools/quic_simple_dispatcher.cc
index 73d516c..4df52e8b 100644
--- a/net/third_party/quic/tools/quic_simple_dispatcher.cc
+++ b/net/third_party/quic/tools/quic_simple_dispatcher.cc
@@ -15,13 +15,15 @@
     std::unique_ptr<QuicConnectionHelperInterface> helper,
     std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
     std::unique_ptr<QuicAlarmFactory> alarm_factory,
-    QuicSimpleServerBackend* quic_simple_server_backend)
+    QuicSimpleServerBackend* quic_simple_server_backend,
+    uint8_t expected_connection_id_length)
     : QuicDispatcher(config,
                      crypto_config,
                      version_manager,
                      std::move(helper),
                      std::move(session_helper),
-                     std::move(alarm_factory)),
+                     std::move(alarm_factory),
+                     expected_connection_id_length),
       quic_simple_server_backend_(quic_simple_server_backend) {}
 
 QuicSimpleDispatcher::~QuicSimpleDispatcher() = default;
diff --git a/net/third_party/quic/tools/quic_simple_dispatcher.h b/net/third_party/quic/tools/quic_simple_dispatcher.h
index 48c39e5..b17080b7a 100644
--- a/net/third_party/quic/tools/quic_simple_dispatcher.h
+++ b/net/third_party/quic/tools/quic_simple_dispatcher.h
@@ -20,7 +20,8 @@
       std::unique_ptr<QuicConnectionHelperInterface> helper,
       std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
       std::unique_ptr<QuicAlarmFactory> alarm_factory,
-      QuicSimpleServerBackend* quic_simple_server_backend);
+      QuicSimpleServerBackend* quic_simple_server_backend,
+      uint8_t expected_connection_id_length);
 
   ~QuicSimpleDispatcher() override;
 
diff --git a/net/third_party/quic/test_tools/fake_epoll_server.cc b/net/tools/epoll_server/fake_epoll_server.cc
similarity index 96%
rename from net/third_party/quic/test_tools/fake_epoll_server.cc
rename to net/tools/epoll_server/fake_epoll_server.cc
index 48eaef3..e5ab48c 100644
--- a/net/third_party/quic/test_tools/fake_epoll_server.cc
+++ b/net/tools/epoll_server/fake_epoll_server.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 "net/third_party/quic/test_tools/fake_epoll_server.h"
+#include "net/tools/epoll_server/fake_epoll_server.h"
 
 namespace quic {
 namespace test {
diff --git a/net/third_party/quic/test_tools/fake_epoll_server.h b/net/tools/epoll_server/fake_epoll_server.h
similarity index 94%
rename from net/third_party/quic/test_tools/fake_epoll_server.h
rename to net/tools/epoll_server/fake_epoll_server.h
index d3584ec5..8605c43 100644
--- a/net/third_party/quic/test_tools/fake_epoll_server.h
+++ b/net/tools/epoll_server/fake_epoll_server.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 NET_THIRD_PARTY_QUIC_TEST_TOOLS_FAKE_EPOLL_SERVER_H_
-#define NET_THIRD_PARTY_QUIC_TEST_TOOLS_FAKE_EPOLL_SERVER_H_
+#ifndef NET_TOOLS_EPOLL_SERVER_FAKE_EPOLL_SERVER_H_
+#define NET_TOOLS_EPOLL_SERVER_FAKE_EPOLL_SERVER_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -112,4 +112,4 @@
 }  // namespace test
 }  // namespace quic
 
-#endif  // NET_THIRD_PARTY_QUIC_TEST_TOOLS_FAKE_EPOLL_SERVER_H_
+#endif  // NET_TOOLS_EPOLL_SERVER_FAKE_EPOLL_SERVER_H_
diff --git a/net/tools/quic/quic_http_proxy_backend_stream.h b/net/tools/quic/quic_http_proxy_backend_stream.h
index a64a1cc3..d415fad 100644
--- a/net/tools/quic/quic_http_proxy_backend_stream.h
+++ b/net/tools/quic/quic_http_proxy_backend_stream.h
@@ -55,9 +55,12 @@
 class QuicHttpProxyBackend;
 
 // An adapter for making HTTP requests to net::URLRequest.
+//
+// TODO(https://crbug.com/937621):  This class does not appear to be thread
+// safe, so all its tests are disabled.
 class QuicHttpProxyBackendStream : public net::URLRequest::Delegate {
  public:
-  QuicHttpProxyBackendStream(QuicHttpProxyBackend* context);
+  explicit QuicHttpProxyBackendStream(QuicHttpProxyBackend* context);
   ~QuicHttpProxyBackendStream() override;
 
   static const std::set<std::string> kHopHeaders;
diff --git a/net/tools/quic/quic_http_proxy_backend_stream_test.cc b/net/tools/quic/quic_http_proxy_backend_stream_test.cc
index b0e4372..76cdaf2 100644
--- a/net/tools/quic/quic_http_proxy_backend_stream_test.cc
+++ b/net/tools/quic/quic_http_proxy_backend_stream_test.cc
@@ -195,7 +195,8 @@
   std::unique_ptr<EmbeddedTestServer> test_server_;
 };
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendGetDefault) {
+TEST_F(QuicHttpProxyBackendStreamTest,
+       DISABLED_SendRequestToBackendGetDefault) {
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = kDefaultResponsePath;
   request_headers[":authority"] = "www.example.org";
@@ -214,7 +215,7 @@
   EXPECT_EQ(kDefaultResponseBody, quic_response->body());
 }
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendGetLarge) {
+TEST_F(QuicHttpProxyBackendStreamTest, DISABLED_SendRequestToBackendGetLarge) {
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = "/defaultresponselarge";
   request_headers[":authority"] = "www.example.org";
@@ -235,7 +236,7 @@
   EXPECT_EQ(kLargeResponseBody, quic_response->body());
 }
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostBody) {
+TEST_F(QuicHttpProxyBackendStreamTest, DISABLED_SendRequestToBackendPostBody) {
   const char kUploadData[] = "bobsyeruncle";
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = "/echo";
@@ -258,7 +259,8 @@
   EXPECT_EQ(kUploadData, quic_response->body());
 }
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostEmptyString) {
+TEST_F(QuicHttpProxyBackendStreamTest,
+       DISABLED_SendRequestToBackendPostEmptyString) {
   const char kUploadData[] = "";
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = "/echo";
@@ -281,7 +283,7 @@
   EXPECT_EQ(kUploadData, quic_response->body());
 }
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostFile) {
+TEST_F(QuicHttpProxyBackendStreamTest, DISABLED_SendRequestToBackendPostFile) {
   std::string kUploadData;
   base::FilePath upload_path = GetUploadFileTestPath();
   ASSERT_TRUE(base::ReadFileToString(upload_path, &kUploadData));
@@ -306,7 +308,8 @@
   EXPECT_EQ(kUploadData, quic_response->body());
 }
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendResponse500) {
+TEST_F(QuicHttpProxyBackendStreamTest,
+       DISABLED_SendRequestToBackendResponse500) {
   const char kUploadData[] = "bobsyeruncle";
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = "/echo?status=500";
@@ -326,7 +329,7 @@
   EXPECT_EQ(500, ParseHeaderStatusCode(quic_response->headers()));
 }
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendFail) {
+TEST_F(QuicHttpProxyBackendStreamTest, DISABLED_SendRequestToBackendFail) {
   const char kUploadData[] = "bobsyeruncle";
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = "/echo";
@@ -345,7 +348,8 @@
             quic_response->response_type());
 }
 
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendOnRedirect) {
+TEST_F(QuicHttpProxyBackendStreamTest,
+       DISABLED_SendRequestToBackendOnRedirect) {
   const std::string kRedirectTarget = backend_url_.append("/echo");
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = std::string("/server-redirect?") + kRedirectTarget;
@@ -367,7 +371,8 @@
 
 // Ensure that the proxy rewrites the content-length when receiving a Gzipped
 // response
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendHandleGzip) {
+TEST_F(QuicHttpProxyBackendStreamTest,
+       DISABLED_SendRequestToBackendHandleGzip) {
   const char kGzipData[] =
       "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!";
   uint64_t rawBodyLength = strlen(kGzipData);
@@ -406,7 +411,8 @@
 }
 
 // Ensure cookies are not saved/updated at the proxy
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendCookiesNotSaved) {
+TEST_F(QuicHttpProxyBackendStreamTest,
+       DISABLED_SendRequestToBackendCookiesNotSaved) {
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":authority"] = "www.example.org";
   request_headers[":method"] = "GET";
@@ -450,7 +456,8 @@
 
 // Ensure hop-by-hop headers are removed from the request and response to the
 // backend
-TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendHopHeaders) {
+TEST_F(QuicHttpProxyBackendStreamTest,
+       DISABLED_SendRequestToBackendHopHeaders) {
   spdy::SpdyHeaderBlock request_headers;
   request_headers[":path"] = "/echoall";
   request_headers[":authority"] = "www.example.org";
diff --git a/net/tools/quic/quic_simple_server.cc b/net/tools/quic/quic_simple_server.cc
index 1ba1ed6..4a72f24d 100644
--- a/net/tools/quic/quic_simple_server.cc
+++ b/net/tools/quic/quic_simple_server.cc
@@ -137,7 +137,7 @@
       std::unique_ptr<quic::QuicCryptoServerStream::Helper>(
           new QuicSimpleServerSessionHelper(quic::QuicRandom::GetInstance())),
       std::unique_ptr<quic::QuicAlarmFactory>(alarm_factory_),
-      quic_simple_server_backend_));
+      quic_simple_server_backend_, quic::kQuicDefaultConnectionIdLength));
   QuicSimpleServerPacketWriter* writer =
       new QuicSimpleServerPacketWriter(socket_.get(), dispatcher_.get());
   dispatcher_->InitializeWithWriter(writer);
diff --git a/net/url_request/http_with_dns_over_https_unittest.cc b/net/url_request/http_with_dns_over_https_unittest.cc
index 40971a7..cb877ff 100644
--- a/net/url_request/http_with_dns_over_https_unittest.cc
+++ b/net/url_request/http_with_dns_over_https_unittest.cc
@@ -4,10 +4,11 @@
 
 #include "base/bind.h"
 #include "net/base/proxy_server.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/dns_client.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_transaction.h"
-#include "net/dns/host_resolver_impl.h"
+#include "net/dns/host_resolver_proc.h"
 #include "net/http/http_stream_factory_test_util.h"
 #include "net/log/net_log.h"
 #include "net/socket/transport_client_socket_pool.h"
@@ -47,7 +48,7 @@
 class HttpWithDnsOverHttpsTest : public TestWithScopedTaskEnvironment {
  public:
   HttpWithDnsOverHttpsTest()
-      : resolver_(HostResolver::Options(), nullptr),
+      : resolver_(HostResolver::CreateDefaultResolverImpl(nullptr)),
         request_context_(true),
         doh_server_(EmbeddedTestServer::Type::TYPE_HTTPS),
         test_server_(EmbeddedTestServer::Type::TYPE_HTTPS),
@@ -67,11 +68,11 @@
     config.nameservers.push_back(IPEndPoint());
     config.dns_over_https_servers.emplace_back(url.spec(), true /* use_post */);
     dns_client->SetConfig(config);
-    resolver_.SetRequestContext(&request_context_);
-    resolver_.set_proc_params_for_test(
-        HostResolverImpl::ProcTaskParams(new TestHostResolverProc(), 1));
-    resolver_.SetDnsClient(std::move(dns_client));
-    request_context_.set_host_resolver(&resolver_);
+    resolver_->SetRequestContext(&request_context_);
+    resolver_->SetProcParamsForTesting(
+        ProcTaskParams(new TestHostResolverProc(), 1));
+    resolver_->SetDnsClientForTesting(std::move(dns_client));
+    request_context_.set_host_resolver(resolver_.get());
     request_context_.Init();
   }
 
@@ -119,7 +120,7 @@
   }
 
  protected:
-  HostResolverImpl resolver_;
+  std::unique_ptr<ContextHostResolver> resolver_;
   TestURLRequestContext request_context_;
   EmbeddedTestServer doh_server_;
   EmbeddedTestServer test_server_;
diff --git a/net/url_request/url_request_ftp_fuzzer.cc b/net/url_request/url_request_ftp_fuzzer.cc
index 7028b54..c702813 100644
--- a/net/url_request/url_request_ftp_fuzzer.cc
+++ b/net/url_request/url_request_ftp_fuzzer.cc
@@ -11,7 +11,7 @@
 #include "base/run_loop.h"
 #include "base/test/fuzzed_data_provider.h"
 #include "net/base/request_priority.h"
-#include "net/dns/fuzzed_host_resolver.h"
+#include "net/dns/fuzzed_context_host_resolver.h"
 #include "net/ftp/ftp_network_transaction.h"
 #include "net/ftp/ftp_transaction_factory.h"
 #include "net/socket/client_socket_factory.h"
@@ -60,8 +60,8 @@
   url_request_context.set_client_socket_factory(&fuzzed_socket_factory);
 
   // Need to fuzz the HostResolver to select between IPv4 and IPv6.
-  net::FuzzedHostResolver host_resolver(net::HostResolver::Options(), nullptr,
-                                        &data_provider);
+  net::FuzzedContextHostResolver host_resolver(net::HostResolver::Options(),
+                                               nullptr, &data_provider);
   url_request_context.set_host_resolver(&host_resolver);
 
   net::URLRequestJobFactoryImpl job_factory;
diff --git a/net/websockets/README.md b/net/websockets/README.md
index 6cc512c..bd87b1cc0 100644
--- a/net/websockets/README.md
+++ b/net/websockets/README.md
@@ -2,3 +2,34 @@
 
 This directory contains the implementation of
 [the WebSocket protocol](https://tools.ietf.org/html/rfc6455).
+
+## Design docs
+
+* [WebSocketBasicHandshakeStream design
+  memo](https://docs.google.com/document/d/1r7dQDA9AQBD_kOk-z-yMi0WgLQZ-5m7psMO5pYLFUL8/edit).
+  Some details have changed, but still a mostly-accurate description of
+  Chromium's current implementation.
+* [WebSocket Throttling
+  Design](https://docs.google.com/document/d/1a8sUFQsbN5uve7ziW61ATkrFr3o9A-Tiyw8ig6T3puA/edit)
+  discusses how we enforce WebSocket connection throttling. Also contains
+  detailed discussion of how WebSockets integrate with the socket pools. Dates
+  from 2014, but still mostly relevant.
+* [WebSockets over
+  HTTP/2](https://docs.google.com/document/d/1ZxaHz4j2BDMa1aI5CQHMjtFI3UxGT459pjYv4To9rFY/edit).
+  Current as of 2019 description of WebSocket over H/2 implementation.
+* [WebSocket + Network Service + WebRequest
+  API](https://docs.google.com/document/d/1L85aXX-m5NaV-g223lH7kKB2HPg6kMi1cjrDVeEptE8/edit):
+  design for how extension callbacks are called when the network service is
+  enabled.
+* [WebSocket HTTP Auth
+  Design](https://docs.google.com/document/d/129rLtf5x3hvhP5rayLiSxnEjOXS8Z7EnLJgBL4CdwjI/edit).
+  This document is very low on detail, but can serve as an overview of how auth
+  works for WebSockets.
+* [Per-renderer WebSocket
+  throttling](https://docs.google.com/document/d/1aw2oN5PKfk-1gLnBrlv1OwLA8K3-ykM2ckwX2lubTg4/edit).
+  While the algorithm described in this document is still used, the code has
+  moved around significantly due to network servicification.
+* [WebSocket Protocol Stack in
+  chrome/net](https://docs.google.com/document/d/11n3hpwb9lD9YVqnjX3OwzE_jHgTmKIqd6GvXE9bDGUg/edit).
+  Early design doc for the current implementation. Mostly of historical interest
+  only.
diff --git a/services/identity/BUILD.gn b/services/identity/BUILD.gn
index 41e710a6..8a4ce0a3 100644
--- a/services/identity/BUILD.gn
+++ b/services/identity/BUILD.gn
@@ -33,6 +33,7 @@
     ":lib",
     "//base",
     "//base/test:test_support",
+    "//components/image_fetcher/core:test_support",
     "//components/prefs:test_support",
     "//components/signin/core/browser:internals",
     "//components/signin/core/browser:internals_test_support",
diff --git a/services/identity/DEPS b/services/identity/DEPS
index 9eb4337..76399cc 100644
--- a/services/identity/DEPS
+++ b/services/identity/DEPS
@@ -1,9 +1,9 @@
 include_rules = [
+  "+components/image_fetcher/core/fake_image_decoder.h",
   "+components/prefs/pref_service.h",
   "+components/signin/core/browser/account_info.h",
   "+components/signin/core/browser/account_tracker_service.h",
   "+components/signin/core/browser/device_id_helper.h",
-  "+components/signin/core/browser/test_image_decoder.h",
   "+components/signin/core/browser/fake_profile_oauth2_token_service.h",
   "+components/signin/core/browser/fake_signin_manager.h",
   "+components/signin/core/browser/profile_oauth2_token_service.h",
diff --git a/services/identity/identity_accessor_impl.cc b/services/identity/identity_accessor_impl.cc
index 659d095..afff7d4 100644
--- a/services/identity/identity_accessor_impl.cc
+++ b/services/identity/identity_accessor_impl.cc
@@ -14,35 +14,21 @@
 
 namespace identity {
 
-IdentityAccessorImpl::AccessTokenRequest::AccessTokenRequest(
-    const std::string& account_id,
-    const ScopeSet& scopes,
-    const std::string& consumer_id,
+void IdentityAccessorImpl::OnTokenRequestCompleted(
+    base::UnguessableToken callback_id,
+    scoped_refptr<base::RefCountedData<bool>> is_callback_done,
     GetAccessTokenCallback consumer_callback,
-    IdentityAccessorImpl* manager)
-    : consumer_callback_(std::move(consumer_callback)), manager_(manager) {
-  access_token_fetcher_ =
-      manager->identity_manager_->CreateAccessTokenFetcherForAccount(
-          account_id, consumer_id, scopes,
-          base::BindOnce(&AccessTokenRequest::OnTokenRequestCompleted,
-                         base::Unretained(this)),
-          identity::AccessTokenFetcher::Mode::kImmediate);
-}
-
-IdentityAccessorImpl::AccessTokenRequest::~AccessTokenRequest() = default;
-
-void IdentityAccessorImpl::AccessTokenRequest::OnTokenRequestCompleted(
     GoogleServiceAuthError error,
     AccessTokenInfo access_token_info) {
   if (error.state() == GoogleServiceAuthError::NONE) {
-    std::move(consumer_callback_)
+    std::move(consumer_callback)
         .Run(access_token_info.token, access_token_info.expiration_time, error);
   } else {
-    std::move(consumer_callback_).Run(base::nullopt, base::Time(), error);
+    std::move(consumer_callback).Run(base::nullopt, base::Time(), error);
   }
 
-  // Causes |this| to be deleted.
-  manager_->AccessTokenRequestCompleted(this);
+  is_callback_done->data = true;
+  access_token_fetchers_.erase(callback_id);
 }
 
 // static
@@ -108,12 +94,23 @@
                                           const ScopeSet& scopes,
                                           const std::string& consumer_id,
                                           GetAccessTokenCallback callback) {
-  std::unique_ptr<AccessTokenRequest> access_token_request =
-      std::make_unique<AccessTokenRequest>(account_id, scopes, consumer_id,
-                                           std::move(callback), this);
+  base::UnguessableToken callback_id = base::UnguessableToken::Create();
+  auto is_callback_done =
+      base::MakeRefCounted<base::RefCountedData<bool>>(false);
 
-  access_token_requests_[access_token_request.get()] =
-      std::move(access_token_request);
+  std::unique_ptr<AccessTokenFetcher> fetcher =
+      identity_manager_->CreateAccessTokenFetcherForAccount(
+          account_id, consumer_id, scopes,
+          base::BindOnce(&IdentityAccessorImpl::OnTokenRequestCompleted,
+                         base::Unretained(this), callback_id, is_callback_done,
+                         std::move(callback)),
+          identity::AccessTokenFetcher::Mode::kImmediate);
+
+  // If our callback hasn't already been run, hold on to the AccessTokenFetcher
+  // so it won't be cleaned up until the request is done.
+  if (!is_callback_done->data) {
+    access_token_fetchers_[callback_id] = std::move(fetcher);
+  }
 }
 
 void IdentityAccessorImpl::OnRefreshTokenUpdatedForAccount(
@@ -146,11 +143,6 @@
   }
 }
 
-void IdentityAccessorImpl::AccessTokenRequestCompleted(
-    AccessTokenRequest* request) {
-  access_token_requests_.erase(request);
-}
-
 AccountState IdentityAccessorImpl::GetStateOfAccount(
     const AccountInfo& account_info) {
   AccountState account_state;
diff --git a/services/identity/identity_accessor_impl.h b/services/identity/identity_accessor_impl.h
index 9a41e62..d055c80d 100644
--- a/services/identity/identity_accessor_impl.h
+++ b/services/identity/identity_accessor_impl.h
@@ -5,6 +5,7 @@
 #ifndef SERVICES_IDENTITY_IDENTITY_ACCESSOR_IMPL_H_
 #define SERVICES_IDENTITY_IDENTITY_ACCESSOR_IMPL_H_
 
+#include <map>
 #include <memory>
 
 #include "base/callback_list.h"
@@ -33,29 +34,18 @@
   ~IdentityAccessorImpl() override;
 
  private:
-  // Makes an access token request to the IdentityManager on behalf of a
-  // given consumer that has made the request to the Identity Service.
-  class AccessTokenRequest {
-   public:
-    AccessTokenRequest(const std::string& account_id,
-                       const ScopeSet& scopes,
-                       const std::string& consumer_id,
-                       GetAccessTokenCallback consumer_callback,
-                       IdentityAccessorImpl* manager);
-    ~AccessTokenRequest();
+  // Map of outstanding access token requests.
+  using AccessTokenFetchers =
+      std::map<base::UnguessableToken, std::unique_ptr<AccessTokenFetcher>>;
 
-   private:
-    // Invoked after access token request completes (successful or not).
-    // Completes the pending access token request by calling back the consumer.
-    void OnTokenRequestCompleted(GoogleServiceAuthError error,
-                                 AccessTokenInfo access_token_info);
-
-    std::unique_ptr<AccessTokenFetcher> access_token_fetcher_;
-    GetAccessTokenCallback consumer_callback_;
-    IdentityAccessorImpl* manager_;
-  };
-  using AccessTokenRequests =
-      std::map<AccessTokenRequest*, std::unique_ptr<AccessTokenRequest>>;
+  // Invoked after access token request completes (successful or not).
+  // Completes the pending access token request by calling back the consumer.
+  void OnTokenRequestCompleted(
+      base::UnguessableToken callback_id,
+      scoped_refptr<base::RefCountedData<bool>> is_callback_done,
+      GetAccessTokenCallback consumer_callback,
+      GoogleServiceAuthError error,
+      AccessTokenInfo access_token_info);
 
   // mojom::IdentityAccessor:
   void GetPrimaryAccountInfo(GetPrimaryAccountInfoCallback callback) override;
@@ -78,9 +68,6 @@
   // corresponding to |account_id|.
   void OnAccountStateChange(const std::string& account_id);
 
-  // Deletes |request|.
-  void AccessTokenRequestCompleted(AccessTokenRequest* request);
-
   // Gets the current state of the account represented by |account_info|.
   AccountState GetStateOfAccount(const AccountInfo& account_info);
 
@@ -93,7 +80,7 @@
   AccountTrackerService* account_tracker_;
 
   // The set of pending requests for access tokens.
-  AccessTokenRequests access_token_requests_;
+  AccessTokenFetchers access_token_fetchers_;
 
   // List of callbacks that will be notified when the primary account is
   // available.
diff --git a/services/identity/identity_accessor_impl_unittest.cc b/services/identity/identity_accessor_impl_unittest.cc
index f18ac111..30a6a8a3 100644
--- a/services/identity/identity_accessor_impl_unittest.cc
+++ b/services/identity/identity_accessor_impl_unittest.cc
@@ -6,11 +6,11 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
+#include "components/image_fetcher/core/fake_image_decoder.h"
 #include "components/signin/core/browser/account_info.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_manager.h"
-#include "components/signin/core/browser/test_image_decoder.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "services/identity/identity_service.h"
@@ -71,9 +71,9 @@
     SigninManagerBase::RegisterPrefs(pref_service_.registry());
 
     account_tracker_.Initialize(&pref_service_, base::FilePath());
-    account_fetcher_.Initialize(&signin_client_, &token_service_,
-                                &account_tracker_,
-                                std::make_unique<TestImageDecoder>());
+    account_fetcher_.Initialize(
+        &signin_client_, &token_service_, &account_tracker_,
+        std::make_unique<image_fetcher::FakeImageDecoder>());
     signin_manager_.Initialize(&pref_service_);
   }
 
diff --git a/services/identity/public/cpp/DEPS b/services/identity/public/cpp/DEPS
index 5f3ccaf..008499ed7 100644
--- a/services/identity/public/cpp/DEPS
+++ b/services/identity/public/cpp/DEPS
@@ -1,10 +1,10 @@
 include_rules = [
+  "+components/image_fetcher/core/fake_image_decoder.h",
   "+components/prefs/testing_pref_service.h",
   "+components/signin/core/browser/account_consistency_method.h",
   "+components/signin/core/browser/account_fetcher_service.h",
   "+components/signin/core/browser/account_info.h",
   "+components/signin/core/browser/child_account_info_fetcher_android.h",
-  "+components/signin/core/browser/test_image_decoder.h",
   "+components/signin/core/browser/gaia_cookie_manager_service.h",
   "+components/signin/core/browser/list_accounts_test_utils.h",
   "+components/signin/core/browser/oauth2_token_service_delegate_android.h",
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index c74f6b67..84dc5f0 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -16,13 +16,13 @@
 #include "base/stl_util.h"
 #include "base/test/bind_test_util.h"
 #include "build/build_config.h"
+#include "components/image_fetcher/core/fake_image_decoder.h"
 #include "components/signin/core/browser/account_consistency_method.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
 #include "components/signin/core/browser/list_accounts_test_utils.h"
 #include "components/signin/core/browser/signin_manager.h"
 #include "components/signin/core/browser/signin_switches.h"
-#include "components/signin/core/browser/test_image_decoder.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -341,9 +341,9 @@
     SigninManagerBase::RegisterPrefs(pref_service_.registry());
 
     account_tracker_.Initialize(&pref_service_, base::FilePath());
-    account_fetcher_.Initialize(&signin_client_, &token_service_,
-                                &account_tracker_,
-                                std::make_unique<TestImageDecoder>());
+    account_fetcher_.Initialize(
+        &signin_client_, &token_service_, &account_tracker_,
+        std::make_unique<image_fetcher::FakeImageDecoder>());
 
     RecreateSigninAndIdentityManager(
         signin::AccountConsistencyMethod::kDisabled,
diff --git a/services/identity/public/cpp/identity_test_environment.cc b/services/identity/public/cpp/identity_test_environment.cc
index d2b1c93..2d7dcc4 100644
--- a/services/identity/public/cpp/identity_test_environment.cc
+++ b/services/identity/public/cpp/identity_test_environment.cc
@@ -9,11 +9,11 @@
 
 #include "base/run_loop.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "components/image_fetcher/core/fake_image_decoder.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
 #include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/signin_manager.h"
-#include "components/signin/core/browser/test_image_decoder.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
@@ -131,9 +131,9 @@
   SigninManagerBase::RegisterPrefs(pref_service()->registry());
 
   account_tracker_.Initialize(pref_service(), base::FilePath());
-  account_fetcher_.Initialize(signin_client(), &token_service_,
-                              &account_tracker_,
-                              std::make_unique<TestImageDecoder>());
+  account_fetcher_.Initialize(
+      signin_client(), &token_service_, &account_tracker_,
+      std::make_unique<image_fetcher::FakeImageDecoder>());
   signin_manager_.Initialize(pref_service());
 }
 
diff --git a/services/network/host_resolver_unittest.cc b/services/network/host_resolver_unittest.cc
index d80a15a..430f357 100644
--- a/services/network/host_resolver_unittest.cc
+++ b/services/network/host_resolver_unittest.cc
@@ -20,10 +20,10 @@
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_test_util.h"
 #include "net/dns/host_resolver.h"
-#include "net/dns/host_resolver_impl.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/dns/public/dns_protocol.h"
 #include "net/log/net_log.h"
@@ -1145,9 +1145,9 @@
       std::make_unique<net::MockDnsClient>(net::DnsConfig(), std::move(rules));
 
   net::NetLog net_log;
-  std::unique_ptr<net::HostResolverImpl> inner_resolver =
+  std::unique_ptr<net::ContextHostResolver> inner_resolver =
       net::HostResolver::CreateDefaultResolverImpl(&net_log);
-  inner_resolver->SetDnsClient(std::move(dns_client));
+  inner_resolver->SetDnsClientForTesting(std::move(dns_client));
   inner_resolver->SetBaseDnsConfigForTesting(CreateValidDnsConfig());
 
   HostResolver resolver(inner_resolver.get(), &net_log);
@@ -1183,9 +1183,9 @@
       std::make_unique<net::MockDnsClient>(net::DnsConfig(), std::move(rules));
 
   net::NetLog net_log;
-  std::unique_ptr<net::HostResolverImpl> inner_resolver =
+  std::unique_ptr<net::ContextHostResolver> inner_resolver =
       net::HostResolver::CreateDefaultResolverImpl(&net_log);
-  inner_resolver->SetDnsClient(std::move(dns_client));
+  inner_resolver->SetDnsClientForTesting(std::move(dns_client));
   inner_resolver->SetBaseDnsConfigForTesting(CreateValidDnsConfig());
 
   HostResolver resolver(inner_resolver.get(), &net_log);
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index 5eaceb3..79425af07 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -58,8 +58,8 @@
 #include "net/cookies/cookie_options.h"
 #include "net/cookies/cookie_store.h"
 #include "net/disk_cache/disk_cache.h"
+#include "net/dns/context_host_resolver.h"
 #include "net/dns/dns_test_util.h"
-#include "net/dns/host_resolver_impl.h"
 #include "net/dns/host_resolver_source.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/dns/public/dns_query_type.h"
@@ -2917,8 +2917,8 @@
             network_context->GetNumOutstandingResolveHostRequestsForTesting());
 }
 
-// Test factory of net::HostResolvers. Creates standard net::HostResolverImpl.
-// Keeps pointers to all created resolvers.
+// Test factory of net::HostResolvers. Creates standard
+// net::ContextHostResolver. Keeps pointers to all created resolvers.
 class TestResolverFactory : public net::HostResolver::Factory {
  public:
   static TestResolverFactory* CreateAndSetFactory(NetworkContext* context) {
@@ -2931,18 +2931,18 @@
   std::unique_ptr<net::HostResolver> CreateResolver(
       const net::HostResolver::Options& options,
       net::NetLog* net_log) override {
-    std::unique_ptr<net::HostResolverImpl> resolver =
+    std::unique_ptr<net::ContextHostResolver> resolver =
         net::HostResolver::CreateSystemResolverImpl(options, net_log);
     resolvers_.push_back(resolver.get());
     return resolver;
   }
 
-  const std::vector<net::HostResolverImpl*>& resolvers() const {
+  const std::vector<net::ContextHostResolver*>& resolvers() const {
     return resolvers_;
   }
 
  private:
-  std::vector<net::HostResolverImpl*> resolvers_;
+  std::vector<net::ContextHostResolver*> resolvers_;
 };
 
 TEST_F(NetworkContextTest, CreateHostResolver) {
@@ -3091,7 +3091,7 @@
   // Should create 1 private resolver with a DnsClient (if DnsClient is
   // enablable for the build config).
   ASSERT_EQ(1u, factory->resolvers().size());
-  net::HostResolverImpl* internal_resolver = factory->resolvers().front();
+  net::ContextHostResolver* internal_resolver = factory->resolvers().front();
 #if defined(ENABLE_BUILT_IN_DNS)
   EXPECT_TRUE(internal_resolver->GetDnsConfigAsValue());
 #endif
@@ -3115,7 +3115,7 @@
   auto mock_dns_client =
       std::make_unique<net::MockDnsClient>(net::DnsConfig(), std::move(rules));
   auto* mock_dns_client_ptr = mock_dns_client.get();
-  internal_resolver->SetDnsClient(std::move(mock_dns_client));
+  internal_resolver->SetDnsClientForTesting(std::move(mock_dns_client));
 
   // Force the base configuration to ensure consistent overriding.
   net::DnsConfig base_configuration;
diff --git a/services/service_manager/sandbox/mac/common.sb b/services/service_manager/sandbox/mac/common.sb
index 579d9de..e2d14db9 100644
--- a/services/service_manager/sandbox/mac/common.sb
+++ b/services/service_manager/sandbox/mac/common.sb
@@ -68,10 +68,13 @@
 ; https://crbug.com/850021
 (define (allow-cvms-blobs)
   (if (>= os-version 1014)
-    (allow file-read*
-      (extension "com.apple.cvms.kernel")
-      (subpath "/private/var/db/CVMS")
-    )))
+    (begin
+      (allow file-read* file-write-unlink
+        (prefix "/private/tmp/cvmsCodeSignObj"))
+      (allow file-read*
+        (extension "com.apple.cvms.kernel")
+        (prefix "/private/var/db/CVMS/cvmsCodeSignObj"))
+)))
 
 ; Allow logging for all processes.
 (allow file-write*
@@ -191,3 +194,8 @@
   (sysctl-name "sysctl.proc_cputype")
   (sysctl-name (string-append "kern.proc.pid." (param current-pid)))
 )
+
+(allow network-outbound
+  (literal "/private/var/run/asl_input")
+  (literal "/private/var/run/syslog")
+)
diff --git a/services/service_manager/sandbox/mac/gpu_v2.sb b/services/service_manager/sandbox/mac/gpu_v2.sb
index 6a051984..e0aa0a8 100644
--- a/services/service_manager/sandbox/mac/gpu_v2.sb
+++ b/services/service_manager/sandbox/mac/gpu_v2.sb
@@ -14,6 +14,7 @@
   (global-name "com.apple.CoreServices.coreservicesd")
   (global-name "com.apple.coreservices.launchservicesd")
   (global-name "com.apple.cvmsServ")
+  (global-name "com.apple.gpumemd.source")
   (global-name "com.apple.system.notification_center")
   (global-name "com.apple.tsm.uiserver")
   (global-name "com.apple.windowserver.active")
@@ -24,7 +25,10 @@
   (iokit-connection "IOAccelerator")
   (iokit-user-client-class "AGPMClient")
   (iokit-user-client-class "AppleGraphicsControlClient")
+  (iokit-user-client-class "AppleGraphicsPolicyClient")
+  (iokit-user-client-class "AppleIntelMEUserClient")
   (iokit-user-client-class "AppleMGPUPowerControlClient")
+  (iokit-user-client-class "AppleSNBFBUserClient")
   (iokit-user-client-class "IOAccelerationUserClient")
   (iokit-user-client-class "IOFramebufferSharedUserClient")
   (iokit-user-client-class "IOHIDParamUserClient")
@@ -33,6 +37,16 @@
   (iokit-user-client-class "RootDomainUserClient")
 )
 
+(allow iokit-set-properties
+  (require-all (iokit-connection "IODisplay")
+    (require-any (iokit-property "brightness")
+      (iokit-property "linear-brightness")
+      (iokit-property "commit")
+      (iokit-property "rgcs")
+      (iokit-property "ggcs")
+      (iokit-property "bgcs")
+)))
+
 (allow ipc-posix-shm-read-data
   (ipc-posix-name "apple.shm.notification_center"))
 
@@ -51,7 +65,13 @@
 (allow sysctl-read
   (sysctl-name "hw.logicalcpu_max")
   (sysctl-name "hw.model")
+  (sysctl-name "kern.osvariant_status")
 )
 
 (allow file-read-data
-  (regex (user-homedir-path #"/Library/Preferences/ByHost/com.apple.AppleGVA.*")))
+  (regex (user-homedir-path #"/Library/Preferences/ByHost/com.apple.AppleGVA.*"))
+)
+
+(allow file-read*
+  (subpath "/Library/GPUBundles")
+)
diff --git a/services/shape_detection/android/javatests/src/org/chromium/shape_detection/BarcodeDetectionImplTest.java b/services/shape_detection/android/javatests/src/org/chromium/shape_detection/BarcodeDetectionImplTest.java
index cfdc07f6..5b21176 100644
--- a/services/shape_detection/android/javatests/src/org/chromium/shape_detection/BarcodeDetectionImplTest.java
+++ b/services/shape_detection/android/javatests/src/org/chromium/shape_detection/BarcodeDetectionImplTest.java
@@ -13,8 +13,10 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.shape_detection.mojom.BarcodeDetection;
+import org.chromium.shape_detection.mojom.BarcodeDetectionProvider;
 import org.chromium.shape_detection.mojom.BarcodeDetectionResult;
 import org.chromium.shape_detection.mojom.BarcodeDetectorOptions;
+import org.chromium.shape_detection.mojom.BarcodeFormat;
 
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -27,6 +29,32 @@
     private static final org.chromium.skia.mojom.Bitmap QR_CODE_BITMAP =
             TestUtils.mojoBitmapFromFile("qr_code.png");
 
+    private static final int[] SUPPORTED_FORMATS = {BarcodeFormat.AZTEC, BarcodeFormat.CODE_128,
+            BarcodeFormat.CODE_39, BarcodeFormat.CODE_93, BarcodeFormat.CODABAR,
+            BarcodeFormat.DATA_MATRIX, BarcodeFormat.EAN_13, BarcodeFormat.EAN_8, BarcodeFormat.ITF,
+            BarcodeFormat.PDF417, BarcodeFormat.QR_CODE, BarcodeFormat.UPC_A, BarcodeFormat.UPC_E};
+
+    private static int[] enumerateSupportedFormats() {
+        BarcodeDetectionProvider provider = new BarcodeDetectionProviderImpl();
+
+        final ArrayBlockingQueue<int[]> queue = new ArrayBlockingQueue<>(13);
+        provider.enumerateSupportedFormats(
+                new BarcodeDetectionProvider.EnumerateSupportedFormatsResponse() {
+                    @Override
+                    public void call(int[] results) {
+                        queue.add(results);
+                    }
+                });
+        int[] toReturn = null;
+        try {
+            toReturn = queue.poll(5L, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Assert.fail("Could not get int[] supported formats: " + e.toString());
+        }
+        Assert.assertNotNull(toReturn);
+        return toReturn;
+    }
+
     private static BarcodeDetectionResult[] detect(org.chromium.skia.mojom.Bitmap mojoBitmap) {
         BarcodeDetectorOptions options = new BarcodeDetectorOptions();
         BarcodeDetection detector = new BarcodeDetectionImpl(options);
@@ -51,6 +79,17 @@
     @Test
     @SmallTest
     @Feature({"ShapeDetection"})
+    public void testEnumerateSupportedFormats() {
+        if (!TestUtils.IS_GMS_CORE_SUPPORTED) {
+            return;
+        }
+        int[] results = enumerateSupportedFormats();
+        Assert.assertArrayEquals(SUPPORTED_FORMATS, results);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"ShapeDetection"})
     public void testDetectBase64ValidImageString() {
         if (!TestUtils.IS_GMS_CORE_SUPPORTED) {
             return;
diff --git a/services/shape_detection/barcode_detection_impl_mac_unittest.mm b/services/shape_detection/barcode_detection_impl_mac_unittest.mm
index 161510b..340a83d7 100644
--- a/services/shape_detection/barcode_detection_impl_mac_unittest.mm
+++ b/services/shape_detection/barcode_detection_impl_mac_unittest.mm
@@ -18,6 +18,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "services/shape_detection/barcode_detection_impl_mac_vision.h"
+#include "services/shape_detection/barcode_detection_provider_mac.h"
 #include "services/shape_detection/public/mojom/barcodedetection.mojom.h"
 #include "services/shape_detection/public/mojom/barcodedetection_provider.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -36,8 +37,25 @@
   closure.Run();
 }
 
-std::unique_ptr<mojom::BarcodeDetection> CreateBarcodeDetectorImplMac(
+static const std::vector<mojom::BarcodeFormat>& CISupportedFormats = {
+    mojom::BarcodeFormat::QR_CODE};
+static const std::vector<mojom::BarcodeFormat>& VisionSupportedFormats = {
+    mojom::BarcodeFormat::AZTEC,       mojom::BarcodeFormat::CODE_128,
+    mojom::BarcodeFormat::CODE_39,     mojom::BarcodeFormat::CODE_93,
+    mojom::BarcodeFormat::DATA_MATRIX, mojom::BarcodeFormat::EAN_13,
+    mojom::BarcodeFormat::EAN_8,       mojom::BarcodeFormat::ITF,
+    mojom::BarcodeFormat::PDF417,      mojom::BarcodeFormat::QR_CODE,
+    mojom::BarcodeFormat::UPC_E};
+
+std::unique_ptr<mojom::BarcodeDetectionProvider> CreateBarcodeProviderMac() {
+  return std::make_unique<BarcodeDetectionProviderMac>();
+}
+
+std::unique_ptr<mojom::BarcodeDetection> CreateBarcodeDetectorImplMacCI(
     mojom::BarcodeDetectorOptionsPtr options) {
+  if (@available(macOS 10.13, *)) {
+    return nullptr;
+  }
   if (@available(macOS 10.10, *)) {
     return std::make_unique<BarcodeDetectionImplMac>();
   }
@@ -62,10 +80,13 @@
 struct TestParams {
   size_t num_barcodes;
   const std::string barcode_value;
+  const std::vector<mojom::BarcodeFormat> formats;
   BarcodeDetectorFactory factory;
 } kTestParams[] = {
-    {1, kInfoString, base::Bind(&CreateBarcodeDetectorImplMac)},
-    {1, kInfoString, base::Bind(&CreateBarcodeDetectorImplMacVision)},
+    {1, kInfoString, CISupportedFormats,
+     base::BindRepeating(&CreateBarcodeDetectorImplMacCI)},
+    {1, kInfoString, VisionSupportedFormats,
+     base::BindRepeating(&CreateBarcodeDetectorImplMacVision)},
 };
 }
 
@@ -78,13 +99,12 @@
       vision_framework_ = dlopen(
           "/System/Library/Frameworks/Vision.framework/Vision", RTLD_LAZY);
     }
+    provider_ = base::BindOnce(&CreateBarcodeProviderMac).Run();
   }
 
   void TearDown() override {
-    if (@available(macOS 10.13, *)) {
-      if (vision_framework_)
-        dlclose(vision_framework_);
-    }
+    if (vision_framework_)
+      dlclose(vision_framework_);
   }
 
   void DetectCallback(size_t num_barcodes,
@@ -98,20 +118,49 @@
   }
   MOCK_METHOD0(Detection, void(void));
 
+  void EnumerateSupportedFormatsCallback(
+      const std::vector<mojom::BarcodeFormat>& expected,
+      const std::vector<mojom::BarcodeFormat>& results) {
+    EXPECT_THAT(results,
+                testing::ElementsAreArray(expected.begin(), expected.end()));
+
+    OnEnumerateSupportedFormats();
+  }
+  MOCK_METHOD0(OnEnumerateSupportedFormats, void(void));
+
   std::unique_ptr<mojom::BarcodeDetection> impl_;
+  std::unique_ptr<mojom::BarcodeDetectionProvider> provider_;
   const base::MessageLoop message_loop_;
-  void* vision_framework_;
+  void* vision_framework_ = nullptr;
 };
 
 TEST_P(BarcodeDetectionImplMacTest, CreateAndDestroy) {
   impl_ = GetParam().factory.Run(mojom::BarcodeDetectorOptions::New());
   if (!impl_) {
-    LOG(WARNING) << "Barcode Detection is not supported before Mac OSX 10.10."
-                 << "Skipping test.";
+    LOG(WARNING) << "Barcode Detection for this (library, OS version) pair is "
+                    "not supported, skipping test.";
     return;
   }
 }
 
+TEST_P(BarcodeDetectionImplMacTest, EnumerateSupportedBarcodes) {
+  impl_ = GetParam().factory.Run(mojom::BarcodeDetectorOptions::New());
+  if (!impl_) {
+    LOG(WARNING) << "Barcode Detection for this (library, OS version) pair is "
+                    "not supported, skipping test.";
+    return;
+  }
+
+  base::RunLoop run_loop;
+  base::RepeatingClosure quit_closure = run_loop.QuitClosure();
+  EXPECT_CALL(*this, OnEnumerateSupportedFormats())
+      .WillOnce(RunClosure(quit_closure));
+  provider_->EnumerateSupportedFormats(base::BindOnce(
+      &BarcodeDetectionImplMacTest::EnumerateSupportedFormatsCallback,
+      base::Unretained(this), GetParam().formats));
+  run_loop.Run();
+}
+
 // This test generates a single QR code and scans it back.
 TEST_P(BarcodeDetectionImplMacTest, ScanOneBarcode) {
   // Barcode detection needs at least MAC OS X 10.10, and GPU infrastructure.
diff --git a/services/tracing/agent_registry.cc b/services/tracing/agent_registry.cc
index f1e4764..30e38ae 100644
--- a/services/tracing/agent_registry.cc
+++ b/services/tracing/agent_registry.cc
@@ -118,10 +118,11 @@
   auto id = next_agent_id_++;
   auto entry = std::make_unique<AgentEntry>(id, this, std::move(agent), label,
                                             type, pid);
-  if (!agent_initialization_callback_.is_null())
-    agent_initialization_callback_.Run(entry.get());
+  AgentEntry* entry_ptr = entry.get();
   auto result = agents_.insert(std::make_pair(id, std::move(entry)));
   DCHECK(result.second);
+  if (!agent_initialization_callback_.is_null())
+    agent_initialization_callback_.Run(entry_ptr);
 }
 
 void AgentRegistry::UnregisterAgent(size_t agent_id) {
diff --git a/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc b/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc
index 46de552..a4b30e4 100644
--- a/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc
+++ b/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc
@@ -151,9 +151,10 @@
 }  // namespace
 
 ChromeEventBundleJsonExporter::ChromeEventBundleJsonExporter(
-    bool filter_args,
+    JSONTraceExporter::ArgumentFilterPredicate argument_filter_predicate,
     JSONTraceExporter::OnTraceEventJSONCallback callback)
-    : JSONTraceExporter(filter_args, std::move(callback)) {}
+    : JSONTraceExporter(std::move(argument_filter_predicate),
+                        std::move(callback)) {}
 
 void ChromeEventBundleJsonExporter::ProcessPackets(
     const std::vector<perfetto::TracePacket>& packets) {
diff --git a/services/tracing/perfetto/chrome_event_bundle_json_exporter.h b/services/tracing/perfetto/chrome_event_bundle_json_exporter.h
index 08e331d0..db1cc3c 100644
--- a/services/tracing/perfetto/chrome_event_bundle_json_exporter.h
+++ b/services/tracing/perfetto/chrome_event_bundle_json_exporter.h
@@ -25,8 +25,9 @@
 // Conversion happens on-the-fly as new trace packets are received.
 class ChromeEventBundleJsonExporter : public JSONTraceExporter {
  public:
-  ChromeEventBundleJsonExporter(bool filter_args,
-                                OnTraceEventJSONCallback callback);
+  ChromeEventBundleJsonExporter(
+      ArgumentFilterPredicate argument_filter_predicate,
+      OnTraceEventJSONCallback callback);
   ~ChromeEventBundleJsonExporter() override = default;
 
  protected:
diff --git a/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc b/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc
index cf30a719..395ee5f 100644
--- a/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc
+++ b/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc
@@ -31,11 +31,36 @@
 
 namespace tracing {
 
+namespace {
+
+bool IsArgNameWhitelisted(const char* arg_name) {
+  return base::MatchPattern(arg_name, "granular_arg_whitelisted");
+}
+
+bool IsTraceEventArgsWhitelisted(
+    const char* category_group_name,
+    const char* event_name,
+    base::trace_event::ArgumentNameFilterPredicate* arg_filter) {
+  if (base::MatchPattern(category_group_name, "toplevel") &&
+      base::MatchPattern(event_name, "*")) {
+    return true;
+  }
+  if (base::MatchPattern(category_group_name, "benchmark") &&
+      base::MatchPattern(event_name, "granularly_whitelisted")) {
+    *arg_filter = base::BindRepeating(&IsArgNameWhitelisted);
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace
+
 class ChromeEventBundleJsonExporterTest : public testing::Test {
  public:
   void SetUp() override {
     json_trace_exporter_.reset(new ChromeEventBundleJsonExporter(
-        /*filter_args=*/false,
+        JSONTraceExporter::ArgumentFilterPredicate(),
         base::BindRepeating(
             &ChromeEventBundleJsonExporterTest::OnTraceEventJSON,
             base::Unretained(this))));
@@ -44,7 +69,8 @@
   void TearDown() override { json_trace_exporter_.reset(); }
 
   void EnableArgumentFilter() {
-    json_trace_exporter_->set_filter_args_for_testing(true);
+    json_trace_exporter_->SetArgumentFilterForTesting(
+        base::BindRepeating(&IsTraceEventArgsWhitelisted));
   }
 
   void OnTraceEventJSON(const std::string& json,
@@ -670,13 +696,13 @@
     auto* new_trace_event =
         trace_packet_proto.mutable_chrome_events()->add_trace_events();
     SetTestPacketBasicData(new_trace_event);
-    new_trace_event->set_name("ScopedBlockingCallTest");
-    new_trace_event->set_category_group_name("base");
+    new_trace_event->set_name("granularly_whitelisted");
+    new_trace_event->set_category_group_name("benchmark");
     auto* new_arg1 = new_trace_event->add_args();
-    new_arg1->set_name("file_name");
+    new_arg1->set_name("granular_arg_whitelisted");
     new_arg1->set_string_value("whitelisted_value");
     auto* new_arg2 = new_trace_event->add_args();
-    new_arg2->set_name("file_number");
+    new_arg2->set_name("granular_arg_blacklisted");
     new_arg2->set_string_value("blacklisted_value");
   }
 
@@ -701,12 +727,12 @@
   {
     const auto* trace_event = trace_analyzer()->FindFirstOf(
         trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
-        trace_analyzer::Query::String("ScopedBlockingCallTest"));
+        trace_analyzer::Query::String("granularly_whitelisted"));
     EXPECT_TRUE(trace_event);
     EXPECT_EQ("whitelisted_value",
-              trace_event->GetKnownArgAsString(("file_name")));
+              trace_event->GetKnownArgAsString(("granular_arg_whitelisted")));
     EXPECT_EQ("__stripped__",
-              trace_event->GetKnownArgAsString(("file_number")));
+              trace_event->GetKnownArgAsString(("granular_arg_blacklisted")));
   }
 }
 
diff --git a/services/tracing/perfetto/json_trace_exporter.cc b/services/tracing/perfetto/json_trace_exporter.cc
index 6cda65b..6e6b4af7 100644
--- a/services/tracing/perfetto/json_trace_exporter.cc
+++ b/services/tracing/perfetto/json_trace_exporter.cc
@@ -11,7 +11,6 @@
 #include "base/json/json_writer.h"
 #include "base/json/string_escape.h"
 #include "base/trace_event/trace_event.h"
-#include "services/tracing/public/cpp/trace_event_args_whitelist.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_packet.h"
 #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
 #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_packet.pb.h"
@@ -24,11 +23,12 @@
 
 }  // namespace
 
-JSONTraceExporter::JSONTraceExporter(bool filter_args,
-                                     OnTraceEventJSONCallback callback)
+JSONTraceExporter::JSONTraceExporter(
+    ArgumentFilterPredicate argument_filter_predicate,
+    OnTraceEventJSONCallback callback)
     : out_(callback),
       metadata_(std::make_unique<base::DictionaryValue>()),
-      filter_args_(filter_args) {}
+      argument_filter_predicate_(std::move(argument_filter_predicate)) {}
 
 JSONTraceExporter::~JSONTraceExporter() = default;
 
@@ -194,8 +194,8 @@
                                  int32_t tid) {
   DCHECK(ShouldOutputTraceEvents());
   return JSONTraceExporter::ScopedJSONTraceEventAppender(
-      AddJSONTraceEvent(), filter_args_, name, categories, phase, timestamp,
-      pid, tid);
+      AddJSONTraceEvent(), argument_filter_predicate_, name, categories, phase,
+      timestamp, pid, tid);
 }
 
 JSONTraceExporter::StringBuffer* JSONTraceExporter::AddJSONTraceEvent() {
@@ -279,14 +279,16 @@
 }
 
 JSONTraceExporter::ArgumentBuilder::ArgumentBuilder(
-    bool filter_args,
+    const ArgumentFilterPredicate& argument_filter_predicate,
     const char* name,
     const char* category_group_name,
     StringBuffer* out)
     : out_(out) {
-  strip_args_ = filter_args &&
-                !IsTraceEventArgsWhitelisted(category_group_name, name,
-                                             &argument_name_filter_predicate_);
+  JSONTraceExporter::ArgumentNameFilterPredicate argument_name_filter_predicate;
+  strip_args_ =
+      !argument_filter_predicate.is_null() &&
+      !argument_filter_predicate.Run(category_group_name, name,
+                                     &argument_name_filter_predicate_);
   *out_ += ",\"args\":";
 }
 
@@ -341,7 +343,7 @@
 
 JSONTraceExporter::ScopedJSONTraceEventAppender::ScopedJSONTraceEventAppender(
     JSONTraceExporter::StringBuffer* out,
-    bool filter_args,
+    JSONTraceExporter::ArgumentFilterPredicate argument_filter_predicate,
     const char* name,
     const char* categories,
     int32_t phase,
@@ -353,7 +355,7 @@
       out_(out),
       event_name_(name),
       category_group_name_(categories),
-      filter_args_(filter_args) {
+      argument_filter_predicate_(std::move(argument_filter_predicate)) {
   out_->AppendF("{\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64
                 ",\"ph\":\"%c\",\"cat\":\"%s\",\"name\":",
                 pid, tid, timestamp, phase_, categories);
@@ -364,7 +366,7 @@
     JSONTraceExporter::ScopedJSONTraceEventAppender&& move) {
   out_ = move.out_;
   phase_ = move.phase_;
-  filter_args_ = move.filter_args_;
+  argument_filter_predicate_ = std::move(move.argument_filter_predicate_);
   // We null out the string so that the destructor knows not to append the
   // closing brace for the json.
   move.out_ = nullptr;
@@ -475,7 +477,7 @@
 JSONTraceExporter::ScopedJSONTraceEventAppender::BuildArgs() {
   DCHECK(!added_args_);
   added_args_ = true;
-  return std::make_unique<ArgumentBuilder>(filter_args_, event_name_,
-                                           category_group_name_, out_);
+  return std::make_unique<ArgumentBuilder>(
+      argument_filter_predicate_, event_name_, category_group_name_, out_);
 }
 }  // namespace tracing
diff --git a/services/tracing/perfetto/json_trace_exporter.h b/services/tracing/perfetto/json_trace_exporter.h
index 4aae8e0..2b1878b 100644
--- a/services/tracing/perfetto/json_trace_exporter.h
+++ b/services/tracing/perfetto/json_trace_exporter.h
@@ -36,19 +36,29 @@
   using ArgumentNameFilterPredicate =
       base::RepeatingCallback<bool(const char* arg_name)>;
 
+  // Given trace event name and category group name, returns a argument name
+  // filter predicate callback that can filter arguments for the given event.
+  using ArgumentFilterPredicate =
+      base::RepeatingCallback<bool(const char* category_group_name,
+                                   const char* event_name,
+                                   ArgumentNameFilterPredicate*)>;
+
   using OnTraceEventJSONCallback =
       base::RepeatingCallback<void(const std::string& json,
                                    base::DictionaryValue* metadata,
                                    bool has_more)>;
 
-  JSONTraceExporter(bool filter_args, OnTraceEventJSONCallback callback);
+  JSONTraceExporter(ArgumentFilterPredicate argument_filter_predicate,
+                    OnTraceEventJSONCallback callback);
   virtual ~JSONTraceExporter();
 
   // Called to notify the exporter of new trace packets. Will call the
   // |json_callback| passed in the constructor with the converted trace data.
   void OnTraceData(std::vector<perfetto::TracePacket> packets, bool has_more);
 
-  void set_filter_args_for_testing(bool value) { filter_args_ = value; }
+  void SetArgumentFilterForTesting(const ArgumentFilterPredicate& predicate) {
+    argument_filter_predicate_ = predicate;
+  }
 
   void set_label_filter(const std::string& label_filter) {
     label_filter_ = label_filter;
@@ -92,7 +102,7 @@
 
   class ArgumentBuilder {
    public:
-    ArgumentBuilder(bool filter_args,
+    ArgumentBuilder(const ArgumentFilterPredicate& argument_filter_predicate,
                     const char* name,
                     const char* category_group_name,
                     StringBuffer* out);
@@ -172,14 +182,15 @@
    private:
     // Subclasses of JSONTraceExporter can create a new instance by calling
     // AddTraceEvent().
-    ScopedJSONTraceEventAppender(StringBuffer* out,
-                                 bool filter_args,
-                                 const char* name,
-                                 const char* categories,
-                                 int32_t phase,
-                                 int64_t timestamp,
-                                 int32_t pid,
-                                 int32_t tid);
+    ScopedJSONTraceEventAppender(
+        StringBuffer* out,
+        ArgumentFilterPredicate argument_filter_predicate,
+        const char* name,
+        const char* categories,
+        int32_t phase,
+        int64_t timestamp,
+        int32_t pid,
+        int32_t tid);
     friend class JSONTraceExporter;
 
     char phase_;
@@ -187,7 +198,7 @@
     StringBuffer* out_;
     const char* event_name_;
     const char* category_group_name_;
-    bool filter_args_;
+    ArgumentFilterPredicate argument_filter_predicate_;
   };
 
   // Subclasses implement this to add data from |packets| to the JSON output.
@@ -237,7 +248,7 @@
   std::string label_filter_;
   std::string legacy_system_ftrace_output_;
   std::unique_ptr<base::DictionaryValue> metadata_;
-  bool filter_args_;
+  ArgumentFilterPredicate argument_filter_predicate_;
 
   DISALLOW_COPY_AND_ASSIGN(JSONTraceExporter);
 };
diff --git a/services/tracing/perfetto/json_trace_exporter_unittest.cc b/services/tracing/perfetto/json_trace_exporter_unittest.cc
index 983a251..da512a135 100644
--- a/services/tracing/perfetto/json_trace_exporter_unittest.cc
+++ b/services/tracing/perfetto/json_trace_exporter_unittest.cc
@@ -84,8 +84,10 @@
 
 class TestJSONTraceExporter : public JSONTraceExporter {
  public:
-  TestJSONTraceExporter(bool filter_args, OnTraceEventJSONCallback callback)
-      : JSONTraceExporter(filter_args, std::move(callback)) {}
+  TestJSONTraceExporter(ArgumentFilterPredicate argument_filter_predicate,
+                        OnTraceEventJSONCallback callback)
+      : JSONTraceExporter(std::move(argument_filter_predicate),
+                          std::move(callback)) {}
   ~TestJSONTraceExporter() override = default;
 
   int process_packets_calls() const { return process_packets_calls_; }
@@ -186,7 +188,7 @@
  public:
   JsonTraceExporterTest()
       : json_trace_exporter_(new TestJSONTraceExporter(
-            /* filter_args =*/false,
+            JSONTraceExporter::ArgumentFilterPredicate(),
             base::BindRepeating(&JsonTraceExporterTest::OnTraceEventJSON,
                                 base::Unretained(this)))) {}
 
@@ -440,53 +442,70 @@
 
 TEST_F(JsonTraceExporterTest, TestAddArgsArgumentStripping) {
   std::vector<FakeTraceInfo> infos = {
-      FakeTraceInfo("event1", "base", 'B', /* timestamp = */ 1,
+      FakeTraceInfo("event1", "toplevel", 'B', /* timestamp = */ 1,
                     /* pid = */ 2,
                     /* tid = */ 3),
-      FakeTraceInfo("whitewashed", "base", 'B', /* timestamp = */ 1,
+      FakeTraceInfo("event2", "whitewashed", 'B', /* timestamp = */ 1,
                     /* pid = */ 2, /* tid = */ 3),
-      FakeTraceInfo("ScopedBlockingCallTest", "base", 'B',
+      FakeTraceInfo("event3", "granular_whitelisted", 'B',
                     /* timestamp = */ 1, /* pid = */ 2, /* tid = */ 3)};
   infos[0].args.emplace_back("int_one", int64_t(1));
 
   infos[1].args.emplace_back("int_two", uint64_t(2));
 
   // Third arg only has index into the string table.
-  infos[2].args.emplace_back("file_name", std::string("\"whitelisted_value\""));
-  infos[2].args.emplace_back("file_number",
+  infos[2].args.emplace_back("granular_arg_whitelisted",
+                             std::string("\"whitelisted_value\""));
+  infos[2].args.emplace_back("granular_arg_blacklisted",
                              std::string("\"blacklisted_value\""));
 
-  json_trace_exporter_->set_filter_args_for_testing(true);
+  json_trace_exporter_->SetArgumentFilterForTesting(base::BindRepeating(
+      [](const char* category_group_name, const char* event_name,
+         base::trace_event::ArgumentNameFilterPredicate* arg_filter) {
+        if (base::MatchPattern(category_group_name, "toplevel") &&
+            base::MatchPattern(event_name, "*")) {
+          return true;
+        }
+        if (base::MatchPattern(category_group_name, "granular_whitelisted") &&
+            base::MatchPattern(event_name, "event3")) {
+          *arg_filter = base::BindRepeating([](const char* arg_name) {
+            return base::MatchPattern(arg_name, "granular_arg_whitelisted");
+          });
+          return true;
+        }
+        return false;
+      }));
 
   json_trace_exporter_->SetFakeTraceEvents(infos);
   json_trace_exporter_->OnTraceData(
       std::vector<perfetto::TracePacket>(infos.size()), false);
   EXPECT_EQ(
       "{\"traceEvents\":[{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\","
-      "\"cat\":\"base\",\"name\":\"event1\",\"args\":\"__stripped__\"},\n"
-      "{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\",\"cat\":\"base\","
-      "\"name\":\"whitewashed\",\"args\":\"__stripped__\"},\n"
+      "\"cat\":\"toplevel\",\"name\":\"event1\",\"args\":{\"int_one\":1}},\n"
+      "{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\",\"cat\":\"whitewashed\","
+      "\"name\":\"event2\",\"args\":\"__stripped__\"},\n"
       "{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\","
-      "\"cat\":\"base\",\"name\":\"ScopedBlockingCallTest\","
-      "\"args\":{\"file_name\":\"whitelisted_value\","
-      "\"file_number\":\"__stripped__\"}}]}",
+      "\"cat\":\"granular_whitelisted\",\"name\":\"event3\","
+      "\"args\":{\"granular_arg_whitelisted\":\"whitelisted_value\","
+      "\"granular_arg_blacklisted\":\"__stripped__\"}}]}",
       unparsed_trace_data_);
 
   const trace_analyzer::TraceEvent* trace_event = trace_analyzer_->FindFirstOf(
       trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
       trace_analyzer::Query::String("event1"));
-  EXPECT_FALSE(trace_event->HasArg("int_one"));
   EXPECT_TRUE(trace_event);
+  EXPECT_EQ(1, trace_event->GetKnownArgAsDouble("int_one"));
   trace_event = trace_analyzer_->FindFirstOf(
       trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
-      trace_analyzer::Query::String("whitewashed"));
+      trace_analyzer::Query::String("event2"));
   EXPECT_FALSE(trace_event->HasArg("int_two"));
   trace_event = trace_analyzer_->FindFirstOf(
       trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
-      trace_analyzer::Query::String("ScopedBlockingCallTest"));
+      trace_analyzer::Query::String("event3"));
   EXPECT_EQ("whitelisted_value",
-            trace_event->GetKnownArgAsString(("file_name")));
-  EXPECT_EQ("__stripped__", trace_event->GetKnownArgAsString(("file_number")));
+            trace_event->GetKnownArgAsString(("granular_arg_whitelisted")));
+  EXPECT_EQ("__stripped__",
+            trace_event->GetKnownArgAsString(("granular_arg_blacklisted")));
 }
 
 TEST_F(JsonTraceExporterTest, TestFtraceLegacyOutput) {
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator.cc b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
index e4f43527..20156e4 100644
--- a/services/tracing/perfetto/perfetto_tracing_coordinator.cc
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
@@ -11,10 +11,12 @@
 #include "base/bind.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/trace_event/trace_log.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "services/tracing/perfetto/chrome_event_bundle_json_exporter.h"
 #include "services/tracing/perfetto/perfetto_service.h"
+#include "services/tracing/public/cpp/trace_event_args_whitelist.h"
 #include "services/tracing/public/mojom/constants.mojom.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/consumer.h"
@@ -32,12 +34,27 @@
 // vs. proto).
 class PerfettoTracingCoordinator::TracingSession : public perfetto::Consumer {
  public:
-  TracingSession(const std::string& config,
+  TracingSession(const base::trace_event::TraceConfig& chrome_config,
                  base::OnceClosure tracing_over_callback)
       : tracing_over_callback_(std::move(tracing_over_callback)) {
-    base::trace_event::TraceConfig chrome_trace_config_obj(config);
+    // In legacy backend, the trace event agent sets the predicate used by
+    // TraceLog. For perfetto backend, ensure that predicate is always set
+    // before creating the exporter. The agent can be created later than this
+    // point.
+    if (base::trace_event::TraceLog::GetInstance()
+            ->GetArgumentFilterPredicate()
+            .is_null()) {
+      base::trace_event::TraceLog::GetInstance()->SetArgumentFilterPredicate(
+          base::BindRepeating(&IsTraceEventArgsWhitelisted));
+      base::trace_event::TraceLog::GetInstance()->SetMetadataFilterPredicate(
+          base::BindRepeating(&IsMetadataWhitelisted));
+    }
+
     json_trace_exporter_ = std::make_unique<ChromeEventBundleJsonExporter>(
-        chrome_trace_config_obj.IsArgumentFilterEnabled(),
+        chrome_config.IsArgumentFilterEnabled()
+            ? base::trace_event::TraceLog::GetInstance()
+                  ->GetArgumentFilterPredicate()
+            : JSONTraceExporter::ArgumentFilterPredicate(),
         base::BindRepeating(&TracingSession::OnJSONTraceEventCallback,
                             base::Unretained(this)));
     perfetto::TracingService* service =
@@ -45,55 +62,8 @@
     consumer_endpoint_ = service->ConnectConsumer(this, /*uid=*/0);
 
     // Start tracing.
-    perfetto::TraceConfig trace_config;
-    size_t size_limit = chrome_trace_config_obj.GetTraceBufferSizeInKb();
-    if (size_limit == 0) {
-      size_limit = 100 * 1024;
-    }
-    trace_config.add_buffers()->set_size_kb(size_limit);
-
-    // Perfetto uses clock_gettime for its internal snapshotting, which gets
-    // blocked by the sandboxed and isn't needed for Chrome regardless.
-    trace_config.set_disable_clock_snapshotting(true);
-
-    auto* trace_event_data_source = trace_config.add_data_sources();
-    for (auto& enabled_pid : chrome_trace_config_obj.process_filter_config()
-                                 .included_process_ids()) {
-      *trace_event_data_source->add_producer_name_filter() =
-          base::StrCat({mojom::kPerfettoProducerName, ".",
-                        base::NumberToString(enabled_pid)});
-    }
-
-    auto* trace_event_config = trace_event_data_source->mutable_config();
-    trace_event_config->set_name(mojom::kTraceEventDataSourceName);
-    trace_event_config->set_target_buffer(0);
-    auto* chrome_config = trace_event_config->mutable_chrome_config();
-    chrome_config->set_trace_config(config);
-
-// Only CrOS and Cast support system tracing.
-#if defined(OS_CHROMEOS) || (defined(IS_CHROMECAST) && defined(OS_LINUX))
-    auto* system_trace_config =
-        trace_config.add_data_sources()->mutable_config();
-    system_trace_config->set_name(mojom::kSystemTraceDataSourceName);
-    system_trace_config->set_target_buffer(0);
-    auto* system_chrome_config = system_trace_config->mutable_chrome_config();
-    system_chrome_config->set_trace_config(config);
-#endif
-
-#if defined(OS_CHROMEOS)
-    auto* arc_trace_config = trace_config.add_data_sources()->mutable_config();
-    arc_trace_config->set_name(mojom::kArcTraceDataSourceName);
-    arc_trace_config->set_target_buffer(0);
-    auto* arc_chrome_config = arc_trace_config->mutable_chrome_config();
-    arc_chrome_config->set_trace_config(config);
-#endif
-
-    auto* trace_metadata_config =
-        trace_config.add_data_sources()->mutable_config();
-    trace_metadata_config->set_name(mojom::kMetaDataSourceName);
-    trace_metadata_config->set_target_buffer(0);
-
-    consumer_endpoint_->EnableTracing(trace_config);
+    auto perfetto_config = CreatePerfettoConfiguration(chrome_config);
+    consumer_endpoint_->EnableTracing(perfetto_config);
   }
 
   ~TracingSession() override {
@@ -105,6 +75,86 @@
     stream_.reset();
   }
 
+  void ChangeTraceConfig(const base::trace_event::TraceConfig& chrome_config) {
+    auto perfetto_config = CreatePerfettoConfiguration(chrome_config);
+    consumer_endpoint_->ChangeTraceConfig(perfetto_config);
+  }
+
+  perfetto::TraceConfig CreatePerfettoConfiguration(
+      const base::trace_event::TraceConfig& chrome_config) {
+    perfetto::TraceConfig perfetto_config;
+    size_t size_limit = chrome_config.GetTraceBufferSizeInKb();
+    if (size_limit == 0) {
+      size_limit = 100 * 1024;
+    }
+    perfetto_config.add_buffers()->set_size_kb(size_limit);
+
+    // Perfetto uses clock_gettime for its internal snapshotting, which gets
+    // blocked by the sandboxed and isn't needed for Chrome regardless.
+    perfetto_config.set_disable_clock_snapshotting(true);
+
+    auto* trace_event_data_source = perfetto_config.add_data_sources();
+    for (auto& enabled_pid :
+         chrome_config.process_filter_config().included_process_ids()) {
+      *trace_event_data_source->add_producer_name_filter() =
+          base::StrCat({mojom::kPerfettoProducerName, ".",
+                        base::NumberToString(enabled_pid)});
+    }
+
+    // We strip the process filter from the config string we send to Perfetto,
+    // so perfetto doesn't reject it from a future
+    // TracingService::ChangeTraceConfig call due to being an unsupported
+    // update.
+    base::trace_event::TraceConfig processfilter_stripped_config(chrome_config);
+    processfilter_stripped_config.SetProcessFilterConfig(
+        base::trace_event::TraceConfig::ProcessFilterConfig());
+
+#if DCHECK_IS_ON()
+    // Ensure that the process filter is the only thing that gets changed
+    // in a configuration during a tracing session.
+    DCHECK((last_config_for_perfetto_.ToString() ==
+            base::trace_event::TraceConfig().ToString()) ||
+           (last_config_for_perfetto_.ToString() ==
+            processfilter_stripped_config.ToString()));
+    last_config_for_perfetto_ = processfilter_stripped_config;
+#endif
+
+    auto* trace_event_config = trace_event_data_source->mutable_config();
+    trace_event_config->set_name(mojom::kTraceEventDataSourceName);
+    trace_event_config->set_target_buffer(0);
+    auto* chrome_proto_config = trace_event_config->mutable_chrome_config();
+    chrome_proto_config->set_trace_config(
+        processfilter_stripped_config.ToString());
+
+// Only CrOS and Cast support system tracing.
+#if defined(OS_CHROMEOS) || (defined(IS_CHROMECAST) && defined(OS_LINUX))
+    auto* system_trace_config =
+        perfetto_config.add_data_sources()->mutable_config();
+    system_trace_config->set_name(mojom::kSystemTraceDataSourceName);
+    system_trace_config->set_target_buffer(0);
+    auto* system_chrome_config = system_trace_config->mutable_chrome_config();
+    system_chrome_config->set_trace_config(
+        processfilter_stripped_config.ToString());
+#endif
+
+#if defined(OS_CHROMEOS)
+    auto* arc_trace_config =
+        perfetto_config.add_data_sources()->mutable_config();
+    arc_trace_config->set_name(mojom::kArcTraceDataSourceName);
+    arc_trace_config->set_target_buffer(0);
+    auto* arc_chrome_config = arc_trace_config->mutable_chrome_config();
+    arc_chrome_config->set_trace_config(
+        processfilter_stripped_config.ToString());
+#endif
+
+    auto* trace_metadata_config =
+        perfetto_config.add_data_sources()->mutable_config();
+    trace_metadata_config->set_name(mojom::kMetaDataSourceName);
+    trace_metadata_config->set_target_buffer(0);
+
+    return perfetto_config;
+  }
+
   void StopAndFlush(mojo::ScopedDataPipeProducerHandle stream,
                     const std::string& agent_label,
                     StopAndFlushCallback callback) {
@@ -191,6 +241,10 @@
   base::OnceClosure tracing_over_callback_;
   RequestBufferUsageCallback request_buffer_usage_callback_;
 
+#if DCHECK_IS_ON()
+  base::trace_event::TraceConfig last_config_for_perfetto_;
+#endif
+
   // Keep last to avoid edge-cases where its callbacks come in mid-destruction.
   std::unique_ptr<perfetto::TracingService::ConsumerEndpoint>
       consumer_endpoint_;
@@ -231,9 +285,14 @@
                                               StartTracingCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   parsed_config_ = base::trace_event::TraceConfig(config);
-  tracing_session_ = std::make_unique<TracingSession>(
-      config, base::BindOnce(&PerfettoTracingCoordinator::OnTracingOverCallback,
-                             weak_factory_.GetWeakPtr()));
+  if (!tracing_session_) {
+    tracing_session_ = std::make_unique<TracingSession>(
+        parsed_config_,
+        base::BindOnce(&PerfettoTracingCoordinator::OnTracingOverCallback,
+                       weak_factory_.GetWeakPtr()));
+  } else {
+    tracing_session_->ChangeTraceConfig(parsed_config_);
+  }
 
   agent_registry_->SetAgentInitializationCallback(
       base::BindRepeating(&PerfettoTracingCoordinator::PingAgent,
diff --git a/services/ws/public/mojom/window_manager.mojom b/services/ws/public/mojom/window_manager.mojom
index a49223c3..9b9e0df 100644
--- a/services/ws/public/mojom/window_manager.mojom
+++ b/services/ws/public/mojom/window_manager.mojom
@@ -63,8 +63,11 @@
   // A boolean determining whether animations are disabled for the window.
   const string kAnimationsDisabled_Property = "prop:animations-disabled";
 
-  // The application icon; typically larger for shelf icons, etc. Type: SkBitmap
-  const string kAppIcon_Property = "prop:app-icon";
+  // A large version of the application icon. Type: SkBitmap
+  const string kAppIconLarge_Property = "prop:app-icon-large";
+
+  // A small version of the application icon. Type: SkBitmap
+  const string kAppIconSmall_Property = "prop:app-icon-small";
 
   // The Android Java-style package name for an ARC++ window, such as
   // "com.google.Photos". Type: mojom::String.
@@ -153,9 +156,6 @@
   // aura::client::kWindowCornerRadiusKey. Type: int.
   const string kWindowCornerRadius_Property = "prop:window-corner-radius";
 
-  // The window icon; typically 16x16 for titlebars. Type: SkBitmap
-  const string kWindowIcon_Property = "prop:window-icon";
-
   // The window's title. Maps to aura::client::kTitleKey. Type: mojom::String
   const string kWindowTitle_Property = "prop:window-title";
 
diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc
index 49d9ade..80bbbf44 100644
--- a/storage/browser/quota/quota_manager.cc
+++ b/storage/browser/quota/quota_manager.cc
@@ -87,25 +87,6 @@
   return type == StorageType::kTemporary || type == StorageType::kPersistent;
 }
 
-void CountOriginType(const std::set<url::Origin>& origins,
-                     SpecialStoragePolicy* policy,
-                     size_t* protected_origins,
-                     size_t* unlimited_origins) {
-  DCHECK(protected_origins);
-  DCHECK(unlimited_origins);
-  *protected_origins = 0;
-  *unlimited_origins = 0;
-  if (!policy)
-    return;
-  for (const auto& origin : origins) {
-    const GURL url = origin.GetURL();
-    if (policy->IsStorageProtected(url))
-      ++*protected_origins;
-    if (policy->IsStorageUnlimited(url))
-      ++*unlimited_origins;
-  }
-}
-
 bool GetPersistentHostQuotaOnDBThread(const std::string& host,
                                       int64_t* quota,
                                       QuotaDatabase* database) {
@@ -1495,22 +1476,6 @@
   UMA_HISTOGRAM_PERCENTAGE("Quota.PercentUsedForTemporaryStorage2",
                            static_cast<int>((usage * 100) / total_space));
 
-  std::set<url::Origin> origins;
-  GetCachedOrigins(StorageType::kTemporary, &origins);
-
-  size_t num_origins = origins.size();
-  size_t protected_origins = 0;
-  size_t unlimited_origins = 0;
-  CountOriginType(origins,
-                  special_storage_policy_.get(),
-                  &protected_origins,
-                  &unlimited_origins);
-  UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfTemporaryStorageOrigins", num_origins);
-  UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfProtectedTemporaryStorageOrigins",
-                          protected_origins);
-  UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfUnlimitedTemporaryStorageOrigins",
-                          unlimited_origins);
-
   GetGlobalUsage(
       StorageType::kPersistent,
       base::BindOnce(&QuotaManager::DidGetPersistentGlobalUsageForHistogram,
@@ -1523,23 +1488,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage);
 
-  std::set<url::Origin> origins;
-  GetCachedOrigins(StorageType::kPersistent, &origins);
-
-  size_t num_origins = origins.size();
-  size_t protected_origins = 0;
-  size_t unlimited_origins = 0;
-  CountOriginType(origins,
-                  special_storage_policy_.get(),
-                  &protected_origins,
-                  &unlimited_origins);
-  UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfPersistentStorageOrigins",
-                          num_origins);
-  UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfProtectedPersistentStorageOrigins",
-                          protected_origins);
-  UMA_HISTOGRAM_COUNTS_1M("Quota.NumberOfUnlimitedPersistentStorageOrigins",
-                          unlimited_origins);
-
   // We DumpOriginInfoTable last to ensure the trackers caches are loaded.
   DumpOriginInfoTable(
       base::BindOnce(&QuotaManager::DidDumpOriginInfoTableForHistogram,
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc
index 92253025..1aca6fe8 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor.cc
@@ -34,7 +34,6 @@
 QuotaTemporaryStorageEvictor::EvictionRoundStatistics::EvictionRoundStatistics()
     : in_round(false),
       is_initialized(false),
-      usage_overage_at_round(-1),
       diskspace_shortage_at_round(-1),
       usage_on_beginning_of_round(-1),
       usage_on_end_of_round(-1),
@@ -83,8 +82,6 @@
   if (!time_of_end_of_last_round_.is_null())
     UMA_HISTOGRAM_MINUTES("Quota.TimeDeltaOfEvictionRounds",
                           now - time_of_end_of_last_round_);
-  UMA_HISTOGRAM_MBYTES("Quota.UsageOverageOfTemporaryGlobalStorage",
-                       round_statistics_.usage_overage_at_round);
   UMA_HISTOGRAM_MBYTES("Quota.DiskspaceShortage",
                        round_statistics_.diskspace_shortage_at_round);
   UMA_HISTOGRAM_MBYTES("Quota.EvictedBytesPerRound",
@@ -100,10 +97,6 @@
   stats_in_hour.subtract_assign(previous_statistics_);
   previous_statistics_ = statistics_;
 
-  UMA_HISTOGRAM_COUNTS_1M("Quota.ErrorsOnEvictingOriginPerHour",
-                          stats_in_hour.num_errors_on_evicting_origin);
-  UMA_HISTOGRAM_COUNTS_1M("Quota.ErrorsOnGettingUsageAndQuotaPerHour",
-                          stats_in_hour.num_errors_on_getting_usage_and_quota);
   UMA_HISTOGRAM_COUNTS_1M("Quota.EvictedOriginsPerHour",
                           stats_in_hour.num_evicted_origins);
   UMA_HISTOGRAM_COUNTS_1M("Quota.EvictionRoundsPerHour",
@@ -199,7 +192,6 @@
   }
 
   if (!round_statistics_.is_initialized) {
-    round_statistics_.usage_overage_at_round = usage_overage;
     round_statistics_.diskspace_shortage_at_round = diskspace_shortage;
     round_statistics_.usage_on_beginning_of_round = current_usage;
     round_statistics_.is_initialized = true;
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h
index 4cfecb2c..73aa55c6 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.h
+++ b/storage/browser/quota/quota_temporary_storage_evictor.h
@@ -64,7 +64,6 @@
     bool is_initialized;
 
     base::Time start_time;
-    int64_t usage_overage_at_round;
     int64_t diskspace_shortage_at_round;
 
     int64_t usage_on_beginning_of_round;
diff --git a/testing/BUILD.gn b/testing/BUILD.gn
index 34cf492f..a5ccdd3 100644
--- a/testing/BUILD.gn
+++ b/testing/BUILD.gn
@@ -32,6 +32,7 @@
     "//testing/scripts/common.py",
     "//testing/scripts/run_performance_tests.py",
     "//tools/perf/generate_legacy_perf_dashboard_json.py",
+    "//tools/perf/core/results_merger.py",
   ]
 
   data_deps = [
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index 0f3b55d..8e3a348 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -563,6 +563,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -574,6 +575,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -585,6 +587,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -596,6 +599,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -607,6 +611,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -618,6 +623,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -629,6 +635,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -640,6 +647,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -651,6 +659,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -663,6 +672,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -674,6 +684,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -685,6 +696,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -696,6 +708,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ],
@@ -713,6 +726,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ],
@@ -725,6 +739,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -739,6 +754,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -750,6 +766,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -761,6 +778,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -772,6 +790,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -783,6 +802,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -794,6 +814,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -805,6 +826,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -816,6 +838,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -827,6 +850,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -838,6 +862,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -849,6 +874,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -860,6 +886,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ],
@@ -877,6 +904,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -888,6 +916,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -899,6 +928,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -910,6 +940,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -921,6 +952,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -932,6 +964,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -943,6 +976,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -954,6 +988,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -965,6 +1000,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -976,6 +1012,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -987,6 +1024,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -998,6 +1036,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1009,6 +1048,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1020,6 +1060,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1031,6 +1072,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1042,6 +1084,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1053,6 +1096,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1064,6 +1108,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1075,6 +1120,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1086,6 +1132,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1097,6 +1144,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1108,6 +1156,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1119,6 +1168,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1130,6 +1180,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "integrity": "high",
               "os": "Windows-10-15063"
             }
@@ -1142,6 +1193,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ],
@@ -1159,6 +1211,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1170,6 +1223,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1181,6 +1235,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1192,6 +1247,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1203,6 +1259,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1214,6 +1271,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1225,6 +1283,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1236,6 +1295,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1247,6 +1307,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1258,6 +1319,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1269,6 +1331,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1280,6 +1343,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1291,6 +1355,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1302,6 +1367,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1313,6 +1379,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1324,6 +1391,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1335,6 +1403,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1346,6 +1415,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1357,6 +1427,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1368,6 +1439,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1379,6 +1451,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1390,6 +1463,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "integrity": "high",
               "os": "Windows-10-15063"
             }
@@ -1402,6 +1476,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1413,6 +1488,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1424,6 +1500,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1435,6 +1512,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1446,6 +1524,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "integrity": "high",
               "os": "Windows-10-15063"
             }
@@ -1458,6 +1537,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1469,6 +1549,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1480,6 +1561,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1491,6 +1573,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1502,6 +1585,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1513,6 +1597,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1524,6 +1609,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1535,6 +1621,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1546,6 +1633,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1557,6 +1645,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1568,6 +1657,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1579,6 +1669,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1590,6 +1681,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1601,6 +1693,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1612,6 +1705,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1623,6 +1717,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1634,6 +1729,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1645,6 +1741,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1660,6 +1757,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1671,6 +1769,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1682,6 +1781,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1693,6 +1793,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1704,6 +1805,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1715,6 +1817,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1726,6 +1829,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1737,6 +1841,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1748,6 +1853,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1760,6 +1866,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1771,6 +1878,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1782,6 +1890,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1793,6 +1902,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1807,6 +1917,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1818,6 +1929,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1829,6 +1941,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1840,6 +1953,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1851,6 +1965,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1862,6 +1977,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1873,6 +1989,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1884,6 +2001,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1895,6 +2013,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1906,6 +2025,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1917,6 +2037,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1933,6 +2054,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1944,6 +2066,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1955,6 +2078,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1966,6 +2090,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1977,6 +2102,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1988,6 +2114,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -1999,6 +2126,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2010,6 +2138,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2021,6 +2150,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2032,6 +2162,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2043,6 +2174,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2054,6 +2186,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2065,6 +2198,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2076,6 +2210,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2087,6 +2222,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2098,6 +2234,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2109,6 +2246,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2120,6 +2258,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2131,6 +2270,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2142,6 +2282,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2153,6 +2294,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2164,6 +2306,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "integrity": "high",
               "os": "Windows-10-15063"
             }
@@ -2181,6 +2324,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2192,6 +2336,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2203,6 +2348,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2214,6 +2360,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2225,6 +2372,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2236,6 +2384,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2247,6 +2396,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2258,6 +2408,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2269,6 +2420,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2280,6 +2432,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2291,6 +2444,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2302,6 +2456,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2313,6 +2468,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2324,6 +2480,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2335,6 +2492,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2346,6 +2504,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2357,6 +2516,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2368,6 +2528,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2379,6 +2540,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2390,6 +2552,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2401,6 +2564,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2412,6 +2576,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "integrity": "high",
               "os": "Windows-10-15063"
             }
@@ -2424,6 +2589,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2435,6 +2601,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2446,6 +2613,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2457,6 +2625,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2468,6 +2637,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "integrity": "high",
               "os": "Windows-10-15063"
             }
@@ -2480,6 +2650,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2491,6 +2662,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2502,6 +2674,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2513,6 +2686,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2524,6 +2698,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2535,6 +2710,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2546,6 +2722,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2557,6 +2734,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2568,6 +2746,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2579,6 +2758,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2590,6 +2770,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2601,6 +2782,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2612,6 +2794,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2623,6 +2806,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2634,6 +2818,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2645,6 +2830,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -2656,6 +2842,7 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
+              "cpu": "x86-64",
               "os": "Windows-10-15063"
             }
           ]
@@ -15245,80 +15432,158 @@
     "gtest_tests": [
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "accessibility_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "angle_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "app_shell_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "aura_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "base_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_common_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_fuzzer_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_heap_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_platform_unittests"
       },
       {
         "name": "webkit_unit_tests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "boringssl_crypto_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "boringssl_ssl_tests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 10
         },
         "test": "browser_tests"
@@ -15331,13 +15596,25 @@
         "name": "webui_polymer1_browser_tests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 4
         },
         "test": "browser_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cacheinvalidation_unittests"
       },
@@ -15346,73 +15623,145 @@
           "--gtest_filter=-*UsingRealWebcam*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "capture_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cast_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cc_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_app_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_cleaner_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_elf_import_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_elf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chromedriver_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "components_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "components_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "compositor_unittests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 6
         },
         "test": "content_browsertests"
@@ -15424,139 +15773,277 @@
         ],
         "name": "perfetto_content_browsertests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "courgette_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "crashpad_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cronet_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cronet_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "crypto_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "device_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "display_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "elevation_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "events_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "extensions_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "extensions_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "filesystem_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gcm_unit_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gcp_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gfx_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gin_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "google_apis_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gpu_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "headless_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "headless_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "install_static_unittests"
       },
@@ -15565,7 +16052,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -15574,6 +16063,12 @@
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 3
         },
         "test": "interactive_ui_tests"
@@ -15585,121 +16080,241 @@
         ],
         "name": "webui_polymer1_interactive_ui_tests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "interactive_ui_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ipc_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "latency_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "leveldb_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "libjingle_xmpp_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_blink_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "message_center_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "midi_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "mojo_core_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "mojo_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "nacl_loader_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "native_theme_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "net_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "pdf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ppapi_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "printing_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "remoting_unittests"
       },
@@ -15708,7 +16323,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -15716,25 +16333,49 @@
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sbox_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sbox_validation_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "service_manager_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "services_unittests"
       },
@@ -15743,7 +16384,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -15751,109 +16394,217 @@
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "shell_dialogs_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "skia_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "snapshot_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sql_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "storage_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sync_integration_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "traffic_annotation_auditor_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ui_base_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ui_touch_selection_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "unit_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "url_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "views_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "viz_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "vr_common_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "vr_pixeltests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "wm_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "wtf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "zucchini_unittests"
       }
@@ -15863,80 +16614,158 @@
     "gtest_tests": [
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "accessibility_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "angle_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "app_shell_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "aura_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "base_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_common_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_fuzzer_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_heap_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_platform_unittests"
       },
       {
         "name": "webkit_unit_tests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "boringssl_crypto_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "boringssl_ssl_tests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 20
         },
         "test": "browser_tests"
@@ -15949,13 +16778,25 @@
         "name": "webui_polymer1_browser_tests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 4
         },
         "test": "browser_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cacheinvalidation_unittests"
       },
@@ -15964,73 +16805,145 @@
           "--gtest_filter=-*UsingRealWebcam*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "capture_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cast_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cc_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_app_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_cleaner_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_elf_import_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_elf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chromedriver_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "components_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "components_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "compositor_unittests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 6
         },
         "test": "content_browsertests"
@@ -16042,139 +16955,277 @@
         ],
         "name": "perfetto_content_browsertests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "courgette_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "crashpad_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cronet_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cronet_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "crypto_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "device_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "display_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "elevation_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "events_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "extensions_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "extensions_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "filesystem_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gcm_unit_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gcp_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gfx_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gin_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "google_apis_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gpu_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "headless_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "headless_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "install_static_unittests"
       },
@@ -16183,7 +17234,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -16192,6 +17245,12 @@
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 3
         },
         "test": "interactive_ui_tests"
@@ -16203,121 +17262,241 @@
         ],
         "name": "webui_polymer1_interactive_ui_tests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "interactive_ui_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ipc_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "latency_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "leveldb_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "libjingle_xmpp_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_blink_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "message_center_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "midi_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "mojo_core_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "mojo_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "nacl_loader_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "native_theme_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "net_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "pdf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ppapi_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "printing_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "remoting_unittests"
       },
@@ -16326,7 +17505,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -16334,25 +17515,49 @@
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sbox_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sbox_validation_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "service_manager_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "services_unittests"
       },
@@ -16361,7 +17566,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -16369,109 +17576,217 @@
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "shell_dialogs_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "skia_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "snapshot_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sql_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "storage_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sync_integration_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "traffic_annotation_auditor_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ui_base_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ui_touch_selection_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "unit_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "url_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "views_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "viz_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "vr_common_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "vr_pixeltests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "wm_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "wtf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "zucchini_unittests"
       }
@@ -16481,80 +17796,158 @@
     "gtest_tests": [
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "accessibility_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "angle_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "app_shell_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "aura_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "base_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_common_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_fuzzer_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_heap_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_platform_unittests"
       },
       {
         "name": "webkit_unit_tests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "blink_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "boringssl_crypto_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "boringssl_ssl_tests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 10
         },
         "test": "browser_tests"
@@ -16567,13 +17960,25 @@
         "name": "webui_polymer1_browser_tests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 4
         },
         "test": "browser_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cacheinvalidation_unittests"
       },
@@ -16582,73 +17987,145 @@
           "--gtest_filter=-*UsingRealWebcam*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "capture_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cast_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cc_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_app_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_cleaner_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_elf_import_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chrome_elf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "chromedriver_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "components_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "components_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "compositor_unittests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 6
         },
         "test": "content_browsertests"
@@ -16660,139 +18137,277 @@
         ],
         "name": "perfetto_content_browsertests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "courgette_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "crashpad_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cronet_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "cronet_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "crypto_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "device_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "display_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "elevation_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "events_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "extensions_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "extensions_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "filesystem_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gcm_unit_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gcp_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gfx_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gin_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "google_apis_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "gpu_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "headless_browsertests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "headless_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "install_static_unittests"
       },
@@ -16801,7 +18416,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -16810,6 +18427,12 @@
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ],
           "shards": 3
         },
         "test": "interactive_ui_tests"
@@ -16821,121 +18444,241 @@
         ],
         "name": "webui_polymer1_interactive_ui_tests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "interactive_ui_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ipc_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "latency_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "leveldb_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "libjingle_xmpp_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_blink_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_service_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "media_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "message_center_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "midi_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "mojo_core_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "mojo_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "nacl_loader_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "native_theme_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "net_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "pdf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ppapi_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "printing_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "remoting_unittests"
       },
@@ -16944,7 +18687,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -16952,25 +18697,49 @@
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sbox_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sbox_validation_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "service_manager_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "services_unittests"
       },
@@ -16979,7 +18748,9 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "integrity": "high"
+              "cpu": "x86-64",
+              "integrity": "high",
+              "os": "Windows-10-15063"
             }
           ]
         },
@@ -16987,109 +18758,217 @@
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "shell_dialogs_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "skia_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "snapshot_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sql_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "storage_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "sync_integration_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "traffic_annotation_auditor_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ui_base_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "ui_touch_selection_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "unit_tests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "url_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "views_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "viz_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "vr_common_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "vr_pixeltests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "wm_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "wtf_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "zucchini_unittests"
       }
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index b35b4e6..9ee9894 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -303,37 +303,6 @@
           "ignore_task_failure": false,
           "io_timeout": 3600
         }
-      },
-      {
-        "args": [
-          "--benchmarks=loading.desktop.network_service",
-          "-v",
-          "--upload-results",
-          "--output-format=histograms",
-          "--browser=release"
-        ],
-        "isolate_name": "performance_test_suite",
-        "merge": {
-          "script": "//tools/perf/process_perf_results.py"
-        },
-        "name": "performance_test_suite",
-        "override_compile_targets": [
-          "performance_test_suite"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de",
-              "id": "build186-b7",
-              "pool": "chrome.tests.perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 14400,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
       }
     ]
   }
diff --git a/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
index e116591..7b7bcf7d 100644
--- a/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
@@ -19,8 +19,11 @@
 # https://crbug.com/893575
 -org.chromium.android_webview.test.CookieManagerStartupTest.testStartup
 
-# https://crbug.com/893576
+# https://crbug.com/936317
+-org.chromium.android_webview.test.CookieManagerTest.testAcceptCookie_falseDoNotSendCookies
 -org.chromium.android_webview.test.CookieManagerTest.testAcceptCookie_falseWontSetCookies
+
+# https://crbug.com/893576
 -org.chromium.android_webview.test.CookieManagerTest.testAcceptFileSchemeCookies
 -org.chromium.android_webview.test.CookieManagerTest.testThirdPartyCookie
 
@@ -37,3 +40,11 @@
 -org.chromium.android_webview.test.AwProxyControllerTest.testValidInput
 -org.chromium.android_webview.test.AwProxyControllerTest.testInvalidProxyUrls
 -org.chromium.android_webview.test.AwProxyControllerTest.testInvalidBypassRules
+
+# Flaky tests on android_mojo and android_mojo_rel bots
+# https://crbug.com/936757, https://crbug.com/939355
+-org.chromium.android_webview.test.AwContentsClientFullScreenTest#testOnShowCustomViewAndPlayWithHtmlControl_videoInsideDiv
+-org.chromium.android_webview.test.AwContentsClientFullScreenTest#testPowerSaveBlockerIsEnabledDuringEmbeddedPlayback
+-org.chromium.android_webview.test.AwContentsClientFullScreenTest#testPowerSaveBlockerIsTransferredToEmbedded
+-org.chromium.android_webview.test.AwContentsClientFullScreenTest#testPowerSaveBlockerIsTransferredToFullscreen
+-org.chromium.android_webview.test.AwContentsClientFullScreenTest#testPowerSaveBlockerIsEnabledDuringFullscreenPlayback_videoInsideDiv
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index ef96db4..840723cd 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -707,28 +707,22 @@
         },
       },
       'CrWinAsan': {
+        'mixins': [
+          'x86-64',
+          'win10',
+        ],
         'test_suites': {
           'gtest_tests': 'chromium_win_gtests',
         },
-        'swarming': {
-          'dimension_sets': [
-            {
-              'os': 'Windows-10-15063',
-            },
-          ],
-        },
       },
       'CrWinAsan(dll)': {
+        'mixins': [
+          'x86-64',
+          'win10',
+        ],
         'test_suites': {
           'gtest_tests': 'chromium_win_gtests',
         },
-        'swarming': {
-          'dimension_sets': [
-            {
-              'os': 'Windows-10-15063',
-            },
-          ],
-        },
       },
       'ToTAndroid': {
         'additional_compile_targets': [
@@ -884,16 +878,28 @@
         },
       },
       'ToTWin64': {
+        'mixins': [
+          'x86-64',
+          'win10',
+        ],
         'test_suites': {
           'gtest_tests': 'chromium_win_gtests',
         },
       },
       'ToTWin64(dbg)': {
+        'mixins': [
+          'x86-64',
+          'win10',
+        ],
         'test_suites': {
           'gtest_tests': 'chromium_win_gtests',
         },
       },
       'ToTWin64(dll)': {
+        'mixins': [
+          'x86-64',
+          'win10',
+        ],
         'test_suites': {
           'gtest_tests': 'chromium_win_gtests',
         },
diff --git a/testing/scripts/run_flatbuffers_unittests.py b/testing/scripts/run_flatbuffers_unittests.py
index f0ead30..69436b43 100755
--- a/testing/scripts/run_flatbuffers_unittests.py
+++ b/testing/scripts/run_flatbuffers_unittests.py
@@ -40,15 +40,16 @@
     exe = os.path.join('.', 'flatbuffers_unittests')
 
   env = os.environ.copy()
+  failures = []
   with common.temporary_file() as tempfile_path:
     rc = xvfb.run_executable([exe], env, stdoutfile=tempfile_path)
 
     # The flatbuffer tests do not really conform to anything parsable, except
     # that they will succeed with "ALL TESTS PASSED".
     with open(tempfile_path) as f:
-      failures = f.read()
-      if failures == "ALL TESTS PASSED\n":
-        failures = []
+      output = f.read()
+      if output != "ALL TESTS PASSED\n":
+        failures = [output]
 
   with open(args.isolated_script_test_output, 'w') as fp:
     json.dump({'valid': True,'failures': failures}, fp)
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 4628d63d..0fe143d 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -33,7 +33,9 @@
 has to be passed in to the script so the script knows it is running
 an executable and not the run_benchmark command.
 
-The results of running the benchmark are put in separate directories per
+This script obeys the --isolated-script-test-output flag and merges test results
+from all the benchmarks into the one output.json file. The test results and perf
+results are also put in separate directories per
 benchmark. Two files will be present in each directory; perf_results.json, which
 is the perf specific results (with unenforced format, could be histogram or
 graph json), and test_results.json, which is a JSON test results
@@ -59,12 +61,15 @@
 
 CHROMIUM_SRC_DIR = os.path.abspath(
     os.path.join(os.path.dirname(__file__), '..', '..'))
-PERF_DIR = os.path.join(CHROMIUM_SRC_DIR, 'tools', 'perf')
-# Add src/tools/perf where generate_legacy_perf_dashboard_json.py lives
-sys.path.append(PERF_DIR)
 
+PERF_DIR = os.path.join(CHROMIUM_SRC_DIR, 'tools', 'perf')
+sys.path.append(PERF_DIR)
 import generate_legacy_perf_dashboard_json
 
+PERF_CORE_DIR = os.path.join(PERF_DIR, 'core')
+sys.path.append(PERF_CORE_DIR)
+import results_merger
+
 # Add src/testing/ into sys.path for importing xvfb and test_env.
 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
 import xvfb
@@ -394,6 +399,12 @@
   options = parse_arguments(args)
   isolated_out_dir = os.path.dirname(options.isolated_script_test_output)
   overall_return_code = 0
+  # This is a list of test results files to be merged into a standard
+  # output.json file for use by infrastructure including FindIt.
+  # This list should not contain reference build runs
+  # since we do not monitor those. Also, merging test reference build results
+  # with standard build results may not work properly.
+  test_results_files = []
 
   if options.non_telemetry:
     command_generator = GtestCommandGenerator(options)
@@ -407,6 +418,7 @@
     output_paths = OutputFilePaths(isolated_out_dir, benchmark_name).SetUp()
     overall_return_code = execute_gtest_perf_test(
         command_generator, output_paths, options.xvfb)
+    test_results_files.append(output_paths.test_results)
   else:
     # If the user has supplied a list of benchmark names, execute those instead
     # of using the shard map.
@@ -419,6 +431,7 @@
         return_code = execute_telemetry_benchmark(
             command_generator, output_paths, options.xvfb)
         overall_return_code = return_code or overall_return_code
+        test_results_files.append(output_paths.test_results)
       if options.run_ref_build:
         print ('Not running reference build. --run-ref-build argument is only '
                'supported for sharded benchmarks. It is simple to support '
@@ -458,6 +471,7 @@
         return_code = execute_telemetry_benchmark(
             command_generator, output_paths, options.xvfb)
         overall_return_code = return_code or overall_return_code
+        test_results_files.append(output_paths.test_results)
         if options.run_ref_build:
           reference_benchmark_foldername = benchmark + '.reference'
           reference_output_paths = OutputFilePaths(
@@ -465,7 +479,8 @@
           reference_command_generator = TelemetryCommandGenerator(
               benchmark, options,
               stories=stories, is_reference=True)
-          # We intentionally ignore the return code of the reference build.
+          # We intentionally ignore the return code and test results of the
+          # reference build.
           execute_telemetry_benchmark(
               reference_command_generator, reference_output_paths,
               options.xvfb)
@@ -473,6 +488,14 @@
       raise Exception('Telemetry tests must provide either a shard map or a '
                       '--benchmarks list so that we know which stories to run.')
 
+  test_results_list = []
+  for test_results_file in test_results_files:
+    with open(test_results_file, 'r') as fh:
+      test_results_list.append(json.load(fh))
+  merged_test_results = results_merger.merge_test_results(test_results_list)
+  with open(options.isolated_script_test_output, 'w') as f:
+    json.dump(merged_test_results, f)
+
   return overall_return_code
 
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 889e9113..dc64967 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -551,6 +551,26 @@
             ]
         }
     ],
+    "AutocompleteRetentionPolicy": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AutocompleteRetentionPolicyEnabled"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutofillDropdownLayout": [
         {
             "platforms": [
@@ -1275,6 +1295,9 @@
                     "name": "Enabled",
                     "enable_features": [
                         "CompressParkableStringsInForeground"
+                    ],
+                    "disable_features": [
+                        "CompressParkableStringsInBackground"
                     ]
                 }
             ]
@@ -3320,6 +3343,23 @@
             ]
         }
     ],
+    "OutOfBlinkCors": [
+        {
+            "platforms": [
+                "windows",
+                "mac",
+                "linux"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "OutOfBlinkCors"
+                    ]
+                }
+            ]
+        }
+    ],
     "OverflowIconsForMediaControls": [
         {
             "platforms": [
@@ -3940,7 +3980,7 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_Pool_Four_Fifths_Origin_ThreeQuarters",
+                    "name": "Enabled_Pool_Four_Fifths_Origin_ThreeQuarters_20190307",
                     "params": {
                         "PerHostRatio": "0.75",
                         "PoolSizeRatio": "0.8"
@@ -3982,6 +4022,24 @@
             ]
         }
     ],
+    "RemoveNTPFakebox": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "RemoveNTPFakebox",
+                    "enable_features": [
+                        "RemoveNtpFakebox"
+                    ]
+                }
+            ]
+        }
+    ],
     "RendererSchedulerWakeUpThrottling": [
         {
             "platforms": [
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 1de7942cc1..c1e8b6ca 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -42,7 +42,7 @@
 // Enable a new compositing mode called BlinkGenPropertyTrees where Blink
 // generates the compositor property trees. See: https://crbug.com/836884.
 const base::Feature kBlinkGenPropertyTrees{"BlinkGenPropertyTrees",
-                                           base::FEATURE_ENABLED_BY_DEFAULT};
+                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enable LayoutNG.
 const base::Feature kLayoutNG{"LayoutNG", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index d217061..5fbcafd 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2257,6 +2257,8 @@
   kCSSValueAppearanceTextFieldForTemporalRendered = 2823,
   kBuiltInModuleKvStorage = 2824,
   kBuiltInModuleVirtualScroller = 2825,
+  kAdClickNavigation = 2826,
+  kRTCStatsRelativePacketArrivalDelay = 2827,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/web/web_document_loader.h b/third_party/blink/public/web/web_document_loader.h
index 88b836a..771af52c 100644
--- a/third_party/blink/public/web/web_document_loader.h
+++ b/third_party/blink/public/web/web_document_loader.h
@@ -93,6 +93,9 @@
   virtual bool HasUnreachableURL() const = 0;
   virtual WebURL UnreachableURL() const = 0;
 
+  // The error code for loading an error page.
+  virtual int ErrorCode() const = 0;
+
   // Returns all redirects that occurred (both client and server) before
   // at last committing the current page.  This will contain one entry
   // for each intermediate URL, and one entry for the last URL (so if
diff --git a/third_party/blink/public/web/web_navigation_params.h b/third_party/blink/public/web/web_navigation_params.h
index 66c8584..9119816 100644
--- a/third_party/blink/public/web/web_navigation_params.h
+++ b/third_party/blink/public/web/web_navigation_params.h
@@ -152,7 +152,8 @@
       WebDocumentLoader* failed_document_loader,
       base::span<const char> html,
       const WebURL& base_url,
-      const WebURL& unreachable_url);
+      const WebURL& unreachable_url,
+      int error_code);
 
 #if INSIDE_BLINK
   // Shortcut for loading html with "text/html" mime type and "UTF8" encoding.
@@ -200,6 +201,10 @@
   // This URL can be retrieved through WebDocumentLoader::UnreachableURL.
   WebURL unreachable_url;
 
+  // The net error code for failed navigation. Must be non-zero when
+  // |unreachable_url| is non-null.
+  int error_code = 0;
+
   // This block defines the document content. The alternatives in the order
   // of precedence are:
   // 1. If |is_static_data| is false:
diff --git a/third_party/blink/renderer/bindings/core/v8/dictionary_helper_for_core.cc b/third_party/blink/renderer/bindings/core/v8/dictionary_helper_for_core.cc
index 37fa366..a0319ea7 100644
--- a/third_party/blink/renderer/bindings/core/v8/dictionary_helper_for_core.cc
+++ b/third_party/blink/renderer/bindings/core/v8/dictionary_helper_for_core.cc
@@ -144,29 +144,27 @@
 template <>
 bool DictionaryHelper::Get(const Dictionary& dictionary,
                            const StringView& key,
-                           unsigned& value) {
-  return GetNumericType<unsigned>(dictionary, key, value);
+                           uint32_t& value) {
+  return GetNumericType<uint32_t>(dictionary, key, value);
 }
 
 template <>
 bool DictionaryHelper::Get(const Dictionary& dictionary,
                            const StringView& key,
-                           unsigned long& value) {
+                           int64_t& value) {
   v8::Local<v8::Value> v8_value;
   if (!dictionary.Get(key, v8_value))
     return false;
 
-  int64_t int64_value;
-  if (!v8_value->IntegerValue(dictionary.V8Context()).To(&int64_value))
+  if (!v8_value->IntegerValue(dictionary.V8Context()).To(&value))
     return false;
-  value = static_cast<unsigned long>(int64_value);
   return true;
 }
 
 template <>
 bool DictionaryHelper::Get(const Dictionary& dictionary,
                            const StringView& key,
-                           unsigned long long& value) {
+                           uint64_t& value) {
   v8::Local<v8::Value> v8_value;
   if (!dictionary.Get(key, v8_value))
     return false;
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h b/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h
index 65f3e3f..43c91e2c 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h
@@ -372,7 +372,16 @@
 inline float ToFloat(v8::Isolate* isolate,
                      v8::Local<v8::Value> value,
                      ExceptionState& exception_state) {
-  return static_cast<float>(ToDouble(isolate, value, exception_state));
+  double double_value = ToDouble(isolate, value, exception_state);
+  // TODO(crbug.com/939598): These cases should throw a TypeError instead
+  // of returning Infinity; but before fixing that, we must ensure that
+  // ToFloat is not called in cases where ToDouble should be called instead.
+  using Limits = std::numeric_limits<float>;
+  if (UNLIKELY(double_value > Limits::max()))
+    return Limits::infinity();
+  if (UNLIKELY(double_value < Limits::lowest()))
+    return -Limits::infinity();
+  return static_cast<float>(double_value);
 }
 
 // Convert a value to a single precision float, throwing on non-finite values.
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
index 50183ba..4f261839 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
@@ -213,6 +213,10 @@
       V8CodeCache::SetCacheTimeStamp(cache_handler);
       break;
     case V8CodeCache::ProduceCacheOptions::kProduceCodeCache: {
+      // TODO(crbug.com/938269): Investigate why this can be empty here.
+      if (unbound_script.IsEmpty())
+        break;
+
       constexpr const char* kTraceEventCategoryGroup = "v8,devtools.timeline";
       TRACE_EVENT_BEGIN1(kTraceEventCategoryGroup, trace_name, "fileName",
                          source_url.GetString().Utf8());
diff --git a/third_party/blink/renderer/core/aom/accessible_node.cc b/third_party/blink/renderer/core/aom/accessible_node.cc
index d4b6f8a0..d940a04 100644
--- a/third_party/blink/renderer/core/aom/accessible_node.cc
+++ b/third_party/blink/renderer/core/aom/accessible_node.cc
@@ -285,9 +285,8 @@
   for (wtf_size_t i = 0; i < node_list->length(); ++i) {
     AccessibleNode* accessible_node = node_list->item(i);
     if (accessible_node) {
-      Element* element = accessible_node->element();
-      if (element)
-        targets.push_back(element);
+      if (Element* target_element = accessible_node->element())
+        targets.push_back(target_element);
     }
   }
 
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.cc b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
index a889c15..e295b58 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.cc
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
@@ -110,6 +110,16 @@
   clipboard_->CommitWrite(mojom::ClipboardBuffer::kStandard);
 }
 
+void SystemClipboard::WritePlainTextNoCommit(const String& plain_text,
+                                             SmartReplaceOption) {
+  // TODO(huangdarwin): add support for smart replace
+  String text = plain_text;
+#if defined(OS_WIN)
+  ReplaceNewlinesWithWindowsStyleNewlines(text);
+#endif
+  clipboard_->WriteText(mojom::ClipboardBuffer::kStandard, NonNullString(text));
+}
+
 String SystemClipboard::ReadHTML(KURL& url,
                                  unsigned& fragment_start,
                                  unsigned& fragment_end) {
@@ -191,9 +201,8 @@
   clipboard_->CommitWrite(mojom::ClipboardBuffer::kStandard);
 }
 
-void SystemClipboard::WriteImage(const SkBitmap& bitmap) {
+void SystemClipboard::WriteImageNoCommit(const SkBitmap& bitmap) {
   clipboard_->WriteImage(mojom::ClipboardBuffer::kStandard, bitmap);
-  clipboard_->CommitWrite(mojom::ClipboardBuffer::kStandard);
 }
 
 String SystemClipboard::ReadCustomData(const String& type) {
@@ -238,6 +247,10 @@
   clipboard_->CommitWrite(mojom::ClipboardBuffer::kStandard);
 }
 
+void SystemClipboard::CommitWrite() {
+  clipboard_->CommitWrite(mojom::ClipboardBuffer::kStandard);
+}
+
 bool SystemClipboard::IsValidBufferType(mojom::ClipboardBuffer buffer) {
   switch (buffer) {
     case mojom::ClipboardBuffer::kStandard:
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.h b/third_party/blink/renderer/core/clipboard/system_clipboard.h
index 091e1b6..caf484c 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.h
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.h
@@ -37,6 +37,8 @@
   String ReadPlainText();
   String ReadPlainText(mojom::ClipboardBuffer buffer);
   void WritePlainText(const String&, SmartReplaceOption = kCannotSmartReplace);
+  void WritePlainTextNoCommit(const String&,
+                              SmartReplaceOption = kCannotSmartReplace);
 
   // If no data is read, an empty string will be returned and all out parameters
   // will be cleared. If applicable, the page URL will be assigned to the KURL
@@ -56,11 +58,15 @@
   // Write the image and its associated tag (bookmark/HTML types).
   void WriteImageWithTag(Image*, const KURL&, const String& title);
   // Write the image only.
-  void WriteImage(const SkBitmap&);
+  void WriteImageNoCommit(const SkBitmap&);
 
   String ReadCustomData(const String& type);
   void WriteDataObject(DataObject*);
 
+  // Clipboard write functions that don't commit (explicitly labelled as
+  // NoCommit) must use CommitWrite for changes to reach the OS clipboard.
+  void CommitWrite();
+
  private:
   SystemClipboard();
   bool IsValidBufferType(mojom::ClipboardBuffer);
diff --git a/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h b/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h
index 4056e9c..b88eb6e 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h
@@ -67,14 +67,6 @@
   DISALLOW_COPY_AND_ASSIGN(CSSUnsupportedStyleValue);
 };
 
-DEFINE_TYPE_CASTS(CSSUnsupportedStyleValue,
-                  CSSStyleValue,
-                  value,
-                  value->GetType() ==
-                      CSSStyleValue::StyleValueType::kUnknownType,
-                  value.GetType() ==
-                      CSSStyleValue::StyleValueType::kUnknownType);
-
 template <>
 struct DowncastTraits<CSSUnsupportedStyleValue> {
   static bool AllowFrom(const CSSStyleValue& value) {
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index a60ac38..2a763d8f 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -2391,7 +2391,7 @@
     if (layout_object->HasLayer()) {
       ToLayoutBoxModelObject(layout_object)
           ->Layer()
-          ->SetNeeedsCompositingReasonsUpdate();
+          ->SetNeedsCompositingReasonsUpdate();
     }
   }
 }
diff --git a/third_party/blink/renderer/core/dom/range.cc b/third_party/blink/renderer/core/dom/range.cc
index 14317a5f..911fa657 100644
--- a/third_party/blink/renderer/core/dom/range.cc
+++ b/third_party/blink/renderer/core/dom/range.cc
@@ -1707,61 +1707,50 @@
     LayoutText* const layout_text = ToText(node)->GetLayoutObject();
     if (!layout_text)
       continue;
+
+    // TODO(editing-dev): Offset in |LayoutText| doesn't match to DOM offset
+    // when |text-transform| applied. We should map DOM offset to offset in
+    // |LayouText| for |start_offset| and |end_offset|.
+    const unsigned start_offset =
+        (node == start_container) ? start_.Offset() : 0;
+    const unsigned end_offset = (node == end_container)
+                                    ? end_.Offset()
+                                    : std::numeric_limits<unsigned>::max();
     if (!layout_text->IsTextFragment()) {
-      // TODO(editing-dev): Offset in |LayoutText| doesn't match to DOM offset
-      // when |text-transform| applied. We should map DOM offset to offset in
-      // |LayouText| for |start_offset| and |end_offset|.
-      const unsigned start_offset =
-          (node == start_container) ? start_.Offset() : 0;
-      const unsigned end_offset = (node == end_container)
-                                      ? end_.Offset()
-                                      : std::numeric_limits<unsigned>::max();
       quads.AppendVector(ComputeTextQuads(*owner_document_, *layout_text,
                                           start_offset, end_offset));
       continue;
     }
+
+    // Handle ::first-letter
     const LayoutTextFragment& first_letter_part =
         *ToLayoutTextFragment(AssociatedLayoutObjectOf(*node, 0));
+    const bool overlaps_with_first_letter =
+        start_offset < first_letter_part.FragmentLength() ||
+        (start_offset == first_letter_part.FragmentLength() &&
+         end_offset == start_offset);
+    if (overlaps_with_first_letter) {
+      const unsigned start_in_first_letter = start_offset;
+      const unsigned end_in_first_letter =
+          std::min(end_offset, first_letter_part.FragmentLength());
+      quads.AppendVector(ComputeTextQuads(*owner_document_, first_letter_part,
+                                          start_in_first_letter,
+                                          end_in_first_letter));
+    }
     const LayoutTextFragment& remaining_part =
         *ToLayoutTextFragment(layout_text);
-    // Set offsets in |LayoutTextFragment| to cover whole text in
-    // |LayoutTextFragment|.
-    unsigned first_letter_part_start = 0;
-    unsigned first_letter_part_end = first_letter_part.FragmentLength();
-    unsigned remaining_part_start = 0;
-    unsigned remaining_part_end = remaining_part.FragmentLength();
-    if (node == start_container) {
-      if (start_.Offset() < first_letter_part_end) {
-        // |this| range starts in first-letter part.
-        first_letter_part_start = start_.Offset();
-      } else {
-        first_letter_part_start = first_letter_part_end;
-        DCHECK_GE(static_cast<unsigned>(start_.Offset()),
-                  remaining_part.Start());
-        remaining_part_start = start_.Offset() - remaining_part.Start();
-      }
-    }
-    if (node == end_container) {
-      if (end_.Offset() <= first_letter_part_end) {
-        // |this| range ends in first-letter part.
-        first_letter_part_end = end_.Offset();
-        remaining_part_end = remaining_part_start;
-      } else {
-        DCHECK_GE(static_cast<unsigned>(end_.Offset()), remaining_part.Start());
-        remaining_part_end = end_.Offset() - remaining_part.Start();
-      }
-    }
-    DCHECK_LE(first_letter_part_start, first_letter_part_end);
-    DCHECK_LE(remaining_part_start, remaining_part_end);
-    if (first_letter_part_start < first_letter_part_end) {
-      quads.AppendVector(ComputeTextQuads(*owner_document_, first_letter_part,
-                                          first_letter_part_start,
-                                          first_letter_part_end));
-    }
-    if (remaining_part_start < remaining_part_end) {
+    if (end_offset > remaining_part.Start()) {
+      const unsigned start_in_remaining_part =
+          std::max(start_offset, remaining_part.Start()) -
+          remaining_part.Start();
+      // TODO(editing-dev): As we previously set |end_offset == UINT_MAX| as a
+      // hacky support for |text-transform|, we need the same hack here.
+      const unsigned end_in_remaining_part =
+          end_offset == UINT_MAX ? end_offset
+                                 : end_offset - remaining_part.Start();
       quads.AppendVector(ComputeTextQuads(*owner_document_, remaining_part,
-                                          remaining_part_start,
-                                          remaining_part_end));
+                                          start_in_remaining_part,
+                                          end_in_remaining_part));
     }
   }
 }
diff --git a/third_party/blink/renderer/core/dom/range_test.cc b/third_party/blink/renderer/core/dom/range_test.cc
index 187b7735..3564642 100644
--- a/third_party/blink/renderer/core/dom/range_test.cc
+++ b/third_party/blink/renderer/core/dom/range_test.cc
@@ -447,4 +447,57 @@
       << "Partial first-letter part and remaining part";
 }
 
+TEST_F(RangeTest, CollapsedRangeGetBorderAndTextQuadsWithFirstLetter) {
+  GetDocument().body()->SetInnerHTMLFromString(R"HTML(
+    <style>
+      body { font-size: 20px; }
+      #sample::first-letter { font-size: 500%; }
+    </style>
+    <p id=sample>abc</p>
+    <p id=expected><span style='font-size: 500%'>a</span>bc</p>
+  )HTML");
+  GetDocument().UpdateStyleAndLayout();
+
+  Element* const expected = GetDocument().getElementById("expected");
+  Element* const sample = GetDocument().getElementById("sample");
+
+  const Vector<FloatQuad> expected_quads =
+      GetBorderAndTextQuads(Position(expected, 0), Position(expected, 2));
+  const Vector<FloatQuad> sample_quads =
+      GetBorderAndTextQuads(Position(sample, 0), Position(sample, 1));
+  ASSERT_EQ(2u, sample_quads.size());
+  ASSERT_EQ(3u, expected_quads.size())
+      << "expected_quads has SPAN, SPAN.firstChild and P.lastChild";
+  EXPECT_EQ(expected_quads[0].EnclosingBoundingBox().Size(),
+            sample_quads[0].EnclosingBoundingBox().Size())
+      << "Check size of first-letter part";
+  EXPECT_EQ(expected_quads[2].EnclosingBoundingBox().Size(),
+            sample_quads[1].EnclosingBoundingBox().Size())
+      << "Check size of first-letter part";
+
+  EXPECT_EQ(ComputeSizesOfQuads(GetBorderAndTextQuads(
+                Position(expected->firstChild()->firstChild(), 0),
+                Position(expected->firstChild()->firstChild(), 0))),
+            ComputeSizesOfQuads(
+                GetBorderAndTextQuads(Position(sample->firstChild(), 0),
+                                      Position(sample->firstChild(), 0))))
+      << "Collapsed range before first-letter part";
+
+  EXPECT_EQ(ComputeSizesOfQuads(GetBorderAndTextQuads(
+                Position(expected->firstChild()->firstChild(), 1),
+                Position(expected->firstChild()->firstChild(), 1))),
+            ComputeSizesOfQuads(
+                GetBorderAndTextQuads(Position(sample->firstChild(), 1),
+                                      Position(sample->firstChild(), 1))))
+      << "Collapsed range after first-letter part";
+
+  EXPECT_EQ(ComputeSizesOfQuads(GetBorderAndTextQuads(
+                Position(expected->firstChild()->nextSibling(), 1),
+                Position(expected->firstChild()->nextSibling(), 1))),
+            ComputeSizesOfQuads(
+                GetBorderAndTextQuads(Position(sample->firstChild(), 2),
+                                      Position(sample->firstChild(), 2))))
+      << "Collapsed range in remaining text part";
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/editing/selection_controller.cc b/third_party/blink/renderer/core/editing/selection_controller.cc
index d3a2489a..acf54cc 100644
--- a/third_party/blink/renderer/core/editing/selection_controller.cc
+++ b/third_party/blink/renderer/core/editing/selection_controller.cc
@@ -546,10 +546,17 @@
                                          Selection().Granularity())
           : SelectionInFlatTree::Builder().Collapse(adjusted_position).Build();
 
+  // When |adjusted_selection| is caret, it's already canonical. No need to re-
+  // canonicalize it.
+  const SelectionInFlatTree new_visible_selection =
+      adjusted_selection.IsRange()
+          ? CreateVisibleSelection(adjusted_selection).AsSelection()
+          : adjusted_selection;
+
   const bool selection_is_directional =
       should_extend_selection ? Selection().IsDirectional() : false;
   SetNonDirectionalSelectionIfNeeded(
-      CreateVisibleSelection(adjusted_selection).AsSelection(),
+      new_visible_selection,
       SetSelectionOptions::Builder()
           .SetGranularity(Selection().Granularity())
           .SetIsDirectional(selection_is_directional)
diff --git a/third_party/blink/renderer/core/exported/web_document_loader_impl.cc b/third_party/blink/renderer/core/exported/web_document_loader_impl.cc
index b8c8d9b..12a49925 100644
--- a/third_party/blink/renderer/core/exported/web_document_loader_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_document_loader_impl.cc
@@ -91,6 +91,10 @@
   return DocumentLoader::UnreachableURL();
 }
 
+int WebDocumentLoaderImpl::ErrorCode() const {
+  return DocumentLoader::ErrorCode();
+}
+
 void WebDocumentLoaderImpl::RedirectChain(WebVector<WebURL>& result) const {
   result.Assign(redirect_chain_);
 }
diff --git a/third_party/blink/renderer/core/exported/web_document_loader_impl.h b/third_party/blink/renderer/core/exported/web_document_loader_impl.h
index 067d6af3..17a17c7a 100644
--- a/third_party/blink/renderer/core/exported/web_document_loader_impl.h
+++ b/third_party/blink/renderer/core/exported/web_document_loader_impl.h
@@ -67,6 +67,7 @@
   const WebURLResponse& GetResponse() const override;
   bool HasUnreachableURL() const override;
   WebURL UnreachableURL() const override;
+  int ErrorCode() const override;
   void RedirectChain(WebVector<WebURL>&) const override;
   bool IsClientRedirect() const override;
   bool ReplacesCurrentHistoryItem() const override;
diff --git a/third_party/blink/renderer/core/exported/web_navigation_params.cc b/third_party/blink/renderer/core/exported/web_navigation_params.cc
index b41b8a9..3c85a4c 100644
--- a/third_party/blink/renderer/core/exported/web_navigation_params.cc
+++ b/third_party/blink/renderer/core/exported/web_navigation_params.cc
@@ -57,9 +57,12 @@
     WebDocumentLoader* failed_document_loader,
     base::span<const char> html,
     const WebURL& base_url,
-    const WebURL& unreachable_url) {
+    const WebURL& unreachable_url,
+    int error_code) {
   auto result = WebNavigationParams::CreateWithHTMLString(html, base_url);
+  DCHECK(!unreachable_url.IsEmpty() || error_code != 0);
   result->unreachable_url = unreachable_url;
+  result->error_code = error_code;
   static_cast<WebDocumentLoaderImpl*>(failed_document_loader)
       ->FillNavigationParamsForErrorPage(result.get());
   return result;
diff --git a/third_party/blink/renderer/core/fileapi/file_reader_loader.cc b/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
index 1ccf2c0..69563a6 100644
--- a/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
+++ b/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
@@ -355,17 +355,18 @@
   while (true) {
     uint32_t num_bytes;
     const void* buffer;
-    MojoResult result = consumer_handle_->BeginReadData(
+    MojoResult pipe_result = consumer_handle_->BeginReadData(
         &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
-    if (result == MOJO_RESULT_SHOULD_WAIT) {
+    if (pipe_result == MOJO_RESULT_SHOULD_WAIT) {
       if (!IsSyncLoad())
         return;
 
-      result = mojo::Wait(consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE);
-      if (result == MOJO_RESULT_OK)
+      pipe_result =
+          mojo::Wait(consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE);
+      if (pipe_result == MOJO_RESULT_OK)
         continue;
     }
-    if (result == MOJO_RESULT_FAILED_PRECONDITION) {
+    if (pipe_result == MOJO_RESULT_FAILED_PRECONDITION) {
       // Pipe closed.
       if (!received_all_data_) {
         Failed(FileErrorCode::kNotReadableErr,
@@ -373,7 +374,7 @@
       }
       return;
     }
-    if (result != MOJO_RESULT_OK) {
+    if (pipe_result != MOJO_RESULT_OK) {
       Failed(FileErrorCode::kNotReadableErr,
              FailureType::kMojoPipeUnexpectedReadError);
       return;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index c0aecdf..c6cc53b 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1828,4 +1828,9 @@
   }
 }
 
+void LocalFrame::MaybeLogAdClickNavigation() {
+  if (HasTransientUserActivation() && IsAdSubframe())
+    UseCounter::Count(GetDocument(), WebFeature::kAdClickNavigation);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index cc997e0..072816b 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -437,6 +437,15 @@
 
   void SetLifecycleState(mojom::FrameLifecycleState state);
 
+  // For a navigation initiated from this LocalFrame with user gesture, record
+  // the UseCounter AdClickNavigation if this frame is an adframe.
+  //
+  // TODO(crbug.com/939370): Currently this is called in a couple of sites,
+  // which is fragile and prone to break. If we have the ad status in
+  // RemoteFrame, we could call it at FrameLoader::StartNavigation where all
+  // navigations go through.
+  void MaybeLogAdClickNavigation();
+
  private:
   friend class FrameNavigationDisabler;
 
diff --git a/third_party/blink/renderer/core/frame/location.cc b/third_party/blink/renderer/core/frame/location.cc
index 01384aaf..fb8f1fd0c 100644
--- a/third_party/blink/renderer/core/frame/location.cc
+++ b/third_party/blink/renderer/core/frame/location.cc
@@ -321,6 +321,8 @@
   WebFrameLoadType frame_load_type = WebFrameLoadType::kStandard;
   if (set_location_policy == SetLocationPolicy::kReplaceThisFrame)
     frame_load_type = WebFrameLoadType::kReplaceCurrentItem;
+
+  current_window->GetFrame()->MaybeLogAdClickNavigation();
   dom_window_->GetFrame()->ScheduleNavigation(*current_window->document(),
                                               completed_url, frame_load_type,
                                               UserGestureStatus::kNone);
diff --git a/third_party/blink/renderer/core/frame/settings.json5 b/third_party/blink/renderer/core/frame/settings.json5
index 030c1755..59c9d03 100644
--- a/third_party/blink/renderer/core/frame/settings.json5
+++ b/third_party/blink/renderer/core/frame/settings.json5
@@ -916,7 +916,7 @@
     },
     {
       name: "darkModePagePolicy",
-      initial: "DarkModePagePolicy::kFilterAll",
+      initial: "DarkModePagePolicy::kFilterByBackground",
       type: "DarkModePagePolicy",
       invalidate: "Paint",
     },
diff --git a/third_party/blink/renderer/core/html/html_anchor_element.cc b/third_party/blink/renderer/core/html/html_anchor_element.cc
index ab8734e..acfe7a2a 100644
--- a/third_party/blink/renderer/core/html/html_anchor_element.cc
+++ b/third_party/blink/renderer/core/html/html_anchor_element.cc
@@ -462,6 +462,8 @@
   frame_request.SetInputStartTime(event.PlatformTimeStamp());
   // TODO(japhet): Link clicks can be emulated via JS without a user gesture.
   // Why doesn't this go through NavigationScheduler?
+
+  frame->MaybeLogAdClickNavigation();
   frame->Loader().StartNavigation(frame_request, WebFrameLoadType::kStandard,
                                   NavigationPolicyFromEvent(&event));
 }
diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index 965e7a9..0ffee8d 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -1257,7 +1257,9 @@
     frame_->LocalFrameRoot().GetEventHandler().SetPointerCapture(pointer_id,
                                                                  target);
   } else {
-    pointer_event_manager_->SetPointerCapture(pointer_id, target);
+    if (pointer_event_manager_->SetPointerCapture(pointer_id, target) &&
+        pointer_id == PointerEventFactory::kMouseId)
+      CaptureMouseEventsToWidget(true);
   }
 }
 
@@ -1267,7 +1269,9 @@
     frame_->LocalFrameRoot().GetEventHandler().ReleasePointerCapture(pointer_id,
                                                                      target);
   } else {
-    pointer_event_manager_->ReleasePointerCapture(pointer_id, target);
+    if (pointer_event_manager_->ReleasePointerCapture(pointer_id, target) &&
+        pointer_id == PointerEventFactory::kMouseId)
+      CaptureMouseEventsToWidget(false);
   }
 }
 
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.h b/third_party/blink/renderer/core/input/mouse_event_manager.h
index 278f8c30..85e6dd06 100644
--- a/third_party/blink/renderer/core/input/mouse_event_manager.h
+++ b/third_party/blink/renderer/core/input/mouse_event_manager.h
@@ -43,7 +43,7 @@
   virtual ~MouseEventManager();
   void Trace(blink::Visitor*) override;
 
-  enum UpdateHoverReason { kScrollOffsetChanged, kLayoutOrStyleChanged };
+  enum class UpdateHoverReason { kScrollOffsetChanged, kLayoutOrStyleChanged };
 
   WebInputEventResult DispatchMouseEvent(EventTarget*,
                                          const AtomicString&,
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index 3b9b732..21f30c6 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -892,7 +892,7 @@
                                           target);
 }
 
-void PointerEventManager::SetPointerCapture(PointerId pointer_id,
+bool PointerEventManager::SetPointerCapture(PointerId pointer_id,
                                             Element* target) {
   UseCounter::Count(frame_->GetDocument(), WebFeature::kPointerEventSetCapture);
   if (pointer_event_factory_.IsActiveButtonsState(pointer_id)) {
@@ -901,10 +901,12 @@
                         WebFeature::kPointerEventSetCaptureOutsideDispatch);
     }
     pending_pointer_capture_target_.Set(pointer_id, target);
+    return true;
   }
+  return false;
 }
 
-void PointerEventManager::ReleasePointerCapture(PointerId pointer_id,
+bool PointerEventManager::ReleasePointerCapture(PointerId pointer_id,
                                                 Element* target) {
   // Only the element that is going to get the next pointer event can release
   // the capture. Note that this might be different from
@@ -913,8 +915,11 @@
   // but |m_pendingPointerCaptureTarget| indicated the element that gets the
   // very next pointer event. They will be the same if there was no change in
   // capturing of a particular |pointerId|. See crbug.com/614481.
-  if (pending_pointer_capture_target_.at(pointer_id) == target)
+  if (pending_pointer_capture_target_.at(pointer_id) == target) {
     ReleasePointerCapture(pointer_id);
+    return true;
+  }
+  return false;
 }
 
 void PointerEventManager::ReleaseMousePointerCapture() {
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.h b/third_party/blink/renderer/core/input/pointer_event_manager.h
index a6b8ba1c6..971f0d5 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.h
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.h
@@ -75,8 +75,8 @@
 
   void ElementRemoved(Element*);
 
-  void SetPointerCapture(PointerId, Element*);
-  void ReleasePointerCapture(PointerId, Element*);
+  bool SetPointerCapture(PointerId, Element*);
+  bool ReleasePointerCapture(PointerId, Element*);
   void ReleaseMousePointerCapture();
 
   // See Element::hasPointerCapture(PointerId).
diff --git a/third_party/blink/renderer/core/invisible_dom/invisible_dom.cc b/third_party/blink/renderer/core/invisible_dom/invisible_dom.cc
index 7a71cc10..dc13370 100644
--- a/third_party/blink/renderer/core/invisible_dom/invisible_dom.cc
+++ b/third_party/blink/renderer/core/invisible_dom/invisible_dom.cc
@@ -46,9 +46,9 @@
   for (Node& node : range.Nodes()) {
     if (!InvisibleDOM::IsInsideInvisibleSubtree(node))
       continue;
-    for (Node& node : FlatTreeTraversal::AncestorsOf(node)) {
-      if (node.IsElementNode()) {
-        elements_to_activate.push_back(ToElement(node));
+    for (Node& ancestor_node : FlatTreeTraversal::AncestorsOf(node)) {
+      if (ancestor_node.IsElementNode()) {
+        elements_to_activate.push_back(ToElement(ancestor_node));
         break;
       }
     }
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index f2da6f3a..85a22c1 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -1579,7 +1579,7 @@
 
     scoped_refptr<const ComputedStyle> child_style = child->Style();
     if (child->IsFloating() ||
-        (child->IsBox() && ToLayoutBox(child)->AvoidsFloats())) {
+        (child->IsBox() && ToLayoutBox(child)->CreatesNewFormattingContext())) {
       LayoutUnit float_total_width = float_left_width + float_right_width;
       EClear c = ResolvedClear(*child_style, style_to_use);
       if (c == EClear::kBoth || c == EClear::kLeft) {
@@ -1624,7 +1624,7 @@
     w = child_max_preferred_logical_width + margin;
 
     if (!child->IsFloating()) {
-      if (child->IsBox() && ToLayoutBox(child)->AvoidsFloats()) {
+      if (child->IsBox() && ToLayoutBox(child)->CreatesNewFormattingContext()) {
         // Determine a left and right max value based off whether or not the
         // floats can fit in the margins of the object. For negative margins, we
         // will attempt to overlap the float if the negative margin is smaller
diff --git a/third_party/blink/renderer/core/layout/layout_block.h b/third_party/blink/renderer/core/layout/layout_block.h
index 7294694..aa9a33e 100644
--- a/third_party/blink/renderer/core/layout/layout_block.h
+++ b/third_party/blink/renderer/core/layout/layout_block.h
@@ -138,8 +138,6 @@
   LayoutUnit MinLineHeightForReplacedObject(bool is_first_line,
                                             LayoutUnit replaced_height) const;
 
-  virtual bool CreatesNewFormattingContext() const { return true; }
-
   const char* GetName() const override;
 
  protected:
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.cc b/third_party/blink/renderer/core/layout/layout_block_flow.cc
index b34d3c4..9e2b1b33 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.cc
@@ -688,7 +688,7 @@
   LayoutUnit child_margin_start = MarginStartForChild(child);
   LayoutUnit new_position = start_position + child_margin_start;
 
-  if (child.AvoidsFloats() && ContainsFloats()) {
+  if (child.CreatesNewFormattingContext() && ContainsFloats()) {
     LayoutUnit position_to_avoid_floats = StartOffsetForAvoidingFloats(
         LogicalTopForChild(child), LogicalHeightForChild(child));
 
@@ -739,8 +739,8 @@
   // TODO(mstensho): rework the code to return early when there is no need for
   // marking, instead of this |markDescendantsWithFloats| flag.
   bool mark_descendants_with_floats = false;
-  if (new_logical_top != child.LogicalTop() && !child.AvoidsFloats() &&
-      child.ContainsFloats()) {
+  if (new_logical_top != child.LogicalTop() &&
+      !child.CreatesNewFormattingContext() && child.ContainsFloats()) {
     mark_descendants_with_floats = true;
   } else if (UNLIKELY(new_logical_top.MightBeSaturated())) {
     // The logical top might be saturated for very large elements. Comparing
@@ -748,7 +748,8 @@
     // removing margins, borders etc. from a saturated number might yield
     // incorrect results. If this is the case, always mark for layout.
     mark_descendants_with_floats = true;
-  } else if (!child.AvoidsFloats() || child.ShrinkToAvoidFloats()) {
+  } else if (!child.CreatesNewFormattingContext() ||
+             child.ShrinkToAvoidFloats()) {
     // If an element might be affected by the presence of floats, then always
     // mark it for layout.
     LayoutUnit lowest_float =
@@ -1349,7 +1350,7 @@
 
   // Inline blocks are covered by the isAtomicInlineLevel() check in the
   // avoidFloats method.
-  if (AvoidsFloats() || IsDocumentElement() || IsLayoutView() ||
+  if (CreatesNewFormattingContext() || IsDocumentElement() || IsLayoutView() ||
       IsFloatingOrOutOfFlowPositioned() || IsTableCell()) {
     if (floating_objects_) {
       floating_objects_->Clear();
@@ -1384,7 +1385,6 @@
   bool sibling_float_may_intrude = false;
   LayoutObject* prev = PreviousSibling();
   while (prev && (!prev->IsBox() || !prev->IsLayoutBlock() ||
-                  ToLayoutBlock(prev)->AvoidsFloats() ||
                   ToLayoutBlock(prev)->CreatesNewFormattingContext())) {
     if (prev->IsFloating())
       sibling_float_may_intrude = true;
@@ -1947,7 +1947,8 @@
     bool logical_top_intrudes_into_float =
         logical_top < before_collapse_logical_top;
     if (logical_top_intrudes_into_float && ContainsFloats() &&
-        !child.AvoidsFloats() && LowestFloatLogicalBottom() > logical_top) {
+        !child.CreatesNewFormattingContext() &&
+        LowestFloatLogicalBottom() > logical_top) {
       child.SetNeedsLayoutAndFullPaintInvalidation(
           layout_invalidation_reason::kAncestorMarginCollapsing);
     }
@@ -2847,12 +2848,12 @@
   FloatingObjectSetIterator end = floating_object_set.end();
 
   for (LayoutObject* next = NextSibling(); next; next = next->NextSibling()) {
-    if (!next->IsLayoutBlockFlow() ||
-        (!float_to_remove && (next->IsFloatingOrOutOfFlowPositioned() ||
-                              ToLayoutBlockFlow(next)->AvoidsFloats())))
+    LayoutBlockFlow* next_block = ToLayoutBlockFlowOrNull(next);
+    if (!next_block ||
+        (!float_to_remove && (next_block->IsFloatingOrOutOfFlowPositioned() ||
+                              next_block->CreatesNewFormattingContext())))
       continue;
 
-    LayoutBlockFlow* next_block = ToLayoutBlockFlow(next);
     for (FloatingObjectSetIterator it = floating_object_set.begin(); it != end;
          ++it) {
       LayoutBox* floating_box = (*it)->GetLayoutObject();
@@ -2880,7 +2881,7 @@
   LayoutUnit result = clear != EClear::kNone
                           ? (logical_bottom - logical_top).ClampNegativeToZero()
                           : LayoutUnit();
-  if (!result && child->AvoidsFloats()) {
+  if (!result && child->CreatesNewFormattingContext()) {
     LayoutUnit new_logical_top = logical_top;
     LayoutRect border_box = child->BorderBoxRect();
     LayoutUnit child_logical_width_at_old_logical_top_offset =
@@ -2972,8 +2973,9 @@
 void LayoutBlockFlow::StyleWillChange(StyleDifference diff,
                                       const ComputedStyle& new_style) {
   const ComputedStyle* old_style = Style();
-  can_propagate_float_into_sibling_ =
-      old_style ? !IsFloatingOrOutOfFlowPositioned() && !AvoidsFloats() : false;
+  can_propagate_float_into_sibling_ = old_style &&
+                                      !IsFloatingOrOutOfFlowPositioned() &&
+                                      !CreatesNewFormattingContext();
   if (old_style && Parent() && diff.NeedsFullLayout() &&
       old_style->GetPosition() != new_style.GetPosition() && ContainsFloats() &&
       !IsFloating() && !IsOutOfFlowPositioned() &&
@@ -2995,7 +2997,7 @@
   // and clear all floats from its next sibling blocks that exist in our
   // floating objects list. See crbug.com/56299 and crbug.com/62875.
   bool can_propagate_float_into_sibling =
-      !IsFloatingOrOutOfFlowPositioned() && !AvoidsFloats();
+      !IsFloatingOrOutOfFlowPositioned() && !CreatesNewFormattingContext();
   bool sibling_float_propagation_changed =
       diff.NeedsFullLayout() && can_propagate_float_into_sibling_ &&
       !can_propagate_float_into_sibling && HasOverhangingFloats();
@@ -3966,9 +3968,10 @@
   if (child.LogicalBottom() <= old_logical_top) {
     LayoutObject* next = child.NextSibling();
     if (next && next->IsLayoutBlockFlow()) {
-      LayoutBlockFlow* nextBlock = ToLayoutBlockFlow(next);
-      if (!nextBlock->AvoidsFloats() || nextBlock->ShrinkToAvoidFloats())
-        nextBlock->MarkAllDescendantsWithFloatsForLayout();
+      LayoutBlockFlow* next_block = ToLayoutBlockFlow(next);
+      if (!next_block->CreatesNewFormattingContext() ||
+          next_block->ShrinkToAvoidFloats())
+        next_block->MarkAllDescendantsWithFloatsForLayout();
     }
   }
 
@@ -4030,7 +4033,7 @@
 void LayoutBlockFlow::AddIntrudingFloats(LayoutBlockFlow* prev,
                                          LayoutUnit logical_left_offset,
                                          LayoutUnit logical_top_offset) {
-  DCHECK(!AvoidsFloats());
+  DCHECK(!CreatesNewFormattingContext());
 
   // If we create our own block formatting context then our contents don't
   // interact with floats outside it, even those from our parent.
@@ -4474,11 +4477,10 @@
   if (IsRenderedLegend())
     return true;
 
-  return false;
-}
+  if (ShouldBeConsideredAsReplaced())
+    return true;
 
-bool LayoutBlockFlow::AvoidsFloats() const {
-  return ShouldBeConsideredAsReplaced() || CreatesNewFormattingContext();
+  return false;
 }
 
 void LayoutBlockFlow::MoveChildrenTo(LayoutBoxModelObject* to_box_model_object,
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.h b/third_party/blink/renderer/core/layout/layout_block_flow.h
index 52beaa4..9f500a39 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.h
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.h
@@ -361,7 +361,6 @@
   void PositionSpannerDescendant(LayoutMultiColumnSpannerPlaceholder& child);
 
   bool CreatesNewFormattingContext() const override;
-  bool AvoidsFloats() const final;
 
   using LayoutBoxModelObject::MoveChildrenTo;
   void MoveChildrenTo(LayoutBoxModelObject* to_box_model_object,
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 8584bb6..b5af813 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -3127,7 +3127,7 @@
       MinimumValueForLength(margin_end_length, container_width);
 
   LayoutUnit available_width = container_width;
-  if (AvoidsFloats() && containing_block->IsLayoutBlockFlow() &&
+  if (CreatesNewFormattingContext() && containing_block->IsLayoutBlockFlow() &&
       ToLayoutBlockFlow(containing_block)->ContainsFloats()) {
     available_width = ContainingBlockAvailableLineWidth();
     if (ShrinkToAvoidFloats() && available_width < container_width) {
@@ -5114,7 +5114,7 @@
 bool LayoutBox::ShrinkToAvoidFloats() const {
   // Floating objects don't shrink.  Objects that don't avoid floats don't
   // shrink.
-  if (IsInline() || !AvoidsFloats() || IsFloating())
+  if (IsInline() || !CreatesNewFormattingContext() || IsFloating())
     return false;
 
   // Only auto width objects can possibly shrink to avoid floats.
@@ -5143,10 +5143,6 @@
           IsHTMLImageElement(ToElement(node)));
 }
 
-bool LayoutBox::AvoidsFloats() const {
-  return true;
-}
-
 bool LayoutBox::HasNonCompositedScrollbars() const {
   if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
     if (scrollable_area->HasHorizontalScrollbar() &&
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index c7fc0ad..c0f5f8d 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -1203,7 +1203,7 @@
      // inline-block.
 
   bool ShrinkToAvoidFloats() const;
-  virtual bool AvoidsFloats() const;
+  virtual bool CreatesNewFormattingContext() const { return true; }
   bool ShouldBeConsideredAsReplaced() const;
 
   void UpdateFragmentationInfoForChild(LayoutBox&);
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 29169fd5..376712e 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -1128,18 +1128,19 @@
 }
 
 LayoutBlock* LayoutObject::ContainingBlock(AncestorSkipInfo* skip_info) const {
-  LayoutObject* object = Parent();
-  if (!object && IsLayoutScrollbarPart())
-    object = ToLayoutScrollbarPart(this)->GetScrollableArea()->GetLayoutBox();
   if (!IsTextOrSVGChild()) {
     if (style_->GetPosition() == EPosition::kFixed)
       return ContainingBlockForFixedPosition(skip_info);
     if (style_->GetPosition() == EPosition::kAbsolute)
       return ContainingBlockForAbsolutePosition(skip_info);
   }
+  LayoutObject* object;
   if (IsColumnSpanAll()) {
     object = SpannerPlaceholder()->ContainingBlock();
   } else {
+    object = Parent();
+    if (!object && IsLayoutScrollbarPart())
+      object = ToLayoutScrollbarPart(this)->GetScrollableArea()->GetLayoutBox();
     while (object && ((object->IsInline() && !object->IsAtomicInlineLevel()) ||
                       !object->IsLayoutBlock())) {
       if (skip_info)
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 71e57d4..705157a 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -500,6 +500,10 @@
     return ShouldApplyPaintContainment() && ShouldApplyLayoutContainment() &&
            ShouldApplyStyleContainment();
   }
+  inline bool ShouldApplyStrictContainment() const {
+    return ShouldApplyPaintContainment() && ShouldApplyLayoutContainment() &&
+           ShouldApplyStyleContainment() && ShouldApplySizeContainment();
+  }
 
  private:
   //////////////////////////////////////////
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.cc
index b8aa6f02..7ba19329 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.cc
@@ -231,7 +231,7 @@
 }
 
 scoped_refptr<ShapeResult> NGInlineItemSegments::ShapeText(
-    HarfBuzzShaper* shaper,
+    const HarfBuzzShaper* shaper,
     const Font* font,
     TextDirection direction,
     unsigned start_offset,
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.h
index 3d6e1c9..b22da6db 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_segment.h
@@ -138,7 +138,7 @@
                         unsigned item_index) const;
 
   // Shape runs in the range and return the concatenated |ShapeResult|.
-  scoped_refptr<ShapeResult> ShapeText(HarfBuzzShaper* shaper,
+  scoped_refptr<ShapeResult> ShapeText(const HarfBuzzShaper* shaper,
                                        const Font* font,
                                        TextDirection direction,
                                        unsigned start_offset,
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
index 951deac..41762634 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
@@ -1031,22 +1031,23 @@
 
   NGExclusionSpace empty_exclusion_space;
   NGPositionedFloatVector empty_leading_floats;
-  Vector<LayoutObject*> floats_for_min_max;
   NGLineLayoutOpportunity line_opportunity(available_inline_size);
   LayoutUnit result;
   LayoutUnit previous_floats_inline_size =
       input.float_left_inline_size + input.float_right_inline_size;
   DCHECK_GE(previous_floats_inline_size, 0);
-  NGLineBreaker line_breaker(
-      node, mode, space, line_opportunity, empty_leading_floats,
-      /* handled_leading_floats_index */ 0u,
-      /* break_token */ nullptr, &empty_exclusion_space,
-      input.percentage_resolution_block_size, &floats_for_min_max);
+  NGLineBreaker line_breaker(node, mode, space, line_opportunity,
+                             empty_leading_floats,
+                             /* handled_leading_floats_index */ 0u,
+                             /* break_token */ nullptr, &empty_exclusion_space);
+
+  Vector<LayoutObject*> floats_for_min_max;
   do {
     floats_for_min_max.Shrink(0);
 
     NGLineInfo line_info;
-    line_breaker.NextLine(&line_info);
+    line_breaker.NextLine(input.percentage_resolution_block_size,
+                          &floats_for_min_max, &line_info);
     if (line_info.Results().IsEmpty())
       break;
 
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
index 573869b..2137aff 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -77,19 +77,70 @@
          NeedsAccurateEndPosition(line_end_item);
 }
 
+inline void ComputeCanBreakAfter(NGInlineItemResult* item_result,
+                                 bool auto_wrap,
+                                 const LazyLineBreakIterator& break_iterator) {
+  item_result->can_break_after =
+      auto_wrap && break_iterator.IsBreakable(item_result->end_offset);
+}
+
+// To correctly determine if a float is allowed to be on the same line as its
+// content, we need to determine if it has any ancestors with inline-end
+// padding, border, or margin.
+// The inline-end size from all of these ancestors contribute to the "used
+// size" of the float, and may cause the float to be pushed down.
+LayoutUnit ComputeFloatAncestorInlineEndSize(const NGConstraintSpace& space,
+                                             const Vector<NGInlineItem>& items,
+                                             wtf_size_t item_index) {
+  LayoutUnit inline_end_size;
+  while (item_index < items.size()) {
+    const NGInlineItem& item = items[item_index++];
+
+    if (item.Type() == NGInlineItem::kCloseTag) {
+      if (item.HasEndEdge())
+        inline_end_size += ComputeInlineEndSize(space, item.Style());
+      continue;
+    }
+
+    // For this calculation, any open tag (even if its empty) stops this
+    // calculation, and allows the float to appear on the same line. E.g.
+    // <span style="padding-right: 20px;"><f></f><span></span></span>
+    //
+    // Any non-empty item also allows the float to be on the same line.
+    if (item.Type() == NGInlineItem::kOpenTag || !item.IsEmptyItem())
+      break;
+  }
+  return inline_end_size;
+}
+
+scoped_refptr<const NGPhysicalTextFragment> CreateHyphenFragment(
+    NGInlineNode node,
+    WritingMode writing_mode,
+    const NGInlineItem& item) {
+  DCHECK(item.Style());
+  const ComputedStyle& style = *item.Style();
+  TextDirection direction = style.Direction();
+  String hyphen_string = style.HyphenString();
+  HarfBuzzShaper shaper(hyphen_string);
+  scoped_refptr<ShapeResult> hyphen_result =
+      shaper.Shape(&style.GetFont(), direction);
+  NGTextFragmentBuilder builder(node, writing_mode);
+  builder.SetText(item.GetLayoutObject(), hyphen_string, &style,
+                  /* is_ellipsis_style */ false,
+                  ShapeResultView::Create(hyphen_result.get()));
+  return builder.ToTextFragment();
+}
+
 }  // namespace
 
-NGLineBreaker::NGLineBreaker(
-    NGInlineNode node,
-    NGLineBreakerMode mode,
-    const NGConstraintSpace& space,
-    const NGLineLayoutOpportunity& line_opportunity,
-    const NGPositionedFloatVector& leading_floats,
-    unsigned handled_leading_floats_index,
-    const NGInlineBreakToken* break_token,
-    NGExclusionSpace* exclusion_space,
-    LayoutUnit percentage_resolution_block_size_for_min_max,
-    Vector<LayoutObject*>* out_floats_for_min_max)
+NGLineBreaker::NGLineBreaker(NGInlineNode node,
+                             NGLineBreakerMode mode,
+                             const NGConstraintSpace& space,
+                             const NGLineLayoutOpportunity& line_opportunity,
+                             const NGPositionedFloatVector& leading_floats,
+                             unsigned handled_leading_floats_index,
+                             const NGInlineBreakToken* break_token,
+                             NGExclusionSpace* exclusion_space)
     : line_opportunity_(line_opportunity),
       node_(node),
       is_first_formatted_line_((!break_token || (!break_token->ItemIndex() &&
@@ -108,10 +159,7 @@
       spacing_(items_data_.text_content),
       leading_floats_(leading_floats),
       handled_leading_floats_index_(handled_leading_floats_index),
-      base_direction_(node_.BaseDirection()),
-      percentage_resolution_block_size_for_min_max_(
-          percentage_resolution_block_size_for_min_max),
-      out_floats_for_min_max_(out_floats_for_min_max) {
+      base_direction_(node_.BaseDirection()) {
   break_iterator_.SetBreakSpace(BreakSpaceType::kBeforeSpaceRun);
 
   if (break_token) {
@@ -143,37 +191,34 @@
 NGLineBreaker::~NGLineBreaker() = default;
 
 inline NGInlineItemResult* NGLineBreaker::AddItem(const NGInlineItem& item,
-                                                  unsigned end_offset) {
+                                                  unsigned end_offset,
+                                                  NGLineInfo* line_info) {
   DCHECK_LE(end_offset, item.EndOffset());
-  return &item_results_->emplace_back(&item, item_index_, offset_, end_offset,
-                                      break_anywhere_if_overflow_,
-                                      ShouldCreateLineBox(*item_results_),
-                                      HasUnpositionedFloats(*item_results_));
+  NGInlineItemResults* item_results = line_info->MutableResults();
+  return &item_results->emplace_back(
+      &item, item_index_, offset_, end_offset, break_anywhere_if_overflow_,
+      ShouldCreateLineBox(*item_results), HasUnpositionedFloats(*item_results));
 }
 
-inline NGInlineItemResult* NGLineBreaker::AddItem(const NGInlineItem& item) {
-  return AddItem(item, item.EndOffset());
+inline NGInlineItemResult* NGLineBreaker::AddItem(const NGInlineItem& item,
+                                                  NGLineInfo* line_info) {
+  return AddItem(item, item.EndOffset(), line_info);
 }
 
 void NGLineBreaker::SetLineEndFragment(
-    scoped_refptr<const NGPhysicalTextFragment> fragment) {
+    scoped_refptr<const NGPhysicalTextFragment> fragment,
+    NGLineInfo* line_info) {
   bool is_horizontal =
       IsHorizontalWritingMode(constraint_space_.GetWritingMode());
-  if (line_info_->LineEndFragment()) {
-    const NGPhysicalSize& size = line_info_->LineEndFragment()->Size();
+  if (line_info->LineEndFragment()) {
+    const NGPhysicalSize& size = line_info->LineEndFragment()->Size();
     position_ -= is_horizontal ? size.width : size.height;
   }
   if (fragment) {
     const NGPhysicalSize& size = fragment->Size();
     position_ += is_horizontal ? size.width : size.height;
   }
-  line_info_->SetLineEndFragment(std::move(fragment));
-}
-
-inline void NGLineBreaker::ComputeCanBreakAfter(
-    NGInlineItemResult* item_result) const {
-  item_result->can_break_after =
-      auto_wrap_ && break_iterator_.IsBreakable(item_result->end_offset);
+  line_info->SetLineEndFragment(std::move(fragment));
 }
 
 // Compute the base direction for bidi algorithm for this line.
@@ -195,10 +240,11 @@
 }
 
 // Initialize internal states for the next line.
-void NGLineBreaker::PrepareNextLine() {
+void NGLineBreaker::PrepareNextLine(NGLineInfo* line_info) {
   // NGLineInfo is not supposed to be re-used becase it's not much gain and to
   // avoid rare code path.
-  DCHECK(item_results_->IsEmpty());
+  NGInlineItemResults* item_results = line_info->MutableResults();
+  DCHECK(item_results->IsEmpty());
 
   if (item_index_) {
     // We're past the first line
@@ -208,44 +254,51 @@
     use_first_line_style_ = false;
   }
 
-  line_info_->SetStartOffset(offset_);
-  line_info_->SetLineStyle(node_, items_data_, use_first_line_style_);
+  line_info->SetStartOffset(offset_);
+  line_info->SetLineStyle(node_, items_data_, use_first_line_style_);
 
-  DCHECK(!line_info_->TextIndent());
-  if (line_info_->LineStyle().ShouldUseTextIndent(
+  DCHECK(!line_info->TextIndent());
+  if (line_info->LineStyle().ShouldUseTextIndent(
           is_first_formatted_line_, previous_line_had_forced_break_)) {
-    const Length& length = line_info_->LineStyle().TextIndent();
+    const Length& length = line_info->LineStyle().TextIndent();
     LayoutUnit maximum_value;
     // Ignore percentages (resolve to 0) when calculating min/max intrinsic
     // sizes.
     if (length.IsPercentOrCalc() && mode_ == NGLineBreakerMode::kContent)
       maximum_value = constraint_space_.AvailableSize().inline_size;
-    line_info_->SetTextIndent(MinimumValueForLength(length, maximum_value));
+    line_info->SetTextIndent(MinimumValueForLength(length, maximum_value));
   }
 
   // Set the initial style of this line from the break token. Example:
   //   <p>...<span>....</span></p>
   // When the line wraps in <span>, the 2nd line needs to start with the style
   // of the <span>.
-  SetCurrentStyle(current_style_ ? *current_style_ : line_info_->LineStyle());
+  SetCurrentStyle(current_style_ ? *current_style_ : line_info->LineStyle());
   ComputeBaseDirection();
-  line_info_->SetBaseDirection(base_direction_);
+  line_info->SetBaseDirection(base_direction_);
 
   // Use 'text-indent' as the initial position. This lets tab positions to align
   // regardless of 'text-indent'.
-  position_ = line_info_->TextIndent();
+  position_ = line_info->TextIndent();
 }
 
-void NGLineBreaker::NextLine(NGLineInfo* line_info) {
-  line_info_ = line_info;
-  item_results_ = line_info->MutableResults();
+void NGLineBreaker::NextLine(
+    LayoutUnit percentage_resolution_block_size_for_min_max,
+    Vector<LayoutObject*>* out_floats_for_min_max,
+    NGLineInfo* line_info) {
+  // out_floats_for_min_max is required for min/max and prohibited for regular
+  // content mode.
+  DCHECK(mode_ == NGLineBreakerMode::kContent || out_floats_for_min_max);
+  DCHECK(!(mode_ == NGLineBreakerMode::kContent && out_floats_for_min_max));
 
-  PrepareNextLine();
-  BreakLine();
-  RemoveTrailingCollapsibleSpace();
+  PrepareNextLine(line_info);
+  BreakLine(percentage_resolution_block_size_for_min_max,
+            out_floats_for_min_max, line_info);
+  RemoveTrailingCollapsibleSpace(line_info);
 
+  NGInlineItemResults* item_results = line_info->MutableResults();
 #if DCHECK_IS_ON()
-  for (const auto& result : *item_results_)
+  for (const auto& result : *item_results)
     result.CheckConsistency(mode_ == NGLineBreakerMode::kMinContent);
 #endif
 
@@ -257,25 +310,24 @@
   //
   // TODO(kojii): There are cases where we need to PlaceItems() without creating
   // line boxes. These cases need to be reviewed.
-  bool should_create_line_box =
-      ShouldCreateLineBox(*item_results_) ||
-      (has_list_marker_ && line_info_->IsLastLine()) ||
-      mode_ != NGLineBreakerMode::kContent;
+  bool should_create_line_box = ShouldCreateLineBox(*item_results) ||
+                                (has_list_marker_ && line_info->IsLastLine()) ||
+                                mode_ != NGLineBreakerMode::kContent;
 
   if (!should_create_line_box)
-    line_info_->SetIsEmptyLine();
-  line_info_->SetEndItemIndex(item_index_);
+    line_info->SetIsEmptyLine();
+  line_info->SetEndItemIndex(item_index_);
   DCHECK_NE(trailing_whitespace_, WhitespaceState::kUnknown);
   if (trailing_whitespace_ == WhitespaceState::kPreserved)
-    line_info_->SetHasTrailingSpaces();
+    line_info->SetHasTrailingSpaces();
 
-  ComputeLineLocation();
-
-  line_info_ = nullptr;
-  item_results_ = nullptr;
+  ComputeLineLocation(line_info);
 }
 
-void NGLineBreaker::BreakLine() {
+void NGLineBreaker::BreakLine(
+    LayoutUnit percentage_resolution_block_size_for_min_max,
+    Vector<LayoutObject*>* out_floats_for_min_max,
+    NGLineInfo* line_info) {
   const Vector<NGInlineItem>& items = Items();
   state_ = LineBreakState::kContinue;
   trailing_whitespace_ = WhitespaceState::kLeading;
@@ -285,78 +337,82 @@
     // |HandleOverflow| will rewind |item_index_|.
     if (state_ == LineBreakState::kContinue &&
         position_ > AvailableWidthToFit()) {
-      HandleOverflow();
+      HandleOverflow(line_info);
     }
 
     // If we reach at the end of the block, this is the last line.
     DCHECK_LE(item_index_, items.size());
     if (item_index_ == items.size()) {
-      line_info_->SetIsLastLine(true);
+      line_info->SetIsLastLine(true);
       return;
     }
 
+    NGInlineItemResults* item_results = line_info->MutableResults();
+
     // Handle trailable items first. These items may not be break before.
     // They (or part of them) may also overhang the available width.
     const NGInlineItem& item = items[item_index_];
     if (item.Type() == NGInlineItem::kText) {
-      HandleText(item);
+      HandleText(item, line_info);
 #if DCHECK_IS_ON()
-      if (!item_results_->IsEmpty())
-        item_results_->back().CheckConsistency(true);
+      if (!item_results->IsEmpty())
+        item_results->back().CheckConsistency(true);
 #endif
       continue;
     }
     if (item.Type() == NGInlineItem::kCloseTag) {
-      HandleCloseTag(item);
+      HandleCloseTag(item, line_info);
       continue;
     }
     if (item.Type() == NGInlineItem::kControl) {
-      HandleControlItem(item);
+      HandleControlItem(item, line_info);
       continue;
     }
     if (item.Type() == NGInlineItem::kFloating) {
-      HandleFloat(item);
+      HandleFloat(item, out_floats_for_min_max, line_info);
       continue;
     }
     if (item.Type() == NGInlineItem::kBidiControl) {
-      HandleBidiControlItem(item);
+      HandleBidiControlItem(item, line_info);
       continue;
     }
 
     // Items after this point are not trailable. Break at the earliest break
     // opportunity if we're trailing.
     if (state_ == LineBreakState::kTrailing &&
-        CanBreakAfterLast(*item_results_)) {
+        CanBreakAfterLast(*item_results)) {
       if (sticky_images_quirk_ && IsImage(item) &&
           (trailing_whitespace_ == WhitespaceState::kNone ||
            trailing_whitespace_ == WhitespaceState::kUnknown)) {
         // If this is an image that follows text that doesn't end with something
         // breakable, we cannot break between the two items.
-        HandleAtomicInline(item);
+        HandleAtomicInline(item, percentage_resolution_block_size_for_min_max,
+                           line_info);
         continue;
       }
 
-      line_info_->SetIsLastLine(false);
+      line_info->SetIsLastLine(false);
       return;
     }
 
     if (item.Type() == NGInlineItem::kAtomicInline) {
-      HandleAtomicInline(item);
+      HandleAtomicInline(item, percentage_resolution_block_size_for_min_max,
+                         line_info);
     } else if (item.Type() == NGInlineItem::kOpenTag) {
-      HandleOpenTag(item);
+      HandleOpenTag(item, line_info);
     } else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) {
-      AddItem(item);
+      AddItem(item, line_info);
       MoveToNextOf(item);
     } else if (item.Length()) {
       NOTREACHED();
       // For other items with text (e.g., bidi controls), use their text to
       // determine the break opportunity.
-      NGInlineItemResult* item_result = AddItem(item);
+      NGInlineItemResult* item_result = AddItem(item, line_info);
       item_result->can_break_after =
           break_iterator_.IsBreakable(item_result->end_offset);
       MoveToNextOf(item);
     } else if (item.Type() == NGInlineItem::kListMarker) {
-      NGInlineItemResult* item_result = AddItem(item);
+      NGInlineItemResult* item_result = AddItem(item, line_info);
       has_list_marker_ = true;
       DCHECK(!item_result->can_break_after);
       MoveToNextOf(item);
@@ -367,49 +423,36 @@
   }
 }
 
-// Re-compute the current position from NGLineInfo.
-// The current position is usually updated as NGLineBreaker builds
-// NGInlineItemResults. This function re-computes it when it was lost.
-void NGLineBreaker::UpdatePosition() {
-  position_ = line_info_->ComputeWidth();
-}
-
-void NGLineBreaker::ComputeLineLocation() const {
+void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
   // Negative margins can make the position negative, but the inline size is
   // always positive or 0.
   LayoutUnit available_width = AvailableWidth();
-  DCHECK_EQ(position_, line_info_->ComputeWidth());
+  DCHECK_EQ(position_, line_info->ComputeWidth());
 
-  line_info_->SetWidth(available_width, position_);
-  line_info_->SetBfcOffset(
+  line_info->SetWidth(available_width, position_);
+  line_info->SetBfcOffset(
       {line_opportunity_.line_left_offset, line_opportunity_.bfc_block_offset});
 }
 
-void NGLineBreaker::HandleText(const NGInlineItem& item) {
-  DCHECK_EQ(item.Type(), NGInlineItem::kText);
-  DCHECK(item.TextShapeResult());
-  HandleText(item, *item.TextShapeResult());
-}
-
 void NGLineBreaker::HandleText(const NGInlineItem& item,
-                               const ShapeResult& shape_result) {
+                               const ShapeResult& shape_result,
+                               NGLineInfo* line_info) {
   DCHECK(item.Type() == NGInlineItem::kText ||
          (item.Type() == NGInlineItem::kControl &&
           Text()[item.StartOffset()] == kTabulationCharacter));
-  DCHECK(&shape_result);
   DCHECK_EQ(auto_wrap_, item.Style()->AutoWrap());
 
   // If we're trailing, only trailing spaces can be included in this line.
   if (state_ == LineBreakState::kTrailing) {
-    if (CanBreakAfterLast(*item_results_))
-      return HandleTrailingSpaces(item, shape_result);
+    if (CanBreakAfterLast(*line_info->MutableResults()))
+      return HandleTrailingSpaces(item, shape_result, line_info);
     // When a run of preserved spaces are across items, |CanBreakAfterLast| is
     // false for between spaces. But we still need to handle them as trailing
     // spaces.
     const String& text = Text();
     if (auto_wrap_ && offset_ < text.length() &&
         IsBreakableSpace(text[offset_]))
-      return HandleTrailingSpaces(item, shape_result);
+      return HandleTrailingSpaces(item, shape_result, line_info);
   }
 
   // Skip leading collapsible spaces.
@@ -430,18 +473,20 @@
     // |trailing_whitespace_| will be updated as we read the text.
   }
 
-  NGInlineItemResult* item_result = AddItem(item);
+  NGInlineItemResult* item_result = AddItem(item, line_info);
   item_result->should_create_line_box = true;
 
   if (auto_wrap_) {
     if (mode_ == NGLineBreakerMode::kMinContent &&
-        HandleTextForFastMinContent(item_result, item, shape_result)) {
+        HandleTextForFastMinContent(item_result, item, shape_result,
+                                    line_info)) {
       return;
     }
 
     // Try to break inside of this text item.
     LayoutUnit available_width = AvailableWidthToFit();
-    BreakText(item_result, item, shape_result, available_width - position_);
+    BreakText(item_result, item, shape_result, available_width - position_,
+              line_info);
 
     if (item.IsSymbolMarker()) {
       LayoutUnit symbol_width = LayoutListMarker::WidthOfSymbol(*item.Style());
@@ -461,7 +506,7 @@
       if (item_result->end_offset < item.EndOffset()) {
         // The break point found, and text follows. Break here, after trailing
         // spaces.
-        return HandleTrailingSpaces(item, shape_result);
+        return HandleTrailingSpaces(item, shape_result, line_info);
       }
 
       // The break point found, but items that prohibit breaking before them may
@@ -469,7 +514,7 @@
       return;
     }
 
-    return HandleOverflow();
+    return HandleOverflow(line_info);
   }
 
   // Add the whole item if !auto_wrap. The previous line should not have wrapped
@@ -494,16 +539,9 @@
 
 void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
                               const NGInlineItem& item,
-                              LayoutUnit available_width) {
-  DCHECK_EQ(item.Type(), NGInlineItem::kText);
-  DCHECK(item.TextShapeResult());
-  BreakText(item_result, item, *item.TextShapeResult(), available_width);
-}
-
-void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
-                              const NGInlineItem& item,
                               const ShapeResult& item_shape_result,
-                              LayoutUnit available_width) {
+                              LayoutUnit available_width,
+                              NGLineInfo* line_info) {
   DCHECK(item.Type() == NGInlineItem::kText ||
          (item.Type() == NGInlineItem::kControl &&
           Text()[item.StartOffset()] == kTabulationCharacter));
@@ -537,13 +575,13 @@
 
   // Use kStartShouldBeSafe if at the beginning of a line.
   unsigned options = ShapingLineBreaker::kDefaultOptions;
-  if (item_result->start_offset != line_info_->StartOffset())
+  if (item_result->start_offset != line_info->StartOffset())
     options |= ShapingLineBreaker::kDontReshapeStart;
 
   // Reshaping between the last character and trailing spaces is needed only
   // when we need accurate end position, because kerning between trailing spaces
   // is not visible.
-  if (!NeedsAccurateEndPosition(*line_info_, item))
+  if (!NeedsAccurateEndPosition(*line_info, item))
     options |= ShapingLineBreaker::kDontReshapeEndIfAtSpace;
 
   // Use kNoResultIfOverflow if 'break-word' and we're trying to break normally
@@ -568,7 +606,10 @@
             result.break_offset - item_result->start_offset);
 
   if (result.is_hyphenated) {
-    AppendHyphen(item);
+    SetLineEndFragment(
+        CreateHyphenFragment(node_, constraint_space_.GetWritingMode(), item),
+        line_info);
+
     // TODO(kojii): Implement when adding a hyphen caused overflow.
     // crbug.com/714962: Should be removed when switched to NGPaint.
     item_result->text_end_effect = NGTextEndEffect::kHyphen;
@@ -619,10 +660,10 @@
 // The first word and the last word, "1" and "6" in the example above, are
 // handled in normal |HandleText()| because they may form a word with the
 // previous/next item.
-bool NGLineBreaker::HandleTextForFastMinContent(
-    NGInlineItemResult* item_result,
-    const NGInlineItem& item,
-    const ShapeResult& shape_result) {
+bool NGLineBreaker::HandleTextForFastMinContent(NGInlineItemResult* item_result,
+                                                const NGInlineItem& item,
+                                                const ShapeResult& shape_result,
+                                                NGLineInfo* line_info) {
   DCHECK_EQ(mode_, NGLineBreakerMode::kMinContent);
   DCHECK(auto_wrap_);
   DCHECK(item.Type() == NGInlineItem::kText ||
@@ -634,7 +675,7 @@
   // item. Fallback to |HandleText()|.
   unsigned start_offset = item_result->start_offset;
   DCHECK_LT(start_offset, item.EndOffset());
-  if (start_offset != line_info_->StartOffset() &&
+  if (start_offset != line_info->StartOffset() &&
       start_offset == item.StartOffset())
     return false;
   // If this is the last part of the text, it may form a word with the next
@@ -730,6 +771,7 @@
 // Compute a new ShapeResult for the specified end offset.
 // The end is re-shaped if it is not safe-to-break.
 scoped_refptr<ShapeResultView> NGLineBreaker::TruncateLineEndResult(
+    const NGLineInfo& line_info,
     const NGInlineItemResult& item_result,
     unsigned end_offset) {
   DCHECK(item_result.item);
@@ -744,7 +786,7 @@
   DCHECK(start_offset > source_result->StartIndex() ||
          end_offset < source_result->EndIndex());
 
-  if (!NeedsAccurateEndPosition(*line_info_, item)) {
+  if (!NeedsAccurateEndPosition(line_info, item)) {
     return ShapeResultView::Create(source_result, start_offset, end_offset);
   }
 
@@ -765,16 +807,18 @@
 
 // Update |ShapeResult| in |item_result| to match to its |start_offset| and
 // |end_offset|. The end is re-shaped if it is not safe-to-break.
-void NGLineBreaker::UpdateShapeResult(NGInlineItemResult* item_result) {
+void NGLineBreaker::UpdateShapeResult(const NGLineInfo& line_info,
+                                      NGInlineItemResult* item_result) {
   DCHECK(item_result);
   item_result->shape_result =
-      TruncateLineEndResult(*item_result, item_result->end_offset);
+      TruncateLineEndResult(line_info, *item_result, item_result->end_offset);
   DCHECK(item_result->shape_result);
   item_result->inline_size = item_result->shape_result->SnappedWidth();
 }
 
 void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item,
-                                         const ShapeResult& shape_result) {
+                                         const ShapeResult& shape_result,
+                                         NGLineInfo* line_info) {
   DCHECK(item.Type() == NGInlineItem::kText ||
          (item.Type() == NGInlineItem::kControl &&
           Text()[item.StartOffset()] == kTabulationCharacter));
@@ -801,8 +845,9 @@
     trailing_whitespace_ = WhitespaceState::kCollapsed;
 
     // Make the last item breakable after, even if it was nowrap.
-    DCHECK(!item_results_->IsEmpty());
-    item_results_->back().can_break_after = true;
+    NGInlineItemResults* item_results = line_info->MutableResults();
+    DCHECK(!item_results->IsEmpty());
+    item_results->back().can_break_after = true;
   } else {
     // Find the end of the run of space characters in this item.
     // Other white space characters (e.g., tab) are not included in this item.
@@ -815,14 +860,14 @@
       return;
     }
 
-    NGInlineItemResult* item_result = AddItem(item, end);
+    NGInlineItemResult* item_result = AddItem(item, end, line_info);
     item_result->has_only_trailing_spaces = true;
     item_result->shape_result = ShapeResultView::Create(&shape_result);
     if (item_result->start_offset == item.StartOffset() &&
         item_result->end_offset == item.EndOffset())
       item_result->inline_size = item_result->shape_result->SnappedWidth();
     else
-      UpdateShapeResult(item_result);
+      UpdateShapeResult(*line_info, item_result);
     position_ += item_result->inline_size;
     item_result->can_break_after =
         end < text.length() && !IsBreakableSpace(text[end]);
@@ -843,8 +888,8 @@
 
 // Remove trailing collapsible spaces in |line_info|.
 // https://drafts.csswg.org/css-text-3/#white-space-phase-2
-void NGLineBreaker::RemoveTrailingCollapsibleSpace() {
-  ComputeTrailingCollapsibleSpace();
+void NGLineBreaker::RemoveTrailingCollapsibleSpace(NGLineInfo* line_info) {
+  ComputeTrailingCollapsibleSpace(line_info);
   if (!trailing_collapsible_space_.has_value()) {
     return;
   }
@@ -860,15 +905,15 @@
     item_result->inline_size = item_result->shape_result->SnappedWidth();
     position_ += item_result->inline_size;
   } else {
-    item_results_->erase(item_result);
+    line_info->MutableResults()->erase(item_result);
   }
   trailing_collapsible_space_.reset();
   trailing_whitespace_ = WhitespaceState::kCollapsed;
 }
 
 // Compute the width of trailing spaces without removing it.
-LayoutUnit NGLineBreaker::TrailingCollapsibleSpaceWidth() {
-  ComputeTrailingCollapsibleSpace();
+LayoutUnit NGLineBreaker::TrailingCollapsibleSpaceWidth(NGLineInfo* line_info) {
+  ComputeTrailingCollapsibleSpace(line_info);
   if (!trailing_collapsible_space_.has_value())
     return LayoutUnit();
 
@@ -884,7 +929,7 @@
 
 // Find trailing collapsible space if exists. The result is cached to
 // |trailing_collapsible_space_|.
-void NGLineBreaker::ComputeTrailingCollapsibleSpace() {
+void NGLineBreaker::ComputeTrailingCollapsibleSpace(NGLineInfo* line_info) {
   if (trailing_whitespace_ == WhitespaceState::kLeading ||
       trailing_whitespace_ == WhitespaceState::kNone ||
       trailing_whitespace_ == WhitespaceState::kCollapsed ||
@@ -897,7 +942,8 @@
 
   trailing_whitespace_ = WhitespaceState::kNone;
   const String& text = Text();
-  for (auto it = item_results_->rbegin(); it != item_results_->rend(); ++it) {
+  NGInlineItemResults* item_results = line_info->MutableResults();
+  for (auto it = item_results->rbegin(); it != item_results->rend(); ++it) {
     NGInlineItemResult& item_result = *it;
     DCHECK(item_result.item);
     const NGInlineItem& item = *item_result.item;
@@ -923,7 +969,8 @@
         trailing_collapsible_space_->item_result = &item_result;
         if (item_result.end_offset - 1 > item_result.start_offset) {
           trailing_collapsible_space_->collapsed_shape_result =
-              TruncateLineEndResult(item_result, item_result.end_offset - 1);
+              TruncateLineEndResult(*line_info, item_result,
+                                    item_result.end_offset - 1);
         }
       }
       trailing_whitespace_ = WhitespaceState::kCollapsible;
@@ -943,33 +990,19 @@
   trailing_collapsible_space_.reset();
 }
 
-void NGLineBreaker::AppendHyphen(const NGInlineItem& item) {
-  DCHECK(item.Style());
-  const ComputedStyle& style = *item.Style();
-  TextDirection direction = style.Direction();
-  String hyphen_string = style.HyphenString();
-  HarfBuzzShaper shaper(hyphen_string);
-  scoped_refptr<ShapeResult> hyphen_result =
-      shaper.Shape(&style.GetFont(), direction);
-  NGTextFragmentBuilder builder(node_, constraint_space_.GetWritingMode());
-  builder.SetText(item.GetLayoutObject(), hyphen_string, &style,
-                  /* is_ellipsis_style */ false,
-                  ShapeResultView::Create(hyphen_result.get()));
-  SetLineEndFragment(builder.ToTextFragment());
-}
-
 // Measure control items; new lines and tab, that are similar to text, affect
 // layout, but do not need shaping/painting.
-void NGLineBreaker::HandleControlItem(const NGInlineItem& item) {
+void NGLineBreaker::HandleControlItem(const NGInlineItem& item,
+                                      NGLineInfo* line_info) {
   DCHECK_GE(item.Length(), 1u);
   UChar character = Text()[item.StartOffset()];
   switch (character) {
     case kNewlineCharacter: {
-      NGInlineItemResult* item_result = AddItem(item);
+      NGInlineItemResult* item_result = AddItem(item, line_info);
       item_result->should_create_line_box = true;
       item_result->has_only_trailing_spaces = true;
       is_after_forced_break_ = true;
-      line_info_->SetIsLastLine(true);
+      line_info->SetIsLastLine(true);
       state_ = LineBreakState::kDone;
       break;
     }
@@ -980,12 +1013,12 @@
           ShapeResult::CreateForTabulationCharacters(
               &style.GetFont(), item.Direction(), style.GetTabSize(), position_,
               item.StartOffset(), item.Length());
-      HandleText(item, *shape_result);
+      HandleText(item, *shape_result, line_info);
       return;
     }
     case kZeroWidthSpaceCharacter: {
       // <wbr> tag creates break opportunities regardless of auto_wrap.
-      NGInlineItemResult* item_result = AddItem(item);
+      NGInlineItemResult* item_result = AddItem(item, line_info);
       item_result->should_create_line_box = true;
       item_result->can_break_after = true;
       break;
@@ -1003,7 +1036,8 @@
   MoveToNextOf(item);
 }
 
-void NGLineBreaker::HandleBidiControlItem(const NGInlineItem& item) {
+void NGLineBreaker::HandleBidiControlItem(const NGInlineItem& item,
+                                          NGLineInfo* line_info) {
   DCHECK_EQ(item.Length(), 1u);
 
   // Bidi control characters have enter/exit semantics. Handle "enter"
@@ -1012,33 +1046,37 @@
   UChar character = Text()[item.StartOffset()];
   bool is_pop = character == kPopDirectionalIsolateCharacter ||
                 character == kPopDirectionalFormattingCharacter;
+  NGInlineItemResults* item_results = line_info->MutableResults();
   if (is_pop) {
-    if (!item_results_->IsEmpty()) {
-      NGInlineItemResult* item_result = AddItem(item);
-      NGInlineItemResult* last = &(*item_results_)[item_results_->size() - 2];
+    if (!item_results->IsEmpty()) {
+      NGInlineItemResult* item_result = AddItem(item, line_info);
+      NGInlineItemResult* last = &(*item_results)[item_results->size() - 2];
       item_result->can_break_after = last->can_break_after;
       last->can_break_after = false;
     } else {
-      AddItem(item);
+      AddItem(item, line_info);
     }
   } else {
     if (state_ == LineBreakState::kTrailing &&
-        CanBreakAfterLast(*item_results_)) {
-      line_info_->SetIsLastLine(false);
+        CanBreakAfterLast(*item_results)) {
+      line_info->SetIsLastLine(false);
       MoveToNextOf(item);
       state_ = LineBreakState::kDone;
       return;
     }
-    NGInlineItemResult* item_result = AddItem(item);
+    NGInlineItemResult* item_result = AddItem(item, line_info);
     DCHECK(!item_result->can_break_after);
   }
   MoveToNextOf(item);
 }
 
-void NGLineBreaker::HandleAtomicInline(const NGInlineItem& item) {
+void NGLineBreaker::HandleAtomicInline(
+    const NGInlineItem& item,
+    LayoutUnit percentage_resolution_block_size_for_min_max,
+    NGLineInfo* line_info) {
   DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline);
 
-  NGInlineItemResult* item_result = AddItem(item);
+  NGInlineItemResult* item_result = AddItem(item, line_info);
   item_result->should_create_line_box = true;
   // When we're just computing min/max content sizes, we can skip the full
   // layout and just compute those sizes. On the other hand, for regular
@@ -1049,8 +1087,8 @@
     item_result->layout_result =
         NGBlockNode(ToLayoutBox(item.GetLayoutObject()))
             .LayoutAtomicInline(constraint_space_, node_.Style(),
-                                line_info_->LineStyle().GetFontBaseline(),
-                                line_info_->UseFirstLineStyle());
+                                line_info->LineStyle().GetFontBaseline(),
+                                line_info->UseFirstLineStyle());
     DCHECK(item_result->layout_result->PhysicalFragment());
 
     item_result->inline_size =
@@ -1059,7 +1097,7 @@
             .InlineSize();
   } else {
     NGBlockNode child(ToLayoutBox(item.GetLayoutObject()));
-    MinMaxSizeInput input(percentage_resolution_block_size_for_min_max_);
+    MinMaxSizeInput input(percentage_resolution_block_size_for_min_max);
     MinMaxSize sizes =
         ComputeMinAndMaxContentContribution(node_.Style(), child, input);
     item_result->inline_size = mode_ == NGLineBreakerMode::kMinContent
@@ -1078,7 +1116,7 @@
 
   trailing_whitespace_ = WhitespaceState::kNone;
   position_ += item_result->inline_size;
-  ComputeCanBreakAfter(item_result);
+  ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
 
   if (sticky_images_quirk_ && IsImage(item)) {
     const auto& items = Items();
@@ -1109,7 +1147,9 @@
 // We have this check if there are already UnpositionedFloats as we aren't
 // allowed to position a float "above" another float which has come before us
 // in the document.
-void NGLineBreaker::HandleFloat(const NGInlineItem& item) {
+void NGLineBreaker::HandleFloat(const NGInlineItem& item,
+                                Vector<LayoutObject*>* out_floats_for_min_max,
+                                NGLineInfo* line_info) {
   // When rewind occurs, an item may be handled multiple times.
   // Since floats are put into a separate list, avoid handling same floats
   // twice.
@@ -1119,15 +1159,15 @@
   // Additionally, we need to skip floats if we're retrying a line after a
   // fragmentainer break. In that case the floats associated with this line will
   // already have been processed.
-  NGInlineItemResult* item_result = AddItem(item);
-  ComputeCanBreakAfter(item_result);
+  NGInlineItemResult* item_result = AddItem(item, line_info);
+  ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
   MoveToNextOf(item);
 
   // If we are currently computing our min/max-content size simply append to
   // the unpositioned floats list and abort.
   if (mode_ != NGLineBreakerMode::kContent) {
-    DCHECK(out_floats_for_min_max_);
-    out_floats_for_min_max_->push_back(item.GetLayoutObject());
+    DCHECK(out_floats_for_min_max);
+    out_floats_for_min_max->push_back(item.GetLayoutObject());
     return;
   }
 
@@ -1154,8 +1194,9 @@
 
   LayoutUnit bfc_block_offset = line_opportunity_.bfc_block_offset;
 
-  LayoutUnit used_size =
-      position_ + inline_margin_size + ComputeFloatAncestorInlineEndSize();
+  LayoutUnit used_size = position_ + inline_margin_size +
+                         ComputeFloatAncestorInlineEndSize(
+                             constraint_space_, Items(), item_index_);
   bool can_fit_float =
       used_size <= line_opportunity_.AvailableFloatInlineSize().AddEpsilon();
   if (!can_fit_float) {
@@ -1164,7 +1205,7 @@
     // removed if this line breaks here because they should be collapsed across
     // floats, but they are still included in the current line position at this
     // point. Exclude it when computing whether this float can fit or not.
-    can_fit_float = used_size - TrailingCollapsibleSpaceWidth() <=
+    can_fit_float = used_size - TrailingCollapsibleSpaceWidth(line_info) <=
                     line_opportunity_.AvailableFloatInlineSize().AddEpsilon();
   }
 
@@ -1181,7 +1222,7 @@
 
   // Check if we already have a pending float. That's because a float cannot be
   // higher than any block or floated box generated before.
-  if (HasUnpositionedFloats(*item_results_) || float_after_line) {
+  if (HasUnpositionedFloats(*line_info->MutableResults()) || float_after_line) {
     item_result->has_unpositioned_floats = true;
   } else {
     NGPositionedFloat positioned_float = PositionFloat(
@@ -1207,39 +1248,6 @@
   }
 }
 
-// To correctly determine if a float is allowed to be on the same line as its
-// content, we need to determine if it has any ancestors with inline-end
-// padding, border, or margin.
-// The inline-end size from all of these ancestors contribute to the "used
-// size" of the float, and may cause the float to be pushed down.
-LayoutUnit NGLineBreaker::ComputeFloatAncestorInlineEndSize() const {
-  const Vector<NGInlineItem>& items = Items();
-  wtf_size_t item_index = item_index_;
-
-  LayoutUnit inline_end_size;
-  while (item_index < items.size()) {
-    const NGInlineItem& item = items[item_index++];
-
-    if (item.Type() == NGInlineItem::kCloseTag) {
-      if (item.HasEndEdge()) {
-        inline_end_size +=
-            ComputeInlineEndSize(constraint_space_, item.Style());
-      }
-      continue;
-    }
-
-    // For this calculation, any open tag (even if its empty) stops this
-    // calculation, and allows the float to appear on the same line. E.g.
-    // <span style="padding-right: 20px;"><f></f><span></span></span>
-    //
-    // Any non-empty item also allows the float to be on the same line.
-    if (item.Type() == NGInlineItem::kOpenTag || !item.IsEmptyItem())
-      break;
-  }
-
-  return inline_end_size;
-}
-
 bool NGLineBreaker::ComputeOpenTagResult(
     const NGInlineItem& item,
     const NGConstraintSpace& constraint_space,
@@ -1264,8 +1272,9 @@
   return false;
 }
 
-void NGLineBreaker::HandleOpenTag(const NGInlineItem& item) {
-  NGInlineItemResult* item_result = AddItem(item);
+void NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
+                                  NGLineInfo* line_info) {
+  NGInlineItemResult* item_result = AddItem(item, line_info);
 
   if (ComputeOpenTagResult(item, constraint_space_, item_result)) {
     position_ += item_result->inline_size;
@@ -1285,13 +1294,15 @@
   MoveToNextOf(item);
 
   DCHECK(!item_result->can_break_after);
-  if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results_->size() >= 2)) {
-    ComputeCanBreakAfter(std::prev(item_result));
+  NGInlineItemResults* item_results = line_info->MutableResults();
+  if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results->size() >= 2)) {
+    ComputeCanBreakAfter(std::prev(item_result), auto_wrap_, break_iterator_);
   }
 }
 
-void NGLineBreaker::HandleCloseTag(const NGInlineItem& item) {
-  NGInlineItemResult* item_result = AddItem(item);
+void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
+                                   NGLineInfo* line_info) {
+  NGInlineItemResult* item_result = AddItem(item, line_info);
 
   item_result->has_edge = item.HasEndEdge();
   if (item_result->has_edge) {
@@ -1310,7 +1321,8 @@
   // If the line can break after the previous item, prohibit it and allow break
   // after this close tag instead.
   if (was_auto_wrap) {
-    if (item_results_->size() >= 2) {
+    NGInlineItemResults* item_results = line_info->MutableResults();
+    if (item_results->size() >= 2) {
       NGInlineItemResult* last = std::prev(item_result);
       item_result->can_break_after = last->can_break_after;
       last->can_break_after = false;
@@ -1332,13 +1344,13 @@
     item_result->can_break_after = true;
     return;
   }
-  ComputeCanBreakAfter(item_result);
+  ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
 }
 
 // Handles when the last item overflows.
 // At this point, item_results does not fit into the current line, and there
 // are no break opportunities in item_results.back().
-void NGLineBreaker::HandleOverflow() {
+void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) {
   // Compute the width needing to rewind. When |width_to_rewind| goes negative,
   // items can fit within the line.
   LayoutUnit available_width = AvailableWidthToFit();
@@ -1355,14 +1367,15 @@
   bool has_break_anywhere_if_overflow = break_anywhere_if_overflow_;
 
   // Search for a break opportunity that can fit.
-  for (unsigned i = item_results_->size(); i;) {
-    NGInlineItemResult* item_result = &(*item_results_)[--i];
+  NGInlineItemResults* item_results = line_info->MutableResults();
+  for (unsigned i = item_results->size(); i;) {
+    NGInlineItemResult* item_result = &(*item_results)[--i];
 
     // Try to break after this item.
-    if (i < item_results_->size() - 1 && item_result->can_break_after) {
+    if (i < item_results->size() - 1 && item_result->can_break_after) {
       if (width_to_rewind <= 0) {
         position_ = available_width + width_to_rewind;
-        Rewind(i + 1);
+        Rewind(i + 1, line_info);
         state_ = LineBreakState::kTrailing;
         return;
       }
@@ -1383,7 +1396,8 @@
         LayoutUnit item_available_width =
             std::min(-width_to_rewind, item_result->inline_size - 1);
         SetCurrentStyle(*item.Style());
-        BreakText(item_result, item, item_available_width);
+        BreakText(item_result, item, *item.TextShapeResult(),
+                  item_available_width, line_info);
 #if DCHECK_IS_ON()
         item_result->CheckConsistency(true);
 #endif
@@ -1391,19 +1405,19 @@
         if (item_result->inline_size <= item_available_width) {
           DCHECK(item_result->end_offset < item.EndOffset());
           DCHECK(item_result->can_break_after);
-          DCHECK_LE(i + 1, item_results_->size());
-          if (i + 1 == item_results_->size()) {
+          DCHECK_LE(i + 1, item_results->size());
+          if (i + 1 == item_results->size()) {
             // If this is the last item, adjust states to accomodate the change.
             position_ =
                 available_width + width_to_rewind + item_result->inline_size;
-            if (line_info_->LineEndFragment())
-              SetLineEndFragment(nullptr);
-            DCHECK_EQ(position_, line_info_->ComputeWidth());
+            if (line_info->LineEndFragment())
+              SetLineEndFragment(nullptr, line_info);
+            DCHECK_EQ(position_, line_info->ComputeWidth());
             item_index_ = item_result->item_index;
             offset_ = item_result->end_offset;
             items_data_.AssertOffset(item_index_, offset_);
           } else {
-            Rewind(i + 1);
+            Rewind(i + 1, line_info);
           }
           state_ = LineBreakState::kTrailing;
           return;
@@ -1422,32 +1436,32 @@
     break_iterator_.SetBreakType(LineBreakType::kBreakCharacter);
     // TODO(kojii): Not all items need to rewind, but such case is rare and
     // rewinding all items simplifes the code.
-    if (!item_results_->IsEmpty())
-      Rewind(0);
+    if (!item_results->IsEmpty())
+      Rewind(0, line_info);
     state_ = LineBreakState::kContinue;
     return;
   }
 
   // Let this line overflow.
-  line_info_->SetHasOverflow();
+  line_info->SetHasOverflow();
 
   // If there was a break opportunity, the overflow should stop there.
   if (break_before) {
-    Rewind(break_before);
+    Rewind(break_before, line_info);
     state_ = LineBreakState::kTrailing;
     return;
   }
 
   if (position_maybe_changed) {
     trailing_whitespace_ = WhitespaceState::kUnknown;
-    UpdatePosition();
+    position_ = line_info->ComputeWidth();
   }
 
   state_ = LineBreakState::kTrailing;
 }
 
-void NGLineBreaker::Rewind(unsigned new_end) {
-  NGInlineItemResults& item_results = *item_results_;
+void NGLineBreaker::Rewind(unsigned new_end, NGLineInfo* line_info) {
+  NGInlineItemResults& item_results = *line_info->MutableResults();
   DCHECK_LT(new_end, item_results.size());
 
   // Avoid rewinding floats if possible. They will be added back anyway while
@@ -1457,7 +1471,7 @@
   while (item_results[new_end].item->Type() == NGInlineItem::kFloating) {
     ++new_end;
     if (new_end == item_results.size()) {
-      UpdatePosition();
+      position_ = line_info->ComputeWidth();
       return;
     }
   }
@@ -1473,7 +1487,7 @@
       // still better than rewinding them.
       new_end = i + 1;
       if (new_end == item_results.size()) {
-        UpdatePosition();
+        position_ = line_info->ComputeWidth();
         return;
       }
       break;
@@ -1492,21 +1506,22 @@
     offset_ = first_remove.start_offset;
     trailing_whitespace_ = WhitespaceState::kLeading;
   }
-  SetCurrentStyle(ComputeCurrentStyle(new_end));
+  SetCurrentStyle(ComputeCurrentStyle(new_end, line_info));
 
   item_results.Shrink(new_end);
 
   trailing_collapsible_space_.reset();
-  SetLineEndFragment(nullptr);
-  UpdatePosition();
+  SetLineEndFragment(nullptr, line_info);
+  position_ = line_info->ComputeWidth();
 }
 
 // Returns the style to use for |item_result_index|. Normally when handling
 // items sequentially, the current style is updated on open/close tag. When
 // rewinding, this function computes the style for the specified item.
 const ComputedStyle& NGLineBreaker::ComputeCurrentStyle(
-    unsigned item_result_index) const {
-  NGInlineItemResults& item_results = *item_results_;
+    unsigned item_result_index,
+    NGLineInfo* line_info) const {
+  NGInlineItemResults& item_results = *line_info->MutableResults();
 
   // Use the current item if it can compute the current style.
   const NGInlineItem* item = item_results[item_result_index].item;
@@ -1534,7 +1549,7 @@
     DCHECK(break_token_->Style());
     return *break_token_->Style();
   }
-  return line_info_->LineStyle();
+  return line_info->LineStyle();
 }
 
 void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
index 61fd33e..11e43be 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
@@ -39,15 +39,23 @@
                 const NGPositionedFloatVector& leading_floats,
                 unsigned handled_leading_floats_index,
                 const NGInlineBreakToken*,
-                NGExclusionSpace*,
-                LayoutUnit percentage_resolution_block_size_for_min_max =
-                    NGSizeIndefinite,
-                Vector<LayoutObject*>* out_floats_for_min_max = nullptr);
+                NGExclusionSpace*);
   ~NGLineBreaker();
 
   // Compute the next line break point and produces NGInlineItemResults for
   // the line.
-  void NextLine(NGLineInfo*);
+  inline void NextLine(NGLineInfo* line_info) {
+    NextLine(NGSizeIndefinite, nullptr, line_info);
+  }
+
+  // During the min/max size calculation we need a special percentage
+  // resolution block-size to pass to children/pass to children.
+  // TODO(layout-dev): Split into two methods (NextLine/NextLineForMinMax) or,
+  // better yet, subclass or templetize the line-breaker for Min/Max computation
+  // if we can do that without incurring a performance penalty
+  void NextLine(LayoutUnit percentage_resolution_block_size_for_min_max,
+                Vector<LayoutObject*>* out_floats_for_min_max,
+                NGLineInfo*);
 
   bool IsFinished() const { return item_index_ >= Items().size(); }
 
@@ -78,17 +86,19 @@
   const String& Text() const { return items_data_.text_content; }
   const Vector<NGInlineItem>& Items() const { return items_data_.items; }
 
-  NGInlineItemResult* AddItem(const NGInlineItem&, unsigned end_offset);
-  NGInlineItemResult* AddItem(const NGInlineItem&);
-  void SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>);
-  void ComputeCanBreakAfter(NGInlineItemResult*) const;
+  NGInlineItemResult* AddItem(const NGInlineItem&,
+                              unsigned end_offset,
+                              NGLineInfo*);
+  NGInlineItemResult* AddItem(const NGInlineItem&, NGLineInfo*);
+  void SetLineEndFragment(scoped_refptr<const NGPhysicalTextFragment>,
+                          NGLineInfo*);
 
-  void BreakLine();
+  void BreakLine(LayoutUnit percentage_resolution_block_size_for_min_max,
+                 Vector<LayoutObject*>* out_floats_for_min_max,
+                 NGLineInfo*);
+  void PrepareNextLine(NGLineInfo*);
 
-  void PrepareNextLine();
-
-  void UpdatePosition();
-  void ComputeLineLocation() const;
+  void ComputeLineLocation(NGLineInfo*) const;
 
   enum class LineBreakState {
     // The line breaking is complete.
@@ -103,49 +113,55 @@
     kContinue,
   };
 
-  void HandleText(const NGInlineItem&);
-  void HandleText(const NGInlineItem&, const ShapeResult& shape_result);
+  inline void HandleText(const NGInlineItem& item, NGLineInfo* line_info) {
+    DCHECK(item.TextShapeResult());
+    HandleText(item, *item.TextShapeResult(), line_info);
+  }
+  void HandleText(const NGInlineItem& item, const ShapeResult&, NGLineInfo*);
   void BreakText(NGInlineItemResult*,
                  const NGInlineItem&,
-                 LayoutUnit available_width);
-  void BreakText(NGInlineItemResult*,
-                 const NGInlineItem&,
-                 const ShapeResult& shape_result,
-                 LayoutUnit available_width);
-  bool HandleTextForFastMinContent(NGInlineItemResult* item_result,
-                                   const NGInlineItem& item,
-                                   const ShapeResult& shape_result);
+                 const ShapeResult&,
+                 LayoutUnit available_width,
+                 NGLineInfo*);
+  bool HandleTextForFastMinContent(NGInlineItemResult*,
+                                   const NGInlineItem&,
+                                   const ShapeResult&,
+                                   NGLineInfo*);
 
   scoped_refptr<ShapeResultView> TruncateLineEndResult(
-      const NGInlineItemResult& item_result,
+      const NGLineInfo&,
+      const NGInlineItemResult&,
       unsigned end_offset);
-  void UpdateShapeResult(NGInlineItemResult*);
-  scoped_refptr<ShapeResult> ShapeText(const NGInlineItem& item,
+  void UpdateShapeResult(const NGLineInfo&, NGInlineItemResult*);
+  scoped_refptr<ShapeResult> ShapeText(const NGInlineItem&,
                                        unsigned start,
                                        unsigned end);
 
   void HandleTrailingSpaces(const NGInlineItem&,
-                            const ShapeResult& shape_result);
-  void RemoveTrailingCollapsibleSpace();
-  LayoutUnit TrailingCollapsibleSpaceWidth();
-  void ComputeTrailingCollapsibleSpace();
+                            const ShapeResult&,
+                            NGLineInfo*);
+  void RemoveTrailingCollapsibleSpace(NGLineInfo*);
+  LayoutUnit TrailingCollapsibleSpaceWidth(NGLineInfo*);
+  void ComputeTrailingCollapsibleSpace(NGLineInfo*);
 
-  void AppendHyphen(const NGInlineItem& item);
+  void HandleControlItem(const NGInlineItem&, NGLineInfo*);
+  void HandleBidiControlItem(const NGInlineItem&, NGLineInfo*);
+  void HandleAtomicInline(
+      const NGInlineItem&,
+      LayoutUnit percentage_resolution_block_size_for_min_max,
+      NGLineInfo*);
+  void HandleFloat(const NGInlineItem&,
+                   Vector<LayoutObject*>* out_floats_for_min_max,
+                   NGLineInfo*);
 
-  void HandleControlItem(const NGInlineItem&);
-  void HandleBidiControlItem(const NGInlineItem&);
-  void HandleAtomicInline(const NGInlineItem&);
-  void HandleFloat(const NGInlineItem&);
+  void HandleOpenTag(const NGInlineItem&, NGLineInfo*);
+  void HandleCloseTag(const NGInlineItem&, NGLineInfo*);
 
-  LayoutUnit ComputeFloatAncestorInlineEndSize() const;
+  void HandleOverflow(NGLineInfo*);
+  void Rewind(unsigned new_end, NGLineInfo*);
 
-  void HandleOpenTag(const NGInlineItem&);
-  void HandleCloseTag(const NGInlineItem&);
-
-  void HandleOverflow();
-  void Rewind(unsigned new_end);
-
-  const ComputedStyle& ComputeCurrentStyle(unsigned item_result_index) const;
+  const ComputedStyle& ComputeCurrentStyle(unsigned item_result_index,
+                                           NGLineInfo*) const;
   void SetCurrentStyle(const ComputedStyle&);
 
   void MoveToNextOf(const NGInlineItem&);
@@ -160,12 +176,6 @@
     return AvailableWidth().AddEpsilon();
   }
 
-  // These fields are the output of the current line.
-  // NGInlineItemResults is a pointer because the move operation is not cheap
-  // due to its inline buffer.
-  NGLineInfo* line_info_ = nullptr;
-  NGInlineItemResults* item_results_ = nullptr;
-
   // Represents the current offset of the input.
   LineBreakState state_;
   unsigned item_index_ = 0;
@@ -255,11 +265,6 @@
   // This is copied from NGInlineNode, then updated after each forced line break
   // if 'unicode-bidi: plaintext'.
   TextDirection base_direction_;
-
-  // During the min/max size calculation we need a special percentage
-  // resolution block-size to pass to children/pass to children.
-  LayoutUnit percentage_resolution_block_size_for_min_max_;
-  Vector<LayoutObject*>* out_floats_for_min_max_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
index 330fe32..9ef582897 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
@@ -503,7 +503,7 @@
     return PositionWithAffinity(GetLastPosition(position.index),
                                 TextAffinity::kDownstream);
   }
-  return PositionWithAffinity(GetLastPosition(position.index + 1),
+  return PositionWithAffinity(GetFirstPosition(position.index + 1),
                               TextAffinity::kUpstream);
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
index bd82b7d..aa1a1dc 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
@@ -141,7 +141,7 @@
   scoped_refptr<const NGLayoutResult> result =
       container_builder.ToBoxFragment();
   // These are the unpositioned OOF descendants of the current OOF block.
-  for (NGOutOfFlowPositionedDescendant descendant :
+  for (const NGOutOfFlowPositionedDescendant& descendant :
        result->OutOfFlowPositionedDescendants())
     descendant.node.UseOldOutOfFlowPositioning();
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
index e63405c..4982b14 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
@@ -138,7 +138,7 @@
   }
 
   bool CreatesNewFormattingContext() const {
-    return IsBlock() && box_->AvoidsFloats();
+    return IsBlock() && box_->CreatesNewFormattingContext();
   }
 
   // Returns true if this node should pass its percentage resolution block-size
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index eeadd72..c197575 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -236,7 +236,7 @@
 }
 
 void NGOutOfFlowLayoutPart::ComputeInlineContainingBlocks(
-    Vector<NGOutOfFlowPositionedDescendant> descendants) {
+    const Vector<NGOutOfFlowPositionedDescendant>& descendants) {
   NGBoxFragmentBuilder::InlineContainingBlockMap inline_container_fragments;
 
   for (auto& descendant : descendants) {
@@ -391,7 +391,7 @@
 }
 
 void NGOutOfFlowLayoutPart::LayoutDescendantCandidates(
-    const Vector<NGOutOfFlowPositionedDescendant> descendant_candidates,
+    const Vector<NGOutOfFlowPositionedDescendant>& descendant_candidates,
     const LayoutBox* only_layout,
     HashSet<const LayoutObject*>* placed_objects) {
   for (auto& candidate : descendant_candidates) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
index 133c372..b167f31b9 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
@@ -93,10 +93,11 @@
   ContainingBlockInfo GetContainingBlockInfo(
       const NGOutOfFlowPositionedDescendant&) const;
 
-  void ComputeInlineContainingBlocks(Vector<NGOutOfFlowPositionedDescendant>);
+  void ComputeInlineContainingBlocks(
+      const Vector<NGOutOfFlowPositionedDescendant>&);
 
   void LayoutDescendantCandidates(
-      const Vector<NGOutOfFlowPositionedDescendant> descendant_candidates,
+      const Vector<NGOutOfFlowPositionedDescendant>& descendant_candidates,
       const LayoutBox* only_layout,
       HashSet<const LayoutObject*>* placed_objects);
 
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index ab0914f..dc43e06 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -160,6 +160,7 @@
   origin_policy_ = params_->origin_policy;
   requestor_origin_ = params_->requestor_origin;
   unreachable_url_ = params_->unreachable_url;
+  error_code_ = params_->error_code;
   previews_state_ = params_->previews_state;
 
   // See WebNavigationParams for special case explanations.
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index 42acae7e..e6d63d54 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -254,6 +254,8 @@
 
   void ProvideDocumentToResourceFetcherProperties(Document&);
 
+  int ErrorCode() const { return error_code_; }
+
  protected:
   bool had_transient_activation() const { return had_transient_activation_; }
 
@@ -346,6 +348,7 @@
   String origin_policy_;
   scoped_refptr<const SecurityOrigin> requestor_origin_;
   KURL unreachable_url_;
+  int error_code_;
   std::unique_ptr<WebNavigationBodyLoader> body_loader_;
 
   // Params are saved in constructor and are cleared after StartLoading().
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 300738c..72affbe 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -893,8 +893,8 @@
                              ->ExperimentalFeaturesEnabled()) {
     initiator_csp = origin_document->GetContentSecurityPolicy()
                         ->ExposeForNavigationalChecks();
-    auto request = mojo::MakeRequest(&navigation_initiator);
-    origin_document->BindNavigationInitiatorRequest(std::move(request));
+    auto mojo_request = mojo::MakeRequest(&navigation_initiator);
+    origin_document->BindNavigationInitiatorRequest(std::move(mojo_request));
   }
 
   if (origin_document && origin_document->GetContentSecurityPolicy()) {
diff --git a/third_party/blink/renderer/core/page/create_window.cc b/third_party/blink/renderer/core/page/create_window.cc
index 5ad345e1..40b84e1 100644
--- a/third_party/blink/renderer/core/page/create_window.cc
+++ b/third_party/blink/renderer/core/page/create_window.cc
@@ -424,6 +424,7 @@
   // createWindow(LocalFrame& openerFrame, ...).
   // This value will be set in ResourceRequest loaded in a new LocalFrame.
   bool has_user_gesture = LocalFrame::HasTransientUserActivation(&opener_frame);
+  opener_frame.MaybeLogAdClickNavigation();
 
   // We pass the opener frame for the lookupFrame in case the active frame is
   // different from the opener frame, and the name references a frame relative
diff --git a/third_party/blink/renderer/core/paint/BUILD.gn b/third_party/blink/renderer/core/paint/BUILD.gn
index f95d1ebb0..13b93f65 100644
--- a/third_party/blink/renderer/core/paint/BUILD.gn
+++ b/third_party/blink/renderer/core/paint/BUILD.gn
@@ -41,6 +41,8 @@
     "collapsed_border_painter.h",
     "compositing/composited_layer_mapping.cc",
     "compositing/composited_layer_mapping.h",
+    "compositing/compositing_inputs_root.cc",
+    "compositing/compositing_inputs_root.h",
     "compositing/compositing_inputs_updater.cc",
     "compositing/compositing_inputs_updater.h",
     "compositing/compositing_layer_assigner.cc",
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.cc b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.cc
new file mode 100644
index 0000000..1086d93
--- /dev/null
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.cc
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.h"
+
+#include "third_party/blink/renderer/core/paint/paint_layer.h"
+
+namespace blink {
+
+void CompositingInputsRoot::Update(PaintLayer* new_root_layer) {
+  DCHECK(new_root_layer);
+
+  if (!root_layer_) {
+    // This is the first time we call Update() so just let set the root layer.
+    root_layer_ = new_root_layer;
+    return;
+  }
+
+  if (root_layer_ == new_root_layer)
+    return;
+
+  PaintLayer* common_ancestor =
+      const_cast<PaintLayer*>(root_layer_->CommonAncestor(new_root_layer));
+  if (!common_ancestor)
+    common_ancestor = const_cast<PaintLayer*>(root_layer_->Root());
+
+  root_layer_->SetChildNeedsCompositingInputsUpdateUpToAncestor(
+      common_ancestor);
+  new_root_layer->SetChildNeedsCompositingInputsUpdateUpToAncestor(
+      common_ancestor);
+
+  root_layer_ = common_ancestor;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.h b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.h
new file mode 100644
index 0000000..896f3d48
--- /dev/null
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.h
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_COMPOSITING_COMPOSITING_INPUTS_ROOT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_COMPOSITING_COMPOSITING_INPUTS_ROOT_H_
+
+namespace blink {
+
+class PaintLayer;
+
+class CompositingInputsRoot {
+ public:
+  PaintLayer* Get() const { return root_layer_; }
+
+  void Update(PaintLayer* new_root_layer);
+  void Clear() { root_layer_ = nullptr; }
+
+ private:
+  PaintLayer* root_layer_ = nullptr;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_COMPOSITING_COMPOSITING_INPUTS_ROOT_H_
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
index 48bf5a3..16d6abf0 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
@@ -24,19 +24,50 @@
              : clip_chain_parent->ClippingContainer();
 }
 
-CompositingInputsUpdater::CompositingInputsUpdater(PaintLayer* root_layer)
-    : geometry_map_(kUseTransforms), root_layer_(root_layer) {}
+CompositingInputsUpdater::CompositingInputsUpdater(
+    PaintLayer* root_layer,
+    PaintLayer* compositing_inputs_root)
+    : geometry_map_(kUseTransforms),
+      root_layer_(root_layer),
+      compositing_inputs_root_(compositing_inputs_root) {}
 
 CompositingInputsUpdater::~CompositingInputsUpdater() = default;
 
 void CompositingInputsUpdater::Update() {
   TRACE_EVENT0("blink", "CompositingInputsUpdater::update");
-  UpdateRecursive(root_layer_, kDoNotForceUpdate, AncestorInfo());
+
+  AncestorInfo info;
+  UpdateType update_type = kDoNotForceUpdate;
+  ApplyAncestorInfoToSelfAndAncestorsRecursively(
+      compositing_inputs_root_ ? compositing_inputs_root_ : root_layer_,
+      update_type, info);
+
+  UpdateSelfAndDescendantsRecursively(
+      compositing_inputs_root_ ? compositing_inputs_root_ : root_layer_,
+      update_type, info);
 }
 
-void CompositingInputsUpdater::UpdateRecursive(PaintLayer* layer,
-                                               UpdateType update_type,
-                                               AncestorInfo info) {
+void CompositingInputsUpdater::ApplyAncestorInfoToSelfAndAncestorsRecursively(
+    PaintLayer* layer,
+    UpdateType& update_type,
+    AncestorInfo& info) {
+  if (!layer)
+    return;
+
+  // We first recursively call ApplyAncestorInfoToSelfAndAncestorsRecursively()
+  // to ensure that we start to compute the geometry_map_ and AncestorInfo from
+  // the root layer (as we need to do a top-down tree walk to incrementally
+  // update this information).
+  ApplyAncestorInfoToSelfAndAncestorsRecursively(layer->Parent(), update_type,
+                                                 info);
+  geometry_map_.PushMappingsToAncestor(layer, layer->Parent());
+  UpdateAncestorInfo(layer, update_type, info);
+}
+
+void CompositingInputsUpdater::UpdateSelfAndDescendantsRecursively(
+    PaintLayer* layer,
+    UpdateType update_type,
+    AncestorInfo info) {
   LayoutBoxModelObject& layout_object = layer->GetLayoutObject();
   const ComputedStyle& style = layout_object.StyleRef();
 
@@ -74,7 +105,84 @@
     layer->UpdateLayerPosition();
   }
 
-  geometry_map_.PushMappingsToAncestor(layer, layer->Parent());
+  // geometry_map_ has been already updated in ApplyAncestorInfo() and
+  // UpdateAncestorInfo has been already computed in ApplyAncestorInfo() for
+  // layers from root_layer_ down to compositing_inputs_root_ both included.
+  if (layer != root_layer_ && layer != compositing_inputs_root_) {
+    geometry_map_.PushMappingsToAncestor(layer, layer->Parent());
+    UpdateAncestorInfo(layer, update_type, info);
+  }
+
+  PaintLayerCompositor* compositor =
+      layer->GetLayoutObject().View()->Compositor();
+
+  // The sequence of updates to compositing triggers goes like this:
+  // 1. Apply all triggers from kComboAllDirectNonStyleDeterminedReasons for
+  //    |layer|. This may depend on ancestor composited scrolling (i.e. step
+  //    2 for an ancestor PaintLayer).
+  // 2. Put |layer| in composited scrolling mode if needed.
+  // 3. Reset DescendantHasDirectCompositingReason to false for |layer|.
+  // 4. Recurse into child PaintLayers.
+  // 5. Set DescendantHasDirectCompositingReason to true if it was for any
+  //    child.
+  // 6. If |layer| is the root, composite if
+  //    DescendantHasDirectCompositingReason is true for |layer|.
+
+  layer->SetPotentialCompositingReasonsFromNonStyle(
+      CompositingReasonFinder::NonStyleDeterminedDirectReasons(*layer));
+
+  if (layer->GetScrollableArea()) {
+    layer->GetScrollableArea()->UpdateNeedsCompositedScrolling(
+        compositor->CanBeComposited(layer) &&
+        layer->DirectCompositingReasons());
+  }
+
+  bool should_recurse =
+      layer->ChildNeedsCompositingInputsUpdate() || update_type == kForceUpdate;
+
+  layer->SetDescendantHasDirectOrScrollingCompositingReason(false);
+  bool descendant_has_direct_compositing_reason = false;
+  for (PaintLayer* child = layer->FirstChild(); child;
+       child = child->NextSibling()) {
+    if (should_recurse)
+      UpdateSelfAndDescendantsRecursively(child, update_type, info);
+    descendant_has_direct_compositing_reason |=
+        child->DescendantHasDirectOrScrollingCompositingReason() ||
+        child->NeedsCompositedScrolling() ||
+        (compositor->CanBeComposited(child) &&
+         child->DirectCompositingReasons());
+  }
+  layer->SetDescendantHasDirectOrScrollingCompositingReason(
+      descendant_has_direct_compositing_reason);
+
+  if (layer->IsRootLayer() && layer->ScrollsOverflow() &&
+      layer->DescendantHasDirectOrScrollingCompositingReason() &&
+      !layer->NeedsCompositedScrolling())
+    layer->GetScrollableArea()->UpdateNeedsCompositedScrolling(true);
+
+  layer->ClearChildNeedsCompositingInputsUpdate();
+
+  geometry_map_.PopMappingsToAncestor(layer->Parent());
+
+  if (layer->SelfPaintingStatusChanged()) {
+    layer->ClearSelfPaintingStatusChanged();
+    // If the floating object becomes non-self-painting, so some ancestor should
+    // paint it; if it becomes self-painting, it should paint itself and no
+    // ancestor should paint it.
+    if (layout_object.IsFloating()) {
+      LayoutBlockFlow::UpdateAncestorShouldPaintFloatingObject(
+          *layer->GetLayoutBox());
+    }
+  }
+
+  compositor->ClearCompositingInputsRoot();
+}
+
+void CompositingInputsUpdater::UpdateAncestorInfo(PaintLayer* layer,
+                                                  UpdateType& update_type,
+                                                  AncestorInfo& info) {
+  LayoutBoxModelObject& layout_object = layer->GetLayoutObject();
+  const ComputedStyle& style = layout_object.StyleRef();
 
   PaintLayer* enclosing_stacking_composited_layer =
       info.enclosing_stacking_composited_layer;
@@ -208,69 +316,8 @@
         info.needs_reparent_scroll_for_fixed = false;
   }
 
-  PaintLayerCompositor* compositor =
-      layer->GetLayoutObject().View()->Compositor();
-
-  // The sequence of updates to compositing triggers goes like this:
-  // 1. Apply all triggers from kComboAllDirectNonStyleDeterminedReasons for
-  //    |layer|. This may depend on ancestor composited scrolling (i.e. step
-  //    2 for an ancestor PaintLayer).
-  // 2. Put |layer| in composited scrolling mode if needed.
-  // 3. Reset DescendantHasDirectCompositingReason to false for |layer|.
-  // 4. Recurse into child PaintLayers.
-  // 5. Set DescendantHasDirectCompositingReason to true if it was for any
-  //    child.
-  // 6. If |layer| is the root, composite if
-  //    DescendantHasDirectCompositingReason is true for |layer|.
-  layer->SetPotentialCompositingReasonsFromNonStyle(
-      CompositingReasonFinder::NonStyleDeterminedDirectReasons(*layer));
-
-  if (layer->GetScrollableArea()) {
-    layer->GetScrollableArea()->UpdateNeedsCompositedScrolling(
-        compositor->CanBeComposited(layer) &&
-        layer->DirectCompositingReasons());
-  }
-
   if (layer->GetLayoutObject().IsVideo())
     info.is_under_video = true;
-
-  bool should_recurse =
-      layer->ChildNeedsCompositingInputsUpdate() || update_type == kForceUpdate;
-
-  layer->SetDescendantHasDirectOrScrollingCompositingReason(false);
-  bool descendant_has_direct_compositing_reason = false;
-  for (PaintLayer* child = layer->FirstChild(); child;
-       child = child->NextSibling()) {
-    if (should_recurse)
-      UpdateRecursive(child, update_type, info);
-    descendant_has_direct_compositing_reason |=
-        child->DescendantHasDirectOrScrollingCompositingReason() ||
-        child->NeedsCompositedScrolling() ||
-        (compositor->CanBeComposited(child) &&
-         child->DirectCompositingReasons());
-  }
-  layer->SetDescendantHasDirectOrScrollingCompositingReason(
-      descendant_has_direct_compositing_reason);
-
-  if (layer->IsRootLayer() && layer->ScrollsOverflow() &&
-      layer->DescendantHasDirectOrScrollingCompositingReason() &&
-      !layer->NeedsCompositedScrolling())
-    layer->GetScrollableArea()->UpdateNeedsCompositedScrolling(true);
-
-  layer->ClearChildNeedsCompositingInputsUpdate();
-
-  geometry_map_.PopMappingsToAncestor(layer->Parent());
-
-  if (layer->SelfPaintingStatusChanged()) {
-    layer->ClearSelfPaintingStatusChanged();
-    // If the floating object becomes non-self-painting, so some ancestor should
-    // paint it; if it becomes self-painting, it should paint itself and no
-    // ancestor should paint it.
-    if (layout_object.IsFloating()) {
-      LayoutBlockFlow::UpdateAncestorShouldPaintFloatingObject(
-          *layer->GetLayoutBox());
-    }
-  }
 }
 
 void CompositingInputsUpdater::UpdateAncestorDependentCompositingInputs(
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.h b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.h
index 04fbc010..86574662 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.h
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.h
@@ -16,7 +16,8 @@
   STACK_ALLOCATED();
 
  public:
-  explicit CompositingInputsUpdater(PaintLayer* root_layer);
+  explicit CompositingInputsUpdater(PaintLayer* root_layer,
+                                    PaintLayer* compositing_inputs_root);
   ~CompositingInputsUpdater();
 
   void Update();
@@ -63,12 +64,23 @@
     bool is_under_video = false;
   };
 
-  void UpdateRecursive(PaintLayer*, UpdateType, AncestorInfo);
+  void UpdateSelfAndDescendantsRecursively(PaintLayer*,
+                                           UpdateType,
+                                           AncestorInfo);
   void UpdateAncestorDependentCompositingInputs(PaintLayer*,
                                                 const AncestorInfo&);
+  // This is a recursive method to compute the geometry_map_ and AncestorInfo
+  // starting from the root layer down to the compositing_inputs_root_.
+  void ApplyAncestorInfoToSelfAndAncestorsRecursively(PaintLayer*,
+                                                      UpdateType&,
+                                                      AncestorInfo&);
+  // This method takes care of updating AncestorInfo taking into account the
+  // current value of AncestorInfo.
+  void UpdateAncestorInfo(PaintLayer*, UpdateType&, AncestorInfo&);
 
   LayoutGeometryMap geometry_map_;
   PaintLayer* root_layer_;
+  PaintLayer* compositing_inputs_root_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
index da735a7ee..b6a7ff2d 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
@@ -372,8 +372,9 @@
     CompositingUpdateType update_type) {
   DCHECK(!HasAcceleratedCompositing());
 
-  if (update_type >= kCompositingUpdateAfterCompositingInputChange)
-    CompositingInputsUpdater(RootLayer()).Update();
+  if (update_type >= kCompositingUpdateAfterCompositingInputChange) {
+    CompositingInputsUpdater(RootLayer(), GetCompositingInputsRoot()).Update();
+  }
 
 #if DCHECK_IS_ON()
   CompositingInputsUpdater::AssertNeedsCompositingInputsUpdateBitsCleared(
@@ -459,7 +460,7 @@
   Vector<PaintLayer*> layers_needing_paint_invalidation;
 
   if (update_type >= kCompositingUpdateAfterCompositingInputChange) {
-    CompositingInputsUpdater(update_root).Update();
+    CompositingInputsUpdater(RootLayer(), GetCompositingInputsRoot()).Update();
 
 #if DCHECK_IS_ON()
     // FIXME: Move this check to the end of the compositing update.
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h
index 348f71a4..7264ea9 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h
@@ -30,6 +30,7 @@
 #include "base/gtest_prod_util.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/document_lifecycle.h"
+#include "third_party/blink/renderer/core/paint/compositing/compositing_inputs_root.h"
 #include "third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h"
 
 namespace blink {
@@ -156,6 +157,16 @@
 
   void AttachRootLayerViaChromeClient();
 
+  PaintLayer* GetCompositingInputsRoot() {
+    return compositing_inputs_root_.Get();
+  }
+
+  void ClearCompositingInputsRoot() { compositing_inputs_root_.Clear(); }
+
+  void UpdateCompositingInputsRoot(PaintLayer* layer) {
+    compositing_inputs_root_.Update(layer);
+  }
+
  private:
 #if DCHECK_IS_ON()
   void AssertNoUnresolvedDirtyBits();
@@ -222,6 +233,8 @@
   };
   RootLayerAttachment root_layer_attachment_;
 
+  CompositingInputsRoot compositing_inputs_root_;
+
   FRIEND_TEST_ALL_PREFIXES(FrameThrottlingTest,
                            IntersectionObservationOverridesThrottling);
 };
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor_test.cc b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor_test.cc
index 993d399..2add7985 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor_test.cc
@@ -110,4 +110,35 @@
   EXPECT_EQ(main_graphics_layer_parent, main_graphics_layer->Parent());
 }
 
+TEST_F(PaintLayerCompositorTest, CompositingInputsUpdateStopsContainStrict) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>
+      div {
+        position: relative;
+      }
+      #wrapper {
+        contain: strict;
+        }
+    </style>
+    <div id='wrapper'>
+      <div id='target'></div>
+    </div>
+  )HTML");
+
+  PaintLayer* wrapper = GetPaintLayerByElementId("wrapper");
+  PaintLayer* target = GetPaintLayerByElementId("target");
+  EXPECT_FALSE(wrapper->NeedsCompositingInputsUpdate());
+  EXPECT_FALSE(target->NeedsCompositingInputsUpdate());
+
+  target->SetNeedsCompositingInputsUpdate();
+  EXPECT_FALSE(wrapper->NeedsCompositingInputsUpdate());
+  EXPECT_TRUE(target->NeedsCompositingInputsUpdate());
+
+  GetDocument().View()->UpdateLifecycleToCompositingInputsClean();
+  EXPECT_EQ(DocumentLifecycle::kCompositingInputsClean,
+            GetDocument().Lifecycle().GetState());
+  EXPECT_FALSE(wrapper->NeedsCompositingInputsUpdate());
+  EXPECT_FALSE(target->NeedsCompositingInputsUpdate());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/image_element_timing.h b/third_party/blink/renderer/core/paint/image_element_timing.h
index 1c3b110..f009389 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing.h
+++ b/third_party/blink/renderer/core/paint/image_element_timing.h
@@ -45,7 +45,7 @@
   void Trace(blink::Visitor*) override;
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(ImageElementTimingTest, ImageInsideSVG);
+  friend class ImageElementTimingTest;
   // Callback for the swap promise. Reports paint timestamps.
   void ReportImagePaintSwapTime(WebLayerTreeView::SwapResult,
                                 base::TimeTicks timestamp);
diff --git a/third_party/blink/renderer/core/paint/image_element_timing_test.cc b/third_party/blink/renderer/core/paint/image_element_timing_test.cc
index 4e59631..528befe 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing_test.cc
+++ b/third_party/blink/renderer/core/paint/image_element_timing_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/paint/image_element_timing.h"
 
 #include "third_party/blink/renderer/core/layout/layout_image.h"
+#include "third_party/blink/renderer/core/layout/svg/layout_svg_image.h"
 #include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
@@ -24,8 +25,17 @@
     return layout_image;
   }
 
-  const ImageElementTiming& GetImageElementTiming() {
-    return ImageElementTiming::From(*GetDocument().domWindow());
+  // Similar to above but for a LayoutSVGImage.
+  LayoutSVGImage* SetSVGImageResource(const char* id, int width, int height) {
+    ImageResourceContent* content = CreateImageForTest(width, height);
+    auto* layout_image = ToLayoutSVGImage(GetLayoutObjectByElementId(id));
+    layout_image->ImageResource()->SetImageResource(content);
+    return layout_image;
+  }
+
+  const WTF::HashSet<const LayoutObject*>& GetImagesNotified() {
+    return ImageElementTiming::From(*GetDocument().domWindow())
+        .images_notified_;
   }
 
  private:
@@ -59,9 +69,44 @@
   // Enable compositing and also update document lifecycle.
   EnableCompositing();
 
-  const ImageElementTiming& timing = GetImageElementTiming();
   // |layout_image| should have had its paint notified to ImageElementTiming.
-  EXPECT_TRUE(timing.images_notified_.Contains(layout_image));
+  EXPECT_TRUE(GetImagesNotified().Contains(layout_image));
+}
+
+TEST_F(ImageElementTimingTest, ImageRemoved) {
+  EnableCompositing();
+  GetDocument().SetBaseURLOverride(KURL("http://test.com"));
+  SetBodyInnerHTML(R"HTML(
+    <img id="target" style='width: 100px; height: 100px;'/>
+  )HTML");
+  LayoutImage* layout_image = SetImageResource("target", 5, 5);
+  ASSERT_TRUE(layout_image);
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_TRUE(GetImagesNotified().Contains(layout_image));
+
+  GetDocument().getElementById("target")->remove();
+  // |layout_image| should no longer be part of |images_notified| since it will
+  // be destroyed.
+  EXPECT_TRUE(GetImagesNotified().IsEmpty());
+}
+
+TEST_F(ImageElementTimingTest, SVGImageRemoved) {
+  EnableCompositing();
+  GetDocument().SetBaseURLOverride(KURL("http://test.com"));
+  SetBodyInnerHTML(R"HTML(
+    <svg>
+      <image id="target" style='width: 100px; height: 100px;'/>
+    </svg>
+  )HTML");
+  LayoutSVGImage* layout_image = SetSVGImageResource("target", 5, 5);
+  ASSERT_TRUE(layout_image);
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_TRUE(GetImagesNotified().Contains(layout_image));
+
+  GetDocument().getElementById("target")->remove();
+  // |layout_image| should no longer be part of |images_notified| since it will
+  // be destroyed.
+  EXPECT_TRUE(GetImagesNotified().IsEmpty());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index 9adae7e..38c33fd8 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -449,8 +449,8 @@
   DrawingRecorder recorder(paint_info.context, box_fragment_, paint_info.phase);
   LayoutRect paint_rect =
       LayoutRect(paint_offset, box_fragment_.Size().ToLayoutSize());
-  PaintMaskImages(paint_info, paint_rect, box_fragment_, geometry,
-                  border_edges_.line_left, border_edges_.line_right);
+  PaintMaskImages(paint_info, paint_rect, *box_fragment_.GetLayoutObject(),
+                  geometry, border_edges_.line_left, border_edges_.line_right);
 }
 
 // TODO(kojii): This logic is kept in sync with BoxPainter. Not much efforts to
@@ -631,9 +631,10 @@
         ShouldPaintBoxFragmentBorders(layout_object)) {
       Node* generating_node = layout_object.GeneratingNode();
       const Document& document = layout_object.GetDocument();
-      PaintBorder(box_fragment_, document, generating_node, paint_info,
-                  paint_rect, style, box_decoration_data.bleed_avoidance,
-                  border_edges_.line_left, border_edges_.line_right);
+      PaintBorder(*box_fragment_.GetLayoutObject(), document, generating_node,
+                  paint_info, paint_rect, style,
+                  box_decoration_data.bleed_avoidance, border_edges_.line_left,
+                  border_edges_.line_right);
     }
   }
 
diff --git a/third_party/blink/renderer/core/paint/ng/ng_fieldset_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_fieldset_painter.cc
index 603f9a683..34d98e9 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_fieldset_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_fieldset_painter.cc
@@ -93,8 +93,9 @@
 
   LayoutObject* layout_object = fieldset_.GetLayoutObject();
   Node* node = layout_object->GeneratingNode();
-  fragment_painter.PaintBorder(fieldset_, layout_object->GetDocument(), node,
-                               paint_info, contracted_rect, fieldset_.Style());
+  fragment_painter.PaintBorder(*fieldset_.GetLayoutObject(),
+                               layout_object->GetDocument(), node, paint_info,
+                               contracted_rect, fieldset_.Style());
 }
 
 void NGFieldsetPainter::PaintLegend(const NGPaintFragment& legend,
diff --git a/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc
index 1db5137..d489a0f 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc
@@ -20,19 +20,28 @@
 namespace blink {
 
 NGInlineBoxFragmentPainter::NGInlineBoxFragmentPainter(
-    const NGPaintFragment& inline_box_fragment)
-    : InlineBoxPainterBase(
-          inline_box_fragment,
-          &inline_box_fragment.GetLayoutObject()->GetDocument(),
-          inline_box_fragment.GetLayoutObject()->GeneratingNode(),
-          inline_box_fragment.Style(),
-          // TODO(layout-dev): Should be first-line style.
-          inline_box_fragment.Style()),
+    const NGPaintFragment& inline_box_fragment,
+    const LayoutObject& layout_object,
+    const ComputedStyle& style,
+    const ComputedStyle& line_style)
+    : InlineBoxPainterBase(layout_object,
+                           &layout_object.GetDocument(),
+                           layout_object.GeneratingNode(),
+                           style,
+                           line_style),
       inline_box_fragment_(inline_box_fragment),
       border_edges_(NGBorderEdges::FromPhysical(
           inline_box_fragment.PhysicalFragment().BorderEdges(),
-          inline_box_fragment.Style().GetWritingMode())) {
-}
+          style.GetWritingMode())) {}
+
+NGInlineBoxFragmentPainter::NGInlineBoxFragmentPainter(
+    const NGPaintFragment& inline_box_fragment)
+    : NGInlineBoxFragmentPainter(
+          inline_box_fragment,
+          *inline_box_fragment.GetLayoutObject(),
+          inline_box_fragment.Style(),
+          // TODO(layout-dev): Should be first-line style.
+          inline_box_fragment.Style()) {}
 
 void NGInlineBoxFragmentPainter::Paint(const PaintInfo& paint_info,
                                        const LayoutPoint& paint_offset) {
diff --git a/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h b/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h
index 1b4a60e..f08bd538 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h
+++ b/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.h
@@ -45,6 +45,11 @@
                            const LayoutRect& paint_rect) override;
 
  private:
+  NGInlineBoxFragmentPainter(const NGPaintFragment& inline_box_fragment,
+                             const LayoutObject& layout_object,
+                             const ComputedStyle& style,
+                             const ComputedStyle& line_style);
+
   void PaintBackgroundBorderShadow(const PaintInfo&,
                                    const LayoutPoint& paint_offset);
 
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
index 9504842..38ff7981 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
@@ -36,8 +36,7 @@
 namespace {
 
 struct SameSizeAsNGPaintFragment : public RefCounted<NGPaintFragment>,
-                                   public DisplayItemClient,
-                                   public ImageResourceObserver {
+                                   public DisplayItemClient {
   void* pointers[6];
   NGPhysicalOffset offsets[2];
   unsigned flags;
@@ -316,25 +315,6 @@
   return paint_fragment;
 }
 
-const NGPaintFragment* NGPaintFragment::Last() const {
-  for (const NGPaintFragment* fragment = this;;) {
-    const NGPaintFragment* next = fragment->Next();
-    if (!next)
-      return fragment;
-    fragment = next;
-  }
-}
-
-const NGPaintFragment* NGPaintFragment::Last(
-    const NGBreakToken& break_token) const {
-  for (const NGPaintFragment* fragment = this; fragment;
-       fragment = fragment->Next()) {
-    if (fragment->PhysicalFragment().BreakToken() == &break_token)
-      return fragment;
-  }
-  return nullptr;
-}
-
 scoped_refptr<NGPaintFragment>* NGPaintFragment::Find(
     scoped_refptr<NGPaintFragment>* fragment,
     const NGBlockBreakToken* break_token) {
@@ -358,10 +338,6 @@
   NOTREACHED();
 }
 
-void NGPaintFragment::SetNext(scoped_refptr<NGPaintFragment> fragment) {
-  next_fragmented_ = std::move(fragment);
-}
-
 bool NGPaintFragment::IsDescendantOfNotSelf(
     const NGPaintFragment& ancestor) const {
   for (const NGPaintFragment* fragment = Parent(); fragment;
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
index 4bf2e757..8a2e2a6 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
@@ -9,7 +9,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h"
-#include "third_party/blink/renderer/core/loader/resource/image_resource_observer.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item_client.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
@@ -36,8 +35,7 @@
 // - image (<img>, svg <image>) or video (<video>) elements that are
 //   placeholders for displaying them.
 class CORE_EXPORT NGPaintFragment : public RefCounted<NGPaintFragment>,
-                                    public DisplayItemClient,
-                                    public ImageResourceObserver {
+                                    public DisplayItemClient {
  public:
   NGPaintFragment(scoped_refptr<const NGPhysicalFragment>,
                   NGPhysicalOffset offset,
@@ -53,11 +51,6 @@
     return *physical_fragment_;
   }
 
-  // Next/last fragment for  when this is fragmented.
-  const NGPaintFragment* Next() const { return next_fragmented_.get(); }
-  void SetNext(scoped_refptr<NGPaintFragment>);
-  const NGPaintFragment* Last() const;
-  const NGPaintFragment* Last(const NGBreakToken&) const;
   static scoped_refptr<NGPaintFragment>* Find(scoped_refptr<NGPaintFragment>*,
                                               const NGBlockBreakToken*);
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 3e43e99d..8c15805 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -1085,20 +1085,39 @@
   MarkAncestorChainForFlagsUpdate();
 }
 
+void PaintLayer::SetChildNeedsCompositingInputsUpdateUpToAncestor(
+    PaintLayer* ancestor) {
+  DCHECK(ancestor);
+
+  for (auto* layer = this; layer && layer != ancestor; layer = layer->Parent())
+    layer->child_needs_compositing_inputs_update_ = true;
+
+  ancestor->child_needs_compositing_inputs_update_ = true;
+}
+
 void PaintLayer::SetNeedsCompositingInputsUpdateInternal() {
   if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
     return;
 
   needs_ancestor_dependent_compositing_inputs_update_ = true;
 
+  PaintLayer* last_ancestor = nullptr;
   for (PaintLayer* current = this;
        current && !current->child_needs_compositing_inputs_update_;
-       current = current->Parent())
+       current = current->Parent()) {
+    last_ancestor = current;
     current->child_needs_compositing_inputs_update_ = true;
+    if (Compositor() &&
+        current->GetLayoutObject().ShouldApplyStrictContainment())
+      break;
+  }
 
   if (Compositor()) {
     Compositor()->SetNeedsCompositingUpdate(
         kCompositingUpdateAfterCompositingInputChange);
+
+    if (last_ancestor)
+      Compositor()->UpdateCompositingInputsRoot(last_ancestor);
   }
 }
 
@@ -3413,6 +3432,43 @@
   needs_repaint_ = false;
 }
 
+const PaintLayer* PaintLayer::CommonAncestor(const PaintLayer* other) const {
+  DCHECK(other);
+  if (this == other)
+    return this;
+
+  int this_depth = 0;
+  for (auto* layer = this; layer; layer = layer->Parent()) {
+    if (layer == other)
+      return layer;
+    this_depth++;
+  }
+  int other_depth = 0;
+  for (auto* layer = other; layer; layer = layer->Parent()) {
+    if (layer == this)
+      return layer;
+    other_depth++;
+  }
+
+  const PaintLayer* this_iterator = this;
+  const PaintLayer* other_iterator = other;
+  for (; this_depth > other_depth; this_depth--)
+    this_iterator = this_iterator->Parent();
+  for (; other_depth > this_depth; other_depth--)
+    other_iterator = other_iterator->Parent();
+
+  while (this_iterator) {
+    if (this_iterator == other_iterator)
+      return this_iterator;
+    this_iterator = this_iterator->Parent();
+    other_iterator = other_iterator->Parent();
+  }
+
+  DCHECK(!this_iterator);
+  DCHECK(!other_iterator);
+  return nullptr;
+}
+
 DisableCompositingQueryAsserts::DisableCompositingQueryAsserts()
     : disabler_(&g_compositing_query_mode, kCompositingQueriesAreAllowed) {}
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index 7934510b..7d619ac 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -245,6 +245,8 @@
   PaintLayer* FirstChild() const { return first_; }
   PaintLayer* LastChild() const { return last_; }
 
+  const PaintLayer* CommonAncestor(const PaintLayer*) const;
+
   // TODO(wangxianzhu): Find a better name for it. 'paintContainer' might be
   // good but we can't use it for now because it conflicts with
   // PaintInfo::paintContainer.
@@ -764,6 +766,9 @@
   void SetNeedsVisualOverflowRecalc();
   void SetNeedsCompositingInputsUpdate();
 
+  // This methods marks everything from this layer up to the |ancestor| argument
+  // (both included).
+  void SetChildNeedsCompositingInputsUpdateUpToAncestor(PaintLayer* ancestor);
   // Use this internal method only for cases during the descendant-dependent
   // tree walk.
   bool ChildNeedsCompositingInputsUpdate() const {
@@ -1064,7 +1069,7 @@
   // for the definition of a replaced normal-flow stacking element.
   bool IsReplacedNormalFlowStacking() const;
 
-  void SetNeeedsCompositingReasonsUpdate() {
+  void SetNeedsCompositingReasonsUpdate() {
     needs_compositing_reasons_update_ = true;
   }
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h b/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h
index c813fab4..76ad5db 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_stacking_node.h
@@ -169,12 +169,6 @@
   // have z-order lists.
   bool z_order_lists_dirty_ : 1;
 
-  // This attribute caches whether the element was stacked. It's needed to check
-  // the current stacked status (instead of the new stacked status determined by
-  // the new style which has not been realized yet) when a layer is removed due
-  // to style change.
-  bool is_stacked_ : 1;
-
 #if DCHECK_IS_ON()
   bool layer_list_mutation_allowed_ : 1;
 #endif
diff --git a/third_party/blink/renderer/core/paint/paint_layer_test.cc b/third_party/blink/renderer/core/paint/paint_layer_test.cc
index 82107f7..b35bf3b 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_test.cc
@@ -1896,4 +1896,67 @@
   EXPECT_TRUE(target->Parent()->GetLayoutObject().NeedsPaintPropertyUpdate());
 }
 
+TEST_P(PaintLayerTest, PaintLayerCommonAncestor) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      div {
+        position: relative;
+      }
+    </style>
+    <div id='wrapper'>
+      <div id='target1'>
+        <div id='target1x1'></div>
+      </div>
+      <div id='target2'></div>
+      <div>
+        <div id='target3'></div>
+      </div>
+    </div>
+  )HTML");
+
+  PaintLayer* wrapper = GetPaintLayerByElementId("wrapper");
+  PaintLayer* target1 = GetPaintLayerByElementId("target1");
+  PaintLayer* target1x1 = GetPaintLayerByElementId("target1x1");
+  PaintLayer* target2 = GetPaintLayerByElementId("target2");
+  PaintLayer* target3 = GetPaintLayerByElementId("target3");
+
+  EXPECT_EQ(target1->CommonAncestor(target1), target1);
+  EXPECT_EQ(target1->CommonAncestor(target1x1), target1);
+  EXPECT_EQ(target1->CommonAncestor(target2), wrapper);
+  EXPECT_EQ(target1->CommonAncestor(target3), wrapper);
+
+  EXPECT_EQ(target1x1->CommonAncestor(target1), target1);
+  EXPECT_EQ(target1x1->CommonAncestor(target1x1), target1x1);
+  EXPECT_EQ(target1x1->CommonAncestor(target2), wrapper);
+  EXPECT_EQ(target1x1->CommonAncestor(target3), wrapper);
+
+  EXPECT_EQ(target2->CommonAncestor(target1), wrapper);
+  EXPECT_EQ(target2->CommonAncestor(target1x1), wrapper);
+  EXPECT_EQ(target2->CommonAncestor(target2), target2);
+  EXPECT_EQ(target2->CommonAncestor(target3), wrapper);
+
+  EXPECT_EQ(target3->CommonAncestor(target1), wrapper);
+  EXPECT_EQ(target3->CommonAncestor(target1x1), wrapper);
+  EXPECT_EQ(target3->CommonAncestor(target2), wrapper);
+  EXPECT_EQ(target3->CommonAncestor(target3), target3);
+}
+
+TEST_P(PaintLayerTest, PaintLayerCommonAncestorBody) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      body, div {
+        position: relative;
+      }
+    </style>
+    <div id='target1'></div>
+    <div id='target2'></div>
+  )HTML");
+
+  PaintLayer* target1 = GetPaintLayerByElementId("target1");
+  PaintLayer* target2 = GetPaintLayerByElementId("target2");
+
+  EXPECT_EQ(target1->CommonAncestor(target2)->GetLayoutObject(),
+            GetDocument().body()->GetLayoutObject());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/script/modulator.cc b/third_party/blink/renderer/core/script/modulator.cc
index b0561f36..cfa10329 100644
--- a/third_party/blink/renderer/core/script/modulator.cc
+++ b/third_party/blink/renderer/core/script/modulator.cc
@@ -42,20 +42,22 @@
     // See comment in LocalDOMWindow::modulator_ for this workaround.
     LocalDOMWindow* window = document->ExecutingWindow();
     window->SetModulator(modulator);
-  } else if (auto* scope = DynamicTo<WorkletGlobalScope>(execution_context)) {
+  } else if (auto* worklet_scope =
+                 DynamicTo<WorkletGlobalScope>(execution_context)) {
     modulator = WorkletModulatorImpl::Create(script_state);
     Modulator::SetModulator(script_state, modulator);
 
     // See comment in WorkerOrWorkletGlobalScope::modulator_ for this
     // workaround.
-    scope->SetModulator(modulator);
-  } else if (auto* scope = DynamicTo<WorkerGlobalScope>(execution_context)) {
+    worklet_scope->SetModulator(modulator);
+  } else if (auto* worker_scope =
+                 DynamicTo<WorkerGlobalScope>(execution_context)) {
     modulator = WorkerModulatorImpl::Create(script_state);
     Modulator::SetModulator(script_state, modulator);
 
     // See comment in WorkerOrWorkletGlobalScope::modulator_ for this
     // workaround.
-    scope->SetModulator(modulator);
+    worker_scope->SetModulator(modulator);
   } else {
     NOTREACHED();
   }
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 152e7ec1..9eacec8 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -883,6 +883,15 @@
       HasCurrentFilterAnimation() != other.HasCurrentFilterAnimation() ||
       HasCurrentBackdropFilterAnimation() !=
           other.HasCurrentBackdropFilterAnimation() ||
+      IsRunningTransformAnimationOnCompositor() !=
+          other.IsRunningTransformAnimationOnCompositor() ||
+      IsRunningOpacityAnimationOnCompositor() !=
+          other.IsRunningOpacityAnimationOnCompositor() ||
+      IsRunningFilterAnimationOnCompositor() !=
+          other.IsRunningFilterAnimationOnCompositor() ||
+      IsRunningBackdropFilterAnimationOnCompositor() !=
+          other.IsRunningBackdropFilterAnimationOnCompositor() ||
+      SubtreeWillChangeContents() != other.SubtreeWillChangeContents() ||
       BackfaceVisibility() != other.BackfaceVisibility() ||
       HasWillChangeCompositingHint() != other.HasWillChangeCompositingHint() ||
       UsedTransformStyle3D() != other.UsedTransformStyle3D() ||
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index fa33f59..5af2e920 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -282,8 +282,9 @@
     // flat tree in the presence of display:contents.
     kDisplayAffectingDescendantStyles,
   };
-  static Difference ComputeDifference(const ComputedStyle* old_style,
-                                      const ComputedStyle* new_style);
+  CORE_EXPORT static Difference ComputeDifference(
+      const ComputedStyle* old_style,
+      const ComputedStyle* new_style);
 
   // Returns true if the ComputedStyle change requires a LayoutObject re-attach.
   static bool NeedsReattachLayoutTree(const ComputedStyle* old_style,
@@ -317,8 +318,8 @@
   StyleContentAlignmentData ResolvedJustifyContent(
       const StyleContentAlignmentData& normal_behaviour) const;
 
-  StyleDifference VisualInvalidationDiff(const Document&,
-                                         const ComputedStyle&) const;
+  CORE_EXPORT StyleDifference
+  VisualInvalidationDiff(const Document&, const ComputedStyle&) const;
 
   CORE_EXPORT void InheritFrom(const ComputedStyle& inherit_parent,
                                IsAtShadowBoundary = kNotAtShadowBoundary);
diff --git a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5 b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
index 4279172..9846eb76 100644
--- a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
+++ b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
@@ -783,7 +783,6 @@
       field_template: "primitive",
       type_name: "bool",
       field_group: "*",
-      custom_compare: true,
       default_value: "false",
     },
     {
@@ -791,7 +790,6 @@
       field_template: "primitive",
       type_name: "bool",
       field_group: "*",
-      custom_compare: true,
       default_value: "false",
     },
     {
@@ -799,7 +797,6 @@
       field_template: "primitive",
       type_name: "bool",
       field_group: "*",
-      custom_compare: true,
       default_value: "false",
     },
     {
@@ -807,7 +804,6 @@
       field_template: "primitive",
       type_name: "bool",
       field_group: "*",
-      custom_compare: true,
       default_value: "false",
     },
     // A stacking context is painted atomically and defines a stacking order,
diff --git a/third_party/blink/renderer/core/style/computed_style_test.cc b/third_party/blink/renderer/core/style/computed_style_test.cc
index a2e26f8..b05fbee5 100644
--- a/third_party/blink/renderer/core/style/computed_style_test.cc
+++ b/third_party/blink/renderer/core/style/computed_style_test.cc
@@ -7,6 +7,7 @@
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/css/css_gradient_value.h"
+#include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/style/clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/shape_value.h"
@@ -401,4 +402,33 @@
   EXPECT_TRUE(style->BorderSizeEquals(*other));
 }
 
+#define TEST_ANIMATION_FLAG(flag, inherited)                               \
+  do {                                                                     \
+    auto style = ComputedStyle::Create();                                  \
+    auto other = ComputedStyle::Create();                                  \
+    EXPECT_FALSE(style->flag());                                           \
+    EXPECT_FALSE(other->flag());                                           \
+    style->Set##flag(true);                                                \
+    EXPECT_TRUE(style->flag());                                            \
+    EXPECT_EQ(ComputedStyle::Difference::inherited,                        \
+              ComputedStyle::ComputeDifference(style.get(), other.get())); \
+    auto diff = style->VisualInvalidationDiff(*document, *other);          \
+    EXPECT_TRUE(diff.HasDifference());                                     \
+    EXPECT_TRUE(diff.CompositingReasonsChanged());                         \
+  } while (false)
+
+TEST(ComputedStyleTest, AnimationFlags) {
+  Persistent<Document> document = Document::CreateForTest();
+  TEST_ANIMATION_FLAG(HasCurrentTransformAnimation, kNonInherited);
+  TEST_ANIMATION_FLAG(HasCurrentOpacityAnimation, kNonInherited);
+  TEST_ANIMATION_FLAG(HasCurrentFilterAnimation, kNonInherited);
+  TEST_ANIMATION_FLAG(HasCurrentBackdropFilterAnimation, kNonInherited);
+  TEST_ANIMATION_FLAG(IsRunningTransformAnimationOnCompositor, kNonInherited);
+  TEST_ANIMATION_FLAG(IsRunningOpacityAnimationOnCompositor, kNonInherited);
+  TEST_ANIMATION_FLAG(IsRunningFilterAnimationOnCompositor, kNonInherited);
+  TEST_ANIMATION_FLAG(IsRunningBackdropFilterAnimationOnCompositor,
+                      kNonInherited);
+  TEST_ANIMATION_FLAG(SubtreeWillChangeContents, kInherited);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/style/style_difference.h b/third_party/blink/renderer/core/style/style_difference.h
index e5a387a6..4e80373 100644
--- a/third_party/blink/renderer/core/style/style_difference.h
+++ b/third_party/blink/renderer/core/style/style_difference.h
@@ -40,13 +40,13 @@
         visual_rect_update_(false),
         property_specific_differences_(0),
         scroll_anchor_disabling_property_changed_(false),
-        composited_reasons_changed_(false) {}
+        compositing_reasons_changed_(false) {}
 
   bool HasDifference() const {
     return paint_invalidation_type_ || layout_type_ ||
            property_specific_differences_ || recompute_overflow_ ||
            visual_rect_update_ || scroll_anchor_disabling_property_changed_ ||
-           composited_reasons_changed_;
+           compositing_reasons_changed_;
   }
 
   bool HasAtMostPropertySpecificDifferences(
@@ -167,8 +167,10 @@
   void SetScrollAnchorDisablingPropertyChanged() {
     scroll_anchor_disabling_property_changed_ = true;
   }
-  bool CompositingReasonsChanged() const { return composited_reasons_changed_; }
-  void SetCompositingReasonsChanged() { composited_reasons_changed_ = true; }
+  bool CompositingReasonsChanged() const {
+    return compositing_reasons_changed_;
+  }
+  void SetCompositingReasonsChanged() { compositing_reasons_changed_ = true; }
 
  private:
   static constexpr int kPropertyDifferenceCount = 10;
@@ -189,7 +191,7 @@
   unsigned visual_rect_update_ : 1;
   unsigned property_specific_differences_ : kPropertyDifferenceCount;
   unsigned scroll_anchor_disabling_property_changed_ : 1;
-  unsigned composited_reasons_changed_ : 1;
+  unsigned compositing_reasons_changed_ : 1;
 };
 
 CORE_EXPORT std::ostream& operator<<(std::ostream&, const StyleDifference&);
diff --git a/third_party/blink/renderer/core/timing/memory_info_test.cc b/third_party/blink/renderer/core/timing/memory_info_test.cc
index 5cebb2f..ec95e15 100644
--- a/third_party/blink/renderer/core/timing/memory_info_test.cc
+++ b/third_party/blink/renderer/core/timing/memory_info_test.cc
@@ -54,6 +54,12 @@
   EXPECT_EQ(10000000u, QuantizeMemorySize(3));
   EXPECT_EQ(10000000u, QuantizeMemorySize(1));
   EXPECT_EQ(10000000u, QuantizeMemorySize(0));
+  // Rounding differences between OS's may affect the precise value of the last
+  // bucket.
+  EXPECT_LE(3760000000u,
+            QuantizeMemorySize(std::numeric_limits<size_t>::max()));
+  EXPECT_GT(4000000000u,
+            QuantizeMemorySize(std::numeric_limits<size_t>::max()));
 }
 
 static constexpr int kModForBucketizationCheck = 100000;
diff --git a/third_party/blink/renderer/devtools/BUILD.gn b/third_party/blink/renderer/devtools/BUILD.gn
index 1b7f2fe..98860d8 100644
--- a/third_party/blink/renderer/devtools/BUILD.gn
+++ b/third_party/blink/renderer/devtools/BUILD.gn
@@ -466,6 +466,7 @@
   "front_end/perf_ui/FlameChart.js",
   "front_end/perf_ui/GCActionDelegate.js",
   "front_end/perf_ui/LineLevelProfile.js",
+  "front_end/perf_ui/LiveHeapProfile.js",
   "front_end/perf_ui/NetworkPriorities.js",
   "front_end/perf_ui/OverviewGrid.js",
   "front_end/perf_ui/PieChart.js",
diff --git a/third_party/blink/renderer/devtools/front_end/elements/StylesSidebarPane.js b/third_party/blink/renderer/devtools/front_end/elements/StylesSidebarPane.js
index 4ef306a2..bc7d485 100644
--- a/third_party/blink/renderer/devtools/front_end/elements/StylesSidebarPane.js
+++ b/third_party/blink/renderer/devtools/front_end/elements/StylesSidebarPane.js
@@ -1394,6 +1394,8 @@
       const isShorthand = !!style.longhandProperties(property.name).length;
       const inherited = this.isPropertyInherited(property.name);
       const overloaded = this._isPropertyOverloaded(property);
+      if (style.parentRule && style.parentRule.isUserAgent() && inherited)
+        continue;
       const item = new Elements.StylePropertyTreeElement(
           this._parentPane, this._matchedStyles, property, isShorthand, inherited, overloaded, false);
       this.propertiesTreeOutline.appendChild(item);
diff --git a/third_party/blink/renderer/devtools/front_end/help/Help.js b/third_party/blink/renderer/devtools/front_end/help/Help.js
index 38e2ca0..b4846e71 100644
--- a/third_party/blink/renderer/devtools/front_end/help/Help.js
+++ b/third_party/blink/renderer/devtools/front_end/help/Help.js
@@ -60,7 +60,8 @@
    * @override
    */
   run() {
-    Help._showReleaseNoteIfNeeded();
+    if (!Host.isUnderTest())
+      Help._showReleaseNoteIfNeeded();
   }
 };
 
diff --git a/third_party/blink/renderer/devtools/front_end/main/Main.js b/third_party/blink/renderer/devtools/front_end/main/Main.js
index 3d3214e..53e24c0a 100644
--- a/third_party/blink/renderer/devtools/front_end/main/Main.js
+++ b/third_party/blink/renderer/devtools/front_end/main/Main.js
@@ -106,15 +106,16 @@
   _initializeExperiments() {
     // Keep this sorted alphabetically: both keys and values.
     Runtime.experiments.register('applyCustomStylesheet', 'Allow custom UI themes');
+    Runtime.experiments.register('sourcesPrettyPrint', 'Automatically pretty print in the Sources Panel');
     Runtime.experiments.register('backgroundServices', 'Background web platform feature events', true);
     Runtime.experiments.register('blackboxJSFramesOnTimeline', 'Blackbox JavaScript frames on Timeline', true);
     Runtime.experiments.register('emptySourceMapAutoStepping', 'Empty sourcemap auto-stepping');
     Runtime.experiments.register('inputEventsOnTimelineOverview', 'Input events on Timeline overview', true);
+    Runtime.experiments.register('liveHeapProfile', 'Live heap profile', true);
     Runtime.experiments.register('nativeHeapProfiler', 'Native memory sampling heap profiler', true);
     Runtime.experiments.register('protocolMonitor', 'Protocol Monitor');
     Runtime.experiments.register('samplingHeapProfilerTimeline', 'Sampling heap profiler timeline', true);
     Runtime.experiments.register('sourceDiff', 'Source diff');
-    Runtime.experiments.register('sourcesPrettyPrint', 'Automatically pretty print in the Sources Panel');
     Runtime.experiments.register('splitInDrawer', 'Split in drawer', true);
     Runtime.experiments.register('terminalInDrawer', 'Terminal in drawer', true);
 
@@ -128,6 +129,9 @@
 
     Runtime.experiments.cleanUpStaleExperiments();
     Runtime.experiments.setDefaultExperiments([]);
+
+    if (Host.isUnderTest() && Runtime.queryParam('test').includes('live-line-level-heap-profile.js'))
+      Runtime.experiments.enableForTest('liveHeapProfile');
   }
 
   /**
@@ -261,10 +265,8 @@
     Main.Main.time('Main._lateInitialization');
     this._registerShortcuts();
     Extensions.extensionServer.initializeExtensions();
-    if (!Host.isUnderTest()) {
-      for (const extension of self.runtime.extensions('late-initialization'))
-        extension.instance().then(instance => (/** @type {!Common.Runnable} */ (instance)).run());
-    }
+    for (const extension of self.runtime.extensions('late-initialization'))
+      extension.instance().then(instance => (/** @type {!Common.Runnable} */ (instance)).run());
     Main.Main.timeEnd('Main._lateInitialization');
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js b/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js
index 4541b619..c4ef519 100644
--- a/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js
+++ b/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js
@@ -254,7 +254,7 @@
   _createElement(type, value) {
     const element = createElementWithClass('div', 'text-editor-line-marker-text');
     if (type === 'performance') {
-      const intensity = Number.constrain(Math.log10(1 + 2 * value) / 5, 0.02, 1);
+      const intensity = Number.constrain(Math.log10(1 + 10 * value) / 5, 0.02, 1);
       element.textContent = Common.UIString('%.1f', value);
       element.style.backgroundColor = `hsla(44, 100%, 50%, ${intensity.toFixed(3)})`;
       element.createChild('span', 'line-marker-units').textContent = ls`ms`;
diff --git a/third_party/blink/renderer/devtools/front_end/perf_ui/LiveHeapProfile.js b/third_party/blink/renderer/devtools/front_end/perf_ui/LiveHeapProfile.js
new file mode 100644
index 0000000..fc275a8
--- /dev/null
+++ b/third_party/blink/renderer/devtools/front_end/perf_ui/LiveHeapProfile.js
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @implements {Common.Runnable}
+ * @implements {SDK.SDKModelObserver<!SDK.HeapProfilerModel>}
+ */
+PerfUI.LiveHeapProfile = class {
+  /**
+   * @override
+   */
+  run() {
+    SDK.targetManager.observeModels(SDK.HeapProfilerModel, this);
+    requestIdleCallback(() => this.onUpdateProfiles());
+    PerfUI.LiveHeapProfile.hasStartedForTest(true);
+  }
+
+  /**
+   * @param {boolean=} started
+   * @return {!Promise}
+   */
+  static hasStartedForTest(started) {
+    if (!PerfUI.LiveHeapProfile._startedPromise)
+      PerfUI.LiveHeapProfile._startedPromise = new Promise(r => PerfUI.LiveHeapProfile._startedCallback = r);
+    if (started)
+      PerfUI.LiveHeapProfile._startedCallback();
+    return PerfUI.LiveHeapProfile._startedPromise;
+  }
+
+  /**
+   * @override
+   * @param {!SDK.HeapProfilerModel} model
+   */
+  modelAdded(model) {
+    model.startSampling(1024);
+  }
+
+  /**
+   * @override
+   * @param {!SDK.HeapProfilerModel} model
+   */
+  modelRemoved(model) {
+    model.stopSampling();
+  }
+
+  async onUpdateProfiles() {
+    const models = SDK.targetManager.models(SDK.HeapProfilerModel);
+    const profiles = await Promise.all(models.map(model => model.getSamplingProfile()));
+    const lineLevelProfile = PerfUI.LineLevelProfile.Memory.instance();
+    lineLevelProfile.reset();
+    for (let i = 0; i < profiles.length; ++i) {
+      if (profiles[i])
+        lineLevelProfile.appendHeapProfile(profiles[i], models[i].target());
+    }
+    const updateInterval = Host.isUnderTest() ? 10 : 5000;
+    setTimeout(() => requestIdleCallback(() => this.onUpdateProfiles()), updateInterval);
+  }
+};
diff --git a/third_party/blink/renderer/devtools/front_end/perf_ui/module.json b/third_party/blink/renderer/devtools/front_end/perf_ui/module.json
index ce8d46c9..476922e1 100644
--- a/third_party/blink/renderer/devtools/front_end/perf_ui/module.json
+++ b/third_party/blink/renderer/devtools/front_end/perf_ui/module.json
@@ -1,6 +1,11 @@
 {
     "extensions": [
         {
+            "type": "late-initialization",
+            "className": "PerfUI.LiveHeapProfile",
+            "experiment": "liveHeapProfile"
+        },
+        {
             "type": "@SourceFrame.LineDecorator",
             "className": "PerfUI.LineLevelProfile.LineDecorator",
             "decoratorType": "performance"
@@ -51,6 +56,7 @@
         "FlameChart.js",
         "GCActionDelegate.js",
         "LineLevelProfile.js",
+        "LiveHeapProfile.js",
         "NetworkPriorities.js",
         "OverviewGrid.js",
         "PieChart.js",
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/CSSMatchedStyles.js b/third_party/blink/renderer/devtools/front_end/sdk/CSSMatchedStyles.js
index 7feefae..89af71b9 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/CSSMatchedStyles.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/CSSMatchedStyles.js
@@ -38,6 +38,14 @@
     this._nodeForStyle = new Map();
     /** @type {!Set<!SDK.CSSStyleDeclaration>} */
     this._inheritedStyles = new Set();
+
+    for (const result of matchedPayload)
+      cleanUserAgentSelectors(result);
+    for (const inheritedResult of inheritedPayload) {
+      for (const result of inheritedResult.matchedCSSRules)
+        cleanUserAgentSelectors(result);
+    }
+
     this._mainDOMCascade = this._buildMainCascade(inlinePayload, attributesPayload, matchedPayload, inheritedPayload);
     this._pseudoDOMCascades = this._buildPseudoCascades(pseudoPayload);
 
@@ -47,6 +55,18 @@
       for (const style of domCascade.styles())
         this._styleToDOMCascade.set(style, domCascade);
     }
+
+    /**
+     * @param {!Protocol.CSS.RuleMatch} ruleMatch
+     */
+    function cleanUserAgentSelectors(ruleMatch) {
+      const {matchingSelectors, rule} = ruleMatch;
+      if (rule.origin !== 'user-agent' || !matchingSelectors.length)
+        return;
+      rule.selectorList.selectors = rule.selectorList.selectors.filter((item, i) => matchingSelectors.includes(i));
+      rule.selectorList.text = rule.selectorList.selectors.map(item => item.text).join(', ');
+      ruleMatch.matchingSelectors = matchingSelectors.map((item, i) => i);
+    }
   }
 
   /**
diff --git a/third_party/blink/renderer/devtools/front_end/test_runner/TestRunner.js b/third_party/blink/renderer/devtools/front_end/test_runner/TestRunner.js
index 8848d38a..32cb07e 100644
--- a/third_party/blink/renderer/devtools/front_end/test_runner/TestRunner.js
+++ b/third_party/blink/renderer/devtools/front_end/test_runner/TestRunner.js
@@ -1210,6 +1210,7 @@
  */
 TestRunner.loadedModules = function() {
   return self.runtime._modules.filter(module => module._loadedForTest)
+      .filter(module => module.name() !== 'help')
       .filter(module => module.name().indexOf('test_runner') === -1);
 };
 
diff --git a/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css b/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css
index b68d407..22df9a2c9 100644
--- a/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css
+++ b/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css
@@ -9,12 +9,7 @@
     overflow: visible !important;
 }
 
-.CodeMirror-gutter-performance {
-    width: 60px;
-    background-color: white;
-    margin-left: 3px;
-}
-
+.CodeMirror-gutter-performance,
 .CodeMirror-gutter-memory {
     width: 60px;
     background-color: white;
@@ -437,7 +432,8 @@
 }
 
 .CodeMirror .text-editor-line-marker-text span.line-marker-units {
-    color: #999;
+    color: #555;
+    font-size: 75%;
     margin-left: 3px;
 }
 
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_event.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_event.idl
index 65c470a..e4c633e 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_event.idl
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_event.idl
@@ -7,7 +7,7 @@
 [
     Constructor(DOMString type, BackgroundFetchEventInit init),
     Exposed=ServiceWorker,
-    OriginTrialEnabled=BackgroundFetch
+    RuntimeEnabled=BackgroundFetch
 ] interface BackgroundFetchEvent : ExtendableEvent {
     readonly attribute BackgroundFetchRegistration registration;
 };
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_manager.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_manager.idl
index cd401cc..d62ceda 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_manager.idl
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_manager.idl
@@ -6,7 +6,7 @@
 
 [
     Exposed=(Window,Worker),
-    OriginTrialEnabled=BackgroundFetch
+    RuntimeEnabled=BackgroundFetch
 ] interface BackgroundFetchManager {
     [CallWith=ScriptState, RaisesException, MeasureAs=BackgroundFetchManagerFetch] Promise<BackgroundFetchRegistration> fetch(DOMString id, (RequestInfo or sequence<RequestInfo>) requests, optional BackgroundFetchOptions options);
     [CallWith=ScriptState, MeasureAs=BackgroundFetchManagerGet] Promise<BackgroundFetchRegistration?> get(DOMString id);
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_record.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_record.idl
index f902243..ae757f28 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_record.idl
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_record.idl
@@ -6,7 +6,7 @@
 
 [
     Exposed=(Window,Worker),
-    OriginTrialEnabled=BackgroundFetch
+    RuntimeEnabled=BackgroundFetch
 ] interface BackgroundFetchRecord {
     readonly attribute Request request;
     [CallWith=ScriptState] readonly attribute Promise<Response> responseReady;
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_registration.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_registration.idl
index 6442de9..fa906d7 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_registration.idl
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_registration.idl
@@ -5,7 +5,7 @@
 // https://wicg.github.io/background-fetch/#background-fetch-registration
 [
     Exposed=(Window,Worker),
-    OriginTrialEnabled=BackgroundFetch,
+    RuntimeEnabled=BackgroundFetch,
     ActiveScriptWrappable
 ] interface BackgroundFetchRegistration : EventTarget {
     readonly attribute DOMString id;
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_event.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_event.idl
index 20a3655..b494c006 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_event.idl
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_event.idl
@@ -7,7 +7,7 @@
 [
     Constructor(DOMString type, BackgroundFetchEventInit init),
     Exposed=ServiceWorker,
-    OriginTrialEnabled=BackgroundFetch
+    RuntimeEnabled=BackgroundFetch
 ] interface BackgroundFetchUpdateUIEvent : BackgroundFetchEvent {
     [CallWith=ScriptState] Promise<void> updateUI(BackgroundFetchUIOptions options);
 };
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/background_fetch/service_worker_global_scope_background_fetch.idl b/third_party/blink/renderer/modules/background_fetch/service_worker_global_scope_background_fetch.idl
index 7dc81fe2..2f9f6c0 100644
--- a/third_party/blink/renderer/modules/background_fetch/service_worker_global_scope_background_fetch.idl
+++ b/third_party/blink/renderer/modules/background_fetch/service_worker_global_scope_background_fetch.idl
@@ -6,7 +6,7 @@
 
 [
     ImplementedAs=ServiceWorkerGlobalScopeBackgroundFetch,
-    OriginTrialEnabled=BackgroundFetch
+    RuntimeEnabled=BackgroundFetch
 ] partial interface ServiceWorkerGlobalScope {
     attribute EventHandler onbackgroundfetchsuccess;
     attribute EventHandler onbackgroundfetchfail;
diff --git a/third_party/blink/renderer/modules/background_fetch/service_worker_registration_background_fetch.idl b/third_party/blink/renderer/modules/background_fetch/service_worker_registration_background_fetch.idl
index 529646b..a1d9dee7 100644
--- a/third_party/blink/renderer/modules/background_fetch/service_worker_registration_background_fetch.idl
+++ b/third_party/blink/renderer/modules/background_fetch/service_worker_registration_background_fetch.idl
@@ -7,7 +7,7 @@
 [
     Exposed=(Window,Worker),
     ImplementedAs=ServiceWorkerRegistrationBackgroundFetch,
-    OriginTrialEnabled=BackgroundFetch
+    RuntimeEnabled=BackgroundFetch
 ] partial interface ServiceWorkerRegistration {
     readonly attribute BackgroundFetchManager backgroundFetch;
 };
diff --git a/third_party/blink/renderer/modules/clipboard/clipboard.cc b/third_party/blink/renderer/modules/clipboard/clipboard.cc
index fa4c735..57a75b78 100644
--- a/third_party/blink/renderer/modules/clipboard/clipboard.cc
+++ b/third_party/blink/renderer/modules/clipboard/clipboard.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/clipboard/clipboard.h"
 
+#include <utility>
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/modules/clipboard/clipboard_promise.h"
 
@@ -20,8 +21,9 @@
   return ClipboardPromise::CreateForReadText(script_state);
 }
 
-ScriptPromise Clipboard::write(ScriptState* script_state, Blob* data) {
-  return ClipboardPromise::CreateForWrite(script_state, data);
+ScriptPromise Clipboard::write(ScriptState* script_state,
+                               HeapVector<Member<Blob>> data) {
+  return ClipboardPromise::CreateForWrite(script_state, std::move(data));
 }
 
 ScriptPromise Clipboard::writeText(ScriptState* script_state,
diff --git a/third_party/blink/renderer/modules/clipboard/clipboard.h b/third_party/blink/renderer/modules/clipboard/clipboard.h
index b0c38601..424b9a8 100644
--- a/third_party/blink/renderer/modules/clipboard/clipboard.h
+++ b/third_party/blink/renderer/modules/clipboard/clipboard.h
@@ -26,7 +26,7 @@
   ScriptPromise read(ScriptState*);
   ScriptPromise readText(ScriptState*);
 
-  ScriptPromise write(ScriptState*, Blob*);
+  ScriptPromise write(ScriptState*, HeapVector<Member<Blob>>);
   ScriptPromise writeText(ScriptState*, const String&);
 
   // EventTarget
diff --git a/third_party/blink/renderer/modules/clipboard/clipboard.idl b/third_party/blink/renderer/modules/clipboard/clipboard.idl
index dfe2c09..bf45d33 100644
--- a/third_party/blink/renderer/modules/clipboard/clipboard.idl
+++ b/third_party/blink/renderer/modules/clipboard/clipboard.idl
@@ -11,7 +11,7 @@
     [MeasureAs=AsyncClipboardAPIRead,
      CallWith=ScriptState,
      RuntimeEnabled=AsyncClipboard
-    ] Promise<Blob> read();
+    ] Promise<sequence<Blob>> read();
 
     [MeasureAs=AsyncClipboardAPIReadText,
      CallWith=ScriptState
@@ -21,7 +21,7 @@
     [MeasureAs=AsyncClipboardAPIWrite,
      CallWith=ScriptState,
      RuntimeEnabled=AsyncClipboard
-    ] Promise<void> write(Blob data);
+    ] Promise<void> write(sequence<Blob> data);
 
     [MeasureAs=AsyncClipboardAPIWriteText,
      CallWith=ScriptState
diff --git a/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc b/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc
index 695497f8..d81079b 100644
--- a/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc
+++ b/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc
@@ -5,6 +5,8 @@
 #include "third_party/blink/renderer/modules/clipboard/clipboard_promise.h"
 
 #include <memory>
+#include <utility>
+
 #include "base/single_thread_task_runner.h"
 #include "base/task/post_task.h"
 #include "third_party/blink/public/platform/modules/permissions/permission.mojom-blink.h"
@@ -38,6 +40,26 @@
 // access is gated behind a permission prompt. Both clipboard read and write
 // require the tab to be focused (and Chromium must be the foreground app) for
 // the operation to be allowed.
+//
+// Writing a Blob's data to the system clipboard is accomplished by:
+// (1) Reading the blob's contents using a ClipboardFileReader.
+// (2) Decoding the blob's contents to avoid RCE in native applications that may
+//     vulnerabilities in their decoders.
+// (3) Writing the blob's decoded contents to the system clipboard.
+//
+// Reading a type from the system clipboard to a Blob is accomplished by:
+// (1) Reading the item from the system clipboard
+// (2) Encoding the blob's contents.
+// (3) Writing the contents to a blob.
+//
+// TODO (huangdarwin): Strongly consider making a class break to have
+// decoders and encoders in a separate class, with this class mostly checking
+// for permissions, and the next class doing decoding/encoding for individual
+// types.
+//
+// TODO (huangdarwin): Use a SequencedTaskRunner to write one clipboard type
+// before the next one? This could allow for removing awkward logic regarding
+// clipboard_representation_index_.
 
 namespace blink {
 
@@ -65,13 +87,15 @@
 }
 
 ScriptPromise ClipboardPromise::CreateForWrite(ScriptState* script_state,
-                                               Blob* data) {
+                                               HeapVector<Member<Blob>> data) {
   ClipboardPromise* clipboard_promise =
       MakeGarbageCollected<ClipboardPromise>(script_state);
+  HeapVector<Member<Blob>>* blob_sequence =
+      MakeGarbageCollected<HeapVector<Member<Blob>>>(data);
   clipboard_promise->GetTaskRunner()->PostTask(
-      FROM_HERE,
-      WTF::Bind(&ClipboardPromise::HandleWrite,
-                WrapPersistent(clipboard_promise), WrapPersistent(data)));
+      FROM_HERE, WTF::Bind(&ClipboardPromise::HandleWrite,
+                           WrapPersistent(clipboard_promise),
+                           WrapPersistent(blob_sequence)));
   return clipboard_promise->script_promise_resolver_->Promise();
 }
 
@@ -90,6 +114,7 @@
       script_state_(script_state),
       script_promise_resolver_(ScriptPromiseResolver::Create(script_state)),
       buffer_(mojom::ClipboardBuffer::kStandard),
+      clipboard_representation_index_(0),
       file_reading_task_runner_(
           GetExecutionContext()->GetTaskRunner(TaskType::kFileReading)) {}
 
@@ -180,24 +205,26 @@
     return;
   }
 
-  String type_to_read = TypeToRead();
-  Blob* blob = nullptr;
-
-  if (type_to_read == kMimeTypeImagePng) {
-    blob = ReadImageAsBlob();
-  } else if (type_to_read == kMimeTypeTextPlain) {
-    blob = ReadTextAsBlob();
-  } else {
-    // Reject when there's no data on clipboard.
+  Vector<String> types_to_read = TypesToRead();
+  HeapVector<Member<Blob>> blobs;
+  blobs.ReserveInitialCapacity(types_to_read.size());
+  for (const String& type_to_read : types_to_read) {
+    if (type_to_read == kMimeTypeImagePng) {
+      blobs.push_back(ReadImageAsBlob());
+    } else if (type_to_read == kMimeTypeTextPlain) {
+      blobs.push_back(ReadTextAsBlob());
+    } else {
+      NOTREACHED() << "Type " << type_to_read << " was not implemented";
+    }
   }
 
-  if (!blob) {
+  if (!blobs.size()) {
     script_promise_resolver_->Reject(DOMException::Create(
         DOMExceptionCode::kDataError, "No valid data on clipboard."));
     return;
   }
 
-  script_promise_resolver_->Resolve(std::move(blob));
+  script_promise_resolver_->Resolve(std::move(blobs));
 }
 
 void ClipboardPromise::HandleReadText() {
@@ -218,11 +245,11 @@
   script_promise_resolver_->Resolve(text);
 }
 
-// TODO(huangdarwin): This currently only handles plain text and images.
-// TODO(huangdarwin): Use an array or structure to hold multiple
-// Blobs, like how DataTransfer holds multiple DataTransferItems.
-void ClipboardPromise::HandleWrite(Blob* data) {
-  blob_data_ = data;
+void ClipboardPromise::HandleWrite(HeapVector<Member<Blob>>* data) {
+  // TODO(huangdarwin): This currently only handles plain text and images.
+  DCHECK_CALLED_ON_VALID_SEQUENCE(async_clipboard_sequence_checker);
+  CHECK(data);
+  blob_sequence_data_ = std::move(*data);
 
   CheckWritePermission(WTF::Bind(&ClipboardPromise::HandleWriteWithPermission,
                                  WrapPersistent(this)));
@@ -236,17 +263,48 @@
     return;
   }
 
-  String type_to_read = blob_data_->type();
-  if (type_to_read == kMimeTypeImagePng || type_to_read == kMimeTypeTextPlain) {
-    DCHECK(!file_reader_);
-    file_reader_ = std::make_unique<ClipboardFileReader>(
-        blob_data_, this, file_reading_task_runner_);
-  } else {
-    script_promise_resolver_->Reject(
-        DOMException::Create(DOMExceptionCode::kNotAllowedError,
-                             "Write type " + type_to_read + " not supported."));
+  // Check that all blobs have valid and unique MIME types.
+  HashSet<String> unique_types;
+  unique_types.ReserveCapacityForSize(blob_sequence_data_.size());
+  for (const Member<Blob>& blob : blob_sequence_data_) {
+    String type = blob->type();
+    if (!IsValidClipboardType(type)) {
+      script_promise_resolver_->Reject(
+          DOMException::Create(DOMExceptionCode::kNotAllowedError,
+                               "Write type " + type + " not supported."));
+      return;
+    }
+    if (unique_types.Contains(type)) {
+      script_promise_resolver_->Reject(
+          DOMException::Create(DOMExceptionCode::kNotAllowedError,
+                               "Attempting to write duplicate type " + type +
+                                   +". All types must be unique"));
+    }
+    unique_types.insert(type);
+  }
+
+  DCHECK(!clipboard_representation_index_);
+  WriteNextRepresentation();
+}
+
+// Called to begin writing a type, or after writing each type.
+void ClipboardPromise::WriteNextRepresentation() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(async_clipboard_sequence_checker);
+  if (clipboard_representation_index_ == blob_sequence_data_.size()) {
+    SystemClipboard::GetInstance().CommitWrite();
+    script_promise_resolver_->Resolve();
     return;
   }
+
+  const Member<Blob>& blob =
+      blob_sequence_data_[clipboard_representation_index_];
+  clipboard_representation_index_++;
+  DCHECK(IsValidClipboardType(blob->type()));
+
+  // Creates a FileReader to read the Blob.
+  DCHECK(!file_reader_);
+  file_reader_ = std::make_unique<ClipboardFileReader>(
+      blob, this, file_reading_task_runner_);
 }
 
 void ClipboardPromise::HandleWriteText(const String& data) {
@@ -264,14 +322,15 @@
     return;
   }
 
-  ResolveAndWriteText(write_data_);
+  SystemClipboard::GetInstance().WritePlainText(write_data_);
+  script_promise_resolver_->Resolve();
 }
 
 void ClipboardPromise::OnLoadBufferComplete(DOMArrayBuffer* array_buffer) {
+  String blob_type =
+      blob_sequence_data_[clipboard_representation_index_ - 1]->type();
   DCHECK_CALLED_ON_VALID_SEQUENCE(async_clipboard_sequence_checker);
-  String blob_type = blob_data_->type();
-  DCHECK(blob_type == kMimeTypeImagePng || blob_type == kMimeTypeTextPlain);
-
+  DCHECK(IsValidClipboardType(blob_type));
   file_reader_.reset();
 
   if (blob_type == kMimeTypeImagePng) {
@@ -286,6 +345,8 @@
         CrossThreadBind(&ClipboardPromise::DecodeTextOnBackgroundThread,
                         WrapCrossThreadPersistent(this), GetTaskRunner(),
                         WrapCrossThreadPersistent(array_buffer)));
+  } else {
+    NOTREACHED();
   }
 }
 
@@ -322,7 +383,7 @@
 
   PostCrossThreadTask(
       *task_runner, FROM_HERE,
-      CrossThreadBind(&ClipboardPromise::ResolveAndWriteImage,
+      CrossThreadBind(&ClipboardPromise::WriteDecodedImage,
                       WrapCrossThreadPersistent(this), std::move(image)));
 }
 
@@ -336,25 +397,25 @@
 
   PostCrossThreadTask(
       *task_runner, FROM_HERE,
-      CrossThreadBind(&ClipboardPromise::ResolveAndWriteText,
+      CrossThreadBind(&ClipboardPromise::WriteDecodedText,
                       WrapCrossThreadPersistent(this), std::move(wtf_string)));
 }
 
-void ClipboardPromise::ResolveAndWriteImage(sk_sp<SkImage> image) {
+void ClipboardPromise::WriteDecodedImage(sk_sp<SkImage> image) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(async_clipboard_sequence_checker);
 
   SkBitmap bitmap;
   image->asLegacyBitmap(&bitmap);
 
-  SystemClipboard::GetInstance().WriteImage(std::move(bitmap));
-  script_promise_resolver_->Resolve();
+  SystemClipboard::GetInstance().WriteImageNoCommit(std::move(bitmap));
+  WriteNextRepresentation();
 }
 
-void ClipboardPromise::ResolveAndWriteText(const String& text) {
+void ClipboardPromise::WriteDecodedText(const String& text) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(async_clipboard_sequence_checker);
 
-  SystemClipboard::GetInstance().WritePlainText(text);
-  script_promise_resolver_->Resolve();
+  SystemClipboard::GetInstance().WritePlainTextNoCommit(text);
+  WriteNextRepresentation();
 }
 
 void ClipboardPromise::Reject() {
@@ -365,17 +426,17 @@
 
 // TODO(huangdarwin): This is beginning to share responsibility
 // with data_object.cc, so how can we share some code?
-String ClipboardPromise::TypeToRead() {
+Vector<String> ClipboardPromise::TypesToRead() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(async_clipboard_sequence_checker);
-  String type_to_read;
-  for (const String& available_type :
-       SystemClipboard::GetInstance().ReadAvailableTypes()) {
-    if (available_type == kMimeTypeImagePng)
-      return available_type;
-    if (available_type == kMimeTypeTextPlain)
-      type_to_read = available_type;
+  Vector<String> available_types =
+      SystemClipboard::GetInstance().ReadAvailableTypes();
+  Vector<String> types_to_read;
+  types_to_read.ReserveInitialCapacity(available_types.size());
+  for (const String& available_type : available_types) {
+    if (IsValidClipboardType(available_type))
+      types_to_read.push_back(available_type);
   }
-  return type_to_read;
+  return types_to_read;
 }
 
 Blob* ClipboardPromise::ReadTextAsBlob() {
@@ -408,10 +469,15 @@
   return Blob::Create(png_data.data(), png_data.size(), kMimeTypeImagePng);
 }
 
+bool ClipboardPromise::IsValidClipboardType(const String& type) {
+  // TODO (https://crbug.com/931839): Add support for text/html and other types.
+  return type == kMimeTypeImagePng || type == kMimeTypeTextPlain;
+}
+
 void ClipboardPromise::Trace(blink::Visitor* visitor) {
   visitor->Trace(script_state_);
   visitor->Trace(script_promise_resolver_);
-  visitor->Trace(blob_data_);
+  visitor->Trace(blob_sequence_data_);
   ContextLifecycleObserver::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/modules/clipboard/clipboard_promise.h b/third_party/blink/renderer/modules/clipboard/clipboard_promise.h
index 66620930..c093f1b 100644
--- a/third_party/blink/renderer/modules/clipboard/clipboard_promise.h
+++ b/third_party/blink/renderer/modules/clipboard/clipboard_promise.h
@@ -31,11 +31,13 @@
   ClipboardPromise(ScriptState*);
   virtual ~ClipboardPromise();
 
+  // Creates promise to execute Clipboard API functions off the main thread.
   static ScriptPromise CreateForRead(ScriptState*);
   static ScriptPromise CreateForReadText(ScriptState*);
-  static ScriptPromise CreateForWrite(ScriptState*, Blob*);
+  static ScriptPromise CreateForWrite(ScriptState*, HeapVector<Member<Blob>>);
   static ScriptPromise CreateForWriteText(ScriptState*, const String&);
 
+  // Entry points back into ClipboardPromise, from ClipboardFileReader.
   void OnLoadBufferComplete(DOMArrayBuffer*);
   void Reject();
 
@@ -43,55 +45,66 @@
 
  private:
   scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner();
-  mojom::blink::PermissionService* GetPermissionService();
 
   bool IsFocusedDocument(ExecutionContext*);
 
+  // Checks for permissions (interacting with PermissionService).
+  mojom::blink::PermissionService* GetPermissionService();
   void RequestReadPermission(
       mojom::blink::PermissionService::RequestPermissionCallback);
   void CheckWritePermission(
       mojom::blink::PermissionService::HasPermissionCallback);
 
+  // Checks Read/Write permission (interacting with PermissionService).
   void HandleRead();
-  void HandleReadWithPermission(mojom::blink::PermissionStatus);
-
   void HandleReadText();
-  void HandleReadTextWithPermission(mojom::blink::PermissionStatus);
-
-  void HandleWrite(Blob*);
-  void HandleWriteWithPermission(mojom::blink::PermissionStatus);
-
+  void HandleWrite(HeapVector<Member<Blob>>*);
   void HandleWriteText(const String&);
+
+  // Reads/Writes after permission check.
+  void HandleReadWithPermission(mojom::blink::PermissionStatus);
+  void HandleReadTextWithPermission(mojom::blink::PermissionStatus);
+  void HandleWriteWithPermission(mojom::blink::PermissionStatus);
   void HandleWriteTextWithPermission(mojom::blink::PermissionStatus);
 
+  void WriteNextRepresentation();
+
+  // Decodes for writing.
   void DecodeImageOnBackgroundThread(
       scoped_refptr<base::SingleThreadTaskRunner>,
       DOMArrayBuffer*);
   void DecodeTextOnBackgroundThread(scoped_refptr<base::SingleThreadTaskRunner>,
                                     DOMArrayBuffer*);
 
-  void ResolveAndWriteImage(sk_sp<SkImage>);
-  void ResolveAndWriteText(const String&);
+  // Writes decoded payload to System Clipboard.
+  void WriteDecodedImage(sk_sp<SkImage>);
+  void WriteDecodedText(const String&);
 
-  // Detect whether an image or text is on the clipboard.
-  // Prioritizes image/png over text/plain over none.
-  String TypeToRead();
+  // Detects whether an image or text is on the clipboard, and
+  // returns all valid clipboard types on the clipboard.
+  Vector<String> TypesToRead();
 
-  // Get Blob containing System Clipboard contents.
+  // Gets Blob containing System Clipboard contents.
   Blob* ReadTextAsBlob();
   Blob* ReadImageAsBlob();
 
-  // Because v8 is thread-hostile, ensure that all interactions with ScriptState
-  // and ScriptPromiseResolver occur on the main thread.
+  bool IsValidClipboardType(const String&);
+
+  // Because v8 is thread-hostile, ensures that all interactions with
+  // ScriptState and ScriptPromiseResolver occur on the main thread.
   Member<ScriptState> script_state_;
   Member<ScriptPromiseResolver> script_promise_resolver_;
 
+  // Reads a Blob's data so that it can be written to the Clipboard.
   std::unique_ptr<ClipboardFileReader> file_reader_;
+  // Checks for Read and Write permission.
   mojom::blink::PermissionServicePtr permission_service_;
   mojom::ClipboardBuffer buffer_;
 
   String write_data_;
-  Member<Blob> blob_data_;
+  HeapVector<Member<Blob>> blob_sequence_data_;
+  // Index of clipboard representation currently being processed.
+  wtf_size_t clipboard_representation_index_;
 
   scoped_refptr<base::SingleThreadTaskRunner> file_reading_task_runner_;
 
diff --git a/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc b/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc
index c1690f0..ecb9c88 100644
--- a/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc
+++ b/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc
@@ -35,7 +35,6 @@
 #include "third_party/blink/renderer/core/clipboard/data_transfer_item.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/fileapi/file.h"
-#include "third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/modules/filesystem/directory_entry.h"
 #include "third_party/blink/renderer/modules/filesystem/dom_file_path.h"
 #include "third_party/blink/renderer/modules/filesystem/dom_file_system.h"
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc
index 8102e31..614d3c6 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc
+++ b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc
@@ -69,7 +69,7 @@
   void DidWrite(int64_t byte_count, bool complete) override { NOTREACHED(); }
 
  private:
-  std::unique_ptr<AsyncFileSystemCallbacks> callbacks_;
+  std::unique_ptr<EntriesCallbacks> callbacks_;
 };
 
 FileSystemDispatcher::FileSystemDispatcher(ExecutionContext& context)
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc b/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc
index 71b11c1..5d8b699 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_stats_report.cc
@@ -120,11 +120,11 @@
 RTCStatsFilter GetRTCStatsFilter(const ScriptState* script_state) {
   const ExecutionContext* context = ExecutionContext::From(script_state);
   DCHECK(context->IsContextThread());
-  // If this original trial is enabled then it exposes jitterBufferFlushes
-  // metric
-  return origin_trials::RtcAudioJitterBufferMaxPacketsEnabled(context)
-             ? RTCStatsFilter::kIncludeNonStandardMembers
-             : RTCStatsFilter::kIncludeOnlyStandardMembers;
+  if (origin_trials::RtcAudioJitterBufferMaxPacketsEnabled(context) ||
+      origin_trials::RTCStatsRelativePacketArrivalDelayEnabled(context)) {
+    return RTCStatsFilter::kIncludeNonStandardMembers;
+  }
+  return RTCStatsFilter::kIncludeOnlyStandardMembers;
 }
 
 RTCStatsReport::RTCStatsReport(std::unique_ptr<WebRTCStatsReport> report)
diff --git a/third_party/blink/renderer/modules/permissions/permissions.cc b/third_party/blink/renderer/modules/permissions/permissions.cc
index a116cf6..7988916a 100644
--- a/third_party/blink/renderer/modules/permissions/permissions.cc
+++ b/third_party/blink/renderer/modules/permissions/permissions.cc
@@ -140,11 +140,6 @@
   if (name == "payment-handler")
     return CreatePermissionDescriptor(PermissionName::PAYMENT_HANDLER);
   if (name == "background-fetch") {
-    if (!origin_trials::BackgroundFetchEnabled(
-            ExecutionContext::From(script_state))) {
-      exception_state.ThrowTypeError("Background Fetch is not enabled.");
-      return nullptr;
-    }
     return CreatePermissionDescriptor(PermissionName::BACKGROUND_FETCH);
   }
   if (name == "idle-detection")
diff --git a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc
index d8ea0a16..7ff9a42f 100644
--- a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc
+++ b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc
@@ -215,7 +215,8 @@
 // JavaScript.
 void FetchRespondWithObserver::OnResponseRejected(
     ServiceWorkerResponseError error) {
-  DCHECK(GetExecutionContext());
+  // TODO(crbug.com/934622): Temporary CHECK for the crash bug.
+  CHECK(GetExecutionContext());
   GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
       kJSMessageSource, mojom::ConsoleMessageLevel::kWarning,
       GetMessageForResponseError(error, request_url_)));
@@ -234,7 +235,8 @@
     ExceptionState::ContextType context_type,
     const char* interface_name,
     const char* property_name) {
-  DCHECK(GetExecutionContext());
+  // TODO(crbug.com/934622): Temporary CHECK for the crash bug.
+  CHECK(GetExecutionContext());
   if (!V8Response::HasInstance(value.V8Value(), value.GetIsolate())) {
     OnResponseRejected(ServiceWorkerResponseError::kNoV8Instance);
     return;
diff --git a/third_party/blink/renderer/modules/service_worker/respond_with_observer.cc b/third_party/blink/renderer/modules/service_worker/respond_with_observer.cc
index 216334f6..1e48691 100644
--- a/third_party/blink/renderer/modules/service_worker/respond_with_observer.cc
+++ b/third_party/blink/renderer/modules/service_worker/respond_with_observer.cc
@@ -18,21 +18,13 @@
 
 namespace blink {
 
-void RespondWithObserver::ContextDestroyed(ExecutionContext*) {
-  if (observer_) {
-    DCHECK_EQ(kPending, state_);
-    observer_.Clear();
-  }
-  state_ = kDone;
-}
-
 void RespondWithObserver::WillDispatchEvent() {
   event_dispatch_time_ = WTF::CurrentTimeTicks();
 }
 
 void RespondWithObserver::DidDispatchEvent(
     DispatchEventResult dispatch_result) {
-  DCHECK(GetExecutionContext());
+  CHECK(GetExecutionContext());
   if (state_ != kInitial)
     return;
 
@@ -49,7 +41,7 @@
 void RespondWithObserver::RespondWith(ScriptState* script_state,
                                       ScriptPromise script_promise,
                                       ExceptionState& exception_state) {
-  if (state_ != kInitial) {
+  if (state_ != kInitial || !GetExecutionContext()) {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kInvalidStateError,
         "The event has already been responded to.");
@@ -88,14 +80,14 @@
 RespondWithObserver::RespondWithObserver(ExecutionContext* context,
                                          int event_id,
                                          WaitUntilObserver* observer)
-    : ContextLifecycleObserver(context),
+    : ContextClient(context),
       event_id_(event_id),
       state_(kInitial),
       observer_(observer) {}
 
 void RespondWithObserver::Trace(blink::Visitor* visitor) {
   visitor->Trace(observer_);
-  ContextLifecycleObserver::Trace(visitor);
+  ContextClient::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/service_worker/respond_with_observer.h b/third_party/blink/renderer/modules/service_worker/respond_with_observer.h
index 42130eaf..f62f889 100644
--- a/third_party/blink/renderer/modules/service_worker/respond_with_observer.h
+++ b/third_party/blink/renderer/modules/service_worker/respond_with_observer.h
@@ -26,14 +26,12 @@
 // overriding onResponseFulfilled, onResponseRejected and onNoResponse.
 class MODULES_EXPORT RespondWithObserver
     : public GarbageCollectedFinalized<RespondWithObserver>,
-      public ContextLifecycleObserver {
+      public ContextClient {
   USING_GARBAGE_COLLECTED_MIXIN(RespondWithObserver);
 
  public:
   virtual ~RespondWithObserver() = default;
 
-  void ContextDestroyed(ExecutionContext*) override;
-
   void WillDispatchEvent();
   void DidDispatchEvent(DispatchEventResult dispatch_result);
 
diff --git a/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc b/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc
index 473f8b1..f510810f 100644
--- a/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc
+++ b/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc
@@ -111,6 +111,9 @@
 }
 
 void WaitUntilObserver::WillDispatchEvent() {
+  // TODO(crbug.com/934622): Temporary CHECK for the crash bug.
+  CHECK(GetExecutionContext());
+
   // When handling a notificationclick, paymentrequest, or backgroundfetchclick
   // event, we want to allow one window to be focused or opened. These calls are
   // allowed between the call to willDispatchEvent() and the last call to
@@ -118,7 +121,7 @@
   // between willDispatchEvent() and didDispatchEvent().
   if (type_ == kNotificationClick || type_ == kPaymentRequest ||
       type_ == kBackgroundFetchClick) {
-    execution_context_->AllowWindowInteraction();
+    GetExecutionContext()->AllowWindowInteraction();
   }
 
   DCHECK_EQ(EventDispatchState::kInitial, event_dispatch_state_);
@@ -147,7 +150,7 @@
     return;
   }
 
-  if (!execution_context_)
+  if (!GetExecutionContext())
     return;
 
   // When handling a notificationclick event, we want to allow one window to
@@ -195,7 +198,7 @@
 WaitUntilObserver::WaitUntilObserver(ExecutionContext* context,
                                      EventType type,
                                      int event_id)
-    : execution_context_(context),
+    : ContextClient(context),
       type_(type),
       event_id_(event_id),
       consume_window_interaction_timer_(
@@ -223,7 +226,7 @@
 }
 
 void WaitUntilObserver::MaybeCompleteEvent() {
-  if (!execution_context_)
+  if (!GetExecutionContext())
     return;
 
   switch (event_dispatch_state_) {
@@ -246,7 +249,7 @@
   }
 
   ServiceWorkerGlobalScopeClient* client =
-      ServiceWorkerGlobalScopeClient::From(execution_context_);
+      ServiceWorkerGlobalScopeClient::From(GetExecutionContext());
   mojom::ServiceWorkerEventStatus status =
       (event_dispatch_state_ == EventDispatchState::kFailed ||
        has_rejected_promise_)
@@ -269,7 +272,8 @@
       client->DidHandleFetchEvent(event_id_, status);
       break;
     case kInstall:
-      To<ServiceWorkerGlobalScope>(*execution_context_).SetIsInstalling(false);
+      To<ServiceWorkerGlobalScope>(*GetExecutionContext())
+          .SetIsInstalling(false);
       client->DidHandleInstallEvent(event_id_, status);
       break;
     case kMessage:
@@ -305,17 +309,15 @@
       client->DidHandleBackgroundFetchSuccessEvent(event_id_, status);
       break;
   }
-  execution_context_ = nullptr;
 }
 
 void WaitUntilObserver::ConsumeWindowInteraction(TimerBase*) {
-  if (!execution_context_)
-    return;
-  execution_context_->ConsumeWindowInteraction();
+  if (ExecutionContext* context = GetExecutionContext())
+    context->ConsumeWindowInteraction();
 }
 
 void WaitUntilObserver::Trace(blink::Visitor* visitor) {
-  visitor->Trace(execution_context_);
+  ContextClient::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/service_worker/wait_until_observer.h b/third_party/blink/renderer/modules/service_worker/wait_until_observer.h
index 2f025fc..a1bd25f2 100644
--- a/third_party/blink/renderer/modules/service_worker/wait_until_observer.h
+++ b/third_party/blink/renderer/modules/service_worker/wait_until_observer.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SERVICE_WORKER_WAIT_UNTIL_OBSERVER_H_
 
 #include "base/callback.h"
+#include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope_client.h"
 #include "third_party/blink/renderer/platform/timer.h"
@@ -14,14 +15,16 @@
 namespace blink {
 
 class ExceptionState;
-class ExecutionContext;
 class ScriptPromise;
 class ScriptState;
 class ScriptValue;
 
 // Created for each ExtendableEvent instance.
 class MODULES_EXPORT WaitUntilObserver final
-    : public GarbageCollectedFinalized<WaitUntilObserver> {
+    : public GarbageCollectedFinalized<WaitUntilObserver>,
+      public ContextClient {
+  USING_GARBAGE_COLLECTED_MIXIN(WaitUntilObserver);
+
  public:
   using PromiseSettledCallback =
       base::RepeatingCallback<void(const ScriptValue&)>;
@@ -75,7 +78,7 @@
   // https://w3c.github.io/ServiceWorker/#extendableevent-active.
   bool IsEventActive(ScriptState* script_state) const;
 
-  virtual void Trace(blink::Visitor*);
+  void Trace(blink::Visitor*) override;
 
  private:
   friend class InternalsServiceWorker;
@@ -108,7 +111,6 @@
 
   void MaybeCompleteEvent();
 
-  Member<ExecutionContext> execution_context_;
   EventType type_;
   int event_id_;
   int pending_promises_ = 0;
diff --git a/third_party/blink/renderer/modules/shapedetection/barcode_detector.cc b/third_party/blink/renderer/modules/shapedetection/barcode_detector.cc
index 2060b0a7..13b20ce 100644
--- a/third_party/blink/renderer/modules/shapedetection/barcode_detector.cc
+++ b/third_party/blink/renderer/modules/shapedetection/barcode_detector.cc
@@ -16,19 +16,59 @@
 
 namespace blink {
 
+namespace {
+
+WebString BarcodeFormatToString(
+    const shape_detection::mojom::blink::BarcodeFormat format) {
+  switch (format) {
+    case shape_detection::mojom::blink::BarcodeFormat::AZTEC:
+      return WebString::FromUTF8("aztec");
+    case shape_detection::mojom::blink::BarcodeFormat::CODE_128:
+      return WebString::FromUTF8("code_128");
+    case shape_detection::mojom::blink::BarcodeFormat::CODE_39:
+      return WebString::FromUTF8("code_39");
+    case shape_detection::mojom::blink::BarcodeFormat::CODE_93:
+      return WebString::FromUTF8("code_93");
+    case shape_detection::mojom::blink::BarcodeFormat::CODABAR:
+      return WebString::FromUTF8("codabar");
+    case shape_detection::mojom::blink::BarcodeFormat::DATA_MATRIX:
+      return WebString::FromUTF8("data_matrix");
+    case shape_detection::mojom::blink::BarcodeFormat::EAN_13:
+      return WebString::FromUTF8("ean_13");
+    case shape_detection::mojom::blink::BarcodeFormat::EAN_8:
+      return WebString::FromUTF8("ean_8");
+    case shape_detection::mojom::blink::BarcodeFormat::ITF:
+      return WebString::FromUTF8("itf");
+    case shape_detection::mojom::blink::BarcodeFormat::PDF417:
+      return WebString::FromUTF8("pdf417");
+    case shape_detection::mojom::blink::BarcodeFormat::QR_CODE:
+      return WebString::FromUTF8("qr_code");
+    case shape_detection::mojom::blink::BarcodeFormat::UNKNOWN:
+      return WebString::FromUTF8("unknown");
+    case shape_detection::mojom::blink::BarcodeFormat::UPC_A:
+      return WebString::FromUTF8("upc_a");
+    case shape_detection::mojom::blink::BarcodeFormat::UPC_E:
+      return WebString::FromUTF8("upc_e");
+    default:
+      NOTREACHED() << "Invalid BarcodeFormat";
+  }
+  return WebString();
+}
+
+}  // namespace
+
 BarcodeDetector* BarcodeDetector::Create(ExecutionContext* context) {
   return MakeGarbageCollected<BarcodeDetector>(context);
 }
 
 BarcodeDetector::BarcodeDetector(ExecutionContext* context) : ShapeDetector() {
-  shape_detection::mojom::blink::BarcodeDetectionProviderPtr provider;
   // See https://bit.ly/2S0zRAS for task types.
   auto task_runner = context->GetTaskRunner(TaskType::kMiscPlatformAPI);
-  auto request = mojo::MakeRequest(&provider, task_runner);
+  auto request = mojo::MakeRequest(&barcode_provider_, task_runner);
   if (auto* interface_provider = context->GetInterfaceProvider()) {
     interface_provider->GetInterface(std::move(request));
   }
-  provider->CreateBarcodeDetection(
+  barcode_provider_->CreateBarcodeDetection(
       mojo::MakeRequest(&barcode_service_, task_runner),
       shape_detection::mojom::blink::BarcodeDetectorOptions::New());
 
@@ -37,6 +77,40 @@
                 WrapWeakPersistent(this)));
 }
 
+ScriptPromise BarcodeDetector::getSupportedFormats(ScriptState* script_state) {
+  ExecutionContext* context = ExecutionContext::From(script_state);
+  BarcodeDetector* detector = BarcodeDetector::Create(context);
+
+  ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
+  ScriptPromise promise = resolver->Promise();
+  if (!detector->barcode_service_) {
+    resolver->Reject(
+        DOMException::Create(DOMExceptionCode::kNotSupportedError,
+                             "Barcode detection service unavailable."));
+    return promise;
+  }
+
+  detector->barcode_service_requests_.insert(resolver);
+  detector->barcode_provider_->EnumerateSupportedFormats(
+      WTF::Bind(&BarcodeDetector::OnEnumerateSupportedFormats,
+                WrapPersistent(detector), WrapPersistent(resolver)));
+  return promise;
+}
+
+void BarcodeDetector::OnEnumerateSupportedFormats(
+    ScriptPromiseResolver* resolver,
+    const Vector<shape_detection::mojom::blink::BarcodeFormat>&
+        format_results) {
+  DCHECK(barcode_service_requests_.Contains(resolver));
+  barcode_service_requests_.erase(resolver);
+
+  Vector<WTF::String> formats;
+  formats.ReserveInitialCapacity(format_results.size());
+  for (const auto& format : format_results)
+    formats.push_back(BarcodeFormatToString(format));
+  resolver->Resolve(formats);
+}
+
 ScriptPromise BarcodeDetector::DoDetect(ScriptPromiseResolver* resolver,
                                         SkBitmap bitmap) {
   ScriptPromise promise = resolver->Promise();
@@ -88,6 +162,7 @@
   }
   barcode_service_requests_.clear();
   barcode_service_.reset();
+  barcode_provider_.reset();
 }
 
 void BarcodeDetector::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/modules/shapedetection/barcode_detector.h b/third_party/blink/renderer/modules/shapedetection/barcode_detector.h
index d596079..5ad4870d 100644
--- a/third_party/blink/renderer/modules/shapedetection/barcode_detector.h
+++ b/third_party/blink/renderer/modules/shapedetection/barcode_detector.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SHAPEDETECTION_BARCODE_DETECTOR_H_
 
 #include "services/shape_detection/public/mojom/barcodedetection.mojom-blink.h"
+#include "services/shape_detection/public/mojom/barcodedetection_provider.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h"
@@ -22,6 +23,9 @@
  public:
   static BarcodeDetector* Create(ExecutionContext*);
 
+  // Barcode Detection API functions.
+  static ScriptPromise getSupportedFormats(ScriptState*);
+
   explicit BarcodeDetector(ExecutionContext*);
 
   void Trace(blink::Visitor*) override;
@@ -29,13 +33,19 @@
  private:
   ~BarcodeDetector() override = default;
 
+  void OnEnumerateSupportedFormats(
+      ScriptPromiseResolver*,
+      const Vector<shape_detection::mojom::blink::BarcodeFormat>&);
+
   ScriptPromise DoDetect(ScriptPromiseResolver*, SkBitmap) override;
   void OnDetectBarcodes(
       ScriptPromiseResolver*,
       Vector<shape_detection::mojom::blink::BarcodeDetectionResultPtr>);
+
   void OnBarcodeServiceConnectionError();
 
   shape_detection::mojom::blink::BarcodeDetectionPtr barcode_service_;
+  shape_detection::mojom::blink::BarcodeDetectionProviderPtr barcode_provider_;
 
   HeapHashSet<Member<ScriptPromiseResolver>> barcode_service_requests_;
 };
diff --git a/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl b/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl
index 47ac42c..4e9b0e5 100644
--- a/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl
+++ b/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl
@@ -11,5 +11,6 @@
     MeasureAs=ShapeDetection_BarcodeDetectorConstructor,
     RuntimeEnabled=ShapeDetection
 ] interface BarcodeDetector {
+    [CallWith=ScriptState] static Promise<sequence<BarcodeFormat>> getSupportedFormats();
     [CallWith=ScriptState, MeasureAs=ShapeDetectionAPI] Promise<sequence<DetectedBarcode>> detect(ImageBitmapSource image);
 };
diff --git a/third_party/blink/renderer/modules/vr/vr_display.cc b/third_party/blink/renderer/modules/vr/vr_display.cc
index 1bbfb4a..1e37c86f 100644
--- a/third_party/blink/renderer/modules/vr/vr_display.cc
+++ b/third_party/blink/renderer/modules/vr/vr_display.cc
@@ -50,6 +50,15 @@
 constexpr WTF::TimeDelta kNonImmersivePoseAgeThreshold =
     WTF::TimeDelta::FromMilliseconds(250);
 
+device::mojom::blink::XRFrameDataPtr CreateIdentityFrameData() {
+  auto data = device::mojom::blink::XRFrameData::New();
+  data->pose = device::mojom::blink::VRPose::New();
+  data->pose->orientation.emplace({0.0f, 0.0f, 0.0f, 1.0f});
+  data->pose->position.emplace({0.0f, 0.0f, 0.0f});
+
+  return data;
+}
+
 VREye StringToVREye(const String& which_eye) {
   if (which_eye == "left")
     return kVREyeLeft;
@@ -279,18 +288,28 @@
     DVLOG(2) << __FUNCTION__ << " done: pending_presenting_vsync_="
              << pending_presenting_vsync_;
   } else {
-    // Check if non_immersive_provider_, if not then we are not fully
-    // initialized, or we do not support non-immersive, so don't request the
-    // vsync. If and when non_immersive_provider_ is set it will run this code
-    // again.
-    if (!non_immersive_provider_)
+    // If we haven't been fully initialized yet, then we need to keep waiting
+    // so that we know if we have a non immersive provider or if we need to
+    // pass out identity poses.  When the callback from initialization happens
+    // it will run this code again.
+    if (!non_immersive_session_initialized_)
       return;
     if (pending_non_immersive_vsync_)
       return;
     non_immersive_vsync_waiting_for_pose_.Reset();
     non_immersive_pose_request_time_ = WTF::CurrentTimeTicks();
-    non_immersive_provider_->GetFrameData(WTF::Bind(
-        &VRDisplay::OnNonImmersiveFrameData, WrapWeakPersistent(this)));
+
+    if (non_immersive_provider_) {
+      non_immersive_provider_->GetFrameData(WTF::Bind(
+          &VRDisplay::OnNonImmersiveFrameData, WrapWeakPersistent(this)));
+    } else {
+      // If we don't have a non immersive provider, we should just return
+      // an identity pose.  We're not worried about re-entrant calls right now
+      // because we should end up waiting for the RAF callback which we request
+      // below. If we start to see errors with this, we'll want to do this as a
+      // posted task.
+      OnNonImmersiveFrameData(CreateIdentityFrameData());
+    }
     pending_non_immersive_vsync_ = true;
     pending_non_immersive_vsync_id_ = doc->RequestAnimationFrame(
         MakeGarbageCollected<VRDisplayFrameRequestCallback>(this));
@@ -586,14 +605,20 @@
 
 void VRDisplay::OnNonImmersiveSessionRequestReturned(
     device::mojom::blink::XRSessionPtr session) {
-  if (!session) {
-    // System does not support any kind of session.
-    return;
+  non_immersive_session_initialized_ = true;
+
+  // Only create the non immersive provider if we actually got a session.
+  // If we didn't get a session, we will just hand out identity poses.
+  if (session) {
+    non_immersive_provider_.Bind(std::move(session->data_provider));
+    non_immersive_client_binding_ = MakeGarbageCollected<SessionClientBinding>(
+        this, SessionClientBinding::SessionBindingType::kNonImmersive,
+        std::move(session->client_request));
   }
-  non_immersive_provider_.Bind(std::move(session->data_provider));
-  non_immersive_client_binding_ = MakeGarbageCollected<SessionClientBinding>(
-      this, SessionClientBinding::SessionBindingType::kNonImmersive,
-      std::move(session->client_request));
+
+  // Now that we're initialized, we need to ensure that the data is flowing
+  // by requesting a VSync, since it may have skipped requesting one because
+  // we weren't yet initialized.
   RequestVSync();
 }
 
diff --git a/third_party/blink/renderer/modules/vr/vr_display.h b/third_party/blink/renderer/modules/vr/vr_display.h
index 8c19d5dd..94a20fc 100644
--- a/third_party/blink/renderer/modules/vr/vr_display.h
+++ b/third_party/blink/renderer/modules/vr/vr_display.h
@@ -259,6 +259,7 @@
   bool did_log_getFrameData_ = false;
   bool did_log_requestPresent_ = false;
 
+  bool non_immersive_session_initialized_ = false;
   device::mojom::blink::XRFrameDataProviderPtr non_immersive_provider_;
 
   device::mojom::blink::XRDevicePtr device_ptr_;
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl
index 48dfa39..9cd92a0f 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl
+++ b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.idl
@@ -48,6 +48,7 @@
     const GLenum TRANSFORM_FEEDBACK_BARRIER_BIT         = 0x00000800;
     const GLenum ATOMIC_COUNTER_BARRIER_BIT             = 0x00001000;
     const GLenum SHADER_STORAGE_BARRIER_BIT             = 0x00002000;
+    const GLenum ALL_BARRIER_BITS                       = 0xFFFFFFFF;
     const GLenum FALSE = 0;
     const GLenum TRUE = 1;
     const GLenum READ_ONLY                              = 0x88B8;
diff --git a/third_party/blink/renderer/modules/websockets/README.md b/third_party/blink/renderer/modules/websockets/README.md
index a8e8f54..16b973ad 100644
--- a/third_party/blink/renderer/modules/websockets/README.md
+++ b/third_party/blink/renderer/modules/websockets/README.md
@@ -7,3 +7,14 @@
 
 They use WebSocketChannelImpl to connect to the WebSocket service i.e. the
 blink.mojom.WebSocket implementation in content/browser/websockets/.
+
+## Design docs
+
+See also [//net/websockets/README.md](../../../../../net/websockets/README.md)
+for the design of the network service side of the implementation.
+
+* [WebSocket SafeBrowsing
+  Support](https://docs.google.com/document/d/1iR3XMIQukqlXb6ajIHE91apHZAxyF_wvRoB5JGeJYPs/edit)
+  describes how SafeBrowsing checks are done on WebSocket URLs. Some class names
+  and details have changed as Worker WebSockets are no longer proxies via the
+  main thread.
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index ef7a4f44..9768856 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -265,11 +265,7 @@
 void XR::DispatchRequestSession(PendingSessionQuery* query) {
   if (!device_) {
     if (query->mode == XRSession::kModeInline) {
-      XRSession* session = MakeGarbageCollected<XRSession>(
-          this, nullptr /* client request */, query->mode,
-          XRSession::kBlendModeOpaque);
-      sessions_.insert(session);
-      query->resolver->Resolve(session);
+      CreateInlineIdentitySession(query);
       return;
     }
 
@@ -370,6 +366,11 @@
   // TODO(https://crbug.com/872316) Improve the error messaging to indicate why
   // a request failed.
   if (!session_ptr) {
+    if (query->mode == XRSession::kModeInline) {
+      CreateInlineIdentitySession(query);
+      return;
+    }
+
     DOMException* exception = DOMException::Create(
         DOMExceptionCode::kNotSupportedError, kSessionNotSupported);
     query->resolver->Reject(exception);
@@ -448,6 +449,14 @@
   Dispose();
 }
 
+void XR::CreateInlineIdentitySession(PendingSessionQuery* query) {
+  XRSession* session =
+      MakeGarbageCollected<XRSession>(this, nullptr /* client request */,
+                                      query->mode, XRSession::kBlendModeOpaque);
+  sessions_.insert(session);
+  query->resolver->Resolve(session);
+}
+
 void XR::Dispose() {
   // If the document context was destroyed, shut down the client connection
   // and never call the mojo service again.
diff --git a/third_party/blink/renderer/modules/xr/xr.h b/third_party/blink/renderer/modules/xr/xr.h
index 7a936861..1e7719e 100644
--- a/third_party/blink/renderer/modules/xr/xr.h
+++ b/third_party/blink/renderer/modules/xr/xr.h
@@ -103,6 +103,8 @@
   void AddedEventListener(const AtomicString& event_type,
                           RegisteredEventListener&) override;
 
+  void CreateInlineIdentitySession(PendingSessionQuery*);
+
   void Dispose();
 
   bool pending_device_ = false;
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index 70eb402..e1b4176 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -155,7 +155,13 @@
 
   // TODO(jacde): Update the mojom to deliver this per-frame.
   bool EmulatedPosition() const {
-    return !display_info_->capabilities->hasPosition;
+    if (display_info_) {
+      return !display_info_->capabilities->hasPosition;
+    }
+
+    // If we don't have display info then we should be using the identity
+    // reference space, which by definition will be emulating the position.
+    return true;
   }
 
   void UpdateDisplayInfo(
diff --git a/third_party/blink/renderer/platform/geometry/calculation_value.h b/third_party/blink/renderer/platform/geometry/calculation_value.h
index 24c7ae0..99594ea 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_value.h
+++ b/third_party/blink/renderer/platform/geometry/calculation_value.h
@@ -34,11 +34,14 @@
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/platform/geometry/length.h"
 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
 
 namespace blink {
 
 class PLATFORM_EXPORT CalculationValue : public RefCounted<CalculationValue> {
+  USING_FAST_MALLOC(CalculationValue);
+
  public:
   static scoped_refptr<CalculationValue> Create(PixelsAndPercent value,
                                                 ValueRange range) {
diff --git a/third_party/blink/renderer/platform/geometry/float_polygon_test.cc b/third_party/blink/renderer/platform/geometry/float_polygon_test.cc
index 03aac2d3b..5919b57 100644
--- a/third_party/blink/renderer/platform/geometry/float_polygon_test.cc
+++ b/third_party/blink/renderer/platform/geometry/float_polygon_test.cc
@@ -34,10 +34,13 @@
 #include <utility>
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
 class FloatPolygonTestValue {
+  STACK_ALLOCATED();
+
  public:
   FloatPolygonTestValue(const float* coordinates, unsigned coordinates_length) {
     DCHECK(!(coordinates_length % 2));
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
index 28e17efa..51b99aa 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
@@ -1586,22 +1586,13 @@
   GLuint rgb_texture = back_color_buffer_->rgb_workaround_texture_id;
   DCHECK_EQ(texture_target_, GC3D_TEXTURE_RECTANGLE_ARB);
   if (!rgb_texture) {
-    gpu::SharedImageInterface* sii = ContextProvider()->SharedImageInterface();
-    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager =
-        Platform::Current()->GetGpuMemoryBufferManager();
-    back_color_buffer_->rgb_workaround_mailbox = sii->CreateSharedImage(
-        back_color_buffer_->gpu_memory_buffer.get(), gpu_memory_buffer_manager,
-        storage_color_space_,
-        gpu::SHARED_IMAGE_USAGE_GLES2 |
-            gpu::SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT |
-            gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT |
-            gpu::SHARED_IMAGE_USAGE_RGB_EMULATION);
-    gl_->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
-    rgb_texture = gl_->CreateAndTexStorage2DSharedImageCHROMIUM(
-        back_color_buffer_->rgb_workaround_mailbox.name);
+    rgb_texture =
+        gl_->CreateAndTexStorage2DSharedImageWithInternalFormatCHROMIUM(
+            back_color_buffer_->mailbox.name, GL_RGB);
     back_color_buffer_->rgb_workaround_texture_id = rgb_texture;
   }
 
+  gl_->EndSharedImageAccessDirectCHROMIUM(back_color_buffer_->texture_id);
   gl_->BeginSharedImageAccessDirectCHROMIUM(
       rgb_texture, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
   gl_->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER_ANGLE, GL_COLOR_ATTACHMENT0,
@@ -1616,6 +1607,9 @@
   DCHECK(back_color_buffer_->gpu_memory_buffer);
   gl_->EndSharedImageAccessDirectCHROMIUM(
       back_color_buffer_->rgb_workaround_texture_id);
+  gl_->BeginSharedImageAccessDirectCHROMIUM(
+      back_color_buffer_->texture_id,
+      GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
   gl_->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER_ANGLE, GL_COLOR_ATTACHMENT0,
                             texture_target_, back_color_buffer_->texture_id, 0);
   // Clear the alpha channel.
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 2bcf056..733effae 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -138,8 +138,7 @@
     },
     {
       name: "BackgroundFetch",
-      origin_trial_feature_name: "BackgroundFetch",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "BackgroundVideoTrackOptimization",
@@ -155,7 +154,7 @@
     },
     {
       name: "BlinkGenPropertyTrees",
-      status: "stable",
+      status: "experimental",
     },
     {
       name: "BlinkRuntimeCallStats",
@@ -1213,6 +1212,11 @@
       name: "RTCRtpSenderParameters",
       status: "stable",
     },
+    {
+      name: "RTCStatsRelativePacketArrivalDelay",
+      origin_trial_feature_name: "RTCStatsRelativePacketArrivalDelay",
+      status: "experimental",
+    },
     // Enables the use of |RTCConfiguration::sdpSemantics| to override the
     // default SDP semantics at RTCPeerConnection construction.
     {
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
index 4c99f80..23d6a3a 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
@@ -817,6 +817,12 @@
   if (fixed_priority)
     return fixed_priority.value();
 
+  if (!parent_page_scheduler_) {
+    // Frame might be detached during its shutdown. Return a default priority
+    // in that case.
+    return TaskQueue::QueuePriority::kNormalPriority;
+  }
+
   // A hidden page with no audio.
   if (parent_page_scheduler_->IsBackgrounded()) {
     if (main_thread_scheduler_->scheduling_settings()
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index a889e8f..50e65225 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -1850,6 +1850,13 @@
             task_queue->FixedPriority().value());
 }
 
+TEST_F(FrameSchedulerImplTest, ComputePriorityForDetachedFrame) {
+  auto task_queue = GetTaskQueue(TaskType::kJavascriptTimer);
+  // Just check that it does not crash.
+  page_scheduler_.reset();
+  frame_scheduler_->ComputePriority(task_queue.get());
+}
+
 }  // namespace frame_scheduler_impl_unittest
 }  // namespace scheduler
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/scheduler/test/fuzzer/sequence_manager_fuzzer_processor.h b/third_party/blink/renderer/platform/scheduler/test/fuzzer/sequence_manager_fuzzer_processor.h
index a150d20f..c5b156da 100644
--- a/third_party/blink/renderer/platform/scheduler/test/fuzzer/sequence_manager_fuzzer_processor.h
+++ b/third_party/blink/renderer/platform/scheduler/test/fuzzer/sequence_manager_fuzzer_processor.h
@@ -26,7 +26,7 @@
 // by the |thread_pool_manager_| should live for the scope of the main thread
 // entry function i.e RunTest.
 class PLATFORM_EXPORT SequenceManagerFuzzerProcessor {
-  DISALLOW_NEW();
+  USING_FAST_MALLOC(SequenceManagerFuzzerProcessor);
 
  public:
   // Public interface used to parse the fuzzer's test description and
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index b07140a..e60135a 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -1727,8 +1727,10 @@
 external/wpt/battery-status/battery-charging-manual.https.html [ WontFix ]
 external/wpt/battery-status/battery-plugging-in-manual.https.html [ WontFix ]
 external/wpt/battery-status/battery-unplugging-manual.https.html [ WontFix ]
+external/wpt/clipboard-apis/async-write-blobs-read-blobs-manual.https.html [ WontFix ]
 external/wpt/clipboard-apis/async-write-blobtext-read-blobtext-manual.https.html [ WontFix ]
 external/wpt/clipboard-apis/async-write-blobtext-read-text-manual.https.html [ WontFix ]
+external/wpt/clipboard-apis/async-write-duplicate-mime-type-manual.https.html [ WontFix ]
 external/wpt/clipboard-apis/async-write-image-read-image-manual.https.html [ WontFix ]
 external/wpt/clipboard-apis/async-write-text-read-blobtext-manual.https.html [ WontFix ]
 external/wpt/clipboard-apis/async-write-text-read-text-manual.https.html [ WontFix ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 9222235..b3df8d3 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2903,6 +2903,8 @@
 
 crbug.com/723741 virtual/threaded/http/tests/devtools/tracing/idle-callback.js [ Failure Crash Pass Timeout ]
 
+# Sheriff 2019-03-07
+crbug.com/939406 external/wpt/html/browsers/windows/embedded-opener-remove-frame.html [ Failure ]
 
 # Untriaged failures after https://crrev.com/c/543695/.
 # These need to be updated but appear not to be related to that change.
@@ -2934,7 +2936,7 @@
 # Failure messages are unstable so we cannot create baselines.
 crbug.com/832071 external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 crbug.com/832071 virtual/navigation-mojo-response/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
-crbug.com/832071 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
+crbug.com/832071 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Timeout ]
 crbug.com/832071 virtual/outofblink-cors/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 
 # failures in external/wpt/css/css-animations/ and web-animations/ from Mozilla tests
@@ -3032,8 +3034,9 @@
 crbug.com/939181 virtual/not-site-per-process/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html [ Failure Timeout ]
 
 # ====== New tests from wpt-importer added here ======
-crbug.com/626703 [ Mac10.10 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Failure ]
-crbug.com/626703 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-sizing/range-percent-intrinsic-size-2a.html [ Failure ]
+crbug.com/626703 external/wpt/infrastructure/reftest/reftest_fuzzy.html [ Failure ]
+crbug.com/626703 external/wpt/infrastructure/reftest/reftest_fuzzy_1.html [ Failure ]
 crbug.com/626703 external/wpt/screen-orientation/onchange-event.html [ Timeout ]
 crbug.com/626703 external/wpt/html/semantics/forms/the-fieldset-element/accessibility/fieldset-div-display-contents-manual.html [ Skip ]
 crbug.com/626703 external/wpt/html/semantics/forms/the-fieldset-element/accessibility/role-manual.html [ Skip ]
@@ -3183,7 +3186,7 @@
 crbug.com/626703 external/wpt/svg/painting/marker-005.svg [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/css/css-variables/variable-exponential-blowup.html [ Timeout Crash ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-variables/variable-exponential-blowup.html [ Timeout ]
-crbug.com/626703 [ Win ] external/wpt/css/css-variables/variable-exponential-blowup.html [ Crash ]
+crbug.com/626703 [ Win ] external/wpt/css/css-variables/variable-exponential-blowup.html [ Timeout Crash ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/preload/dynamic-adding-preload-imagesrcset.tentative.html [ Timeout ]
 crbug.com/906369 external/wpt/css/css-text/text-transform/text-transform-capitalize-033.html [ Failure ]
 crbug.com/626703 virtual/outofblink-cors/external/wpt/fetch/content-type/response.window.html [ Timeout ]
@@ -4214,6 +4217,14 @@
 # This fails because off-the-main-thread script loading goes through different
 # loading path. Although we could remove this test. disable it for now.
 crbug.com/835717 virtual/omt-worker-fetch/fast/workers/chromium/worker-crash-with-invalid-location.html [ Skip ]
+# Needs investigation. These are timing out.
+crbug.com/835717 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/clients-get-client-types.https.html [ Skip ]
+crbug.com/835717 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/fetch-request-xhr-sync-on-worker.https.html [ Skip ]
+crbug.com/835717 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html [ Skip ]
+crbug.com/835717 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/worker-in-sandboxed-iframe-by-csp-fetch-event.https.html [ Skip ]
+crbug.com/835717 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/worker-interception.https.html [ Skip ]
+# This is crashing because of DCHECK.
+crbug.com/835717 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/dedicated-worker-service-worker-interception.https.html [ Skip ]
 
 # This fails because AllowedByNoSniff::MimeTypeAsScript() blocks the nested worker's
 # worker script, because the script url has a .html file extension.
@@ -4746,6 +4757,7 @@
 crbug.com/669329 virtual/threaded/http/tests/devtools/tracing/timeline-js/timeline-runtime-stats.js [ Pass Failure Crash ]
 
 crbug.com/799619 [ Debug ] http/tests/devtools/profiler/heap-snapshot-inspect-dom-wrapper.js [ Pass Timeout ]
+crbug.com/939037 [ Win7 ] http/tests/devtools/profiler/heap-snapshot-location.js [ Pass Timeout ]
 crbug.com/939037 [ Mac Linux ] http/tests/devtools/profiler/heap-snapshot-location.js [ Pass Timeout ]
 
 crbug.com/769347 [ Mac ] fast/dom/inert/inert-node-is-uneditable.html [ Failure ]
@@ -5985,8 +5997,7 @@
 crbug.com/936827 external/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual.html [ Failure Pass ]
 
 # Enable WPT animation-worklet test to run with threaded-compositing
-crbug.com/915352 virtual/threaded/external/wpt/animation-worklet/playback-rate.https.html [ Pass Failure ]
-crbug.com/915352 [ Linux ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-pause-resume.https.html [ Pass Failure ]
+crbug.com/915352 virtual/threaded/external/wpt/animation-worklet/worklet-animation-pause-resume.https.html [ Pass Failure ]
 
 crbug.com/915352 [ Mac10.10 Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html [ Pass Failure ]
 crbug.com/915352 [ Mac10.10 Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Pass Failure ]
@@ -6016,8 +6027,12 @@
 
 # Sheriff 2019-03-05
 crbug.com/938200 http/tests/devtools/network/network-blocked-reason.js [ Timeout Pass ]
-crbug.com/938780 [ Mac ] external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Failure ]
+crbug.com/938780 [ Mac ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Failure ]
 
 # Sheriff 2019-03-06
 crbug.com/938591 [ Linux ] fast/events/middleClickAutoscroll-nested-divs-forbidden.html [ Failure Pass ]
 crbug.com/938884 [ Win7 Mac ] http/tests/devtools/elements/styles-3/styles-add-blank-property.js [ Pass Timeout ]
+
+# Caused a revert of a good change.
+crbug.com/931533 media/video-played-collapse.html [ Pass Failure ]
+crbug.com/931533 virtual/video-surface-layer/media/video-played-collapse.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 09b2651a..880925d 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -622,7 +622,7 @@
   {
     "prefix": "omt-worker-fetch",
     "base": "external/wpt/service-workers",
-    "args": ["--enable-features=OffMainThreadServiceWorkerScriptFetch"]
+    "args": ["--enable-features=OffMainThreadDedicatedWorkerScriptFetch,PlzDedicatedWorker,OffMainThreadServiceWorkerScriptFetch,OffMainThreadSharedWorkerScriptFetch"]
   },
   {
     "prefix": "omt-worker-fetch",
diff --git a/third_party/blink/web_tests/clipboard/async-clipboard/async-navigator-clipboard-basics.https.html b/third_party/blink/web_tests/clipboard/async-clipboard/async-navigator-clipboard-basics.https.html
index bb00bb9..618bdf76 100644
--- a/third_party/blink/web_tests/clipboard/async-clipboard/async-navigator-clipboard-basics.https.html
+++ b/third_party/blink/web_tests/clipboard/async-clipboard/async-navigator-clipboard-basics.https.html
@@ -18,34 +18,29 @@
   assert_equals(navigator.clipboard, navigator.clipboard);
 }, "navigator.clipboard exists");
 
-/* clipboard.write(text/plain Blob) */
-
 promise_test(async () => {
   await getPermissions();
   const blob = new Blob(["hello"], {type: 'text/plain'});
-  await navigator.clipboard.write(blob);
-}, "navigator.clipboard.write(text/plain Blob) succeeds");
+  await navigator.clipboard.write([blob]);
+}, "navigator.clipboard.write([text/plain Blob]) succeeds");
 
 promise_test(async t => {
   await getPermissions();
   await promise_rejects(t, new TypeError(),
                          navigator.clipboard.write());
-}, "navigator.clipboard.write() fails (expect Blob)");
+}, "navigator.clipboard.write() fails (expect [Blob])");
 
 promise_test(async t => {
   await getPermissions();
   await promise_rejects(t, new TypeError(),
                          navigator.clipboard.write(null));
-}, "navigator.clipboard.write(null) fails (expect Blob)");
+}, "navigator.clipboard.write(null) fails (expect [Blob])");
 
 promise_test(async t => {
   await getPermissions();
   await promise_rejects(t, new TypeError(),
                          navigator.clipboard.write("Bad string"));
-}, "navigator.clipboard.write(DOMString) fails (expect Blob)");
-
-
-/* clipboard.writeText() */
+}, "navigator.clipboard.write(DOMString) fails (expect [Blob])");
 
 promise_test(async () => {
   await getPermissions();
@@ -58,18 +53,14 @@
                          navigator.clipboard.writeText());
 }, "navigator.clipboard.writeText() fails (expect DOMString)");
 
-/* text/plain or image/png Blob clipboard.read() */
-
 promise_test(async () => {
   await getPermissions();
   const result = await navigator.clipboard.read();
-  assert_true(result instanceof Blob);
+  assert_true(result instanceof Array);
+  assert_true(result[0] instanceof Blob);
   assert_equals(typeof result, "object");
 }, "navigator.clipboard.read() succeeds");
 
-
-/* clipboard.readText() */
-
 promise_test(async () => {
   await getPermissions();
   const result = await navigator.clipboard.readText();
diff --git a/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-blobtext.https.html b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-blobtext.https.html
index 7aadc20..7be86d9 100644
--- a/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-blobtext.https.html
+++ b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-blobtext.https.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Async Clipboard write (text/plain Blob) -> read (text/plain Blob) tests</title>
+<title>
+  Async Clipboard write ([text/plain Blob]) -> read ([text/plain Blob]) tests
+</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../../http/tests/resources/permissions-helper.js"></script>
@@ -12,8 +14,10 @@
 
     const blobInput = new Blob([textInput], {type: 'text/plain'});
 
-    await navigator.clipboard.write(blobInput);
-    const blobOutput = await navigator.clipboard.read();
+    await navigator.clipboard.write([blobInput]);
+    const blobsOutput = await navigator.clipboard.read();
+    assert_equals(blobsOutput.length, 1);
+    const blobOutput = blobsOutput[0];
     assert_equals(blobOutput.type, "text/plain");
 
     const textOutput = await (new Response(blobOutput)).text();
@@ -21,6 +25,6 @@
   }, "Verify write and read clipboard given text: " + textInput);
 }
 
-readWriteTest("Clipboard write (text/plain Blob) -> read (text/plain Blob) test");
+readWriteTest("Clipboard write ([text/plain Blob]) -> read ([text/plain Blob]) test");
 readWriteTest("non-Latin1 text encoding test データ");
 </script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-text.https.html b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-text.https.html
index 1305e5b..62e73a0 100644
--- a/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-text.https.html
+++ b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-blobtext-read-text.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Async Clipboard write (text/plain Blob) -> readText tests</title>
+<title>Async Clipboard write ([text/plain Blob]) -> readText tests</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../../http/tests/resources/permissions-helper.js"></script>
@@ -12,13 +12,13 @@
 
     const blobInput = new Blob([textInput], {type: 'text/plain'});
 
-    await navigator.clipboard.write(blobInput);
+    await navigator.clipboard.write([blobInput]);
     const textOutput = await navigator.clipboard.readText();
 
     assert_equals(textOutput, textInput);
   }, "Verify write and read clipboard given text: " + textInput);
 }
 
-readWriteTest("Clipboard write (text/plain Blob) -> read text test");
+readWriteTest("Clipboard write ([text/plain Blob]) -> read text test");
 readWriteTest("non-Latin1 text encoding test データ");
 </script>
diff --git a/third_party/blink/web_tests/clipboard/async-clipboard/async-write-duplicate-mime-type.https.html b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-duplicate-mime-type.https.html
new file mode 100644
index 0000000..1d54dd4
--- /dev/null
+++ b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-duplicate-mime-type.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>
+  Async Clipboard write duplicate mime type test
+</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../http/tests/resources/permissions-helper.js"></script>
+
+<script>
+promise_test(async t => {
+  await PermissionsHelper.setPermission('clipboard-write', 'granted');
+
+  const blobText = new Blob(["test text"], {type: 'text/plain'});
+  const blobText2 = new Blob(["test text"], {type: 'text/plain'});
+
+  assert_equals(blobText.type, "text/plain");
+  assert_equals(blobText2.type, "text/plain");
+
+  await promise_rejects(t, 'NotAllowedError',
+      navigator.clipboard.write([blobText, blobText2]));
+}, "Verify write and read clipboard (multiple blobs)");
+</script>
diff --git a/third_party/blink/web_tests/clipboard/async-clipboard/async-write-text-read-blobtext.https.html b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-text-read-blobtext.https.html
index c896aa1..2dfeb5d9 100644
--- a/third_party/blink/web_tests/clipboard/async-clipboard/async-write-text-read-blobtext.https.html
+++ b/third_party/blink/web_tests/clipboard/async-clipboard/async-write-text-read-blobtext.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Async Clipboard writeText -> read (text/plain Blob) tests</title>
+<title>Async Clipboard writeText -> read ([text/plain Blob]) tests</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../../http/tests/resources/permissions-helper.js"></script>
@@ -11,7 +11,9 @@
     await PermissionsHelper.setPermission('clipboard-write', 'granted');
 
     await navigator.clipboard.writeText(textInput);
-    const blobOutput = await navigator.clipboard.read();
+    const blobsOutput = await navigator.clipboard.read();
+    assert_equals(blobsOutput.length, 1);
+    const blobOutput = blobsOutput[0];
     assert_equals(blobOutput.type, "text/plain");
 
     const textOutput = await (new Response(blobOutput)).text();
@@ -19,6 +21,6 @@
   }, "Verify write and read clipboard given text: " + textInput);
 }
 
-readWriteTest("Clipboard write text -> read (text/plain Blob) test");
+readWriteTest("Clipboard write text -> read ([text/plain Blob]) test");
 readWriteTest("non-Latin1 text encoding test データ");
 </script>
diff --git a/third_party/blink/web_tests/clipboard/async-clipboard/writetext-denied.https.html b/third_party/blink/web_tests/clipboard/async-clipboard/writetext-denied.https.html
index 04ce46c..4a0f6ed 100644
--- a/third_party/blink/web_tests/clipboard/async-clipboard/writetext-denied.https.html
+++ b/third_party/blink/web_tests/clipboard/async-clipboard/writetext-denied.https.html
@@ -7,6 +7,6 @@
 <script>
 promise_test(async t => {
   await PermissionsHelper.setPermission('clipboard-write', 'denied');
-  await promise_rejects(t, 'NotAllowedError', navigator.clipboard.readText());
+  await promise_rejects(t, 'NotAllowedError', navigator.clipboard.writeText("xyz"));
 }, "navigator.clipboard.writeText() fails when permission denied");
 </script>
\ No newline at end of file
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 379450c..eacba0e 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
@@ -60111,6 +60111,18 @@
      {}
     ]
    ],
+   "css/css-sizing/range-percent-intrinsic-size-2a.html": [
+    [
+     "/css/css-sizing/range-percent-intrinsic-size-2a.html",
+     [
+      [
+       "/css/css-sizing/range-percent-intrinsic-size-2a-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-sizing/whitespace-and-break.html": [
     [
      "/css/css-sizing/whitespace-and-break.html",
@@ -60819,6 +60831,18 @@
      {}
     ]
    ],
+   "css/css-text-decor/text-decoration-propagation-shadow.html": [
+    [
+     "/css/css-text-decor/text-decoration-propagation-shadow.html",
+     [
+      [
+       "/css/css-text-decor/reference/text-decoration-underline-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-text-decor/text-decoration-style-multiple.html": [
     [
      "/css/css-text-decor/text-decoration-style-multiple.html",
@@ -93387,6 +93411,378 @@
      {}
     ]
    ],
+   "css/filter-effects/tainting-feblend-001.html": [
+    [
+     "/css/filter-effects/tainting-feblend-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feblend-002.html": [
+    [
+     "/css/filter-effects/tainting-feblend-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fecomponenttransfer-001.html": [
+    [
+     "/css/filter-effects/tainting-fecomponenttransfer-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fecomponenttransfer-002.html": [
+    [
+     "/css/filter-effects/tainting-fecomponenttransfer-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fecomposite-001.html": [
+    [
+     "/css/filter-effects/tainting-fecomposite-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fecomposite-002.html": [
+    [
+     "/css/filter-effects/tainting-fecomposite-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feconvolvematrix-001.html": [
+    [
+     "/css/filter-effects/tainting-feconvolvematrix-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feconvolvematrix-002.html": [
+    [
+     "/css/filter-effects/tainting-feconvolvematrix-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fediffuselighting-001.html": [
+    [
+     "/css/filter-effects/tainting-fediffuselighting-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fediffuselighting-002.html": [
+    [
+     "/css/filter-effects/tainting-fediffuselighting-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fediffuselighting-003.html": [
+    [
+     "/css/filter-effects/tainting-fediffuselighting-003.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fediffuselighting-dynamic.html": [
+    [
+     "/css/filter-effects/tainting-fediffuselighting-dynamic.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fedisplacementmap-001.html": [
+    [
+     "/css/filter-effects/tainting-fedisplacementmap-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fedisplacementmap-002.html": [
+    [
+     "/css/filter-effects/tainting-fedisplacementmap-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fedropshadow-001.html": [
+    [
+     "/css/filter-effects/tainting-fedropshadow-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fedropshadow-002.html": [
+    [
+     "/css/filter-effects/tainting-fedropshadow-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fedropshadow-003.html": [
+    [
+     "/css/filter-effects/tainting-fedropshadow-003.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feflood-001.html": [
+    [
+     "/css/filter-effects/tainting-feflood-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feflood-002.html": [
+    [
+     "/css/filter-effects/tainting-feflood-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feflood-dynamic.html": [
+    [
+     "/css/filter-effects/tainting-feflood-dynamic.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fegaussianblur-001.html": [
+    [
+     "/css/filter-effects/tainting-fegaussianblur-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fegaussianblur-002.html": [
+    [
+     "/css/filter-effects/tainting-fegaussianblur-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-femorphology-001.html": [
+    [
+     "/css/filter-effects/tainting-femorphology-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-femorphology-002.html": [
+    [
+     "/css/filter-effects/tainting-femorphology-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feoffset-001.html": [
+    [
+     "/css/filter-effects/tainting-feoffset-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-feoffset-002.html": [
+    [
+     "/css/filter-effects/tainting-feoffset-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fespecularlighting-001.html": [
+    [
+     "/css/filter-effects/tainting-fespecularlighting-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fespecularlighting-002.html": [
+    [
+     "/css/filter-effects/tainting-fespecularlighting-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fespecularlighting-003.html": [
+    [
+     "/css/filter-effects/tainting-fespecularlighting-003.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fetile-001.html": [
+    [
+     "/css/filter-effects/tainting-fetile-001.html",
+     [
+      [
+       "/css/filter-effects/reference/green-blue-stripe-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/tainting-fetile-002.html": [
+    [
+     "/css/filter-effects/tainting-fetile-002.html",
+     [
+      [
+       "/css/filter-effects/reference/green-100x100.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/mediaqueries/aspect-ratio-001.html": [
     [
      "/css/mediaqueries/aspect-ratio-001.html",
@@ -107955,6 +108351,30 @@
      {}
     ]
    ],
+   "infrastructure/reftest/reftest_fuzzy.html": [
+    [
+     "/infrastructure/reftest/reftest_fuzzy.html",
+     [
+      [
+       "/infrastructure/reftest/fuzzy-ref-1.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "infrastructure/reftest/reftest_fuzzy_1.html": [
+    [
+     "/infrastructure/reftest/reftest_fuzzy_1.html",
+     [
+      [
+       "/infrastructure/reftest/fuzzy-ref-1.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "infrastructure/reftest/reftest_match.html": [
     [
      "/infrastructure/reftest/reftest_match.html",
@@ -108775,18 +109195,6 @@
      {}
     ]
    ],
-   "shadow-dom/untriaged/shadow-trees/text-decoration-001.html": [
-    [
-     "/shadow-dom/untriaged/shadow-trees/text-decoration-001.html",
-     [
-      [
-       "/shadow-dom/untriaged/shadow-trees/text-decoration-001-ref.html",
-       "=="
-      ]
-     ],
-     {}
-    ]
-   ],
    "shadow-dom/untriaged/styles/not-apply-in-shadow-root-001.html": [
     [
      "/shadow-dom/untriaged/styles/not-apply-in-shadow-root-001.html",
@@ -142863,6 +143271,11 @@
      {}
     ]
    ],
+   "css/css-sizing/range-percent-intrinsic-size-2a-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-sizing/support/dynamic-available-size-iframe.html": [
     [
      {}
@@ -143498,6 +143911,11 @@
      {}
     ]
    ],
+   "css/css-text-decor/reference/text-decoration-underline-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-text-decor/reference/text-emphasis-color-001-ref.xht": [
     [
      {}
@@ -153948,6 +154366,11 @@
      {}
     ]
    ],
+   "css/filter-effects/reference/green-blue-stripe-100x100.html": [
+    [
+     {}
+    ]
+   ],
    "css/filter-effects/reference/svg-feflood-ref.html": [
     [
      {}
@@ -162088,6 +162511,11 @@
      {}
     ]
    ],
+   "fetch/stale-while-revalidate/sw-intercept.js": [
+    [
+     {}
+    ]
+   ],
    "fonts/AD.woff": [
     [
      {}
@@ -164663,6 +165091,11 @@
      {}
     ]
    ],
+   "html/browsers/the-window-object/support/closed.html": [
+    [
+     {}
+    ]
+   ],
    "html/browsers/the-window-object/support/noopener-target.html": [
     [
      {}
@@ -175213,6 +175646,11 @@
      {}
     ]
    ],
+   "infrastructure/metadata/infrastructure/reftest/reftest_fuzzy.html.ini": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/metadata/infrastructure/reftest/reftest_match_fail.html.ini": [
     [
      {}
@@ -175298,6 +175736,11 @@
      {}
     ]
    ],
+   "infrastructure/reftest/fuzzy-ref-1.html": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/reftest/green.html": [
     [
      {}
@@ -184863,6 +185306,11 @@
      {}
     ]
    ],
+   "service-workers/cache-storage/serviceworker/cache-match.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/cache-storage/serviceworker/cache-matchAll.https-expected.txt": [
     [
      {}
@@ -184878,6 +185326,11 @@
      {}
     ]
    ],
+   "service-workers/cache-storage/window/cache-match.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/cache-storage/window/cache-matchAll.https-expected.txt": [
     [
      {}
@@ -184898,6 +185351,11 @@
      {}
     ]
    ],
+   "service-workers/cache-storage/worker/cache-match.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/cache-storage/worker/cache-matchAll.https-expected.txt": [
     [
      {}
@@ -186873,11 +187331,6 @@
      {}
     ]
    ],
-   "shadow-dom/untriaged/shadow-trees/text-decoration-001-ref.html": [
-    [
-     {}
-    ]
-   ],
    "shadow-dom/untriaged/styles/not-apply-in-shadow-root-001-ref.html": [
     [
      {}
@@ -244957,6 +245410,12 @@
      {}
     ]
    ],
+   "fetch/stale-while-revalidate/fetch-sw.https.tentative.html": [
+    [
+     "/fetch/stale-while-revalidate/fetch-sw.https.tentative.html",
+     {}
+    ]
+   ],
    "fetch/stale-while-revalidate/fetch.tentative.html": [
     [
      "/fetch/stale-while-revalidate/fetch.tentative.html",
@@ -246917,6 +247376,12 @@
      }
     ]
    ],
+   "html/browsers/the-window-object/closed-attribute.window.js": [
+    [
+     "/html/browsers/the-window-object/closed-attribute.window.html",
+     {}
+    ]
+   ],
    "html/browsers/the-window-object/focus.window.js": [
     [
      "/html/browsers/the-window-object/focus.window.html",
@@ -317789,7 +318254,7 @@
    "testharness"
   ],
   "animation-worklet/playback-rate.https.html": [
-   "dba27afe20e1087328339b03f917855af31d518c",
+   "5367497b831d87741bb4aca67d4e0efcee6389e2",
    "testharness"
   ],
   "animation-worklet/references/translated-box-ref.html": [
@@ -368069,11 +368534,11 @@
    "testharness"
   ],
   "css/css-sizing/range-percent-intrinsic-size-1-ref.html": [
-   "a4419eab319e0732ae9d2ca1422a8154fdf2a781",
+   "9a68590b6389eb9c1fefc2dc37f42b2a60b3b41a",
    "support"
   ],
   "css/css-sizing/range-percent-intrinsic-size-1.html": [
-   "87399578988a1c09c5d031c9090cdce7ba34c783",
+   "018129aae3a7fcdc127c757d9b4ddba558115d3c",
    "reftest"
   ],
   "css/css-sizing/range-percent-intrinsic-size-2-ref.html": [
@@ -368084,6 +368549,14 @@
    "e11e2955891f251409e8519abd48f10fb2dd89e9",
    "reftest"
   ],
+  "css/css-sizing/range-percent-intrinsic-size-2a-ref.html": [
+   "815b0a1021cb9113c39fad85b5052429dc74533f",
+   "support"
+  ],
+  "css/css-sizing/range-percent-intrinsic-size-2a.html": [
+   "f2c2431978b1596078f48727da0953f23e1f0d3e",
+   "reftest"
+  ],
   "css/css-sizing/support/dynamic-available-size-iframe.html": [
    "8b61c876389e1fbd0792dd58763e3e2a3d4ef133",
    "support"
@@ -369284,6 +369757,10 @@
    "8c0bee6720355c6216ce6f11d27e2f1fb4d4b401",
    "support"
   ],
+  "css/css-text-decor/reference/text-decoration-underline-ref.html": [
+   "2370054a8fbd5cb8c00bfb95401129d01de38d00",
+   "support"
+  ],
   "css/css-text-decor/reference/text-emphasis-color-001-ref.xht": [
    "8380c197b326fa184369094e75c7748fff209ee2",
    "support"
@@ -369408,6 +369885,10 @@
    "ea6a0c86c19bb3bb27b04cc0c49d2bdf433c5dbf",
    "reftest"
   ],
+  "css/css-text-decor/text-decoration-propagation-shadow.html": [
+   "ac365ee046666c7e2945d1e76622b38f723cf6a4",
+   "reftest"
+  ],
   "css/css-text-decor/text-decoration-serialization.tentative-expected.txt": [
    "2fac902c4641be1db62dc791301731042c21d1c6",
    "support"
@@ -394172,6 +394653,10 @@
    "f718ea6abfbab54333ba674ff0dcd320d8672bcd",
    "support"
   ],
+  "css/filter-effects/reference/green-blue-stripe-100x100.html": [
+   "01546f115d112aa27495b3fd45347b22e30fe7ee",
+   "support"
+  ],
   "css/filter-effects/reference/svg-feflood-ref.html": [
    "5623b08ecd71b292e698ee249a79b59d0046300f",
    "support"
@@ -394400,6 +394885,130 @@
    "8c9e3ea05805a5030b8034a7a01e53d4984a39dc",
    "testharness"
   ],
+  "css/filter-effects/tainting-feblend-001.html": [
+   "416f57849a479e01e2f70a489b92fc4f8bf0ecff",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feblend-002.html": [
+   "3d753413a6cdf44d39d832f3171a8aceb0f6fb6b",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fecomponenttransfer-001.html": [
+   "89c60ba9688b6d680dfa77924531f1ab738e6f65",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fecomponenttransfer-002.html": [
+   "6f29b35ba3144fda4a6fbdce9dee5eaadfca83b5",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fecomposite-001.html": [
+   "ddaa53e1e24c02bba47919dadd572bcb1e146173",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fecomposite-002.html": [
+   "1e68378ac6cc2d6bd5444ea48c12768437ceea1c",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feconvolvematrix-001.html": [
+   "eda1d7e2fc80346191af68a8b780ada1a70dea99",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feconvolvematrix-002.html": [
+   "e7ec038fbdd249541af70caa704997f0c8c765f5",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fediffuselighting-001.html": [
+   "bce3a291819a7273ae1926bfe8dd08314ece3a79",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fediffuselighting-002.html": [
+   "773c25bf029940c84f2f9f8d50445a079755a34e",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fediffuselighting-003.html": [
+   "0b7bb4d19af286e29cf7a64c5cc9c5ea89f5c908",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fediffuselighting-dynamic.html": [
+   "89674095a0cab68653690b543b70c3385c69d0fe",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fedisplacementmap-001.html": [
+   "2c99cc4981e71434bef0786e540945d20fec243a",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fedisplacementmap-002.html": [
+   "9648b54bf3dfd90c40a7142f2ce5f4fc04e74608",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fedropshadow-001.html": [
+   "1b3dbb497ce6a1dde712941ed2d0ca0628a55578",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fedropshadow-002.html": [
+   "26fc687d88a08557c279f71db254e3f1acd55c36",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fedropshadow-003.html": [
+   "d42102aa6c58204da2ed37352851428310403206",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feflood-001.html": [
+   "714e6b992fbd91816366246b3970d4b612f0b339",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feflood-002.html": [
+   "79ea8dac522df16c3532f620e68b6db8b5216b23",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feflood-dynamic.html": [
+   "fc9b3354fab77cdcccb10a42e9dac73a35236587",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fegaussianblur-001.html": [
+   "a938c7494fec6fd7024109338b8a31f6cf13dea1",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fegaussianblur-002.html": [
+   "55dabbbdbb34d8f23f78c776f2080f6dd4656a58",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-femorphology-001.html": [
+   "9af566410d2f1aad93efea604280ef4ff680f708",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-femorphology-002.html": [
+   "2c097202ee888444a0b7dcf37564744dcd986771",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feoffset-001.html": [
+   "8704b693e812388c44eff46a812718fc504dc1ba",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-feoffset-002.html": [
+   "3c04268f5cbedb46a26381ac8f5cdf9e4577e785",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fespecularlighting-001.html": [
+   "c2825258a571c975f0cb6ea64966c2417f763fa7",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fespecularlighting-002.html": [
+   "773b0c7065caffe13462982d7e30dc38c7706ec1",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fespecularlighting-003.html": [
+   "173ed2da9f5ad935ea56ee8649c3a8a87c21eb46",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fetile-001.html": [
+   "c5b955eef85246434a9e884d41e3b0c143608708",
+   "reftest"
+  ],
+  "css/filter-effects/tainting-fetile-002.html": [
+   "ed77301d2ef6675662a5f3b121e2489e699cec10",
+   "reftest"
+  ],
   "css/geometry/DOMMatrix-001.html": [
    "a8a357bff606925aaa95dce6c4642b81bd8c88ea",
    "testharness"
@@ -410056,12 +410665,16 @@
    "20d307e9188405dcec011042487aa2c7354930bf",
    "support"
   ],
+  "fetch/stale-while-revalidate/fetch-sw.https.tentative.html": [
+   "2286739ecf27966d1efedad7c4d4031475693988",
+   "testharness"
+  ],
   "fetch/stale-while-revalidate/fetch.tentative.html": [
-   "5b9b2dc5de2835d1f4a9f49cc63e0b3f03317698",
+   "33d844fd08f3352bff1677cabf0c1dd06177e40a",
    "testharness"
   ],
   "fetch/stale-while-revalidate/stale-css.py": [
-   "425c889ac5ec58527e63ab25a4f70558b7daeef3",
+   "9566833e507603a35aadd4de622f388d4f77307f",
    "support"
   ],
   "fetch/stale-while-revalidate/stale-css.tentative.html": [
@@ -410069,7 +410682,7 @@
    "testharness"
   ],
   "fetch/stale-while-revalidate/stale-image.py": [
-   "ce7f0fc782613e0a36e3d0aba0521e45f1ca6bf0",
+   "e0cf94bcd0abc3e2f3aba3ad4448d5f519312335",
    "support"
   ],
   "fetch/stale-while-revalidate/stale-image.tentative.html": [
@@ -410077,13 +410690,17 @@
    "testharness"
   ],
   "fetch/stale-while-revalidate/stale-script.py": [
-   "0f91a9b83486678eabd600ecb6336695e5dd6970",
+   "5ea5987db3dd707b7ab77d11d883c0217705aaf6",
    "support"
   ],
   "fetch/stale-while-revalidate/stale-script.tentative.html": [
    "8cbb54b7dab3bc9b9e8763c5358a9232d24c1e7f",
    "testharness"
   ],
+  "fetch/stale-while-revalidate/sw-intercept.js": [
+   "dca7de51b0b8c5518276e70ae219b7bc8f869a95",
+   "support"
+  ],
   "fonts/AD.woff": [
    "3df8ea8efdabd11bc45fdcc6d4f3fec771be6650",
    "support"
@@ -413656,6 +414273,10 @@
    "a9d42e26dea16afa9743d31aa7b72f3f09e46e68",
    "support"
   ],
+  "html/browsers/the-window-object/closed-attribute.window.js": [
+   "88a3beba6f10b80b0b90acdc0b0f957e1a17cefc",
+   "testharness"
+  ],
   "html/browsers/the-window-object/focus.window.js": [
    "6ec7feee281e756ae6452e0f9b17d9b93032d010",
    "testharness"
@@ -413756,6 +414377,10 @@
    "1fb0ed7c1e62da55b890c6434bee6e46637e0209",
    "testharness"
   ],
+  "html/browsers/the-window-object/support/closed.html": [
+   "3b70598e34d0e9b46a8ba2150a1589fecfb90ea8",
+   "support"
+  ],
   "html/browsers/the-window-object/support/noopener-target.html": [
    "41e197a74630ac70cb34ab2bf164b188767f7218",
    "support"
@@ -414097,7 +414722,7 @@
    "support"
   ],
   "html/browsers/windows/embedded-opener-remove-frame-expected.txt": [
-   "b2fe3032be484df54f4e71aab73f3b0a36331caf",
+   "1d304732ae89dc1d919737f00e0d29eaec6d5e91",
    "support"
   ],
   "html/browsers/windows/embedded-opener-remove-frame.html": [
@@ -431121,7 +431746,7 @@
    "testharness"
   ],
   "import-maps/resolving.tentative-expected.txt": [
-   "4dc845e52ab307d1207f56e990be52d726459d97",
+   "b5da00638a0048517bd8af6c5909be4e906ec4e2",
    "support"
   ],
   "import-maps/resolving.tentative.html": [
@@ -431141,7 +431766,7 @@
    "support"
   ],
   "import-maps/resources/resolving.js": [
-   "ec2645e599119ab64949c016076164b40277006c",
+   "0409962e4d7904bd9ddbe8601b7328d8d0ad6d11",
    "support"
   ],
   "import-maps/resources/test-helper.js": [
@@ -431308,6 +431933,10 @@
    "472b33f7764bde6e2aea7bc2ccd8bf3739babad2",
    "support"
   ],
+  "infrastructure/metadata/infrastructure/reftest/reftest_fuzzy.html.ini": [
+   "1ab2d770afef92b0af4eaf9153ce5344bbbdc964",
+   "support"
+  ],
   "infrastructure/metadata/infrastructure/reftest/reftest_match_fail.html.ini": [
    "f3dc3362fac41bbe8ded44589e898ef589cb1a89",
    "support"
@@ -431380,6 +432009,10 @@
    "c2e9986ad85dde0483a553459964a0345399a782",
    "reftest"
   ],
+  "infrastructure/reftest/fuzzy-ref-1.html": [
+   "e50fc11ef6ea80754e702becfbf675feebe3dbb9",
+   "support"
+  ],
   "infrastructure/reftest/green-ref.html": [
    "0e145d60b55b3502639d15f10d4d63a6b0f79b7d",
    "reftest"
@@ -431436,6 +432069,14 @@
    "c8e548c462255638a32c474a177759ff6d7cceaf",
    "reftest_node"
   ],
+  "infrastructure/reftest/reftest_fuzzy.html": [
+   "7429025798151b620dd72db71a46070aafe6c070",
+   "reftest"
+  ],
+  "infrastructure/reftest/reftest_fuzzy_1.html": [
+   "1930fe0ae8fb1aee30e91e691fe6a73ccfc87d0e",
+   "reftest"
+  ],
   "infrastructure/reftest/reftest_match.html": [
    "333cc6c1ecdf2000e4b118565661761b876a7299",
    "reftest"
@@ -457565,7 +458206,7 @@
    "support"
   ],
   "service-workers/cache-storage/script-tests/cache-match.js": [
-   "b2b731cc6546529770c20a0bb5a30168d60b0ec6",
+   "8bf7fda30967fbb4ed25325d49e0cd99fc16df55",
    "support"
   ],
   "service-workers/cache-storage/script-tests/cache-matchAll.js": [
@@ -457616,6 +458257,10 @@
    "736f74d98960aa81e1569432683705ccc7c3ceee",
    "testharness"
   ],
+  "service-workers/cache-storage/serviceworker/cache-match.https-expected.txt": [
+   "3f30ee17d732d3526b7af3db038eebcc39d8c07c",
+   "support"
+  ],
   "service-workers/cache-storage/serviceworker/cache-match.https.html": [
    "81db70943f0a9871719b704d094bfd042152f809",
    "testharness"
@@ -457672,6 +458317,10 @@
    "8398c33e146c5983c10922457b7f8591b49f5ed8",
    "testharness"
   ],
+  "service-workers/cache-storage/window/cache-match.https-expected.txt": [
+   "7326e233734b782029455fa300496f7047a70c45",
+   "support"
+  ],
   "service-workers/cache-storage/window/cache-match.https.html": [
    "f28efad0b76b341171ad230e18d243e229153687",
    "testharness"
@@ -457732,6 +458381,10 @@
    "6bafe21d30bb55d4e0dfdc8f3633bcf359fc7d6c",
    "testharness"
   ],
+  "service-workers/cache-storage/worker/cache-match.https-expected.txt": [
+   "7326e233734b782029455fa300496f7047a70c45",
+   "support"
+  ],
   "service-workers/cache-storage/worker/cache-match.https.html": [
    "479a29d1eec5f49416a075aae9306bcd5b5cc3e6",
    "testharness"
@@ -460684,14 +461337,6 @@
    "24d5d016b91a50f1b794c62b81e6ace1d8dd04ba",
    "reftest"
   ],
-  "shadow-dom/untriaged/shadow-trees/text-decoration-001-ref.html": [
-   "8c10d251557cfecbdd2bbb1228db284f0fd4b971",
-   "support"
-  ],
-  "shadow-dom/untriaged/shadow-trees/text-decoration-001.html": [
-   "d8def126d64a6c0c9b50677fba0b38e486cef479",
-   "reftest"
-  ],
   "shadow-dom/untriaged/shadow-trees/upper-boundary-encapsulation/dom-tree-accessors-001.html": [
    "71421481e0739e566ea71e1cf59ba8457ce90197",
    "testharness"
@@ -464053,7 +464698,7 @@
    "support"
   ],
   "tools/manifest/item.py": [
-   "c06daee3e24f07fbe83ab82c916e175832f386ed",
+   "c6363a707013e83e6ece724eee8069078a3413f4",
    "support"
   ],
   "tools/manifest/log.py": [
@@ -464065,7 +464710,7 @@
    "support"
   ],
   "tools/manifest/sourcefile.py": [
-   "b5d7cdf8799c98d5b7e00452a54fcd63ccd3e17b",
+   "78843b089b87c111efe24762e7bee55cf44f55ec",
    "support"
   ],
   "tools/manifest/update.py": [
@@ -468293,7 +468938,7 @@
    "support"
   ],
   "tools/wptrunner/requirements.txt": [
-   "24a7d3d7e0341472dd032c71d7abfd1f10425ca8",
+   "37f4fde47869bd0c1f30ab12ef96cb5d625b8c9d",
    "support"
   ],
   "tools/wptrunner/requirements_chrome.txt": [
@@ -468425,7 +469070,7 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/environment.py": [
-   "493d3c43fc06ff1e0bc2105a2c86fb851b3f4de9",
+   "6563721caee8288047ac84ef62456ce0723738bc",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/__init__.py": [
@@ -468433,7 +469078,7 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/base.py": [
-   "8958ecfc3bb240ebda2999098f04951d14c90b6e",
+   "5fa30563890cc1d95bc7cd1843c0aeca4dd3e197",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executorchrome.py": [
@@ -468449,7 +469094,7 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executormarionette.py": [
-   "b70f0ed0b0c2dc9d7dd0fe24934818c0a25f2d3b",
+   "f9fd97b6ba8b88907db2115157407d153b443a82",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executoropera.py": [
@@ -468473,7 +469118,7 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executorwebdriver.py": [
-   "73f5fcf84a8e742ea74a4d96760547671d593252",
+   "563252ce2bbfee5a3a92fd39d8c171d8aacdeb18",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executorwebkit.py": [
@@ -468541,7 +469186,7 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/manifestexpected.py": [
-   "80284bd8a215e73a1140239a9aec1f312d8cacb5",
+   "fb3ef627d94dc643f2e64fb0c7203621214e4267",
    "support"
   ],
   "tools/wptrunner/wptrunner/manifestinclude.py": [
@@ -468645,7 +469290,7 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py": [
-   "4eb292efb7508a0aac72f392c1af0e88e07fe5fc",
+   "7ad35756d0c6c34282f3d1a278a40b98a882ae9d",
    "support"
   ],
   "tools/wptrunner/wptrunner/wptmanifest/backends/static.py": [
@@ -468693,7 +469338,7 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/wpttest.py": [
-   "dc1c6b63aefef5ab225bef70e6c3f09e379f60e2",
+   "6a4fa4fdf82dca14e44d8e3c6d63ce2c7e2b56b2",
    "support"
   ],
   "tools/wptserve/LICENSE": [
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-key.html b/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-key.html
deleted file mode 100644
index 2fbe95e..0000000
--- a/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-key.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>IndexedDB: ES bindings - Inject a key into a value</title>
-<meta name="help" href="https://w3c.github.io/IndexedDB/#inject-key-into-value">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="support-promises.js"></script>
-<script>
-
-promise_test(async t => {
-  const db = await createDatabase(t, db => {
-    db.createObjectStore('store');
-  });
-
-  let setter_called = false;
-  Object.defineProperty(Object.prototype, '10', {
-    configurable: true,
-    set: value => { setter_called = true; },
-  });
-  t.add_cleanup(() => { delete Object.prototype['10']; });
-
-  const tx = db.transaction('store', 'readwrite');
-  const result = await promiseForRequest(t, tx.objectStore('store').put(
-      'value', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'key']));
-
-  assert_false(setter_called,
-               'Setter should not be called for key result.');
-  assert_true(result.hasOwnProperty('10'),
-              'Result should have own-property overriding prototype setter.');
-  assert_equals(result[10], 'key',
-                'Result should have expected property.');
-}, 'Returning keys to script should bypass prototype setters');
-
-promise_test(async t => {
-  const db = await createDatabase(t, db => {
-    db.createObjectStore('store', {autoIncrement: true, keyPath: 'id'});
-  });
-
-  let setter_called = false;
-  Object.defineProperty(Object.prototype, 'id', {
-    configurable: true,
-    set: value => { setter_called = true; },
-  });
-  t.add_cleanup(() => { delete Object.prototype['id']; });
-
-  const tx = db.transaction('store', 'readwrite');
-  tx.objectStore('store').put({});
-  const result = await promiseForRequest(t, tx.objectStore('store').get(1));
-
-  assert_false(setter_called,
-               'Setter should not be called for key result.');
-  assert_true(result.hasOwnProperty('id'),
-              'Result should have own-property overriding prototype setter.');
-  assert_equals(result.id, 1,
-                'Own property should match primary key generator value');
-}, 'Returning values to script should bypass prototype setters');
-
-promise_test(async t => {
-  const db = await createDatabase(t, db => {
-    db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.c'});
-  });
-
-  Object.prototype.a = {b: {c: 'on proto'}};
-  t.add_cleanup(() => { delete Object.prototype.a; });
-
-  const tx = db.transaction('store', 'readwrite');
-  tx.objectStore('store').put({});
-  const result = await promiseForRequest(t, tx.objectStore('store').get(1));
-
-  assert_true(result.hasOwnProperty('a'),
-              'Result should have own-properties overriding prototype.');
-  assert_true(result.a.hasOwnProperty('b'),
-              'Result should have own-properties overriding prototype.');
-  assert_true(result.a.b.hasOwnProperty('c'),
-              'Result should have own-properties overriding prototype.');
-  assert_equals(result.a.b.c, 1,
-                'Own property should match primary key generator value');
-  assert_equals(Object.prototype.a.b.c, 'on proto',
-                'Prototype should not be modified');
-}, 'Returning values to script should bypass prototype chain');
-
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-keys-bypass-setters.html b/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-keys-bypass-setters.html
new file mode 100644
index 0000000..91d586b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-keys-bypass-setters.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>IndexedDB: ES bindings - Inject a key into a value - Keys bypass setters</title>
+<meta name="help"
+href="https://w3c.github.io/IndexedDB/#inject-key-into-value-keys-bypass-setters">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support-promises.js"></script>
+<script>
+
+promise_test(async t => {
+  const db = await createDatabase(t, db => {
+    db.createObjectStore('store');
+  });
+
+  let setter_called = false;
+  Object.defineProperty(Object.prototype, '10', {
+    configurable: true,
+    set: value => { setter_called = true; },
+  });
+  t.add_cleanup(() => { delete Object.prototype['10']; });
+
+  const tx = db.transaction('store', 'readwrite');
+  const result = await promiseForRequest(t, tx.objectStore('store').put(
+      'value', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'key']));
+
+  assert_false(setter_called,
+               'Setter should not be called for key result.');
+  assert_true(result.hasOwnProperty('10'),
+              'Result should have own-property overriding prototype setter.');
+  assert_equals(result[10], 'key',
+                'Result should have expected property.');
+}, 'Returning keys to script should bypass prototype setters');
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-values-bypass-chain.html b/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-values-bypass-chain.html
new file mode 100644
index 0000000..02fdb8a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-values-bypass-chain.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>IndexedDB: ES bindings - Inject a key into a value - Values bypass chain</title>
+<meta name="help"
+href="https://w3c.github.io/IndexedDB/#inject-key-into-value-values-bypass-chain">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support-promises.js"></script>
+<script>
+
+promise_test(async t => {
+  const db = await createDatabase(t, db => {
+    db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.c'});
+  });
+
+  Object.prototype.a = {b: {c: 'on proto'}};
+  t.add_cleanup(() => { delete Object.prototype.a; });
+
+  const tx = db.transaction('store', 'readwrite');
+  tx.objectStore('store').put({});
+  const result = await promiseForRequest(t, tx.objectStore('store').get(1));
+
+  assert_true(result.hasOwnProperty('a'),
+              'Result should have own-properties overriding prototype.');
+  assert_true(result.a.hasOwnProperty('b'),
+              'Result should have own-properties overriding prototype.');
+  assert_true(result.a.b.hasOwnProperty('c'),
+              'Result should have own-properties overriding prototype.');
+  assert_equals(result.a.b.c, 1,
+                'Own property should match primary key generator value');
+  assert_equals(Object.prototype.a.b.c, 'on proto',
+                'Prototype should not be modified');
+}, 'Returning values to script should bypass prototype chain');
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-values-bypass-setters.html b/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-values-bypass-setters.html
new file mode 100644
index 0000000..c16c0a4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/bindings-inject-values-bypass-setters.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>IndexedDB: ES bindings - Inject a key into a value - Values bypass
+  setters</title>
+<meta name="help"
+href="https://w3c.github.io/IndexedDB/#inject-key-into-value-values-bypass-setters">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support-promises.js"></script>
+<script>
+
+promise_test(async t => {
+  const db = await createDatabase(t, db => {
+    db.createObjectStore('store', {autoIncrement: true, keyPath: 'id'});
+  });
+
+  let setter_called = false;
+  Object.defineProperty(Object.prototype, 'id', {
+    configurable: true,
+    set: value => { setter_called = true; },
+  });
+  t.add_cleanup(() => { delete Object.prototype['id']; });
+
+  const tx = db.transaction('store', 'readwrite');
+  tx.objectStore('store').put({});
+  const result = await promiseForRequest(t, tx.objectStore('store').get(1));
+
+  assert_false(setter_called,
+               'Setter should not be called for key result.');
+  assert_true(result.hasOwnProperty('id'),
+              'Result should have own-property overriding prototype setter.');
+  assert_equals(result.id, 1,
+                'Own property should match primary key generator value');
+}, 'Returning values to script should bypass prototype setters');
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/playback-rate.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/playback-rate.https.html
index dba27af..5367497 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/playback-rate.https.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/playback-rate.https.html
@@ -87,11 +87,11 @@
 
       animation.play();
 
-      waitForAnimationFrameWithCondition(_=> {
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
       // Make sure the current time is not Zero.
-      await waitForNextFrame();
+      await waitForDocumentTimelineAdvance();
 
       // Set playback rate while the animation is playing.
       const prevCurrentTime = animation.currentTime;
@@ -104,11 +104,11 @@
 
     promise_test(async t => {
       const animation = createWorkletAnimation(t);
-      const playbackRate = 2;
+      const playbackRate = 0.2;
 
       animation.play();
 
-      waitForAnimationFrameWithCondition(_=> {
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
 
@@ -118,7 +118,7 @@
       animation.playbackRate = playbackRate;
 
       // Play the animation some more.
-      await waitForNextFrame();
+      await waitForDocumentTimelineAdvance();
 
       const currentTime = animation.currentTime;
       const currentTimelineTime = document.timeline.currentTime;
@@ -126,7 +126,7 @@
       assert_times_equal(
         currentTime - prevCurrentTime,
         (currentTimelineTime - prevTimelineTime) * playbackRate,
-        'The current time should increase two times faster than timeline.');
+        'The current time should increase 0.2 times faster than timeline.');
     }, 'The playback rate affects the rate of progress of the current time.');
 
     promise_test(async t => {
@@ -135,13 +135,13 @@
 
       // Set playback rate while the animation is in 'idle' state.
       animation.playbackRate = playbackRate;
+      const prevTimelineTime = document.timeline.currentTime;
       animation.play();
-      waitForAnimationFrameWithCondition(_=> {
+
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
-      const prevTimelineTime = document.timeline.currentTime;
-
-      await waitForNextFrame();
+      await waitForDocumentTimelineAdvance();
 
       const currentTime = animation.currentTime;
       const timelineTime = document.timeline.currentTime;
@@ -158,28 +158,48 @@
                       fill: 'none',
                       iterations: 1
                      };
+      // TODO(crbug.com/937382): Currently composited
+      // workletAnimation.currentTime and the corresponding
+      // effect.getComputedTiming().localTime are computed by main and
+      // compositing threads respectively and, as a result, don't match.
+      // To workaround this limitation we compare the output of two identical
+      // animations that only differ in playback rate. The expectation is that
+      // their output matches after taking their playback rates into
+      // consideration. This works since these two animations start at the same
+      // time on the same thread.
+      // Once the issue is fixed, this test needs to change so expected
+      // effect.getComputedTiming().localTime is compared against
+      // workletAnimation.currentTime.
       const target = createDiv(t);
+      const targetRef = createDiv(t);
       const keyframeEffect = new KeyframeEffect(
-        target, { opacity: [0, 1] }, timing);
+        target, { opacity: [1, 0] }, timing);
+      const keyframeEffectRef = new KeyframeEffect(
+        targetRef, { opacity: [1, 0] }, timing);
       const animation = new WorkletAnimation(
         'passthrough', keyframeEffect, document.timeline);
+      const animationRef = new WorkletAnimation(
+        'passthrough', keyframeEffectRef, document.timeline);
       const playbackRate = 2;
-
-      animation.play();
-      waitForAnimationFrameWithCondition(_=> {
-        return animation.playState == "running"
-      });
       animation.playbackRate = playbackRate;
+      animation.play();
+      animationRef.play();
 
-      await waitForNextFrame();
+      // wait until local times are synced back to the main thread.
+      await waitForAnimationFrameWithCondition(_ => {
+        return getComputedStyle(target).opacity != '1';
+      });
 
       assert_times_equal(
-        keyframeEffect.getComputedTiming().localTime, animation.currentTime,
+        keyframeEffect.getComputedTiming().localTime,
+        keyframeEffectRef.getComputedTiming().localTime * playbackRate,
         'When playback rate is set on WorkletAnimation, the underlying ' +
         'effect\'s timing should be properly updated.');
 
-      assert_approx_equals(Number(getComputedStyle(target).opacity),
-        animation.currentTime / 100, 0.001,
+      assert_approx_equals(
+        1 - Number(getComputedStyle(target).opacity),
+        (1 - Number(getComputedStyle(targetRef).opacity)) * playbackRate,
+        0.001,
         'When playback rate is set on WorkletAnimation, the underlying effect' +
         ' should produce correct visual result.');
     }, 'When playback rate is updated, the underlying effect is properly ' +
@@ -195,7 +215,7 @@
 
       animation.playbackRate = 0.5;
       animation.play();
-      waitForAnimationFrameWithCondition(_=> {
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
       assert_equals(animation.currentTime, 0.2 * timeRange * 0.5,
@@ -212,9 +232,7 @@
 
       animation.play();
       animation.playbackRate = 0.5;
-      waitForAnimationFrameWithCondition(_=> {
-        return animation.playState == "running"
-      });
+
       assert_equals(animation.currentTime, 0.2 * timeRange,
         'Initial current time is not affected by playbackRate.');
     }, 'Initial current time is not affected by playbackRate set while '+
@@ -229,7 +247,7 @@
 
       animation.play();
       scroller.scrollTop = 0.2 * maxScroll;
-      waitForAnimationFrameWithCondition(_=> {
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
       // Set playback rate while the animation is playing.
@@ -247,7 +265,7 @@
       const timeRange = animation.timeline.timeRange;
 
       animation.play();
-      waitForAnimationFrameWithCondition(_=> {
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
       scroller.scrollTop = 0.1 * maxScroll;
@@ -273,7 +291,7 @@
       // Set playback rate while the animation is in 'idle' state.
       animation.playbackRate = playbackRate;
       animation.play();
-      waitForAnimationFrameWithCondition(_=> {
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
       scroller.scrollTop = 0.2 * maxScroll;
@@ -296,7 +314,7 @@
                     };
       const target = createDiv(t);
       const keyframeEffect = new KeyframeEffect(
-        target, { opacity: [0, 1] }, timing);
+        target, { opacity: [1, 0] }, timing);
       const animation = new WorkletAnimation(
         'passthrough', keyframeEffect, timeline);
       const playbackRate = 2;
@@ -305,12 +323,15 @@
 
       animation.play();
       animation.playbackRate = playbackRate;
-      waitForAnimationFrameWithCondition(_=> {
+      await waitForAnimationFrameWithCondition(_=> {
         return animation.playState == "running"
       });
 
       scroller.scrollTop = 0.2 * maxScroll;
-      await waitForNextFrame();
+      // wait until local times are synced back to the main thread.
+      await waitForAnimationFrameWithCondition(_ => {
+        return getComputedStyle(target).opacity != '1';
+      });
 
       assert_times_equal(
         keyframeEffect.getComputedTiming().localTime,
@@ -319,7 +340,7 @@
         'effect\'s timing should be properly updated.');
       assert_approx_equals(
         Number(getComputedStyle(target).opacity),
-        0.2 * timeRange * playbackRate / 1000, 0.001,
+        1 - 0.2 * timeRange * playbackRate / 1000, 0.001,
         'When playback rate is set on WorkletAnimation, the underlying ' +
         'effect should produce correct visual result.');
     }, 'When playback rate is updated, the underlying effect is properly ' +
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
index 9841c575..6f98185 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
@@ -61,7 +61,10 @@
     const animation = new WorkletAnimation('passthrough', effect, timeline);
     animation.play();
 
+    // Ensure that the WorkletAnimation will have been started on the compositor.
     waitForAsyncAnimationFrames(1).then(_ => {
+      // Now return the scroller to the world, which will cause it to be composited
+      // and the animation should update on the compositor side.
       scroller.classList.remove('removed');
       const maxScroll = scroller.scrollHeight - scroller.clientHeight;
       scroller.scrollTop = 0.5 * maxScroll;
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-overflow-hidden-ref.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-overflow-hidden-ref.html
index 2004e6df..c6d7314e 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-overflow-hidden-ref.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-overflow-hidden-ref.html
@@ -7,6 +7,7 @@
     background-color: green;
     transform: translate(0, 100px);
     opacity: 0.5;
+    will-change: transform; /* force compositing */
   }
 
   #covered {
@@ -19,6 +20,7 @@
     overflow: hidden;
     height: 100px;
     width: 100px;
+    will-change: transform; /* force compositing */
   }
 
   #contents {
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
index f30c861f..fe92232 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
@@ -7,6 +7,7 @@
     background-color: green;
     transform: translate(0, 100px);
     opacity: 0.5;
+    will-change: transform; /* force compositing */
   }
 
   #covered {
@@ -19,8 +20,7 @@
     overflow: auto;
     height: 100px;
     width: 100px;
-    /* TODO(yigu): Rewrite the test to not rely on compositing. */
-    will-change: transform;
+    will-change: transform; /* force compositing */
   }
 
   #contents {
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html
index 3b527dc..5810e17 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html
@@ -14,6 +14,7 @@
     background-color: green;
     transform: translate(0, 100px);
     opacity: 0.5;
+    will-change: transform; /* force compositing */
   }
 
   #covered {
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-navigator-clipboard-basics.https.html b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-navigator-clipboard-basics.https.html
index 3c1a0af7..2d2ebf6 100644
--- a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-navigator-clipboard-basics.https.html
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-navigator-clipboard-basics.https.html
@@ -11,32 +11,25 @@
   assert_equals(navigator.clipboard, navigator.clipboard);
 }, "navigator.clipboard exists");
 
-/* clipboard.write(text/plain Blob) */
-
 promise_test(async () => {
   const blob = new Blob(["hello"], {type: 'text/plain'});
-  await navigator.clipboard.write(blob);
-}, "navigator.clipboard.write(text/plain Blob) succeeds");
-
-/* clipboard.write(invalid input) */
+  await navigator.clipboard.write([blob]);
+}, "navigator.clipboard.write([text/plain Blob]) succeeds");
 
 promise_test(async t => {
   await promise_rejects(t, new TypeError(),
                          navigator.clipboard.write());
-}, "navigator.clipboard.write() fails (expect Blob)");
+}, "navigator.clipboard.write() fails (expect [Blob])");
 
 promise_test(async t => {
   await promise_rejects(t, new TypeError(),
                          navigator.clipboard.write(null));
-}, "navigator.clipboard.write(null) fails (expect Blob)");
+}, "navigator.clipboard.write(null) fails (expect [Blob])");
 
 promise_test(async t => {
   await promise_rejects(t, new TypeError(),
                          navigator.clipboard.write("Bad string"));
-}, "navigator.clipboard.write(DOMString) fails (expect Blob)");
-
-
-/* clipboard.writeText() */
+}, "navigator.clipboard.write(DOMString) fails (expect [Blob])");
 
 promise_test(async () => {
   await navigator.clipboard.writeText("New clipboard text");
@@ -47,27 +40,21 @@
                          navigator.clipboard.writeText());
 }, "navigator.clipboard.writeText() fails (expect DOMString)");
 
-/* clipboard.write(image/png Blob) */
-
 promise_test(async () => {
   const fetched = await fetch(
         'http://localhost:8001/clipboard-apis/resources/greenbox.png');
   const image = await fetched.blob();
 
-  await navigator.clipboard.write(image);
-}, "navigator.clipboard.write(image/png Blob) succeeds");
-
-/* text/plain or image/png Blob clipboard.read() */
+  await navigator.clipboard.write([image]);
+}, "navigator.clipboard.write([image/png Blob]) succeeds");
 
 promise_test(async () => {
   const result = await navigator.clipboard.read();
-  assert_true(result instanceof Blob);
+  assert_true(result instanceof Array);
+  assert_true(result[0] instanceof Blob);
   assert_equals(typeof result, "object");
 }, "navigator.clipboard.read() succeeds");
 
-
-/* clipboard.readText() */
-
 promise_test(async () => {
   const result = await navigator.clipboard.readText();
   assert_equals(typeof result, "string");
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobs-read-blobs-manual.https.html b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobs-read-blobs-manual.https.html
new file mode 100644
index 0000000..e616b5e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobs-read-blobs-manual.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>
+  Async Clipboard write blobs -> read blobs tests
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+async function loadBlob(fileName) {
+  const fetched = await fetch(fileName);
+  return await fetched.blob();
+}
+
+promise_test(async t => {
+  const blobText = new Blob(["test text"], {type: 'text/plain'});
+  const blobImage = await loadBlob('resources/greenbox.png');
+
+  assert_equals(blobText.type, "text/plain");
+  assert_equals(blobImage.type, "image/png");
+
+  await navigator.clipboard.write([blobText, blobImage]);
+  const output = await navigator.clipboard.read();
+
+  assert_equals(output.length, 2);
+  assert_equals(output[0].type, "text/plain");
+  assert_equals(output[1].type, "image/png");
+}, "Verify write and read clipboard (multiple blobs)");
+</script>
+<p>
+  Note: This is a manual test because it writes/reads to the shared system
+  clipboard and thus cannot be run async with other tests that might interact
+  with the clipboard.
+</p>
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-blobtext-manual.https.html b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-blobtext-manual.https.html
index ea6e936..bc8511e 100644
--- a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-blobtext-manual.https.html
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-blobtext-manual.https.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Async Clipboard write (text/plain Blob) -> read (text/plain Blob) tests</title>
+<title>
+  Async Clipboard write ([text/plain Blob]) -> read ([text/plain Blob]) tests
+</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
@@ -8,8 +10,10 @@
   promise_test(async t => {
     const blobInput = new Blob([textInput], {type: 'text/plain'});
 
-    await navigator.clipboard.write(blobInput);
-    const blobOutput = await navigator.clipboard.read();
+    await navigator.clipboard.write([blobInput]);
+    const blobsOutput = await navigator.clipboard.read();
+    assert_equals(blobsOutput.length, 1);
+    const blobOutput = blobsOutput[0];
     assert_equals(blobOutput.type, "text/plain");
 
     const textOutput = await (new Response(blobOutput)).text();
@@ -17,7 +21,7 @@
   }, "Verify write and read clipboard given text: " + textInput);
 }
 
-readWriteTest("Clipboard write (text/plain Blob) -> read (text/plain Blob) test");
+readWriteTest("Clipboard write ([text/plain Blob]) -> read ([text/plain Blob]) test");
 readWriteTest("non-Latin1 text encoding test データ");
 </script>
 <p>
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-text-manual.https.html b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-text-manual.https.html
index ecb744a..b1b85de6 100644
--- a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-text-manual.https.html
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-blobtext-read-text-manual.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Async Clipboard write (text/plain Blob) -> readText tests</title>
+<title>Async Clipboard write ([text/plain Blob]) -> readText tests</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
@@ -8,14 +8,14 @@
   promise_test(async t => {
     const blobInput = new Blob([textInput], {type: 'text/plain'});
 
-    await navigator.clipboard.write(blobInput);
+    await navigator.clipboard.write([blobInput]);
     const textOutput = await navigator.clipboard.readText();
 
     assert_equals(textOutput, textInput);
   }, "Verify write and read clipboard given text: " + textInput);
 }
 
-readWriteTest("Clipboard write (text/plain Blob) -> read text test");
+readWriteTest("Clipboard write ([text/plain Blob]) -> read text test");
 readWriteTest("non-Latin1 text encoding test データ");
 </script>
 <p>
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-duplicate-mime-type-manual.https.html b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-duplicate-mime-type-manual.https.html
new file mode 100644
index 0000000..8e249fc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-duplicate-mime-type-manual.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>
+  Async Clipboard write duplicate mime type test
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+promise_test(async t => {
+  const blobText = new Blob(["test text"], {type: 'text/plain'});
+  const blobText2 = new Blob(["test text"], {type: 'text/plain'});
+
+  assert_equals(blobText.type, "text/plain");
+  assert_equals(blobText2.type, "text/plain");
+
+
+  await promise_rejects(t, 'NotAllowedError',
+      navigator.clipboard.write([blobText, blobText2]));
+}, "Verify write and read clipboard (multiple blobs)");
+</script>
+<p>
+  Note: This is a manual test because it writes/reads to the shared system
+  clipboard and thus cannot be run async with other tests that might interact
+  with the clipboard.
+</p>
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-image-read-image-manual.https.html b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-image-read-image-manual.https.html
index a8e2956..76d3d872 100644
--- a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-image-read-image-manual.https.html
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-image-read-image-manual.https.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>
-  Async Clipboard write image/png Blob -> read image/png Blob tests
+  Async Clipboard write [image/png Blob] -> read [image/png Blob] tests
 </title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
@@ -37,21 +37,23 @@
 }
 
 promise_test(async t => {
-  const input = await loadBlob('resources/greenbox.png');
+  const blobInput = await loadBlob('resources/greenbox.png');
 
-  assert_equals(input.type, "image/png");
-  await navigator.clipboard.write(input);
-  const output = await navigator.clipboard.read();
-  assert_equals(output.type, "image/png");
+  assert_equals(blobInput.type, "image/png");
+  await navigator.clipboard.write([blobInput]);
+  const blobsOutput = await navigator.clipboard.read();
+  assert_equals(blobsOutput.length, 1);
+  const blobOutput = blobsOutput[0];
+  assert_equals(blobOutput.type, "image/png");
 
   document.getElementById('image-on-clipboard').src =
-      window.URL.createObjectURL(output);
+      window.URL.createObjectURL(blobOutput);
 
-  const comparableInput = await getBitmapString(input);
-  const comparableOutput = await getBitmapString(output);
+  const comparableInput = await getBitmapString(blobInput);
+  const comparableOutput = await getBitmapString(blobOutput);
 
   assert_equals(comparableOutput, comparableInput);
-}, "Verify write and read clipboard (DOMString)");
+}, "Verify write and read clipboard ([image/png Blob])");
 </script>
 <p>
   Note: This is a manual test because it writes/reads to the shared system
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-text-read-blobtext-manual.https.html b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-text-read-blobtext-manual.https.html
index 7e682f1d..b54fa60 100644
--- a/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-text-read-blobtext-manual.https.html
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/async-write-text-read-blobtext-manual.https.html
@@ -1,13 +1,15 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Async Clipboard writeText -> read (text/plain Blob) tests</title>
+<title>Async Clipboard writeText -> read ([text/plain Blob]) tests</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
 async function readWriteTest(textInput) {
   promise_test(async t => {
     await navigator.clipboard.writeText(textInput);
-    const blobOutput = await navigator.clipboard.read();
+    const blobsOutput = await navigator.clipboard.read();
+    assert_equals(blobsOutput.length, 1);
+    const blobOutput = blobsOutput[0];
     assert_equals(blobOutput.type, "text/plain");
 
     const textOutput = await (new Response(blobOutput)).text();
@@ -15,7 +17,7 @@
   }, "Verify write and read clipboard given text: " + textInput);
 }
 
-readWriteTest("Clipboard write text -> read (text/plain Blob) test");
+readWriteTest("Clipboard write text -> read ([text/plain Blob]) test");
 readWriteTest("non-Latin1 text encoding test データ");
 </script>
 <p>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1-ref.html
index a4419ea..9a68590b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1-ref.html
@@ -69,10 +69,6 @@
   <input type="range" class="i" style="max-width:50%">
 </div></div>
 
-<div class="grid" style="grid-template-columns:4px">
-  <input type="range" class="i" style="width:2px;">
-</div>
-
 <div class="grid" style="grid-template-columns:30px">
   <input type="range" class="i" style="width:15px">
 </div>
@@ -89,10 +85,6 @@
   <input type="range" class="i n" style="width:14px; margin-right:0">
 </div></div>
 
-<div class="grid" style="grid-template-columns:4px">
-  <input type="range" class="i n" style="width:2px;">
-</div>
-
 <div class="grid" style="grid-template-columns:30px">
   <input type="range" class="i n" style="width:15px">
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1.html
index 87399578..018129aae 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-1.html
@@ -77,10 +77,6 @@
   <input type="range" class="i">
 </div></div>
 
-<div class="grid">
-  <input type="range" class="i">
-</div>
-
 <div class="grid" style="grid-template-columns:minmax(min-content,30px)">
   <input type="range" class="i">
 </div>
@@ -97,10 +93,6 @@
   <input type="range" class="i n">
 </div></div>
 
-<div class="grid">
-  <input type="range" class="i n">
-</div>
-
 <div class="grid" style="grid-template-columns:minmax(min-content,30px)">
   <input type="range" class="i n">
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-2a-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-2a-ref.html
new file mode 100644
index 0000000..815b0a1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-2a-ref.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: INPUT type=range percent intrinsic block-size</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1513959">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace;
+}
+
+input { margin: 2px; }
+
+input.b {
+  min-height: 0;
+  background: lime;
+}
+
+input.mb {
+  min-height: 0;
+  max-height: 100%;
+  background: lime;
+}
+
+div {
+  display: inline-block;
+  border:1px solid;
+}
+
+.grid {
+  display: inline-grid;
+  grid: auto / min-content;
+  place-items: start;
+}
+input[orient="vertical"] {
+  -webkit-appearance: slider-vertical;
+  -webkit-appearance: range;
+}
+
+</style></head><body>
+
+<div style="height:30px"><div>
+  <input type="range" class="b" orient="vertical">
+</div></div>
+
+<div class="grid" style="grid: min-content / auto">
+  <input type="range" class="b" orient="vertical" style="height:50%; grid-area:1/1">
+  <input type="range" class="b" orient="vertical" style="visibility:hidden; grid-area:1/1">
+</div>
+
+<div class="grid" style="grid: min-content / auto">
+  <input type="range" class="b" orient="vertical" style="height:50%; grid-area:1/1">
+  <input type="range" class="b" orient="vertical" style="visibility:hidden; grid-area:1/1">
+</div>
+
+<div class="grid" style="grid: 30px / auto">
+  <input type="range" class="b" orient="vertical" style="height:15px">
+</div>
+
+<div class="grid" style="grid: 30px / auto">
+  <input type="range" class="b" orient="vertical" style="height:15px">
+</div>
+
+<br>
+<br>
+
+<div style="height:30px"><div>
+  <input type="range" class="mb" orient="vertical">
+</div></div>
+
+<div class="grid" style="grid: 30px / auto">
+  <input type="range" class="b" orient="vertical" style="height:15px">
+</div>
+
+<div class="grid" style="grid: 30px / auto">
+  <input type="range" class="b" orient="vertical" style="height:15px">
+</div>
+
+<div class="grid" style="grid: 30px / auto">
+  <input type="range" class="b" orient="vertical" style="height:15px">
+</div>
+
+</body></html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-2a.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-2a.html
new file mode 100644
index 0000000..f2c2431
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/range-percent-intrinsic-size-2a.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Test: INPUT type=range percent intrinsic block-size</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1513959">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-3/#percentage-sizing">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-3/#min-content-zero">
+  <link rel="match" href="range-percent-intrinsic-size-2a-ref.html">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace;
+}
+
+input { margin: 2px; }
+
+input.b {
+  height: 50%;
+  min-height: -moz-min-content;
+  min-height: min-content;
+  background: lime;
+}
+
+input.mb {
+  max-height: 50%;
+  min-height: -moz-min-content;
+  min-height: min-content;
+  background: lime;
+}
+
+input.b.min-auto, input.mb.min-auto, {
+  min-height: auto;
+}
+
+div {
+  display: inline-block;
+  border:1px solid;
+}
+
+.grid {
+  display: inline-grid;
+  grid: auto / min-content;
+  place-items: start;
+}
+input[orient="vertical"] {
+  -webkit-appearance: slider-vertical;
+  -webkit-appearance: range;
+}
+
+</style></head><body>
+
+<div style="height:30px"><div>
+  <input type="range" class="b" orient="vertical">
+</div></div>
+
+<div class="grid" style="grid: min-content / auto">
+  <input type="range" class="b" orient="vertical">
+</div>
+
+<div class="grid" style="grid: minmax(min-content,30px) / auto">
+  <input type="range" class="b" orient="vertical">
+</div>
+
+<div class="grid" style="grid: minmax(auto,30px) / auto">
+  <input type="range" class="b" orient="vertical">
+</div>
+
+<div class="grid" style="grid: minmax(auto,30px) / auto">
+  <input type="range" class="b min-auto" orient="vertical">
+</div>
+
+<br>
+<br>
+
+<div style="height:30px"><div>
+  <input type="range" class="mb" orient="vertical">
+</div></div>
+
+<div class="grid" style="grid: minmax(min-content,30px) / auto">
+  <input type="range" class="mb" orient="vertical">
+</div>
+
+<div class="grid" style="grid: minmax(auto,30px) / auto">
+  <input type="range" class="mb" orient="vertical">
+</div>
+
+<div class="grid" style="grid: minmax(auto,30px) / auto">
+  <input type="range" class="mb min-auto" orient="vertical">
+</div>
+
+</body></html>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/closed-attribute.window.js b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/closed-attribute.window.js
new file mode 100644
index 0000000..88a3beb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/closed-attribute.window.js
@@ -0,0 +1,69 @@
+// META: script=/common/get-host-info.sub.js
+
+function closedTest(newWindow, closeNewWindowsBrowsingContext) {
+  assert_equals(newWindow.closed, false);
+  closeNewWindowsBrowsingContext();
+  assert_equals(newWindow.closed, true);
+}
+
+test(() => {
+  const frame = document.body.appendChild(document.createElement("iframe"));
+  closedTest(frame.contentWindow, () => frame.remove());
+}, "closed and same-origin nested browsing context");
+
+test(() => {
+  const openee = window.open();
+  closedTest(openee, () => openee.close());
+
+  // close() is a no-op once "is closing" is set
+  openee.close();
+  assert_equals(openee.closed, true);
+}, "closed/close() and same-origin auxiliary browsing context");
+
+const support = new URL("support/closed.html", location.href).pathname;
+[
+  {
+    type: "cross-origin",
+    url: `${get_host_info().HTTP_REMOTE_ORIGIN}${support}`
+  },
+  {
+    type: "cross-site",
+    url: `${get_host_info().HTTP_NOTSAMESITE_ORIGIN}${support}`
+  }
+].forEach(val => {
+  async_test(t => {
+    const frame = document.createElement("iframe"),
+          ident = `${val.type}-nested-bc`;
+    frame.src = `${val.url}?window=parent&ident=${ident}`;
+    const listener = t.step_func(e => {
+      if (e.data === ident) {
+        closedTest(frame.contentWindow, () => frame.remove());
+        self.removeEventListener("message", listener);
+        t.done();
+      }
+    });
+    // Use a message event rather than onload for consistency with auxiliary browsing contexts.
+    self.addEventListener("message", listener);
+    document.body.append(frame);
+  }, `closed and ${val.type} nested browsing context`);
+
+  async_test(t => {
+    const ident = `${val.type}-auxiliary-bc`,
+          support = new URL("support/closed.html", location.href).pathname,
+          openee = window.open(`${val.url}?window=opener&ident=${ident}`),
+          listener = t.step_func(e => {
+      if (e.data === ident) {
+        closedTest(openee, () => openee.close());
+
+        // close() is a no-op once "is closing" is set
+        openee.close();
+        assert_equals(openee.closed, true);
+
+        self.removeEventListener("message", listener);
+        t.done();
+      }
+    });
+    // As there's no cross-origin onload, use a message event.
+    self.addEventListener("message", listener);
+  }, `closed/close() and ${val.type} auxiliary browsing context`);
+});
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/support/closed.html b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/support/closed.html
new file mode 100644
index 0000000..3b70598e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/support/closed.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<!--
+  There's two URL parameters supported here:
+
+  window: The property name of a getter on the global object that returns the relevant WindowProxy
+          object to message.
+  ident:  A reasonably unique identifier that will be echoed as the message.
+-->
+<script>
+  const params = new URLSearchParams(location.search);
+  self[params.get("window")].postMessage(params.get("ident"), "*");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
index b2fe3032..1d304732 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
@@ -1,4 +1,5 @@
 This is a testharness.js-based test.
-FAIL opener and "removed" embedded documents openerGet is not a function
+FAIL opener of discarded nested browsing context openerGet is not a function
+FAIL opener of discarded auxiliary browsing context openerGet is not a function
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/reftest/reftest_fuzzy.html.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/reftest/reftest_fuzzy.html.ini
new file mode 100644
index 0000000..1ab2d77
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/reftest/reftest_fuzzy.html.ini
@@ -0,0 +1,2 @@
+[reftest_fuzzy.html]
+  fuzzy: fuzzy-ref-1.html:maxDifference=255;100-100
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/reftest/fuzzy-ref-1.html b/third_party/blink/web_tests/external/wpt/infrastructure/reftest/fuzzy-ref-1.html
new file mode 100644
index 0000000..e50fc11
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/reftest/fuzzy-ref-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  background-color: green;
+}
+</style>
+<div></div>
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/reftest/reftest_fuzzy.html b/third_party/blink/web_tests/external/wpt/infrastructure/reftest/reftest_fuzzy.html
new file mode 100644
index 0000000..7429025
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/reftest/reftest_fuzzy.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel=match href=fuzzy-ref-1.html>
+<!-- This meta is overridden in the corresponding ini file -->
+<meta name=fuzzy content="fuzzy-ref-1.html:128;100">
+<style>
+div {
+  width: 99px;
+  height: 100px;
+  background-color: green;
+}
+</style>
+<div></div>
+
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/reftest/reftest_fuzzy_1.html b/third_party/blink/web_tests/external/wpt/infrastructure/reftest/reftest_fuzzy_1.html
new file mode 100644
index 0000000..1930fe0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/reftest/reftest_fuzzy_1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel=match href=fuzzy-ref-1.html>
+<meta name=fuzzy content="fuzzy-ref-1.html:255;100">
+<style>
+div {
+  width: 99px;
+  height: 100px;
+  background-color: green;
+}
+</style>
+<div></div>
+
diff --git a/third_party/blink/web_tests/external/wpt/shape-detection/idlharness.any-expected.txt b/third_party/blink/web_tests/external/wpt/shape-detection/idlharness.any-expected.txt
index 4f812297..a2c26dc 100644
--- a/third_party/blink/web_tests/external/wpt/shape-detection/idlharness.any-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/shape-detection/idlharness.any-expected.txt
@@ -23,7 +23,7 @@
 PASS BarcodeDetector interface: existence and properties of interface prototype object
 PASS BarcodeDetector interface: existence and properties of interface prototype object's "constructor" property
 PASS BarcodeDetector interface: existence and properties of interface prototype object's @@unscopables property
-FAIL BarcodeDetector interface: operation getSupportedFormats() assert_own_property: interface object missing static operation expected property "getSupportedFormats" missing
+PASS BarcodeDetector interface: operation getSupportedFormats()
 PASS BarcodeDetector interface: operation detect(ImageBitmapSource)
 FAIL DetectedBarcode interface: existence and properties of interface object assert_throws: interface object didn't throw TypeError when called as a constructor function "function() {
                 new interface_object();
diff --git a/third_party/blink/web_tests/external/wpt/shape-detection/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/shape-detection/idlharness.any.worker-expected.txt
deleted file mode 100644
index 6d71cf9..0000000
--- a/third_party/blink/web_tests/external/wpt/shape-detection/idlharness.any.worker-expected.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-This is a testharness.js-based test.
-PASS Test shape-detection IDL interface
-PASS FaceDetector interface: existence and properties of interface object
-PASS FaceDetector interface object length
-PASS FaceDetector interface object name
-PASS FaceDetector interface: existence and properties of interface prototype object
-PASS FaceDetector interface: existence and properties of interface prototype object's "constructor" property
-PASS FaceDetector interface: existence and properties of interface prototype object's @@unscopables property
-PASS FaceDetector interface: operation detect(ImageBitmapSource)
-PASS DetectedFace interface: existence and properties of interface object
-PASS BarcodeDetector interface: existence and properties of interface object
-PASS BarcodeDetector interface object length
-PASS BarcodeDetector interface object name
-PASS BarcodeDetector interface: existence and properties of interface prototype object
-PASS BarcodeDetector interface: existence and properties of interface prototype object's "constructor" property
-PASS BarcodeDetector interface: existence and properties of interface prototype object's @@unscopables property
-FAIL BarcodeDetector interface: operation getSupportedFormats() assert_own_property: interface object missing static operation expected property "getSupportedFormats" missing
-PASS BarcodeDetector interface: operation detect(ImageBitmapSource)
-PASS DetectedBarcode interface: existence and properties of interface object
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/tools/manifest/item.py b/third_party/blink/web_tests/external/wpt/tools/manifest/item.py
index c06daee3..c6363a70 100644
--- a/third_party/blink/web_tests/external/wpt/tools/manifest/item.py
+++ b/third_party/blink/web_tests/external/wpt/tools/manifest/item.py
@@ -1,5 +1,5 @@
 from copy import copy
-
+from six import iteritems
 from six.moves.urllib.parse import urljoin, urlparse
 from abc import ABCMeta, abstractproperty
 
@@ -169,6 +169,14 @@
     def dpi(self):
         return self._extras.get("dpi")
 
+    @property
+    def fuzzy(self):
+        rv = self._extras.get("fuzzy", [])
+        if isinstance(rv, list):
+            return {tuple(item[0]): item[1]
+                    for item in self._extras.get("fuzzy", [])}
+        return rv
+
     def meta_key(self):
         return (self.timeout, self.viewport_size, self.dpi)
 
@@ -181,6 +189,8 @@
             extras["viewport_size"] = self.viewport_size
         if self.dpi is not None:
             extras["dpi"] = self.dpi
+        if self.fuzzy:
+            extras["fuzzy"] = list(iteritems(self.fuzzy))
         return rv
 
     @classmethod
diff --git a/third_party/blink/web_tests/external/wpt/tools/manifest/sourcefile.py b/third_party/blink/web_tests/external/wpt/tools/manifest/sourcefile.py
index b5d7cdf8..78843b0 100644
--- a/third_party/blink/web_tests/external/wpt/tools/manifest/sourcefile.py
+++ b/third_party/blink/web_tests/external/wpt/tools/manifest/sourcefile.py
@@ -1,6 +1,7 @@
 import hashlib
 import re
 import os
+from collections import deque
 from six import binary_type
 from six.moves.urllib.parse import urljoin
 from fnmatch import fnmatch
@@ -453,6 +454,79 @@
         return self.dpi_nodes[0].attrib.get("content", None)
 
     @cached_property
+    def fuzzy_nodes(self):
+        """List of ElementTree Elements corresponding to nodes in a test that
+        specify reftest fuzziness"""
+        return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='fuzzy']")
+
+    @cached_property
+    def fuzzy(self):
+        rv = {}
+        if self.root is None:
+            return rv
+
+        if not self.fuzzy_nodes:
+            return rv
+
+        args = ["maxDifference", "totalPixels"]
+
+        for node in self.fuzzy_nodes:
+            item = node.attrib.get("content", "")
+
+            parts = item.rsplit(":", 1)
+            if len(parts) == 1:
+                key = None
+                value = parts[0]
+            else:
+                key = urljoin(self.url, parts[0])
+                reftype = None
+                for ref in self.references:
+                    if ref[0] == key:
+                        reftype = ref[1]
+                        break
+                if reftype not in ("==", "!="):
+                    raise ValueError("Fuzzy key %s doesn't correspond to a references" % key)
+                key = (self.url, key, reftype)
+                value = parts[1]
+            ranges = value.split(";")
+            if len(ranges) != 2:
+                raise ValueError("Malformed fuzzy value %s" % item)
+            arg_values = {None: deque()}
+            for range_str_value in ranges:
+                if "=" in range_str_value:
+                    name, range_str_value = [part.strip()
+                                             for part in range_str_value.split("=", 1)]
+                    if name not in args:
+                        raise ValueError("%s is not a valid fuzzy property" % name)
+                    if arg_values.get(name):
+                        raise ValueError("Got multiple values for argument %s" % name)
+                else:
+                    name = None
+                if "-" in range_str_value:
+                    range_min, range_max = range_str_value.split("-")
+                else:
+                    range_min = range_str_value
+                    range_max = range_str_value
+                try:
+                    range_value = [int(x.strip()) for x in (range_min, range_max)]
+                except ValueError:
+                    raise ValueError("Fuzzy value %s must be a range of integers" %
+                                     range_str_value)
+                if name is None:
+                    arg_values[None].append(range_value)
+                else:
+                    arg_values[name] = range_value
+            rv[key] = []
+            for arg_name in args:
+                if arg_values.get(arg_name):
+                    value = arg_values.pop(arg_name)
+                else:
+                    value = arg_values[None].popleft()
+                rv[key].append(value)
+            assert list(arg_values.keys()) == [None] and len(arg_values[None]) == 0
+        return rv
+
+    @cached_property
     def testharness_nodes(self):
         """List of ElementTree Elements corresponding to nodes representing a
         testharness.js script"""
@@ -749,7 +823,8 @@
                     references=self.references,
                     timeout=self.timeout,
                     viewport_size=self.viewport_size,
-                    dpi=self.dpi
+                    dpi=self.dpi,
+                    fuzzy=self.fuzzy
                 )]
 
         elif self.content_is_css_visual and not self.name_is_reference:
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt b/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt
index 24a7d3d..37f4fde 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt
@@ -2,4 +2,6 @@
 mozinfo == 0.10
 mozlog==4.0
 mozdebug==0.1.1
+pillow == 5.2.0
 urllib3[secure]==1.24.1
+
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/environment.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/environment.py
index 493d3c4..6563721 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/environment.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/environment.py
@@ -202,11 +202,13 @@
 
     def ensure_started(self):
         # Pause for a while to ensure that the server has a chance to start
-        for _ in xrange(60):
+        total_sleep_secs = 30
+        each_sleep_secs = 0.01
+        for _ in xrange(int(total_sleep_secs / each_sleep_secs)):
             failed = self.test_servers()
             if not failed:
                 return
-            time.sleep(0.5)
+            time.sleep(each_sleep_secs)
         raise EnvironmentError("Servers failed to start: %s" %
                                ", ".join("%s:%s" % item for item in failed))
 
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/base.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/base.py
index 8958ecf..5fa3056 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/base.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/base.py
@@ -1,6 +1,7 @@
 import base64
 import hashlib
 import httplib
+import io
 import os
 import threading
 import traceback
@@ -8,6 +9,8 @@
 import urlparse
 from abc import ABCMeta, abstractmethod
 
+from PIL import Image, ImageChops, ImageStat
+
 from ..testrunner import Stop
 from protocol import Protocol, BaseProtocolPart
 
@@ -286,8 +289,7 @@
 
             screenshot = data
             hash_value = hash_screenshot(data)
-
-            self.screenshot_cache[key] = (hash_value, None)
+            self.screenshot_cache[key] = (hash_value, screenshot)
 
             rv = (hash_value, screenshot)
         else:
@@ -299,11 +301,32 @@
     def reset(self):
         self.screenshot_cache.clear()
 
-    def is_pass(self, lhs_hash, rhs_hash, relation):
+    def is_pass(self, hashes, screenshots, relation, fuzzy):
         assert relation in ("==", "!=")
-        self.message.append("Testing %s %s %s" % (lhs_hash, relation, rhs_hash))
-        return ((relation == "==" and lhs_hash == rhs_hash) or
-                (relation == "!=" and lhs_hash != rhs_hash))
+        if not fuzzy or fuzzy == ((0,0), (0,0)):
+            equal = hashes[0] == hashes[1]
+        else:
+            max_per_channel, pixels_different = self.get_differences(screenshots)
+            allowed_per_channel, allowed_different = fuzzy
+            self.logger.info("Allowed %s pixels different, maximum difference per channel %s" %
+                             ("-".join(str(item) for item in allowed_different),
+                              "-".join(str(item) for item in allowed_per_channel)))
+            equal = (allowed_per_channel[0] <= max_per_channel <= allowed_per_channel[1] and
+                     allowed_different[0] <= pixels_different <= allowed_different[1])
+        return equal if relation == "==" else not equal
+
+    def get_differences(self, screenshots):
+        lhs = Image.open(io.BytesIO(base64.b64decode(screenshots[0]))).convert("RGB")
+        rhs = Image.open(io.BytesIO(base64.b64decode(screenshots[1]))).convert("RGB")
+        diff = ImageChops.difference(lhs, rhs)
+        minimal_diff = diff.crop(diff.getbbox())
+        mask = minimal_diff.convert("L", dither=None)
+        stat = ImageStat.Stat(minimal_diff, mask)
+        per_channel = max(item[1] for item in stat.extrema)
+        count = stat.count[0]
+        self.logger.info("Found %s pixels different, maximum difference per channel %s" %
+                         (count, per_channel))
+        return per_channel, count
 
     def run_test(self, test):
         viewport_size = test.viewport_size
@@ -319,6 +342,7 @@
             screenshots = [None, None]
 
             nodes, relation = stack.pop()
+            fuzzy = self.get_fuzzy(test, nodes, relation)
 
             for i, node in enumerate(nodes):
                 success, data = self.get_hash(node, viewport_size, dpi)
@@ -327,7 +351,8 @@
 
                 hashes[i], screenshots[i] = data
 
-            if self.is_pass(hashes[0], hashes[1], relation):
+            if self.is_pass(hashes, screenshots, relation, fuzzy):
+                fuzzy = self.get_fuzzy(test, nodes, relation)
                 if nodes[1].references:
                     stack.extend(list(((nodes[1], item[0]), item[1]) for item in reversed(nodes[1].references)))
                 else:
@@ -352,6 +377,25 @@
                 "message": "\n".join(self.message),
                 "extra": {"reftest_screenshots": log_data}}
 
+    def get_fuzzy(self, root_test, test_nodes, relation):
+        full_key = tuple([item.url for item in test_nodes] + [relation])
+        ref_only_key = test_nodes[1].url
+
+        fuzzy_override = root_test.fuzzy_override
+        fuzzy = test_nodes[0].fuzzy
+
+        sources = [fuzzy_override, fuzzy]
+        keys = [full_key, ref_only_key, None]
+        value = None
+        for source in sources:
+            for key in keys:
+                if key in source:
+                    value = source[key]
+                    break
+            if value:
+                break
+        return value
+
     def retake_screenshot(self, node, viewport_size, dpi):
         success, data = self.executor.screenshot(node, viewport_size, dpi)
         if not success:
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
index b70f0ed0..f9fd97b 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -846,7 +846,7 @@
         return screenshot
 
 
-class InternalRefTestImplementation(object):
+class InternalRefTestImplementation(RefTestImplementation):
     def __init__(self, executor):
         self.timeout_multiplier = executor.timeout_multiplier
         self.executor = executor
@@ -870,7 +870,7 @@
         pass
 
     def run_test(self, test):
-        references = self.get_references(test)
+        references = self.get_references(test, test)
         timeout = (test.timeout * 1000) * self.timeout_multiplier
         rv = self.executor.protocol.marionette._send_message("reftest:run",
                                                              {"test": self.executor.test_url(test),
@@ -881,10 +881,11 @@
                                                               "height": 600})["value"]
         return rv
 
-    def get_references(self, node):
+    def get_references(self, root_test, node):
         rv = []
         for item, relation in node.references:
-            rv.append([self.executor.test_url(item), self.get_references(item), relation])
+            rv.append([self.executor.test_url(item), self.get_references(root_test, item), relation,
+                       {"fuzzy": self.get_fuzzy(root_test, [node, item], relation)}])
         return rv
 
     def teardown(self):
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
index 73f5fcf8..563252c 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
@@ -416,7 +416,11 @@
             """return [window.outerWidth - window.innerWidth,
                        window.outerHeight - window.innerHeight];"""
         )
-        self.protocol.webdriver.window.position = (0, 0)
+        try:
+            self.protocol.webdriver.window.position = (0, 0)
+        except client.InvalidArgumentException:
+            # Safari 12 throws with 0 or 1, treating them as bools; fixed in STP
+            self.protocol.webdriver.window.position = (2, 2)
         self.protocol.webdriver.window.size = (800 + width_offset, 600 + height_offset)
 
         result = self.implementation.run_test(test)
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/manifestexpected.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/manifestexpected.py
index 80284bd8..fb3ef62 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/manifestexpected.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/manifestexpected.py
@@ -1,5 +1,6 @@
 import os
 import urlparse
+from collections import deque
 
 from wptmanifest.backends import static
 from wptmanifest.backends.static import ManifestItem
@@ -97,6 +98,105 @@
     return rv
 
 
+def fuzzy_prop(node):
+    """Fuzzy reftest match
+
+    This can either be a list of strings or a single string. When a list is
+    supplied, the format of each item matches the description below.
+
+    The general format is
+    fuzzy = [key ":"] <prop> ";" <prop>
+    key = <test name> [reftype <reference name>]
+    reftype = "==" | "!="
+    prop = [propName "=" ] range
+    propName = "maxDifferences" | "totalPixels"
+    range = <digits> ["-" <digits>]
+
+    So for example:
+      maxDifferences=10;totalPixels=10-20
+
+      specifies that for any test/ref pair for which no other rule is supplied,
+      there must be a maximum pixel difference of exactly 10, and betwen 10 and
+      20 total pixels different.
+
+      test.html==ref.htm:10;20
+
+      specifies that for a equality comparison between test.html and ref.htm,
+      resolved relative to the test path, there can be a maximum difference
+      of 10 in the pixel value for any channel and 20 pixels total difference.
+
+      ref.html:10;20
+
+      is just like the above but applies to any comparison involving ref.html
+      on the right hand side.
+
+    The return format is [(key, (maxDifferenceRange, totalPixelsRange))], where
+    the key is either None where no specific reference is specified, the reference
+    name where there is only one component or a tuple (test, ref, reftype) when the
+    exact comparison is specified. maxDifferenceRange and totalPixelsRange are tuples
+    of integers indicating the inclusive range of allowed values.
+"""
+    rv = []
+    args = ["maxDifference", "totalPixels"]
+    try:
+        value = node.get("fuzzy")
+    except KeyError:
+        return rv
+    if not isinstance(value, list):
+        value = [value]
+    for item in value:
+        if not isinstance(item, (str, unicode)):
+            rv.append(item)
+            continue
+        parts = item.rsplit(":", 1)
+        if len(parts) == 1:
+            key = None
+            fuzzy_values = parts[0]
+        else:
+            key, fuzzy_values = parts
+            for reftype in ["==", "!="]:
+                if reftype in key:
+                    key = key.split(reftype)
+                    key.append(reftype)
+                    key = tuple(key)
+        ranges = fuzzy_values.split(";")
+        if len(ranges) != 2:
+            raise ValueError("Malformed fuzzy value %s" % item)
+        arg_values = {None: deque()}
+        for range_str_value in ranges:
+            if "=" in range_str_value:
+                name, range_str_value = [part.strip()
+                                         for part in range_str_value.split("=", 1)]
+                if name not in args:
+                    raise ValueError("%s is not a valid fuzzy property" % name)
+                if arg_values.get(name):
+                    raise ValueError("Got multiple values for argument %s" % name)
+            else:
+                name = None
+            if "-" in range_str_value:
+                range_min, range_max = range_str_value.split("-")
+            else:
+                range_min = range_str_value
+                range_max = range_str_value
+            try:
+                range_value = tuple(int(item.strip()) for item in (range_min, range_max))
+            except ValueError:
+                raise ValueError("Fuzzy value %s must be a range of integers" % range_str_value)
+            if name is None:
+                arg_values[None].append(range_value)
+            else:
+                arg_values[name] = range_value
+        range_values = []
+        for arg_name in args:
+            if arg_values.get(arg_name):
+                value = arg_values.pop(arg_name)
+            else:
+                value = arg_values[None].popleft()
+            range_values.append(value)
+        rv.append((key, tuple(range_values)))
+    return rv
+
+
 class ExpectedManifest(ManifestItem):
     def __init__(self, name, test_path, url_base):
         """Object representing all the tests in a particular manifest
@@ -183,6 +283,10 @@
     def lsan_max_stack_depth(self):
         return int_prop("lsan-max-stack-depth", self)
 
+    @property
+    def fuzzy(self):
+        return fuzzy_prop(self)
+
 
 class DirectoryManifest(ManifestItem):
     @property
@@ -229,6 +333,11 @@
     def lsan_max_stack_depth(self):
         return int_prop("lsan-max-stack-depth", self)
 
+    @property
+    def fuzzy(self):
+        return fuzzy_prop(self)
+
+
 class TestNode(ManifestItem):
     def __init__(self, name):
         """Tree node associated with a particular test in a manifest
@@ -301,6 +410,10 @@
     def lsan_max_stack_depth(self):
         return int_prop("lsan-max-stack-depth", self)
 
+    @property
+    def fuzzy(self):
+        return fuzzy_prop(self)
+
     def append(self, node):
         """Add a subtest to the current test
 
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py
index 4eb292ef..7ad3575 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py
@@ -341,6 +341,8 @@
             yield item
 
     def remove_value(self, key, value):
+        if key not in self._data:
+            return
         try:
             self._data[key].remove(value)
         except ValueError:
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py
index dc1c6b63..6a4fa4f 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wpttest.py
@@ -1,5 +1,6 @@
 import os
 import subprocess
+import urlparse
 from collections import defaultdict
 
 from wptmanifest.parser import atoms
@@ -279,12 +280,12 @@
     @property
     def prefs(self):
         prefs = {}
-        for meta in self.itermeta():
+        for meta in reversed(list(self.itermeta())):
             meta_prefs = meta.prefs
-            prefs.update(meta_prefs)
             if atom_reset in meta_prefs:
-                del prefs[atom_reset]
-                break
+                del meta_prefs[atom_reset]
+                prefs = {}
+            prefs.update(meta_prefs)
         return prefs
 
     def expected(self, subtest=None):
@@ -359,7 +360,7 @@
     test_type = "reftest"
 
     def __init__(self, tests_root, url, inherit_metadata, test_metadata, references,
-                 timeout=None, path=None, viewport_size=None, dpi=None, protocol="http"):
+                 timeout=None, path=None, viewport_size=None, dpi=None, fuzzy=None, protocol="http"):
         Test.__init__(self, tests_root, url, inherit_metadata, test_metadata, timeout,
                       path, protocol)
 
@@ -370,6 +371,7 @@
         self.references = references
         self.viewport_size = viewport_size
         self.dpi = dpi
+        self._fuzzy = fuzzy or {}
 
     @classmethod
     def from_manifest(cls,
@@ -398,7 +400,8 @@
                    path=manifest_test.path,
                    viewport_size=manifest_test.viewport_size,
                    dpi=manifest_test.dpi,
-                   protocol="https" if hasattr(manifest_test, "https") and manifest_test.https else "http")
+                   protocol="https" if hasattr(manifest_test, "https") and manifest_test.https else "http",
+                   fuzzy=manifest_test.fuzzy)
 
         nodes[url] = node
 
@@ -454,6 +457,30 @@
     def keys(self):
         return ("reftype", "refurl")
 
+    @property
+    def fuzzy(self):
+        return self._fuzzy
+
+    @property
+    def fuzzy_override(self):
+        values = {}
+        for meta in reversed(list(self.itermeta(None))):
+            value = meta.fuzzy
+            if not value:
+                continue
+            if atom_reset in value:
+                value.remove(atom_reset)
+                values = {}
+            for key, data in value:
+                if len(key) == 3:
+                    key[0] = urlparse.urljoin(self.url, key[0])
+                    key[1] = urlparse.urljoin(self.url, key[1])
+                else:
+                    # Key is just a relative url to a ref
+                    key = urlparse.urljoin(self.url, key)
+                values[key] = data
+        return values
+
 
 class WdspecTest(Test):
 
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/images/rendering-broken-block-flow-images-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/images/rendering-broken-block-flow-images-expected.png
index 1ce9e7d..3cb7975a 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/images/rendering-broken-block-flow-images-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/images/rendering-broken-block-flow-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/clipboard/async-write-blobs-read-blobs.html b/third_party/blink/web_tests/http/tests/clipboard/async-write-blobs-read-blobs.html
new file mode 100644
index 0000000..6c64664
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/clipboard/async-write-blobs-read-blobs.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>
+  Async Clipboard write blobs -> read blobs tests
+</title>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../resources/permissions-helper.js"></script>
+
+<script>
+async function loadBlob(fileName) {
+  const fetched = await fetch(fileName);
+  return await fetched.blob();
+}
+
+promise_test(async t => {
+  await PermissionsHelper.setPermission('clipboard-read', 'granted');
+  await PermissionsHelper.setPermission('clipboard-write', 'granted');
+
+  const blobText = new Blob(["test text"], {type: 'text/plain'});
+  const blobImage = await loadBlob('resources/greenbox.png');
+
+  assert_equals(blobText.type, "text/plain");
+  assert_equals(blobImage.type, "image/png");
+
+  await navigator.clipboard.write([blobText, blobImage]);
+  const output = await navigator.clipboard.read();
+
+  assert_equals(output.length, 2);
+  assert_equals(output[0].type, "text/plain");
+  assert_equals(output[1].type, "image/png");
+}, "Verify write and read clipboard (multiple blobs)");
+</script>
diff --git a/third_party/blink/web_tests/http/tests/clipboard/async-write-image-read-image.html b/third_party/blink/web_tests/http/tests/clipboard/async-write-image-read-image.html
index 86e06f6..1e8c4a80 100644
--- a/third_party/blink/web_tests/http/tests/clipboard/async-write-image-read-image.html
+++ b/third_party/blink/web_tests/http/tests/clipboard/async-write-image-read-image.html
@@ -41,19 +41,21 @@
   await PermissionsHelper.setPermission('clipboard-read', 'granted');
   await PermissionsHelper.setPermission('clipboard-write', 'granted');
 
-  const input = await loadBlob('resources/greenbox.png');
+  const blobInput = await loadBlob('resources/greenbox.png');
 
-  assert_equals(input.type, "image/png");
-  await navigator.clipboard.write(input);
-  const output = await navigator.clipboard.read();
-  assert_equals(output.type, "image/png");
+  assert_equals(blobInput.type, "image/png");
+  await navigator.clipboard.write([blobInput]);
+  const blobsOutput = await navigator.clipboard.read();
+  assert_equals(blobsOutput.length, 1);
+  const blobOutput = blobsOutput[0];
+  assert_equals(blobOutput.type, "image/png");
 
   document.getElementById('image-on-clipboard').src =
-      window.URL.createObjectURL(output);
+      window.URL.createObjectURL(blobOutput);
 
-  const comparableInput = await getBitmapString(input);
-  const comparableOutput = await getBitmapString(output);
+  const comparableInput = await getBitmapString(blobInput);
+  const comparableOutput = await getBitmapString(blobOutput);
 
   assert_equals(comparableOutput, comparableInput);
-}, "Verify write and read clipboard (DOMString)");
+}, "Verify write and read clipboard (image/png Blob)");
 </script>
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/filter-matched-styles-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/filter-matched-styles-expected.txt
index e04db1d..9ffee3a9 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/filter-matched-styles-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/filter-matched-styles-expected.txt
@@ -65,7 +65,6 @@
    display: block
 }
 html {
-   display: block
    color: -internal-root-color
 }
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-2/filter-matched-styles-hides-separators-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-2/filter-matched-styles-hides-separators-expected.txt
index 2f4328d2..3aab58d 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-2/filter-matched-styles-hides-separators-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-2/filter-matched-styles-hides-separators-expected.txt
@@ -21,7 +21,6 @@
    display: block
 }
 html {
-   display: block
    color: -internal-root-color
 }
 #third::before {
@@ -52,7 +51,7 @@
 Filtering styles by: display
 [ HIDDEN ]  Inherited from div#second
 [ VISIBLE ] Inherited from div#first
-[ VISIBLE ] Inherited from html
+[ HIDDEN ]  Inherited from html
 [ HIDDEN ]  Pseudo ::before element
 [ VISIBLE ] Pseudo ::after element
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-links-4-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-links-4-expected.txt
index 23c16d4..9a9c19f2 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-links-4-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-links-4-expected.txt
@@ -13,7 +13,7 @@
 element.style { ()
 
 [expanded] 
-article, aside, footer, header, hgroup, main, nav, section { (user agent stylesheet)
+section { (user agent stylesheet)
     display: block;
 
 ======== Inherited from html ========
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/svg-style-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/svg-style-expected.txt
index e5f8d2e6..7a9e368 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/svg-style-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/svg-style-expected.txt
@@ -12,7 +12,7 @@
 svg:rect[Attributes Style] { ()
 
 [expanded] 
-:not(svg), :not(foreignObject) > svg { (user agent stylesheet)
+:not(svg) { (user agent stylesheet)
     transform-origin: 0px 0px;
 
 ======== Inherited from html ========
diff --git a/third_party/blink/web_tests/http/tests/devtools/profiler/live-line-level-heap-profile-expected.txt b/third_party/blink/web_tests/http/tests/devtools/profiler/live-line-level-heap-profile-expected.txt
new file mode 100644
index 0000000..392a6c2
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/profiler/live-line-level-heap-profile-expected.txt
@@ -0,0 +1,5 @@
+Tests that the live line-level heap profile is shown in the text editor.
+
+allocator.js
+Memory annotation added to line 12.
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/profiler/live-line-level-heap-profile.js b/third_party/blink/web_tests/http/tests/devtools/profiler/live-line-level-heap-profile.js
new file mode 100644
index 0000000..09ed03d7
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/profiler/live-line-level-heap-profile.js
@@ -0,0 +1,29 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function() {
+  TestRunner.addResult(`Tests that the live line-level heap profile is shown in the text editor.\n`);
+  await self.runtime.loadModulePromise('perf_ui');
+  await PerfUI.LiveHeapProfile.hasStartedForTest();
+  await TestRunner.loadModule('sources_test_runner');
+  await TestRunner.showPanel('sources');
+
+  await TestRunner.evaluateInPagePromise(`
+      let dump = new Array(10000).fill(42).map(x => Date.now() + '42');
+      //# sourceURL=allocator.js`);
+
+  TestRunner.addSniffer(SourceFrame.SourcesTextEditor.prototype, 'setGutterDecoration', decorationAdded, true);
+  SourcesTestRunner.showScriptSource('allocator.js', frameRevealed);
+
+  function decorationAdded(line, type, element) {
+    if (line !== 12 || type !== 'CodeMirror-gutter-memory' || !element.textContent || !element.style.backgroundColor)
+      return;
+    TestRunner.addResult(`Memory annotation added to line ${line}.`);
+    TestRunner.completeTest();
+  }
+
+  function frameRevealed(frame) {
+    TestRunner.addResult(TestRunner.formatters.formatAsURL(frame.uiSourceCode().url()));
+  }
+})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-expected.txt b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-expected.txt
index bef9e0b..842c89dc 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-expected.txt
@@ -1,11 +1,11 @@
 Tests that a line-level CPU profile is shown in the text editor.
 
 .../devtools/tracing/resources/empty.js
-99 CodeMirror-gutter-performance 10.0ms rgba(255, 187, 0, 0.263)
-101 CodeMirror-gutter-performance 1900.0ms rgba(255, 187, 0, 0.718)
-0 CodeMirror-gutter-performance 100.0ms rgba(255, 187, 0, 0.463)
-1 CodeMirror-gutter-performance 200.0ms rgba(255, 187, 0, 0.52)
-2 CodeMirror-gutter-performance 300.0ms rgba(255, 187, 0, 0.557)
-3 CodeMirror-gutter-performance 400.0ms rgba(255, 187, 0, 0.58)
-54 CodeMirror-gutter-performance 220.0ms rgba(255, 187, 0, 0.53)
+99 CodeMirror-gutter-performance 10.0ms rgba(255, 187, 0, 0.4)
+101 CodeMirror-gutter-performance 1900.0ms rgba(255, 187, 0, 0.855)
+0 CodeMirror-gutter-performance 100.0ms rgba(255, 187, 0, 0.6)
+1 CodeMirror-gutter-performance 200.0ms rgba(255, 187, 0, 0.66)
+2 CodeMirror-gutter-performance 300.0ms rgba(255, 187, 0, 0.694)
+3 CodeMirror-gutter-performance 400.0ms rgba(255, 187, 0, 0.72)
+54 CodeMirror-gutter-performance 220.0ms rgba(255, 187, 0, 0.67)
 
diff --git a/third_party/blink/web_tests/http/tests/images/restyle-decode-error.html b/third_party/blink/web_tests/http/tests/images/restyle-decode-error.html
index b774e2b..4bb9ed20 100644
--- a/third_party/blink/web_tests/http/tests/images/restyle-decode-error.html
+++ b/third_party/blink/web_tests/http/tests/images/restyle-decode-error.html
@@ -1,5 +1,7 @@
 <html>
-<body>
+<!-- Avoid scrollbars; we'd get a scrollbar otherwise, because the (broken and
+     invisible) image is quite large. -->
+<body style="overflow:hidden;">
 <img style="content: url(#MyFilter); display: block;">
 <script>
 setTimeout(function() {
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/backgroundfetch-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/backgroundfetch-origin-trial-interfaces.html
deleted file mode 100644
index d6d96c3..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/backgroundfetch-origin-trial-interfaces.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<!-- Generate token with the command:
-generate_token.py http://127.0.0.1:8000 BackgroundFetch --expire-timestamp=2000000000
--->
-
-<meta http-equiv="origin-trial" content="AtDl/AukAuUX0Sw7KRz+mrV2vpSYrfDyVS4vdO3I1clqoNgKGqCX5Np5KIhlC6oQl8XcULXJz5bc9Y4CcYj9xA4AAABXeyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiQmFja2dyb3VuZEZldGNoIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9" />
-<title>Background Fetch API - interfaces exposed by origin trial</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/origin-trials-helper.js"></script>
-<script src="/serviceworker/resources/test-helpers.js"></script>
-<script>
-
-test(t => {
-  OriginTrialsHelper.check_properties(this, {
-    'BackgroundFetchManager': ['fetch', 'get', 'getIds'],
-    'BackgroundFetchUpdateUIEvent': ['updateUI'],
-    'BackgroundFetchRegistration': ['id', 'uploadTotal', 'uploaded',
-                                    'downloadTotal', 'downloaded', 'result',
-                                    'failureReason', 'recordsAvailable',
-                                    'onprogress', 'match', 'matchAll'],
-    'ServiceWorkerRegistration': ['backgroundFetch'],
-  });
-}, 'Background Fetch API interfaces and properties in Origin-Trial enabled document.');
-
-fetch_tests_from_worker(new Worker('resources/backgroundfetch-origin-trial-interfaces-worker.js'));
-
-// Only run "disabled" tests if the feature is not enabled via runtime flags.
-if (!self.internals.runtimeFlags.backgroundFetchEnabled) {
-  service_worker_test('resources/backgroundfetch-origin-trial-interfaces-serviceworker-disabled.js');
-}
-
-service_worker_test('resources/backgroundfetch-origin-trial-interfaces-serviceworker-enabled.php');
-
-</script>
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-serviceworker-disabled.js b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-serviceworker-disabled.js
deleted file mode 100644
index 4438be84..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-serviceworker-disabled.js
+++ /dev/null
@@ -1,12 +0,0 @@
-importScripts('/resources/testharness.js',
-              '/resources/origin-trials-helper.js');
-
-test(t => {
-  OriginTrialsHelper.check_interfaces_missing(
-    self,
-    ['BackgroundFetchEvent', 'BackgroundFetchFetch', 'BackgroundFetchManager',
-     'BackgroundFetchUpdateUIEvent', 'BackgroundFetchRecord',
-     'BackgroundFetchRegistration']);
-}, 'Background Fetch API interfaces in Origin-Trial disabled worker.');
-
-done();
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-serviceworker-enabled.php b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-serviceworker-enabled.php
deleted file mode 100644
index 438a3993..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-serviceworker-enabled.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-// Generate token with the command:
-// generate_token.py http://127.0.0.1:8000 BackgroundFetch --expire-timestamp=2000000000
-header('Origin-Trial: AtDl/AukAuUX0Sw7KRz+mrV2vpSYrfDyVS4vdO3I1clqoNgKGqCX5Np5KIhlC6oQl8XcULXJz5bc9Y4CcYj9xA4AAABXeyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiQmFja2dyb3VuZEZldGNoIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9');
-header('Content-Type: application/javascript');
-?>
-importScripts('/resources/testharness.js',
-              '/resources/origin-trials-helper.js');
-
-test(t => {
-  OriginTrialsHelper.check_properties(this, {
-    'ServiceWorkerRegistration': ['backgroundFetch'],
-    'BackgroundFetchManager': ['fetch', 'get', 'getIds'],
-    'BackgroundFetchEvent': ['registration'],
-    'BackgroundFetchUpdateUIEvent': ['updateUI'],
-    'BackgroundFetchRecord': ['request', 'responseReady'],
-    'BackgroundFetchRegistration': ['id', 'uploadTotal', 'uploaded',
-                                    'downloadTotal', 'downloaded', 'result',
-                                    'failureReason', 'recordsAvailable',
-                                    'onprogress', 'abort', 'match', 'matchAll'],
-  });
-});
-
-test(t => {
-  assert_true('onbackgroundfetchsuccess' in self, 'onbackgroundfetchsuccess property exists on global');
-  assert_true('onbackgroundfetchfail' in self, 'onbackgroundfetchfail property exists on global');
-  assert_true('onbackgroundfetchabort' in self, 'onbackgroundfetchabort property exists on global');
-  assert_true('onbackgroundfetchclick' in self, 'onbackgroundfetchclick property exists on global');
-
-}, 'Background Fetch API entry points in Origin-Trial enabled serviceworker.');
-
-done();
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-worker.js b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-worker.js
deleted file mode 100644
index b885bf6..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/resources/backgroundfetch-origin-trial-interfaces-worker.js
+++ /dev/null
@@ -1,16 +0,0 @@
-importScripts('/resources/testharness.js',
-              '/resources/origin-trials-helper.js');
-
-test(t => {
-
-  OriginTrialsHelper.check_properties(this, {
-       'ServiceWorkerRegistration': ['backgroundFetch'],
-       'BackgroundFetchManager': ['fetch', 'get', 'getIds'],
-       'BackgroundFetchRegistration': ['id', 'uploadTotal', 'uploaded',
-                                       'downloadTotal', 'downloaded', 'result',
-                                       'failureReason', 'recordsAvailable',
-                                       'onprogress'],
-       });
-}, 'Background Fetch API interfaces in an Origin-Trial enabled worker.');
-
-done();
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 39730843..66eb4c3 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -50,6 +50,7 @@
     method constructor
     method updateUI
 interface BarcodeDetector
+    static method getSupportedFormats
     attribute @@toStringTag
     method constructor
     method detect
@@ -1310,6 +1311,7 @@
     attribute ACTIVE_VARIABLES
     attribute ALIASED_LINE_WIDTH_RANGE
     attribute ALIASED_POINT_SIZE_RANGE
+    attribute ALL_BARRIER_BITS
     attribute ALPHA
     attribute ALPHA_BITS
     attribute ALREADY_SIGNALED
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
deleted file mode 100644
index 1d304732..0000000
--- a/third_party/blink/web_tests/platform/linux/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL opener of discarded nested browsing context openerGet is not a function
-FAIL opener of discarded auxiliary browsing context openerGet is not a function
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/linux/images/rendering-broken-block-flow-images-expected.png b/third_party/blink/web_tests/platform/linux/images/rendering-broken-block-flow-images-expected.png
index 87ed1dd..485be0f 100644
--- a/third_party/blink/web_tests/platform/linux/images/rendering-broken-block-flow-images-expected.png
+++ b/third_party/blink/web_tests/platform/linux/images/rendering-broken-block-flow-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png b/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png
index 8326e8da6..42fa90e 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/rendering-broken-block-flow-images-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/rendering-broken-block-flow-images-expected.png
index 0e7bcef..dc32f49 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/rendering-broken-block-flow-images-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/rendering-broken-block-flow-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
deleted file mode 100644
index 1d304732..0000000
--- a/third_party/blink/web_tests/platform/mac/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL opener of discarded nested browsing context openerGet is not a function
-FAIL opener of discarded auxiliary browsing context openerGet is not a function
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/images/rendering-broken-block-flow-images-expected.png b/third_party/blink/web_tests/platform/win/images/rendering-broken-block-flow-images-expected.png
index c051414a..3818f8bb 100644
--- a/third_party/blink/web_tests/platform/win/images/rendering-broken-block-flow-images-expected.png
+++ b/third_party/blink/web_tests/platform/win/images/rendering-broken-block-flow-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png
index c7fee50..51ac7d9e 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/rendering-broken-block-flow-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt b/third_party/blink/web_tests/platform/win7/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
deleted file mode 100644
index 1d304732..0000000
--- a/third_party/blink/web_tests/platform/win7/external/wpt/html/browsers/windows/embedded-opener-remove-frame-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL opener of discarded nested browsing context openerGet is not a function
-FAIL opener of discarded auxiliary browsing context openerGet is not a function
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/shapedetection/detection-support.html b/third_party/blink/web_tests/shapedetection/detection-support.html
new file mode 100644
index 0000000..f8968b07
--- /dev/null
+++ b/third_party/blink/web_tests/shapedetection/detection-support.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<link rel="help" href="https://wicg.github.io/shape-detection-api/#dom-barcodedetector-getsupportedformats">
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
+<script src="file:///gen/services/shape_detection/public/mojom/barcodedetection.mojom.js"></script>
+<script src="file:///gen/services/shape_detection/public/mojom/barcodedetection_provider.mojom.js"></script>
+<script src="resources/mock-barcodedetection.js"></script>
+<script>
+'use strict';
+
+promise_test(() => {
+  return new Promise((resolve, reject) => {
+    var formats = BarcodeDetector.getSupportedFormats();
+    resolve(formats);
+  }).then(result => {
+    assert_equals(result.length, 3, 'Number of supported formats');
+    assert_equals(result[0], 'aztec', 'format 1');
+    assert_equals(result[1], 'data_matrix', 'format 2');
+    assert_equals(result[2], 'qr_code', 'format 3');
+  });
+}, 'get supported barcode formats');
+
+</script>
diff --git a/third_party/blink/web_tests/shapedetection/resources/mock-barcodedetection.js b/third_party/blink/web_tests/shapedetection/resources/mock-barcodedetection.js
index 21ea5f55..b542ca5 100644
--- a/third_party/blink/web_tests/shapedetection/resources/mock-barcodedetection.js
+++ b/third_party/blink/web_tests/shapedetection/resources/mock-barcodedetection.js
@@ -16,6 +16,16 @@
     this.mockService_ = new MockBarcodeDetection(request, options);
   }
 
+  enumerateSupportedFormats() {
+    return Promise.resolve({
+      supportedFormats: [
+        shapeDetection.mojom.BarcodeFormat.AZTEC,
+        shapeDetection.mojom.BarcodeFormat.DATA_MATRIX,
+        shapeDetection.mojom.BarcodeFormat.QR_CODE,
+      ]
+    });
+  }
+
   getFrameData() {
     return this.mockService_.bufferData_;
   }
diff --git a/third_party/blink/web_tests/virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/claim-worker-fetch.https-expected.txt b/third_party/blink/web_tests/virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/claim-worker-fetch.https-expected.txt
new file mode 100644
index 0000000..757707b6
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/claim-worker-fetch.https-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+FAIL fetch() in Worker should be intercepted after the client is claimed. assert_equals: fetch() in the worker should be intercepted. expected "Intercepted!" but got "a simple text file\n"
+FAIL fetch() in nested Worker should be intercepted after the client is claimed. assert_equals: fetch() in the worker should be intercepted. expected "Intercepted!" but got "a simple text file\n"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/local-url-inherit-controller.https-expected.txt b/third_party/blink/web_tests/virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/local-url-inherit-controller.https-expected.txt
new file mode 100644
index 0000000..c7fc4ea
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/local-url-inherit-controller.https-expected.txt
@@ -0,0 +1,10 @@
+This is a testharness.js-based test.
+FAIL Same-origin blob URL iframe should inherit service worker controller. assert_equals: blob URL iframe should inherit controller expected (string) "https://web-platform.test:8444/service-workers/service-worker/resources/local-url-inherit-controller-worker.js" but got (object) null
+FAIL Same-origin blob URL iframe should intercept fetch(). assert_equals: blob URL iframe should intercept fetch expected "intercepted" but got "Hello world\n"
+FAIL Same-origin blob URL worker should inherit service worker controller. promise_test: Unhandled rejection with value: "Uncaught TypeError: Cannot read property 'controller' of undefined"
+FAIL Same-origin blob URL worker should intercept fetch(). assert_equals: blob URL worker should intercept fetch expected "intercepted" but got "Hello world\n"
+PASS Data URL iframe should not intercept fetch().
+FAIL Data URL worker should not inherit service worker controller. promise_test: Unhandled rejection with value: "Uncaught TypeError: Cannot read property 'controller' of undefined"
+PASS Data URL worker should not intercept fetch().
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index ec0a5dda..65c2b06 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -14,6 +14,41 @@
     getter onabort
     method constructor
     setter onabort
+interface BackgroundFetchEvent : ExtendableEvent
+    attribute @@toStringTag
+    getter registration
+    method constructor
+interface BackgroundFetchManager
+    attribute @@toStringTag
+    method constructor
+    method fetch
+    method get
+    method getIds
+interface BackgroundFetchRecord
+    attribute @@toStringTag
+    getter request
+    getter responseReady
+    method constructor
+interface BackgroundFetchRegistration : EventTarget
+    attribute @@toStringTag
+    getter downloadTotal
+    getter downloaded
+    getter failureReason
+    getter id
+    getter onprogress
+    getter recordsAvailable
+    getter result
+    getter uploadTotal
+    getter uploaded
+    method abort
+    method constructor
+    method match
+    method matchAll
+    setter onprogress
+interface BackgroundFetchUpdateUIEvent : BackgroundFetchEvent
+    attribute @@toStringTag
+    method constructor
+    method updateUI
 interface Blob
     attribute @@toStringTag
     getter size
@@ -1021,6 +1056,7 @@
 interface ServiceWorkerRegistration : EventTarget
     attribute @@toStringTag
     getter active
+    getter backgroundFetch
     getter installing
     getter navigationPreload
     getter onupdatefound
@@ -2527,6 +2563,10 @@
     getter clients
     getter onabortpayment
     getter onactivate
+    getter onbackgroundfetchabort
+    getter onbackgroundfetchclick
+    getter onbackgroundfetchfail
+    getter onbackgroundfetchsuccess
     getter oncanmakepayment
     getter onfetch
     getter oninstall
@@ -2542,6 +2582,10 @@
     method skipWaiting
     setter onabortpayment
     setter onactivate
+    setter onbackgroundfetchabort
+    setter onbackgroundfetchclick
+    setter onbackgroundfetchfail
+    setter onbackgroundfetchsuccess
     setter oncanmakepayment
     setter onfetch
     setter oninstall
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index d2429750..db855c8 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -15,6 +15,33 @@
 [Worker]     getter onabort
 [Worker]     method constructor
 [Worker]     setter onabort
+[Worker] interface BackgroundFetchManager
+[Worker]     attribute @@toStringTag
+[Worker]     method constructor
+[Worker]     method fetch
+[Worker]     method get
+[Worker]     method getIds
+[Worker] interface BackgroundFetchRecord
+[Worker]     attribute @@toStringTag
+[Worker]     getter request
+[Worker]     getter responseReady
+[Worker]     method constructor
+[Worker] interface BackgroundFetchRegistration : EventTarget
+[Worker]     attribute @@toStringTag
+[Worker]     getter downloadTotal
+[Worker]     getter downloaded
+[Worker]     getter failureReason
+[Worker]     getter id
+[Worker]     getter onprogress
+[Worker]     getter recordsAvailable
+[Worker]     getter result
+[Worker]     getter uploadTotal
+[Worker]     getter uploaded
+[Worker]     method abort
+[Worker]     method constructor
+[Worker]     method match
+[Worker]     method matchAll
+[Worker]     setter onprogress
 [Worker] interface Blob
 [Worker]     attribute @@toStringTag
 [Worker]     getter size
@@ -961,6 +988,7 @@
 [Worker] interface ServiceWorkerRegistration : EventTarget
 [Worker]     attribute @@toStringTag
 [Worker]     getter active
+[Worker]     getter backgroundFetch
 [Worker]     getter installing
 [Worker]     getter navigationPreload
 [Worker]     getter onupdatefound
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index c8f0635..8521df8 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -228,6 +228,33 @@
     attribute @@toStringTag
     getter clientDataJSON
     method constructor
+interface BackgroundFetchManager
+    attribute @@toStringTag
+    method constructor
+    method fetch
+    method get
+    method getIds
+interface BackgroundFetchRecord
+    attribute @@toStringTag
+    getter request
+    getter responseReady
+    method constructor
+interface BackgroundFetchRegistration : EventTarget
+    attribute @@toStringTag
+    getter downloadTotal
+    getter downloaded
+    getter failureReason
+    getter id
+    getter onprogress
+    getter recordsAvailable
+    getter result
+    getter uploadTotal
+    getter uploaded
+    method abort
+    method constructor
+    method match
+    method matchAll
+    setter onprogress
 interface BarProp
     attribute @@toStringTag
     getter visible
@@ -6362,6 +6389,7 @@
 interface ServiceWorkerRegistration : EventTarget
     attribute @@toStringTag
     getter active
+    getter backgroundFetch
     getter installing
     getter navigationPreload
     getter onupdatefound
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index cb5b1c9..3fa7246 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -15,6 +15,33 @@
 [Worker]     getter onabort
 [Worker]     method constructor
 [Worker]     setter onabort
+[Worker] interface BackgroundFetchManager
+[Worker]     attribute @@toStringTag
+[Worker]     method constructor
+[Worker]     method fetch
+[Worker]     method get
+[Worker]     method getIds
+[Worker] interface BackgroundFetchRecord
+[Worker]     attribute @@toStringTag
+[Worker]     getter request
+[Worker]     getter responseReady
+[Worker]     method constructor
+[Worker] interface BackgroundFetchRegistration : EventTarget
+[Worker]     attribute @@toStringTag
+[Worker]     getter downloadTotal
+[Worker]     getter downloaded
+[Worker]     getter failureReason
+[Worker]     getter id
+[Worker]     getter onprogress
+[Worker]     getter recordsAvailable
+[Worker]     getter result
+[Worker]     getter uploadTotal
+[Worker]     getter uploaded
+[Worker]     method abort
+[Worker]     method constructor
+[Worker]     method match
+[Worker]     method matchAll
+[Worker]     setter onprogress
 [Worker] interface Blob
 [Worker]     attribute @@toStringTag
 [Worker]     getter size
@@ -956,6 +983,7 @@
 [Worker] interface ServiceWorkerRegistration : EventTarget
 [Worker]     attribute @@toStringTag
 [Worker]     getter active
+[Worker]     getter backgroundFetch
 [Worker]     getter installing
 [Worker]     getter navigationPreload
 [Worker]     getter onupdatefound
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 8163fa72..4549102 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -43,6 +43,7 @@
 [Worker]     method matchAll
 [Worker]     setter onprogress
 [Worker] interface BarcodeDetector
+[Worker]     static method getSupportedFormats
 [Worker]     attribute @@toStringTag
 [Worker]     method constructor
 [Worker]     method detect
@@ -1362,6 +1363,7 @@
 [Worker]     attribute ACTIVE_VARIABLES
 [Worker]     attribute ALIASED_LINE_WIDTH_RANGE
 [Worker]     attribute ALIASED_POINT_SIZE_RANGE
+[Worker]     attribute ALL_BARRIER_BITS
 [Worker]     attribute ALPHA
 [Worker]     attribute ALPHA_BITS
 [Worker]     attribute ALREADY_SIGNALED
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 0ae5b01..908c3fa 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -452,6 +452,7 @@
     getter visible
     method constructor
 interface BarcodeDetector
+    static method getSupportedFormats
     attribute @@toStringTag
     method constructor
     method detect
@@ -8060,6 +8061,7 @@
     attribute ACTIVE_VARIABLES
     attribute ALIASED_LINE_WIDTH_RANGE
     attribute ALIASED_POINT_SIZE_RANGE
+    attribute ALL_BARRIER_BITS
     attribute ALPHA
     attribute ALPHA_BITS
     attribute ALREADY_SIGNALED
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index 22af926..fe76c974 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -43,6 +43,7 @@
 [Worker]     method matchAll
 [Worker]     setter onprogress
 [Worker] interface BarcodeDetector
+[Worker]     static method getSupportedFormats
 [Worker]     attribute @@toStringTag
 [Worker]     method constructor
 [Worker]     method detect
@@ -1228,6 +1229,7 @@
 [Worker]     attribute ACTIVE_VARIABLES
 [Worker]     attribute ALIASED_LINE_WIDTH_RANGE
 [Worker]     attribute ALIASED_POINT_SIZE_RANGE
+[Worker]     attribute ALL_BARRIER_BITS
 [Worker]     attribute ALPHA
 [Worker]     attribute ALPHA_BITS
 [Worker]     attribute ALREADY_SIGNALED
diff --git a/third_party/flatbuffers/BUILD.gn b/third_party/flatbuffers/BUILD.gn
index 1cbc22d..1c389cc 100644
--- a/third_party/flatbuffers/BUILD.gn
+++ b/third_party/flatbuffers/BUILD.gn
@@ -17,6 +17,10 @@
     "src/include/flatbuffers/stl_emulation.h",
   ]
 
+  if (is_win) {
+    data_deps = [ "//build/win:runtime_libs" ]
+  }
+
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [ "//build/config/compiler:no_chromium_code" ]
 
diff --git a/third_party/ink/ink_resources.grd b/third_party/ink/ink_resources.grd
index 4c7a2b9..3d2e058 100644
--- a/third_party/ink/ink_resources.grd
+++ b/third_party/ink/ink_resources.grd
@@ -12,8 +12,10 @@
     <includes>
       <if expr="chromeos">
         <include name="IDR_INK_LIB_BINARY_JS" file="build/ink_lib_binary.js" type="BINDATA" />
-        <include name="IDR_INK_GLCORE_BASE_WASM" file="build/wasm/glcore_base.wasm" compress="gzip" type="BINDATA" />
-        <include name="IDR_INK_GLCORE_WASM_BOOTSTRAP_COMPILED_JS" file="build/wasm/glcore_wasm_bootstrap_compiled.js" type="BINDATA" />
+        <include name="IDR_INK_PTHREAD_MAIN_JS" file="build/wasm-threads/pthread-main.js" type="BINDATA" />
+        <include name="IDR_INK_GLCORE_BASE_WASM" file="build/wasm-threads/glcore_base.wasm" compress="gzip" type="BINDATA" />
+        <include name="IDR_INK_GLCORE_BASE_JS_MEM" file="build/wasm-threads/glcore_base.js.mem" compress="gzip" type="BINDATA" />
+        <include name="IDR_INK_GLCORE_WASM_BOOTSTRAP_COMPILED_JS" file="build/wasm-threads/glcore_wasm_bootstrap_compiled.js" type="BINDATA" />
       </if>
     </includes>
   </release>
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium
index 2f8f38f..331f07e5 100644
--- a/third_party/libvpx/README.chromium
+++ b/third_party/libvpx/README.chromium
@@ -5,9 +5,9 @@
 License File: source/libvpx/LICENSE
 Security Critical: yes
 
-Date: Tuesday March 05 2019
+Date: Thursday March 07 2019
 Branch: master
-Commit: d64e328624e09cbc36e7077598bf0ff367dcdb4c
+Commit: 8256c8b297c8b7c7ee4de24edff82ed67d6ef207
 
 Description:
 Contains the sources used to compile libvpx binaries used by Google Chrome and
diff --git a/third_party/libvpx/libvpx_srcs.gni b/third_party/libvpx/libvpx_srcs.gni
index 376acb9..13614df 100644
--- a/third_party/libvpx/libvpx_srcs.gni
+++ b/third_party/libvpx/libvpx_srcs.gni
@@ -444,7 +444,8 @@
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/variance_sse2.c",
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/vpx_subpixel_4t_intrin_sse2.c",
 ]
-libvpx_srcs_x86_sse3 = []
+libvpx_srcs_x86_sse3 = [
+]
 libvpx_srcs_x86_ssse3 = [
   "//third_party/libvpx/source/libvpx/vp8/encoder/x86/vp8_quantize_ssse3.c",
   "//third_party/libvpx/source/libvpx/vp9/encoder/x86/vp9_dct_ssse3.c",
@@ -480,7 +481,8 @@
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/variance_avx2.c",
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/vpx_subpixel_8t_intrin_avx2.c",
 ]
-libvpx_srcs_x86_avx512 = []
+libvpx_srcs_x86_avx512 = [
+]
 libvpx_srcs_x86_64 = [
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.c",
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.h",
@@ -895,8 +897,9 @@
   "//third_party/libvpx/source/libvpx/vpx_ports/emms_mmx.asm",
   "//third_party/libvpx/source/libvpx/vpx_ports/float_control_word.asm",
 ]
-libvpx_srcs_x86_64_mmx =
-    [ "//third_party/libvpx/source/libvpx/vp8/common/x86/idct_blk_mmx.c" ]
+libvpx_srcs_x86_64_mmx = [
+  "//third_party/libvpx/source/libvpx/vp8/common/x86/idct_blk_mmx.c",
+]
 libvpx_srcs_x86_64_sse2 = [
   "//third_party/libvpx/source/libvpx/vp8/common/x86/bilinear_filter_sse2.c",
   "//third_party/libvpx/source/libvpx/vp8/common/x86/idct_blk_sse2.c",
@@ -927,7 +930,8 @@
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/variance_sse2.c",
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/vpx_subpixel_4t_intrin_sse2.c",
 ]
-libvpx_srcs_x86_64_sse3 = []
+libvpx_srcs_x86_64_sse3 = [
+]
 libvpx_srcs_x86_64_ssse3 = [
   "//third_party/libvpx/source/libvpx/vp8/encoder/x86/vp8_quantize_ssse3.c",
   "//third_party/libvpx/source/libvpx/vp9/encoder/x86/vp9_dct_ssse3.c",
@@ -963,7 +967,8 @@
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/variance_avx2.c",
   "//third_party/libvpx/source/libvpx/vpx_dsp/x86/vpx_subpixel_8t_intrin_avx2.c",
 ]
-libvpx_srcs_x86_64_avx512 = []
+libvpx_srcs_x86_64_avx512 = [
+]
 libvpx_srcs_arm = [
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.c",
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.h",
@@ -1306,7 +1311,8 @@
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.c",
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.h",
 ]
-libvpx_srcs_arm_assembly = []
+libvpx_srcs_arm_assembly = [
+]
 libvpx_srcs_arm_neon = [
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.c",
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.h",
@@ -2564,7 +2570,8 @@
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.c",
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.h",
 ]
-libvpx_srcs_arm64_assembly = []
+libvpx_srcs_arm64_assembly = [
+]
 libvpx_srcs_arm_neon_highbd = [
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.c",
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.h",
@@ -3429,7 +3436,8 @@
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.c",
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.h",
 ]
-libvpx_srcs_arm64_highbd_assembly = []
+libvpx_srcs_arm64_highbd_assembly = [
+]
 libvpx_srcs_mips = [
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.c",
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.h",
@@ -3771,7 +3779,8 @@
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.c",
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.h",
 ]
-libvpx_srcs_mips_assembly = []
+libvpx_srcs_mips_assembly = [
+]
 libvpx_srcs_nacl = [
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.c",
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.h",
@@ -4112,7 +4121,8 @@
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.c",
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.h",
 ]
-libvpx_srcs_nacl_assembly = []
+libvpx_srcs_nacl_assembly = [
+]
 libvpx_srcs_generic = [
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.c",
   "//third_party/libvpx/source/libvpx/vp8/common/alloccommon.h",
@@ -4453,4 +4463,5 @@
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.c",
   "//third_party/libvpx/source/libvpx/vpx_util/vpx_write_yuv_frame.h",
 ]
-libvpx_srcs_generic_assembly = []
+libvpx_srcs_generic_assembly = [
+]
diff --git a/third_party/libvpx/source/config/vpx_version.h b/third_party/libvpx/source/config/vpx_version.h
index 88749942..bde94740 100644
--- a/third_party/libvpx/source/config/vpx_version.h
+++ b/third_party/libvpx/source/config/vpx_version.h
@@ -2,7 +2,7 @@
 #define VERSION_MAJOR  1
 #define VERSION_MINOR  8
 #define VERSION_PATCH  0
-#define VERSION_EXTRA  "203-gd64e328624"
+#define VERSION_EXTRA  "207-g8256c8b29"
 #define VERSION_PACKED ((VERSION_MAJOR<<16)|(VERSION_MINOR<<8)|(VERSION_PATCH))
-#define VERSION_STRING_NOSP "v1.8.0-203-gd64e328624"
-#define VERSION_STRING      " v1.8.0-203-gd64e328624"
+#define VERSION_STRING_NOSP "v1.8.0-207-g8256c8b29"
+#define VERSION_STRING      " v1.8.0-207-g8256c8b29"
diff --git a/third_party/protobuf/BUILD.gn b/third_party/protobuf/BUILD.gn
index 84ce0a6c..8e02f217 100644
--- a/third_party/protobuf/BUILD.gn
+++ b/third_party/protobuf/BUILD.gn
@@ -222,10 +222,6 @@
     # The SQLite fuzzer's corpus generator needs protobuf_full and is not
     # included in Chrome.
     "//third_party/sqlite:sqlite3_lpm_corpus_gen",
-
-    # Some tests inside ChromeOS need reflection to parse golden files.
-    # Not included in production code.
-    "//chrome/browser/chromeos:time_limit_tests",
   ]
 
   sources = protobuf_lite_sources + [
diff --git a/third_party/sqlite/README.chromium b/third_party/sqlite/README.chromium
index af2cd26..8a2fb275 100644
--- a/third_party/sqlite/README.chromium
+++ b/third_party/sqlite/README.chromium
@@ -116,6 +116,18 @@
 git rm patches/*
 git format-patch --output-directory=patches --zero-commit \
     sqlite-base..sqlite-dev
+
+### Document and link any backported patches to the upstream repository URL
+### and crbug.
+# Under the *.patch's added by this change, the below information should be
+# added between the "Subject: *" line and the first "---" line. For an example,
+# please reference https://crrev.com/c/1480822/2/third_party/sqlite/patches/0008-Adjustments-to-the-page-cache-to-try-to-avoid-harmle.patch.
+#
+# This backports https://www.sqlite.org/src/info/XXX
+#
+# Bug: XXX
+
+### Commit and create CL
 git add amalgamation/
 git add patches/
 git commit -m "Squash: regenerate amalgamation and patches."
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 94276ce..c66b6eb 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -370,7 +370,6 @@
       'GPU FYI Win Builder': 'gpu_fyi_tests_release_trybot_x86',
       'GPU FYI Win Builder (dbg)': 'gpu_fyi_tests_debug_trybot_x86',
       'GPU FYI Win dEQP Builder': 'deqp_release_trybot_x86',
-      'GPU FYI Win Clang Builder (dbg)': 'gpu_fyi_tests_win_clang_debug_bot',
       'GPU FYI Win x64 Builder': 'gpu_fyi_tests_release_trybot',
       'GPU FYI Win x64 Builder (dbg)': 'gpu_fyi_tests_debug_trybot',
       'GPU FYI Win x64 dEQP Builder': 'deqp_release_trybot',
@@ -1428,10 +1427,6 @@
       'gpu_fyi_tests', 'release_trybot', 'x86',
     ],
 
-    'gpu_fyi_tests_win_clang_debug_bot': [
-      'gpu_tests', 'internal_gles_conform_tests', 'clang', 'debug_bot', 'minimal_symbols',
-    ],
-
     'gpu_tests_android_release_bot_minimal_symbols_arm64': [
       'android', 'release_bot', 'minimal_symbols', 'arm64',
       'strip_debug_info', 'static_angle',
diff --git a/tools/metrics/actions/README.md b/tools/metrics/actions/README.md
index 6361c038..daed02c 100644
--- a/tools/metrics/actions/README.md
+++ b/tools/metrics/actions/README.md
@@ -152,12 +152,26 @@
 
 ### Owners
 
-User actions need to be owned by a person or set of people.  These indicate who
-the current experts on it are.  Being the owner means you are responsible for
-answering questions about it, handling the maintenance if there are functional
-changes.  The owners should be added in the original user action description.
-If you are using a user action heavily and understand it intimately, feel free
-to add yourself as an owner. @chromium.org email addresses are preferred.
+User actions need to have owners, who are the current experts on the metric. The
+owners are the contact points for any questions or maintenance tasks. It's a
+best practice to list multiple owners, so that there's no single point of
+failure for such communication.
+
+Being an owner means you are responsible for answering questions about the
+metric, handling the maintenance if there are functional changes, and
+deprecating the metric if it outlives its usefulness. If you are using a metric
+heavily and understand it intimately, feel free to add yourself as an owner.
+@chromium.org email addresses are preferred.
+
+If an appropriate mailing list is available, it's a good idea to include the
+mailing list as a secondary owner. However, it's always a best practice to list
+an individual as the primary owner. Listing an individual owner makes it clearer
+who is ultimately most responsible for maintaining the metric, which makes it
+less likely that such maintenance tasks will slip through the cracks.
+
+Notably, owners are asked to evaluate whether histograms have outlived their
+usefulness. The metrics team may file a bug in Monorail. It's important that
+somebody familiar with the user action notices and triages such bugs!
 
 ### Beware `not_user_triggered="true"`
 
diff --git a/tools/metrics/histograms/README.md b/tools/metrics/histograms/README.md
index 9b32990d..d975abca 100644
--- a/tools/metrics/histograms/README.md
+++ b/tools/metrics/histograms/README.md
@@ -32,9 +32,9 @@
 
 When using histogram macros (calls such as `UMA_HISTOGRAM_ENUMERATION`), you're
 not allow to construct your string dynamically so that it can vary at a
-callsite.  At a given callsite (preferably you have only one), the string should
-be the same every time the macro is called.  If you need to use dynamic names,
-use the functions in histogram_functions.h instead of the macros.
+callsite.  At a given callsite (preferably you have only one), the string
+should be the same every time the macro is called.  If you need to use dynamic
+names, use the functions in histogram_functions.h instead of the macros.
 
 ### Don't Use Same String in Multiple Places
 
@@ -402,14 +402,27 @@
 
 ### Owners
 
-Histograms need to be owned by a person or set of people. These indicate who
-the current experts on this metric are. Being the owner means you are
-responsible for answering questions about the metric, handling the maintenance
-if there are functional changes, and deprecating the metric if it outlives its
-usefulness. The owners should be added in the original histogram description.
-If you are using a metric heavily and understand it intimately, feel free to
-add yourself as an owner. @chromium.org email addresses are preferred.
+Histograms need to have owners, who are the current experts on the metric. The
+owners are the contact points for any questions or maintenance tasks. It's a
+best practice to list multiple owners, so that there's no single point of
+failure for such communication.
 
+Being an owner means you are responsible for answering questions about the
+metric, handling the maintenance if there are functional changes, and
+deprecating the metric if it outlives its usefulness. If you are using a metric
+heavily and understand it intimately, feel free to add yourself as an owner.
+@chromium.org email addresses are preferred.
+
+If an appropriate mailing list is available, it's a good idea to include the
+mailing list as a secondary owner. However, it's always a best practice to list
+an individual as the primary owner. Listing an individual owner makes it clearer
+who is ultimately most responsible for maintaining the metric, which makes it
+less likely that such maintenance tasks will slip through the cracks.
+
+Notably, owners are asked to evaluate whether histograms have outlived their
+usefulness. When a histogram is nearing expiry, a robot will file a reminder bug
+in Monorail. It's important that somebody familiar with the histogram notices
+and triages such bugs!
 
 ### Deleting Histogram Entries
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e4048c91..d990102 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -377,6 +377,188 @@
   <int value="157" label="UMA_API_UNSELECT_ROW">unselectRow</int>
 </enum>
 
+<enum name="AccessibilityWinAPIGetPropertyValueEnum">
+<!-- Property IDs are defined in UIAutomationClient.h in the Windows SDK.
+Unknown properties are collapsed to zero. -->
+
+  <int value="0" label="UNKNOWN"/>
+  <int value="30000" label="RuntimeId"/>
+  <int value="30001" label="BoundingRectangle"/>
+  <int value="30002" label="ProcessId"/>
+  <int value="30003" label="ControlType"/>
+  <int value="30004" label="LocalizedControlType"/>
+  <int value="30005" label="Name"/>
+  <int value="30006" label="AcceleratorKey"/>
+  <int value="30007" label="AccessKey"/>
+  <int value="30008" label="HasKeyboardFocus"/>
+  <int value="30009" label="IsKeyboardFocusable"/>
+  <int value="30010" label="IsEnabled"/>
+  <int value="30011" label="AutomationId"/>
+  <int value="30012" label="ClassName"/>
+  <int value="30013" label="HelpText"/>
+  <int value="30014" label="ClickablePoint"/>
+  <int value="30015" label="Culture"/>
+  <int value="30016" label="IsControlElement"/>
+  <int value="30017" label="IsContentElement"/>
+  <int value="30018" label="LabeledBy"/>
+  <int value="30019" label="IsPassword"/>
+  <int value="30020" label="NativeWindowHandle"/>
+  <int value="30021" label="ItemType"/>
+  <int value="30022" label="IsOffscreen"/>
+  <int value="30023" label="Orientation"/>
+  <int value="30024" label="FrameworkId"/>
+  <int value="30025" label="IsRequiredForForm"/>
+  <int value="30026" label="ItemStatus"/>
+  <int value="30027" label="IsDockPatternAvailable"/>
+  <int value="30028" label="IsExpandCollapsePatternAvailable"/>
+  <int value="30029" label="IsGridItemPatternAvailable"/>
+  <int value="30030" label="IsGridPatternAvailable"/>
+  <int value="30031" label="IsInvokePatternAvailable"/>
+  <int value="30032" label="IsMultipleViewPatternAvailable"/>
+  <int value="30033" label="IsRangeValuePatternAvailable"/>
+  <int value="30034" label="IsScrollPatternAvailable"/>
+  <int value="30035" label="IsScrollItemPatternAvailable"/>
+  <int value="30036" label="IsSelectionItemPatternAvailable"/>
+  <int value="30037" label="IsSelectionPatternAvailable"/>
+  <int value="30038" label="IsTablePatternAvailable"/>
+  <int value="30039" label="IsTableItemPatternAvailable"/>
+  <int value="30040" label="IsTextPatternAvailable"/>
+  <int value="30041" label="IsTogglePatternAvailable"/>
+  <int value="30042" label="IsTransformPatternAvailable"/>
+  <int value="30043" label="IsValuePatternAvailable"/>
+  <int value="30044" label="IsWindowPatternAvailable"/>
+  <int value="30045" label="ValueValue"/>
+  <int value="30046" label="ValueIsReadOnly"/>
+  <int value="30047" label="RangeValueValue"/>
+  <int value="30048" label="RangeValueIsReadOnly"/>
+  <int value="30049" label="RangeValueMinimum"/>
+  <int value="30050" label="RangeValueMaximum"/>
+  <int value="30051" label="RangeValueLargeChange"/>
+  <int value="30052" label="RangeValueSmallChange"/>
+  <int value="30053" label="ScrollHorizontalScrollPercent"/>
+  <int value="30054" label="ScrollHorizontalViewSize"/>
+  <int value="30055" label="ScrollVerticalScrollPercent"/>
+  <int value="30056" label="ScrollVerticalViewSize"/>
+  <int value="30057" label="ScrollHorizontallyScrollable"/>
+  <int value="30058" label="ScrollVerticallyScrollable"/>
+  <int value="30059" label="SelectionSelection"/>
+  <int value="30060" label="SelectionCanSelectMultiple"/>
+  <int value="30061" label="SelectionIsSelectionRequired"/>
+  <int value="30062" label="GridRowCount"/>
+  <int value="30063" label="GridColumnCount"/>
+  <int value="30064" label="GridItemRow"/>
+  <int value="30065" label="GridItemColumn"/>
+  <int value="30066" label="GridItemRowSpan"/>
+  <int value="30067" label="GridItemColumnSpan"/>
+  <int value="30068" label="GridItemContainingGrid"/>
+  <int value="30069" label="DockDockPosition"/>
+  <int value="30070" label="ExpandCollapseExpandCollapseState"/>
+  <int value="30071" label="MultipleViewCurrentView"/>
+  <int value="30072" label="MultipleViewSupportedViews"/>
+  <int value="30073" label="WindowCanMaximize"/>
+  <int value="30074" label="WindowCanMinimize"/>
+  <int value="30075" label="WindowWindowVisualState"/>
+  <int value="30076" label="WindowWindowInteractionState"/>
+  <int value="30077" label="WindowIsModal"/>
+  <int value="30078" label="WindowIsTopmost"/>
+  <int value="30079" label="SelectionItemIsSelected"/>
+  <int value="30080" label="SelectionItemSelectionContainer"/>
+  <int value="30081" label="TableRowHeaders"/>
+  <int value="30082" label="TableColumnHeaders"/>
+  <int value="30083" label="TableRowOrColumnMajor"/>
+  <int value="30084" label="TableItemRowHeaderItems"/>
+  <int value="30085" label="TableItemColumnHeaderItems"/>
+  <int value="30086" label="ToggleToggleState"/>
+  <int value="30087" label="TransformCanMove"/>
+  <int value="30088" label="TransformCanResize"/>
+  <int value="30089" label="TransformCanRotate"/>
+  <int value="30090" label="IsLegacyIAccessiblePatternAvailable"/>
+  <int value="30091" label="LegacyIAccessibleChildId"/>
+  <int value="30092" label="LegacyIAccessibleName"/>
+  <int value="30093" label="LegacyIAccessibleValue"/>
+  <int value="30094" label="LegacyIAccessibleDescription"/>
+  <int value="30095" label="LegacyIAccessibleRole"/>
+  <int value="30096" label="LegacyIAccessibleState"/>
+  <int value="30097" label="LegacyIAccessibleHelp"/>
+  <int value="30098" label="LegacyIAccessibleKeyboardShortcut"/>
+  <int value="30099" label="LegacyIAccessibleSelection"/>
+  <int value="30100" label="LegacyIAccessibleDefaultAction"/>
+  <int value="30101" label="AriaRole"/>
+  <int value="30102" label="AriaProperties"/>
+  <int value="30103" label="IsDataValidForForm"/>
+  <int value="30104" label="ControllerFor"/>
+  <int value="30105" label="DescribedBy"/>
+  <int value="30106" label="FlowsTo"/>
+  <int value="30107" label="ProviderDescription"/>
+  <int value="30108" label="IsItemContainerPatternAvailable"/>
+  <int value="30109" label="IsVirtualizedItemPatternAvailable"/>
+  <int value="30110" label="IsSynchronizedInputPatternAvailable"/>
+  <int value="30111" label="OptimizeForVisualContent"/>
+  <int value="30112" label="IsObjectModelPatternAvailable"/>
+  <int value="30113" label="AnnotationAnnotationTypeId"/>
+  <int value="30114" label="AnnotationAnnotationTypeName"/>
+  <int value="30115" label="AnnotationAuthor"/>
+  <int value="30116" label="AnnotationDateTime"/>
+  <int value="30117" label="AnnotationTarget"/>
+  <int value="30118" label="IsAnnotationPatternAvailable"/>
+  <int value="30119" label="IsTextPattern2Available"/>
+  <int value="30120" label="StylesStyleId"/>
+  <int value="30121" label="StylesStyleName"/>
+  <int value="30122" label="StylesFillColor"/>
+  <int value="30123" label="StylesFillPatternStyle"/>
+  <int value="30124" label="StylesShape"/>
+  <int value="30125" label="StylesFillPatternColor"/>
+  <int value="30126" label="StylesExtendedProperties"/>
+  <int value="30127" label="IsStylesPatternAvailable"/>
+  <int value="30128" label="IsSpreadsheetPatternAvailable"/>
+  <int value="30129" label="SpreadsheetItemFormula"/>
+  <int value="30130" label="SpreadsheetItemAnnotationObjects"/>
+  <int value="30131" label="SpreadsheetItemAnnotationTypes"/>
+  <int value="30132" label="IsSpreadsheetItemPatternAvailable"/>
+  <int value="30133" label="Transform2CanZoom"/>
+  <int value="30134" label="IsTransformPattern2Available"/>
+  <int value="30135" label="LiveSetting"/>
+  <int value="30136" label="IsTextChildPatternAvailable"/>
+  <int value="30137" label="IsDragPatternAvailable"/>
+  <int value="30138" label="DragIsGrabbed"/>
+  <int value="30139" label="DragDropEffect"/>
+  <int value="30140" label="DragDropEffects"/>
+  <int value="30141" label="IsDropTargetPatternAvailable"/>
+  <int value="30142" label="DropTargetDropTargetEffect"/>
+  <int value="30143" label="DropTargetDropTargetEffects"/>
+  <int value="30144" label="DragGrabbedItems"/>
+  <int value="30145" label="Transform2ZoomLevel"/>
+  <int value="30146" label="Transform2ZoomMinimum"/>
+  <int value="30147" label="Transform2ZoomMaximum"/>
+  <int value="30148" label="FlowsFrom"/>
+  <int value="30149" label="IsTextEditPatternAvailable"/>
+  <int value="30150" label="IsPeripheral"/>
+  <int value="30151" label="IsCustomNavigationPatternAvailable"/>
+  <int value="30152" label="PositionInSet"/>
+  <int value="30153" label="SizeOfSet"/>
+  <int value="30154" label="Level"/>
+  <int value="30155" label="AnnotationTypes"/>
+  <int value="30156" label="AnnotationObjects"/>
+  <int value="30157" label="LandmarkType"/>
+  <int value="30158" label="LocalizedLandmarkType"/>
+  <int value="30159" label="FullDescription"/>
+  <int value="30160" label="FillColor"/>
+  <int value="30161" label="OutlineColor"/>
+  <int value="30162" label="FillType"/>
+  <int value="30163" label="VisualEffects"/>
+  <int value="30164" label="OutlineThickness"/>
+  <int value="30165" label="CenterPoint"/>
+  <int value="30166" label="Rotation"/>
+  <int value="30167" label="Size"/>
+  <int value="30168" label="IsSelectionPattern2Available"/>
+  <int value="30169" label="Selection2FirstSelectedItem"/>
+  <int value="30170" label="Selection2LastSelectedItem"/>
+  <int value="30171" label="Selection2CurrentSelectedItem"/>
+  <int value="30172" label="Selection2ItemCount"/>
+  <int value="30173" label="HeadingLevel"/>
+  <int value="30174" label="IsDialog"/>
+</enum>
+
 <enum name="AccessoryAction">
   <int value="0" label="Automatic password generation"/>
   <int value="1" label="'Manage all passwords' link"/>
@@ -2817,6 +2999,7 @@
   <int value="12" label="User requested to provide cardholder name"/>
   <int value="13" label="Max strikes, did not show infobar (mobile only)"/>
   <int value="14" label="User requested to provide expiration date"/>
+  <int value="15" label="Card was not in a supported bin range"/>
 </enum>
 
 <enum name="AutofillCreditCardInfoBar">
@@ -12185,6 +12368,187 @@
              is ARC app(Android app in ChromeOS)"/>
 </enum>
 
+<enum name="DownEventInputFormFactorDestinationCombination2">
+  <int value="0"
+      label="Unknown input in clamshell mode, destination is
+             others(everything except browser and apps)"/>
+  <int value="1"
+      label="Unknown input in touchview mode landscape orientation,
+             destination is others(everything except browser and apps)"/>
+  <int value="2"
+      label="Unknown input in touchview mode portrait orientation,
+             destination is others(everything except browser and apps)"/>
+  <int value="3"
+      label="Mouse input in clamshell mode, destination is others(everything
+             except browser and apps)"/>
+  <int value="4"
+      label="Mouse input in touchview mode landscape orientation, destination
+             is others(everything except browser and apps)"/>
+  <int value="5"
+      label="Mouse input in touchview mode portrait orientation, destination
+             is others(everything except browser and apps)"/>
+  <int value="6"
+      label="Stylus input in clamshell mode, destination is others(everything
+             except browser and apps)"/>
+  <int value="7"
+      label="Stylus input in touchview mode landscape orientation,
+             destination is others(everything except browser and apps)"/>
+  <int value="8"
+      label="Stylus input in touchview mode portrait orientation, destination
+             is others(everything except browser and apps)"/>
+  <int value="9"
+      label="Touch input in clamshell mode, destination is others(everything
+             except browser and apps)"/>
+  <int value="10"
+      label="Touch input in touchview mode landscape orientation, destination
+             is others(everything except browser and apps)"/>
+  <int value="11"
+      label="Touch input in touchview mode portrait orientation, destination
+             is others(everything except browser and apps)"/>
+  <int value="12"
+      label="Unknown input in clamshell mode, destination is inside the
+             browser frame"/>
+  <int value="13"
+      label="Unknown input in touchview mode landscape orientation,
+             destination is inside the browser frame"/>
+  <int value="14"
+      label="Unknown input in touchview mode portrait orientation,
+             destination is inside the browser frame"/>
+  <int value="15"
+      label="Mouse input in clamshell mode, destination is inside the browser
+             frame"/>
+  <int value="16"
+      label="Mouse input in touchview mode landscape orientation, destination
+             is inside the browser frame"/>
+  <int value="17"
+      label="Mouse input in touchview mode portrait orientation, destination
+             is inside the browser frame"/>
+  <int value="18"
+      label="Stylus input in clamshell mode, destination is inside the
+             browser frame"/>
+  <int value="19"
+      label="Stylus input in touchview mode landscape orientation,
+             destination is inside the browser frame"/>
+  <int value="20"
+      label="Stylus input in touchview mode portrait orientation, destination
+             is inside the browser frame"/>
+  <int value="21"
+      label="Touch input in clamshell mode, destination is inside the browser
+             frame"/>
+  <int value="22"
+      label="Touch input in touchview mode landscape orientation, destination
+             is inside the browser frame"/>
+  <int value="23"
+      label="Touch input in touchview mode portrait orientation, destination
+             is inside the browser frame"/>
+  <int value="24"
+      label="Unknown input in clamshell mode, destination is regular chrome
+             app"/>
+  <int value="25"
+      label="Unknown input in touchview mode landscape orientation,
+             destination is regular chrome app"/>
+  <int value="26"
+      label="Unknown input in touchview mode portrait orientation,
+             destination is regular chrome app"/>
+  <int value="27"
+      label="Mouse input in clamshell mode, destination is regular chrome app"/>
+  <int value="28"
+      label="Mouse input in touchview mode landscape orientation, destination
+             is regular chrome app"/>
+  <int value="29"
+      label="Mouse input in touchview mode portrait orientation, destination
+             is regular chrome app"/>
+  <int value="30"
+      label="Stylus input in clamshell mode, destination is regular chrome
+             app"/>
+  <int value="31"
+      label="Stylus input in touchview mode landscape orientation,
+             destination is regular chrome app"/>
+  <int value="32"
+      label="Stylus input in touchview mode portrait orientation, destination
+             is regular chrome app"/>
+  <int value="33"
+      label="Touch input in clamshell mode, destination is regular chrome app"/>
+  <int value="34"
+      label="Touch input in touchview mode landscape orientation, destination
+             is regular chrome app"/>
+  <int value="35"
+      label="Touch input in touchview mode portrait orientation, destination
+             is regular chrome app"/>
+  <int value="36"
+      label="Unknown input in clamshell mode, destination is ARC app(Android
+             app in ChromeOS)"/>
+  <int value="37"
+      label="Unknown input in touchview mode landscape orientation,
+             destination is ARC app(Android app in ChromeOS)"/>
+  <int value="38"
+      label="Unknown input in touchview mode portrait orientation,
+             destination is ARC app(Android app in ChromeOS)"/>
+  <int value="39"
+      label="Mouse input in clamshell mode, destination is ARC app(Android
+             app in ChromeOS)"/>
+  <int value="40"
+      label="Mouse input in touchview mode landscape orientation, destination
+             is ARC app(Android app in ChromeOS)"/>
+  <int value="41"
+      label="Mouse input in touchview mode portrait orientation, destination
+             is ARC app(Android app in ChromeOS)"/>
+  <int value="42"
+      label="Stylus input in clamshell mode, destination is ARC app(Android
+             app in ChromeOS)"/>
+  <int value="43"
+      label="Stylus input in touchview mode landscape orientation,
+             destination is ARC app(Android app in ChromeOS)"/>
+  <int value="44"
+      label="Stylus input in touchview mode portrait orientation, destination
+             is ARC app(Android app in ChromeOS)"/>
+  <int value="45"
+      label="Touch input in clamshell mode, destination is ARC app(Android
+             app in ChromeOS)"/>
+  <int value="46"
+      label="Touch input in touchview mode landscape orientation, destination
+             is ARC app(Android app in ChromeOS)"/>
+  <int value="47"
+      label="Touch input in touchview mode portrait orientation, destination
+             is ARC app(Android app in ChromeOS)"/>
+  <int value="48"
+      label="Unknown input in clamshell mode, destination is Crostini
+             app(Linux app in ChromeOS)"/>
+  <int value="49"
+      label="Unknown input in touchview mode landscape orientation,
+             destination is Crostini app(Linux app in ChromeOS)"/>
+  <int value="50"
+      label="Unknown input in touchview mode portrait orientation,
+             destination is Crostini app(Linux app in ChromeOS)"/>
+  <int value="51"
+      label="Mouse input in clamshell mode, destination is Crostini app(Linux
+             app in ChromeOS)"/>
+  <int value="52"
+      label="Mouse input in touchview mode landscape orientation, destination
+             is Crostini app(Linux app in ChromeOS)"/>
+  <int value="53"
+      label="Mouse input in touchview mode portrait orientation, destination
+             is Crostini app(Linux app in ChromeOS)"/>
+  <int value="54"
+      label="Stylus input in clamshell mode, destination is Crostini
+             app(Linux app in ChromeOS)"/>
+  <int value="55"
+      label="Stylus input in touchview mode landscape orientation,
+             destination is Crostini app(Linux app in ChromeOS)"/>
+  <int value="56"
+      label="Stylus input in touchview mode portrait orientation, destination
+             is Crostini app(Linux app in ChromeOS)"/>
+  <int value="57"
+      label="Touch input in clamshell mode, destination is Crostini app(Linux
+             app in ChromeOS)"/>
+  <int value="58"
+      label="Touch input in touchview mode landscape orientation, destination
+             is Crostini app(Linux app in ChromeOS)"/>
+  <int value="59"
+      label="Touch input in touchview mode portrait orientation, destination
+             is Crostini app(Linux app in ChromeOS)"/>
+</enum>
+
 <enum name="DownEventSource">
   <int value="0" label="Unknown"/>
   <int value="1" label="Mouse"/>
@@ -12959,6 +13323,7 @@
   <int value="353" label="potm"/>
   <int value="354" label="ppsm"/>
   <int value="355" label="pps"/>
+  <int value="356" label="mobileconfig"/>
 </enum>
 
 <enum name="DownloadItem.DangerType">
@@ -21794,6 +22159,8 @@
   <int value="2823" label="CSSValueAppearanceTextFieldForTemporalRendered"/>
   <int value="2824" label="BuiltInModuleKvStorage"/>
   <int value="2825" label="BuiltInModuleVirtualScroller"/>
+  <int value="2826" label="AdClickNavigation"/>
+  <int value="2827" label="RTCStatsRelativePacketArrivalDelay"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -21869,6 +22236,30 @@
   <int value="2" label="Fetch failure"/>
 </enum>
 
+<enum name="FeedInternalError">
+  <int value="0" label="SWITCH_TO_EPHEMERAL"/>
+  <int value="1" label="NO_URL_FOR_OPEN"/>
+  <int value="2" label="FAILED_TO_RESTORE"/>
+  <int value="3" label="NO_ROOT_FEATURE"/>
+  <int value="4" label="TOP_LEVEL_UNBOUND_CHILD"/>
+  <int value="5" label="TOP_LEVEL_INVALID_FEATURE_TYPE"/>
+  <int value="6" label="CLUSTER_CHILD_MISSING_FEATURE"/>
+  <int value="7" label="CLUSTER_CHILD_NOT_CARD"/>
+  <int value="8" label="CARD_CHILD_MISSING_FEATURE"/>
+  <int value="9" label="NULL_SHARED_STATES"/>
+</enum>
+
+<enum name="FeedRequestReason">
+  <int value="0" label="UNKNOWN"/>
+  <int value="1" label="ZERO_STATE"/>
+  <int value="2" label="HOST_REQUESTED"/>
+  <int value="3" label="OPEN_WITH_CONTENT"/>
+  <int value="4" label="MANUAL_CONTINUATION"/>
+  <int value="5" label="AUTOMATIC_CONTINUATION"/>
+  <int value="6" label="OPEN_WITHOUT_CONTENT"/>
+  <int value="7" label="CLEAR_ALL"/>
+</enum>
+
 <enum name="FeedSchedulerRefreshStatus">
   <int value="0" label="Should refresh"/>
   <int value="1" label="Outstanding request"/>
@@ -21889,6 +22280,12 @@
   <int value="5" label="Infinite feed"/>
 </enum>
 
+<enum name="FeedZeroStateShowReason">
+  <int value="0" label="ERROR"/>
+  <int value="1" label="NO_CONTENT"/>
+  <int value="2" label="CONTENT_DISMISSED"/>
+</enum>
+
 <enum name="FetchRequestMode">
   <int value="0" label="SameOrigin"/>
   <int value="1" label="NoCORS"/>
@@ -27844,6 +28241,26 @@
   <int value="4" label="Script"/>
 </enum>
 
+<enum name="InlineUpdateCallFailure">
+  <int value="0" label="StartUpdate Failed"/>
+  <int value="1" label="StartUpdate Exception"/>
+  <int value="2" label="CompleteUpdate Failed"/>
+  <int value="3" label="GetUpdateStatus Failed"/>
+</enum>
+
+<enum name="InlineUpdateErrorCodes">
+  <int value="0" label="No Error"/>
+  <int value="1" label="No Error (Partially Allowed)"/>
+  <int value="2" label="Error - Unknown"/>
+  <int value="3" label="Error - API Not Available"/>
+  <int value="4" label="Error - Invalid Request"/>
+  <int value="5" label="Error - Install Unavailable"/>
+  <int value="6" label="Error - Install Not Allowed"/>
+  <int value="7" label="Error - Download Not Present"/>
+  <int value="8" label="Error - Internal Error"/>
+  <int value="9" label="Error - Untracked (Not Known to UMA)"/>
+</enum>
+
 <enum name="InputMethodCategory">
   <int value="0" label="Unkown"/>
   <int value="1" label="XKB">XKeyboard</int>
@@ -31478,6 +31895,7 @@
   <int value="-1335017208" label="KeyboardLockAPI:enabled"/>
   <int value="-1334327410" label="ash-enable-touch-view-testing"/>
   <int value="-1332267458" label="RemoveNavigationHistory:enabled"/>
+  <int value="-1331831950" label="site-isolation-for-password-sites:enabled"/>
   <int value="-1327676774" label="disable-accelerated-mjpeg-decode"/>
   <int value="-1326463296" label="SSLCommittedInterstitials:disabled"/>
   <int value="-1325887476" label="NewPrintPreview:enabled"/>
@@ -32051,6 +32469,8 @@
   <int value="-366949535" label="KeyboardShortcutViewerApp:enabled"/>
   <int value="-365920680" label="ImageCaptureAPI:disabled"/>
   <int value="-365806433" label="BuiltInModuleAll:disabled"/>
+  <int value="-364604989"
+      label="LinkManagedNoticeToChromeUIManagementURL:disabled"/>
   <int value="-364587218" label="ResourceLoadScheduler:enabled"/>
   <int value="-364325011" label="enable-files-quick-view"/>
   <int value="-364267715" label="disable-native-cups"/>
@@ -32308,6 +32728,7 @@
   <int value="54571864" label="EnableDisplayZoomSetting:enabled"/>
   <int value="56723110" label="enable-webfonts-intervention"/>
   <int value="56900498" label="OmniboxOneClickUnelide:enabled"/>
+  <int value="57255632" label="site-isolation-for-password-sites:disabled"/>
   <int value="57555893" label="NewContactsPicker:disabled"/>
   <int value="57639188" label="SoundContentSetting:disabled"/>
   <int value="57791920" label="MemoryCoordinator:enabled"/>
@@ -32470,6 +32891,7 @@
   <int value="359601954" label="CrostiniUsbSupport:enabled"/>
   <int value="360391863" label="NTPOfflineBadge:enabled"/>
   <int value="360599302" label="enable-gpu-rasterization"/>
+  <int value="362644448" label="memlog-in-process"/>
   <int value="365467768" label="prefetch-search-results"/>
   <int value="367063319" label="PasswordImport:disabled"/>
   <int value="368854020" label="ash-screen-rotation-animation"/>
@@ -32578,6 +33000,8 @@
   <int value="549483647" label="EnableUnifiedMultiDeviceSettings:disabled"/>
   <int value="550378029" label="reset-app-list-install-state"/>
   <int value="550387510" label="NTPAssetDownloadSuggestions:disabled"/>
+  <int value="556555487"
+      label="AutofillDoNotUploadSaveUnsupportedCards:disabled"/>
   <int value="557200974" label="WebSocketHandshakeReuseConnection:disabled"/>
   <int value="557915559" label="CCTModuleCustomRequestHeader:enabled"/>
   <int value="558873715" label="SiteDetails:disabled"/>
@@ -33070,6 +33494,8 @@
   <int value="1346994602" label="SyncPseudoUSSDictionary:enabled"/>
   <int value="1351830811" label="do-not-ignore-autocomplete-off"/>
   <int value="1352447982" label="enable-lcd-text"/>
+  <int value="1353066950"
+      label="AutofillDoNotUploadSaveUnsupportedCards:enabled"/>
   <int value="1353629763" label="MediaSessionAccelerators:enabled"/>
   <int value="1355923367" label="CrOSContainer:disabled"/>
   <int value="1359972809" label="enable-gesture-deletion"/>
@@ -33463,6 +33889,8 @@
   <int value="1988810119" label="AutofillDropdownLayout:enabled"/>
   <int value="1989051182" label="view-passwords:enabled"/>
   <int value="1989877708" label="PostScriptPrinting:enabled"/>
+  <int value="1990562608"
+      label="LinkManagedNoticeToChromeUIManagementURL:enabled"/>
   <int value="1991771852" label="LeftToRightUrls:enabled"/>
   <int value="1991912338" label="ModuleScriptsDynamicImport:enabled"/>
   <int value="1992466116" label="enable-passive-event-listeners-due-to-fling"/>
@@ -33486,6 +33914,7 @@
   <int value="2005614493" label="tab-management-experiment-type-dill"/>
   <int value="2006413281"
       label="ContextualSuggestionsAlternateCardLayout:enabled"/>
+  <int value="2009097351" label="memlog-sampling-rate"/>
   <int value="2009362691" label="AllowStartingServiceManagerOnly:enabled"/>
   <int value="2014331873" label="NTPDownloadSuggestions:disabled"/>
   <int value="2014629801" label="view-passwords:disabled"/>
@@ -33548,7 +33977,6 @@
   <int value="2123567684" label="OptimizeLoadingIPCForSmallResources:enabled"/>
   <int value="2126203058" label="force-show-update-menu-badge"/>
   <int value="2129184006" label="NTPOfflinePageDownloadSuggestions:enabled"/>
-  <int value="2129251171" label="memlog-sampling"/>
   <int value="2129929643" label="enable-use-zoom-for-dsf"/>
   <int value="2134480727" label="MediaSessionAccelerators:disabled"/>
   <int value="2135408204" label="OverscrollHistoryNavigation:disabled"/>
@@ -47637,6 +48065,7 @@
   <int value="353" label="POTM"/>
   <int value="354" label="PPSM"/>
   <int value="355" label="PPS"/>
+  <int value="356" label="MOBILECONFIG"/>
 </enum>
 
 <enum name="SBClientDownloadIsSignedBinary">
@@ -53940,6 +54369,11 @@
   <int value="2" label="Forced Full"/>
 </enum>
 
+<enum name="UpdateInteractionSource">
+  <int value="0" label="From Menu"/>
+  <int value="1" label="From Infobar"/>
+</enum>
+
 <enum name="UpdatePasswordSubmissionEvent">
   <int value="0" label="NO_ACCOUNTS_CLICKED_UPDATE"/>
   <int value="1" label="NO_ACCOUNTS_CLICKED_NOPE"/>
@@ -53962,6 +54396,16 @@
   <int value="3" label="AUTO_UPDATES_ONLY"/>
 </enum>
 
+<enum name="UpdateState">
+  <int value="0" label="None"/>
+  <int value="1" label="Update Available"/>
+  <int value="2" label="Unsupported OS Version"/>
+  <int value="3" label="Inline Update Available"/>
+  <int value="4" label="Inline Update Downloading"/>
+  <int value="5" label="Inline Update Ready"/>
+  <int value="6" label="Inline Update Failed"/>
+</enum>
+
 <enum name="UpgradeDetectorRollbackReason">
   <int value="0" label="Channel switch to more stable channel"/>
   <int value="1" label="Admin-initiated enterprise rollback"/>
@@ -55927,6 +56371,13 @@
   <int value="4" label="Abandoned"/>
 </enum>
 
+<enum name="WebAuthenticationRelyingPartySecurityCheckFailure">
+  <int value="0" label="Opaque or Non-Secure Origin"/>
+  <int value="1" label="Relying Party Id Invalid"/>
+  <int value="2" label="AppId Extension Invalid"/>
+  <int value="3" label="AppId Extension Domain Mismatch"/>
+</enum>
+
 <enum name="WebAuthenticationU2FAttestationPromptResult">
   <int value="0" label="Queried"/>
   <int value="1" label="Allowed"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 05ea8200..522cea84 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -539,6 +539,16 @@
   <summary>Tracks usage of all public Windows accessibility APIs.</summary>
 </histogram>
 
+<histogram name="Accessibility.WinAPIs.GetPropertyValue"
+    enum="AccessibilityWinAPIGetPropertyValueEnum" expires_after="2019-09-07">
+  <owner>dmazzoni@chromium.org</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>kbabbitt@microsoft.com</owner>
+  <summary>
+    Tracks properties requested via UI Automation GetPropertyValue().
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.WinAudioDescription" enum="BooleanEnabled"
     expires_after="2019-11-02">
   <owner>dmazzoni@chromium.org</owner>
@@ -3900,6 +3910,16 @@
   </summary>
 </histogram>
 
+<histogram name="Apps.AppListResultClickIndexAndQueryLength">
+  <owner>tby@chromium.org</owner>
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    The index of a clicked result in the search result box and the length of the
+    search query. The index is relative to the SearchResultListView, not the
+    overall position in the suggestion window.
+  </summary>
+</histogram>
+
 <histogram name="Apps.AppListSearchBoxActivated"
     enum="SearchBoxActivationSource">
   <owner>newcomer@chromium.org</owner>
@@ -4033,6 +4053,16 @@
   </summary>
 </histogram>
 
+<histogram name="Apps.AppListTileClickIndexAndQueryLength">
+  <owner>tby@chromium.org</owner>
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    The index of a clicked result in the search result app tiles and the length
+    of the search query. The index is relative to the SearchTileItemListView,
+    not the overall position in the suggestion window.
+  </summary>
+</histogram>
+
 <histogram name="Apps.AppListTimeToDiscover" units="ms">
   <owner>tapted@chromium.org</owner>
   <summary>
@@ -4947,10 +4977,9 @@
 </histogram>
 
 <histogram name="Arc.Supervision.Transition.Result"
-    enum="ArcSupervisionTransitionResult" expires_after="M74">
+    enum="ArcSupervisionTransitionResult" expires_after="M76">
   <owner>menegola@google.com</owner>
   <owner>escordeiro@google.com</owner>
-  <owner>brunokim@google.com</owner>
   <owner>unichromeos-eng@google.com</owner>
   <summary>
     The result (success or the type of failure) of ARC supervision transition
@@ -17818,6 +17847,13 @@
   </summary>
 </histogram>
 
+<histogram name="ContentSuggestions.Feed.InternalError"
+    units="FeedInternalError" expires_after="2020-02-22">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>The Feed library encountered an error at any time.</summary>
+</histogram>
+
 <histogram name="ContentSuggestions.Feed.LoadKeysSuccess" enum="BooleanSuccess"
     expires_after="2019-10-01">
   <owner>gangwu@chromium.org</owner>
@@ -18078,6 +18114,46 @@
   </summary>
 </histogram>
 
+<histogram name="ContentSuggestions.Feed.ServerRequest.Reason"
+    enum="FeedRequestReason" expires_after="2020-02-25">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>
+    The reason for a server request from the Feed's prespective, when the
+    request is started.
+  </summary>
+</histogram>
+
+<histogram name="ContentSuggestions.Feed.TokenCompleted.ContentCount"
+    units="count" expires_after="2020-02-25">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>
+    How many top level features were in the continuation response, typically
+    clusters. Recorded when the fetch completes successfully.
+  </summary>
+</histogram>
+
+<histogram name="ContentSuggestions.Feed.TokenCompleted.TokenCount"
+    units="count" expires_after="2020-02-25">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>
+    How many tokens were in the continuation response, typically ways to fetch
+    more articles. Recorded when the fetch completes successfully.
+  </summary>
+</histogram>
+
+<histogram name="ContentSuggestions.Feed.TokenFailedToCompleted" units="count"
+    expires_after="2020-02-25">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>
+    When a token fails to complete, how many failures this token has seen.
+    Record when the fetch fails.
+  </summary>
+</histogram>
+
 <histogram name="ContentSuggestions.Feed.TokenFetchStatus"
     enum="GoogleServiceAuthError">
   <obsolete>
@@ -18086,10 +18162,40 @@
   <owner>pnoland@chromium.org</owner>
   <summary>
     Android: failure reason when attempting to fetch an OAuth token for the
-    feed. Recorded when a token fetch completes.
+    feed. Recorded when an artcile fetch completes.
   </summary>
 </histogram>
 
+<histogram
+    name="ContentSuggestions.Feed.ZeroStateRefreshCompleted.ContentCount"
+    units="count" expires_after="2020-02-25">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>
+    How many top level features were in an initial article fetch, typically
+    clusters. Recorded when an article fetch completes and were previously in
+    zero state.
+  </summary>
+</histogram>
+
+<histogram name="ContentSuggestions.Feed.ZeroStateRefreshCompleted.TokenCount"
+    units="count" expires_after="2020-02-25">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>
+    How many tokens were in an initial article fetch, typically ways to fetch
+    more articles. Recorded when an article fetch completes and were previously
+    in zero state.
+  </summary>
+</histogram>
+
+<histogram name="ContentSuggestions.Feed.ZeroStateShown.Reason"
+    enum="FeedZeroStateShowReason" expires_after="2020-02-25">
+  <owner>skym@chromium.org</owner>
+  <owner>gangwu@chromium.org</owner>
+  <summary>The reason the zero state (no articles) is shown to a user.</summary>
+</histogram>
+
 <histogram name="ContentSuggestions.FetchPendingPlaceholder.VisibleDuration"
     units="ms">
   <obsolete>
@@ -20491,6 +20597,17 @@
   </summary>
 </histogram>
 
+<histogram name="DataReductionProxy.ConfigService.ConnectionSetupTime"
+    units="ms" expires_after="2019-12-31">
+  <owner>robertogden@chromium.org</owner>
+  <owner>tbansal@chromium.org</owner>
+  <summary>
+    The time delta between connectStart and connectEnd of the client config
+    request. This is recorded only on successful requests when connectStart and
+    connectEnd were non-null.
+  </summary>
+</histogram>
+
 <histogram
     name="DataReductionProxy.ConfigService.FetchFailedAttemptsBeforeSuccess">
   <owner>bengr@chromium.org</owner>
@@ -20523,6 +20640,16 @@
   </summary>
 </histogram>
 
+<histogram name="DataReductionProxy.ConfigService.HttpRequestRTT" units="ms"
+    expires_after="2019-12-31">
+  <owner>robertogden@chromium.org</owner>
+  <owner>tbansal@chromium.org</owner>
+  <summary>
+    The time delta between requestStart and requestEnd of the client config
+    request. This is recorded only on successful requests.
+  </summary>
+</histogram>
+
 <histogram name="DataReductionProxy.ConfigService.HTTPRequests"
     enum="DataReductionProxyConfigServiceHTTPRequests">
   <owner>bengr@chromium.org</owner>
@@ -29111,16 +29238,36 @@
 </histogram>
 
 <histogram name="Event.DownEventCount.PerInputFormFactorDestinationCombination"
-    enum="DownEventInputFormFactorDestinationCombination">
+    enum="DownEventInputFormFactorDestinationCombination" expires_after="M76">
   <owner>tbuckley@chromium.org</owner>
   <summary>
+    Deprecated 02/2019 in favor of
+    Event.DownEventCount.PerInputFormFactorDestinationCombination2.
+
     The number of down events received per input, form factor, and destination
     combination.
 
     Input is down events generated by mouse/touch/stylus. Form factor is down
     events generated by clamshell/touchviewLandscape/touchviewPortrait.
     Destination: Every down event that is targeted to each destination will be
-    counted,including those that don't have an effect. For example: Tapping on a
+    counted including those that don't have an effect. For example: Tapping on a
+    disabled button inside the browser frame will be treated as down events on
+    browser window.
+  </summary>
+</histogram>
+
+<histogram
+    name="Event.DownEventCount.PerInputFormFactorDestinationCombination2"
+    enum="DownEventInputFormFactorDestinationCombination2" expires_after="M76">
+  <owner>tbuckley@chromium.org</owner>
+  <summary>
+    The number of down events received per destination, input and form factor
+    combination.
+
+    Input is down events generated by mouse/touch/stylus. Form factor is down
+    events generated by clamshell/touchviewLandscape/touchviewPortrait.
+    Destination: Every down event that is targeted to each destination will be
+    counted including those that don't have an effect. For example: Tapping on a
     disabled button inside the browser frame will be treated as down events on
     browser window.
   </summary>
@@ -37703,6 +37850,16 @@
   </summary>
 </histogram>
 
+<histogram name="FileBrowser.Recent.LoadCrostini" units="ms"
+    expires_after="M79">
+  <owner>slangley@chromium.org</owner>
+  <owner>weifangsun@chromium.org</owner>
+  <summary>
+    Time to load a recently modified file list from Crostini. It is triggered
+    when the user opens or reloads Recent view in the Files app.
+  </summary>
+</histogram>
+
 <histogram name="FileBrowser.Recent.LoadDownloads" units="ms"
     expires_after="M79">
   <owner>slangley@chromium.org</owner>
@@ -39323,6 +39480,56 @@
   </summary>
 </histogram>
 
+<histogram name="GoogleUpdate.Inline.CallFailure"
+    enum="InlineUpdateCallFailure" expires_after="2019-10-30">
+  <owner>dtrainor@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    (Android-only) Records the instances where Play update API calls failed.
+  </summary>
+</histogram>
+
+<histogram name="GoogleUpdate.Inline.StateChange.Error"
+    enum="InlineUpdateErrorCodes" expires_after="2019-10-30">
+<!-- Name completed by histogram_suffixes name="GoogleUpdate.Inline.InstallStatus" -->
+
+  <owner>dtrainor@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    (Android-only) Records the instances where Play update API notified us of an
+    install error during an update. This is keyed on the specific state so we
+    can tell which states are seeing which errors.
+  </summary>
+</histogram>
+
+<histogram name="GoogleUpdate.Inline.UI.Install.Source"
+    enum="UpdateInteractionSource" expires_after="2019-10-30">
+  <owner>dtrainor@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    (Android-only) The UI component that triggered an inline update to finish
+    and install.
+  </summary>
+</histogram>
+
+<histogram name="GoogleUpdate.Inline.UI.Retry.Source"
+    enum="UpdateInteractionSource" expires_after="2019-10-30">
+  <owner>dtrainor@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    (Android-only) The UI component that triggered an inline update to retry.
+  </summary>
+</histogram>
+
+<histogram name="GoogleUpdate.Inline.UI.Start.Source"
+    enum="UpdateInteractionSource" expires_after="2019-10-30">
+  <owner>dtrainor@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    (Android-only) The UI component that triggered an inline update to start.
+  </summary>
+</histogram>
+
 <histogram name="GoogleUpdate.InstallerExitCode" enum="InstallStatus">
   <owner>grt@chromium.org</owner>
   <summary>
@@ -39352,6 +39559,16 @@
   </summary>
 </histogram>
 
+<histogram name="GoogleUpdate.StartUp.State" enum="UpdateState"
+    expires_after="2019-10-30">
+  <owner>dtrainor@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    (Android-only) The state of any in-progress updates when the process first
+    starts and queries for it.
+  </summary>
+</histogram>
+
 <histogram name="GoogleUpdate.UnexpectedState">
   <owner>grt@chromium.org</owner>
   <summary>
@@ -51857,6 +52074,18 @@
   </summary>
 </histogram>
 
+<histogram name="Media.WebMediaPlayerImpl.HLS.HasAccessControl" enum="Boolean"
+    expires_after="2019-12-31">
+  <owner>sandersd@chromium.org</owner>
+  <owner>tguilbert@chromium.org</owner>
+  <summary>
+    When an HLS manifest is found during loading (on Android only), and the
+    response is CORS cross-origin, this histogram records whether the response
+    included an Access-Control-Allow-Origin header. Such requests are likely to
+    be supported by fetch() if the mode is correctly configured.
+  </summary>
+</histogram>
+
 <histogram name="Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin" enum="Boolean"
     expires_after="2019-12-31">
   <owner>sandersd@chromium.org</owner>
@@ -51883,6 +52112,11 @@
 
 <histogram name="Media.WebMediaPlayerImpl.HLS.WouldTaintOrigin" enum="Boolean"
     expires_after="2019-12-31">
+  <obsolete>
+    Deprecated because an overwhelming majority of pages do not set the
+    crossorigin attribute and as a result most HLS content is counted.
+    Superseded by Media.WebMediaPlayerImpl.HLS.HasAccessControl.
+  </obsolete>
   <owner>sandersd@chromium.org</owner>
   <owner>tguilbert@chromium.org</owner>
   <summary>
@@ -75952,6 +76186,9 @@
 
 <histogram name="OfflinePages.SQLStorage.CreateDirectoryResult"
     enum="PlatformFileError">
+  <obsolete>
+    Removed 3/2019.
+  </obsolete>
   <owner>dimich@chromium.org</owner>
   <summary>
     If the directory to store the SQLite database file does not exist, it is
@@ -79743,6 +79980,21 @@
 </histogram>
 
 <histogram
+    name="PageLoad.Experimental.PaintTiming.LargestContentPaint.AllFrames.ContentType"
+    enum="LargestContentType" expires_after="2019-04-23">
+  <owner>maxlg@chromium.org</owner>
+  <owner>npm@chromium.org</owner>
+  <owner>speed-metrics-dev@chromium.org</owner>
+  <summary>
+    Measures whether the largest content paint, whose timestamp is measured by
+    PageLoad.Experimental.PaintTiming.NavigationToLargestContentPaintAllFrames,
+    comes from text or image. This value is recorded whenever
+    PageLoad.Experimental.PaintTiming.NavigationToLargestContentPaintAllFrames
+    is recorded.
+  </summary>
+</histogram>
+
+<histogram
     name="PageLoad.Experimental.PaintTiming.LargestContentPaint.ContentType"
     enum="LargestContentType" expires_after="2019-04-23">
   <owner>maxlg@chromium.org</owner>
@@ -79786,6 +80038,22 @@
 </histogram>
 
 <histogram
+    name="PageLoad.Experimental.PaintTiming.NavigationToLargestContentPaintAllFrames"
+    units="ms" expires_after="2019-04-23">
+  <owner>maxlg@chromium.org</owner>
+  <owner>speed-metrics-dev@chromium.org</owner>
+  <summary>
+    Measures the time from navigation timing's navigation start to the time the
+    largest content (text or image) is first painted, for main frame documents.
+    Excludes any content painted after user input. The value is recorded at the
+    end of each page load unless there is an abort or user input before text or
+    image paint. Compared with NavigationToLargestContentPaint, this is the
+    aggregate results from all frames, while NavigationToLargestContentPaint is
+    only for main frame. See http://bit.ly/fcp_plus_plus for details.
+  </summary>
+</histogram>
+
+<histogram
     name="PageLoad.Experimental.PaintTiming.NavigationToLargestImagePaint"
     units="ms" expires_after="2019-04-23">
   <owner>maxlg@chromium.org</owner>
@@ -86792,6 +87060,40 @@
   </summary>
 </histogram>
 
+<histogram name="Platform.ZramIncompressiblePages" units="pages"
+    expires_after="2020-03-06">
+  <owner>asavery@chromium.org</owner>
+  <owner>gwendal@chromium.org</owner>
+  <summary>
+    Number of incompressible pages stored in zram. A large number suggests lower
+    compression effectiveness. Snapshot every 30s.
+  </summary>
+</histogram>
+
+<histogram name="Platform.ZramIncompressibleRatioPercent.PostCompression"
+    units="%" expires_after="2020-03-06">
+  <owner>asavery@chromium.org</owner>
+  <owner>gwendal@chromium.org</owner>
+  <summary>
+    The fraction of compressed memory that consists of incompressible pages. We
+    express this as a percentage (between 0% and 100%). Values close to 100%
+    mean we are not able to effectively benefit from compression. Snapshot every
+    30s.
+  </summary>
+</histogram>
+
+<histogram name="Platform.ZramIncompressibleRatioPercent.PreCompression"
+    units="%" expires_after="2020-03-06">
+  <owner>asavery@chromium.org</owner>
+  <owner>gwendal@chromium.org</owner>
+  <summary>
+    The fraction of the uncompressed memory size that consists of incompressible
+    pages. We express this as a percentage (between 0% and 100%). Values close
+    to 100% mean we are not able to effectively benefit from compression.
+    Snapshot every 30s.
+  </summary>
+</histogram>
+
 <histogram name="Platform.ZramSavings" units="MB">
   <owner>semenzato@google.com</owner>
   <summary>
@@ -93190,8 +93492,10 @@
   </summary>
 </histogram>
 
-<histogram name="Quota.ErrorsOnEvictingOriginPerHour"
-    expires_after="2018-08-30">
+<histogram name="Quota.ErrorsOnEvictingOriginPerHour">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>
     Number of errors on evicting origin by QuotaTemporaryStorageEvictor in an
@@ -93199,8 +93503,10 @@
   </summary>
 </histogram>
 
-<histogram name="Quota.ErrorsOnGettingUsageAndQuotaPerHour"
-    expires_after="2018-08-30">
+<histogram name="Quota.ErrorsOnGettingUsageAndQuotaPerHour">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>
     Number of errors on getting usage and quota by QuotaTemporaryStorageEvictor
@@ -93296,38 +93602,50 @@
   <summary>Number of evicted origins per round.</summary>
 </histogram>
 
-<histogram name="Quota.NumberOfPersistentStorageOrigins"
-    expires_after="2018-08-30">
+<histogram name="Quota.NumberOfPersistentStorageOrigins">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>Number of origins using persistent storage.</summary>
 </histogram>
 
-<histogram name="Quota.NumberOfProtectedPersistentStorageOrigins"
-    expires_after="2018-08-30">
+<histogram name="Quota.NumberOfProtectedPersistentStorageOrigins">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>Number of protected origins using persistent storage.</summary>
 </histogram>
 
-<histogram name="Quota.NumberOfProtectedTemporaryStorageOrigins"
-    expires_after="2018-08-30">
+<histogram name="Quota.NumberOfProtectedTemporaryStorageOrigins">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>Number of protected origins using temporary storage.</summary>
 </histogram>
 
-<histogram name="Quota.NumberOfTemporaryStorageOrigins"
-    expires_after="2018-08-30">
+<histogram name="Quota.NumberOfTemporaryStorageOrigins">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>Number of origins using temporary storage.</summary>
 </histogram>
 
-<histogram name="Quota.NumberOfUnlimitedPersistentStorageOrigins"
-    expires_after="2018-08-30">
+<histogram name="Quota.NumberOfUnlimitedPersistentStorageOrigins">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>Number of unlimited origins using persistent storage.</summary>
 </histogram>
 
-<histogram name="Quota.NumberOfUnlimitedTemporaryStorageOrigins"
-    expires_after="2018-08-30">
+<histogram name="Quota.NumberOfUnlimitedTemporaryStorageOrigins">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M74.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>Number of unlimited origins using temporary storage.</summary>
 </histogram>
@@ -93443,8 +93761,10 @@
   </summary>
 </histogram>
 
-<histogram name="Quota.UsageOverageOfTemporaryGlobalStorage" units="MB"
-    expires_after="2018-08-30">
+<histogram name="Quota.UsageOverageOfTemporaryGlobalStorage" units="MB">
+  <obsolete>
+    Expired on 2018-08-30. Removed in M75.
+  </obsolete>
   <owner>tzik@chromium.org</owner>
   <summary>
     Overage of the temporary global storage usage at beginning of an eviction
@@ -128260,6 +128580,40 @@
   </summary>
 </histogram>
 
+<histogram name="WebAuthentication.CredentialRequestAllowCredentialsCount"
+    units="credentials" expires_after="2019-12-31">
+  <owner>kenrb@chromium.org</owner>
+  <owner>kpaulhamus@chromium.org</owner>
+  <summary>
+    When a relying party is attempting to authenticate a user using the
+    WebAuthentication API, this metric reports the number of valid credentials
+    that the RP has registered for the user.
+  </summary>
+</histogram>
+
+<histogram name="WebAuthentication.MakeCredentialExcludeCredentialsCount"
+    units="credentials" expires_after="2019-12-31">
+  <owner>kenrb@chromium.org</owner>
+  <summary>
+    When a relying party is attempting to register a credential for a new user
+    using the WebAuthentication API, this metric reports the number of existing
+    credentials already registered in order to prevent re-registration.
+  </summary>
+</histogram>
+
+<histogram name="WebAuthentication.RelyingPartySecurityCheckFailure"
+    enum="WebAuthenticationRelyingPartySecurityCheckFailure"
+    expires_after="2019-12-31">
+  <owner>kenrb@chromium.org</owner>
+  <summary>
+    Records failures associated with verifying the relying party origin when
+    that relying party is attempting to make a credential or get an assertion
+    using the WebAuthentication API. Failures include when the relying party's
+    origin is opaque or non-secure, or when the caller-provided relying party ID
+    or app ID is not valid for this origin.
+  </summary>
+</histogram>
+
 <histogram name="WebAuthentication.U2FAttestationPromptResult"
     enum="WebAuthenticationU2FAttestationPromptResult">
   <owner>agl@chromium.org</owner>
@@ -137796,6 +138150,18 @@
   <affected-histogram name="Favicons.DownloadAttempts"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="FeedIsSynthetic" separator="." ordering="suffix">
+  <suffix name="NotSynthetic"
+      label="Continuations that require making remote requests to fetch more
+             articles."/>
+  <suffix name="Synthetic"
+      label="Continuations generated by the Feed library."/>
+  <affected-histogram
+      name="ContentSuggestions.Feed.TokenCompleted.ContentCount"/>
+  <affected-histogram name="ContentSuggestions.Feed.TokenCompleted.TokenCount"/>
+  <affected-histogram name="ContentSuggestions.Feed.TokenFailedToCompleted"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="FeedOrHostOlder" separator=".">
   <suffix name="FeedIsOlder"/>
   <suffix name="HostIsOlder"/>
@@ -138106,6 +138472,20 @@
   <affected-histogram name="GoogleSearch.AccessPoint"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="GoogleUpdate.Inline.InstallStatus" separator=".">
+  <suffix name="Canceled" label="Canceled"/>
+  <suffix name="Downloaded" label="Downloaded"/>
+  <suffix name="Downloading" label="Downloading"/>
+  <suffix name="Failed" label="Failed"/>
+  <suffix name="Installed" label="Installed"/>
+  <suffix name="Installing" label="Installing"/>
+  <suffix name="Pending" label="Pending"/>
+  <suffix name="RequiresUiIntent" label="Requires UI Intent"/>
+  <suffix name="Unknown" label="Unknown"/>
+  <suffix name="Untracked" label="Untracked Status (Not Known to UMA)"/>
+  <affected-histogram name="GoogleUpdate.Inline.StateChange.Error"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="GPU.ContextType" separator=".">
   <suffix name="GLES" label="GLES Context."/>
   <suffix name="WebGL" label="WebGL Context."/>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index d1e3be0..478d407 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -3825,6 +3825,17 @@
       image) within viewport. See http://bit.ly/fcp_plus_plus for more details.
     </summary>
   </metric>
+  <metric
+      name="Experimental.PaintTiming.NavigationToLargestContentPaintAllFrames">
+    <summary>
+      Measures the time in milliseconds from navigation timing's navigation
+      start to the time when the page first paints the largest content (text or
+      image) within viewport. Compared with NavigationToLargestContentPaint,
+      this is the aggregate results from all frames, while
+      NavigationToLargestContentPaint is only for main frame. See
+      http://bit.ly/fcp_plus_plus for more details.
+    </summary>
+  </metric>
   <metric name="Experimental.PaintTiming.NavigationToLargestImagePaint">
     <summary>
       Measures the time in milliseconds from navigation timing's navigation
diff --git a/tools/perf/cli_tools/soundwave/commands.py b/tools/perf/cli_tools/soundwave/commands.py
index 61e9cc7..b6f8522a 100644
--- a/tools/perf/cli_tools/soundwave/commands.py
+++ b/tools/perf/cli_tools/soundwave/commands.py
@@ -4,7 +4,10 @@
 
 import json
 import logging
-import sqlite3
+try:
+  import sqlite3
+except ImportError:
+  pass
 
 from core import cli_utils
 from core.external_modules import pandas
diff --git a/tools/perf/cli_tools/soundwave/pandas_sqlite_test.py b/tools/perf/cli_tools/soundwave/pandas_sqlite_test.py
index 648f420..8fdc19ff 100644
--- a/tools/perf/cli_tools/soundwave/pandas_sqlite_test.py
+++ b/tools/perf/cli_tools/soundwave/pandas_sqlite_test.py
@@ -2,7 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import sqlite3
+try:
+  import sqlite3
+except ImportError:
+  pass
 import unittest
 
 from cli_tools.soundwave import pandas_sqlite
diff --git a/tools/perf/cli_tools/soundwave/tables/__init__.py b/tools/perf/cli_tools/soundwave/tables/__init__.py
index e8528f3..44705e1 100644
--- a/tools/perf/cli_tools/soundwave/tables/__init__.py
+++ b/tools/perf/cli_tools/soundwave/tables/__init__.py
@@ -5,7 +5,10 @@
 import contextlib
 import os
 
-import sqlite3
+try:
+  import sqlite3
+except ImportError:
+  pass
 
 from cli_tools.soundwave import pandas_sqlite
 from cli_tools.soundwave.tables import alerts
diff --git a/tools/perf/cli_tools/soundwave/worker_pool_test.py b/tools/perf/cli_tools/soundwave/worker_pool_test.py
index 47197b3d..3e806fe 100644
--- a/tools/perf/cli_tools/soundwave/worker_pool_test.py
+++ b/tools/perf/cli_tools/soundwave/worker_pool_test.py
@@ -5,7 +5,10 @@
 import argparse
 import os
 import shutil
-import sqlite3
+try:
+  import sqlite3
+except ImportError:
+  pass
 import tempfile
 import unittest
 
diff --git a/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py b/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py
index 489c754..ba64cc5 100644
--- a/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py
+++ b/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py
@@ -20,13 +20,17 @@
   def setUp(self):
     self.maxDiff = None
 
+    # TODO(crbug.com/938487): This pattern of mocking leads to double-mocked
+    # functions (when inside a test case we mock something that has already been
+    # mocked), which cannot be reliably unmocked by calling
+    # mock.patch.stopall(), so then the mock leaks to other test cases run
+    # later and breaks them.
     self._check_log = mock.patch('core.cli_helpers.CheckLog').start()
     self._run = mock.patch('core.cli_helpers.Run').start()
     self._check_output = mock.patch('subprocess.check_output').start()
     self._check_call = mock.patch('subprocess.check_call').start()
     self._info = mock.patch('core.cli_helpers.Info').start()
     self._comment = mock.patch('core.cli_helpers.Comment').start()
-    self._ask = mock.patch('core.cli_helpers.Ask').start()
     self._open = mock.patch('__builtin__.open').start()
     datetime = mock.patch('datetime.datetime').start()
     datetime.now.return_value.strftime.return_value = '<tstamp>'
diff --git a/tools/perf/contrib/network_service/OWNERS b/tools/perf/contrib/network_service/OWNERS
deleted file mode 100644
index c97b0ac..0000000
--- a/tools/perf/contrib/network_service/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-juncai@chromium.org
-
-file://services/network/OWNERS
diff --git a/tools/perf/contrib/network_service/__init__.py b/tools/perf/contrib/network_service/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tools/perf/contrib/network_service/__init__.py
+++ /dev/null
diff --git a/tools/perf/contrib/network_service/loading.py b/tools/perf/contrib/network_service/loading.py
deleted file mode 100644
index daa37c0..0000000
--- a/tools/perf/contrib/network_service/loading.py
+++ /dev/null
@@ -1,238 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import copy
-import json
-import logging
-import os
-import traceback
-
-from benchmarks import loading
-
-from telemetry import benchmark
-from telemetry.internal import story_runner
-from telemetry.value import none_values
-from telemetry.value.list_of_scalar_values import StandardDeviation
-
-from tracing.value import convert_chart_json
-
-def _ListSubtraction(diff_list, control_list):
-  """Subtract |control_list|'s elements from the corresponding elements in
-  |diff_list|, and store the results in |diff_list|.
-
-  Lists may have different length and we will align with the shorter one.
-  e.g. 'timeToInteractive' may be missing for some runs.
-  """
-
-  min_len = min(len(diff_list), len(control_list))
-  for i in xrange(min_len):
-    diff_list[i] = diff_list[i] - control_list[i]
-  while len(diff_list) > min_len:
-    diff_list.pop()
-
-def _PointSubtraction(diff_point, control_point):
-  """Subtract |control_point| from |diff_point| and store the result in
-  |diff_point|.
-
-  Args:
-    diff_point: A chart point (could either be a story point (e.g. 'FIFA') or a
-      'summary' point), will hold the result.
-    control_point: A chart point.
-  """
-
-  if diff_point['type'] == 'scalar':
-    diff_point['value'] = diff_point['value'] - control_point['value']
-  elif diff_point['type'] == 'list_of_scalar_values':
-    # Points may have None 'values' regardless their types.
-    if not diff_point['values'] or not control_point['values']:
-      none_value_reason = (
-          none_values.MERGE_FAILURE_REASON +
-          ' None values: %s' % repr([diff_point, control_point]))
-      diff_point['values'] = None
-      diff_point['std'] = None
-      diff_point['none_value_reason'] = none_value_reason
-      return
-    _ListSubtraction(diff_point['values'], control_point['values'])
-    diff_point['std'] = StandardDeviation(diff_point['values'])
-  else:
-    raise NotImplementedError('invalid point type: %s' % diff_point['type'])
-
-def _RenameChartsAndPointsWithSuffix(charts, suffix):
-  """Append |suffix| to all chart names (except 'trace') and point names
-  (except 'summary').
-
-  Args:
-    charts: A dictionary of charts.
-    suffix: A string suffix, e.g. '_control'.
-  """
-
-  # First rename all points except 'summary.
-  for chart_name in charts:
-    chart = charts[chart_name]
-    old_point_names = chart.keys()
-    for point_name in old_point_names:
-      if point_name == 'summary':
-        continue
-      chart[point_name + suffix] = chart[point_name]
-      chart.pop(point_name, None)
-
-  # Then rename all charts except 'trace'.
-  old_chart_names = charts.keys()
-  for chart_name in old_chart_names:
-    if chart_name == 'trace':
-      continue
-    chart = charts[chart_name]
-    # Before adding the |suffix|, clean up |chart_name| by removing '@@' and
-    # any characters before it.
-    new_chart_name = chart_name.split('@@', 1)[-1] + suffix
-    for point_name in chart:
-      chart[point_name]['name'] = new_chart_name
-    charts[new_chart_name] = chart
-    charts.pop(chart_name, None)
-
-def _MergeCharts(dest_charts, source_charts):
-  """Update |dest_charts| with |source_charts| and merge 'trace'.
-
-  Args:
-    dest_charts: A dictionary of charts, will hold the result.
-    source_charts: A dictionary of charts.
-  """
-
-  for chart_name in source_charts:
-    if chart_name == 'trace':
-      if chart_name not in dest_charts:
-        dest_charts[chart_name] = {}
-      dest_charts[chart_name].update(source_charts[chart_name])
-    else:
-      dest_charts[chart_name] = source_charts[chart_name]
-
-def _MergeControlChartJsonIntoEnabled(enabled_chart_json, control_chart_json):
-  """Creates a diff chart_json from |enabled_chart_json| and
-  |control_chart_json|, then append appropriated suffix to all three chart_json
-  and merge them into |enabled_chart_json|.
-
-  Args:
-    enabled_chart_json: A dictionary of charts, will hold the result..
-    control_chart_json: A dictionary of charts.
-  """
-
-  # Leaving fields as-is other than 'charts'
-  enabled_charts = enabled_chart_json['charts']
-  control_charts = control_chart_json['charts']
-  diff_charts = copy.deepcopy(enabled_charts)
-  diff_charts['trace'] = {}
-  for chart_name in diff_charts.keys():
-    if chart_name not in control_charts:
-      # Charts like 'timeToInteractive_std' may not be there if all values are
-      # None.
-      del diff_charts[chart_name]
-      continue
-    for point_name in diff_charts[chart_name].keys():
-      if point_name not in control_charts[chart_name]:
-        del diff_charts[chart_name][point_name]
-        continue
-      _PointSubtraction(diff_charts[chart_name][point_name],
-                        control_charts[chart_name][point_name])
-
-  _RenameChartsAndPointsWithSuffix(enabled_charts, '_enabled')
-  _RenameChartsAndPointsWithSuffix(control_charts, '_control')
-  _RenameChartsAndPointsWithSuffix(diff_charts, '_diff')
-  _MergeCharts(enabled_charts, control_charts)
-  _MergeCharts(enabled_charts, diff_charts)
-
-@benchmark.Info(emails=['juncai@chromium.org'])
-class LoadingDesktopNetworkService(loading.LoadingDesktop):
-  """Measures loading performance of desktop sites, with the network service
-  enabled.
-
-  Will run the test twice with feature on/off, and return the
-  difference as well as the original results.
-  """
-
-  def __init__(self, max_failures=None):
-    super(LoadingDesktopNetworkService, self).__init__(max_failures)
-    self.enable_feature = False
-
-  @classmethod
-  def Name(cls):
-    return 'loading.desktop.network_service'
-
-  def Run(self, finder_options):
-    """We shouldn't be overriding this according to
-    telemetry.benchmark.Benchmark"""
-    assert 'histograms' in finder_options.output_formats, (
-      'loading.desktop.network_service requires --output-format=histograms.')
-
-    # feed the story_runner with 'chartjson' output formats.
-    # TODO(https://crbug.com/929765): Make loading.desktop.network_service
-    # benchmark produce histograms natively.
-    while 'histograms' in finder_options.output_formats:
-      finder_options.output_formats.remove('histograms')
-    finder_options.output_formats.append('chartjson')
-
-    assert finder_options.output_dir
-    output_dir = finder_options.output_dir
-    temp_file_path = os.path.join(output_dir, 'results-chart.json')
-
-    # Run test with feature disabled.
-    self.enable_feature = False
-    control_return_code = story_runner.RunBenchmark(self, finder_options)
-    if control_return_code != 0:
-      return control_return_code
-    control_chart_json = json.load(open(temp_file_path))
-
-    # Run test again with feature enabled.
-    self.enable_feature = True
-    enabled_return_code = story_runner.RunBenchmark(self, finder_options)
-    if enabled_return_code != 0:
-      return enabled_return_code
-    enabled_chart_json = json.load(open(temp_file_path))
-
-    logging.info('Starting to merge control chartjson into enabled chartjson')
-    try:
-      # Merge the result and compute the difference.
-      _MergeControlChartJsonIntoEnabled(enabled_chart_json, control_chart_json)
-    except Exception as e:
-      logging.error('exception merging two chart json: %s', repr(e))
-      traceback.print_exc()
-      with open(temp_file_path, 'w') as f:
-        json.dump({
-          'control_chart_json': control_chart_json,
-          'enabled_chart_json': enabled_chart_json},
-          f, indent=2, separators=(',', ': '))
-        f.write('\n')
-        return 1
-    else:
-      logging.info('Finished merging chartjsons, writing back to disk')
-      with open(temp_file_path, 'w') as f:
-        json.dump(enabled_chart_json, f, indent=2, separators=(',', ': '))
-        f.write('\n')
-      logging.info('Converting chartjsons to histograms')
-      histogram_result = convert_chart_json.ConvertChartJson(temp_file_path)
-      if histogram_result.returncode != 0:
-        logging.error('Error converting chart json to Histograms:\n' +
-            histogram_result.stdout)
-        return 1
-
-      temp_file_path = os.path.join(output_dir, 'histograms.json')
-      with open(temp_file_path, 'w') as f:
-        f.write(histogram_result.stdout)
-
-    return 0
-
-  def SetExtraBrowserOptions(self, options):
-    if not self.enable_feature:
-      return
-
-    enable_features_arg = '--enable-features=NetworkService'
-
-    # If an "--enable-features" argument has been specified, append to the value
-    # list of that argument.
-    for arg in options.extra_browser_args:
-      if arg.startswith('--enable-features='):
-        options.extra_browser_args.remove(arg)
-        enable_features_arg = arg + ',NetworkService'
-        break
-
-    options.AppendExtraBrowserArgs([enable_features_arg])
diff --git a/tools/perf/core/cli_helpers_unittest.py b/tools/perf/core/cli_helpers_unittest.py
index e8f3754..c187f4fa 100644
--- a/tools/perf/core/cli_helpers_unittest.py
+++ b/tools/perf/core/cli_helpers_unittest.py
@@ -17,38 +17,28 @@
       cli_helpers.Colored('message', 'pink')
 
   @mock.patch('__builtin__.print')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testPrintsInfo(self, print_mock):
     cli_helpers.Info('foo {sval} {ival}', sval='s', ival=42)
     print_mock.assert_called_once_with('foo s 42')
 
   @mock.patch('__builtin__.print')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testPrintsComment(self, print_mock):
     cli_helpers.Comment('foo')
     print_mock.assert_called_once_with('\033[93mfoo\033[0m')
 
   @mock.patch('__builtin__.print')
   @mock.patch('sys.exit')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testFatal(self, sys_exit_mock, print_mock):
     cli_helpers.Fatal('foo')
     print_mock.assert_called_once_with('\033[91mfoo\033[0m')
     sys_exit_mock.assert_called_once()
 
   @mock.patch('__builtin__.print')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testPrintsError(self, print_mock):
     cli_helpers.Error('foo')
     print_mock.assert_called_once_with('\033[91mfoo\033[0m')
 
   @mock.patch('__builtin__.print')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testPrintsStep(self, print_mock):
     long_step_name = 'foobar' * 15
     cli_helpers.Step(long_step_name)
@@ -60,8 +50,8 @@
 
   @mock.patch('__builtin__.print')
   @mock.patch('__builtin__.raw_input')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
+  # https://crbug.com/938575.
+  @decorators.Disabled('chromeos')
   def testAskAgainOnInvalidAnswer(self, raw_input_mock, print_mock):
     raw_input_mock.side_effect = ['foobar', 'y']
     self.assertTrue(cli_helpers.Ask('Ready?'))
@@ -73,8 +63,8 @@
 
   @mock.patch('__builtin__.print')
   @mock.patch('__builtin__.raw_input')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
+  # https://crbug.com/938575.
+  @decorators.Disabled('chromeos')
   def testAskWithCustomAnswersAndDefault(self, raw_input_mock, print_mock):
     raw_input_mock.side_effect = ['']
     self.assertFalse(
@@ -84,8 +74,8 @@
 
   @mock.patch('__builtin__.print')
   @mock.patch('__builtin__.raw_input')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
+  # https://crbug.com/938575.
+  @decorators.Disabled('chromeos')
   def testAskNoDefaultCustomAnswersAsList(self, raw_input_mock, print_mock):
     raw_input_mock.side_effect = ['', 'FoO']
     self.assertEqual(cli_helpers.Ask('Ready?', ['foo', 'bar']), 'foo')
@@ -95,8 +85,6 @@
       mock.call('\033[96mReady? [foo/bar] \033[0m', end=' ')
     ])
 
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testAskWithInvalidDefaultAnswer(self):
     with self.assertRaises(ValueError):
       cli_helpers.Ask('Ready?', ['foo', 'bar'], 'baz')
@@ -105,8 +93,6 @@
   @mock.patch('subprocess.check_call')
   @mock.patch('__builtin__.open')
   @mock.patch('datetime.datetime')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testCheckLog(
       self, dt_mock, open_mock, check_call_mock, print_mock):
     file_mock = mock.Mock()
@@ -133,8 +119,6 @@
   @mock.patch('subprocess.check_call')
   @mock.patch('subprocess.call')
   @mock.patch('__builtin__.open')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testCheckLogError(
       self, open_mock, call_mock, check_call_mock, error_mock, print_mock):
     del print_mock, open_mock  # Unused.
@@ -153,8 +137,6 @@
 
   @mock.patch('__builtin__.print')
   @mock.patch('subprocess.check_call')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testRun(self, check_call_mock, print_mock):
     check_call_mock.side_effect = [subprocess.CalledProcessError(87, ['cmd'])]
     with self.assertRaises(subprocess.CalledProcessError):
@@ -165,23 +147,17 @@
 
   @mock.patch('__builtin__.print')
   @mock.patch('subprocess.check_call')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testRunOkFail(self, check_call_mock, print_mock):
     del print_mock  # Unused.
     check_call_mock.side_effect = [subprocess.CalledProcessError(87, ['cmd'])]
     cli_helpers.Run(['cmd'], ok_fail=True)
 
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testRunWithNonListCommand(self):
     with self.assertRaises(ValueError):
       cli_helpers.Run('cmd with args')
 
   @mock.patch('__builtin__.print')
   @mock.patch('__builtin__.raw_input')
-  # https://crbug.com/938487
-  @decorators.Disabled('all')
   def testPrompt(self, raw_input_mock, print_mock):
     raw_input_mock.side_effect = ['', '42']
     self.assertEqual(cli_helpers.Prompt(
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 276756c1..eba2829 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -195,13 +195,9 @@
 [ All ] rendering.mobile/paper_shadow [ Skip ] # Polymer test, needs to be modernized.
 [ All ] rendering.mobile/paper_tabs [ Skip ] # Polymer test, needs to be modernized.
 [ All ] rendering.mobile/paper_toggle_button [ Skip ] # Polymer test, needs to be modernized.
-crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_15000_pixels_per_second [ Skip ]
 crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_20000_pixels_per_second [ Skip ]
 crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_40000_pixels_per_second [ Skip ]
-crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_50000_pixels_per_second [ Skip ]
 crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_10000_pixels_per_second [ Skip ]
-crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_05000_pixels_per_second [ Skip ]
-crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_30000_pixels_per_second [ Skip ]
 crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_75000_pixels_per_second [ Skip ]
 crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_60000_pixels_per_second [ Skip ]
 crbug.com/785473 [ Android_Webview ] rendering.mobile/canvas_90000_pixels_per_second [ Skip ]
@@ -414,22 +410,6 @@
 
 
 ##### Perf FYI benchmarks go after here #####
-# Benchmark: loading.desktop.network_service
-crbug.com/752611 [ Linux ] loading.desktop.network_service/uol.com.br_cold [ Skip ]
-crbug.com/752611 [ Linux ] loading.desktop.network_service/uol.com.br_warm [ Skip ]
-crbug.com/851171 [ Linux ] loading.desktop.network_service/Elmundo_cold [ Skip ]
-crbug.com/851171 [ Linux ] loading.desktop.network_service/Elmundo_warm [ Skip ]
-crbug.com/853835 [ Linux ] loading.desktop.network_service/AirBnB_cold [ Skip ]
-crbug.com/853835 [ Linux ] loading.desktop.network_service/AirBnB_warm [ Skip ]
-crbug.com/853835 [ Linux ] loading.desktop.network_service/Kenh14_cold [ Skip ]
-crbug.com/853835 [ Linux ] loading.desktop.network_service/Kenh14_warm [ Skip ]
-crbug.com/853835 [ Linux ] loading.desktop.network_service/Taobao_cold [ Skip ]
-crbug.com/853835 [ Linux ] loading.desktop.network_service/Taobao_warm [ Skip ]
-crbug.com/879833 [ Linux ] loading.desktop.network_service/Walgreens_cold [ Skip ]
-crbug.com/879833 [ Linux ] loading.desktop.network_service/Walgreens_warm [ Skip ]
-crbug.com/927758 [ Desktop ] loading.desktop.network_service/TheOnion_cold [ Skip ]
-crbug.com/927758 [ Desktop ] loading.desktop.network_service/TheOnion_warm [ Skip ]
-
 # Benchmark: loading.desktop_layout_ng
 crbug.com/879833 [ Linux ] loading.desktop_layout_ng/Walgreens_cold [ Skip ]
 crbug.com/879833 [ Linux ] loading.desktop_layout_ng/Walgreens_warm [ Skip ]
diff --git a/tools/perf/page_sets/rendering/tough_scrolling_cases.py b/tools/perf/page_sets/rendering/tough_scrolling_cases.py
index faeba04f..38919040 100644
--- a/tools/perf/page_sets/rendering/tough_scrolling_cases.py
+++ b/tools/perf/page_sets/rendering/tough_scrolling_cases.py
@@ -45,36 +45,18 @@
   SPEED_IN_PIXELS_PER_SECOND = 10000
 
 
-class ScrollingText15000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_15000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text.html'
-  SPEED_IN_PIXELS_PER_SECOND = 15000
-
-
 class ScrollingText20000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_20000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text.html'
   SPEED_IN_PIXELS_PER_SECOND = 20000
 
 
-class ScrollingText30000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_30000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text.html'
-  SPEED_IN_PIXELS_PER_SECOND = 30000
-
-
 class ScrollingText40000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_40000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text.html'
   SPEED_IN_PIXELS_PER_SECOND = 40000
 
 
-class ScrollingText50000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_50000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text.html'
-  SPEED_IN_PIXELS_PER_SECOND = 50000
-
-
 class ScrollingText60000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_60000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text.html'
@@ -107,13 +89,6 @@
   SYNTHETIC_GESTURE_SOURCE = page_action.GESTURE_SOURCE_MOUSE
 
 
-class ScrollingTextHover15000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_hover_15000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text_hover.html'
-  SPEED_IN_PIXELS_PER_SECOND = 15000
-  SYNTHETIC_GESTURE_SOURCE = page_action.GESTURE_SOURCE_MOUSE
-
-
 class ScrollingTextHover20000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_hover_20000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text_hover.html'
@@ -121,13 +96,6 @@
   SYNTHETIC_GESTURE_SOURCE = page_action.GESTURE_SOURCE_MOUSE
 
 
-class ScrollingTextHover30000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_hover_30000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text_hover.html'
-  SPEED_IN_PIXELS_PER_SECOND = 30000
-  SYNTHETIC_GESTURE_SOURCE = page_action.GESTURE_SOURCE_MOUSE
-
-
 class ScrollingTextHover40000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_hover_40000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text_hover.html'
@@ -135,13 +103,6 @@
   SYNTHETIC_GESTURE_SOURCE = page_action.GESTURE_SOURCE_MOUSE
 
 
-class ScrollingTextHover50000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_hover_50000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text_hover.html'
-  SPEED_IN_PIXELS_PER_SECOND = 50000
-  SYNTHETIC_GESTURE_SOURCE = page_action.GESTURE_SOURCE_MOUSE
-
-
 class ScrollingTextHover60000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_hover_60000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text_hover.html'
@@ -175,36 +136,18 @@
   SPEED_IN_PIXELS_PER_SECOND = 10000
 
 
-class ScrollingTextRaster15000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_constant_full_page_raster_15000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text_constant_full_page_raster.html'
-  SPEED_IN_PIXELS_PER_SECOND = 15000
-
-
 class ScrollingTextRaster20000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_constant_full_page_raster_20000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text_constant_full_page_raster.html'
   SPEED_IN_PIXELS_PER_SECOND = 20000
 
 
-class ScrollingTextRaster30000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_constant_full_page_raster_30000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text_constant_full_page_raster.html'
-  SPEED_IN_PIXELS_PER_SECOND = 30000
-
-
 class ScrollingTextRaster40000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_constant_full_page_raster_40000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text_constant_full_page_raster.html'
   SPEED_IN_PIXELS_PER_SECOND = 40000
 
 
-class ScrollingTextRaster50000Page(ToughFastScrollingPage):
-  BASE_NAME = 'text_constant_full_page_raster_50000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/text_constant_full_page_raster.html'
-  SPEED_IN_PIXELS_PER_SECOND = 50000
-
-
 class ScrollingTextRaster60000Page(ToughFastScrollingPage):
   BASE_NAME = 'text_constant_full_page_raster_60000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text_constant_full_page_raster.html'
@@ -235,36 +178,18 @@
   SPEED_IN_PIXELS_PER_SECOND = 10000
 
 
-class ScrollingCanvas15000Page(ToughFastScrollingPage):
-  BASE_NAME = 'canvas_15000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/canvas.html'
-  SPEED_IN_PIXELS_PER_SECOND = 15000
-
-
 class ScrollingCanvas20000Page(ToughFastScrollingPage):
   BASE_NAME = 'canvas_20000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 20000
 
 
-class ScrollingCanvas30000Page(ToughFastScrollingPage):
-  BASE_NAME = 'canvas_30000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/canvas.html'
-  SPEED_IN_PIXELS_PER_SECOND = 30000
-
-
 class ScrollingCanvas40000Page(ToughFastScrollingPage):
   BASE_NAME = 'canvas_40000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 40000
 
 
-class ScrollingCanvas50000Page(ToughFastScrollingPage):
-  BASE_NAME = 'canvas_50000_pixels_per_second'
-  URL = 'file://../tough_scrolling_cases/canvas.html'
-  SPEED_IN_PIXELS_PER_SECOND = 50000
-
-
 class ScrollingCanvas60000Page(ToughFastScrollingPage):
   BASE_NAME = 'canvas_60000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
diff --git a/tools/perf/process_perf_results.py b/tools/perf/process_perf_results.py
index a4e2aed..ab58f74 100755
--- a/tools/perf/process_perf_results.py
+++ b/tools/perf/process_perf_results.py
@@ -300,15 +300,16 @@
       f for f in listdir(task_output_dir)
       if not isfile(join(task_output_dir, f))
   ]
+
   benchmark_directory_list = []
   benchmarks_shard_map_file = None
   for directory in directory_list:
     for f in listdir(join(task_output_dir, directory)):
       path = join(task_output_dir, directory, f)
-      if path.endswith('benchmarks_shard_map.json'):
-        benchmarks_shard_map_file = path
-      else:
+      if os.path.isdir(path):
         benchmark_directory_list.append(path)
+      elif path.endswith('benchmarks_shard_map.json'):
+        benchmarks_shard_map_file = path
 
   # Now create a map of benchmark name to the list of directories
   # the lists were written to.
@@ -492,7 +493,7 @@
           benchmark_name, directories, configuration_name,
           build_properties, output_json_file, service_account_file))
 
-    # Kick off the uploads in mutliple processes
+    # Kick off the uploads in multiple processes
     pool = mp.Pool()
     try:
       async_result = pool.map_async(
@@ -500,10 +501,11 @@
       results = async_result.get(timeout=2000)
     except mp.TimeoutError:
       logging.error('Failed uploading benchmarks to perf dashboard in parallel')
-      pool.terminate()
       results = []
       for benchmark_name in benchmark_directory_map:
         results.append((benchmark_name, False))
+    finally:
+      pool.terminate()
 
     # Keep a mapping of benchmarks to their upload results
     benchmark_upload_result_map = {}
diff --git a/tools/perf/process_perf_results_unittest.py b/tools/perf/process_perf_results_unittest.py
index 98ecfe19..31a5e7f 100755
--- a/tools/perf/process_perf_results_unittest.py
+++ b/tools/perf/process_perf_results_unittest.py
@@ -118,7 +118,7 @@
         })
     return_code, benchmark_upload_result_map = ppr_module.process_perf_results(
         self.output_json, configuration_name='test-builder',
-        service_account_file = self.service_account_file,
+        service_account_file=self.service_account_file,
         build_properties=build_properties,
         task_output_dir=self.task_output_dir,
         smoke_test_mode=False)
diff --git a/tools/perf/scripts_smoke_unittest.py b/tools/perf/scripts_smoke_unittest.py
index a78ea68..e0d03da 100644
--- a/tools/perf/scripts_smoke_unittest.py
+++ b/tools/perf/scripts_smoke_unittest.py
@@ -69,35 +69,40 @@
     options = options_for_unittests.GetCopy()
     browser_type = options.browser_type
     tempdir = tempfile.mkdtemp()
-    benchmark = 'dummy_benchmark.stable_benchmark_1'
+    benchmarks = ['dummy_benchmark.stable_benchmark_1',
+                  'dummy_benchmark.noisy_benchmark_1']
     return_code, stdout = self.RunPerfScript(
         '../../testing/scripts/run_performance_tests.py '
         '../../tools/perf/run_benchmark '
-        '--benchmarks=dummy_benchmark.stable_benchmark_1 '
+        '--benchmarks=%s '
         '--browser=%s '
-        '--isolated-script-test-repeat=2 '
         '--isolated-script-test-also-run-disabled-tests '
         '--isolated-script-test-output=%s' % (
+            ','.join(benchmarks),
             browser_type,
             os.path.join(tempdir, 'output.json')
         ))
     self.assertEquals(return_code, 0, stdout)
     try:
-      # 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:
+      with open(os.path.join(tempdir, 'output.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']
+        benchmarks_run = [str(b) for b in test_results['tests'].keys()]
+        self.assertEqual(sorted(benchmarks_run), sorted(benchmarks))
+        story_runs = test_results['num_failures_by_type']['PASS']
         self.assertEqual(
-            test_repeats, 2, '--isolated-script-test-repeat=2 should work.')
-      with open(os.path.join(tempdir, benchmark, 'perf_results.json')) as f:
-        perf_results = json.load(f)
-        self.assertIsNotNone(
-            perf_results, 'json perf results should be populated: ' + stdout)
+            story_runs, 2,
+            'Total runs should be 2 since each benchmark has one story.')
+      for benchmark in benchmarks:
+        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)
+        with open(os.path.join(tempdir, benchmark, 'perf_results.json')) as f:
+          perf_results = json.load(f)
+          self.assertIsNotNone(
+              perf_results, 'json perf results should be populated: ' + stdout)
     except IOError as e:
       self.fail('json_test_results should be populated: ' + stdout + str(e))
     finally:
@@ -130,6 +135,13 @@
       expected_benchmark_folders = (
           'dummy_benchmark.stable_benchmark_1',
           'dummy_benchmark.stable_benchmark_1.reference')
+      with open(os.path.join(tempdir, 'output.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.')
       for folder in expected_benchmark_folders:
         with open(os.path.join(tempdir, folder, 'test_results.json')) as f:
           test_results = json.load(f)
@@ -168,10 +180,10 @@
         ))
     self.assertEquals(return_code, 0, stdout)
     try:
-      # By design, run_performance_tests.py does not output test results
-      # to the location passed in by --isolated-script-test-output. Instead
-      # it uses the directory of that file and puts stuff in its own
-      # subdirectories for the purposes of merging later.
+      with open(os.path.join(tempdir, 'output.json')) as f:
+        test_results = json.load(f)
+        self.assertIsNotNone(
+            test_results, 'json_test_results should be populated: ' + stdout)
       with open(os.path.join(tempdir, benchmark, 'test_results.json')) as f:
         test_results = json.load(f)
         self.assertIsNotNone(
diff --git a/tools/perf/testdata/task_output_dir/0/output.json b/tools/perf/testdata/task_output_dir/0/output.json
new file mode 100644
index 0000000..88ed9fa
--- /dev/null
+++ b/tools/perf/testdata/task_output_dir/0/output.json
@@ -0,0 +1,2 @@
+["This is a fake json file to make sure that ",
+"process_perf_results_unittest.py can handle it."]
diff --git a/tools/perf/testdata/task_output_dir/1/output.json b/tools/perf/testdata/task_output_dir/1/output.json
new file mode 100644
index 0000000..88ed9fa
--- /dev/null
+++ b/tools/perf/testdata/task_output_dir/1/output.json
@@ -0,0 +1,2 @@
+["This is a fake json file to make sure that ",
+"process_perf_results_unittest.py can handle it."]
diff --git a/tools/perf/testdata/task_output_dir/2/output.json b/tools/perf/testdata/task_output_dir/2/output.json
new file mode 100644
index 0000000..88ed9fa
--- /dev/null
+++ b/tools/perf/testdata/task_output_dir/2/output.json
@@ -0,0 +1,2 @@
+["This is a fake json file to make sure that ",
+"process_perf_results_unittest.py can handle it."]
diff --git a/tools/perf/testdata/task_output_dir/3/output.json b/tools/perf/testdata/task_output_dir/3/output.json
new file mode 100644
index 0000000..88ed9fa
--- /dev/null
+++ b/tools/perf/testdata/task_output_dir/3/output.json
@@ -0,0 +1,2 @@
+["This is a fake json file to make sure that ",
+"process_perf_results_unittest.py can handle it."]
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 90e9c726..c21a77a 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -48,6 +48,7 @@
     "ax_event_generator.h",
     "ax_export.h",
     "ax_language_info.h",
+    "ax_mode.cc",
     "ax_mode.h",
     "ax_mode_observer.h",
     "ax_node.cc",
@@ -78,6 +79,9 @@
     "ax_tree_id.h",
     "ax_tree_id_registry.cc",
     "ax_tree_id_registry.h",
+    "ax_tree_manager.h",
+    "ax_tree_manager_map.cc",
+    "ax_tree_manager_map.h",
     "ax_tree_observer.cc",
     "ax_tree_observer.h",
     "ax_tree_serializer.cc",
@@ -108,6 +112,10 @@
       "platform/ax_fragment_root_win.h",
       "platform/ax_platform_node_mac.h",
       "platform/ax_platform_node_mac.mm",
+      "platform/ax_platform_node_textprovider_win.cc",
+      "platform/ax_platform_node_textprovider_win.h",
+      "platform/ax_platform_node_textrangeprovider_win.cc",
+      "platform/ax_platform_node_textrangeprovider_win.h",
       "platform/ax_platform_node_win.cc",
       "platform/ax_platform_node_win.h",
       "platform/ax_platform_relation_win.cc",
@@ -235,6 +243,8 @@
     "mojom/ax_tree_id_mojom_traits_unittest.cc",
     "mojom/ax_tree_update_mojom_traits_unittest.cc",
     "platform/ax_fragment_root_win_unittest.cc",
+    "platform/ax_platform_node_textprovider_win_unittest.cc",
+    "platform/ax_platform_node_textrangeprovider_win_unittest.cc",
     "platform/ax_platform_node_unittest.cc",
     "platform/ax_platform_node_unittest.h",
     "platform/ax_platform_node_win_unittest.cc",
diff --git a/ui/accessibility/PRESUBMIT.py b/ui/accessibility/PRESUBMIT.py
index b3038b8..b5bb4259 100644
--- a/ui/accessibility/PRESUBMIT.py
+++ b/ui/accessibility/PRESUBMIT.py
@@ -159,7 +159,11 @@
     # Look for lines of the form "static constexpr <type> NAME "
     m = re.search('static constexpr [\w]+ ([\w]+)', line)
     if m:
-      values.append(m.group(1))
+      value = m.group(1)
+      # Skip first/last sentinels
+      if value == 'kFirstModeFlag' or value == 'kLastModeFlag':
+        continue
+      values.append(value)
 
   return values
 
diff --git a/ui/accessibility/ax_mode.cc b/ui/accessibility/ax_mode.cc
new file mode 100644
index 0000000..bf37969
--- /dev/null
+++ b/ui/accessibility/ax_mode.cc
@@ -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.
+
+#include "ui/accessibility/ax_mode.h"
+
+#include <vector>
+
+#include "base/strings/string_util.h"
+
+namespace ui {
+
+std::ostream& operator<<(std::ostream& stream, const AXMode& mode) {
+  std::vector<std::string> tokens;
+
+  // Written as a loop with a switch so that this crashes if a new
+  // mode flag is added without adding support for logging it.
+  for (uint32_t mode_flag = AXMode::kFirstModeFlag;
+       mode_flag <= AXMode::kLastModeFlag; mode_flag = mode_flag << 1) {
+    const char* flag_name = nullptr;
+    switch (mode_flag) {
+      case AXMode::kNativeAPIs:
+        flag_name = "kNativeAPIs";
+        break;
+      case AXMode::kWebContents:
+        flag_name = "kWebContents";
+        break;
+      case AXMode::kInlineTextBoxes:
+        flag_name = "kInlineTextBoxes";
+        break;
+      case AXMode::kScreenReader:
+        flag_name = "kScreenReader";
+        break;
+      case AXMode::kHTML:
+        flag_name = "kHTML";
+        break;
+      case AXMode::kLabelImages:
+        flag_name = "kLabelImages";
+        break;
+    }
+
+    DCHECK(flag_name);
+
+    if (mode.has_mode(mode_flag))
+      tokens.push_back(flag_name);
+  }
+  return stream << base::JoinString(tokens, " | ");
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/ax_mode.h b/ui/accessibility/ax_mode.h
index be3d44f2..5703942 100644
--- a/ui/accessibility/ax_mode.h
+++ b/ui/accessibility/ax_mode.h
@@ -5,10 +5,20 @@
 #ifndef UI_ACCESSIBILITY_AX_MODE_H_
 #define UI_ACCESSIBILITY_AX_MODE_H_
 
+#include <stdint.h>
+
+#include <ostream>
+#include <string>
+
+#include "base/logging.h"
+#include "ui/accessibility/ax_export.h"
+
 namespace ui {
 
-class AXMode {
+class AX_EXPORT AXMode {
  public:
+  static constexpr uint32_t kFirstModeFlag = 1 << 0;
+
   // Native accessibility APIs, specific to each platform, are enabled.
   // When this mode is set that indicates the presence of a third-party
   // client accessing Chrome via accessibility APIs. However, unless one
@@ -50,6 +60,11 @@
   // The accessibility tree will contain automatic image annotations.
   static constexpr uint32_t kLabelImages = 1 << 5;
 
+  // Update this to include the last supported mode flag. If you add
+  // another, be sure to update the stream insertion operator for
+  // logging and debugging.
+  static constexpr uint32_t kLastModeFlag = 1 << 5;
+
   constexpr AXMode() : flags_(0) {}
   constexpr AXMode(uint32_t flags) : flags_(flags) {}
 
@@ -86,6 +101,9 @@
                                         AXMode::kInlineTextBoxes |
                                         AXMode::kScreenReader | AXMode::kHTML);
 
+// For debugging, test assertions, etc.
+AX_EXPORT std::ostream& operator<<(std::ostream& stream, const AXMode& mode);
+
 }  // namespace ui
 
 #endif  // UI_ACCESSIBILITY_AX_MODE_H_
diff --git a/ui/accessibility/ax_node_position.cc b/ui/accessibility/ax_node_position.cc
index 3e52235..c9dbd0cb 100644
--- a/ui/accessibility/ax_node_position.cc
+++ b/ui/accessibility/ax_node_position.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/string_util.h"
 #include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_tree_manager_map.h"
 
 namespace ui {
 
@@ -74,9 +75,18 @@
 }
 
 AXNode* AXNodePosition::GetNodeInTree(AXTreeID tree_id, int32_t node_id) const {
-  if (!tree_ || node_id == INVALID_ANCHOR_ID)
+  if (node_id == INVALID_ANCHOR_ID)
     return nullptr;
-  return AXNodePosition::tree_->GetFromId(node_id);
+
+  // Used for testing via AXNodePosition::SetTreeForTesting
+  if (AXNodePosition::tree_)
+    return AXNodePosition::tree_->GetFromId(node_id);
+
+  AXTreeManager* manager = AXTreeManagerMap::GetInstance().GetManager(tree_id);
+  if (manager)
+    return manager->GetNodeFromTree(tree_id, node_id);
+
+  return nullptr;
 }
 
 int AXNodePosition::MaxTextOffset() const {
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 4bd039c..80de0c9 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -460,8 +460,9 @@
     int adjusted_offset = AsTextPosition()->text_offset_;
     AXPositionInstance child_position = tree_position->CreateChildPositionAt(0);
     DCHECK(child_position);
-    for (int i = 1; i <= tree_position->child_index_ &&
-                    i < tree_position->AnchorChildCount();
+    for (int i = 1;
+         i <= tree_position->child_index_ &&
+         i < tree_position->AnchorChildCount() && adjusted_offset > 0;
          ++i) {
       adjusted_offset -= child_position->MaxTextOffsetInParent();
       child_position = tree_position->CreateChildPositionAt(i);
@@ -482,8 +483,9 @@
       case AXPositionKind::NULL_POSITION:
         return CreateNullPosition();
       case AXPositionKind::TREE_POSITION:
-        if (!AnchorChildCount())
+        if (!AnchorChildCount()) {
           return CreateTreePosition(tree_id_, anchor_id_, BEFORE_TEXT);
+        }
         return CreateTreePosition(tree_id_, anchor_id_, 0 /* child_index */);
       case AXPositionKind::TEXT_POSITION:
         return CreateTextPosition(tree_id_, anchor_id_, 0 /* text_offset */,
diff --git a/ui/accessibility/ax_tree_id.h b/ui/accessibility/ax_tree_id.h
index 3fd82a77..cbe94a4 100644
--- a/ui/accessibility/ax_tree_id.h
+++ b/ui/accessibility/ax_tree_id.h
@@ -65,6 +65,14 @@
   base::Optional<base::UnguessableToken> token_;
 };
 
+// For use in std::unordered_map.
+struct AXTreeIDHash {
+  size_t operator()(const ui::AXTreeID& tree_id) const {
+    DCHECK(tree_id.type() == ax::mojom::AXTreeIDType::kToken);
+    return base::UnguessableTokenHash()(tree_id.token().value());
+  }
+};
+
 AX_EXPORT std::ostream& operator<<(std::ostream& stream, const AXTreeID& value);
 
 // The value to use when an AXTreeID is unknown.
diff --git a/ui/accessibility/ax_tree_manager.h b/ui/accessibility/ax_tree_manager.h
new file mode 100644
index 0000000..5f96a65
--- /dev/null
+++ b/ui/accessibility/ax_tree_manager.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_ACCESSIBILITY_AX_TREE_MANAGER_H_
+#define UI_ACCESSIBILITY_AX_TREE_MANAGER_H_
+
+#include "base/macros.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_tree_id.h"
+
+namespace ui {
+
+// Each AXNode has access to its own tree, but a manager of multiple AXTrees
+// is necessary for operations that span across trees.
+class AX_EXPORT AXTreeManager {
+ public:
+  // Exposes the mapping between AXTreeID's and AXNodes based on a node_id.
+  // This allows for callers to access nodes outside of their own tree.
+  // Returns nullptr if the AXTreeID or node_id is not found.
+  virtual AXNode* GetNodeFromTree(AXTreeID tree_id, int32_t node_id) = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_ACCESSIBILITY_AX_TREE_MANAGER_H_
diff --git a/ui/accessibility/ax_tree_manager_map.cc b/ui/accessibility/ax_tree_manager_map.cc
new file mode 100644
index 0000000..108c3d6
--- /dev/null
+++ b/ui/accessibility/ax_tree_manager_map.cc
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/accessibility/ax_tree_manager_map.h"
+
+namespace ui {
+
+AXTreeManagerMap::AXTreeManagerMap() {}
+
+AXTreeManagerMap::~AXTreeManagerMap() {}
+
+AXTreeManagerMap& AXTreeManagerMap::GetInstance() {
+  static base::NoDestructor<AXTreeManagerMap> instance;
+  return *instance;
+}
+
+void AXTreeManagerMap::AddTreeManager(AXTreeID tree_id,
+                                      AXTreeManager* manager) {
+  if (tree_id != AXTreeIDUnknown())
+    map_[tree_id] = manager;
+}
+
+void AXTreeManagerMap::RemoveTreeManager(AXTreeID tree_id) {
+  if (tree_id != AXTreeIDUnknown())
+    map_.erase(tree_id);
+}
+
+AXTreeManager* AXTreeManagerMap::GetManager(AXTreeID tree_id) {
+  if (tree_id == AXTreeIDUnknown())
+    return nullptr;
+
+  return map_[tree_id];
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/ax_tree_manager_map.h b/ui/accessibility/ax_tree_manager_map.h
new file mode 100644
index 0000000..5e60e74
--- /dev/null
+++ b/ui/accessibility/ax_tree_manager_map.h
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_ACCESSIBILITY_AX_TREE_MANAGER_MAP_H_
+#define UI_ACCESSIBILITY_AX_TREE_MANAGER_MAP_H_
+
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree_manager.h"
+
+namespace ui {
+
+// This class manages AXTreeManager instances. It is a singleton wrapper
+// around a std::unordered_map. AXTreeID's are used as the key for the map.
+// Since AXTreeID's might refer to AXTreeIDUnknown, callers should not expect
+// AXTreeIDUnknown to map to a particular AXTreeManager.
+class AX_EXPORT AXTreeManagerMap {
+ public:
+  AXTreeManagerMap();
+  ~AXTreeManagerMap();
+
+  static AXTreeManagerMap& GetInstance();
+  void AddTreeManager(AXTreeID tree_id, AXTreeManager* manager);
+  void RemoveTreeManager(AXTreeID tree_id);
+  AXTreeManager* GetManager(AXTreeID tree_id);
+
+ private:
+  std::unordered_map<AXTreeID, AXTreeManager*, AXTreeIDHash> map_;
+};
+
+}  // namespace ui
+
+#endif  // UI_ACCESSIBILITY_AX_TREE_MANAGER_MAP_H_
diff --git a/ui/accessibility/platform/OWNERS b/ui/accessibility/platform/OWNERS
new file mode 100644
index 0000000..b2c4d0f
--- /dev/null
+++ b/ui/accessibility/platform/OWNERS
@@ -0,0 +1,2 @@
+# For ATK / AuraLinux
+mrobinson@igalia.com
diff --git a/ui/accessibility/platform/ax_fragment_root_win.cc b/ui/accessibility/platform/ax_fragment_root_win.cc
index 4cab105..adcffb4e 100644
--- a/ui/accessibility/platform/ax_fragment_root_win.cc
+++ b/ui/accessibility/platform/ax_fragment_root_win.cc
@@ -158,6 +158,16 @@
   return platform_node_.Get();
 }
 
+void AXFragmentRootWin::SetParent(gfx::NativeViewAccessible parent) {
+  if (parent != nullptr) {
+    parent_ = static_cast<ui::AXPlatformNodeWin*>(
+        ui::AXPlatformNode::FromNativeViewAccessible(parent));
+    DCHECK(parent_);
+  } else {
+    parent_ = nullptr;
+  }
+}
+
 void AXFragmentRootWin::SetChild(gfx::NativeViewAccessible child) {
   if (child != nullptr) {
     child_ = static_cast<ui::AXPlatformNodeWin*>(
@@ -168,6 +178,14 @@
   }
 }
 
+gfx::NativeViewAccessible AXFragmentRootWin::GetParent() {
+  if (parent_ != nullptr) {
+    return parent_->GetNativeViewAccessible();
+  }
+
+  return nullptr;
+}
+
 int AXFragmentRootWin::GetChildCount() {
   return (child_ != nullptr) ? 1 : 0;
 }
diff --git a/ui/accessibility/platform/ax_fragment_root_win.h b/ui/accessibility/platform/ax_fragment_root_win.h
index dc323b55..0682bca 100644
--- a/ui/accessibility/platform/ax_fragment_root_win.h
+++ b/ui/accessibility/platform/ax_fragment_root_win.h
@@ -41,6 +41,10 @@
   // Return the NativeViewAccessible for this fragment root.
   gfx::NativeViewAccessible GetNativeViewAccessible();
 
+  // The legacy window needs to provide navigation to its parent for tree
+  // linkage to be correct.
+  void SetParent(gfx::NativeViewAccessible parent);
+
   // The sole child of a fragment root is permitted to change during the
   // fragment root's lifetime. This will happen, for example, on a web content
   // navigation.
@@ -48,6 +52,7 @@
 
  private:
   // AXPlatformNodeDelegate overrides.
+  gfx::NativeViewAccessible GetParent() override;
   int GetChildCount() override;
   gfx::NativeViewAccessible ChildAtIndex(int index) override;
   gfx::NativeViewAccessible HitTestSync(int x, int y) override;
@@ -56,6 +61,7 @@
   gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override;
 
   Microsoft::WRL::ComPtr<ui::AXFragmentRootPlatformNodeWin> platform_node_;
+  Microsoft::WRL::ComPtr<ui::AXPlatformNodeWin> parent_;
   Microsoft::WRL::ComPtr<ui::AXPlatformNodeWin> child_;
   ui::AXUniqueId unique_id_;
   gfx::AcceleratedWidget widget_;
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 1dc99c7..335dec19f 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -318,6 +318,10 @@
   return delegate_->AccessibilityPerformAction(action_data);
 }
 
+bool AXPlatformNodeBase::IsDocument() const {
+  return ui::IsDocument(GetData().role);
+}
+
 bool AXPlatformNodeBase::IsTextOnlyObject() const {
   return GetData().role == ax::mojom::Role::kStaticText ||
          GetData().role == ax::mojom::Role::kLineBreak ||
diff --git a/ui/accessibility/platform/ax_platform_node_base.h b/ui/accessibility/platform/ax_platform_node_base.h
index 2c5c3f36..d7fd18f 100644
--- a/ui/accessibility/platform/ax_platform_node_base.h
+++ b/ui/accessibility/platform/ax_platform_node_base.h
@@ -222,6 +222,7 @@
   AXPlatformNodeDelegate* delegate_;
 
  protected:
+  bool IsDocument() const;
   bool IsTextOnlyObject() const;
   bool IsPlainTextField() const;
   // Is in a focused textfield with a related suggestion popup available,
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.h b/ui/accessibility/platform/ax_platform_node_delegate.h
index 1ec22a1..3e7f481 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -11,6 +11,8 @@
 
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_export.h"
+#include "ui/accessibility/ax_node_position.h"
+#include "ui/accessibility/ax_position.h"
 #include "ui/accessibility/ax_text_utils.h"
 #include "ui/accessibility/platform/ax_unique_id.h"
 #include "ui/gfx/geometry/vector2d.h"
@@ -53,6 +55,12 @@
   // Get the accessibility tree data for this node.
   virtual const AXTreeData& GetTreeData() const = 0;
 
+  // Creates a text position rooted at this object.
+  virtual AXNodePosition::AXPositionInstance CreateTextPositionAt(
+      int offset,
+      ax::mojom::TextAffinity affinity =
+          ax::mojom::TextAffinity::kDownstream) const = 0;
+
   // Get the accessibility node for the NSWindow the node is contained in. This
   // method is only meaningful on macOS.
   virtual gfx::NativeViewAccessible GetNSWindow() = 0;
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.cc b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
index 91dd3a1..21d9fe2 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -28,6 +28,13 @@
   return *empty_data;
 }
 
+AXNodePosition::AXPositionInstance
+AXPlatformNodeDelegateBase::CreateTextPositionAt(
+    int offset,
+    ax::mojom::TextAffinity affinity) const {
+  return AXNodePosition::CreateNullPosition();
+}
+
 gfx::NativeViewAccessible AXPlatformNodeDelegateBase::GetNSWindow() {
   return nullptr;
 }
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.h b/ui/accessibility/platform/ax_platform_node_delegate_base.h
index d027544..34baed1b 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -29,6 +29,12 @@
   // Get the accessibility tree data for this node.
   const AXTreeData& GetTreeData() const override;
 
+  // Creates a text position rooted at this object.
+  AXNodePosition::AXPositionInstance CreateTextPositionAt(
+      int offset,
+      ax::mojom::TextAffinity affinity =
+          ax::mojom::TextAffinity::kDownstream) const override;
+
   // See comments in AXPlatformNodeDelegate.
   gfx::NativeViewAccessible GetNSWindow() override;
 
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
new file mode 100644
index 0000000..2817508
--- /dev/null
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
@@ -0,0 +1,130 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/accessibility/platform/ax_platform_node_textprovider_win.h"
+
+#include <utility>
+
+#include "ui/accessibility/ax_node_position.h"
+#include "ui/accessibility/platform/ax_platform_node_delegate.h"
+#include "ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h"
+
+#define UIA_VALIDATE_TEXTPROVIDER_CALL() \
+  if (!owner()->GetDelegate())           \
+    return UIA_E_ELEMENTNOTAVAILABLE;
+
+namespace ui {
+
+AXPlatformNodeTextProviderWin::AXPlatformNodeTextProviderWin() {
+  DVLOG(1) << __func__;
+}
+
+AXPlatformNodeTextProviderWin::~AXPlatformNodeTextProviderWin() {}
+
+// static
+HRESULT AXPlatformNodeTextProviderWin::Create(ui::AXPlatformNodeWin* owner,
+                                              IUnknown** provider) {
+  CComObject<AXPlatformNodeTextProviderWin>* text_provider = nullptr;
+  HRESULT hr =
+      CComObject<AXPlatformNodeTextProviderWin>::CreateInstance(&text_provider);
+  if (SUCCEEDED(hr)) {
+    DCHECK(text_provider);
+    text_provider->owner_ = owner;
+    hr = text_provider->QueryInterface(IID_PPV_ARGS(provider));
+  }
+
+  return hr;
+}
+
+//
+// ITextProvider methods.
+//
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::GetSelection(
+    SAFEARRAY** selection) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::GetVisibleRanges(
+    SAFEARRAY** visible_ranges) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::RangeFromChild(
+    IRawElementProviderSimple* child,
+    ITextRangeProvider** range) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::RangeFromPoint(
+    UiaPoint uia_point,
+    ITextRangeProvider** range) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::get_DocumentRange(
+    ITextRangeProvider** range) {
+  UIA_VALIDATE_TEXTPROVIDER_CALL();
+  DVLOG(1) << __func__;
+
+  *range = nullptr;
+
+  // Start and end should be leaf text positions that span the beginning
+  // and end of text content within a node for get_DocumentRange. The start
+  // position should be the directly first child and the end position should
+  // be the deepest last child node.
+  AXNodePosition::AXPositionInstance start =
+      owner()->GetDelegate()->CreateTextPositionAt(0)->AsLeafTextPosition();
+
+  AXNodePosition::AXPositionInstance end;
+  if (owner()->GetChildCount() == 0) {
+    end = start->CreatePositionAtEndOfAnchor()->AsLeafTextPosition();
+  } else {
+    AXPlatformNode* deepest_last_child =
+        AXPlatformNode::FromNativeViewAccessible(
+            owner()->ChildAtIndex(owner()->GetChildCount() - 1));
+
+    while (deepest_last_child &&
+           deepest_last_child->GetDelegate()->GetChildCount() > 0) {
+      deepest_last_child =
+          AXPlatformNode::FromNativeViewAccessible(owner()->ChildAtIndex(
+              deepest_last_child->GetDelegate()->GetChildCount() - 1));
+    }
+    end = deepest_last_child->GetDelegate()
+              ->CreateTextPositionAt(0)
+              ->CreatePositionAtEndOfAnchor()
+              ->AsLeafTextPosition();
+  }
+
+  return AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
+      owner_, std::move(start), std::move(end), range);
+}
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::get_SupportedTextSelection(
+    enum SupportedTextSelection* text_selection) {
+  UIA_VALIDATE_TEXTPROVIDER_CALL();
+
+  *text_selection = SupportedTextSelection_Single;
+  return S_OK;
+}
+
+//
+// ITextEditProvider methods.
+//
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::GetActiveComposition(
+    ITextRangeProvider** range) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextProviderWin::GetConversionTarget(
+    ITextRangeProvider** range) {
+  return E_NOTIMPL;
+}
+
+ui::AXPlatformNodeWin* AXPlatformNodeTextProviderWin::owner() const {
+  return owner_;
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win.h b/ui/accessibility/platform/ax_platform_node_textprovider_win.h
new file mode 100644
index 0000000..394d4d1
--- /dev/null
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win.h
@@ -0,0 +1,62 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTPROVIDER_WIN_H_
+#define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTPROVIDER_WIN_H_
+
+#include "ui/accessibility/platform/ax_platform_node_win.h"
+
+namespace ui {
+class AXPlatformNodeTextProviderWin
+    : public CComObjectRootEx<CComMultiThreadModel>,
+      public ITextEditProvider {
+ public:
+  BEGIN_COM_MAP(AXPlatformNodeTextProviderWin)
+  COM_INTERFACE_ENTRY(ITextProvider)
+  COM_INTERFACE_ENTRY(ITextEditProvider)
+  END_COM_MAP()
+
+  AXPlatformNodeTextProviderWin();
+  ~AXPlatformNodeTextProviderWin();
+
+  // Creates an instance of the class.
+  // Returns true on success
+  static HRESULT Create(ui::AXPlatformNodeWin* owner, IUnknown** provider);
+
+  //
+  // ITextProvider methods.
+  //
+
+  STDMETHOD(GetSelection)(SAFEARRAY** selection) override;
+
+  STDMETHOD(GetVisibleRanges)(SAFEARRAY** visible_ranges) override;
+
+  STDMETHOD(RangeFromChild)
+  (IRawElementProviderSimple* child, ITextRangeProvider** range) override;
+
+  STDMETHOD(RangeFromPoint)
+  (UiaPoint point, ITextRangeProvider** range) override;
+
+  STDMETHOD(get_DocumentRange)(ITextRangeProvider** range) override;
+
+  STDMETHOD(get_SupportedTextSelection)
+  (enum SupportedTextSelection* text_selection) override;
+
+  //
+  // ITextEditProvider methods.
+  //
+
+  STDMETHOD(GetActiveComposition)(ITextRangeProvider** range) override;
+
+  STDMETHOD(GetConversionTarget)(ITextRangeProvider** range) override;
+
+ private:
+  ui::AXPlatformNodeWin* owner() const;
+
+  CComPtr<ui::AXPlatformNodeWin> owner_;
+};
+
+}  // namespace ui
+
+#endif  // UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTPROVIDER_WIN_H_
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
new file mode 100644
index 0000000..8e653f5
--- /dev/null
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/accessibility/platform/ax_platform_node_win_unittest.h"
+
+#include <UIAutomationClient.h>
+#include "base/win/scoped_bstr.h"
+#include "ui/accessibility/platform/ax_fragment_root_win.h"
+#include "ui/base/win/accessibility_misc_utils.h"
+
+using Microsoft::WRL::ComPtr;
+
+namespace ui {
+
+class AXPlatformNodeTextProviderTest : public ui::AXPlatformNodeWinTest {};
+
+TEST_F(AXPlatformNodeTextProviderTest, TestITextProviderDocumentRange) {
+  ui::AXNodeData text_data;
+  text_data.id = 2;
+  text_data.role = ax::mojom::Role::kStaticText;
+  text_data.SetName("some text");
+
+  ui::AXNodeData root_data;
+  root_data.id = 1;
+  root_data.SetName("Document");
+  root_data.role = ax::mojom::Role::kRootWebArea;
+  root_data.child_ids.push_back(2);
+
+  Init(root_data, text_data);
+
+  ComPtr<IRawElementProviderSimple> root_node =
+      GetRootIRawElementProviderSimple();
+
+  ComPtr<ITextProvider> text_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      root_node->GetPatternProvider(UIA_TextPatternId, &text_provider));
+
+  ComPtr<ITextRangeProvider> text_range_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_provider->get_DocumentRange(&text_range_provider));
+}
+
+TEST_F(AXPlatformNodeTextProviderTest, TestITextProviderSupportedSelection) {
+  ui::AXNodeData text_data;
+  text_data.id = 2;
+  text_data.role = ax::mojom::Role::kStaticText;
+  text_data.SetName("some text");
+
+  ui::AXNodeData root_data;
+  root_data.id = 1;
+  root_data.SetName("Document");
+  root_data.role = ax::mojom::Role::kRootWebArea;
+  root_data.child_ids.push_back(2);
+
+  Init(root_data, text_data);
+
+  ComPtr<IRawElementProviderSimple> root_node =
+      GetRootIRawElementProviderSimple();
+
+  ComPtr<ITextProvider> text_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      root_node->GetPatternProvider(UIA_TextPatternId, &text_provider));
+
+  SupportedTextSelection text_selection_mode;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_provider->get_SupportedTextSelection(&text_selection_mode));
+  EXPECT_EQ(text_selection_mode, SupportedTextSelection_Single);
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
new file mode 100644
index 0000000..274fa573
--- /dev/null
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -0,0 +1,180 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h"
+
+#include <utility>
+#include <vector>
+
+#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL()                        \
+  if (!owner() || !owner()->GetDelegate() || !start_->GetAnchor() || \
+      !end_->GetAnchor())                                            \
+    return UIA_E_ELEMENTNOTAVAILABLE;
+
+namespace ui {
+
+AXPlatformNodeTextRangeProviderWin::AXPlatformNodeTextRangeProviderWin() {
+  DVLOG(1) << __func__;
+}
+
+AXPlatformNodeTextRangeProviderWin::~AXPlatformNodeTextRangeProviderWin() {}
+
+HRESULT AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
+    ui::AXPlatformNodeWin* owner,
+    AXNodePosition::AXPositionInstance start,
+    AXNodePosition::AXPositionInstance end,
+    ITextRangeProvider** provider) {
+  CComObject<AXPlatformNodeTextRangeProviderWin>* text_range_provider = nullptr;
+  HRESULT hr = CComObject<AXPlatformNodeTextRangeProviderWin>::CreateInstance(
+      &text_range_provider);
+  if (SUCCEEDED(hr)) {
+    DCHECK(text_range_provider);
+    text_range_provider->owner_ = owner;
+    text_range_provider->start_ = std::move(start);
+    text_range_provider->end_ = std::move(end);
+    text_range_provider->AddRef();
+    *provider = text_range_provider;
+  }
+
+  return hr;
+}
+
+//
+// ITextRangeProvider methods.
+//
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Clone(
+    ITextRangeProvider** clone) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Compare(
+    __in ITextRangeProvider* other,
+    __out BOOL* result) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::CompareEndpoints(
+    TextPatternRangeEndpoint endpoint,
+    __in ITextRangeProvider* other,
+    TextPatternRangeEndpoint targetEndpoint,
+    __out int* result) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
+    TextUnit unit) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::FindAttribute(
+    TEXTATTRIBUTEID text_attribute_id,
+    VARIANT val,
+    BOOL backward,
+    ITextRangeProvider** result) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::FindText(
+    BSTR string,
+    BOOL backwards,
+    BOOL ignore_case,
+    ITextRangeProvider** result) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
+    TEXTATTRIBUTEID attributeId,
+    VARIANT* value) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetBoundingRectangles(
+    SAFEARRAY** rectangles) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetEnclosingElement(
+    IRawElementProviderSimple** element) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetText(int max_count,
+                                                         BSTR* text) {
+  UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+
+  // -1 is a valid value that signifies that the caller wants complete text.
+  // Any other negative value is invalid.
+  if (max_count < -1 || !text)
+    return E_INVALIDARG;
+
+  base::string16 full_text = GetString();
+  if (!full_text.empty()) {
+    size_t length = full_text.length();
+
+    if (max_count != -1 && max_count < static_cast<int>(length))
+      *text = SysAllocStringLen(full_text.c_str(), max_count);
+    else
+      *text = SysAllocStringLen(full_text.c_str(), length);
+  } else {
+    *text = SysAllocString(L"");
+  }
+
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
+                                                      int count,
+                                                      int* units_moved) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnit(
+    TextPatternRangeEndpoint endpoint,
+    TextUnit unit,
+    int count,
+    int* units_moved) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::MoveEndpointByRange(
+    TextPatternRangeEndpoint endpoint,
+    ITextRangeProvider* other,
+    TextPatternRangeEndpoint targetEndpoint) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Select() {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::AddToSelection() {
+  return UIA_E_INVALIDOPERATION;  // not supporting disjoint text selections
+}
+
+STDMETHODIMP
+AXPlatformNodeTextRangeProviderWin::RemoveFromSelection() {
+  return UIA_E_INVALIDOPERATION;  // not supporting disjoint text selections
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ScrollIntoView(
+    BOOL align_to_top) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetChildren(
+    SAFEARRAY** children) {
+  return E_NOTIMPL;
+}
+
+base::string16 AXPlatformNodeTextRangeProviderWin::GetString() {
+  AXNodeRange range(start_->Clone(), end_->Clone());
+
+  return range.GetText();
+}
+
+ui::AXPlatformNodeWin* AXPlatformNodeTextRangeProviderWin::owner() const {
+  return owner_;
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
new file mode 100644
index 0000000..927df09a
--- /dev/null
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
@@ -0,0 +1,99 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_
+#define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_
+
+#include <tuple>
+#include <vector>
+
+#include "ui/accessibility/ax_node_position.h"
+#include "ui/accessibility/ax_position.h"
+#include "ui/accessibility/ax_range.h"
+#include "ui/accessibility/platform/ax_platform_node_win.h"
+
+namespace ui {
+class __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
+    AXPlatformNodeTextRangeProviderWin
+    : public CComObjectRootEx<CComMultiThreadModel>,
+      public ITextRangeProvider {
+ public:
+  BEGIN_COM_MAP(AXPlatformNodeTextRangeProviderWin)
+  COM_INTERFACE_ENTRY(ITextRangeProvider)
+  COM_INTERFACE_ENTRY(AXPlatformNodeTextRangeProviderWin)
+  END_COM_MAP()
+
+  AXPlatformNodeTextRangeProviderWin();
+  ~AXPlatformNodeTextRangeProviderWin();
+
+  // Creates an instance of the class.
+  // Returns a successful HRESULT on success
+  static HRESULT CreateTextRangeProvider(
+      ui::AXPlatformNodeWin* owner,
+      AXNodePosition::AXPositionInstance start,
+      AXNodePosition::AXPositionInstance end,
+      ITextRangeProvider** provider);
+
+  //
+  // ITextRangeProvider methods.
+  //
+
+  STDMETHODIMP Clone(ITextRangeProvider** clone) override;
+  STDMETHODIMP Compare(ITextRangeProvider* other, BOOL* result) override;
+  STDMETHODIMP
+  CompareEndpoints(TextPatternRangeEndpoint endpoint,
+                   ITextRangeProvider* other,
+                   TextPatternRangeEndpoint targetEndpoint,
+                   int* result) override;
+  STDMETHODIMP ExpandToEnclosingUnit(TextUnit unit) override;
+  STDMETHODIMP
+  FindAttribute(TEXTATTRIBUTEID attributeId,
+                VARIANT val,
+                BOOL backward,
+                ITextRangeProvider** result) override;
+  STDMETHODIMP
+  FindText(BSTR string,
+           BOOL backwards,
+           BOOL ignore_case,
+           ITextRangeProvider** result) override;
+  STDMETHODIMP GetAttributeValue(TEXTATTRIBUTEID attributeId,
+                                 VARIANT* value) override;
+  STDMETHODIMP
+  GetBoundingRectangles(SAFEARRAY** rectangles) override;
+  STDMETHODIMP
+  GetEnclosingElement(IRawElementProviderSimple** element) override;
+  STDMETHODIMP GetText(int max_count, BSTR* text) override;
+  STDMETHODIMP Move(TextUnit unit, int count, int* units_moved) override;
+  STDMETHODIMP
+  MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
+                     TextUnit unit,
+                     int count,
+                     int* units_moved) override;
+  STDMETHODIMP
+  MoveEndpointByRange(TextPatternRangeEndpoint endpoint,
+                      ITextRangeProvider* other,
+                      TextPatternRangeEndpoint targetEndpoint) override;
+  STDMETHODIMP Select() override;
+  STDMETHODIMP AddToSelection() override;
+  STDMETHODIMP RemoveFromSelection() override;
+  STDMETHODIMP ScrollIntoView(BOOL align_to_top) override;
+  STDMETHODIMP GetChildren(SAFEARRAY** children) override;
+
+ private:
+  base::string16 GetString();
+  ui::AXPlatformNodeWin* owner() const;
+
+  using AXPositionInstance = AXNodePosition::AXPositionInstance;
+  using AXNodeRange = AXRange<AXNodePosition::AXPositionInstance::element_type>;
+  using AXPositionInstancePair =
+      std::tuple<AXPositionInstance, AXPositionInstance>;
+
+  AXNodePosition::AXPositionInstance start_;
+  AXNodePosition::AXPositionInstance end_;
+  CComPtr<ui::AXPlatformNodeWin> owner_;
+};
+
+}  // namespace ui
+
+#endif  // UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
new file mode 100644
index 0000000..0b6a10d
--- /dev/null
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -0,0 +1,149 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/accessibility/platform/ax_platform_node_win_unittest.h"
+
+#include <UIAutomationClient.h>
+#include <UIAutomationCoreApi.h>
+#include "base/win/atl.h"
+#include "base/win/scoped_bstr.h"
+#include "ui/accessibility/platform/ax_fragment_root_win.h"
+
+using Microsoft::WRL::ComPtr;
+
+namespace ui {
+
+class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {};
+
+TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetText) {
+  ui::AXNodeData text_data;
+  text_data.id = 2;
+  text_data.role = ax::mojom::Role::kStaticText;
+  text_data.SetName("some text");
+
+  ui::AXNodeData more_text_data;
+  more_text_data.id = 3;
+  more_text_data.role = ax::mojom::Role::kStaticText;
+  more_text_data.SetName("more text");
+
+  ui::AXNodeData root_data;
+  root_data.id = 1;
+  root_data.role = ax::mojom::Role::kRootWebArea;
+  root_data.child_ids.push_back(2);
+  root_data.child_ids.push_back(3);
+
+  ui::AXTreeUpdate update;
+  ui::AXTreeData tree_data;
+  tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  update.tree_data = tree_data;
+  update.has_tree_data = true;
+  update.root_id = root_data.id;
+  update.nodes.push_back(root_data);
+  update.nodes.push_back(text_data);
+  update.nodes.push_back(more_text_data);
+
+  Init(update);
+
+  AXNode* root_node = GetRootNode();
+  AXNodePosition::SetTreeForTesting(tree_.get());
+  AXNode* text_node = root_node->children()[0];
+
+  ComPtr<IRawElementProviderSimple> text_node_raw =
+      QueryInterfaceFromNode<IRawElementProviderSimple>(text_node);
+
+  ComPtr<ITextProvider> text_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+
+  ComPtr<ITextRangeProvider> text_range_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_provider->get_DocumentRange(&text_range_provider));
+
+  base::win::ScopedBstr text_content;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_range_provider->GetText(-1, text_content.Receive()));
+  EXPECT_STREQ(text_content, L"some text");
+  text_content.Reset();
+
+  EXPECT_HRESULT_SUCCEEDED(
+      text_range_provider->GetText(4, text_content.Receive()));
+  EXPECT_STREQ(text_content, L"some");
+  text_content.Reset();
+
+  EXPECT_HRESULT_SUCCEEDED(
+      text_range_provider->GetText(0, text_content.Receive()));
+  EXPECT_STREQ(text_content, L"");
+  text_content.Reset();
+
+  EXPECT_HRESULT_SUCCEEDED(
+      text_range_provider->GetText(9, text_content.Receive()));
+  EXPECT_STREQ(text_content, L"some text");
+  text_content.Reset();
+
+  EXPECT_HRESULT_SUCCEEDED(
+      text_range_provider->GetText(10, text_content.Receive()));
+  EXPECT_STREQ(text_content, L"some text");
+  text_content.Reset();
+
+  EXPECT_HRESULT_FAILED(text_range_provider->GetText(-1, nullptr));
+
+  EXPECT_HRESULT_FAILED(
+      text_range_provider->GetText(-2, text_content.Receive()));
+  text_content.Reset();
+
+  Microsoft::WRL::ComPtr<IRawElementProviderSimple> root_node_raw =
+      QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
+
+  Microsoft::WRL::ComPtr<ITextProvider> document_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
+
+  ComPtr<ITextRangeProvider> document_textrange;
+  EXPECT_HRESULT_SUCCEEDED(
+      document_provider->get_DocumentRange(&document_textrange));
+
+  EXPECT_HRESULT_SUCCEEDED(
+      document_textrange->GetText(-1, text_content.Receive()));
+  EXPECT_STREQ(text_content, L"some textmore text");
+  text_content.Reset();
+
+  AXNodePosition::SetTreeForTesting(nullptr);
+}
+
+TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderSelection) {
+  ui::AXNodeData text_data;
+  text_data.id = 2;
+  text_data.role = ax::mojom::Role::kStaticText;
+  text_data.SetName("some text");
+
+  ui::AXNodeData root_data;
+  root_data.id = 1;
+  root_data.role = ax::mojom::Role::kRootWebArea;
+  root_data.child_ids.push_back(2);
+
+  Init(root_data, text_data);
+
+  AXNode* root_node = GetRootNode();
+  AXNodePosition::SetTreeForTesting(tree_.get());
+
+  ComPtr<IRawElementProviderSimple> root_node_raw =
+      QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
+
+  ComPtr<ITextProvider> document_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
+
+  ComPtr<ITextRangeProvider> text_range_provider;
+  EXPECT_HRESULT_SUCCEEDED(
+      document_provider->get_DocumentRange(&text_range_provider));
+
+  ASSERT_EQ(text_range_provider->AddToSelection(),
+            static_cast<HRESULT>(UIA_E_INVALIDOPERATION));
+  ASSERT_EQ(text_range_provider->RemoveFromSelection(),
+            static_cast<HRESULT>(UIA_E_INVALIDOPERATION));
+
+  AXNodePosition::SetTreeForTesting(nullptr);
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 3d46535..763b3bd2 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -15,6 +15,7 @@
 #include <vector>
 
 #include "base/lazy_instance.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -25,11 +26,13 @@
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/ax_mode_observer.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_position.h"
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/accessibility/ax_text_utils.h"
 #include "ui/accessibility/ax_tree_data.h"
 #include "ui/accessibility/platform/ax_fragment_root_win.h"
 #include "ui/accessibility/platform/ax_platform_node_delegate.h"
+#include "ui/accessibility/platform/ax_platform_node_textprovider_win.h"
 #include "ui/accessibility/platform/ax_platform_relation_win.h"
 #include "ui/base/win/atl_module.h"
 #include "ui/display/win/screen_win.h"
@@ -397,6 +400,21 @@
   return uia_array;
 }
 
+SAFEARRAY* AXPlatformNodeWin::CreateClickablePointArray() {
+  SAFEARRAY* clickable_point_array = SafeArrayCreateVector(VT_R8, 0, 2);
+  gfx::Point center =
+      GetDelegate()->GetUnclippedScreenBoundsRect().CenterPoint();
+
+  double* double_array;
+  SafeArrayAccessData(clickable_point_array,
+                      reinterpret_cast<void**>(&double_array));
+  double_array[0] = center.x();
+  double_array[1] = center.y();
+  SafeArrayUnaccessData(clickable_point_array);
+
+  return clickable_point_array;
+}
+
 gfx::Vector2d AXPlatformNodeWin::CalculateUIAScrollPoint(
     const ScrollAmount horizontal_amount,
     const ScrollAmount vertical_amount) const {
@@ -3558,6 +3576,14 @@
       }
       break;
 
+    case UIA_TextEditPatternId:
+    case UIA_TextPatternId:
+      if (IsTextOnlyObject() || IsDocument() ||
+          HasBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot)) {
+        return AXPlatformNodeTextProviderWin::Create(this, result);
+      }
+      break;
+
     case UIA_TogglePatternId:
       if (SupportsToggle(data.role)) {
         AddRef();
@@ -3589,8 +3615,6 @@
     case UIA_SpreadsheetItemPatternId:
     case UIA_StylesPatternId:
     case UIA_TextChildPatternId:
-    case UIA_TextEditPatternId:
-    case UIA_TextPatternId:
     case UIA_TextPattern2Id:
     case UIA_TransformPattern2Id:
     case UIA_VirtualizedItemPatternId:
@@ -3602,6 +3626,18 @@
 IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
                                                    VARIANT* result) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PROPERTY_VALUE);
+
+  constexpr long kFirstKnownUiaPropertyId = UIA_RuntimeIdPropertyId;
+  constexpr long kLastKnownUiaPropertyId = UIA_IsDialogPropertyId;
+  if (property_id >= kFirstKnownUiaPropertyId &&
+      property_id <= kLastKnownUiaPropertyId) {
+    base::UmaHistogramSparse("Accessibility.WinAPIs.GetPropertyValue",
+                             property_id);
+  } else {
+    // Collapse all unknown property IDs into a single bucket.
+    base::UmaHistogramSparse("Accessibility.WinAPIs.GetPropertyValue", 0);
+  }
+
   UIA_VALIDATE_CALL_1_ARG(result);
 
   const AXNodeData& data = GetData();
@@ -3635,7 +3671,8 @@
       break;
 
     case UIA_ClickablePointPropertyId:
-      // TODO(suproteem)
+      result->vt = VT_ARRAY | VT_R8;
+      result->parray = CreateClickablePointArray();
       break;
 
     case UIA_ControllerForPropertyId:
diff --git a/ui/accessibility/platform/ax_platform_node_win.h b/ui/accessibility/platform/ax_platform_node_win.h
index 4c37ccb..b2076fd 100644
--- a/ui/accessibility/platform/ax_platform_node_win.h
+++ b/ui/accessibility/platform/ax_platform_node_win.h
@@ -1067,6 +1067,10 @@
   // of |AXNode| ids.
   SAFEARRAY* CreateUIAElementsArrayFromIdVector(std::vector<int32_t>& ids);
 
+  // Return an array that contains the center x, y coordinates of the
+  // clickable point.
+  SAFEARRAY* CreateClickablePointArray();
+
   // Returns the scroll offsets to which UI Automation should scroll an
   // accessible object, given the horizontal and vertical scroll amounts.
   gfx::Vector2d CalculateUIAScrollPoint(
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index ae1d66c..0c7805a 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -8,6 +8,7 @@
 
 #include <memory>
 
+#include "base/test/metrics/histogram_tester.h"
 #include "base/win/atl.h"
 #include "base/win/scoped_bstr.h"
 #include "base/win/scoped_variant.h"
@@ -71,6 +72,31 @@
     EXPECT_EQ(expectedVariant.ptr()->boolVal, actual.ptr()->boolVal); \
   }
 
+#define EXPECT_UIA_DOUBLE_ARRAY_EQ(node, array_property_id,                 \
+                                   expected_property_values)                \
+  {                                                                         \
+    ScopedVariant array;                                                    \
+    ASSERT_HRESULT_SUCCEEDED(                                               \
+        node->GetPropertyValue(array_property_id, array.Receive()));        \
+    ASSERT_EQ(VT_ARRAY | VT_R8, array.type());                              \
+    ASSERT_EQ(1u, SafeArrayGetDim(array.ptr()->parray));                    \
+    LONG array_lower_bound;                                                 \
+    ASSERT_HRESULT_SUCCEEDED(                                               \
+        SafeArrayGetLBound(array.ptr()->parray, 1, &array_lower_bound));    \
+    LONG array_upper_bound;                                                 \
+    ASSERT_HRESULT_SUCCEEDED(                                               \
+        SafeArrayGetUBound(array.ptr()->parray, 1, &array_upper_bound));    \
+    double* array_data;                                                     \
+    ASSERT_HRESULT_SUCCEEDED(::SafeArrayAccessData(                         \
+        array.ptr()->parray, reinterpret_cast<void**>(&array_data)));       \
+    size_t count = array_upper_bound - array_lower_bound + 1;               \
+    ASSERT_EQ(expected_property_values.size(), count);                      \
+    for (size_t i = 0; i < count; ++i) {                                    \
+      EXPECT_EQ(array_data[i], expected_property_values[i]);                \
+    }                                                                       \
+    ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(array.ptr()->parray)); \
+  }
+
 #define EXPECT_UIA_INT_EQ(node, property_id, expected)              \
   {                                                                 \
     ScopedVariant expectedVariant(expected, VT_I4);                 \
@@ -158,6 +184,7 @@
   // Destroy the tree and make sure we're not leaking any objects.
   ax_fragment_root_.reset(nullptr);
   tree_.reset(nullptr);
+  AXNodePosition::SetTreeForTesting(nullptr);
   ASSERT_EQ(0U, AXPlatformNodeBase::GetInstanceCountForTesting());
 }
 
@@ -3223,6 +3250,49 @@
   EXPECT_UIA_INT_EQ(child_node1, UIA_PositionInSetPropertyId, 1);
 }
 
+TEST_F(AXPlatformNodeWinTest, TestUIAGetPropertyValueClickablePoint) {
+  AXNodeData root;
+  root.id = 0;
+  root.role = ax::mojom::Role::kButton;
+  root.relative_bounds.bounds = gfx::RectF(20, 30, 100, 200);
+  Init(root);
+
+  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
+      GetRootIRawElementProviderSimple();
+
+  // The clickable point of a rectangle {20, 30, 100, 200} is the rectangle's
+  // center, with coordinates {x: 70, y: 130}.
+  std::vector<double> expected_values = {70, 130};
+  EXPECT_UIA_DOUBLE_ARRAY_EQ(raw_element_provider_simple,
+                             UIA_ClickablePointPropertyId, expected_values);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestUIAGetPropertyValue_Histogram) {
+  AXNodeData root;
+  root.id = 0;
+  Init(root);
+  ComPtr<IRawElementProviderSimple> root_node =
+      GetRootIRawElementProviderSimple();
+
+  // Log a known property ID
+  {
+    base::HistogramTester histogram_tester;
+    ScopedVariant property_value;
+    root_node->GetPropertyValue(UIA_NamePropertyId, property_value.Receive());
+    histogram_tester.ExpectUniqueSample(
+        "Accessibility.WinAPIs.GetPropertyValue", UIA_NamePropertyId, 1);
+  }
+
+  // Collapse unknown property IDs to zero
+  {
+    base::HistogramTester histogram_tester;
+    ScopedVariant property_value;
+    root_node->GetPropertyValue(42, property_value.Receive());
+    histogram_tester.ExpectUniqueSample(
+        "Accessibility.WinAPIs.GetPropertyValue", 0, 1);
+  }
+}
+
 TEST_F(AXPlatformNodeWinTest, TestUIAGetDescribedByPropertyId) {
   AXNodeData root;
   std::vector<int32_t> describedby_ids = {1, 2, 3};
@@ -4187,7 +4257,8 @@
   Init(root, text_field_with_combo_box, table, table_cell, meter,
        group_with_scroll, grid, checkbox);
 
-  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId}),
+  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
+                        UIA_TextEditPatternId, UIA_TextPatternId}),
             GetSupportedPatternsFromNodeId(root_id));
 
   EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.h b/ui/accessibility/platform/ax_platform_node_win_unittest.h
index 7220828..c4d929e 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.h
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.h
@@ -9,6 +9,8 @@
 
 #include <unordered_set>
 
+#include "ui/base/win/accessibility_misc_utils.h"
+
 struct IAccessible;
 struct IAccessible2;
 struct IAccessible2_2;
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 9b9ee6ca..c410b5a 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -88,6 +88,13 @@
   return tree_->data();
 }
 
+AXNodePosition::AXPositionInstance TestAXNodeWrapper::CreateTextPositionAt(
+    int offset,
+    ax::mojom::TextAffinity affinity) const {
+  return ui::AXNodePosition::CreateTextPosition(GetTreeData().tree_id,
+                                                node_->id(), offset, affinity);
+}
+
 gfx::NativeViewAccessible TestAXNodeWrapper::GetParent() {
   TestAXNodeWrapper* parent_wrapper = GetOrCreate(tree_, node_->parent());
   return parent_wrapper ?
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.h b/ui/accessibility/platform/test_ax_node_wrapper.h
index 62ade134..0fea6a3d 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -44,6 +44,10 @@
   // AXPlatformNodeDelegate.
   const AXNodeData& GetData() const override;
   const AXTreeData& GetTreeData() const override;
+  AXNodePosition::AXPositionInstance CreateTextPositionAt(
+      int offset,
+      ax::mojom::TextAffinity affinity =
+          ax::mojom::TextAffinity::kDownstream) const override;
   gfx::NativeViewAccessible GetParent() override;
   int GetChildCount() override;
   gfx::NativeViewAccessible ChildAtIndex(int index) override;
diff --git a/ui/android/delegated_frame_host_android.cc b/ui/android/delegated_frame_host_android.cc
index 82c97a31..6020195 100644
--- a/ui/android/delegated_frame_host_android.cc
+++ b/ui/android/delegated_frame_host_android.cc
@@ -196,7 +196,7 @@
     content_layer_->RemoveFromParent();
     content_layer_ = nullptr;
   }
-  if (!HasSavedFrame() || frame_evictor_->visible())
+  if (!HasSavedFrame())
     return;
   std::vector<viz::SurfaceId> surface_ids = {
       viz::SurfaceId(frame_sink_id_, local_surface_id_)};
diff --git a/ui/android/delegated_frame_host_android_unittest.cc b/ui/android/delegated_frame_host_android_unittest.cc
index 2736d64..ad53248 100644
--- a/ui/android/delegated_frame_host_android_unittest.cc
+++ b/ui/android/delegated_frame_host_android_unittest.cc
@@ -367,15 +367,16 @@
 // Make sure frame evictor is notified of the newly embedded surface after
 // WasShown.
 TEST_F(DelegatedFrameHostAndroidVizTest, EmbedWhileHidden) {
-  // Ensure there is currently no frame.
-  frame_host_->WasHidden();
-  frame_host_->EvictDelegatedFrame();
+  {
+    EXPECT_CALL(client_, WasEvicted());
+    frame_host_->EvictDelegatedFrame();
+  }
   EXPECT_FALSE(frame_host_->HasSavedFrame());
-
   allocator_.GenerateId();
   viz::LocalSurfaceId id =
       allocator_.GetCurrentLocalSurfaceIdAllocation().local_surface_id();
   gfx::Size size(100, 100);
+  frame_host_->WasHidden();
   frame_host_->EmbedSurface(id, size, cc::DeadlinePolicy::UseDefaultDeadline());
   EXPECT_FALSE(frame_host_->HasSavedFrame());
   frame_host_->WasShown(id, size);
diff --git a/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java b/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java
index bb69ee0e..cce3db0a 100644
--- a/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java
+++ b/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java
@@ -36,7 +36,7 @@
     }
 
     private final Context mContext;
-    private final List<Pair<Integer, PropertyModel>> mSuggestionItems = new ArrayList<>();
+    private final List<Pair<Integer, PropertyModel>> mModelList = new ArrayList<>();
     private final SparseArray<Pair<ViewBuilder, PropertyModelChangeProcessor.ViewBinder>>
             mViewBuilderMap = new SparseArray<>();
 
@@ -45,22 +45,22 @@
     }
 
     /**
-     * Update the visible omnibox suggestions.
+     * Update the visible models (list items).
      */
-    public void updateSuggestions(List<Pair<Integer, PropertyModel>> suggestionModels) {
-        mSuggestionItems.clear();
-        mSuggestionItems.addAll(suggestionModels);
+    public void updateModels(List<Pair<Integer, PropertyModel>> models) {
+        mModelList.clear();
+        mModelList.addAll(models);
         notifyDataSetChanged();
     }
 
     @Override
     public int getCount() {
-        return mSuggestionItems.size();
+        return mModelList.size();
     }
 
     @Override
     public Object getItem(int position) {
-        return mSuggestionItems.get(position);
+        return mModelList.get(position);
     }
 
     @Override
@@ -83,7 +83,7 @@
 
     @Override
     public int getItemViewType(int position) {
-        return mSuggestionItems.get(position).first;
+        return mModelList.get(position).first;
     }
 
     @Override
@@ -96,33 +96,33 @@
     public View getView(int position, View convertView, ViewGroup parent) {
         if (convertView == null || convertView.getTag(R.id.view_type) == null
                 || (int) convertView.getTag(R.id.view_type) != getItemViewType(position)) {
-            int suggestionTypeId = mSuggestionItems.get(position).first;
-            convertView = mViewBuilderMap.get(suggestionTypeId).first.buildView();
+            int modelTypeId = mModelList.get(position).first;
+            convertView = mViewBuilderMap.get(modelTypeId).first.buildView();
 
             // Since the view type returned by getView is not guaranteed to return a view of that
             // type, we need a means of checking it. The "view_type" tag is attached to the views
             // and identify what type the view is. This should allow lists that aren't necessarily
             // recycler views to work correctly with heterogeneous lists.
-            convertView.setTag(R.id.view_type, suggestionTypeId);
+            convertView.setTag(R.id.view_type, modelTypeId);
         }
 
-        PropertyModel suggestionModel = mSuggestionItems.get(position).second;
+        PropertyModel model = mModelList.get(position).second;
         PropertyModel viewModel =
-                getOrCreateModelFromExisting(convertView, mSuggestionItems.get(position));
-        for (PropertyKey key : suggestionModel.getAllSetProperties()) {
+                getOrCreateModelFromExisting(convertView, mModelList.get(position));
+        for (PropertyKey key : model.getAllSetProperties()) {
             if (key instanceof WritableIntPropertyKey) {
                 WritableIntPropertyKey intKey = (WritableIntPropertyKey) key;
-                viewModel.set(intKey, suggestionModel.get(intKey));
+                viewModel.set(intKey, model.get(intKey));
             } else if (key instanceof WritableBooleanPropertyKey) {
                 WritableBooleanPropertyKey booleanKey = (WritableBooleanPropertyKey) key;
-                viewModel.set(booleanKey, suggestionModel.get(booleanKey));
+                viewModel.set(booleanKey, model.get(booleanKey));
             } else if (key instanceof WritableFloatPropertyKey) {
                 WritableFloatPropertyKey floatKey = (WritableFloatPropertyKey) key;
-                viewModel.set(floatKey, suggestionModel.get(floatKey));
+                viewModel.set(floatKey, model.get(floatKey));
             } else if (key instanceof WritableObjectPropertyKey<?>) {
                 @SuppressWarnings({"unchecked", "rawtypes"})
                 WritableObjectPropertyKey objectKey = (WritableObjectPropertyKey) key;
-                viewModel.set(objectKey, suggestionModel.get(objectKey));
+                viewModel.set(objectKey, model.get(objectKey));
             } else {
                 assert false : "Unexpected key received";
             }
diff --git a/ui/aura/client/aura_constants.cc b/ui/aura/client/aura_constants.cc
index 9a7c3b3e..9182c6b 100644
--- a/ui/aura/client/aura_constants.cc
+++ b/ui/aura/client/aura_constants.cc
@@ -44,6 +44,8 @@
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kAlwaysOnTopKey, false)
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kAnimationsDisabledKey, false)
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::ImageSkia, kAppIconKey, nullptr)
+DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::ImageSkia, kAppIconLargeKey, nullptr)
+DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::ImageSkia, kAppIconSmallKey, nullptr)
 DEFINE_UI_CLASS_PROPERTY_KEY(int, kAppType, 0)
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::SizeF, kAspectRatio, nullptr)
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::ImageSkia, kAvatarIconKey, nullptr)
diff --git a/ui/aura/client/aura_constants.h b/ui/aura/client/aura_constants.h
index ca4b6c26..f3acf3f 100644
--- a/ui/aura/client/aura_constants.h
+++ b/ui/aura/client/aura_constants.h
@@ -49,8 +49,19 @@
 AURA_EXPORT extern const WindowProperty<bool>* const kAnimationsDisabledKey;
 
 // A property key to store the app icon, typically larger for shelf icons, etc.
+// This is not transported to the window service.
 AURA_EXPORT extern const WindowProperty<gfx::ImageSkia*>* const kAppIconKey;
 
+// A property key to store a large version of the app icon, which is
+// transported to the window service.
+AURA_EXPORT extern const WindowProperty<gfx::ImageSkia*>* const
+    kAppIconLargeKey;
+
+// A property key to store a smaller version of the app icon, which is
+// transported to the window service.
+AURA_EXPORT extern const WindowProperty<gfx::ImageSkia*>* const
+    kAppIconSmallKey;
+
 // A property key to store the type of window that will be used to record
 // pointer metrics. See AppType in ash/public/cpp/app_types.h for more details.
 AURA_EXPORT extern const WindowProperty<int>* const kAppType;
diff --git a/ui/aura/mus/property_converter.cc b/ui/aura/mus/property_converter.cc
index 70727eca..dd9cd90 100644
--- a/ui/aura/mus/property_converter.cc
+++ b/ui/aura/mus/property_converter.cc
@@ -72,10 +72,10 @@
 
 PropertyConverter::PropertyConverter() {
   // Add known aura properties with associated mus properties.
-  RegisterImageSkiaProperty(client::kAppIconKey,
-                            ws::mojom::WindowManager::kAppIcon_Property);
-  RegisterImageSkiaProperty(client::kWindowIconKey,
-                            ws::mojom::WindowManager::kWindowIcon_Property);
+  RegisterImageSkiaProperty(client::kAppIconLargeKey,
+                            ws::mojom::WindowManager::kAppIconLarge_Property);
+  RegisterImageSkiaProperty(client::kAppIconSmallKey,
+                            ws::mojom::WindowManager::kAppIconSmall_Property);
   RegisterPrimitiveProperty(client::kAlwaysOnTopKey,
                             ws::mojom::WindowManager::kAlwaysOnTop_Property,
                             CreateAcceptAnyValueCallback());
diff --git a/ui/aura/mus/window_tree_host_mus.cc b/ui/aura/mus/window_tree_host_mus.cc
index 3206b5a..a06c87c 100644
--- a/ui/aura/mus/window_tree_host_mus.cc
+++ b/ui/aura/mus/window_tree_host_mus.cc
@@ -202,7 +202,7 @@
   const gfx::Rect pixel_bounds(
       gfx::ScaleToFlooredPoint(bounds_in_dip.origin(), dsf),
       gfx::ScaleToCeiledSize(bounds_in_dip.size(), dsf));
-  if (!in_set_bounds_from_server_) {
+  if (!is_server_setting_bounds_) {
     // Update the LocalSurfaceIdAllocation here, rather than in WindowTreeHost
     // as WindowTreeClient (the delegate) needs that information before
     // OnWindowTreeHostBoundsWillChange().
@@ -223,7 +223,7 @@
 void WindowTreeHostMus::SetBoundsFromServer(
     const gfx::Rect& bounds,
     const viz::LocalSurfaceIdAllocation& local_surface_id_allocation) {
-  base::AutoReset<bool> resetter(&in_set_bounds_from_server_, true);
+  base::AutoReset<bool> resetter(&is_server_setting_bounds_, true);
   SetBounds(bounds, local_surface_id_allocation);
 }
 
diff --git a/ui/aura/mus/window_tree_host_mus.h b/ui/aura/mus/window_tree_host_mus.h
index 0d96a6a..45cdbf0 100644
--- a/ui/aura/mus/window_tree_host_mus.h
+++ b/ui/aura/mus/window_tree_host_mus.h
@@ -127,7 +127,7 @@
       const viz::LocalSurfaceIdAllocation& local_surface_id_allocation =
           viz::LocalSurfaceIdAllocation()) override;
 
-  bool in_set_bounds_from_server() const { return in_set_bounds_from_server_; }
+  bool is_server_setting_bounds() const { return is_server_setting_bounds_; }
 
  private:
   int64_t display_id_;
@@ -135,7 +135,7 @@
   WindowTreeHostMusDelegate* delegate_;
 
   // If true, the server initiated the bounds change.
-  bool in_set_bounds_from_server_ = false;
+  bool is_server_setting_bounds_ = false;
 
   std::unique_ptr<InputMethodMus> input_method_;
 
diff --git a/ui/chromeos/events/event_rewriter_chromeos.cc b/ui/chromeos/events/event_rewriter_chromeos.cc
index ea4ba92..69a8aeb2 100644
--- a/ui/chromeos/events/event_rewriter_chromeos.cc
+++ b/ui/chromeos/events/event_rewriter_chromeos.cc
@@ -285,6 +285,30 @@
 
 }  // namespace
 
+///////////////////////////////////////////////////////////////////////////////
+
+EventRewriterChromeOS::MutableKeyState::MutableKeyState()
+    : MutableKeyState(0, ui::DomCode::NONE, 0, ui::KeyboardCode::VKEY_NONAME) {}
+
+EventRewriterChromeOS::MutableKeyState::MutableKeyState(
+    const ui::KeyEvent* key_event)
+    : MutableKeyState(key_event->flags(),
+                      key_event->code(),
+                      key_event->GetDomKey(),
+                      key_event->key_code()) {}
+
+EventRewriterChromeOS::MutableKeyState::MutableKeyState(
+    int input_flags,
+    ui::DomCode input_code,
+    ui::DomKey::Base input_key,
+    ui::KeyboardCode input_key_code)
+    : flags(input_flags),
+      code(input_code),
+      key(input_key),
+      key_code(input_key_code) {}
+
+///////////////////////////////////////////////////////////////////////////////
+
 EventRewriterChromeOS::EventRewriterChromeOS(
     Delegate* delegate,
     ui::EventRewriter* sticky_keys_controller)
@@ -365,8 +389,11 @@
     std::unique_ptr<ui::Event>* rewritten_event) {
   if ((event.type() == ui::ET_KEY_PRESSED) ||
       (event.type() == ui::ET_KEY_RELEASED)) {
-    return RewriteKeyEvent(static_cast<const ui::KeyEvent&>(event),
-                           rewritten_event);
+    ui::EventRewriteStatus status =
+        RewriteKeyEvent(*((&event)->AsKeyEvent()), rewritten_event);
+    RewriteKeyEventInContext(*((&event)->AsKeyEvent()), rewritten_event,
+                             &status);
+    return status;
   }
   if ((event.type() == ui::ET_MOUSE_PRESSED) ||
       (event.type() == ui::ET_MOUSE_RELEASED)) {
@@ -392,6 +419,15 @@
 ui::EventRewriteStatus EventRewriterChromeOS::NextDispatchEvent(
     const ui::Event& last_event,
     std::unique_ptr<ui::Event>* new_event) {
+  if (dispatched_key_events_.size()) {
+    *new_event = std::move(dispatched_key_events_.back());
+    dispatched_key_events_.pop_back();
+
+    if (dispatched_key_events_.size())
+      return ui::EVENT_REWRITE_DISPATCH_ANOTHER;
+    return ui::EVENT_REWRITE_REWRITTEN;
+  }
+
   if (sticky_keys_controller_) {
     // In the case of sticky keys, we know what the events obtained here are:
     // modifier key releases that match the ones previously discarded. So, we
@@ -1220,6 +1256,123 @@
   return ui::EF_NONE;
 }
 
+void EventRewriterChromeOS::RewriteKeyEventInContext(
+    const ui::KeyEvent& key_event,
+    std::unique_ptr<ui::Event>* rewritten_event,
+    ui::EventRewriteStatus* status) {
+  DCHECK(status);
+
+  if (*status == EventRewriteStatus::EVENT_REWRITE_DISCARD)
+    return;
+
+  MutableKeyState current_key_state;
+  auto key_state_comparator =
+      [&current_key_state](
+          const std::pair<MutableKeyState, MutableKeyState>& key_state) {
+        return (current_key_state.code == key_state.first.code) &&
+               (current_key_state.key == key_state.first.key) &&
+               (current_key_state.key_code == key_state.first.key_code);
+      };
+
+  const int mapped_flag = ModifierDomKeyToEventFlag(key_event.GetDomKey());
+
+  if (key_event.type() == ET_KEY_PRESSED) {
+    current_key_state = MutableKeyState(
+        rewritten_event->get()
+            ? static_cast<const ui::KeyEvent*>(rewritten_event->get())
+            : &key_event);
+    MutableKeyState original_key_state(&key_event);
+    auto iter = std::find_if(pressed_key_states_.begin(),
+                             pressed_key_states_.end(), key_state_comparator);
+
+    // When a key is pressed, store |current_key_state| if it is not stored
+    // before.
+    if (iter == pressed_key_states_.end()) {
+      pressed_key_states_.push_back(
+          std::make_pair(current_key_state, original_key_state));
+    }
+
+    return;
+  }
+
+  DCHECK_EQ(key_event.type(), ET_KEY_RELEASED);
+
+  if (mapped_flag != EF_NONE) {
+    // The released key is a modifier
+
+    DomKey::Base current_key = key_event.GetDomKey();
+    auto key_state_iter = pressed_key_states_.begin();
+    int event_flags = rewritten_event->get() ? (*rewritten_event)->flags()
+                                             : key_event.flags();
+    rewritten_event->reset();
+
+    // Iterate the keys being pressed. Release the key events which satisfy one
+    // of the following conditions:
+    // (1) the key event's original key code (before key event rewriting if
+    // any) is the same with the key to be released.
+    // (2) the key event is rewritten and its original flags are influenced by
+    // the key to be released.
+    // Example: Press the Launcher button, Press the Up Arrow button, Release
+    // the Launcher button. When Launcher is released: the key event whose key
+    // code is Launcher should be released because it satisfies the condition 1;
+    // the key event whose key code is PageUp should be released because it
+    // satisfies the condition 2.
+    while (key_state_iter != pressed_key_states_.end()) {
+      const bool is_rewritten =
+          (key_state_iter->first.key != key_state_iter->second.key);
+      const bool flag_affected = key_state_iter->second.flags & mapped_flag;
+      const bool should_release = key_state_iter->second.key == current_key ||
+                                  (flag_affected && is_rewritten);
+
+      if (should_release) {
+        // If the key should be released, create a key event for it.
+        std::unique_ptr<ui::KeyEvent> dispatched_event =
+            std::make_unique<ui::KeyEvent>(
+                key_event.type(), key_state_iter->first.key_code,
+                key_state_iter->first.code, event_flags,
+                key_state_iter->first.key, key_event.time_stamp());
+        if (!rewritten_event->get())
+          *rewritten_event = std::move(dispatched_event);
+        else
+          dispatched_key_events_.push_back(std::move(dispatched_event));
+
+        key_state_iter = pressed_key_states_.erase(key_state_iter);
+        continue;
+      }
+      key_state_iter++;
+    }
+
+    if (dispatched_key_events_.empty()) {
+      *status = rewritten_event->get()
+                    ? EventRewriteStatus::EVENT_REWRITE_REWRITTEN
+                    : EventRewriteStatus::EVENT_REWRITE_DISCARD;
+    } else {
+      *status = EventRewriteStatus::EVENT_REWRITE_DISPATCH_ANOTHER;
+    }
+  } else {
+    // The released key is not a modifier
+
+    current_key_state = MutableKeyState(
+        rewritten_event->get()
+            ? static_cast<const ui::KeyEvent*>(rewritten_event->get())
+            : &key_event);
+    auto iter = std::find_if(pressed_key_states_.begin(),
+                             pressed_key_states_.end(), key_state_comparator);
+    if (iter != pressed_key_states_.end()) {
+      pressed_key_states_.erase(iter);
+    } else {
+      // Event rewriting may create a meaningless key event.
+      // For example: press the Up Arrow button, press the Launcher button,
+      // release the Up Arrow. When the Up Arrow button is released, key event
+      // rewriting happens. However, the rewritten event is not among
+      // |pressed_key_states_|. So it should be blocked and the original event
+      // should be propagated.
+      rewritten_event->reset();
+      *status = EventRewriteStatus::EVENT_REWRITE_CONTINUE;
+    }
+  }
+}
+
 void EventRewriterChromeOS::KeyboardDeviceAddedInternal(
     int device_id,
     DeviceType type,
diff --git a/ui/chromeos/events/event_rewriter_chromeos.h b/ui/chromeos/events/event_rewriter_chromeos.h
index f33ab8c..ca389643 100644
--- a/ui/chromeos/events/event_rewriter_chromeos.h
+++ b/ui/chromeos/events/event_rewriter_chromeos.h
@@ -5,10 +5,13 @@
 #ifndef UI_CHROMEOS_EVENTS_EVENT_REWRITER_CHROMEOS_H_
 #define UI_CHROMEOS_EVENTS_EVENT_REWRITER_CHROMEOS_H_
 
+#include <list>
 #include <map>
 #include <memory>
 #include <set>
 #include <string>
+#include <utility>
+#include <vector>
 
 #include "base/files/file_path.h"
 #include "base/macros.h"
@@ -64,6 +67,13 @@
 
   // Things that keyboard-related rewriter phases can change about an Event.
   struct MutableKeyState {
+    MutableKeyState();
+    explicit MutableKeyState(const ui::KeyEvent* key_event);
+    MutableKeyState(int input_flags,
+                    ui::DomCode input_code,
+                    ui::DomKey::Base input_key,
+                    ui::KeyboardCode input_key_code);
+
     int flags;
     ui::DomCode code;
     ui::DomKey::Base key;
@@ -215,6 +225,13 @@
   void RewriteLocatedEvent(const ui::Event& event, int* flags);
   int RewriteModifierClick(const ui::MouseEvent& event, int* flags);
 
+  // Take the keys being pressed into consideration, in contrast to
+  // RewriteKeyEvent which computes the rewritten event and event rewrite status
+  // in stateless way.
+  void RewriteKeyEventInContext(const ui::KeyEvent& event,
+                                std::unique_ptr<ui::Event>* rewritten_event,
+                                ui::EventRewriteStatus* status);
+
   // A set of device IDs whose press event has been rewritten.
   // This is to ensure that press and release events are rewritten consistently.
   std::set<int> pressed_device_ids_;
@@ -229,6 +246,14 @@
 
   Delegate* const delegate_;
 
+  // For each pair, the first element is the rewritten key state and the second
+  // one is the original key state. If no key event rewriting happens, the first
+  // element and the second element are identical.
+  std::list<std::pair<MutableKeyState, MutableKeyState>> pressed_key_states_;
+
+  // Store key events when there are more than one key events to be dispatched.
+  std::vector<std::unique_ptr<ui::KeyEvent>> dispatched_key_events_;
+
   // The sticky keys controller is not owned here;
   // at time of writing it is a singleton in ash::Shell.
   ui::EventRewriter* const sticky_keys_controller_;
diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc
index 9a35b6a1..231d93b 100644
--- a/ui/events/blink/input_handler_proxy.cc
+++ b/ui/events/blink/input_handler_proxy.cc
@@ -261,6 +261,14 @@
     std::unique_ptr<EventWithCallback> event_with_callback,
     const base::TimeTicks now) {
   ui::LatencyInfo monitored_latency_info = event_with_callback->latency_info();
+
+  if (event_with_callback->event().GetType() ==
+      WebInputEvent::kGestureScrollUpdate) {
+    monitored_latency_info.set_scroll_update_delta(
+        static_cast<const WebGestureEvent&>(event_with_callback->event())
+            .data.scroll_update.delta_y);
+  }
+
   std::unique_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor =
       input_handler_->CreateLatencyInfoSwapPromiseMonitor(
           &monitored_latency_info);
diff --git a/ui/events/blink/input_handler_proxy_unittest.cc b/ui/events/blink/input_handler_proxy_unittest.cc
index 75a2795e..edabb33 100644
--- a/ui/events/blink/input_handler_proxy_unittest.cc
+++ b/ui/events/blink/input_handler_proxy_unittest.cc
@@ -1916,6 +1916,67 @@
   testing::Mock::VerifyAndClearExpectations(&mock_input_handler_);
 }
 
+TEST_F(InputHandlerProxyEventQueueTest, LatencyInfoScrollUpdateDelta) {
+  // Scroll on compositor.
+  cc::InputHandlerScrollResult scroll_result_did_scroll_;
+  scroll_result_did_scroll_.did_scroll = true;
+
+  EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_))
+      .WillOnce(testing::Return(kImplThreadScrollState));
+  EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(1);
+  EXPECT_CALL(
+      mock_input_handler_,
+      ScrollBy(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0))))
+      .WillOnce(testing::Return(scroll_result_did_scroll_));
+  EXPECT_CALL(mock_input_handler_, ScrollEnd(testing::_, true));
+
+  HandleGestureEvent(WebInputEvent::kGestureScrollBegin);
+  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -20);
+  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -40);
+  HandleGestureEvent(WebInputEvent::kGestureScrollEnd);
+  input_handler_proxy_->DeliverInputForBeginFrame();
+
+  EXPECT_EQ(0ul, event_queue().size());
+  // Should run callbacks for every original events.
+  EXPECT_EQ(4ul, event_disposition_recorder_.size());
+  EXPECT_EQ(4ul, latency_info_recorder_.size());
+  EXPECT_EQ(false, latency_info_recorder_[1].coalesced());
+  EXPECT_EQ(-60, latency_info_recorder_[1].scroll_update_delta());
+
+  EXPECT_EQ(true, latency_info_recorder_[2].coalesced());
+  EXPECT_EQ(-60, latency_info_recorder_[2].scroll_update_delta());
+
+  testing::Mock::VerifyAndClearExpectations(&mock_input_handler_);
+
+  latency_info_recorder_.clear();
+
+  // Scroll on main thread.
+  cc::InputHandlerScrollResult scroll_result_did_not_scroll_;
+  scroll_result_did_not_scroll_.did_scroll = false;
+  EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_))
+      .WillOnce(testing::Return(kMainThreadScrollState));
+  EXPECT_CALL(mock_input_handler_, ScrollEnd(testing::_, true));
+
+  HandleGestureEvent(WebInputEvent::kGestureScrollBegin);
+  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -20);
+  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -40);
+  HandleGestureEvent(WebInputEvent::kGestureScrollEnd);
+  input_handler_proxy_->DeliverInputForBeginFrame();
+
+  EXPECT_EQ(0ul, event_queue().size());
+  // Should run callbacks for every original events.
+  EXPECT_EQ(8ul, event_disposition_recorder_.size());
+  EXPECT_EQ(4ul, latency_info_recorder_.size());
+
+  EXPECT_EQ(false, latency_info_recorder_[1].coalesced());
+  EXPECT_EQ(-20, latency_info_recorder_[1].scroll_update_delta());
+
+  EXPECT_EQ(false, latency_info_recorder_[2].coalesced());
+  EXPECT_EQ(-40, latency_info_recorder_[2].scroll_update_delta());
+
+  testing::Mock::VerifyAndClearExpectations(&mock_input_handler_);
+}
+
 class InputHandlerProxyMainThreadScrollingReasonTest
     : public InputHandlerProxyTest {
  public:
diff --git a/ui/events/keycodes/keyboard_code_conversion.cc b/ui/events/keycodes/keyboard_code_conversion.cc
index 45a6999..c1f48404 100644
--- a/ui/events/keycodes/keyboard_code_conversion.cc
+++ b/ui/events/keycodes/keyboard_code_conversion.cc
@@ -263,4 +263,34 @@
       DomCodeToUsLayoutKeyboardCode(dom_code));
 }
 
+int ModifierDomKeyToEventFlag(DomKey key) {
+  switch (key) {
+    case DomKey::ALT:
+      return EF_ALT_DOWN;
+    case DomKey::ALT_GRAPH:
+      return EF_ALTGR_DOWN;
+    case DomKey::CAPS_LOCK:
+      return EF_CAPS_LOCK_ON;
+    case DomKey::CONTROL:
+      return EF_CONTROL_DOWN;
+    case DomKey::META:
+      return EF_COMMAND_DOWN;
+    case DomKey::SHIFT:
+      return EF_SHIFT_DOWN;
+    case DomKey::SHIFT_LEVEL5:
+      return EF_MOD3_DOWN;
+    default:
+      return EF_NONE;
+  }
+  // Not represented:
+  //   DomKey::ACCEL
+  //   DomKey::FN
+  //   DomKey::FN_LOCK
+  //   DomKey::HYPER
+  //   DomKey::NUM_LOCK
+  //   DomKey::SCROLL_LOCK
+  //   DomKey::SUPER
+  //   DomKey::SYMBOL_LOCK
+}
+
 }  // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion.h b/ui/events/keycodes/keyboard_code_conversion.h
index d3c035ce..3450f520 100644
--- a/ui/events/keycodes/keyboard_code_conversion.h
+++ b/ui/events/keycodes/keyboard_code_conversion.h
@@ -107,6 +107,10 @@
 EVENTS_BASE_EXPORT KeyboardCode
 DomCodeToUsLayoutNonLocatedKeyboardCode(DomCode dom_code);
 
+// Returns the ui::EventFlags value associated with a modifier key,
+// or 0 (EF_NONE) if the key is not a modifier.
+EVENTS_BASE_EXPORT int ModifierDomKeyToEventFlag(DomKey key);
+
 }  // namespace ui
 
 #endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_H_
diff --git a/ui/events/ozone/BUILD.gn b/ui/events/ozone/BUILD.gn
index 48f7ad99..d1c8e6c 100644
--- a/ui/events/ozone/BUILD.gn
+++ b/ui/events/ozone/BUILD.gn
@@ -188,8 +188,6 @@
     "layout/keyboard_layout_engine.h",
     "layout/keyboard_layout_engine_manager.cc",
     "layout/keyboard_layout_engine_manager.h",
-    "layout/layout_util.cc",
-    "layout/layout_util.h",
     "layout/no/no_keyboard_layout_engine.cc",
     "layout/no/no_keyboard_layout_engine.h",
     "layout/stub/stub_keyboard_layout_engine.cc",
diff --git a/ui/events/ozone/evdev/keyboard_evdev.cc b/ui/events/ozone/evdev/keyboard_evdev.cc
index a0f5420..a4ba3f5 100644
--- a/ui/events/ozone/evdev/keyboard_evdev.cc
+++ b/ui/events/ozone/evdev/keyboard_evdev.cc
@@ -12,10 +12,10 @@
 #include "ui/events/event_utils.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
+#include "ui/events/keycodes/keyboard_code_conversion.h"
 #include "ui/events/ozone/evdev/keyboard_util_evdev.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
-#include "ui/events/ozone/layout/layout_util.h"
 
 namespace ui {
 
diff --git a/ui/events/ozone/layout/layout_util.cc b/ui/events/ozone/layout/layout_util.cc
deleted file mode 100644
index eaafb10e..0000000
--- a/ui/events/ozone/layout/layout_util.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/events/ozone/layout/layout_util.h"
-
-#include "ui/events/event_constants.h"
-#include "ui/events/keycodes/dom/dom_key.h"
-
-namespace ui {
-
-int ModifierDomKeyToEventFlag(DomKey key) {
-  switch (key) {
-    case DomKey::ALT:
-      return EF_ALT_DOWN;
-    case DomKey::ALT_GRAPH:
-      return EF_ALTGR_DOWN;
-    case DomKey::CAPS_LOCK:
-      return EF_CAPS_LOCK_ON;
-    case DomKey::CONTROL:
-      return EF_CONTROL_DOWN;
-    case DomKey::META:
-      return EF_COMMAND_DOWN;
-    case DomKey::SHIFT:
-      return EF_SHIFT_DOWN;
-    case DomKey::SHIFT_LEVEL5:
-      return EF_MOD3_DOWN;
-    default:
-      return EF_NONE;
-  }
-  // Not represented:
-  //   DomKey::ACCEL
-  //   DomKey::FN
-  //   DomKey::FN_LOCK
-  //   DomKey::HYPER
-  //   DomKey::NUM_LOCK
-  //   DomKey::SCROLL_LOCK
-  //   DomKey::SUPER
-  //   DomKey::SYMBOL_LOCK
-}
-
-}  // namespace ui
diff --git a/ui/events/ozone/layout/layout_util.h b/ui/events/ozone/layout/layout_util.h
deleted file mode 100644
index 5a2ba52..0000000
--- a/ui/events/ozone/layout/layout_util.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_EVENTS_OZONE_LAYOUT_LAYOUT_UTIL_H_
-#define UI_EVENTS_OZONE_LAYOUT_LAYOUT_UTIL_H_
-
-// TODO(kpschoedel): consider moving this out of Ozone.
-
-#include "base/strings/string16.h"
-#include "ui/events/keycodes/dom/dom_key.h"
-#include "ui/events/keycodes/keyboard_codes.h"
-#include "ui/events/ozone/layout/events_ozone_layout_export.h"
-
-namespace ui {
-
-// Returns the ui::EventFlags value associated with a modifier key,
-// or 0 (EF_NONE) if the key is not a modifier.
-EVENTS_OZONE_LAYOUT_EXPORT int ModifierDomKeyToEventFlag(DomKey key);
-
-}  // namespace ui
-
-#endif  // UI_EVENTS_OZONE_LAYOUT_LAYOUT_UTIL_H_
diff --git a/ui/events/ozone/layout/stub/stub_keyboard_layout_engine.cc b/ui/events/ozone/layout/stub/stub_keyboard_layout_engine.cc
index 090f95158..7ca33e2 100644
--- a/ui/events/ozone/layout/stub/stub_keyboard_layout_engine.cc
+++ b/ui/events/ozone/layout/stub/stub_keyboard_layout_engine.cc
@@ -8,7 +8,6 @@
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/dom_key.h"
 #include "ui/events/keycodes/keyboard_code_conversion.h"
-#include "ui/events/ozone/layout/layout_util.h"
 
 namespace ui {
 
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
index 7e8a26f..90577c4ae 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -836,6 +836,11 @@
   icon.classList.add('item-icon');
   icon.setAttribute('root-type-icon', rootType);
 
+  // MyFiles shows expanded by default.
+  if (rootType === VolumeManagerCommon.RootType.MY_FILES) {
+    item.mayHaveChildren_ = true;
+    item.expanded = true;
+  }
   // Populate children of this volume.
   item.updateSubDirectories(false /* recursive */);
 
@@ -918,9 +923,6 @@
   const onSuccess = (entries) => {
     this.entries_ = entries;
     this.updateSubElementsFromList(recursive);
-    if (this.entries_.length > 0) {
-      this.expanded = true;
-    }
     opt_successCallback && opt_successCallback();
   };
   const reader = this.entry.createReader();
diff --git a/ui/file_manager/integration_tests/file_manager/background.js b/ui/file_manager/integration_tests/file_manager/background.js
index 1fc3e3e..2b0c51cc 100644
--- a/ui/file_manager/integration_tests/file_manager/background.js
+++ b/ui/file_manager/integration_tests/file_manager/background.js
@@ -271,7 +271,7 @@
  * @param {?string} initialRoot Root path to be used as a default current
  *     directory during initialization. Can be null, for no default path.
  * @param {!Array<TestEntryInfo>>} initialLocalEntries List of initial
- *     entries to load in Google Drive (defaults to a basic entry set).
+ *     entries to load in Downloads (defaults to a basic entry set).
  * @param {!Array<TestEntryInfo>>} initialDriveEntries List of initial
  *     entries to load in Google Drive (defaults to a basic entry set).
  * @param {Object} appState App state to be passed with on opening the Files
@@ -469,3 +469,26 @@
   const expandedSubtree = treeItem + '> .tree-children[expanded]';
   await remoteCall.waitForElement(appId, expandedSubtree);
 }
+
+/**
+ * Mounts crostini volume by clicking on the fake crostini root.
+ * @param {string} appId Files app windowId.
+ * @param {!Array<TestEntryInfo>>} initialEntries List of initial entries to
+ *     load in Crostini (defaults to a basic entry set).
+ */
+async function mountCrostini(appId, initialEntries = BASIC_CROSTINI_ENTRY_SET) {
+  const fakeLinuxFiles = '#directory-tree [root-type-icon="crostini"]';
+  const realLinxuFiles = '#directory-tree [volume-type-icon="crostini"]';
+
+  // Add entries to crostini volume, but do not mount.
+  await addEntries(['crostini'], initialEntries);
+
+  // Linux files fake root is shown.
+  await remoteCall.waitForElement(appId, fakeLinuxFiles);
+
+  // Mount crostini, and ensure real root and files are shown.
+  remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [fakeLinuxFiles]);
+  await remoteCall.waitForElement(appId, realLinxuFiles);
+  const files = TestEntryInfo.getExpectedRows(BASIC_CROSTINI_ENTRY_SET);
+  await remoteCall.waitForFiles(appId, files);
+}
diff --git a/ui/file_manager/integration_tests/file_manager/crostini.js b/ui/file_manager/integration_tests/file_manager/crostini.js
index f53d97b..ed0889ba 100644
--- a/ui/file_manager/integration_tests/file_manager/crostini.js
+++ b/ui/file_manager/integration_tests/file_manager/crostini.js
@@ -10,17 +10,7 @@
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
-  // Add entries to crostini volume, but do not mount.
-  await addEntries(['crostini'], BASIC_CROSTINI_ENTRY_SET);
-
-  // Linux files fake root is shown.
-  await remoteCall.waitForElement(appId, fakeLinuxFiles);
-
-  // Mount crostini, and ensure real root and files are shown.
-  remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [fakeLinuxFiles]);
-  await remoteCall.waitForElement(appId, realLinxuFiles);
-  const files = TestEntryInfo.getExpectedRows(BASIC_CROSTINI_ENTRY_SET);
-  await remoteCall.waitForFiles(appId, files);
+  await mountCrostini(appId);
 
   // Unmount and ensure fake root is shown.
   remoteCall.callRemoteTestUtil('unmount', null, ['crostini']);
diff --git a/ui/file_manager/integration_tests/file_manager/metadata.js b/ui/file_manager/integration_tests/file_manager/metadata.js
index 3618e06..b4a7bb0f 100644
--- a/ui/file_manager/integration_tests/file_manager/metadata.js
+++ b/ui/file_manager/integration_tests/file_manager/metadata.js
@@ -121,7 +121,7 @@
   // Navigate 2 folders deep, because navigating in directory tree might
   // trigger further metadata fetches.
   await remoteCall.navigateWithDirectoryTree(
-      appId, '/root/photos1/folder1', 'My Drive');
+      appId, '/root/photos1/folder1', 'My Drive', 'drive');
 
   // Fetch the metadata stats.
   const metadataStats =
@@ -216,11 +216,12 @@
 
   // Open Files app on Drive.
   const appId = await setupAndWaitUntilReady(RootPath.DRIVE, entries, entries);
+  console.log('setupAndWaitUntilReady finished!');
 
   // Navigate only 1 folder deep,which is slightly different from
   // metadatatDrive test.
   await remoteCall.navigateWithDirectoryTree(
-      appId, '/root/folder1', 'My Drive');
+      appId, '/root/folder1', 'My Drive', 'drive');
 
   // Wait for the metadata stats to reach the desired count.
   // File list component, doesn't display all files at once for performance
@@ -287,7 +288,7 @@
 
   // Navigate to Team Drives root.
   await remoteCall.navigateWithDirectoryTree(
-      appId, '/team_drives', 'Team Drives');
+      appId, '/team_drives', 'Team Drives', 'drive');
 
   // Expand Team Drives, because expanding might need metadata.
   const expandIcon = teamDriveTreeItem + ' > .tree-row > .expand-icon';
diff --git a/ui/file_manager/integration_tests/file_manager/my_files.js b/ui/file_manager/integration_tests/file_manager/my_files.js
index aab3fb6..3faf773f 100644
--- a/ui/file_manager/integration_tests/file_manager/my_files.js
+++ b/ui/file_manager/integration_tests/file_manager/my_files.js
@@ -294,3 +294,41 @@
       appId, expectedRows2,
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 };
+
+/**
+ * Tests that MyFiles only auto expands once.
+ */
+testcase.myFilesAutoExpandOnce = async () => {
+  // Open Files app on local Downloads.
+  const appId = await setupAndWaitUntilReady(
+      RootPath.DOWNLOADS, [ENTRIES.photos], [ENTRIES.beautiful]);
+
+  // Collapse MyFiles.
+  const myFiles = '#directory-tree [entry-label="My files"]';
+  let expandIcon = myFiles + '[expanded] > .tree-row[has-children=true]' +
+      '> .expand-icon';
+  await remoteCall.waitAndClickElement(appId, expandIcon);
+  await remoteCall.waitForElement(appId, myFiles + ':not([expanded])');
+
+  // Expand Google Drive.
+  const driveGrandRoot = '#directory-tree [entry-label="Google Drive"]';
+  expandIcon = driveGrandRoot + ' > .tree-row > .expand-icon';
+  await remoteCall.waitAndClickElement(appId, expandIcon);
+
+  // Wait for its subtree to expand and display its children.
+  const expandedSubItems =
+      driveGrandRoot + ' > .tree-children[expanded] > .tree-item';
+  await remoteCall.waitForElement(appId, expandedSubItems);
+
+  // Click on My Drive
+  const myDrive = '#directory-tree [entry-label="My Drive"]';
+  await remoteCall.waitAndClickElement(appId, myDrive);
+
+  // Wait for My Drive to selected.
+  await remoteCall.waitForFiles(
+      appId, [ENTRIES.beautiful.getExpectedRow()],
+      {ignoreFileSize: true, ignoreLastModifiedTime: true});
+
+  // Check that MyFiles is still collapsed.
+  await remoteCall.waitForElement(appId, myFiles + ':not([expanded])');
+};
diff --git a/ui/file_manager/integration_tests/file_manager/quick_view.js b/ui/file_manager/integration_tests/file_manager/quick_view.js
index 651f5473..20a92515 100644
--- a/ui/file_manager/integration_tests/file_manager/quick_view.js
+++ b/ui/file_manager/integration_tests/file_manager/quick_view.js
@@ -236,33 +236,12 @@
  * Tests opening Quick View on a Crostini file.
  */
 testcase.openQuickViewCrostini = async () => {
-  const fakeLinuxFiles = '#directory-tree [root-type-icon="crostini"]';
-  const realLinuxFiles = '#directory-tree [volume-type-icon="crostini"]';
-
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
 
-  // Check: the fake Linux files icon should be shown.
-  await remoteCall.waitForElement(appId, fakeLinuxFiles);
-
-  // Add files to the Crostini volume.
-  await addEntries(['crostini'], BASIC_CROSTINI_ENTRY_SET);
-
-  // Click the fake Linux files icon to mount the Crostini volume.
-  chrome.test.assertTrue(
-      !!await remoteCall.callRemoteTestUtil(
-          'fakeMouseClick', appId, [fakeLinuxFiles]),
-      'fakeMouseClick failed');
-
-  // Check: the Crostini volume icon should appear.
-  await remoteCall.waitForElement(appId, realLinuxFiles);
-
-  // Check: the Crostini files should appear in the file list.
-  const files = TestEntryInfo.getExpectedRows(BASIC_CROSTINI_ENTRY_SET);
-  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
-
   // Open a Crostini file in Quick View.
+  await mountCrostini(appId);
   await openQuickView(appId, ENTRIES.hello.nameText);
 };
 
diff --git a/ui/file_manager/integration_tests/file_manager/recents.js b/ui/file_manager/integration_tests/file_manager/recents.js
index 69c387cd..6c11f6c 100644
--- a/ui/file_manager/integration_tests/file_manager/recents.js
+++ b/ui/file_manager/integration_tests/file_manager/recents.js
@@ -39,6 +39,24 @@
   await verifyRecents(appId);
 };
 
+testcase.recentsCrostiniNotMounted = async () => {
+  // Add entries to crostini volume, but do not mount.
+  // The crostini entries should not show up in recents.
+  await addEntries(['crostini'], BASIC_CROSTINI_ENTRY_SET);
+
+  const appId = await setupAndWaitUntilReady(
+      RootPath.DOWNLOADS, [ENTRIES.beautiful, ENTRIES.photos], []);
+  await verifyRecents(appId, [ENTRIES.beautiful]);
+};
+
+testcase.recentsCrostiniMounted = async () => {
+  const appId = await setupAndWaitUntilReady(
+      RootPath.DOWNLOADS, [ENTRIES.beautiful, ENTRIES.photos], []);
+  // Mount crostini and both downloads and crostini entries will be in recents.
+  await mountCrostini(appId);
+  await verifyRecents(appId);
+};
+
 testcase.recentsDownloadsAndDrive = async () => {
   // Populate both downloads and drive with disjoint sets of files.
   const appId = await setupAndWaitUntilReady(
diff --git a/ui/gfx/geometry/mojo/geometry.typemap b/ui/gfx/geometry/mojo/geometry.typemap
index f7939a8b..db70bf0 100644
--- a/ui/gfx/geometry/mojo/geometry.typemap
+++ b/ui/gfx/geometry/mojo/geometry.typemap
@@ -18,7 +18,7 @@
   "//ui/gfx/geometry/vector3d_f.h",
 ]
 traits_headers = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.h" ]
-deps = [
+public_deps = [
   "//ui/gfx/geometry/mojo:struct_traits",
 ]
 type_mappings = [
diff --git a/ui/gl/init/create_gr_gl_interface.cc b/ui/gl/init/create_gr_gl_interface.cc
index 7d3c3eb..bdba564 100644
--- a/ui/gl/init/create_gr_gl_interface.cc
+++ b/ui/gl/init/create_gr_gl_interface.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "ui/gl/init/create_gr_gl_interface.h"
+#include "build/build_config.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_version_info.h"
 #include "ui/gl/progress_reporter.h"
@@ -40,6 +41,20 @@
   };
 }
 
+template <typename R, typename... Args>
+GrGLFunction<R GR_GL_FUNCTION_TYPE(Args...)> bind_with_flush_on_mac(
+    R(GL_BINDING_CALL* func)(Args...)) {
+#if defined(OS_MACOSX)
+  return [func](Args... args) {
+    glFlush();
+    func(args...);
+    glFlush();
+  };
+#else
+  return func;
+#endif
+}
+
 const GLubyte* GetStringHook(const char* version_string, GLenum name) {
   switch (name) {
     case GL_VERSION:
@@ -300,7 +315,8 @@
       gl->glGetFramebufferAttachmentParameterivEXTFn;
   functions->fGetRenderbufferParameteriv =
       gl->glGetRenderbufferParameterivEXTFn;
-  functions->fBindFramebuffer = gl->glBindFramebufferEXTFn;
+  functions->fBindFramebuffer =
+      bind_with_flush_on_mac(gl->glBindFramebufferEXTFn);
   functions->fFramebufferTexture2D = gl->glFramebufferTexture2DEXTFn;
   functions->fCheckFramebufferStatus = gl->glCheckFramebufferStatusEXTFn;
   functions->fDeleteFramebuffers =
diff --git a/ui/login/account_picker/chromeos_screen_account_picker.js b/ui/login/account_picker/chromeos_screen_account_picker.js
index 931b0d8..5dd3af3 100644
--- a/ui/login/account_picker/chromeos_screen_account_picker.js
+++ b/ui/login/account_picker/chromeos_screen_account_picker.js
@@ -25,10 +25,6 @@
   return {
     EXTERNAL_API: [
       'loadUsers',
-      'runAppForTesting',
-      'setApps',
-      'setShouldShowApps',
-      'showAppError',
       'updateUserImage',
       'setCapsLockState',
       'removeUser',
@@ -123,9 +119,10 @@
       this.showing_ = true;
 
       chrome.send('loginUIStateChanged', ['account-picker', true]);
-      $('login-header-bar').signinUIState = SIGNIN_UI_STATE.ACCOUNT_PICKER;
+
       chrome.send('hideCaptivePortal');
       var podRow = $('pod-row');
+      podRow.signinUIState = SIGNIN_UI_STATE.ACCOUNT_PICKER;
       podRow.handleBeforeShow();
 
       // In case of the preselected pod onShow will be called once pod
@@ -165,8 +162,9 @@
       $('bubble-persistent').hide();
       this.showing_ = false;
       chrome.send('loginUIStateChanged', ['account-picker', false]);
-      $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
-      $('pod-row').handleHide();
+      var podRow = $('pod-row');
+      podRow.signinUIState = SIGNIN_UI_STATE.HIDDEN;
+      podRow.handleHide();
     },
 
     /**
@@ -257,55 +255,9 @@
     /**
      * Loads given users in pod row.
      * @param {array} users Array of user.
-     * @param {boolean} showGuest Whether to show guest session button.
      */
-    loadUsers: function(users, showGuest) {
+    loadUsers: function(users) {
       $('pod-row').loadPods(users);
-      $('login-header-bar').showGuestButton = showGuest;
-      // On Desktop, #login-header-bar has a shadow if there are 8+ profiles.
-      if (Oobe.getInstance().displayType == DISPLAY_TYPE.DESKTOP_USER_MANAGER)
-        $('login-header-bar').classList.toggle('shadow', users.length > 8);
-    },
-
-    /**
-     * Runs app with a given id from the list of loaded apps.
-     * @param {!string} app_id of an app to run.
-     * @param {boolean=} opt_diagnostic_mode Whether to run the app in
-     *     diagnostic mode.  Default is false.
-     */
-    runAppForTesting: function(app_id, opt_diagnostic_mode) {
-      $('pod-row').findAndRunAppForTesting(app_id, opt_diagnostic_mode);
-    },
-
-    /**
-     * Adds given apps to the pod row.
-     * @param {array} apps Array of apps.
-     */
-    setApps: function(apps) {
-      $('pod-row').setApps(apps);
-    },
-
-    /**
-     * Sets the flag of whether app pods should be visible.
-     * @param {boolean} shouldShowApps Whether to show app pods.
-     */
-    setShouldShowApps: function(shouldShowApps) {
-      $('pod-row').setShouldShowApps(shouldShowApps);
-    },
-
-    /**
-     * Shows the given kiosk app error message.
-     * @param {!string} message Error message to show.
-     */
-    showAppError: function(message) {
-      // TODO(nkostylev): Figure out a way to show kiosk app launch error
-      // pointing to the kiosk app pod.
-      /** @const */ var BUBBLE_PADDING = 12;
-      $('bubble').showTextForElement($('pod-row'),
-                                     message,
-                                     cr.ui.Bubble.Attachment.BOTTOM,
-                                     $('pod-row').offsetWidth / 2,
-                                     BUBBLE_PADDING);
     },
 
     /**
diff --git a/ui/login/account_picker/chromeos_user_pod_row.css b/ui/login/account_picker/chromeos_user_pod_row.css
index 75a143df..88c4092c 100644
--- a/ui/login/account_picker/chromeos_user_pod_row.css
+++ b/ui/login/account_picker/chromeos_user_pod_row.css
@@ -496,20 +496,6 @@
   width: 27px;
 }
 
-.launch-app-button-container {
-  display: block;
-  flex: auto;
-  text-align: center;
-}
-
-.launch-app-button {
-  display: inline;
-  margin-top: 6px !important;
-  max-width: 100%;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
 .pod[auth-type='onlineSignIn'] .reauth-hint-container {
   display: flex;
   justify-content: center;
diff --git a/ui/login/account_picker/chromeos_user_pod_row.js b/ui/login/account_picker/chromeos_user_pod_row.js
index ef413054..9ae0957 100644
--- a/ui/login/account_picker/chromeos_user_pod_row.js
+++ b/ui/login/account_picker/chromeos_user_pod_row.js
@@ -1088,22 +1088,6 @@
     },
 
     /**
-     * Gets the container holding the launch app button.
-     * @type {!HTMLButtonElement}
-     */
-    get launchAppButtonContainerElement() {
-      return this.querySelector('.launch-app-button-container');
-    },
-
-    /**
-     * Gets launch app button.
-     * @type {!HTMLButtonElement}
-     */
-    get launchAppButtonElement() {
-      return this.querySelector('.launch-app-button');
-    },
-
-    /**
      * Gets action box area.
      * @type {!HTMLInputElement}
      */
@@ -1289,7 +1273,7 @@
     },
 
     updateActionBoxArea: function() {
-      if (this.user_.publicAccount || this.user_.isApp) {
+      if (this.user_.publicAccount) {
         this.actionBoxAreaElement.hidden = true;
         return;
       }
@@ -1326,8 +1310,6 @@
         } else {
           this.querySelector('.mp-policy-not-allowed-msg').hidden = false;
         }
-      } else if (this.user_.isApp) {
-        this.setUserPodIconType('app');
       }
     },
 
@@ -2906,115 +2888,6 @@
   };
 
   /**
-   * Creates a user pod that represents kiosk app.
-   * @constructor
-   * @extends {UserPod}
-   */
-  var KioskAppPod = cr.ui.define(function() {
-    var node = UserPod();
-    return node;
-  });
-
-  KioskAppPod.prototype = {
-    __proto__: UserPod.prototype,
-
-    /** @override */
-    decorate: function() {
-      UserPod.prototype.decorate.call(this);
-      this.launchAppButtonElement.addEventListener('click',
-                                                   this.activate.bind(this));
-    },
-
-    /** @override */
-    update: function() {
-      this.imageElement.src = this.user.iconUrl;
-      this.imageElement.alt = this.user.label;
-      this.imageElement.title = this.user.label;
-      this.smallPodImageElement.src = this.user.iconUrl;
-      this.smallPodImageElement.alt = this.user.label;
-      this.smallPodImageElement.title = this.user.label;
-      this.passwordEntryContainerElement.hidden = true;
-      this.launchAppButtonContainerElement.hidden = false;
-      this.nameElement.textContent = this.user.label;
-      this.smallPodNameElement.textContent = this.user.label;
-      this.reauthNameHintElement.textContent = this.user.label;
-
-      UserPod.prototype.updateActionBoxArea.call(this);
-      UserPod.prototype.customizeUserPodPerUserType.call(this);
-    },
-
-    /** @override */
-    get mainInput() {
-      return this.launchAppButtonElement;
-    },
-
-    /** @override */
-    focusInput: function() {
-      // Move tabIndex from the whole pod to the main input.
-      this.tabIndex = -1;
-      this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
-      this.mainInput.focus();
-    },
-
-    /** @override */
-    get forceOnlineSignin() {
-      return false;
-    },
-
-    /** @override */
-    activate: function(e) {
-      var diagnosticMode = e && e.ctrlKey;
-      this.launchApp_(this.user, diagnosticMode);
-      return true;
-    },
-
-    /** @override */
-    handleClickOnPod_: function(e) {
-      if (this.parentNode.disabled)
-        return;
-
-      if (this.getPodStyle() != UserPod.Style.LARGE) {
-        $('pod-row').switchMainPod(this);
-        return;
-      }
-
-      Oobe.clearErrors();
-      this.parentNode.lastFocusedPod_ = this;
-      this.activate(e);
-    },
-
-    /**
-     * Launch the app. If |diagnosticMode| is true, ask user to confirm.
-     * @param {Object} app App data.
-     * @param {boolean} diagnosticMode Whether to run the app in diagnostic
-     *     mode.
-     */
-    launchApp_: function(app, diagnosticMode) {
-      if (!diagnosticMode) {
-        chrome.send('launchKioskApp', [app.id, false]);
-        return;
-      }
-
-      var oobe = $('oobe');
-      if (!oobe.confirmDiagnosticMode_) {
-        oobe.confirmDiagnosticMode_ =
-            new cr.ui.dialogs.ConfirmDialog(document.body);
-        oobe.confirmDiagnosticMode_.setOkLabel(
-            loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
-        oobe.confirmDiagnosticMode_.setCancelLabel(
-            loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
-      }
-
-      oobe.confirmDiagnosticMode_.show(
-          loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat',
-                                  app.label),
-          function() {
-            chrome.send('launchKioskApp', [app.id, true]);
-          });
-    },
-  };
-
-  /**
    * Creates a new pod row element.
    * @constructor
    * @extends {HTMLDivElement}
@@ -3052,12 +2925,6 @@
     userPodHeight_: 0,
     userPodWidth_: 0,
 
-    // Array of apps that are shown in addition to other user pods.
-    apps_: [],
-
-    // True to show app pods along with user pods.
-    shouldShowApps_: true,
-
     // Array of users that are shown (public/supervised/regular).
     users_: [],
 
@@ -3074,6 +2941,9 @@
     // Whether we should add background behind user pods.
     showPodBackground_: false,
 
+    // Current UI state of the sign-in screen.
+    signinUIState_: SIGNIN_UI_STATE.HIDDEN,
+
     /** @override */
     decorate: function() {
       // Event listeners that are installed for the time period during which
@@ -3122,20 +2992,6 @@
     },
 
     /**
-     * Returns pod with the given app id.
-     * @param {!string} app_id Application id to be matched.
-     * @return {Object} Pod with the given app id. null if pod hasn't been
-     *     found.
-     */
-    getPodWithAppId_: function(app_id) {
-      for (var i = 0, pod; pod = this.pods[i]; ++i) {
-        if (pod.user.isApp && pod.user.id == app_id)
-          return pod;
-      }
-      return null;
-    },
-
-    /**
      * Returns pod with the given username (null if there is no such pod).
      * @param {string} username Username to be matched.
      * @return {Object} Pod with the given username. null if pod hasn't been
@@ -3174,8 +3030,6 @@
         userPod = new DesktopUserPod({user: user});
       else if (user.publicAccount)
         userPod = new PublicAccountUserPod({user: user});
-      else if (user.isApp)
-        userPod = new KioskAppPod({user: user});
       else
         userPod = new UserPod({user: user});
 
@@ -3212,23 +3066,6 @@
     },
 
     /**
-     * Runs app with a given id from the list of loaded apps.
-     * @param {!string} app_id of an app to run.
-     * @param {boolean=} opt_diagnosticMode Whether to run the app in
-     *     diagnostic mode. Default is false.
-     */
-    findAndRunAppForTesting: function(app_id, opt_diagnosticMode) {
-      var app = this.getPodWithAppId_(app_id);
-      if (app) {
-        var activationEvent = cr.doc.createEvent('MouseEvents');
-        var ctrlKey = opt_diagnosticMode;
-        activationEvent.initMouseEvent('click', true, true, null,
-            0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null);
-        app.dispatchEvent(activationEvent);
-      }
-    },
-
-    /**
      * Enables or disables the pin keyboard for the given user. A disabled pin
      * keyboard will never be displayed.
      *
@@ -3330,7 +3167,22 @@
     },
 
     /**
-     * Rebuilds pod row using users_ and apps_ that were previously set or
+     * Current header bar UI / sign in state.
+     *
+     * @type {number} state Current state of the sign-in screen (see
+     *       SIGNIN_UI_STATE).
+     */
+    get signinUIState() {
+      return this.signinUIState_;
+    },
+
+    set signinUIState(state) {
+      this.signinUIState_ = state;
+      this.rebuildPods();
+    },
+
+    /**
+     * Rebuilds pod row using users_ that were previously set or
      * updated.
      */
     rebuildPods: function() {
@@ -3355,12 +3207,6 @@
       for (var i = 0, pod; pod = this.pods[i]; ++i)
         this.podsWithPendingImages_.push(pod);
 
-      // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
-      if (this.shouldShowApps_) {
-        for (var i = 0; i < this.apps_.length; ++i)
-          this.addUserPod(this.apps_[i]);
-      }
-
       // Make sure we eventually show the pod row, even if some image is stuck.
       setTimeout(function() {
         $('pod-row').classList.remove('images-loading');
@@ -3369,8 +3215,8 @@
         this.bottomMask.classList.remove('images-loading');
       }.bind(this), POD_ROW_IMAGES_LOAD_TIMEOUT_MS);
 
-      var isAccountPicker = $('login-header-bar').signinUIState ==
-          SIGNIN_UI_STATE.ACCOUNT_PICKER;
+      var isAccountPicker =
+          this.signinUIState_ == SIGNIN_UI_STATE.ACCOUNT_PICKER;
 
       // Immediately recalculate pods layout only when current UI is account
       // picker. Otherwise postpone it.
@@ -3385,14 +3231,6 @@
         }, 0);
       } else {
         this.podPlacementPostponed_ = true;
-
-        // Update [Cancel] button state.
-        if ($('login-header-bar').signinUIState ==
-                SIGNIN_UI_STATE.GAIA_SIGNIN &&
-            emptyPodRow &&
-            this.pods.length > 0) {
-          login.GaiaSigninScreen.updateControlsState();
-        }
       }
     },
 
@@ -3421,33 +3259,6 @@
     },
 
     /**
-     * Adds given apps to the pod row.
-     * @param {array} apps Array of apps.
-     */
-    setApps: function(apps) {
-      this.apps_ = apps;
-      this.rebuildPods();
-      chrome.send('kioskAppsLoaded');
-
-      // Check whether there's a pending kiosk app error.
-      window.setTimeout(function() {
-        chrome.send('checkKioskAppLaunchError');
-      }, 500);
-    },
-
-    /**
-     * Sets whether should show app pods.
-     * @param {boolean} shouldShowApps Whether app pods should be shown.
-     */
-    setShouldShowApps: function(shouldShowApps) {
-      if (this.shouldShowApps_ == shouldShowApps)
-        return;
-
-      this.shouldShowApps_ = shouldShowApps;
-      this.rebuildPods();
-    },
-
-    /**
      * Shows a custom icon on a user pod besides the input field.
      * @param {string} username Username of pod to add button
      * @param {!{id: !string,
@@ -3718,7 +3529,7 @@
      */
     onWindowResize: function() {
       var isAccountPicker =
-          $('login-header-bar').signinUIState == SIGNIN_UI_STATE.ACCOUNT_PICKER;
+          this.signinUIState_ == SIGNIN_UI_STATE.ACCOUNT_PICKER;
       if (isAccountPicker) {
         // Redo pod placement if account picker is the current screen.
         this.placePods_();
@@ -4425,8 +4236,6 @@
       var pods = this.pods;
       for (var pod of pods)
         pod.classList.toggle('show-pod-background', showPodBackground);
-      $('login-header-bar')
-          .classList.toggle('translucent-background', showPodBackground);
 
       var isShowingScrollList =
           this.smallPodsContainer.classList.contains('scroll');
@@ -4524,13 +4333,11 @@
           podToFocus.focus();
         }
 
-        if (!podToFocus.user.isApp) {
-          // Only updates wallpaper when the focused pod is in large style.
-          chrome.send('focusPod', [
-            podToFocus.user.username,
-            podToFocus.getPodStyle() == UserPod.Style.LARGE
-          ]);
-        }
+        // Only updates wallpaper when the focused pod is in large style.
+        chrome.send('focusPod', [
+          podToFocus.user.username,
+          podToFocus.getPodStyle() == UserPod.Style.LARGE
+        ]);
         this.firstShown_ = false;
         this.lastFocusedPod_ = podToFocus;
         this.setUserPodFingerprintIcon(
@@ -4701,8 +4508,6 @@
 
       // Return focus back to single pod.
       if (this.alwaysFocusSinglePod && !pod) {
-        if ($('login-header-bar').contains(e.target))
-          return;
         this.focusPod(this.focusedPod_, true /* force */);
         this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
         this.focusedPod_.isActionBoxMenuHovered = false;
@@ -4929,12 +4734,6 @@
         this.ownerDocument.addEventListener(
             event, this.listeners_[event][0], this.listeners_[event][1]);
       }
-      $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR;
-      // Header bar should be hidden when virtual keyboard is shown, or
-      // views-based shelf is shown.
-      Oobe.getInstance().headerHidden =
-          this.isScreenShrinked_() || Oobe.getInstance().showingViewsBasedShelf;
-
       if (this.podPlacementPostponed_) {
         this.podPlacementPostponed_ = false;
         this.placePods_();
@@ -4955,7 +4754,6 @@
         this.ownerDocument.removeEventListener(
             event, this.listeners_[event][0], this.listeners_[event][1]);
       }
-      $('login-header-bar').buttonsTabIndex = 0;
 
       // Clear global states that should only applies to account picker.
       $('scroll-container').classList.remove('disable-scroll');
diff --git a/ui/login/account_picker/chromeos_user_pod_template.html b/ui/login/account_picker/chromeos_user_pod_template.html
index 0161e8d5..1370221b 100644
--- a/ui/login/account_picker/chromeos_user_pod_template.html
+++ b/ui/login/account_picker/chromeos_user_pod_template.html
@@ -95,9 +95,6 @@
           <span class="reauth-warning"></span>
           <span class="reauth-name-hint"></span>
         </div>
-        <div class="launch-app-button-container" hidden>
-          <button class="launch-app-button">$i18n{launchAppButton}</button>
-        </div>
         <div class="input-line">
           <svg>
             <line x1="0" y1="0" x2="204" y2="0">
diff --git a/ui/login/account_picker/screen_account_picker.js b/ui/login/account_picker/screen_account_picker.js
index af7f756..78f31c0 100644
--- a/ui/login/account_picker/screen_account_picker.js
+++ b/ui/login/account_picker/screen_account_picker.js
@@ -24,10 +24,6 @@
    return {
      EXTERNAL_API: [
        'loadUsers',
-       'runAppForTesting',
-       'setApps',
-       'setShouldShowApps',
-       'showAppError',
        'updateUserImage',
        'setCapsLockState',
        'removeUser',
@@ -106,7 +102,6 @@
        chrome.send('loginUIStateChanged', ['account-picker', true]);
        $('login-header-bar').signinUIState = SIGNIN_UI_STATE.ACCOUNT_PICKER;
        // Header bar should be always visible on Account Picker screen.
-       Oobe.getInstance().headerHidden = false;
        chrome.send('hideCaptivePortal');
        var podRow = $('pod-row');
        podRow.handleBeforeShow();
@@ -264,45 +259,6 @@
      },
 
      /**
-      * Runs app with a given id from the list of loaded apps.
-      * @param {!string} app_id of an app to run.
-      * @param {boolean=} opt_diagnostic_mode Whether to run the app in
-      *     diagnostic mode.  Default is false.
-      */
-     runAppForTesting: function(app_id, opt_diagnostic_mode) {
-       $('pod-row').findAndRunAppForTesting(app_id, opt_diagnostic_mode);
-     },
-
-     /**
-      * Adds given apps to the pod row.
-      * @param {array} apps Array of apps.
-      */
-     setApps: function(apps) {
-       $('pod-row').setApps(apps);
-     },
-
-     /**
-      * Sets the flag of whether app pods should be visible.
-      * @param {boolean} shouldShowApps Whether to show app pods.
-      */
-     setShouldShowApps: function(shouldShowApps) {
-       $('pod-row').setShouldShowApps(shouldShowApps);
-     },
-
-     /**
-      * Shows the given kiosk app error message.
-      * @param {!string} message Error message to show.
-      */
-     showAppError: function(message) {
-       // TODO(nkostylev): Figure out a way to show kiosk app launch error
-       // pointing to the kiosk app pod.
-       /** @const */ var BUBBLE_PADDING = 12;
-       $('bubble').showTextForElement(
-           $('pod-row'), message, cr.ui.Bubble.Attachment.BOTTOM,
-           $('pod-row').offsetWidth / 2, BUBBLE_PADDING);
-     },
-
-     /**
       * Updates current image of a user.
       * @param {string} username User for which to update the image.
       */
diff --git a/ui/login/account_picker/user_pod_row.css b/ui/login/account_picker/user_pod_row.css
index 8ec451da..d8c0301 100644
--- a/ui/login/account_picker/user_pod_row.css
+++ b/ui/login/account_picker/user_pod_row.css
@@ -427,20 +427,6 @@
   width: 27px;
 }
 
-.launch-app-button-container {
-  display: block;
-  flex: auto;
-  text-align: center;
-}
-
-.launch-app-button {
-  display: inline;
-  margin-top: 6px !important;
-  max-width: 100%;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
 .pod[auth-type='onlineSignIn'] .reauth-hint-container {
   display: flex;
   justify-content: center;
diff --git a/ui/login/account_picker/user_pod_row.js b/ui/login/account_picker/user_pod_row.js
index 4c3ff254..971899d 100644
--- a/ui/login/account_picker/user_pod_row.js
+++ b/ui/login/account_picker/user_pod_row.js
@@ -1031,22 +1031,6 @@
     },
 
     /**
-     * Gets the container holding the launch app button.
-     * @type {!HTMLButtonElement}
-     */
-    get launchAppButtonContainerElement() {
-      return this.querySelector('.launch-app-button-container');
-    },
-
-    /**
-     * Gets launch app button.
-     * @type {!HTMLButtonElement}
-     */
-    get launchAppButtonElement() {
-      return this.querySelector('.launch-app-button');
-    },
-
-    /**
      * Gets action box area.
      * @type {!HTMLInputElement}
      */
@@ -1193,7 +1177,7 @@
     },
 
     updateActionBoxArea: function() {
-      if (this.user_.publicAccount || this.user_.isApp) {
+      if (this.user_.publicAccount) {
         this.actionBoxAreaElement.hidden = true;
         return;
       }
@@ -1239,8 +1223,6 @@
         } else {
           this.querySelector('.mp-policy-not-allowed-msg').hidden = false;
         }
-      } else if (this.user_.isApp) {
-        this.setUserPodIconType('app');
       }
     },
 
@@ -2542,109 +2524,6 @@
   };
 
   /**
-   * Creates a user pod that represents kiosk app.
-   * @constructor
-   * @extends {UserPod}
-   */
-  var KioskAppPod = cr.ui.define(function() {
-    var node = UserPod();
-    return node;
-  });
-
-  KioskAppPod.prototype = {
-    __proto__: UserPod.prototype,
-
-    /** @override */
-    decorate: function() {
-      UserPod.prototype.decorate.call(this);
-      this.launchAppButtonElement.addEventListener('click',
-                                                   this.activate.bind(this));
-    },
-
-    /** @override */
-    update: function() {
-      this.imageElement.src = this.user.iconUrl;
-      this.imageElement.alt = this.user.label;
-      this.imageElement.title = this.user.label;
-      this.animatedImageElement.src = this.user.iconUrl;
-      this.animatedImageElement.alt = this.user.label;
-      this.animatedImageElement.title = this.user.label;
-      this.passwordEntryContainerElement.hidden = true;
-      this.launchAppButtonContainerElement.hidden = false;
-      this.nameElement.textContent = this.user.label;
-      this.reauthNameHintElement.textContent = this.user.label;
-
-      UserPod.prototype.updateActionBoxArea.call(this);
-      UserPod.prototype.customizeUserPodPerUserType.call(this);
-    },
-
-    /** @override */
-    get mainInput() {
-      return this.launchAppButtonElement;
-    },
-
-    /** @override */
-    focusInput: function() {
-      // Move tabIndex from the whole pod to the main input.
-      this.tabIndex = -1;
-      this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
-      this.mainInput.focus();
-    },
-
-    /** @override */
-    get forceOnlineSignin() {
-      return false;
-    },
-
-    /** @override */
-    activate: function(e) {
-      var diagnosticMode = e && e.ctrlKey;
-      this.launchApp_(this.user, diagnosticMode);
-      return true;
-    },
-
-    /** @override */
-    handleClickOnPod_: function(e) {
-      if (this.parentNode.disabled)
-        return;
-
-      Oobe.clearErrors();
-      this.parentNode.lastFocusedPod_ = this;
-      this.activate(e);
-    },
-
-    /**
-     * Launch the app. If |diagnosticMode| is true, ask user to confirm.
-     * @param {Object} app App data.
-     * @param {boolean} diagnosticMode Whether to run the app in diagnostic
-     *     mode.
-     */
-    launchApp_: function(app, diagnosticMode) {
-      if (!diagnosticMode) {
-        chrome.send('launchKioskApp', [app.id, false]);
-        return;
-      }
-
-      var oobe = $('oobe');
-      if (!oobe.confirmDiagnosticMode_) {
-        oobe.confirmDiagnosticMode_ =
-            new cr.ui.dialogs.ConfirmDialog(document.body);
-        oobe.confirmDiagnosticMode_.setOkLabel(
-            loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
-        oobe.confirmDiagnosticMode_.setCancelLabel(
-            loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
-      }
-
-      oobe.confirmDiagnosticMode_.show(
-          loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat',
-                                  app.label),
-          function() {
-            chrome.send('launchKioskApp', [app.id, true]);
-          });
-    },
-  };
-
-  /**
    * Creates a new pod row element.
    * @constructor
    * @extends {HTMLDivElement}
@@ -2679,12 +2558,6 @@
     userPodHeight_: 0,
     userPodWidth_: 0,
 
-    // Array of apps that are shown in addition to other user pods.
-    apps_: [],
-
-    // True to show app pods along with user pods.
-    shouldShowApps_: true,
-
     // Array of users that are shown (public/supervised/regular).
     users_: [],
 
@@ -2738,20 +2611,6 @@
     },
 
     /**
-     * Returns pod with the given app id.
-     * @param {!string} app_id Application id to be matched.
-     * @return {Object} Pod with the given app id. null if pod hasn't been
-     *     found.
-     */
-    getPodWithAppId_: function(app_id) {
-      for (var i = 0, pod; pod = this.pods[i]; ++i) {
-        if (pod.user.isApp && pod.user.id == app_id)
-          return pod;
-      }
-      return null;
-    },
-
-    /**
      * Returns pod with the given username (null if there is no such pod).
      * @param {string} username Username to be matched.
      * @return {Object} Pod with the given username. null if pod hasn't been
@@ -2790,8 +2649,6 @@
         userPod = new DesktopUserPod({user: user});
       else if (user.publicAccount)
         userPod = new PublicAccountUserPod({user: user});
-      else if (user.isApp)
-        userPod = new KioskAppPod({user: user});
       else
         userPod = new UserPod({user: user});
 
@@ -2828,23 +2685,6 @@
     },
 
     /**
-     * Runs app with a given id from the list of loaded apps.
-     * @param {!string} app_id of an app to run.
-     * @param {boolean=} opt_diagnosticMode Whether to run the app in
-     *     diagnostic mode. Default is false.
-     */
-    findAndRunAppForTesting: function(app_id, opt_diagnosticMode) {
-      var app = this.getPodWithAppId_(app_id);
-      if (app) {
-        var activationEvent = cr.doc.createEvent('MouseEvents');
-        var ctrlKey = opt_diagnosticMode;
-        activationEvent.initMouseEvent('click', true, true, null,
-            0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null);
-        app.dispatchEvent(activationEvent);
-      }
-    },
-
-    /**
      * Enables or disables the pin keyboard for the given user. A disabled pin
      * keyboard will never be displayed.
      *
@@ -2967,8 +2807,7 @@
     },
 
     /**
-     * Rebuilds pod row using users_ and apps_ that were previously set or
-     * updated.
+     * Rebuilds pod row using users_ that were previously set or updated.
      */
     rebuildPods: function() {
       var emptyPodRow = this.pods.length == 0;
@@ -2989,12 +2828,6 @@
       for (var i = 0, pod; pod = this.pods[i]; ++i)
         this.podsWithPendingImages_.push(pod);
 
-      // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
-      if (this.shouldShowApps_) {
-        for (var i = 0; i < this.apps_.length; ++i)
-          this.addUserPod(this.apps_[i]);
-      }
-
       // Make sure we eventually show the pod row, even if some image is stuck.
       setTimeout(function() {
         $('pod-row').classList.remove('images-loading');
@@ -3016,45 +2849,10 @@
         }, 0);
       } else {
         this.podPlacementPostponed_ = true;
-
-        // Update [Cancel] button state.
-        if ($('login-header-bar').signinUIState ==
-                SIGNIN_UI_STATE.GAIA_SIGNIN &&
-            emptyPodRow &&
-            this.pods.length > 0) {
-          login.GaiaSigninScreen.updateControlsState();
-        }
       }
     },
 
     /**
-     * Adds given apps to the pod row.
-     * @param {array} apps Array of apps.
-     */
-    setApps: function(apps) {
-      this.apps_ = apps;
-      this.rebuildPods();
-      chrome.send('kioskAppsLoaded');
-
-      // Check whether there's a pending kiosk app error.
-      window.setTimeout(function() {
-        chrome.send('checkKioskAppLaunchError');
-      }, 500);
-    },
-
-    /**
-     * Sets whether should show app pods.
-     * @param {boolean} shouldShowApps Whether app pods should be shown.
-     */
-    setShouldShowApps: function(shouldShowApps) {
-      if (this.shouldShowApps_ == shouldShowApps)
-        return;
-
-      this.shouldShowApps_ = shouldShowApps;
-      this.rebuildPods();
-    },
-
-    /**
      * Shows a custom icon on a user pod besides the input field.
      * @param {string} username Username of pod to add button
      * @param {!{id: !string,
@@ -3539,10 +3337,8 @@
           podToFocus.focus();
         }
 
-        if (!podToFocus.user.isApp)
-          chrome.send(
-              'focusPod',
-              [podToFocus.user.username, true /* loads wallpaper */]);
+        chrome.send(
+            'focusPod', [podToFocus.user.username, true /* loads wallpaper */]);
         this.firstShown_ = false;
         this.lastFocusedPod_ = podToFocus;
         this.scrollFocusedPodIntoView();
diff --git a/ui/login/account_picker/user_pod_template.html b/ui/login/account_picker/user_pod_template.html
index 367374f..5235cfa 100644
--- a/ui/login/account_picker/user_pod_template.html
+++ b/ui/login/account_picker/user_pod_template.html
@@ -81,9 +81,6 @@
         <span class="reauth-warning"></span>
         <span class="reauth-name-hint"></span>
       </div>
-      <div class="launch-app-button-container" hidden>
-        <button class="launch-app-button">$i18n{launchAppButton}</button>
-      </div>
     </div>
   </div>
   <div class="action-box-area">
diff --git a/ui/login/display_manager.js b/ui/login/display_manager.js
index c86684e..f8e3f00 100644
--- a/ui/login/display_manager.js
+++ b/ui/login/display_manager.js
@@ -285,10 +285,6 @@
       document.documentElement.setAttribute('screen', displayType);
     },
 
-    get newKioskUI() {
-      return loadTimeData.getString('newKioskUI') == 'on';
-    },
-
     /**
      * Returns dimensions of screen exluding header bar.
      * @type {Object}
@@ -307,32 +303,6 @@
     },
 
     /**
-     * Hides/shows header (Shutdown/Add User/Cancel buttons).
-     * @param {boolean} hidden Whether header is hidden.
-     * TODO(crbug/914578): talk to the views login shelf through Mojo.
-     */
-    get headerHidden() {
-      return $('login-header-bar').hidden;
-    },
-
-    set headerHidden(hidden) {
-      if (this.showingViewsBasedShelf && !hidden) {
-        // When views-based shelf is enabled, toggling header bar visibility
-        // is handled by ash. Prevent showing a duplicate header bar here.
-        return;
-      }
-      $('login-header-bar').hidden = hidden;
-    },
-
-    /**
-     * The header bar should be hidden when views-based shelf is shown.
-     */
-    get showingViewsBasedShelf() {
-      // TODO: remove this method once webui shelf has been removed.
-      return true;
-    },
-
-    /**
      * Returns true if we are showing views based login screen.
      * @return {boolean}
      */
@@ -727,16 +697,10 @@
       // Make sure the screen is decorated.
       this.preloadScreen(screen);
 
-      if (screen.data !== undefined && screen.data.disableAddUser)
-        DisplayManager.updateAddUserButtonStatus(true);
-
 
       // Show sign-in screen instead of account picker if pod row is empty.
       if (screenId == SCREEN_ACCOUNT_PICKER && $('pod-row').pods.length == 0 &&
           cr.isChromeOS) {
-        // Manually hide 'add-user' header bar, because of the case when
-        // 'Cancel' button is used on the offline login page.
-        $('add-user-header-bar-item').hidden = true;
         Oobe.showSigninUI();
         return;
       }
@@ -1309,22 +1273,9 @@
   };
 
   /**
-   * Disable Add users button if said.
-   * @param {boolean} disable true to disable
+   * Clears password field in user-pod.
    */
-  DisplayManager.updateAddUserButtonStatus =
-      function(disable) {
-    $('add-user-button').disabled = disable;
-    $('add-user-button')
-        .classList[disable ? 'add' : 'remove']('button-restricted');
-    $('add-user-button').title =
-        disable ? loadTimeData.getString('disabledAddUserTooltip') : '';
-  }
-
-      /**
-       * Clears password field in user-pod.
-       */
-      DisplayManager.clearUserPodPassword = function() {
+  DisplayManager.clearUserPodPassword = function() {
     $('pod-row').clearFocusedPod();
   };
 
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
index 08d5bc5..0df107b 100644
--- a/ui/native_theme/native_theme_win.cc
+++ b/ui/native_theme/native_theme_win.cc
@@ -587,6 +587,11 @@
 }
 
 bool NativeThemeWin::SystemDarkModeEnabled() const {
+  // Windows high contrast modes are entirely different themes,
+  // so let them take priority over dark mode.
+  // ...unless --force-dark-mode was specified in which case caveat emptor.
+  if (UsesHighContrastColors() && !NativeTheme::SystemDarkModeEnabled())
+    return false;
   bool fDarkModeEnabled = false;
   if (hkcu_themes_regkey_.Valid()) {
     DWORD apps_use_light_theme = 1;
diff --git a/ui/ozone/platform/wayland/test/mock_surface.h b/ui/ozone/platform/wayland/test/mock_surface.h
index 1140ce2..0f83878b 100644
--- a/ui/ozone/platform/wayland/test/mock_surface.h
+++ b/ui/ozone/platform/wayland/test/mock_surface.h
@@ -5,10 +5,14 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_SURFACE_H_
 #define UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_SURFACE_H_
 
+#include <memory>
+#include <utility>
+
 #include <wayland-server-protocol-core.h>
 
 #include "base/macros.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "ui/ozone/platform/wayland/test/mock_xdg_popup.h"
 #include "ui/ozone/platform/wayland/test/mock_xdg_surface.h"
 #include "ui/ozone/platform/wayland/test/server_object.h"
 
@@ -38,8 +42,14 @@
   }
   MockXdgSurface* xdg_surface() const { return xdg_surface_.get(); }
 
+  void set_xdg_popup(std::unique_ptr<MockXdgPopup> xdg_popup) {
+    xdg_popup_ = std::move(xdg_popup);
+  }
+  MockXdgPopup* xdg_popup() const { return xdg_popup_.get(); }
+
  private:
   std::unique_ptr<MockXdgSurface> xdg_surface_;
+  std::unique_ptr<MockXdgPopup> xdg_popup_;
 
   DISALLOW_COPY_AND_ASSIGN(MockSurface);
 };
diff --git a/ui/ozone/platform/wayland/test/mock_xdg_popup.cc b/ui/ozone/platform/wayland/test/mock_xdg_popup.cc
index cecb5a9..6970b63 100644
--- a/ui/ozone/platform/wayland/test/mock_xdg_popup.cc
+++ b/ui/ozone/platform/wayland/test/mock_xdg_popup.cc
@@ -26,7 +26,10 @@
     &Grab,             // grab
 };
 
-MockXdgPopup::MockXdgPopup(wl_resource* resource) : ServerObject(resource) {}
+MockXdgPopup::MockXdgPopup(wl_resource* resource, const void* implementation)
+    : ServerObject(resource) {
+  SetImplementationUnretained(resource, implementation, this);
+}
 
 MockXdgPopup::~MockXdgPopup() {}
 
diff --git a/ui/ozone/platform/wayland/test/mock_xdg_popup.h b/ui/ozone/platform/wayland/test/mock_xdg_popup.h
index 4ece023c..4ebb050 100644
--- a/ui/ozone/platform/wayland/test/mock_xdg_popup.h
+++ b/ui/ozone/platform/wayland/test/mock_xdg_popup.h
@@ -5,12 +5,15 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_POPUP_H_
 #define UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_POPUP_H_
 
+#include <utility>
+
 #include <xdg-shell-unstable-v5-server-protocol.h>
 #include <xdg-shell-unstable-v6-server-protocol.h>
 
 #include "base/macros.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/ozone/platform/wayland/test/server_object.h"
+#include "ui/ozone/platform/wayland/test/test_positioner.h"
 
 struct wl_resource;
 
@@ -21,12 +24,27 @@
 
 class MockXdgPopup : public ServerObject {
  public:
-  MockXdgPopup(wl_resource* resource);
+  MockXdgPopup(wl_resource* resource, const void* implementation);
   ~MockXdgPopup() override;
 
   MOCK_METHOD1(Grab, void(uint32_t serial));
 
+  void set_position(struct TestPositioner::PopupPosition position) {
+    position_ = std::move(position);
+  }
+
+  gfx::Rect anchor_rect() const { return position_.anchor_rect; }
+  gfx::Size size() const { return position_.size; }
+  uint32_t anchor() const { return position_.anchor; }
+  uint32_t gravity() const { return position_.gravity; }
+  uint32_t constraint_adjustment() const {
+    return position_.constraint_adjustment;
+  }
+
  private:
+  // Position of the popup. Used only with V6.
+  struct TestPositioner::PopupPosition position_;
+
   DISALLOW_COPY_AND_ASSIGN(MockXdgPopup);
 };
 
diff --git a/ui/ozone/platform/wayland/test/mock_xdg_shell.cc b/ui/ozone/platform/wayland/test/mock_xdg_shell.cc
index a3cb6653..0941ace 100644
--- a/ui/ozone/platform/wayland/test/mock_xdg_shell.cc
+++ b/ui/ozone/platform/wayland/test/mock_xdg_shell.cc
@@ -72,9 +72,13 @@
     return;
   }
 
-  CreateResourceWithImpl<MockXdgPopup>(client, &xdg_popup_interface,
-                                       wl_resource_get_version(resource),
-                                       &kXdgPopupImpl, id);
+  wl_resource* xdg_popup_resource = wl_resource_create(
+      client, &xdg_popup_interface, wl_resource_get_version(resource), id);
+
+  auto mock_xdg_popup =
+      std::make_unique<MockXdgPopup>(xdg_popup_resource, &kXdgPopupImpl);
+
+  mock_surface->set_xdg_popup(std::move(mock_xdg_popup));
 }
 
 void Pong(wl_client* client, wl_resource* resource, uint32_t serial) {
diff --git a/ui/ozone/platform/wayland/test/mock_xdg_surface.cc b/ui/ozone/platform/wayland/test/mock_xdg_surface.cc
index 8797a94c..06f1d58 100644
--- a/ui/ozone/platform/wayland/test/mock_xdg_surface.cc
+++ b/ui/ozone/platform/wayland/test/mock_xdg_surface.cc
@@ -104,17 +104,22 @@
     return;
   }
 
+  wl_resource* xdg_popup_resource = wl_resource_create(
+      client, &zxdg_popup_v6_interface, wl_resource_get_version(resource), id);
   auto* positioner = GetUserDataAs<TestPositioner>(positioner_resource);
-  if (positioner->size().width() == 0 ||
-      positioner->anchor_rect().width() == 0) {
+  DCHECK(positioner);
+
+  auto mock_xdg_popup =
+      std::make_unique<MockXdgPopup>(xdg_popup_resource, &kZxdgPopupV6Impl);
+  mock_xdg_popup->set_position(positioner->position());
+  if (mock_xdg_popup->size().IsEmpty() ||
+      mock_xdg_popup->anchor_rect().IsEmpty()) {
     wl_resource_post_error(resource, ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER,
                            "Positioner object is not complete");
     return;
   }
 
-  CreateResourceWithImpl<MockXdgPopup>(client, &zxdg_popup_v6_interface,
-                                       wl_resource_get_version(resource),
-                                       &kZxdgPopupV6Impl, id);
+  mock_xdg_surface->set_xdg_popup(std::move(mock_xdg_popup));
 }
 
 const struct xdg_surface_interface kMockXdgSurfaceImpl = {
diff --git a/ui/ozone/platform/wayland/test/mock_xdg_surface.h b/ui/ozone/platform/wayland/test/mock_xdg_surface.h
index 3793f25b..0576a1e 100644
--- a/ui/ozone/platform/wayland/test/mock_xdg_surface.h
+++ b/ui/ozone/platform/wayland/test/mock_xdg_surface.h
@@ -5,10 +5,14 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_SURFACE_H_
 #define UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_XDG_SURFACE_H_
 
+#include <memory>
+#include <utility>
+
 #include <xdg-shell-unstable-v5-server-protocol.h>
 #include <xdg-shell-unstable-v6-server-protocol.h>
 
 #include "testing/gmock/include/gmock/gmock.h"
+#include "ui/ozone/platform/wayland/test/mock_xdg_popup.h"
 #include "ui/ozone/platform/wayland/test/server_object.h"
 
 struct wl_resource;
@@ -49,10 +53,17 @@
   }
   MockXdgTopLevel* xdg_toplevel() const { return xdg_toplevel_.get(); }
 
+  void set_xdg_popup(std::unique_ptr<MockXdgPopup> xdg_popup) {
+    xdg_popup_ = std::move(xdg_popup);
+  }
+  MockXdgPopup* xdg_popup() const { return xdg_popup_.get(); }
+
  private:
   // Used when xdg v6 is used.
   std::unique_ptr<MockXdgTopLevel> xdg_toplevel_;
 
+  std::unique_ptr<MockXdgPopup> xdg_popup_;
+
   DISALLOW_COPY_AND_ASSIGN(MockXdgSurface);
 };
 
diff --git a/ui/ozone/platform/wayland/test/test_positioner.cc b/ui/ozone/platform/wayland/test/test_positioner.cc
index e576711..6fbf7232 100644
--- a/ui/ozone/platform/wayland/test/test_positioner.cc
+++ b/ui/ozone/platform/wayland/test/test_positioner.cc
@@ -69,16 +69,23 @@
   GetUserDataAs<TestPositioner>(resource)->set_gravity(gravity);
 }
 
+void SetConstraintAdjustment(struct wl_client* client,
+                             struct wl_resource* resource,
+                             uint32_t constraint_adjustment) {
+  GetUserDataAs<TestPositioner>(resource)->set_constraint_adjustment(
+      constraint_adjustment);
+}
+
 }  // namespace
 
 const struct zxdg_positioner_v6_interface kTestZxdgPositionerV6Impl = {
-    &DestroyResource,  // destroy
-    &SetSize,          // set_size
-    &SetAnchorRect,    // set_anchor_rect
-    &SetAnchor,        // set_anchor
-    &SetGravity,       // set_gravity
-    nullptr,           // set_constraint_adjustment
-    nullptr,           // set_offset
+    &DestroyResource,          // destroy
+    &SetSize,                  // set_size
+    &SetAnchorRect,            // set_anchor_rect
+    &SetAnchor,                // set_anchor
+    &SetGravity,               // set_gravity
+    &SetConstraintAdjustment,  // set_constraint_adjustment
+    nullptr,                   // set_offset
 };
 
 TestPositioner::TestPositioner(wl_resource* resource)
diff --git a/ui/ozone/platform/wayland/test/test_positioner.h b/ui/ozone/platform/wayland/test/test_positioner.h
index 58a3ff4..872f57c 100644
--- a/ui/ozone/platform/wayland/test/test_positioner.h
+++ b/ui/ozone/platform/wayland/test/test_positioner.h
@@ -5,6 +5,8 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_POSITIONER_H_
 #define UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_POSITIONER_H_
 
+#include <utility>
+
 #include <xdg-shell-unstable-v6-server-protocol.h>
 
 #include "base/macros.h"
@@ -22,24 +24,30 @@
 // surface relative to a parent surface.
 class TestPositioner : public ServerObject {
  public:
+  struct PopupPosition {
+    gfx::Rect anchor_rect;
+    gfx::Size size;
+    uint32_t anchor = 0;
+    uint32_t gravity = 0;
+    uint32_t constraint_adjustment = 0;
+  };
+
   explicit TestPositioner(wl_resource* resource);
   ~TestPositioner() override;
 
-  void set_size(gfx::Size size) { size_ = size; }
-  gfx::Size size() const { return size_; }
-
-  void set_anchor_rect(gfx::Rect anchor_rect) { anchor_rect_ = anchor_rect; }
-  gfx::Rect anchor_rect() const { return anchor_rect_; }
-
-  void set_anchor(uint32_t anchor) { anchor_ = anchor; }
-
-  void set_gravity(uint32_t gravity) { gravity_ = gravity; }
+  PopupPosition position() { return std::move(position_); }
+  void set_size(gfx::Size size) { position_.size = size; }
+  void set_anchor_rect(gfx::Rect anchor_rect) {
+    position_.anchor_rect = anchor_rect;
+  }
+  void set_anchor(uint32_t anchor) { position_.anchor = anchor; }
+  void set_gravity(uint32_t gravity) { position_.gravity = gravity; }
+  void set_constraint_adjustment(uint32_t constraint_adjustment) {
+    position_.constraint_adjustment = constraint_adjustment;
+  }
 
  private:
-  gfx::Rect anchor_rect_;
-  gfx::Size size_;
-  uint32_t anchor_;
-  uint32_t gravity_;
+  PopupPosition position_;
 
   DISALLOW_COPY_AND_ASSIGN(TestPositioner);
 };
diff --git a/ui/ozone/platform/wayland/wayland_keyboard.cc b/ui/ozone/platform/wayland/wayland_keyboard.cc
index 82677ae..910a5ec 100644
--- a/ui/ozone/platform/wayland/wayland_keyboard.cc
+++ b/ui/ozone/platform/wayland/wayland_keyboard.cc
@@ -13,10 +13,10 @@
 #include "ui/events/event.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
+#include "ui/events/keycodes/keyboard_code_conversion.h"
 #include "ui/events/ozone/evdev/keyboard_util_evdev.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
-#include "ui/events/ozone/layout/layout_util.h"
 #include "ui/ozone/platform/wayland/wayland_connection.h"
 #include "ui/ozone/platform/wayland/wayland_window.h"
 
diff --git a/ui/ozone/platform/wayland/wayland_window.cc b/ui/ozone/platform/wayland/wayland_window.cc
index 0ffa1228..0245365 100644
--- a/ui/ozone/platform/wayland/wayland_window.cc
+++ b/ui/ozone/platform/wayland/wayland_window.cc
@@ -66,11 +66,20 @@
   DISALLOW_COPY_AND_ASSIGN(XDGShellObjectFactory);
 };
 
+// Translates bounds relative to top level window to specified parent.
 gfx::Rect TranslateBoundsToParentCoordinates(const gfx::Rect& child_bounds,
                                              const gfx::Rect& parent_bounds) {
-  int x = child_bounds.x() - parent_bounds.x();
-  int y = child_bounds.y() - parent_bounds.y();
-  return gfx::Rect(gfx::Point(x, y), child_bounds.size());
+  return gfx::Rect(gfx::Point(child_bounds.x() - parent_bounds.x(),
+                              child_bounds.y() - parent_bounds.y()),
+                   child_bounds.size());
+}
+
+// Translates bounds relative to parent window to top level window.
+gfx::Rect TranslateBoundsToTopLevelCoordinates(const gfx::Rect& child_bounds,
+                                               const gfx::Rect& parent_bounds) {
+  return gfx::Rect(gfx::Point(child_bounds.x() + parent_bounds.x(),
+                              child_bounds.y() + parent_bounds.y()),
+                   child_bounds.size());
 }
 
 }  // namespace
@@ -183,8 +192,7 @@
 
   DCHECK(parent_window_ && !xdg_popup_);
 
-  gfx::Rect bounds =
-      TranslateBoundsToParentCoordinates(bounds_, parent_window_->GetBounds());
+  auto bounds = AdjustPopupWindowPosition();
 
   xdg_popup_ = xdg_shell_objects_factory_->CreateXDGPopup(connection_, this);
   if (!xdg_popup_ ||
@@ -235,9 +243,9 @@
 void WaylandWindow::ApplyPendingBounds() {
   if (pending_bounds_.IsEmpty())
     return;
+  DCHECK(xdg_surface_);
 
   SetBounds(pending_bounds_);
-  DCHECK(xdg_surface_);
   xdg_surface_->SetWindowGeometry(bounds_);
   xdg_surface_->AckConfigure();
   pending_bounds_ = gfx::Rect();
@@ -514,6 +522,8 @@
                                            bool is_maximized,
                                            bool is_fullscreen,
                                            bool is_activated) {
+  DCHECK(!xdg_popup());
+
   // Propagate the window state information to the client.
   PlatformWindowState old_state = state_;
 
@@ -591,6 +601,48 @@
   MaybeTriggerPendingStateChange();
 }
 
+void WaylandWindow::HandlePopupConfigure(const gfx::Rect& bounds) {
+  DCHECK(xdg_popup());
+  gfx::Rect new_bounds = bounds;
+
+  // It's not enough to just set new bounds. If it is a menu window, whose
+  // parent is a top level window aka browser window, it can be flipped
+  // vertically along y-axis and have negative values set. Chromium cannot
+  // understand that and starts to position nested menu windows incorrectly. To
+  // fix that, we have to bear in mind that Wayland compositor does not share
+  // global coordinates for any surfaces, and Chromium assumes the top level
+  // window is always located at 0,0 origin. What is more, child windows must
+  // always be positioned relative to parent window local surface coordinates.
+  // Thus, if the menu window is flipped along y-axis by Wayland and its origin
+  // is above the top level parent window, the origin of the top level window
+  // has to be shifted by that value on y-axis so that the origin of the menu
+  // becomes x,0, and events can be handled normally.
+  if (!parent_window_->xdg_popup()) {
+    gfx::Rect parent_bounds = parent_window_->GetBounds();
+    // The menu window is flipped along y-axis and have x,-y origin. Shift the
+    // parent top level window instead.
+    if (new_bounds.y() < 0) {
+      // Move parent bounds along y-axis.
+      parent_bounds.set_y(-(new_bounds.y()));
+      new_bounds.set_y(0);
+    } else {
+      // If the menu window is located at correct origin from the browser point
+      // of view, return the top level window back to 0,0.
+      parent_bounds.set_y(0);
+    }
+    parent_window_->SetBounds(parent_bounds);
+  } else {
+    // The nested menu windows are located relative to the parent menu windows.
+    // Thus, the location must be translated to be relative to the top level
+    // window, which automatically becomes the same as relative to an origin of
+    // a display.
+    new_bounds = TranslateBoundsToTopLevelCoordinates(
+        new_bounds, parent_window_->GetBounds());
+    DCHECK(new_bounds.y() >= 0);
+  }
+  SetBounds(new_bounds);
+}
+
 void WaylandWindow::OnCloseRequest() {
   // Before calling OnCloseRequest, the |xdg_popup_| must become hidden and
   // only then call OnCloseRequest().
@@ -753,6 +805,50 @@
   }
 }
 
+gfx::Rect WaylandWindow::AdjustPopupWindowPosition() const {
+  auto* parent_window = parent_window_->xdg_popup()
+                            ? parent_window_->parent_window_
+                            : parent_window_;
+  DCHECK(parent_window);
+  // Chromium positions windows in screen coordinates, but Wayland requires them
+  // to be in local surface coordinates aka relative to parent window.
+  const gfx::Rect parent_bounds = parent_window_->GetBounds();
+  gfx::Rect new_bounds =
+      TranslateBoundsToParentCoordinates(bounds_, parent_bounds);
+
+  // Chromium may decide to position nested menu windows on the left side
+  // instead of the right side of parent menu windows when the size of the
+  // window becomes larger than the display it is shown on. It's correct when
+  // the window is located on one display and occupies the whole work area, but
+  // as soon as it's moved and there is space on the right side, Chromium
+  // continues positioning the nested menus on the left side relative to the
+  // parent menu (Wayland does not provide clients with global coordinates).
+  // Instead, reposition that window to be on the right side of the parent menu
+  // window and let the compositor decide how to position it if it does not fit
+  // a single display. However, there is one exception - if the window is
+  // maximized, let Chromium position it on the left side as long as the Wayland
+  // compositor may decide to position the nested window on the right side of
+  // the parent menu window, which results in showing it on a second display if
+  // more than one display is used.
+  if (parent_window_->xdg_popup() && parent_window_->parent_window_ &&
+      !parent_window_->parent_window_->IsMaximized()) {
+    auto* top_level_window = parent_window_->parent_window_;
+    DCHECK(top_level_window && !top_level_window->xdg_popup());
+    if (new_bounds.x() <= 0 && !top_level_window->IsMaximized()) {
+      // Position the child menu window on the right side of the parent window
+      // and let the Wayland compositor decide how to do constraint
+      // adjustements.
+      int new_x = parent_bounds.width() - (new_bounds.width() + new_bounds.x());
+      new_bounds.set_x(new_x);
+    }
+  }
+  return new_bounds;
+}
+
+WaylandWindow* WaylandWindow::GetTopLevelWindow() {
+  return parent_window_ ? parent_window_->GetTopLevelWindow() : this;
+}
+
 // static
 void WaylandWindow::Enter(void* data,
                           struct wl_surface* wl_surface,
diff --git a/ui/ozone/platform/wayland/wayland_window.h b/ui/ozone/platform/wayland/wayland_window.h
index 5a97c35..7a32d8dc 100644
--- a/ui/ozone/platform/wayland/wayland_window.h
+++ b/ui/ozone/platform/wayland/wayland_window.h
@@ -134,6 +134,7 @@
                               bool is_maximized,
                               bool is_fullscreen,
                               bool is_activated);
+  void HandlePopupConfigure(const gfx::Rect& bounds);
 
   void OnCloseRequest();
 
@@ -173,6 +174,11 @@
 
   void UpdateCursorPositionFromEvent(std::unique_ptr<Event> event);
 
+  // Returns bounds with origin relative to parent window's origin.
+  gfx::Rect AdjustPopupWindowPosition() const;
+
+  WaylandWindow* GetTopLevelWindow();
+
   // wl_surface_listener
   static void Enter(void* data,
                     struct wl_surface* wl_surface,
diff --git a/ui/ozone/platform/wayland/wayland_window_unittest.cc b/ui/ozone/platform/wayland/wayland_window_unittest.cc
index d3a99e1..c85ff8e 100644
--- a/ui/ozone/platform/wayland/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_window_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "ui/ozone/platform/wayland/wayland_window.h"
 
+#include <memory>
+
 #include <linux/input.h>
 #include <wayland-server-core.h>
 #include <xdg-shell-unstable-v5-server-protocol.h>
@@ -34,6 +36,14 @@
 
 namespace {
 
+struct PopupPosition {
+  gfx::Rect anchor_rect;
+  gfx::Size size;
+  uint32_t anchor = 0;
+  uint32_t gravity = 0;
+  uint32_t constraint_adjustment = 0;
+};
+
 class ScopedWlArray {
  public:
   ScopedWlArray() { wl_array_init(&array_); }
@@ -100,6 +110,18 @@
                                     width, height, states);
   }
 
+  void SendConfigureEventPopup(gfx::AcceleratedWidget menu_widget,
+                               const gfx::Rect bounds) {
+    auto* popup = GetPopupByWidget(menu_widget);
+    ASSERT_TRUE(popup);
+    if (GetParam() == kXdgShellV5) {
+      LOG(WARNING) << "XDG V5 does not support configure events for popups.";
+    } else {
+      zxdg_popup_v6_send_configure(popup->resource(), bounds.x(), bounds.y(),
+                                   bounds.width(), bounds.height());
+    }
+  }
+
   // Depending on a shell version, xdg_surface_ or xdg_toplevel surface should
   // get the mock calls. This method decided, which surface to use.
   wl::MockXdgSurface* GetXdgSurface() {
@@ -159,6 +181,37 @@
     Mock::VerifyAndClearExpectations(&delegate_);
   }
 
+  void VerifyXdgPopupPosition(gfx::AcceleratedWidget menu_widget,
+                              const PopupPosition& position) {
+    auto* popup = GetPopupByWidget(menu_widget);
+    ASSERT_TRUE(popup);
+
+    if (GetParam() == kXdgShellV5) {
+      LOG(WARNING) << "XDG V5 does not support xdg_positioner";
+      return;
+    }
+
+    EXPECT_EQ(popup->anchor_rect(), position.anchor_rect);
+    EXPECT_EQ(popup->size(), position.size);
+    EXPECT_EQ(popup->anchor(), position.anchor);
+    EXPECT_EQ(popup->gravity(), position.gravity);
+    EXPECT_EQ(popup->constraint_adjustment(), position.constraint_adjustment);
+  }
+
+  wl::MockXdgPopup* GetPopupByWidget(gfx::AcceleratedWidget widget) {
+    wl::MockSurface* mock_surface = server_.GetObject<wl::MockSurface>(widget);
+    if (mock_surface) {
+      if (GetParam() == kXdgShellV5) {
+        return mock_surface->xdg_popup();
+      } else {
+        auto* mock_xdg_surface = mock_surface->xdg_surface();
+        if (mock_xdg_surface)
+          return mock_xdg_surface->xdg_popup();
+      }
+    }
+    return nullptr;
+  }
+
   wl::MockXdgSurface* xdg_surface_;
 
   MouseEvent test_mouse_event_;
@@ -797,6 +850,253 @@
   }
 }
 
+// Tests WaylandWindow repositions menu windows to be relative to parent window
+// in a right way. Also, tests it sends right anchor and is able to calculate
+// bounds back from relative to parent to be relative to screen/toplevel window.
+// All bounds values are taken by manually running the browser.
+TEST_P(WaylandWindowTest, AdjustPopupBounds) {
+  // Only shell v6 exercises this test as long as shell v5 does not support
+  // positioners.
+  if (GetParam() == kXdgShellV5)
+    return;
+
+  PopupPosition menu_window_positioner = {
+      gfx::Rect(439, 46, 1, 30), gfx::Size(287, 409),
+      ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT,
+      ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT,
+      ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X |
+          ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y};
+
+  PopupPosition nested_menu_window_positioner = {
+      gfx::Rect(4, 80, 279, 1), gfx::Size(305, 99),
+      ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT,
+      ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT,
+      ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X |
+          ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y};
+
+  auto* toplevel_window = window_.get();
+  toplevel_window->SetBounds(gfx::Rect(0, 0, 739, 574));
+
+  // Case 1: the top menu window is positioned normally.
+  MockPlatformWindowDelegate menu_window_delegate;
+  gfx::Rect menu_window_bounds(gfx::Point(440, 76),
+                               menu_window_positioner.size);
+  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
+      PlatformWindowType::kMenu, toplevel_window->GetWidget(),
+      menu_window_bounds, &menu_window_delegate);
+
+  Sync();
+
+  gfx::AcceleratedWidget menu_window_widget = menu_window->GetWidget();
+  VerifyXdgPopupPosition(menu_window_widget, menu_window_positioner);
+
+  EXPECT_CALL(menu_window_delegate, OnBoundsChanged(_)).Times(0);
+  SendConfigureEventPopup(menu_window_widget, menu_window_bounds);
+
+  Sync();
+
+  EXPECT_EQ(menu_window->GetBounds(), menu_window_bounds);
+
+  // Case 2: the nested menu window is positioned normally.
+  MockPlatformWindowDelegate nested_menu_window_delegate;
+  gfx::Rect nested_menu_window_bounds(gfx::Point(723, 156),
+                                      nested_menu_window_positioner.size);
+  std::unique_ptr<WaylandWindow> nested_menu_window =
+      CreateWaylandWindowWithParams(
+          PlatformWindowType::kMenu, menu_window_widget,
+          nested_menu_window_bounds, &nested_menu_window_delegate);
+
+  Sync();
+
+  gfx::AcceleratedWidget nested_menu_window_widget =
+      nested_menu_window->GetWidget();
+  VerifyXdgPopupPosition(nested_menu_window_widget,
+                         nested_menu_window_positioner);
+
+  EXPECT_CALL(nested_menu_window_delegate, OnBoundsChanged(_)).Times(0);
+  const gfx::Point origin(nested_menu_window_positioner.anchor_rect.x() +
+                              nested_menu_window_positioner.anchor_rect.width(),
+                          nested_menu_window_positioner.anchor_rect.y());
+  gfx::Rect calculated_nested_bounds = nested_menu_window_bounds;
+  calculated_nested_bounds.set_origin(origin);
+  SendConfigureEventPopup(nested_menu_window_widget, calculated_nested_bounds);
+
+  Sync();
+
+  EXPECT_EQ(nested_menu_window->GetBounds(), nested_menu_window_bounds);
+
+  // Case 3: imagine the menu window was positioned near to the right edge of a
+  // display. Nothing changes in the way how WaylandWindow calculates bounds,
+  // because the Wayland compositor does not provide global location of windows.
+  // Though, the compositor can reposition the window (flip along x or y axis or
+  // slide along those axis). WaylandWindow just needs to correctly translate
+  // bounds from relative to parent to be relative to screen. The Wayland
+  // compositor does not reposition the menu, because it fits the screen, but
+  // the nested menu window is repositioned to the left.
+  EXPECT_CALL(
+      nested_menu_window_delegate,
+      OnBoundsChanged(gfx::Rect({139, 156}, nested_menu_window_bounds.size())));
+  calculated_nested_bounds.set_origin({-301, 80});
+  SendConfigureEventPopup(nested_menu_window_widget, calculated_nested_bounds);
+
+  Sync();
+
+  // Case 4: imagine the top level window was moved down to the bottom edge of a
+  // display and only tab strip with 3-dot menu buttons left visible. In this
+  // case, Chromium also does not know about that and positions the window
+  // normally (normal bounds are sent), but the Wayland compositor flips the top
+  // menu window along y-axis and fixes bounds of a top level window so that it
+  // is located (from the Chromium point of view) below origin of the menu
+  // window.
+  EXPECT_CALL(delegate_, OnBoundsChanged(
+                             gfx::Rect({0, 363}, window_->GetBounds().size())));
+  EXPECT_CALL(menu_window_delegate,
+              OnBoundsChanged(gfx::Rect({440, 0}, menu_window_bounds.size())));
+  SendConfigureEventPopup(menu_window_widget,
+                          gfx::Rect({440, -363}, menu_window_bounds.size()));
+
+  Sync();
+
+  // The nested menu window is also repositioned accordingly, but it's not
+  // Wayland compositor reposition, but rather reposition from the Chromium
+  // side. Thus, we have to check that anchor rect is correct.
+  nested_menu_window.reset();
+  nested_menu_window_bounds.set_origin({723, 258});
+  nested_menu_window = CreateWaylandWindowWithParams(
+      PlatformWindowType::kMenu, menu_window_widget, nested_menu_window_bounds,
+      &nested_menu_window_delegate);
+
+  Sync();
+
+  nested_menu_window_widget = nested_menu_window->GetWidget();
+  // We must get the anchor on gfx::Point(4, 258).
+  nested_menu_window_positioner.anchor_rect.set_origin({4, 258});
+  VerifyXdgPopupPosition(nested_menu_window_widget,
+                         nested_menu_window_positioner);
+
+  Sync();
+
+  EXPECT_CALL(nested_menu_window_delegate, OnBoundsChanged(_)).Times(0);
+  calculated_nested_bounds.set_origin({283, 258});
+  SendConfigureEventPopup(nested_menu_window_widget, calculated_nested_bounds);
+
+  Sync();
+
+  // Case 5: this case involves case 4. Thus, it concerns only the nested menu
+  // window. imagine that the top menu window is flipped along y-axis and
+  // positioned near to the right side of a display. The nested menu window is
+  // flipped along x-axis by the compositor and WaylandWindow must calculate
+  // bounds back to be relative to display correctly. If the window is near to
+  // the left edge of a display, nothing is going to change, and the origin will
+  // be the same as in the previous case.
+  EXPECT_CALL(
+      nested_menu_window_delegate,
+      OnBoundsChanged(gfx::Rect({149, 258}, nested_menu_window_bounds.size())));
+  calculated_nested_bounds.set_origin({-291, 258});
+  SendConfigureEventPopup(nested_menu_window_widget, calculated_nested_bounds);
+
+  Sync();
+
+  // Case 6: imagine the top level window was moved back to normal position. In
+  // this case, the Wayland compositor positions the menu window normally and
+  // the WaylandWindow repositions the top level window back to 0,0 (which had
+  // an offset to compensate the position of the menu window fliped along
+  // y-axis. It just has had negative y value, which is wrong for Chromium.
+  EXPECT_CALL(delegate_,
+              OnBoundsChanged(gfx::Rect({0, 0}, window_->GetBounds().size())));
+  EXPECT_CALL(menu_window_delegate,
+              OnBoundsChanged(gfx::Rect({440, 76}, menu_window_bounds.size())));
+  SendConfigureEventPopup(menu_window_widget,
+                          gfx::Rect({440, 76}, menu_window_bounds.size()));
+
+  Sync();
+
+  VerifyAndClearExpectations();
+
+  // Case 7: imagine the top level window has the size corresponding near to the
+  // maximum work area of a display. Despite being unaware where the top level
+  // window is, Chromium positions the nested menu window to be on the left side
+  // of the menu window. But, WaylandWindow must reposition it to be on the
+  // right side of the menu window, and let the Wayland compositor decide how to
+  // position the nested menu (if its pixels do not fit one display, it can be
+  // flipped along x-axis). PS: all the values are taken after manually using
+  // the browser and logging bounds.
+  nested_menu_window.reset();
+  menu_window.reset();
+
+  window_->SetBounds(gfx::Rect(0, 0, 2493, 1413));
+
+  menu_window_bounds.set_origin({2206, 67});
+  menu_window = CreateWaylandWindowWithParams(
+      PlatformWindowType::kMenu, toplevel_window->GetWidget(),
+      menu_window_bounds, &menu_window_delegate);
+
+  Sync();
+
+  menu_window_widget = menu_window->GetWidget();
+  menu_window_positioner.anchor_rect.set_origin({2205, 37});
+  VerifyXdgPopupPosition(menu_window_widget, menu_window_positioner);
+
+  EXPECT_CALL(menu_window_delegate, OnBoundsChanged(_)).Times(0);
+  SendConfigureEventPopup(menu_window_widget, menu_window_bounds);
+
+  Sync();
+
+  nested_menu_window_bounds.set_origin({1905, 147});
+  nested_menu_window = CreateWaylandWindowWithParams(
+      PlatformWindowType::kMenu, menu_window_widget, nested_menu_window_bounds,
+      &nested_menu_window_delegate);
+
+  Sync();
+
+  nested_menu_window_widget = nested_menu_window->GetWidget();
+  nested_menu_window_positioner.anchor_rect.set_origin({4, 80});
+  VerifyXdgPopupPosition(nested_menu_window_widget,
+                         nested_menu_window_positioner);
+
+  VerifyAndClearExpectations();
+
+  // Case 8: now, the top level window becomes maximized. Compared to the case
+  // 7, despite having the size corresponding to the work area of a display, the
+  // WaylandWindow must not reposition the nested menu window to the right side,
+  // and let it be on the left side of a menu window as long as letting the
+  // Wayland compositor repositioning the nested window may result in a window
+  // shown on another display.
+  auto active_maximized = MakeStateArray(
+      {XDG_SURFACE_STATE_ACTIVATED, XDG_SURFACE_STATE_MAXIMIZED});
+  EXPECT_CALL(*GetXdgSurface(), SetMaximized());
+
+  window_->Maximize();
+  SendConfigureEvent(2493, 1413, 1, active_maximized.get());
+
+  Sync();
+
+  nested_menu_window.reset();
+
+  nested_menu_window = CreateWaylandWindowWithParams(
+      PlatformWindowType::kMenu, menu_window_widget, nested_menu_window_bounds,
+      &nested_menu_window_delegate);
+
+  Sync();
+
+  nested_menu_window_widget = nested_menu_window->GetWidget();
+  // The anchor and gravity must change to be on the right side.
+  nested_menu_window_positioner.anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+  nested_menu_window_positioner.anchor |= ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+  nested_menu_window_positioner.gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+  nested_menu_window_positioner.gravity |= ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+  VerifyXdgPopupPosition(nested_menu_window_widget,
+                         nested_menu_window_positioner);
+
+  calculated_nested_bounds.set_origin({-301, 80});
+  EXPECT_CALL(nested_menu_window_delegate, OnBoundsChanged(_)).Times(0);
+  SendConfigureEventPopup(nested_menu_window_widget, calculated_nested_bounds);
+
+  Sync();
+
+  VerifyAndClearExpectations();
+}
+
 INSTANTIATE_TEST_SUITE_P(XdgVersionV5Test,
                          WaylandWindowTest,
                          ::testing::Values(kXdgShellV5));
diff --git a/ui/ozone/platform/wayland/xdg_popup_wrapper_v5.cc b/ui/ozone/platform/wayland/xdg_popup_wrapper_v5.cc
index 0da36f5..80cd1ac 100644
--- a/ui/ozone/platform/wayland/xdg_popup_wrapper_v5.cc
+++ b/ui/ozone/platform/wayland/xdg_popup_wrapper_v5.cc
@@ -37,6 +37,10 @@
 
   xdg_popup_add_listener(xdg_popup_.get(), &xdg_popup_listener, this);
 
+  // xdg_popup_v5 does not support configure events. Thus, manually call it to
+  // propagate final bounds.
+  wayland_window_->HandlePopupConfigure(bounds);
+
   return true;
 }
 
diff --git a/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.cc b/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.cc
index 714c8433..38b274f 100644
--- a/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.cc
+++ b/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.cc
@@ -6,13 +6,170 @@
 
 #include <xdg-shell-unstable-v6-client-protocol.h>
 
+#include "ui/events/event_constants.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/ozone/platform/wayland/wayland_connection.h"
+#include "ui/ozone/platform/wayland/wayland_pointer.h"
 #include "ui/ozone/platform/wayland/wayland_window.h"
 #include "ui/ozone/platform/wayland/xdg_surface_wrapper_v6.h"
 
 namespace ui {
 
+namespace {
+
+constexpr uint32_t kAnchorDefaultWidth = 1;
+constexpr uint32_t kAnchorDefaultHeight = 1;
+constexpr uint32_t kAnchorHeightParentMenu = 30;
+
+enum class MenuType {
+  TYPE_RIGHT_CLICK,
+  TYPE_3DOT_PARENT_MENU,
+  TYPE_3DOT_CHILD_MENU,
+  TYPE_UNKNOWN,
+};
+
+uint32_t GetAnchor(MenuType menu_type, const gfx::Rect& bounds) {
+  uint32_t anchor = 0;
+  switch (menu_type) {
+    case MenuType::TYPE_RIGHT_CLICK:
+      anchor = ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+      break;
+    case MenuType::TYPE_3DOT_PARENT_MENU:
+      anchor =
+          ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+      break;
+    case MenuType::TYPE_3DOT_CHILD_MENU:
+      anchor = ZXDG_POSITIONER_V6_ANCHOR_TOP;
+      // Chromium may want to manually position a child menu on the left side of
+      // its parent menu. Thus, react accordingly. Positive x means the child is
+      // located on the right side of the parent and negative - on the left
+      // side.
+      if (bounds.x() >= 0)
+        anchor |= ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+      else
+        anchor |= ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+      break;
+    case MenuType::TYPE_UNKNOWN:
+      NOTREACHED() << "Unsupported menu type";
+      break;
+  }
+
+  return anchor;
+}
+
+uint32_t GetGravity(MenuType menu_type, const gfx::Rect& bounds) {
+  uint32_t gravity = 0;
+  switch (menu_type) {
+    case MenuType::TYPE_RIGHT_CLICK:
+      gravity =
+          ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+      break;
+    case MenuType::TYPE_3DOT_PARENT_MENU:
+      gravity =
+          ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+      break;
+    case MenuType::TYPE_3DOT_CHILD_MENU:
+      gravity = ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
+      // Chromium may want to manually position a child menu on the left side of
+      // its parent menu. Thus, react accordingly. Positive x means the child is
+      // located on the right side of the parent and negative - on the left
+      // side.
+      if (bounds.x() >= 0)
+        gravity |= ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+      else
+        gravity |= ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+      break;
+    case MenuType::TYPE_UNKNOWN:
+      NOTREACHED() << "Unsupported menu type";
+      break;
+  }
+
+  return gravity;
+}
+
+uint32_t GetConstraintAdjustment(MenuType menu_type) {
+  uint32_t constraint = 0;
+
+  switch (menu_type) {
+    case MenuType::TYPE_RIGHT_CLICK:
+      constraint = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X |
+                   ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y |
+                   ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
+      break;
+    case MenuType::TYPE_3DOT_PARENT_MENU:
+      constraint = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X |
+                   ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
+      break;
+    case MenuType::TYPE_3DOT_CHILD_MENU:
+      constraint = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X |
+                   ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
+      break;
+    case MenuType::TYPE_UNKNOWN:
+      NOTREACHED() << "Unsupported menu type";
+      break;
+  }
+
+  return constraint;
+}
+
+gfx::Rect GetAnchorRect(MenuType menu_type,
+                        const gfx::Rect& menu_bounds,
+                        const gfx::Rect& parent_window_bounds) {
+  gfx::Rect anchor_rect;
+  switch (menu_type) {
+    case MenuType::TYPE_RIGHT_CLICK:
+      // Place anchor for right click menus normally.
+      anchor_rect = gfx::Rect(menu_bounds.x(), menu_bounds.y(),
+                              kAnchorDefaultWidth, kAnchorDefaultHeight);
+      break;
+    case MenuType::TYPE_3DOT_PARENT_MENU:
+      // The anchor for parent menu windows is positioned slightly above the
+      // specified bounds to ensure flipped window along y-axis won't hide 3-dot
+      // menu button.
+      anchor_rect = gfx::Rect(menu_bounds.x() - kAnchorDefaultWidth,
+                              menu_bounds.y() - kAnchorHeightParentMenu,
+                              kAnchorDefaultWidth, kAnchorHeightParentMenu);
+      break;
+    case MenuType::TYPE_3DOT_CHILD_MENU:
+      // The child menu's anchor must meet the following requirements: at some
+      // point, the Wayland compositor can flip it along x-axis. To make sure
+      // it's positioned correctly, place it closer to the beginning of the
+      // parent menu shifted by the same value along x-axis. The width of anchor
+      // must correspond the width between two points - specified origin by the
+      // Chromium and calculated point shifted by the same value along x-axis
+      // from the beginning of the parent menu width.
+      //
+      // We also have to bear in mind that Chromium may decide to flip the
+      // position of the menu window along the x-axis and show it on the other
+      // side of the parent menu window (normally, the Wayland compositor does
+      // it). Thus, check which side the child menu window is going to be
+      // presented on and create right anchor.
+      if (menu_bounds.x() >= 0) {
+        auto anchor_width =
+            parent_window_bounds.width() -
+            (parent_window_bounds.width() - menu_bounds.x()) * 2;
+        anchor_rect =
+            gfx::Rect(parent_window_bounds.width() - menu_bounds.x(),
+                      menu_bounds.y(), anchor_width, kAnchorDefaultHeight);
+      } else {
+        DCHECK_LE(menu_bounds.x(), 0);
+        auto position = menu_bounds.width() + menu_bounds.x();
+        DCHECK(position > 0 && position < parent_window_bounds.width());
+        auto anchor_width = parent_window_bounds.width() - position * 2;
+        anchor_rect = gfx::Rect(position, menu_bounds.y(), anchor_width,
+                                kAnchorDefaultHeight);
+      }
+      break;
+    case MenuType::TYPE_UNKNOWN:
+      NOTREACHED() << "Unsupported menu type";
+      break;
+  }
+
+  return anchor_rect;
+}
+
+}  // namespace
+
 XDGPopupWrapperV6::XDGPopupWrapperV6(std::unique_ptr<XDGSurfaceWrapper> surface,
                                      WaylandWindow* wayland_window)
     : wayland_window_(wayland_window), zxdg_surface_v6_(std::move(surface)) {
@@ -51,7 +208,8 @@
   if (!parent_xdg_surface)
     return false;
 
-  zxdg_positioner_v6* positioner = CreatePositioner(connection, bounds);
+  zxdg_positioner_v6* positioner =
+      CreatePositioner(connection, parent_window, bounds);
   if (!positioner)
     return false;
 
@@ -73,20 +231,41 @@
 
 zxdg_positioner_v6* XDGPopupWrapperV6::CreatePositioner(
     WaylandConnection* connection,
+    WaylandWindow* parent_window,
     const gfx::Rect& bounds) {
   struct zxdg_positioner_v6* positioner;
   positioner = zxdg_shell_v6_create_positioner(connection->shell_v6());
   if (!positioner)
     return nullptr;
 
-  zxdg_positioner_v6_set_anchor_rect(positioner, bounds.x(), bounds.y(), 1, 1);
+  auto* pointer = connection->pointer();
+  uint32_t flags = 0;
+  if (pointer)
+    flags = pointer->GetFlagsWithKeyboardModifiers();
+  bool is_right_click_menu = flags & EF_RIGHT_MOUSE_BUTTON;
+
+  // Different types of menu require different anchors, constraint adjustments,
+  // gravity and etc.
+  MenuType menu_type = MenuType::TYPE_UNKNOWN;
+  if (is_right_click_menu)
+    menu_type = MenuType::TYPE_RIGHT_CLICK;
+  else if (parent_window->xdg_popup())
+    menu_type = MenuType::TYPE_3DOT_CHILD_MENU;
+  else
+    menu_type = MenuType::TYPE_3DOT_PARENT_MENU;
+
+  // Place anchor to the end of the possible position.
+  gfx::Rect anchor_rect =
+      GetAnchorRect(menu_type, bounds, parent_window->GetBounds());
+
+  zxdg_positioner_v6_set_anchor_rect(positioner, anchor_rect.x(),
+                                     anchor_rect.y(), anchor_rect.width(),
+                                     anchor_rect.height());
   zxdg_positioner_v6_set_size(positioner, bounds.width(), bounds.height());
-  zxdg_positioner_v6_set_anchor(
-      positioner,
-      ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
-  zxdg_positioner_v6_set_gravity(
-      positioner,
-      ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
+  zxdg_positioner_v6_set_anchor(positioner, GetAnchor(menu_type, bounds));
+  zxdg_positioner_v6_set_gravity(positioner, GetGravity(menu_type, bounds));
+  zxdg_positioner_v6_set_constraint_adjustment(
+      positioner, GetConstraintAdjustment(menu_type));
   return positioner;
 }
 
@@ -96,7 +275,19 @@
                                   int32_t x,
                                   int32_t y,
                                   int32_t width,
-                                  int32_t height) {}
+                                  int32_t height) {
+  // As long as the Wayland compositor repositions/requires to position windows
+  // relative to their parents, do not propagate final bounds information to
+  // Chromium. The browser places windows in respect to screen origin, but
+  // Wayland requires doing so in respect to parent window's origin. To properly
+  // place windows, the bounds are translated and adjusted according to the
+  // Wayland compositor needs during WaylandWindow::CreateXdgPopup call.
+  gfx::Rect new_bounds(x, y, width, height);
+  WaylandWindow* window =
+      static_cast<XDGPopupWrapperV6*>(data)->wayland_window_;
+  DCHECK(window);
+  window->HandlePopupConfigure(new_bounds);
+}
 
 // static
 void XDGPopupWrapperV6::PopupDone(void* data,
diff --git a/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.h b/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.h
index b3df086..4e43d2dd 100644
--- a/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.h
+++ b/ui/ozone/platform/wayland/xdg_popup_wrapper_v6.h
@@ -26,6 +26,7 @@
                   const gfx::Rect& bounds) override;
 
   zxdg_positioner_v6* CreatePositioner(WaylandConnection* connection,
+                                       WaylandWindow* parent_window,
                                        const gfx::Rect& bounds);
 
   // xdg_popup_listener
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index 7bb14468..c27df5f 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -66,7 +66,6 @@
                               ? Widget::InitParams::TRANSLUCENT_WINDOW
                               : Widget::InitParams::OPAQUE_WINDOW;
   bubble_params.accept_events = bubble->accept_events();
-  bubble_params.remove_standard_frame = true;
   // Use a window default shadow if the bubble doesn't provides its own.
   if (bubble->GetShadow() == BubbleBorder::NO_ASSETS)
     bubble_params.shadow_type = Widget::InitParams::SHADOW_TYPE_DEFAULT;
@@ -153,8 +152,10 @@
   std::unique_ptr<BubbleBorder> border =
       std::make_unique<BubbleBorder>(adjusted_arrow, GetShadow(), color());
   if (CustomShadowsSupported() && ShouldHaveRoundCorners()) {
-    const int corner_radius = provider->GetCornerRadiusMetric(EMPHASIS_HIGH);
-    border->SetCornerRadius(corner_radius);
+    // TODO(sajadm): Remove when fixing https://crbug.com/822075 and use
+    // EMPHASIS_HIGH metric values from the LayoutProvider to get the
+    // corner radius.
+    border->SetCornerRadius(2);
   }
 
   frame->SetBubbleBorder(std::move(border));
diff --git a/ui/views/controls/webview/webview.cc b/ui/views/controls/webview/webview.cc
index 71a4d87..c5e4525 100644
--- a/ui/views/controls/webview/webview.cc
+++ b/ui/views/controls/webview/webview.cc
@@ -16,7 +16,6 @@
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
 #include "ipc/ipc_message.h"
-#include "ui/accessibility/accessibility_switches.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/events/event.h"
@@ -261,26 +260,11 @@
 }
 
 gfx::NativeViewAccessible WebView::GetNativeViewAccessible() {
-  // On Windows, when UI Automation is enabled, we do not link to the render
-  // widget's NativeViewAccessible here. UIA recognizes the HWND parent-child
-  // relationship between the browser UI HWND and the LegacyRenderWidgetHostHWND
-  // associated with the render widget. By default, it assumes that HWND
-  // parent-child relationships map to accessibility parent-child relationships.
-  //
-  // If we were to return the render widget's NativeViewAccessible here, the
-  // browser content accessibility tree would appear rooted in two places: (1)
-  // in the UI widget hierarchy at the WebView's position, and (2) as a direct
-  // child of the browser UI fragment root, owing to the HWND parent-child
-  // relationship between the browser UI HWND and LegacyRenderWidgetHostHWND.
-  // That would lead to tree navigation inconsistencies that confuse assistive
-  // technologies such as Narrator.
-  if (!::switches::IsExperimentalAccessibilityPlatformUIAEnabled()) {
-    if (web_contents() && !web_contents()->IsCrashed()) {
-      content::RenderWidgetHostView* host_view =
-          web_contents()->GetRenderWidgetHostView();
-      if (host_view)
-        return host_view->GetNativeViewAccessible();
-    }
+  if (web_contents() && !web_contents()->IsCrashed()) {
+    content::RenderWidgetHostView* host_view =
+        web_contents()->GetRenderWidgetHostView();
+    if (host_view)
+      return host_view->GetNativeViewAccessible();
   }
   return View::GetNativeViewAccessible();
 }
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index 887cfbd3..dd9210c 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -25,6 +25,7 @@
 #include "ui/events/gestures/gesture_recognizer_observer.h"
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/geometry/vector2d_conversions.h"
+#include "ui/gfx/image/image_skia_operations.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/corewm/tooltip_aura.h"
 #include "ui/views/mus/cursor_manager_owner.h"
@@ -345,11 +346,6 @@
     SetBoundsInDIP(params.bounds);
   }
 
-  // If the standard frame is not used, the frame area (rendered by the client)
-  // should be handled by the client, so it shouldn't update the client area
-  // by itself. See https://crbug.com/935338.
-  auto_update_client_area_ = !params.remove_standard_frame;
-
   cursor_manager_owner_ = std::make_unique<CursorManagerOwner>(window());
   InitHost();
 
@@ -897,7 +893,20 @@
 
 void DesktopWindowTreeHostMus::SetWindowIcons(const gfx::ImageSkia& window_icon,
                                               const gfx::ImageSkia& app_icon) {
-  NativeWidgetAura::AssignIconToAuraWindow(window(), window_icon, app_icon);
+  // In Ash, the app icon is always used in preference to the window icon, so
+  // ignore the window icon. The max size that ash needs is 24dip, which is
+  // kIconSize in caption_container_view.cc.
+  constexpr gfx::Size kMaxUsefulAppIconSize(24, 24);
+  DCHECK_EQ(app_icon.width(), app_icon.height());
+  gfx::ImageSkia app_icon_resized =
+      app_icon.width() > kMaxUsefulAppIconSize.width()
+          ? gfx::ImageSkiaOperations::CreateResizedImage(
+                app_icon, skia::ImageOperations::RESIZE_BEST,
+                kMaxUsefulAppIconSize)
+          : app_icon;
+
+  window()->GetRootWindow()->SetProperty(aura::client::kAppIconSmallKey,
+                                         new gfx::ImageSkia(app_icon_resized));
 }
 
 void DesktopWindowTreeHostMus::InitModalType(ui::ModalType modal_type) {
@@ -1032,7 +1041,7 @@
     const viz::LocalSurfaceIdAllocation& local_surface_id_allocation) {
   gfx::Rect final_bounds = bounds;
   // If the server initiated the bounds change, then we need to honor it.
-  if (!in_set_bounds_from_server() && bounds_in_dip().size() != bounds.size()) {
+  if (!is_server_setting_bounds() && bounds_in_dip().size() != bounds.size()) {
     gfx::Size size = bounds.size();
     size.SetToMax(native_widget_delegate_->GetMinimumSize());
     const gfx::Size max_size = native_widget_delegate_->GetMaximumSize();
@@ -1051,7 +1060,7 @@
   // WindowTreeHost exposes SetBoundsInPixels() this function may be called too.
   // If the server initiated the bounds change, then we need to honor it.
   gfx::Rect final_bounds_in_pixels = bounds_in_pixels;
-  if (!in_set_bounds_from_server() &&
+  if (!is_server_setting_bounds() &&
       GetBoundsInPixels().size() != bounds_in_pixels.size()) {
     gfx::Size size = bounds_in_pixels.size();
     size.SetToMax(gfx::ConvertSizeToPixel(
diff --git a/ui/views/mus/desktop_window_tree_host_mus.h b/ui/views/mus/desktop_window_tree_host_mus.h
index 5df1a1cd..56cea18 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.h
+++ b/ui/views/mus/desktop_window_tree_host_mus.h
@@ -40,6 +40,11 @@
   // Called when the window was deleted on the server.
   void ServerDestroyedWindow() { CloseNow(); }
 
+  // Controls whether the client area is automatically updated as necessary.
+  void set_auto_update_client_area(bool value) {
+    auto_update_client_area_ = value;
+  }
+
  private:
   class WindowTreeHostWindowObserver;
 
diff --git a/ui/views/mus/mus_client.cc b/ui/views/mus/mus_client.cc
index 56c57cf3..ad63c545 100644
--- a/ui/views/mus/mus_client.cc
+++ b/ui/views/mus/mus_client.cc
@@ -246,22 +246,6 @@
           mojo::ConvertTo<TransportType>(
               init_params.delegate->GetWindowTitle());
     }
-
-    // TODO(crbug.com/667566): Support additional scales or gfx::Image[Skia].
-    gfx::ImageSkia app_icon = init_params.delegate->GetWindowAppIcon();
-    SkBitmap app_bitmap = app_icon.GetRepresentation(1.f).GetBitmap();
-    if (!app_bitmap.isNull()) {
-      properties[WindowManager::kAppIcon_Property] =
-          mojo::ConvertTo<TransportType>(app_bitmap);
-    }
-
-    // TODO(crbug.com/667566): Support additional scales or gfx::Image[Skia].
-    gfx::ImageSkia window_icon = init_params.delegate->GetWindowIcon();
-    SkBitmap window_bitmap = window_icon.GetRepresentation(1.f).GetBitmap();
-    if (!window_bitmap.isNull()) {
-      properties[WindowManager::kWindowIcon_Property] =
-          mojo::ConvertTo<TransportType>(window_bitmap);
-    }
   }
 
   return properties;
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index 79fccf5..c98222f8 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -792,8 +792,8 @@
   // platform code might show the desktop window tree host early, meaning we
   // aren't fully visible as we haven't shown the content window. Callers may
   // short-circuit a call to show this widget if they think its already visible.
-  return content_window_ && content_window_->IsVisible() &&
-      desktop_window_tree_host_->IsVisible();
+  return content_window_ && content_window_->TargetVisibility() &&
+         desktop_window_tree_host_->IsVisible();
 }
 
 void DesktopNativeWidgetAura::Activate() {
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 1a540cc..55b8c93 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -16,6 +16,7 @@
 #include "base/timer/timer.h"
 #include "base/win/windows_version.h"
 #include "build/build_config.h"
+#include "ui/aura/window.h"
 #include "ui/base/ime/input_method.h"
 #include "ui/base/ime/text_input_client.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -41,7 +42,6 @@
 #include "ui/wm/public/activation_client.h"
 
 #if defined(OS_WIN)
-#include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
 #include "ui/views/win/hwnd_util.h"
@@ -1419,38 +1419,32 @@
 }
 
 #if defined(OS_WIN)
-// Tests that widget visibility toggles correctly when minimized and maximized
-// on Windows. Test using both the widget API as well as native win32 functions
-// that operate directly on the underlying HWND. Behavior should be the same.
+// TODO(davidbienvenu): Get this test to pass on Linux and ChromeOS by hiding
+// the root window when desktop widget is minimized.
+// Tests that root window visibility toggles correctly when the desktop widget
+// is minimized and maximized on Windows, and the Widget remains visible.
 TEST_F(DesktopWidgetTestInteractive, RestoreAndMinimizeVisibility) {
   Widget* widget = CreateWidget();
+  aura::Window* root_window = GetRootWindow(widget);
   ShowSync(widget);
   ASSERT_FALSE(widget->IsMinimized());
+  EXPECT_TRUE(root_window->IsVisible());
 
   PropertyWaiter minimize_widget_waiter(
-      base::Bind(&Widget::IsMinimized, base::Unretained(widget)), true);
+      base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget)),
+      true);
   widget->Minimize();
   EXPECT_TRUE(minimize_widget_waiter.Wait());
-  EXPECT_FALSE(widget->IsVisible());
+  EXPECT_TRUE(widget->IsVisible());
+  EXPECT_FALSE(root_window->IsVisible());
 
   PropertyWaiter restore_widget_waiter(
-      base::Bind(&Widget::IsMinimized, base::Unretained(widget)), false);
+      base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget)),
+      false);
   widget->Restore();
   EXPECT_TRUE(restore_widget_waiter.Wait());
   EXPECT_TRUE(widget->IsVisible());
-
-  PropertyWaiter minimize_hwnd_waiter(
-      base::Bind(&Widget::IsMinimized, base::Unretained(widget)), true);
-  CloseWindow(HWNDForWidget(widget));
-  EXPECT_TRUE(minimize_hwnd_waiter.Wait());
-  EXPECT_FALSE(widget->IsVisible());
-
-  PropertyWaiter restore_hwnd_waiter(
-      base::Bind(&Widget::IsMinimized, base::Unretained(widget)), false);
-  OpenIcon(HWNDForWidget(widget));
-  EXPECT_TRUE(restore_hwnd_waiter.Wait());
-  EXPECT_TRUE(widget->IsVisible());
-
+  EXPECT_TRUE(root_window->IsVisible());
   widget->CloseNow();
 }
 #endif  // defined(OS_WIN)
diff --git a/ui/views/window/dialog_delegate.cc b/ui/views/window/dialog_delegate.cc
index 3060b8b..3c706c8 100644
--- a/ui/views/window/dialog_delegate.cc
+++ b/ui/views/window/dialog_delegate.cc
@@ -220,8 +220,10 @@
   DialogDelegate* delegate = widget->widget_delegate()->AsDialogDelegate();
   if (delegate) {
     if (delegate->ShouldHaveRoundCorners()) {
-      const int corner_radius = provider->GetCornerRadiusMetric(EMPHASIS_HIGH);
-      border->SetCornerRadius(corner_radius);
+      // TODO(sajadm): Remove when fixing https://crbug.com/822075 and use
+      // EMPHASIS_HIGH metric values from the LayoutProvider to get the
+      // corner radius.
+      border->SetCornerRadius(2);
     }
     frame->SetFootnoteView(delegate->CreateFootnoteView());
   }
diff --git a/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html b/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html
index 32cb0875..df88d87 100644
--- a/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html
+++ b/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html
@@ -10,7 +10,7 @@
   <template>
     <style>
       :host {
-        --avatar-size: 48px;
+        --avatar-size: 96px;
         --avatar-spacing: 24px;
         display: inline-flex;
 
@@ -22,7 +22,7 @@
         background-position: center;
         background-repeat: no-repeat;
         border: 1px solid rgba(0, 0, 0, .12);
-        border-radius: var(--cr-card-border-radius);
+        border-radius: 100%;
         display: flex;
         height: var(--avatar-size);
         margin: calc(var(--avatar-spacing) / 2);
@@ -41,6 +41,10 @@
         border: 2px solid var(--google-blue-500);
       }
 
+      paper-button {
+        background-size: var(--avatar-size);
+      }
+
       :host-context([dark]) #avatar-grid .avatar.iron-selected {
         border-color: var(--google-blue-refresh-300);
       }