diff --git a/BUILD.gn b/BUILD.gn
index 662dea5..5d692b4b 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -188,7 +188,7 @@
       "//chrome/test:telemetry_perf_unittests",
       "//chrome/test:unit_tests",
       "//components:components_browsertests",
-      "//components/ui_devtools/viz_views",
+      "//components/ui_devtools/viz",
       "//components/viz:viz_perftests",
       "//components/viz:viz_unittests",
       "//components/viz/common:viz_benchmark",
diff --git a/DEPS b/DEPS
index 79132286..1ead8ca 100644
--- a/DEPS
+++ b/DEPS
@@ -117,15 +117,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling sfntly
   # and whatever else without interference from each other.
-  'sfntly_revision': 'b55ff303ea2f9e26702b514cf6a3196a2e3e2974',
+  'sfntly_revision': 'e24c73130c663c9f329e78f5ca3fd5bd83b02622',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'a9a06907f39c5c771c33a6101f97fce613f674b8',
+  'skia_revision': 'f1202c61de0875f38dfe3c93ec99a08b9c625cc7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '336755dcde17709b01b76af34da2e6f898bbdfac',
+  'v8_revision': 'dadf4cbe89c1e9ee9fed6181216cb4d3ba647a68',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -133,7 +133,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '6344d8ea30d7a5ce14042cdd4dcf90a53107b9e5',
+  'angle_revision': '988d9a068976cd893eb1cd1093299b166b58594a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -145,7 +145,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '800298c07d246b4235ef7cd699a75271e6904c61',
+  'pdfium_revision': '45531b950b6a04a7bcae12431ccfebec9aec77d6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -181,7 +181,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': '4dbf1ef8fd93fd90aa85fb7739736fa25d226187',
+  'catapult_revision': '3511bed449613cd4dc1231eedf29f2d8a5423d7e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -679,7 +679,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '82b851dfd6665ba6452703654b3f8adbcbc2c565',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'af1fe10f99b95a23575cc9db6a30c0c1f26b1fa3',
       'condition': 'checkout_linux',
   },
 
@@ -1230,7 +1230,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@14b74af5cc204ee9cd89a51aef9556d05f8636d8',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@05b6bde0074d047c78884a00904b44e1de47d19e',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index a2aadee..3d79a67f 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -276,6 +276,20 @@
 
   network::mojom::NetworkContextPtr network_context;
   network::mojom::NetworkContextParamsPtr context_params =
+      GetNetworkContextParams();
+
+  content::GetNetworkService()->CreateNetworkContext(
+      MakeRequest(&network_context), std::move(context_params));
+
+  // Quic is not currently supported in WebView.
+  content::GetNetworkService()->DisableQuic();
+
+  return network_context;
+}
+
+network::mojom::NetworkContextParamsPtr
+AwContentBrowserClient::GetNetworkContextParams() {
+  network::mojom::NetworkContextParamsPtr context_params =
       network::mojom::NetworkContextParams::New();
   context_params->user_agent = GetUserAgent();
   // TODO(ntfschr): set this value to a proper value based on the user's
@@ -289,19 +303,21 @@
   context_params->http_cache_max_size = GetHttpCacheSize();
   context_params->http_cache_path = AwBrowserContext::GetCacheDir();
 
+  context_params->initial_ssl_config = network::mojom::SSLConfig::New();
+  // Allow SHA-1 to be used for locally-installed trust anchors, as WebView
+  // should behave like the Android system would.
+  context_params->initial_ssl_config->sha1_local_anchors_enabled = true;
+  // Do not enforce the Legacy Symantec PKI policies outlined in
+  // https://security.googleblog.com/2017/09/chromes-plan-to-distrust-symantec.html,
+  // defer to the Android system.
+  context_params->initial_ssl_config->symantec_enforcement_disabled = true;
+
   // WebView does not currently support Certificate Transparency.
   context_params->enforce_chrome_ct_policy = false;
 
   // WebView does not support ftp yet.
   context_params->enable_ftp_url_support = false;
-
-  content::GetNetworkService()->CreateNetworkContext(
-      MakeRequest(&network_context), std::move(context_params));
-
-  // Quic is not currently supported in WebView.
-  content::GetNetworkService()->DisableQuic();
-
-  return network_context;
+  return context_params;
 }
 
 AwBrowserContext* AwContentBrowserClient::InitBrowserContext() {
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index 08ef1d7..5fa0fce 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -58,6 +58,7 @@
       content::BrowserContext* context,
       bool in_memory,
       const base::FilePath& relative_partition_path) override;
+  network::mojom::NetworkContextParamsPtr GetNetworkContextParams();
 
   content::BrowserMainParts* CreateBrowserMainParts(
       const content::MainFunctionParams& parameters) override;
diff --git a/android_webview/browser/aw_content_browser_client_unittest.cc b/android_webview/browser/aw_content_browser_client_unittest.cc
index 462075e..e72a1927 100644
--- a/android_webview/browser/aw_content_browser_client_unittest.cc
+++ b/android_webview/browser/aw_content_browser_client_unittest.cc
@@ -19,4 +19,33 @@
   EXPECT_FALSE(client.ShouldCreateTaskScheduler());
 }
 
+// Tests that constraints on trust for Symantec-issued certificates are not
+// enforced for the NetworkContext, as it should behave like the Android system.
+TEST_F(AwContentBrowserClientTest, SymantecPoliciesExempted) {
+  AwFeatureListCreator aw_feature_list_creator;
+  AwContentBrowserClient client(&aw_feature_list_creator);
+  network::mojom::NetworkContextParamsPtr network_context_params =
+      client.GetNetworkContextParams();
+
+  ASSERT_TRUE(network_context_params);
+  ASSERT_TRUE(network_context_params->initial_ssl_config);
+  ASSERT_TRUE(network_context_params->initial_ssl_config
+                  ->symantec_enforcement_disabled);
+}
+
+// Tests that SHA-1 is still allowed for locally-installed trust anchors,
+// including those in application manifests, as it should behave like
+// the Android system.
+TEST_F(AwContentBrowserClientTest, SHA1LocalAnchorsAllowed) {
+  AwFeatureListCreator aw_feature_list_creator;
+  AwContentBrowserClient client(&aw_feature_list_creator);
+  network::mojom::NetworkContextParamsPtr network_context_params =
+      client.GetNetworkContextParams();
+
+  ASSERT_TRUE(network_context_params);
+  ASSERT_TRUE(network_context_params->initial_ssl_config);
+  ASSERT_TRUE(
+      network_context_params->initial_ssl_config->sha1_local_anchors_enabled);
+}
+
 }  // namespace android_webview
diff --git a/android_webview/browser/compositor_frame_producer.h b/android_webview/browser/compositor_frame_producer.h
index c0c6f52..a207c4d 100644
--- a/android_webview/browser/compositor_frame_producer.h
+++ b/android_webview/browser/compositor_frame_producer.h
@@ -17,7 +17,7 @@
 
 class CompositorFrameProducer {
  public:
-  virtual base::WeakPtr<CompositorFrameProducer> GetWeakPtr();
+  virtual base::WeakPtr<CompositorFrameProducer> GetWeakPtr() = 0;
   virtual void ReturnUsedResources(
       const std::vector<viz::ReturnedResource>& resources,
       const CompositorID& compositor_id,
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwNetworkConfigurationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwNetworkConfigurationTest.java
new file mode 100644
index 0000000..96b6863
--- /dev/null
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwNetworkConfigurationTest.java
@@ -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.
+
+package org.chromium.android_webview.test;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.android_webview.AwContents;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.Feature;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
+
+/**
+ * A test suite for WebView's network-related configuration. This tests WebView's default settings,
+ * which are configured by either AwURLRequestContextGetter or NetworkContext.
+ */
+@RunWith(AwJUnit4ClassRunner.class)
+public class AwNetworkConfigurationTest {
+    @Rule
+    public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
+
+    private AwTestContainerView mTestContainerView;
+    private TestAwContentsClient mContentsClient;
+    private AwTestContainerView mContainerView;
+    private AwContents mAwContents;
+
+    private EmbeddedTestServer mTestServer;
+
+    @Before
+    public void setUp() throws Exception {
+        mContentsClient = new TestAwContentsClient();
+        mTestContainerView = mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
+        mAwContents = mTestContainerView.getAwContents();
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView", "Network"})
+    public void testSHA1LocalAnchorsAllowed() throws Throwable {
+        mTestServer = EmbeddedTestServer.createAndStartHTTPSServer(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                ServerCertificate.CERT_SHA1_LEAF);
+        try {
+            CallbackHelper onReceivedSslErrorHelper = mContentsClient.getOnReceivedSslErrorHelper();
+            int count = onReceivedSslErrorHelper.getCallCount();
+            String url = mTestServer.getURL("/android_webview/test/data/hello_world.html");
+            mActivityTestRule.loadUrlSync(
+                    mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+            // TODO(ntfschr): update this assertion whenever
+            // https://android.googlesource.com/platform/external/conscrypt/+/1d6a0b8453054b7dd703693f2ce2896ae061aee3
+            // rolls into an Android release, as this will mean Android intends to distrust SHA1
+            // (http://crbug.com/919749).
+            Assert.assertEquals("We should not have received any SSL errors", count,
+                    onReceivedSslErrorHelper.getCallCount());
+        } finally {
+            mTestServer.stopAndDestroyServer();
+        }
+    }
+}
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 8e6f6b3..beb8f2e 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -211,6 +211,7 @@
     "../javatests/src/org/chromium/android_webview/test/AwJavaBridgeTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwLayoutSizerTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwLegacyQuirksTest.java",
+    "../javatests/src/org/chromium/android_webview/test/AwNetworkConfigurationTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwPermissionManagerTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwQuotaManagerBridgeTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwScrollOffsetManagerTest.java",
diff --git a/ash/accessibility/accessibility_controller.cc b/ash/accessibility/accessibility_controller.cc
index 78e45d2..6489aff 100644
--- a/ash/accessibility/accessibility_controller.cc
+++ b/ash/accessibility/accessibility_controller.cc
@@ -595,6 +595,11 @@
   switch_access_event_handler_->set_ignore_virtual_key_events(should_ignore);
 }
 
+void AccessibilityController::ForwardKeyEventsToSwitchAccess(
+    bool should_forward) {
+  switch_access_event_handler_->set_forward_key_events(should_forward);
+}
+
 void AccessibilityController::SetSwitchAccessEventHandlerDelegate(
     mojom::SwitchAccessEventHandlerDelegatePtr delegate) {
   switch_access_event_handler_delegate_ptr_ = std::move(delegate);
diff --git a/ash/accessibility/accessibility_controller.h b/ash/accessibility/accessibility_controller.h
index 4863985..a94d275b 100644
--- a/ash/accessibility/accessibility_controller.h
+++ b/ash/accessibility/accessibility_controller.h
@@ -182,6 +182,7 @@
   void SetSwitchAccessEventHandlerDelegate(
       mojom::SwitchAccessEventHandlerDelegatePtr delegate) override;
   void ToggleDictationFromSource(mojom::DictationToggleSource source) override;
+  void ForwardKeyEventsToSwitchAccess(bool should_forward) override;
 
   // SessionObserver:
   void OnSigninScreenPrefServiceInitialized(PrefService* prefs) override;
diff --git a/ash/app_list/BUILD.gn b/ash/app_list/BUILD.gn
index ff72324..fdedfee 100644
--- a/ash/app_list/BUILD.gn
+++ b/ash/app_list/BUILD.gn
@@ -86,8 +86,6 @@
     "views/suggestion_chip_container_view.h",
     "views/suggestion_chip_view.cc",
     "views/suggestion_chip_view.h",
-    "views/suggestions_container_view.cc",
-    "views/suggestions_container_view.h",
     "views/top_icon_animation_view.cc",
     "views/top_icon_animation_view.h",
   ]
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index b7baf50..23f7b75 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -35,7 +35,6 @@
 #include "ash/app_list/views/search_result_view.h"
 #include "ash/app_list/views/suggestion_chip_container_view.h"
 #include "ash/app_list/views/suggestion_chip_view.h"
-#include "ash/app_list/views/suggestions_container_view.h"
 #include "ash/app_list/views/test/apps_grid_view_test_api.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_constants.h"
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index 7339e13..b3f2d966 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -18,7 +18,6 @@
 #include "ash/app_list/views/page_switcher.h"
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/app_list/views/suggestion_chip_container_view.h"
-#include "ash/app_list/views/suggestions_container_view.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
 #include "base/command_line.h"
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 22434b31..b78fce7 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -51,6 +51,7 @@
 #include "ui/gfx/geometry/vector2d_conversions.h"
 #include "ui/gfx/skia_paint_util.h"
 #include "ui/strings/grit/ui_strings.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -385,6 +386,12 @@
 void AppsGridView::DisableFocusForShowingActiveFolder(bool disabled) {
   for (int i = 0; i < view_model_.view_size(); ++i)
     view_model_.view_at(i)->SetEnabled(!disabled);
+
+  // Ignore the grid view in accessibility tree so that items inside it will not
+  // be accessed by ChromeVox.
+  GetViewAccessibility().OverrideIsIgnored(disabled);
+  GetViewAccessibility().NotifyAccessibilityEvent(
+      ax::mojom::Event::kTreeChanged);
 }
 
 void AppsGridView::OnTabletModeChanged(bool started) {
diff --git a/ash/app_list/views/apps_grid_view_unittest.cc b/ash/app_list/views/apps_grid_view_unittest.cc
index 428cb04..b165914d 100644
--- a/ash/app_list/views/apps_grid_view_unittest.cc
+++ b/ash/app_list/views/apps_grid_view_unittest.cc
@@ -28,7 +28,6 @@
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/app_list/views/search_result_tile_item_view.h"
 #include "ash/app_list/views/suggestion_chip_container_view.h"
-#include "ash/app_list/views/suggestions_container_view.h"
 #include "ash/app_list/views/test/apps_grid_view_test_api.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_constants.h"
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 5496994..ed2479bb 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -102,6 +102,7 @@
   title_->SetLineHeight(kTileTextLineHeight);
   title_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
   title_->SetHandlesTooltips(false);
+  title_->SetAllowCharacterBreak(true);
   AddChildView(title_);
 
   if (is_play_store_app_search_enabled_) {
diff --git a/ash/app_list/views/suggestion_chip_container_view.cc b/ash/app_list/views/suggestion_chip_container_view.cc
index 98fcfd6b..371e8d1b 100644
--- a/ash/app_list/views/suggestion_chip_container_view.cc
+++ b/ash/app_list/views/suggestion_chip_container_view.cc
@@ -14,6 +14,7 @@
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_constants.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/focus/focus_manager.h"
 #include "ui/views/layout/box_layout.h"
@@ -128,6 +129,12 @@
     bool disabled) {
   for (auto* chip : suggestion_chip_views_)
     chip->suggestion_chip_view()->SetEnabled(!disabled);
+
+  // Ignore the container view in accessibility tree so that suggestion chips
+  // will not be accessed by ChromeVox.
+  GetViewAccessibility().OverrideIsIgnored(disabled);
+  GetViewAccessibility().NotifyAccessibilityEvent(
+      ax::mojom::Event::kTreeChanged);
 }
 
 void SuggestionChipContainerView::OnTabletModeChanged(bool started) {
diff --git a/ash/app_list/views/suggestions_container_view.cc b/ash/app_list/views/suggestions_container_view.cc
deleted file mode 100644
index c0187cf..0000000
--- a/ash/app_list/views/suggestions_container_view.cc
+++ /dev/null
@@ -1,122 +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 "ash/app_list/views/suggestions_container_view.h"
-
-#include <memory>
-
-#include "ash/app_list/views/app_list_main_view.h"
-#include "ash/app_list/views/contents_view.h"
-#include "ash/app_list/views/search_result_tile_item_view.h"
-#include "ash/public/cpp/app_list/app_list_config.h"
-#include "ash/public/cpp/app_list/app_list_constants.h"
-#include "ash/public/cpp/app_list/app_list_features.h"
-#include "ui/views/background.h"
-#include "ui/views/layout/grid_layout.h"
-
-namespace app_list {
-
-SuggestionsContainerView::SuggestionsContainerView(
-    ContentsView* contents_view,
-    PaginationModel* pagination_model)
-    : contents_view_(contents_view), pagination_model_(pagination_model) {
-  SetPaintToLayer();
-  layer()->SetFillsBoundsOpaquely(false);
-
-  DCHECK(contents_view);
-  view_delegate_ = contents_view_->GetAppListMainView()->view_delegate();
-  SetBackground(views::CreateSolidBackground(kLabelBackgroundColor));
-
-  CreateAppsGrid(kNumStartPageTiles);
-}
-
-SuggestionsContainerView::~SuggestionsContainerView() = default;
-
-int SuggestionsContainerView::DoUpdate() {
-  // Ignore updates and disable buttons when suggestions container view is not
-  // shown.
-  const ash::AppListState state = contents_view_->GetActiveState();
-  if (state != ash::AppListState::kStateStart &&
-      state != ash::AppListState::kStateApps) {
-    for (auto* view : search_result_tile_views_)
-      view->SetEnabled(false);
-
-    return num_results();
-  }
-
-  std::vector<SearchResult*> display_results =
-      SearchModel::FilterSearchResultsByDisplayType(
-          results(), ash::SearchResultDisplayType::kRecommendation,
-          /*excludes=*/{}, kNumStartPageTiles);
-  if (display_results.size() != search_result_tile_views_.size()) {
-    // We should recreate the grid layout in this case.
-    for (size_t i = 0; i < search_result_tile_views_.size(); ++i)
-      delete search_result_tile_views_[i];
-    search_result_tile_views_.clear();
-
-    CreateAppsGrid(display_results.size());
-  }
-
-  // Update the tile item results.
-  for (size_t i = 0; i < search_result_tile_views_.size(); ++i) {
-    DCHECK(i < display_results.size());
-    search_result_tile_views_[i]->SetSearchResult(display_results[i]);
-    search_result_tile_views_[i]->SetEnabled(true);
-
-    // Notify text change after accessible name is updated and the tile view
-    // is re-enabled, so that ChromeVox will announce the updated text.
-    search_result_tile_views_[i]->NotifyAccessibilityEvent(
-        ax::mojom::Event::kTextChanged, true);
-  }
-
-  parent()->Layout();
-  return display_results.size();
-}
-
-void SuggestionsContainerView::NotifyFirstResultYIndex(int /*y_index*/) {
-  NOTREACHED();
-}
-
-int SuggestionsContainerView::GetYSize() {
-  NOTREACHED();
-  return 0;
-}
-
-SearchResultBaseView* SuggestionsContainerView::GetFirstResultView() {
-  return nullptr;
-}
-
-const char* SuggestionsContainerView::GetClassName() const {
-  return "SuggestionsContainerView";
-}
-
-void SuggestionsContainerView::CreateAppsGrid(int apps_num) {
-  DCHECK(search_result_tile_views_.empty());
-  views::GridLayout* tiles_layout_manager =
-      SetLayoutManager(std::make_unique<views::GridLayout>(this));
-
-  views::ColumnSet* column_set = tiles_layout_manager->AddColumnSet(0);
-  for (int col = 0; col < kNumStartPageTiles; ++col) {
-    column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
-                          views::GridLayout::USE_PREF, 0, 0);
-    column_set->AddPaddingColumn(0,
-                                 AppListConfig::instance().grid_tile_spacing());
-  }
-
-  // Add SearchResultTileItemViews to the container.
-  int i = 0;
-  search_result_tile_views_.reserve(apps_num);
-  tiles_layout_manager->StartRow(0, 0);
-  DCHECK_LE(apps_num, kNumStartPageTiles);
-  for (; i < apps_num; ++i) {
-    SearchResultTileItemView* tile_item = new SearchResultTileItemView(
-        view_delegate_, pagination_model_, true /* show_in_apps_page */);
-    tiles_layout_manager->AddView(tile_item);
-    AddChildView(tile_item);
-    tile_item->SetParentBackgroundColor(kLabelBackgroundColor);
-    search_result_tile_views_.emplace_back(tile_item);
-  }
-}
-
-}  // namespace app_list
diff --git a/ash/app_list/views/suggestions_container_view.h b/ash/app_list/views/suggestions_container_view.h
deleted file mode 100644
index 2da5e24..0000000
--- a/ash/app_list/views/suggestions_container_view.h
+++ /dev/null
@@ -1,54 +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 ASH_APP_LIST_VIEWS_SUGGESTIONS_CONTAINER_VIEW_H_
-#define ASH_APP_LIST_VIEWS_SUGGESTIONS_CONTAINER_VIEW_H_
-
-#include <vector>
-
-#include "ash/app_list/views/search_result_container_view.h"
-#include "base/macros.h"
-
-namespace app_list {
-
-class AppListViewDelegate;
-class ContentsView;
-class PaginationModel;
-class SearchResultTileItemView;
-
-// A container that holds the suggested app tiles. If fullscreen app list is not
-// enabled, it also holds the all apps button.
-class SuggestionsContainerView : public SearchResultContainerView {
- public:
-  SuggestionsContainerView(ContentsView* contents_view,
-                           PaginationModel* pagination_model);
-  ~SuggestionsContainerView() override;
-
-  const std::vector<SearchResultTileItemView*>& tile_views() const {
-    return search_result_tile_views_;
-  }
-
-  // Overridden from SearchResultContainerView:
-  int DoUpdate() override;
-  void NotifyFirstResultYIndex(int y_index) override;
-  int GetYSize() override;
-  SearchResultBaseView* GetFirstResultView() override;
-  const char* GetClassName() const override;
-
- private:
-  void CreateAppsGrid(int apps_num);
-
-  ContentsView* contents_view_ = nullptr;
-  AppListViewDelegate* view_delegate_ = nullptr;
-
-  std::vector<SearchResultTileItemView*> search_result_tile_views_;
-
-  PaginationModel* const pagination_model_;  // Owned by AppsGridView.
-
-  DISALLOW_COPY_AND_ASSIGN(SuggestionsContainerView);
-};
-
-}  // namespace app_list
-
-#endif  // ASH_APP_LIST_VIEWS_SUGGESTIONS_CONTAINER_VIEW_H_
diff --git a/ash/events/switch_access_event_handler.cc b/ash/events/switch_access_event_handler.cc
index 2102618..121c92c 100644
--- a/ash/events/switch_access_event_handler.cc
+++ b/ash/events/switch_access_event_handler.cc
@@ -27,7 +27,7 @@
 
 SwitchAccessEventHandler::SwitchAccessEventHandler(
     mojom::SwitchAccessEventHandlerDelegatePtr delegate_ptr)
-    : delegate_ptr_(std::move(delegate_ptr)), ignore_virtual_key_events_(true) {
+    : delegate_ptr_(std::move(delegate_ptr)) {
   DCHECK(delegate_ptr_.is_bound());
   Shell::Get()->AddPreTargetHandler(this,
                                     ui::EventTarget::Priority::kAccessibility);
@@ -45,15 +45,22 @@
   DCHECK(IsSwitchAccessEnabled());
   DCHECK(event);
 
-  // Ignore virtual key events so users can type with the onscreen keyboard.
-  if (ignore_virtual_key_events_ && !event->HasNativeEvent())
-    return;
-
-  ui::KeyboardCode key_code = event->key_code();
-  if (keys_to_capture_.find(key_code) != keys_to_capture_.end()) {
+  if (ShouldForwardEvent(*event)) {
     CancelEvent(event);
     delegate_ptr_->DispatchKeyEvent(ui::Event::Clone(*event));
   }
 }
 
+bool SwitchAccessEventHandler::ShouldForwardEvent(
+    const ui::KeyEvent& event) const {
+  // Ignore virtual key events so users can type with the onscreen keyboard.
+  if (ignore_virtual_key_events_ && !event.HasNativeEvent())
+    return false;
+
+  if (forward_key_events_)
+    return true;
+
+  return keys_to_capture_.find(event.key_code()) != keys_to_capture_.end();
+}
+
 }  // namespace ash
diff --git a/ash/events/switch_access_event_handler.h b/ash/events/switch_access_event_handler.h
index bed2b40..035473f 100644
--- a/ash/events/switch_access_event_handler.h
+++ b/ash/events/switch_access_event_handler.h
@@ -30,6 +30,12 @@
     ignore_virtual_key_events_ = should_ignore;
   }
 
+  // Tells the handler whether to forward all incoming key events to the Switch
+  // Access extension.
+  void set_forward_key_events(bool should_forward) {
+    forward_key_events_ = should_forward;
+  }
+
   // For testing usage only.
   void FlushMojoForTest();
 
@@ -37,11 +43,14 @@
   // ui::EventHandler:
   void OnKeyEvent(ui::KeyEvent* event) override;
 
+  bool ShouldForwardEvent(const ui::KeyEvent& event) const;
+
   // The delegate used to send key events to the Switch Access extension.
   mojom::SwitchAccessEventHandlerDelegatePtr delegate_ptr_;
 
   std::set<int> keys_to_capture_;
-  bool ignore_virtual_key_events_;
+  bool forward_key_events_ = false;
+  bool ignore_virtual_key_events_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(SwitchAccessEventHandler);
 };
diff --git a/ash/events/switch_access_event_handler_unittest.cc b/ash/events/switch_access_event_handler_unittest.cc
index 2d48bfb6..1788a35 100644
--- a/ash/events/switch_access_event_handler_unittest.cc
+++ b/ash/events/switch_access_event_handler_unittest.cc
@@ -205,5 +205,43 @@
   EXPECT_FALSE(event_capturer_.last_key_event()->handled());
 }
 
+TEST_F(SwitchAccessEventHandlerTest, ForwardKeyEvents) {
+  Shell::Get()->accessibility_controller()->SetSwitchAccessKeysToCapture(
+      {ui::VKEY_1, ui::VKEY_2, ui::VKEY_3});
+
+  EXPECT_FALSE(event_capturer_.last_key_event());
+
+  // Tell the Switch Access Event Handler to forward key events.
+  Shell::Get()->accessibility_controller()->ForwardKeyEventsToSwitchAccess(
+      true);
+
+  // Press the "T" key.
+  generator_->PressKey(ui::VKEY_T, ui::EF_NONE);
+
+  // The event should be handled by SwitchAccessEventHandler.
+  EXPECT_FALSE(event_capturer_.last_key_event());
+  EXPECT_TRUE(GetDelegate()->last_key_event());
+
+  // Release the "T" key.
+  generator_->ReleaseKey(ui::VKEY_T, ui::EF_NONE);
+
+  // The event should be handled by SwitchAccessEventHandler.
+  EXPECT_FALSE(event_capturer_.last_key_event());
+  EXPECT_TRUE(GetDelegate()->last_key_event());
+
+  // Tell the Switch Access Event Handler to stop forwarding key events.
+  Shell::Get()->accessibility_controller()->ForwardKeyEventsToSwitchAccess(
+      false);
+
+  // Press the "T" key.
+  generator_->PressKey(ui::VKEY_T, ui::EF_NONE);
+
+  // The release event is not handled by SwitchAccessEventHandler.
+  EXPECT_TRUE(event_capturer_.last_key_event());
+
+  // Release the "T" key.
+  generator_->ReleaseKey(ui::VKEY_T, ui::EF_NONE);
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/public/interfaces/accessibility_controller.mojom b/ash/public/interfaces/accessibility_controller.mojom
index b969db7..c26e1eed 100644
--- a/ash/public/interfaces/accessibility_controller.mojom
+++ b/ash/public/interfaces/accessibility_controller.mojom
@@ -169,6 +169,10 @@
 
   // Starts or stops dictation. Records metrics for toggling via SwitchAccess.
   ToggleDictationFromSource(DictationToggleSource source);
+
+  // Tells the Switch Access Event Handler whether to forward all key events to
+  // the Switch Access extension.
+  ForwardKeyEventsToSwitchAccess(bool should_forward);
 };
 
 // Interface for ash to request accessibility service from its client (e.g.
diff --git a/base/android/jni_generator/golden/testInnerClassNativesBothInnerAndOuterRegistrations.golden b/base/android/jni_generator/golden/testInnerClassNativesBothInnerAndOuterRegistrations.golden
index 04d7680..0034740 100644
--- a/base/android/jni_generator/golden/testInnerClassNativesBothInnerAndOuterRegistrations.golden
+++ b/base/android/jni_generator/golden/testInnerClassNativesBothInnerAndOuterRegistrations.golden
@@ -14,6 +14,7 @@
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 #include "base/android/jni_int_wrapper.h"
+#include "base/stl_util.h"  // For base::size().
 
 
 // Step 1: Forward declarations (classes).
@@ -64,7 +65,7 @@
 
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_TestJni(JNIEnv* env) {
   const int kMethods_org_chromium_TestJniSize =
-      arraysize(kMethods_org_chromium_TestJni);
+      base::size(kMethods_org_chromium_TestJni);
   if (env->RegisterNatives(
       org_chromium_TestJni_clazz(env),
       kMethods_org_chromium_TestJni,
@@ -77,7 +78,7 @@
 
 
   const int kMethods_org_chromium_TestJni_00024MyOtherInnerClassSize =
-      arraysize(kMethods_org_chromium_TestJni_00024MyOtherInnerClass);
+      base::size(kMethods_org_chromium_TestJni_00024MyOtherInnerClass);
   if (env->RegisterNatives(
       org_chromium_TestJni_00024MyOtherInnerClass_clazz(env),
       kMethods_org_chromium_TestJni_00024MyOtherInnerClass,
diff --git a/base/android/jni_generator/golden/testNativesRegistrations.golden b/base/android/jni_generator/golden/testNativesRegistrations.golden
index b3b99745..3f1d8e2 100644
--- a/base/android/jni_generator/golden/testNativesRegistrations.golden
+++ b/base/android/jni_generator/golden/testNativesRegistrations.golden
@@ -14,6 +14,7 @@
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 #include "base/android/jni_int_wrapper.h"
+#include "base/stl_util.h"  // For base::size().
 
 
 // Step 1: Forward declarations (classes).
@@ -143,7 +144,7 @@
 
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_TestJni(JNIEnv* env) {
   const int kMethods_org_chromium_TestJniSize =
-      arraysize(kMethods_org_chromium_TestJni);
+      base::size(kMethods_org_chromium_TestJni);
   if (env->RegisterNatives(
       org_chromium_TestJni_clazz(env),
       kMethods_org_chromium_TestJni,
diff --git a/base/android/jni_generator/golden/testProxyNativesMainDex.golden b/base/android/jni_generator/golden/testProxyNativesMainDex.golden
index f093270..eaa03c2 100644
--- a/base/android/jni_generator/golden/testProxyNativesMainDex.golden
+++ b/base/android/jni_generator/golden/testProxyNativesMainDex.golden
@@ -14,6 +14,7 @@
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 #include "base/android/jni_int_wrapper.h"
+#include "base/stl_util.h"  // For base::size().
 
 
 // Step 1: Forward declarations (classes).
@@ -37,7 +38,7 @@
 };
 
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNIMAIN_DEX(JNIEnv* env) {
-  const int number_of_methods = arraysize(kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX);
+  const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX);
 
   base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
diff --git a/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden b/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden
index 6b909a7..e917fc9 100644
--- a/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden
+++ b/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden
@@ -14,6 +14,7 @@
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 #include "base/android/jni_int_wrapper.h"
+#include "base/stl_util.h"  // For base::size().
 
 
 // Step 1: Forward declarations (classes).
@@ -44,7 +45,7 @@
 };
 
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNI(JNIEnv* env) {
-  const int number_of_methods = arraysize(kMethods_org_chromium_base_natives_GEN_1JNI);
+  const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNI);
 
   base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
@@ -67,7 +68,7 @@
 };
 
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNIMAIN_DEX(JNIEnv* env) {
-  const int number_of_methods = arraysize(kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX);
+  const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX);
 
   base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
diff --git a/base/android/jni_generator/golden/testProxyNativesRegistrations.golden b/base/android/jni_generator/golden/testProxyNativesRegistrations.golden
index 13b5cc2..d43101b 100644
--- a/base/android/jni_generator/golden/testProxyNativesRegistrations.golden
+++ b/base/android/jni_generator/golden/testProxyNativesRegistrations.golden
@@ -14,6 +14,7 @@
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 #include "base/android/jni_int_wrapper.h"
+#include "base/stl_util.h"  // For base::size().
 
 
 // Step 1: Forward declarations (classes).
@@ -57,7 +58,7 @@
 };
 
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNI(JNIEnv* env) {
-  const int number_of_methods = arraysize(kMethods_org_chromium_base_natives_GEN_1JNI);
+  const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNI);
 
   base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
diff --git a/base/android/jni_generator/jni_registration_generator.py b/base/android/jni_generator/jni_registration_generator.py
index 1499baf..1a6ae3b 100755
--- a/base/android/jni_generator/jni_registration_generator.py
+++ b/base/android/jni_generator/jni_registration_generator.py
@@ -124,7 +124,7 @@
 };
 
 JNI_REGISTRATION_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) {
-  const int number_of_methods = arraysize(kMethods_${ESCAPED_PROXY_CLASS});
+  const int number_of_methods = base::size(kMethods_${ESCAPED_PROXY_CLASS});
 
   base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "${PROXY_CLASS}");
   if (env->RegisterNatives(
@@ -238,6 +238,7 @@
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 #include "base/android/jni_int_wrapper.h"
+#include "base/stl_util.h"  // For base::size().
 
 
 // Step 1: Forward declarations (classes).
@@ -482,7 +483,7 @@
     """Returns the shared implementation for RegisterNatives."""
     template = string.Template("""\
   const int kMethods_${JAVA_CLASS}Size =
-      arraysize(${NAMESPACE}kMethods_${JAVA_CLASS});
+      base::size(${NAMESPACE}kMethods_${JAVA_CLASS});
   if (env->RegisterNatives(
       ${JAVA_CLASS}_clazz(env),
       ${NAMESPACE}kMethods_${JAVA_CLASS},
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
index b81c261..f0031c3 100644
--- a/base/task/sequence_manager/task_queue_impl.cc
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -89,6 +89,7 @@
       immediate_work_queue(new WorkQueue(task_queue,
                                          "immediate",
                                          WorkQueue::QueueType::kImmediate)),
+      set_index(0),
       is_enabled_refcount(0),
       voter_refcount(0),
       blame_context(nullptr),
diff --git a/base/task/sequence_manager/task_queue_impl.h b/base/task/sequence_manager/task_queue_impl.h
index 873fec0..90dcac1 100644
--- a/base/task/sequence_manager/task_queue_impl.h
+++ b/base/task/sequence_manager/task_queue_impl.h
@@ -322,6 +322,7 @@
     std::unique_ptr<WorkQueue> immediate_work_queue;
     DelayedIncomingQueue delayed_incoming_queue;
     ObserverList<MessageLoop::TaskObserver>::Unchecked task_observers;
+    size_t set_index;
     base::internal::HeapHandle heap_handle;
     int is_enabled_refcount;
     int voter_refcount;
diff --git a/base/task/sequence_manager/task_queue_selector.cc b/base/task/sequence_manager/task_queue_selector.cc
index 6583cdd..5f301b0 100644
--- a/base/task/sequence_manager/task_queue_selector.cc
+++ b/base/task/sequence_manager/task_queue_selector.cc
@@ -17,13 +17,11 @@
 namespace sequence_manager {
 namespace internal {
 
-constexpr const int64_t TaskQueueSelector::per_priority_starvation_tolerance_[];
-
 TaskQueueSelector::TaskQueueSelector(
     scoped_refptr<AssociatedThreadId> associated_thread)
     : associated_thread_(std::move(associated_thread)),
-      delayed_work_queue_sets_("delayed", this),
-      immediate_work_queue_sets_("immediate", this) {}
+      delayed_work_queue_sets_("delayed"),
+      immediate_work_queue_sets_("immediate") {}
 
 TaskQueueSelector::~TaskQueueSelector() = default;
 
@@ -113,39 +111,6 @@
 #endif
 }
 
-int64_t TaskQueueSelector::GetSortKeyForPriorty(size_t set_index) const {
-  switch (set_index) {
-    case TaskQueue::kControlPriority:
-      return std::numeric_limits<int64_t>::min();
-
-    case TaskQueue::kBestEffortPriority:
-      return std::numeric_limits<int64_t>::max();
-
-    default:
-      return selection_count_ + per_priority_starvation_tolerance_[set_index];
-  }
-}
-
-void TaskQueueSelector::WorkQueueSetBecameEmpty(size_t set_index) {
-  non_empty_set_counts_[set_index]--;
-  DCHECK_GE(non_empty_set_counts_[set_index], 0);
-
-  // There are no delayed or immediate tasks for |set_index| so remove from
-  // |active_priorities_|.
-  if (non_empty_set_counts_[set_index] == 0)
-    active_priorities_.erase(set_index);
-}
-
-void TaskQueueSelector::WorkQueueSetBecameNonEmpty(size_t set_index) {
-  non_empty_set_counts_[set_index]++;
-  DCHECK_LE(non_empty_set_counts_[set_index], 2);
-
-  // There is now a delayed or an immediate task for |set_index|, so add to
-  // |active_priorities_|.
-  if (non_empty_set_counts_[set_index] == 1)
-    active_priorities_.insert(GetSortKeyForPriorty(set_index), set_index);
-}
-
 WorkQueue* TaskQueueSelector::ChooseOldestImmediateTaskWithPriority(
     TaskQueue::QueuePriority priority) const {
   return immediate_work_queue_sets_.GetOldestQueueInSet(priority);
@@ -196,6 +161,58 @@
       priority, out_chose_delayed_over_immediate);
 }
 
+WorkQueue* TaskQueueSelector::SelectWorkQueueToServiceImpl(
+    TaskQueue::QueuePriority max_priority,
+    bool* out_chose_delayed_over_immediate) {
+  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
+  DCHECK_EQ(*out_chose_delayed_over_immediate, false);
+
+  // Always service the control queue if it has any work.
+  if (max_priority > TaskQueue::kControlPriority) {
+    WorkQueue* queue = ChooseOldestWithPriority(
+        TaskQueue::kControlPriority, out_chose_delayed_over_immediate);
+    if (queue)
+      return queue;
+  }
+
+  // Select from the low priority queue if we are starving it.
+  if (max_priority > TaskQueue::kLowPriority &&
+      low_priority_starvation_score_ >= kMaxLowPriorityStarvationScore) {
+    WorkQueue* queue = ChooseOldestWithPriority(
+        TaskQueue::kLowPriority, out_chose_delayed_over_immediate);
+    if (queue)
+      return queue;
+  }
+
+  // Select from the normal priority queue if we are starving it.
+  if (max_priority > TaskQueue::kNormalPriority &&
+      normal_priority_starvation_score_ >= kMaxNormalPriorityStarvationScore) {
+    WorkQueue* queue = ChooseOldestWithPriority(
+        TaskQueue::kNormalPriority, out_chose_delayed_over_immediate);
+    if (queue)
+      return queue;
+  }
+
+  // Select from the high priority queue if we are starving it.
+  if (max_priority > TaskQueue::kHighPriority &&
+      high_priority_starvation_score_ >= kMaxHighPriorityStarvationScore) {
+    WorkQueue* queue = ChooseOldestWithPriority(
+        TaskQueue::kHighPriority, out_chose_delayed_over_immediate);
+    if (queue)
+      return queue;
+  }
+
+  // Otherwise choose in priority order.
+  for (TaskQueue::QueuePriority priority = TaskQueue::kHighestPriority;
+       priority < max_priority; priority = NextPriority(priority)) {
+    WorkQueue* queue =
+        ChooseOldestWithPriority(priority, out_chose_delayed_over_immediate);
+    if (queue)
+      return queue;
+  }
+  return nullptr;
+}
+
 #if DCHECK_IS_ON() || !defined(NDEBUG)
 bool TaskQueueSelector::CheckContainsQueueForTest(
     const internal::TaskQueueImpl* queue) const {
@@ -214,36 +231,81 @@
 
 WorkQueue* TaskQueueSelector::SelectWorkQueueToService() {
   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
-
-  if (active_priorities_.empty())
-    return nullptr;
-
-  selection_count_++;
-
-  // Select the priority from which we will select a task. Usually this is
-  // the highest priority for which we have work, unless we are starving a lower
-  // priority.
-  size_t set_index = active_priorities_.min_id();
   bool chose_delayed_over_immediate = false;
-  WorkQueue* queue =
-      ChooseOldestWithPriority(static_cast<TaskQueue::QueuePriority>(set_index),
-                               &chose_delayed_over_immediate);
+  WorkQueue* queue = SelectWorkQueueToServiceImpl(
+      TaskQueue::kQueuePriorityCount, &chose_delayed_over_immediate);
+  if (queue) {
+    // We could use |(*out_work_queue)->task_queue()->GetQueuePriority()| here
+    // but for re-queued non-nestable tasks |task_queue()| returns null.
+    DidSelectQueueWithPriority(
+        static_cast<TaskQueue::QueuePriority>(queue->work_queue_set_index()),
+        chose_delayed_over_immediate);
+  }
+  return queue;
+}
 
-  // If we still have any tasks remaining for |set_index| then adjust it's
-  // sort key.
-  if (active_priorities_.IsInQueue(set_index))
-    active_priorities_.ChangeMinKey(GetSortKeyForPriorty(set_index));
-
+void TaskQueueSelector::DidSelectQueueWithPriority(
+    TaskQueue::QueuePriority priority,
+    bool chose_delayed_over_immediate) {
+  switch (priority) {
+    case TaskQueue::kControlPriority:
+      break;
+    case TaskQueue::kHighestPriority:
+      low_priority_starvation_score_ +=
+          HasTasksWithPriority(TaskQueue::kLowPriority)
+              ? kSmallScoreIncrementForLowPriorityStarvation
+              : 0;
+      normal_priority_starvation_score_ +=
+          HasTasksWithPriority(TaskQueue::kNormalPriority)
+              ? kSmallScoreIncrementForNormalPriorityStarvation
+              : 0;
+      high_priority_starvation_score_ +=
+          HasTasksWithPriority(TaskQueue::kHighPriority)
+              ? kSmallScoreIncrementForHighPriorityStarvation
+              : 0;
+      break;
+    case TaskQueue::kHighPriority:
+      low_priority_starvation_score_ +=
+          HasTasksWithPriority(TaskQueue::kLowPriority)
+              ? kLargeScoreIncrementForLowPriorityStarvation
+              : 0;
+      normal_priority_starvation_score_ +=
+          HasTasksWithPriority(TaskQueue::kNormalPriority)
+              ? kLargeScoreIncrementForNormalPriorityStarvation
+              : 0;
+      high_priority_starvation_score_ = 0;
+      break;
+    case TaskQueue::kNormalPriority:
+      low_priority_starvation_score_ +=
+          HasTasksWithPriority(TaskQueue::kLowPriority)
+              ? kLargeScoreIncrementForLowPriorityStarvation
+              : 0;
+      normal_priority_starvation_score_ = 0;
+      break;
+    case TaskQueue::kLowPriority:
+    case TaskQueue::kBestEffortPriority:
+      low_priority_starvation_score_ = 0;
+      high_priority_starvation_score_ = 0;
+      normal_priority_starvation_score_ = 0;
+      break;
+    default:
+      NOTREACHED();
+  }
   if (chose_delayed_over_immediate) {
     immediate_starvation_count_++;
   } else {
     immediate_starvation_count_ = 0;
   }
-  return queue;
 }
 
 void TaskQueueSelector::AsValueInto(trace_event::TracedValue* state) const {
   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
+  state->SetInteger("high_priority_starvation_score",
+                    high_priority_starvation_score_);
+  state->SetInteger("normal_priority_starvation_score",
+                    normal_priority_starvation_score_);
+  state->SetInteger("low_priority_starvation_score",
+                    low_priority_starvation_score_);
   state->SetInteger("immediate_starvation_count", immediate_starvation_count_);
 }
 
@@ -253,7 +315,15 @@
 
 bool TaskQueueSelector::AllEnabledWorkQueuesAreEmpty() const {
   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
-  return active_priorities_.empty();
+  for (TaskQueue::QueuePriority priority = TaskQueue::kControlPriority;
+       priority < TaskQueue::kQueuePriorityCount;
+       priority = NextPriority(priority)) {
+    if (!delayed_work_queue_sets_.IsSetEmpty(priority) ||
+        !immediate_work_queue_sets_.IsSetEmpty(priority)) {
+      return false;
+    }
+  }
+  return true;
 }
 
 void TaskQueueSelector::SetImmediateStarvationCountForTest(
@@ -267,62 +337,6 @@
          !immediate_work_queue_sets_.IsSetEmpty(priority);
 }
 
-TaskQueueSelector::SmallPriorityQueue::SmallPriorityQueue() {
-  for (size_t i = 0; i < TaskQueue::kQueuePriorityCount; i++) {
-    id_to_index_[i] = kInvalidIndex;
-  }
-}
-
-void TaskQueueSelector::SmallPriorityQueue::insert(int64_t key, uint8_t id) {
-  DCHECK_LE(size_, TaskQueue::kQueuePriorityCount);
-  DCHECK_LT(id, TaskQueue::kQueuePriorityCount);
-  DCHECK(!IsInQueue(id));
-  // Insert while keeping |keys_| sorted.
-  size_t i = size_;
-  while (i > 0 && key < keys_[i - 1]) {
-    keys_[i] = keys_[i - 1];
-    uint8_t moved_id = index_to_id_[i - 1];
-    index_to_id_[i] = moved_id;
-    id_to_index_[moved_id] = i;
-    i--;
-  }
-  keys_[i] = key;
-  index_to_id_[i] = id;
-  id_to_index_[id] = i;
-  size_++;
-}
-
-void TaskQueueSelector::SmallPriorityQueue::erase(uint8_t id) {
-  DCHECK_NE(size_, 0u);
-  DCHECK_LT(id, TaskQueue::kQueuePriorityCount);
-  DCHECK(IsInQueue(id));
-  // Erase while keeping |keys_| sorted.
-  size_--;
-  for (size_t i = id_to_index_[id]; i < size_; i++) {
-    keys_[i] = keys_[i + 1];
-    uint8_t moved_id = index_to_id_[i + 1];
-    index_to_id_[i] = moved_id;
-    id_to_index_[moved_id] = i;
-  }
-  id_to_index_[id] = kInvalidIndex;
-}
-
-void TaskQueueSelector::SmallPriorityQueue::ChangeMinKey(int64_t new_key) {
-  DCHECK_NE(size_, 0u);
-  uint8_t id = index_to_id_[0];
-  size_t i = 0;
-  while ((i + 1) < size_ && keys_[i + 1] < new_key) {
-    keys_[i] = keys_[i + 1];
-    uint8_t moved_id = index_to_id_[i + 1];
-    index_to_id_[i] = moved_id;
-    id_to_index_[moved_id] = i;
-    i++;
-  }
-  keys_[i] = new_key;
-  index_to_id_[i] = id;
-  id_to_index_[id] = i;
-}
-
 }  // namespace internal
 }  // namespace sequence_manager
 }  // namespace base
diff --git a/base/task/sequence_manager/task_queue_selector.h b/base/task/sequence_manager/task_queue_selector.h
index b7ab2b6..153492f 100644
--- a/base/task/sequence_manager/task_queue_selector.h
+++ b/base/task/sequence_manager/task_queue_selector.h
@@ -21,11 +21,11 @@
 
 // TaskQueueSelector is used by the SchedulerHelper to enable prioritization
 // of particular task queues.
-class BASE_EXPORT TaskQueueSelector : public WorkQueueSets::Observer {
+class BASE_EXPORT TaskQueueSelector {
  public:
   explicit TaskQueueSelector(
       scoped_refptr<AssociatedThreadId> associated_thread);
-  ~TaskQueueSelector() override;
+  ~TaskQueueSelector();
 
   // Called to register a queue that can be selected. This function is called
   // on the main thread.
@@ -70,10 +70,6 @@
   // otherwise.
   bool AllEnabledWorkQueuesAreEmpty() const;
 
-  // WorkQueueSets::Observer implementation:
-  void WorkQueueSetBecameEmpty(size_t set_index) override;
-  void WorkQueueSetBecameNonEmpty(size_t set_index) override;
-
  protected:
   WorkQueue* ChooseOldestWithPriority(
       TaskQueue::QueuePriority priority,
@@ -97,50 +93,48 @@
   // the presence of highest priority tasks.
   static const size_t kMaxHighPriorityStarvationScore = 3;
 
+  // Increment to be applied to the high priority starvation score when a task
+  // should have only a small effect on the score. E.g. A number of highest
+  // priority tasks must run before the high priority queue is considered
+  // starved.
+  static const size_t kSmallScoreIncrementForHighPriorityStarvation = 1;
+
   // Maximum score to accumulate before normal priority tasks are run even in
   // the presence of higher priority tasks i.e. highest and high priority tasks.
   static const size_t kMaxNormalPriorityStarvationScore = 5;
 
+  // Increment to be applied to the normal priority starvation score when a task
+  // should have a large effect on the score. E.g Only a few high priority
+  // priority tasks must run before the normal priority queue is considered
+  // starved.
+  static const size_t kLargeScoreIncrementForNormalPriorityStarvation = 2;
+
+  // Increment to be applied to the normal priority starvation score when a task
+  // should have only a small effect on the score. E.g. A number of highest
+  // priority tasks must run before the normal priority queue is considered
+  // starved.
+  static const size_t kSmallScoreIncrementForNormalPriorityStarvation = 1;
+
   // Maximum score to accumulate before low priority tasks are run even in the
   // presence of highest, high, or normal priority tasks.
   static const size_t kMaxLowPriorityStarvationScore = 25;
 
+  // Increment to be applied to the low priority starvation score when a task
+  // should have a large effect on the score. E.g. Only a few normal/high
+  // priority tasks must run before the low priority queue is considered
+  // starved.
+  static const size_t kLargeScoreIncrementForLowPriorityStarvation = 5;
+
+  // Increment to be applied to the low priority starvation score when a task
+  // should have only a small effect on the score. E.g. A lot of highest
+  // priority tasks must run before the low priority queue is considered
+  // starved.
+  static const size_t kSmallScoreIncrementForLowPriorityStarvation = 1;
+
   // Maximum number of delayed tasks tasks which can be run while there's a
   // waiting non-delayed task.
   static const size_t kMaxDelayedStarvationTasks = 3;
 
-  // Because there are only a handful of priorities, we can get away with using
-  // a very simple priority queue. This queue has a stable sorting order.
-  // Note IDs must be in the range [0..TaskQueue::kQueuePriorityCount)
-  class BASE_EXPORT SmallPriorityQueue {
-   public:
-    SmallPriorityQueue();
-
-    bool empty() const { return size_ == 0; }
-
-    int min_id() const { return index_to_id_[0]; };
-
-    void insert(int64_t key, uint8_t id);
-
-    void erase(uint8_t id);
-
-    void ChangeMinKey(int64_t new_key);
-
-    bool IsInQueue(uint8_t id) const {
-      return id_to_index_[id] != kInvalidIndex;
-    }
-
-   private:
-    static constexpr uint8_t kInvalidIndex = 255;
-
-    size_t size_ = 0;
-
-    // These are sorted in ascending order.
-    int64_t keys_[TaskQueue::kQueuePriorityCount];
-    uint8_t id_to_index_[TaskQueue::kQueuePriorityCount];
-    uint8_t index_to_id_[TaskQueue::kQueuePriorityCount];
-  };
-
  private:
   void ChangeSetIndex(internal::TaskQueueImpl* queue,
                       TaskQueue::QueuePriority priority);
@@ -148,6 +142,10 @@
                     TaskQueue::QueuePriority priority);
   void RemoveQueueImpl(internal::TaskQueueImpl* queue);
 
+  WorkQueue* SelectWorkQueueToServiceImpl(
+      TaskQueue::QueuePriority max_priority,
+      bool* out_chose_delayed_over_immediate);
+
 #if DCHECK_IS_ON() || !defined(NDEBUG)
   bool CheckContainsQueueForTest(const internal::TaskQueueImpl* queue) const;
 #endif
@@ -166,61 +164,22 @@
   static TaskQueue::QueuePriority NextPriority(
       TaskQueue::QueuePriority priority);
 
+  // Called whenever the selector chooses a task queue for execution with the
+  // priority |priority|.
+  void DidSelectQueueWithPriority(TaskQueue::QueuePriority priority,
+                                  bool chose_delayed_over_immediate);
+
   // Returns true if there are pending tasks with priority |priority|.
   bool HasTasksWithPriority(TaskQueue::QueuePriority priority);
 
   scoped_refptr<AssociatedThreadId> associated_thread_;
 
-  // Count of the number of sets (delayed or immediate) for each priority.
-  // Should only contain 0, 1 or 2.
-  std::array<int, TaskQueue::kQueuePriorityCount> non_empty_set_counts_ = {{0}};
-
-  // The Priority sort key is adjusted based on these values. The idea being the
-  // larger the adjustment, the more the queue can be starved before being
-  // selected. The kControlPriority queues should run immediately so it always
-  // has the lowest possible value. Conversely kBestEffortPriority queues should
-  // only run if there's nothing else to do so they always have the highest
-  // possible value.
-  static constexpr const int64_t
-      per_priority_starvation_tolerance_[TaskQueue::kQueuePriorityCount] = {
-          // kControlPriority (unused)
-          std::numeric_limits<int64_t>::min(),
-
-          // kHighestPriority
-          0,
-
-          // kHighPriority
-          TaskQueueSelector::per_priority_starvation_tolerance_[1] +
-              kMaxHighPriorityStarvationScore,
-
-          // kNormalPriority
-          TaskQueueSelector::per_priority_starvation_tolerance_[2] +
-              kMaxNormalPriorityStarvationScore,
-
-          // kLowPriority
-          TaskQueueSelector::per_priority_starvation_tolerance_[3] +
-              kMaxLowPriorityStarvationScore,
-
-          // kBestEffortPriority (unused)
-          std::numeric_limits<int64_t>::max()};
-
-  int64_t GetSortKeyForPriorty(size_t set_index) const;
-
-  // Min priority queue of priorities, which is used to work out which priority
-  // to run next.
-  SmallPriorityQueue active_priorities_;
-
-  // Each time we select a queue this is incremented. This forms the basis of
-  // the |active_priorities_| sort key. I.e. when a priority becomes selectable
-  // it's inserted into |active_priorities_| with a sort key of
-  // |selection_count_| plus an adjustment from
-  // |per_priority_starvation_tolerance_|. In theory this could wrap around and
-  // start misbehaving but in typical usage that would take a great many years.
-  int64_t selection_count_ = 0;
-
   WorkQueueSets delayed_work_queue_sets_;
   WorkQueueSets immediate_work_queue_sets_;
   size_t immediate_starvation_count_ = 0;
+  size_t high_priority_starvation_score_ = 0;
+  size_t normal_priority_starvation_score_ = 0;
+  size_t low_priority_starvation_score_ = 0;
 
   Observer* task_queue_selector_observer_ = nullptr;  // Not owned.
   DISALLOW_COPY_AND_ASSIGN(TaskQueueSelector);
diff --git a/base/task/sequence_manager/task_queue_selector_unittest.cc b/base/task/sequence_manager/task_queue_selector_unittest.cc
index c1e634c..fa6d02c 100644
--- a/base/task/sequence_manager/task_queue_selector_unittest.cc
+++ b/base/task/sequence_manager/task_queue_selector_unittest.cc
@@ -49,14 +49,50 @@
   using TaskQueueSelector::ChooseOldestWithPriority;
   using TaskQueueSelector::delayed_work_queue_sets;
   using TaskQueueSelector::immediate_work_queue_sets;
-  using TaskQueueSelector::kMaxHighPriorityStarvationScore;
-  using TaskQueueSelector::kMaxLowPriorityStarvationScore;
-  using TaskQueueSelector::kMaxNormalPriorityStarvationScore;
   using TaskQueueSelector::SetImmediateStarvationCountForTest;
-  using TaskQueueSelector::SmallPriorityQueue;
 
   TaskQueueSelectorForTest(scoped_refptr<AssociatedThreadId> associated_thread)
       : TaskQueueSelector(associated_thread) {}
+
+  // Returns the number of highest priority tasks needed to starve high priority
+  // task.
+  static constexpr size_t NumberOfHighestPriorityToStarveHighPriority() {
+    return (kMaxHighPriorityStarvationScore +
+            kSmallScoreIncrementForHighPriorityStarvation - 1) /
+           kSmallScoreIncrementForHighPriorityStarvation;
+  }
+
+  // Returns the number of highest priority tasks needed to starve normal
+  // priority tasks.
+  static constexpr size_t NumberOfHighestPriorityToStarveNormalPriority() {
+    return (kMaxNormalPriorityStarvationScore +
+            kSmallScoreIncrementForNormalPriorityStarvation - 1) /
+           kSmallScoreIncrementForNormalPriorityStarvation;
+  }
+
+  // Returns the number of high priority tasks needed to starve normal priority
+  // tasks.
+  static constexpr size_t NumberOfHighPriorityToStarveNormalPriority() {
+    return (kMaxNormalPriorityStarvationScore +
+            kLargeScoreIncrementForNormalPriorityStarvation - 1) /
+           kLargeScoreIncrementForNormalPriorityStarvation;
+  }
+
+  // Returns the number of highest priority tasks needed to starve low priority
+  // ones.
+  static constexpr size_t NumberOfHighestPriorityToStarveLowPriority() {
+    return (kMaxLowPriorityStarvationScore +
+            kSmallScoreIncrementForLowPriorityStarvation - 1) /
+           kSmallScoreIncrementForLowPriorityStarvation;
+  }
+
+  // Returns the number of high/normal priority tasks needed to starve low
+  // priority ones.
+  static constexpr size_t NumberOfHighAndNormalPriorityToStarveLowPriority() {
+    return (kMaxLowPriorityStarvationScore +
+            kLargeScoreIncrementForLowPriorityStarvation - 1) /
+           kLargeScoreIncrementForLowPriorityStarvation;
+  }
 };
 
 class TaskQueueSelectorTest : public testing::Test {
@@ -516,13 +552,12 @@
   selector_.SetQueuePriority(task_queues_[1].get(),
                              TaskQueue::kHighestPriority);
 
-  constexpr const size_t kNumberOfHighestPriorityToStarveHighPriority =
-      TaskQueueSelectorForTest::kMaxHighPriorityStarvationScore;
-
   // Run a number of highest priority tasks needed to starve high priority
   // tasks (when present).
   for (size_t num_tasks = 0;
-       num_tasks <= kNumberOfHighestPriorityToStarveHighPriority; num_tasks++) {
+       num_tasks <=
+       TaskQueueSelectorForTest::NumberOfHighestPriorityToStarveHighPriority();
+       num_tasks++) {
     ASSERT_THAT(selector_.SelectWorkQueueToService(), NotNull());
     // Don't remove task from queue to simulate the queue is still full.
   }
@@ -549,14 +584,11 @@
   selector_.SetQueuePriority(task_queues_[1].get(),
                              TaskQueue::kHighestPriority);
 
-  constexpr const size_t kNumberOfHighestPriorityToStarveNormalPriority =
-      TaskQueueSelectorForTest::kMaxHighPriorityStarvationScore +
-      TaskQueueSelectorForTest::kMaxNormalPriorityStarvationScore;
-
   // Run a number of highest priority tasks needed to starve normal priority
   // tasks (when present).
   for (size_t num_tasks = 0;
-       num_tasks <= kNumberOfHighestPriorityToStarveNormalPriority;
+       num_tasks <= TaskQueueSelectorForTest::
+                        NumberOfHighestPriorityToStarveNormalPriority();
        num_tasks++) {
     ASSERT_THAT(selector_.SelectWorkQueueToService(), NotNull());
     // Don't remove task from queue to simulate the queue is still full.
@@ -565,13 +597,12 @@
   selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kHighPriority);
   selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
 
-  constexpr const size_t kNumberOfHighPriorityToStarveNormalPriority =
-      TaskQueueSelectorForTest::kMaxNormalPriorityStarvationScore;
-
   // Run a number of high priority tasks needed to starve normal priority
   // tasks (when present).
   for (size_t num_tasks = 0;
-       num_tasks <= kNumberOfHighPriorityToStarveNormalPriority; num_tasks++) {
+       num_tasks <=
+       TaskQueueSelectorForTest::NumberOfHighPriorityToStarveNormalPriority();
+       num_tasks++) {
     ASSERT_THAT(selector_.SelectWorkQueueToService(), NotNull());
     // Don't remove task from queue to simulate the queue is still full.
   }
@@ -598,15 +629,12 @@
   selector_.SetQueuePriority(task_queues_[1].get(),
                              TaskQueue::kHighestPriority);
 
-  constexpr const size_t kNumberOfHighestPriorityToStarveLowPriority =
-      TaskQueueSelectorForTest::kMaxHighPriorityStarvationScore +
-      TaskQueueSelectorForTest::kMaxNormalPriorityStarvationScore +
-      TaskQueueSelectorForTest::kMaxLowPriorityStarvationScore;
-
   // Run a number of highest priority tasks needed to starve low priority
   // tasks (when present).
   for (size_t num_tasks = 0;
-       num_tasks <= kNumberOfHighestPriorityToStarveLowPriority; num_tasks++) {
+       num_tasks <=
+       TaskQueueSelectorForTest::NumberOfHighestPriorityToStarveLowPriority();
+       num_tasks++) {
     ASSERT_THAT(selector_.SelectWorkQueueToService(), NotNull());
     // Don't remove task from queue to simulate the queue is still full.
   }
@@ -614,14 +642,11 @@
   selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kHighPriority);
   selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kNormalPriority);
 
-  constexpr const size_t kNumberOfHighAndNormalPriorityToStarveLowPriority =
-      TaskQueueSelectorForTest::kMaxNormalPriorityStarvationScore +
-      TaskQueueSelectorForTest::kMaxLowPriorityStarvationScore;
-
   // Run a number of high/normal priority tasks needed to starve low priority
   // tasks (when present).
   for (size_t num_tasks = 0;
-       num_tasks <= kNumberOfHighAndNormalPriorityToStarveLowPriority;
+       num_tasks <= TaskQueueSelectorForTest::
+                        NumberOfHighAndNormalPriorityToStarveLowPriority();
        num_tasks++) {
     ASSERT_THAT(selector_.SelectWorkQueueToService(), NotNull());
     // Don't remove task from queue to simulate the queue is still full.
@@ -808,169 +833,6 @@
                         ChooseOldestWithPriorityTest,
                         testing::ValuesIn(kChooseOldestWithPriorityTestCases));
 
-class SmallPriorityQueueTest : public testing::Test {
- public:
-  TaskQueueSelectorForTest::SmallPriorityQueue queue_;
-
-  std::vector<uint8_t> PopAllIds() {
-    std::vector<uint8_t> result;
-    while (!queue_.empty()) {
-      result.push_back(queue_.min_id());
-      queue_.erase(queue_.min_id());
-    }
-    return result;
-  }
-};
-
-TEST_F(SmallPriorityQueueTest, Insert) {
-  EXPECT_TRUE(queue_.empty());
-
-  EXPECT_FALSE(queue_.IsInQueue(1));
-  queue_.insert(1000, 1);
-  EXPECT_TRUE(queue_.IsInQueue(1));
-  EXPECT_EQ(1, queue_.min_id());
-  EXPECT_FALSE(queue_.empty());
-
-  EXPECT_FALSE(queue_.IsInQueue(2));
-  queue_.insert(1002, 2);
-  EXPECT_TRUE(queue_.IsInQueue(2));
-  EXPECT_EQ(1, queue_.min_id());
-
-  EXPECT_FALSE(queue_.IsInQueue(3));
-  queue_.insert(999, 3);
-  EXPECT_TRUE(queue_.IsInQueue(3));
-  EXPECT_EQ(3, queue_.min_id());
-
-  EXPECT_FALSE(queue_.IsInQueue(4));
-  queue_.insert(1003, 4);
-  EXPECT_TRUE(queue_.IsInQueue(4));
-  EXPECT_EQ(3, queue_.min_id());
-}
-
-TEST_F(SmallPriorityQueueTest, EraseMin) {
-  queue_.insert(1000, 1);
-  queue_.insert(1002, 2);
-  queue_.insert(999, 3);
-  queue_.insert(1003, 4);
-
-  EXPECT_EQ(3, queue_.min_id());
-  EXPECT_TRUE(queue_.IsInQueue(4));
-
-  queue_.erase(4);
-  EXPECT_FALSE(queue_.IsInQueue(4));
-  EXPECT_EQ(3, queue_.min_id());
-  EXPECT_TRUE(queue_.IsInQueue(3));
-
-  queue_.erase(3);
-  EXPECT_FALSE(queue_.IsInQueue(3));
-  EXPECT_EQ(1, queue_.min_id());
-  EXPECT_TRUE(queue_.IsInQueue(2));
-
-  queue_.erase(2);
-  EXPECT_FALSE(queue_.IsInQueue(2));
-  EXPECT_EQ(1, queue_.min_id());
-  EXPECT_TRUE(queue_.IsInQueue(1));
-
-  queue_.erase(1);
-  EXPECT_FALSE(queue_.IsInQueue(1));
-  EXPECT_TRUE(queue_.empty());
-}
-
-TEST_F(SmallPriorityQueueTest, EraseMiddle) {
-  queue_.insert(100, 1);
-  queue_.insert(101, 2);
-  queue_.insert(102, 3);
-  queue_.insert(103, 4);
-  queue_.insert(104, 5);
-
-  queue_.erase(3);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(1, 2, 4, 5));
-}
-
-TEST_F(SmallPriorityQueueTest, EraseAllButOne) {
-  queue_.insert(100, 1);
-  queue_.insert(101, 2);
-  queue_.insert(102, 3);
-  queue_.insert(103, 4);
-  queue_.insert(104, 5);
-
-  queue_.erase(5);
-  queue_.erase(1);
-  queue_.erase(3);
-  queue_.erase(2);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(4));
-}
-
-TEST_F(SmallPriorityQueueTest, ChangeMinKeyNoOrderDifference) {
-  queue_.insert(100, 1);
-  queue_.insert(101, 2);
-  queue_.insert(102, 3);
-  queue_.insert(105, 4);
-  queue_.insert(106, 5);
-
-  queue_.ChangeMinKey(99);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(1, 2, 3, 4, 5));
-}
-
-TEST_F(SmallPriorityQueueTest, ChangeMinKeyToMiddle) {
-  queue_.insert(100, 1);
-  queue_.insert(101, 2);
-  queue_.insert(102, 3);
-  queue_.insert(105, 4);
-  queue_.insert(106, 5);
-
-  queue_.ChangeMinKey(103);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(2, 3, 1, 4, 5));
-}
-
-TEST_F(SmallPriorityQueueTest, ChangeMinKeyToLast) {
-  queue_.insert(100, 1);
-  queue_.insert(101, 2);
-  queue_.insert(102, 3);
-  queue_.insert(105, 4);
-  queue_.insert(106, 5);
-
-  queue_.ChangeMinKey(107);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(2, 3, 4, 5, 1));
-}
-
-TEST_F(SmallPriorityQueueTest, StableSortingOrder) {
-  queue_.insert(100, 1);
-  queue_.insert(100, 2);
-  queue_.insert(100, 3);
-  queue_.insert(100, 4);
-  queue_.insert(100, 5);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(1, 2, 3, 4, 5));
-}
-
-TEST_F(SmallPriorityQueueTest, StableSortingOrderRemoveMiddle) {
-  queue_.insert(100, 1);
-  queue_.insert(100, 2);
-  queue_.insert(100, 3);
-  queue_.insert(100, 4);
-  queue_.insert(100, 5);
-  queue_.erase(3);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(1, 2, 4, 5));
-}
-
-TEST_F(SmallPriorityQueueTest, StableSortingOrderChangeMinToLast) {
-  queue_.insert(100, 1);
-  queue_.insert(100, 2);
-  queue_.insert(100, 3);
-  queue_.insert(100, 4);
-  queue_.insert(100, 5);
-  queue_.ChangeMinKey(101);
-
-  EXPECT_THAT(PopAllIds(), ElementsAre(2, 3, 4, 5, 1));
-}
-
 }  // namespace task_queue_selector_unittest
 }  // namespace internal
 }  // namespace sequence_manager
diff --git a/base/task/sequence_manager/work_queue_sets.cc b/base/task/sequence_manager/work_queue_sets.cc
index b538233e..b940be9 100644
--- a/base/task/sequence_manager/work_queue_sets.cc
+++ b/base/task/sequence_manager/work_queue_sets.cc
@@ -10,8 +10,7 @@
 namespace sequence_manager {
 namespace internal {
 
-WorkQueueSets::WorkQueueSets(const char* name, Observer* observer)
-    : name_(name), observer_(observer) {}
+WorkQueueSets::WorkQueueSets(const char* name) : name_(name) {}
 
 WorkQueueSets::~WorkQueueSets() = default;
 
@@ -24,10 +23,7 @@
   work_queue->AssignSetIndex(set_index);
   if (!has_enqueue_order)
     return;
-  bool was_empty = work_queue_heaps_[set_index].empty();
   work_queue_heaps_[set_index].insert({enqueue_order, work_queue});
-  if (was_empty)
-    observer_->WorkQueueSetBecameNonEmpty(set_index);
 }
 
 void WorkQueueSets::RemoveQueue(WorkQueue* work_queue) {
@@ -39,8 +35,6 @@
   size_t set_index = work_queue->work_queue_set_index();
   DCHECK_LT(set_index, work_queue_heaps_.size());
   work_queue_heaps_[set_index].erase(heap_handle);
-  if (work_queue_heaps_[set_index].empty())
-    observer_->WorkQueueSetBecameEmpty(set_index);
 }
 
 void WorkQueueSets::ChangeSetIndex(WorkQueue* work_queue, size_t set_index) {
@@ -55,12 +49,7 @@
   if (!has_enqueue_order)
     return;
   work_queue_heaps_[old_set].erase(work_queue->heap_handle());
-  bool was_empty = work_queue_heaps_[set_index].empty();
   work_queue_heaps_[set_index].insert({enqueue_order, work_queue});
-  if (work_queue_heaps_[old_set].empty())
-    observer_->WorkQueueSetBecameEmpty(old_set);
-  if (was_empty)
-    observer_->WorkQueueSetBecameNonEmpty(set_index);
 }
 
 void WorkQueueSets::OnFrontTaskChanged(WorkQueue* work_queue) {
@@ -84,10 +73,7 @@
       << " set_index = " << set_index;
   // |work_queue| should not be in work_queue_heaps_[set_index].
   DCHECK(!work_queue->heap_handle().IsValid());
-  bool was_empty = work_queue_heaps_[set_index].empty();
   work_queue_heaps_[set_index].insert({enqueue_order, work_queue});
-  if (was_empty)
-    observer_->WorkQueueSetBecameNonEmpty(set_index);
 }
 
 void WorkQueueSets::OnPopQueue(WorkQueue* work_queue) {
@@ -108,9 +94,6 @@
     work_queue_heaps_[set_index].Pop();
     DCHECK(work_queue_heaps_[set_index].empty() ||
            work_queue_heaps_[set_index].Min().value != work_queue);
-    if (work_queue_heaps_[set_index].empty()) {
-      observer_->WorkQueueSetBecameEmpty(set_index);
-    }
   }
 }
 
@@ -122,8 +105,6 @@
   size_t set_index = work_queue->work_queue_set_index();
   DCHECK_LT(set_index, work_queue_heaps_.size());
   work_queue_heaps_[set_index].erase(heap_handle);
-  if (work_queue_heaps_[set_index].empty())
-    observer_->WorkQueueSetBecameEmpty(set_index);
 }
 
 WorkQueue* WorkQueueSets::GetOldestQueueInSet(size_t set_index) const {
diff --git a/base/task/sequence_manager/work_queue_sets.h b/base/task/sequence_manager/work_queue_sets.h
index 8e23c7a0..4889b2f 100644
--- a/base/task/sequence_manager/work_queue_sets.h
+++ b/base/task/sequence_manager/work_queue_sets.h
@@ -28,16 +28,7 @@
 // values are kept in sorted order.
 class BASE_EXPORT WorkQueueSets {
  public:
-  class Observer {
-   public:
-    virtual ~Observer() {}
-
-    virtual void WorkQueueSetBecameEmpty(size_t set_index) = 0;
-
-    virtual void WorkQueueSetBecameNonEmpty(size_t set_index) = 0;
-  };
-
-  WorkQueueSets(const char* name, Observer* observer);
+  explicit WorkQueueSets(const char* name);
   ~WorkQueueSets();
 
   // O(log num queues)
@@ -99,14 +90,12 @@
     }
   };
 
-  const char* const name_;
-  Observer* const observer_;
-
   // For each set |work_queue_heaps_| has a queue of WorkQueue ordered by the
   // oldest task in each WorkQueue.
   std::array<base::internal::IntrusiveHeap<OldestTaskEnqueueOrder>,
              TaskQueue::kQueuePriorityCount>
       work_queue_heaps_;
+  const char* const name_;
 
   DISALLOW_COPY_AND_ASSIGN(WorkQueueSets);
 };
diff --git a/base/task/sequence_manager/work_queue_sets_unittest.cc b/base/task/sequence_manager/work_queue_sets_unittest.cc
index ad9d320..c8426fe5 100644
--- a/base/task/sequence_manager/work_queue_sets_unittest.cc
+++ b/base/task/sequence_manager/work_queue_sets_unittest.cc
@@ -17,20 +17,9 @@
 
 namespace internal {
 
-namespace {
-
-class MockObserver : public WorkQueueSets::Observer {
-  MOCK_METHOD1(WorkQueueSetBecameEmpty, void(size_t set_index));
-  MOCK_METHOD1(WorkQueueSetBecameNonEmpty, void(size_t set_index));
-};
-
-}  // namespace
-
 class WorkQueueSetsTest : public testing::Test {
  public:
-  void SetUp() override {
-    work_queue_sets_.reset(new WorkQueueSets("test", &mock_observer_));
-  }
+  void SetUp() override { work_queue_sets_.reset(new WorkQueueSets("test")); }
 
   void TearDown() override {
     for (std::unique_ptr<WorkQueue>& work_queue : work_queues_) {
@@ -63,7 +52,6 @@
     return fake_task;
   }
 
-  MockObserver mock_observer_;
   std::vector<std::unique_ptr<WorkQueue>> work_queues_;
   std::unique_ptr<WorkQueueSets> work_queue_sets_;
 };
diff --git a/base/task/sequence_manager/work_queue_unittest.cc b/base/task/sequence_manager/work_queue_unittest.cc
index cfb8075..23fc3ee 100644
--- a/base/task/sequence_manager/work_queue_unittest.cc
+++ b/base/task/sequence_manager/work_queue_unittest.cc
@@ -20,11 +20,6 @@
 
 namespace {
 
-class MockObserver : public WorkQueueSets::Observer {
-  MOCK_METHOD1(WorkQueueSetBecameEmpty, void(size_t set_index));
-  MOCK_METHOD1(WorkQueueSetBecameNonEmpty, void(size_t set_index));
-};
-
 void NopTask() {}
 
 struct Cancelable {
@@ -52,8 +47,7 @@
 
     work_queue_.reset(new WorkQueue(task_queue_.get(), "test",
                                     WorkQueue::QueueType::kImmediate));
-    mock_observer_.reset(new MockObserver);
-    work_queue_sets_.reset(new WorkQueueSets("test", mock_observer_.get()));
+    work_queue_sets_.reset(new WorkQueueSets("test"));
     work_queue_sets_->AddQueue(work_queue_.get(), 0);
   }
 
@@ -88,7 +82,6 @@
     return fake_task;
   }
 
-  std::unique_ptr<MockObserver> mock_observer_;
   std::unique_ptr<SequenceManagerImpl> dummy_sequence_manager_;
   std::unique_ptr<RealTimeDomain> time_domain_;
   std::unique_ptr<TaskQueueImpl> task_queue_;
diff --git a/build/android/gyp/apkbuilder.py b/build/android/gyp/apkbuilder.py
index 86e77a6a..b5097aa 100755
--- a/build/android/gyp/apkbuilder.py
+++ b/build/android/gyp/apkbuilder.py
@@ -212,7 +212,7 @@
     compress = None
     if (uncompress and os.path.splitext(basename)[1] == '.so'
         and 'android_linker' not in basename
-        and 'clang_rt' not in basename
+        and (not has_crazy_linker or 'clang_rt' not in basename)
         and 'crashpad_handler' not in basename):
       compress = False
       # Add prefix to prevent android install from extracting upon install.
diff --git a/build/args/headless.gn b/build/args/headless.gn
index ae5e043..9a6bfd1 100644
--- a/build/args/headless.gn
+++ b/build/args/headless.gn
@@ -40,3 +40,4 @@
 use_libpci = false
 use_pulseaudio = false
 use_udev = false
+rtc_use_pipewire = false
diff --git a/build/util/lastchange.py b/build/util/lastchange.py
index 1f9de67d..fd55ce3 100755
--- a/build/util/lastchange.py
+++ b/build/util/lastchange.py
@@ -7,27 +7,28 @@
 lastchange.py -- Chromium revision fetching utility.
 """
 
-import argparse
-import collections
+import re
 import logging
+import argparse
 import os
 import subprocess
 import sys
 
-VersionInfo = collections.namedtuple("VersionInfo",
-                                     ("revision_id", "revision", "timestamp"))
+class VersionInfo(object):
+  def __init__(self, revision_id, full_revision_string, timestamp):
+    self.revision_id = revision_id
+    self.revision = full_revision_string
+    self.timestamp = timestamp
 
-class GitError(Exception):
-  pass
 
 def RunGitCommand(directory, command):
   """
   Launches git subcommand.
 
+  Errors are swallowed.
+
   Returns:
-    The stripped stdout of the git command.
-  Raises:
-    GitError on failure, including a nonzero return code.
+    A process object or None.
   """
   command = ['git'] + command
   # Force shell usage under cygwin. This is a workaround for
@@ -37,81 +38,61 @@
   if sys.platform == 'cygwin':
     command = ['sh', '-c', ' '.join(command)]
   try:
-    logging.info("Executing '%s' in %s", ' '.join(command), directory)
     proc = subprocess.Popen(command,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             cwd=directory,
                             shell=(sys.platform=='win32'))
-    stdout, stderr = proc.communicate()
-    stdout = stdout.strip()
-    logging.debug("returncode: %d", proc.returncode)
-    logging.debug("stdout: %s", stdout)
-    logging.debug("stderr: %s", stderr)
-    if proc.returncode != 0 or not stdout:
-      raise GitError((
-          "Git command 'git {}' in {} failed: "
-          "rc={}, stdout='{}' stderr='{}'").format(
-          " ".join(command), directory, proc.returncode, stdout, stderr))
-    return stdout
+    return proc
   except OSError as e:
-    raise GitError("Git command 'git {}' in {} failed: {}".format(
-        " ".join(command), directory, e))
+    logging.error('Command %r failed: %s' % (' '.join(command), e))
+    return None
 
-def GetMergeBase(directory, ref):
+
+def FetchGitRevision(directory, filter):
   """
-  Return the merge-base of HEAD and ref.
+  Fetch the Git hash (and Cr-Commit-Position if any) for a given directory.
 
-  Args:
-    directory: The directory containing the .git directory.
-    ref: The ref to use to find the merge base.
+  Errors are swallowed.
+
   Returns:
-    The git commit SHA of the merge-base as a string.
+    A VersionInfo object or None on error.
   """
-  logging.debug("Calculating merge base between HEAD and %s in %s",
-                ref, directory)
-  command = ['merge-base', 'HEAD', ref]
-  return RunGitCommand(directory, command)
+  hsh = ''
+  git_args = ['log', '-1', '--format=%H %ct']
+  if filter is not None:
+    git_args.append('--grep=' + filter)
+  proc = RunGitCommand(directory, git_args)
+  if proc:
+    output = proc.communicate()[0].strip()
+    if proc.returncode == 0 and output:
+      hsh, ct = output.split()
+    else:
+      logging.error('Git error: rc=%d, output=%r' %
+                    (proc.returncode, output))
+  if not hsh:
+    return None
+  pos = ''
+  proc = RunGitCommand(directory, ['cat-file', 'commit', hsh])
+  if proc:
+    output = proc.communicate()[0]
+    if proc.returncode == 0 and output:
+      for line in reversed(output.splitlines()):
+        if line.startswith('Cr-Commit-Position:'):
+          pos = line.rsplit()[-1].strip()
+          break
+  return VersionInfo(hsh, '%s-%s' % (hsh, pos), int(ct))
 
-def FetchGitRevision(directory, commit_filter, start_commit="HEAD"):
+
+def FetchVersionInfo(directory=None, filter=None):
   """
   Returns the last change (as a VersionInfo object)
   from some appropriate revision control system.
-
-
-  Args:
-    directory: The directory containing the .git directory.
-    commit_filter: A filter to supply to grep to filter commits
-    start_commit: A commit identifier. The result of this function
-      will be limited to only consider commits before the provided
-      commit.
-  Returns:
-    A VersionInfo object. On error all values will be 0.
   """
-  hash_ = ''
-
-  git_args = ['log', '-1', '--format=%H %ct']
-  if commit_filter is not None:
-    git_args.append('--grep=' + commit_filter)
-
-  git_args.append(start_commit)
-
-  output = RunGitCommand(directory, git_args)
-  hash_, commit_timestamp = output.split()
-  if not hash_:
-    return VersionInfo('0', '0', 0)
-
-  revision = hash_
-  output = RunGitCommand(directory, ['cat-file', 'commit', hash_])
-  for line in reversed(output.splitlines()):
-    if line.startswith('Cr-Commit-Position:'):
-      pos = line.rsplit()[-1].strip()
-      logging.debug("Found Cr-Commit-Position '%s'", pos)
-      revision = "{}-{}".format(hash_, pos)
-      break
-  return VersionInfo(hash_, revision, int(commit_timestamp))
-
-
+  version_info = FetchGitRevision(directory, filter)
+  if not version_info:
+    version_info = VersionInfo('0', '0', 0)
+  return version_info
 
 
 def GetHeaderGuard(path):
@@ -152,16 +133,6 @@
   return header_contents
 
 
-def GetGitTopDirectory(source_dir):
-  """Get the top git directory - the directory that contains the .git directory.
-
-  Args:
-    source_dir: The directory to search.
-  Returns:
-    The output of "git rev-parse --show-toplevel" as a string
-  """
-  return RunGitCommand(source_dir, ['rev-parse', '--show-toplevel'])
-
 def WriteIfChanged(file_name, contents):
   """
   Writes the specified contents to the specified file_name
@@ -186,23 +157,20 @@
 
   parser = argparse.ArgumentParser(usage="lastchange.py [options]")
   parser.add_argument("-m", "--version-macro",
-                    help=("Name of C #define when using --header. Defaults to "
-                          "LAST_CHANGE."))
+                    help="Name of C #define when using --header. Defaults to " +
+                    "LAST_CHANGE.",
+                    default="LAST_CHANGE")
   parser.add_argument("-o", "--output", metavar="FILE",
-                    help=("Write last change to FILE. "
-                          "Can be combined with --header to write both files."))
+                    help="Write last change to FILE. " +
+                    "Can be combined with --header to write both files.")
   parser.add_argument("--header", metavar="FILE",
                     help=("Write last change to FILE as a C/C++ header. "
                           "Can be combined with --output to write both files."))
-  parser.add_argument("--merge-base-ref",
-                    default=None,
-                    help=("Only consider changes since the merge "
-                          "base between HEAD and the provided ref"))
   parser.add_argument("--revision-id-only", action='store_true',
                     help=("Output the revision as a VCS revision ID only (in "
                           "Git, a 40-character commit hash, excluding the "
                           "Cr-Commit-Position)."))
-  parser.add_argument("--print-only", action="store_true",
+  parser.add_argument("--print-only", action='store_true',
                     help=("Just print the revision string. Overrides any "
                           "file-output-related options."))
   parser.add_argument("-s", "--source-dir", metavar="DIR",
@@ -212,14 +180,13 @@
                           "matches the supplied filter regex. Defaults to "
                           "'^Change-Id:' to suppress local commits."),
                     default='^Change-Id:')
-
   args, extras = parser.parse_known_args(argv[1:])
 
   logging.basicConfig(level=logging.WARNING)
 
   out_file = args.output
   header = args.header
-  commit_filter = args.filter
+  filter=args.filter
 
   while len(extras) and out_file is None:
     if out_file is None:
@@ -229,37 +196,18 @@
     parser.print_help()
     sys.exit(2)
 
-  source_dir = args.source_dir or os.path.dirname(os.path.abspath(__file__))
-  try:
-    git_top_dir = GetGitTopDirectory(source_dir)
-  except GitError as e:
-    logging.error("Failed to get git top directory from '%s': %s",
-                  source_dir, e)
-    return 2
-
-  if args.merge_base_ref:
-    try:
-      merge_base_sha = GetMergeBase(git_top_dir, args.merge_base_ref)
-    except GitError as e:
-      logging.error("You requested a --merge-base-ref value of '%s' but no "
-                    "merge base could be found between it and HEAD. Git "
-                    "reports: %s", args.merge_base_ref, e)
-      return 3
+  if args.source_dir:
+    src_dir = args.source_dir
   else:
-    merge_base_sha = 'HEAD'
+    src_dir = os.path.dirname(os.path.abspath(__file__))
 
-  try:
-    version_info = FetchGitRevision(git_top_dir, commit_filter, merge_base_sha)
-  except GitError as e:
-    logging.error("Failed to get version info: %s")
-    return 1
-
+  version_info = FetchVersionInfo(directory=src_dir, filter=filter)
   revision_string = version_info.revision
   if args.revision_id_only:
     revision_string = version_info.revision_id
 
   if args.print_only:
-    print(revision_string)
+    print revision_string
   else:
     contents = "LASTCHANGE=%s\n" % revision_string
     if not out_file and not args.header:
diff --git a/cc/mojo_embedder/async_layer_tree_frame_sink.cc b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
index e702ad1..286c328 100644
--- a/cc/mojo_embedder/async_layer_tree_frame_sink.cc
+++ b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
@@ -218,8 +218,13 @@
   if (show_hit_test_borders && hit_test_region_list)
     hit_test_region_list->flags |= viz::HitTestRegionFlags::kHitTestDebug;
 
-  // Do not send duplicate hit-test data.
-  if (hit_test_region_list && !hit_test_data_changed) {
+  // If |hit_test_data_changed| was set or local_surface_id has been updated,
+  // we always send hit-test data; otherwise we check for equality with the
+  // last submitted hit-test data for possible optimization.
+  if (!hit_test_region_list) {
+    last_hit_test_data_ = viz::HitTestRegionList();
+  } else if (!hit_test_data_changed &&
+             local_surface_id_ == last_submitted_local_surface_id_) {
     if (viz::HitTestRegionList::IsEqual(*hit_test_region_list,
                                         last_hit_test_data_)) {
       DCHECK(!viz::HitTestRegionList::IsEqual(*hit_test_region_list,
@@ -232,7 +237,7 @@
     UMA_HISTOGRAM_BOOLEAN("Event.VizHitTest.HitTestDataIsEqualAccuracy",
                           !hit_test_region_list);
   } else {
-    last_hit_test_data_ = viz::HitTestRegionList();
+    last_hit_test_data_ = *hit_test_region_list;
   }
 
   if (last_submitted_local_surface_id_ != local_surface_id_) {
diff --git a/cc/paint/color_space_transfer_cache_entry.cc b/cc/paint/color_space_transfer_cache_entry.cc
index adde337..31f2743 100644
--- a/cc/paint/color_space_transfer_cache_entry.cc
+++ b/cc/paint/color_space_transfer_cache_entry.cc
@@ -14,6 +14,7 @@
   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() =
@@ -23,8 +24,8 @@
   return id_;
 }
 
-size_t ClientColorSpaceTransferCacheEntry::SerializedSize() const {
-  return pickle_.size();
+uint32_t ClientColorSpaceTransferCacheEntry::SerializedSize() const {
+  return static_cast<uint32_t>(pickle_.size());
 }
 
 bool ClientColorSpaceTransferCacheEntry::Serialize(
diff --git a/cc/paint/color_space_transfer_cache_entry.h b/cc/paint/color_space_transfer_cache_entry.h
index baeeb4ce..4923c73 100644
--- a/cc/paint/color_space_transfer_cache_entry.h
+++ b/cc/paint/color_space_transfer_cache_entry.h
@@ -29,7 +29,7 @@
       const RasterColorSpace& raster_color_space);
   ~ClientColorSpaceTransferCacheEntry() override;
   uint32_t Id() const override;
-  size_t SerializedSize() const override;
+  uint32_t SerializedSize() const override;
   bool Serialize(base::span<uint8_t> data) const final;
 
  private:
diff --git a/cc/paint/image_transfer_cache_entry.cc b/cc/paint/image_transfer_cache_entry.cc
index 170e55f2..3a9b9781 100644
--- a/cc/paint/image_transfer_cache_entry.cc
+++ b/cc/paint/image_transfer_cache_entry.cc
@@ -69,7 +69,7 @@
                             : 0u;
 
   // Compute and cache the size of the data.
-  base::CheckedNumeric<size_t> safe_size;
+  base::CheckedNumeric<uint32_t> safe_size;
   safe_size += PaintOpWriter::HeaderBytes();
   safe_size += sizeof(uint32_t);  // color type
   safe_size += sizeof(uint32_t);  // width
@@ -90,7 +90,7 @@
 // static
 base::AtomicSequenceNumber ClientImageTransferCacheEntry::s_next_id_;
 
-size_t ClientImageTransferCacheEntry::SerializedSize() const {
+uint32_t ClientImageTransferCacheEntry::SerializedSize() const {
   return size_;
 }
 
diff --git a/cc/paint/image_transfer_cache_entry.h b/cc/paint/image_transfer_cache_entry.h
index db31ade..7b563c4 100644
--- a/cc/paint/image_transfer_cache_entry.h
+++ b/cc/paint/image_transfer_cache_entry.h
@@ -31,7 +31,7 @@
   uint32_t Id() const final;
 
   // ClientTransferCacheEntry implementation:
-  size_t SerializedSize() const final;
+  uint32_t SerializedSize() const final;
   bool Serialize(base::span<uint8_t> data) const final;
 
  private:
@@ -39,7 +39,7 @@
   const SkPixmap* const pixmap_;
   const SkColorSpace* const target_color_space_;
   const bool needs_mips_;
-  size_t size_ = 0;
+  uint32_t size_ = 0;
   static base::AtomicSequenceNumber s_next_id_;
 };
 
diff --git a/cc/paint/raw_memory_transfer_cache_entry.cc b/cc/paint/raw_memory_transfer_cache_entry.cc
index aa6fb64..8f8e746 100644
--- a/cc/paint/raw_memory_transfer_cache_entry.cc
+++ b/cc/paint/raw_memory_transfer_cache_entry.cc
@@ -10,15 +10,18 @@
 
 ClientRawMemoryTransferCacheEntry::ClientRawMemoryTransferCacheEntry(
     std::vector<uint8_t> data)
-    : id_(s_next_id_.GetNext()), data_(std::move(data)) {}
+    : id_(s_next_id_.GetNext()), data_(std::move(data)) {
+  DCHECK_LE(data_.size(), UINT32_MAX);
+}
+
 ClientRawMemoryTransferCacheEntry::~ClientRawMemoryTransferCacheEntry() =
     default;
 
 // static
 base::AtomicSequenceNumber ClientRawMemoryTransferCacheEntry::s_next_id_;
 
-size_t ClientRawMemoryTransferCacheEntry::SerializedSize() const {
-  return data_.size();
+uint32_t ClientRawMemoryTransferCacheEntry::SerializedSize() const {
+  return static_cast<uint32_t>(data_.size());
 }
 
 uint32_t ClientRawMemoryTransferCacheEntry::Id() const {
diff --git a/cc/paint/raw_memory_transfer_cache_entry.h b/cc/paint/raw_memory_transfer_cache_entry.h
index c6ce52af..2e9aafd4 100644
--- a/cc/paint/raw_memory_transfer_cache_entry.h
+++ b/cc/paint/raw_memory_transfer_cache_entry.h
@@ -22,7 +22,7 @@
   explicit ClientRawMemoryTransferCacheEntry(std::vector<uint8_t> data);
   ~ClientRawMemoryTransferCacheEntry() final;
   uint32_t Id() const final;
-  size_t SerializedSize() const final;
+  uint32_t SerializedSize() const final;
   bool Serialize(base::span<uint8_t> data) const final;
 
  private:
diff --git a/cc/paint/transfer_cache_entry.h b/cc/paint/transfer_cache_entry.h
index 374607e2..5488aa1 100644
--- a/cc/paint/transfer_cache_entry.h
+++ b/cc/paint/transfer_cache_entry.h
@@ -45,7 +45,7 @@
   // Returns the serialized sized of this entry in bytes. This function will be
   // used to determine how much memory is going to be allocated and passed to
   // the Serialize() call.
-  virtual size_t SerializedSize() const = 0;
+  virtual uint32_t SerializedSize() const = 0;
 
   // Serializes the entry into the given span of memory. The size of the span is
   // guaranteed to be at least SerializedSize() bytes. Returns true on success
diff --git a/cc/paint/transfer_cache_unittest.cc b/cc/paint/transfer_cache_unittest.cc
index f24917a..7b606ab 100644
--- a/cc/paint/transfer_cache_unittest.cc
+++ b/cc/paint/transfer_cache_unittest.cc
@@ -75,7 +75,7 @@
   }
   void CreateEntry(const ClientTransferCacheEntry& entry) {
     auto* context_support = ContextSupport();
-    size_t size = entry.SerializedSize();
+    uint32_t size = entry.SerializedSize();
     void* data = context_support->MapTransferCacheEntry(size);
     ASSERT_TRUE(data);
     entry.Serialize(base::make_span(static_cast<uint8_t*>(data), size));
diff --git a/cc/test/transfer_cache_test_helper.cc b/cc/test/transfer_cache_test_helper.cc
index deb3fd3..67cb937 100644
--- a/cc/test/transfer_cache_test_helper.cc
+++ b/cc/test/transfer_cache_test_helper.cc
@@ -106,7 +106,7 @@
   DCHECK(entries_.find(key) == entries_.end());
 
   // Serialize data.
-  size_t size = client_entry.SerializedSize();
+  uint32_t size = client_entry.SerializedSize();
   std::unique_ptr<uint8_t[]> data(new uint8_t[size]);
   auto span = base::make_span(data.get(), size);
   bool success = client_entry.Serialize(span);
diff --git a/cc/tiles/gpu_image_decode_cache.cc b/cc/tiles/gpu_image_decode_cache.cc
index 6e3ae2b..18fd073 100644
--- a/cc/tiles/gpu_image_decode_cache.cc
+++ b/cc/tiles/gpu_image_decode_cache.cc
@@ -1543,7 +1543,7 @@
 
     ClientImageTransferCacheEntry image_entry(&pixmap, color_space.get(),
                                               image_data->needs_mips);
-    size_t size = image_entry.SerializedSize();
+    uint32_t size = image_entry.SerializedSize();
     void* data = context_->ContextSupport()->MapTransferCacheEntry(size);
     if (data) {
       bool succeeded = image_entry.Serialize(
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index 526c1dd..9c841f10 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -132,7 +132,7 @@
   void CompleteLockDiscardableTexureOnContextThread(
       uint32_t texture_id) override {}
 
-  void* MapTransferCacheEntry(size_t serialized_size) override {
+  void* MapTransferCacheEntry(uint32_t serialized_size) override {
     mapped_entry_size_ = serialized_size;
     mapped_entry_.reset(new uint8_t[serialized_size]);
     return mapped_entry_.get();
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 6b8ca78..052e9b7 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1347,7 +1347,6 @@
 
 generate_jni("test_support_jni_headers") {
   sources = [
-    "javatests/src/org/chromium/chrome/browser/offlinepages/OfflineTestUtil.java",
     "javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchTestBridge.java",
     "javatests/src/org/chromium/chrome/browser/test/MockCertVerifierRuleAndroid.java",
   ]
@@ -1359,15 +1358,12 @@
   testonly = true
   java_files = [
     "javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchTestBridge.java",
-    "javatests/src/org/chromium/chrome/browser/offlinepages/OfflineTestUtil.java",
     "javatests/src/org/chromium/chrome/browser/sync/FakeServerHelper.java",
     "javatests/src/org/chromium/chrome/browser/test/MockCertVerifierRuleAndroid.java",
   ]
   deps = [
     ":chrome_java",
     "//base:base_java",
-    "//base:base_java_test_support",
-    "//components/offline_items_collection/core:core_java",
     "//components/sync:test_support_proto_java",
     "//content/public/test/android:content_java_test_support",
     "//third_party/android_deps:com_google_protobuf_protobuf_lite_java",
@@ -1380,7 +1376,6 @@
   sources = [
     "../browser/android/ssl/mock_cert_verifier_rule_android.cc",
     "../browser/android/ssl/mock_cert_verifier_rule_android.h",
-    "../browser/offline_pages/android/offline_test_util_jni.cc",
     "../browser/offline_pages/android/prefetch_test_bridge.cc",
     "../browser/ssl/chrome_mock_cert_verifier.cc",
     "../browser/ssl/chrome_mock_cert_verifier.h",
@@ -1388,7 +1383,6 @@
   deps = [
     ":test_support_jni_headers",
     "//chrome/browser",
-    "//components/offline_pages/core/background:test_support",
   ]
 }
 
diff --git a/chrome/android/java/res/layout/ephemeral_tab_caption_view.xml b/chrome/android/java/res/layout/ephemeral_tab_caption_view.xml
new file mode 100644
index 0000000..6a4e61b
--- /dev/null
+++ b/chrome/android/java/res/layout/ephemeral_tab_caption_view.xml
@@ -0,0 +1,15 @@
+<?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. -->
+
+<!-- Ephemeral Tab caption view where we display 'Open in new tab'. Hidden in
+     peeked state, and gets visible only when being expanded to maximized state. -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/ephemeral_tab_caption_view"
+    style="@style/ContextualSearchTextViewLayout" >
+    <TextView
+        android:id="@+id/ephemeral_tab_caption"
+        style="@style/ContextualSearchCaptionTextView" />
+</FrameLayout>
diff --git a/chrome/android/java/res/layout/url_bar.xml b/chrome/android/java/res/layout/url_bar.xml
index fd1155c3..08544e8 100644
--- a/chrome/android/java/res/layout/url_bar.xml
+++ b/chrome/android/java/res/layout/url_bar.xml
@@ -14,4 +14,5 @@
     android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
     android:textSize="@dimen/location_bar_url_text_size"
     android:inputType="textUri"
-    android:hint="@string/search_or_type_web_address" />
\ No newline at end of file
+    android:hint="@string/search_or_type_web_address"
+    android:importantForAutofill="no" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimator.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimator.java
index 4402ee6..05ece2d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimator.java
@@ -45,8 +45,6 @@
     /** The list of frame update listeners for this animation. */
     private final ArrayList<AnimatorUpdateListener> mAnimatorUpdateListeners = new ArrayList<>();
 
-    private FloatProperty mFloatProperty;
-
     /**
      * A cached copy of the list of {@link AnimatorUpdateListener}s to prevent allocating a new list
      * every update.
@@ -137,7 +135,6 @@
         animator.setDuration(durationMs);
         animator.addUpdateListener(
                 (CompositorAnimator a) -> property.setValue(target, a.getAnimatedValue()));
-        animator.setFloatProperty(property);
         animator.setInterpolator(interpolator);
         return animator;
     }
@@ -232,17 +229,6 @@
         mAnimatorUpdateListeners.add(listener);
     }
 
-    private void setFloatProperty(FloatProperty property) {
-        mFloatProperty = property;
-    }
-
-    /**
-     * @return Whether this animation is of the given FloatProperty.
-     */
-    public boolean isOfFloatProperty(FloatProperty property) {
-        return mFloatProperty == property;
-    }
-
     /**
      * @return Whether or not the animation has ended after being started. If the animation is
      *         started after ending, this value will be reset to true.
@@ -284,7 +270,6 @@
     public void removeAllListeners() {
         mListeners.clear();
         mAnimatorUpdateListeners.clear();
-        mFloatProperty = null;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabBarControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabBarControl.java
index 5b540eb..66ca809 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabBarControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabBarControl.java
@@ -5,35 +5,78 @@
 package org.chromium.chrome.browser.compositor.bottombar.ephemeraltab;
 
 import android.content.Context;
-import android.view.View;
 import android.view.ViewGroup;
-import android.widget.TextView;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
-import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelInflater;
 import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
 
 /**
  * Top control used for ephemeral tab.
  */
-public class EphemeralTabBarControl extends OverlayPanelInflater {
-    /**
-     * The tab title View.
-     */
-    private TextView mBarText;
+public class EphemeralTabBarControl {
+    /** Full opacity -- fully visible. */
+    private static final float SOLID_OPAQUE = 1.0f;
+
+    /** Transparent opacity -- completely transparent (not visible). */
+    private static final float SOLID_TRANSPARENT = 0.0f;
+
+    private final EphemeralTabTitleControl mTitle;
+    private final EphemeralTabCaptionControl mCaption;
+
+    // Dimensions used for laying out the controls in the bar.
+    private final float mTextLayerMinHeight;
+    private final float mTitleCaptionSpacing;
 
     /**
      * @param panel The panel.
      * @param context The Android Context used to inflate the View.
      * @param container The container View used to inflate the View.
-     * @param resourceLoader The resource loader that will handle the snapshot capturing.
+     * @param loader The resource loader that will handle the snapshot capturing.
      */
-    public EphemeralTabBarControl(OverlayPanel panel, Context context, ViewGroup container,
-            DynamicResourceLoader resourceLoader) {
-        super(panel, R.layout.ephemeral_tab_text_view, R.id.ephemeral_tab_text_view, context,
-                container, resourceLoader);
-        invalidate();
+    public EphemeralTabBarControl(EphemeralTabPanel panel, Context context, ViewGroup container,
+            DynamicResourceLoader loader) {
+        mTitle = new EphemeralTabTitleControl(panel, context, container, loader);
+        mCaption = panel.canPromoteToNewTab()
+                ? new EphemeralTabCaptionControl(panel, context, container, loader)
+                : null;
+        mTextLayerMinHeight = context.getResources().getDimension(
+                R.dimen.contextual_search_text_layer_min_height);
+        mTitleCaptionSpacing =
+                context.getResources().getDimension(R.dimen.contextual_search_term_caption_spacing);
+    }
+
+    /**
+     * Returns the minimum height that the text layer (containing the title and the caption)
+     * should be.
+     */
+    public float getTextLayerMinHeight() {
+        return mTextLayerMinHeight;
+    }
+
+    /**
+     * Returns the spacing that should be placed between the title and the caption.
+     */
+    public float getTitleCaptionSpacing() {
+        return mTitleCaptionSpacing;
+    }
+
+    /**
+     * Updates this bar when in transition to closed/peeked states.
+     * @param percentage The percentage to the more opened state.
+     */
+    public void updateForCloseOrPeek(float percentage) {
+        if (percentage == SOLID_OPAQUE) updateForMaximize(SOLID_TRANSPARENT);
+
+        // When the panel is completely closed the caption should be hidden.
+        if (percentage == SOLID_TRANSPARENT && mCaption != null) mCaption.hide();
+    }
+
+    /**
+     * Updates this bar when in transition to maximized states.
+     * @param percentage The percentage to the more opened state.
+     */
+    public void updateForMaximize(float percentage) {
+        if (mCaption != null) mCaption.updatePanelForMaximization(percentage);
     }
 
     /**
@@ -41,15 +84,38 @@
      * @param text The string to set the text to.
      */
     public void setBarText(String text) {
-        inflate();
-        mBarText.setText(text);
-        invalidate();
+        mTitle.setBarText(text);
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        View view = getView();
-        mBarText = (TextView) view.findViewById(R.id.ephemeral_tab_text);
+    /**
+     * @return {@link EphemeralTabTitleControl} object.
+     */
+    public EphemeralTabTitleControl getTitleControl() {
+        return mTitle;
+    }
+
+    /**
+     * @return {@link EphemeralTabCaptionControl} object.
+     */
+    public EphemeralTabCaptionControl getCaptionControl() {
+        return mCaption;
+    }
+
+    /**
+     * Gets the current animation percentage for the Caption control, which guides the vertical
+     * position and opacity of the caption.
+     * @return The animation percentage ranging from 0.0 to 1.0.
+     *
+     */
+    public float getCaptionAnimationPercentage() {
+        return mCaption != null ? mCaption.getAnimationPercentage() : 0;
+    }
+
+    /**
+     * Removes the bottom bar views from the parent container.
+     */
+    public void destroy() {
+        mTitle.destroy();
+        if (mCaption != null) mCaption.destroy();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCaptionControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCaptionControl.java
new file mode 100644
index 0000000..9f07e901
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCaptionControl.java
@@ -0,0 +1,124 @@
+// 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.compositor.bottombar.ephemeraltab;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelTextViewInflater;
+import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
+
+/**
+ * Controls the Caption View that is shown at the bottom of the control and used
+ * as a dynamic resource.
+ */
+public class EphemeralTabCaptionControl extends OverlayPanelTextViewInflater {
+    /** The caption View. */
+    private TextView mCaption;
+
+    /** Whether the caption is showing. */
+    private boolean mShowingCaption;
+
+    /** The caption visibility. */
+    private boolean mIsVisible;
+
+    /**
+     * The caption animation percentage, which controls how and where to draw. It is
+     * 0 when the Contextual Search bar is peeking and 1 when it is maxmized.
+     */
+    private float mAnimationPercentage;
+
+    /**
+     * @param panel                     The panel.
+     * @param context                   The Android Context used to inflate the View.
+     * @param container                 The container View used to inflate the View.
+     * @param resourceLoader            The resource loader that will handle the snapshot capturing.
+     */
+    public EphemeralTabCaptionControl(OverlayPanel panel, Context context, ViewGroup container,
+            DynamicResourceLoader resourceLoader) {
+        super(panel, R.layout.ephemeral_tab_caption_view, R.id.ephemeral_tab_caption_view, context,
+                container, resourceLoader);
+    }
+
+    /**
+     * Updates the caption when in transition between peeked to maximized states.
+     * @param percentage The percentage to the more opened state.
+     */
+    public void updatePanelForMaximization(float percentage) {
+        // If the caption is not showing, show it now.
+        if (!mShowingCaption && percentage > 0.f) {
+            mShowingCaption = true;
+
+            if (mCaption == null) {
+                // |mCaption| gets initialized synchronously in |onFinishInflate|.
+                inflate();
+                mCaption.setText(R.string.contextmenu_open_in_new_tab);
+            }
+            invalidate();
+            mIsVisible = true;
+        }
+
+        mAnimationPercentage = percentage;
+        if (mAnimationPercentage == 0.f) mShowingCaption = false;
+    }
+
+    /**
+     * Hides the caption.
+     */
+    public void hide() {
+        if (mShowingCaption) {
+            mIsVisible = false;
+            mAnimationPercentage = 0.f;
+        }
+    }
+
+    /**
+     * Controls whether the caption is visible and can be rendered.
+     * The caption must be visible in order to draw it and take a snapshot.
+     * Even though the caption is visible the user might not be able to see it due to a
+     * completely transparent opacity associated with an animation percentage of zero.
+     * @return Whether the caption is visible or not.
+     */
+    public boolean getIsVisible() {
+        return mIsVisible;
+    }
+
+    /**
+     * Gets the animation percentage which controls the drawing of the caption and how high to
+     * position it in the Bar.
+     * @return The current percentage ranging from 0.0 to 1.0.
+     */
+    public float getAnimationPercentage() {
+        return mAnimationPercentage;
+    }
+
+    /**
+     * @return The text currently showing in the caption view.
+     */
+    public CharSequence getCaptionText() {
+        return mCaption.getText();
+    }
+
+    // OverlayPanelTextViewInflater
+
+    @Override
+    protected TextView getTextView() {
+        return mCaption;
+    }
+
+    // OverlayPanelInflater
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        View view = getView();
+        mCaption = (TextView) view.findViewById(R.id.ephemeral_tab_caption);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java
index e40e1c3..48d30234 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java
@@ -23,14 +23,16 @@
 import org.chromium.chrome.browser.compositor.layouts.eventfilter.OverlayPanelEventFilter;
 import org.chromium.chrome.browser.compositor.scene_layer.EphemeralTabSceneLayer;
 import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.resources.ResourceManager;
 
 /**
  * The panel containing an ephemeral tab.
  * TODO(jinsukkim): Write tests.
  *                  Add animation effect upon opening ephemeral tab.
- *                  Bring back the bottom bar animation hidden behind ephemeral tab.
  */
 public class EphemeralTabPanel extends OverlayPanel {
     /** The compositor layer used for drawing the panel. */
@@ -42,6 +44,9 @@
     /** True if the Tab from which the panel is opened is in incognito mode. */
     private boolean mIsIncognito;
 
+    /** Url for which this epehemral tab was created. */
+    private String mUrl;
+
     /**
      * Checks if this feature (a.k.a. "Sneak peek") for html and image is supported.
      * @return {@code true} if the feature is enabled.
@@ -121,7 +126,8 @@
     @Override
     public SceneOverlayLayer getUpdatedSceneOverlayTree(RectF viewport, RectF visibleViewport,
             LayerTitleCache layerTitleCache, ResourceManager resourceManager, float yOffset) {
-        mSceneLayer.update(resourceManager, this, getBarTextViewId(), 1.0f);
+        mSceneLayer.update(resourceManager, this, getBarControl(),
+                getBarControl().getTitleControl(), getBarControl().getCaptionControl());
         return mSceneLayer;
     }
 
@@ -140,16 +146,28 @@
         if (isCoordinateInsideCloseButton(x)) {
             closePanel(StateChangeReason.CLOSE_BUTTON, true);
         } else {
-            maximizePanel(StateChangeReason.SEARCH_BAR_TAP);
+            if (isPeeking()) {
+                maximizePanel(StateChangeReason.SEARCH_BAR_TAP);
+            } else if (canPromoteToNewTab() && mUrl != null) {
+                closePanel(StateChangeReason.TAB_PROMOTION, false);
+                mActivity.getCurrentTabCreator().createNewTab(
+                        new LoadUrlParams(mUrl, PageTransition.LINK),
+                        TabModel.TabLaunchType.FROM_LINK,
+                        mActivity.getActivityTabProvider().getActivityTab());
+            }
         }
     }
 
+    boolean canPromoteToNewTab() {
+        return !mActivity.isCustomTab();
+    }
+
     // Panel base methods
 
     @Override
     public void destroyComponents() {
         super.destroyComponents();
-        destroyEphemeralTabBarControl();
+        destroyBarControl();
     }
 
     @Override
@@ -168,6 +186,18 @@
         if (mSceneLayer != null) mSceneLayer.hideTree();
     }
 
+    @Override
+    protected void updatePanelForCloseOrPeek(float percentage) {
+        super.updatePanelForCloseOrPeek(percentage);
+        getBarControl().updateForCloseOrPeek(percentage);
+    }
+
+    @Override
+    protected void updatePanelForMaximization(float percentage) {
+        super.updatePanelForMaximization(percentage);
+        getBarControl().updateForMaximize(percentage);
+    }
+
     /**
      * Request opening the ephemeral tab panel when triggered from context menu.
      * @param url URL of the content to open in the panel
@@ -175,34 +205,29 @@
      * @param isIncognito {@link True} if the panel is opened from an incognito tab.
      */
     public void requestOpenPanel(String url, String text, boolean isIncognito) {
+        if (isShowing()) closePanel(StateChangeReason.RESET, false);
         mIsIncognito = isIncognito;
+        mUrl = url;
         loadUrlInPanel(url);
         WebContents panelWebContents = getWebContents();
         if (panelWebContents != null) panelWebContents.onShow();
-        getEphemeralTabBarControl().setBarText(text);
+        getBarControl().setBarText(text);
         requestPanelShow(StateChangeReason.CLICK);
     }
 
     @Override
     public void onLayoutChanged(float width, float height, float visibleViewportOffsetY) {
-        if (width != getWidth()) destroyEphemeralTabBarControl();
+        if (width != getWidth()) destroyBarControl();
         super.onLayoutChanged(width, height, visibleViewportOffsetY);
     }
 
     private EphemeralTabBarControl mEphemeralTabBarControl;
 
     /**
-     * @return The Id of the Search Term View.
-     */
-    public int getBarTextViewId() {
-        return getEphemeralTabBarControl().getViewId();
-    }
-
-    /**
      * Creates the EphemeralTabBarControl, if needed. The Views are set to INVISIBLE, because
      * they won't actually be displayed on the screen (their snapshots will be displayed instead).
      */
-    protected EphemeralTabBarControl getEphemeralTabBarControl() {
+    private EphemeralTabBarControl getBarControl() {
         assert mContainerView != null;
         assert mResourceLoader != null;
         if (mEphemeralTabBarControl == null) {
@@ -216,7 +241,7 @@
     /**
      * Destroys the EphemeralTabBarControl.
      */
-    protected void destroyEphemeralTabBarControl() {
+    private void destroyBarControl() {
         if (mEphemeralTabBarControl != null) {
             mEphemeralTabBarControl.destroy();
             mEphemeralTabBarControl = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabTitleControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabTitleControl.java
new file mode 100644
index 0000000..09d68aa
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabTitleControl.java
@@ -0,0 +1,55 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.compositor.bottombar.ephemeraltab;
+
+import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelTextViewInflater;
+import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
+
+/**
+ * Title control showing URL for ephemeral tab.
+ */
+public class EphemeralTabTitleControl extends OverlayPanelTextViewInflater {
+    private TextView mBarText;
+
+    /**
+     * @param panel The panel.
+     * @param context The Android Context used to inflate the View.
+     * @param container The container View used to inflate the View.
+     * @param resourceLoader The resource loader that will handle the snapshot capturing.
+     */
+    public EphemeralTabTitleControl(OverlayPanel panel, Context context, ViewGroup container,
+            DynamicResourceLoader resourceLoader) {
+        super(panel, R.layout.ephemeral_tab_text_view, R.id.ephemeral_tab_text_view, context,
+                container, resourceLoader);
+        invalidate();
+    }
+
+    void setBarText(String text) {
+        if (mBarText == null) inflate();
+        mBarText.setText(text);
+        invalidate();
+    }
+
+    // OverlayPanelTextViewInflater
+
+    @Override
+    protected TextView getTextView() {
+        return mBarText;
+    }
+
+    // OverlayPanelInflater
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mBarText = (TextView) getView().findViewById(R.id.ephemeral_tab_text);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
index 0fe3d76..10654f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
@@ -4,8 +4,8 @@
 
 package org.chromium.chrome.browser.compositor.layouts.phone;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
+import static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.AnimatableAnimation.createAnimation;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -22,7 +22,8 @@
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.LayerTitleCache;
 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;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
@@ -62,45 +63,14 @@
 /**
  * Base class for layouts that show one or more stacks of tabs.
  */
-public abstract class StackLayoutBase extends Layout {
-    private static final FloatProperty<StackLayoutBase> INNER_MARGIN_PERCENT =
-            new FloatProperty<StackLayoutBase>("") {
-                @Override
-                public void setValue(StackLayoutBase layoutBase, float v) {
-                    layoutBase.setInnerMarginPercent(v);
-                }
-
-                @Override
-                public Float get(StackLayoutBase layoutTab) {
-                    return null;
-                }
-            };
-
-    private static final FloatProperty<StackLayoutBase> STACK_OFFSET_Y_PERCENT =
-            new FloatProperty<StackLayoutBase>("") {
-                @Override
-                public void setValue(StackLayoutBase layoutBase, float v) {
-                    layoutBase.setStackOffsetYPercent(v);
-                }
-
-                @Override
-                public Float get(StackLayoutBase layoutTab) {
-                    return null;
-                }
-            };
-
-    private static final FloatProperty<StackLayoutBase> STACK_SNAP =
-            new FloatProperty<StackLayoutBase>("") {
-                @Override
-                public void setValue(StackLayoutBase layoutBase, float v) {
-                    layoutBase.setStackSnap(v);
-                }
-
-                @Override
-                public Float get(StackLayoutBase layoutTab) {
-                    return null;
-                }
-            };
+public abstract class StackLayoutBase extends Layout implements Animatable {
+    @IntDef({Property.INNER_MARGIN_PERCENT, Property.STACK_SNAP, Property.STACK_OFFSET_Y_PERCENT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Property {
+        int INNER_MARGIN_PERCENT = 0;
+        int STACK_SNAP = 1;
+        int STACK_OFFSET_Y_PERCENT = 2;
+    }
 
     @IntDef({DragDirection.NONE, DragDirection.HORIZONTAL, DragDirection.VERTICAL})
     @Retention(RetentionPolicy.SOURCE)
@@ -224,7 +194,7 @@
 
     private StackLayoutGestureHandler mGestureHandler;
 
-    private AnimatorSet mLayoutAnimations;
+    private ChromeAnimation<Animatable> mLayoutAnimations;
 
     private class StackLayoutGestureHandler implements GestureHandler {
         @Override
@@ -379,34 +349,6 @@
     }
 
     /**
-     * Sets the stack offset percent for vertical axis.
-     *
-     * @param v Value to set.
-     */
-    public void setStackOffsetYPercent(float v) {
-        mStackOffsetYPercent = v;
-    }
-
-    /**
-     * Sets the inner margin percent.
-     *
-     * @param v Value to set.
-     */
-    public void setInnerMarginPercent(float v) {
-        mInnerMarginPercent = v;
-    }
-
-    /**
-     * Sets the stack stap value.
-     *
-     * @param v Value to set.
-     */
-    public void setStackSnap(float v) {
-        mRenderedScrollOffset = v;
-        mScrollIndexOffset = v;
-    }
-
-    /**
      * Whether or not the HorizontalTabSwitcherAndroid flag (which enables the new horizontal tab
      * switcher in both portrait and landscape mode) is enabled.
      */
@@ -706,9 +648,10 @@
         boolean animationsWasDone = true;
         if (mLayoutAnimations != null) {
             if (jumpToEnd) {
-                mLayoutAnimations.end();
+                animationsWasDone = mLayoutAnimations.finished();
+                mLayoutAnimations.updateAndFinish();
             } else {
-                animationsWasDone = !mLayoutAnimations.isRunning();
+                animationsWasDone = mLayoutAnimations.update(time);
             }
 
             if (animationsWasDone || jumpToEnd) {
@@ -832,23 +775,23 @@
 
     protected void startMarginAnimation(boolean enter, boolean showMargin) {
         // Any outstanding animations must be cancelled to avoid race condition.
-        cancelAnimation(INNER_MARGIN_PERCENT);
+        cancelAnimation(this, Property.INNER_MARGIN_PERCENT);
 
         float start = mInnerMarginPercent;
         float end = enter && showMargin ? 1.0f : 0.0f;
         if (start != end) {
-            addToAnimation(INNER_MARGIN_PERCENT, start, end, 200, 0);
+            addToAnimation(this, Property.INNER_MARGIN_PERCENT, start, end, 200, 0);
         }
     }
 
     private void startYOffsetAnimation(boolean enter) {
         // Any outstanding animations must be cancelled to avoid race condition.
-        cancelAnimation(STACK_OFFSET_Y_PERCENT);
+        cancelAnimation(this, Property.STACK_OFFSET_Y_PERCENT);
 
         float start = mStackOffsetYPercent;
         float end = enter ? 1.f : 0.f;
         if (start != end) {
-            addToAnimation(STACK_OFFSET_Y_PERCENT, start, end, 300, 0);
+            addToAnimation(this, Property.STACK_OFFSET_Y_PERCENT, start, end, 300, 0);
         }
     }
 
@@ -1189,7 +1132,7 @@
      * @param delta The amount to scroll by.
      */
     private void scrollStacks(float delta) {
-        cancelAnimation(STACK_SNAP);
+        cancelAnimation(this, Property.STACK_SNAP);
         float fullDistance = getFullScrollDistance();
         mScrollIndexOffset += MathUtils.flipSignIf(delta / fullDistance,
                 !isUsingHorizontalLayout() && LocalizationUtils.isLayoutRtl());
@@ -1217,16 +1160,16 @@
      * incognito to non-incognito, which leaves the up event in the incognito side.
      */
     private void finishScrollStacks() {
-        cancelAnimation(STACK_SNAP);
+        cancelAnimation(this, Property.STACK_SNAP);
         final int currentModelIndex = getTabStackIndex();
         float delta = Math.abs(currentModelIndex + mRenderedScrollOffset);
         float target = -currentModelIndex;
         if (delta != 0) {
             long duration = FLING_MIN_DURATION
                     + (long) Math.abs(delta * getFullScrollDistance() / mFlingSpeed);
-            addToAnimation(STACK_SNAP, mRenderedScrollOffset, target, duration, 0);
+            addToAnimation(this, Property.STACK_SNAP, mRenderedScrollOffset, target, duration, 0);
         } else {
-            setStackSnap(target);
+            setProperty(Property.STACK_SNAP, target);
             onAnimationFinished();
         }
     }
@@ -1545,6 +1488,30 @@
     }
 
     /**
+     * Sets properties for animations.
+     * @param prop The property to update
+     * @param p New value of the property
+     */
+    @Override
+    public void setProperty(@Property int prop, float p) {
+        switch (prop) {
+            case Property.STACK_SNAP:
+                mRenderedScrollOffset = p;
+                mScrollIndexOffset = p;
+                break;
+            case Property.INNER_MARGIN_PERCENT:
+                mInnerMarginPercent = p;
+                break;
+            case Property.STACK_OFFSET_Y_PERCENT:
+                mStackOffsetYPercent = p;
+                break;
+        }
+    }
+
+    @Override
+    public void onPropertyAnimationFinished(@Property int prop) {}
+
+    /**
      * Called by the stacks whenever they start an animation.
      */
     public void onStackAnimationStarted() {
@@ -1581,20 +1548,20 @@
     }
 
     /**
-     * Creates an {@link CompositorAnimator} and adds it to the animation.
+     * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation
+     * .AnimatableAnimation} and adds it to the animation.
      * Automatically sets the start value at the beginning of the animation.
      */
-    protected void addToAnimation(FloatProperty<StackLayoutBase> property, float start, float end,
-            long duration, long startTime) {
-        if (mLayoutAnimations == null) mLayoutAnimations = new AnimatorSet();
-
-        CompositorAnimator compositorAnimator = CompositorAnimator.ofFloatProperty(
-                getAnimationHandler(), this, property, start, end, duration);
-        compositorAnimator.setStartDelay(startTime);
-
-        mLayoutAnimations.playTogether(compositorAnimator);
-        mLayoutAnimations.start();
-
+    protected void addToAnimation(
+            Animatable object, int prop, float start, float end, long duration, long startTime) {
+        ChromeAnimation.Animation<Animatable> component = createAnimation(object, prop, start, end,
+                duration, startTime, false, CompositorAnimator.DECELERATE_INTERPOLATOR);
+        if (mLayoutAnimations == null || mLayoutAnimations.finished()) {
+            mLayoutAnimations = new ChromeAnimation<Animatable>();
+            mLayoutAnimations.start();
+        }
+        component.start();
+        mLayoutAnimations.add(component);
         requestUpdate();
     }
 
@@ -1602,7 +1569,7 @@
     protected void forceAnimationToFinish() {
         super.forceAnimationToFinish();
         if (mLayoutAnimations != null) {
-            mLayoutAnimations.end();
+            mLayoutAnimations.updateAndFinish();
             mLayoutAnimations = null;
         }
     }
@@ -1612,18 +1579,13 @@
      * @param object The object being animated.
      * @param prop   The property to search for.
      */
-    protected void cancelAnimation(FloatProperty<StackLayoutBase> property) {
-        if (mLayoutAnimations == null) return;
-
-        for (Animator animator : mLayoutAnimations.getChildAnimations()) {
-            CompositorAnimator a = (CompositorAnimator) animator;
-            if (a.isOfFloatProperty(property)) a.cancel();
-        }
+    protected void cancelAnimation(Animatable object, int prop) {
+        if (mLayoutAnimations != null) mLayoutAnimations.cancel(object, prop);
     }
 
     @Override
     @VisibleForTesting
     public boolean isLayoutAnimating() {
-        return mLayoutAnimations != null && mLayoutAnimations.isRunning();
+        return mLayoutAnimations != null && !mLayoutAnimations.finished();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/EphemeralTabSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/EphemeralTabSceneLayer.java
index 2d6e7495..2ecd971 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/EphemeralTabSceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/EphemeralTabSceneLayer.java
@@ -4,9 +4,14 @@
 
 package org.chromium.chrome.browser.compositor.scene_layer;
 
+import android.support.annotation.Nullable;
+
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
+import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabBarControl;
+import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabCaptionControl;
+import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabPanel;
+import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabTitleControl;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.resources.ResourceManager;
 
@@ -35,37 +40,50 @@
      * Update the scene layer to draw an OverlayPanel.
      * @param resourceManager Manager to get view and image resources.
      * @param panel The OverlayPanel to render.
-     * @param barTextViewId The ID of the view containing the ephemeral tab bar.
-     * @param barTextOpacity The opacity of the text specified by {@code barTextViewId}.
+     * @param bar {@link EphemeralTabBarControl} object.
+     * @param title {@link EphemeralTabTitleControl} object.
+     * @param caption {@link EphemeralTabCaptionControl} object.
      */
-    public void update(ResourceManager resourceManager, OverlayPanel panel, int barTextViewId,
-            float barTextOpacity) {
+    public void update(ResourceManager resourceManager, EphemeralTabPanel panel,
+            EphemeralTabBarControl bar, EphemeralTabTitleControl title,
+            @Nullable EphemeralTabCaptionControl caption) {
         // Don't try to update the layer if not initialized or showing.
         if (resourceManager == null || !panel.isShowing()) return;
         if (!mIsInitialized) {
             nativeCreateEphemeralTabLayer(mNativePtr, resourceManager);
 
             // TODO(jinsukkim): Find the right icon/background resource for the tab bar.
-            nativeSetResourceIds(mNativePtr, barTextViewId,
+            nativeSetResourceIds(mNativePtr, title.getViewId(),
                     R.drawable.contextual_search_bar_background, R.drawable.modern_toolbar_shadow,
                     R.drawable.infobar_chrome, R.drawable.btn_close);
             mIsInitialized = true;
         }
+
+        int titleViewId = title.getViewId();
+        int captionViewId = 0;
+        float captionAnimationPercentage = 0.f;
+        boolean captionVisible = false;
+        if (caption != null) {
+            captionViewId = caption.getViewId();
+            captionAnimationPercentage = caption.getAnimationPercentage();
+            captionVisible = caption.getIsVisible();
+        }
         boolean isProgressBarVisible = panel.isProgressBarVisible();
         float progressBarHeight = panel.getProgressBarHeight();
         float progressBarOpacity = panel.getProgressBarOpacity();
         int progressBarCompletion = panel.getProgressBarCompletion();
 
         WebContents panelWebContents = panel.getWebContents();
-        nativeUpdate(mNativePtr, R.drawable.progress_bar_background,
-                R.drawable.progress_bar_foreground, mDpToPx, panel.getBasePageBrightness(),
-                panel.getBasePageY() * mDpToPx, panelWebContents, panel.getOffsetX() * mDpToPx,
-                panel.getOffsetY() * mDpToPx, panel.getWidth() * mDpToPx,
-                panel.getHeight() * mDpToPx, panel.getBarMarginSide() * mDpToPx,
-                panel.getBarHeight() * mDpToPx, barTextOpacity, panel.isBarBorderVisible(),
-                panel.getBarBorderHeight() * mDpToPx, panel.getBarShadowVisible(),
-                panel.getBarShadowOpacity(), isProgressBarVisible, progressBarHeight * mDpToPx,
-                progressBarOpacity, progressBarCompletion);
+        nativeUpdate(mNativePtr, titleViewId, captionViewId, captionAnimationPercentage,
+                bar.getTextLayerMinHeight(), bar.getTitleCaptionSpacing(), captionVisible,
+                R.drawable.progress_bar_background, R.drawable.progress_bar_foreground, mDpToPx,
+                panel.getBasePageBrightness(), panel.getBasePageY() * mDpToPx, panelWebContents,
+                panel.getOffsetX() * mDpToPx, panel.getOffsetY() * mDpToPx,
+                panel.getWidth() * mDpToPx, panel.getHeight() * mDpToPx,
+                panel.getBarMarginSide() * mDpToPx, panel.getBarHeight() * mDpToPx,
+                panel.isBarBorderVisible(), panel.getBarBorderHeight() * mDpToPx,
+                panel.getBarShadowVisible(), panel.getBarShadowOpacity(), isProgressBarVisible,
+                progressBarHeight * mDpToPx, progressBarOpacity, progressBarCompletion);
     }
 
     @Override
@@ -108,11 +126,13 @@
     private native void nativeSetResourceIds(long nativeEphemeralTabSceneLayer,
             int barTextResourceId, int barBackgroundResourceId, int barShadowResourceId,
             int panelIconResourceId, int closeIconResourceId);
-    private native void nativeUpdate(long nativeEphemeralTabSceneLayer,
-            int progressBarBackgroundResourceId, int progressBarResourceId, float dpToPx,
-            float basePageBrightness, float basePageYOffset, WebContents webContents, float panelX,
-            float panelY, float panelWidth, float panelHeight, float barMarginSide, float barHeight,
-            float textOpacity, boolean barBorderVisible, float barBorderHeight,
-            boolean barShadowVisible, float barShadowOpacity, boolean isProgressBarVisible,
-            float progressBarHeight, float progressBarOpacity, int progressBarCompletion);
+    private native void nativeUpdate(long nativeEphemeralTabSceneLayer, int titleViewId,
+            int captionViewId, float captionAnimationPercentage, float textLayerMinHeight,
+            float titleCaptionSpacing, boolean captionVisible, int progressBarBackgroundResourceId,
+            int progressBarResourceId, float dpToPx, float basePageBrightness,
+            float basePageYOffset, WebContents webContents, float panelX, float panelY,
+            float panelWidth, float panelHeight, float barMarginSide, float barHeight,
+            boolean barBorderVisible, float barBorderHeight, boolean barShadowVisible,
+            float barShadowOpacity, boolean isProgressBarVisible, float progressBarHeight,
+            float progressBarOpacity, int progressBarCompletion);
 }
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 5e6a59c..e0c7821 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
@@ -34,22 +34,17 @@
 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;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.init.EmptyBrowserParts;
-import org.chromium.chrome.browser.init.ServiceManagerStartupUtils;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.LegacyHelpers;
 import org.chromium.components.offline_items_collection.PendingState;
 import org.chromium.content_public.browser.BrowserStartupController;
 
-import java.util.HashSet;
-import java.util.Set;
-
 /**
  * Class that spins up native when an interaction with a notification happens and passes the
  * relevant information on to native.
@@ -202,10 +197,7 @@
             @Override
             public boolean startServiceManagerOnly() {
                 if (!LegacyHelpers.isLegacyDownload(id)) return false;
-                Set<String> features = new HashSet<String>();
-                features.add(ChromeFeatureList.SERVICE_MANAGER_FOR_DOWNLOAD);
-                features.add(ChromeFeatureList.NETWORK_SERVICE);
-                return ServiceManagerStartupUtils.canStartServiceManager(features)
+                return DownloadUtils.shouldStartServiceManagerOnly()
                         && !ACTION_DOWNLOAD_OPEN.equals(intent.getAction());
             }
         };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
index 265beeb..e3e5d66 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
@@ -42,6 +42,7 @@
 import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.OfflineItemWrapper;
 import org.chromium.chrome.browser.feature_engagement.ScreenshotTabObserver;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.init.ServiceManagerStartupUtils;
 import org.chromium.chrome.browser.media.MediaViewerUtils;
 import org.chromium.chrome.browser.offlinepages.DownloadUiActionFlags;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
@@ -81,9 +82,11 @@
 import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -999,6 +1002,14 @@
                 && entry.isAutoResumable;
     }
 
+    /** @return Whether we should start service manager only, based off the features enabled. */
+    public static boolean shouldStartServiceManagerOnly() {
+        Set<String> features = new HashSet<String>();
+        features.add(ChromeFeatureList.SERVICE_MANAGER_FOR_DOWNLOAD);
+        features.add(ChromeFeatureList.NETWORK_SERVICE);
+        return ServiceManagerStartupUtils.canStartServiceManager(features);
+    }
+
     /**
      * Format the number of bytes into KB, or MB, or GB and return the corresponding string
      * resource. Uses default download-related set of strings.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadBackgroundTask.java b/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadBackgroundTask.java
index 80a55b62e..a21f939 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadBackgroundTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadBackgroundTask.java
@@ -10,6 +10,7 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask;
 import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask.StartBeforeNativeResult;
+import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.background_task_scheduler.TaskParameters;
 import org.chromium.components.download.DownloadTaskType;
@@ -22,6 +23,8 @@
  * Entry point for the download service to perform desired action when the task is fired by the
  * scheduler. The scheduled task is executed for the regular profile and also for incognito profile
  * if an incognito profile exists.
+ * TODO(shaktisahu): Since we probably don't need to run tasks for incognito profile, cleanup this
+ * class and remove any reference to profiles.
  */
 @JNINamespace("download::android")
 public class DownloadBackgroundTask extends NativeBackgroundTask {
@@ -37,6 +40,9 @@
     // Keeps track of in progress tasks which haven't invoked their {@link TaskFinishedCallback}s.
     private Map<Integer, PendingTaskCounter> mPendingTaskCounters = new HashMap<>();
 
+    @DownloadTaskType
+    private int mCurrentTaskType;
+
     @Override
     protected @StartBeforeNativeResult int onStartTaskBeforeNativeLoaded(
             Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
@@ -59,34 +65,42 @@
         // In case of future upgrades, we would need to build an intent for the old version and
         // validate that this code still works. This would require decoupling this immediate class
         // from native as well.
-        @DownloadTaskType
-        final int taskType =
-                taskParameters.getExtras().getInt(DownloadTaskScheduler.EXTRA_TASK_TYPE);
+        mCurrentTaskType = taskParameters.getExtras().getInt(DownloadTaskScheduler.EXTRA_TASK_TYPE);
 
         Callback<Boolean> wrappedCallback = new Callback<Boolean>() {
             @Override
             public void onResult(Boolean needsReschedule) {
-                if (mPendingTaskCounters.get(taskType) == null) return;
+                if (mPendingTaskCounters.get(mCurrentTaskType) == null) return;
 
                 boolean noPendingCallbacks =
-                        decrementPendingCallbackCount(taskType, needsReschedule);
+                        decrementPendingCallbackCount(mCurrentTaskType, needsReschedule);
                 if (noPendingCallbacks) {
-                    callback.taskFinished(mPendingTaskCounters.get(taskType).needsReschedule);
-                    mPendingTaskCounters.remove(taskType);
+                    callback.taskFinished(
+                            mPendingTaskCounters.get(mCurrentTaskType).needsReschedule);
+                    mPendingTaskCounters.remove(mCurrentTaskType);
                 }
             }
         };
 
-        Profile profile = Profile.getLastUsedProfile().getOriginalProfile();
-        incrementPendingCallbackCount(taskType);
-        nativeStartBackgroundTask(profile, taskType, wrappedCallback);
+        Profile profile = supportsServiceManagerOnly()
+                ? null
+                : Profile.getLastUsedProfile().getOriginalProfile();
+        incrementPendingCallbackCount(mCurrentTaskType);
+        nativeStartBackgroundTask(profile, mCurrentTaskType, wrappedCallback);
 
-        if (profile.hasOffTheRecordProfile()) {
-            incrementPendingCallbackCount(taskType);
-            nativeStartBackgroundTask(profile.getOffTheRecordProfile(), taskType, wrappedCallback);
+        if (profile != null && profile.hasOffTheRecordProfile()) {
+            incrementPendingCallbackCount(mCurrentTaskType);
+            nativeStartBackgroundTask(
+                    profile.getOffTheRecordProfile(), mCurrentTaskType, wrappedCallback);
         }
     }
 
+    @Override
+    protected boolean supportsServiceManagerOnly() {
+        return mCurrentTaskType == DownloadTaskType.DOWNLOAD_AUTO_RESUMPTION_TASK
+                && DownloadUtils.shouldStartServiceManagerOnly();
+    }
+
     private void incrementPendingCallbackCount(@DownloadTaskType int taskType) {
         PendingTaskCounter taskCounter = mPendingTaskCounters.containsKey(taskType)
                 ? mPendingTaskCounters.get(taskType)
@@ -117,10 +131,12 @@
         int taskType = taskParameters.getExtras().getInt(DownloadTaskScheduler.EXTRA_TASK_TYPE);
         mPendingTaskCounters.remove(taskType);
 
-        Profile profile = Profile.getLastUsedProfile().getOriginalProfile();
+        Profile profile = supportsServiceManagerOnly()
+                ? null
+                : Profile.getLastUsedProfile().getOriginalProfile();
         boolean needsReschedule = nativeStopBackgroundTask(profile, taskType);
 
-        if (profile.hasOffTheRecordProfile()) {
+        if (profile != null && profile.hasOffTheRecordProfile()) {
             needsReschedule |= nativeStopBackgroundTask(profile.getOffTheRecordProfile(), taskType);
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadTaskScheduler.java b/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadTaskScheduler.java
index e54d9e3..3b71fa1f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadTaskScheduler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/service/DownloadTaskScheduler.java
@@ -29,6 +29,7 @@
     public static final String EXTRA_TASK_TYPE = "extra_task_type";
     static final long TWELVE_HOURS_IN_SECONDS = TimeUnit.HOURS.toSeconds(12);
     static final long FIVE_MINUTES_IN_SECONDS = TimeUnit.MINUTES.toSeconds(5);
+    static final long ONE_DAY_IN_SECONDS = TimeUnit.DAYS.toSeconds(1);
 
     @CalledByNative
     private static void scheduleTask(@DownloadTaskType int taskType,
@@ -69,6 +70,8 @@
                 2 * FIVE_MINUTES_IN_SECONDS);
         scheduleTask(DownloadTaskType.CLEANUP_TASK, false, false, 0, TWELVE_HOURS_IN_SECONDS,
                 2 * TWELVE_HOURS_IN_SECONDS);
+        scheduleTask(DownloadTaskType.DOWNLOAD_AUTO_RESUMPTION_TASK, false, false, 0,
+                FIVE_MINUTES_IN_SECONDS, ONE_DAY_IN_SECONDS);
     }
 
     private static int getTaskId(@DownloadTaskType int taskType) {
@@ -77,6 +80,8 @@
                 return TaskIds.DOWNLOAD_SERVICE_JOB_ID;
             case DownloadTaskType.CLEANUP_TASK:
                 return TaskIds.DOWNLOAD_CLEANUP_JOB_ID;
+            case DownloadTaskType.DOWNLOAD_AUTO_RESUMPTION_TASK:
+                return TaskIds.DOWNLOAD_AUTO_RESUMPTION_JOB_ID;
             default:
                 assert false;
                 return -1;
@@ -85,9 +90,16 @@
 
     private static int getRequiredNetworkType(
             @DownloadTaskType int taskType, boolean requiresUnmeteredNetwork) {
-        if (taskType != DownloadTaskType.DOWNLOAD_TASK) return TaskInfo.NETWORK_TYPE_NONE;
-
-        return requiresUnmeteredNetwork ? TaskInfo.NETWORK_TYPE_UNMETERED
-                                        : TaskInfo.NETWORK_TYPE_ANY;
+        switch (taskType) {
+            case DownloadTaskType.CLEANUP_TASK:
+                return TaskInfo.NETWORK_TYPE_NONE;
+            case DownloadTaskType.DOWNLOAD_TASK: // intentional fall-through
+            case DownloadTaskType.DOWNLOAD_AUTO_RESUMPTION_TASK:
+                return requiresUnmeteredNetwork ? TaskInfo.NETWORK_TYPE_UNMETERED
+                                                : TaskInfo.NETWORK_TYPE_ANY;
+            default:
+                assert false;
+                return TaskInfo.NETWORK_TYPE_ANY;
+        }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTask.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTask.java
index d1c1495..2a75f0f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTask.java
@@ -63,7 +63,7 @@
     protected void onStartTaskWithNative(
             Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
         assert taskParameters.getTaskId() == TaskIds.EXPLORE_SITES_REFRESH_JOB_ID;
-        if (ExploreSitesBridge.getVariation() != ExploreSitesVariation.ENABLED) {
+        if (ExploreSitesBridge.isEnabled(ExploreSitesBridge.getVariation())) {
             cancelTask();
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
index bf3ff36..1843d1d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
@@ -83,6 +83,15 @@
         return nativeGetVariation();
     }
 
+    public static boolean isEnabled(@ExploreSitesVariation int variation) {
+        return variation == ExploreSitesVariation.ENABLED
+                || variation == ExploreSitesVariation.PERSONALIZED;
+    }
+
+    public static boolean isExperimental(@ExploreSitesVariation int variation) {
+        return variation == ExploreSitesVariation.EXPERIMENT;
+    }
+
     @CalledByNative
     static void scheduleDailyTask() {
         ExploreSitesBackgroundTask.schedule(false /* updateCurrent */);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java
index d34d2ff..e4bcac54 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java
@@ -33,7 +33,7 @@
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
-import org.chromium.chrome.browser.webapps.WebApkActivity;
+import org.chromium.chrome.browser.webapps.WebappLauncherActivity;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.ChildAccountStatus;
 import org.chromium.components.signin.ChromeSigninController;
@@ -368,8 +368,8 @@
 
             Intent intentToLaunchAfterFreComplete = fromIntent;
             String associatedAppNameForLightweightFre = null;
-            WebApkActivity.FreParams webApkFreParams =
-                    WebApkActivity.slowGenerateFreParamsIfIntentIsForWebApkActivity(fromIntent);
+            WebappLauncherActivity.FreParams webApkFreParams =
+                    WebappLauncherActivity.slowGenerateFreParamsIfIntentIsForWebApk(fromIntent);
             if (webApkFreParams != null) {
                 intentToLaunchAfterFreComplete =
                         webApkFreParams.getIntentToLaunchAfterFreComplete();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
index 0ebe578..2c7859fb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
@@ -302,7 +302,7 @@
 
         if (requiresFirstRunToBeCompleted(intent)
                 && FirstRunFlowSequencer.launch(this, intent, false /* requiresBroadcast */,
-                           shouldPreferLightweightFre(intent))) {
+                        false /* preferLightweightFre */)) {
             abortLaunch(LaunchIntentDispatcher.Action.FINISH_ACTIVITY_REMOVE_TASK);
             return;
         }
@@ -401,14 +401,6 @@
     }
 
     /**
-     * Whether to use the Lightweight First Run Experience instead of the
-     * non-Lightweight First Run Experience.
-     */
-    protected boolean shouldPreferLightweightFre(Intent intent) {
-        return false;
-    }
-
-    /**
      * Whether or not the Activity was started up via a valid Intent.
      */
     protected boolean isStartedUpCorrectly(Intent intent) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java
index 8d14401..5f7c0e8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java
@@ -9,6 +9,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.ntp.ForeignSessionHelper;
+import org.chromium.chrome.browser.profiles.Profile;
 
 import java.util.concurrent.TimeUnit;
 
@@ -44,7 +45,7 @@
     /**
      * Used to call native code that enables and disables session invalidations.
      */
-    private final ForeignSessionHelper mForeignSessionHelper;
+    private final Profile mProfile;
 
     private static SessionsInvalidationManager sInstance;
 
@@ -63,19 +64,17 @@
      *
      * Calling this method will create the instance if it does not yet exist.
      */
-    public static SessionsInvalidationManager get(ForeignSessionHelper foreignSessionHelper) {
+    public static SessionsInvalidationManager get(Profile profile) {
         ThreadUtils.assertOnUiThread();
         if (sInstance == null) {
-            sInstance = new SessionsInvalidationManager(
-                    foreignSessionHelper, new ResumableDelayedTaskRunner());
+            sInstance = new SessionsInvalidationManager(profile, new ResumableDelayedTaskRunner());
         }
         return sInstance;
     }
 
     @VisibleForTesting
-    SessionsInvalidationManager(
-            ForeignSessionHelper foreignSessionHelper, ResumableDelayedTaskRunner runner) {
-        mForeignSessionHelper = foreignSessionHelper;
+    SessionsInvalidationManager(Profile profile, ResumableDelayedTaskRunner runner) {
+        mProfile = profile;
         mIsSessionInvalidationsEnabled = false;
         mEnableSessionInvalidationsRunner = runner;
         ApplicationStatus.registerApplicationStateListener(this);
@@ -117,7 +116,9 @@
 
         mEnableSessionInvalidationsRunner.setRunnable(() -> {
             mIsSessionInvalidationsEnabled = isEnabled;
-            mForeignSessionHelper.setInvalidationsForSessionsEnabled(isEnabled);
+            ForeignSessionHelper foreignSessionHelper = new ForeignSessionHelper(mProfile);
+            foreignSessionHelper.setInvalidationsForSessionsEnabled(isEnabled);
+            foreignSessionHelper.destroy();
         }, delayMs);
         mEnableSessionInvalidationsRunner.resume();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ForeignSessionHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ForeignSessionHelper.java
index 292272b1..6911b0d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ForeignSessionHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ForeignSessionHelper.java
@@ -120,14 +120,14 @@
      * Initialize this class with the given profile.
      * @param profile Profile that will be used for syncing.
      */
-    ForeignSessionHelper(Profile profile) {
+    public ForeignSessionHelper(Profile profile) {
         mNativeForeignSessionHelper = nativeInit(profile);
     }
 
     /**
      * Clean up the C++ side of this class. After the call, this class instance shouldn't be used.
      */
-    void destroy() {
+    public void destroy() {
         assert mNativeForeignSessionHelper != 0;
         nativeDestroy(mNativeForeignSessionHelper);
         mNativeForeignSessionHelper = 0;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index 76e4bff..b8d3f713 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -36,7 +36,6 @@
 import org.chromium.chrome.browser.explore_sites.ExperimentalExploreSitesSection;
 import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge;
 import org.chromium.chrome.browser.explore_sites.ExploreSitesSection;
-import org.chromium.chrome.browser.explore_sites.ExploreSitesVariation;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener;
@@ -191,9 +190,10 @@
         mSearchBoxView = findViewById(R.id.search_box);
         insertSiteSectionView();
 
-        if (ExploreSitesBridge.getVariation() == ExploreSitesVariation.ENABLED) {
+        int variation = ExploreSitesBridge.getVariation();
+        if (ExploreSitesBridge.isEnabled(variation)) {
             mExploreSectionView = ((ViewStub) findViewById(R.id.explore_sites_stub)).inflate();
-        } else if (ExploreSitesBridge.getVariation() == ExploreSitesVariation.EXPERIMENT) {
+        } else if (ExploreSitesBridge.isExperimental(variation)) {
             ViewStub exploreStub = findViewById(R.id.explore_sites_stub);
             exploreStub.setLayoutResource(R.layout.experimental_explore_sites_section);
             mExploreSectionView = exploreStub.inflate();
@@ -244,19 +244,12 @@
         mSiteSectionViewHolder.bindDataSource(mTileGroup, tileRenderer);
 
         int variation = ExploreSitesBridge.getVariation();
-        switch (variation) {
-            case ExploreSitesVariation.ENABLED: // fall-through
-            case ExploreSitesVariation.PERSONALIZED:
-                mExploreSection = new ExploreSitesSection(mExploreSectionView, profile,
-                        mManager.getNavigationDelegate(),
-                        SuggestionsConfig.getTileStyle(mUiConfig));
-                break;
-            case ExploreSitesVariation.EXPERIMENT:
-                mExploreSection = new ExperimentalExploreSitesSection(
-                        mExploreSectionView, profile, mManager.getNavigationDelegate());
-                break;
-            case ExploreSitesVariation.DISABLED: // fall-through
-            default: // do nothing.
+        if (ExploreSitesBridge.isEnabled(variation)) {
+            mExploreSection = new ExploreSitesSection(mExploreSectionView, profile,
+                    mManager.getNavigationDelegate(), SuggestionsConfig.getTileStyle(mUiConfig));
+        } else if (ExploreSitesBridge.isExperimental(variation)) {
+            mExploreSection = new ExperimentalExploreSitesSection(
+                    mExploreSectionView, profile, mManager.getNavigationDelegate());
         }
 
         mSearchProviderLogoView = findViewById(R.id.search_provider_logo);
@@ -476,7 +469,7 @@
         ViewGroup.LayoutParams layoutParams = mSiteSectionView.getLayoutParams();
         layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
         // If the explore sites section exists, then space it more closely.
-        if (ExploreSitesBridge.getVariation() == ExploreSitesVariation.ENABLED) {
+        if (ExploreSitesBridge.isEnabled(ExploreSitesBridge.getVariation())) {
             ((MarginLayoutParams) layoutParams).bottomMargin =
                     getResources().getDimensionPixelOffset(
                             R.dimen.tile_grid_layout_vertical_spacing);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java
index 765dc829..e842949 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsManager.java
@@ -120,7 +120,7 @@
 
         if (ChromeFeatureList.isInitialized()
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.FCM_INVALIDATIONS)) {
-            SessionsInvalidationManager.get(mForeignSessionHelper).onRecentTabsPageOpened();
+            SessionsInvalidationManager.get(mProfile).onRecentTabsPageOpened();
         } else {
             InvalidationController.get().onRecentTabsPageOpened();
         }
@@ -153,7 +153,7 @@
 
         if (ChromeFeatureList.isInitialized()
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.FCM_INVALIDATIONS)) {
-            SessionsInvalidationManager.get(mForeignSessionHelper).onRecentTabsPageClosed();
+            SessionsInvalidationManager.get(mProfile).onRecentTabsPageClosed();
         } else {
             InvalidationController.get().onRecentTabsPageClosed();
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
index a6879a0..a84e064 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
@@ -135,6 +135,7 @@
     /**
      * @return True if offline pages sharing is enabled.
      */
+    @VisibleForTesting
     public static boolean isPageSharingEnabled() {
         return nativeIsPageSharingEnabled();
     }
@@ -172,6 +173,39 @@
     }
 
     /**
+     * Gets all available offline pages, returning results via the provided callback.
+     *
+     * @param callback The callback to run when the operation completes.
+     */
+    @VisibleForTesting
+    public void getAllPages(final Callback<List<OfflinePageItem>> callback) {
+        List<OfflinePageItem> result = new ArrayList<>();
+        nativeGetAllPages(mNativeOfflinePageBridge, result, callback);
+    }
+
+    /**
+     * Gets the offline pages associated with the provided client IDs.
+     *
+     * @param clientIds Client's IDs associated with offline pages.
+     * @return A list of {@link OfflinePageItem} matching the provided IDs, or an empty list if none
+     * exist.
+     */
+    @VisibleForTesting
+    public void getPagesByClientIds(
+            final List<ClientId> clientIds, final Callback<List<OfflinePageItem>> callback) {
+        String[] namespaces = new String[clientIds.size()];
+        String[] ids = new String[clientIds.size()];
+
+        for (int i = 0; i < clientIds.size(); i++) {
+            namespaces[i] = clientIds.get(i).getNamespace();
+            ids[i] = clientIds.get(i).getId();
+        }
+
+        List<OfflinePageItem> result = new ArrayList<>();
+        nativeGetPagesByClientId(mNativeOfflinePageBridge, result, namespaces, ids, callback);
+    }
+
+    /**
      * Gets the offline pages associated with the provided origin.
      * @param origin The JSON-like string of the app's package name and encrypted signature hash.
      * @return A list of {@link OfflinePageItem} matching the provided origin, or an empty
@@ -195,6 +229,16 @@
         nativeGetPagesByNamespace(mNativeOfflinePageBridge, result, namespace, callback);
     }
 
+    /**
+     * Gets all the URLs in the request queue.
+     *
+     * @return A list of {@link SavePageRequest} representing all the queued requests.
+     */
+    @VisibleForTesting
+    public void getRequestsInQueue(Callback<SavePageRequest[]> callback) {
+        nativeGetRequestsInQueue(mNativeOfflinePageBridge, callback);
+    }
+
     private static class RequestsRemovedCallback {
         private Callback<List<RequestRemovedResult>> mCallback;
 
@@ -796,14 +840,23 @@
     private static native boolean nativeIsPageSharingEnabled();
     private static native boolean nativeCanSavePage(String url);
     private static native OfflinePageBridge nativeGetOfflinePageBridgeForProfile(Profile profile);
-
+    @VisibleForTesting
+    native void nativeGetAllPages(long nativeOfflinePageBridge, List<OfflinePageItem> offlinePages,
+            final Callback<List<OfflinePageItem>> callback);
     private native void nativeWillCloseTab(long nativeOfflinePageBridge, WebContents webContents);
 
-    private native void nativeRemoveRequestsFromQueue(
+    @VisibleForTesting
+    native void nativeGetRequestsInQueue(
+            long nativeOfflinePageBridge, Callback<SavePageRequest[]> callback);
+    @VisibleForTesting
+    native void nativeRemoveRequestsFromQueue(
             long nativeOfflinePageBridge, long[] requestIds, RequestsRemovedCallback callback);
-    private native void nativeGetPageByOfflineId(
+    @VisibleForTesting
+    native void nativeGetPageByOfflineId(
             long nativeOfflinePageBridge, long offlineId, Callback<OfflinePageItem> callback);
-
+    @VisibleForTesting
+    native void nativeGetPagesByClientId(long nativeOfflinePageBridge, List<OfflinePageItem> result,
+            String[] namespaces, String[] ids, Callback<List<OfflinePageItem>> callback);
     native void nativeGetPagesByRequestOrigin(long nativeOfflinePageBridge,
             List<OfflinePageItem> result, String requestOrigin,
             Callback<List<OfflinePageItem>> callback);
@@ -818,8 +871,10 @@
     @VisibleForTesting
     native void nativeDeletePagesByOfflineId(
             long nativeOfflinePageBridge, long[] offlineIds, Callback<Integer> callback);
+    @VisibleForTesting
     private native void nativePublishInternalPageByOfflineId(
             long nativeOfflinePageBridge, long offlineId, Callback<String> publishedCallback);
+    @VisibleForTesting
     private native void nativePublishInternalPageByGuid(
             long nativeOfflinePageBridge, String guid, Callback<String> publishedCallback);
     private native void nativeSelectPageForOnlineUrl(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
index bbaa8be6..b090be5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.webapps;
 
-import static org.chromium.webapk.lib.common.WebApkConstants.WEBAPK_PACKAGE_PREFIX;
-
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.SystemClock;
@@ -16,8 +14,6 @@
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.metrics.WebApkSplashscreenMetrics;
 import org.chromium.chrome.browser.metrics.WebApkUma;
-import org.chromium.chrome.browser.util.IntentUtils;
-import org.chromium.webapk.lib.common.WebApkConstants;
 
 import java.util.concurrent.TimeUnit;
 
@@ -39,48 +35,6 @@
     @VisibleForTesting
     public static final String STARTUP_UMA_HISTOGRAM_SUFFIX = ".WebApk";
 
-    /** WebAPK first run experience parameters. */
-    public static class FreParams {
-        private final Intent mIntentToLaunchAfterFreComplete;
-        private final String mShortName;
-
-        public FreParams(Intent intentToLaunchAfterFreComplete, String shortName) {
-            mIntentToLaunchAfterFreComplete = intentToLaunchAfterFreComplete;
-            mShortName = shortName;
-        }
-
-        /** Returns the intent launch when the user completes the first run experience. */
-        public Intent getIntentToLaunchAfterFreComplete() {
-            return mIntentToLaunchAfterFreComplete;
-        }
-
-        /** Returns the WebAPK's short name. */
-        public String webApkShortName() {
-            return mShortName;
-        }
-    }
-
-    /**
-     * Generates parameters for the WebAPK first run experience for the given intent. Returns null
-     * if the intent does not launch a WebApkActivity. This method is slow. It makes several
-     * PackageManager calls.
-     */
-    public static FreParams slowGenerateFreParamsIfIntentIsForWebApkActivity(Intent fromIntent) {
-        // Check for intents targeted at WebApkActivity, WebApkActivity0-9 and
-        // SameTaskWebApkActivity.
-        String targetActivityClassName = fromIntent.getComponent().getClassName();
-        if (!targetActivityClassName.startsWith(WebApkActivity.class.getName())
-                && !targetActivityClassName.equals(SameTaskWebApkActivity.class.getName())) {
-            return null;
-        }
-
-        WebApkInfo info = WebApkInfo.create(fromIntent);
-        return (info != null)
-                ? new FreParams(WebappLauncherActivity.createRelaunchWebApkIntent(fromIntent, info),
-                        info.shortName())
-                : null;
-    }
-
     @Override
     public int getActivityType() {
         return ActivityType.WEBAPK;
@@ -103,17 +57,6 @@
     }
 
     @Override
-    public boolean shouldPreferLightweightFre(Intent intent) {
-        // We cannot use getWebApkPackageName() because {@link WebappActivity#preInflationStartup()}
-        // may not have been called yet.
-        String webApkPackageName =
-                IntentUtils.safeGetStringExtra(intent, WebApkConstants.EXTRA_WEBAPK_PACKAGE_NAME);
-
-        // Use the lightweight FRE for unbound WebAPKs.
-        return webApkPackageName != null && !webApkPackageName.startsWith(WEBAPK_PACKAGE_PREFIX);
-    }
-
-    @Override
     public String getNativeClientPackageName() {
         return getWebappInfo().webApkPackageName();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.java
index aab0849..e039bd74 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.java
@@ -21,10 +21,12 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.ShortcutHelper;
 import org.chromium.chrome.browser.ShortcutSource;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
+import org.chromium.chrome.browser.firstrun.FirstRunFlowSequencer;
 import org.chromium.chrome.browser.metrics.LaunchMetrics;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.util.IntentUtils;
@@ -54,6 +56,27 @@
 
     private static final String TAG = "webapps";
 
+    /** WebAPK first run experience parameters. */
+    public static class FreParams {
+        private final Intent mIntentToLaunchAfterFreComplete;
+        private final String mShortName;
+
+        public FreParams(Intent intentToLaunchAfterFreComplete, String shortName) {
+            mIntentToLaunchAfterFreComplete = intentToLaunchAfterFreComplete;
+            mShortName = shortName;
+        }
+
+        /** Returns the intent launch when the user completes the first run experience. */
+        public Intent getIntentToLaunchAfterFreComplete() {
+            return mIntentToLaunchAfterFreComplete;
+        }
+
+        /** Returns the WebAPK's short name. */
+        public String webApkShortName() {
+            return mShortName;
+        }
+    }
+
     /** Creates intent to relaunch WebAPK. */
     public static Intent createRelaunchWebApkIntent(Intent sourceIntent, WebApkInfo webApkInfo) {
         assert webApkInfo != null;
@@ -93,6 +116,27 @@
         return false;
     }
 
+    /**
+     * Generates parameters for the WebAPK first run experience for the given intent. Returns null
+     * if the intent does not launch either a WebappLauncherActivity or a WebApkActivity. This
+     * method is slow. It makes several PackageManager calls.
+     */
+    public static FreParams slowGenerateFreParamsIfIntentIsForWebApk(Intent fromIntent) {
+        // Check for intents targeted at WebApkActivity, WebApkActivity0-9,
+        // SameTaskWebApkActivity and WebappLauncherActivity.
+        String targetActivityClassName = fromIntent.getComponent().getClassName();
+        if (!targetActivityClassName.startsWith(WebApkActivity.class.getName())
+                && !targetActivityClassName.equals(SameTaskWebApkActivity.class.getName())
+                && !targetActivityClassName.equals(WebappLauncherActivity.class.getName())) {
+            return null;
+        }
+
+        WebApkInfo info = WebApkInfo.create(fromIntent);
+        return (info != null)
+                ? new FreParams(createRelaunchWebApkIntent(fromIntent, info), info.shortName())
+                : null;
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -107,6 +151,12 @@
             return;
         }
 
+        if (FirstRunFlowSequencer.launch(this, intent, false /* requiresBroadcast */,
+                    shouldPreferLightweightFre(webappInfo))) {
+            ApiCompatibilityUtils.finishAndRemoveTask(this);
+            return;
+        }
+
         if (shouldLaunchWebapp(intent, webappInfo)) {
             launchWebapp(this, intent, webappInfo, createTimestamp);
             return;
@@ -115,6 +165,17 @@
         launchInTab(this, intent, webappInfo);
     }
 
+    /**
+     * Returns whether to prefer the Lightweight First Run Experience instead of the
+     * non-Lightweight First Run Experience when launching the given webapp.
+     */
+    private static boolean shouldPreferLightweightFre(WebappInfo webappInfo) {
+        // Use lightweight FRE for unbound WebAPKs.
+        return webappInfo != null && webappInfo.webApkPackageName() != null
+                && !webappInfo.webApkPackageName().startsWith(
+                        WebApkConstants.WEBAPK_PACKAGE_PREFIX);
+    }
+
     private static boolean shouldLaunchWebapp(Intent intent, WebappInfo webappInfo) {
         // {@link WebApkInfo#create()} and {@link WebappInfo#create()} return null if the intent
         // does not specify required values such as the uri.
@@ -354,7 +415,8 @@
     }
 
     /** Tries to create WebappInfo/WebApkInfo for the intent. */
-    private static WebappInfo tryCreateWebappInfo(Intent intent) {
+    @VisibleForTesting
+    static WebappInfo tryCreateWebappInfo(Intent intent) {
         // Builds WebApkInfo for the intent if the WebAPK package specified in the intent is a valid
         // WebAPK and the URL specified in the intent can be fulfilled by the WebAPK.
         String webApkPackage =
@@ -366,6 +428,9 @@
             return WebApkInfo.create(intent);
         }
 
+        // This is not a valid WebAPK. Modify the intent so that WebApkInfo#create() returns null.
+        intent.removeExtra(WebApkConstants.EXTRA_WEBAPK_PACKAGE_NAME);
+
         Log.d(TAG, "%s is either not a WebAPK or %s is not within the WebAPK's scope",
                 webApkPackage, url);
         return WebappInfo.create(intent);
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 1145e05..2baeba1 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -3002,7 +3002,9 @@
         No bookmarks
       </message>
       <message name="IDS_BOOKMARKS_COUNT" desc="Text describing the number of bookmarks in a bookmark folder [ICU Syntax]">
-        {BOOKMARKS_COUNT, plural, =1 {%1$d bookmark} other {%1$d bookmarks}}
+        {BOOKMARKS_COUNT, plural,
+          =1 {<ph name="BOOKMARKS_COUNT_ONE">%1$d<ex>1</ex></ph> bookmark}
+          other {<ph name="BOOKMARKS_COUNT_MANY">%1$d<ex>8</ex></ph> bookmarks}}
       </message>
       <message name="IDS_BOOKMARK_PAGE_SAVED" desc="App-based message shown after user adds a new bookmark. [CHAR-LIMIT=32]">
         Bookmarked in <ph name="PRODUCT_NAME">%1$s<ex>Chrome</ex></ph>
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 6fe74b5..27ecc04 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -259,7 +259,9 @@
   "java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchQuickActionControl.java",
   "java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchTermControl.java",
   "java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabBarControl.java",
+  "java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCaptionControl.java",
   "java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java",
+  "java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabTitleControl.java",
   "java/src/org/chromium/chrome/browser/compositor/layouts/ChromeAnimation.java",
   "java/src/org/chromium/chrome/browser/compositor/layouts/EmptyOverviewModeObserver.java",
   "java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java",
@@ -2541,6 +2543,7 @@
   "junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappDirectoryManagerTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappDisclosureSnackbarControllerTest.java",
+  "junit/src/org/chromium/chrome/browser/webapps/WebappLauncherActivityTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappInfoTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java",
   "junit/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetSwipeDetectorTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java
index 2aa2ad4..b12588b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeTest.java
@@ -50,7 +50,6 @@
 import java.util.Set;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
@@ -131,7 +130,7 @@
     @SmallTest
     @RetryOnFailure
     public void testLoadOfflinePagesWhenEmpty() throws Exception {
-        List<OfflinePageItem> offlinePages = OfflineTestUtil.getAllPages();
+        List<OfflinePageItem> offlinePages = getAllPages();
         Assert.assertEquals("Offline pages count incorrect.", 0, offlinePages.size());
     }
 
@@ -143,7 +142,7 @@
     public void testAddOfflinePageAndLoad() throws Exception {
         mActivityTestRule.loadUrl(mTestPage);
         savePage(SavePageResult.SUCCESS, mTestPage);
-        List<OfflinePageItem> allPages = OfflineTestUtil.getAllPages();
+        List<OfflinePageItem> allPages = getAllPages();
         OfflinePageItem offlinePage = allPages.get(0);
         Assert.assertEquals("Offline pages count incorrect.", 1, allPages.size());
         Assert.assertEquals("Offline page item url incorrect.", mTestPage, offlinePage.getUrl());
@@ -155,11 +154,10 @@
     public void testGetPageByBookmarkId() throws Exception {
         mActivityTestRule.loadUrl(mTestPage);
         savePage(SavePageResult.SUCCESS, mTestPage);
-        OfflinePageItem offlinePage = OfflineTestUtil.getPageByClientId(TEST_CLIENT_ID);
+        OfflinePageItem offlinePage = getPageByClientId(TEST_CLIENT_ID);
         Assert.assertEquals("Offline page item url incorrect.", mTestPage, offlinePage.getUrl());
         Assert.assertNull("Offline page is not supposed to exist",
-                OfflineTestUtil.getPageByClientId(
-                        new ClientId(OfflinePageBridge.BOOKMARK_NAMESPACE, "-42")));
+                getPageByClientId(new ClientId(OfflinePageBridge.BOOKMARK_NAMESPACE, "-42")));
     }
 
     @Test
@@ -172,10 +170,10 @@
         mActivityTestRule.loadUrl(mTestPage);
         savePage(SavePageResult.SUCCESS, mTestPage);
         Assert.assertNotNull("Offline page should be available, but it is not.",
-                OfflineTestUtil.getPageByClientId(TEST_CLIENT_ID));
+                getPageByClientId(TEST_CLIENT_ID));
         deletePage(TEST_CLIENT_ID, DeletePageResult.SUCCESS);
         Assert.assertNull("Offline page should be gone, but it is available.",
-                OfflineTestUtil.getPageByClientId(TEST_CLIENT_ID));
+                getPageByClientId(TEST_CLIENT_ID));
     }
 
     @Test
@@ -200,7 +198,7 @@
         String url = "https://www.google.com/";
         String namespace = "custom_tabs";
         savePageLater(url, namespace);
-        SavePageRequest[] requests = OfflineTestUtil.getRequestsInQueue();
+        SavePageRequest[] requests = getRequestsInQueue();
         Assert.assertEquals(1, requests.length);
         Assert.assertEquals(namespace, requests[0].getClientId().getNamespace());
         Assert.assertEquals(url, requests[0].getUrl());
@@ -208,7 +206,7 @@
         String url2 = "https://mail.google.com/";
         String namespace2 = "last_n";
         savePageLater(url2, namespace2);
-        requests = OfflineTestUtil.getRequestsInQueue();
+        requests = getRequestsInQueue();
         Assert.assertEquals(2, requests.length);
 
         HashSet<String> expectedUrls = new HashSet<>();
@@ -247,7 +245,7 @@
         String namespace2 = "last_n";
         savePageLater(url2, namespace2);
 
-        SavePageRequest[] requests = OfflineTestUtil.getRequestsInQueue();
+        SavePageRequest[] requests = getRequestsInQueue();
         Assert.assertEquals(2, requests.length);
 
         List<Long> requestsToRemove = new ArrayList<>();
@@ -257,7 +255,7 @@
                 removeRequestsFromQueue(requestsToRemove);
         Assert.assertEquals(requests[1].getRequestId(), removed.get(0).getRequestId());
         Assert.assertEquals(UpdateRequestResult.SUCCESS, removed.get(0).getUpdateRequestResult());
-        SavePageRequest[] remaining = OfflineTestUtil.getRequestsInQueue();
+        SavePageRequest[] remaining = getRequestsInQueue();
         Assert.assertEquals(1, remaining.length);
 
         Assert.assertEquals(requests[0].getRequestId(), remaining[0].getRequestId());
@@ -373,7 +371,7 @@
         Assert.assertTrue("Semaphore acquire failed. Timed out.",
                 semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
-        List<OfflinePageItem> pages = OfflineTestUtil.getAllPages();
+        List<OfflinePageItem> pages = getAllPages();
         Assert.assertEquals(originString, pages.get(0).getRequestOrigin());
     }
 
@@ -406,7 +404,7 @@
 
         Assert.assertTrue("Semaphore acquire failed. Timed out.",
                 semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        List<OfflinePageItem> pages = OfflineTestUtil.getAllPages();
+        List<OfflinePageItem> pages = getAllPages();
         Assert.assertEquals(originString, pages.get(0).getRequestOrigin());
     }
 
@@ -416,7 +414,7 @@
     public void testSavePageNoOrigin() throws Exception {
         mActivityTestRule.loadUrl(mTestPage);
         savePage(SavePageResult.SUCCESS, mTestPage);
-        List<OfflinePageItem> pages = OfflineTestUtil.getAllPages();
+        List<OfflinePageItem> pages = getAllPages();
         Assert.assertEquals("", pages.get(0).getRequestOrigin());
     }
 
@@ -428,7 +426,7 @@
     public void testGetLoadUrlParamsForOpeningMhtmlFileUrl() throws Exception {
         mActivityTestRule.loadUrl(mTestPage);
         savePage(SavePageResult.SUCCESS, mTestPage);
-        List<OfflinePageItem> allPages = OfflineTestUtil.getAllPages();
+        List<OfflinePageItem> allPages = getAllPages();
         Assert.assertEquals(1, allPages.size());
         OfflinePageItem offlinePage = allPages.get(0);
         File archiveFile = new File(offlinePage.getFilePath());
@@ -558,6 +556,25 @@
         Assert.assertEquals("Delete result incorrect.", expectedResult, deletePageResultRef.get());
     }
 
+    private List<OfflinePageItem> getAllPages() throws InterruptedException {
+        final List<OfflinePageItem> result = new ArrayList<OfflinePageItem>();
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.getAllPages(new Callback<List<OfflinePageItem>>() {
+                    @Override
+                    public void onResult(List<OfflinePageItem> pages) {
+                        result.addAll(pages);
+                        semaphore.release();
+                    }
+                });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return result;
+    }
+
     private List<OfflinePageItem> getPagesByNamespace(final String namespace)
             throws InterruptedException {
         final List<OfflinePageItem> result = new ArrayList<OfflinePageItem>();
@@ -588,10 +605,49 @@
         });
     }
 
+    private OfflinePageItem getPageByClientId(ClientId clientId) throws InterruptedException {
+        final OfflinePageItem[] result = {null};
+        final Semaphore semaphore = new Semaphore(0);
+        final List<ClientId> clientIdList = new ArrayList<>();
+        clientIdList.add(clientId);
+
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.getPagesByClientIds(
+                        clientIdList, new Callback<List<OfflinePageItem>>() {
+                            @Override
+                            public void onResult(List<OfflinePageItem> items) {
+                                if (!items.isEmpty()) {
+                                    result[0] = items.get(0);
+                                }
+                                semaphore.release();
+                            }
+                        });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return result[0];
+    }
+
     private Set<String> getUrlsExistOfflineFromSet(final Set<String> query)
-            throws InterruptedException, TimeoutException {
+            throws InterruptedException {
         final Set<String> result = new HashSet<>();
-        final List<OfflinePageItem> pages = OfflineTestUtil.getAllPages();
+        final List<OfflinePageItem> pages = new ArrayList<>();
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.getAllPages(new Callback<List<OfflinePageItem>>() {
+                    @Override
+                    public void onResult(List<OfflinePageItem> offlinePages) {
+                        pages.addAll(offlinePages);
+                        semaphore.release();
+                    }
+                });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         for (String url : query) {
             for (OfflinePageItem page : pages) {
                 if (url.equals(page.getUrl())) {
@@ -623,6 +679,25 @@
         Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
+    private SavePageRequest[] getRequestsInQueue() throws InterruptedException {
+        final AtomicReference<SavePageRequest[]> ref = new AtomicReference<>();
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.getRequestsInQueue(new Callback<SavePageRequest[]>() {
+                    @Override
+                    public void onResult(SavePageRequest[] requestsInQueue) {
+                        ref.set(requestsInQueue);
+                        semaphore.release();
+                    }
+                });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return ref.get();
+    }
+
     private List<OfflinePageBridge.RequestRemovedResult> removeRequestsFromQueue(
             final List<Long> requestsToRemove) throws InterruptedException {
         final AtomicReference<List<OfflinePageBridge.RequestRemovedResult>> ref =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageRequestTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageRequestTest.java
index 49985c9..4d517b2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageRequestTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageRequestTest.java
@@ -18,7 +18,9 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.offlinepages.OfflinePageBridge.OfflinePageModelObserver;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge.SavePageCallback;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -48,6 +50,7 @@
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
+        final Semaphore semaphore = new Semaphore(0);
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
@@ -55,9 +58,23 @@
                     NetworkChangeNotifier.init();
                 }
                 NetworkChangeNotifier.forceConnectivityState(true);
+
+                Profile profile = Profile.getLastUsedProfile();
+                mOfflinePageBridge = OfflinePageBridge.getForProfile(profile);
+                if (mOfflinePageBridge.isOfflinePageModelLoaded()) {
+                    semaphore.release();
+                } else {
+                    mOfflinePageBridge.addObserver(new OfflinePageModelObserver() {
+                        @Override
+                        public void offlinePageModelLoaded() {
+                            semaphore.release();
+                            mOfflinePageBridge.removeObserver(this);
+                        }
+                    });
+                }
             }
         });
-        mOfflinePageBridge = OfflineTestUtil.getOfflinePageBridge();
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java
index 1aa75406..ed0c917c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java
@@ -405,10 +405,24 @@
     /**
      * Get saved offline pages and align them with the metadata we got from testing.
      */
-    private void loadSavedPages() throws TimeoutException, InterruptedException {
-        for (OfflinePageItem page : OfflineTestUtil.getAllPages()) {
-            mRequestMetadata.get(page.getOfflineId()).mPage = page;
-        }
+    private void loadSavedPages() throws InterruptedException {
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mBridge.getAllPages(new Callback<List<OfflinePageItem>>() {
+                    @Override
+                    public void onResult(List<OfflinePageItem> pages) {
+                        for (OfflinePageItem page : pages) {
+                            mRequestMetadata.get(page.getOfflineId()).mPage = page;
+                        }
+                        semaphore.release();
+                    }
+                });
+            }
+        });
+        checkTrue(semaphore.tryAcquire(GET_PAGES_TIMEOUT_MS, TimeUnit.MILLISECONDS),
+                "Timed out when getting all offline pages");
     }
 
     private boolean copyToShareableLocation(File src, File dst) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflineTestUtil.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflineTestUtil.java
deleted file mode 100644
index 8e949e6..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflineTestUtil.java
+++ /dev/null
@@ -1,119 +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.offlinepages;
-
-import android.support.annotation.Nullable;
-
-import org.junit.Assert;
-
-import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.test.util.CallbackHelper;
-import org.chromium.chrome.browser.download.items.OfflineContentAggregatorFactory;
-import org.chromium.chrome.browser.offlinepages.OfflinePageBridge.OfflinePageModelObserver;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.components.offline_items_collection.OfflineItem;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/** Test utility functions for OfflinePages. */
-@JNINamespace("offline_pages")
-public class OfflineTestUtil {
-    // Gets all the URLs in the request queue.
-    public static SavePageRequest[] getRequestsInQueue()
-            throws TimeoutException, InterruptedException {
-        final AtomicReference<SavePageRequest[]> result = new AtomicReference<>();
-        final CallbackHelper callbackHelper = new CallbackHelper();
-        ThreadUtils.runOnUiThreadBlocking(() -> {
-            nativeGetRequestsInQueue((SavePageRequest[] requests) -> {
-                result.set(requests);
-                callbackHelper.notifyCalled();
-            });
-        });
-        callbackHelper.waitForCallback(0);
-        return result.get();
-    }
-
-    // Gets all available offline pages.
-    public static List<OfflinePageItem> getAllPages()
-            throws TimeoutException, InterruptedException {
-        final AtomicReference<List<OfflinePageItem>> result =
-                new AtomicReference<List<OfflinePageItem>>();
-        final CallbackHelper callbackHelper = new CallbackHelper();
-        ThreadUtils.runOnUiThreadBlocking(() -> {
-            nativeGetAllPages(new ArrayList<OfflinePageItem>(), (List<OfflinePageItem> items) -> {
-                result.set(items);
-                callbackHelper.notifyCalled();
-            });
-        });
-        callbackHelper.waitForCallback(0);
-        return result.get();
-    }
-
-    // Returns the OfflinePageItem with the given clientId, or null if one doesn't exist.
-    public static @Nullable OfflinePageItem getPageByClientId(ClientId clientId)
-            throws TimeoutException, InterruptedException {
-        ArrayList<OfflinePageItem> result = new ArrayList<OfflinePageItem>();
-        for (OfflinePageItem item : getAllPages()) {
-            if (item.getClientId().equals(clientId)) {
-                return item;
-            }
-        }
-        return null;
-    }
-
-    // Returns all OfflineItems provided by the OfflineContentProvider.
-    public static List<OfflineItem> getOfflineItems()
-            throws TimeoutException, InterruptedException {
-        CallbackHelper finished = new CallbackHelper();
-        final AtomicReference<ArrayList<OfflineItem>> result =
-                new AtomicReference<ArrayList<OfflineItem>>();
-        ThreadUtils.runOnUiThreadBlocking(() -> {
-            OfflineContentAggregatorFactory
-                    .forProfile(Profile.getLastUsedProfile().getOriginalProfile())
-                    .getAllItems(items -> {
-                        result.set(items);
-                        finished.notifyCalled();
-                    });
-        });
-        finished.waitForCallback(0);
-        return result.get();
-    }
-
-    // Waits for the offline model to initialize and returns an OfflinePageBridge.
-    public static OfflinePageBridge getOfflinePageBridge()
-            throws TimeoutException, InterruptedException {
-        final CallbackHelper ready = new CallbackHelper();
-        final AtomicReference<OfflinePageBridge> result = new AtomicReference<OfflinePageBridge>();
-        ThreadUtils.runOnUiThread(() -> {
-            OfflinePageBridge bridge =
-                    OfflinePageBridge.getForProfile(Profile.getLastUsedProfile());
-            if (bridge == null || bridge.isOfflinePageModelLoaded()) {
-                result.set(bridge);
-                ready.notifyCalled();
-                return;
-            }
-            bridge.addObserver(new OfflinePageModelObserver() {
-                @Override
-                public void offlinePageModelLoaded() {
-                    result.set(bridge);
-                    ready.notifyCalled();
-                    bridge.removeObserver(this);
-                }
-            });
-        });
-        ready.waitForCallback(0);
-        Assert.assertTrue(result.get() != null);
-        return result.get();
-    }
-
-    private static native void nativeGetRequestsInQueue(Callback<SavePageRequest[]> callback);
-    private static native void nativeGetAllPages(
-            List<OfflinePageItem> offlinePages, final Callback<List<OfflinePageItem>> callback);
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/RecentTabsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/RecentTabsTest.java
index 313f225..61cf5ffb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/RecentTabsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/RecentTabsTest.java
@@ -14,21 +14,26 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.offlinepages.OfflinePageBridge.OfflinePageModelObserver;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.net.NetworkChangeNotifier;
 import org.chromium.net.test.EmbeddedTestServer;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 /** Integration tests for the Last 1 feature of Offline Pages. */
 @RunWith(ChromeJUnit4ClassRunner.class)
@@ -44,6 +49,34 @@
     private EmbeddedTestServer mTestServer;
     private String mTestPage;
 
+    private void initializeBridgeForProfile(final boolean incognitoProfile)
+            throws InterruptedException {
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Profile profile = Profile.getLastUsedProfile();
+                if (incognitoProfile) {
+                    profile = profile.getOffTheRecordProfile();
+                }
+                // Ensure we start in an offline state.
+                mOfflinePageBridge = OfflinePageBridge.getForProfile(profile);
+                if (mOfflinePageBridge == null || mOfflinePageBridge.isOfflinePageModelLoaded()) {
+                    semaphore.release();
+                    return;
+                }
+                mOfflinePageBridge.addObserver(new OfflinePageModelObserver() {
+                    @Override
+                    public void offlinePageModelLoaded() {
+                        semaphore.release();
+                        mOfflinePageBridge.removeObserver(this);
+                    }
+                });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
@@ -58,7 +91,8 @@
             }
         });
 
-        mOfflinePageBridge = OfflineTestUtil.getOfflinePageBridge();
+        initializeBridgeForProfile(false);
+
         mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
         mTestPage = mTestServer.getURL(TEST_PAGE);
     }
@@ -79,7 +113,7 @@
                 new ClientId(OfflinePageBridge.LAST_N_NAMESPACE, Integer.toString(tab.getId()));
 
         // The tab should be foreground and so no snapshot should exist.
-        Assert.assertNull(OfflineTestUtil.getPageByClientId(firstTabClientId));
+        Assert.assertNull(getPageByClientId(firstTabClientId));
 
         // Note, that switching to a new tab must occur after the SnapshotController believes the
         // page quality is good enough.  With the debug flag, the delay after DomContentLoaded is 0
@@ -110,7 +144,7 @@
         TabModelSelector tabModelSelector = tab.getTabModelSelector();
         Assert.assertEquals(tabModelSelector.getCurrentTab(), tab);
         Assert.assertFalse(tab.isHidden());
-        Assert.assertNull(OfflineTestUtil.getPageByClientId(firstTabClientId));
+        Assert.assertNull(getPageByClientId(firstTabClientId));
 
         // The tab model is expected to support pending closures.
         final TabModel tabModel = tabModelSelector.getModelForTabId(tab.getId());
@@ -129,7 +163,7 @@
 
         // Wait a bit and checks that no snapshot was created.
         Thread.sleep(100); // Note: Flakiness potential here.
-        Assert.assertNull(OfflineTestUtil.getPageByClientId(firstTabClientId));
+        Assert.assertNull(getPageByClientId(firstTabClientId));
 
         // Undo the closure and make sure the tab is again the current one on foreground.
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@@ -151,9 +185,49 @@
         waitForPageWithClientId(firstTabClientId);
     }
 
-    private void waitForPageWithClientId(final ClientId clientId)
-            throws TimeoutException, InterruptedException {
-        CriteriaHelper.pollInstrumentationThread(
-                () -> { return OfflineTestUtil.getPageByClientId(clientId) != null; });
+    private void waitForPageWithClientId(final ClientId clientId) throws InterruptedException {
+        if (getPageByClientId(clientId) != null) return;
+
+        final Semaphore semaphore = new Semaphore(0);
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.addObserver(new OfflinePageModelObserver() {
+                    @Override
+                    public void offlinePageAdded(OfflinePageItem newPage) {
+                        if (newPage.getClientId().equals(clientId)) {
+                            mOfflinePageBridge.removeObserver(this);
+                            semaphore.release();
+                        }
+                    }
+                });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    private OfflinePageItem getPageByClientId(ClientId clientId) throws InterruptedException {
+        final OfflinePageItem[] result = {null};
+        final Semaphore semaphore = new Semaphore(0);
+        final List<ClientId> clientIdList = new ArrayList<>();
+        clientIdList.add(clientId);
+
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOfflinePageBridge.getPagesByClientIds(
+                        clientIdList, new Callback<List<OfflinePageItem>>() {
+                            @Override
+                            public void onResult(List<OfflinePageItem> items) {
+                                if (!items.isEmpty()) {
+                                    result[0] = items.get(0);
+                                }
+                                semaphore.release();
+                            }
+                        });
+            }
+        });
+        Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return result[0];
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchFeedFlowTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchFeedFlowTest.java
index 4f2097ef..4432e51f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchFeedFlowTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchFeedFlowTest.java
@@ -34,7 +34,6 @@
 import org.chromium.chrome.browser.feed.TestNetworkClient;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
 import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
-import org.chromium.chrome.browser.offlinepages.OfflineTestUtil;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.test.ChromeActivityTestRule;
@@ -203,12 +202,20 @@
     }
 
     private OfflineItem findItemByUrl(String url) throws InterruptedException, TimeoutException {
-        for (OfflineItem item : OfflineTestUtil.getOfflineItems()) {
-            if (item.pageUrl.equals(URL1)) {
-                return item;
-            }
-        }
-        return null;
+        CallbackHelper finished = new CallbackHelper();
+        final AtomicReference<OfflineItem> result = new AtomicReference<OfflineItem>();
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            offlineContentProvider().getAllItems(items -> {
+                for (OfflineItem item : items) {
+                    if (item.pageUrl.equals(URL1)) {
+                        result.set(item);
+                    }
+                }
+                finished.notifyCalled();
+            });
+        });
+        finished.waitForCallback(0);
+        return result.get();
     }
 
     private Bitmap findVisuals(ContentId id) throws InterruptedException, TimeoutException {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationUnitTest.java
index e33017af..ca98287 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationUnitTest.java
@@ -12,7 +12,6 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.support.customtabs.CustomTabsIntent;
-import android.text.TextUtils;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -33,9 +32,6 @@
 import org.chromium.chrome.browser.ShortcutHelper;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.searchwidget.SearchActivity;
-import org.chromium.chrome.browser.webapps.SameTaskWebApkActivity;
-import org.chromium.chrome.browser.webapps.WebApkActivity;
-import org.chromium.chrome.browser.webapps.WebApkActivity0;
 import org.chromium.chrome.browser.webapps.WebappLauncherActivity;
 import org.chromium.webapk.lib.client.WebApkValidator;
 import org.chromium.webapk.lib.common.WebApkConstants;
@@ -76,11 +72,6 @@
         return false;
     }
 
-    /** Checks whether the intent has the provided action. */
-    private boolean checkIntentHasAction(Intent intent, String action) {
-        return intent != null && TextUtils.equals(intent.getAction(), action);
-    }
-
     /**
      * Checks that intent is either for {@link FirstRunActivity} or
      * {@link TabbedModeFirstRunActivity}.
@@ -187,15 +178,9 @@
         Robolectric.buildActivity(WebappLauncherActivity.class, intent).create();
 
         Intent launchedIntent = mShadowApplication.getNextStartedActivity();
-        while (checkIntentHasAction(launchedIntent, WebApkConstants.ACTION_SHOW_SPLASH)
-                || checkIntentComponentClassOneOf(launchedIntent,
-                        new Class[] {WebApkActivity.class, WebApkActivity0.class,
-                                SameTaskWebApkActivity.class})) {
-            if (launchedIntent.getComponent() != null) {
-                buildActivityWithClassNameFromIntent(launchedIntent);
-            }
-            // Get next intent even if we did not build an activity to deal with the case of
-            // several activities having been started simultaneously.
+        while (checkIntentComponentClassOneOf(
+                launchedIntent, new Class[] {WebappLauncherActivity.class})) {
+            buildActivityWithClassNameFromIntent(launchedIntent);
             launchedIntent = mShadowApplication.getNextStartedActivity();
         }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManagerTest.java
index 30bc041..c54b7ee 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManagerTest.java
@@ -23,8 +23,8 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.ntp.ForeignSessionHelper;
+import org.chromium.chrome.browser.profiles.Profile;
 
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -37,6 +37,9 @@
     private ResumableDelayedTaskRunner mResumableDelayedTaskRunner;
 
     @Mock
+    private Profile mProfile;
+
+    @Mock
     private ForeignSessionHelper mForeignSessionHelper;
 
     private Activity mActivity;
@@ -56,7 +59,7 @@
         final AtomicBoolean listenerCallbackCalled = new AtomicBoolean();
 
         // Create instance.
-        new SessionsInvalidationManager(mForeignSessionHelper, mResumableDelayedTaskRunner) {
+        new SessionsInvalidationManager(mProfile, mResumableDelayedTaskRunner) {
             @Override
             public void onApplicationStateChange(int newState) {
                 listenerCallbackCalled.set(true);
@@ -72,46 +75,12 @@
     }
 
     /**
-     * Test that sessions invalidations state with opening/closing recent pages.
-     */
-    @Test
-    public void testIsSessionInvalidationsEnabled() {
-        SessionsInvalidationManager manager =
-                SessionsInvalidationManager.get(mForeignSessionHelper);
-        assertFalse(manager.isSessionInvalidationsEnabled());
-
-        manager.onRecentTabsPageOpened();
-        Robolectric.getForegroundThreadScheduler().advanceBy(
-                SessionsInvalidationManager.REGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS,
-                TimeUnit.MILLISECONDS);
-        // 1 recent tabs page.
-        assertTrue(manager.isSessionInvalidationsEnabled());
-        verify(mForeignSessionHelper).setInvalidationsForSessionsEnabled(/*enabled=*/true);
-
-        manager.onRecentTabsPageOpened();
-        // 2 recent tabs pages.
-        assertTrue(manager.isSessionInvalidationsEnabled());
-
-        manager.onRecentTabsPageClosed();
-        // 1 recent tabs page.
-        assertTrue(manager.isSessionInvalidationsEnabled());
-
-        manager.onRecentTabsPageClosed();
-        Robolectric.getForegroundThreadScheduler().advanceBy(
-                SessionsInvalidationManager.UNREGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS,
-                TimeUnit.MILLISECONDS);
-        // 0 recent tabs page.
-        assertFalse(manager.isSessionInvalidationsEnabled());
-        verify(mForeignSessionHelper).setInvalidationsForSessionsEnabled(/*enabled=*/false);
-    }
-
-    /**
      * Test that timer pauses when the application goes to the background.
      */
     @Test
     public void testTimerPausesWhenTheApplicationPauses() {
         SessionsInvalidationManager manager =
-                new SessionsInvalidationManager(mForeignSessionHelper, mResumableDelayedTaskRunner);
+                new SessionsInvalidationManager(mProfile, mResumableDelayedTaskRunner);
 
         manager.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
         verify(mResumableDelayedTaskRunner).resume();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
index 3840dd8..5c7060a2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java
@@ -118,6 +118,80 @@
     }
 
     /**
+     * Tests OfflinePageBridge#GetAllPages() callback when there are no pages.
+     */
+    @Test
+    @Feature({"OfflinePages"})
+    public void testGetAllPages_listOfPagesEmpty() {
+        final int itemCount = 0;
+
+        answerNativeGetAllPages(itemCount);
+        Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
+        mBridge.getAllPages(callback);
+
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        verify(callback, times(1)).onResult(itemList);
+    }
+
+    /**
+     * Tests OfflinePageBridge#GetAllPages() callback when there are pages.
+     */
+    @Test
+    @Feature({"OfflinePages"})
+    public void testGetAllPages_listOfPagesNonEmpty() {
+        final int itemCount = 2;
+
+        answerNativeGetAllPages(itemCount);
+        Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
+        mBridge.getAllPages(callback);
+
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        verify(callback, times(1)).onResult(itemList);
+    }
+
+    /**
+     * Tests OfflinePageBridge#GetPagesByClientIds() callback when there are no pages.
+     */
+    @Test
+    @Feature({"OfflinePages"})
+    public void testGetPagesByClientIds_listOfClientIdsEmpty() {
+        final int itemCount = 0;
+
+        answerGetPagesByClientIds(itemCount);
+        Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
+        ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
+        List<ClientId> list = new ArrayList<>();
+        mBridge.getPagesByClientIds(list, callback);
+
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        verify(callback, times(1)).onResult(itemList);
+    }
+
+    /**
+     * Tests OfflinePageBridge#GetPagesByClientIds() callback when there are pages.
+     */
+    @Test
+    @Feature({"OfflinePages"})
+    public void testGetPagesByClientIds() {
+        final int itemCount = 2;
+
+        answerGetPagesByClientIds(itemCount);
+        Callback<List<OfflinePageItem>> callback = createMultipleItemCallback(itemCount);
+        ClientId secondClientId = new ClientId(TEST_NAMESPACE, "id number two");
+        List<ClientId> list = new ArrayList<>();
+        list.add(TEST_CLIENT_ID);
+        list.add(secondClientId);
+        mBridge.getPagesByClientIds(list, callback);
+
+        List<OfflinePageItem> itemList = new ArrayList<OfflinePageItem>();
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        itemList.add(TEST_OFFLINE_PAGE_ITEM);
+        verify(callback, times(1)).onResult(itemList);
+    }
+
+    /**
      * Tests OfflinePageBridge#DeletePagesByClientIds() callback when there are no pages.
      */
     @Test
@@ -222,6 +296,50 @@
         });
     }
 
+    private void answerNativeGetAllPages(final int itemCount) {
+        Answer<Void> answer = new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List<OfflinePageItem> result = mResultArgument.getValue();
+                for (int i = 0; i < itemCount; i++) {
+                    result.add(TEST_OFFLINE_PAGE_ITEM);
+                }
+
+                mCallbackArgument.getValue().onResult(result);
+
+                return null;
+            }
+        };
+        doAnswer(answer).when(mBridge).nativeGetAllPages(
+                anyLong(), mResultArgument.capture(), mCallbackArgument.capture());
+    }
+
+    private void answerGetPagesByClientIds(final int itemCount) {
+        Answer<Void> answer = new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List<OfflinePageItem> result = mResultArgument.getValue();
+                String[] namespaces = mNamespacesArgument.getValue();
+                String[] ids = mIdsArgument.getValue();
+
+                assertEquals(namespaces.length, itemCount);
+                assertEquals(ids.length, itemCount);
+
+                for (int i = 0; i < itemCount; i++) {
+                    result.add(TEST_OFFLINE_PAGE_ITEM);
+                }
+
+                mCallbackArgument.getValue().onResult(result);
+
+                return null;
+            }
+        };
+
+        doAnswer(answer).when(mBridge).nativeGetPagesByClientId(anyLong(),
+                mResultArgument.capture(), mNamespacesArgument.capture(), mIdsArgument.capture(),
+                mCallbackArgument.capture());
+    }
+
     private void answerDeletePagesByOfflineIds(final int itemCount) {
         Answer<Void> answer = new Answer<Void>() {
             @Override
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappLauncherActivityTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappLauncherActivityTest.java
new file mode 100644
index 0000000..5316235
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappLauncherActivityTest.java
@@ -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.
+
+package org.chromium.chrome.browser.webapps;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+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.ShortcutHelper;
+import org.chromium.webapk.lib.common.WebApkConstants;
+import org.chromium.webapk.lib.common.WebApkMetaDataKeys;
+import org.chromium.webapk.test.WebApkTestHelper;
+
+/** JUnit test for WebappLauncherActivity. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class WebappLauncherActivityTest {
+    private static final String WEBAPK_PACKAGE_NAME = "org.chromium.webapk.test_package";
+    private static final String START_URL = "https://www.google.com/scope/a_is_for_apple";
+
+    /**
+     * Test that WebappLauncherActivity#tryCreateWebappInfo() modifies the passed-in intent so
+     * that WebApkInfo#create() returns null if the intent does not refer to a valid
+     * WebAPK.
+     */
+    @Test
+    public void tryCreateWebappInfoAltersIntentIfNotValidWebApk() {
+        Bundle bundle = new Bundle();
+        bundle.putString(WebApkMetaDataKeys.START_URL, START_URL);
+        WebApkTestHelper.registerWebApkWithMetaData(
+                WEBAPK_PACKAGE_NAME, bundle, null /* shareTargetMetaData */);
+
+        Intent intent = new Intent();
+        intent.putExtra(WebApkConstants.EXTRA_WEBAPK_PACKAGE_NAME, WEBAPK_PACKAGE_NAME);
+        intent.putExtra(ShortcutHelper.EXTRA_URL, START_URL);
+
+        Assert.assertNotNull(WebApkInfo.create(intent));
+        Assert.assertNull(WebappLauncherActivity.tryCreateWebappInfo(intent));
+        Assert.assertNull(WebApkInfo.create(intent));
+    }
+}
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 508ebff..a75f656d 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -4385,18 +4385,18 @@
       Items not listed here will also be removed, if needed. Learn more about &lt;a href=&quot;<ph name="URL">$1<ex>https://www.google.com/chrome/browser/privacy/whitepaper.html#unwantedsoftware</ex></ph>&quot;&gt;unwanted software protection&lt;/a&gt; in the Chrome privacy white paper.
     </message>
     <message name="IDS_SETTINGS_RESET_CLEANUP_DETAILS_EXTENSIONS" desc="Introduces a bullet list containing the names of extensions to be removed by Chrome.">
-      Extensions:
+      Extensions to be removed:
     </message>
     <message name="IDS_SETTINGS_RESET_CLEANUP_DETAILS_EXTENSION_UNKNOWN" desc="Text for a list item among a list of extensions, where we could not determine the name of the extension being cleaned up.">
       Unknown extension with ID <ph name="EXTENSION_ID">$1<ex>exampleextensionababababcdcdcdcd</ex></ph>
     </message>
-    <message name="IDS_SETTINGS_RESET_CLEANUP_DETAILS_FILES_AND_PROGRAMS" desc="Introduces a bullet list containing the names of files and programs to be removed by Chrome.">
-      Files and programs:
+    <message name="IDS_SETTINGS_RESET_CLEANUP_DETAILS_FILES_AND_PROGRAMS" desc="Introduces a bullet list containing the names of files and programs to be quarantined by Chrome (i.e. removed from the original location, moved and archived in the Quarantine folder).">
+      Files and programs to be quarantined:
     </message>
-    <message name="IDS_SETTINGS_RESET_CLEANUP_DETAILS_ITEMS_TO_BE_REMOVED" desc="Label for an expansion arrow. On expansion, the screen displays a list of files and programs that Chrome will remove. Placeholder can be 2 or more items.">
+    <message name="IDS_SETTINGS_RESET_CLEANUP_DETAILS_ITEMS_TO_BE_REMOVED" desc="Label for an expansion arrow. On expansion, the screen displays a list of files and programs that Chrome will remove or quarantine. Placeholder can be 2 or more items.">
       {NUM_ITEMS, plural,
-        =1 {1 item to be removed}
-        other {# items to be removed}}
+        =1 {1 item}
+        other {# items}}
     </message>
     <message name="IDS_SETTINGS_RESET_CLEANUP_DETAILS_REGISTRY_ENTRIES" desc="Introduces a bullet list containing the names of registry entries to be removed/changed by Chrome.">
       Registry entries to be removed or changed:
diff --git a/chrome/app/theme/default_100_percent/common/alert_small.png b/chrome/app/theme/default_100_percent/common/alert_small.png
deleted file mode 100644
index 342b2dda..0000000
--- a/chrome/app/theme/default_100_percent/common/alert_small.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/app_list_v1_overlay.png b/chrome/app/theme/default_100_percent/common/app_list_v1_overlay.png
deleted file mode 100644
index 8476557..0000000
--- a/chrome/app/theme/default_100_percent/common/app_list_v1_overlay.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/cookies.png b/chrome/app/theme/default_100_percent/common/cookies.png
deleted file mode 100644
index 7ebf43f..0000000
--- a/chrome/app/theme/default_100_percent/common/cookies.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/supervised_user_placeholder.png b/chrome/app/theme/default_100_percent/common/supervised_user_placeholder.png
deleted file mode 100644
index 7a03286..0000000
--- a/chrome/app/theme/default_100_percent/common/supervised_user_placeholder.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_200_percent/common/alert_small.png b/chrome/app/theme/default_200_percent/common/alert_small.png
deleted file mode 100644
index cad7944..0000000
--- a/chrome/app/theme/default_200_percent/common/alert_small.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_200_percent/common/app_list_v1_overlay.png b/chrome/app/theme/default_200_percent/common/app_list_v1_overlay.png
deleted file mode 100644
index 9a6f805..0000000
--- a/chrome/app/theme/default_200_percent/common/app_list_v1_overlay.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_200_percent/common/cookies.png b/chrome/app/theme/default_200_percent/common/cookies.png
deleted file mode 100644
index 467b1ca..0000000
--- a/chrome/app/theme/default_200_percent/common/cookies.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_200_percent/common/supervised_user_placeholder.png b/chrome/app/theme/default_200_percent/common/supervised_user_placeholder.png
deleted file mode 100644
index d0036dcb..0000000
--- a/chrome/app/theme/default_200_percent/common/supervised_user_placeholder.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 5b66242e..872f38af 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -25,9 +25,6 @@
       <!-- KEEP THESE IN ALPHABETICAL ORDER!  DO NOT ADD TO RANDOM PLACES JUST
            BECAUSE YOUR RESOURCES ARE FUNCTIONALLY RELATED OR FALL UNDER THE
            SAME CONDITIONALS. -->
-      <if expr="enable_app_list">
-        <structure type="chrome_scaled_image" name="IDR_APP_LIST_TAB_OVERLAY" file="common/app_list_v1_overlay.png" />
-      </if>
       <if expr="toolkit_views and not is_macosx">
         <structure type="chrome_scaled_image" name="IDR_APP_WINDOW_CLOSE" file="common/app_window_close.png" />
         <structure type="chrome_scaled_image" name="IDR_APP_WINDOW_CLOSE_H" file="common/app_window_close_hover.png" />
@@ -90,7 +87,6 @@
       <if expr="is_win">
         <structure type="chrome_scaled_image" name="IDR_CONFLICT_FAVICON" file="common/favicon_conflicts.png" />
       </if>
-      <structure type="chrome_scaled_image" name="IDR_COOKIES" file="common/cookies.png" />
       <structure type="chrome_scaled_image" name="IDR_COOKIE_STORAGE_ICON" file="common/cookie_storage.png" />
       <if expr="chromeos">
         <structure type="chrome_scaled_image" name="IDR_DEVICE_DISABLED" file="cros/device_disabled.png" />
@@ -265,7 +261,6 @@
         <structure type="chrome_scaled_image" name="IDR_LOGO_AVATAR_CIRCLE_BLUE_COLOR" file="cros/logo_avatar_circle_blue_color.png" />
         <structure type="chrome_scaled_image" name="IDR_LOGO_GOOGLE_COLOR_90" file="cros/logo_google_color_90.png" />
       </if>
-      <structure type="chrome_scaled_image" name="IDR_SUPERVISED_USER_PLACEHOLDER" file="common/supervised_user_placeholder.png" />
       <if expr="is_macosx">
         <structure type="chrome_scaled_image" name="IDR_SWIPE_BACK" file="mac/back_large.png" />
         <structure type="chrome_scaled_image" name="IDR_SWIPE_FORWARD" file="mac/forward_large.png" />
@@ -324,7 +319,6 @@
         <structure type="chrome_scaled_image" name="IDR_USER_IMAGE_CAPTURE" file="cros/snapshot_wide.png" />
         <structure type="chrome_scaled_image" name="IDR_USER_IMAGE_RECYCLE" file="cros/discard_wide.png" />
       </if>
-      <structure type="chrome_scaled_image" name="IDR_WARNING" file="common/alert_small.png" />
       <if expr="not _google_chrome">
         <structure type="chrome_scaled_image" name="IDR_WEBSTORE_ICON" file="chromium/webstore_icon.png" />
         <structure type="chrome_scaled_image" name="IDR_WEBSTORE_ICON_16" file="chromium/webstore_icon_16.png" />
diff --git a/chrome/browser/accessibility/accessibility_extension_api.cc b/chrome/browser/accessibility/accessibility_extension_api.cc
index 33760181..617c0a40 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api.cc
@@ -408,4 +408,18 @@
   return RespondNow(NoArguments());
 }
 
+ExtensionFunction::ResponseAction
+AccessibilityPrivateForwardKeyEventsToSwitchAccessFunction::Run() {
+  std::unique_ptr<accessibility_private::ForwardKeyEventsToSwitchAccess::Params>
+      params =
+          accessibility_private::ForwardKeyEventsToSwitchAccess::Params::Create(
+              *args_);
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  GetAccessibilityController()->ForwardKeyEventsToSwitchAccess(
+      params->should_forward);
+
+  return RespondNow(NoArguments());
+}
+
 #endif  // defined (OS_CHROMEOS)
diff --git a/chrome/browser/accessibility/accessibility_extension_api.h b/chrome/browser/accessibility/accessibility_extension_api.h
index 6489a7d..8f94901 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.h
+++ b/chrome/browser/accessibility/accessibility_extension_api.h
@@ -135,6 +135,17 @@
                              ACCESSIBILITY_PRIVATE_SETSWITCHACCESSMENUSTATE)
 };
 
+// API function that requests that key events be forwarded to the Switch
+// Access extension.
+class AccessibilityPrivateForwardKeyEventsToSwitchAccessFunction
+    : public UIThreadExtensionFunction {
+  ~AccessibilityPrivateForwardKeyEventsToSwitchAccessFunction() override {}
+  ResponseAction Run() override;
+  DECLARE_EXTENSION_FUNCTION(
+      "accessibilityPrivate.forwardKeyEventsToSwitchAccess",
+      ACCESSIBILITY_PRIVATE_FORWARDKEYEVENTSTOSWITCHACCESS)
+};
+
 #endif  // defined (OS_CHROMEOS)
 
 #endif  // CHROME_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EXTENSION_API_H_
diff --git a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc
index 09681cea..eb59385 100644
--- a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc
+++ b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc
@@ -19,6 +19,12 @@
 }
 
 void EphemeralTabLayer::SetProperties(
+    int title_view_resource_id,
+    int caption_view_resource_id,
+    jfloat caption_animation_percentage,
+    jfloat text_layer_min_height,
+    jfloat title_caption_spacing,
+    jboolean caption_visible,
     int progress_bar_background_resource_id,
     int progress_bar_resource_id,
     float dp_to_px,
@@ -29,7 +35,6 @@
     float panel_height,
     float bar_margin_side,
     float bar_height,
-    float text_opacity,
     bool bar_border_visible,
     float bar_border_height,
     bool bar_shadow_visible,
@@ -45,11 +50,19 @@
   bool should_render_progress_bar =
       progress_bar_visible && progress_bar_opacity > 0.f;
 
+  // Title needs no rendering in the base layer as it can be rendered
+  // together with caption below. Make it invisible.
+  float title_opacity = 0.f;
   OverlayPanelLayer::SetProperties(
       dp_to_px, content_layer, bar_height, panel_x, panel_y, panel_width,
-      panel_height, bar_margin_side, bar_height, 0.0f, text_opacity,
+      panel_height, bar_margin_side, bar_height, 0.0f, title_opacity,
       bar_border_visible, bar_border_height, bar_shadow_visible,
-      bar_shadow_opacity, 1.0f);
+      bar_shadow_opacity, 1.0f /* icon opacity */);
+
+  SetupTextLayer(bar_top, bar_height, text_layer_min_height,
+                 caption_view_resource_id, caption_animation_percentage,
+                 caption_visible, title_view_resource_id,
+                 title_caption_spacing);
 
   // ---------------------------------------------------------------------------
   // Progress Bar
@@ -107,14 +120,137 @@
   }
 }
 
+void EphemeralTabLayer::SetupTextLayer(float bar_top,
+                                       float bar_height,
+                                       float text_layer_min_height,
+                                       int caption_resource_id,
+                                       float animation_percentage,
+                                       bool caption_visible,
+                                       int title_resource_id,
+                                       float title_caption_spacing) {
+  // ---------------------------------------------------------------------------
+  // Setup the Drawing Hierarchy
+  // ---------------------------------------------------------------------------
+
+  DCHECK(text_layer_.get());
+  DCHECK(caption_.get());
+  DCHECK(title_.get());
+
+  // Title
+  ui::Resource* title_resource = resource_manager_->GetResource(
+      ui::ANDROID_RESOURCE_TYPE_DYNAMIC, title_resource_id);
+  if (title_resource) {
+    title_->SetUIResourceId(title_resource->ui_resource()->id());
+    title_->SetBounds(title_resource->size());
+  }
+
+  // Caption
+  ui::Resource* caption_resource = nullptr;
+  if (caption_visible) {
+    // Grabs the dynamic Search Caption resource so we can get a snapshot.
+    caption_resource = resource_manager_->GetResource(
+        ui::ANDROID_RESOURCE_TYPE_DYNAMIC, caption_resource_id);
+  }
+
+  if (animation_percentage != 0.f) {
+    if (caption_->parent() != text_layer_) {
+      text_layer_->AddChild(caption_);
+    }
+    if (caption_resource) {
+      caption_->SetUIResourceId(caption_resource->ui_resource()->id());
+      caption_->SetBounds(caption_resource->size());
+    }
+  } else if (caption_->parent()) {
+    caption_->RemoveFromParent();
+  }
+
+  // ---------------------------------------------------------------------------
+  // Calculate Text Layer Size
+  // ---------------------------------------------------------------------------
+  // The caption_ may not have had its resource set by this point, if so
+  // the bounds will be zero and everything will still work.
+  float title_height = title_->bounds().height();
+  float caption_height = caption_->bounds().height();
+
+  float layer_height =
+      std::max(text_layer_min_height,
+               title_height + caption_height + title_caption_spacing);
+  float layer_width =
+      std::max(title_->bounds().width(), caption_->bounds().width());
+
+  float layer_top = bar_top + (bar_height - layer_height) / 2;
+  text_layer_->SetBounds(gfx::Size(layer_width, layer_height));
+  text_layer_->SetPosition(gfx::PointF(0.f, layer_top));
+  text_layer_->SetMasksToBounds(true);
+
+  // ---------------------------------------------------------------------------
+  // Layout Text Layer
+  // ---------------------------------------------------------------------------
+  // ---Top of Panel Bar---  <- bar_top
+  //
+  // ---Top of Text Layer--- <- layer_top
+  //                         } remaining_height / 2
+  // Title                   } title_height
+  //                         } title_caption_spacing
+  // Caption                 } caption_height
+  //                         } remaining_height / 2
+  // --Bottom of Text Layer-
+  //
+  // --Bottom of Panel Bar-
+  // If the Caption is not visible the Title is centered in this space, when
+  // the Caption becomes visible it is animated sliding up into it's position
+  // with the spacings determined by UI.
+
+  // If there is no caption, just vertically center the title.
+  float title_top = (layer_height - title_height) / 2;
+
+  // If we aren't displaying the caption we're done.
+  if (animation_percentage == 0.f || !caption_resource) {
+    title_->SetPosition(gfx::PointF(0.f, title_top));
+    return;
+  }
+
+  // Calculate the positions for the Title and Caption when the Caption
+  // animation is complete.
+  float remaining_height =
+      layer_height - title_height - title_caption_spacing - caption_height;
+
+  float title_top_end = remaining_height / 2;
+  float caption_top_end = title_top_end + title_height + title_caption_spacing;
+
+  // Interpolate between the animation start and end positions (short cut
+  // if the animation is at the end or start).
+  title_top = title_top * (1.f - animation_percentage) +
+              title_top_end * animation_percentage;
+
+  // The Caption starts off the bottom of the Text Layer.
+  float caption_top = layer_height * (1.f - animation_percentage) +
+                      caption_top_end * animation_percentage;
+
+  title_->SetPosition(gfx::PointF(0.f, title_top));
+  caption_->SetPosition(gfx::PointF(0.f, caption_top));
+}
+
 EphemeralTabLayer::EphemeralTabLayer(ui::ResourceManager* resource_manager)
     : OverlayPanelLayer(resource_manager),
+      title_(cc::UIResourceLayer::Create()),
+      caption_(cc::UIResourceLayer::Create()),
+      text_layer_(cc::UIResourceLayer::Create()),
       progress_bar_(cc::NinePatchLayer::Create()),
       progress_bar_background_(cc::NinePatchLayer::Create()) {
   progress_bar_background_->SetIsDrawable(true);
   progress_bar_background_->SetFillCenter(true);
   progress_bar_->SetIsDrawable(true);
   progress_bar_->SetFillCenter(true);
+
+  // Content layer
+  text_layer_->SetIsDrawable(true);
+
+  title_->SetIsDrawable(true);
+  caption_->SetIsDrawable(true);
+
+  AddBarTextLayer(text_layer_);
+  text_layer_->AddChild(title_);
 }
 
 EphemeralTabLayer::~EphemeralTabLayer() {}
diff --git a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h
index f70c9fd..aa68719 100644
--- a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h
+++ b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h
@@ -23,7 +23,13 @@
  public:
   static scoped_refptr<EphemeralTabLayer> Create(
       ui::ResourceManager* resource_manager);
-  void SetProperties(int progress_bar_background_resource_id,
+  void SetProperties(int title_view_resource_id,
+                     int caption_view_resource_id,
+                     jfloat caption_animation_percentage,
+                     jfloat text_layer_min_height,
+                     jfloat title_caption_spacing,
+                     jboolean caption_visible,
+                     int progress_bar_background_resource_id,
                      int progress_bar_resource_id,
                      float dp_to_px,
                      const scoped_refptr<cc::Layer>& content_layer,
@@ -33,7 +39,6 @@
                      float panel_height,
                      float bar_margin_side,
                      float bar_height,
-                     float text_opacity,
                      bool bar_border_visible,
                      float bar_border_height,
                      bool bar_shadow_visible,
@@ -42,12 +47,23 @@
                      float progress_bar_height,
                      float progress_bar_opacity,
                      int progress_bar_completion);
+  void SetupTextLayer(float bar_top,
+                      float bar_height,
+                      float text_layer_min_height,
+                      int caption_resource_id,
+                      float animation_percentage,
+                      bool caption_visible,
+                      int context_resource_id,
+                      float title_caption_spacing);
 
  protected:
   explicit EphemeralTabLayer(ui::ResourceManager* resource_manager);
   ~EphemeralTabLayer() override;
 
  private:
+  scoped_refptr<cc::UIResourceLayer> title_;
+  scoped_refptr<cc::UIResourceLayer> caption_;
+  scoped_refptr<cc::UIResourceLayer> text_layer_;
   scoped_refptr<cc::NinePatchLayer> progress_bar_;
   scoped_refptr<cc::NinePatchLayer> progress_bar_background_;
 };
diff --git a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc
index 67571aa..a6429f91 100644
--- a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc
@@ -59,19 +59,24 @@
 
 void EphemeralTabSceneLayer::Update(JNIEnv* env,
                                     const JavaParamRef<jobject>& object,
+                                    jint title_view_resource_id,
+                                    jint caption_view_resource_id,
+                                    jfloat caption_animation_percentage,
+                                    jfloat text_layer_min_height,
+                                    jfloat title_caption_spacing,
+                                    jboolean caption_visible,
                                     jint progress_bar_background_resource_id,
                                     jint progress_bar_resource_id,
                                     jfloat dp_to_px,
                                     jfloat base_page_brightness,
                                     jfloat base_page_offset,
                                     const JavaParamRef<jobject>& jweb_contents,
-                                    jfloat panel_X,
+                                    jfloat panel_x,
                                     jfloat panel_y,
                                     jfloat panel_width,
                                     jfloat panel_height,
                                     jfloat bar_margin_side,
                                     jfloat bar_height,
-                                    jfloat text_opacity,
                                     jboolean bar_border_visible,
                                     jfloat bar_border_height,
                                     jboolean bar_shadow_visible,
@@ -100,12 +105,14 @@
   // Move the base page contents up.
   content_container_->SetPosition(gfx::PointF(0.0f, base_page_offset));
   ephemeral_tab_layer_->SetProperties(
+      title_view_resource_id, caption_view_resource_id,
+      caption_animation_percentage, text_layer_min_height,
+      title_caption_spacing, caption_visible,
       progress_bar_background_resource_id, progress_bar_resource_id, dp_to_px,
-      content_layer, panel_X, panel_y, panel_width, panel_height,
-      bar_margin_side, bar_height, text_opacity, bar_border_visible,
-      bar_border_height, bar_shadow_visible, bar_shadow_opacity,
-      progress_bar_visible, progress_bar_height, progress_bar_opacity,
-      progress_bar_completion);
+      content_layer, panel_x, panel_y, panel_width, panel_height,
+      bar_margin_side, bar_height, bar_border_visible, bar_border_height,
+      bar_shadow_visible, bar_shadow_opacity, progress_bar_visible,
+      progress_bar_height, progress_bar_opacity, progress_bar_completion);
   // Make the layer visible if it is not already.
   ephemeral_tab_layer_->layer()->SetHideLayerAndSubtree(false);
 }
diff --git a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h
index 4931df1b..661d59b7 100644
--- a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h
+++ b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h
@@ -45,6 +45,12 @@
 
   void Update(JNIEnv* env,
               const base::android::JavaParamRef<jobject>& object,
+              jint title_view_resource_id,
+              jint caption_view_resource_id,
+              jfloat caption_animation_percentage,
+              jfloat text_layer_min_height,
+              jfloat term_caption_spacing,
+              jboolean caption_visible,
               jint progress_bar_background_resource_id,
               jint progress_bar_resource_id,
               jfloat dp_to_px,
@@ -57,7 +63,6 @@
               jfloat panel_height,
               jfloat bar_margin_side,
               jfloat bar_height,
-              jfloat text_opacity,
               jboolean bar_border_visible,
               jfloat bar_border_height,
               jboolean bar_shadow_visible,
diff --git a/chrome/browser/android/download/download_controller.cc b/chrome/browser/android/download/download_controller.cc
index 166981e1..f569dcd 100644
--- a/chrome/browser/android/download/download_controller.cc
+++ b/chrome/browser/android/download/download_controller.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/android/chrome_feature_list.h"
 #include "chrome/browser/android/download/dangerous_download_infobar_delegate.h"
 #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_stats.h"
 #include "chrome/browser/infobars/infobar_service.h"
@@ -28,6 +29,7 @@
 #include "chrome/browser/ui/android/view_android_helper.h"
 #include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/grit/chromium_strings.h"
+#include "components/download/public/common/auto_resumption_handler.h"
 #include "components/download/public/common/download_url_parameters.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -59,11 +61,6 @@
 // Guards download_controller_
 base::LazyInstance<base::Lock>::DestructorAtExit g_download_controller_lock_;
 
-// If received bytes is more than the size limit and resumption will restart
-// from the beginning, throttle it.
-int kDefaultAutoResumptionSizeLimit = 10 * 1024 * 1024;  // 10 MB
-const char kAutoResumptionSizeLimitParamName[] = "AutoResumptionSizeLimit";
-
 void CreateContextMenuDownload(
     const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
     const content::ContextMenuParams& params,
@@ -116,16 +113,6 @@
   dlm->DownloadUrl(std::move(dl_params));
 }
 
-int GetAutoResumptionSizeLimit() {
-  std::string value = base::GetFieldTrialParamValueByFeature(
-      chrome::android::kDownloadAutoResumptionThrottling,
-      kAutoResumptionSizeLimitParamName);
-  int size_limit;
-  return base::StringToInt(value, &size_limit)
-             ? size_limit
-             : kDefaultAutoResumptionSizeLimit;
-}
-
 // Helper class for retrieving a DownloadManager.
 class DownloadManagerGetter : public DownloadManager::Observer {
  public:
@@ -395,6 +382,8 @@
   download_item->RemoveObserver(this);
   download_item->AddObserver(this);
 
+  download::AutoResumptionHandler::Get()->OnDownloadStarted(download_item);
+
   OnDownloadUpdated(download_item);
 }
 
@@ -480,7 +469,7 @@
   if (!download_item->GetURL().SchemeIsHTTPOrHTTPS())
     return false;
 
-  static int size_limit = GetAutoResumptionSizeLimit();
+  static int size_limit = DownloadUtils::GetAutoResumptionSizeLimit();
   bool exceeds_size_limit = download_item->GetReceivedBytes() > size_limit;
   std::string etag = download_item->GetETag();
   std::string last_modified = download_item->GetLastModifiedTime();
diff --git a/chrome/browser/android/download/download_manager_service.cc b/chrome/browser/android/download/download_manager_service.cc
index aadb99c..7cab0040 100644
--- a/chrome/browser/android/download/download_manager_service.cc
+++ b/chrome/browser/android/download/download_manager_service.cc
@@ -16,6 +16,8 @@
 #include "base/time/time.h"
 #include "chrome/browser/android/chrome_feature_list.h"
 #include "chrome/browser/android/download/download_controller.h"
+#include "chrome/browser/android/download/download_utils.h"
+#include "chrome/browser/android/download/service/download_task_scheduler.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/download/download_core_service.h"
 #include "chrome/browser/download/download_core_service_factory.h"
@@ -23,6 +25,8 @@
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_constants.h"
+#include "components/download/network/android/network_status_listener_android.h"
+#include "components/download/public/common/auto_resumption_handler.h"
 #include "components/download/public/common/download_item.h"
 #include "components/download/public/common/download_item_impl.h"
 #include "components/download/public/common/download_url_loader_factory_getter_impl.h"
@@ -68,6 +72,21 @@
 }  // namespace
 
 // static
+void DownloadManagerService::CreateAutoResumptionHandler() {
+  auto network_listener =
+      std::make_unique<download::NetworkStatusListenerAndroid>();
+  auto task_scheduler =
+      std::make_unique<download::android::DownloadTaskScheduler>();
+  auto task_manager =
+      std::make_unique<download::TaskManager>(std::move(task_scheduler));
+  auto config = std::make_unique<download::AutoResumptionHandler::Config>();
+  config->auto_resumption_size_limit =
+      DownloadUtils::GetAutoResumptionSizeLimit();
+  download::AutoResumptionHandler::Create(
+      std::move(network_listener), std::move(task_manager), std::move(config));
+}
+
+// static
 void DownloadManagerService::OnDownloadCanceled(
     download::DownloadItem* download,
     DownloadController::DownloadCancelReason reason) {
@@ -649,6 +668,22 @@
   if (!in_progress_manager_ && !is_history_query_complete_)
     return;
   is_pending_downloads_loaded_ = true;
+
+  // Kick-off the auto-resumption handler.
+  content::DownloadManager::DownloadVector all_items;
+  if (in_progress_manager_) {
+    in_progress_manager_->GetAllDownloads(&all_items);
+  } else {
+    content::DownloadManager* manager = GetDownloadManager(false);
+    if (manager)
+      manager->GetAllDownloads(&all_items);
+  }
+
+  if (!download::AutoResumptionHandler::Get())
+    CreateAutoResumptionHandler();
+
+  download::AutoResumptionHandler::Get()->SetResumableDownloads(all_items);
+
   for (auto iter = pending_actions_.begin(); iter != pending_actions_.end();
        ++iter) {
     DownloadActionParams params = iter->second;
diff --git a/chrome/browser/android/download/download_manager_service.h b/chrome/browser/android/download/download_manager_service.h
index 61f69d7..d522e7ba 100644
--- a/chrome/browser/android/download/download_manager_service.h
+++ b/chrome/browser/android/download/download_manager_service.h
@@ -38,6 +38,8 @@
       public content::NotificationObserver,
       public service_manager::Service {
  public:
+  static void CreateAutoResumptionHandler();
+
   static void OnDownloadCanceled(
       download::DownloadItem* download,
       DownloadController::DownloadCancelReason reason);
diff --git a/chrome/browser/android/download/download_utils.cc b/chrome/browser/android/download/download_utils.cc
index c413f75..27670e0 100644
--- a/chrome/browser/android/download/download_utils.cc
+++ b/chrome/browser/android/download/download_utils.cc
@@ -5,6 +5,9 @@
 #include "chrome/browser/android/download/download_utils.h"
 
 #include "base/android/jni_string.h"
+#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/offline_item_utils.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/download/public/common/download_utils.h"
@@ -15,6 +18,14 @@
 using base::android::JavaParamRef;
 using base::android::ScopedJavaLocalRef;
 
+namespace {
+// If received bytes is more than the size limit and resumption will restart
+// from the beginning, throttle it.
+int kDefaultAutoResumptionSizeLimit = 10 * 1024 * 1024;  // 10 MB
+const char kAutoResumptionSizeLimitParamName[] = "AutoResumptionSizeLimit";
+
+}  // namespace
+
 static ScopedJavaLocalRef<jstring> JNI_DownloadUtils_GetFailStateMessage(
     JNIEnv* env,
     jint fail_state) {
@@ -46,3 +57,14 @@
   return base::FilePath(
       base::android::ConvertJavaStringToUTF8(env, uri_jstring));
 }
+
+// static
+int DownloadUtils::GetAutoResumptionSizeLimit() {
+  std::string value = base::GetFieldTrialParamValueByFeature(
+      chrome::android::kDownloadAutoResumptionThrottling,
+      kAutoResumptionSizeLimitParamName);
+  int size_limit;
+  return base::StringToInt(value, &size_limit)
+             ? size_limit
+             : kDefaultAutoResumptionSizeLimit;
+}
diff --git a/chrome/browser/android/download/download_utils.h b/chrome/browser/android/download/download_utils.h
index b507ffa..73c0b27a 100644
--- a/chrome/browser/android/download/download_utils.h
+++ b/chrome/browser/android/download/download_utils.h
@@ -11,6 +11,7 @@
 class DownloadUtils {
  public:
   static base::FilePath GetUriStringForPath(const base::FilePath& file_path);
+  static int GetAutoResumptionSizeLimit();
 };
 
 #endif  // CHROME_BROWSER_ANDROID_DOWNLOAD_DOWNLOAD_UTILS_H_
diff --git a/chrome/browser/android/download/service/download_background_task.cc b/chrome/browser/android/download/service/download_background_task.cc
index 86c394a..e418d089 100644
--- a/chrome/browser/android/download/service/download_background_task.cc
+++ b/chrome/browser/android/download/service/download_background_task.cc
@@ -4,10 +4,12 @@
 
 #include "base/android/callback_android.h"
 #include "base/callback_forward.h"
+#include "chrome/browser/android/download/download_manager_service.h"
 #include "chrome/browser/download/download_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_android.h"
 #include "components/download/public/background_service/download_service.h"
+#include "components/download/public/common/auto_resumption_handler.h"
 #include "content/public/browser/browser_context.h"
 #include "jni/DownloadBackgroundTask_jni.h"
 
@@ -16,6 +18,19 @@
 namespace download {
 namespace android {
 
+DownloadService* GetDownloadService(
+    const base::android::JavaParamRef<jobject>& jprofile) {
+  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  DCHECK(profile);
+  return DownloadServiceFactory::GetForBrowserContext(profile);
+}
+
+AutoResumptionHandler* GetAutoResumptionHandler() {
+  if (!AutoResumptionHandler::Get())
+    DownloadManagerService::CreateAutoResumptionHandler();
+  return AutoResumptionHandler::Get();
+}
+
 // static
 void JNI_DownloadBackgroundTask_StartBackgroundTask(
     JNIEnv* env,
@@ -23,17 +38,23 @@
     const base::android::JavaParamRef<jobject>& jprofile,
     jint task_type,
     const base::android::JavaParamRef<jobject>& jcallback) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
-  DCHECK(profile);
-
   TaskFinishedCallback finish_callback =
       base::BindOnce(&base::android::RunBooleanCallbackAndroid,
                      base::android::ScopedJavaGlobalRef<jobject>(jcallback));
 
-  DownloadService* download_service =
-      DownloadServiceFactory::GetForBrowserContext(profile);
-  download_service->OnStartScheduledTask(
-      static_cast<DownloadTaskType>(task_type), std::move(finish_callback));
+  switch (static_cast<DownloadTaskType>(task_type)) {
+    case download::DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK: {
+      GetAutoResumptionHandler()->OnStartScheduledTask(
+          std::move(finish_callback));
+      break;
+    }
+    case download::DownloadTaskType::DOWNLOAD_TASK:
+      FALLTHROUGH;
+    case download::DownloadTaskType::CLEANUP_TASK:
+      GetDownloadService(jprofile)->OnStartScheduledTask(
+          static_cast<DownloadTaskType>(task_type), std::move(finish_callback));
+      break;
+  }
 }
 
 // static
@@ -42,13 +63,18 @@
     const base::android::JavaParamRef<jobject>& jcaller,
     const base::android::JavaParamRef<jobject>& jprofile,
     jint task_type) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
-  DCHECK(profile);
-
-  DownloadService* download_service =
-      DownloadServiceFactory::GetForBrowserContext(profile);
-  return download_service->OnStopScheduledTask(
-      static_cast<DownloadTaskType>(task_type));
+  switch (static_cast<DownloadTaskType>(task_type)) {
+    case download::DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK: {
+      GetAutoResumptionHandler()->OnStopScheduledTask();
+      break;
+    }
+    case download::DownloadTaskType::DOWNLOAD_TASK:
+      FALLTHROUGH;
+    case download::DownloadTaskType::CLEANUP_TASK:
+      return GetDownloadService(jprofile)->OnStopScheduledTask(
+          static_cast<DownloadTaskType>(task_type));
+  }
+  return false;
 }
 
 }  // namespace android
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index 1a6905df..e26e149 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -268,6 +268,8 @@
       input_.from_omnibox_focus() ? base::string16() : input_.text(),
       false, /* don't know */
       input_.type(),
+      false, /* not keyword mode */
+      OmniboxEventProto::INVALID,
       true,
       selected_index,
       WindowOpenDisposition::CURRENT_TAB,
diff --git a/chrome/browser/android/vr/arcore_device/arcore.h b/chrome/browser/android/vr/arcore_device/arcore.h
index 9b0b0cf..cd57addb 100644
--- a/chrome/browser/android/vr/arcore_device/arcore.h
+++ b/chrome/browser/android/vr/arcore_device/arcore.h
@@ -45,7 +45,6 @@
 
   virtual bool RequestHitTest(
       const mojom::XRRayPtr& ray,
-      const gfx::Size& image_size,
       std::vector<mojom::XRHitResultPtr>* hit_results) = 0;
 
   virtual void Pause() = 0;
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
index 370b03a..1f6a024 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -265,8 +265,7 @@
   gl_thread_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&ArCoreGl::ProcessFrame, weak_ptr_factory_.GetWeakPtr(),
-                     base::Passed(&frame_data), frame_size,
-                     base::Passed(&callback)));
+                     base::Passed(&frame_data), base::Passed(&callback)));
 }
 
 void ArCoreGl::RequestHitTest(
@@ -284,7 +283,6 @@
 
 void ArCoreGl::ProcessFrame(
     mojom::XRFrameDataPtr frame_data,
-    const gfx::Size& frame_size,
     mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
   DCHECK(IsOnGlThread());
   DCHECK(is_initialized_);
@@ -304,7 +302,7 @@
   // obvious how the timing between the results and the frame should go.
   for (auto& request : hit_test_requests_) {
     std::vector<mojom::XRHitResultPtr> results;
-    if (arcore_->RequestHitTest(request->ray, frame_size, &results)) {
+    if (arcore_->RequestHitTest(request->ray, &results)) {
       std::move(request->callback).Run(std::move(results));
     } else {
       // Hit test failed, i.e. unprojected location was offscreen.
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/chrome/browser/android/vr/arcore_device/arcore_gl.h
index 1dd2d82..e12f33a 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.h
@@ -67,9 +67,7 @@
   base::WeakPtr<ArCoreGl> GetWeakPtr();
 
  private:
-  // TODO(https://crbug/835948): remove frame_size.
   void ProcessFrame(mojom::XRFrameDataPtr frame_data,
-                    const gfx::Size& frame_size,
                     mojom::XRFrameDataProvider::GetFrameDataCallback callback);
 
   bool InitializeGl();
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.cc b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
index 5ddb02e9..3fd89bd 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
@@ -207,10 +207,8 @@
   return result;
 }
 
-// TODO(835948): remove image-size
 bool ArCoreImpl::RequestHitTest(
     const mojom::XRRayPtr& ray,
-    const gfx::Size& image_size,
     std::vector<mojom::XRHitResultPtr>* hit_results) {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
@@ -227,17 +225,14 @@
     return false;
   }
 
-  gfx::PointF screen_point;
-  if (!TransformRayToScreenSpace(ray, image_size, &screen_point)) {
-    return false;
-  }
-
   // ArCore returns hit-results in sorted order, thus providing the guarantee
   // of sorted results promised by the WebXR spec for requestHitTest().
-  ArFrame_hitTest(arcore_session_.get(), arcore_frame_.get(),
-                  screen_point.x() * image_size.width(),
-                  screen_point.y() * image_size.height(),
-                  arcore_hit_result_list.get());
+  float origin[3] = {ray->origin.x(), ray->origin.y(), ray->origin.z()};
+  float direction[3] = {ray->direction.x(), ray->direction.y(),
+                        ray->direction.z()};
+
+  ArFrame_hitTestRay(arcore_session_.get(), arcore_frame_.get(), origin,
+                     direction, arcore_hit_result_list.get());
 
   int arcore_hit_result_list_size = 0;
   ArHitResultList_getSize(arcore_session_.get(), arcore_hit_result_list.get(),
@@ -315,61 +310,6 @@
   return true;
 }
 
-// TODO(835948): remove this method.
-bool ArCoreImpl::TransformRayToScreenSpace(const mojom::XRRayPtr& ray,
-                                           const gfx::Size& image_size,
-                                           gfx::PointF* screen_point) {
-  DCHECK(IsOnGlThread());
-  DCHECK(arcore_session_.is_valid());
-  DCHECK(arcore_frame_.is_valid());
-
-  internal::ScopedArCoreObject<ArCamera*> arcore_camera;
-  ArFrame_acquireCamera(
-      arcore_session_.get(), arcore_frame_.get(),
-      internal::ScopedArCoreObject<ArCamera*>::Receiver(arcore_camera).get());
-  DCHECK(arcore_camera.is_valid())
-      << "ArFrame_acquireCamera failed despite documentation saying it cannot";
-
-  // Get the projection matrix.
-  float projection_matrix[16];
-  ArCamera_getProjectionMatrix(arcore_session_.get(), arcore_camera.get(), 0.1,
-                               1000, projection_matrix);
-  SkMatrix44 projection44;
-  projection44.setColMajorf(projection_matrix);
-  gfx::Transform projection_transform(projection44);
-
-  // Get the view matrix.
-  float view_matrix[16];
-  ArCamera_getViewMatrix(arcore_session_.get(), arcore_camera.get(),
-                         view_matrix);
-  SkMatrix44 view44;
-  view44.setColMajorf(view_matrix);
-  gfx::Transform view_transform(view44);
-
-  // Create the combined matrix.
-  gfx::Transform proj_view_transform = projection_transform * view_transform;
-
-  // Transform the ray into screen space.
-  gfx::Point3F screen_point_3d = ray->origin + ray->direction;
-
-  proj_view_transform.TransformPoint(&screen_point_3d);
-  if (screen_point_3d.x() < -1 || screen_point_3d.x() > 1 ||
-      screen_point_3d.y() < -1 || screen_point_3d.y() > 1) {
-    // The point does not project back into screen space, so this won't
-    // work with the screen-space-based hit-test API.
-    DLOG(ERROR) << "Invalid ray - does not originate from device screen.";
-    return false;
-  }
-
-  screen_point->set_x((screen_point_3d.x() + 1) / 2);
-  // The calculated point in GL's normalized device coordinates (NDC) ranges
-  // from -1..1, with -1, -1 at the bottom left of the screen, +1 at the top.
-  // The output screen space coordinates range from 0..1, with 0, 0 at the
-  // top left.
-  screen_point->set_y((-screen_point_3d.y() + 1) / 2);
-  return true;
-}
-
 bool ArCoreImpl::IsOnGlThread() {
   return gl_thread_task_runner_->BelongsToCurrentThread();
 }
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.h b/chrome/browser/android/vr/arcore_device/arcore_impl.h
index 2561e08..344d7fa 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.h
@@ -82,14 +82,9 @@
   void Pause() override;
   void Resume() override;
   bool RequestHitTest(const mojom::XRRayPtr& ray,
-                      const gfx::Size& image_size,
                       std::vector<mojom::XRHitResultPtr>* hit_results) override;
 
  private:
-  bool TransformRayToScreenSpace(const mojom::XRRayPtr& ray,
-                                 const gfx::Size& image_size,
-                                 gfx::PointF* screen_point);
-
   bool IsOnGlThread();
   base::WeakPtr<ArCoreImpl> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
diff --git a/chrome/browser/android/vr/arcore_device/arcore_shim.cc b/chrome/browser/android/vr/arcore_device/arcore_shim.cc
index 98a009875..9b9fabff 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_shim.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_shim.cc
@@ -22,7 +22,7 @@
   CALL(ArFrame_acquireCamera)            \
   CALL(ArFrame_create)                   \
   CALL(ArFrame_destroy)                  \
-  CALL(ArFrame_hitTest)                  \
+  CALL(ArFrame_hitTestRay)               \
   CALL(ArFrame_transformDisplayUvCoords) \
   CALL(ArHitResult_create)               \
   CALL(ArHitResult_destroy)              \
@@ -152,13 +152,13 @@
   arcore_api->impl_ArFrame_destroy(frame);
 }
 
-void ArFrame_hitTest(const ArSession* session,
-                     const ArFrame* frame,
-                     float pixel_x,
-                     float pixel_y,
-                     ArHitResultList* out_hit_results) {
-  arcore_api->impl_ArFrame_hitTest(session, frame, pixel_x, pixel_y,
-                                   out_hit_results);
+void ArFrame_hitTestRay(const ArSession* session,
+                        const ArFrame* frame,
+                        const float* ray_origin_3,
+                        const float* ray_direction_3,
+                        ArHitResultList* out_hit_results) {
+  arcore_api->impl_ArFrame_hitTestRay(session, frame, ray_origin_3,
+                                      ray_direction_3, out_hit_results);
 }
 
 void ArFrame_transformDisplayUvCoords(const ArSession* session,
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.cc b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
index b4bdfc19..1205748 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.cc
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
@@ -203,7 +203,6 @@
 
 bool FakeArCore::RequestHitTest(
     const mojom::XRRayPtr& ray,
-    const gfx::Size& image_size,
     std::vector<mojom::XRHitResultPtr>* hit_results) {
   mojom::XRHitResultPtr hit = mojom::XRHitResult::New();
   hit->hit_matrix.resize(16);
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.h b/chrome/browser/android/vr/arcore_device/fake_arcore.h
index d78672a..12b560d8 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.h
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.h
@@ -35,7 +35,6 @@
   void Resume() override;
 
   bool RequestHitTest(const mojom::XRRayPtr& ray,
-                      const gfx::Size& image_size,
                       std::vector<mojom::XRHitResultPtr>* hit_results) override;
 
   void SetCameraAspect(float aspect) { camera_aspect_ = aspect; }
diff --git a/chrome/browser/browsing_data/cookies_tree_model.cc b/chrome/browser/browsing_data/cookies_tree_model.cc
index e2e4e921..6b5b551a 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model.cc
@@ -16,6 +16,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/browsing_data/browsing_data_cookie_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_flash_lso_helper.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
@@ -32,7 +33,9 @@
 #include "net/url_request/url_request_context.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/color_palette.h"
 #include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/paint_vector_icon.h"
 #include "ui/resources/grit/ui_resources.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -1175,12 +1178,8 @@
 // Returns the set of icons for the nodes in the tree. You only need override
 // this if you don't want to use the default folder icons.
 void CookiesTreeModel::GetIcons(std::vector<gfx::ImageSkia>* icons) {
-  icons->push_back(*ui::ResourceBundle::GetSharedInstance()
-                        .GetNativeImageNamed(IDR_DEFAULT_FAVICON)
-                        .ToImageSkia());
-  icons->push_back(*ui::ResourceBundle::GetSharedInstance()
-                        .GetNativeImageNamed(IDR_COOKIES)
-                        .ToImageSkia());
+  icons->push_back(
+      gfx::CreateVectorIcon(kCookieIcon, 18, gfx::kChromeIconGrey));
   icons->push_back(*ui::ResourceBundle::GetSharedInstance()
                         .GetNativeImageNamed(IDR_COOKIE_STORAGE_ICON)
                         .ToImageSkia());
@@ -1192,8 +1191,6 @@
 int CookiesTreeModel::GetIconIndex(ui::TreeModelNode* node) {
   CookieTreeNode* ct_node = static_cast<CookieTreeNode*>(node);
   switch (ct_node->GetDetailedInfo().node_type) {
-    case CookieTreeNode::DetailedInfo::TYPE_HOST:
-      return ORIGIN;
     case CookieTreeNode::DetailedInfo::TYPE_COOKIE:
       return COOKIE;
 
@@ -1209,6 +1206,7 @@
     case CookieTreeNode::DetailedInfo::TYPE_CACHE_STORAGE:
     case CookieTreeNode::DetailedInfo::TYPE_MEDIA_LICENSE:
       return DATABASE;
+    case CookieTreeNode::DetailedInfo::TYPE_HOST:
     case CookieTreeNode::DetailedInfo::TYPE_QUOTA:
       return -1;
     default:
diff --git a/chrome/browser/browsing_data/cookies_tree_model.h b/chrome/browser/browsing_data/cookies_tree_model.h
index 56041697..2ecf397 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.h
+++ b/chrome/browser/browsing_data/cookies_tree_model.h
@@ -865,11 +865,7 @@
   static std::unique_ptr<CookiesTreeModel> CreateForProfile(Profile* profile);
 
  private:
-  enum CookieIconIndex {
-    ORIGIN = 0,
-    COOKIE = 1,
-    DATABASE = 2
-  };
+  enum CookieIconIndex { COOKIE = 0, DATABASE = 1 };
 
   // Reset the counters for batches.
   void ResetBatches();
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 2182646..17539ab 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -111,6 +111,7 @@
     "//chromeos/services/multidevice_setup/public/cpp:oobe_completion_tracker",
     "//chromeos/services/multidevice_setup/public/cpp:prefs",
     "//chromeos/services/secure_channel/public/cpp/client",
+    "//chromeos/tpm",
     "//components/arc",
     "//components/arc/common:struct_traits",
     "//components/arc/media_session",
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index 8ec9dcd..738dbfb 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -84,6 +84,7 @@
 #include "media/audio/sounds/sounds_manager.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/media_session/public/cpp/switches.h"
+#include "services/media_session/public/mojom/constants.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "ui/accessibility/accessibility_switches.h"
 #include "ui/accessibility/ax_enum_util.h"
@@ -322,6 +323,12 @@
       ->BindInterface(ash::mojom::kServiceName,
                       &accessibility_focus_ring_controller_);
 
+  // Connect to the media session service.
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->BindInterface(media_session::mojom::kServiceName,
+                      &audio_focus_manager_ptr_);
+
   CrasAudioHandler::Get()->AddAudioObserver(this);
 }
 
@@ -1325,11 +1332,15 @@
                        base::Unretained(this))));
   }
 
+  // TODO(beccahughes): Remove once we have moved to a feature.
   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
           media_session::switches::kEnableAudioFocus)) {
     base::CommandLine::ForCurrentProcess()->AppendSwitch(
         media_session::switches::kEnableAudioFocus);
   }
+
+  audio_focus_manager_ptr_->SetEnforcementMode(
+      media_session::mojom::EnforcementMode::kSingleSession);
 }
 
 void AccessibilityManager::PostUnloadChromeVox() {
@@ -1355,6 +1366,9 @@
 
   // Stop speech.
   content::TtsController::GetInstance()->Stop();
+
+  audio_focus_manager_ptr_->SetEnforcementMode(
+      media_session::mojom::EnforcementMode::kDefault);
 }
 
 void AccessibilityManager::PostSwitchChromeVoxProfile() {
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h
index 0080332c..b6e11be 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.h
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h
@@ -28,6 +28,7 @@
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_system.h"
+#include "services/media_session/public/mojom/audio_focus.mojom.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
 
@@ -468,6 +469,9 @@
   base::RepeatingCallback<void(const gfx::Rect&)>
       caret_bounds_observer_for_test_;
 
+  // Used to set the audio focus enforcement type for ChromeVox.
+  media_session::mojom::AudioFocusManagerPtr audio_focus_manager_ptr_;
+
   base::WeakPtrFactory<AccessibilityManager> weak_ptr_factory_;
 
   friend class DictationTest;
diff --git a/chrome/browser/chromeos/login/configuration_keys.cc b/chrome/browser/chromeos/login/configuration_keys.cc
index d22894c..284cb46b 100644
--- a/chrome/browser/chromeos/login/configuration_keys.cc
+++ b/chrome/browser/chromeos/login/configuration_keys.cc
@@ -12,6 +12,12 @@
 // All keys should be listed here, even if they are used in JS code only.
 // These keys are used in chrome/browser/resources/chromeos/login/oobe_types.js
 
+// == HID Detection screen:
+
+// Boolean value indicating if we should skip HID detection screen altogether.
+
+const char kSkipHIDDetection[] = "skipHIDDetection";
+
 // == Welcome screen:
 
 // Boolean value indicating if "Next" button on welcome screen is pressed
@@ -109,6 +115,8 @@
   ValueType type;
   ConfigurationHandlerSide side;
 } kAllConfigurationKeys[] = {
+    {kSkipHIDDetection, ValueType::BOOLEAN,
+     ConfigurationHandlerSide::HANDLER_CPP},
     {kWelcomeNext, ValueType::BOOLEAN, ConfigurationHandlerSide::HANDLER_JS},
     {kLanguage, ValueType::STRING, ConfigurationHandlerSide::HANDLER_JS},
     {kInputMethod, ValueType::STRING, ConfigurationHandlerSide::HANDLER_JS},
diff --git a/chrome/browser/chromeos/login/configuration_keys.h b/chrome/browser/chromeos/login/configuration_keys.h
index 8bc2ffc..3d4573d 100644
--- a/chrome/browser/chromeos/login/configuration_keys.h
+++ b/chrome/browser/chromeos/login/configuration_keys.h
@@ -12,6 +12,8 @@
 // Configuration keys that are used to automate OOBE screens go here.
 // Please keep keys grouped by screens and ordered according to OOBE flow.
 
+extern const char kSkipHIDDetection[];
+
 extern const char kLanguage[];
 extern const char kInputMethod[];
 extern const char kWelcomeNext[];
diff --git a/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc b/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc
index 03d8c86..876b207 100644
--- a/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc
+++ b/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc
@@ -36,6 +36,12 @@
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_utils.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "services/device/public/cpp/hid/fake_input_service_linux.h"
+#include "services/device/public/mojom/constants.mojom.h"
+#include "services/device/public/mojom/input_service.mojom.h"
+#include "services/service_manager/public/cpp/service_binding.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
 #include "ui/base/ime/chromeos/input_method_util.h"
 
@@ -560,6 +566,11 @@
   DISALLOW_COPY_AND_ASSIGN(ActiveDirectoryJoinTest);
 };
 
+// This test case will use
+// src/chromeos/test/data/oobe_configuration/<TestName>.json file as
+// OOBE configuration for each of the tests and verify that relevant parts
+// of OOBE automation took place. OOBE WebUI will not be started until
+// LoadConfiguration() is called to allow configure relevant stubs.
 class EnterpriseEnrollmentConfigurationTest
     : public EnterpriseEnrollmentTestBase {
  public:
@@ -696,6 +707,54 @@
   DISALLOW_COPY_AND_ASSIGN(EnterpriseEnrollmentConfigurationTest);
 };
 
+// EnterpriseEnrollmentConfigurationTest with no input devices.
+class EnterpriseEnrollmentConfigurationTestNoHID
+    : public EnterpriseEnrollmentConfigurationTest {
+ public:
+  using InputDeviceInfoPtr = device::mojom::InputDeviceInfoPtr;
+
+  EnterpriseEnrollmentConfigurationTestNoHID() {
+    fake_input_service_manager_ =
+        std::make_unique<device::FakeInputServiceLinux>();
+
+    service_manager::ServiceBinding::OverrideInterfaceBinderForTesting(
+        device::mojom::kServiceName,
+        base::BindRepeating(
+            &device::FakeInputServiceLinux::Bind,
+            base::Unretained(fake_input_service_manager_.get())));
+  }
+
+  ~EnterpriseEnrollmentConfigurationTestNoHID() override {
+    service_manager::ServiceBinding::ClearInterfaceBinderOverrideForTesting<
+        device::mojom::InputDeviceManager>(device::mojom::kServiceName);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    EnterpriseEnrollmentConfigurationTest::SetUpInProcessBrowserTestFixture();
+
+    mock_adapter_ = new testing::NiceMock<device::MockBluetoothAdapter>();
+
+    device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
+    EXPECT_CALL(*mock_adapter_, IsPresent())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(*mock_adapter_, IsPowered())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(*mock_adapter_, GetDevices())
+        .WillRepeatedly(
+            testing::Return(device::BluetoothAdapter::ConstDeviceList()));
+
+    // Note: The SecureChannel service, which is never destroyed until the
+    // browser process is killed, utilizes |mock_adapter_|.
+    testing::Mock::AllowLeak(mock_adapter_.get());
+  }
+
+ private:
+  std::unique_ptr<device::FakeInputServiceLinux> fake_input_service_manager_;
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
+
+  DISALLOW_COPY_AND_ASSIGN(EnterpriseEnrollmentConfigurationTestNoHID);
+};
+
 #if defined(MEMORY_SANITIZER)
 #define TEST_DISABLED_ON_MSAN(test_fixture, test_name) \
   IN_PROC_BROWSER_TEST_F(test_fixture, DISABLED_##test_name)
@@ -1123,4 +1182,20 @@
   EXPECT_TRUE(IsStepDisplayed("success"));
 }
 
+// Check that HID detection screen is shown if it is not specified by
+// configuration.
+IN_PROC_BROWSER_TEST_F(EnterpriseEnrollmentConfigurationTestNoHID,
+                       TestLeaveWelcomeScreen) {
+  LoadConfiguration();
+  OobeScreenWaiter(OobeScreen::SCREEN_OOBE_HID_DETECTION).Wait();
+}
+
+// Check that HID detection screen is really skipped and rest of configuration
+// is applied.
+IN_PROC_BROWSER_TEST_F(EnterpriseEnrollmentConfigurationTestNoHID,
+                       TestSkipHIDDetection) {
+  LoadConfiguration();
+  OobeScreenWaiter(OobeScreen::SCREEN_OOBE_NETWORK).Wait();
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 526537d6..ad900e5e 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -1301,11 +1301,14 @@
   if (!GetOobeUI())
     return;
 
-  if (screen_needed) {
+  const auto* skip_screen_key = oobe_configuration_.FindKeyOfType(
+      configuration::kSkipHIDDetection, base::Value::Type::BOOLEAN);
+  const bool skip_screen = skip_screen_key && skip_screen_key->GetBool();
+
+  if (screen_needed && !skip_screen)
     ShowHIDDetectionScreen();
-  } else {
+  else
     ShowWelcomeScreen();
-  }
 }
 
 void WizardController::UpdateOobeConfiguration() {
diff --git a/chrome/browser/chromeos/smb_client/smb_service.cc b/chrome/browser/chromeos/smb_client/smb_service.cc
index 51f7253..490726c 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.cc
+++ b/chrome/browser/chromeos/smb_client/smb_service.cc
@@ -150,6 +150,12 @@
                                        std::move(shares_callback));
 }
 
+void SmbService::UpdateCredentials(int32_t mount_id,
+                                   const std::string& username,
+                                   const std::string& password) {
+  NOTREACHED();
+}
+
 void SmbService::CallMount(const file_system_provider::MountOptions& options,
                            const base::FilePath& share_path,
                            const std::string& username_input,
@@ -490,6 +496,12 @@
 void SmbService::RequestCredentials(const std::string& share_path,
                                     int32_t mount_id,
                                     base::OnceClosure reply) {
+  update_credential_replies_[mount_id] = std::move(reply);
+  OpenRequestCredentialsDialog(share_path, mount_id);
+}
+
+void SmbService::OpenRequestCredentialsDialog(const std::string& share_path,
+                                              int32_t mount_id) {
   NOTREACHED();
 }
 
diff --git a/chrome/browser/chromeos/smb_client/smb_service.h b/chrome/browser/chromeos/smb_client/smb_service.h
index 415bddcc..4391c43 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.h
+++ b/chrome/browser/chromeos/smb_client/smb_service.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_CHROMEOS_SMB_CLIENT_SMB_SERVICE_H_
 #define CHROME_BROWSER_CHROMEOS_SMB_CLIENT_SMB_SERVICE_H_
 
+#include <map>
 #include <memory>
 #include <string>
 
@@ -84,6 +85,13 @@
   void GatherSharesInNetwork(HostDiscoveryResponse discovery_callback,
                              GatherSharesResponse shares_callback);
 
+  // Updates the credentials for |mount_id|. If there is a stored callback in
+  // |update_credentials_replies_| for |mount_id|, it will be run upon once the
+  // credentials are successfully updated.
+  void UpdateCredentials(int32_t mount_id,
+                         const std::string& username,
+                         const std::string& password);
+
  private:
   // Calls SmbProviderClient::Mount(). |temp_file_manager_| must be initialized
   // before this is called.
@@ -168,6 +176,11 @@
                           int32_t mount_id,
                           base::OnceClosure reply);
 
+  // Opens a request credential dialog for the share path |share_path|.
+  // When a user clicks "Update" in the dialog, UpdateCredentials is run.
+  void OpenRequestCredentialsDialog(const std::string& share_path,
+                                    int32_t mount_id);
+
   // Records metrics on the number of SMB mounts a user has.
   void RecordMountCount() const;
 
@@ -176,6 +189,8 @@
   Profile* profile_;
   std::unique_ptr<TempFileManager> temp_file_manager_;
   std::unique_ptr<SmbShareFinder> share_finder_;
+  // |mount_id| -> |reply|. Stored callbacks to run after updating credential.
+  std::map<int32_t, base::OnceClosure> update_credential_replies_;
 
   DISALLOW_COPY_AND_ASSIGN(SmbService);
 };
diff --git a/chrome/browser/download/download_task_scheduler_impl.cc b/chrome/browser/download/download_task_scheduler_impl.cc
index 325ae9b..d12556d 100644
--- a/chrome/browser/download/download_task_scheduler_impl.cc
+++ b/chrome/browser/download/download_task_scheduler_impl.cc
@@ -49,6 +49,11 @@
 
 void DownloadTaskSchedulerImpl::RunScheduledTask(
     download::DownloadTaskType task_type) {
+  if (task_type == download::DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK) {
+    NOTREACHED();
+    return;
+  }
+
   download::DownloadService* download_service =
       DownloadServiceFactory::GetForBrowserContext(context_);
   download_service->OnStartScheduledTask(
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 6070e39..5f411dd 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -920,7 +920,8 @@
       .SetWithholdHostPermissions(true);
   EXPECT_TRUE(listener.WaitUntilSatisfied());
 
-  // Navigate the browser to a page in a new tab.
+  // Navigate the browser to a page in a new tab. The page at "a.com" has two
+  // two cross-origin iframes to "b.com" and "c.com".
   const std::string kHost = "a.com";
   GURL url = embedded_test_server()->GetURL(kHost, "/iframe_cross_site.html");
   NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
@@ -934,17 +935,10 @@
       ExtensionActionRunner::GetForWebContents(web_contents);
   ASSERT_TRUE(runner);
 
-  int port = embedded_test_server()->port();
-  const std::string kXhrPath = "simple.html";
-
   // The extension shouldn't have currently received any webRequest events,
-  // since it doesn't have permission (and shouldn't receive any from an XHR).
+  // since it doesn't have any permissions.
   EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
-  EXPECT_FALSE(
-      HasSeenWebRequestInBackgroundPage(extension, profile(), "b.com"));
 
-  content::RenderFrameHost* main_frame = nullptr;
-  content::RenderFrameHost* child_frame = nullptr;
   auto get_main_and_child_frame = [](content::WebContents* web_contents,
                                      content::RenderFrameHost** main_frame,
                                      content::RenderFrameHost** child_frame) {
@@ -957,42 +951,53 @@
     ASSERT_TRUE(*child_frame);
   };
 
+  content::RenderFrameHost* main_frame = nullptr;
+  content::RenderFrameHost* child_frame = nullptr;
   get_main_and_child_frame(web_contents, &main_frame, &child_frame);
   const std::string kMainHost = main_frame->GetLastCommittedURL().host();
   const std::string kChildHost = child_frame->GetLastCommittedURL().host();
 
+  int port = embedded_test_server()->port();
+  const std::string kXhrPath = "simple.html";
+
+  // The extension shouldn't be able to intercept the xhr requests since it
+  // doesn't have any permissions.
   PerformXhrInFrame(main_frame, kHost, port, kXhrPath);
   PerformXhrInFrame(child_frame, kChildHost, port, kXhrPath);
   EXPECT_EQ(0, GetWebRequestCountFromBackgroundPage(extension, profile()));
   EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension));
 
-  // Grant activeTab permission, and perform another XHR. The extension should
-  // receive the event.
+  // Grant activeTab permission.
   runner->set_default_bubble_close_action_for_testing(
       base::WrapUnique(new ToolbarActionsBarBubbleDelegate::CloseAction(
           ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE)));
   runner->RunAction(extension, true);
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(content::WaitForLoadStop(web_contents));
-  // The runner will have refreshed the page...
+
+  // The runner will have refreshed the page, and the extension will have
+  // received access to the main-frame ("a.com") and requests initiated by it
+  // (cross-origin sub-frame navigations to "b.com" and "c.com").
   get_main_and_child_frame(web_contents, &main_frame, &child_frame);
+  EXPECT_TRUE(HasSeenWebRequestInBackgroundPage(extension, profile(), "a.com"));
+  EXPECT_TRUE(HasSeenWebRequestInBackgroundPage(extension, profile(), "b.com"));
+  EXPECT_TRUE(HasSeenWebRequestInBackgroundPage(extension, profile(), "c.com"));
+
   EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension));
 
-  int xhr_count = GetWebRequestCountFromBackgroundPage(extension, profile());
-  // ... which means that we should have a non-zero xhr count, and the extension
-  // should see the request for the cross-site subframe...
-  EXPECT_GT(xhr_count, 0);
-  EXPECT_TRUE(HasSeenWebRequestInBackgroundPage(extension, profile(), "b.com"));
+  int request_count =
+      GetWebRequestCountFromBackgroundPage(extension, profile());
+
   // ... and the extension should receive future events.
   PerformXhrInFrame(main_frame, kHost, port, kXhrPath);
-  ++xhr_count;
-  EXPECT_EQ(xhr_count,
+  ++request_count;
+  EXPECT_EQ(request_count,
             GetWebRequestCountFromBackgroundPage(extension, profile()));
 
   // However, activeTab only grants access to the main frame, not to child
   // frames. As such, trying to XHR in the child frame should still fail.
   PerformXhrInFrame(child_frame, kChildHost, port, kXhrPath);
-  EXPECT_EQ(xhr_count,
+  EXPECT_EQ(request_count,
             GetWebRequestCountFromBackgroundPage(extension, profile()));
   // But since there's no way for the user to currently grant access to child
   // frames, this shouldn't show up as a blocked action.
@@ -1014,7 +1019,7 @@
   action_updated_waiter.Wait();
   EXPECT_EQ(web_contents, action_updated_waiter.last_web_contents());
 
-  EXPECT_EQ(xhr_count,
+  EXPECT_EQ(request_count,
             GetWebRequestCountFromBackgroundPage(extension, profile()));
   EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension));
 }
diff --git a/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc b/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
index dbbecc9..b14136b 100644
--- a/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
+++ b/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
@@ -593,6 +593,35 @@
                                "nosniff.xml - body\n");
 }
 
+// Tests that same-origin fetches (same-origin relative to the webpage the
+// content script is injected into) are allowed.  See also
+// https://crbug.com/918660.
+IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest,
+                       FromProgrammaticContentScript_SameOrigin) {
+  // Load the test extension.
+  ASSERT_TRUE(InstallExtension());
+
+  // Navigate to a foo.com page.
+  GURL page_url = GetTestPageUrl("foo.com");
+  ui_test_utils::NavigateToURL(browser(), page_url);
+  ASSERT_EQ(page_url,
+            active_web_contents()->GetMainFrame()->GetLastCommittedURL());
+  ASSERT_EQ(url::Origin::Create(page_url),
+            active_web_contents()->GetMainFrame()->GetLastCommittedOrigin());
+
+  // Inject a content script that performs a same-origin XHR to foo.com.
+  base::HistogramTester histograms;
+  GURL same_origin_resource(
+      embedded_test_server()->GetURL("foo.com", "/nosniff.xml"));
+  std::string fetch_result =
+      FetchViaContentScript(same_origin_resource, active_web_contents());
+
+  // Verify that no blocking occurred.
+  EXPECT_THAT(fetch_result, ::testing::StartsWith("nosniff.xml - body"));
+  VerifyFetchFromContentScriptWasAllowed(histograms,
+                                         false /* expecting_sniffing */);
+}
+
 // Test that responses that would have been allowed by CORB anyway are not
 // reported to LogInitiatorSchemeBypassingDocumentBlocking.
 IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest,
diff --git a/chrome/browser/interstitials/enterprise_util.cc b/chrome/browser/interstitials/enterprise_util.cc
index 6597a0a5..81ba4a6a 100644
--- a/chrome/browser/interstitials/enterprise_util.cc
+++ b/chrome/browser/interstitials/enterprise_util.cc
@@ -97,6 +97,7 @@
     case safe_browsing::SB_THREAT_TYPE_AD_SAMPLE:
     case safe_browsing::SB_THREAT_TYPE_SUSPICIOUS_SITE:
     case safe_browsing::SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE:
+    case safe_browsing::SB_THREAT_TYPE_APK_DOWNLOAD:
       NOTREACHED();
       break;
   }
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.cc b/chrome/browser/offline_pages/android/offline_page_bridge.cc
index 9d611db..3472068 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.cc
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.cc
@@ -65,6 +65,25 @@
 
 const char kOfflinePageBridgeKey[] = "offline-page-bridge";
 
+void JNI_SavePageRequest_ToJavaOfflinePageList(
+    JNIEnv* env,
+    const JavaRef<jobject>& j_result_obj,
+    const std::vector<OfflinePageItem>& offline_pages) {
+  for (const OfflinePageItem& offline_page : offline_pages) {
+    Java_OfflinePageBridge_createOfflinePageAndAddToList(
+        env, j_result_obj,
+        ConvertUTF8ToJavaString(env, offline_page.url.spec()),
+        offline_page.offline_id,
+        ConvertUTF8ToJavaString(env, offline_page.client_id.name_space),
+        ConvertUTF8ToJavaString(env, offline_page.client_id.id),
+        ConvertUTF16ToJavaString(env, offline_page.title),
+        ConvertUTF8ToJavaString(env, offline_page.file_path.value()),
+        offline_page.file_size, offline_page.creation_time.ToJavaTime(),
+        offline_page.access_count, offline_page.last_access_time.ToJavaTime(),
+        ConvertUTF8ToJavaString(env, offline_page.request_origin));
+  }
+}
+
 ScopedJavaLocalRef<jobject> JNI_SavePageRequest_ToJavaOfflinePageItem(
     JNIEnv* env,
     const OfflinePageItem& offline_page) {
@@ -95,7 +114,7 @@
     const ScopedJavaGlobalRef<jobject>& j_callback_obj,
     const OfflinePageModel::MultipleOfflinePageItemResult& result) {
   JNIEnv* env = base::android::AttachCurrentThread();
-  OfflinePageBridge::AddOfflinePageItemsToJavaList(env, j_result_obj, result);
+  JNI_SavePageRequest_ToJavaOfflinePageList(env, j_result_obj, result);
   base::android::RunObjectCallbackAndroid(j_callback_obj, j_result_obj);
 }
 
@@ -215,8 +234,31 @@
 ScopedJavaLocalRef<jobjectArray> JNI_SavePageRequest_CreateJavaSavePageRequests(
     JNIEnv* env,
     std::vector<std::unique_ptr<SavePageRequest>> requests) {
-  return OfflinePageBridge::CreateJavaSavePageRequests(env,
-                                                       std::move(requests));
+  ScopedJavaLocalRef<jclass> save_page_request_clazz = base::android::GetClass(
+      env, "org/chromium/chrome/browser/offlinepages/SavePageRequest");
+  jobjectArray joa = env->NewObjectArray(
+      requests.size(), save_page_request_clazz.obj(), nullptr);
+  base::android::CheckException(env);
+
+  for (size_t i = 0; i < requests.size(); ++i) {
+    SavePageRequest request = *(requests[i]);
+    ScopedJavaLocalRef<jstring> name_space =
+        ConvertUTF8ToJavaString(env, request.client_id().name_space);
+    ScopedJavaLocalRef<jstring> id =
+        ConvertUTF8ToJavaString(env, request.client_id().id);
+    ScopedJavaLocalRef<jstring> url =
+        ConvertUTF8ToJavaString(env, request.url().spec());
+    ScopedJavaLocalRef<jstring> origin =
+        ConvertUTF8ToJavaString(env, request.request_origin());
+
+    ScopedJavaLocalRef<jobject> j_save_page_request =
+        Java_SavePageRequest_create(
+            env, static_cast<int>(request.request_state()),
+            request.request_id(), url, name_space, id, origin);
+    env->SetObjectArrayElement(joa, i, j_save_page_request.obj());
+  }
+
+  return ScopedJavaLocalRef<jobjectArray>(env, joa);
 }
 
 void OnGetAllRequestsDone(
@@ -342,57 +384,6 @@
 }
 
 // static
-ScopedJavaLocalRef<jobjectArray> OfflinePageBridge::CreateJavaSavePageRequests(
-    JNIEnv* env,
-    std::vector<std::unique_ptr<SavePageRequest>> requests) {
-  ScopedJavaLocalRef<jclass> save_page_request_clazz = base::android::GetClass(
-      env, "org/chromium/chrome/browser/offlinepages/SavePageRequest");
-  jobjectArray joa = env->NewObjectArray(
-      requests.size(), save_page_request_clazz.obj(), nullptr);
-  base::android::CheckException(env);
-
-  for (size_t i = 0; i < requests.size(); ++i) {
-    SavePageRequest request = *(requests[i]);
-    ScopedJavaLocalRef<jstring> name_space =
-        ConvertUTF8ToJavaString(env, request.client_id().name_space);
-    ScopedJavaLocalRef<jstring> id =
-        ConvertUTF8ToJavaString(env, request.client_id().id);
-    ScopedJavaLocalRef<jstring> url =
-        ConvertUTF8ToJavaString(env, request.url().spec());
-    ScopedJavaLocalRef<jstring> origin =
-        ConvertUTF8ToJavaString(env, request.request_origin());
-
-    ScopedJavaLocalRef<jobject> j_save_page_request =
-        Java_SavePageRequest_create(
-            env, static_cast<int>(request.request_state()),
-            request.request_id(), url, name_space, id, origin);
-    env->SetObjectArrayElement(joa, i, j_save_page_request.obj());
-  }
-
-  return ScopedJavaLocalRef<jobjectArray>(env, joa);
-}
-
-// static
-void OfflinePageBridge::AddOfflinePageItemsToJavaList(
-    JNIEnv* env,
-    const JavaRef<jobject>& j_result_obj,
-    const std::vector<OfflinePageItem>& offline_pages) {
-  for (const OfflinePageItem& offline_page : offline_pages) {
-    Java_OfflinePageBridge_createOfflinePageAndAddToList(
-        env, j_result_obj,
-        ConvertUTF8ToJavaString(env, offline_page.url.spec()),
-        offline_page.offline_id,
-        ConvertUTF8ToJavaString(env, offline_page.client_id.name_space),
-        ConvertUTF8ToJavaString(env, offline_page.client_id.id),
-        ConvertUTF16ToJavaString(env, offline_page.title),
-        ConvertUTF8ToJavaString(env, offline_page.file_path.value()),
-        offline_page.file_size, offline_page.creation_time.ToJavaTime(),
-        offline_page.access_count, offline_page.last_access_time.ToJavaTime(),
-        ConvertUTF8ToJavaString(env, offline_page.request_origin));
-  }
-}
-
-// static
 std::string OfflinePageBridge::GetEncodedOriginApp(
     const content::WebContents* web_contents) {
   TabAndroid* tab = TabAndroid::FromWebContents(web_contents);
@@ -448,6 +439,24 @@
       JNI_SavePageRequest_ToJavaDeletedPageInfo(env, page_info));
 }
 
+void OfflinePageBridge::GetAllPages(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jobject>& j_result_obj,
+    const JavaParamRef<jobject>& j_callback_obj) {
+  DCHECK(j_result_obj);
+  DCHECK(j_callback_obj);
+
+  ScopedJavaGlobalRef<jobject> j_result_ref;
+  j_result_ref.Reset(env, j_result_obj);
+
+  ScopedJavaGlobalRef<jobject> j_callback_ref;
+  j_callback_ref.Reset(env, j_callback_obj);
+
+  offline_page_model_->GetAllPages(base::BindOnce(
+      &MultipleOfflinePageItemCallback, j_result_ref, j_callback_ref));
+}
+
 void OfflinePageBridge::GetPageByOfflineId(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.h b/chrome/browser/offline_pages/android/offline_page_bridge.h
index fd438c8b..70c34f42 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.h
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.h
@@ -14,7 +14,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/supports_user_data.h"
 #include "components/offline_items_collection/core/launch_location.h"
-#include "components/offline_pages/core/background/save_page_request.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_page_model.h"
 
@@ -40,16 +39,6 @@
       JNIEnv* env,
       const OfflinePageItem& offline_page);
 
-  static base::android::ScopedJavaLocalRef<jobjectArray>
-  CreateJavaSavePageRequests(
-      JNIEnv* env,
-      std::vector<std::unique_ptr<SavePageRequest>> requests);
-
-  static void AddOfflinePageItemsToJavaList(
-      JNIEnv* env,
-      const base::android::JavaRef<jobject>& j_result_obj,
-      const std::vector<OfflinePageItem>& offline_pages);
-
   static std::string GetEncodedOriginApp(
       const content::WebContents* web_contents);
 
@@ -65,6 +54,11 @@
   void OfflinePageDeleted(
       const OfflinePageModel::DeletedPageInfo& page_info) override;
 
+  void GetAllPages(JNIEnv* env,
+                   const base::android::JavaParamRef<jobject>& obj,
+                   const base::android::JavaParamRef<jobject>& j_result_obj,
+                   const base::android::JavaParamRef<jobject>& j_callback_obj);
+
   void GetPageByOfflineId(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/offline_pages/android/offline_test_util_jni.cc b/chrome/browser/offline_pages/android/offline_test_util_jni.cc
deleted file mode 100644
index 58914dab..0000000
--- a/chrome/browser/offline_pages/android/offline_test_util_jni.cc
+++ /dev/null
@@ -1,115 +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 <sstream>
-
-#include "base/android/callback_android.h"
-#include "base/android/jni_android.h"
-#include "base/android/jni_array.h"
-#include "base/android/jni_string.h"
-#include "base/android/jni_utils.h"
-#include "base/json/json_writer.h"
-#include "chrome/browser/offline_pages/android/offline_page_bridge.h"
-#include "chrome/browser/offline_pages/offline_page_model_factory.h"
-#include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
-#include "chrome/browser/offline_pages/request_coordinator_factory.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "components/offline_pages/core/background/request_coordinator.h"
-#include "components/offline_pages/core/offline_page_model.h"
-#include "content/public/test/url_loader_interceptor.h"
-#include "jni/OfflineTestUtil_jni.h"
-
-// Below is the native implementation of OfflineTestUtil.java.
-
-namespace offline_pages {
-namespace {
-using ::base::android::JavaParamRef;
-using ::base::android::ScopedJavaGlobalRef;
-using ::base::android::ScopedJavaLocalRef;
-using ::offline_pages::android::OfflinePageBridge;
-
-Profile* GetProfile() {
-  Profile* profile = ProfileManager::GetLastUsedProfile();
-  DCHECK(profile);
-  return profile;
-}
-RequestCoordinator* GetRequestCoordinator() {
-  return RequestCoordinatorFactory::GetForBrowserContext(GetProfile());
-}
-OfflinePageModel* GetOfflinePageModel() {
-  return OfflinePageModelFactory::GetForBrowserContext(GetProfile());
-}
-
-void OnGetAllRequestsDone(
-    const ScopedJavaGlobalRef<jobject>& j_callback_obj,
-    std::vector<std::unique_ptr<SavePageRequest>> all_requests) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  base::android::RunObjectCallbackAndroid(
-      j_callback_obj,
-      offline_pages::android::OfflinePageBridge::CreateJavaSavePageRequests(
-          env, std::move(all_requests)));
-}
-
-void OnGetAllPagesDone(
-    const ScopedJavaGlobalRef<jobject>& j_result_obj,
-    const ScopedJavaGlobalRef<jobject>& j_callback_obj,
-    const OfflinePageModel::MultipleOfflinePageItemResult& result) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  OfflinePageBridge::AddOfflinePageItemsToJavaList(env, j_result_obj, result);
-  base::android::RunObjectCallbackAndroid(j_callback_obj, j_result_obj);
-}
-
-void OnDeletePageDone(const ScopedJavaGlobalRef<jobject>& j_callback_obj,
-                      OfflinePageModel::DeletePageResult result) {
-  base::android::RunIntCallbackAndroid(j_callback_obj,
-                                       static_cast<int>(result));
-}
-
-}  // namespace
-
-void JNI_OfflineTestUtil_GetRequestsInQueue(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& j_callback_obj) {
-  ScopedJavaGlobalRef<jobject> j_callback_ref(j_callback_obj);
-
-  RequestCoordinator* coordinator = GetRequestCoordinator();
-
-  if (!coordinator) {
-    // Callback with null to signal that results are unavailable.
-    const JavaParamRef<jobject> empty_result(nullptr);
-    base::android::RunObjectCallbackAndroid(j_callback_obj, empty_result);
-    return;
-  }
-
-  coordinator->GetAllRequests(
-      base::BindOnce(&OnGetAllRequestsDone, std::move(j_callback_ref)));
-}
-
-void JNI_OfflineTestUtil_GetAllPages(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& j_result_obj,
-    const JavaParamRef<jobject>& j_callback_obj) {
-  DCHECK(j_result_obj);
-  DCHECK(j_callback_obj);
-
-  ScopedJavaGlobalRef<jobject> j_result_ref(env, j_result_obj);
-  ScopedJavaGlobalRef<jobject> j_callback_ref(env, j_callback_obj);
-  GetOfflinePageModel()->GetAllPages(base::BindOnce(
-      &OnGetAllPagesDone, std::move(j_result_ref), std::move(j_callback_ref)));
-}
-
-void JNI_OfflineTestUtil_DeletePagesByOfflineId(
-    JNIEnv* env,
-    const JavaParamRef<jlongArray>& j_offline_ids_array,
-    const JavaParamRef<jobject>& j_callback_obj) {
-  ScopedJavaGlobalRef<jobject> j_callback_ref(env, j_callback_obj);
-  std::vector<int64_t> offline_ids;
-  base::android::JavaLongArrayToInt64Vector(env, j_offline_ids_array,
-                                            &offline_ids);
-  GetOfflinePageModel()->DeletePagesByOfflineId(
-      offline_ids,
-      base::BindOnce(&OnDeletePageDone, std::move(j_callback_ref)));
-}
-
-}  // namespace offline_pages
diff --git a/chrome/browser/resources/chromeos/switch_access/keyboard_handler.js b/chrome/browser/resources/chromeos/switch_access/keyboard_handler.js
index 5742fb7..3e56d7d 100644
--- a/chrome/browser/resources/chromeos/switch_access/keyboard_handler.js
+++ b/chrome/browser/resources/chromeos/switch_access/keyboard_handler.js
@@ -11,15 +11,35 @@
    */
   constructor(switchAccess) {
     /**
-     * SwitchAccess reference.
+     * Switch Access reference.
      * @private {!SwitchAccessInterface}
      */
     this.switchAccess_ = switchAccess;
 
+    /** @private {function(number)|undefined} */
+    this.keycodeCallback_;
+
     this.init_();
   }
 
   /**
+   * Listens for keycodes. When they're received, they are passed to |callback|.
+   * @param {function(number)} callback
+   */
+  listenForKeycodes(callback) {
+    this.keycodeCallback_ = callback;
+    chrome.accessibilityPrivate.forwardKeyEventsToSwitchAccess(true);
+  }
+
+  /**
+   * Stop listening for keycodes.
+   */
+  stopListeningForKeycodes() {
+    this.keycodeCallback_ = undefined;
+    chrome.accessibilityPrivate.forwardKeyEventsToSwitchAccess(false);
+  }
+
+  /**
    * Update the keyboard keys captured by Switch Access to those stored in
    * prefs.
    */
@@ -35,12 +55,22 @@
   }
 
   /**
+   * Forwards the current key code to the callback.
+   * @param {!Event} event
+   * @private
+   */
+  forwardKeyCode_(event) {
+    if (this.keycodeCallback_)
+      this.keycodeCallback_(event.keyCode);
+  }
+
+  /**
    * Run the command associated with the passed keyboard event.
    *
    * @param {!Event} event
    * @private
    */
-  handleKeyEvent_(event) {
+  handleSwitchActivated_(event) {
     for (const command of this.switchAccess_.getCommands()) {
       if (this.keyCodeFor_(command) === event.keyCode) {
         this.switchAccess_.runCommand(command);
@@ -56,7 +86,8 @@
    */
   init_() {
     this.updateSwitchAccessKeys();
-    document.addEventListener('keyup', this.handleKeyEvent_.bind(this));
+    document.addEventListener('keyup', this.handleSwitchActivated_.bind(this));
+    document.addEventListener('keydown', this.forwardKeyCode_.bind(this));
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access.js b/chrome/browser/resources/chromeos/switch_access/switch_access.js
index 6af5930..c548597 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access.js
@@ -134,6 +134,21 @@
   }
 
   /**
+   * Forwards the keycodes received from keyPressed events to |callback|.
+   * @param {function(number)} callback
+   */
+  listenForKeycodes(callback) {
+    this.keyboardHandler_.listenForKeycodes(callback);
+  }
+
+  /**
+   * Stops forwarding keycodes.
+   */
+  stopListeningForKeycodes() {
+    this.keyboardHandler_.stopListeningForKeycodes();
+  }
+
+  /**
    * Run the function binding for the specified command.
    * @override
    * @param {string} command
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js b/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
index c488f08..97c88ff 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
@@ -47,6 +47,17 @@
   getDefaultKeyCodeFor(command) {}
 
   /**
+   * Forwards keycodes received from keyPress events to |callback|.
+   * @param {function(number)} callback
+   */
+  listenForKeycodes(callback) {}
+
+  /**
+   * Stops forwarding keycodes.
+   */
+  stopListeningForKeycodes() {}
+
+  /**
    * Run the function binding for the specified command.
    * @param {string} command
    */
diff --git a/chrome/browser/resources/print_preview/BUILD.gn b/chrome/browser/resources/print_preview/BUILD.gn
index 6e21770..94826af 100644
--- a/chrome/browser/resources/print_preview/BUILD.gn
+++ b/chrome/browser/resources/print_preview/BUILD.gn
@@ -74,11 +74,6 @@
 }
 
 js_library("print_preview_utils") {
-  deps = [
-    "data:coordinate2d",
-    "data:size",
-    "//ui/webui/resources/js:cr",
-  ]
 }
 
 js_library("metrics") {
diff --git a/chrome/browser/resources/print_preview/data/BUILD.gn b/chrome/browser/resources/print_preview/data/BUILD.gn
index 3d9fdb7d..3b7b082 100644
--- a/chrome/browser/resources/print_preview/data/BUILD.gn
+++ b/chrome/browser/resources/print_preview/data/BUILD.gn
@@ -78,7 +78,6 @@
 
 js_library("destination") {
   deps = [
-    "..:print_preview_utils",
     "//ui/webui/resources/js:cr",
     "//ui/webui/resources/js:load_time_data",
   ]
diff --git a/chrome/browser/resources/print_preview/data/cloud_parsers.js b/chrome/browser/resources/print_preview/data/cloud_parsers.js
index 4069b25..2648923 100644
--- a/chrome/browser/resources/print_preview/data/cloud_parsers.js
+++ b/chrome/browser/resources/print_preview/data/cloud_parsers.js
@@ -111,7 +111,7 @@
     const optionalParams = {
       account: account,
       tags: tags,
-      isOwned: arrayContains(tags, OWNED_TAG),
+      isOwned: tags.includes(OWNED_TAG),
       lastAccessTime:
           parseInt(json[CloudDestinationField.LAST_ACCESS], 10) || Date.now(),
       cloudID: id,
@@ -121,7 +121,7 @@
     const cloudDest = new print_preview.Destination(
         id, parseType(json[CloudDestinationField.TYPE]), origin,
         json[CloudDestinationField.DISPLAY_NAME],
-        arrayContains(tags, RECENT_TAG) /*isRecent*/, connectionStatus,
+        tags.includes(RECENT_TAG) /*isRecent*/, connectionStatus,
         optionalParams);
     if (json.hasOwnProperty(CloudDestinationField.CAPABILITIES)) {
       cloudDest.capabilities = /** @type {!print_preview.Cdd} */ (
diff --git a/chrome/browser/resources/print_preview/data/destination.js b/chrome/browser/resources/print_preview/data/destination.js
index 5580239c5..6b0cfaec 100644
--- a/chrome/browser/resources/print_preview/data/destination.js
+++ b/chrome/browser/resources/print_preview/data/destination.js
@@ -607,12 +607,10 @@
 
     /** @return {boolean} Whether the destination is considered offline. */
     get isOffline() {
-      return arrayContains(
-          [
-            print_preview.DestinationConnectionStatus.OFFLINE,
-            print_preview.DestinationConnectionStatus.DORMANT
-          ],
-          this.connectionStatus_);
+      return [
+        print_preview.DestinationConnectionStatus.OFFLINE,
+        print_preview.DestinationConnectionStatus.DORMANT
+      ].includes(this.connectionStatus_);
     }
 
     /**
diff --git a/chrome/browser/resources/print_preview/data/destination_match.js b/chrome/browser/resources/print_preview/data/destination_match.js
index be030065..e30339c 100644
--- a/chrome/browser/resources/print_preview/data/destination_match.js
+++ b/chrome/browser/resources/print_preview/data/destination_match.js
@@ -51,16 +51,17 @@
     }
 
     /**
-     * @param {string} origin Origin to match.
+     * @param {!print_preview.DestinationOrigin} origin Origin to match.
      * @return {boolean} Whether the origin is one of the {@code origins_}.
      */
     matchOrigin(origin) {
-      return arrayContains(this.origins_, origin);
+      return this.origins_.includes(origin);
     }
 
     /**
      * @param {string} id Id of the destination.
-     * @param {string} origin Origin of the destination.
+     * @param {!print_preview.DestinationOrigin} origin Origin of the
+     *     destination.
      * @return {boolean} Whether destination is the same as initial.
      */
     matchIdAndOrigin(id, origin) {
@@ -98,13 +99,11 @@
      * @private
      */
     isVirtualDestination_(destination) {
-      if (destination.origin == print_preview.DestinationOrigin.LOCAL) {
-        return arrayContains(
-            [print_preview.Destination.GooglePromotedId.SAVE_AS_PDF],
-            destination.id);
+      if (destination.origin === print_preview.DestinationOrigin.LOCAL) {
+        return destination.id ===
+            print_preview.Destination.GooglePromotedId.SAVE_AS_PDF;
       }
-      return arrayContains(
-          [print_preview.Destination.GooglePromotedId.DOCS], destination.id);
+      return destination.id === print_preview.Destination.GooglePromotedId.DOCS;
     }
 
     /**
diff --git a/chrome/browser/resources/print_preview/new/BUILD.gn b/chrome/browser/resources/print_preview/new/BUILD.gn
index 9c71426..f4951f2a 100644
--- a/chrome/browser/resources/print_preview/new/BUILD.gn
+++ b/chrome/browser/resources/print_preview/new/BUILD.gn
@@ -108,6 +108,7 @@
   deps = [
     ":input_behavior",
     ":settings_behavior",
+    "..:print_preview_utils",
     "//ui/webui/resources/cr_elements/cr_input:cr_input",
     "//ui/webui/resources/js:load_time_data",
   ]
@@ -241,7 +242,6 @@
     ":settings_behavior",
     ":state",
     "..:native_layer",
-    "..:print_preview_utils",
     "../../pdf:pdf_scripting_api",
     "../data:coordinate2d",
     "../data:destination",
diff --git a/chrome/browser/resources/print_preview/new/preview_area.html b/chrome/browser/resources/print_preview/new/preview_area.html
index baeebf38..abda42d 100644
--- a/chrome/browser/resources/print_preview/new/preview_area.html
+++ b/chrome/browser/resources/print_preview/new/preview_area.html
@@ -8,7 +8,6 @@
 <link rel="import" href="chrome://resources/html/util.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="../native_layer.html">
-<link rel="import" href="../print_preview_utils.html">
 <link rel="import" href="../data/coordinate2d.html">
 <link rel="import" href="../data/destination.html">
 <link rel="import" href="../data/margins.html">
diff --git a/chrome/browser/resources/print_preview/print_preview_utils.html b/chrome/browser/resources/print_preview/print_preview_utils.html
index 8daa3afe..1e8d2bd8 100644
--- a/chrome/browser/resources/print_preview/print_preview_utils.html
+++ b/chrome/browser/resources/print_preview/print_preview_utils.html
@@ -1,5 +1 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="data/coordinate2d.html">
-<link rel="import" href="data/size.html">
-
 <script src="print_preview_utils.js"></script>
diff --git a/chrome/browser/resources/print_preview/print_preview_utils.js b/chrome/browser/resources/print_preview/print_preview_utils.js
index 7fbc963..a17e8f62 100644
--- a/chrome/browser/resources/print_preview/print_preview_utils.js
+++ b/chrome/browser/resources/print_preview/print_preview_utils.js
@@ -3,46 +3,9 @@
 // found in the LICENSE file.
 
 /**
- * @param {string} toTest The string to be tested.
- * @return {boolean} True if |toTest| contains only digits. Leading and trailing
- *     whitespace is allowed.
- */
-function isInteger(toTest) {
-  const numericExp = /^\s*[0-9]+\s*$/;
-  return numericExp.test(toTest);
-}
-
-/**
- * Returns true if |value| is a valid non zero positive integer.
- * @param {string} value The string to be tested.
- * @return {boolean} true if the |value| is valid non zero positive integer.
- */
-function isPositiveInteger(value) {
-  return isInteger(value) && parseInt(value, 10) > 0;
-}
-
-/**
- * Returns true if the contents of the two arrays are equal.
- * @param {Array<{from: number, to: number}>} array1 The first array.
- * @param {Array<{from: number, to: number}>} array2 The second array.
- * @return {boolean} true if the arrays are equal.
- */
-function areArraysEqual(array1, array2) {
-  if (array1.length != array2.length) {
-    return false;
-  }
-  for (let i = 0; i < array1.length; i++) {
-    if (array1[i] !== array2[i]) {
-      return false;
-    }
-  }
-  return true;
-}
-
-/**
  * Returns true if the contents of the two page ranges are equal.
- * @param {Array} array1 The first array.
- * @param {Array} array2 The second array.
+ * @param {!Array<{ to: number, from: number }>} array1 The first array.
+ * @param {!Array<{ to: number, from: number }>} array2 The second array.
  * @return {boolean} true if the arrays are equal.
  */
 function areRangesEqual(array1, array2) {
@@ -58,179 +21,6 @@
 }
 
 /**
- * Removes duplicate elements from |inArray| and returns a new array.
- * |inArray| is not affected. It assumes that |inArray| is already sorted.
- * @param {!Array<number>} inArray The array to be processed.
- * @return {!Array<number>} The array after processing.
- */
-function removeDuplicates(inArray) {
-  const out = [];
-
-  if (inArray.length == 0) {
-    return out;
-  }
-
-  out.push(inArray[0]);
-  for (let i = 1; i < inArray.length; ++i) {
-    if (inArray[i] != inArray[i - 1]) {
-      out.push(inArray[i]);
-    }
-  }
-  return out;
-}
-
-/** @enum {number} */
-const PageRangeStatus = {
-  NO_ERROR: 0,
-  SYNTAX_ERROR: -1,
-  LIMIT_ERROR: -2
-};
-
-/**
- * Returns a list of ranges in |pageRangeText|. The ranges are
- * listed in the order they appear in |pageRangeText| and duplicates are not
- * eliminated. If |pageRangeText| is not valid, PageRangeStatus.SYNTAX_ERROR
- * is returned.
- * A valid selection has a parsable format and every page identifier is
- * greater than 0 unless wildcards are used(see examples).
- * If a page is greater than |totalPageCount|, PageRangeStatus.LIMIT_ERROR
- * is returned.
- * If |totalPageCount| is 0 or undefined function uses impossibly large number
- * instead.
- * Wildcard the first number must be larger than 0 and less or equal then
- * |totalPageCount|. If it's missed then 1 is used as the first number.
- * Wildcard the second number must be larger then the first number. If it's
- * missed then |totalPageCount| is used as the second number.
- * Example: "1-4, 9, 3-6, 10, 11" is valid, assuming |totalPageCount| >= 11.
- * Example: "1-4, -6" is valid, assuming |totalPageCount| >= 6.
- * Example: "2-" is valid, assuming |totalPageCount| >= 2, means from 2 to the
- *          end.
- * Example: "4-2, 11, -6" is invalid.
- * Example: "-" is valid, assuming |totalPageCount| >= 1.
- * Example: "1-4dsf, 11" is invalid regardless of |totalPageCount|.
- * @param {string} pageRangeText The text to be checked.
- * @param {number=} opt_totalPageCount The total number of pages.
- * @return {!PageRangeStatus|!Array<{from: number, to: number}>}
- */
-function pageRangeTextToPageRanges(pageRangeText, opt_totalPageCount) {
-  if (pageRangeText == '') {
-    return [];
-  }
-
-  const MAX_PAGE_NUMBER = 1000000000;
-  const totalPageCount =
-      opt_totalPageCount ? opt_totalPageCount : MAX_PAGE_NUMBER;
-
-  const regex = /^\s*([0-9]*)\s*-\s*([0-9]*)\s*$/;
-  const parts = pageRangeText.split(/,|\u3001/);
-
-  const pageRanges = [];
-  for (let i = 0; i < parts.length; ++i) {
-    const match = parts[i].match(regex);
-    if (match) {
-      if (!isPositiveInteger(match[1]) && match[1] !== '') {
-        return PageRangeStatus.SYNTAX_ERROR;
-      }
-      if (!isPositiveInteger(match[2]) && match[2] !== '') {
-        return PageRangeStatus.SYNTAX_ERROR;
-      }
-      const from = match[1] ? parseInt(match[1], 10) : 1;
-      const to = match[2] ? parseInt(match[2], 10) : totalPageCount;
-      if (from > to) {
-        return PageRangeStatus.SYNTAX_ERROR;
-      }
-      if (to > totalPageCount) {
-        return PageRangeStatus.LIMIT_ERROR;
-      }
-      pageRanges.push({'from': from, 'to': to});
-    } else {
-      if (!isPositiveInteger(parts[i])) {
-        return PageRangeStatus.SYNTAX_ERROR;
-      }
-      const singlePageNumber = parseInt(parts[i], 10);
-      if (singlePageNumber > totalPageCount) {
-        return PageRangeStatus.LIMIT_ERROR;
-      }
-      pageRanges.push({'from': singlePageNumber, 'to': singlePageNumber});
-    }
-  }
-  return pageRanges;
-}
-
-/**
- * Returns a list of pages defined by |pagesRangeText|. The pages are
- * listed in the order they appear in |pageRangeText| and duplicates are not
- * eliminated. If |pageRangeText| is not valid according or
- * |totalPageCount| undefined [1,2,...,totalPageCount] is returned.
- * See pageRangeTextToPageRanges for details.
- * @param {string} pageRangeText The text to be checked.
- * @param {number} totalPageCount The total number of pages.
- * @return {!Array<number>} A list of all pages.
- */
-function pageRangeTextToPageList(pageRangeText, totalPageCount) {
-  const pageRanges = pageRangeTextToPageRanges(pageRangeText, totalPageCount);
-  const pageList = [];
-  if (Array.isArray(pageRanges)) {
-    for (let i = 0; i < pageRanges.length; ++i) {
-      for (let j = pageRanges[i].from;
-           j <= Math.min(pageRanges[i].to, totalPageCount); ++j) {
-        pageList.push(j);
-      }
-    }
-  }
-  if (pageList.length == 0) {
-    for (let j = 1; j <= totalPageCount; ++j) {
-      pageList.push(j);
-    }
-  }
-  return pageList;
-}
-
-/**
- * @param {!Array<number>} pageList The list to be processed.
- * @return {!Array<number>} The contents of |pageList| in ascending order and
- *     without any duplicates. |pageList| is not affected.
- */
-function pageListToPageSet(pageList) {
-  let pageSet = [];
-  if (pageList.length == 0) {
-    return pageSet;
-  }
-  pageSet = pageList.slice(0);
-  pageSet.sort(function(a, b) {
-    return /** @type {number} */ (a) - /** @type {number} */ (b);
-  });
-  pageSet = removeDuplicates(pageSet);
-  return pageSet;
-}
-
-/**
- * @param {!HTMLElement} element Element to check for visibility.
- * @return {boolean} Whether the given element is visible.
- */
-function getIsVisible(element) {
-  return !element.hidden;
-}
-
-/**
- * Shows or hides an element.
- * @param {!HTMLElement} element Element to show or hide.
- * @param {boolean} isVisible Whether the element should be visible or not.
- */
-function setIsVisible(element, isVisible) {
-  element.hidden = !isVisible;
-}
-
-/**
- * @param {!Array} array Array to check for item.
- * @param {*} item Item to look for in array.
- * @return {boolean} Whether the item is in the array.
- */
-function arrayContains(array, item) {
-  return array.indexOf(item) != -1;
-}
-
-/**
  * @param {!Array<!{locale: string, value: string}>} localizedStrings An array
  *     of strings with corresponding locales.
  * @param {string} locale Locale to look the string up for.
diff --git a/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs b/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs
deleted file mode 100644
index 17fa25be..0000000
--- a/chrome/browser/resources/print_preview/print_preview_utils_unittest.gtestjs
+++ /dev/null
@@ -1,139 +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.
-
-/**
- * Test fixture for print preview utils.
- * @constructor
- * @extends {testing.Test}
- */
-function PrintPreviewUtilsUnitTest () {
-  testing.Test.call(this);
-}
-
-PrintPreviewUtilsUnitTest.prototype = {
-  __proto__: testing.Test.prototype,
-
-  /** @override */
-  extraLibraries: [
-    'print_preview_utils.js',
-  ]
-};
-
-TEST_F('PrintPreviewUtilsUnitTest', 'IsInteger', function() {
-  assertFalse(isInteger("  abc "));
-  assertFalse(isInteger("-7"));
-  assertFalse(isInteger("7.0"));
-  assertFalse(isInteger("a7a"));
-
-  assertTrue(isInteger("0"));
-  assertTrue(isInteger(" 100  "));
-  assertTrue(isInteger("0055 "));
-});
-
-TEST_F('PrintPreviewUtilsUnitTest', 'IsPositiveInteger', function() {
-  assertTrue(isPositiveInteger("100"));
-  assertTrue(isPositiveInteger("0055"));
-
-  assertFalse(isPositiveInteger("0"));
-  assertFalse(isPositiveInteger("-100"));
-  assertFalse(isPositiveInteger("sdfs"));
-});
-
-TEST_F('PrintPreviewUtilsUnitTest', 'AreArraysEqual', function() {
-  assertTrue(areArraysEqual([2,4,6,8,10], [2,4,6,8,10]));
-  assertTrue(areArraysEqual([], []));
-
-  assertFalse(areArraysEqual([2,4,6,8,10,12], [2,4,6,8,10]));
-  assertFalse(areArraysEqual([], [2,4,6,8,10]));
-});
-
-TEST_F('PrintPreviewUtilsUnitTest', 'RemoveDuplicates', function() {
-  var array1 = [1,2,2,3,6,6,6,7,9,10];
-  assertTrue(areArraysEqual(removeDuplicates(array1), [1,2,3,6,7,9,10]));
-});
-
-TEST_F('PrintPreviewUtilsUnitTest', 'PageRanges', function() {
-  function assertRangesEqual(simpleRange1, range2) {
-    var range1 = []
-    for (var i = 0; i < simpleRange1.length; i++) {
-      var from;
-      var to;
-      if (Array.isArray(simpleRange1[i])) {
-        from = simpleRange1[i][0];
-        to = simpleRange1[i][1];
-      } else {
-        from = simpleRange1[i];
-        to = simpleRange1[i];
-      }
-      range1.push({'from': from, 'to': to});
-    }
-    assertTrue(areRangesEqual(range1, range2));
-  };
-  assertRangesEqual([1, 2, 3, 1, 56],
-                    pageRangeTextToPageRanges("1,2,3,1,56", 100));
-  assertRangesEqual([[1, 3],[6, 9], [6, 10]],
-                    pageRangeTextToPageRanges("1-3, 6-9,6-10 ", 100));
-  assertRangesEqual([[10, 100]],
-                    pageRangeTextToPageRanges("10-", 100));
-  assertRangesEqual([[10, 100000]],
-                    pageRangeTextToPageRanges("10-100000", 100000));
-  assertRangesEqual([[1, 100]],
-                    pageRangeTextToPageRanges("-", 100));
-  assertRangesEqual([1, 2],
-                    pageRangeTextToPageRanges("1,2", undefined));
-  assertRangesEqual([[1, 1000000000]],
-                    pageRangeTextToPageRanges("-", null));
-  assertRangesEqual([[1, 1000000000]],
-                    pageRangeTextToPageRanges("-", 0));
-
-  // https://crbug.com/806165
-  assertRangesEqual([1, 2, 3, 1, 56],
-                    pageRangeTextToPageRanges("1\u30012\u30013\u30011\u300156", 100));
-  assertRangesEqual([1, 2, 3, 1, 56],
-                    pageRangeTextToPageRanges("1,2,3\u30011\u300156", 100));
-});
-
-TEST_F('PrintPreviewUtilsUnitTest', 'InvalidPageRanges', function() {
-  assertEquals(PageRangeStatus.LIMIT_ERROR,
-      pageRangeTextToPageRanges("10-100000", 100));
-  assertEquals(PageRangeStatus.LIMIT_ERROR,
-      pageRangeTextToPageRanges("1,100000", 100));
-  assertEquals(PageRangeStatus.SYNTAX_ERROR,
-      pageRangeTextToPageRanges("1,2,0,56", 100));
-  assertEquals(PageRangeStatus.SYNTAX_ERROR,
-      pageRangeTextToPageRanges("-1,1,2,,56", 100));
-  assertEquals(PageRangeStatus.SYNTAX_ERROR,
-      pageRangeTextToPageRanges("1,2,56-40", 100));
-  assertEquals(PageRangeStatus.LIMIT_ERROR,
-      pageRangeTextToPageRanges("101-110", 100));
-
-  assertEquals(PageRangeStatus.SYNTAX_ERROR,
-      pageRangeTextToPageRanges("1\u30012\u30010\u300156", 100));
-  assertEquals(PageRangeStatus.SYNTAX_ERROR,
-      pageRangeTextToPageRanges("-1,1,2\u3001\u300156", 100));
-});
-
-TEST_F('PrintPreviewUtilsUnitTest', 'PageRangeTextToPageList', function() {
-  assertTrue(areArraysEqual([1],
-                            pageRangeTextToPageList("1", 10)));
-  assertTrue(areArraysEqual([1,2,3,4],
-                            pageRangeTextToPageList("1-4", 10)));
-  assertTrue(areArraysEqual([1,2,3,4,2,3,4],
-                            pageRangeTextToPageList("1-4, 2-4", 10)));
-  assertTrue(areArraysEqual([1,2,5,7,8,9,10,2,2,3],
-                            pageRangeTextToPageList("1-2, 5, 7-10, 2, 2, 3",
-                                                    10)));
-  assertTrue(areArraysEqual([5,6,7,8,9,10],
-                            pageRangeTextToPageList("5-", 10)));
-  assertTrue(areArraysEqual([],
-                            pageRangeTextToPageList("1-4", undefined)));
-  assertTrue(areArraysEqual([1,2,3,4,5,6,7,8,9,10],
-                            pageRangeTextToPageList("1-abcd", 10)));
-});
-
-TEST_F('PrintPreviewUtilsUnitTest', 'PageListToPageSet', function() {
-  assertTrue(areArraysEqual([1,2,3,4], pageListToPageSet([4,3,2,1,1,1])));
-  assertTrue(areArraysEqual([1,2,3,4], pageListToPageSet([1,2,2,3,4,1,1,1])));
-  assertTrue(areArraysEqual([], pageListToPageSet([])));
-});
diff --git a/chrome/browser/resources/settings/people_page/account_manager.html b/chrome/browser/resources/settings/people_page/account_manager.html
index 0c996154..1dea615 100644
--- a/chrome/browser/resources/settings/people_page/account_manager.html
+++ b/chrome/browser/resources/settings/people_page/account_manager.html
@@ -32,6 +32,17 @@
       #add-account-button {
         cursor: pointer;
       }
+
+      #error-badge {
+        left: 60%;
+        position: relative;
+        top: 60%;
+      }
+
+      :host-context([dir='rtl']) #error-badge {
+        left: auto;
+        right: 60%;
+      }
     </style>
 
     <div class="settings-box first">$i18n{accountManagerDescription}</div>
@@ -45,7 +56,11 @@
         <div class="settings-box">
 
           <div class="profile-icon"
-              style="background-image: [[getIconImageSet_(item.pic)]]"></div>
+              style="background-image: [[getIconImageSet_(item.pic)]]">
+            <template is="dom-if" if="[[!item.isSignedIn]]">
+              <img id="error-badge" src="chrome://resources/images/error_badge.svg">
+            </template>
+          </div>
 
           <div class="middle two-line no-min-width">
             <div class="flex text-elide">
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 5fdc5156..79395b0 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -99,6 +99,8 @@
       "safe_browsing_service.cc",
       "safe_browsing_service.h",
       "services_delegate.h",
+      "telemetry/telemetry_service.cc",
+      "telemetry/telemetry_service.h",
       "test_safe_browsing_blocking_page_quiet.cc",
       "test_safe_browsing_blocking_page_quiet.h",
       "trigger_creator.cc",
@@ -208,8 +210,8 @@
         "incident_reporting/state_store.h",
         "incident_reporting/tracked_preference_incident.cc",
         "incident_reporting/tracked_preference_incident.h",
-        "services_delegate_impl.cc",
-        "services_delegate_impl.h",
+        "services_delegate_desktop.cc",
+        "services_delegate_desktop.h",
         "signature_evaluator_mac.h",
         "signature_evaluator_mac.mm",
       ]
@@ -233,8 +235,10 @@
       sources += [
         "../loader/safe_browsing_resource_throttle.cc",
         "../loader/safe_browsing_resource_throttle.h",
-        "services_delegate_stub.cc",
-        "services_delegate_stub.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/android:safe_browsing_mobile" ]
     }
diff --git a/chrome/browser/safe_browsing/android/services_delegate_android.cc b/chrome/browser/safe_browsing/android/services_delegate_android.cc
new file mode 100644
index 0000000..0dc7844
--- /dev/null
+++ b/chrome/browser/safe_browsing/android/services_delegate_android.cc
@@ -0,0 +1,133 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/android/services_delegate_android.h"
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/profiles/profile.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 "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_manager.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/preferences/public/mojom/tracked_preference_validation_delegate.mojom.h"
+
+namespace safe_browsing {
+
+// static
+std::unique_ptr<ServicesDelegate> ServicesDelegate::Create(
+    SafeBrowsingService* safe_browsing_service) {
+  return base::WrapUnique(new ServicesDelegateAndroid(safe_browsing_service));
+}
+
+// static
+std::unique_ptr<ServicesDelegate> ServicesDelegate::CreateForTest(
+    SafeBrowsingService* safe_browsing_service,
+    ServicesDelegate::ServicesCreator* services_creator) {
+  NOTREACHED();
+  return base::WrapUnique(new ServicesDelegateAndroid(safe_browsing_service));
+}
+
+ServicesDelegateAndroid::ServicesDelegateAndroid(
+    SafeBrowsingService* safe_browsing_service)
+    : safe_browsing_service_(safe_browsing_service) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+ServicesDelegateAndroid::~ServicesDelegateAndroid() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+void ServicesDelegateAndroid::InitializeCsdService(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {}
+
+const scoped_refptr<SafeBrowsingDatabaseManager>&
+ServicesDelegateAndroid::database_manager() const {
+  return database_manager_;
+}
+
+void ServicesDelegateAndroid::Initialize() {
+  if (!database_manager_set_for_tests_) {
+    database_manager_ =
+        base::WrapRefCounted(new RemoteSafeBrowsingDatabaseManager());
+  }
+}
+void ServicesDelegateAndroid::SetDatabaseManagerForTest(
+    SafeBrowsingDatabaseManager* database_manager) {
+  database_manager_set_for_tests_ = true;
+  database_manager_ = database_manager;
+}
+
+void ServicesDelegateAndroid::ShutdownServices() {}
+
+void ServicesDelegateAndroid::RefreshState(bool enable) {}
+
+void ServicesDelegateAndroid::ProcessResourceRequest(
+    const ResourceRequestInfo* request) {}
+
+std::unique_ptr<prefs::mojom::TrackedPreferenceValidationDelegate>
+ServicesDelegateAndroid::CreatePreferenceValidationDelegate(Profile* profile) {
+  return nullptr;
+}
+
+void ServicesDelegateAndroid::RegisterDelayedAnalysisCallback(
+    const DelayedAnalysisCallback& callback) {}
+
+void ServicesDelegateAndroid::AddDownloadManager(
+    content::DownloadManager* download_manager) {}
+
+ClientSideDetectionService* ServicesDelegateAndroid::GetCsdService() {
+  return nullptr;
+}
+
+DownloadProtectionService* ServicesDelegateAndroid::GetDownloadService() {
+  return nullptr;
+}
+
+void ServicesDelegateAndroid::StartOnIOThread(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    const V4ProtocolConfig& v4_config) {
+  database_manager_->StartOnIOThread(url_loader_factory, v4_config);
+}
+
+void ServicesDelegateAndroid::StopOnIOThread(bool shutdown) {
+  database_manager_->StopOnIOThread(shutdown);
+}
+
+void ServicesDelegateAndroid::CreatePasswordProtectionService(
+    Profile* profile) {}
+void ServicesDelegateAndroid::RemovePasswordProtectionService(
+    Profile* profile) {}
+PasswordProtectionService*
+ServicesDelegateAndroid::GetPasswordProtectionService(Profile* profile) const {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+void ServicesDelegateAndroid::CreateTelemetryService(Profile* profile) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(profile);
+
+  if (profile->IsOffTheRecord()) {
+    return;
+  }
+
+  telemetry_service_ = std::make_unique<AndroidTelemetryService>(
+      safe_browsing_service_, profile);
+}
+
+void ServicesDelegateAndroid::RemoveTelemetryService() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  telemetry_service_.release();
+}
+
+TelemetryService* ServicesDelegateAndroid::GetTelemetryService() const {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  return telemetry_service_.get();
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/services_delegate_stub.h b/chrome/browser/safe_browsing/android/services_delegate_android.h
similarity index 66%
rename from chrome/browser/safe_browsing/services_delegate_stub.h
rename to chrome/browser/safe_browsing/android/services_delegate_android.h
index d86dce84..216fcce 100644
--- a/chrome/browser/safe_browsing/services_delegate_stub.h
+++ b/chrome/browser/safe_browsing/android/services_delegate_android.h
@@ -2,19 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_STUB_H_
-#define CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_STUB_H_
+#ifndef CHROME_BROWSER_SAFE_BROWSING_ANDROID_SERVICES_DELEGATE_ANDROID_H_
+#define CHROME_BROWSER_SAFE_BROWSING_ANDROID_SERVICES_DELEGATE_ANDROID_H_
 
 #include "base/macros.h"
 #include "chrome/browser/safe_browsing/services_delegate.h"
 
 namespace safe_browsing {
 
-// Dummy ServicesDelegate implementation. Create via ServicesDelegate::Create().
-class ServicesDelegateStub : public ServicesDelegate {
+class AndroidTelemetryService;
+class TelemetryService;
+
+// Android ServicesDelegate implementation. Create via
+// ServicesDelegate::Create().
+class ServicesDelegateAndroid : public ServicesDelegate {
  public:
-  ServicesDelegateStub();
-  ~ServicesDelegateStub() override;
+  explicit ServicesDelegateAndroid(SafeBrowsingService* safe_browsing_service);
+  ~ServicesDelegateAndroid() override;
 
  private:
   // ServicesDelegate:
@@ -45,13 +49,22 @@
   PasswordProtectionService* GetPasswordProtectionService(
       Profile* profile) const override;
 
+  void CreateTelemetryService(Profile* profile) override;
+  void RemoveTelemetryService() override;
+  TelemetryService* GetTelemetryService() const override;
+
+  SafeBrowsingService* const safe_browsing_service_;
+
+  // The telemetry service tied to the current profile.
+  std::unique_ptr<AndroidTelemetryService> telemetry_service_;
+
   scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
   // Has the database_manager been set for tests?
   bool database_manager_set_for_tests_ = false;
 
-  DISALLOW_COPY_AND_ASSIGN(ServicesDelegateStub);
+  DISALLOW_COPY_AND_ASSIGN(ServicesDelegateAndroid);
 };
 
 }  // namespace safe_browsing
 
-#endif  // CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_STUB_H_
+#endif  // CHROME_BROWSER_SAFE_BROWSING_ANDROID_SERVICES_DELEGATE_ANDROID_H_
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index 4468b5c6..d22ce50 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -454,6 +454,7 @@
       DCHECK_CURRENTLY_ON(BrowserThread::UI);
       Profile* profile = content::Source<Profile>(source).ptr();
       services_delegate_->CreatePasswordProtectionService(profile);
+      services_delegate_->CreateTelemetryService(profile);
       if (!profile->IsOffTheRecord())
         AddPrefService(profile->GetPrefs());
       break;
@@ -462,6 +463,7 @@
       DCHECK_CURRENTLY_ON(BrowserThread::UI);
       Profile* profile = content::Source<Profile>(source).ptr();
       services_delegate_->RemovePasswordProtectionService(profile);
+      services_delegate_->RemoveTelemetryService();
       if (!profile->IsOffTheRecord())
         RemovePrefService(profile->GetPrefs());
       break;
diff --git a/chrome/browser/safe_browsing/services_delegate.h b/chrome/browser/safe_browsing/services_delegate.h
index e880de68..b474840 100644
--- a/chrome/browser/safe_browsing/services_delegate.h
+++ b/chrome/browser/safe_browsing/services_delegate.h
@@ -36,6 +36,7 @@
 struct ResourceRequestInfo;
 class SafeBrowsingService;
 class SafeBrowsingDatabaseManager;
+class TelemetryService;
 struct V4ProtocolConfig;
 
 // Abstraction to help organize code for mobile vs full safe browsing modes.
@@ -117,6 +118,10 @@
   virtual void RemovePasswordProtectionService(Profile* profile) = 0;
   virtual PasswordProtectionService* GetPasswordProtectionService(
       Profile* profile) const = 0;
+
+  virtual void CreateTelemetryService(Profile* profile) = 0;
+  virtual void RemoveTelemetryService() = 0;
+  virtual TelemetryService* GetTelemetryService() const = 0;
 };
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/services_delegate_impl.cc b/chrome/browser/safe_browsing/services_delegate_desktop.cc
similarity index 74%
rename from chrome/browser/safe_browsing/services_delegate_impl.cc
rename to chrome/browser/safe_browsing/services_delegate_desktop.cc
index 37099457..1eb00c0 100644
--- a/chrome/browser/safe_browsing/services_delegate_impl.cc
+++ b/chrome/browser/safe_browsing/services_delegate_desktop.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/safe_browsing/services_delegate_impl.h"
+#include "chrome/browser/safe_browsing/services_delegate_desktop.h"
 
 #include <utility>
 
@@ -10,6 +10,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
+#include "chrome/browser/safe_browsing/telemetry/telemetry_service.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/safe_browsing/db/v4_local_database_manager.h"
 #include "content/public/browser/browser_thread.h"
@@ -22,7 +23,7 @@
 std::unique_ptr<ServicesDelegate> ServicesDelegate::Create(
     SafeBrowsingService* safe_browsing_service) {
   return base::WrapUnique(
-      new ServicesDelegateImpl(safe_browsing_service, nullptr));
+      new ServicesDelegateDesktop(safe_browsing_service, nullptr));
 }
 
 // static
@@ -30,10 +31,10 @@
     SafeBrowsingService* safe_browsing_service,
     ServicesDelegate::ServicesCreator* services_creator) {
   return base::WrapUnique(
-      new ServicesDelegateImpl(safe_browsing_service, services_creator));
+      new ServicesDelegateDesktop(safe_browsing_service, services_creator));
 }
 
-ServicesDelegateImpl::ServicesDelegateImpl(
+ServicesDelegateDesktop::ServicesDelegateDesktop(
     SafeBrowsingService* safe_browsing_service,
     ServicesDelegate::ServicesCreator* services_creator)
     : safe_browsing_service_(safe_browsing_service),
@@ -41,11 +42,11 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
-ServicesDelegateImpl::~ServicesDelegateImpl() {
+ServicesDelegateDesktop::~ServicesDelegateDesktop() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
-void ServicesDelegateImpl::InitializeCsdService(
+void ServicesDelegateDesktop::InitializeCsdService(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 #if defined(SAFE_BROWSING_CSD)
@@ -57,16 +58,16 @@
 }
 
 ExtendedReportingLevel
-ServicesDelegateImpl::GetEstimatedExtendedReportingLevel() const {
+ServicesDelegateDesktop::GetEstimatedExtendedReportingLevel() const {
   return safe_browsing_service_->estimated_extended_reporting_by_prefs();
 }
 
 const scoped_refptr<SafeBrowsingDatabaseManager>&
-ServicesDelegateImpl::database_manager() const {
+ServicesDelegateDesktop::database_manager() const {
   return database_manager_;
 }
 
-void ServicesDelegateImpl::Initialize() {
+void ServicesDelegateDesktop::Initialize() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   if (!database_manager_set_for_tests_) {
@@ -93,14 +94,14 @@
           : CreateResourceRequestDetector());
 }
 
-void ServicesDelegateImpl::SetDatabaseManagerForTest(
+void ServicesDelegateDesktop::SetDatabaseManagerForTest(
     SafeBrowsingDatabaseManager* database_manager_to_set) {
   DCHECK(!database_manager_);
   database_manager_set_for_tests_ = true;
   database_manager_ = database_manager_to_set;
 }
 
-void ServicesDelegateImpl::ShutdownServices() {
+void ServicesDelegateDesktop::ShutdownServices() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   // The IO thread is going away, so make sure the ClientSideDetectionService
   // dtor executes now since it may call the dtor of URLFetcher which relies
@@ -117,7 +118,7 @@
   download_service_.reset();
 }
 
-void ServicesDelegateImpl::RefreshState(bool enable) {
+void ServicesDelegateDesktop::RefreshState(bool enable) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (csd_service_)
     csd_service_->SetEnabledAndRefreshState(enable);
@@ -125,7 +126,7 @@
     download_service_->SetEnabled(enable);
 }
 
-void ServicesDelegateImpl::ProcessResourceRequest(
+void ServicesDelegateDesktop::ProcessResourceRequest(
     const ResourceRequestInfo* request) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (resource_request_detector_)
@@ -133,68 +134,70 @@
 }
 
 std::unique_ptr<prefs::mojom::TrackedPreferenceValidationDelegate>
-ServicesDelegateImpl::CreatePreferenceValidationDelegate(Profile* profile) {
+ServicesDelegateDesktop::CreatePreferenceValidationDelegate(Profile* profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   return incident_service_->CreatePreferenceValidationDelegate(profile);
 }
 
-void ServicesDelegateImpl::RegisterDelayedAnalysisCallback(
+void ServicesDelegateDesktop::RegisterDelayedAnalysisCallback(
     const DelayedAnalysisCallback& callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   incident_service_->RegisterDelayedAnalysisCallback(callback);
 }
 
-void ServicesDelegateImpl::AddDownloadManager(
+void ServicesDelegateDesktop::AddDownloadManager(
     content::DownloadManager* download_manager) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   incident_service_->AddDownloadManager(download_manager);
 }
 
-ClientSideDetectionService* ServicesDelegateImpl::GetCsdService() {
+ClientSideDetectionService* ServicesDelegateDesktop::GetCsdService() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   return csd_service_.get();
 }
 
-DownloadProtectionService* ServicesDelegateImpl::GetDownloadService() {
+DownloadProtectionService* ServicesDelegateDesktop::GetDownloadService() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   return download_service_.get();
 }
 
 scoped_refptr<SafeBrowsingDatabaseManager>
-ServicesDelegateImpl::CreateDatabaseManager() {
+ServicesDelegateDesktop::CreateDatabaseManager() {
   return V4LocalDatabaseManager::Create(
       SafeBrowsingService::GetBaseFilename(),
       base::BindRepeating(
-          &ServicesDelegateImpl::GetEstimatedExtendedReportingLevel,
+          &ServicesDelegateDesktop::GetEstimatedExtendedReportingLevel,
           base::Unretained(this)));
 }
 
 DownloadProtectionService*
-ServicesDelegateImpl::CreateDownloadProtectionService() {
+ServicesDelegateDesktop::CreateDownloadProtectionService() {
   return new DownloadProtectionService(safe_browsing_service_);
 }
 
 IncidentReportingService*
-ServicesDelegateImpl::CreateIncidentReportingService() {
+ServicesDelegateDesktop::CreateIncidentReportingService() {
   return new IncidentReportingService(safe_browsing_service_);
 }
 
-ResourceRequestDetector* ServicesDelegateImpl::CreateResourceRequestDetector() {
+ResourceRequestDetector*
+ServicesDelegateDesktop::CreateResourceRequestDetector() {
   return new ResourceRequestDetector(safe_browsing_service_->database_manager(),
                                      incident_service_->GetIncidentReceiver());
 }
 
-void ServicesDelegateImpl::StartOnIOThread(
+void ServicesDelegateDesktop::StartOnIOThread(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     const V4ProtocolConfig& v4_config) {
   database_manager_->StartOnIOThread(url_loader_factory, v4_config);
 }
 
-void ServicesDelegateImpl::StopOnIOThread(bool shutdown) {
+void ServicesDelegateDesktop::StopOnIOThread(bool shutdown) {
   database_manager_->StopOnIOThread(shutdown);
 }
 
-void ServicesDelegateImpl::CreatePasswordProtectionService(Profile* profile) {
+void ServicesDelegateDesktop::CreatePasswordProtectionService(
+    Profile* profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(profile);
   auto it = password_protection_service_map_.find(profile);
@@ -205,7 +208,8 @@
   password_protection_service_map_[profile] = std::move(service);
 }
 
-void ServicesDelegateImpl::RemovePasswordProtectionService(Profile* profile) {
+void ServicesDelegateDesktop::RemovePasswordProtectionService(
+    Profile* profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(profile);
   auto it = password_protection_service_map_.find(profile);
@@ -213,12 +217,23 @@
     password_protection_service_map_.erase(it);
 }
 
-PasswordProtectionService* ServicesDelegateImpl::GetPasswordProtectionService(
-    Profile* profile) const {
+PasswordProtectionService*
+ServicesDelegateDesktop::GetPasswordProtectionService(Profile* profile) const {
   DCHECK(profile);
   auto it = password_protection_service_map_.find(profile);
   return it != password_protection_service_map_.end() ? it->second.get()
                                                       : nullptr;
 }
 
+// Only implemented on Android.
+void ServicesDelegateDesktop::CreateTelemetryService(Profile* profile) {}
+
+// Only implemented on Android.
+void ServicesDelegateDesktop::RemoveTelemetryService() {}
+
+// Only meaningful on Android.
+TelemetryService* ServicesDelegateDesktop::GetTelemetryService() const {
+  return nullptr;
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/services_delegate_impl.h b/chrome/browser/safe_browsing/services_delegate_desktop.h
similarity index 85%
rename from chrome/browser/safe_browsing/services_delegate_impl.h
rename to chrome/browser/safe_browsing/services_delegate_desktop.h
index 6faa7b9..62223a1 100644
--- a/chrome/browser/safe_browsing/services_delegate_impl.h
+++ b/chrome/browser/safe_browsing/services_delegate_desktop.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_IMPL_H_
-#define CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_IMPL_H_
+#ifndef CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_DESKTOP_H_
+#define CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_DESKTOP_H_
 
 #include <memory>
 
@@ -23,11 +23,11 @@
 
 // Actual ServicesDelegate implementation. Create via
 // ServicesDelegate::Create().
-class ServicesDelegateImpl : public ServicesDelegate {
+class ServicesDelegateDesktop : public ServicesDelegate {
  public:
-  ServicesDelegateImpl(SafeBrowsingService* safe_browsing_service,
-                       ServicesDelegate::ServicesCreator* services_creator);
-  ~ServicesDelegateImpl() override;
+  ServicesDelegateDesktop(SafeBrowsingService* safe_browsing_service,
+                          ServicesDelegate::ServicesCreator* services_creator);
+  ~ServicesDelegateDesktop() override;
 
  private:
   // ServicesDelegate:
@@ -72,6 +72,10 @@
   PasswordProtectionService* GetPasswordProtectionService(
       Profile* profile) const override;
 
+  void CreateTelemetryService(Profile* profile) override;
+  void RemoveTelemetryService() override;
+  TelemetryService* GetTelemetryService() const override;
+
   std::unique_ptr<ClientSideDetectionService> csd_service_;
   std::unique_ptr<DownloadProtectionService> download_service_;
   std::unique_ptr<IncidentReportingService> incident_service_;
@@ -93,9 +97,9 @@
   std::map<Profile*, std::unique_ptr<ChromePasswordProtectionService>>
       password_protection_service_map_;
 
-  DISALLOW_COPY_AND_ASSIGN(ServicesDelegateImpl);
+  DISALLOW_COPY_AND_ASSIGN(ServicesDelegateDesktop);
 };
 
 }  // namespace safe_browsing
 
-#endif  // CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_IMPL_H_
+#endif  // CHROME_BROWSER_SAFE_BROWSING_SERVICES_DELEGATE_DESKTOP_H_
diff --git a/chrome/browser/safe_browsing/services_delegate_stub.cc b/chrome/browser/safe_browsing/services_delegate_stub.cc
deleted file mode 100644
index a542627..0000000
--- a/chrome/browser/safe_browsing/services_delegate_stub.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/safe_browsing/services_delegate_stub.h"
-
-#include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "components/safe_browsing/android/remote_database_manager.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/preferences/public/mojom/tracked_preference_validation_delegate.mojom.h"
-
-namespace safe_browsing {
-
-// static
-std::unique_ptr<ServicesDelegate> ServicesDelegate::Create(
-    SafeBrowsingService* safe_browsing_service) {
-  return base::WrapUnique(new ServicesDelegateStub);
-}
-
-// static
-std::unique_ptr<ServicesDelegate> ServicesDelegate::CreateForTest(
-    SafeBrowsingService* safe_browsing_service,
-    ServicesDelegate::ServicesCreator* services_creator) {
-  NOTREACHED();
-  return base::WrapUnique(new ServicesDelegateStub);
-}
-
-ServicesDelegateStub::ServicesDelegateStub() {}
-
-ServicesDelegateStub::~ServicesDelegateStub() {}
-
-void ServicesDelegateStub::InitializeCsdService(
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {}
-
-const scoped_refptr<SafeBrowsingDatabaseManager>&
-ServicesDelegateStub::database_manager() const {
-  return database_manager_;
-}
-
-void ServicesDelegateStub::Initialize() {
-  if (!database_manager_set_for_tests_) {
-    database_manager_ =
-        base::WrapRefCounted(new RemoteSafeBrowsingDatabaseManager());
-  }
-}
-void ServicesDelegateStub::SetDatabaseManagerForTest(
-    SafeBrowsingDatabaseManager* database_manager) {
-  database_manager_set_for_tests_ = true;
-  database_manager_ = database_manager;
-}
-
-void ServicesDelegateStub::ShutdownServices() {}
-
-void ServicesDelegateStub::RefreshState(bool enable) {}
-
-void ServicesDelegateStub::ProcessResourceRequest(
-    const ResourceRequestInfo* request) {}
-
-std::unique_ptr<prefs::mojom::TrackedPreferenceValidationDelegate>
-ServicesDelegateStub::CreatePreferenceValidationDelegate(Profile* profile) {
-  return nullptr;
-}
-
-void ServicesDelegateStub::RegisterDelayedAnalysisCallback(
-    const DelayedAnalysisCallback& callback) {}
-
-void ServicesDelegateStub::AddDownloadManager(
-    content::DownloadManager* download_manager) {}
-
-ClientSideDetectionService* ServicesDelegateStub::GetCsdService() {
-  return nullptr;
-}
-
-DownloadProtectionService* ServicesDelegateStub::GetDownloadService() {
-  return nullptr;
-}
-
-void ServicesDelegateStub::StartOnIOThread(
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const V4ProtocolConfig& v4_config) {
-  database_manager_->StartOnIOThread(url_loader_factory, v4_config);
-}
-
-void ServicesDelegateStub::StopOnIOThread(bool shutdown) {
-  database_manager_->StopOnIOThread(shutdown);
-}
-
-void ServicesDelegateStub::CreatePasswordProtectionService(Profile* profile) {}
-void ServicesDelegateStub::RemovePasswordProtectionService(Profile* profile) {}
-PasswordProtectionService* ServicesDelegateStub::GetPasswordProtectionService(
-    Profile* profile) const {
-  NOTIMPLEMENTED();
-  return nullptr;
-}
-
-}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
new file mode 100644
index 0000000..489c9ed9
--- /dev/null
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
@@ -0,0 +1,136 @@
+// 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/browser/safe_browsing/telemetry/android/android_telemetry_service.h"
+
+#include <algorithm>
+
+#include "base/metrics/histogram_macros.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/safe_browsing/safe_browsing_service.h"
+#include "components/download/public/common/download_item.h"
+#include "components/keyed_service/core/service_access_type.h"
+#include "components/prefs/pref_service.h"
+#include "components/safe_browsing/features.h"
+#include "components/safe_browsing/triggers/trigger_manager.h"
+#include "content/public/browser/download_item_utils.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace safe_browsing {
+
+namespace {
+// MIME-type for APKs.
+const char kApkMimeType[] = "application/vnd.android.package-archive";
+
+bool IsFeatureEnabled() {
+  return base::FeatureList::IsEnabled(safe_browsing::kTelemetryForApkDownloads);
+}
+}  // namespace
+
+AndroidTelemetryService::AndroidTelemetryService(
+    SafeBrowsingService* sb_service,
+    Profile* profile)
+    : TelemetryService(),
+      profile_(profile),
+      sb_service_(sb_service),
+      trigger_manager_(sb_service->trigger_manager()) {
+  DCHECK(profile_);
+  DCHECK(IsSafeBrowsingEnabled());
+  if (!IsFeatureEnabled()) {
+    return;
+  }
+
+  content::DownloadManager* download_manager =
+      content::BrowserContext::GetDownloadManager(profile_);
+  if (download_manager) {
+    // Look for new downloads being created.
+    download_manager->AddObserver(this);
+  }
+}
+
+AndroidTelemetryService::~AndroidTelemetryService() {
+  content::DownloadManager* download_manager =
+      content::BrowserContext::GetDownloadManager(profile_);
+  if (download_manager) {
+    download_manager->RemoveObserver(this);
+  }
+}
+
+void AndroidTelemetryService::OnDownloadCreated(
+    content::DownloadManager* manager,
+    download::DownloadItem* item) {
+  DCHECK(IsFeatureEnabled());
+
+  content::WebContents* web_contents =
+      content::DownloadItemUtils::GetWebContents(item);
+  if (!web_contents) {
+    // TODO(vakh): This can happen sometimes on a browser launch. Identify this
+    // case and document it better.
+    return;
+  }
+
+  if (item->GetMimeType() != kApkMimeType) {
+    return;
+  }
+
+  item->AddObserver(this);
+  StartThreatDetailsCollection(web_contents);
+}
+
+void AndroidTelemetryService::OnDownloadUpdated(download::DownloadItem* item) {
+  DCHECK(IsFeatureEnabled());
+  DCHECK_EQ(kApkMimeType, item->GetMimeType());
+
+  if (item->GetState() == download::DownloadItem::COMPLETE) {
+    // Download completed. Send report, if allowed.
+    content::WebContents* web_contents =
+        content::DownloadItemUtils::GetWebContents(item);
+    FinishCollectingThreatDetails(web_contents);
+  }
+}
+
+const PrefService* AndroidTelemetryService::GetPrefs() {
+  return profile_->GetPrefs();
+}
+
+bool AndroidTelemetryService::IsSafeBrowsingEnabled() {
+  return GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
+}
+
+void AndroidTelemetryService::StartThreatDetailsCollection(
+    content::WebContents* web_contents) {
+  security_interstitials::UnsafeResource resource;
+  resource.threat_type = SB_THREAT_TYPE_APK_DOWNLOAD;
+  resource.url = web_contents->GetLastCommittedURL();
+  resource.web_contents_getter = resource.GetWebContentsGetter(
+      web_contents->GetMainFrame()->GetProcess()->GetID(),
+      web_contents->GetMainFrame()->GetRoutingID());
+
+  TriggerManagerReason reason;
+  // Ignores the return of |StartCollectingThreatDetails()| here and
+  // let TriggerManager decide whether it should start data
+  // collection.
+  trigger_manager_->StartCollectingThreatDetailsWithReason(
+      safe_browsing::TriggerType::APK_DOWNLOAD, web_contents, resource,
+      sb_service_->GetURLLoaderFactory(),
+      HistoryServiceFactory::GetForProfile(profile_,
+                                           ServiceAccessType::EXPLICIT_ACCESS),
+      TriggerManager::GetSBErrorDisplayOptions(*GetPrefs(), web_contents),
+      &reason);
+  // TODO(vakh): Log |reason|.
+}
+
+void AndroidTelemetryService::FinishCollectingThreatDetails(
+    content::WebContents* web_contents) {
+  trigger_manager_->FinishCollectingThreatDetails(
+      safe_browsing::TriggerType::APK_DOWNLOAD, web_contents,
+      base::TimeDelta::FromMilliseconds(0), /*did_proceed=*/false,
+      /*num_visit=*/0,
+      TriggerManager::GetSBErrorDisplayOptions(*GetPrefs(), web_contents));
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
new file mode 100644
index 0000000..1c2a33e
--- /dev/null
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
@@ -0,0 +1,85 @@
+// 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 CHROME_BROWSER_SAFE_BROWSING_TELEMETRY_ANDROID_ANDROID_TELEMETRY_SERVICE_H_
+#define CHROME_BROWSER_SAFE_BROWSING_TELEMETRY_ANDROID_ANDROID_TELEMETRY_SERVICE_H_
+
+#include <vector>
+
+#include "chrome/browser/safe_browsing/telemetry/telemetry_service.h"
+#include "components/download/public/common/download_item.h"
+#include "components/safe_browsing/proto/csd.pb.h"
+#include "content/public/browser/download_manager.h"
+
+class Profile;
+class PrefService;
+
+namespace content {
+class WebContents;
+}
+
+namespace safe_browsing {
+
+class SafeBrowsingService;
+class TriggerManager;
+
+// This class is used to send telemetry information to Safe Browsing for
+// security related incidents. The information is sent only if:
+// 1. The user has opted in to extended reporting, AND
+// 2. The security incident did not happen in an incognito window.
+// As the name suggests, this works only on Android.
+
+// The following events are currently considered security related incidents from
+// the perspective of this class:
+// 1. Downloading off-market APKs. See: go/zurkon-v1-referrer-dd
+
+class AndroidTelemetryService : public download::DownloadItem::Observer,
+                                public content::DownloadManager::Observer,
+                                public TelemetryService {
+ public:
+  AndroidTelemetryService(SafeBrowsingService* sb_service, Profile* profile);
+  ~AndroidTelemetryService() override;
+
+  // content::DownloadManager::Observer.
+  // Called when a new download is created.
+  void OnDownloadCreated(content::DownloadManager* manager,
+                         download::DownloadItem* item) override;
+
+  // download::DownloadItem::Observer
+  // Called when the state of a download item changes.
+  void OnDownloadUpdated(download::DownloadItem* download) override;
+
+ private:
+  // Calls into |trigger_manager_| to start collecting information about the
+  // download event. This information is then sent to Safe Browsing service
+  // in the form of a |ClientSafeBrowsingReportRequest| message.
+  // If the download gets cancelled, the report isn't sent.
+  void StartThreatDetailsCollection(content::WebContents* web_contents);
+
+  // Calls into |trigger_manager_| to prepare to send the referrer chain and
+  // other information to Safe Browsing service. Only called when the download
+  // completes and only for users who have opted into extended reporting.
+  void FinishCollectingThreatDetails(content::WebContents* web_contents);
+
+  // Helper method to get prefs from |profile_|.
+  const PrefService* GetPrefs();
+
+  // Helper method to check if Safe Browsing is enabled.
+  bool IsSafeBrowsingEnabled();
+
+  // Profile associated with this instance. Unowned.
+  Profile* profile_;
+
+  // Unowned.
+  SafeBrowsingService* sb_service_;
+
+  // Used to send the ClientSafeBrowsingReportRequest report. Unowned.
+  TriggerManager* trigger_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(AndroidTelemetryService);
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_TELEMETRY_ANDROID_ANDROID_TELEMETRY_SERVICE_H_
diff --git a/chrome/browser/safe_browsing/telemetry/telemetry_service.cc b/chrome/browser/safe_browsing/telemetry/telemetry_service.cc
new file mode 100644
index 0000000..73298e9
--- /dev/null
+++ b/chrome/browser/safe_browsing/telemetry/telemetry_service.cc
@@ -0,0 +1,15 @@
+// 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/browser/safe_browsing/telemetry/telemetry_service.h"
+
+namespace safe_browsing {
+
+TelemetryService::TelemetryService() : weak_factory_(this) {}
+
+TelemetryService::~TelemetryService() {
+  weak_factory_.InvalidateWeakPtrs();
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/telemetry/telemetry_service.h b/chrome/browser/safe_browsing/telemetry/telemetry_service.h
new file mode 100644
index 0000000..da3b0750
--- /dev/null
+++ b/chrome/browser/safe_browsing/telemetry/telemetry_service.h
@@ -0,0 +1,31 @@
+// 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 CHROME_BROWSER_SAFE_BROWSING_TELEMETRY_TELEMETRY_SERVICE_H_
+#define CHROME_BROWSER_SAFE_BROWSING_TELEMETRY_TELEMETRY_SERVICE_H_
+
+#include "base/memory/weak_ptr.h"
+
+namespace safe_browsing {
+
+// This class is used to send telemetry related to security incidents to Safe
+// Browsing. It is currently only implemented for downoads of APK files on
+// Android. See |AndroidTelemetryService|.
+class TelemetryService {
+ public:
+  TelemetryService();
+  virtual ~TelemetryService();
+
+  base::WeakPtr<TelemetryService> GetWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtrFactory<TelemetryService> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(TelemetryService);
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_TELEMETRY_TELEMETRY_SERVICE_H_
diff --git a/chrome/browser/ssl/security_state_tab_helper.cc b/chrome/browser/ssl/security_state_tab_helper.cc
index 9a59c907..254f203e 100644
--- a/chrome/browser/ssl/security_state_tab_helper.cc
+++ b/chrome/browser/ssl/security_state_tab_helper.cc
@@ -251,6 +251,7 @@
       case safe_browsing::SB_THREAT_TYPE_CSD_WHITELIST:
       case safe_browsing::SB_THREAT_TYPE_AD_SAMPLE:
       case safe_browsing::SB_THREAT_TYPE_SUSPICIOUS_SITE:
+      case safe_browsing::SB_THREAT_TYPE_APK_DOWNLOAD:
         // These threat types are not currently associated with
         // interstitials, and thus resources with these threat types are
         // not ever whitelisted or pending whitelisting.
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc
index 80d3832..9e95256 100644
--- a/chrome/browser/themes/theme_service.cc
+++ b/chrome/browser/themes/theme_service.cc
@@ -691,10 +691,12 @@
   // However, if the frame is already very dark or very light, respectively,
   // this won't contrast sufficiently with the frame color, so we'll need to
   // reverse when we're lightening and darkening.
-  const bool lighten = color_utils::GetRelativeLuminance(tab_color) <
-                       color_utils::GetRelativeLuminance(frame_color);
-  SkColor separator_color =
-      lighten ? SK_ColorWHITE : color_utils::GetDarkestColor();
+  SkColor separator_color = SK_ColorWHITE;
+  if (color_utils::GetRelativeLuminance(tab_color) >=
+      color_utils::GetRelativeLuminance(frame_color)) {
+    separator_color =
+        color_utils::BlendTowardOppositeLuma(separator_color, SK_AlphaOPAQUE);
+  }
 
   SkAlpha alpha = color_utils::FindBlendValueForContrastRatio(
       frame_color, separator_color, frame_color, kContrastRatio, 0);
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
index f5e2d77a..3ecac7c 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
@@ -51,6 +51,7 @@
       const std::vector<int>& keys_to_capture) override {}
   void ToggleDictationFromSource(
       ash::mojom::DictationToggleSource source) override {}
+  void ForwardKeyEventsToSwitchAccess(bool should_forward) override {}
 
   bool was_client_set() const { return was_client_set_; }
 
diff --git a/chrome/browser/ui/omnibox/lookalike_url_navigation_observer.cc b/chrome/browser/ui/omnibox/lookalike_url_navigation_observer.cc
index a9f6a96..145334f 100644
--- a/chrome/browser/ui/omnibox/lookalike_url_navigation_observer.cc
+++ b/chrome/browser/ui/omnibox/lookalike_url_navigation_observer.cc
@@ -290,13 +290,27 @@
           domain_and_registry)) {
     return std::string();
   }
+  const std::string domain_without_registry =
+      url_formatter::top_domains::HostnameWithoutRegistry(domain_and_registry);
 
   for (const std::string& skeleton :
        url_formatter::GetSkeletons(base::UTF8ToUTF16(domain_and_registry))) {
     for (const char* const top_domain_skeleton : kTop500) {
       if (IsEditDistanceAtMostOne(base::UTF8ToUTF16(skeleton),
                                   base::UTF8ToUTF16(top_domain_skeleton))) {
-        return url_formatter::LookupSkeletonInTopDomains(top_domain_skeleton);
+        const std::string top_domain =
+            url_formatter::LookupSkeletonInTopDomains(top_domain_skeleton);
+        DCHECK(!top_domain.empty());
+        // If the only difference between the navigated and top
+        // domains is the registry part, this is unlikely to be a spoofing
+        // attempt. Ignore this match and continue. E.g. If the navigated domain
+        // is google.com.tw and the top domain is google.com.tr, this won't
+        // produce a match.
+        const std::string top_domain_without_registry =
+            url_formatter::top_domains::HostnameWithoutRegistry(top_domain);
+        if (domain_without_registry != top_domain_without_registry) {
+          return top_domain;
+        }
       }
     }
   }
diff --git a/chrome/browser/ui/omnibox/lookalike_url_navigation_observer_browsertest.cc b/chrome/browser/ui/omnibox/lookalike_url_navigation_observer_browsertest.cc
index 47e2e37..9b0aa96 100644
--- a/chrome/browser/ui/omnibox/lookalike_url_navigation_observer_browsertest.cc
+++ b/chrome/browser/ui/omnibox/lookalike_url_navigation_observer_browsertest.cc
@@ -337,6 +337,20 @@
            LookalikeUrlNavigationObserver::MatchType::kEditDistance);
 }
 
+// Tests negative examples for the edit distance.
+IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationObserverBrowserTest,
+                       Idn_TopDomainEditDistance_NoMatch) {
+  // Matches google.com.tr but only differs in registry.
+  TestInfobarNotShown(
+      embedded_test_server()->GetURL("google.com.tw", "/title1.html"));
+  CheckNoUkm();
+
+  // Matches ask.com but is too short.
+  TestInfobarNotShown(
+      embedded_test_server()->GetURL("asq.com", "/title1.html"));
+  CheckNoUkm();
+}
+
 // Navigate to a domain whose visual representation looks like a domain with a
 // site engagement score above a certain threshold. This should record metrics.
 // It should also show a "Did you mean to go to ..." infobar if configured via
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 665718d..63b88c0 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -66,7 +66,6 @@
 #include "content/public/browser/notification_source.h"
 #include "content/public/common/content_switches.h"
 #include "extensions/common/switches.h"
-#include "net/base/port_util.h"
 #include "printing/buildflags/buildflags.h"
 
 #if defined(OS_CHROMEOS)
@@ -588,12 +587,6 @@
   }
 #endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)
 
-  if (command_line.HasSwitch(switches::kExplicitlyAllowedPorts)) {
-    std::string allowed_ports =
-        command_line.GetSwitchValueASCII(switches::kExplicitlyAllowedPorts);
-    net::SetExplicitlyAllowedPorts(allowed_ports);
-  }
-
   if (command_line.HasSwitch(switches::kValidateCrx)) {
     if (!process_startup) {
       LOG(ERROR) << "chrome is already running; you must close all running "
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 9f404f1..d4bae96 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -45,6 +45,7 @@
 #include "chrome/browser/previews/resource_loading_hints/resource_loading_hints_web_contents_observer.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/resource_coordinator/tab_helper.h"
+#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h"
 #include "chrome/browser/safe_browsing/trigger_creator.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/ssl/connection_help_tab_helper.h"
@@ -105,7 +106,6 @@
 #else
 #include "chrome/browser/banners/app_banner_manager_desktop.h"
 #include "chrome/browser/plugins/plugin_observer.h"
-#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h"
 #include "chrome/browser/safe_browsing/safe_browsing_tab_observer.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
 #include "chrome/browser/ui/hung_plugin_tab_helper.h"
@@ -245,6 +245,8 @@
   PreviewsUITabHelper::CreateForWebContents(web_contents);
   RecentlyAudibleHelper::CreateForWebContents(web_contents);
   ResourceLoadingHintsWebContentsObserver::CreateForWebContents(web_contents);
+  safe_browsing::SafeBrowsingNavigationObserver::MaybeCreateForWebContents(
+      web_contents);
   safe_browsing::TriggerCreator::MaybeCreateTriggersForWebContents(
       profile, web_contents);
   SearchEngineTabHelper::CreateForWebContents(web_contents);
@@ -300,8 +302,6 @@
   PluginObserver::CreateForWebContents(web_contents);
   SadTabHelper::CreateForWebContents(web_contents);
   safe_browsing::SafeBrowsingTabObserver::CreateForWebContents(web_contents);
-  safe_browsing::SafeBrowsingNavigationObserver::MaybeCreateForWebContents(
-      web_contents);
   SearchTabHelper::CreateForWebContents(web_contents);
   TabDialogs::CreateForWebContents(web_contents);
   web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h
index e7dc251..8e17b69 100644
--- a/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h
@@ -10,7 +10,6 @@
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/chromeos/login/screens/eula_view.h"
 #include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
-#include "chromeos/tpm/tpm_password_fetcher.h"
 #include "components/login/secure_module_util_chromeos.h"
 #include "content/public/browser/web_ui.h"
 
@@ -25,9 +24,7 @@
 
 // WebUI implementation of EulaScreenView. It is used to interact
 // with the eula part of the JS page.
-class EulaScreenHandler : public EulaView,
-                          public BaseScreenHandler,
-                          public TpmPasswordFetcherDelegate {
+class EulaScreenHandler : public EulaView, public BaseScreenHandler {
  public:
   explicit EulaScreenHandler(CoreOobeView* core_oobe_view);
   ~EulaScreenHandler() override;
diff --git a/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc b/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc
index 676de05..33395114 100644
--- a/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc
@@ -26,7 +26,7 @@
 #include "chromeos/dbus/debug_daemon_client.h"
 #include "chromeos/printing/ppd_provider.h"
 #include "chromeos/printing/printer_configuration.h"
-#include "components/printing/common/printer_capabilities.h"
+#include "components/printing/browser/printer_capabilities.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "printing/backend/print_backend_consts.h"
diff --git a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
index 8af704d9..dc23201 100644
--- a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
+++ b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
@@ -13,13 +13,13 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/webui/print_preview/print_preview_utils.h"
-#include "components/printing/common/printer_capabilities.h"
+#include "components/printing/browser/printer_capabilities.h"
 #include "content/public/browser/browser_thread.h"
 #include "printing/backend/print_backend.h"
 
 #if defined(OS_MACOSX)
 #include "components/printing/browser/features.h"
-#include "components/printing/common/printer_capabilities_mac.h"
+#include "components/printing/browser/printer_capabilities_mac.h"
 #endif
 
 namespace printing {
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
index 87977229..42a6368f 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -60,9 +60,9 @@
 #include "components/cloud_devices/common/cloud_devices_urls.h"
 #include "components/cloud_devices/common/printer_description.h"
 #include "components/prefs/pref_service.h"
+#include "components/printing/browser/printer_capabilities.h"
 #include "components/printing/common/cloud_print_cdd_conversion.h"
 #include "components/printing/common/print_messages.h"
-#include "components/printing/common/printer_capabilities.h"
 #include "components/signin/core/browser/account_consistency_method.h"
 #include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_utils.cc b/chrome/browser/ui/webui/print_preview/print_preview_utils.cc
index f3db3ad..48eca940 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_utils.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_utils.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/printing/print_view_manager.h"
 #include "chrome/browser/ui/webui/print_preview/printer_handler.h"
 #include "components/crash/core/common/crash_keys.h"
-#include "components/printing/common/printer_capabilities.h"
+#include "components/printing/browser/printer_capabilities.h"
 #include "content/public/browser/render_frame_host.h"
 #include "printing/backend/print_backend_consts.h"
 #include "printing/page_range.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/account_manager_handler.cc b/chrome/browser/ui/webui/settings/chromeos/account_manager_handler.cc
index f03b7f73..732ffb6d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/account_manager_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/account_manager_handler.cc
@@ -147,7 +147,6 @@
     if (!icon.IsEmpty()) {
       account.SetString("pic", webui::GetBitmapDataUrl(icon.AsBitmap()));
     } else {
-      // TODO(crbug.com/914751): Badge this icon with an exclamation mark.
       gfx::ImageSkia default_icon =
           *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
               IDR_LOGIN_DEFAULT_USER);
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index 14929d15..2e7c672 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -222,6 +222,18 @@
             "platforms": ["chromeos"]
       },
       {
+        "name": "forwardKeyEventsToSwitchAccess",
+        "type": "function",
+        "description": "When enabled, forwards key events to the Switch Access extension",
+        "parameters": [
+          {
+            "name": "shouldForward",
+            "type": "boolean"
+          }
+        ],
+        "platforms": ["chromeos"]
+      },
+      {
         "name": "setNativeChromeVoxArcSupportForCurrentApp",
         "type": "function",
         "description": "Sets current ARC app to use native ARC support.",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index e20a97e..26d0740 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3406,7 +3406,6 @@
     if (include_js_tests) {
       deps += [ "//chrome/test/data/webui:unit_tests_js" ]
       data += [
-        "$root_out_dir/test_data/chrome/browser/resources/print_preview/",
         "$root_out_dir/test_data/chrome/renderer/resources/extensions/",
         "$root_out_dir/test_data/ui/webui/",
       ]
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 293a2b1..d55e2853 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -174,14 +174,11 @@
   test_type = "unit"
   sources = [
     "../../../browser/resources/md_downloads/search_service_unittest.gtestjs",
-    "../../../browser/resources/print_preview/print_preview_utils_unittest.gtestjs",
     "../../../renderer/resources/extensions/notifications_custom_bindings.gtestjs",
     "../unit/framework_unittest.gtestjs",
   ]
   extra_js_files = [
     "../../../browser/resources/md_downloads/browser_proxy.js",
-    "../../../browser/resources/print_preview/data/measurement_system.js",
-    "../../../browser/resources/print_preview/print_preview_utils.js",
     "../../../browser/resources/md_downloads/search_service.js",
     "../../../renderer/resources/extensions/notifications_custom_bindings.js",
     "../../../renderer/resources/extensions/notifications_test_util.js",
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 20a2f232..c351215 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -43,7 +43,6 @@
     "//base/third_party/dynamic_annotations",
     "//chromeos/cryptohome",
     "//chromeos/dbus:authpolicy_proto",
-    "//chromeos/dbus:cryptohome_proto",
     "//chromeos/dbus:vm_applications_apps_proto",
     "//chromeos/login/auth",
     "//chromeos/login/login_state",
@@ -148,12 +147,6 @@
     "timezone/timezone_request.h",
     "timezone/timezone_resolver.cc",
     "timezone/timezone_resolver.h",
-    "tpm/tpm_password_fetcher.cc",
-    "tpm/tpm_password_fetcher.h",
-    "tpm/tpm_token_info_getter.cc",
-    "tpm/tpm_token_info_getter.h",
-    "tpm/tpm_token_loader.cc",
-    "tpm/tpm_token_loader.h",
   ]
   if (current_cpu == "arm" || current_cpu == "x86") {
     defines = [ "BINDER_IPC_32BIT" ]
@@ -303,6 +296,7 @@
     "//chromeos/network:unit_tests",
     "//chromeos/services:unit_tests",
     "//chromeos/settings:unit_tests",
+    "//chromeos/tpm:unit_tests",
     "//components/account_id",
     "//components/onc",
     "//components/policy:generated",
@@ -350,7 +344,6 @@
     "test/run_all_unittests.cc",
     "timezone/timezone_unittest.cc",
     "tools/variable_expander_unittest.cc",
-    "tpm/tpm_token_info_getter_unittest.cc",
   ]
 
   data = [
diff --git a/chromeos/test/data/oobe_configuration/TestSkipHIDDetection.json b/chromeos/test/data/oobe_configuration/TestSkipHIDDetection.json
new file mode 100644
index 0000000..48292d43
--- /dev/null
+++ b/chromeos/test/data/oobe_configuration/TestSkipHIDDetection.json
@@ -0,0 +1,4 @@
+{
+  "skipHIDDetection": true,
+  "welcomeNext": true,
+}
\ No newline at end of file
diff --git a/chromeos/tpm/BUILD.gn b/chromeos/tpm/BUILD.gn
new file mode 100644
index 0000000..272e250
--- /dev/null
+++ b/chromeos/tpm/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//testing/test.gni")
+
+assert(is_chromeos, "Non-Chrome-OS builds must not depend on //chromeos")
+
+component("tpm") {
+  defines = [ "IS_CHROMEOS_TPM_IMPL" ]
+  deps = [
+    "//base",
+    "//chromeos/cryptohome",
+    "//chromeos/dbus",
+    "//chromeos/dbus:cryptohome_proto",
+    "//chromeos/login/login_state",
+    "//components/account_id",
+    "//crypto",
+  ]
+  sources = [
+    "tpm_password_fetcher.cc",
+    "tpm_password_fetcher.h",
+    "tpm_token_info_getter.cc",
+    "tpm_token_info_getter.h",
+    "tpm_token_loader.cc",
+    "tpm_token_loader.h",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  deps = [
+    ":tpm",
+    "//base/test:test_support",
+    "//chromeos:chromeos_constants",
+    "//chromeos/cryptohome",
+    "//chromeos/dbus",
+    "//chromeos/dbus:cryptohome_proto",
+    "//testing/gtest",
+  ]
+  sources = [
+    "tpm_token_info_getter_unittest.cc",
+  ]
+}
diff --git a/chromeos/tpm/DEPS b/chromeos/tpm/DEPS
new file mode 100644
index 0000000..c2bdbe1
--- /dev/null
+++ b/chromeos/tpm/DEPS
@@ -0,0 +1,11 @@
+noparent = True
+
+include_rules = [
+  "+base",
+  "+crypto",
+  "+chromeos/cryptohome",
+  "+chromeos/login/login_state",
+  "+chromeos/dbus",
+  "+components/account_id",
+  "+testing",
+]
diff --git a/chromeos/tpm/tpm_password_fetcher.h b/chromeos/tpm/tpm_password_fetcher.h
index b69d41b8..33db5d6 100644
--- a/chromeos/tpm/tpm_password_fetcher.h
+++ b/chromeos/tpm/tpm_password_fetcher.h
@@ -7,23 +7,23 @@
 
 #include <string>
 
+#include "base/component_export.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
-#include "chromeos/chromeos_export.h"
 
 namespace chromeos {
 
 // Interface which TpmPasswordFetcher uses to notify that password has been
 // fetched.
-class CHROMEOS_EXPORT TpmPasswordFetcherDelegate {
+class COMPONENT_EXPORT(CHROMEOS_TPM) TpmPasswordFetcherDelegate {
  public:
   virtual ~TpmPasswordFetcherDelegate() {}
   virtual void OnPasswordFetched(const std::string& tpm_password) = 0;
 };
 
 // Class for fetching TPM password from the Cryptohome.
-class CHROMEOS_EXPORT TpmPasswordFetcher {
+class COMPONENT_EXPORT(CHROMEOS_TPM) TpmPasswordFetcher {
  public:
   // Creates fetcher with the given delegate to be notified every time fetching
   // is done.
diff --git a/chromeos/tpm/tpm_token_info_getter.h b/chromeos/tpm/tpm_token_info_getter.h
index b9b63f8..a7163de 100644
--- a/chromeos/tpm/tpm_token_info_getter.h
+++ b/chromeos/tpm/tpm_token_info_getter.h
@@ -9,12 +9,12 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/component_export.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/time/time.h"
-#include "chromeos/chromeos_export.h"
 #include "chromeos/dbus/cryptohome_client.h"
 #include "components/account_id/account_id.h"
 
@@ -26,7 +26,7 @@
 
 // Class for getting a user or the system TPM token info from cryptohome during
 // TPM token loading.
-class CHROMEOS_EXPORT TPMTokenInfoGetter {
+class COMPONENT_EXPORT(CHROMEOS_TPM) TPMTokenInfoGetter {
  public:
   using TpmTokenInfoCallback = base::OnceCallback<void(
       base::Optional<CryptohomeClient::TpmTokenInfo> token_info)>;
diff --git a/chromeos/tpm/tpm_token_loader.h b/chromeos/tpm/tpm_token_loader.h
index 146010a..9bde6965 100644
--- a/chromeos/tpm/tpm_token_loader.h
+++ b/chromeos/tpm/tpm_token_loader.h
@@ -10,13 +10,13 @@
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "base/component_export.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
 #include "base/threading/thread_checker.h"
-#include "chromeos/chromeos_export.h"
 #include "chromeos/dbus/cryptohome_client.h"
 #include "chromeos/login/login_state/login_state.h"
 #include "chromeos/tpm/tpm_token_info_getter.h"
@@ -34,7 +34,8 @@
 // session, the observers are notified using |OnTPMTokenReady|.
 // Note: This currently initializes the token with the hard coded default id 0.
 // See CryptohomeClient::OnPkcs11GetTpmTokenInfo.
-class CHROMEOS_EXPORT TPMTokenLoader : public LoginState::Observer {
+class COMPONENT_EXPORT(CHROMEOS_TPM) TPMTokenLoader
+    : public LoginState::Observer {
  public:
   enum TPMTokenStatus {
     TPM_TOKEN_STATUS_UNDETERMINED,
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 262a736..0ca8e4d 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -168,6 +168,7 @@
     "//components/update_client:unit_tests",
     "//components/upload_list:unit_tests",
     "//components/url_formatter:unit_tests",
+    "//components/url_formatter/top_domains:unit_tests",
     "//components/url_matcher:unit_tests",
     "//components/url_pattern_index:unit_tests",
     "//components/variations:unit_tests",
@@ -185,7 +186,7 @@
   }
 
   if (use_viz_devtools) {
-    deps += [ "//components/ui_devtools/viz_views:unit_tests" ]
+    deps += [ "//components/ui_devtools/viz:unit_tests" ]
   }
 
   if (enable_nacl) {
@@ -243,7 +244,6 @@
       "//components/policy/core/browser:unit_tests",
       "//components/policy/core/common:unit_tests",
       "//components/previews/content:unit_tests",
-      "//components/printing/common:unit_tests",
       "//components/safe_browsing/common:unit_tests",
       "//components/safe_browsing/password_protection:password_protection_unittest",
       "//components/safe_browsing/triggers:unit_tests",
@@ -354,7 +354,10 @@
   }
 
   if (enable_basic_printing) {
-    deps += [ "//components/services/pdf_compositor:unit_tests" ]
+    deps += [
+      "//components/printing/browser:unit_tests",
+      "//components/services/pdf_compositor:unit_tests",
+    ]
   }
   if (enable_print_preview) {
     deps += [ "//components/pwg_encoder:unit_tests" ]
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
index 95b34c0..d1ca29e 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
@@ -35,8 +35,9 @@
     static final int BACKGROUND_TASK_COMPONENT_UPDATE = 15;
     static final int BACKGROUND_TASK_DEPRECATED_EXPLORE_SITES_REFRESH = 16;
     static final int BACKGROUND_TASK_EXPLORE_SITES_REFRESH = 17;
+    static final int BACKGROUND_TASK_DOWNLOAD_AUTO_RESUMPTION = 18;
     // Keep this one at the end and increment appropriately when adding new tasks.
-    static final int BACKGROUND_TASK_COUNT = 18;
+    static final int BACKGROUND_TASK_COUNT = 19;
 
     static final String KEY_CACHED_UMA = "bts_cached_uma";
 
@@ -243,6 +244,8 @@
                 return BACKGROUND_TASK_DOWNLOAD_SERVICE;
             case TaskIds.DOWNLOAD_CLEANUP_JOB_ID:
                 return BACKGROUND_TASK_DOWNLOAD_CLEANUP;
+            case TaskIds.DOWNLOAD_AUTO_RESUMPTION_JOB_ID:
+                return BACKGROUND_TASK_DOWNLOAD_AUTO_RESUMPTION;
             case TaskIds.WEBVIEW_VARIATIONS_SEED_FETCH_JOB_ID:
                 return BACKGROUND_TASK_WEBVIEW_VARIATIONS;
             case TaskIds.OFFLINE_PAGES_PREFETCH_NOTIFICATION_JOB_ID:
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
index a885f1c..959377ad 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
@@ -26,6 +26,7 @@
     public static final int WEBVIEW_VARIATIONS_SEED_FETCH_JOB_ID = 83;
     public static final int WEBAPK_UPDATE_JOB_ID = 91;
     public static final int DOWNLOAD_RESUMPTION_JOB_ID = 55;
+    public static final int DOWNLOAD_AUTO_RESUMPTION_JOB_ID = 56;
     public static final int FEED_REFRESH_JOB_ID = 22;
     public static final int COMPONENT_UPDATE_JOB_ID = 2;
     public static final int DEPRECATED_EXPLORE_SITES_REFRESH_JOB_ID = 100;
diff --git a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
index 8a9dc167..905c63d 100644
--- a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
+++ b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
@@ -71,6 +71,9 @@
         assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_DOWNLOAD_CLEANUP,
                 BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
                         TaskIds.DOWNLOAD_CLEANUP_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_DOWNLOAD_AUTO_RESUMPTION,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.DOWNLOAD_AUTO_RESUMPTION_JOB_ID));
         assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_WEBVIEW_VARIATIONS,
                 BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
                         TaskIds.WEBVIEW_VARIATIONS_SEED_FETCH_JOB_ID));
@@ -93,7 +96,7 @@
         assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_DEPRECATED_EXPLORE_SITES_REFRESH,
                 BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
                         TaskIds.DEPRECATED_EXPLORE_SITES_REFRESH_JOB_ID));
-        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT, 18);
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT, 19);
     }
 
     @Test
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index 6554ebef..a82a68f 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -207,6 +207,56 @@
    "dns_api_endpoint": "digicert-yeti2022.ct.googleapis.com"
   },
   {
+   "description": "DigiCert Nessie2018 Log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVqpLa2W+Rz1XDZPBIyKJO+KKFOYZTj9MpJWnZeFUqzc5aivOiWEVhs8Gy2AlH3irWPFjIZPZMs3Dv7M+0LbPyQ==",
+   "url": "nessie2018.ct.digicert.com/log/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    2
+   ],
+   "dns_api_endpoint": "digicert-nessie2018.ct.googleapis.com"
+  },
+  {
+   "description": "DigiCert Nessie2019 Log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX+0nudCKImd7QCtelhMrDW0OXni5RE10tiiClZesmrwUk2iHLCoTHHVV+yg5D4n/rxCRVyRhikPpVDOLMLxJaA==",
+   "url": "nessie2019.ct.digicert.com/log/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    2
+   ],
+   "dns_api_endpoint": "digicert-nessie2019.ct.googleapis.com"
+  },
+  {
+   "description": "DigiCert Nessie2020 Log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4hHIyMVIrR9oShgbQMYEk8WX1lmkfFKB448Gn93KbsZnnwljDHY6MQqEnWfKGgMOq0gh3QK48c5ZB3UKSIFZ4g==",
+   "url": "nessie2020.ct.digicert.com/log/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    2
+   ],
+   "dns_api_endpoint": "digicert-nessie2020.ct.googleapis.com"
+  },
+  {
+   "description": "DigiCert Nessie2021 Log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9o7AiwrbGBIX6Lnc47I6OfLMdZnRzKoP5u072nBi6vpIOEooktTi1gNwlRPzGC2ySGfuc1xLDeaA/wSFGgpYFg==",
+   "url": "nessie2021.ct.digicert.com/log/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    2
+   ],
+   "dns_api_endpoint": "digicert-nessie2021.ct.googleapis.com"
+  },
+  {
+   "description": "DigiCert Nessie2022 Log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJyTdaAMoy/5jvg4RR019F2ihEV1McclBKMe2okuX7MCv/C87v+nxsfz1Af+p+0lADGMkmNd5LqZVqxbGvlHYcQ==",
+   "url": "nessie2022.ct.digicert.com/log/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    2
+   ],
+   "dns_api_endpoint": "digicert-nessie2022.ct.googleapis.com"
+  },
+  {
    "description": "Symantec log",
    "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEluqsHEYMG1XcDfy1lCdGV0JwOmkY4r87xNuroPS2bMBTP01CEDPwWJePa75y9CrsHEKqAy8afig1dpkIPSEUhg==",
    "url": "ct.ws.symantec.com/",
@@ -375,4 +425,4 @@
    "id": 9
   }
  ]
-}
+}
\ No newline at end of file
diff --git a/components/download/internal/background_service/controller_impl.cc b/components/download/internal/background_service/controller_impl.cc
index 717d390..eae2dc6 100644
--- a/components/download/internal/background_service/controller_impl.cc
+++ b/components/download/internal/background_service/controller_impl.cc
@@ -401,6 +401,8 @@
     case DownloadTaskType::CLEANUP_TASK:
       ScheduleCleanupTask();
       break;
+    case DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK:
+      NOTREACHED();
   }
 }
 
diff --git a/components/download/internal/background_service/stats.cc b/components/download/internal/background_service/stats.cc
index ef8cf015..88aebf4a 100644
--- a/components/download/internal/background_service/stats.cc
+++ b/components/download/internal/background_service/stats.cc
@@ -48,6 +48,8 @@
       return "DownloadTask";
     case DownloadTaskType::CLEANUP_TASK:
       return "CleanUpTask";
+    case DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK:
+      return "DownloadAutoResumptionTask";
   }
   NOTREACHED();
   return std::string();
diff --git a/components/download/internal/common/download_item_impl.cc b/components/download/internal/common/download_item_impl.cc
index a39e308..4b536f5 100644
--- a/components/download/internal/common/download_item_impl.cc
+++ b/components/download/internal/common/download_item_impl.cc
@@ -588,10 +588,12 @@
 }
 
 void DownloadItemImpl::UpdateResumptionInfo(bool user_resume) {
-  if (user_resume)
+  if (user_resume) {
     allow_metered_ |= delegate_->IsActiveNetworkMetered();
+    bytes_wasted_ = 0;
+  }
 
-  auto_resume_count_ = user_resume ? 0 : auto_resume_count_++;
+  auto_resume_count_ = user_resume ? 0 : ++auto_resume_count_;
 }
 
 void DownloadItemImpl::Cancel(bool user_cancel) {
diff --git a/components/download/internal/common/download_item_impl_delegate.cc b/components/download/internal/common/download_item_impl_delegate.cc
index 973248a..cc3fb6af5d 100644
--- a/components/download/internal/common/download_item_impl_delegate.cc
+++ b/components/download/internal/common/download_item_impl_delegate.cc
@@ -5,7 +5,9 @@
 #include "components/download/public/common/download_item_impl_delegate.h"
 
 #include "base/logging.h"
+#include "build/build_config.h"
 #include "components/download/database/in_progress/download_entry.h"
+#include "components/download/public/common/auto_resumption_handler.h"
 #include "components/download/public/common/download_danger_type.h"
 #include "components/download/public/common/download_item_impl.h"
 
@@ -94,7 +96,9 @@
 }
 
 bool DownloadItemImplDelegate::IsActiveNetworkMetered() const {
-  return false;
+  return download::AutoResumptionHandler::Get()
+             ? download::AutoResumptionHandler::Get()->IsActiveNetworkMetered()
+             : false;
 }
 
 void DownloadItemImplDelegate::ReportBytesWasted(DownloadItemImpl* download) {}
diff --git a/components/download/internal/common/in_progress_download_manager.cc b/components/download/internal/common/in_progress_download_manager.cc
index 276d856..abecb58 100644
--- a/components/download/internal/common/in_progress_download_manager.cc
+++ b/components/download/internal/common/in_progress_download_manager.cc
@@ -409,6 +409,12 @@
   on_initialized_callbacks_.clear();
 }
 
+void InProgressDownloadManager::GetAllDownloads(
+    std::vector<download::DownloadItem*>* downloads) const {
+  for (auto& item : in_progress_downloads_)
+    downloads->push_back(item.get());
+}
+
 DownloadItemImpl* InProgressDownloadManager::GetInProgressDownload(
     const std::string& guid) {
   for (auto& item : in_progress_downloads_) {
diff --git a/components/download/public/background_service/download_task_types.h b/components/download/public/background_service/download_task_types.h
index 48fcfb35..5140f520 100644
--- a/components/download/public/background_service/download_task_types.h
+++ b/components/download/public/background_service/download_task_types.h
@@ -15,6 +15,9 @@
 
   // Task to remove unnecessary files from the system.
   CLEANUP_TASK = 1,
+
+  // Task to invoke the download auto-resumption handler.
+  DOWNLOAD_AUTO_RESUMPTION_TASK = 2,
 };
 
 }  // namespace download
diff --git a/components/download/public/common/BUILD.gn b/components/download/public/common/BUILD.gn
index 8de5574..e14fe35 100644
--- a/components/download/public/common/BUILD.gn
+++ b/components/download/public/common/BUILD.gn
@@ -15,6 +15,8 @@
 
 component("public") {
   sources = [
+    "auto_resumption_handler.cc",
+    "auto_resumption_handler.h",
     "base_file.h",
     "download_content.h",
     "download_create_info.h",
@@ -65,6 +67,9 @@
 
   public_deps = [
     ":interfaces",
+    "//components/download/network",
+    "//components/download/public/background_service:public",
+    "//services/network/public/cpp",
   ]
 
   deps = [
diff --git a/components/download/public/common/auto_resumption_handler.cc b/components/download/public/common/auto_resumption_handler.cc
new file mode 100644
index 0000000..3e6dfa5
--- /dev/null
+++ b/components/download/public/common/auto_resumption_handler.cc
@@ -0,0 +1,295 @@
+// 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 "components/download/public/common/auto_resumption_handler.h"
+
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/download/public/background_service/task_scheduler.h"
+#include "url/gurl.h"
+
+namespace {
+
+static download::AutoResumptionHandler* g_auto_resumption_handler = nullptr;
+
+// The delay to wait for after a chrome restart before resuming all pending
+// downloads so that tab loading doesn't get impacted.
+const base::TimeDelta kAutoResumeStartupDelay =
+    base::TimeDelta::FromSeconds(10);
+
+// The interval at which various download updates are grouped together for
+// computing the params for the task scheduler.
+const base::TimeDelta kBatchDownloadUpdatesInterval =
+    base::TimeDelta::FromSeconds(1);
+
+// The delay to wait for before immediately retrying a download after it got
+// interrupted due to network reasons.
+const base::TimeDelta kDownloadImmediateRetryDelay =
+    base::TimeDelta::FromSeconds(1);
+
+// The task type to use for scheduling a task.
+const download::DownloadTaskType kResumptionTaskType =
+    download::DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK;
+
+// The window start time after which the system should fire the task.
+const int64_t kWindowStartTimeSeconds = 0;
+
+// The window end time before which the system should fire the task.
+const int64_t kWindowEndTimeSeconds = 24 * 60 * 60;
+
+bool IsMetered(network::mojom::ConnectionType type) {
+  switch (type) {
+    case network::mojom::ConnectionType::CONNECTION_2G:
+    case network::mojom::ConnectionType::CONNECTION_3G:
+    case network::mojom::ConnectionType::CONNECTION_4G:
+      return true;
+    case network::mojom::ConnectionType::CONNECTION_ETHERNET:
+    case network::mojom::ConnectionType::CONNECTION_WIFI:
+    case network::mojom::ConnectionType::CONNECTION_UNKNOWN:
+    case network::mojom::ConnectionType::CONNECTION_NONE:
+    case network::mojom::ConnectionType::CONNECTION_BLUETOOTH:
+      return false;
+  }
+  NOTREACHED();
+  return false;
+}
+
+bool IsConnected(network::mojom::ConnectionType type) {
+  switch (type) {
+    case network::mojom::ConnectionType::CONNECTION_UNKNOWN:
+    case network::mojom::ConnectionType::CONNECTION_NONE:
+    case network::mojom::ConnectionType::CONNECTION_BLUETOOTH:
+      return false;
+    default:
+      return true;
+  }
+}
+
+bool IsInterruptedDownloadAutoResumable(download::DownloadItem* download_item,
+                                        int auto_resumption_size_limit) {
+  if (!download_item->GetURL().SchemeIsHTTPOrHTTPS())
+    return false;
+
+  if (download_item->GetBytesWasted() > auto_resumption_size_limit)
+    return false;
+
+  int interrupt_reason = download_item->GetLastReason();
+  DCHECK_NE(interrupt_reason, download::DOWNLOAD_INTERRUPT_REASON_NONE);
+  return interrupt_reason ==
+             download::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT ||
+         interrupt_reason ==
+             download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED ||
+         interrupt_reason ==
+             download::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED ||
+         interrupt_reason == download::DOWNLOAD_INTERRUPT_REASON_CRASH;
+}
+
+}  // namespace
+
+namespace download {
+
+AutoResumptionHandler::Config::Config() : auto_resumption_size_limit(0) {}
+
+// static
+void AutoResumptionHandler::Create(
+    std::unique_ptr<download::NetworkStatusListener> network_listener,
+    std::unique_ptr<download::TaskManager> task_manager,
+    std::unique_ptr<Config> config) {
+  DCHECK(!g_auto_resumption_handler);
+  g_auto_resumption_handler = new AutoResumptionHandler(
+      std::move(network_listener), std::move(task_manager), std::move(config));
+}
+
+// static
+AutoResumptionHandler* AutoResumptionHandler::Get() {
+  return g_auto_resumption_handler;
+}
+
+AutoResumptionHandler::AutoResumptionHandler(
+    std::unique_ptr<download::NetworkStatusListener> network_listener,
+    std::unique_ptr<download::TaskManager> task_manager,
+    std::unique_ptr<Config> config)
+    : network_listener_(std::move(network_listener)),
+      task_manager_(std::move(task_manager)),
+      config_(std::move(config)),
+      weak_factory_(this) {
+  network_listener_->Start(this);
+}
+
+AutoResumptionHandler::~AutoResumptionHandler() {
+  network_listener_->Stop();
+}
+
+void AutoResumptionHandler::SetResumableDownloads(
+    const std::vector<download::DownloadItem*>& downloads) {
+  resumable_downloads_.clear();
+  for (auto* download : downloads) {
+    if (!IsAutoResumableDownload(download))
+      continue;
+    resumable_downloads_.insert(std::make_pair(download->GetGuid(), download));
+    download->RemoveObserver(this);
+    download->AddObserver(this);
+  }
+
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&AutoResumptionHandler::ResumePendingDownloads,
+                     weak_factory_.GetWeakPtr()),
+      kAutoResumeStartupDelay);
+}
+
+bool AutoResumptionHandler::IsActiveNetworkMetered() const {
+  return IsMetered(network_listener_->GetConnectionType());
+}
+
+void AutoResumptionHandler::OnNetworkChanged(
+    network::mojom::ConnectionType type) {
+  if (!IsConnected(type))
+    return;
+
+  ResumePendingDownloads();
+}
+
+void AutoResumptionHandler::OnDownloadStarted(download::DownloadItem* item) {
+  item->RemoveObserver(this);
+  item->AddObserver(this);
+
+  OnDownloadUpdated(item);
+}
+
+void AutoResumptionHandler::OnDownloadUpdated(download::DownloadItem* item) {
+  if (IsAutoResumableDownload(item))
+    resumable_downloads_[item->GetGuid()] = item;
+  else
+    resumable_downloads_.erase(item->GetGuid());
+
+  if (item->GetState() == download::DownloadItem::INTERRUPTED &&
+      IsAutoResumableDownload(item) && SatisfiesNetworkRequirements(item)) {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&AutoResumptionHandler::ResumeDownload,
+                       weak_factory_.GetWeakPtr(), item),
+        kDownloadImmediateRetryDelay);
+    return;
+  }
+  RecomputeTaskParams();
+}
+
+void AutoResumptionHandler::OnDownloadRemoved(download::DownloadItem* item) {
+  resumable_downloads_.erase(item->GetGuid());
+  RecomputeTaskParams();
+}
+
+void AutoResumptionHandler::ResumeDownload(download::DownloadItem* download) {
+  if (SatisfiesNetworkRequirements(download))
+    download->Resume(false);
+  else
+    RecomputeTaskParams();
+}
+
+void AutoResumptionHandler::OnStartScheduledTask(
+    download::TaskFinishedCallback callback) {
+  task_manager_->OnStartScheduledTask(kResumptionTaskType, std::move(callback));
+  ResumePendingDownloads();
+}
+
+bool AutoResumptionHandler::OnStopScheduledTask() {
+  task_manager_->OnStopScheduledTask(kResumptionTaskType);
+  RescheduleTaskIfNecessary();
+  return false;
+}
+
+void AutoResumptionHandler::RecomputeTaskParams() {
+  if (recompute_task_params_scheduled_)
+    return;
+
+  recompute_task_params_scheduled_ = true;
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&AutoResumptionHandler::RescheduleTaskIfNecessary,
+                     weak_factory_.GetWeakPtr()),
+      kBatchDownloadUpdatesInterval);
+}
+
+void AutoResumptionHandler::RescheduleTaskIfNecessary() {
+  recompute_task_params_scheduled_ = false;
+
+  bool has_resumable_downloads = false;
+  bool has_actionable_downloads = false;
+  bool can_download_on_metered = false;
+  for (auto iter = resumable_downloads_.begin();
+       iter != resumable_downloads_.end(); ++iter) {
+    download::DownloadItem* download = iter->second;
+    if (!IsAutoResumableDownload(download))
+      continue;
+
+    has_resumable_downloads = true;
+    has_actionable_downloads |= SatisfiesNetworkRequirements(download);
+    can_download_on_metered |= download->AllowMetered();
+    if (can_download_on_metered)
+      break;
+  }
+
+  if (!has_actionable_downloads)
+    task_manager_->NotifyTaskFinished(kResumptionTaskType, false);
+
+  if (!has_resumable_downloads) {
+    task_manager_->UnscheduleTask(kResumptionTaskType);
+    return;
+  }
+
+  download::TaskManager::TaskParams task_params;
+  task_params.require_unmetered_network = !can_download_on_metered;
+  task_params.window_start_time_seconds = kWindowStartTimeSeconds;
+  task_params.window_end_time_seconds = kWindowEndTimeSeconds;
+  task_manager_->ScheduleTask(kResumptionTaskType, task_params);
+}
+
+void AutoResumptionHandler::ResumePendingDownloads() {
+  for (auto iter = resumable_downloads_.begin();
+       iter != resumable_downloads_.end(); ++iter) {
+    download::DownloadItem* download = iter->second;
+    if (!IsAutoResumableDownload(download))
+      continue;
+
+    if (SatisfiesNetworkRequirements(download))
+      download->Resume(false);
+  }
+}
+
+bool AutoResumptionHandler::SatisfiesNetworkRequirements(
+    download::DownloadItem* download) {
+  if (!IsConnected(network_listener_->GetConnectionType()))
+    return false;
+
+  return download->AllowMetered() || !IsActiveNetworkMetered();
+}
+
+bool AutoResumptionHandler::IsAutoResumableDownload(
+    download::DownloadItem* item) {
+  if (item->IsDangerous())
+    return false;
+
+  switch (item->GetState()) {
+    case download::DownloadItem::IN_PROGRESS:
+      return !item->IsPaused();
+    case download::DownloadItem::COMPLETE:
+    case download::DownloadItem::CANCELLED:
+      return false;
+    case download::DownloadItem::INTERRUPTED:
+      return !item->IsPaused() &&
+             IsInterruptedDownloadAutoResumable(
+                 item, config_->auto_resumption_size_limit);
+    case download::DownloadItem::MAX_DOWNLOAD_STATE:
+      NOTREACHED();
+  }
+
+  return false;
+}
+
+}  // namespace download
diff --git a/components/download/public/common/auto_resumption_handler.h b/components/download/public/common/auto_resumption_handler.h
new file mode 100644
index 0000000..c0bb504
--- /dev/null
+++ b/components/download/public/common/auto_resumption_handler.h
@@ -0,0 +1,90 @@
+// 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 COMPONENTS_DOWNLOAD_PUBLIC_COMMON_AUTO_RESUMPTION_HANDLER_H_
+#define COMPONENTS_DOWNLOAD_PUBLIC_COMMON_AUTO_RESUMPTION_HANDLER_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/download/network/network_status_listener.h"
+#include "components/download/public/background_service/task_manager.h"
+#include "components/download/public/common/download_export.h"
+#include "components/download/public/common/download_item.h"
+
+namespace download {
+
+// Handles auto-resumptions for downloads. Listens to network changes and
+// schecules task to resume downloads accordingly.
+class COMPONENTS_DOWNLOAD_EXPORT AutoResumptionHandler
+    : public download::NetworkStatusListener::Observer,
+      public download::DownloadItem::Observer {
+ public:
+  struct COMPONENTS_DOWNLOAD_EXPORT Config {
+    Config();
+    ~Config() = default;
+
+    int auto_resumption_size_limit;
+  };
+
+  // Creates the singleton instance of AutoResumptionHandler.
+  static void Create(
+      std::unique_ptr<download::NetworkStatusListener> network_listener,
+      std::unique_ptr<download::TaskManager> task_manager,
+      std::unique_ptr<Config> config);
+
+  // Returns the singleton instance of the AutoResumptionHandler.
+  static AutoResumptionHandler* Get();
+
+  AutoResumptionHandler(
+      std::unique_ptr<download::NetworkStatusListener> network_listener,
+      std::unique_ptr<download::TaskManager> task_manager,
+      std::unique_ptr<Config> config);
+  ~AutoResumptionHandler() override;
+
+  void SetResumableDownloads(
+      const std::vector<download::DownloadItem*>& downloads);
+  bool IsActiveNetworkMetered() const;
+  void OnStartScheduledTask(download::TaskFinishedCallback callback);
+  bool OnStopScheduledTask();
+
+  void OnDownloadStarted(download::DownloadItem* item);
+
+  // DownloadItem::Observer overrides.
+  void OnDownloadUpdated(download::DownloadItem* item) override;
+  void OnDownloadRemoved(download::DownloadItem* item) override;
+
+ private:
+  // NetworkStatusListener::Observer implementation.
+  void OnNetworkChanged(network::mojom::ConnectionType type) override;
+
+  void ResumePendingDownloads();
+  void RecomputeTaskParams();
+  void RescheduleTaskIfNecessary();
+  void ResumeDownload(download::DownloadItem* download);
+  bool SatisfiesNetworkRequirements(download::DownloadItem* download);
+  bool IsAutoResumableDownload(download::DownloadItem* item);
+
+  std::unique_ptr<download::NetworkStatusListener> network_listener_;
+
+  std::unique_ptr<download::TaskManager> task_manager_;
+
+  std::unique_ptr<Config> config_;
+
+  std::map<std::string, download::DownloadItem*> resumable_downloads_;
+
+  bool recompute_task_params_scheduled_ = false;
+
+  base::WeakPtrFactory<AutoResumptionHandler> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(AutoResumptionHandler);
+};
+
+}  // namespace download
+
+#endif  // COMPONENTS_DOWNLOAD_PUBLIC_COMMON_AUTO_RESUMPTION_HANDLER_H_
diff --git a/components/download/public/common/in_progress_download_manager.h b/components/download/public/common/in_progress_download_manager.h
index d76534f..563d97d 100644
--- a/components/download/public/common/in_progress_download_manager.h
+++ b/components/download/public/common/in_progress_download_manager.h
@@ -122,6 +122,9 @@
   // Called to remove an in-progress download.
   void RemoveInProgressDownload(const std::string& guid);
 
+  // Called to get all in-progress downloads.
+  void GetAllDownloads(std::vector<download::DownloadItem*>* downloads) const;
+
   // Called to retrieve an in-progress download.
   DownloadItemImpl* GetInProgressDownload(const std::string& guid);
 
diff --git a/components/gwp_asan/BUILD.gn b/components/gwp_asan/BUILD.gn
index 8829da76..b4b82621 100644
--- a/components/gwp_asan/BUILD.gn
+++ b/components/gwp_asan/BUILD.gn
@@ -4,11 +4,13 @@
 
 source_set("unit_tests") {
   testonly = true
+  deps = [
+    "//components/gwp_asan/common:unit_tests",
+  ]
+  if (is_win || is_mac) {
+    deps += [ "//components/gwp_asan/client:unit_tests" ]
+  }
   if (is_win) {
-    deps = [
-      "//components/gwp_asan/client:unit_tests",
-      "//components/gwp_asan/common:unit_tests",
-      "//components/gwp_asan/crash_handler:unit_tests",
-    ]
+    deps += [ "//components/gwp_asan/crash_handler:unit_tests" ]
   }
 }
diff --git a/components/gwp_asan/client/BUILD.gn b/components/gwp_asan/client/BUILD.gn
index 4018061..071d995 100644
--- a/components/gwp_asan/client/BUILD.gn
+++ b/components/gwp_asan/client/BUILD.gn
@@ -2,8 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-assert(is_win, "GWP-ASan client currently only supports Windows.")
-
 component("client") {
   output_name = "gwp_asan_client"
   sources = [
@@ -20,6 +18,13 @@
     "sampling_allocator_shims_win.h",
   ]
 
+  if (is_posix) {
+    sources += [
+      "guarded_page_allocator_posix.cc",
+      "sampling_allocator_shims_posix.h",
+    ]
+  }
+
   defines = [ "GWP_ASAN_IMPLEMENTATION" ]
 
   deps = [
diff --git a/components/gwp_asan/client/guarded_page_allocator_posix.cc b/components/gwp_asan/client/guarded_page_allocator_posix.cc
new file mode 100644
index 0000000..d3e83f1
--- /dev/null
+++ b/components/gwp_asan/client/guarded_page_allocator_posix.cc
@@ -0,0 +1,38 @@
+// 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 "components/gwp_asan/client/guarded_page_allocator.h"
+
+#include <sys/mman.h>
+
+#include "base/logging.h"
+
+namespace gwp_asan {
+namespace internal {
+
+void* GuardedPageAllocator::MapRegion() {
+  return mmap(nullptr, RegionSize(), PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1,
+              0);
+}
+
+void GuardedPageAllocator::UnmapRegion() {
+  CHECK(state_.pages_base_addr);
+  int err =
+      munmap(reinterpret_cast<void*>(state_.pages_base_addr), RegionSize());
+  DCHECK_EQ(err, 0);
+  (void)err;
+}
+
+void GuardedPageAllocator::MarkPageReadWrite(void* ptr) {
+  int err = mprotect(ptr, state_.page_size, PROT_READ | PROT_WRITE);
+  PCHECK(err == 0) << "mprotect";
+}
+
+void GuardedPageAllocator::MarkPageInaccessible(void* ptr) {
+  int err = mprotect(ptr, state_.page_size, PROT_NONE);
+  PCHECK(err == 0) << "mprotect";
+}
+
+}  // namespace internal
+}  // namespace gwp_asan
diff --git a/components/gwp_asan/client/sampling_allocator_shims.cc b/components/gwp_asan/client/sampling_allocator_shims.cc
index be51e4d9..cc291ad 100644
--- a/components/gwp_asan/client/sampling_allocator_shims.cc
+++ b/components/gwp_asan/client/sampling_allocator_shims.cc
@@ -20,6 +20,10 @@
 #include "components/gwp_asan/client/export.h"
 #include "components/gwp_asan/client/guarded_page_allocator.h"
 
+#if defined(OS_POSIX)
+#include "components/gwp_asan/client/sampling_allocator_shims_posix.h"
+#endif
+
 #if defined(OS_WIN)
 #include "components/gwp_asan/client/sampling_allocator_shims_win.h"
 #endif
@@ -172,9 +176,8 @@
                        void** results,
                        unsigned num_requested,
                        void* context) {
-#if defined(OS_MACOSX) || defined(OS_IOS)
-#error "Implement batch_malloc() support."
-#endif
+  // The batch_malloc() routine is esoteric and only accessible for the system
+  // allocator's zone, GWP-ASan interception is not provided.
 
   return self->next->batch_malloc_function(self->next, size, results,
                                            num_requested, context);
@@ -184,9 +187,18 @@
                  void** to_be_freed,
                  unsigned num_to_be_freed,
                  void* context) {
-#if defined(OS_MACOSX) || defined(OS_IOS)
-#error "Implement batch_free() support."
-#endif
+  // A batch_free() hook is implemented because it is imperative that we never
+  // call free() with a GWP-ASan allocation.
+  for (size_t i = 0; i < num_to_be_freed; i++) {
+    if (UNLIKELY(gpa->PointerIsMine(to_be_freed[i]))) {
+      // If this batch includes guarded allocations, call free() on all of the
+      // individual allocations to ensure the guarded allocations are handled
+      // correctly.
+      for (size_t j = 0; j < num_to_be_freed; j++)
+        FreeFn(self, to_be_freed[j], context);
+      return;
+    }
+  }
 
   self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed,
                                   context);
diff --git a/components/gwp_asan/client/sampling_allocator_shims_posix.h b/components/gwp_asan/client/sampling_allocator_shims_posix.h
new file mode 100644
index 0000000..b85833e89
--- /dev/null
+++ b/components/gwp_asan/client/sampling_allocator_shims_posix.h
@@ -0,0 +1,40 @@
+// 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 COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_ALLOCATOR_SHIMS_POSIX_H_
+#define COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_ALLOCATOR_SHIMS_POSIX_H_
+
+#include <pthread.h>
+#include <stddef.h>
+
+#include "base/logging.h"
+
+namespace gwp_asan {
+namespace internal {
+
+// Due to https://crbug.com/881352 the sampling allocator shims can not use
+// base::ThreadLocalStorage to access the TLS. Instead, we use platform-specific
+// TLS APIs.
+//
+// TODO(vtsyrklevich): This implementation is identical to code in the
+// base::PoissonAllocationSampler, see if they can share code.
+using TLSKey = pthread_key_t;
+
+void TLSInit(TLSKey* key) {
+  int result = pthread_key_create(key, nullptr);
+  CHECK_EQ(0, result);
+}
+
+size_t TLSGetValue(const TLSKey& key) {
+  return reinterpret_cast<size_t>(pthread_getspecific(key));
+}
+
+void TLSSetValue(const TLSKey& key, size_t value) {
+  pthread_setspecific(key, reinterpret_cast<void*>(value));
+}
+
+}  // namespace internal
+}  // namespace gwp_asan
+
+#endif  // COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_ALLOCATOR_SHIMS_POSIX_H_
diff --git a/components/gwp_asan/client/sampling_allocator_shims_unittest.cc b/components/gwp_asan/client/sampling_allocator_shims_unittest.cc
index 88d1f79..4353209 100644
--- a/components/gwp_asan/client/sampling_allocator_shims_unittest.cc
+++ b/components/gwp_asan/client/sampling_allocator_shims_unittest.cc
@@ -31,9 +31,14 @@
 static size_t GetAllocatedSize(void *mem) {
   return _msize(mem);
 }
-#else  // defined(OS_WIN)
+#elif defined(OS_MACOSX)
+#include <malloc/malloc.h>
+static size_t GetAllocatedSize(void* mem) {
+  return malloc_size(mem);
+}
+#else
 #error "Needs to be implemented for platform."
-#endif  // defined(OS_WIN)
+#endif
 
 namespace gwp_asan {
 namespace internal {
@@ -52,6 +57,16 @@
 constexpr int kFailure = 1;
 
 class SamplingAllocatorShimsTest : public base::MultiProcessTest {
+ public:
+  static void multiprocessTestSetup() {
+#if defined(OS_MACOSX)
+    base::allocator::InitializeAllocatorShim();
+#endif  // defined(OS_MACOSX)
+    crash_reporter::InitializeCrashKeys();
+    InstallAllocatorHooks(AllocatorState::kGpaMaxPages,
+                          AllocatorState::kGpaMaxPages, kSamplingFrequency);
+  }
+
  protected:
   void runTest(const char* name) {
     base::Process process = SpawnChild(name);
@@ -91,10 +106,9 @@
   return false;
 }
 
-MULTIPROCESS_TEST_MAIN(BasicFunctionality) {
-  InstallAllocatorHooks(AllocatorState::kGpaMaxPages,
-                        AllocatorState::kGpaMaxPages, kSamplingFrequency);
-
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+    BasicFunctionality,
+    SamplingAllocatorShimsTest::multiprocessTestSetup) {
   const size_t page_size = base::GetPageSize();
   int failures = 0;
 
@@ -111,14 +125,17 @@
   EXPECT_TRUE(
       allocationCheck([&] { return _aligned_realloc(nullptr, 123, 16); },
                       &_aligned_free, &failures));
-#endif
+#endif  // defined(OS_WIN)
 
-#if !defined(OS_WIN)
+#if defined(OS_POSIX)
   EXPECT_TRUE(allocationCheck(
-      [&] { return aligned_alloc(page_size, page_size); }, &free, &failures));
-  EXPECT_TRUE(allocationCheck([&] { return aligned_alloc(1, page_size); },
-                              &free, &failures));
-#endif
+      [&] {
+        void* ptr;
+        posix_memalign(&ptr, page_size, page_size);
+        return ptr;
+      },
+      &free, &failures));
+#endif  // defined(OS_POSIX)
 
   EXPECT_TRUE(allocationCheck([&] { return std::malloc(page_size); },
                               &std::free, &failures));
@@ -150,10 +167,9 @@
   runTest("BasicFunctionality");
 }
 
-MULTIPROCESS_TEST_MAIN(Realloc) {
-  InstallAllocatorHooks(AllocatorState::kGpaMaxPages,
-                        AllocatorState::kGpaMaxPages, kSamplingFrequency);
-
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+    Realloc,
+    SamplingAllocatorShimsTest::multiprocessTestSetup) {
   void* alloc = GetGpaForTesting().Allocate(base::GetPageSize());
   CHECK_NE(alloc, nullptr);
 
@@ -177,10 +193,9 @@
   runTest("Realloc");
 }
 
-MULTIPROCESS_TEST_MAIN(Calloc) {
-  InstallAllocatorHooks(AllocatorState::kGpaMaxPages,
-                        AllocatorState::kGpaMaxPages, kSamplingFrequency);
-
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+    Calloc,
+    SamplingAllocatorShimsTest::multiprocessTestSetup) {
   for (size_t i = 0; i < kLoopIterations; i++) {
     unsigned char* alloc =
         static_cast<unsigned char*>(calloc(base::GetPageSize(), 1));
@@ -206,10 +221,9 @@
 // GetCrashKeyValue() operates on a per-component basis, can't read the crash
 // key from the gwp_asan_client component in a component build.
 #if !defined(COMPONENT_BUILD)
-MULTIPROCESS_TEST_MAIN(CrashKey) {
-  InstallAllocatorHooks(AllocatorState::kGpaMaxPages,
-                        AllocatorState::kGpaMaxPages, kSamplingFrequency);
-
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+    CrashKey,
+    SamplingAllocatorShimsTest::multiprocessTestSetup) {
   std::string crash_key = crash_reporter::GetCrashKeyValue(kGpaCrashKey);
 
   uint64_t value;
@@ -227,10 +241,9 @@
 }
 #endif  // !defined(COMPONENT_BUILD)
 
-MULTIPROCESS_TEST_MAIN(GetSizeEstimate) {
-  InstallAllocatorHooks(AllocatorState::kGpaMaxPages,
-                        AllocatorState::kGpaMaxPages, kSamplingFrequency);
-
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+    GetSizeEstimate,
+    SamplingAllocatorShimsTest::multiprocessTestSetup) {
   constexpr size_t kAllocationSize = 123;
   for (size_t i = 0; i < kLoopIterations; i++) {
     std::unique_ptr<void, decltype(&free)> alloc(malloc(kAllocationSize), free);
@@ -251,11 +264,10 @@
 }
 
 #if defined(OS_WIN)
-MULTIPROCESS_TEST_MAIN(AlignedRealloc) {
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+    AlignedRealloc,
+    SamplingAllocatorShimsTest::multiprocessTestSetup) {
   // Exercise the _aligned_* shims and ensure that we handle them stably.
-  InstallAllocatorHooks(AllocatorState::kGpaMaxPages,
-                        AllocatorState::kGpaMaxPages, kSamplingFrequency);
-
   constexpr size_t kAllocationSize = 123;
   constexpr size_t kAllocationAlignment = 64;
   for (size_t i = 0; i < kLoopIterations; i++) {
@@ -272,7 +284,37 @@
 TEST_F(SamplingAllocatorShimsTest, AlignedRealloc) {
   runTest("AlignedRealloc");
 }
-#endif
+#endif  // defined(OS_WIN)
+
+#if defined(OS_MACOSX)
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+    BatchFree,
+    SamplingAllocatorShimsTest::multiprocessTestSetup) {
+  void* ptrs[AllocatorState::kGpaMaxPages + 1];
+  for (size_t i = 0; i < AllocatorState::kGpaMaxPages; i++) {
+    ptrs[i] = GetGpaForTesting().Allocate(16);
+    CHECK(ptrs[i]);
+  }
+  // Check that all GPA allocations were consumed.
+  CHECK_EQ(GetGpaForTesting().Allocate(16), nullptr);
+
+  ptrs[AllocatorState::kGpaMaxPages] =
+      malloc_zone_malloc(malloc_default_zone(), 16);
+  CHECK(ptrs[AllocatorState::kGpaMaxPages]);
+
+  malloc_zone_batch_free(malloc_default_zone(), ptrs,
+                         AllocatorState::kGpaMaxPages + 1);
+
+  // Check that GPA allocations were freed.
+  CHECK(GetGpaForTesting().Allocate(16));
+
+  return kSuccess;
+}
+
+TEST_F(SamplingAllocatorShimsTest, BatchFree) {
+  runTest("BatchFree");
+}
+#endif  // defined(OS_MACOSX)
 
 }  // namespace
 
diff --git a/components/invalidation/impl/fcm_invalidator.h b/components/invalidation/impl/fcm_invalidator.h
index 1a74535a..92ca158 100644
--- a/components/invalidation/impl/fcm_invalidator.h
+++ b/components/invalidation/impl/fcm_invalidator.h
@@ -23,7 +23,7 @@
 
 class FCMSyncNetworkChannel;
 
-// This class inplements the Invalidator interface and serves as a
+// This class implements the Invalidator interface and serves as a
 // bridge betwen invalidation Listener and invalidationr Service.
 class FCMInvalidator : public Invalidator,
                        public FCMInvalidationListener::Delegate {
diff --git a/components/leveldb_proto/proto_database.h b/components/leveldb_proto/proto_database.h
index e8757920..b6675b9 100644
--- a/components/leveldb_proto/proto_database.h
+++ b/components/leveldb_proto/proto_database.h
@@ -155,7 +155,11 @@
       const std::string& key,
       typename Callbacks::Internal<T>::GetCallback callback) = 0;
 
-  // Asynchronously destroys the database.
+  // Asynchronously destroys the database. Use this call only if the database
+  // needs to be destroyed for this particular profile. If the database is no
+  // longer useful for everyone, the client name must be added to
+  // |kObsoleteSharedProtoDatabaseClients| to ensure automatic clean up of the
+  // database from all users.
   virtual void Destroy(Callbacks::DestroyCallback callback) = 0;
 
  protected:
diff --git a/components/leveldb_proto/proto_database_provider.h b/components/leveldb_proto/proto_database_provider.h
index c5cb565..90d036d8 100644
--- a/components/leveldb_proto/proto_database_provider.h
+++ b/components/leveldb_proto/proto_database_provider.h
@@ -26,12 +26,11 @@
   static ProtoDatabaseProvider* Create(const base::FilePath& profile_dir);
 
   // |client_namespace| is the unique prefix to be used in the shared database
-  // if the database returned is a SharedDatabaseClient<T>.
-  // |type_prefix| is a unique prefix within the |client_namespace| to be used
-  // in the shared database if the database returned is a
-  // SharedProtoDatabaseClient<T>.
-  // |unique_db_dir|: the subdirectory this database should live in within
-  // the profile directory.
+  // if the database returned is a SharedDatabaseClient<T>. This name must be
+  // present in |kCurrentSharedProtoDatabaseClients|. |type_prefix| is a unique
+  // prefix within the |client_namespace| to be used in the shared database if
+  // the database returned is a SharedProtoDatabaseClient<T>. |unique_db_dir|:
+  // the subdirectory this database should live in within the profile directory.
   // |task_runner|: the SequencedTaskRunner to run all database operations on.
   // This isn't used by SharedProtoDatabaseClients since all calls using
   // the SharedProtoDatabase will run on its TaskRunner.
diff --git a/components/leveldb_proto/proto_leveldb_wrapper.cc b/components/leveldb_proto/proto_leveldb_wrapper.cc
index 0c9b246..03005fb 100644
--- a/components/leveldb_proto/proto_leveldb_wrapper.cc
+++ b/components/leveldb_proto/proto_leveldb_wrapper.cc
@@ -47,6 +47,21 @@
       FROM_HERE, base::BindOnce(std::move(callback), success, std::move(keys)));
 }
 
+void RemoveKeysFromTaskRunner(
+    LevelDB* database,
+    const std::string& target_prefix,
+    const LevelDB::KeyFilter& filter,
+    const std::string& client_id,
+    Callbacks::UpdateCallback callback,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner) {
+  leveldb::Status status;
+  bool success = database->UpdateWithRemoveFilter(base::StringPairs(), filter,
+                                                  target_prefix, &status);
+  ProtoLevelDBWrapperMetrics::RecordUpdate(client_id, success, status);
+  callback_task_runner->PostTask(FROM_HERE,
+                                 base::BindOnce(std::move(callback), success));
+}
+
 }  // namespace
 
 ProtoLevelDBWrapper::ProtoLevelDBWrapper(
@@ -86,16 +101,6 @@
       std::move(callback));
 }
 
-void ProtoLevelDBWrapper::Destroy(Callbacks::DestroyCallback callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(db_);
-
-  base::PostTaskAndReplyWithResult(
-      task_runner_.get(), FROM_HERE,
-      base::BindOnce(DestroyFromTaskRunner, base::Unretained(db_), metrics_id_),
-      std::move(callback));
-}
-
 void ProtoLevelDBWrapper::LoadKeys(
     typename Callbacks::LoadKeysCallback callback) {
   LoadKeys(std::string(), std::move(callback));
@@ -111,6 +116,27 @@
                                 base::SequencedTaskRunnerHandle::Get()));
 }
 
+void ProtoLevelDBWrapper::RemoveKeys(const LevelDB::KeyFilter& filter,
+                                     const std::string& target_prefix,
+                                     Callbacks::UpdateCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(RemoveKeysFromTaskRunner, base::Unretained(db_),
+                     target_prefix, filter, metrics_id_, std::move(callback),
+                     base::SequencedTaskRunnerHandle::Get()));
+}
+
+void ProtoLevelDBWrapper::Destroy(Callbacks::DestroyCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(db_);
+
+  base::PostTaskAndReplyWithResult(
+      task_runner_.get(), FROM_HERE,
+      base::BindOnce(DestroyFromTaskRunner, base::Unretained(db_), metrics_id_),
+      std::move(callback));
+}
+
 void ProtoLevelDBWrapper::SetMetricsId(const std::string& id) {
   metrics_id_ = id;
 }
diff --git a/components/leveldb_proto/proto_leveldb_wrapper.h b/components/leveldb_proto/proto_leveldb_wrapper.h
index b6bbf84..444451d 100644
--- a/components/leveldb_proto/proto_leveldb_wrapper.h
+++ b/components/leveldb_proto/proto_leveldb_wrapper.h
@@ -106,6 +106,10 @@
   void GetEntry(const std::string& key,
                 typename Callbacks::Internal<T>::GetCallback callback);
 
+  void RemoveKeys(const LevelDB::KeyFilter& filter,
+                  const std::string& target_prefix,
+                  Callbacks::UpdateCallback callback);
+
   void Destroy(Callbacks::DestroyCallback callback);
 
   void RunInitCallback(Callbacks::InitCallback callback,
diff --git a/components/leveldb_proto/shared_proto_database.cc b/components/leveldb_proto/shared_proto_database.cc
index 831c8176..74911d4 100644
--- a/components/leveldb_proto/shared_proto_database.cc
+++ b/components/leveldb_proto/shared_proto_database.cc
@@ -26,13 +26,9 @@
 
 }  // namespace
 
-inline void RunInitCallbackOnCallingSequence(
-    Callbacks::InitCallback callback,
-    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
-    bool success) {
-  callback_task_runner->PostTask(FROM_HERE,
-                                 base::BindOnce(std::move(callback), success));
-}
+// static
+const base::TimeDelta SharedProtoDatabase::kDelayToClearObsoleteDatabase =
+    base::TimeDelta::FromSeconds(120);
 
 inline void RunInitStatusCallbackOnCallingSequence(
     Callbacks::InitStatusCallback callback,
@@ -193,7 +189,8 @@
                        false /* corruption */);
 }
 
-void SharedProtoDatabase::ProcessInitRequests(Enums::InitStatus status) {
+void SharedProtoDatabase::ProcessOutstandingInitRequests(
+    Enums::InitStatus status) {
   // The pairs are stored as (callback, callback_task_runner).
   while (!outstanding_init_requests_.empty()) {
     auto request = std::move(outstanding_init_requests_.front());
@@ -222,7 +219,7 @@
     init_status_ = Enums::InitStatus::kError;
     callback_task_runner->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), init_status_));
-    ProcessInitRequests(init_status_);
+    ProcessOutstandingInitRequests(init_status_);
     return;
   }
 
@@ -259,7 +256,7 @@
     init_status_ = Enums::InitStatus::kError;
     RunInitStatusCallbackOnCallingSequence(
         std::move(callback), std::move(callback_task_runner), init_status_);
-    ProcessInitRequests(init_status_);
+    ProcessOutstandingInitRequests(init_status_);
     return;
   }
 
@@ -322,7 +319,7 @@
     init_status_ = Enums::InitStatus::kError;
     RunInitStatusCallbackOnCallingSequence(
         std::move(callback), std::move(callback_task_runner), init_status_);
-    ProcessInitRequests(init_status_);
+    ProcessOutstandingInitRequests(init_status_);
     return;
   }
 
@@ -361,7 +358,7 @@
       success ? Enums::InitStatus::kCorrupt : Enums::InitStatus::kError;
   RunInitStatusCallbackOnCallingSequence(
       std::move(callback), std::move(callback_task_runner), init_status_);
-  ProcessInitRequests(init_status_);
+  ProcessOutstandingInitRequests(init_status_);
 }
 
 void SharedProtoDatabase::OnDatabaseInit(
@@ -397,9 +394,26 @@
   init_status_ = status;
   init_state_ = status == Enums::InitStatus::kOK ? InitState::kSuccess
                                                  : InitState::kFailure;
-  ProcessInitRequests(status);
+
   callback_task_runner->PostTask(FROM_HERE,
                                  base::BindOnce(std::move(callback), status));
+  ProcessOutstandingInitRequests(status);
+
+  if (init_state_ == InitState::kSuccess) {
+    // Create a ProtoLevelDBWrapper just like we create for each client, for
+    // deleting data from obsolete clients. It is fine to use the same wrapper
+    // to clear data from all clients. This object will be destroyed after
+    // clearing data for all these clients.
+    auto db_wrapper =
+        std::make_unique<ProtoLevelDBWrapper>(task_runner_, db_.get());
+    Callbacks::UpdateCallback obsolete_cleared_callback = base::DoNothing();
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&DestroyObsoleteSharedProtoDatabaseClients,
+                       std::move(db_wrapper),
+                       std::move(obsolete_cleared_callback)),
+        kDelayToClearObsoleteDatabase);
+  }
 }
 
 SharedProtoDatabase::~SharedProtoDatabase() {
diff --git a/components/leveldb_proto/shared_proto_database.h b/components/leveldb_proto/shared_proto_database.h
index 4bd50a0..9e77cf4 100644
--- a/components/leveldb_proto/shared_proto_database.h
+++ b/components/leveldb_proto/shared_proto_database.h
@@ -87,6 +87,10 @@
     std::string client_name;
   };
 
+  // Make sure to give enough time after startup so that we have less chance of
+  // affecting startup or navigations.
+  static const base::TimeDelta kDelayToClearObsoleteDatabase;
+
   // Private since we only want to create a singleton of it.
   SharedProtoDatabase(
       const std::string& client_name,
@@ -94,7 +98,7 @@
 
   virtual ~SharedProtoDatabase();
 
-  void ProcessInitRequests(Enums::InitStatus status);
+  void ProcessOutstandingInitRequests(Enums::InitStatus status);
 
   template <typename T>
   std::unique_ptr<SharedProtoDatabaseClient<T>> GetClientInternal(
@@ -167,6 +171,11 @@
 
   LevelDB* GetLevelDBForTesting() const;
 
+  scoped_refptr<base::SequencedTaskRunner> database_task_runner_for_testing()
+      const {
+    return task_runner_;
+  }
+
   SEQUENCE_CHECKER(on_task_runner_);
 
   InitState init_state_ = InitState::kNone;
diff --git a/components/leveldb_proto/shared_proto_database_client.cc b/components/leveldb_proto/shared_proto_database_client.cc
index 0510904..14baa8e 100644
--- a/components/leveldb_proto/shared_proto_database_client.cc
+++ b/components/leveldb_proto/shared_proto_database_client.cc
@@ -7,8 +7,36 @@
 #include "base/strings/strcat.h"
 #include "components/leveldb_proto/proto_leveldb_wrapper.h"
 #include "components/leveldb_proto/shared_proto_database.h"
+#include "components/leveldb_proto/shared_proto_database_client_list.h"
 
 namespace leveldb_proto {
+namespace {
+const char* const* g_obsolete_client_list_for_testing = nullptr;
+
+// Holds the db wrapper alive and callback is called at destruction. This class
+// is used to post multiple update tasks on |db_wrapper| and keep the instance
+// alive till all the callbacks are returned.
+class ObsoleteClientsDbHolder
+    : public base::RefCounted<ObsoleteClientsDbHolder> {
+ public:
+  ObsoleteClientsDbHolder(std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
+                          Callbacks::UpdateCallback callback)
+      : success_(true),
+        owned_db_wrapper_(std::move(db_wrapper)),
+        callback_(std::move(callback)) {}
+
+  void set_success(bool success) { success_ &= success; }
+
+ private:
+  friend class RefCounted<ObsoleteClientsDbHolder>;
+  ~ObsoleteClientsDbHolder() { std::move(callback_).Run(success_); }
+
+  bool success_;
+  std::unique_ptr<ProtoLevelDBWrapper> owned_db_wrapper_;
+  Callbacks::UpdateCallback callback_;
+};
+
+}  // namespace
 
 std::string StripPrefix(const std::string& key, const std::string& prefix) {
   return base::StartsWith(key, prefix, base::CompareCase::SENSITIVE)
@@ -46,4 +74,35 @@
   shared_db->UpdateClientCorruptAsync(client_name, std::move(callback));
 }
 
+void DestroyObsoleteSharedProtoDatabaseClients(
+    std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
+    Callbacks::UpdateCallback callback) {
+  ProtoLevelDBWrapper* db_wrapper_ptr = db_wrapper.get();
+  scoped_refptr<ObsoleteClientsDbHolder> db_holder =
+      new ObsoleteClientsDbHolder(std::move(db_wrapper), std::move(callback));
+
+  const char* const* list = g_obsolete_client_list_for_testing
+                                ? g_obsolete_client_list_for_testing
+                                : kObsoleteSharedProtoDatabaseClients;
+  for (size_t i = 0; list[i] != nullptr; ++i) {
+    // Callback keeps a ref pointer to db_holder alive till the changes are
+    // done. |db_holder| will be destroyed once all the RemoveKeys() calls
+    // return.
+    Callbacks::UpdateCallback callback_wrapper =
+        base::BindOnce([](scoped_refptr<ObsoleteClientsDbHolder> db_holder,
+                          bool success) { db_holder->set_success(success); },
+                       db_holder);
+    // Remove all type prefixes for the client.
+    // TODO(ssid): Support cleanup of namespaces for clients. This code assumes
+    // the prefix contains the client namespace at the beginning.
+    db_wrapper_ptr->RemoveKeys(
+        base::BindRepeating([](const std::string& key) { return true; }),
+        list[i], std::move(callback_wrapper));
+  }
+}
+
+void SetObsoleteClientListForTesting(const char* const* list) {
+  g_obsolete_client_list_for_testing = list;
+}
+
 }  // namespace leveldb_proto
diff --git a/components/leveldb_proto/shared_proto_database_client.h b/components/leveldb_proto/shared_proto_database_client.h
index 8995fe7..fd11d224 100644
--- a/components/leveldb_proto/shared_proto_database_client.h
+++ b/components/leveldb_proto/shared_proto_database_client.h
@@ -40,9 +40,21 @@
                               const std::string& client_name,
                               ClientCorruptCallback callback);
 
+// Destroys all the data from obsolete clients, for the given |db_wrapper|
+// instance. |callback| is called once all the obsolete clients data are
+// removed, with failure status if one or more of the update fails.
+void DestroyObsoleteSharedProtoDatabaseClients(
+    std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
+    Callbacks::UpdateCallback callback);
+
+// Sets list of client names that are obsolete and will be cleared by next call
+// to DestroyObsoleteSharedProtoDatabaseClients(). |list| is list of c strings
+// with a nullptr to mark the end of list.
+void SetObsoleteClientListForTesting(const char* const* list);
+
 // An implementation of ProtoDatabase<T> that uses a shared LevelDB and task
 // runner.
-// Should be created, destroyed, and used on the same thread.
+// Should be created, destroyed, and used on the same sequenced task runner.
 template <typename T>
 class SharedProtoDatabaseClient : public ProtoDatabase<T> {
  public:
diff --git a/components/leveldb_proto/shared_proto_database_client_list.cc b/components/leveldb_proto/shared_proto_database_client_list.cc
index 216f4c2..9019b6a15 100644
--- a/components/leveldb_proto/shared_proto_database_client_list.cc
+++ b/components/leveldb_proto/shared_proto_database_client_list.cc
@@ -12,22 +12,6 @@
 
 namespace leveldb_proto {
 
-const char kFeatureEngagementClientName[] = "FeatureEngagement";
-
-const char* const kCurrentSharedProtoDatabaseClients[] = {
-    kFeatureEngagementClientName,
-};
-const size_t kCurrentSharedProtoDatabaseClientsLength =
-    base::size(kCurrentSharedProtoDatabaseClients);
-
-#ifdef SHARED_PROTO_DATABASE_CLIENT_LIST_USE_OBSOLETE_CLIENT_LIST
-const char* const kObsoleteSharedProtoDatabaseClients[] = {
-    /* Add obsolete clients here. */
-};
-const size_t kObsoleteSharedProtoDatabaseClientsLength =
-    base::size(kObsoleteSharedProtoDatabaseClients);
-#endif  // SHARED_PROTO_DATABASE_CLIENT_LIST_USE_OBSOLETE_CLIENT_LIST
-
 // static
 bool SharedProtoDatabaseClientList::ShouldUseSharedDB(
     const std::string& client_name) {
diff --git a/components/leveldb_proto/shared_proto_database_client_list.h b/components/leveldb_proto/shared_proto_database_client_list.h
index 5644a2d..59ef09e 100644
--- a/components/leveldb_proto/shared_proto_database_client_list.h
+++ b/components/leveldb_proto/shared_proto_database_client_list.h
@@ -11,15 +11,19 @@
 
 namespace leveldb_proto {
 
-extern const char kFeatureEngagementClientName[];
+const char* const kFeatureEngagementName = "FeatureEngagement";
 
-extern const char* const kCurrentSharedProtoDatabaseClients[];
-extern const size_t kCurrentSharedProtoDatabaseClientsLength;
+// NOTE: The client names should not have partial or complete prefix overlap
+// with any other client name, current or obsolete. Internally the stored data
+// is grouped by the prefix of client name. These names cannot be renamed
+// without adding the old name to obsolete client list and such rename would
+// make the client be treated as a new client.
+const char* const kCurrentSharedProtoDatabaseClients[] = {
+    kFeatureEngagementName, nullptr};
 
-#ifdef SHARED_PROTO_DATABASE_CLIENT_LIST_USE_OBSOLETE_CLIENT_LIST
-extern const char* const kObsoleteSharedProtoDatabaseClients[];
-extern const size_t kObsoleteSharedProtoDatabaseClientsLength;
-#endif  // SHARED_PROTO_DATABASE_CLIENT_LIST_USE_OBSOLETE_CLIENT_LIST
+const char* const kObsoleteSharedProtoDatabaseClients[] = {
+    nullptr  // Marks the last element.
+};
 
 class SharedProtoDatabaseClientList {
  public:
diff --git a/components/leveldb_proto/shared_proto_database_client_unittest.cc b/components/leveldb_proto/shared_proto_database_client_unittest.cc
index 3f0e996..267f70e 100644
--- a/components/leveldb_proto/shared_proto_database_client_unittest.cc
+++ b/components/leveldb_proto/shared_proto_database_client_unittest.cc
@@ -20,9 +20,10 @@
 
 namespace {
 
-const std::string kDefaultNamespace = "ns";
-const std::string kDefaultNamespace2 = "ns2";
-const std::string kDefaultTypePrefix = "tp";
+const char* kDefaultNamespace = "abc";
+const char* kDefaultNamespace1 = "cde";
+const char* kDefaultNamespace2 = "cfd";
+const char* kDefaultTypePrefix = "tp";
 
 }  // namespace
 
@@ -40,6 +41,7 @@
     temp_dir_.reset();
   }
 
+ protected:
   scoped_refptr<SharedProtoDatabase> db() { return db_; }
   base::ScopedTempDir* temp_dir() { return temp_dir_.get(); }
 
@@ -235,6 +237,26 @@
     destroy_loop.Run();
   }
 
+  // Sets the obsolete client list to given list, runs clean up tasks and waits
+  // for them to complete.
+  void DestroyObsoleteClientsAndWait(const char* const* client_list) {
+    SetObsoleteClientListForTesting(client_list);
+    base::RunLoop wait_loop;
+    Callbacks::UpdateCallback wait_callback = base::BindOnce(
+        [](base::OnceClosure closure, bool success) {
+          EXPECT_TRUE(success);
+          std::move(closure).Run();
+        },
+        wait_loop.QuitClosure());
+
+    DestroyObsoleteSharedProtoDatabaseClients(
+        std::make_unique<ProtoLevelDBWrapper>(
+            db_->database_task_runner_for_testing(), GetLevelDB()),
+        std::move(wait_callback));
+    wait_loop.Run();
+    SetObsoleteClientListForTesting(nullptr);
+  }
+
  private:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 
@@ -463,6 +485,61 @@
   }
 }
 
+TEST_F(SharedProtoDatabaseClientTest, TestCleanupObsoleteClients) {
+  auto status = Enums::InitStatus::kError;
+  auto client_a =
+      GetClientAndWait<TestProto>(kDefaultNamespace, kDefaultTypePrefix,
+                                  true /* create_if_missing */, &status);
+  ASSERT_EQ(status, Enums::InitStatus::kOK);
+  auto client_b =
+      GetClientAndWait<TestProto>(kDefaultNamespace1, kDefaultTypePrefix,
+                                  true /* create_if_missing */, &status);
+  ASSERT_EQ(status, Enums::InitStatus::kOK);
+  auto client_c =
+      GetClientAndWait<TestProto>(kDefaultNamespace2, kDefaultTypePrefix,
+                                  true /* create_if_missing */, &status);
+  ASSERT_EQ(status, Enums::InitStatus::kOK);
+
+  std::vector<std::string> test_keys = {"a", "b", "c"};
+  UpdateEntries(client_a.get(), test_keys, leveldb_proto::KeyVector(), true);
+  UpdateEntries(client_b.get(), test_keys, leveldb_proto::KeyVector(), true);
+  UpdateEntries(client_c.get(), test_keys, leveldb_proto::KeyVector(), true);
+
+  // Check that the original list does not clear any data from test DBs.
+  DestroyObsoleteClientsAndWait(nullptr /* client_list */);
+
+  std::vector<std::string> keys;
+  LevelDB* db = GetLevelDB();
+  db->LoadKeys(&keys);
+  EXPECT_EQ(keys.size(), test_keys.size() * 3);
+
+  // Mark some DBs obsolete.
+  const char* const kObsoleteList1[] = {kDefaultNamespace, kDefaultNamespace1,
+                                        nullptr};
+  DestroyObsoleteClientsAndWait(kObsoleteList1);
+
+  keys.clear();
+  db->LoadKeys(&keys);
+
+  EXPECT_EQ(keys.size(), test_keys.size());
+  EXPECT_FALSE(
+      ContainsKeys(keys, test_keys, kDefaultNamespace, kDefaultTypePrefix));
+  EXPECT_FALSE(
+      ContainsKeys(keys, test_keys, kDefaultNamespace1, kDefaultTypePrefix));
+  EXPECT_TRUE(
+      ContainsKeys(keys, test_keys, kDefaultNamespace2, kDefaultTypePrefix));
+
+  // Make all the DBs obsolete.
+  const char* const kObsoleteList2[] = {kDefaultNamespace, kDefaultNamespace1,
+                                        kDefaultNamespace2, nullptr};
+  DestroyObsoleteClientsAndWait(kObsoleteList2);
+
+  // Nothing should remain.
+  keys.clear();
+  db->LoadKeys(&keys);
+  EXPECT_EQ(keys.size(), 0U);
+}
+
 TEST_F(SharedProtoDatabaseClientTest, TestDestroy) {
   auto status = Enums::InitStatus::kError;
   auto client_a =
diff --git a/components/leveldb_proto/unique_proto_database.h b/components/leveldb_proto/unique_proto_database.h
index 50b6a3c5..85c0b09 100644
--- a/components/leveldb_proto/unique_proto_database.h
+++ b/components/leveldb_proto/unique_proto_database.h
@@ -96,6 +96,10 @@
 
   void Destroy(Callbacks::DestroyCallback callback) override;
 
+  void RemoveKeysForTesting(const LevelDB::KeyFilter& key_filter,
+                            const std::string& target_prefix,
+                            Callbacks::UpdateCallback callback);
+
   bool GetApproximateMemoryUse(uint64_t* approx_mem_use);
 
   // Sets the identifier used by the underlying LevelDB wrapper to record
@@ -282,6 +286,14 @@
 }
 
 template <typename T>
+void UniqueProtoDatabase<T>::RemoveKeysForTesting(
+    const LevelDB::KeyFilter& key_filter,
+    const std::string& target_prefix,
+    Callbacks::UpdateCallback callback) {
+  db_wrapper_->RemoveKeys(key_filter, target_prefix, std::move(callback));
+}
+
+template <typename T>
 bool UniqueProtoDatabase<T>::GetApproximateMemoryUse(uint64_t* approx_mem_use) {
   return db_wrapper_->GetApproximateMemoryUse(approx_mem_use);
 }
diff --git a/components/leveldb_proto/unique_proto_database_unittest.cc b/components/leveldb_proto/unique_proto_database_unittest.cc
index c867056..7c755f1e 100644
--- a/components/leveldb_proto/unique_proto_database_unittest.cc
+++ b/components/leveldb_proto/unique_proto_database_unittest.cc
@@ -59,6 +59,11 @@
                bool(const base::StringPairs&,
                     const KeyFilter&,
                     leveldb::Status*));
+  MOCK_METHOD4(UpdateWithRemoveFilter,
+               bool(const base::StringPairs&,
+                    const KeyFilter&,
+                    const std::string&,
+                    leveldb::Status*));
   MOCK_METHOD1(Load, bool(std::vector<std::string>*));
   MOCK_METHOD2(LoadWithFilter,
                bool(const KeyFilter&, std::vector<std::string>*));
@@ -151,8 +156,6 @@
   return key == "0";
 }
 
-}  // namespace
-
 EntryMap GetSmallModel() {
   EntryMap model;
 
@@ -168,6 +171,8 @@
   return model;
 }
 
+}  // namespace
+
 void ExpectEntryPointersEquals(EntryMap expected,
                                const std::vector<TestProto>& actual) {
   EXPECT_EQ(expected.size(), actual.size());
@@ -405,6 +410,36 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(UniqueProtoDatabaseTest, TestDBRemoveKeys) {
+  const std::string kTestPrefix = "test_prefix";
+  base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+  auto mock_db = std::make_unique<MockDB>();
+  MockDatabaseCaller caller;
+
+  EXPECT_CALL(*mock_db, Init(_, options_, _));
+  EXPECT_CALL(*mock_db, UpdateWithRemoveFilter(_, _, kTestPrefix, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(caller, InitStatusCallback(_));
+  db_->InitWithDatabase(mock_db.get(), path, CreateSimpleOptions(),
+                        base::BindOnce(&MockDatabaseCaller::InitStatusCallback,
+                                       base::Unretained(&caller)));
+
+  base::RunLoop().RunUntilIdle();
+
+  base::RunLoop run_update_entries;
+  auto expect_update_success = base::BindOnce(
+      [](base::OnceClosure signal, bool success) {
+        EXPECT_TRUE(success);
+        std::move(signal).Run();
+      },
+      run_update_entries.QuitClosure());
+  db_->RemoveKeysForTesting(
+      base::BindRepeating([](const std::string& str) { return true; }),
+      kTestPrefix, std::move(expect_update_success));
+  run_update_entries.Run();
+}
+
 class UniqueProtoDatabaseLevelDBTest : public testing::Test {
  public:
   void TearDown() override { base::RunLoop().RunUntilIdle(); }
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index 4d443e5..dfc1313e 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -483,6 +483,9 @@
   // it!
   base::string16 keyword;
 
+  // Set in matches originating from keyword results.
+  bool from_keyword;
+
   // Set to a matching pedal if appropriate.  The pedal is not owned, and the
   // owning OmniboxPedalProvider must outlive this.
   OmniboxPedal* pedal = nullptr;
diff --git a/components/omnibox/browser/base_search_provider.cc b/components/omnibox/browser/base_search_provider.cc
index 7f4dab29..448a426 100644
--- a/components/omnibox/browser/base_search_provider.cc
+++ b/components/omnibox/browser/base_search_provider.cc
@@ -151,7 +151,7 @@
 AutocompleteMatch BaseSearchProvider::CreateSearchSuggestion(
     const base::string16& suggestion,
     AutocompleteMatchType::Type type,
-    bool from_keyword_provider,
+    bool from_keyword,
     const TemplateURL* template_url,
     const SearchTermsData& search_terms_data) {
   // These calls use a number of default values.  For instance, they assume
@@ -159,13 +159,13 @@
   // mode.  They also assume the caller knows what it's doing and we set
   // this match to look as if it was received/created synchronously.
   SearchSuggestionParser::SuggestResult suggest_result(
-      suggestion, type, /*subtype_identifier=*/0, from_keyword_provider,
+      suggestion, type, /*subtype_identifier=*/0, from_keyword,
       /*relevance=*/0, /*relevance_from_server=*/false,
       /*input_text=*/base::string16());
   suggest_result.set_received_after_last_keystroke(false);
-  return CreateSearchSuggestion(nullptr, AutocompleteInput(),
-                                from_keyword_provider, suggest_result,
-                                template_url, search_terms_data, 0, false);
+  return CreateSearchSuggestion(nullptr, AutocompleteInput(), from_keyword,
+                                suggest_result, template_url, search_terms_data,
+                                0, false);
 }
 
 // static
@@ -289,15 +289,18 @@
   const base::string16 input_lower = base::i18n::ToLower(input.text());
   // suggestion.match_contents() should have already been collapsed.
   match.allowed_to_be_default_match =
-      (!in_keyword_mode || suggestion.from_keyword_provider()) &&
+      (!in_keyword_mode || suggestion.from_keyword()) &&
       (base::CollapseWhitespace(input_lower, false) ==
        base::i18n::ToLower(suggestion.match_contents()));
 
+  if (suggestion.from_keyword())
+    match.from_keyword = true;
+
   // We only allow inlinable navsuggestions that were received before the
   // last keystroke because we don't want asynchronous inline autocompletions.
   if (!input.prevent_inline_autocomplete() &&
       !suggestion.received_after_last_keystroke() &&
-      (!in_keyword_mode || suggestion.from_keyword_provider()) &&
+      (!in_keyword_mode || suggestion.from_keyword()) &&
       base::StartsWith(
           base::i18n::ToLower(suggestion.suggestion()), input_lower,
           base::CompareCase::SENSITIVE)) {
@@ -330,8 +333,8 @@
       *match.search_terms_args, search_terms_data));
 
   // Search results don't look like URLs.
-  match.transition = suggestion.from_keyword_provider() ?
-      ui::PAGE_TRANSITION_KEYWORD : ui::PAGE_TRANSITION_GENERATED;
+  match.transition = suggestion.from_keyword() ? ui::PAGE_TRANSITION_KEYWORD
+                                               : ui::PAGE_TRANSITION_GENERATED;
 
   return match;
 }
@@ -342,7 +345,7 @@
     const TemplateURL* template_url) {
   base::string16 fill_into_edit;
 
-  if (suggest_result.from_keyword_provider())
+  if (suggest_result.from_keyword())
     fill_into_edit.append(template_url->keyword() + base::char16(' '));
 
   fill_into_edit.append(suggest_result.suggestion());
@@ -427,8 +430,8 @@
     bool in_keyword_mode,
     MatchMap* map) {
   AutocompleteMatch match = CreateSearchSuggestion(
-      this, GetInput(result.from_keyword_provider()), in_keyword_mode, result,
-      GetTemplateURL(result.from_keyword_provider()),
+      this, GetInput(result.from_keyword()), in_keyword_mode, result,
+      GetTemplateURL(result.from_keyword()),
       client_->GetTemplateURLService()->search_terms_data(),
       accepted_suggestion, ShouldAppendExtraParams(result));
   if (!match.destination_url.is_valid())
diff --git a/components/omnibox/browser/keyword_provider.cc b/components/omnibox/browser/keyword_provider.cc
index db34490..a3a1a95 100644
--- a/components/omnibox/browser/keyword_provider.cc
+++ b/components/omnibox/browser/keyword_provider.cc
@@ -482,6 +482,7 @@
   FillInURLAndContents(remaining_input, template_url, &match);
 
   match.keyword = keyword;
+  match.from_keyword = true;
   match.transition = ui::PAGE_TRANSITION_KEYWORD;
 
   return match;
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index b199ce57..c0a17d1 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -743,8 +743,9 @@
   fake_single_entry_result.AppendMatches(input_, fake_single_entry_matches);
   OmniboxLog log(
       input_.from_omnibox_focus() ? base::string16() : input_text,
-      just_deleted_text_, input_.type(), popup_open,
-      dropdown_ignored ? 0 : index, disposition, !pasted_text.empty(),
+      just_deleted_text_, input_.type(), is_keyword_selected(),
+      keyword_mode_entry_method_, popup_open, dropdown_ignored ? 0 : index,
+      disposition, !pasted_text.empty(),
       SessionID::InvalidValue(),  // don't know tab ID; set later if appropriate
       ClassifyPage(), elapsed_time_since_user_first_modified_omnibox,
       match.allowed_to_be_default_match ? match.inline_autocompletion.length()
diff --git a/components/omnibox/browser/omnibox_edit_model.h b/components/omnibox/browser/omnibox_edit_model.h
index 6d4e1220..d01e927 100644
--- a/components/omnibox/browser/omnibox_edit_model.h
+++ b/components/omnibox/browser/omnibox_edit_model.h
@@ -68,6 +68,8 @@
     OmniboxFocusState focus_state;
     FocusSource focus_source;
     const AutocompleteInput autocomplete_input;
+   private:
+    DISALLOW_ASSIGN(State);
   };
 
   // This is a mirror of content::kMaxURLDisplayChars because ios cannot depend
diff --git a/components/omnibox/browser/omnibox_log.cc b/components/omnibox/browser/omnibox_log.cc
index a35231e..4270b56 100644
--- a/components/omnibox/browser/omnibox_log.cc
+++ b/components/omnibox/browser/omnibox_log.cc
@@ -8,6 +8,8 @@
     const base::string16& text,
     bool just_deleted_text,
     metrics::OmniboxInputType input_type,
+    bool in_keyword_mode,
+    metrics::OmniboxEventProto::KeywordModeEntryMethod entry_method,
     bool is_popup_open,
     size_t selected_index,
     WindowOpenDisposition disposition,
@@ -21,6 +23,8 @@
     : text(text),
       just_deleted_text(just_deleted_text),
       input_type(input_type),
+      in_keyword_mode(in_keyword_mode),
+      keyword_mode_entry_method(entry_method),
       is_popup_open(is_popup_open),
       selected_index(selected_index),
       disposition(disposition),
diff --git a/components/omnibox/browser/omnibox_log.h b/components/omnibox/browser/omnibox_log.h
index ab7ebd6..bbb7b5d 100644
--- a/components/omnibox/browser/omnibox_log.h
+++ b/components/omnibox/browser/omnibox_log.h
@@ -23,6 +23,8 @@
   OmniboxLog(const base::string16& text,
              bool just_deleted_text,
              metrics::OmniboxInputType input_type,
+             bool in_keyword_mode,
+             metrics::OmniboxEventProto::KeywordModeEntryMethod entry_method,
              bool is_popup_open,
              size_t selected_index,
              WindowOpenDisposition disposition,
@@ -46,6 +48,14 @@
   // The detected type of the user's input.
   metrics::OmniboxInputType input_type;
 
+  // Whether the Omnibox was in keyword mode when the user selected a
+  // suggestion.
+  bool in_keyword_mode;
+
+  // Preserves the method that the user used to enter keyword mode. If
+  // |in_keyword_mode| is false, this should be INVALID.
+  metrics::OmniboxEventProto::KeywordModeEntryMethod keyword_mode_entry_method;
+
   // True if the popup is open.
   bool is_popup_open;
 
diff --git a/components/omnibox/browser/omnibox_metrics_provider.cc b/components/omnibox/browser/omnibox_metrics_provider.cc
index 4a5447a..042602c0 100644
--- a/components/omnibox/browser/omnibox_metrics_provider.cc
+++ b/components/omnibox/browser/omnibox_metrics_provider.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "build/build_config.h"
 #include "components/metrics/metrics_log.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_provider.h"
@@ -169,10 +170,25 @@
     if (i->subtype_identifier > 0)
       suggestion->set_result_subtype_identifier(i->subtype_identifier);
     suggestion->set_has_tab_match(i->has_tab_match);
+    // TODO(krb@chromium.org): Try including when libprotobuf is updated.
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+    suggestion->set_is_keyword_suggestion(i->from_keyword);
+#endif
   }
   for (auto i(log.providers_info.begin()); i != log.providers_info.end(); ++i) {
     OmniboxEventProto::ProviderInfo* provider_info =
         omnibox_event->add_provider_info();
     provider_info->CopyFrom(*i);
   }
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+  omnibox_event->set_in_keyword_mode(log.in_keyword_mode);
+  if (log.in_keyword_mode) {
+    if (metrics::OmniboxEventProto_KeywordModeEntryMethod_IsValid(
+            log.keyword_mode_entry_method))
+      omnibox_event->set_keyword_mode_entry_method(
+          log.keyword_mode_entry_method);
+    else
+      omnibox_event->set_keyword_mode_entry_method(OmniboxEventProto::INVALID);
+  }
+#endif
 }
diff --git a/components/omnibox/browser/search_provider.cc b/components/omnibox/browser/search_provider.cc
index 42299c5..3d63271 100644
--- a/components/omnibox/browser/search_provider.cc
+++ b/components/omnibox/browser/search_provider.cc
@@ -340,8 +340,7 @@
 
 bool SearchProvider::ShouldAppendExtraParams(
     const SearchSuggestionParser::SuggestResult& result) const {
-  return !result.from_keyword_provider() ||
-      providers_.default_provider().empty();
+  return !result.from_keyword() || providers_.default_provider().empty();
 }
 
 void SearchProvider::RecordDeletionResult(bool success) {
@@ -1001,8 +1000,8 @@
     SearchSuggestionParser::SuggestResult verbatim(
         /*suggestion=*/trimmed_verbatim,
         AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
-        /*subtype_identifier=*/0, /*from_keyword_provider=*/false,
-        verbatim_relevance, relevance_from_server,
+        /*subtype_identifier=*/0, /*from_keyword=*/false, verbatim_relevance,
+        relevance_from_server,
         /*input_text=*/trimmed_verbatim);
     if (has_answer)
       verbatim.SetAnswer(answer);
@@ -1027,7 +1026,7 @@
         SearchSuggestionParser::SuggestResult verbatim(
             /*suggestion=*/trimmed_verbatim,
             AutocompleteMatchType::SEARCH_OTHER_ENGINE,
-            /*subtype_identifier=*/0, /*from_keyword_provider=*/true,
+            /*subtype_identifier=*/0, /*from_keyword=*/true,
             keyword_verbatim_relevance, keyword_relevance_from_server,
             /*input_text=*/trimmed_verbatim);
         AddMatchToMap(verbatim, std::string(),
@@ -1436,10 +1435,10 @@
 AutocompleteMatch SearchProvider::NavigationToMatch(
     const SearchSuggestionParser::NavigationResult& navigation) {
   base::string16 input;
-  const bool trimmed_whitespace = base::TrimWhitespace(
-      navigation.from_keyword_provider() ?
-          keyword_input_.text() : input_.text(),
-      base::TRIM_TRAILING, &input) != base::TRIM_NONE;
+  const bool trimmed_whitespace =
+      base::TrimWhitespace(
+          navigation.from_keyword() ? keyword_input_.text() : input_.text(),
+          base::TRIM_TRAILING, &input) != base::TRIM_NONE;
   AutocompleteMatch match(this, navigation.relevance(), false,
                           navigation.type());
   match.destination_url = navigation.url();
@@ -1501,6 +1500,8 @@
       navigation.relevance_from_server() ? kTrue : kFalse);
   match.RecordAdditionalInfo(kShouldPrefetchKey, kFalse);
 
+  match.from_keyword = navigation.from_keyword();
+
   return match;
 }
 
diff --git a/components/omnibox/browser/search_suggestion_parser.cc b/components/omnibox/browser/search_suggestion_parser.cc
index d1036c5..d7b8558 100644
--- a/components/omnibox/browser/search_suggestion_parser.cc
+++ b/components/omnibox/browser/search_suggestion_parser.cc
@@ -59,13 +59,13 @@
 
 // SearchSuggestionParser::Result ----------------------------------------------
 
-SearchSuggestionParser::Result::Result(bool from_keyword_provider,
+SearchSuggestionParser::Result::Result(bool from_keyword,
                                        int relevance,
                                        bool relevance_from_server,
                                        AutocompleteMatchType::Type type,
                                        int subtype_identifier,
                                        const std::string& deletion_url)
-    : from_keyword_provider_(from_keyword_provider),
+    : from_keyword_(from_keyword),
       type_(type),
       subtype_identifier_(subtype_identifier),
       relevance_(relevance),
@@ -83,7 +83,7 @@
     const base::string16& suggestion,
     AutocompleteMatchType::Type type,
     int subtype_identifier,
-    bool from_keyword_provider,
+    bool from_keyword,
     int relevance,
     bool relevance_from_server,
     const base::string16& input_text)
@@ -97,7 +97,7 @@
                     /*deletion_url=*/"",
                     /*image_dominant_color=*/"",
                     /*image_url=*/"",
-                    from_keyword_provider,
+                    from_keyword,
                     relevance,
                     relevance_from_server,
                     /*should_prefetch=*/false,
@@ -114,12 +114,12 @@
     const std::string& deletion_url,
     const std::string& image_dominant_color,
     const std::string& image_url,
-    bool from_keyword_provider,
+    bool from_keyword,
     int relevance,
     bool relevance_from_server,
     bool should_prefetch,
     const base::string16& input_text)
-    : Result(from_keyword_provider,
+    : Result(from_keyword,
              relevance,
              relevance_from_server,
              type,
@@ -194,7 +194,7 @@
 int SearchSuggestionParser::SuggestResult::CalculateRelevance(
     const AutocompleteInput& input,
     bool keyword_provider_requested) const {
-  if (!from_keyword_provider_ && keyword_provider_requested)
+  if (!from_keyword_ && keyword_provider_requested)
     return 100;
   return ((input.type() == metrics::OmniboxInputType::URL) ? 300 : 600);
 }
@@ -208,11 +208,11 @@
     int subtype_identifier,
     const base::string16& description,
     const std::string& deletion_url,
-    bool from_keyword_provider,
+    bool from_keyword,
     int relevance,
     bool relevance_from_server,
     const base::string16& input_text)
-    : Result(from_keyword_provider,
+    : Result(from_keyword,
              relevance,
              relevance_from_server,
              type,
@@ -285,7 +285,7 @@
 int SearchSuggestionParser::NavigationResult::CalculateRelevance(
     const AutocompleteInput& input,
     bool keyword_provider_requested) const {
-  return (from_keyword_provider_ || !keyword_provider_requested) ? 800 : 150;
+  return (from_keyword_ || !keyword_provider_requested) ? 800 : 150;
 }
 
 // SearchSuggestionParser::Results ---------------------------------------------
diff --git a/components/omnibox/browser/search_suggestion_parser.h b/components/omnibox/browser/search_suggestion_parser.h
index 75fbc63b..45d2040 100644
--- a/components/omnibox/browser/search_suggestion_parser.h
+++ b/components/omnibox/browser/search_suggestion_parser.h
@@ -42,7 +42,7 @@
   //           highly fragmented SearchProvider logic for each Result type.
   class Result {
    public:
-    Result(bool from_keyword_provider,
+    Result(bool from_keyword,
            int relevance,
            bool relevance_from_server,
            AutocompleteMatchType::Type type,
@@ -51,7 +51,7 @@
     Result(const Result& other);
     virtual ~Result();
 
-    bool from_keyword_provider() const { return from_keyword_provider_; }
+    bool from_keyword() const { return from_keyword_; }
 
     const base::string16& match_contents() const { return match_contents_; }
     const ACMatchClassifications& match_contents_class() const {
@@ -89,8 +89,8 @@
     base::string16 match_contents_;
     ACMatchClassifications match_contents_class_;
 
-    // True if the result came from the keyword provider.
-    bool from_keyword_provider_;
+    // True if the result came from a keyword suggestion.
+    bool from_keyword_;
 
     AutocompleteMatchType::Type type_;
 
@@ -127,7 +127,7 @@
     SuggestResult(const base::string16& suggestion,
                   AutocompleteMatchType::Type type,
                   int subtype_identifier,
-                  bool from_keyword_provider,
+                  bool from_keyword,
                   int relevance,
                   bool relevance_from_server,
                   const base::string16& input_text);
@@ -141,7 +141,7 @@
                   const std::string& deletion_url,
                   const std::string& image_dominant_color,
                   const std::string& image_url,
-                  bool from_keyword_provider,
+                  bool from_keyword,
                   int relevance,
                   bool relevance_from_server,
                   bool should_prefetch,
@@ -220,7 +220,7 @@
                      int subtype_identifier,
                      const base::string16& description,
                      const std::string& deletion_url,
-                     bool from_keyword_provider,
+                     bool from_keyword,
                      int relevance,
                      bool relevance_from_server,
                      const base::string16& input_text);
diff --git a/components/omnibox/browser/shortcuts_provider.cc b/components/omnibox/browser/shortcuts_provider.cc
index 6de34c0..837a5ba 100644
--- a/components/omnibox/browser/shortcuts_provider.cc
+++ b/components/omnibox/browser/shortcuts_provider.cc
@@ -33,6 +33,7 @@
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/url_prefix.h"
 #include "components/prefs/pref_service.h"
+#include "components/search_engines/template_url_service.h"
 #include "components/url_formatter/url_fixer.h"
 #include "third_party/metrics_proto/omnibox_input_type.pb.h"
 #include "url/third_party/mozilla/url_parse.h"
@@ -290,16 +291,26 @@
 
   DCHECK(is_search_type != match.keyword.empty());
 
+  const bool keyword_matches =
+      base::StartsWith(base::UTF16ToUTF8(input.text()),
+                       base::StrCat({base::UTF16ToUTF8(match.keyword), " "}),
+                       base::CompareCase::INSENSITIVE_ASCII);
+  if (is_search_type) {
+    match.from_keyword =
+        // Either the match is not from the default search provider:
+        match.keyword != client_->GetTemplateURLService()
+                             ->GetDefaultSearchProvider()
+                             ->keyword() ||
+        // Or it is, but keyword mode was invoked explicitly and the keyword
+        // in the input is also of the default search provider.
+        (input.prefer_keyword() && keyword_matches);
+  }
   // True if input is in keyword mode and the match is a URL suggestion or the
   // match has a different keyword.
-  bool would_cause_leaving_keyboard_mode =
-      input.prefer_keyword() &&
-      (!is_search_type ||
-       !base::StartsWith(base::UTF16ToUTF8(input.text()),
-                         base::StrCat({base::UTF16ToUTF8(match.keyword), " "}),
-                         base::CompareCase::INSENSITIVE_ASCII));
+  bool would_cause_leaving_keyword_mode =
+      input.prefer_keyword() && !(is_search_type && keyword_matches);
 
-  if (!would_cause_leaving_keyboard_mode) {
+  if (!would_cause_leaving_keyword_mode) {
     if (is_search_type) {
       if (match.fill_into_edit.size() >= input.text().size() &&
           std::equal(match.fill_into_edit.begin(),
diff --git a/components/omnibox/bug-triage.md b/components/omnibox/bug-triage.md
index 44f94028..f4d0e34 100644
--- a/components/omnibox/bug-triage.md
+++ b/components/omnibox/bug-triage.md
@@ -115,7 +115,7 @@
 policies](https://www.chromium.org/for-testers/bug-reporting-guidelines/triage-best-practices).
 *Priority-2* represents wanted for this release but can be punted for a release.
 *Priority-3* are bugs not time sensitive.  There is an even-lower-priority
-state; see the *NextAction=01/08/2019* below.
+state; see the *NextAction=01/07/2020* below.
 
 If you aren't sure of the scope, severity, or implications of an issue, prefer
 to assign it a higher priority (*1* or *2*) and try to assign it to someone
diff --git a/components/open_from_clipboard/BUILD.gn b/components/open_from_clipboard/BUILD.gn
index 73e360e..2f9fa810 100644
--- a/components/open_from_clipboard/BUILD.gn
+++ b/components/open_from_clipboard/BUILD.gn
@@ -40,6 +40,8 @@
   assert_no_deps = [ "//base:i18n" ]
   if (is_ios) {
     configs += [ "//build/config/compiler:enable_arc" ]
+
+    libs = [ "MobileCoreServices.framework" ]
   }
 }
 
@@ -53,6 +55,7 @@
   deps = [
     ":open_from_clipboard",
     "//base",
+    "//ui/gfx:gfx",
     "//url",
   ]
 }
diff --git a/components/open_from_clipboard/DEPS b/components/open_from_clipboard/DEPS
index d80a9dc..9433bfe 100644
--- a/components/open_from_clipboard/DEPS
+++ b/components/open_from_clipboard/DEPS
@@ -3,4 +3,5 @@
   "+net",
   "+ui/base/clipboard",
   "+ui/base/test",
+  "+ui/gfx/image",
 ]
diff --git a/components/open_from_clipboard/clipboard_recent_content.h b/components/open_from_clipboard/clipboard_recent_content.h
index 48063e8..11d25d18 100644
--- a/components/open_from_clipboard/clipboard_recent_content.h
+++ b/components/open_from_clipboard/clipboard_recent_content.h
@@ -10,6 +10,7 @@
 
 #include "base/macros.h"
 #include "base/time/time.h"
+#include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
 // Helper class returning an URL if the content of the clipboard can be turned
@@ -36,6 +37,10 @@
   // is recent enough and has not been suppressed.
   virtual base::Optional<base::string16> GetRecentTextFromClipboard() = 0;
 
+  // Returns clipboard content as image, if it has a compatible type,
+  // is recent enough and has not been suppressed.
+  virtual base::Optional<gfx::Image> GetRecentImageFromClipboard() = 0;
+
   // Returns how old the content of the clipboard is.
   virtual base::TimeDelta GetClipboardContentAge() const = 0;
 
diff --git a/components/open_from_clipboard/clipboard_recent_content_generic.cc b/components/open_from_clipboard/clipboard_recent_content_generic.cc
index 0877cbb..0ca6a01 100644
--- a/components/open_from_clipboard/clipboard_recent_content_generic.cc
+++ b/components/open_from_clipboard/clipboard_recent_content_generic.cc
@@ -68,6 +68,11 @@
   return base::nullopt;
 }
 
+base::Optional<gfx::Image>
+ClipboardRecentContentGeneric::GetRecentImageFromClipboard() {
+  return base::nullopt;
+}
+
 base::TimeDelta ClipboardRecentContentGeneric::GetClipboardContentAge() const {
   const base::Time last_modified_time =
       ui::Clipboard::GetForCurrentThread()->GetLastModifiedTime();
diff --git a/components/open_from_clipboard/clipboard_recent_content_generic.h b/components/open_from_clipboard/clipboard_recent_content_generic.h
index 38422d7..8005485 100644
--- a/components/open_from_clipboard/clipboard_recent_content_generic.h
+++ b/components/open_from_clipboard/clipboard_recent_content_generic.h
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "components/open_from_clipboard/clipboard_recent_content.h"
+#include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
 // An implementation of ClipboardRecentContent that uses
@@ -24,6 +25,7 @@
   // ClipboardRecentContent implementation.
   base::Optional<GURL> GetRecentURLFromClipboard() override;
   base::Optional<base::string16> GetRecentTextFromClipboard() override;
+  base::Optional<gfx::Image> GetRecentImageFromClipboard() override;
   base::TimeDelta GetClipboardContentAge() const override;
   void SuppressClipboardContent() override;
 
diff --git a/components/open_from_clipboard/clipboard_recent_content_impl_ios.h b/components/open_from_clipboard/clipboard_recent_content_impl_ios.h
index da54ba1..0cc789a 100644
--- a/components/open_from_clipboard/clipboard_recent_content_impl_ios.h
+++ b/components/open_from_clipboard/clipboard_recent_content_impl_ios.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_IMPL_IOS_H_
 
 #import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
 
 // A protocol implemented by delegates to handle clipboard changes.
 @protocol ClipboardRecentContentDelegate<NSObject>
@@ -39,6 +40,10 @@
 // not been suppresed. Otherwise, returns nil.
 - (NSString*)recentTextFromClipboard;
 
+// Returns the copied string if the clipboard contains a recent string that has
+// not been suppressed. Otherwise, returns nil.
+- (UIImage*)recentImageFromClipboard;
+
 // Returns how old the content of the clipboard is.
 - (NSTimeInterval)clipboardContentAge;
 
diff --git a/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm b/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm
index f8353ea..7043afd 100644
--- a/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm
+++ b/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm
@@ -5,6 +5,7 @@
 #import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h"
 
 #import <CommonCrypto/CommonDigest.h>
+#import <MobileCoreServices/MobileCoreServices.h>
 #import <UIKit/UIKit.h>
 
 #import "base/mac/foundation_util.h"
@@ -27,15 +28,38 @@
 // hash changed, the pasteboard content is considered to have changed.
 NSString* const kPasteboardEntryMD5Key = @"PasteboardEntryMD5";
 
-// Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|.
-// This value is used to detect pasteboard content change. Keeping only 4 bytes
-// is a privacy requirement to introduce collision and allow deniability of
-// having copied a given string.
-NSData* WeakMD5FromNSString(NSString* string) {
+// Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|,
+// |image_data|, and |url|. This value is used to detect pasteboard content
+// change. Keeping only 4 bytes is a privacy requirement to introduce collision
+// and allow deniability of having copied a given string, image, or url.
+//
+// |image_data| is passed in as NSData instead of UIImage because converting
+// UIImage to NSData can be slow for large images and getting NSData directly
+// from the pasteboard is quicker.
+NSData* WeakMD5FromPasteboardData(NSString* string,
+                                  NSData* image_data,
+                                  NSURL* url) {
+  CC_MD5_CTX ctx;
+  CC_MD5_Init(&ctx);
+
+  const std::string clipboard_string = base::SysNSStringToUTF8(string);
+  const char* c_string = clipboard_string.c_str();
+  CC_MD5_Update(&ctx, c_string, strlen(c_string));
+
+  // This hash is used only to tell if the image has changed, so
+  // limit the number of bytes to hash to prevent slowdown.
+  NSUInteger bytes_to_hash = fmin([image_data length], 1000000);
+  if (bytes_to_hash > 0) {
+    CC_MD5_Update(&ctx, [image_data bytes], bytes_to_hash);
+  }
+
+  const std::string url_string = base::SysNSStringToUTF8([url absoluteString]);
+  const char* url_c_string = url_string.c_str();
+  CC_MD5_Update(&ctx, url_c_string, strlen(url_c_string));
+
   unsigned char hash[CC_MD5_DIGEST_LENGTH];
-  const std::string clipboard = base::SysNSStringToUTF8(string);
-  const char* c_string = clipboard.c_str();
-  CC_MD5(c_string, strlen(c_string), hash);
+  CC_MD5_Final(hash, &ctx);
+
   NSData* data = [NSData dataWithBytes:hash length:4];
   return data;
 }
@@ -124,7 +148,11 @@
 
 - (NSData*)getCurrentMD5 {
   NSString* pasteboardString = [UIPasteboard generalPasteboard].string;
-  NSData* md5 = WeakMD5FromNSString(pasteboardString);
+  NSData* pasteboardImageData = [[UIPasteboard generalPasteboard]
+      dataForPasteboardType:(NSString*)kUTTypeImage];
+  NSURL* pasteboardURL = [UIPasteboard generalPasteboard].URL;
+  NSData* md5 = WeakMD5FromPasteboardData(pasteboardString, pasteboardImageData,
+                                          pasteboardURL);
 
   return md5;
 }
@@ -166,6 +194,14 @@
   return [UIPasteboard generalPasteboard].string;
 }
 
+- (UIImage*)recentImageFromClipboard {
+  [self updateIfNeeded];
+  if ([self clipboardContentAge] > self.maximumAgeOfClipboard) {
+    return nil;
+  }
+  return [UIPasteboard generalPasteboard].image;
+}
+
 - (NSTimeInterval)clipboardContentAge {
   return -[self.lastPasteboardChangeDate timeIntervalSinceNow];
 }
diff --git a/components/open_from_clipboard/clipboard_recent_content_ios.h b/components/open_from_clipboard/clipboard_recent_content_ios.h
index dadc619..ceb4d41 100644
--- a/components/open_from_clipboard/clipboard_recent_content_ios.h
+++ b/components/open_from_clipboard/clipboard_recent_content_ios.h
@@ -40,6 +40,7 @@
   // ClipboardRecentContent implementation.
   base::Optional<GURL> GetRecentURLFromClipboard() override;
   base::Optional<base::string16> GetRecentTextFromClipboard() override;
+  base::Optional<gfx::Image> GetRecentImageFromClipboard() override;
   base::TimeDelta GetClipboardContentAge() const override;
   void SuppressClipboardContent() override;
 
diff --git a/components/open_from_clipboard/clipboard_recent_content_ios.mm b/components/open_from_clipboard/clipboard_recent_content_ios.mm
index ac6d132..28273e6 100644
--- a/components/open_from_clipboard/clipboard_recent_content_ios.mm
+++ b/components/open_from_clipboard/clipboard_recent_content_ios.mm
@@ -89,6 +89,16 @@
   return base::SysNSStringToUTF16(text_from_pasteboard);
 }
 
+base::Optional<gfx::Image>
+ClipboardRecentContentIOS::GetRecentImageFromClipboard() {
+  UIImage* image_from_pasteboard = [implementation_ recentImageFromClipboard];
+  if (!image_from_pasteboard) {
+    return base::nullopt;
+  }
+
+  return gfx::Image(image_from_pasteboard);
+}
+
 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {}
 
 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
diff --git a/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm b/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm
index d11f6a9..96742db 100644
--- a/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm
+++ b/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm
@@ -17,12 +17,12 @@
 
 namespace {
 
-UIImage* TestUIImage() {
+UIImage* TestUIImage(UIColor* color = [UIColor redColor]) {
   CGRect frame = CGRectMake(0, 0, 1.0, 1.0);
   UIGraphicsBeginImageContext(frame.size);
 
   CGContextRef context = UIGraphicsGetCurrentContext();
-  CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
+  CGContextSetFillColorWithColor(context, color.CGColor);
   CGContextFillRect(context, frame);
 
   UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
@@ -156,6 +156,11 @@
   void VerifyClipboardTextDoesNotExist() {
     EXPECT_FALSE(clipboard_content_->GetRecentTextFromClipboard().has_value());
   }
+
+  void VerifyIfClipboardImageExists(bool exists) {
+    EXPECT_EQ(clipboard_content_->GetRecentImageFromClipboard().has_value(),
+              exists);
+  }
 };
 
 TEST_F(ClipboardRecentContentIOSTest, SchemeFiltering) {
@@ -205,7 +210,9 @@
   VerifyClipboardTextDoesNotExist();
 }
 
-TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboard) {
+// Checks that if the user suppresses content, no text will be returned,
+// and if the text changes, the new text will be returned again.
+TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboardText) {
   SetPasteboardContent(kRecognizedURL);
 
   // Test that recent pasteboard data is provided.
@@ -240,6 +247,38 @@
   VerifyClipboardTextExists(kRecognizedURL2);
 }
 
+// Checks that if the user suppresses content, no image will be returned,
+// and if the image changes, the new image will be returned again.
+TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboardImage) {
+  SetPasteboardImage(TestUIImage());
+
+  // Test that recent pasteboard data is provided.
+  VerifyIfClipboardImageExists(true);
+
+  // Suppress the content of the pasteboard.
+  clipboard_content_->SuppressClipboardContent();
+
+  // Check that the pasteboard content is suppressed.
+  VerifyIfClipboardImageExists(false);
+
+  // Create a new clipboard content to test persistence.
+  ResetClipboardRecentContent(kAppSpecificScheme,
+                              base::TimeDelta::FromDays(10));
+
+  // Check that the pasteboard content is still suppressed.
+  VerifyIfClipboardImageExists(false);
+
+  // Check that even if the device is restarted, pasteboard content is
+  // still suppressed.
+  SimulateDeviceRestart();
+  VerifyIfClipboardImageExists(false);
+
+  // Check that if the pasteboard changes, the new content is not
+  // supressed anymore.
+  SetPasteboardImage(TestUIImage([UIColor greenColor]));
+  VerifyIfClipboardImageExists(true);
+}
+
 // Checks that if user copies something other than a string we don't cache the
 // string in pasteboard.
 TEST_F(ClipboardRecentContentIOSTest, AddingNonStringRemovesCachedString) {
@@ -248,6 +287,8 @@
   // Test that recent pasteboard data is provided as text and url.
   VerifyClipboardURLExists(kRecognizedURL);
   VerifyClipboardTextExists(kRecognizedURL);
+  // Image pasteboard should be empty
+  VerifyIfClipboardImageExists(false);
 
   // Overwrite pasteboard with an image.
   SetPasteboardImage(TestUIImage());
@@ -255,9 +296,13 @@
   // Url and text pasteboard should appear empty.
   VerifyClipboardURLDoesNotExist();
   VerifyClipboardTextDoesNotExist();
+  // Image pasteboard should be full
+  VerifyIfClipboardImageExists(true);
 
   // Tests that if URL is added again, pasteboard provides it normally.
   SetPasteboardContent(kRecognizedURL);
   VerifyClipboardURLExists(kRecognizedURL);
   VerifyClipboardTextExists(kRecognizedURL);
+  // Image pasteboard should be empty
+  VerifyIfClipboardImageExists(false);
 }
diff --git a/components/open_from_clipboard/fake_clipboard_recent_content.cc b/components/open_from_clipboard/fake_clipboard_recent_content.cc
index 7a531ce4..f957c3d 100644
--- a/components/open_from_clipboard/fake_clipboard_recent_content.cc
+++ b/components/open_from_clipboard/fake_clipboard_recent_content.cc
@@ -24,6 +24,14 @@
   return clipboard_text_content_;
 }
 
+base::Optional<gfx::Image>
+FakeClipboardRecentContent::GetRecentImageFromClipboard() {
+  if (suppress_content_)
+    return base::nullopt;
+
+  return clipboard_image_content_;
+}
+
 base::TimeDelta FakeClipboardRecentContent::GetClipboardContentAge() const {
   return content_age_;
 }
@@ -37,6 +45,7 @@
   DCHECK(url.is_valid());
   clipboard_url_content_ = url;
   clipboard_text_content_ = base::nullopt;
+  clipboard_image_content_ = base::nullopt;
   content_age_ = content_age;
   suppress_content_ = false;
 }
@@ -45,6 +54,17 @@
                                                   base::TimeDelta content_age) {
   clipboard_url_content_ = base::nullopt;
   clipboard_text_content_ = text;
+  clipboard_image_content_ = base::nullopt;
+  content_age_ = content_age;
+  suppress_content_ = false;
+}
+
+void FakeClipboardRecentContent::SetClipboardImage(
+    const gfx::Image& image,
+    base::TimeDelta content_age) {
+  clipboard_url_content_ = base::nullopt;
+  clipboard_text_content_ = base::nullopt;
+  clipboard_image_content_ = image;
   content_age_ = content_age;
   suppress_content_ = false;
 }
diff --git a/components/open_from_clipboard/fake_clipboard_recent_content.h b/components/open_from_clipboard/fake_clipboard_recent_content.h
index c1170dc..61798399 100644
--- a/components/open_from_clipboard/fake_clipboard_recent_content.h
+++ b/components/open_from_clipboard/fake_clipboard_recent_content.h
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "components/open_from_clipboard/clipboard_recent_content.h"
+#include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
 // FakeClipboardRecentContent implements ClipboardRecentContent interface by
@@ -20,18 +21,22 @@
   // ClipboardRecentContent implementation.
   base::Optional<GURL> GetRecentURLFromClipboard() override;
   base::Optional<base::string16> GetRecentTextFromClipboard() override;
+  base::Optional<gfx::Image> GetRecentImageFromClipboard() override;
   base::TimeDelta GetClipboardContentAge() const override;
   void SuppressClipboardContent() override;
 
-  // Sets the URL and clipboard content age. This clears the text
+  // Sets the URL and clipboard content age. This clears the text and image.
   void SetClipboardURL(const GURL& url, base::TimeDelta content_age);
-  // Sets the text and clipboard content age. This clears the url.
+  // Sets the text and clipboard content age. This clears the URL and image.
   void SetClipboardText(const base::string16& text,
                         base::TimeDelta content_age);
+  // Sets the image and clipboard content age. This clears the URL and text.
+  void SetClipboardImage(const gfx::Image& image, base::TimeDelta content_age);
 
  private:
   base::Optional<GURL> clipboard_url_content_;
   base::Optional<base::string16> clipboard_text_content_;
+  base::Optional<gfx::Image> clipboard_image_content_;
   base::TimeDelta content_age_;
   bool suppress_content_;
 
diff --git a/components/password_manager/content/browser/bad_message.cc b/components/password_manager/content/browser/bad_message.cc
index b59280fa..8f7ad688 100644
--- a/components/password_manager/content/browser/bad_message.cc
+++ b/components/password_manager/content/browser/bad_message.cc
@@ -64,9 +64,7 @@
   return CheckChildProcessSecurityPolicyForURL(frame, password_form.origin,
                                                reason) &&
          CheckChildProcessSecurityPolicyForURL(
-             frame, GURL(password_form.signon_realm), reason) &&
-         CheckChildProcessSecurityPolicyForURL(
-             frame, password_form.form_data.origin, reason);
+             frame, GURL(password_form.signon_realm), reason);
 }
 
 bool CheckChildProcessSecurityPolicy(
diff --git a/components/printing/browser/BUILD.gn b/components/printing/browser/BUILD.gn
index 4857697..9e4b076d 100644
--- a/components/printing/browser/BUILD.gn
+++ b/components/printing/browser/BUILD.gn
@@ -12,6 +12,8 @@
     "print_manager.h",
     "print_manager_utils.cc",
     "print_manager_utils.h",
+    "printer_capabilities.cc",
+    "printer_capabilities.h",
   ]
 
   public_deps = [
@@ -20,10 +22,40 @@
 
   deps = [
     "//base",
+    "//components/crash/core/common",
     "//components/printing/common",
     "//components/services/pdf_compositor/public/interfaces",
+    "//components/strings:components_strings_grit",
     "//printing",
     "//printing/common:common",
     "//services/service_manager/public/cpp",
+    "//ui/base",
+    "//ui/gfx/geometry",
   ]
+
+  if (is_mac) {
+    sources += [
+      "printer_capabilities_mac.h",
+      "printer_capabilities_mac.mm",
+    ]
+  }
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "printer_capabilities_unittest.cc",
+  ]
+
+  deps = [
+    "//base",
+    "//components/printing/browser",
+    "//content/test:test_support",
+    "//printing:test_support",
+    "//testing/gtest",
+  ]
+
+  if (is_mac) {
+    sources += [ "printer_capabilities_mac_unittest.mm" ]
+  }
 }
diff --git a/components/printing/browser/DEPS b/components/printing/browser/DEPS
index 9adac87..a24bd0d5ad 100644
--- a/components/printing/browser/DEPS
+++ b/components/printing/browser/DEPS
@@ -1,7 +1,16 @@
 include_rules = [
+  "+components/crash/core/common",
   "+components/services/pdf_compositor/public/cpp",
   "+components/services/pdf_compositor/public/interfaces",
+  "+components/strings/grit",
   "+content/public/browser",
   "+mojo/public/cpp/system",
   "+services/service_manager/public/cpp",
+  "+ui/base/l10n",
 ]
+
+specific_include_rules = {
+  "printer_capabilities_unittest.cc": [
+    "+content/public/test",
+  ],
+}
diff --git a/components/printing/common/printer_capabilities.cc b/components/printing/browser/printer_capabilities.cc
similarity index 98%
rename from components/printing/common/printer_capabilities.cc
rename to components/printing/browser/printer_capabilities.cc
index 018578b..382db0a 100644
--- a/components/printing/common/printer_capabilities.cc
+++ b/components/printing/browser/printer_capabilities.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 "components/printing/common/printer_capabilities.h"
+#include "components/printing/browser/printer_capabilities.h"
 
 #include <memory>
 #include <string>
diff --git a/components/printing/common/printer_capabilities.h b/components/printing/browser/printer_capabilities.h
similarity index 87%
rename from components/printing/common/printer_capabilities.h
rename to components/printing/browser/printer_capabilities.h
index d02e65f6..698af12 100644
--- a/components/printing/common/printer_capabilities.h
+++ b/components/printing/browser/printer_capabilities.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 COMPONENTS_PRINTING_COMMON_PRINTER_CAPABILITIES_H_
-#define COMPONENTS_PRINTING_COMMON_PRINTER_CAPABILITIES_H_
+#ifndef COMPONENTS_PRINTING_BROWSER_PRINTER_CAPABILITIES_H_
+#define COMPONENTS_PRINTING_BROWSER_PRINTER_CAPABILITIES_H_
 
 #include <memory>
 #include <string>
@@ -39,4 +39,4 @@
 
 }  // namespace printing
 
-#endif  // COMPONENTS_PRINTING_COMMON_PRINTER_CAPABILITIES_H_
+#endif  // COMPONENTS_PRINTING_BROWSER_PRINTER_CAPABILITIES_H_
diff --git a/components/printing/common/printer_capabilities_mac.h b/components/printing/browser/printer_capabilities_mac.h
similarity index 74%
rename from components/printing/common/printer_capabilities_mac.h
rename to components/printing/browser/printer_capabilities_mac.h
index e391bfc..eb37014 100644
--- a/components/printing/common/printer_capabilities_mac.h
+++ b/components/printing/browser/printer_capabilities_mac.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 COMPONENTS_PRINTING_COMMON_PRINTER_CAPABILITIES_MAC_H_
-#define COMPONENTS_PRINTING_COMMON_PRINTER_CAPABILITIES_MAC_H_
+#ifndef COMPONENTS_PRINTING_BROWSER_PRINTER_CAPABILITIES_MAC_H_
+#define COMPONENTS_PRINTING_BROWSER_PRINTER_CAPABILITIES_MAC_H_
 
 #include "printing/backend/print_backend.h"
 
@@ -25,4 +25,4 @@
 
 }  // namespace printing
 
-#endif  // COMPONENTS_PRINTING_COMMON_PRINTER_CAPABILITIES_MAC_H_
+#endif  // COMPONENTS_PRINTING_BROWSER_PRINTER_CAPABILITIES_MAC_H_
diff --git a/components/printing/common/printer_capabilities_mac.mm b/components/printing/browser/printer_capabilities_mac.mm
similarity index 97%
rename from components/printing/common/printer_capabilities_mac.mm
rename to components/printing/browser/printer_capabilities_mac.mm
index 2dc3429..3d790c3 100644
--- a/components/printing/common/printer_capabilities_mac.mm
+++ b/components/printing/browser/printer_capabilities_mac.mm
@@ -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 "components/printing/common/printer_capabilities_mac.h"
+#include "components/printing/browser/printer_capabilities_mac.h"
 
 #import <AppKit/AppKit.h>
 
diff --git a/components/printing/common/printer_capabilities_mac_unittest.mm b/components/printing/browser/printer_capabilities_mac_unittest.mm
similarity index 98%
rename from components/printing/common/printer_capabilities_mac_unittest.mm
rename to components/printing/browser/printer_capabilities_mac_unittest.mm
index af020f30..5dfd716d 100644
--- a/components/printing/common/printer_capabilities_mac_unittest.mm
+++ b/components/printing/browser/printer_capabilities_mac_unittest.mm
@@ -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 "components/printing/common/printer_capabilities_mac.h"
+#include "components/printing/browser/printer_capabilities_mac.h"
 
 #include "base/files/scoped_temp_dir.h"
 #include "base/mac/foundation_util.h"
diff --git a/components/printing/common/printer_capabilities_unittest.cc b/components/printing/browser/printer_capabilities_unittest.cc
similarity index 98%
rename from components/printing/common/printer_capabilities_unittest.cc
rename to components/printing/browser/printer_capabilities_unittest.cc
index 931d275..ec5b23a 100644
--- a/components/printing/common/printer_capabilities_unittest.cc
+++ b/components/printing/browser/printer_capabilities_unittest.cc
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "components/printing/browser/printer_capabilities.h"
+
 #include <memory>
 
 #include "base/bind.h"
 #include "base/memory/ref_counted.h"
 #include "base/stl_util.h"
 #include "base/values.h"
-#include "components/printing/common/printer_capabilities.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "printing/backend/test_print_backend.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/printing/common/BUILD.gn b/components/printing/common/BUILD.gn
index f0dbe71..40da037 100644
--- a/components/printing/common/BUILD.gn
+++ b/components/printing/common/BUILD.gn
@@ -8,51 +8,19 @@
     "cloud_print_cdd_conversion.h",
     "print_messages.cc",
     "print_messages.h",
-    "printer_capabilities.cc",
-    "printer_capabilities.h",
     "printing_param_traits_macros.h",
   ]
 
   deps = [
     "//base",
     "//components/cloud_devices/common:common",
-    "//components/crash/core/common",
-    "//components/strings:components_strings_grit",
     "//ipc",
     "//printing",
     "//printing/common:common",
     "//third_party/blink/public:blink_headers",
-    "//ui/base",
-    "//ui/gfx",
     "//ui/gfx/geometry",
     "//ui/gfx/ipc",
     "//ui/gfx/ipc/geometry",
     "//ui/gfx/ipc/skia",
   ]
-
-  if (is_mac) {
-    sources += [
-      "printer_capabilities_mac.h",
-      "printer_capabilities_mac.mm",
-    ]
-  }
-}
-
-source_set("unit_tests") {
-  testonly = true
-  sources = [
-    "printer_capabilities_unittest.cc",
-  ]
-
-  deps = [
-    ":common",
-    "//base",
-    "//content/test:test_support",
-    "//printing:test_support",
-    "//testing/gtest",
-  ]
-
-  if (is_mac) {
-    sources += [ "printer_capabilities_mac_unittest.mm" ]
-  }
 }
diff --git a/components/printing/common/DEPS b/components/printing/common/DEPS
deleted file mode 100644
index 245e3ade..0000000
--- a/components/printing/common/DEPS
+++ /dev/null
@@ -1,11 +0,0 @@
-include_rules = [
-  "+components/crash/core/common",
-  "+components/strings/grit",
-  "+ui/base/l10n",
-]
-
-specific_include_rules = {
-  "printer_capabilities_unittest.cc": [
-    "+content/public/test",
-  ],
-}
diff --git a/components/safe_browsing/android/DEPS b/components/safe_browsing/android/DEPS
index 1640ef3..f56bb485 100644
--- a/components/safe_browsing/android/DEPS
+++ b/components/safe_browsing/android/DEPS
@@ -2,4 +2,4 @@
   "+components/variations",
   "+content/public/test",
   "+jni",
-]
\ No newline at end of file
+]
diff --git a/components/safe_browsing/browser/threat_details.cc b/components/safe_browsing/browser/threat_details.cc
index 3dca6dc..85f7a04 100644
--- a/components/safe_browsing/browser/threat_details.cc
+++ b/components/safe_browsing/browser/threat_details.cc
@@ -101,8 +101,19 @@
       return ClientSafeBrowsingReportRequest::URL_SUSPICIOUS;
     case SB_THREAT_TYPE_BILLING:
       return ClientSafeBrowsingReportRequest::BILLING;
-    default:  // Gated by SafeBrowsingBlockingPage::ShouldReportThreatDetails.
-      NOTREACHED() << "We should not send report for threat type "
+    case SB_THREAT_TYPE_APK_DOWNLOAD:
+      return ClientSafeBrowsingReportRequest::APK_DOWNLOAD;
+    case SB_THREAT_TYPE_UNUSED:
+    case SB_THREAT_TYPE_SAFE:
+    case SB_THREAT_TYPE_URL_BINARY_MALWARE:
+    case SB_THREAT_TYPE_EXTENSION:
+    case SB_THREAT_TYPE_BLACKLISTED_RESOURCE:
+    case SB_THREAT_TYPE_API_ABUSE:
+    case SB_THREAT_TYPE_SUBRESOURCE_FILTER:
+    case SB_THREAT_TYPE_CSD_WHITELIST:
+    case DEPRECATED_SB_THREAT_TYPE_URL_PASSWORD_PROTECTION_PHISHING:
+      // Gated by SafeBrowsingBlockingPage::ShouldReportThreatDetails.
+      NOTREACHED() << "We should not send report for threat type: "
                    << threat_type;
       return ClientSafeBrowsingReportRequest::UNKNOWN;
   }
@@ -832,7 +843,8 @@
     return;
 
   if (!report_ ||
-      report_->type() != ClientSafeBrowsingReportRequest::URL_SUSPICIOUS) {
+      (report_->type() != ClientSafeBrowsingReportRequest::URL_SUSPICIOUS &&
+       report_->type() != ClientSafeBrowsingReportRequest::APK_DOWNLOAD)) {
     return;
   }
 
diff --git a/components/safe_browsing/db/v4_protocol_manager_util.h b/components/safe_browsing/db/v4_protocol_manager_util.h
index 54dbb8e..d2b74ad 100644
--- a/components/safe_browsing/db/v4_protocol_manager_util.h
+++ b/components/safe_browsing/db/v4_protocol_manager_util.h
@@ -151,11 +151,14 @@
   // The page loaded a resource from the Suspicious Site list.
   SB_THREAT_TYPE_SUSPICIOUS_SITE,
 
-  // Enterprise password reuse detected on low reputation page,
+  // Enterprise password reuse detected on low reputation page.
   SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE,
 
   // Potential billing detected.
   SB_THREAT_TYPE_BILLING,
+
+  // Off-market APK file downloaded, which could be potentially dangerous.
+  SB_THREAT_TYPE_APK_DOWNLOAD,
 };
 
 using SBThreatTypeSet = base::flat_set<SBThreatType>;
diff --git a/components/safe_browsing/features.cc b/components/safe_browsing/features.cc
index bdf60837..acb3525 100644
--- a/components/safe_browsing/features.cc
+++ b/components/safe_browsing/features.cc
@@ -45,6 +45,9 @@
 const base::Feature kSuspiciousSiteTriggerQuotaFeature{
     "SafeBrowsingSuspiciousSiteTriggerQuota", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kTelemetryForApkDownloads{
+    "SafeBrowsingTelemetryForApkDownloads", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kThreatDomDetailsTagAndAttributeFeature{
     "ThreatDomDetailsTagAttributes", base::FEATURE_DISABLED_BY_DEFAULT};
 
@@ -73,6 +76,7 @@
     {&kCommittedSBInterstitials, true},
     {&kForceEnableResetPasswordWebUI, true},
     {&kSuspiciousSiteTriggerQuotaFeature, true},
+    {&kTelemetryForApkDownloads, true},
     {&kThreatDomDetailsTagAndAttributeFeature, false},
     {&kTriggerThrottlerDailyQuotaFeature, false},
     {&kUseLocalBlacklistsV2, true},
diff --git a/components/safe_browsing/features.h b/components/safe_browsing/features.h
index ac5cd1c..2e177b6 100644
--- a/components/safe_browsing/features.h
+++ b/components/safe_browsing/features.h
@@ -37,6 +37,10 @@
 // Controls the daily quota for the suspicious site trigger.
 extern const base::Feature kSuspiciousSiteTriggerQuotaFeature;
 
+// Controls whether we collect and send the referrer chain and other information
+// for APK downloads on Android.
+extern const base::Feature kTelemetryForApkDownloads;
+
 // Specifies which non-resource HTML Elements to collect based on their tag and
 // attributes. It's a single param containing a comma-separated list of pairs.
 // For example: "tag1,id,tag1,height,tag2,foo" - this will collect elements with
diff --git a/components/safe_browsing/proto/csd.proto b/components/safe_browsing/proto/csd.proto
index a7f657a..6a8ed9a 100644
--- a/components/safe_browsing/proto/csd.proto
+++ b/components/safe_browsing/proto/csd.proto
@@ -1064,6 +1064,7 @@
     AD_SAMPLE = 14;
     URL_SUSPICIOUS = 15;
     BILLING = 16;
+    APK_DOWNLOAD = 17;
   }
 
   message HTTPHeader {
diff --git a/components/safe_browsing/triggers/trigger_manager.cc b/components/safe_browsing/triggers/trigger_manager.cc
index 8884318f..b6a088d 100644
--- a/components/safe_browsing/triggers/trigger_manager.cc
+++ b/components/safe_browsing/triggers/trigger_manager.cc
@@ -38,6 +38,10 @@
       // Suspicious site collection happens in the background so the user must
       // already be opted in before the trigger is allowed to run.
       return true;
+    case TriggerType::APK_DOWNLOAD:
+      // APK download collection happens in the background so the user must
+      // already be opted in before the trigger is allowed to run.
+      return true;
   }
   // By default, require opt-in for all triggers.
   return true;
diff --git a/components/safe_browsing/triggers/trigger_throttler.cc b/components/safe_browsing/triggers/trigger_throttler.cc
index 4d7de8d..49458e3 100644
--- a/components/safe_browsing/triggers/trigger_throttler.cc
+++ b/components/safe_browsing/triggers/trigger_throttler.cc
@@ -245,6 +245,7 @@
   switch (trigger_type) {
     case TriggerType::SECURITY_INTERSTITIAL:
     case TriggerType::GAIA_PASSWORD_REUSE:
+    case TriggerType::APK_DOWNLOAD:
       return kUnlimitedTriggerQuota;
     case TriggerType::AD_SAMPLE:
       // Ad Samples have a non-zero default quota, but it can be overwritten
diff --git a/components/safe_browsing/triggers/trigger_throttler.h b/components/safe_browsing/triggers/trigger_throttler.h
index dc0c816d..321fc8b5 100644
--- a/components/safe_browsing/triggers/trigger_throttler.h
+++ b/components/safe_browsing/triggers/trigger_throttler.h
@@ -39,8 +39,9 @@
   AD_SAMPLE = 2,
   GAIA_PASSWORD_REUSE = 3,
   SUSPICIOUS_SITE = 4,
+  APK_DOWNLOAD = 5,
   kMinTriggerType = SECURITY_INTERSTITIAL,
-  kMaxTriggerType = SUSPICIOUS_SITE,
+  kMaxTriggerType = APK_DOWNLOAD,
 };
 
 struct TriggerTypeHash {
diff --git a/components/ui_devtools/viz_views/BUILD.gn b/components/ui_devtools/viz/BUILD.gn
similarity index 94%
rename from components/ui_devtools/viz_views/BUILD.gn
rename to components/ui_devtools/viz/BUILD.gn
index e58f3ff..235b1d6a 100644
--- a/components/ui_devtools/viz_views/BUILD.gn
+++ b/components/ui_devtools/viz/BUILD.gn
@@ -4,7 +4,7 @@
 
 import("//components/viz/viz.gni")
 
-source_set("viz_views") {
+source_set("viz") {
   sources = [
     "dom_agent_viz.cc",
     "dom_agent_viz.h",
@@ -30,7 +30,7 @@
   ]
 
   deps = [
-    ":viz_views",
+    ":viz",
     "//components/ui_devtools",
     "//components/ui_devtools:test_support",
     "//components/viz/service",
diff --git a/components/ui_devtools/viz_views/DEPS b/components/ui_devtools/viz/DEPS
similarity index 100%
rename from components/ui_devtools/viz_views/DEPS
rename to components/ui_devtools/viz/DEPS
diff --git a/components/ui_devtools/viz/OWNERS b/components/ui_devtools/viz/OWNERS
new file mode 100644
index 0000000..65aaa31
--- /dev/null
+++ b/components/ui_devtools/viz/OWNERS
@@ -0,0 +1 @@
+kylechar@chromium.org
diff --git a/components/ui_devtools/viz_views/dom_agent_viz.cc b/components/ui_devtools/viz/dom_agent_viz.cc
similarity index 98%
rename from components/ui_devtools/viz_views/dom_agent_viz.cc
rename to components/ui_devtools/viz/dom_agent_viz.cc
index 4a5c59a94..7605220 100644
--- a/components/ui_devtools/viz_views/dom_agent_viz.cc
+++ b/components/ui_devtools/viz/dom_agent_viz.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/ui_devtools/viz_views/dom_agent_viz.h"
+#include "components/ui_devtools/viz/dom_agent_viz.h"
 
 #include "base/stl_util.h"
 #include "components/ui_devtools/root_element.h"
 #include "components/ui_devtools/ui_element.h"
-#include "components/ui_devtools/viz_views/frame_sink_element.h"
-#include "components/ui_devtools/viz_views/surface_element.h"
+#include "components/ui_devtools/viz/frame_sink_element.h"
+#include "components/ui_devtools/viz/surface_element.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/surface_id.h"
diff --git a/components/ui_devtools/viz_views/dom_agent_viz.h b/components/ui_devtools/viz/dom_agent_viz.h
similarity index 96%
rename from components/ui_devtools/viz_views/dom_agent_viz.h
rename to components/ui_devtools/viz/dom_agent_viz.h
index 8cf00b16..d5618f80 100644
--- a/components/ui_devtools/viz_views/dom_agent_viz.h
+++ b/components/ui_devtools/viz/dom_agent_viz.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 COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_DOM_AGENT_VIZ_H_
-#define COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_DOM_AGENT_VIZ_H_
+#ifndef COMPONENTS_UI_DEVTOOLS_VIZ_DOM_AGENT_VIZ_H_
+#define COMPONENTS_UI_DEVTOOLS_VIZ_DOM_AGENT_VIZ_H_
 
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
@@ -20,7 +20,7 @@
 class SurfaceId;
 class SurfaceInfo;
 class SurfaceManager;
-}
+}  // namespace viz
 
 namespace ui_devtools {
 class FrameSinkElement;
@@ -141,4 +141,4 @@
 
 }  // namespace ui_devtools
 
-#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_DOM_AGENT_VIZ_H_
+#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_DOM_AGENT_VIZ_H_
diff --git a/components/ui_devtools/viz_views/frame_sink_element.cc b/components/ui_devtools/viz/frame_sink_element.cc
similarity index 98%
rename from components/ui_devtools/viz_views/frame_sink_element.cc
rename to components/ui_devtools/viz/frame_sink_element.cc
index 6b75b3da0..697ded6a 100644
--- a/components/ui_devtools/viz_views/frame_sink_element.cc
+++ b/components/ui_devtools/viz/frame_sink_element.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 "components/ui_devtools/viz_views/frame_sink_element.h"
+#include "components/ui_devtools/viz/frame_sink_element.h"
 
 #include "base/strings/string_piece.h"
 #include "components/ui_devtools/Protocol.h"
diff --git a/components/ui_devtools/viz_views/frame_sink_element.h b/components/ui_devtools/viz/frame_sink_element.h
similarity index 91%
rename from components/ui_devtools/viz_views/frame_sink_element.h
rename to components/ui_devtools/viz/frame_sink_element.h
index 76a52f0..70dc5235c 100644
--- a/components/ui_devtools/viz_views/frame_sink_element.h
+++ b/components/ui_devtools/viz/frame_sink_element.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 COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_FRAME_SINK_ELEMENT_H_
-#define COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_FRAME_SINK_ELEMENT_H_
+#ifndef COMPONENTS_UI_DEVTOOLS_VIZ_FRAME_SINK_ELEMENT_H_
+#define COMPONENTS_UI_DEVTOOLS_VIZ_FRAME_SINK_ELEMENT_H_
 
 #include "base/macros.h"
 #include "components/ui_devtools/ui_element.h"
@@ -64,4 +64,4 @@
 
 }  // namespace ui_devtools
 
-#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_FRAME_SINK_ELEMENT_H_
+#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_FRAME_SINK_ELEMENT_H_
diff --git a/components/ui_devtools/viz_views/overlay_agent_viz.cc b/components/ui_devtools/viz/overlay_agent_viz.cc
similarity index 92%
rename from components/ui_devtools/viz_views/overlay_agent_viz.cc
rename to components/ui_devtools/viz/overlay_agent_viz.cc
index 058db6cf..7501924 100644
--- a/components/ui_devtools/viz_views/overlay_agent_viz.cc
+++ b/components/ui_devtools/viz/overlay_agent_viz.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 "components/ui_devtools/viz_views/overlay_agent_viz.h"
+#include "components/ui_devtools/viz/overlay_agent_viz.h"
 
 namespace ui_devtools {
 
diff --git a/components/ui_devtools/viz_views/overlay_agent_viz.h b/components/ui_devtools/viz/overlay_agent_viz.h
similarity index 78%
rename from components/ui_devtools/viz_views/overlay_agent_viz.h
rename to components/ui_devtools/viz/overlay_agent_viz.h
index ef4dcdcc..c71652c6 100644
--- a/components/ui_devtools/viz_views/overlay_agent_viz.h
+++ b/components/ui_devtools/viz/overlay_agent_viz.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_OVERLAY_AGENT_VIZ_H_
-#define COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_OVERLAY_AGENT_VIZ_H_
+#ifndef COMPONENTS_UI_DEVTOOLS_VIZ_OVERLAY_AGENT_VIZ_H_
+#define COMPONENTS_UI_DEVTOOLS_VIZ_OVERLAY_AGENT_VIZ_H_
 
 #include "components/ui_devtools/Overlay.h"
 #include "components/ui_devtools/overlay_agent.h"
-#include "components/ui_devtools/viz_views/dom_agent_viz.h"
+#include "components/ui_devtools/viz/dom_agent_viz.h"
 
 namespace ui_devtools {
 
@@ -32,4 +32,4 @@
 
 }  // namespace ui_devtools
 
-#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_OVERLAY_AGENT_VIZ_H_
+#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_OVERLAY_AGENT_VIZ_H_
diff --git a/components/ui_devtools/viz_views/surface_element.cc b/components/ui_devtools/viz/surface_element.cc
similarity index 97%
rename from components/ui_devtools/viz_views/surface_element.cc
rename to components/ui_devtools/viz/surface_element.cc
index 56ac892..9c46fa9 100644
--- a/components/ui_devtools/viz_views/surface_element.cc
+++ b/components/ui_devtools/viz/surface_element.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 "components/ui_devtools/viz_views/surface_element.h"
+#include "components/ui_devtools/viz/surface_element.h"
 
 #include "components/ui_devtools/Protocol.h"
 #include "components/ui_devtools/ui_element_delegate.h"
diff --git a/components/ui_devtools/viz_views/surface_element.h b/components/ui_devtools/viz/surface_element.h
similarity index 88%
rename from components/ui_devtools/viz_views/surface_element.h
rename to components/ui_devtools/viz/surface_element.h
index 71b75c2d..eed60c7 100644
--- a/components/ui_devtools/viz_views/surface_element.h
+++ b/components/ui_devtools/viz/surface_element.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 COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_SURFACE_ELEMENT_H_
-#define COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_SURFACE_ELEMENT_H_
+#ifndef COMPONENTS_UI_DEVTOOLS_VIZ_SURFACE_ELEMENT_H_
+#define COMPONENTS_UI_DEVTOOLS_VIZ_SURFACE_ELEMENT_H_
 
 #include "base/macros.h"
 #include "components/ui_devtools/ui_element.h"
@@ -47,4 +47,4 @@
 
 }  // namespace ui_devtools
 
-#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_VIEWS_SURFACE_ELEMENT_H_
+#endif  // COMPONENTS_UI_DEVTOOLS_VIZ_SURFACE_ELEMENT_H_
diff --git a/components/ui_devtools/viz_views/viz_devtools_unittest.cc b/components/ui_devtools/viz/viz_devtools_unittest.cc
similarity index 98%
rename from components/ui_devtools/viz_views/viz_devtools_unittest.cc
rename to components/ui_devtools/viz/viz_devtools_unittest.cc
index c51e7777..849168f2 100644
--- a/components/ui_devtools/viz_views/viz_devtools_unittest.cc
+++ b/components/ui_devtools/viz/viz_devtools_unittest.cc
@@ -5,10 +5,10 @@
 #include "components/ui_devtools/css_agent.h"
 #include "components/ui_devtools/ui_devtools_unittest_utils.h"
 #include "components/ui_devtools/ui_element.h"
-#include "components/ui_devtools/viz_views/dom_agent_viz.h"
-#include "components/ui_devtools/viz_views/frame_sink_element.h"
-#include "components/ui_devtools/viz_views/overlay_agent_viz.h"
-#include "components/ui_devtools/viz_views/surface_element.h"
+#include "components/ui_devtools/viz/dom_agent_viz.h"
+#include "components/ui_devtools/viz/frame_sink_element.h"
+#include "components/ui_devtools/viz/overlay_agent_viz.h"
+#include "components/ui_devtools/viz/surface_element.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/surface_id.h"
 #include "components/viz/common/surfaces/surface_info.h"
diff --git a/components/url_formatter/top_domains/BUILD.gn b/components/url_formatter/top_domains/BUILD.gn
index 4abbbec0..5edcf0d5 100644
--- a/components/url_formatter/top_domains/BUILD.gn
+++ b/components/url_formatter/top_domains/BUILD.gn
@@ -93,6 +93,17 @@
   ]
 }
 
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "top_domain_util_unittest.cc",
+  ]
+  deps = [
+    ":common",
+    "//testing/gtest",
+  ]
+}
+
 # TODO(crbug/915921): Combine this and the previous one into a
 # compiled_action_foreach target.
 compiled_action("generate_top_domains_for_edit_distance") {
diff --git a/components/url_formatter/top_domains/top_domain_util.cc b/components/url_formatter/top_domains/top_domain_util.cc
index e1099e7..68c74018 100644
--- a/components/url_formatter/top_domains/top_domain_util.cc
+++ b/components/url_formatter/top_domains/top_domain_util.cc
@@ -15,26 +15,21 @@
 // comparison. Shorter domains are ignored.
 const size_t kMinLengthForEditDistance = 5u;
 
-// Returns the portion of hostname without the registry part.
-// E.g. For hostname = "google.com", the registry is "com", and the return value
-// will be 6 (length of "google").
-size_t GetWithoutRegistryLength(const std::string& hostname) {
+}  // namespace
+
+std::string HostnameWithoutRegistry(const std::string& hostname) {
+  DCHECK(!hostname.empty());
   const size_t registry_size =
       net::registry_controlled_domains::PermissiveGetHostRegistryLength(
           hostname.c_str(),
           net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
           net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
-  if (registry_size == 0) {
-    return hostname.size();
-  }
-  DCHECK_LE(registry_size, hostname.size() - 1);
-  return hostname.size() - registry_size - 1;
+  return hostname.substr(0, hostname.size() - registry_size);
 }
 
-}  // namespace
-
 bool IsEditDistanceCandidate(const std::string& hostname) {
-  return GetWithoutRegistryLength(hostname) >= kMinLengthForEditDistance;
+  return !hostname.empty() &&
+         HostnameWithoutRegistry(hostname).size() >= kMinLengthForEditDistance;
 }
 
 }  // namespace top_domains
diff --git a/components/url_formatter/top_domains/top_domain_util.h b/components/url_formatter/top_domains/top_domain_util.h
index f9774ce..33818581 100644
--- a/components/url_formatter/top_domains/top_domain_util.h
+++ b/components/url_formatter/top_domains/top_domain_util.h
@@ -15,6 +15,14 @@
 // comparison. Will generally return false for short domain names.
 bool IsEditDistanceCandidate(const std::string& hostname);
 
+// Returns the portion of hostname without the registry part.
+// E.g. For hostname = "google.com", the registry is "com", and the return value
+// will be "google.". Note that the return value contains a trailing dot. This
+// doesn't affect the result when comparing two different domains excluding
+// their registries (e.g. when checking google.com.tr and google.com.tw likely
+// belong to the same organization).
+std::string HostnameWithoutRegistry(const std::string& hostname);
+
 }  // namespace top_domains
 
 }  // namespace url_formatter
diff --git a/components/url_formatter/top_domains/top_domain_util_unittest.cc b/components/url_formatter/top_domains/top_domain_util_unittest.cc
new file mode 100644
index 0000000..65e2edc1
--- /dev/null
+++ b/components/url_formatter/top_domains/top_domain_util_unittest.cc
@@ -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.
+
+#include "components/url_formatter/top_domains/top_domain_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using url_formatter::top_domains::HostnameWithoutRegistry;
+using url_formatter::top_domains::IsEditDistanceCandidate;
+
+TEST(TopDomainUtilTest, IsEditDistanceCandidate) {
+  EXPECT_FALSE(IsEditDistanceCandidate(""));
+  EXPECT_TRUE(IsEditDistanceCandidate("google.com"));
+  // Domain label ("abc") is too short, even though the whole string is long
+  // enough.
+  EXPECT_FALSE(IsEditDistanceCandidate("abc.com.tr"));
+}
+
+TEST(TopDomainUtilTest, HostnameWithoutRegistry) {
+  EXPECT_EQ("google", HostnameWithoutRegistry("google"));
+  EXPECT_EQ("google.", HostnameWithoutRegistry("google."));
+  EXPECT_EQ("google..", HostnameWithoutRegistry("google.."));
+  EXPECT_EQ("google.", HostnameWithoutRegistry("google.com"));
+  EXPECT_EQ("google.", HostnameWithoutRegistry("google.com.tr"));
+  // blogspot.com is a private registry.
+  EXPECT_EQ("blogspot.", HostnameWithoutRegistry("blogspot.com"));
+}
diff --git a/components/viz/client/frame_eviction_manager.cc b/components/viz/client/frame_eviction_manager.cc
index dc8c3f8..86c3ed6 100644
--- a/components/viz/client/frame_eviction_manager.cc
+++ b/components/viz/client/frame_eviction_manager.cc
@@ -96,8 +96,8 @@
 
 FrameEvictionManager::FrameEvictionManager()
     : memory_pressure_listener_(new base::MemoryPressureListener(
-          base::Bind(&FrameEvictionManager::OnMemoryPressure,
-                     base::Unretained(this)))) {
+          base::BindRepeating(&FrameEvictionManager::OnMemoryPressure,
+                              base::Unretained(this)))) {
   max_number_of_saved_frames_ =
 #if defined(OS_ANDROID)
       // If the amount of memory on the device is >= 3.5 GB, save up to 5
diff --git a/components/viz/common/frame_sinks/delay_based_time_source.cc b/components/viz/common/frame_sinks/delay_based_time_source.cc
index eb34c49..ddab391b 100644
--- a/components/viz/common/frame_sinks/delay_based_time_source.cc
+++ b/components/viz/common/frame_sinks/delay_based_time_source.cc
@@ -155,8 +155,8 @@
       next_tick_time_ += interval_;
     DCHECK_GT(next_tick_time_, now);
   }
-  tick_closure_.Reset(base::Bind(&DelayBasedTimeSource::OnTimerTick,
-                                 weak_factory_.GetWeakPtr()));
+  tick_closure_.Reset(base::BindOnce(&DelayBasedTimeSource::OnTimerTick,
+                                     weak_factory_.GetWeakPtr()));
   task_runner_->PostDelayedTask(FROM_HERE, tick_closure_.callback(),
                                 next_tick_time_ - now);
 }
diff --git a/components/viz/common/frame_sinks/delay_based_time_source.h b/components/viz/common/frame_sinks/delay_based_time_source.h
index 7d58a2e..322ad3a 100644
--- a/components/viz/common/frame_sinks/delay_based_time_source.h
+++ b/components/viz/common/frame_sinks/delay_based_time_source.h
@@ -76,7 +76,7 @@
   base::TimeTicks last_tick_time_;
   base::TimeTicks next_tick_time_;
 
-  base::CancelableClosure tick_closure_;
+  base::CancelableOnceClosure tick_closure_;
 
   base::SingleThreadTaskRunner* task_runner_;
 
diff --git a/components/viz/common/yuv_readback_unittest.cc b/components/viz/common/yuv_readback_unittest.cc
index a780291..fa3ae47d 100644
--- a/components/viz/common/yuv_readback_unittest.cc
+++ b/components/viz/common/yuv_readback_unittest.cc
@@ -74,7 +74,7 @@
   }
 
   static void TraceDataCB(
-      const base::Callback<void()>& callback,
+      base::OnceClosure quit_closure,
       std::string* output,
       const scoped_refptr<base::RefCountedString>& json_events_str,
       bool has_more_events) {
@@ -83,7 +83,7 @@
     }
     output->append(json_events_str->data());
     if (!has_more_events) {
-      callback.Run();
+      std::move(quit_closure).Run();
     }
   }
 
@@ -93,9 +93,9 @@
     std::string json_data = "[";
     base::trace_event::TraceLog::GetInstance()->SetDisabled();
     base::RunLoop run_loop;
-    base::trace_event::TraceLog::GetInstance()->Flush(
-        base::Bind(&YUVReadbackTest::TraceDataCB, run_loop.QuitClosure(),
-                   base::Unretained(&json_data)));
+    base::trace_event::TraceLog::GetInstance()->Flush(base::BindRepeating(
+        &YUVReadbackTest::TraceDataCB, run_loop.QuitClosure(),
+        base::Unretained(&json_data)));
     run_loop.Run();
     json_data.append("]");
 
@@ -257,11 +257,6 @@
     return ret;
   }
 
-  static void callcallback(const base::Callback<void()>& callback,
-                           bool result) {
-    callback.Run();
-  }
-
   void PrintPlane(unsigned char* plane, int xsize, int stride, int ysize) {
     for (int y = 0; y < std::min(24, ysize); y++) {
       std::string formatted;
@@ -391,16 +386,20 @@
             base::TimeDelta::FromSeconds(0));
 
     base::RunLoop run_loop;
-    yuv_reader->ReadbackYUV(mailbox, sync_token, gfx::Size(xsize, ysize),
-                            gfx::Rect(0, 0, xsize, ysize),
-                            output_frame->stride(media::VideoFrame::kYPlane),
-                            output_frame->data(media::VideoFrame::kYPlane),
-                            output_frame->stride(media::VideoFrame::kUPlane),
-                            output_frame->data(media::VideoFrame::kUPlane),
-                            output_frame->stride(media::VideoFrame::kVPlane),
-                            output_frame->data(media::VideoFrame::kVPlane),
-                            gfx::Point(xmargin, ymargin),
-                            base::Bind(&callcallback, run_loop.QuitClosure()));
+    auto run_quit_closure = [](base::OnceClosure quit_closure, bool result) {
+      std::move(quit_closure).Run();
+    };
+    yuv_reader->ReadbackYUV(
+        mailbox, sync_token, gfx::Size(xsize, ysize),
+        gfx::Rect(0, 0, xsize, ysize),
+        output_frame->stride(media::VideoFrame::kYPlane),
+        output_frame->data(media::VideoFrame::kYPlane),
+        output_frame->stride(media::VideoFrame::kUPlane),
+        output_frame->data(media::VideoFrame::kUPlane),
+        output_frame->stride(media::VideoFrame::kVPlane),
+        output_frame->data(media::VideoFrame::kVPlane),
+        gfx::Point(xmargin, ymargin),
+        base::BindOnce(run_quit_closure, run_loop.QuitClosure()));
 
     const gfx::Rect paste_rect(gfx::Point(xmargin, ymargin),
                                gfx::Size(xsize, ysize));
diff --git a/components/viz/service/display/copy_output_scaling_pixeltest.cc b/components/viz/service/display/copy_output_scaling_pixeltest.cc
index bc136a2..771585a 100644
--- a/components/viz/service/display/copy_output_scaling_pixeltest.cc
+++ b/components/viz/service/display/copy_output_scaling_pixeltest.cc
@@ -155,7 +155,7 @@
           base::BindOnce(
               [](bool* dummy_ran,
                  std::unique_ptr<CopyOutputResult>* test_result,
-                 const base::Closure& quit_closure,
+                 const base::RepeatingClosure& quit_closure,
                  std::unique_ptr<CopyOutputResult> result_from_renderer) {
                 EXPECT_TRUE(*dummy_ran);
                 *test_result = std::move(result_from_renderer);
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 0f8b0b4..9b8780f 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -135,13 +135,8 @@
     if (scheduler_)
       surface_manager_->RemoveObserver(scheduler_.get());
   }
-  if (aggregator_) {
-    for (const auto& id_entry : aggregator_->previous_contained_surfaces()) {
-      Surface* surface = surface_manager_->GetSurfaceForId(id_entry.first);
-      if (surface)
-        surface->RunDrawCallback();
-    }
-  }
+
+  RunDrawCallbacks();
 }
 
 void Display::Initialize(DisplayClient* client,
@@ -382,12 +377,7 @@
                            swapped_trace_id_);
 
   // Run callbacks early to allow pipelining and collect presented callbacks.
-  for (const auto& surface_id : surfaces_to_ack_on_next_draw_) {
-    Surface* surface = surface_manager_->GetSurfaceForId(surface_id);
-    if (surface)
-      surface->RunDrawCallback();
-  }
-  surfaces_to_ack_on_next_draw_.clear();
+  RunDrawCallbacks();
 
   frame.metadata.latency_info.insert(frame.metadata.latency_info.end(),
                                      stored_latency_info_.begin(),
@@ -829,4 +819,22 @@
           std::numeric_limits<uint64_t>::max())));
 }
 
+void Display::RunDrawCallbacks() {
+  for (const auto& surface_id : surfaces_to_ack_on_next_draw_) {
+    Surface* surface = surface_manager_->GetSurfaceForId(surface_id);
+    if (surface)
+      surface->RunDrawCallback();
+  }
+  surfaces_to_ack_on_next_draw_.clear();
+  // |surfaces_to_ack_on_next_draw_| does not cover surfaces that are being
+  // embedded for the first time, so also go through SurfaceAggregator's list.
+  if (aggregator_) {
+    for (const auto& id_entry : aggregator_->previous_contained_surfaces()) {
+      Surface* surface = surface_manager_->GetSurfaceForId(id_entry.first);
+      if (surface)
+        surface->RunDrawCallback();
+    }
+  }
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display/display.h b/components/viz/service/display/display.h
index fa336615..6d738d4 100644
--- a/components/viz/service/display/display.h
+++ b/components/viz/service/display/display.h
@@ -136,6 +136,7 @@
  private:
   void InitializeRenderer();
   void UpdateRootFrameMissing();
+  void RunDrawCallbacks();
 
   // ContextLostObserver implementation.
   void OnContextLost() override;
diff --git a/components/viz/service/display/display_resource_provider.cc b/components/viz/service/display/display_resource_provider.cc
index 29b7c1e3..eb1ba0a9 100644
--- a/components/viz/service/display/display_resource_provider.cc
+++ b/components/viz/service/display/display_resource_provider.cc
@@ -522,11 +522,7 @@
 
   metadata.mailbox_holder = resource->transferable.mailbox_holder;
   metadata.size = resource->transferable.size;
-  metadata.mip_mapped = GrMipMapped::kNo;
-  metadata.origin = kTopLeft_GrSurfaceOrigin;
   metadata.resource_format = resource->transferable.format;
-  metadata.alpha_type = kPremul_SkAlphaType;
-  metadata.color_space = nullptr;
   metadata.mailbox_holder.sync_token = resource->sync_token();
 
   resource->locked_for_external_use = true;
diff --git a/components/viz/service/display/display_scheduler.cc b/components/viz/service/display/display_scheduler.cc
index 7c5c22d..3d8ef719 100644
--- a/components/viz/service/display/display_scheduler.cc
+++ b/components/viz/service/display/display_scheduler.cc
@@ -36,7 +36,7 @@
       wait_for_all_surfaces_before_draw_(wait_for_all_surfaces_before_draw),
       observing_begin_frame_source_(false),
       weak_ptr_factory_(this) {
-  begin_frame_deadline_closure_ = base::Bind(
+  begin_frame_deadline_closure_ = base::BindRepeating(
       &DisplayScheduler::OnBeginFrameDeadline, weak_ptr_factory_.GetWeakPtr());
 
   // The DisplayScheduler handles animate_only BeginFrames as if they were
@@ -232,7 +232,7 @@
     // CompositorFrame for a SurfaceFactory).
     DCHECK_EQ(args.type, BeginFrameArgs::MISSED);
     DCHECK(missed_begin_frame_task_.IsCancelled());
-    missed_begin_frame_task_.Reset(base::Bind(
+    missed_begin_frame_task_.Reset(base::BindOnce(
         base::IgnoreResult(&DisplayScheduler::OnBeginFrameDerivedImpl),
         // The CancelableCallback will not run after it is destroyed, which
         // happens when |this| is destroyed.
diff --git a/components/viz/service/display/display_scheduler.h b/components/viz/service/display/display_scheduler.h
index e3a8760..06ffc24 100644
--- a/components/viz/service/display/display_scheduler.h
+++ b/components/viz/service/display/display_scheduler.h
@@ -132,11 +132,11 @@
   base::SingleThreadTaskRunner* task_runner_;
 
   BeginFrameArgs current_begin_frame_args_;
-  base::Closure begin_frame_deadline_closure_;
-  base::CancelableClosure begin_frame_deadline_task_;
+  base::RepeatingClosure begin_frame_deadline_closure_;
+  base::CancelableOnceClosure begin_frame_deadline_task_;
   base::TimeTicks begin_frame_deadline_task_time_;
 
-  base::CancelableClosure missed_begin_frame_task_;
+  base::CancelableOnceClosure missed_begin_frame_task_;
   bool inside_surface_damaged_;
 
   bool visible_;
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index ad03eba..e7c6461 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -3578,7 +3578,8 @@
   int max_result = current_surface_size_.GetArea();
   DCHECK_GT(max_result, 0);
 
-  OverdrawFeedbackCallback overdraw_feedback_callback = base::Bind(
+  // Callback is repeating to allow sharing the owned vector<int>.
+  auto overdraw_feedback_callback = base::BindRepeating(
       &GLRenderer::ProcessOverdrawFeedback, weak_ptr_factory_.GetWeakPtr(),
       base::Owned(new std::vector<int>), base::size(stencil_tests), max_result);
 
diff --git a/components/viz/service/display/gl_renderer.h b/components/viz/service/display/gl_renderer.h
index 85a6599..6aa68062 100644
--- a/components/viz/service/display/gl_renderer.h
+++ b/components/viz/service/display/gl_renderer.h
@@ -317,7 +317,6 @@
   void SetupOverdrawFeedback();
   void FlushOverdrawFeedback(const gfx::Rect& output_rect);
   // Process overdraw feedback from query.
-  using OverdrawFeedbackCallback = base::Callback<void(unsigned, int)>;
   void ProcessOverdrawFeedback(std::vector<int>* overdraw,
                                size_t num_expected_results,
                                int max_result,
diff --git a/components/viz/service/display/gl_renderer_copier_pixeltest.cc b/components/viz/service/display/gl_renderer_copier_pixeltest.cc
index 2a4e7378..8c5f5d5 100644
--- a/components/viz/service/display/gl_renderer_copier_pixeltest.cc
+++ b/components/viz/service/display/gl_renderer_copier_pixeltest.cc
@@ -251,10 +251,10 @@
         result_format_,
         base::BindOnce(
             [](std::unique_ptr<CopyOutputResult>* result,
-               const base::Closure& quit_closure,
+               base::OnceClosure quit_closure,
                std::unique_ptr<CopyOutputResult> result_from_copier) {
               *result = std::move(result_from_copier);
-              quit_closure.Run();
+              std::move(quit_closure).Run();
             },
             &result, loop.QuitClosure()));
     if (scale_by_half_) {
diff --git a/components/viz/service/display/gl_renderer_unittest.cc b/components/viz/service/display/gl_renderer_unittest.cc
index 3c24c62..b5f0bef 100644
--- a/components/viz/service/display/gl_renderer_unittest.cc
+++ b/components/viz/service/display/gl_renderer_unittest.cc
@@ -2399,7 +2399,7 @@
   provider->BindToCurrentThread();
 
   MockOverlayScheduler overlay_scheduler;
-  provider->support()->SetScheduleOverlayPlaneCallback(base::Bind(
+  provider->support()->SetScheduleOverlayPlaneCallback(base::BindRepeating(
       &MockOverlayScheduler::Schedule, base::Unretained(&overlay_scheduler)));
 
   cc::FakeOutputSurfaceClient output_surface_client;
diff --git a/components/viz/service/display/overlay_unittest.cc b/components/viz/service/display/overlay_unittest.cc
index d9619d2b..8a7cc7c 100644
--- a/components/viz/service/display/overlay_unittest.cc
+++ b/components/viz/service/display/overlay_unittest.cc
@@ -2862,7 +2862,7 @@
     resource_provider_ = std::make_unique<DisplayResourceProvider>(
         DisplayResourceProvider::kGpu, provider_.get(), nullptr);
 
-    provider_->support()->SetScheduleOverlayPlaneCallback(base::Bind(
+    provider_->support()->SetScheduleOverlayPlaneCallback(base::BindRepeating(
         &MockOverlayScheduler::Schedule, base::Unretained(&scheduler_)));
 
     child_provider_ = TestContextProvider::Create();
diff --git a/components/viz/service/display/resource_metadata.h b/components/viz/service/display/resource_metadata.h
index 864dd38..ef8cb4f2 100644
--- a/components/viz/service/display/resource_metadata.h
+++ b/components/viz/service/display/resource_metadata.h
@@ -9,9 +9,7 @@
 #include "components/viz/service/viz_service_export.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
 #include "gpu/command_buffer/common/sync_token.h"
-#include "third_party/skia/include/core/SkColorSpace.h"
-#include "third_party/skia/include/core/SkImageInfo.h"
-#include "third_party/skia/include/gpu/GrTypes.h"
+#include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace viz {
@@ -30,20 +28,11 @@
   // The resource size.
   gfx::Size size;
 
-  // the mipmap of the resource texture.
-  GrMipMapped mip_mapped = GrMipMapped::kNo;
-
-  // The origin type for the resource texture.
-  GrSurfaceOrigin origin = kTopLeft_GrSurfaceOrigin;
-
   // ResourceFormat from the resource texture.
   ResourceFormat resource_format = RGBA_8888;
 
-  // The alpha type for the resource texture.
-  SkAlphaType alpha_type = kUnknown_SkAlphaType;
-
-  // The color space for the resource texture. It could be null.
-  sk_sp<SkColorSpace> color_space;
+  // The color space for the resource texture.
+  gfx::ColorSpace color_space;
 };
 
 }  // namespace viz
diff --git a/components/viz/service/display/software_renderer_unittest.cc b/components/viz/service/display/software_renderer_unittest.cc
index 5ed0040b..4aff55f 100644
--- a/components/viz/service/display/software_renderer_unittest.cc
+++ b/components/viz/service/display/software_renderer_unittest.cc
@@ -112,13 +112,13 @@
   }
 
   static void SaveBitmapResult(std::unique_ptr<SkBitmap>* bitmap_result,
-                               const base::Closure& quit_closure,
+                               base::OnceClosure quit_closure,
                                std::unique_ptr<CopyOutputResult> result) {
     DCHECK(!result->IsEmpty());
     DCHECK_EQ(result->format(), CopyOutputResult::Format::RGBA_BITMAP);
     *bitmap_result = std::make_unique<SkBitmap>(result->AsSkBitmap());
     DCHECK((*bitmap_result)->readyToDraw());
-    quit_closure.Run();
+    std::move(quit_closure).Run();
   }
 
  protected:
diff --git a/components/viz/service/display/texture_deleter.cc b/components/viz/service/display/texture_deleter.cc
index 714441f..5545622 100644
--- a/components/viz/service/display/texture_deleter.cc
+++ b/components/viz/service/display/texture_deleter.cc
@@ -64,9 +64,9 @@
 
   // The raw pointer to the impl-side callback is valid as long as this
   // class is alive. So we guard it with a WeakPtr.
-  ReleaseCallback run_impl_callback(
-      base::Bind(&TextureDeleter::RunDeleteTextureOnImplThread,
-                 weak_ptr_factory_.GetWeakPtr(), impl_callbacks_.back().get()));
+  ReleaseCallback run_impl_callback = base::BindOnce(
+      &TextureDeleter::RunDeleteTextureOnImplThread,
+      weak_ptr_factory_.GetWeakPtr(), impl_callbacks_.back().get());
 
   // Provide a callback for the main thread that posts back to the impl
   // thread.
diff --git a/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc b/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc
index e0bdbf80..845d211f 100644
--- a/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc
+++ b/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc
@@ -48,16 +48,16 @@
                               base::SPLIT_WANT_NONEMPTY)) {
     if (strategy_name == "single-fullscreen") {
       strategies_instantiators_.push_back(
-          base::Bind(MakeOverlayStrategy<OverlayStrategyFullscreen>));
+          base::BindRepeating(MakeOverlayStrategy<OverlayStrategyFullscreen>));
     } else if (strategy_name == "single-on-top") {
       strategies_instantiators_.push_back(
-          base::Bind(MakeOverlayStrategy<OverlayStrategySingleOnTop>));
+          base::BindRepeating(MakeOverlayStrategy<OverlayStrategySingleOnTop>));
     } else if (strategy_name == "underlay") {
       strategies_instantiators_.push_back(
-          base::Bind(MakeOverlayStrategy<OverlayStrategyUnderlay>));
+          base::BindRepeating(MakeOverlayStrategy<OverlayStrategyUnderlay>));
     } else if (strategy_name == "cast") {
-      strategies_instantiators_.push_back(
-          base::Bind(MakeOverlayStrategy<OverlayStrategyUnderlayCast>));
+      strategies_instantiators_.push_back(base::BindRepeating(
+          MakeOverlayStrategy<OverlayStrategyUnderlayCast>));
     } else {
       LOG(WARNING) << "Unrecognized overlay strategy " << strategy_name;
     }
diff --git a/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h b/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h
index 3c03de0..cca7a65 100644
--- a/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h
+++ b/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h
@@ -40,7 +40,7 @@
   std::unique_ptr<ui::OverlayCandidatesOzone> overlay_candidates_;
   // Callback declaration to allocate a new OverlayProcessor::Strategy.
   using StrategyInstantiator =
-      base::Callback<std::unique_ptr<OverlayProcessor::Strategy>(
+      base::RepeatingCallback<std::unique_ptr<OverlayProcessor::Strategy>(
           CompositorOverlayCandidateValidatorOzone*)>;
   // List callbacks used to instantiate OverlayProcessor::Strategy
   // as defined by |strategies_string| paramter in the constructor.
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 2bf4861..de5f5ba 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -47,57 +47,100 @@
 }  // namespace
 
 // A helper class for fulfilling promise image on the GPU thread.
-template <class FulfillContextType>
 class SkiaOutputSurfaceImpl::PromiseTextureHelper {
  public:
-  using HelperType = PromiseTextureHelper<FulfillContextType>;
-
-  PromiseTextureHelper(base::WeakPtr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu,
-                       FulfillContextType context)
-      : impl_on_gpu_(impl_on_gpu), context_(std::move(context)) {}
-  ~PromiseTextureHelper() = default;
-
-  static sk_sp<SkImage> MakePromiseSkImage(
+  static sk_sp<SkImage> MakePromiseSkImageFromMetadata(
       SkiaOutputSurfaceImpl* impl,
-      SkDeferredDisplayListRecorder* recorder,
-      const GrBackendFormat& backend_format,
+      const ResourceMetadata& metadata) {
+    auto* helper = new PromiseTextureHelper(
+        impl->impl_on_gpu_->weak_ptr(), metadata.size, metadata.resource_format,
+        metadata.mailbox_holder);
+    return helper->MakePromiseSkImage(impl);
+  }
+
+  static sk_sp<SkImage> MakePromiseSkImageFromRenderPass(
+      SkiaOutputSurfaceImpl* impl,
+      ResourceFormat resource_format,
       gfx::Size size,
-      GrMipMapped mip_mapped,
-      GrSurfaceOrigin origin,
-      SkColorType color_type,
-      SkAlphaType alpha_type,
-      sk_sp<SkColorSpace> color_space,
-      FulfillContextType context) {
+      RenderPassId render_pass_id) {
     DCHECK_CALLED_ON_VALID_THREAD(impl->thread_checker_);
     // The ownership of the helper will be passed into makePromisTexture(). The
-    // HelperType::Done will always be called. It will delete the helper.
-    auto* helper =
-        new HelperType(impl->impl_on_gpu_->weak_ptr(), std::move(context));
-    auto image = recorder->makePromiseTexture(
-        backend_format, size.width(), size.height(), mip_mapped, origin,
-        color_type, alpha_type, color_space, HelperType::Fulfill,
-        HelperType::Release, HelperType::Done, helper);
-    return image;
+    // PromiseTextureHelper::Done will always be called. It will delete the
+    // helper.
+    auto* helper = new PromiseTextureHelper(
+        impl->impl_on_gpu_->weak_ptr(), size, resource_format, render_pass_id);
+    return helper->MakePromiseSkImage(impl);
   }
 
  private:
   friend class SkiaOutputSurfaceImpl::YUVAPromiseTextureHelper;
 
+  PromiseTextureHelper(base::WeakPtr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu,
+                       const gfx::Size& size,
+                       ResourceFormat resource_format,
+                       RenderPassId render_pass_id)
+      : impl_on_gpu_(impl_on_gpu),
+        size_(size),
+        resource_format_(resource_format),
+        render_pass_id_(render_pass_id) {}
+  PromiseTextureHelper(base::WeakPtr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu,
+                       const gfx::Size& size,
+                       ResourceFormat resource_format,
+                       const gpu::MailboxHolder& mailbox_holder)
+      : impl_on_gpu_(impl_on_gpu),
+        size_(size),
+        resource_format_(resource_format),
+        render_pass_id_(0u),
+        mailbox_holder_(mailbox_holder) {}
+  ~PromiseTextureHelper() = default;
+
+  sk_sp<SkImage> MakePromiseSkImage(SkiaOutputSurfaceImpl* impl) {
+    SkColorType color_type = ResourceFormatToClosestSkColorType(
+        true /* gpu_compositing */, resource_format_);
+    GrBackendFormat backend_format;
+    if (!impl->gpu_service_->is_using_vulkan()) {
+      // Convert internal format from GLES2 to platform GL.
+      const auto* version_info = impl->impl_on_gpu_->gl_version_info();
+      unsigned int texture_storage_format =
+          TextureStorageFormat(resource_format_);
+      backend_format = GrBackendFormat::MakeGL(
+          gl::GetInternalFormat(version_info, texture_storage_format),
+          GL_TEXTURE_2D);
+    } else {
+#if BUILDFLAG(ENABLE_VULKAN)
+      backend_format = GrBackendFormat::MakeVk(ToVkFormat(resource_format_));
+#else
+      NOTREACHED();
+#endif
+    }
+    return impl->recorder_->makePromiseTexture(
+        backend_format, size_.width(), size_.height(), GrMipMapped::kNo,
+        kTopLeft_GrSurfaceOrigin /* origin */, color_type, kPremul_SkAlphaType,
+        nullptr /* color_space */, PromiseTextureHelper::Fulfill,
+        PromiseTextureHelper::Release, PromiseTextureHelper::Done, this);
+  }
+
   static void Fulfill(void* texture_context,
                       GrBackendTexture* backend_texture) {
     DCHECK(texture_context);
-    auto* helper = static_cast<HelperType*>(texture_context);
+    auto* helper = static_cast<PromiseTextureHelper*>(texture_context);
     // The fulfill is always called by SkiaOutputSurfaceImplOnGpu::SwapBuffers
     // or SkiaOutputSurfaceImplOnGpu::FinishPaintRenderPass, so impl_on_gpu_
     // should be always valid.
     DCHECK(helper->impl_on_gpu_);
-    helper->impl_on_gpu_->FulfillPromiseTexture(
-        helper->context_, &helper->shared_image_, backend_texture);
+    if (helper->render_pass_id_) {
+      helper->impl_on_gpu_->FulfillPromiseTexture(
+          helper->render_pass_id_, &helper->shared_image_, backend_texture);
+    } else {
+      helper->impl_on_gpu_->FulfillPromiseTexture(
+          helper->mailbox_holder_, helper->size_, helper->resource_format_,
+          &helper->shared_image_, backend_texture);
+    }
   }
 
   static void Release(void* texture_context) {
     DCHECK(texture_context);
-    auto* helper = static_cast<HelperType*>(texture_context);
+    auto* helper = static_cast<PromiseTextureHelper*>(texture_context);
     if (helper->shared_image_) {
       helper->shared_image_->EndReadAccess();
       helper->shared_image_.reset();
@@ -106,14 +149,15 @@
 
   static void Done(void* texture_context) {
     DCHECK(texture_context);
-    std::unique_ptr<HelperType> helper(
-        static_cast<HelperType*>(texture_context));
+    auto* helper = static_cast<PromiseTextureHelper*>(texture_context);
+    delete helper;
   }
 
   base::WeakPtr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu_;
-
-  // The data for calling the fulfill methods in SkiaOutputSurfaceImpl.
-  FulfillContextType context_;
+  const gfx::Size size_;
+  const ResourceFormat resource_format_;
+  RenderPassId render_pass_id_;
+  gpu::MailboxHolder mailbox_holder_;
 
   // If non-null, an outstanding SharedImageRepresentation that must be freed on
   // Release. Only written / read from GPU thread.
@@ -127,14 +171,12 @@
  public:
   static sk_sp<SkImage> MakeYUVAPromiseSkImage(
       SkiaOutputSurfaceImpl* impl,
-      SkDeferredDisplayListRecorder* recorder,
       SkYUVColorSpace yuv_color_space,
       std::vector<ResourceMetadata> metadatas,
       bool has_alpha) {
     DCHECK_CALLED_ON_VALID_THREAD(impl->thread_checker_);
     DCHECK_LE(metadatas.size(), 4u);
 
-    using PlaneHelper = PromiseTextureHelper<ResourceMetadata>;
     bool is_i420 = has_alpha ? metadatas.size() == 4 : metadatas.size() == 3;
 
     GrBackendFormat formats[4];
@@ -149,8 +191,8 @@
         nullptr, nullptr, nullptr, nullptr};
 
     // The ownership of the contexts will be passed into
-    // makeYUVAPromisTexture(). The HelperType::Done will always be called. It
-    // will delete contexts.
+    // makeYUVAPromiseTexture(). The PromiseTextureHelper::Done will always be
+    // called. It will delete contexts.
     const auto process_planar = [&](size_t i, ResourceFormat resource_format,
                                     GLenum gl_format) {
       auto& metadata = metadatas[i];
@@ -158,8 +200,9 @@
       formats[i] = GrBackendFormat::MakeGL(
           gl_format, metadata.mailbox_holder.texture_target);
       yuva_sizes[i].set(metadata.size.width(), metadata.size.height());
-      contexts[i] =
-          new PlaneHelper(impl->impl_on_gpu_->weak_ptr(), std::move(metadata));
+      contexts[i] = new PromiseTextureHelper(
+          impl->impl_on_gpu_->weak_ptr(), metadata.size,
+          metadata.resource_format, metadata.mailbox_holder);
     };
 
     if (is_i420) {
@@ -197,11 +240,11 @@
       }
     }
 
-    auto image = recorder->makeYUVAPromiseTexture(
+    auto image = impl->recorder_->makeYUVAPromiseTexture(
         yuv_color_space, formats, yuva_sizes, indices, yuva_sizes[0].width(),
         yuva_sizes[0].height(), kTopLeft_GrSurfaceOrigin,
-        nullptr /* color_space */, PlaneHelper::Fulfill, PlaneHelper::Release,
-        PlaneHelper::Done, contexts);
+        nullptr /* color_space */, PromiseTextureHelper::Fulfill,
+        PromiseTextureHelper::Release, PromiseTextureHelper::Done, contexts);
     return image;
   }
 
@@ -397,32 +440,9 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(recorder_);
 
-  GrBackendFormat backend_format;
-  if (!gpu_service_->is_using_vulkan()) {
-    // Convert internal format from GLES2 to platform GL.
-    const auto* version_info = impl_on_gpu_->gl_version_info();
-    backend_format = GrBackendFormat::MakeGL(
-        gl::GetInternalFormat(version_info,
-                              TextureStorageFormat(metadata.resource_format)),
-        metadata.mailbox_holder.texture_target);
-  } else {
-#if BUILDFLAG(ENABLE_VULKAN)
-    backend_format =
-        GrBackendFormat::MakeVk(ToVkFormat(metadata.resource_format));
-#else
-    NOTREACHED();
-#endif
-  }
-
   DCHECK(!metadata.mailbox_holder.mailbox.IsZero());
   resource_sync_tokens_.push_back(metadata.mailbox_holder.sync_token);
-  SkColorType sk_color_type = ResourceFormatToClosestSkColorType(
-      /*gpu_compositing=*/true, metadata.resource_format);
-
-  return PromiseTextureHelper<ResourceMetadata>::MakePromiseSkImage(
-      this, &recorder_.value(), backend_format, metadata.size,
-      metadata.mip_mapped, metadata.origin, sk_color_type, metadata.alpha_type,
-      metadata.color_space, std::move(metadata));
+  return PromiseTextureHelper::MakePromiseSkImageFromMetadata(this, metadata);
 }
 
 sk_sp<SkImage> SkiaOutputSurfaceImpl::MakePromiseSkImageFromYUV(
@@ -435,8 +455,7 @@
          (!has_alpha && (metadatas.size() == 2 || metadatas.size() == 3)));
 
   return YUVAPromiseTextureHelper::MakeYUVAPromiseSkImage(
-      this, &recorder_.value(), yuv_color_space, std::move(metadatas),
-      has_alpha);
+      this, yuv_color_space, std::move(metadatas), has_alpha);
 }
 
 void SkiaOutputSurfaceImpl::SkiaSwapBuffers(OutputSurfaceFrame frame) {
@@ -524,29 +543,11 @@
     bool mipmap) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(recorder_);
+  // TODO(penghuang): remove this mipmap argument, because we always pass false.
+  DCHECK(!mipmap);
 
-  SkColorType color_type =
-      ResourceFormatToClosestSkColorType(true /* gpu_compositing */, format);
-  GrBackendFormat backend_format;
-
-  if (!gpu_service_->is_using_vulkan()) {
-    // Convert internal format from GLES2 to platform GL.
-    const auto* version_info = impl_on_gpu_->gl_version_info();
-    unsigned int texture_storage_format = TextureStorageFormat(format);
-    backend_format = GrBackendFormat::MakeGL(
-        gl::GetInternalFormat(version_info, texture_storage_format),
-        GL_TEXTURE_2D);
-  } else {
-#if BUILDFLAG(ENABLE_VULKAN)
-    backend_format = GrBackendFormat::MakeVk(ToVkFormat(format));
-#else
-    NOTREACHED();
-#endif
-  }
-  return PromiseTextureHelper<RenderPassId>::MakePromiseSkImage(
-      this, &recorder_.value(), backend_format, size,
-      mipmap ? GrMipMapped::kYes : GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin,
-      color_type, kPremul_SkAlphaType, nullptr /* color_space */, id);
+  return PromiseTextureHelper::MakePromiseSkImageFromRenderPass(this, format,
+                                                                size, id);
 }
 
 void SkiaOutputSurfaceImpl::RemoveRenderPassResource(
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index 22d7495..31a0a65 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -99,7 +99,6 @@
   void RemoveContextLostObserver(ContextLostObserver* observer) override;
 
  private:
-  template <class T>
   class PromiseTextureHelper;
   class YUVAPromiseTextureHelper;
   void InitializeOnGpuThread(base::WaitableEvent* event);
@@ -151,6 +150,7 @@
   // Whether to send OutputSurfaceClient::DidSwapWithSize notifications.
   bool needs_swap_size_notifications_ = false;
 
+  // Observers for context lost.
   base::ObserverList<ContextLostObserver>::Unchecked observers_;
 
   THREAD_CHECKER(thread_checker_);
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index f18f8c5e..fdb9272 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -414,16 +414,18 @@
 }
 
 void SkiaOutputSurfaceImplOnGpu::FulfillPromiseTexture(
-    const ResourceMetadata& metadata,
+    const gpu::MailboxHolder& mailbox_holder,
+    const gfx::Size& size,
+    const ResourceFormat resource_format,
     std::unique_ptr<gpu::SharedImageRepresentationSkia>* shared_image_out,
     GrBackendTexture* backend_texture) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!*shared_image_out);
   if (shared_image_representation_factory_->IsSharedImage(
-          metadata.mailbox_holder.mailbox)) {
+          mailbox_holder.mailbox)) {
     std::unique_ptr<gpu::SharedImageRepresentationSkia> shared_image =
         shared_image_representation_factory_->ProduceSkia(
-            metadata.mailbox_holder.mailbox);
+            mailbox_holder.mailbox);
     DCHECK(shared_image);
     if (!shared_image->BeginReadAccess(sk_surface_.get(), backend_texture)) {
       DLOG(ERROR)
@@ -442,16 +444,15 @@
   }
 
   auto* mailbox_manager = gpu_service_->mailbox_manager();
-  auto* texture_base =
-      mailbox_manager->ConsumeTexture(metadata.mailbox_holder.mailbox);
+  auto* texture_base = mailbox_manager->ConsumeTexture(mailbox_holder.mailbox);
   if (!texture_base) {
     DLOG(ERROR) << "Failed to fulfill the promise texture.";
     return;
   }
   BindOrCopyTextureIfNecessary(texture_base);
-  gpu::GetGrBackendTexture(gl_version_info_, texture_base->target(),
-                           metadata.size, texture_base->service_id(),
-                           metadata.resource_format, backend_texture);
+  gpu::GetGrBackendTexture(gl_version_info_, texture_base->target(), size,
+                           texture_base->service_id(), resource_format,
+                           backend_texture);
 }
 
 void SkiaOutputSurfaceImplOnGpu::FulfillPromiseTexture(
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index 6fac3e6..3b9422a 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -109,7 +109,9 @@
 
   // Fulfill callback for promise SkImage created from a resource.
   void FulfillPromiseTexture(
-      const ResourceMetadata& metadata,
+      const gpu::MailboxHolder& mailbox_holder,
+      const gfx::Size& size,
+      const ResourceFormat resource_format,
       std::unique_ptr<gpu::SharedImageRepresentationSkia>* shared_image_out,
       GrBackendTexture* backend_texture);
   // Fulfill callback for promise SkImage created from a render pass.
diff --git a/components/viz/service/frame_sinks/external_begin_frame_source_android_unittest.cc b/components/viz/service/frame_sinks/external_begin_frame_source_android_unittest.cc
index 1b4e644..273beda 100644
--- a/components/viz/service/frame_sinks/external_begin_frame_source_android_unittest.cc
+++ b/components/viz/service/frame_sinks/external_begin_frame_source_android_unittest.cc
@@ -21,16 +21,17 @@
 
     thread_->task_runner()->PostTask(
         FROM_HERE,
-        base::Bind(&ExternalBeginFrameSourceAndroidTest::InitOnThread,
-                   base::Unretained(this)));
+        base::BindOnce(&ExternalBeginFrameSourceAndroidTest::InitOnThread,
+                       base::Unretained(this)));
   }
 
   void WaitForFrames(uint32_t frame_count) {
     frames_done_event_.Reset();
     thread_->task_runner()->PostTask(
         FROM_HERE,
-        base::Bind(&ExternalBeginFrameSourceAndroidTest::AddObserverOnThread,
-                   base::Unretained(this), frame_count));
+        base::BindOnce(
+            &ExternalBeginFrameSourceAndroidTest::AddObserverOnThread,
+            base::Unretained(this), frame_count));
     frames_done_event_.Wait();
   }
 
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index 0fe9bd6..ba02e1f 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -84,7 +84,7 @@
 
 namespace {
 
-static base::LazyInstance<base::Callback<
+static base::LazyInstance<base::RepeatingCallback<
     void(int severity, size_t message_start, const std::string& message)>>::
     Leaky g_log_callback = LAZY_INSTANCE_INITIALIZER;
 
@@ -171,8 +171,7 @@
   DCHECK(main_runner_->BelongsToCurrentThread());
   bind_task_tracker_.TryCancelAll();
   logging::SetLogMessageHandler(nullptr);
-  g_log_callback.Get() =
-      base::Callback<void(int, size_t, const std::string&)>();
+  g_log_callback.Get().Reset();
   base::WaitableEvent wait;
   if (io_runner_->PostTask(
           FROM_HERE, base::BindOnce(&DestroyBinding, bindings_.get(), &wait))) {
@@ -232,8 +231,8 @@
     // The global callback is reset from the dtor. So Unretained() here is safe.
     // Note that the callback can be called from any thread. Consequently, the
     // callback cannot use a WeakPtr.
-    g_log_callback.Get() =
-        base::Bind(&GpuServiceImpl::RecordLogMessage, base::Unretained(this));
+    g_log_callback.Get() = base::BindRepeating(
+        &GpuServiceImpl::RecordLogMessage, base::Unretained(this));
     logging::SetLogMessageHandler(GpuLogMessageHandler);
   }
 
@@ -422,7 +421,7 @@
   DCHECK(io_runner_->BelongsToCurrentThread());
   media::MojoVideoEncodeAcceleratorProvider::Create(
       std::move(vea_provider_request),
-      base::Bind(&media::GpuVideoEncodeAcceleratorFactory::CreateVEA),
+      base::BindRepeating(&media::GpuVideoEncodeAcceleratorFactory::CreateVEA),
       gpu_preferences_);
 }
 
diff --git a/components/viz/service/main/BUILD.gn b/components/viz/service/main/BUILD.gn
index a9aa094..13b0a2796 100644
--- a/components/viz/service/main/BUILD.gn
+++ b/components/viz/service/main/BUILD.gn
@@ -25,7 +25,7 @@
     "//components/discardable_memory/client",
     "//components/discardable_memory/public/interfaces",
     "//components/ui_devtools",
-    "//components/ui_devtools/viz_views",
+    "//components/ui_devtools/viz",
     "//components/viz/service",
     "//gpu/ipc:gl_in_process_context",
     "//gpu/ipc/common",
diff --git a/components/viz/service/main/viz_compositor_thread_runner.cc b/components/viz/service/main/viz_compositor_thread_runner.cc
index 47ecab5..70b6b86 100644
--- a/components/viz/service/main/viz_compositor_thread_runner.cc
+++ b/components/viz/service/main/viz_compositor_thread_runner.cc
@@ -25,8 +25,8 @@
 #if defined(USE_VIZ_DEVTOOLS)
 #include "components/ui_devtools/css_agent.h"
 #include "components/ui_devtools/devtools_server.h"
-#include "components/ui_devtools/viz_views/dom_agent_viz.h"
-#include "components/ui_devtools/viz_views/overlay_agent_viz.h"
+#include "components/ui_devtools/viz/dom_agent_viz.h"
+#include "components/ui_devtools/viz/overlay_agent_viz.h"
 #endif
 
 namespace viz {
diff --git a/components/viz/test/fake_external_begin_frame_source.cc b/components/viz/test/fake_external_begin_frame_source.cc
index 75a0b0d..0f22be5 100644
--- a/components/viz/test/fake_external_begin_frame_source.cc
+++ b/components/viz/test/fake_external_begin_frame_source.cc
@@ -99,12 +99,11 @@
 
 void FakeExternalBeginFrameSource::PostTestOnBeginFrame() {
   begin_frame_task_.Reset(
-      base::Bind(&FakeExternalBeginFrameSource::TestOnBeginFrame,
-                 weak_ptr_factory_.GetWeakPtr()));
+      base::BindOnce(&FakeExternalBeginFrameSource::TestOnBeginFrame,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     CreateBeginFrameArgs(BEGINFRAME_FROM_HERE)));
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(begin_frame_task_.callback(),
-                     CreateBeginFrameArgs(BEGINFRAME_FROM_HERE)),
+      FROM_HERE, begin_frame_task_.callback(),
       base::TimeDelta::FromMilliseconds(milliseconds_per_frame_));
   next_begin_frame_number_++;
 }
diff --git a/components/viz/test/fake_external_begin_frame_source.h b/components/viz/test/fake_external_begin_frame_source.h
index 39f893b..47b43fa 100644
--- a/components/viz/test/fake_external_begin_frame_source.h
+++ b/components/viz/test/fake_external_begin_frame_source.h
@@ -61,7 +61,7 @@
   BeginFrameArgs current_args_;
   uint64_t next_begin_frame_number_ = BeginFrameArgs::kStartingFrameNumber;
   std::set<BeginFrameObserver*> observers_;
-  base::CancelableCallback<void(const BeginFrameArgs&)> begin_frame_task_;
+  base::CancelableOnceClosure begin_frame_task_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/components/viz/test/test_context_support.cc b/components/viz/test/test_context_support.cc
index 31c7b295..bf92ee2 100644
--- a/components/viz/test/test_context_support.cc
+++ b/components/viz/test/test_context_support.cc
@@ -105,7 +105,7 @@
   return false;
 }
 
-void* TestContextSupport::MapTransferCacheEntry(size_t serialized_size) {
+void* TestContextSupport::MapTransferCacheEntry(uint32_t serialized_size) {
   NOTIMPLEMENTED();
   return nullptr;
 }
diff --git a/components/viz/test/test_context_support.h b/components/viz/test/test_context_support.h
index e9e4d63..c69c4437 100644
--- a/components/viz/test/test_context_support.h
+++ b/components/viz/test/test_context_support.h
@@ -68,7 +68,7 @@
       uint32_t texture_id) override;
   bool ThreadsafeDiscardableTextureIsDeletedForTracing(
       uint32_t texture_id) override;
-  void* MapTransferCacheEntry(size_t serialized_size) override;
+  void* MapTransferCacheEntry(uint32_t serialized_size) override;
   void UnmapAndCreateTransferCacheEntry(uint32_t type, uint32_t id) override;
   bool ThreadsafeLockTransferCacheEntry(uint32_t entry_type,
                                         uint32_t entry_id) override;
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 07f209d..b20ddba 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1008,12 +1008,12 @@
     "indexed_db/indexed_db_transaction_coordinator.h",
     "indexed_db/indexed_db_value.cc",
     "indexed_db/indexed_db_value.h",
+    "indexed_db/leveldb/leveldb_comparator.cc",
     "indexed_db/leveldb/leveldb_comparator.h",
     "indexed_db/leveldb/leveldb_database.cc",
     "indexed_db/leveldb/leveldb_database.h",
     "indexed_db/leveldb/leveldb_env.cc",
     "indexed_db/leveldb/leveldb_env.h",
-    "indexed_db/leveldb/leveldb_factory.h",
     "indexed_db/leveldb/leveldb_iterator.cc",
     "indexed_db/leveldb/leveldb_iterator.h",
     "indexed_db/leveldb/leveldb_iterator_impl.cc",
@@ -1025,6 +1025,8 @@
     "indexed_db/list_set.h",
     "indexed_db/scopes/disjoint_range_lock_manager.cc",
     "indexed_db/scopes/disjoint_range_lock_manager.h",
+    "indexed_db/scopes/leveldb_state.cc",
+    "indexed_db/scopes/leveldb_state.h",
     "indexed_db/scopes/scope_lock.cc",
     "indexed_db/scopes/scope_lock.h",
     "indexed_db/scopes/scope_lock_range.cc",
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 3c0f81b..8afe258 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -1187,6 +1187,14 @@
   return accessibility_state->disable_hot_tracking_for_testing();
 }
 
+bool BrowserAccessibility::IsOrderedSetItem() const {
+  return node()->IsOrderedSetItem();
+}
+
+bool BrowserAccessibility::IsOrderedSet() const {
+  return node()->IsOrderedSet();
+}
+
 int32_t BrowserAccessibility::GetPosInSet() const {
   return node()->GetPosInSet();
 }
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index 6c7d3b4..e5e5d4a3 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -383,6 +383,8 @@
                                         int32_t dst_id) override;
   std::set<int32_t> GetReverseRelations(ax::mojom::IntListAttribute attr,
                                         int32_t dst_id) override;
+  bool IsOrderedSetItem() const override;
+  bool IsOrderedSet() const override;
   int32_t GetPosInSet() const override;
   int32_t GetSetSize() const override;
 
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index 0d9cc373..9e5070b 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -1198,13 +1198,11 @@
   if (![self instanceActive])
     return nil;
   if ([self internalRole] == ax::mojom::Role::kColumn) {
-    int columnIndex =
-        owner_->GetIntAttribute(ax::mojom::IntAttribute::kTableColumnIndex);
-    return [NSNumber numberWithInt:columnIndex];
+    DCHECK(owner_->node());
+    return @(owner_->node()->GetTableColColIndex());
   } else if ([self internalRole] == ax::mojom::Role::kRow) {
-    int rowIndex =
-        owner_->GetIntAttribute(ax::mojom::IntAttribute::kTableRowIndex);
-    return [NSNumber numberWithInt:rowIndex];
+    DCHECK(owner_->node());
+    return @(owner_->node()->GetTableRowRowIndex());
   }
 
   return nil;
diff --git a/content/browser/dom_storage/storage_area_impl.cc b/content/browser/dom_storage/storage_area_impl.cc
index b5cff87..c7e1ce3 100644
--- a/content/browser/dom_storage/storage_area_impl.cc
+++ b/content/browser/dom_storage/storage_area_impl.cc
@@ -84,8 +84,8 @@
       commit_rate_limiter_(options.max_commits_per_hour,
                            base::TimeDelta::FromHours(1)),
       weak_ptr_factory_(this) {
-  bindings_.set_connection_error_handler(
-      base::Bind(&StorageAreaImpl::OnConnectionError, base::Unretained(this)));
+  bindings_.set_connection_error_handler(base::BindRepeating(
+      &StorageAreaImpl::OnConnectionError, weak_ptr_factory_.GetWeakPtr()));
 }
 
 StorageAreaImpl::~StorageAreaImpl() {
@@ -146,7 +146,7 @@
 void StorageAreaImpl::ScheduleImmediateCommit() {
   if (!on_load_complete_tasks_.empty()) {
     LoadMap(base::BindOnce(&StorageAreaImpl::ScheduleImmediateCommit,
-                           base::Unretained(this)));
+                           weak_ptr_factory_.GetWeakPtr()));
     return;
   }
 
@@ -240,9 +240,9 @@
     const std::string& source,
     PutCallback callback) {
   if (!IsMapLoaded() || IsMapUpgradeNeeded()) {
-    LoadMap(base::BindOnce(&StorageAreaImpl::Put, base::Unretained(this), key,
-                           value, client_old_value, source,
-                           std::move(callback)));
+    LoadMap(base::BindOnce(&StorageAreaImpl::Put,
+                           weak_ptr_factory_.GetWeakPtr(), key, value,
+                           client_old_value, source, std::move(callback)));
     return;
   }
 
@@ -357,8 +357,9 @@
   // |client_old_value| can race. Thus any changes require checking for an
   // upgrade.
   if (!IsMapLoaded() || IsMapUpgradeNeeded()) {
-    LoadMap(base::BindOnce(&StorageAreaImpl::Delete, base::Unretained(this),
-                           key, client_old_value, source, std::move(callback)));
+    LoadMap(base::BindOnce(&StorageAreaImpl::Delete,
+                           weak_ptr_factory_.GetWeakPtr(), key,
+                           client_old_value, source, std::move(callback)));
     return;
   }
 
@@ -423,8 +424,9 @@
   // Don't check if a map upgrade is needed here and instead just create an
   // empty map ourself.
   if (!IsMapLoaded()) {
-    LoadMap(base::BindOnce(&StorageAreaImpl::DeleteAll, base::Unretained(this),
-                           source, std::move(callback)));
+    LoadMap(base::BindOnce(&StorageAreaImpl::DeleteAll,
+                           weak_ptr_factory_.GetWeakPtr(), source,
+                           std::move(callback)));
     return;
   }
 
@@ -468,7 +470,8 @@
     return;
   }
   if (!IsMapLoaded() || IsMapUpgradeNeeded()) {
-    LoadMap(base::BindOnce(&StorageAreaImpl::Get, base::Unretained(this), key,
+    LoadMap(base::BindOnce(&StorageAreaImpl::Get,
+                           weak_ptr_factory_.GetWeakPtr(), key,
                            std::move(callback)));
     return;
   }
@@ -486,7 +489,8 @@
     GetAllCallback callback) {
   // The map must always be loaded for the KEYS_ONLY_WHEN_POSSIBLE mode.
   if (map_state_ != MapState::LOADED_KEYS_AND_VALUES) {
-    LoadMap(base::BindOnce(&StorageAreaImpl::GetAll, base::Unretained(this),
+    LoadMap(base::BindOnce(&StorageAreaImpl::GetAll,
+                           weak_ptr_factory_.GetWeakPtr(),
                            std::move(complete_callback), std::move(callback)));
     return;
   }
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 70fc8ad..6cdb9cd 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -2146,27 +2146,27 @@
 
   parameters.injected_errors.pop();
   TestDownloadHttpResponse::StartServing(parameters, server_url2);
-  download->Resume(false);
+  download->Resume(true);
   WaitForInterrupt(download);
 
   parameters.injected_errors.pop();
   TestDownloadHttpResponse::StartServing(parameters, server_url2);
-  download->Resume(false);
+  download->Resume(true);
   WaitForInterrupt(download);
 
   parameters.injected_errors.pop();
   TestDownloadHttpResponse::StartServing(parameters, server_url2);
-  download->Resume(false);
+  download->Resume(true);
   WaitForInterrupt(download);
 
   parameters.injected_errors.pop();
   TestDownloadHttpResponse::StartServing(parameters, server_url2);
-  download->Resume(false);
+  download->Resume(true);
   WaitForInterrupt(download);
 
   parameters.injected_errors.pop();
   TestDownloadHttpResponse::StartServing(parameters, server_url2);
-  download->Resume(false);
+  download->Resume(true);
   WaitForCompletion(download);
 
   EXPECT_EQ(expected_hash, download->GetHash());
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index 644ddcb9..163932d 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -774,11 +774,6 @@
   return browser_context_->IsOffTheRecord();
 }
 
-bool DownloadManagerImpl::IsActiveNetworkMetered() const {
-  // TODO(shaktisahu): Call ChromeDownloadManagerDelegate to get this.
-  return false;
-}
-
 void DownloadManagerImpl::ReportBytesWasted(
     download::DownloadItemImpl* download) {
   in_progress_manager_->ReportBytesWasted(download);
diff --git a/content/browser/download/download_manager_impl.h b/content/browser/download/download_manager_impl.h
index d596137..b94e85cf 100644
--- a/content/browser/download/download_manager_impl.h
+++ b/content/browser/download/download_manager_impl.h
@@ -257,7 +257,6 @@
   base::Optional<download::DownloadEntry> GetInProgressEntry(
       download::DownloadItemImpl* download) override;
   bool IsOffTheRecord() const override;
-  bool IsActiveNetworkMetered() const override;
   void ReportBytesWasted(download::DownloadItemImpl* download) override;
 
   // Drops a download before it is created.
diff --git a/content/browser/frame_host/navigation_handle_impl.cc b/content/browser/frame_host/navigation_handle_impl.cc
index f9c428a3..7d36042 100644
--- a/content/browser/frame_host/navigation_handle_impl.cc
+++ b/content/browser/frame_host/navigation_handle_impl.cc
@@ -457,7 +457,10 @@
 }
 
 net::HostPortPair NavigationHandleImpl::GetSocketAddress() {
-  DCHECK(state_ >= WILL_PROCESS_RESPONSE);
+  // This is CANCELING because although the data comes in after
+  // WILL_PROCESS_RESPONSE, it's possible for the navigation to be cancelled
+  // after and the caller might want this value.
+  DCHECK(state_ >= CANCELING);
   return socket_address_;
 }
 
diff --git a/content/browser/indexed_db/docs/open_and_verify_leveldb_database.code2flow b/content/browser/indexed_db/docs/open_and_verify_leveldb_database.code2flow
new file mode 100644
index 0000000..bb2e241
--- /dev/null
+++ b/content/browser/indexed_db/docs/open_and_verify_leveldb_database.code2flow
@@ -0,0 +1,112 @@
+// 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.
+
+// open_and_verify_leveldb_database.pdf was created from this file by
+// https://www.code2flow.com/app
+
+function OpenLevelDB {
+  |Read free disk space|;
+  Histogram(FreeDiskSpace);
+  switch (leveldb_env::OpenDB) {
+    case Status Not OK:{
+      Histogram(LevelDBOpenErrors);
+      if (disk space < 100kb)
+        Report disk space as failure reason;
+      return leveldb_env::OpenDB error status;
+    }
+    case Status OK: {
+      Histogram(LevelDB.OpenTime);
+      return Status::OK;
+    }
+  }
+}
+
+OpenAndVerifyLevelDBDatabase;
+switch (Create IDB Directory) {
+  case Failure:
+    Histogram(FAILED_DIRECTORY);
+    return IOError("Unable to create IndexedDB database path");
+  case Success:
+}
+Create File path & blob path;
+if (File Path is too long) {
+  Histogram(ORIGIN_TOO_LONG);
+  return IOError("File path too long);
+}
+
+call OpenLevelDB;
+
+switch(OpenLevelDB status) {
+  case IOError:
+    Histogram(OPEN_NO_RECOVERY);
+    return OpenLevelDB error status;
+  case Corruption:
+    Set data loss info;
+    break;
+  case OK:
+    if (Corruption Info Exists & Last Open was corrupt) {
+      Histogram(FAILED_PRIOR_CORRUPTION);
+      db_.reset();
+      Populate data loss info
+      (previous corruption);
+      break;
+    }
+    switch (try to read schema) {
+      case Failure reading schema:
+        Histogram(FAILED_IO_ERROR_CHECKING_SCHEMA);
+        db_.reset();
+        Populate data loss info
+        (schema checking failure);
+        break;
+      case Schema is unknown:
+        Histogram(FAILED_UNKNOWN_SCHEMA);
+        db_.reset();
+        Populate data loss info
+        (unknown schema);
+        break;
+      case Success & Valid:
+        Histogram(OPEN_SUCCESS);
+        goto end;
+    }
+}
+switch(DestroyLevelDB(file_path)) {
+  case Not OK:
+    Histogram(CLEANUP_DESTROY_FAILED);
+    return DestroyLevelDB error status;
+  case OK:
+}
+call OpenLevelDB;
+switch(OpenLevelDB status) {
+  case Not OK:
+    Histogram(CLEANUP_REOPEN_FAILED);
+    return OpenLevelDB error status;
+  case OK:
+    Histogram(CLEANUP_REOPEN_SUCCESS);
+}
+
+end:
+Histogram(OPEN_SUCCESS);
+block {
+Create BackingStore;
+switch (Parse schema & metadata) {
+  case Failure:
+    Delete Database & BackingStore;
+    break;
+  case Success:
+    if (Should we clean blob journal?) {
+    switch(CleanUpBlobJournal) {
+      case OK:
+        report `Status::OK`;
+        goto done;
+      case Not OK:
+        Histogram(FAILED_CLEANUP_JOURNAL_ERROR);
+        return CleanUpBlobJournal error status;
+    }
+  }
+}
+return BackingStore error status;
+}
+
+done:
+return done;
\ No newline at end of file
diff --git a/content/browser/indexed_db/docs/open_and_verify_leveldb_database.pdf b/content/browser/indexed_db/docs/open_and_verify_leveldb_database.pdf
new file mode 100644
index 0000000..29ba7e5
--- /dev/null
+++ b/content/browser/indexed_db/docs/open_and_verify_leveldb_database.pdf
Binary files differ
diff --git a/content/browser/indexed_db/indexed_db_backing_store.cc b/content/browser/indexed_db/indexed_db_backing_store.cc
index 0654cb3..0e48fa7 100644
--- a/content/browser/indexed_db/indexed_db_backing_store.cc
+++ b/content/browser/indexed_db/indexed_db_backing_store.cc
@@ -36,7 +36,6 @@
 #include "content/browser/indexed_db/indexed_db_value.h"
 #include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
 #include "content/browser/indexed_db/leveldb/leveldb_database.h"
-#include "content/browser/indexed_db/leveldb/leveldb_factory.h"
 #include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
 #include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -65,21 +64,21 @@
 using url::Origin;
 
 namespace content {
-using indexed_db::CheckObjectStoreAndMetaDataType;
 using indexed_db::CheckIndexAndMetaDataKey;
+using indexed_db::CheckObjectStoreAndMetaDataType;
 using indexed_db::FindGreatestKeyLessThanOrEqual;
 using indexed_db::GetInt;
 using indexed_db::GetString;
 using indexed_db::GetVarInt;
-using indexed_db::HistogramOpenStatus;
-using indexed_db::IOErrorStatus;
 using indexed_db::InternalInconsistencyStatus;
 using indexed_db::InvalidDBKeyStatus;
+using indexed_db::IOErrorStatus;
 using indexed_db::PutBool;
-using indexed_db::PutInt;
-using indexed_db::PutVarInt;
-using indexed_db::PutString;
 using indexed_db::PutIDBKeyPath;
+using indexed_db::PutInt;
+using indexed_db::PutString;
+using indexed_db::PutVarInt;
+using indexed_db::ReportOpenStatus;
 
 namespace {
 
@@ -115,44 +114,6 @@
   return storage::GetIdentifierFromOrigin(origin) + "@1";
 }
 
-FilePath ComputeCorruptionFileName(const Origin& origin) {
-  return IndexedDBContextImpl::GetLevelDBFileName(origin).Append(
-      FILE_PATH_LITERAL("corruption_info.json"));
-}
-
-WARN_UNUSED_RESULT bool IsSchemaKnown(LevelDBDatabase* db, bool* known) {
-  int64_t db_schema_version = 0;
-  bool found = false;
-  Status s = GetInt(db, SchemaVersionKey::Encode(), &db_schema_version, &found);
-  if (!s.ok())
-    return false;
-  if (!found) {
-    *known = true;
-    return true;
-  }
-  if (db_schema_version < 0)
-    return false;  // Only corruption should cause this.
-  if (db_schema_version > indexed_db::kLatestKnownSchemaVersion) {
-    *known = false;
-    return true;
-  }
-
-  int64_t raw_db_data_version = 0;
-  s = GetInt(db, DataVersionKey::Encode(), &raw_db_data_version, &found);
-  if (!s.ok())
-    return false;
-  if (!found) {
-    *known = true;
-    return true;
-  }
-  if (raw_db_data_version < 0)
-    return false;  // Only corruption should cause this.
-
-  *known = IndexedDBDataFormatVersion::GetCurrent().IsAtLeast(
-      IndexedDBDataFormatVersion::Decode(raw_db_data_version));
-  return true;
-}
-
 // TODO(ericu): Error recovery. If we persistently can't read the
 // blob journal, the safe thing to do is to clear it and leak the blobs,
 // though that may be costly. Still, database/directory deletion should always
@@ -548,31 +509,11 @@
 
 }  // namespace
 
-class DefaultLevelDBFactory : public LevelDBFactory {
- public:
-  DefaultLevelDBFactory() {}
-  Status OpenLevelDB(const FilePath& file_name,
-                     const LevelDBComparator* comparator,
-                     std::unique_ptr<LevelDBDatabase>* db,
-                     bool* is_disk_full) override {
-    return LevelDBDatabase::Open(
-        file_name, comparator,
-        LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase, db, is_disk_full);
-  }
-  Status DestroyLevelDB(const FilePath& file_name) override {
-    return LevelDBDatabase::Destroy(file_name);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DefaultLevelDBFactory);
-};
-
 IndexedDBBackingStore::IndexedDBBackingStore(
     IndexedDBFactory* indexed_db_factory,
     const Origin& origin,
     const FilePath& blob_path,
     std::unique_ptr<LevelDBDatabase> db,
-    std::unique_ptr<LevelDBComparator> comparator,
     base::SequencedTaskRunner* task_runner)
     : indexed_db_factory_(indexed_db_factory),
       origin_(origin),
@@ -580,7 +521,6 @@
       origin_identifier_(ComputeOriginIdentifier(origin)),
       task_runner_(task_runner),
       db_(std::move(db)),
-      comparator_(std::move(comparator)),
       active_blob_registry_(this),
       committing_transaction_count_(0) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
@@ -594,9 +534,6 @@
     for (const auto& pid : child_process_ids_granted_)
       policy->RevokeAllPermissionsForFile(pid, blob_path_);
   }
-  // db_'s destructor uses comparator_. The order of destruction is important.
-  db_.reset();
-  comparator_.reset();
 }
 
 IndexedDBBackingStore::RecordIdentifier::RecordIdentifier(
@@ -615,123 +552,11 @@
 constexpr const base::TimeDelta
     IndexedDBBackingStore::kInitialJournalCleaningWindowTime;
 
-// static
-scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
-    IndexedDBFactory* indexed_db_factory,
-    const Origin& origin,
-    const FilePath& path_base,
-    IndexedDBDataLossInfo* data_loss_info,
-    bool* disk_full,
-    base::SequencedTaskRunner* task_runner,
-    bool clean_journal,
-    Status* status) {
-  DefaultLevelDBFactory leveldb_factory;
-  return IndexedDBBackingStore::Open(
-      indexed_db_factory, origin, path_base, data_loss_info, disk_full,
-      &leveldb_factory, task_runner, clean_journal, status);
-}
-
-Status IndexedDBBackingStore::DestroyBackingStore(const FilePath& path_base,
-                                                  const Origin& origin) {
-  const FilePath file_path =
-      path_base.Append(IndexedDBContextImpl::GetLevelDBFileName(origin));
-  DefaultLevelDBFactory leveldb_factory;
-  return leveldb_factory.DestroyLevelDB(file_path);
-}
-
-Status IndexedDBBackingStore::AnyDatabaseContainsBlobs(
-    LevelDBTransaction* transaction,
-    bool* blobs_exist) {
+leveldb::Status IndexedDBBackingStore::Initialize(bool clean_live_journal) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
-
-  Status status = leveldb::Status::OK();
-  std::vector<base::string16> names;
-  IndexedDBMetadataCoding metadata_coding;
-  status = metadata_coding.ReadDatabaseNames(transaction, origin_identifier_,
-                                             &names);
-  if (!status.ok())
-    return status;
-
-  *blobs_exist = false;
-  for (const auto& name : names) {
-    IndexedDBDatabaseMetadata metadata;
-    bool found = false;
-    status = metadata_coding.ReadMetadataForDatabaseName(
-        transaction, origin_identifier_, name, &metadata, &found);
-    if (!found)
-      return Status::NotFound("Metadata not found for \"%s\".",
-                              base::UTF16ToUTF8(name));
-    for (const auto& store_id_metadata_pair : metadata.object_stores) {
-      std::unique_ptr<LevelDBIterator> iterator = transaction->CreateIterator();
-      std::string min_key = BlobEntryKey::EncodeMinKeyForObjectStore(
-          metadata.id, store_id_metadata_pair.first);
-      std::string max_key = BlobEntryKey::EncodeStopKeyForObjectStore(
-          metadata.id, store_id_metadata_pair.first);
-      status = iterator->Seek(base::StringPiece(min_key));
-      if (status.IsNotFound()) {
-        status = Status::OK();
-        continue;
-      }
-      if (!status.ok())
-        return status;
-      if (iterator->IsValid() &&
-          comparator_->Compare(iterator->Key(), base::StringPiece(max_key)) <
-              0) {
-        *blobs_exist = true;
-        return Status::OK();
-      }
-    }
-
-    if (!status.ok())
-      return status;
-  }
-  return Status::OK();
-}
-
-Status IndexedDBBackingStore::RevertSchemaToV2() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  const std::string schema_version_key = SchemaVersionKey::Encode();
-  scoped_refptr<LevelDBTransaction> transaction =
-      IndexedDBClassFactory::Get()->CreateLevelDBTransaction(db_.get());
-
-  PutInt(transaction.get(), schema_version_key, 2);
-  Status s = transaction->Commit();
-  if (!s.ok())
-    INTERNAL_WRITE_ERROR_UNTESTED(REVERT_SCHEMA_TO_V2);
-  return s;
-}
-
-V2SchemaCorruptionStatus IndexedDBBackingStore::HasV2SchemaCorruption() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  const std::string schema_version_key = SchemaVersionKey::Encode();
-  scoped_refptr<LevelDBTransaction> transaction =
-      IndexedDBClassFactory::Get()->CreateLevelDBTransaction(db_.get());
-
-  int64_t db_schema_version = 0;
-  bool found = false;
-  Status s =
-      GetInt(transaction.get(), schema_version_key, &db_schema_version, &found);
-  if (!s.ok())
-    return V2SchemaCorruptionStatus::kUnknown;
-  if (db_schema_version != 2)
-    return V2SchemaCorruptionStatus::kNo;
-
-  bool has_blobs = false;
-  s = AnyDatabaseContainsBlobs(transaction.get(), &has_blobs);
-  if (!s.ok())
-    return V2SchemaCorruptionStatus::kUnknown;
-  if (!has_blobs)
-    return V2SchemaCorruptionStatus::kNo;
-
-  s = transaction->Commit();
-  if (!s.ok())
-    return V2SchemaCorruptionStatus::kUnknown;
-  return V2SchemaCorruptionStatus::kYes;
-}
-
-WARN_UNUSED_RESULT Status IndexedDBBackingStore::SetUpMetadata() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-
+#if DCHECK_IS_ON()
+  DCHECK(!initialized_);
+#endif
   const IndexedDBDataFormatVersion latest_known_data_version =
       IndexedDBDataFormatVersion::GetCurrent();
   const std::string schema_version_key = SchemaVersionKey::Encode();
@@ -746,7 +571,7 @@
   Status s =
       GetInt(transaction.get(), schema_version_key, &db_schema_version, &found);
   if (!s.ok()) {
-    INTERNAL_READ_ERROR_UNTESTED(SET_UP_METADATA);
+    INTERNAL_READ_ERROR(SET_UP_METADATA);
     return s;
   }
   indexed_db::ReportSchemaVersion(db_schema_version, origin_);
@@ -867,14 +692,127 @@
   DCHECK(db_data_version == latest_known_data_version);
 
   s = transaction->Commit();
-  if (!s.ok())
+  if (!s.ok()) {
     INTERNAL_WRITE_ERROR_UNTESTED(SET_UP_METADATA);
+    return s;
+  }
+
+  if (clean_live_journal)
+    s = CleanUpBlobJournal(LiveBlobJournalKey::Encode());
+  if (!s.ok()) {
+    indexed_db::ReportOpenStatus(
+        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_FAILED_CLEANUP_JOURNAL_ERROR,
+        origin_);
+    return s;
+  }
+#if DCHECK_IS_ON()
+  initialized_ = true;
+#endif
   return s;
 }
 
+Status IndexedDBBackingStore::AnyDatabaseContainsBlobs(
+    LevelDBTransaction* transaction,
+    bool* blobs_exist) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+  Status status = leveldb::Status::OK();
+  std::vector<base::string16> names;
+  IndexedDBMetadataCoding metadata_coding;
+  status = metadata_coding.ReadDatabaseNames(transaction, origin_identifier_,
+                                             &names);
+  if (!status.ok())
+    return status;
+
+  *blobs_exist = false;
+  for (const auto& name : names) {
+    IndexedDBDatabaseMetadata metadata;
+    bool found = false;
+    status = metadata_coding.ReadMetadataForDatabaseName(
+        transaction, origin_identifier_, name, &metadata, &found);
+    if (!found)
+      return Status::NotFound("Metadata not found for \"%s\".",
+                              base::UTF16ToUTF8(name));
+    for (const auto& store_id_metadata_pair : metadata.object_stores) {
+      std::unique_ptr<LevelDBIterator> iterator = transaction->CreateIterator();
+      std::string min_key = BlobEntryKey::EncodeMinKeyForObjectStore(
+          metadata.id, store_id_metadata_pair.first);
+      std::string max_key = BlobEntryKey::EncodeStopKeyForObjectStore(
+          metadata.id, store_id_metadata_pair.first);
+      status = iterator->Seek(base::StringPiece(min_key));
+      if (status.IsNotFound()) {
+        status = Status::OK();
+        continue;
+      }
+      if (!status.ok())
+        return status;
+      if (iterator->IsValid() &&
+          db()->Comparator()->Compare(iterator->Key(),
+                                      base::StringPiece(max_key)) < 0) {
+        *blobs_exist = true;
+        return Status::OK();
+      }
+    }
+
+    if (!status.ok())
+      return status;
+  }
+  return Status::OK();
+}
+
+Status IndexedDBBackingStore::RevertSchemaToV2() {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
+  const std::string schema_version_key = SchemaVersionKey::Encode();
+  scoped_refptr<LevelDBTransaction> transaction =
+      IndexedDBClassFactory::Get()->CreateLevelDBTransaction(db_.get());
+
+  PutInt(transaction.get(), schema_version_key, 2);
+  Status s = transaction->Commit();
+  if (!s.ok())
+    INTERNAL_WRITE_ERROR_UNTESTED(REVERT_SCHEMA_TO_V2);
+  return s;
+}
+
+V2SchemaCorruptionStatus IndexedDBBackingStore::HasV2SchemaCorruption() {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
+  const std::string schema_version_key = SchemaVersionKey::Encode();
+  scoped_refptr<LevelDBTransaction> transaction =
+      IndexedDBClassFactory::Get()->CreateLevelDBTransaction(db_.get());
+
+  int64_t db_schema_version = 0;
+  bool found = false;
+  Status s =
+      GetInt(transaction.get(), schema_version_key, &db_schema_version, &found);
+  if (!s.ok())
+    return V2SchemaCorruptionStatus::kUnknown;
+  if (db_schema_version != 2)
+    return V2SchemaCorruptionStatus::kNo;
+
+  bool has_blobs = false;
+  s = AnyDatabaseContainsBlobs(transaction.get(), &has_blobs);
+  if (!s.ok())
+    return V2SchemaCorruptionStatus::kUnknown;
+  if (!has_blobs)
+    return V2SchemaCorruptionStatus::kNo;
+
+  s = transaction->Commit();
+  if (!s.ok())
+    return V2SchemaCorruptionStatus::kUnknown;
+  return V2SchemaCorruptionStatus::kYes;
+}
+
 leveldb::Status IndexedDBBackingStore::GetCompleteMetadata(
     std::vector<IndexedDBDatabaseMetadata>* output) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
 
   IndexedDBMetadataCoding metadata_coding;
   leveldb::Status status = leveldb::Status::OK();
@@ -902,49 +840,11 @@
 }
 
 // static
-bool IndexedDBBackingStore::ReadCorruptionInfo(const FilePath& path_base,
-                                               const Origin& origin,
-                                               std::string* message) {
-  const FilePath info_path =
-      path_base.Append(ComputeCorruptionFileName(origin));
-
-  if (IsPathTooLong(info_path))
-    return false;
-
-  const int64_t kMaxJsonLength = 4096;
-  int64_t file_size = 0;
-  if (!GetFileSize(info_path, &file_size))
-    return false;
-  if (!file_size || file_size > kMaxJsonLength) {
-    base::DeleteFile(info_path, false);
-    return false;
-  }
-
-  base::File file(info_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
-  bool success = false;
-  if (file.IsValid()) {
-    std::string input_js(file_size, '\0');
-    if (file_size == file.Read(0, base::data(input_js), file_size)) {
-      base::JSONReader reader;
-      std::unique_ptr<base::DictionaryValue> val(
-          base::DictionaryValue::From(reader.ReadToValue(input_js)));
-      if (val)
-        success = val->GetString("message", message);
-    }
-    file.Close();
-  }
-
-  base::DeleteFile(info_path, false);
-
-  return success;
-}
-
-// static
 bool IndexedDBBackingStore::RecordCorruptionInfo(const FilePath& path_base,
                                                  const Origin& origin,
                                                  const std::string& message) {
   const FilePath info_path =
-      path_base.Append(ComputeCorruptionFileName(origin));
+      path_base.Append(indexed_db::ComputeCorruptionFileName(origin));
   if (IsPathTooLong(info_path))
     return false;
 
@@ -961,220 +861,6 @@
   return size_t(written) == output_js.length();
 }
 
-// static
-scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
-    IndexedDBFactory* indexed_db_factory,
-    const Origin& origin,
-    const FilePath& path_base,
-    IndexedDBDataLossInfo* data_loss_info,
-    bool* is_disk_full,
-    LevelDBFactory* leveldb_factory,
-    base::SequencedTaskRunner* task_runner,
-    bool clean_journal,
-    Status* status) {
-  IDB_TRACE("IndexedDBBackingStore::Open");
-  DCHECK(!path_base.empty());
-  *is_disk_full = false;
-
-  data_loss_info->status = blink::mojom::IDBDataLoss::None;
-  *status = Status::OK();
-
-  std::unique_ptr<LevelDBComparator> comparator(std::make_unique<Comparator>());
-
-  if (!base::IsStringASCII(path_base.AsUTF8Unsafe())) {
-    HistogramOpenStatus(
-        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_ATTEMPT_NON_ASCII, origin);
-  }
-  if (!base::CreateDirectory(path_base)) {
-    *status = Status::IOError("Unable to create IndexedDB database path");
-    LOG(ERROR) << status->ToString() << ": \"" << path_base.AsUTF8Unsafe()
-               << "\"";
-    HistogramOpenStatus(
-        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY, origin);
-    return scoped_refptr<IndexedDBBackingStore>();
-  }
-
-  const FilePath file_path =
-      path_base.Append(IndexedDBContextImpl::GetLevelDBFileName(origin));
-  const FilePath blob_path =
-      path_base.Append(IndexedDBContextImpl::GetBlobStoreFileName(origin));
-
-  if (IsPathTooLong(file_path)) {
-    *status = Status::IOError("File path too long");
-    HistogramOpenStatus(
-        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_ORIGIN_TOO_LONG, origin);
-    return scoped_refptr<IndexedDBBackingStore>();
-  }
-
-  std::unique_ptr<LevelDBDatabase> db;
-  *status = leveldb_factory->OpenLevelDB(
-      file_path, comparator.get(), &db, is_disk_full);
-
-  DCHECK(!db == !status->ok());
-  if (!status->ok()) {
-    if (leveldb_env::IndicatesDiskFull(*status)) {
-      *is_disk_full = true;
-    } else if (status->IsCorruption()) {
-      data_loss_info->status = blink::mojom::IDBDataLoss::Total;
-      data_loss_info->message = leveldb_env::GetCorruptionMessage(*status);
-    }
-  }
-
-  bool is_schema_known = false;
-  if (db) {
-    std::string corruption_message;
-    if (ReadCorruptionInfo(path_base, origin, &corruption_message)) {
-      LOG(ERROR) << "IndexedDB recovering from a corrupted (and deleted) "
-                    "database.";
-      HistogramOpenStatus(
-          indexed_db::INDEXED_DB_BACKING_STORE_OPEN_FAILED_PRIOR_CORRUPTION,
-          origin);
-      db.reset();
-      data_loss_info->status = blink::mojom::IDBDataLoss::Total;
-      data_loss_info->message =
-          "IndexedDB (database was corrupt): " + corruption_message;
-    } else if (!IsSchemaKnown(db.get(), &is_schema_known)) {
-      LOG(ERROR) << "IndexedDB had IO error checking schema, treating it as "
-                    "failure to open";
-      HistogramOpenStatus(
-          indexed_db::
-              INDEXED_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA,
-          origin);
-      db.reset();
-      data_loss_info->status = blink::mojom::IDBDataLoss::Total;
-      data_loss_info->message = "I/O error checking schema";
-    } else if (!is_schema_known) {
-      LOG(ERROR) << "IndexedDB backing store had unknown schema, treating it "
-                    "as failure to open";
-      HistogramOpenStatus(
-          indexed_db::INDEXED_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA,
-          origin);
-      db.reset();
-      data_loss_info->status = blink::mojom::IDBDataLoss::Total;
-      data_loss_info->message = "Unknown schema";
-    }
-  }
-
-  DCHECK(status->ok() || !is_schema_known || status->IsIOError() ||
-         status->IsCorruption());
-
-  if (db) {
-    HistogramOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_SUCCESS,
-                        origin);
-  } else if (status->IsIOError()) {
-    LOG(ERROR) << "Unable to open backing store, not trying to recover - "
-               << status->ToString();
-    HistogramOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_NO_RECOVERY,
-                        origin);
-    return scoped_refptr<IndexedDBBackingStore>();
-  } else {
-    DCHECK(!is_schema_known || status->IsCorruption());
-    LOG(ERROR) << "IndexedDB backing store open failed, attempting cleanup";
-    *status = leveldb_factory->DestroyLevelDB(file_path);
-    if (!status->ok()) {
-      LOG(ERROR) << "IndexedDB backing store cleanup failed";
-      HistogramOpenStatus(
-          indexed_db::INDEXED_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED,
-          origin);
-      return scoped_refptr<IndexedDBBackingStore>();
-    }
-
-    LOG(ERROR) << "IndexedDB backing store cleanup succeeded, reopening";
-    *status =
-        leveldb_factory->OpenLevelDB(file_path, comparator.get(), &db, nullptr);
-    if (!status->ok()) {
-      DCHECK(!db);
-      LOG(ERROR) << "IndexedDB backing store reopen after recovery failed";
-      HistogramOpenStatus(
-          indexed_db::INDEXED_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED,
-          origin);
-      return scoped_refptr<IndexedDBBackingStore>();
-    }
-    HistogramOpenStatus(
-        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS,
-        origin);
-  }
-
-  base::trace_event::MemoryDumpManager::GetInstance()
-      ->RegisterDumpProviderWithSequencedTaskRunner(
-          db.get(), "IndexedDBBackingStore", task_runner,
-          base::trace_event::MemoryDumpProvider::Options());
-
-  scoped_refptr<IndexedDBBackingStore> backing_store =
-      Create(indexed_db_factory, origin, blob_path, std::move(db),
-             std::move(comparator), task_runner, status);
-
-  if (clean_journal && backing_store.get()) {
-    *status = backing_store->CleanUpBlobJournal(LiveBlobJournalKey::Encode());
-    if (!status->ok()) {
-      HistogramOpenStatus(
-          indexed_db::
-              INDEXED_DB_BACKING_STORE_OPEN_FAILED_CLEANUP_JOURNAL_ERROR,
-          origin);
-      return scoped_refptr<IndexedDBBackingStore>();
-    }
-  }
-  return backing_store;
-}
-
-// static
-scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory(
-    const Origin& origin,
-    base::SequencedTaskRunner* task_runner,
-    Status* status) {
-  DefaultLevelDBFactory leveldb_factory;
-  return IndexedDBBackingStore::OpenInMemory(origin, &leveldb_factory,
-                                             task_runner, status);
-}
-
-// static
-scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory(
-    const Origin& origin,
-    LevelDBFactory* leveldb_factory,
-    base::SequencedTaskRunner* task_runner,
-    Status* status) {
-  IDB_TRACE("IndexedDBBackingStore::OpenInMemory");
-
-  std::unique_ptr<LevelDBComparator> comparator(std::make_unique<Comparator>());
-  std::unique_ptr<LevelDBDatabase> db =
-      LevelDBDatabase::OpenInMemory(comparator.get());
-  if (!db) {
-    LOG(ERROR) << "LevelDBDatabase::OpenInMemory failed.";
-    HistogramOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_MEMORY_FAILED,
-                        origin);
-    return scoped_refptr<IndexedDBBackingStore>();
-  }
-  HistogramOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_MEMORY_SUCCESS,
-                      origin);
-  base::trace_event::MemoryDumpManager::GetInstance()
-      ->RegisterDumpProviderWithSequencedTaskRunner(
-          db.get(), "IndexedDBBackingStore", task_runner,
-          base::trace_event::MemoryDumpProvider::Options());
-
-  return Create(nullptr /* indexed_db_factory */, origin, FilePath(),
-                std::move(db), std::move(comparator), task_runner, status);
-}
-
-// static
-scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Create(
-    IndexedDBFactory* indexed_db_factory,
-    const Origin& origin,
-    const FilePath& blob_path,
-    std::unique_ptr<LevelDBDatabase> db,
-    std::unique_ptr<LevelDBComparator> comparator,
-    base::SequencedTaskRunner* task_runner,
-    Status* status) {
-  // TODO(jsbell): Handle comparator name changes.
-  scoped_refptr<IndexedDBBackingStore> backing_store(new IndexedDBBackingStore(
-      indexed_db_factory, origin, blob_path, std::move(db),
-      std::move(comparator), task_runner));
-  *status = backing_store->SetUpMetadata();
-  if (!status->ok())
-    return scoped_refptr<IndexedDBBackingStore>();
-
-  return backing_store;
-}
-
 void IndexedDBBackingStore::GrantChildProcessPermissions(int child_process_id) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
@@ -1187,6 +873,9 @@
 
 Status IndexedDBBackingStore::DeleteDatabase(const base::string16& name) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
 
   IDB_TRACE("IndexedDBBackingStore::DeleteDatabase");
   std::unique_ptr<LevelDBDirectTransaction> transaction =
@@ -1255,6 +944,9 @@
 
 void IndexedDBBackingStore::Compact() {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   db_->CompactAll();
 }
 
@@ -1265,6 +957,9 @@
     const IndexedDBKey& key,
     IndexedDBValue* record) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
 
   IDB_TRACE("IndexedDBBackingStore::GetRecord");
   if (!KeyPrefix::ValidIds(database_id, object_store_id))
@@ -1323,6 +1018,9 @@
     IndexedDBValue* value,
     RecordIdentifier* record_identifier) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
 
   IDB_TRACE("IndexedDBBackingStore::PutRecord");
   if (!KeyPrefix::ValidIds(database_id, object_store_id))
@@ -1366,6 +1064,9 @@
     int64_t database_id,
     int64_t object_store_id) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
 
   IDB_TRACE("IndexedDBBackingStore::ClearObjectStore");
   if (!KeyPrefix::ValidIds(database_id, object_store_id))
@@ -1414,6 +1115,9 @@
     int64_t object_store_id,
     const IndexedDBKeyRange& key_range) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
 
   Status s;
   std::unique_ptr<IndexedDBBackingStore::Cursor> start_cursor =
@@ -1467,6 +1171,9 @@
     int64_t database_id,
     int64_t object_store_id,
     int64_t* key_generator_current_number) {
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   if (!KeyPrefix::ValidIds(database_id, object_store_id))
     return InvalidDBKeyStatus();
   LevelDBTransaction* leveldb_transaction = transaction->transaction();
@@ -1539,6 +1246,9 @@
     int64_t object_store_id,
     int64_t new_number,
     bool check_current) {
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   if (!KeyPrefix::ValidIds(database_id, object_store_id))
     return InvalidDBKeyStatus();
 
@@ -1568,6 +1278,9 @@
     const IndexedDBKey& key,
     RecordIdentifier* found_record_identifier,
     bool* found) {
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   IDB_TRACE("IndexedDBBackingStore::KeyExistsInObjectStore");
   if (!KeyPrefix::ValidIds(database_id, object_store_id))
     return InvalidDBKeyStatus();
@@ -1809,6 +1522,9 @@
     int64_t database_id,
     const Transaction::WriteDescriptor& descriptor,
     Transaction::ChainedBlobWriter* chained_blob_writer) {
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   if (!MakeIDBBlobDirectory(blob_path_, database_id, descriptor.key()))
     return false;
 
@@ -1857,6 +1573,9 @@
 void IndexedDBBackingStore::ReportBlobUnused(int64_t database_id,
                                              int64_t blob_key) {
   DCHECK(KeyPrefix::IsValidDatabaseId(database_id));
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   bool all_blobs = blob_key == DatabaseMetaDataKey::kAllBlobsKey;
   DCHECK(all_blobs || DatabaseMetaDataKey::IsValidBlobKey(blob_key));
   scoped_refptr<LevelDBTransaction> transaction =
@@ -1920,6 +1639,9 @@
 // HasLastBackingStoreReference.  It's safe because if the backing store is
 // deleted, the timer will automatically be canceled on destruction.
 void IndexedDBBackingStore::StartJournalCleaningTimer() {
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   ++num_aggregated_journal_cleaning_requests_;
 
   if (execute_journal_cleaning_on_no_txns_)
@@ -2114,6 +1836,9 @@
     int64_t object_store_id,
     int64_t index_id) {
   IDB_TRACE("IndexedDBBackingStore::ClearIndex");
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
     return InvalidDBKeyStatus();
   LevelDBTransaction* leveldb_transaction = transaction->transaction();
@@ -2139,6 +1864,9 @@
     const IndexedDBKey& key,
     const RecordIdentifier& record_identifier) {
   IDB_TRACE("IndexedDBBackingStore::PutIndexDataForRecord");
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   DCHECK(key.IsValid());
   if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
     return InvalidDBKeyStatus();
@@ -2171,6 +1899,9 @@
     std::string* found_encoded_primary_key,
     bool* found) {
   IDB_TRACE("IndexedDBBackingStore::FindKeyInIndex");
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   DCHECK(KeyPrefix::ValidIds(database_id, object_store_id, index_id));
 
   DCHECK(found_encoded_primary_key->empty());
@@ -2226,6 +1957,9 @@
     const IndexedDBKey& key,
     std::unique_ptr<IndexedDBKey>* primary_key) {
   IDB_TRACE("IndexedDBBackingStore::GetPrimaryKeyViaIndex");
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
     return InvalidDBKeyStatus();
 
@@ -2260,6 +1994,9 @@
     std::unique_ptr<IndexedDBKey>* found_primary_key,
     bool* exists) {
   IDB_TRACE("IndexedDBBackingStore::KeyExistsInIndex");
+#if DCHECK_IS_ON()
+  DCHECK(initialized_);
+#endif
   if (!KeyPrefix::ValidIds(database_id, object_store_id, index_id))
     return InvalidDBKeyStatus();
 
diff --git a/content/browser/indexed_db/indexed_db_backing_store.h b/content/browser/indexed_db/indexed_db_backing_store.h
index 3646a91..1f5f3ee4 100644
--- a/content/browser/indexed_db/indexed_db_backing_store.h
+++ b/content/browser/indexed_db/indexed_db_backing_store.h
@@ -56,8 +56,6 @@
 class IndexedDBFactory;
 class LevelDBComparator;
 class LevelDBDatabase;
-class LevelDBFactory;
-struct IndexedDBDataLossInfo;
 struct IndexedDBValue;
 
 namespace indexed_db_backing_store_unittest {
@@ -71,6 +69,8 @@
   kYes = 2,
 };
 
+// All interaction with this class should be done on the task runner given to
+// Open.
 class CONTENT_EXPORT IndexedDBBackingStore
     : public base::RefCounted<IndexedDBBackingStore> {
  public:
@@ -391,6 +391,16 @@
   static constexpr const base::TimeDelta kInitialJournalCleaningWindowTime =
       base::TimeDelta::FromSeconds(2);
 
+  IndexedDBBackingStore(IndexedDBFactory* indexed_db_factory,
+                        const url::Origin& origin,
+                        const base::FilePath& blob_path,
+                        std::unique_ptr<LevelDBDatabase> db,
+                        base::SequencedTaskRunner* task_runner);
+
+  // Initializes the backing store. This must be called before doing any
+  // operations or method calls on this object.
+  leveldb::Status Initialize(bool clean_live_blob_journal);
+
   const url::Origin& origin() const { return origin_; }
   IndexedDBFactory* factory() const { return indexed_db_factory_; }
   base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); }
@@ -399,44 +409,12 @@
     return &active_blob_registry_;
   }
 
-  static scoped_refptr<IndexedDBBackingStore> Open(
-      IndexedDBFactory* indexed_db_factory,
-      const url::Origin& origin,
-      const base::FilePath& path_base,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      base::SequencedTaskRunner* task_runner,
-      bool clean_journal,
-      leveldb::Status* status);
-  static scoped_refptr<IndexedDBBackingStore> Open(
-      IndexedDBFactory* indexed_db_factory,
-      const url::Origin& origin,
-      const base::FilePath& path_base,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      LevelDBFactory* leveldb_factory,
-      base::SequencedTaskRunner* task_runner,
-      bool clean_journal,
-      leveldb::Status* status);
-  static scoped_refptr<IndexedDBBackingStore> OpenInMemory(
-      const url::Origin& origin,
-      base::SequencedTaskRunner* task_runner,
-      leveldb::Status* status);
-  static scoped_refptr<IndexedDBBackingStore> OpenInMemory(
-      const url::Origin& origin,
-      LevelDBFactory* leveldb_factory,
-      base::SequencedTaskRunner* task_runner,
-      leveldb::Status* status);
-
   void GrantChildProcessPermissions(int child_process_id);
 
   // Compact is public for testing.
   virtual void Compact();
   virtual leveldb::Status DeleteDatabase(const base::string16& name);
 
-  // Assumes caller has already closed the backing store.
-  static leveldb::Status DestroyBackingStore(const base::FilePath& path_base,
-                                             const url::Origin& origin);
   static bool RecordCorruptionInfo(const base::FilePath& path_base,
                                    const url::Origin& origin,
                                    const std::string& message);
@@ -592,14 +570,6 @@
 
  protected:
   friend class base::RefCounted<IndexedDBBackingStore>;
-
-  IndexedDBBackingStore(
-      IndexedDBFactory* indexed_db_factory,
-      const url::Origin& origin,
-      const base::FilePath& blob_path,
-      std::unique_ptr<LevelDBDatabase> db,
-      std::unique_ptr<LevelDBComparator> comparator,
-      base::SequencedTaskRunner* task_runner);
   virtual ~IndexedDBBackingStore();
 
   bool is_incognito() const { return !indexed_db_factory_; }
@@ -607,8 +577,6 @@
   leveldb::Status AnyDatabaseContainsBlobs(LevelDBTransaction* transaction,
                                            bool* blobs_exist);
 
-  leveldb::Status SetUpMetadata();
-
   // TODO(dmurph): Move this completely to IndexedDBMetadataFactory.
   leveldb::Status GetCompleteMetadata(
       std::vector<blink::IndexedDBDatabaseMetadata>* output);
@@ -633,23 +601,6 @@
   void CleanPrimaryJournalIgnoreReturn();
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(
-      indexed_db_backing_store_unittest::IndexedDBBackingStoreTest,
-      ReadCorruptionInfo);
-
-  static scoped_refptr<IndexedDBBackingStore> Create(
-      IndexedDBFactory* indexed_db_factory,
-      const url::Origin& origin,
-      const base::FilePath& blob_path,
-      std::unique_ptr<LevelDBDatabase> db,
-      std::unique_ptr<LevelDBComparator> comparator,
-      base::SequencedTaskRunner* task_runner,
-      leveldb::Status* status);
-
-  static bool ReadCorruptionInfo(const base::FilePath& path_base,
-                                 const url::Origin& origin,
-                                 std::string* message);
-
   leveldb::Status FindKeyInIndex(
       IndexedDBBackingStore::Transaction* transaction,
       int64_t database_id,
@@ -704,9 +655,8 @@
 #endif
 
   std::unique_ptr<LevelDBDatabase> db_;
-  std::unique_ptr<LevelDBComparator> comparator_;
-  // Whenever blobs are registered in active_blob_registry_, indexed_db_factory_
-  // will hold a reference to this backing store.
+  // Whenever blobs are registered in active_blob_registry_,
+  // indexed_db_factory_ will hold a reference to this backing store.
   IndexedDBActiveBlobRegistry active_blob_registry_;
   base::OneShotTimer close_timer_;
   std::unique_ptr<IndexedDBPreCloseTaskQueue> pre_close_task_queue_;
@@ -715,7 +665,9 @@
   // complete. While > 0, temporary journal entries may exist so out-of-band
   // journal cleaning must be deferred.
   size_t committing_transaction_count_;
-
+#if DCHECK_IS_ON()
+  bool initialized_ = false;
+#endif
   DISALLOW_COPY_AND_ASSIGN(IndexedDBBackingStore);
 };
 
diff --git a/content/browser/indexed_db/indexed_db_backing_store_unittest.cc b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
index 5ac3c10..a09b20b 100644
--- a/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
@@ -27,7 +27,6 @@
 #include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
 #include "content/browser/indexed_db/indexed_db_metadata_coding.h"
 #include "content/browser/indexed_db/indexed_db_value.h"
-#include "content/browser/indexed_db/leveldb/leveldb_factory.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
@@ -52,84 +51,25 @@
 namespace content {
 namespace indexed_db_backing_store_unittest {
 
-static const size_t kDefaultMaxOpenIteratorsPerDatabase = 50;
-
 // Write |content| to |file|. Returns true on success.
 bool WriteFile(const base::FilePath& file, base::StringPiece content) {
   int write_size = base::WriteFile(file, content.data(), content.length());
   return write_size >= 0 && write_size == static_cast<int>(content.length());
 }
 
-class Comparator : public LevelDBComparator {
- public:
-  int Compare(const base::StringPiece& a,
-              const base::StringPiece& b) const override {
-    return content::Compare(a, b, false /*index_keys*/);
-  }
-  const char* Name() const override { return "idb_cmp1"; }
-};
-
-class DefaultLevelDBFactory : public LevelDBFactory {
- public:
-  DefaultLevelDBFactory() {}
-
-  leveldb::Status OpenLevelDB(const base::FilePath& file_name,
-                              const LevelDBComparator* comparator,
-                              std::unique_ptr<LevelDBDatabase>* db,
-                              bool* is_disk_full) override {
-    return LevelDBDatabase::Open(file_name, comparator,
-                                 kDefaultMaxOpenIteratorsPerDatabase, db,
-                                 is_disk_full);
-  }
-  leveldb::Status DestroyLevelDB(const base::FilePath& file_name) override {
-    return LevelDBDatabase::Destroy(file_name);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DefaultLevelDBFactory);
-};
-
 class TestableIndexedDBBackingStore : public IndexedDBBackingStore {
  public:
-  static scoped_refptr<TestableIndexedDBBackingStore> Open(
-      IndexedDBFactory* indexed_db_factory,
-      const Origin& origin,
-      const base::FilePath& path_base,
-      LevelDBFactory* leveldb_factory,
-      base::SequencedTaskRunner* task_runner,
-      leveldb::Status* status) {
-    DCHECK(!path_base.empty());
-
-    std::unique_ptr<LevelDBComparator> comparator =
-        std::make_unique<Comparator>();
-
-    if (!base::CreateDirectory(path_base)) {
-      *status = leveldb::Status::IOError("Unable to create base dir");
-      return scoped_refptr<TestableIndexedDBBackingStore>();
-    }
-
-    const base::FilePath file_path = path_base.AppendASCII("test_db_path");
-    const base::FilePath blob_path = path_base.AppendASCII("test_blob_path");
-
-    std::unique_ptr<LevelDBDatabase> db;
-    bool is_disk_full = false;
-    *status = leveldb_factory->OpenLevelDB(file_path, comparator.get(), &db,
-                                           &is_disk_full);
-
-    if (!db || !status->ok())
-      return scoped_refptr<TestableIndexedDBBackingStore>();
-
-    scoped_refptr<TestableIndexedDBBackingStore> backing_store(
-        new TestableIndexedDBBackingStore(indexed_db_factory, origin, blob_path,
-                                          std::move(db), std::move(comparator),
-                                          task_runner));
-
-    *status = backing_store->SetUpMetadata();
-    if (!status->ok())
-      return scoped_refptr<TestableIndexedDBBackingStore>();
-
-    return backing_store;
-  }
+  TestableIndexedDBBackingStore(IndexedDBFactory* indexed_db_factory,
+                                const url::Origin& origin,
+                                const base::FilePath& blob_path,
+                                std::unique_ptr<LevelDBDatabase> db,
+                                base::SequencedTaskRunner* task_runner)
+      : IndexedDBBackingStore(indexed_db_factory,
+                              origin,
+                              blob_path,
+                              std::move(db),
+                              task_runner),
+        database_id_(0) {}
 
   const std::vector<IndexedDBBackingStore::Transaction::WriteDescriptor>&
   writes() const {
@@ -175,20 +115,6 @@
   }
 
  private:
-  TestableIndexedDBBackingStore(IndexedDBFactory* indexed_db_factory,
-                                const Origin& origin,
-                                const base::FilePath& blob_path,
-                                std::unique_ptr<LevelDBDatabase> db,
-                                std::unique_ptr<LevelDBComparator> comparator,
-                                base::SequencedTaskRunner* task_runner)
-      : IndexedDBBackingStore(indexed_db_factory,
-                              origin,
-                              blob_path,
-                              std::move(db),
-                              std::move(comparator),
-                              task_runner),
-        database_id_(0) {}
-
   int64_t database_id_;
   std::vector<Transaction::WriteDescriptor> writes_;
 
@@ -202,15 +128,18 @@
 class TestIDBFactory : public IndexedDBFactoryImpl {
  public:
   explicit TestIDBFactory(IndexedDBContextImpl* idb_context)
-      : IndexedDBFactoryImpl(idb_context, base::DefaultClock::GetInstance()) {}
+      : IndexedDBFactoryImpl(idb_context,
+                             indexed_db::GetDefaultLevelDBFactory(),
+                             base::DefaultClock::GetInstance()) {}
 
   scoped_refptr<TestableIndexedDBBackingStore> OpenBackingStoreForTest(
       const Origin& origin) {
     IndexedDBDataLossInfo data_loss_info;
     bool disk_full;
-    leveldb::Status status;
-    auto backing_store = OpenBackingStore(origin, context()->data_path(),
-                                          &data_loss_info, &disk_full, &status);
+    leveldb::Status s;
+    scoped_refptr<IndexedDBBackingStore> backing_store;
+    std::tie(backing_store, s, data_loss_info, disk_full) =
+        OpenBackingStore(origin, context()->data_path());
     scoped_refptr<TestableIndexedDBBackingStore> testable_store =
         static_cast<TestableIndexedDBBackingStore*>(backing_store.get());
     return testable_store;
@@ -219,17 +148,13 @@
  protected:
   ~TestIDBFactory() override {}
 
-  scoped_refptr<IndexedDBBackingStore> OpenBackingStoreHelper(
-      const Origin& origin,
-      const base::FilePath& data_directory,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      bool first_time,
-      leveldb::Status* status) override {
-    DefaultLevelDBFactory leveldb_factory;
-    return TestableIndexedDBBackingStore::Open(this, origin, data_directory,
-                                               &leveldb_factory,
-                                               context()->TaskRunner(), status);
+  scoped_refptr<IndexedDBBackingStore> CreateBackingStore(
+      const url::Origin& origin,
+      const base::FilePath& blob_path,
+      std::unique_ptr<LevelDBDatabase> db,
+      base::SequencedTaskRunner* task_runner) override {
+    return base::MakeRefCounted<TestableIndexedDBBackingStore>(
+        this, origin, blob_path, std::move(db), task_runner);
   }
 
  private:
@@ -275,7 +200,8 @@
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
 
     idb_context_ = base::MakeRefCounted<IndexedDBContextImpl>(
-        temp_dir_.GetPath(), special_storage_policy_, quota_manager_proxy_);
+        temp_dir_.GetPath(), special_storage_policy_, quota_manager_proxy_,
+        indexed_db::GetDefaultLevelDBFactory());
 
     CreateFactoryAndBackingStore();
 
@@ -446,6 +372,7 @@
     called = true;
     switch (result) {
       case IndexedDBBackingStore::BlobWriteResult::FAILURE_ASYNC:
+        // Not tested.
         succeeded = false;
         break;
       case IndexedDBBackingStore::BlobWriteResult::SUCCESS_ASYNC:
@@ -1266,11 +1193,8 @@
 
 TEST_F(IndexedDBBackingStoreTest, ReadCorruptionInfo) {
   // No |path_base|.
-  std::string message;
-  EXPECT_FALSE(IndexedDBBackingStore::ReadCorruptionInfo(base::FilePath(),
-                                                         Origin(), &message));
-  EXPECT_TRUE(message.empty());
-  message.clear();
+  EXPECT_TRUE(
+      indexed_db::ReadCorruptionInfo(base::FilePath(), Origin()).empty());
 
   const base::FilePath path_base = temp_dir_.GetPath();
   const Origin origin = Origin::Create(GURL("http://www.google.com/"));
@@ -1278,10 +1202,7 @@
   ASSERT_TRUE(PathIsWritable(path_base));
 
   // File not found.
-  EXPECT_FALSE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
-  EXPECT_TRUE(message.empty());
-  message.clear();
+  EXPECT_TRUE(indexed_db::ReadCorruptionInfo(path_base, origin).empty());
 
   const base::FilePath info_path =
       path_base.AppendASCII("http_www.google.com_0.indexeddb.leveldb")
@@ -1291,65 +1212,46 @@
   // Empty file.
   std::string dummy_data;
   ASSERT_TRUE(WriteFile(info_path, dummy_data));
-  EXPECT_FALSE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  EXPECT_TRUE(indexed_db::ReadCorruptionInfo(path_base, origin).empty());
   EXPECT_FALSE(PathExists(info_path));
-  EXPECT_TRUE(message.empty());
-  message.clear();
 
   // File size > 4 KB.
   dummy_data.resize(5000, 'c');
   ASSERT_TRUE(WriteFile(info_path, dummy_data));
-  EXPECT_FALSE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  EXPECT_TRUE(indexed_db::ReadCorruptionInfo(path_base, origin).empty());
   EXPECT_FALSE(PathExists(info_path));
-  EXPECT_TRUE(message.empty());
-  message.clear();
 
   // Random string.
   ASSERT_TRUE(WriteFile(info_path, "foo bar"));
-  EXPECT_FALSE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  EXPECT_TRUE(indexed_db::ReadCorruptionInfo(path_base, origin).empty());
   EXPECT_FALSE(PathExists(info_path));
-  EXPECT_TRUE(message.empty());
-  message.clear();
 
   // Not a dictionary.
   ASSERT_TRUE(WriteFile(info_path, "[]"));
-  EXPECT_FALSE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  EXPECT_TRUE(indexed_db::ReadCorruptionInfo(path_base, origin).empty());
   EXPECT_FALSE(PathExists(info_path));
-  EXPECT_TRUE(message.empty());
-  message.clear();
 
   // Empty dictionary.
   ASSERT_TRUE(WriteFile(info_path, "{}"));
-  EXPECT_FALSE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  EXPECT_TRUE(indexed_db::ReadCorruptionInfo(path_base, origin).empty());
   EXPECT_FALSE(PathExists(info_path));
-  EXPECT_TRUE(message.empty());
-  message.clear();
 
   // Dictionary, no message key.
   ASSERT_TRUE(WriteFile(info_path, "{\"foo\":\"bar\"}"));
-  EXPECT_FALSE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  EXPECT_TRUE(indexed_db::ReadCorruptionInfo(path_base, origin).empty());
   EXPECT_FALSE(PathExists(info_path));
-  EXPECT_TRUE(message.empty());
-  message.clear();
 
   // Dictionary, message key.
   ASSERT_TRUE(WriteFile(info_path, "{\"message\":\"bar\"}"));
-  EXPECT_TRUE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  std::string message = indexed_db::ReadCorruptionInfo(path_base, origin);
+  EXPECT_FALSE(message.empty());
   EXPECT_FALSE(PathExists(info_path));
   EXPECT_EQ("bar", message);
-  message.clear();
 
   // Dictionary, message key and more.
   ASSERT_TRUE(WriteFile(info_path, "{\"message\":\"foo\",\"bar\":5}"));
-  EXPECT_TRUE(
-      IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
+  message = indexed_db::ReadCorruptionInfo(path_base, origin);
+  EXPECT_FALSE(message.empty());
   EXPECT_FALSE(PathExists(info_path));
   EXPECT_EQ("foo", message);
 }
diff --git a/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc b/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
index 5f730766..4a1e27b 100644
--- a/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
@@ -3,168 +3,85 @@
 // found in the LICENSE file.
 
 #include <cerrno>
+#include <memory>
 
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
+#include "base/memory/ptr_util.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "content/browser/indexed_db/indexed_db_backing_store.h"
-#include "content/browser/indexed_db/indexed_db_data_loss_info.h"
+#include "content/browser/indexed_db/leveldb/fake_leveldb_factory.h"
 #include "content/browser/indexed_db/leveldb/leveldb_database.h"
-#include "content/browser/indexed_db/leveldb/mock_leveldb_factory.h"
-#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/leveldatabase/env_chromium.h"
 
 using base::StringPiece;
-using content::IndexedDBBackingStore;
-using content::LevelDBComparator;
-using content::LevelDBDatabase;
-using content::LevelDBFactory;
-using content::LevelDBSnapshot;
-using testing::_;
-using testing::Exactly;
-using testing::Invoke;
 
 namespace base {
 class TaskRunner;
 }
 
 namespace content {
-class IndexedDBFactory;
-}
-
 namespace {
 
-class BustedLevelDBDatabase : public LevelDBDatabase {
- public:
-  BustedLevelDBDatabase()
-      : LevelDBDatabase(LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase) {}
-  static std::unique_ptr<LevelDBDatabase> Open(
-      const base::FilePath& file_name,
-      const LevelDBComparator* /*comparator*/) {
-    return std::make_unique<BustedLevelDBDatabase>();
-  }
-  leveldb::Status Get(const base::StringPiece& key,
-                      std::string* value,
-                      bool* found,
-                      const LevelDBSnapshot* = nullptr) override {
-    return leveldb::Status::IOError("It's busted!");
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BustedLevelDBDatabase);
-};
-
-class BustedLevelDBFactory : public LevelDBFactory {
- public:
-  leveldb::Status OpenLevelDB(const base::FilePath& file_name,
-                              const LevelDBComparator* comparator,
-                              std::unique_ptr<LevelDBDatabase>* db,
-                              bool* is_disk_full = nullptr) override {
-    if (open_error_.ok())
-      *db = BustedLevelDBDatabase::Open(file_name, comparator);
-    return open_error_;
-  }
-  leveldb::Status DestroyLevelDB(const base::FilePath& file_name) override {
-    return leveldb::Status::IOError("error");
-  }
-  void SetOpenError(const leveldb::Status& open_error) {
-    open_error_ = open_error;
-  }
-
- private:
-  leveldb::Status open_error_;
-};
-
 TEST(IndexedDBIOErrorTest, CleanUpTest) {
-  content::IndexedDBFactory* factory = nullptr;
+  base::test::ScopedTaskEnvironment task_env;
   const url::Origin origin = url::Origin::Create(GURL("http://localhost:81"));
   base::ScopedTempDir temp_directory;
   ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
   const base::FilePath path = temp_directory.GetPath();
 
-  BustedLevelDBFactory busted_factory;
-  content::MockLevelDBFactory mock_leveldb_factory;
-  ON_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).WillByDefault(
-      Invoke(&busted_factory, &BustedLevelDBFactory::OpenLevelDB));
-  ON_CALL(mock_leveldb_factory, DestroyLevelDB(_)).WillByDefault(
-      Invoke(&busted_factory, &BustedLevelDBFactory::DestroyLevelDB));
-
-  EXPECT_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).Times(Exactly(1));
-  EXPECT_CALL(mock_leveldb_factory, DestroyLevelDB(_)).Times(Exactly(1));
-  content::IndexedDBDataLossInfo data_loss_info;
-  bool disk_full = false;
-  base::SequencedTaskRunner* task_runner = nullptr;
-  bool clean_journal = false;
-  leveldb::Status s;
+  auto task_runner = base::SequencedTaskRunnerHandle::Get();
   scoped_refptr<IndexedDBBackingStore> backing_store =
-      IndexedDBBackingStore::Open(factory, origin, path, &data_loss_info,
-                                  &disk_full, &mock_leveldb_factory,
-                                  task_runner, clean_journal, &s);
+      base::MakeRefCounted<IndexedDBBackingStore>(
+          nullptr, origin, path,
+          std::make_unique<LevelDBDatabase>(
+              indexed_db::FakeLevelDBFactory::GetBrokenLevelDB(
+                  leveldb::Status::IOError("It's broken!"), path),
+              task_runner.get(),
+              LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase),
+          task_runner.get());
+  leveldb::Status s = backing_store->Initialize(false);
+  EXPECT_FALSE(s.ok());
 }
 
 TEST(IndexedDBNonRecoverableIOErrorTest, NuancedCleanupTest) {
-  content::IndexedDBFactory* factory = nullptr;
+  base::test::ScopedTaskEnvironment task_env;
   const url::Origin origin = url::Origin::Create(GURL("http://localhost:81"));
   base::ScopedTempDir temp_directory;
   ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
   const base::FilePath path = temp_directory.GetPath();
-  content::IndexedDBDataLossInfo data_loss_info;
-  bool disk_full = false;
-  base::SequencedTaskRunner* task_runner = nullptr;
-  bool clean_journal = false;
+  auto task_runner = base::SequencedTaskRunnerHandle::Get();
   leveldb::Status s;
 
-  BustedLevelDBFactory busted_factory;
-  content::MockLevelDBFactory mock_leveldb_factory;
-  ON_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).WillByDefault(
-      Invoke(&busted_factory, &BustedLevelDBFactory::OpenLevelDB));
-  ON_CALL(mock_leveldb_factory, DestroyLevelDB(_)).WillByDefault(
-      Invoke(&busted_factory, &BustedLevelDBFactory::DestroyLevelDB));
-
-  EXPECT_CALL(mock_leveldb_factory, OpenLevelDB(_, _, _, _)).Times(Exactly(4));
-  EXPECT_CALL(mock_leveldb_factory, DestroyLevelDB(_)).Times(Exactly(0));
-
-  busted_factory.SetOpenError(MakeIOError("some filename", "some message",
-                                          leveldb_env::kNewLogger,
-                                          base::File::FILE_ERROR_NO_SPACE));
-  scoped_refptr<IndexedDBBackingStore> backing_store =
-      IndexedDBBackingStore::Open(factory, origin, path, &data_loss_info,
-                                  &disk_full, &mock_leveldb_factory,
-                                  task_runner, clean_journal, &s);
-  ASSERT_TRUE(s.IsIOError());
-
-  busted_factory.SetOpenError(MakeIOError("some filename",
-                                          "some message",
-                                          leveldb_env::kNewLogger,
-                                          base::File::FILE_ERROR_NO_MEMORY));
-  scoped_refptr<IndexedDBBackingStore> backing_store2 =
-      IndexedDBBackingStore::Open(factory, origin, path, &data_loss_info,
-                                  &disk_full, &mock_leveldb_factory,
-                                  task_runner, clean_journal, &s);
-  ASSERT_TRUE(s.IsIOError());
-
-  busted_factory.SetOpenError(MakeIOError("some filename", "some message",
-                                          leveldb_env::kNewLogger,
-                                          base::File::FILE_ERROR_IO));
-  scoped_refptr<IndexedDBBackingStore> backing_store3 =
-      IndexedDBBackingStore::Open(factory, origin, path, &data_loss_info,
-                                  &disk_full, &mock_leveldb_factory,
-                                  task_runner, clean_journal, &s);
-  ASSERT_TRUE(s.IsIOError());
-
-  busted_factory.SetOpenError(MakeIOError("some filename",
-                                          "some message",
-                                          leveldb_env::kNewLogger,
-                                          base::File::FILE_ERROR_FAILED));
-  scoped_refptr<IndexedDBBackingStore> backing_store4 =
-      IndexedDBBackingStore::Open(factory, origin, path, &data_loss_info,
-                                  &disk_full, &mock_leveldb_factory,
-                                  task_runner, clean_journal, &s);
-  ASSERT_TRUE(s.IsIOError());
+  std::array<leveldb::Status, 4> errors = {
+      MakeIOError("some filename", "some message", leveldb_env::kNewLogger,
+                  base::File::FILE_ERROR_NO_SPACE),
+      MakeIOError("some filename", "some message", leveldb_env::kNewLogger,
+                  base::File::FILE_ERROR_NO_MEMORY),
+      MakeIOError("some filename", "some message", leveldb_env::kNewLogger,
+                  base::File::FILE_ERROR_IO),
+      MakeIOError("some filename", "some message", leveldb_env::kNewLogger,
+                  base::File::FILE_ERROR_FAILED)};
+  for (leveldb::Status error_status : errors) {
+    scoped_refptr<IndexedDBBackingStore> backing_store =
+        base::MakeRefCounted<IndexedDBBackingStore>(
+            nullptr, origin, path,
+            std::make_unique<LevelDBDatabase>(
+                indexed_db::FakeLevelDBFactory::GetBrokenLevelDB(error_status,
+                                                                 path),
+                task_runner.get(),
+                LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase),
+            task_runner.get());
+    leveldb::Status s = backing_store->Initialize(false);
+    ASSERT_TRUE(s.IsIOError());
+  }
 }
 
 }  // namespace
+}  // namespace content
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc
index cd6dd1eb..7cf3cb14c 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -27,9 +27,11 @@
 #include "content/browser/indexed_db/indexed_db_database.h"
 #include "content/browser/indexed_db/indexed_db_dispatcher_host.h"
 #include "content/browser/indexed_db/indexed_db_factory_impl.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
 #include "content/browser/indexed_db/indexed_db_quota_client.h"
 #include "content/browser/indexed_db/indexed_db_tracing.h"
 #include "content/browser/indexed_db/indexed_db_transaction.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_usage_info.h"
 #include "content/public/common/content_switches.h"
@@ -49,15 +51,6 @@
 const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] =
     FILE_PATH_LITERAL("IndexedDB");
 
-static const base::FilePath::CharType kBlobExtension[] =
-    FILE_PATH_LITERAL(".blob");
-
-static const base::FilePath::CharType kIndexedDBExtension[] =
-    FILE_PATH_LITERAL(".indexeddb");
-
-static const base::FilePath::CharType kLevelDBExtension[] =
-    FILE_PATH_LITERAL(".leveldb");
-
 namespace {
 
 // This may be called after the IndexedDBContext is destroyed.
@@ -68,14 +61,18 @@
   // if a global handle to it is ever available.
   if (indexeddb_path.empty())
     return;
-  base::FileEnumerator file_enumerator(
-      indexeddb_path, false, base::FileEnumerator::DIRECTORIES);
+  base::FileEnumerator file_enumerator(indexeddb_path, false,
+                                       base::FileEnumerator::DIRECTORIES);
   for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty();
        file_path = file_enumerator.Next()) {
-    if (file_path.Extension() == kLevelDBExtension &&
-        file_path.RemoveExtension().Extension() == kIndexedDBExtension) {
-      std::string origin_id = file_path.BaseName().RemoveExtension()
-          .RemoveExtension().MaybeAsASCII();
+    if (file_path.Extension() == indexed_db::kLevelDBExtension &&
+        file_path.RemoveExtension().Extension() ==
+            indexed_db::kIndexedDBExtension) {
+      // TODO(dmurph): Unittest this.
+      std::string origin_id = file_path.BaseName()
+                                  .RemoveExtension()
+                                  .RemoveExtension()
+                                  .MaybeAsASCII();
       origins->push_back(storage::GetOriginFromIdentifier(origin_id));
       if (file_paths)
         file_paths->push_back(file_path);
@@ -88,7 +85,8 @@
 IndexedDBContextImpl::IndexedDBContextImpl(
     const base::FilePath& data_path,
     scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
-    scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy)
+    scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
+    indexed_db::LevelDBFactory* leveldb_factory)
     : force_keep_session_state_(false),
       special_storage_policy_(special_storage_policy),
       quota_manager_proxy_(quota_manager_proxy),
@@ -96,7 +94,8 @@
           {base::MayBlock(), base::WithBaseSyncPrimitives(),
            base::TaskPriority::USER_VISIBLE,
            // BLOCK_SHUTDOWN to support clearing session-only storage.
-           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
+      leveldb_factory_(leveldb_factory) {
   IDB_TRACE("init");
   if (!data_path.empty())
     data_path_ = data_path.Append(kIndexedDBDirectory);
@@ -105,14 +104,14 @@
 
 IndexedDBFactory* IndexedDBContextImpl::GetIDBFactory() {
   DCHECK(TaskRunner()->RunsTasksInCurrentSequence());
-  if (!factory_.get()) {
+  if (!indexeddb_factory_.get()) {
     // Prime our cache of origins with existing databases so we can
     // detect when dbs are newly created.
     GetOriginSet();
-    factory_ =
-        new IndexedDBFactoryImpl(this, base::DefaultClock::GetInstance());
+    indexeddb_factory_ = base::MakeRefCounted<IndexedDBFactoryImpl>(
+        this, leveldb_factory_, base::DefaultClock::GetInstance());
   }
-  return factory_.get();
+  return indexeddb_factory_.get();
 }
 
 std::vector<Origin> IndexedDBContextImpl::GetAllOrigins() {
@@ -170,10 +169,10 @@
     // to extract just those in the origin, and we're iterating over all
     // origins in the outer loop.
 
-    if (factory_.get()) {
+    if (indexeddb_factory_.get()) {
       std::pair<IndexedDBFactory::OriginDBMapIterator,
                 IndexedDBFactory::OriginDBMapIterator>
-          range = factory_->GetOpenDatabasesForOrigin(origin);
+          range = indexeddb_factory_->GetOpenDatabasesForOrigin(origin);
       // TODO(jsbell): Sort by name?
       std::unique_ptr<base::ListValue> database_list(
           std::make_unique<base::ListValue>());
@@ -292,9 +291,9 @@
     return base::Time();
 
   if (is_incognito()) {
-    if (!factory_)
+    if (!indexeddb_factory_)
       return base::Time();
-    return factory_->GetLastModified(origin);
+    return indexeddb_factory_->GetLastModified(origin);
   }
 
   base::FilePath idb_directory = GetLevelDBPath(origin);
@@ -318,7 +317,9 @@
 
   base::FilePath idb_directory = GetLevelDBPath(origin);
   EnsureDiskUsageCacheInitialized(origin);
-  leveldb::Status s = LevelDBDatabase::Destroy(idb_directory);
+
+  leveldb::Status s =
+      indexed_db::DefaultLevelDBFactory().DestroyLevelDB(idb_directory);
   if (!s.ok()) {
     LOG(WARNING) << "Failed to delete LevelDB database: "
                  << idb_directory.AsUTF8Unsafe();
@@ -372,14 +373,13 @@
                                       ForceCloseReason reason) {
   DCHECK(TaskRunner()->RunsTasksInCurrentSequence());
   UMA_HISTOGRAM_ENUMERATION("WebCore.IndexedDB.Context.ForceCloseReason",
-                            reason,
-                            FORCE_CLOSE_REASON_MAX);
+                            reason, FORCE_CLOSE_REASON_MAX);
 
   if (!HasOrigin(origin))
     return;
 
-  if (factory_.get())
-    factory_->ForceClose(origin, reason == FORCE_CLOSE_DELETE_ORIGIN);
+  if (indexeddb_factory_.get())
+    indexeddb_factory_->ForceClose(origin, reason == FORCE_CLOSE_DELETE_ORIGIN);
   DCHECK_EQ(0UL, GetConnectionCount(origin));
 }
 
@@ -389,8 +389,8 @@
   if (is_incognito() || !HasOrigin(origin))
     return false;
 
-  if (factory_.get()) {
-    factory_->ForceSchemaDowngrade(origin);
+  if (indexeddb_factory_.get()) {
+    indexeddb_factory_->ForceSchemaDowngrade(origin);
     return true;
   }
   this->ForceClose(origin, FORCE_SCHEMA_DOWNGRADE_INTERNALS_PAGE);
@@ -404,8 +404,8 @@
   if (is_incognito() || !HasOrigin(origin))
     return V2SchemaCorruptionStatus::kUnknown;
 
-  if (factory_.get())
-    return factory_->HasV2SchemaCorruption(origin);
+  if (indexeddb_factory_.get())
+    return indexeddb_factory_->HasV2SchemaCorruption(origin);
   return V2SchemaCorruptionStatus::kUnknown;
 }
 
@@ -414,10 +414,10 @@
   if (!HasOrigin(origin))
     return 0;
 
-  if (!factory_.get())
+  if (!indexeddb_factory_.get())
     return 0;
 
-  return factory_->GetConnectionCount(origin);
+  return indexeddb_factory_->GetConnectionCount(origin);
 }
 
 std::vector<base::FilePath> IndexedDBContextImpl::GetStoragePaths(
@@ -463,12 +463,14 @@
   quota_manager_proxy()->NotifyStorageAccessed(
       storage::QuotaClient::kIndexedDatabase, origin,
       blink::mojom::StorageType::kTemporary);
-  if (factory_.get() && factory_->GetConnectionCount(origin) == 0)
+  if (indexeddb_factory_.get() &&
+      indexeddb_factory_->GetConnectionCount(origin) == 0)
     QueryDiskAndUpdateQuotaUsage(origin);
 }
 
 void IndexedDBContextImpl::TransactionComplete(const Origin& origin) {
-  DCHECK(!factory_.get() || factory_->GetConnectionCount(origin) > 0);
+  DCHECK(!indexeddb_factory_.get() ||
+         indexeddb_factory_->GetConnectionCount(origin) > 0);
   QueryDiskAndUpdateQuotaUsage(origin);
 }
 
@@ -508,10 +510,10 @@
 }
 
 IndexedDBContextImpl::~IndexedDBContextImpl() {
-  if (factory_.get()) {
+  if (indexeddb_factory_.get()) {
     TaskRunner()->PostTask(FROM_HERE,
                            base::BindOnce(&IndexedDBFactory::ContextDestroyed,
-                                          std::move(factory_)));
+                                          std::move(indexeddb_factory_)));
   }
 }
 
@@ -549,46 +551,27 @@
                 base::DeleteFile(*file_path, true);
               }
             },
-            data_path_, factory_, special_storage_policy_));
+            data_path_, indexeddb_factory_, special_storage_policy_));
   }
 }
 
-// static
-base::FilePath IndexedDBContextImpl::GetBlobStoreFileName(
-    const Origin& origin) {
-  std::string origin_id = storage::GetIdentifierFromOrigin(origin);
-  return base::FilePath()
-      .AppendASCII(origin_id)
-      .AddExtension(kIndexedDBExtension)
-      .AddExtension(kBlobExtension);
-}
-
-// static
-base::FilePath IndexedDBContextImpl::GetLevelDBFileName(const Origin& origin) {
-  std::string origin_id = storage::GetIdentifierFromOrigin(origin);
-  return base::FilePath()
-      .AppendASCII(origin_id)
-      .AddExtension(kIndexedDBExtension)
-      .AddExtension(kLevelDBExtension);
-}
-
 base::FilePath IndexedDBContextImpl::GetBlobStorePath(
     const Origin& origin) const {
   DCHECK(!is_incognito());
-  return data_path_.Append(GetBlobStoreFileName(origin));
+  return data_path_.Append(indexed_db::GetBlobStoreFileName(origin));
 }
 
 base::FilePath IndexedDBContextImpl::GetLevelDBPath(
     const Origin& origin) const {
   DCHECK(!is_incognito());
-  return data_path_.Append(GetLevelDBFileName(origin));
+  return data_path_.Append(indexed_db::GetLevelDBFileName(origin));
 }
 
 int64_t IndexedDBContextImpl::ReadUsageFromDisk(const Origin& origin) const {
   if (is_incognito()) {
-    if (!factory_)
+    if (!indexeddb_factory_)
       return 0;
-    return factory_->GetInMemoryDBSize(origin);
+    return indexeddb_factory_->GetInMemoryDBSize(origin);
   }
 
   int64_t total_size = 0;
diff --git a/content/browser/indexed_db/indexed_db_context_impl.h b/content/browser/indexed_db/indexed_db_context_impl.h
index ea5fa2b..c599504d 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.h
+++ b/content/browser/indexed_db/indexed_db_context_impl.h
@@ -19,6 +19,7 @@
 #include "base/macros.h"
 #include "content/browser/browser_main_loop.h"
 #include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/public/browser/indexed_db_context.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "storage/browser/quota/special_storage_policy.h"
@@ -71,7 +72,8 @@
   IndexedDBContextImpl(
       const base::FilePath& data_path,
       scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
-      scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy);
+      scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
+      indexed_db::LevelDBFactory* leveldb_factory);
 
   IndexedDBFactory* GetIDBFactory();
 
@@ -99,9 +101,6 @@
   void TransactionComplete(const url::Origin& origin);
   void DatabaseDeleted(const url::Origin& origin);
 
-  static base::FilePath GetBlobStoreFileName(const url::Origin& origin);
-  static base::FilePath GetLevelDBFileName(const url::Origin& origin);
-
   // Called when blob files have been cleaned (an aggregated delayed task).
   void BlobFilesCleaned(const url::Origin& origin);
 
@@ -178,7 +177,7 @@
   // backing stores); the cache will be primed as needed by checking disk.
   std::set<url::Origin>* GetOriginSet();
 
-  scoped_refptr<IndexedDBFactory> factory_;
+  scoped_refptr<IndexedDBFactory> indexeddb_factory_;
 
   // If |data_path_| is empty then this is an incognito session and the backing
   // store will be held in-memory rather than on-disk.
@@ -192,6 +191,7 @@
   std::unique_ptr<std::set<url::Origin>> origin_set_;
   std::map<url::Origin, int64_t> origin_size_map_;
   base::ObserverList<Observer>::Unchecked observers_;
+  indexed_db::LevelDBFactory* leveldb_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(IndexedDBContextImpl);
 };
diff --git a/content/browser/indexed_db/indexed_db_dispatcher_host.cc b/content/browser/indexed_db/indexed_db_dispatcher_host.cc
index 2685bf0..76e73c53 100644
--- a/content/browser/indexed_db/indexed_db_dispatcher_host.cc
+++ b/content/browser/indexed_db/indexed_db_dispatcher_host.cc
@@ -144,6 +144,11 @@
 void IndexedDBDispatcherHost::AddBinding(
     blink::mojom::IDBFactoryRequest request,
     const url::Origin& origin) {
+  if (!IsValidOrigin(origin)) {
+    mojo::ReportBadMessage(kInvalidOrigin);
+    return;
+  }
+
   bindings_.AddBinding(this, std::move(request), {origin});
 }
 
@@ -174,11 +179,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const auto& context = bindings_.dispatch_context();
-  if (!IsValidOrigin(context.origin)) {
-    mojo::ReportBadMessage(kInvalidOrigin);
-    return;
-  }
-
   scoped_refptr<IndexedDBCallbacks> callbacks(
       new IndexedDBCallbacks(this->AsWeakPtr(), context.origin,
                              std::move(callbacks_info), IDBTaskRunner()));
@@ -193,11 +193,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const auto& context = bindings_.dispatch_context();
-  if (!IsValidOrigin(context.origin)) {
-    mojo::ReportBadMessage(kInvalidOrigin);
-    return;
-  }
-
   scoped_refptr<IndexedDBCallbacks> callbacks(
       new IndexedDBCallbacks(this->AsWeakPtr(), context.origin,
                              std::move(callbacks_info), IDBTaskRunner()));
@@ -216,11 +211,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const auto& context = bindings_.dispatch_context();
-  if (!IsValidOrigin(context.origin)) {
-    mojo::ReportBadMessage(kInvalidOrigin);
-    return;
-  }
-
   scoped_refptr<IndexedDBCallbacks> callbacks(
       new IndexedDBCallbacks(this->AsWeakPtr(), context.origin,
                              std::move(callbacks_info), IDBTaskRunner()));
@@ -242,11 +232,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const auto& context = bindings_.dispatch_context();
-  if (!IsValidOrigin(context.origin)) {
-    mojo::ReportBadMessage(kInvalidOrigin);
-    return;
-  }
-
   scoped_refptr<IndexedDBCallbacks> callbacks(
       new IndexedDBCallbacks(this->AsWeakPtr(), context.origin,
                              std::move(callbacks_info), IDBTaskRunner()));
@@ -262,11 +247,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const auto& context = bindings_.dispatch_context();
-  if (!IsValidOrigin(context.origin)) {
-    mojo::ReportBadMessage(kInvalidOrigin);
-    return;
-  }
-
   base::OnceCallback<void(leveldb::Status)> callback_on_io = base::BindOnce(
       &CallCompactionStatusCallbackOnIOThread,
       base::ThreadTaskRunnerHandle::Get(), std::move(mojo_callback));
@@ -283,11 +263,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const auto& context = bindings_.dispatch_context();
-  if (!IsValidOrigin(context.origin)) {
-    mojo::ReportBadMessage(kInvalidOrigin);
-    return;
-  }
-
   base::OnceCallback<void(leveldb::Status)> callback_on_io = base::BindOnce(
       &CallAbortStatusCallbackOnIOThread, base::ThreadTaskRunnerHandle::Get(),
       std::move(mojo_callback));
diff --git a/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
index 193e3a5..0bd4a5a 100644
--- a/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
@@ -19,6 +19,7 @@
 #include "content/browser/indexed_db/indexed_db_database_callbacks.h"
 #include "content/browser/indexed_db/indexed_db_factory.h"
 #include "content/browser/indexed_db/indexed_db_pending_connection.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/browser/indexed_db/mock_mojo_indexed_db_callbacks.h"
 #include "content/browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -170,7 +171,8 @@
         context_impl_(base::MakeRefCounted<IndexedDBContextImpl>(
             CreateAndReturnTempDir(&temp_dir_),
             special_storage_policy_,
-            quota_manager_->proxy())),
+            quota_manager_->proxy(),
+            indexed_db::GetDefaultLevelDBFactory())),
         host_(new IndexedDBDispatcherHost(
             kFakeProcessId,
             context_impl_,
diff --git a/content/browser/indexed_db/indexed_db_factory.h b/content/browser/indexed_db/indexed_db_factory.h
index 4ade927..7a90e80 100644
--- a/content/browser/indexed_db/indexed_db_factory.h
+++ b/content/browser/indexed_db/indexed_db_factory.h
@@ -10,6 +10,7 @@
 #include <map>
 #include <memory>
 #include <set>
+#include <tuple>
 #include <utility>
 
 #include "base/files/file_path.h"
@@ -113,20 +114,12 @@
   IndexedDBFactory() {}
   virtual ~IndexedDBFactory() {}
 
-  virtual scoped_refptr<IndexedDBBackingStore> OpenBackingStore(
-      const url::Origin& origin,
-      const base::FilePath& data_directory,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      leveldb::Status* status) = 0;
-
-  virtual scoped_refptr<IndexedDBBackingStore> OpenBackingStoreHelper(
-      const url::Origin& origin,
-      const base::FilePath& data_directory,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      bool first_time,
-      leveldb::Status* status) = 0;
+  virtual std::tuple<scoped_refptr<IndexedDBBackingStore>,
+                     leveldb::Status,
+                     IndexedDBDataLossInfo,
+                     bool /* disk_full */>
+  OpenBackingStore(const url::Origin& origin,
+                   const base::FilePath& data_directory) = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(IndexedDBFactory);
diff --git a/content/browser/indexed_db/indexed_db_factory_impl.cc b/content/browser/indexed_db/indexed_db_factory_impl.cc
index 392ca7d8..8d8baba 100644
--- a/content/browser/indexed_db/indexed_db_factory_impl.cc
+++ b/content/browser/indexed_db/indexed_db_factory_impl.cc
@@ -119,9 +119,12 @@
 constexpr const base::TimeDelta
     IndexedDBFactoryImpl::kMaxEarliestOriginSweepFromNow;
 
-IndexedDBFactoryImpl::IndexedDBFactoryImpl(IndexedDBContextImpl* context,
-                                           base::Clock* clock)
+IndexedDBFactoryImpl::IndexedDBFactoryImpl(
+    IndexedDBContextImpl* context,
+    indexed_db::LevelDBFactory* leveldb_factory,
+    base::Clock* clock)
     : context_(context),
+      leveldb_factory_(leveldb_factory),
       clock_(clock),
       earliest_sweep_(GenerateNextGlobalSweepTime(clock_->Now())) {}
 
@@ -408,9 +411,9 @@
   IndexedDBDataLossInfo data_loss_info;
   bool disk_full;
   leveldb::Status s;
-  // TODO(dmurph): Handle this error
-  scoped_refptr<IndexedDBBackingStore> backing_store =
-      OpenBackingStore(origin, data_directory, &data_loss_info, &disk_full, &s);
+  scoped_refptr<IndexedDBBackingStore> backing_store;
+  std::tie(backing_store, s, data_loss_info, disk_full) =
+      OpenBackingStore(origin, data_directory);
   if (!backing_store.get()) {
     IndexedDBDatabaseError error(
         blink::kWebIDBDatabaseExceptionUnknownError,
@@ -448,13 +451,13 @@
     const Origin& origin,
     const base::FilePath& data_directory) {
   IDB_TRACE("IndexedDBFactoryImpl::GetDatabaseNames");
-  // TODO(dgrogan): Plumb data_loss back to script eventually?
+  // TODO(dmurph): Plumb data_loss back to script eventually?
   IndexedDBDataLossInfo data_loss_info;
   bool disk_full;
   leveldb::Status s;
-  // TODO(cmumford): Handle this error
-  scoped_refptr<IndexedDBBackingStore> backing_store =
-      OpenBackingStore(origin, data_directory, &data_loss_info, &disk_full, &s);
+  scoped_refptr<IndexedDBBackingStore> backing_store;
+  std::tie(backing_store, s, data_loss_info, disk_full) =
+      OpenBackingStore(origin, data_directory);
   if (!backing_store.get()) {
     IndexedDBDatabaseError error(
         blink::kWebIDBDatabaseExceptionUnknownError,
@@ -502,12 +505,13 @@
     return;
   }
 
-  // TODO(dgrogan): Plumb data_loss back to script eventually?
+  // TODO(dmurph): Plumb data_loss back to script eventually?
   IndexedDBDataLossInfo data_loss_info;
-  bool disk_full = false;
+  bool disk_full;
   leveldb::Status s;
-  scoped_refptr<IndexedDBBackingStore> backing_store =
-      OpenBackingStore(origin, data_directory, &data_loss_info, &disk_full, &s);
+  scoped_refptr<IndexedDBBackingStore> backing_store;
+  std::tie(backing_store, s, data_loss_info, disk_full) =
+      OpenBackingStore(origin, data_directory);
   if (!backing_store.get()) {
     IndexedDBDatabaseError error(
         blink::kWebIDBDatabaseExceptionUnknownError,
@@ -641,8 +645,9 @@
   HandleBackingStoreFailure(saved_origin);
   // Note: DestroyBackingStore only deletes LevelDB files, leaving all others,
   //       so our corruption info file will remain.
-  leveldb::Status s =
-      IndexedDBBackingStore::DestroyBackingStore(path_base, saved_origin);
+  const base::FilePath file_path =
+      path_base.Append(indexed_db::GetLevelDBFileName(saved_origin));
+  leveldb::Status s = leveldb_factory_->DestroyLevelDB(file_path);
   DLOG_IF(ERROR, !s.ok()) << "Unable to delete backing store: " << s.ToString();
   UMA_HISTOGRAM_ENUMERATION(
       "WebCore.IndexedDB.DestroyCorruptBackingStoreStatus",
@@ -669,69 +674,78 @@
          it->second->pre_close_task_queue();
 }
 
-scoped_refptr<IndexedDBBackingStore>
-IndexedDBFactoryImpl::OpenBackingStoreHelper(
-    const Origin& origin,
-    const base::FilePath& data_directory,
-    IndexedDBDataLossInfo* data_loss_info,
-    bool* disk_full,
-    bool first_time,
-    leveldb::Status* status) {
-  return IndexedDBBackingStore::Open(
-      this, origin, data_directory, data_loss_info, disk_full,
-      context_->TaskRunner(), first_time, status);
-}
-
-scoped_refptr<IndexedDBBackingStore> IndexedDBFactoryImpl::OpenBackingStore(
-    const Origin& origin,
-    const base::FilePath& data_directory,
-    IndexedDBDataLossInfo* data_loss_info,
-    bool* disk_full,
-    leveldb::Status* status) {
-  const bool open_in_memory = data_directory.empty();
-
+std::tuple<scoped_refptr<IndexedDBBackingStore>,
+           leveldb::Status,
+           IndexedDBDataLossInfo,
+           bool /* disk_full */>
+IndexedDBFactoryImpl::OpenBackingStore(const Origin& origin,
+                                       const base::FilePath& data_directory) {
   const auto& it2 = backing_store_map_.find(origin);
+  scoped_refptr<IndexedDBBackingStore> backing_store;
   if (it2 != backing_store_map_.end()) {
     // Grab a refptr so the completion of the preclose task list doesn't close
     // the backing store.
-    scoped_refptr<IndexedDBBackingStore> backing_store = it2->second;
+    backing_store = it2->second;
     backing_store->close_timer()->Stop();
     if (it2->second->pre_close_task_queue()) {
       backing_store->pre_close_task_queue()->StopForNewConnection();
       backing_store->SetPreCloseTaskList(nullptr);
     }
-    return it2->second;
+    return {std::move(backing_store), leveldb::Status::OK(),
+            IndexedDBDataLossInfo(), false};
   }
 
-  scoped_refptr<IndexedDBBackingStore> backing_store;
-  bool first_time = false;
-  if (open_in_memory) {
-    backing_store = IndexedDBBackingStore::OpenInMemory(
-        origin, context_->TaskRunner(), status);
-  } else {
-    first_time = !backends_opened_since_boot_.count(origin);
-
-    backing_store = OpenBackingStoreHelper(
-        origin, data_directory, data_loss_info, disk_full, first_time, status);
+  base::FilePath blob_path;
+  base::FilePath database_path;
+  leveldb::Status s;
+  if (!data_directory.empty()) {
+    // The database will be on-disk and not in-memory.
+    std::tie(database_path, blob_path, s) =
+        indexed_db::CreateDatabaseDirectories(data_directory, origin);
+    if (!s.ok())
+      return {std::move(backing_store), s, IndexedDBDataLossInfo(), false};
   }
+  std::unique_ptr<LevelDBDatabase> database;
+  IndexedDBDataLossInfo data_loss_info;
+  bool disk_full;
+  std::tie(database, s, data_loss_info, disk_full) =
+      indexed_db::OpenAndVerifyLevelDBDatabase(origin, data_directory,
+                                               database_path, leveldb_factory_,
+                                               context_->TaskRunner());
+  if (!s.ok())
+    return {std::move(backing_store), s, data_loss_info, disk_full};
 
-  if (backing_store.get()) {
-    if (first_time)
-      backends_opened_since_boot_.insert(origin);
-    backing_store_map_[origin] = backing_store;
+  backing_store = CreateBackingStore(origin, blob_path, std::move(database),
+                                     context_->TaskRunner());
 
-    // If an in-memory database, bind lifetime to this factory instance.
-    if (open_in_memory)
-      in_memory_backing_stores_.insert(backing_store);
-
-    // All backing stores associated with this factory should be of the same
-    // type.
-    DCHECK_NE(in_memory_backing_stores_.empty(), open_in_memory);
-
-    return backing_store;
+  bool first_open_since_startup =
+      backends_opened_since_startup_.insert(origin).second;
+  s = backing_store->Initialize(
+      /*cleanup_live_journal=*/!database_path.empty() &&
+      first_open_since_startup);
+  if (!s.ok()) {
+    backing_store.reset();
+    return {std::move(backing_store), s, data_loss_info, disk_full};
   }
+  // If an in-memory database, bind lifetime to this factory instance.
+  if (database_path.empty())
+    in_memory_backing_stores_.insert(backing_store);
+  backing_store_map_[origin] = backing_store;
 
-  return nullptr;
+  // All backing stores associated with this factory should be of the same
+  // type.
+  DCHECK_EQ(!in_memory_backing_stores_.empty(), database_path.empty());
+  DCHECK(backing_store);
+  return {std::move(backing_store), s, data_loss_info, disk_full};
+}
+
+scoped_refptr<IndexedDBBackingStore> IndexedDBFactoryImpl::CreateBackingStore(
+    const url::Origin& origin,
+    const base::FilePath& blob_path,
+    std::unique_ptr<LevelDBDatabase> db,
+    base::SequencedTaskRunner* task_runner) {
+  return base::MakeRefCounted<IndexedDBBackingStore>(
+      this, origin, blob_path, std::move(db), task_runner);
 }
 
 void IndexedDBFactoryImpl::Open(
@@ -740,61 +754,56 @@
     const Origin& origin,
     const base::FilePath& data_directory) {
   IDB_TRACE("IndexedDBFactoryImpl::Open");
-  scoped_refptr<IndexedDBDatabase> database;
   IndexedDBDatabase::Identifier unique_identifier(origin, name);
-  const auto& it = database_map_.find(unique_identifier);
-  IndexedDBDataLossInfo data_loss_info;
-  bool disk_full = false;
-  bool was_open = (it != database_map_.end());
-  if (!was_open) {
-    leveldb::Status s;
-    scoped_refptr<IndexedDBBackingStore> backing_store = OpenBackingStore(
-        origin, data_directory, &data_loss_info, &disk_full, &s);
-    if (!backing_store.get()) {
-      if (disk_full) {
-        connection->callbacks->OnError(IndexedDBDatabaseError(
-            blink::kWebIDBDatabaseExceptionQuotaError,
-            ASCIIToUTF16("Encountered full disk while opening "
-                         "backing store for indexedDB.open.")));
-        return;
-      }
-      IndexedDBDatabaseError error(
-          blink::kWebIDBDatabaseExceptionUnknownError,
-          ASCIIToUTF16("Internal error opening backing store"
-                       " for indexedDB.open."));
-      connection->callbacks->OnError(error);
-      if (s.IsCorruption()) {
-        HandleBackingStoreCorruption(origin, error);
-      }
+  auto it = database_map_.find(unique_identifier);
+  if (it != database_map_.end()) {
+    it->second->OpenConnection(std::move(connection));
+    return;
+  }
+  leveldb::Status s;
+  scoped_refptr<IndexedDBBackingStore> backing_store;
+  bool disk_full;
+  std::tie(backing_store, s, connection->data_loss_info, disk_full) =
+      OpenBackingStore(origin, data_directory);
+  if (!backing_store) {
+    if (disk_full) {
+      connection->callbacks->OnError(IndexedDBDatabaseError(
+          blink::kWebIDBDatabaseExceptionQuotaError,
+          ASCIIToUTF16("Encountered full disk while opening "
+                       "backing store for indexedDB.open.")));
       return;
     }
-
-    std::tie(database, s) = IndexedDBDatabase::Create(
-        name, backing_store.get(), this,
-        std::make_unique<IndexedDBMetadataCoding>(), unique_identifier);
-    if (!database.get()) {
-      DLOG(ERROR) << "Unable to create the database";
-      IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError,
-                                   ASCIIToUTF16("Internal error creating "
-                                                "database backend for "
-                                                "indexedDB.open."));
-      connection->callbacks->OnError(error);
-      if (s.IsCorruption()) {
-        backing_store =
-            nullptr;  // Closes the LevelDB so that it can be deleted
-        HandleBackingStoreCorruption(origin, error);
-      }
-      return;
+    IndexedDBDatabaseError error(
+        blink::kWebIDBDatabaseExceptionUnknownError,
+        ASCIIToUTF16("Internal error opening backing store"
+                     " for indexedDB.open."));
+    connection->callbacks->OnError(error);
+    if (s.IsCorruption()) {
+      HandleBackingStoreCorruption(origin, error);
     }
-  } else {
-    database = it->second;
+    return;
   }
 
-  connection->data_loss_info = data_loss_info;
+  scoped_refptr<IndexedDBDatabase> database;
+  std::tie(database, s) = IndexedDBDatabase::Create(
+      name, backing_store.get(), this,
+      std::make_unique<IndexedDBMetadataCoding>(), unique_identifier);
+  if (!database.get()) {
+    DLOG(ERROR) << "Unable to create the database";
+    IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError,
+                                 ASCIIToUTF16("Internal error creating "
+                                              "database backend for "
+                                              "indexedDB.open."));
+    connection->callbacks->OnError(error);
+    if (s.IsCorruption()) {
+      backing_store = nullptr;  // Closes the LevelDB so that it can be deleted
+      HandleBackingStoreCorruption(origin, error);
+    }
+    return;
+  }
 
   database->OpenConnection(std::move(connection));
-
-  if (!was_open && database->ConnectionCount() > 0) {
+  if (database->ConnectionCount() > 0) {
     database_map_[unique_identifier] = database.get();
     origin_dbs_.insert(std::make_pair(origin, database.get()));
   }
diff --git a/content/browser/indexed_db/indexed_db_factory_impl.h b/content/browser/indexed_db/indexed_db_factory_impl.h
index 5a95564..7ec91479 100644
--- a/content/browser/indexed_db/indexed_db_factory_impl.h
+++ b/content/browser/indexed_db/indexed_db_factory_impl.h
@@ -10,12 +10,15 @@
 #include <map>
 #include <memory>
 #include <set>
+#include <tuple>
+#include <utility>
 
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
 #include "content/browser/indexed_db/indexed_db_factory.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 
 namespace base {
 struct Feature;
@@ -47,7 +50,9 @@
   static constexpr const base::TimeDelta kMaxEarliestOriginSweepFromNow =
       base::TimeDelta::FromDays(7);
 
-  IndexedDBFactoryImpl(IndexedDBContextImpl* context, base::Clock* clock);
+  IndexedDBFactoryImpl(IndexedDBContextImpl* context,
+                       indexed_db::LevelDBFactory* leveldb_factory,
+                       base::Clock* clock);
 
   // content::IndexedDBFactory overrides:
   void ReleaseDatabase(const IndexedDBDatabase::Identifier& identifier,
@@ -118,20 +123,19 @@
  protected:
   ~IndexedDBFactoryImpl() override;
 
-  scoped_refptr<IndexedDBBackingStore> OpenBackingStore(
-      const url::Origin& origin,
-      const base::FilePath& data_directory,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      leveldb::Status* s) override;
+  std::tuple<scoped_refptr<IndexedDBBackingStore>,
+             leveldb::Status,
+             IndexedDBDataLossInfo,
+             bool /* disk_full */>
+  OpenBackingStore(const url::Origin& origin,
+                   const base::FilePath& data_directory) override;
 
-  scoped_refptr<IndexedDBBackingStore> OpenBackingStoreHelper(
+  // Used by unittests to allow subclassing of IndexedDBBackingStore.
+  virtual scoped_refptr<IndexedDBBackingStore> CreateBackingStore(
       const url::Origin& origin,
-      const base::FilePath& data_directory,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      bool first_time,
-      leveldb::Status* s) override;
+      const base::FilePath& blob_path,
+      std::unique_ptr<LevelDBDatabase> db,
+      base::SequencedTaskRunner* task_runner);
 
   void ReleaseBackingStore(const url::Origin& origin, bool immediate);
   void CloseBackingStore(const url::Origin& origin);
@@ -174,6 +178,7 @@
   void RemoveDatabaseFromMaps(const IndexedDBDatabase::Identifier& identifier);
 
   IndexedDBContextImpl* context_;
+  indexed_db::LevelDBFactory* leveldb_factory_;
 
   std::map<IndexedDBDatabase::Identifier, IndexedDBDatabase*> database_map_;
   OriginDBMap origin_dbs_;
@@ -186,7 +191,7 @@
   std::set<scoped_refptr<IndexedDBBackingStore>> in_memory_backing_stores_;
   std::map<url::Origin, scoped_refptr<IndexedDBBackingStore>>
       backing_stores_with_active_blobs_;
-  std::set<url::Origin> backends_opened_since_boot_;
+  std::set<url::Origin> backends_opened_since_startup_;
 
   base::Clock* clock_;
   base::Time earliest_sweep_;
diff --git a/content/browser/indexed_db/indexed_db_factory_unittest.cc b/content/browser/indexed_db/indexed_db_factory_unittest.cc
index c4d1569..e7ce069 100644
--- a/content/browser/indexed_db/indexed_db_factory_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_factory_unittest.cc
@@ -22,6 +22,7 @@
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
 #include "content/browser/indexed_db/indexed_db_data_format_version.h"
 #include "content/browser/indexed_db/indexed_db_factory_impl.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/browser/indexed_db/mock_indexed_db_callbacks.h"
 #include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
 #include "content/public/test/test_browser_thread_bundle.h"
@@ -46,15 +47,18 @@
   explicit MockIDBFactory(IndexedDBContextImpl* context)
       : MockIDBFactory(context, base::DefaultClock::GetInstance()) {}
   MockIDBFactory(IndexedDBContextImpl* context, base::Clock* clock)
-      : IndexedDBFactoryImpl(context, clock) {}
+      : IndexedDBFactoryImpl(context,
+                             indexed_db::GetDefaultLevelDBFactory(),
+                             clock) {}
   scoped_refptr<IndexedDBBackingStore> TestOpenBackingStore(
       const Origin& origin,
       const base::FilePath& data_directory) {
     IndexedDBDataLossInfo data_loss_info;
     bool disk_full;
     leveldb::Status s;
-    auto backing_store = OpenBackingStore(origin, data_directory,
-                                          &data_loss_info, &disk_full, &s);
+    scoped_refptr<IndexedDBBackingStore> backing_store;
+    std::tie(backing_store, s, data_loss_info, disk_full) =
+        OpenBackingStore(origin, data_directory);
     EXPECT_EQ(blink::mojom::IDBDataLoss::None, data_loss_info.status);
     return backing_store;
   }
@@ -86,7 +90,7 @@
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     context_ = base::MakeRefCounted<IndexedDBContextImpl>(
         temp_dir_.GetPath(), /*special_storage_policy=*/nullptr,
-        quota_manager_proxy_.get());
+        quota_manager_proxy_.get(), indexed_db::GetDefaultLevelDBFactory());
   }
 
   void TearDown() override {
@@ -415,19 +419,21 @@
 class DiskFullFactory : public IndexedDBFactoryImpl {
  public:
   explicit DiskFullFactory(IndexedDBContextImpl* context)
-      : IndexedDBFactoryImpl(context, base::DefaultClock::GetInstance()) {}
+      : IndexedDBFactoryImpl(context,
+                             indexed_db::GetDefaultLevelDBFactory(),
+                             base::DefaultClock::GetInstance()) {}
 
  private:
   ~DiskFullFactory() override {}
-  scoped_refptr<IndexedDBBackingStore> OpenBackingStore(
-      const Origin& origin,
-      const base::FilePath& data_directory,
-      IndexedDBDataLossInfo* data_loss_info,
-      bool* disk_full,
-      leveldb::Status* s) override {
-    *disk_full = true;
-    *s = leveldb::Status::IOError("Disk is full");
-    return scoped_refptr<IndexedDBBackingStore>();
+
+  std::tuple<scoped_refptr<IndexedDBBackingStore>,
+             leveldb::Status,
+             IndexedDBDataLossInfo,
+             bool /* disk_full */>
+  OpenBackingStore(const url::Origin& origin,
+                   const base::FilePath& data_directory) override {
+    return std::make_tuple(nullptr, leveldb::Status::IOError("Disk is full"),
+                           IndexedDBDataLossInfo(), true);
   }
 
   DISALLOW_COPY_AND_ASSIGN(DiskFullFactory);
diff --git a/content/browser/indexed_db/indexed_db_fake_backing_store.cc b/content/browser/indexed_db/indexed_db_fake_backing_store.cc
index e83e2a7..9a7b452 100644
--- a/content/browser/indexed_db/indexed_db_fake_backing_store.cc
+++ b/content/browser/indexed_db/indexed_db_fake_backing_store.cc
@@ -20,7 +20,6 @@
                             url::Origin::Create(GURL("http://localhost:81")),
                             base::FilePath(),
                             std::unique_ptr<LevelDBDatabase>(),
-                            std::unique_ptr<LevelDBComparator>(),
                             base::SequencedTaskRunnerHandle::Get().get()) {}
 IndexedDBFakeBackingStore::IndexedDBFakeBackingStore(
     IndexedDBFactory* factory,
@@ -29,7 +28,6 @@
                             url::Origin::Create(GURL("http://localhost:81")),
                             base::FilePath(),
                             std::unique_ptr<LevelDBDatabase>(),
-                            std::unique_ptr<LevelDBComparator>(),
                             task_runner) {}
 IndexedDBFakeBackingStore::~IndexedDBFakeBackingStore() {}
 
diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding.cc b/content/browser/indexed_db/indexed_db_leveldb_coding.cc
index 80d5d12..bf1b0c9 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_coding.cc
+++ b/content/browser/indexed_db/indexed_db_leveldb_coding.cc
@@ -21,59 +21,62 @@
 using blink::IndexedDBKeyPath;
 
 namespace content {
+namespace {
 
 // As most of the IndexedDBKeys and encoded values are short, we
 // initialize some std::vectors with a default inline buffer size to reduce
 // the memory re-allocations when the std::vectors are appended.
-static const size_t kDefaultInlineBufferSize = 32;
+const size_t kDefaultInlineBufferSize = 32;
 
-static const unsigned char kIndexedDBKeyNullTypeByte = 0;
-static const unsigned char kIndexedDBKeyStringTypeByte = 1;
-static const unsigned char kIndexedDBKeyDateTypeByte = 2;
-static const unsigned char kIndexedDBKeyNumberTypeByte = 3;
-static const unsigned char kIndexedDBKeyArrayTypeByte = 4;
-static const unsigned char kIndexedDBKeyMinKeyTypeByte = 5;
-static const unsigned char kIndexedDBKeyBinaryTypeByte = 6;
+constexpr unsigned char kIndexedDBKeyNullTypeByte = 0;
+constexpr unsigned char kIndexedDBKeyStringTypeByte = 1;
+constexpr unsigned char kIndexedDBKeyDateTypeByte = 2;
+constexpr unsigned char kIndexedDBKeyNumberTypeByte = 3;
+constexpr unsigned char kIndexedDBKeyArrayTypeByte = 4;
+constexpr unsigned char kIndexedDBKeyMinKeyTypeByte = 5;
+constexpr unsigned char kIndexedDBKeyBinaryTypeByte = 6;
 
-static const unsigned char kIndexedDBKeyPathTypeCodedByte1 = 0;
-static const unsigned char kIndexedDBKeyPathTypeCodedByte2 = 0;
+constexpr unsigned char kIndexedDBKeyPathTypeCodedByte1 = 0;
+constexpr unsigned char kIndexedDBKeyPathTypeCodedByte2 = 0;
 
-static const unsigned char kIndexedDBKeyPathNullTypeByte = 0;
-static const unsigned char kIndexedDBKeyPathStringTypeByte = 1;
-static const unsigned char kIndexedDBKeyPathArrayTypeByte = 2;
+constexpr unsigned char kIndexedDBKeyPathNullTypeByte = 0;
+constexpr unsigned char kIndexedDBKeyPathStringTypeByte = 1;
+constexpr unsigned char kIndexedDBKeyPathArrayTypeByte = 2;
 
-static const unsigned char kObjectStoreDataIndexId = 1;
-static const unsigned char kExistsEntryIndexId = 2;
-static const unsigned char kBlobEntryIndexId = 3;
+constexpr unsigned char kObjectStoreDataIndexId = 1;
+constexpr unsigned char kExistsEntryIndexId = 2;
+constexpr unsigned char kBlobEntryIndexId = 3;
 
-static const unsigned char kSchemaVersionTypeByte = 0;
-static const unsigned char kMaxDatabaseIdTypeByte = 1;
-static const unsigned char kDataVersionTypeByte = 2;
-static const unsigned char kBlobJournalTypeByte = 3;
-static const unsigned char kLiveBlobJournalTypeByte = 4;
-static const unsigned char kEarliestSweepTimeTypeByte = 5;
-static const unsigned char kMaxSimpleGlobalMetaDataTypeByte =
+constexpr unsigned char kSchemaVersionTypeByte = 0;
+constexpr unsigned char kMaxDatabaseIdTypeByte = 1;
+constexpr unsigned char kDataVersionTypeByte = 2;
+constexpr unsigned char kBlobJournalTypeByte = 3;
+constexpr unsigned char kLiveBlobJournalTypeByte = 4;
+constexpr unsigned char kEarliestSweepTimeTypeByte = 5;
+constexpr unsigned char kMaxSimpleGlobalMetaDataTypeByte =
     6;  // Insert before this and increment.
-static const unsigned char kDatabaseFreeListTypeByte = 100;
-static const unsigned char kDatabaseNameTypeByte = 201;
+constexpr unsigned char kDatabaseFreeListTypeByte = 100;
+constexpr unsigned char kDatabaseNameTypeByte = 201;
 
-static const unsigned char kObjectStoreMetaDataTypeByte = 50;
-static const unsigned char kIndexMetaDataTypeByte = 100;
-static const unsigned char kObjectStoreFreeListTypeByte = 150;
-static const unsigned char kIndexFreeListTypeByte = 151;
-static const unsigned char kObjectStoreNamesTypeByte = 200;
-static const unsigned char kIndexNamesKeyTypeByte = 201;
+constexpr unsigned char kObjectStoreMetaDataTypeByte = 50;
+constexpr unsigned char kIndexMetaDataTypeByte = 100;
+constexpr unsigned char kObjectStoreFreeListTypeByte = 150;
+constexpr unsigned char kIndexFreeListTypeByte = 151;
+constexpr unsigned char kObjectStoreNamesTypeByte = 200;
+constexpr unsigned char kIndexNamesKeyTypeByte = 201;
 
-static const unsigned char kObjectMetaDataTypeMaximum = 255;
-static const unsigned char kIndexMetaDataTypeMaximum = 255;
-
-const unsigned char kMinimumIndexId = 30;
+constexpr unsigned char kObjectMetaDataTypeMaximum = 255;
+constexpr unsigned char kIndexMetaDataTypeMaximum = 255;
 
 inline void EncodeIntSafely(int64_t value, int64_t max, std::string* into) {
   DCHECK_LE(value, max);
   return EncodeInt(value, into);
 }
 
+}  // namespace
+
+const unsigned char kMinimumIndexId = 30;
+
 std::string MaxIDBKey() {
   std::string ret;
   EncodeByte(kIndexedDBKeyNullTypeByte, &ret);
@@ -986,6 +989,7 @@
             bool only_compare_index_keys) {
   bool ok;
   int result = Compare(a, b, only_compare_index_keys, &ok);
+  // TODO(dmurph): Report this somehow. https://crbug.com/913121
   DCHECK(ok);
   if (!ok)
     return 0;
diff --git a/content/browser/indexed_db/indexed_db_leveldb_operations.cc b/content/browser/indexed_db/indexed_db_leveldb_operations.cc
index 1e981b9..9469413 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_operations.cc
+++ b/content/browser/indexed_db/indexed_db_leveldb_operations.cc
@@ -4,11 +4,22 @@
 
 #include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
 
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/values.h"
+#include "content/browser/indexed_db/indexed_db_data_format_version.h"
+#include "content/browser/indexed_db/indexed_db_data_loss_info.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
 #include "content/browser/indexed_db/indexed_db_reporting.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
 #include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
 #include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
+#include "storage/common/database/database_identifier.h"
+#include "third_party/leveldatabase/env_chromium.h"
 
 using base::StringPiece;
 using blink::IndexedDBKeyPath;
@@ -16,20 +27,61 @@
 
 namespace content {
 namespace indexed_db {
-
-leveldb::Status InternalInconsistencyStatus() {
-  return leveldb::Status::Corruption("Internal inconsistency");
-}
-
-leveldb::Status InvalidDBKeyStatus() {
-  return leveldb::Status::InvalidArgument("Invalid database key ID");
-}
-
-leveldb::Status IOErrorStatus() {
-  return leveldb::Status::IOError("IO Error");
-}
-
 namespace {
+
+class IDBComparator : public LevelDBComparator {
+ public:
+  IDBComparator() = default;
+  ~IDBComparator() override = default;
+  int Compare(const base::StringPiece& a,
+              const base::StringPiece& b) const override {
+    return content::Compare(a, b, false /*index_keys*/);
+  }
+  const char* Name() const override { return "idb_cmp1"; }
+};
+
+class LDBComparator : public leveldb::Comparator {
+ public:
+  LDBComparator() = default;
+  ~LDBComparator() override = default;
+  int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override {
+    return content::Compare(leveldb_env::MakeStringPiece(a),
+                            leveldb_env::MakeStringPiece(b),
+                            false /*index_keys*/);
+  }
+  const char* Name() const override { return "idb_cmp1"; }
+  void FindShortestSeparator(std::string* start,
+                             const leveldb::Slice& limit) const override {}
+  void FindShortSuccessor(std::string* key) const override {}
+};
+
+bool IsPathTooLong(const base::FilePath& leveldb_dir) {
+  int limit = base::GetMaximumPathComponentLength(leveldb_dir.DirName());
+  if (limit == -1) {
+    DLOG(WARNING) << "GetMaximumPathComponentLength returned -1";
+// In limited testing, ChromeOS returns 143, other OSes 255.
+#if defined(OS_CHROMEOS)
+    limit = 143;
+#else
+    limit = 255;
+#endif
+  }
+  size_t component_length = leveldb_dir.BaseName().value().length();
+  if (component_length > static_cast<uint32_t>(limit)) {
+    DLOG(WARNING) << "Path component length (" << component_length
+                  << ") exceeds maximum (" << limit
+                  << ") allowed by this filesystem.";
+    const int min = 140;
+    const int max = 300;
+    const int num_buckets = 12;
+    UMA_HISTOGRAM_CUSTOM_COUNTS(
+        "WebCore.IndexedDB.BackingStore.OverlyLargeOriginLength",
+        component_length, min, max, num_buckets);
+    return true;
+  }
+  return false;
+}
+
 template <typename DBOrTransaction>
 Status GetIntInternal(DBOrTransaction* db,
                       const StringPiece& key,
@@ -46,8 +98,161 @@
     return s;
   return InternalInconsistencyStatus();
 }
+
+WARN_UNUSED_RESULT bool IsSchemaKnown(LevelDBDatabase* db, bool* known) {
+  int64_t db_schema_version = 0;
+  bool found = false;
+  Status s = GetInt(db, SchemaVersionKey::Encode(), &db_schema_version, &found);
+  if (!s.ok())
+    return false;
+  if (!found) {
+    *known = true;
+    return true;
+  }
+  if (db_schema_version < 0)
+    return false;  // Only corruption should cause this.
+  if (db_schema_version > indexed_db::kLatestKnownSchemaVersion) {
+    *known = false;
+    return true;
+  }
+
+  int64_t raw_db_data_version = 0;
+  s = GetInt(db, DataVersionKey::Encode(), &raw_db_data_version, &found);
+  if (!s.ok())
+    return false;
+  if (!found) {
+    *known = true;
+    return true;
+  }
+  if (raw_db_data_version < 0)
+    return false;  // Only corruption should cause this.
+
+  *known = IndexedDBDataFormatVersion::GetCurrent().IsAtLeast(
+      IndexedDBDataFormatVersion::Decode(raw_db_data_version));
+  return true;
+}
+std::tuple<std::unique_ptr<LevelDBDatabase>,
+           leveldb::Status,
+           bool /* is_disk_full */>
+DeleteAndRecreateDatabase(
+    const url::Origin& origin,
+    base::FilePath database_path,
+    LevelDBFactory* ldb_factory,
+    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+  scoped_refptr<LevelDBState> state;
+  DCHECK(!database_path.empty())
+      << "Opening an in-memory database should not have failed.";
+  LOG(ERROR) << "IndexedDB backing store open failed, attempting cleanup";
+  state.reset();
+  leveldb::Status status = ldb_factory->DestroyLevelDB(database_path);
+  if (!status.ok()) {
+    LOG(ERROR) << "IndexedDB backing store cleanup failed";
+    ReportOpenStatus(
+        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_CLEANUP_DESTROY_FAILED,
+        origin);
+    return {nullptr, status, false};
+  }
+
+  LOG(ERROR) << "IndexedDB backing store cleanup succeeded, reopening";
+  state.reset();
+  bool is_disk_full;
+  std::tie(state, status, is_disk_full) =
+      ldb_factory->OpenLevelDB(database_path, GetDefaultIndexedDBComparator(),
+                               GetDefaultLevelDBComparator());
+  if (!status.ok()) {
+    LOG(ERROR) << "IndexedDB backing store reopen after recovery failed";
+    ReportOpenStatus(
+        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_FAILED,
+        origin);
+    return {nullptr, status, is_disk_full};
+  }
+  std::unique_ptr<LevelDBDatabase> database = std::make_unique<LevelDBDatabase>(
+      std::move(state), std::move(task_runner),
+      LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase);
+  ReportOpenStatus(
+      indexed_db::INDEXED_DB_BACKING_STORE_OPEN_CLEANUP_REOPEN_SUCCESS, origin);
+
+  return {std::move(database), status, is_disk_full};
+}
+
 }  // namespace
 
+const base::FilePath::CharType kBlobExtension[] = FILE_PATH_LITERAL(".blob");
+const base::FilePath::CharType kIndexedDBExtension[] =
+    FILE_PATH_LITERAL(".indexeddb");
+const base::FilePath::CharType kLevelDBExtension[] =
+    FILE_PATH_LITERAL(".leveldb");
+
+// static
+base::FilePath GetBlobStoreFileName(const url::Origin& origin) {
+  std::string origin_id = storage::GetIdentifierFromOrigin(origin);
+  return base::FilePath()
+      .AppendASCII(origin_id)
+      .AddExtension(kIndexedDBExtension)
+      .AddExtension(kBlobExtension);
+}
+
+// static
+base::FilePath GetLevelDBFileName(const url::Origin& origin) {
+  std::string origin_id = storage::GetIdentifierFromOrigin(origin);
+  return base::FilePath()
+      .AppendASCII(origin_id)
+      .AddExtension(kIndexedDBExtension)
+      .AddExtension(kLevelDBExtension);
+}
+
+base::FilePath ComputeCorruptionFileName(const url::Origin& origin) {
+  return GetLevelDBFileName(origin).Append(
+      FILE_PATH_LITERAL("corruption_info.json"));
+}
+
+std::string ReadCorruptionInfo(const base::FilePath& path_base,
+                               const url::Origin& origin) {
+  const base::FilePath info_path =
+      path_base.Append(ComputeCorruptionFileName(origin));
+  std::string message;
+  if (IsPathTooLong(info_path))
+    return message;
+
+  const int64_t kMaxJsonLength = 4096;
+  int64_t file_size = 0;
+  if (!base::GetFileSize(info_path, &file_size))
+    return message;
+  if (!file_size || file_size > kMaxJsonLength) {
+    base::DeleteFile(info_path, false);
+    return message;
+  }
+
+  base::File file(info_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  if (file.IsValid()) {
+    std::string input_js(file_size, '\0');
+    if (file_size == file.Read(0, base::data(input_js), file_size)) {
+      base::JSONReader reader;
+      std::unique_ptr<base::DictionaryValue> val(
+          base::DictionaryValue::From(reader.ReadToValue(input_js)));
+      if (val)
+        val->GetString("message", &message);
+    }
+    file.Close();
+  }
+
+  base::DeleteFile(info_path, false);
+
+  return message;
+}
+
+leveldb::Status InternalInconsistencyStatus() {
+  return leveldb::Status::Corruption("Internal inconsistency");
+}
+
+leveldb::Status InvalidDBKeyStatus() {
+  return leveldb::Status::InvalidArgument("Invalid database key ID");
+}
+
+leveldb::Status IOErrorStatus() {
+  return leveldb::Status::IOError("IO Error");
+}
+
 Status GetInt(LevelDBTransaction* txn,
               const StringPiece& key,
               int64_t* found_int,
@@ -415,7 +620,7 @@
     LevelDBTransaction* leveldb_transaction,
     int64_t database_id,
     int64_t blob_key_generator_current_number) {
-#ifndef NDEBUG
+#if DCHECK_IS_ON()
   int64_t old_number;
   if (!GetBlobKeyGeneratorCurrentNumber(leveldb_transaction, database_id,
                                         &old_number))
@@ -455,5 +660,142 @@
   indexed_db::PutInt(txn, earliest_sweep_time_key, time_micros);
 }
 
+const LevelDBComparator* GetDefaultIndexedDBComparator() {
+  static const base::NoDestructor<IDBComparator> kIDBComparator;
+  return kIDBComparator.get();
+}
+
+const leveldb::Comparator* GetDefaultLevelDBComparator() {
+  static const base::NoDestructor<LDBComparator> ldb_comparator;
+  return ldb_comparator.get();
+}
+
+std::tuple<base::FilePath /*leveldb_path*/,
+           base::FilePath /*blob_path*/,
+           leveldb::Status>
+CreateDatabaseDirectories(const base::FilePath& path_base,
+                          const url::Origin& origin) {
+  leveldb::Status status;
+  if (!base::CreateDirectoryAndGetError(path_base, nullptr)) {
+    status = Status::IOError("Unable to create IndexedDB database path");
+    LOG(ERROR) << status.ToString() << ": \"" << path_base.AsUTF8Unsafe()
+               << "\"";
+    ReportOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_FAILED_DIRECTORY,
+                     origin);
+    return {base::FilePath(), base::FilePath(), status};
+  }
+
+  base::FilePath leveldb_path = path_base.Append(GetLevelDBFileName(origin));
+  base::FilePath blob_path = path_base.Append(GetBlobStoreFileName(origin));
+  if (IsPathTooLong(leveldb_path)) {
+    ReportOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_ORIGIN_TOO_LONG,
+                     origin);
+    status = Status::IOError("File path too long");
+    return {base::FilePath(), base::FilePath(), status};
+  }
+  return {leveldb_path, blob_path, status};
+}
+
+std::tuple<std::unique_ptr<LevelDBDatabase>,
+           leveldb::Status,
+           IndexedDBDataLossInfo,
+           bool /* is_disk_full */>
+OpenAndVerifyLevelDBDatabase(
+    const url::Origin& origin,
+    base::FilePath path_base,
+    base::FilePath database_path,
+    LevelDBFactory* ldb_factory,
+    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+  // Please see docs/open_and_verify_leveldb_database.code2flow, and the
+  // generated pdf (from https://code2flow.com).
+  // The intended strategy here is to have this function match that flowchart,
+  // where the flowchart should be seen as the 'master' logic template. Please
+  // check the git history of both to make sure they are supposed to be in sync.
+  DCHECK_EQ(database_path.empty(), path_base.empty());
+  IDB_TRACE("indexed_db::OpenAndVerifyLevelDBDatabase");
+  bool is_disk_full;
+  std::unique_ptr<LevelDBDatabase> database;
+  leveldb::Status status;
+  scoped_refptr<LevelDBState> state;
+  std::tie(state, status, is_disk_full) =
+      ldb_factory->OpenLevelDB(database_path, GetDefaultIndexedDBComparator(),
+                               GetDefaultLevelDBComparator());
+  bool is_schema_known = false;
+  // On I/O error the database isn't deleted, in case the issue is temporary.
+  if (status.IsIOError()) {
+    ReportOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_NO_RECOVERY,
+                     origin);
+    return {std::move(database), status, IndexedDBDataLossInfo(), is_disk_full};
+  }
+
+  IndexedDBDataLossInfo data_loss_info;
+  data_loss_info.status = blink::mojom::IDBDataLoss::None;
+  if (status.IsCorruption()) {
+    // On corruption, recovery will happen in the next section.
+    data_loss_info.status = blink::mojom::IDBDataLoss::Total;
+    data_loss_info.message = leveldb_env::GetCorruptionMessage(status);
+    std::tie(database, status, is_disk_full) = DeleteAndRecreateDatabase(
+        origin, database_path, ldb_factory, task_runner);
+    // If successful, then the database should be empty and doesn't need any of
+    // the corruption or schema checks below.
+    if (status.ok()) {
+      ReportOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_SUCCESS,
+                       origin);
+    }
+    return {std::move(database), status, data_loss_info, is_disk_full};
+  }
+  // The leveldb database is successfully opened.
+  DCHECK(status.ok());
+  database = std::make_unique<LevelDBDatabase>(
+      std::move(state), std::move(task_runner),
+      LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase);
+
+  // Check for previous corruption or invalid schemas.
+  std::string corruption_message;
+  if (!path_base.empty())
+    corruption_message = ReadCorruptionInfo(path_base, origin);
+  if (!corruption_message.empty()) {
+    LOG(ERROR) << "IndexedDB recovering from a corrupted (and deleted) "
+                  "database.";
+    ReportOpenStatus(
+        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_FAILED_PRIOR_CORRUPTION,
+        origin);
+    status = leveldb::Status::Corruption(corruption_message);
+    database.reset();
+    data_loss_info.status = blink::mojom::IDBDataLoss::Total;
+    data_loss_info.message =
+        "IndexedDB (database was corrupt): " + corruption_message;
+  } else if (!IsSchemaKnown(database.get(), &is_schema_known)) {
+    LOG(ERROR) << "IndexedDB had IO error checking schema, treating it as "
+                  "failure to open";
+    ReportOpenStatus(
+        indexed_db::
+            INDEXED_DB_BACKING_STORE_OPEN_FAILED_IO_ERROR_CHECKING_SCHEMA,
+        origin);
+    database.reset();
+    data_loss_info.status = blink::mojom::IDBDataLoss::Total;
+    data_loss_info.message = "I/O error checking schema";
+  } else if (!is_schema_known) {
+    LOG(ERROR) << "IndexedDB backing store had unknown schema, treating it "
+                  "as failure to open";
+    ReportOpenStatus(
+        indexed_db::INDEXED_DB_BACKING_STORE_OPEN_FAILED_UNKNOWN_SCHEMA,
+        origin);
+    database.reset();
+    data_loss_info.status = blink::mojom::IDBDataLoss::Total;
+    data_loss_info.message = "Unknown schema";
+  }
+  // Try to delete & recreate the database for any of the above issues.
+  if (!database.get()) {
+    DCHECK(!is_schema_known || status.IsCorruption());
+    std::tie(database, status, is_disk_full) = DeleteAndRecreateDatabase(
+        origin, database_path, ldb_factory, task_runner);
+  }
+
+  if (status.ok())
+    ReportOpenStatus(indexed_db::INDEXED_DB_BACKING_STORE_OPEN_SUCCESS, origin);
+  return {std::move(database), status, data_loss_info, is_disk_full};
+}
+
 }  // namespace indexed_db
 }  // namespace content
diff --git a/content/browser/indexed_db/indexed_db_leveldb_operations.h b/content/browser/indexed_db/indexed_db_leveldb_operations.h
index fbbc9957..dfa747e 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_operations.h
+++ b/content/browser/indexed_db/indexed_db_leveldb_operations.h
@@ -5,14 +5,23 @@
 #ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_OPERATIONS_H_
 #define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_LEVELDB_OPERATIONS_H_
 
+#include <memory>
 #include <string>
+#include <tuple>
 
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
 #include "content/common/content_export.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
+#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
 #include "third_party/leveldatabase/src/include/leveldb/status.h"
+#include "url/origin.h"
 
 // Contains common operations for LevelDBTransactions and/or LevelDBDatabases.
 
@@ -20,8 +29,25 @@
 class LevelDBDatabase;
 class LevelDBIterator;
 class LevelDBTransaction;
+struct IndexedDBDataLossInfo;
 
 namespace indexed_db {
+class LevelDBFactory;
+
+extern const base::FilePath::CharType kBlobExtension[];
+extern const base::FilePath::CharType kIndexedDBExtension[];
+extern const base::FilePath::CharType kLevelDBExtension[];
+
+base::FilePath GetBlobStoreFileName(const url::Origin& origin);
+base::FilePath GetLevelDBFileName(const url::Origin& origin);
+base::FilePath ComputeCorruptionFileName(const url::Origin& origin);
+
+// If a corruption file for the given |origin| at the given |path_base| exists
+// it is deleted, and the message is returned. If the file does not exist, or if
+// there is an error parsing the message, then this method returns an empty
+// string (and deletes the file).
+std::string CONTENT_EXPORT ReadCorruptionInfo(const base::FilePath& path_base,
+                                              const url::Origin& origin);
 
 // Was able to use LevelDB to read the data w/o error, but the data read was not
 // in the expected format.
@@ -139,6 +165,32 @@
 
 void SetEarliestSweepTime(LevelDBTransaction* txn, base::Time earliest_sweep);
 
+CONTENT_EXPORT const LevelDBComparator* GetDefaultIndexedDBComparator();
+
+CONTENT_EXPORT const leveldb::Comparator* GetDefaultLevelDBComparator();
+
+// Creates the leveldb and blob storage directories for IndexedDB.
+std::tuple<base::FilePath /*leveldb_path*/,
+           base::FilePath /*blob_path*/,
+           leveldb::Status>
+CreateDatabaseDirectories(const base::FilePath& path_base,
+                          const url::Origin& origin);
+
+// |path_base| is the directory that will contain the database directory, the
+// blob directory, and any data loss info. |database_path| is the directory for
+// the leveldb database. If |path_base| is empty, then an in-memory database is
+// opened.
+std::tuple<std::unique_ptr<LevelDBDatabase>,
+           leveldb::Status,
+           IndexedDBDataLossInfo,
+           bool /* is_disk_full */>
+OpenAndVerifyLevelDBDatabase(
+    const url::Origin& origin,
+    base::FilePath path_base,
+    base::FilePath database_path,
+    LevelDBFactory* ldb_factory,
+    scoped_refptr<base::SequencedTaskRunner> task_runner);
+
 }  // namespace indexed_db
 }  // namespace content
 
diff --git a/content/browser/indexed_db/indexed_db_quota_client_unittest.cc b/content/browser/indexed_db/indexed_db_quota_client_unittest.cc
index 529c597..be0d471c 100644
--- a/content/browser/indexed_db/indexed_db_quota_client_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_quota_client_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
 #include "content/browser/indexed_db/indexed_db_quota_client.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_browser_thread_bundle.h"
@@ -56,7 +57,8 @@
 
     idb_context_ = new IndexedDBContextImpl(
         browser_context_->GetPath(),
-        browser_context_->GetSpecialStoragePolicy(), quota_manager->proxy());
+        browser_context_->GetSpecialStoragePolicy(), quota_manager->proxy(),
+        indexed_db::GetDefaultLevelDBFactory());
     base::RunLoop().RunUntilIdle();
     setup_temp_dir();
   }
diff --git a/content/browser/indexed_db/indexed_db_reporting.cc b/content/browser/indexed_db/indexed_db_reporting.cc
index 199141c..b8c4baa 100644
--- a/content/browser/indexed_db/indexed_db_reporting.cc
+++ b/content/browser/indexed_db/indexed_db_reporting.cc
@@ -7,7 +7,9 @@
 #include <string>
 
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/strcat.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "url/origin.h"
 
 namespace content {
@@ -21,10 +23,46 @@
   return std::string();
 }
 
+void ParseAndReportIOErrorDetails(const std::string& histogram_name,
+                                  const leveldb::Status& s) {
+  leveldb_env::MethodID method;
+  base::File::Error error = base::File::FILE_OK;
+  leveldb_env::ErrorParsingResult result =
+      leveldb_env::ParseMethodAndError(s, &method, &error);
+  if (result == leveldb_env::NONE)
+    return;
+  base::LinearHistogram::FactoryGet(
+      base::StrCat({histogram_name, ".EnvMethod"}), 1, leveldb_env::kNumEntries,
+      leveldb_env::kNumEntries + 1,
+      base::HistogramBase::kUmaTargetedHistogramFlag)
+      ->Add(method);
+
+  if (result == leveldb_env::METHOD_AND_BFE) {
+    DCHECK_LT(error, 0);
+    base::LinearHistogram::FactoryGet(
+        base::StrCat(
+            {histogram_name, ".BFE.", leveldb_env::MethodIDToString(method)}),
+        1, -base::File::FILE_ERROR_MAX, -base::File::FILE_ERROR_MAX + 1,
+        base::HistogramBase::kUmaTargetedHistogramFlag)
+        ->Add(-error);
+  }
+}
+
+void ParseAndReportCorruptionDetails(const std::string& histogram_name,
+                                     const leveldb::Status& status) {
+  int error = leveldb_env::GetCorruptionCode(status);
+  DCHECK_GE(error, 0);
+  const int kNumPatterns = leveldb_env::GetNumCorruptionCodes();
+  base::LinearHistogram::FactoryGet(
+      base::StrCat({histogram_name, ".Corruption"}), 1, kNumPatterns,
+      kNumPatterns + 1, base::HistogramBase::kUmaTargetedHistogramFlag)
+      ->Add(error);
+}
+
 }  // namespace
 
-void HistogramOpenStatus(IndexedDBBackingStoreOpenResult result,
-                         const url::Origin& origin) {
+void ReportOpenStatus(IndexedDBBackingStoreOpenResult result,
+                      const url::Origin& origin) {
   UMA_HISTOGRAM_ENUMERATION("WebCore.IndexedDB.BackingStore.OpenStatus", result,
                             INDEXED_DB_BACKING_STORE_OPEN_MAX);
   const std::string suffix = OriginToCustomHistogramSuffix(origin);
@@ -34,7 +72,7 @@
   // separately (below).
   if (!suffix.empty()) {
     base::LinearHistogram::FactoryGet(
-        "WebCore.IndexedDB.BackingStore.OpenStatus" + suffix, 1,
+        base::StrCat({"WebCore.IndexedDB.BackingStore.OpenStatus", suffix}), 1,
         INDEXED_DB_BACKING_STORE_OPEN_MAX,
         INDEXED_DB_BACKING_STORE_OPEN_MAX + 1,
         base::HistogramBase::kUmaTargetedHistogramFlag)
@@ -44,11 +82,10 @@
 
 void ReportInternalError(const char* type,
                          IndexedDBBackingStoreErrorSource location) {
-  std::string name;
-  name.append("WebCore.IndexedDB.BackingStore.").append(type).append("Error");
-  base::Histogram::FactoryGet(name, 1, INTERNAL_ERROR_MAX,
-                              INTERNAL_ERROR_MAX + 1,
-                              base::HistogramBase::kUmaTargetedHistogramFlag)
+  base::Histogram::FactoryGet(
+      base::StrCat({"WebCore.IndexedDB.BackingStore.", type, "Error"}), 1,
+      INTERNAL_ERROR_MAX, INTERNAL_ERROR_MAX + 1,
+      base::HistogramBase::kUmaTargetedHistogramFlag)
       ->Add(location);
 }
 
@@ -58,7 +95,7 @@
   const std::string suffix = OriginToCustomHistogramSuffix(origin);
   if (!suffix.empty()) {
     base::LinearHistogram::FactoryGet(
-        "WebCore.IndexedDB.SchemaVersion" + suffix, 0,
+        base::StrCat({"WebCore.IndexedDB.SchemaVersion", suffix}), 0,
         indexed_db::kLatestKnownSchemaVersion,
         indexed_db::kLatestKnownSchemaVersion + 1,
         base::HistogramBase::kUmaTargetedHistogramFlag)
@@ -71,11 +108,41 @@
   const std::string suffix = OriginToCustomHistogramSuffix(origin);
   if (!suffix.empty()) {
     base::BooleanHistogram::FactoryGet(
-        "WebCore.IndexedDB.SchemaV2HasBlobs" + suffix,
+        base::StrCat({"WebCore.IndexedDB.SchemaV2HasBlobs", suffix}),
         base::HistogramBase::kUmaTargetedHistogramFlag)
         ->Add(has_broken_blobs);
   }
 }
 
+void ReportLevelDBError(const std::string& histogram_name,
+                        const leveldb::Status& s) {
+  if (s.ok()) {
+    NOTREACHED();
+    return;
+  }
+  enum {
+    LEVEL_DB_NOT_FOUND,
+    LEVEL_DB_CORRUPTION,
+    LEVEL_DB_IO_ERROR,
+    LEVEL_DB_OTHER,
+    LEVEL_DB_MAX_ERROR
+  };
+  int leveldb_error = LEVEL_DB_OTHER;
+  if (s.IsNotFound())
+    leveldb_error = LEVEL_DB_NOT_FOUND;
+  else if (s.IsCorruption())
+    leveldb_error = LEVEL_DB_CORRUPTION;
+  else if (s.IsIOError())
+    leveldb_error = LEVEL_DB_IO_ERROR;
+  base::Histogram::FactoryGet(histogram_name, 1, LEVEL_DB_MAX_ERROR,
+                              LEVEL_DB_MAX_ERROR + 1,
+                              base::HistogramBase::kUmaTargetedHistogramFlag)
+      ->Add(leveldb_error);
+  if (s.IsIOError())
+    ParseAndReportIOErrorDetails(histogram_name, s);
+  else
+    ParseAndReportCorruptionDetails(histogram_name, s);
+}
+
 }  // namespace indexed_db
 }  // namespace content
diff --git a/content/browser/indexed_db/indexed_db_reporting.h b/content/browser/indexed_db/indexed_db_reporting.h
index ef82e103..6fe9fde 100644
--- a/content/browser/indexed_db/indexed_db_reporting.h
+++ b/content/browser/indexed_db/indexed_db_reporting.h
@@ -73,8 +73,8 @@
   INDEXED_DB_BACKING_STORE_OPEN_MAX,
 };
 
-void HistogramOpenStatus(IndexedDBBackingStoreOpenResult result,
-                         const url::Origin& origin);
+void ReportOpenStatus(IndexedDBBackingStoreOpenResult result,
+                      const url::Origin& origin);
 
 void ReportInternalError(const char* type,
                          IndexedDBBackingStoreErrorSource location);
@@ -83,6 +83,9 @@
 
 void ReportV2Schema(bool has_broken_blobs, const url::Origin& origin);
 
+void ReportLevelDBError(const std::string& histogram_name,
+                        const leveldb::Status& s);
+
 // Use to signal conditions caused by data corruption.
 // A macro is used instead of an inline function so that the assert and log
 // report the line number.
diff --git a/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc b/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc
index 77ea5cc..f37faf3 100644
--- a/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc
@@ -5,14 +5,17 @@
 #include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/time/tick_clock.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
 #include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
 #include "content/browser/indexed_db/leveldb/leveldb_database.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/browser/indexed_db/leveldb/mock_level_db.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -77,15 +80,6 @@
   MOCK_CONST_METHOD0(NowTicks, base::TimeTicks());
 };
 
-class Comparator : public LevelDBComparator {
- public:
-  int Compare(const base::StringPiece& a,
-              const base::StringPiece& b) const override {
-    return content::Compare(a, b, false /*index_keys*/);
-  }
-  const char* Name() const override { return "idb_cmp1"; }
-};
-
 class IndexedDBTombstoneSweeperTest : public testing::TestWithParam<Mode> {
  public:
   IndexedDBTombstoneSweeperTest() {}
@@ -143,8 +137,16 @@
   }
 
   void SetupRealDB() {
-    comparator_.reset(new Comparator());
-    in_memory_db_ = LevelDBDatabase::OpenInMemory(comparator_.get());
+    scoped_refptr<LevelDBState> level_db_state;
+    leveldb::Status s;
+    std::tie(level_db_state, s, std::ignore) =
+        indexed_db::GetDefaultLevelDBFactory()->OpenLevelDB(
+            base::FilePath(), indexed_db::GetDefaultIndexedDBComparator(),
+            indexed_db::GetDefaultLevelDBComparator());
+    ASSERT_TRUE(s.ok());
+    in_memory_db_ = std::make_unique<LevelDBDatabase>(
+        std::move(level_db_state), nullptr,
+        LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase);
     sweeper_ = std::make_unique<IndexedDBTombstoneSweeper>(
         GetParam(), kRoundIterations, kMaxIterations, in_memory_db_->db());
     sweeper_->SetStartSeedsForTesting(0, 0, 0);
@@ -239,7 +241,6 @@
   }
 
  protected:
-  std::unique_ptr<Comparator> comparator_;
   std::unique_ptr<LevelDBDatabase> in_memory_db_;
   leveldb::MockLevelDB mock_db_;
 
diff --git a/content/browser/indexed_db/indexed_db_unittest.cc b/content/browser/indexed_db/indexed_db_unittest.cc
index 10c541b..2b923a12 100644
--- a/content/browser/indexed_db/indexed_db_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_unittest.cc
@@ -14,6 +14,7 @@
 #include "content/browser/indexed_db/indexed_db_connection.h"
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
 #include "content/browser/indexed_db/indexed_db_factory_impl.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/browser/indexed_db/mock_indexed_db_callbacks.h"
 #include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
 #include "content/public/browser/storage_partition.h"
@@ -31,6 +32,37 @@
 using url::Origin;
 
 namespace content {
+namespace {
+
+class LevelDBLock {
+ public:
+  LevelDBLock() : env_(nullptr), lock_(nullptr) {}
+  LevelDBLock(leveldb::Env* env, leveldb::FileLock* lock)
+      : env_(env), lock_(lock) {}
+  ~LevelDBLock() {
+    if (env_)
+      env_->UnlockFile(lock_);
+  }
+
+ private:
+  leveldb::Env* env_;
+  leveldb::FileLock* lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(LevelDBLock);
+};
+
+std::unique_ptr<LevelDBLock> LockForTesting(const base::FilePath& file_name) {
+  leveldb::Env* env = LevelDBEnv::Get();
+  base::FilePath lock_path = file_name.AppendASCII("LOCK");
+  leveldb::FileLock* lock = nullptr;
+  leveldb::Status status = env->LockFile(lock_path.AsUTF8Unsafe(), &lock);
+  if (!status.ok())
+    return std::unique_ptr<LevelDBLock>();
+  DCHECK(lock);
+  return std::make_unique<LevelDBLock>(env, lock);
+}
+
+}  // namespace
 
 class IndexedDBTest : public testing::Test {
  public:
@@ -69,7 +101,7 @@
 
   auto idb_context = base::MakeRefCounted<IndexedDBContextImpl>(
       temp_dir.GetPath(), special_storage_policy_.get(),
-      quota_manager_proxy_.get());
+      quota_manager_proxy_.get(), indexed_db::GetDefaultLevelDBFactory());
 
   normal_path = idb_context->GetFilePathForTesting(kNormalOrigin);
   session_only_path = idb_context->GetFilePathForTesting(kSessionOnlyOrigin);
@@ -99,7 +131,7 @@
   // With the levelDB backend, these are directories.
   auto idb_context = base::MakeRefCounted<IndexedDBContextImpl>(
       temp_dir.GetPath(), special_storage_policy_.get(),
-      quota_manager_proxy_.get());
+      quota_manager_proxy_.get(), indexed_db::GetDefaultLevelDBFactory());
 
   // Save session state. This should bypass the destruction-time deletion.
   idb_context->SetForceKeepSessionState();
@@ -153,7 +185,7 @@
 
   auto idb_context = base::MakeRefCounted<IndexedDBContextImpl>(
       temp_dir.GetPath(), special_storage_policy_.get(),
-      quota_manager_proxy_.get());
+      quota_manager_proxy_.get(), indexed_db::GetDefaultLevelDBFactory());
 
   const Origin kTestOrigin = Origin::Create(GURL("http://test/"));
 
@@ -210,12 +242,12 @@
 
   auto idb_context = base::MakeRefCounted<IndexedDBContextImpl>(
       temp_dir.GetPath(), special_storage_policy_.get(),
-      quota_manager_proxy_.get());
+      quota_manager_proxy_.get(), indexed_db::GetDefaultLevelDBFactory());
 
   base::FilePath test_path = idb_context->GetFilePathForTesting(kTestOrigin);
   ASSERT_TRUE(base::CreateDirectory(test_path));
 
-  auto lock = LevelDBDatabase::LockForTesting(test_path);
+  auto lock = LockForTesting(test_path);
   ASSERT_TRUE(lock);
 
   base::RunLoop loop;
@@ -235,7 +267,7 @@
 
   auto idb_context = base::MakeRefCounted<IndexedDBContextImpl>(
       temp_dir.GetPath(), special_storage_policy_.get(),
-      quota_manager_proxy_.get());
+      quota_manager_proxy_.get(), indexed_db::GetDefaultLevelDBFactory());
 
   auto temp_path = temp_dir.GetPath();
   auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>();
diff --git a/content/browser/indexed_db/leveldb/fake_leveldb_factory.cc b/content/browser/indexed_db/leveldb/fake_leveldb_factory.cc
new file mode 100644
index 0000000..baf374a
--- /dev/null
+++ b/content/browser/indexed_db/leveldb/fake_leveldb_factory.cc
@@ -0,0 +1,101 @@
+// 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 "content/browser/indexed_db/leveldb/fake_leveldb_factory.h"
+
+#include "base/memory/ptr_util.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/slice.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+
+namespace content {
+namespace indexed_db {
+namespace {
+
+// BrokenDB is a fake leveldb::DB that will return a given error status on every
+// method call, or no-op if there is nothing to return.
+class BrokenDB : public leveldb::DB {
+ public:
+  BrokenDB(leveldb::Status returned_status)
+      : returned_status_(std::move(returned_status)) {}
+  ~BrokenDB() override {}
+
+  // Implementations of the DB interface
+  leveldb::Status Put(const leveldb::WriteOptions&,
+                      const leveldb::Slice& key,
+                      const leveldb::Slice& value) override {
+    return returned_status_;
+  }
+  leveldb::Status Delete(const leveldb::WriteOptions&,
+                         const leveldb::Slice& key) override {
+    return returned_status_;
+  }
+  leveldb::Status Write(const leveldb::WriteOptions& options,
+                        leveldb::WriteBatch* updates) override {
+    return returned_status_;
+  }
+  leveldb::Status Get(const leveldb::ReadOptions& options,
+                      const leveldb::Slice& key,
+                      std::string* value) override {
+    return returned_status_;
+  }
+  leveldb::Iterator* NewIterator(const leveldb::ReadOptions&) override {
+    return nullptr;
+  }
+  const leveldb::Snapshot* GetSnapshot() override { return nullptr; }
+  void ReleaseSnapshot(const leveldb::Snapshot* snapshot) override {}
+  bool GetProperty(const leveldb::Slice& property,
+                   std::string* value) override {
+    return false;
+  }
+  void GetApproximateSizes(const leveldb::Range* range,
+                           int n,
+                           uint64_t* sizes) override {}
+  void CompactRange(const leveldb::Slice* begin,
+                    const leveldb::Slice* end) override {}
+
+ private:
+  leveldb::Status returned_status_;
+};
+
+}  // namespace
+
+FakeLevelDBFactory::FakeLevelDBFactory() = default;
+FakeLevelDBFactory::~FakeLevelDBFactory() {}
+
+// static
+scoped_refptr<LevelDBState> FakeLevelDBFactory::GetBrokenLevelDB(
+    leveldb::Status error_to_return,
+    const base::FilePath& reported_file_path) {
+  return LevelDBState::CreateForDiskDB(
+      indexed_db::GetDefaultLevelDBComparator(),
+      indexed_db::GetDefaultIndexedDBComparator(),
+      std::make_unique<BrokenDB>(error_to_return), reported_file_path);
+}
+
+void FakeLevelDBFactory::EnqueueNextOpenLevelDBResult(
+    scoped_refptr<LevelDBState> state,
+    leveldb::Status status,
+    bool is_disk_full) {
+  next_leveldb_states_.push(
+      std::make_tuple(std::move(state), status, is_disk_full));
+}
+
+std::tuple<scoped_refptr<LevelDBState>, leveldb::Status, bool /*disk_full*/>
+FakeLevelDBFactory::OpenLevelDB(
+    const base::FilePath& file_name,
+    const LevelDBComparator* idb_comparator,
+    const leveldb::Comparator* ldb_comparator) const {
+  if (next_leveldb_states_.empty()) {
+    return DefaultLevelDBFactory::OpenLevelDB(file_name, idb_comparator,
+                                              ldb_comparator);
+  }
+  auto tuple = std::move(next_leveldb_states_.front());
+  next_leveldb_states_.pop();
+  return tuple;
+}
+
+}  // namespace indexed_db
+}  // namespace content
diff --git a/content/browser/indexed_db/leveldb/fake_leveldb_factory.h b/content/browser/indexed_db/leveldb/fake_leveldb_factory.h
new file mode 100644
index 0000000..9f21b63
--- /dev/null
+++ b/content/browser/indexed_db/leveldb/fake_leveldb_factory.h
@@ -0,0 +1,47 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_FAKE_LEVELDB_FACTORY_H_
+#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_FAKE_LEVELDB_FACTORY_H_
+
+#include <queue>
+#include <utility>
+
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
+
+namespace content {
+namespace indexed_db {
+
+// Used for unittests, this factory will only create in-memory leveldb
+// databases, and will optionally allow the user to override the next
+// LevelDBState returned with |EnqueueNextLevelDBState|.
+class FakeLevelDBFactory : public DefaultLevelDBFactory {
+ public:
+  FakeLevelDBFactory();
+  ~FakeLevelDBFactory() override;
+
+  static scoped_refptr<LevelDBState> GetBrokenLevelDB(
+      leveldb::Status error_to_return,
+      const base::FilePath& reported_file_path);
+
+  void EnqueueNextOpenLevelDBResult(scoped_refptr<LevelDBState> state,
+                                    leveldb::Status status,
+                                    bool is_disk_full);
+
+  std::tuple<scoped_refptr<LevelDBState>, leveldb::Status, bool /*disk_full*/>
+  OpenLevelDB(const base::FilePath& file_name,
+              const LevelDBComparator* idb_comparator,
+              const leveldb::Comparator* ldb_comparator) const override;
+
+ private:
+  mutable std::queue<std::tuple<scoped_refptr<LevelDBState>,
+                                leveldb::Status,
+                                bool /*disk_full*/>>
+      next_leveldb_states_;
+};
+
+}  // namespace indexed_db
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_INDEXED_DB_LEVELDB_FAKE_LEVELDB_FACTORY_H_
diff --git a/content/browser/indexed_db/leveldb/leveldb_comparator.cc b/content/browser/indexed_db/leveldb/leveldb_comparator.cc
new file mode 100644
index 0000000..6eb3d2a6
--- /dev/null
+++ b/content/browser/indexed_db/leveldb/leveldb_comparator.cc
@@ -0,0 +1,52 @@
+// 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 "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+
+#include <utility>
+
+#include "base/no_destructor.h"
+#include "base/strings/string_piece.h"
+#include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/slice.h"
+
+namespace content {
+namespace {
+
+// Adapter from the leveldb::Slice comparator version to the base::StringPiece
+// version.
+class LevelDBComparatorToIDBComparator : public LevelDBComparator {
+ public:
+  explicit LevelDBComparatorToIDBComparator(
+      const leveldb::Comparator* comparator)
+      : comparator_(comparator) {}
+
+  int Compare(const base::StringPiece& a,
+              const base::StringPiece& b) const override {
+    return comparator_->Compare(leveldb_env::MakeSlice(a),
+                                leveldb_env::MakeSlice(b));
+  }
+  const char* Name() const override { return comparator_->Name(); }
+  void FindShortestSeparator(std::string* start,
+                             const base::StringPiece& limit) const override {
+    comparator_->FindShortestSeparator(start, leveldb_env::MakeSlice(limit));
+  }
+  void FindShortSuccessor(std::string* key) const override {
+    comparator_->FindShortSuccessor(key);
+  }
+
+ private:
+  const leveldb::Comparator* comparator_;
+};
+
+}  // namespace
+
+// static
+const LevelDBComparator* LevelDBComparator::BytewiseComparator() {
+  static const base::NoDestructor<LevelDBComparatorToIDBComparator>
+      idb_comparator(leveldb::BytewiseComparator());
+  return idb_comparator.get();
+}
+
+}  // namespace content
diff --git a/content/browser/indexed_db/leveldb/leveldb_comparator.h b/content/browser/indexed_db/leveldb/leveldb_comparator.h
index aac8808..3af7052 100644
--- a/content/browser/indexed_db/leveldb/leveldb_comparator.h
+++ b/content/browser/indexed_db/leveldb/leveldb_comparator.h
@@ -5,18 +5,29 @@
 #ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_
 #define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_COMPARATOR_H_
 
+#include <memory>
+
 #include "base/strings/string_piece.h"
 #include "content/common/content_export.h"
+#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
 
 namespace content {
 
 class CONTENT_EXPORT LevelDBComparator {
  public:
+  // Returns a global singleton bytewise comparator.
+  static const LevelDBComparator* BytewiseComparator();
+
   virtual ~LevelDBComparator() {}
 
   virtual int Compare(const base::StringPiece& a,
                       const base::StringPiece& b) const = 0;
   virtual const char* Name() const = 0;
+
+  // TODO(dmurph): Support the methods below in the future.
+  virtual void FindShortestSeparator(std::string* start,
+                                     const base::StringPiece& limit) const {}
+  virtual void FindShortSuccessor(std::string* key) const {}
 };
 
 }  // namespace content
diff --git a/content/browser/indexed_db/leveldb/leveldb_database.cc b/content/browser/indexed_db/leveldb/leveldb_database.cc
index 65e8dd55..96ca28b 100644
--- a/content/browser/indexed_db/leveldb/leveldb_database.cc
+++ b/content/browser/indexed_db/leveldb/leveldb_database.cc
@@ -28,6 +28,7 @@
 #include "base/trace_event/process_memory_dump.h"
 #include "build/build_config.h"
 #include "content/browser/indexed_db/indexed_db_class_factory.h"
+#include "content/browser/indexed_db/indexed_db_reporting.h"
 #include "content/browser/indexed_db/indexed_db_tracing.h"
 #include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
 #include "content/browser/indexed_db/leveldb/leveldb_env.h"
@@ -36,7 +37,6 @@
 #include "third_party/leveldatabase/env_chromium.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
 #include "third_party/leveldatabase/src/include/leveldb/db.h"
-#include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
 #include "third_party/leveldatabase/src/include/leveldb/slice.h"
 
 using base::StringPiece;
@@ -61,186 +61,31 @@
 // See http://crbug.com/338385.
 const bool kSyncWrites = true;
 #endif
-
-class LockImpl : public LevelDBLock {
- public:
-  explicit LockImpl(leveldb::Env* env, leveldb::FileLock* lock)
-      : env_(env), lock_(lock) {}
-  ~LockImpl() override { env_->UnlockFile(lock_); }
-
- private:
-  leveldb::Env* env_;
-  leveldb::FileLock* lock_;
-
-  DISALLOW_COPY_AND_ASSIGN(LockImpl);
-};
-
-class ComparatorAdapter : public leveldb::Comparator {
- public:
-  explicit ComparatorAdapter(const LevelDBComparator* comparator)
-      : comparator_(comparator) {}
-
-  int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override {
-    return comparator_->Compare(leveldb_env::MakeStringPiece(a),
-                                leveldb_env::MakeStringPiece(b));
-  }
-
-  const char* Name() const override { return comparator_->Name(); }
-
-  // TODO(jsbell): Support the methods below in the future.
-  void FindShortestSeparator(std::string* start,
-                             const leveldb::Slice& limit) const override {}
-
-  void FindShortSuccessor(std::string* key) const override {}
-
- private:
-  const LevelDBComparator* comparator_;
-};
-
-leveldb::Status OpenDB(
-    leveldb::Comparator* comparator,
-    leveldb::Env* env,
-    const base::FilePath& path,
-    std::unique_ptr<leveldb::DB>* db,
-    std::unique_ptr<const leveldb::FilterPolicy>* filter_policy) {
-  filter_policy->reset(leveldb::NewBloomFilterPolicy(10));
-  leveldb_env::Options options;
-  options.comparator = comparator;
-  options.create_if_missing = true;
-  options.paranoid_checks = true;
-  options.filter_policy = filter_policy->get();
-  options.compression = leveldb::kSnappyCompression;
-  options.write_buffer_size =
-      leveldb_env::WriteBufferSize(base::SysInfo::AmountOfTotalDiskSpace(path));
-
-  // For info about the troubles we've run into with this parameter, see:
-  // https://code.google.com/p/chromium/issues/detail?id=227313#c11
-  options.max_open_files = 80;
-  options.env = env;
-  options.block_cache = leveldb_chrome::GetSharedWebBlockCache();
-
-  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
-  return leveldb_env::OpenDB(options, path.AsUTF8Unsafe(), db);
-}
-
-int CheckFreeSpace(const char* const type, const base::FilePath& file_name) {
-  std::string name =
-      std::string("WebCore.IndexedDB.LevelDB.Open") + type + "FreeDiskSpace";
-  int64_t free_disk_space_in_k_bytes =
-      base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024;
-  if (free_disk_space_in_k_bytes < 0) {
-    base::Histogram::FactoryGet(
-        "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
-        1,
-        2 /*boundary*/,
-        2 /*boundary*/ + 1,
-        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(1 /*sample*/);
-    return -1;
-  }
-  int clamped_disk_space_k_bytes = free_disk_space_in_k_bytes > INT_MAX
-                                       ? INT_MAX
-                                       : free_disk_space_in_k_bytes;
-  const uint64_t histogram_max = static_cast<uint64_t>(1e9);
-  static_assert(histogram_max <= INT_MAX, "histogram_max too big");
-  base::Histogram::FactoryGet(name,
-                              1,
-                              histogram_max,
-                              11 /*buckets*/,
-                              base::HistogramBase::kUmaTargetedHistogramFlag)
-      ->Add(clamped_disk_space_k_bytes);
-  return clamped_disk_space_k_bytes;
-}
-
-void ParseAndHistogramIOErrorDetails(const std::string& histogram_name,
-                                     const leveldb::Status& s) {
-  leveldb_env::MethodID method;
-  base::File::Error error = base::File::FILE_OK;
-  leveldb_env::ErrorParsingResult result =
-      leveldb_env::ParseMethodAndError(s, &method, &error);
-  if (result == leveldb_env::NONE)
-    return;
-  std::string method_histogram_name(histogram_name);
-  method_histogram_name.append(".EnvMethod");
-  base::LinearHistogram::FactoryGet(
-      method_histogram_name,
-      1,
-      leveldb_env::kNumEntries,
-      leveldb_env::kNumEntries + 1,
-      base::HistogramBase::kUmaTargetedHistogramFlag)->Add(method);
-
-  std::string error_histogram_name(histogram_name);
-
-  if (result == leveldb_env::METHOD_AND_BFE) {
-    DCHECK_LT(error, 0);
-    error_histogram_name.append(std::string(".BFE.") +
-                                leveldb_env::MethodIDToString(method));
-    base::LinearHistogram::FactoryGet(
-        error_histogram_name,
-        1,
-        -base::File::FILE_ERROR_MAX,
-        -base::File::FILE_ERROR_MAX + 1,
-        base::HistogramBase::kUmaTargetedHistogramFlag)->Add(-error);
-  }
-}
-
-void ParseAndHistogramCorruptionDetails(const std::string& histogram_name,
-                                        const leveldb::Status& status) {
-  int error = leveldb_env::GetCorruptionCode(status);
-  DCHECK_GE(error, 0);
-  std::string corruption_histogram_name(histogram_name);
-  corruption_histogram_name.append(".Corruption");
-  const int kNumPatterns = leveldb_env::GetNumCorruptionCodes();
-  base::LinearHistogram::FactoryGet(
-      corruption_histogram_name,
-      1,
-      kNumPatterns,
-      kNumPatterns + 1,
-      base::HistogramBase::kUmaTargetedHistogramFlag)->Add(error);
-}
-
-void HistogramLevelDBError(const std::string& histogram_name,
-                           const leveldb::Status& s) {
-  if (s.ok()) {
-    NOTREACHED();
-    return;
-  }
-  enum {
-    LEVEL_DB_NOT_FOUND,
-    LEVEL_DB_CORRUPTION,
-    LEVEL_DB_IO_ERROR,
-    LEVEL_DB_OTHER,
-    LEVEL_DB_MAX_ERROR
-  };
-  int leveldb_error = LEVEL_DB_OTHER;
-  if (s.IsNotFound())
-    leveldb_error = LEVEL_DB_NOT_FOUND;
-  else if (s.IsCorruption())
-    leveldb_error = LEVEL_DB_CORRUPTION;
-  else if (s.IsIOError())
-    leveldb_error = LEVEL_DB_IO_ERROR;
-  base::Histogram::FactoryGet(histogram_name,
-                              1,
-                              LEVEL_DB_MAX_ERROR,
-                              LEVEL_DB_MAX_ERROR + 1,
-                              base::HistogramBase::kUmaTargetedHistogramFlag)
-      ->Add(leveldb_error);
-  if (s.IsIOError())
-    ParseAndHistogramIOErrorDetails(histogram_name, s);
-  else
-    ParseAndHistogramCorruptionDetails(histogram_name, s);
-}
-
 }  // namespace
 
 LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db)
-    : db_(db->db_.get()), snapshot_(db_->GetSnapshot()) {}
+    : db_(db->db()), snapshot_(db_->GetSnapshot()) {}
 
 LevelDBSnapshot::~LevelDBSnapshot() {
   db_->ReleaseSnapshot(snapshot_);
 }
 
-LevelDBDatabase::LevelDBDatabase(size_t max_open_iterators)
-    : clock_(new base::DefaultClock()), iterator_lru_(max_open_iterators) {
+// static
+constexpr const size_t LevelDBDatabase::kDefaultMaxOpenIteratorsPerDatabase;
+
+LevelDBDatabase::LevelDBDatabase(
+    scoped_refptr<LevelDBState> level_db_state,
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    size_t max_open_iterators)
+    : level_db_state_(std::move(level_db_state)),
+      clock_(new base::DefaultClock()),
+      iterator_lru_(max_open_iterators) {
+  if (task_runner) {
+    base::trace_event::MemoryDumpManager::GetInstance()
+        ->RegisterDumpProviderWithSequencedTaskRunner(
+            this, "IndexedDBBackingStore", std::move(task_runner),
+            base::trace_event::MemoryDumpProvider::Options());
+  }
   DCHECK(max_open_iterators);
 }
 
@@ -249,118 +94,6 @@
                                max_iterators_);
   base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
       this);
-  // db_'s destructor uses comparator_adapter_; order of deletion is important.
-  CloseDatabase();
-  comparator_adapter_.reset();
-  env_.reset();
-}
-
-void LevelDBDatabase::CloseDatabase() {
-  if (db_) {
-    base::TimeTicks begin_time = base::TimeTicks::Now();
-    db_.reset();
-    UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.CloseTime",
-                               base::TimeTicks::Now() - begin_time);
-  }
-}
-
-// static
-leveldb::Status LevelDBDatabase::Destroy(const base::FilePath& file_name) {
-  leveldb_env::Options options;
-  options.env = LevelDBEnv::Get();
-  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
-  return leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options);
-}
-
-// static
-std::unique_ptr<LevelDBLock> LevelDBDatabase::LockForTesting(
-    const base::FilePath& file_name) {
-  leveldb::Env* env = LevelDBEnv::Get();
-  base::FilePath lock_path = file_name.AppendASCII("LOCK");
-  leveldb::FileLock* lock = nullptr;
-  leveldb::Status status = env->LockFile(lock_path.AsUTF8Unsafe(), &lock);
-  if (!status.ok())
-    return std::unique_ptr<LevelDBLock>();
-  DCHECK(lock);
-  return std::make_unique<LockImpl>(env, lock);
-}
-
-// static
-leveldb::Status LevelDBDatabase::Open(const base::FilePath& file_name,
-                                      const LevelDBComparator* comparator,
-                                      size_t max_open_cursors,
-                                      std::unique_ptr<LevelDBDatabase>* result,
-                                      bool* is_disk_full) {
-  IDB_TRACE("LevelDBDatabase::Open");
-  base::TimeTicks begin_time = base::TimeTicks::Now();
-
-  std::unique_ptr<ComparatorAdapter> comparator_adapter(
-      std::make_unique<ComparatorAdapter>(comparator));
-
-  std::unique_ptr<leveldb::DB> db;
-  std::unique_ptr<const leveldb::FilterPolicy> filter_policy;
-  const leveldb::Status s = OpenDB(comparator_adapter.get(), LevelDBEnv::Get(),
-                                   file_name, &db, &filter_policy);
-
-  if (!s.ok()) {
-    HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s);
-    int free_space_k_bytes = CheckFreeSpace("Failure", file_name);
-    // Disks with <100k of free space almost never succeed in opening a
-    // leveldb database.
-    if (is_disk_full)
-      *is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100;
-
-    LOG(ERROR) << "Failed to open LevelDB database from "
-               << file_name.AsUTF8Unsafe() << "," << s.ToString();
-    return s;
-  }
-
-  UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.OpenTime",
-                             base::TimeTicks::Now() - begin_time);
-
-  CheckFreeSpace("Success", file_name);
-
-  (*result) = base::WrapUnique(new LevelDBDatabase(max_open_cursors));
-  (*result)->db_ = std::move(db);
-  (*result)->comparator_adapter_ = std::move(comparator_adapter);
-  (*result)->comparator_ = comparator;
-  (*result)->filter_policy_ = std::move(filter_policy);
-  (*result)->file_name_for_tracing = file_name.BaseName().AsUTF8Unsafe();
-
-  return s;
-}
-
-// static
-std::unique_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory(
-    const LevelDBComparator* comparator) {
-  std::unique_ptr<ComparatorAdapter> comparator_adapter(
-      std::make_unique<ComparatorAdapter>(comparator));
-  std::unique_ptr<leveldb::Env> in_memory_env(
-      leveldb_chrome::NewMemEnv("indexed-db", LevelDBEnv::Get()));
-
-  std::unique_ptr<leveldb::DB> db;
-  std::unique_ptr<const leveldb::FilterPolicy> filter_policy;
-  const leveldb::Status s = OpenDB(comparator_adapter.get(),
-                                   in_memory_env.get(),
-                                   base::FilePath(),
-                                   &db,
-                                   &filter_policy);
-
-  if (!s.ok()) {
-    LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString();
-    return std::unique_ptr<LevelDBDatabase>();
-  }
-
-  std::unique_ptr<LevelDBDatabase> result = base::WrapUnique(
-      new LevelDBDatabase(kDefaultMaxOpenIteratorsPerDatabase));
-  result->env_ = std::move(in_memory_env);
-  result->db_ = std::move(db);
-  result->comparator_adapter_ = std::move(comparator_adapter);
-  result->comparator_ = comparator;
-  result->filter_policy_ = std::move(filter_policy);
-  result->file_name_for_tracing = "in-memory-database";
-
-  return result;
 }
 
 leveldb::Status LevelDBDatabase::Put(const StringPiece& key,
@@ -370,8 +103,9 @@
   leveldb::WriteOptions write_options;
   write_options.sync = kSyncWrites;
 
-  const leveldb::Status s = db_->Put(write_options, leveldb_env::MakeSlice(key),
-                                     leveldb_env::MakeSlice(*value));
+  const leveldb::Status s =
+      db()->Put(write_options, leveldb_env::MakeSlice(key),
+                leveldb_env::MakeSlice(*value));
   if (!s.ok())
     LOG(ERROR) << "LevelDB put failed: " << s.ToString();
   else
@@ -386,7 +120,7 @@
   write_options.sync = kSyncWrites;
 
   const leveldb::Status s =
-      db_->Delete(write_options, leveldb_env::MakeSlice(key));
+      db()->Delete(write_options, leveldb_env::MakeSlice(key));
   if (!s.ok() && !s.IsNotFound())
     LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
   last_modified_ = clock_->Now();
@@ -404,14 +138,14 @@
   read_options.snapshot = snapshot ? snapshot->snapshot_ : nullptr;
 
   const leveldb::Status s =
-      db_->Get(read_options, leveldb_env::MakeSlice(key), value);
+      db()->Get(read_options, leveldb_env::MakeSlice(key), value);
   if (s.ok()) {
     *found = true;
     return s;
   }
   if (s.IsNotFound())
     return leveldb::Status::OK();
-  HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s);
+  indexed_db::ReportLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s);
   LOG(ERROR) << "LevelDB get failed: " << s.ToString();
   return s;
 }
@@ -422,9 +156,9 @@
   write_options.sync = kSyncWrites;
 
   const leveldb::Status s =
-      db_->Write(write_options, write_batch.write_batch_.get());
+      db()->Write(write_options, write_batch.write_batch_.get());
   if (!s.ok()) {
-    HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
+    indexed_db::ReportLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
     LOG(ERROR) << "LevelDB write failed: " << s.ToString();
   } else {
     UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.LevelDB.WriteTime",
@@ -440,28 +174,24 @@
   max_iterators_ = std::max(max_iterators_, num_iterators_);
   // Iterator isn't added to lru cache until it is used, as memory isn't loaded
   // for the iterator until it's first Seek call.
-  std::unique_ptr<leveldb::Iterator> i(db_->NewIterator(options));
+  std::unique_ptr<leveldb::Iterator> i(db()->NewIterator(options));
   return std::unique_ptr<LevelDBIterator>(
       IndexedDBClassFactory::Get()->CreateIteratorImpl(std::move(i), this,
                                                        options.snapshot));
 }
 
-const LevelDBComparator* LevelDBDatabase::Comparator() const {
-  return comparator_;
-}
-
 void LevelDBDatabase::Compact(const base::StringPiece& start,
                               const base::StringPiece& stop) {
   IDB_TRACE("LevelDBDatabase::Compact");
   const leveldb::Slice start_slice = leveldb_env::MakeSlice(start);
   const leveldb::Slice stop_slice = leveldb_env::MakeSlice(stop);
   // NULL batch means just wait for earlier writes to be done
-  db_->Write(leveldb::WriteOptions(), nullptr);
-  db_->CompactRange(&start_slice, &stop_slice);
+  db()->Write(leveldb::WriteOptions(), nullptr);
+  db()->CompactRange(&start_slice, &stop_slice);
 }
 
 void LevelDBDatabase::CompactAll() {
-  db_->CompactRange(nullptr, nullptr);
+  db()->CompactRange(nullptr, nullptr);
 }
 
 leveldb::ReadOptions LevelDBDatabase::DefaultReadOptions() {
@@ -480,31 +210,30 @@
 bool LevelDBDatabase::OnMemoryDump(
     const base::trace_event::MemoryDumpArgs& args,
     base::trace_event::ProcessMemoryDump* pmd) {
-  if (!db_)
+  if (!level_db_state_)
     return false;
   // All leveldb databases are already dumped by leveldb_env::DBTracker. Add
   // an edge to the existing database.
   auto* db_tracker_dump =
-      leveldb_env::DBTracker::GetOrCreateAllocatorDump(pmd, db_.get());
+      leveldb_env::DBTracker::GetOrCreateAllocatorDump(pmd, db());
   if (!db_tracker_dump)
     return true;
 
   auto* db_dump = pmd->CreateAllocatorDump(
       base::StringPrintf("site_storage/index_db/db_0x%" PRIXPTR,
-                         reinterpret_cast<uintptr_t>(db_.get())));
+                         reinterpret_cast<uintptr_t>(db())));
   db_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
                      base::trace_event::MemoryAllocatorDump::kUnitsBytes,
                      db_tracker_dump->GetSizeInternal());
   pmd->AddOwnershipEdge(db_dump->guid(), db_tracker_dump->guid());
 
-  if (env_ && leveldb_chrome::IsMemEnv(env_.get())) {
+  if (env() && leveldb_chrome::IsMemEnv(env())) {
     // All leveldb env's are already dumped by leveldb_env::DBTracker. Add
     // an edge to the existing env.
-    auto* env_tracker_dump =
-        DBTracker::GetOrCreateAllocatorDump(pmd, env_.get());
+    auto* env_tracker_dump = DBTracker::GetOrCreateAllocatorDump(pmd, env());
     auto* env_dump = pmd->CreateAllocatorDump(
         base::StringPrintf("site_storage/index_db/memenv_0x%" PRIXPTR,
-                           reinterpret_cast<uintptr_t>(env_.get())));
+                           reinterpret_cast<uintptr_t>(env())));
     env_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
                         base::trace_event::MemoryAllocatorDump::kUnitsBytes,
                         env_tracker_dump->GetSizeInternal());
@@ -532,7 +261,7 @@
   leveldb::ReadOptions read_options;
   read_options.verify_checksums = true;
   read_options.snapshot = snapshot;
-  return std::unique_ptr<leveldb::Iterator>(db_->NewIterator(read_options));
+  return base::WrapUnique(db()->NewIterator(read_options));
 }
 
 LevelDBDatabase::DetachIteratorOnDestruct::~DetachIteratorOnDestruct() {
diff --git a/content/browser/indexed_db/leveldb/leveldb_database.h b/content/browser/indexed_db/leveldb/leveldb_database.h
index 56bf775..516a8a15 100644
--- a/content/browser/indexed_db/leveldb/leveldb_database.h
+++ b/content/browser/indexed_db/leveldb/leveldb_database.h
@@ -12,10 +12,12 @@
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
+#include "base/sequenced_task_runner.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_piece.h"
 #include "base/time/clock.h"
 #include "base/trace_event/memory_dump_provider.h"
+#include "content/browser/indexed_db/scopes/leveldb_state.h"
 #include "content/common/content_export.h"
 #include "third_party/leveldatabase/src/include/leveldb/comparator.h"
 #include "third_party/leveldatabase/src/include/leveldb/options.h"
@@ -24,7 +26,6 @@
 namespace leveldb {
 class Comparator;
 class DB;
-class FilterPolicy;
 class Iterator;
 class Env;
 class Snapshot;
@@ -51,15 +52,6 @@
   DISALLOW_COPY_AND_ASSIGN(LevelDBSnapshot);
 };
 
-class CONTENT_EXPORT LevelDBLock {
- public:
-  LevelDBLock() {}
-  virtual ~LevelDBLock() {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(LevelDBLock);
-};
-
 class CONTENT_EXPORT LevelDBDatabase
     : public base::trace_event::MemoryDumpProvider {
  public:
@@ -68,15 +60,11 @@
   static const size_t kDefaultMaxOpenIteratorsPerDatabase = 50;
 
   // |max_open_cursors| cannot be 0.
-  static leveldb::Status Open(const base::FilePath& file_name,
-                              const LevelDBComparator* comparator,
-                              size_t max_open_cursors,
-                              std::unique_ptr<LevelDBDatabase>* db,
-                              bool* is_disk_full = 0);
+  // All calls to this class should be done on |task_runner|.
+  LevelDBDatabase(scoped_refptr<LevelDBState> level_db_state,
+                  scoped_refptr<base::SequencedTaskRunner> task_runner,
+                  size_t max_open_iterators);
 
-  static std::unique_ptr<LevelDBDatabase> OpenInMemory(
-      const LevelDBComparator* comparator);
-  static leveldb::Status Destroy(const base::FilePath& file_name);
   ~LevelDBDatabase() override;
 
   leveldb::Status Put(const base::StringPiece& key, std::string* value);
@@ -89,7 +77,9 @@
   // Note: Use DefaultReadOptions() and then adjust any values afterwards.
   std::unique_ptr<LevelDBIterator> CreateIterator(
       const leveldb::ReadOptions& options);
-  const LevelDBComparator* Comparator() const;
+  const LevelDBComparator* Comparator() const {
+    return level_db_state_->idb_comparator();
+  }
   void Compact(const base::StringPiece& start, const base::StringPiece& stop);
   void CompactAll();
 
@@ -100,23 +90,17 @@
   bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
                     base::trace_event::ProcessMemoryDump* pmd) override;
 
-  leveldb::DB* db() { return db_.get(); }
-  leveldb::Env* env() { return env_.get(); }
+  leveldb::DB* db() { return level_db_state_->db(); }
+  leveldb::Env* env() { return level_db_state_->in_memory_env(); }
   base::Time LastModified() const { return last_modified_; }
 
   void SetClockForTesting(std::unique_ptr<base::Clock> clock);
 
- protected:
-  explicit LevelDBDatabase(size_t max_open_iterators);
-
  private:
   friend class LevelDBSnapshot;
   friend class LevelDBIteratorImpl;
   FRIEND_TEST_ALL_PREFIXES(IndexedDBTest, DeleteFailsIfDirectoryLocked);
 
-  static std::unique_ptr<LevelDBLock> LockForTesting(
-      const base::FilePath& file_name);
-
   // Methods for iterator pooling.
   std::unique_ptr<leveldb::Iterator> CreateLevelDBIterator(
       const leveldb::Snapshot*);
@@ -125,11 +109,7 @@
 
   void CloseDatabase();
 
-  std::unique_ptr<leveldb::Env> env_;
-  std::unique_ptr<leveldb::Comparator> comparator_adapter_;
-  std::unique_ptr<leveldb::DB> db_;
-  std::unique_ptr<const leveldb::FilterPolicy> filter_policy_;
-  const LevelDBComparator* comparator_;
+  scoped_refptr<LevelDBState> level_db_state_;
   base::Time last_modified_;
   std::unique_ptr<base::Clock> clock_;
 
diff --git a/content/browser/indexed_db/leveldb/leveldb_env.cc b/content/browser/indexed_db/leveldb/leveldb_env.cc
index cdb0a89..f61b8bb 100644
--- a/content/browser/indexed_db/leveldb/leveldb_env.cc
+++ b/content/browser/indexed_db/leveldb/leveldb_env.cc
@@ -4,15 +4,45 @@
 
 #include "content/browser/indexed_db/leveldb/leveldb_env.h"
 
+#include "base/files/file_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/system/sys_info.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
+#include "content/browser/indexed_db/indexed_db_reporting.h"
+#include "content/browser/indexed_db/indexed_db_tracing.h"
+#include "third_party/leveldatabase/leveldb_chrome.h"
+#include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
+
+namespace content {
 namespace {
 
 // Static member initialization.
 base::LazyInstance<content::LevelDBEnv>::Leaky g_leveldb_env =
     LAZY_INSTANCE_INITIALIZER;
 
-}  // namespace
+leveldb_env::Options GetLevelDBOptions(leveldb::Env* env,
+                                       const leveldb::Comparator* comparator,
+                                       size_t write_buffer_size,
+                                       bool paranoid_checks) {
+  static const leveldb::FilterPolicy* kIDBFilterPolicy =
+      leveldb::NewBloomFilterPolicy(10);
+  leveldb_env::Options options;
+  options.comparator = comparator;
+  options.create_if_missing = true;
+  options.paranoid_checks = paranoid_checks;
+  options.filter_policy = kIDBFilterPolicy;
+  options.compression = leveldb::kSnappyCompression;
+  options.write_buffer_size = write_buffer_size;
+  // For info about the troubles we've run into with this parameter, see:
+  // https://crbug.com/227313#c11
+  options.max_open_files = 80;
+  options.env = env;
+  options.block_cache = leveldb_chrome::GetSharedWebBlockCache();
+  return options;
+}
 
-namespace content {
+}  // namespace
 
 LevelDBEnv::LevelDBEnv() : ChromiumEnv("LevelDBEnv.IDB") {}
 
@@ -20,4 +50,112 @@
   return g_leveldb_env.Pointer();
 }
 
+namespace indexed_db {
+
+std::tuple<scoped_refptr<LevelDBState>, leveldb::Status, bool /* disk_full*/>
+DefaultLevelDBFactory::OpenLevelDB(
+    const base::FilePath& file_name,
+    const LevelDBComparator* idb_comparator,
+    const leveldb::Comparator* ldb_comparator) const {
+  // Please see docs/open_and_verify_leveldb_database.code2flow, and the
+  // generated pdf (from https://code2flow.com).
+  // The intended strategy here is to have this function match that flowchart,
+  // where the flowchart should be seen as the 'master' logic template. Please
+  // check the git history of both to make sure they are supposed to be in sync.
+  IDB_TRACE("indexed_db::OpenLDB");
+  DCHECK(strcmp(ldb_comparator->Name(), idb_comparator->Name()) == 0);
+  base::TimeTicks begin_time = base::TimeTicks::Now();
+  leveldb::Status status;
+  std::unique_ptr<leveldb::DB> db;
+
+  if (file_name.empty()) {
+    std::unique_ptr<leveldb::Env> in_memory_env =
+        leveldb_chrome::NewMemEnv("indexed-db", LevelDBEnv::Get());
+
+    constexpr int64_t kBytesInOneMegabyte = 1024 * 1024;
+    leveldb_env::Options in_memory_options =
+        GetLevelDBOptions(in_memory_env.get(), ldb_comparator,
+                          /* default of 4MB */ 4 * kBytesInOneMegabyte,
+                          /*paranoid_checks=*/false);
+    status = leveldb_env::OpenDB(in_memory_options, std::string(), &db);
+    if (!status.ok()) {
+      LOG(ERROR) << "Failed to open in-memory LevelDB database: "
+                 << status.ToString();
+      // Must match the other returns in this function for RVO.
+      return {nullptr, status, false};
+    }
+
+    return {LevelDBState::CreateForInMemoryDB(
+                std::move(in_memory_env), ldb_comparator, idb_comparator,
+                std::move(db), "in-memory-database"),
+            status, false};
+  }
+
+  leveldb_env::Options options =
+      GetLevelDBOptions(LevelDBEnv::Get(), ldb_comparator,
+                        leveldb_env::WriteBufferSize(
+                            base::SysInfo::AmountOfTotalDiskSpace(file_name)),
+                        /*paranoid_checks=*/true);
+
+  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
+  status = leveldb_env::OpenDB(options, file_name.AsUTF8Unsafe(), &db);
+  if (!status.ok()) {
+    ReportLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", status);
+
+    constexpr int64_t kBytesInOneKilobyte = 1024;
+    int64_t free_disk_space_bytes =
+        base::SysInfo::AmountOfFreeDiskSpace(file_name);
+    bool below_100kb = free_disk_space_bytes != -1 &&
+                       free_disk_space_bytes < 100 * kBytesInOneKilobyte;
+
+    // Disks with <100k of free space almost never succeed in opening a
+    // leveldb database.
+    bool is_disk_full = below_100kb || leveldb_env::IndicatesDiskFull(status);
+
+    LOG(ERROR) << "Failed to open LevelDB database from "
+               << file_name.AsUTF8Unsafe() << "," << status.ToString();
+    // Must match the other returns in this function for RVO.
+    return {nullptr, status, is_disk_full};
+  }
+
+  UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.OpenTime",
+                             base::TimeTicks::Now() - begin_time);
+
+  // Must match the other returns in this function for RVO.
+  return {LevelDBState::CreateForDiskDB(ldb_comparator, idb_comparator,
+                                        std::move(db), std::move(file_name)),
+          status, false};
+}
+
+leveldb::Status DefaultLevelDBFactory::DestroyLevelDB(
+    scoped_refptr<LevelDBState> level_db_state) const {
+  DCHECK(level_db_state);
+  DCHECK(!level_db_state->in_memory_env() &&
+         !level_db_state->database_path().empty())
+      << "Cannot destroy an in-memory databases.";
+  DCHECK(level_db_state->HasOneRef())
+      << "Cannot destroy the database when someone else is keeping the state "
+         "alive.";
+  leveldb_env::Options options;
+  options.env = LevelDBEnv::Get();
+  std::string file_path = level_db_state->database_path().AsUTF8Unsafe();
+  level_db_state.reset();
+  // ChromiumEnv assumes UTF8, converts back to FilePath before using.
+  return leveldb::DestroyDB(std::move(file_path), options);
+}
+
+leveldb::Status DefaultLevelDBFactory::DestroyLevelDB(
+    const base::FilePath& path) const {
+  leveldb_env::Options options;
+  options.env = LevelDBEnv::Get();
+  return leveldb::DestroyDB(path.AsUTF8Unsafe(), options);
+}
+
+// static
+DefaultLevelDBFactory* GetDefaultLevelDBFactory() {
+  static base::NoDestructor<DefaultLevelDBFactory> singleton;
+  return singleton.get();
+}
+
+}  // namespace indexed_db
 }  // namespace content
diff --git a/content/browser/indexed_db/leveldb/leveldb_env.h b/content/browser/indexed_db/leveldb/leveldb_env.h
index 9debbaf..d142c10 100644
--- a/content/browser/indexed_db/leveldb/leveldb_env.h
+++ b/content/browser/indexed_db/leveldb/leveldb_env.h
@@ -5,9 +5,18 @@
 #ifndef CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ENV_H_
 #define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ENV_H_
 
+#include <tuple>
+
+#include "base/files/file_path.h"
 #include "base/lazy_instance.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+#include "content/browser/indexed_db/scopes/leveldb_state.h"
 #include "content/common/content_export.h"
 #include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
 
 namespace content {
 
@@ -21,6 +30,51 @@
   CONTENT_EXPORT static LevelDBEnv* Get();
 };
 
+namespace indexed_db {
+
+// Factory class used to open leveldb databases, and stores all necessary
+// objects in a LevelDBState. This interface exists so that it can be mocked out
+// for tests.
+class CONTENT_EXPORT LevelDBFactory {
+ public:
+  virtual ~LevelDBFactory() {}
+
+  // Opens a leveldb database with the given comparators and populates it in
+  // |output_state|. If the |file_name| is empty, then the database will be
+  // in-memory. The comparator names must match.
+  virtual std::
+      tuple<scoped_refptr<LevelDBState>, leveldb::Status, bool /* disk_full*/>
+      OpenLevelDB(const base::FilePath& file_name,
+                  const LevelDBComparator* idb_comparator,
+                  const leveldb::Comparator* ldb_comparator) const = 0;
+
+  // A somewhat safe way to destroy a leveldb database. This asserts that there
+  // are no other references to the given LevelDBState, and deletes the database
+  // on disk. |level_db_state| must only have one ref.
+  virtual leveldb::Status DestroyLevelDB(
+      scoped_refptr<LevelDBState> output_state) const = 0;
+
+  // Assumes that there is no leveldb database currently running for this path.
+  virtual leveldb::Status DestroyLevelDB(const base::FilePath& path) const = 0;
+};
+
+class CONTENT_EXPORT DefaultLevelDBFactory : public LevelDBFactory {
+ public:
+  DefaultLevelDBFactory() = default;
+  ~DefaultLevelDBFactory() override {}
+  std::tuple<scoped_refptr<LevelDBState>, leveldb::Status, bool /* disk_full*/>
+  OpenLevelDB(const base::FilePath& file_name,
+              const LevelDBComparator* idb_comparator,
+              const leveldb::Comparator* ldb_comparator) const override;
+  leveldb::Status DestroyLevelDB(
+      scoped_refptr<LevelDBState> output_state) const override;
+  leveldb::Status DestroyLevelDB(const base::FilePath& path) const override;
+};
+
+// Returns a singleton default factory.
+CONTENT_EXPORT DefaultLevelDBFactory* GetDefaultLevelDBFactory();
+
+}  // namespace indexed_db
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_ENV_H_
diff --git a/content/browser/indexed_db/leveldb/leveldb_env_unittest.cc b/content/browser/indexed_db/leveldb/leveldb_env_unittest.cc
new file mode 100644
index 0000000..dbec948
--- /dev/null
+++ b/content/browser/indexed_db/leveldb/leveldb_env_unittest.cc
@@ -0,0 +1,32 @@
+// 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 "content/browser/indexed_db/leveldb/leveldb_env.h"
+
+#include <utility>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/optional.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace indexed_db {
+namespace {
+
+TEST(LevelDBEnvTest, TestInMemory) {
+  DefaultLevelDBFactory default_factory;
+  scoped_refptr<LevelDBState> state;
+  std::tie(state, std::ignore, std::ignore) = default_factory.OpenLevelDB(
+      base::FilePath(), GetDefaultIndexedDBComparator(),
+      GetDefaultLevelDBComparator());
+  EXPECT_TRUE(state);
+  EXPECT_TRUE(state->in_memory_env());
+}
+
+}  // namespace
+}  // namespace indexed_db
+}  // namespace content
diff --git a/content/browser/indexed_db/leveldb/leveldb_factory.h b/content/browser/indexed_db/leveldb/leveldb_factory.h
deleted file mode 100644
index 42fffad..0000000
--- a/content/browser/indexed_db/leveldb/leveldb_factory.h
+++ /dev/null
@@ -1,34 +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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_FACTORY_H_
-#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_FACTORY_H_
-
-#include <memory>
-
-#include "content/common/content_export.h"
-#include "third_party/leveldatabase/src/include/leveldb/status.h"
-
-namespace base {
-class FilePath;
-}
-
-namespace content {
-
-class LevelDBComparator;
-class LevelDBDatabase;
-
-class CONTENT_EXPORT LevelDBFactory {
- public:
-  virtual ~LevelDBFactory() {}
-  virtual leveldb::Status OpenLevelDB(const base::FilePath& file_name,
-                                      const LevelDBComparator* comparator,
-                                      std::unique_ptr<LevelDBDatabase>* db,
-                                      bool* is_disk_full) = 0;
-  virtual leveldb::Status DestroyLevelDB(const base::FilePath& file_name) = 0;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_INDEXED_DB_LEVELDB_LEVELDB_FACTORY_H_
diff --git a/content/browser/indexed_db/leveldb/leveldb_transaction_unittest.cc b/content/browser/indexed_db/leveldb/leveldb_transaction_unittest.cc
index 373fe388..ca07c5d7 100644
--- a/content/browser/indexed_db/leveldb/leveldb_transaction_unittest.cc
+++ b/content/browser/indexed_db/leveldb/leveldb_transaction_unittest.cc
@@ -6,11 +6,13 @@
 
 #include <algorithm>
 #include <cstring>
+#include <memory>
 #include <string>
 
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_piece.h"
 #include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
 #include "content/browser/indexed_db/leveldb/leveldb_database.h"
@@ -19,22 +21,12 @@
 #include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
 
 namespace content {
 
 namespace {
 static const size_t kTestingMaxOpenCursors = 3;
-
-class SimpleComparator : public LevelDBComparator {
- public:
-  int Compare(const base::StringPiece& a,
-              const base::StringPiece& b) const override {
-    size_t len = std::min(a.size(), b.size());
-    return memcmp(a.begin(), b.begin(), len);
-  }
-  const char* Name() const override { return "temp_comparator"; }
-};
-
 }  // namespace
 
 class LevelDBTransactionTest : public testing::Test {
@@ -42,10 +34,17 @@
   LevelDBTransactionTest() {}
   void SetUp() override {
     ASSERT_TRUE(temp_directory_.CreateUniqueTempDir());
-    leveldb::Status s =
-        LevelDBDatabase::Open(temp_directory_.GetPath(), &comparator_,
-                              kTestingMaxOpenCursors, &leveldb_);
-    ASSERT_TRUE(s.ok());
+    scoped_refptr<LevelDBState> ldb_state;
+    leveldb::Status status;
+    std::tie(ldb_state, status, std::ignore) =
+        indexed_db::GetDefaultLevelDBFactory()->OpenLevelDB(
+            temp_directory_.GetPath(), LevelDBComparator::BytewiseComparator(),
+            leveldb::BytewiseComparator());
+    EXPECT_TRUE(status.ok());
+    ASSERT_TRUE(ldb_state);
+    ASSERT_TRUE(ldb_state->db());
+    leveldb_ = std::make_unique<LevelDBDatabase>(std::move(ldb_state), nullptr,
+                                                 kTestingMaxOpenCursors);
     ASSERT_TRUE(leveldb_);
   }
   void TearDown() override {}
@@ -96,7 +95,7 @@
   }
 
   int Compare(const base::StringPiece& a, const base::StringPiece& b) const {
-    return comparator_.Compare(a, b);
+    return leveldb_->Comparator()->Compare(a, b);
   }
 
   LevelDBDatabase* db() { return leveldb_.get(); }
@@ -111,7 +110,6 @@
 
  private:
   base::ScopedTempDir temp_directory_;
-  SimpleComparator comparator_;
   std::unique_ptr<LevelDBDatabase> leveldb_;
 
   DISALLOW_COPY_AND_ASSIGN(LevelDBTransactionTest);
diff --git a/content/browser/indexed_db/leveldb/leveldb_unittest.cc b/content/browser/indexed_db/leveldb/leveldb_unittest.cc
index ea75c48..f569f1a 100644
--- a/content/browser/indexed_db/leveldb/leveldb_unittest.cc
+++ b/content/browser/indexed_db/leveldb/leveldb_unittest.cc
@@ -2,15 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <content/browser/indexed_db/scopes/leveldb_state.h>
 #include <stddef.h>
 
 #include <algorithm>
 #include <cstring>
 #include <string>
+#include <tuple>
+#include <utility>
 
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_piece.h"
 #include "base/test/simple_test_clock.h"
 #include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
@@ -20,6 +25,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/leveldatabase/env_chromium.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
+#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
 
 namespace content {
 namespace leveldb_unittest {
@@ -28,6 +34,10 @@
 
 class SimpleComparator : public LevelDBComparator {
  public:
+  static const SimpleComparator* Get() {
+    static const base::NoDestructor<SimpleComparator> simple_comparator;
+    return simple_comparator.get();
+  }
   int Compare(const base::StringPiece& a,
               const base::StringPiece& b) const override {
     size_t len = std::min(a.size(), b.size());
@@ -36,6 +46,28 @@
   const char* Name() const override { return "temp_comparator"; }
 };
 
+class SimpleLDBComparator : public leveldb::Comparator {
+ public:
+  static const SimpleLDBComparator* Get() {
+    static const base::NoDestructor<SimpleLDBComparator> simple_ldb_comparator;
+    return simple_ldb_comparator.get();
+  }
+  int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override {
+    size_t len = std::min(a.size(), b.size());
+    return memcmp(a.data(), b.data(), len);
+  }
+  const char* Name() const override { return "temp_comparator"; }
+  void FindShortestSeparator(std::string* start,
+                             const leveldb::Slice& limit) const override {}
+  void FindShortSuccessor(std::string* key) const override {}
+};
+
+std::tuple<scoped_refptr<LevelDBState>, leveldb::Status, bool /* disk_full*/>
+OpenLevelDB(base::FilePath file_name) {
+  return indexed_db::GetDefaultLevelDBFactory()->OpenLevelDB(
+      file_name, SimpleComparator::Get(), SimpleLDBComparator::Get());
+}
+
 TEST(LevelDBDatabaseTest, CorruptionTest) {
   base::ScopedTempDir temp_directory;
   ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
@@ -44,20 +76,26 @@
   const std::string value("value");
   std::string put_value;
   std::string got_value;
-  SimpleComparator comparator;
+  scoped_refptr<LevelDBState> ldb_state;
+  leveldb::Status status;
+  std::tie(ldb_state, status, std::ignore) =
+      OpenLevelDB(temp_directory.GetPath());
+  EXPECT_TRUE(status.ok());
 
-  std::unique_ptr<LevelDBDatabase> leveldb;
-  LevelDBDatabase::Open(temp_directory.GetPath(), &comparator,
-                        kDefaultMaxOpenIteratorsPerDatabase, &leveldb);
+  std::unique_ptr<LevelDBDatabase> leveldb = std::make_unique<LevelDBDatabase>(
+      std::move(ldb_state), nullptr, kDefaultMaxOpenIteratorsPerDatabase);
   EXPECT_TRUE(leveldb);
   put_value = value;
-  leveldb::Status status = leveldb->Put(key, &put_value);
+  status = leveldb->Put(key, &put_value);
   EXPECT_TRUE(status.ok());
   leveldb.reset();
   EXPECT_FALSE(leveldb);
 
-  LevelDBDatabase::Open(temp_directory.GetPath(), &comparator,
-                        kDefaultMaxOpenIteratorsPerDatabase, &leveldb);
+  std::tie(ldb_state, status, std::ignore) =
+      OpenLevelDB(temp_directory.GetPath());
+  EXPECT_TRUE(status.ok());
+  leveldb = std::make_unique<LevelDBDatabase>(
+      std::move(ldb_state), nullptr, kDefaultMaxOpenIteratorsPerDatabase);
   EXPECT_TRUE(leveldb);
   bool found = false;
   status = leveldb->Get(key, &got_value, &found);
@@ -70,19 +108,21 @@
   EXPECT_TRUE(
       leveldb_chrome::CorruptClosedDBForTesting(temp_directory.GetPath()));
 
-  status = LevelDBDatabase::Open(temp_directory.GetPath(), &comparator,
-                                 kDefaultMaxOpenIteratorsPerDatabase, &leveldb);
-  EXPECT_FALSE(leveldb);
+  std::tie(ldb_state, status, std::ignore) =
+      OpenLevelDB(temp_directory.GetPath());
   EXPECT_FALSE(status.ok());
   EXPECT_TRUE(status.IsCorruption());
 
-  status = LevelDBDatabase::Destroy(temp_directory.GetPath());
+  status = indexed_db::GetDefaultLevelDBFactory()->DestroyLevelDB(
+      temp_directory.GetPath());
   EXPECT_TRUE(status.ok());
 
-  status = LevelDBDatabase::Open(temp_directory.GetPath(), &comparator,
-                                 kDefaultMaxOpenIteratorsPerDatabase, &leveldb);
+  std::tie(ldb_state, status, std::ignore) =
+      OpenLevelDB(temp_directory.GetPath());
   EXPECT_TRUE(status.ok());
-  EXPECT_TRUE(leveldb);
+  leveldb = std::make_unique<LevelDBDatabase>(
+      std::move(ldb_state), nullptr, kDefaultMaxOpenIteratorsPerDatabase);
+  ASSERT_TRUE(leveldb);
   status = leveldb->Get(key, &got_value, &found);
   EXPECT_TRUE(status.ok());
   EXPECT_FALSE(found);
@@ -120,14 +160,20 @@
   auto test_clock = std::make_unique<base::SimpleTestClock>();
   base::SimpleTestClock* clock_ptr = test_clock.get();
   clock_ptr->Advance(base::TimeDelta::FromHours(2));
-  std::unique_ptr<LevelDBDatabase> leveldb =
-      LevelDBDatabase::OpenInMemory(&comparator);
+
+  leveldb::Status status;
+  scoped_refptr<LevelDBState> ldb_state;
+  std::tie(ldb_state, status, std::ignore) = OpenLevelDB(base::FilePath());
+  EXPECT_TRUE(status.ok());
+
+  std::unique_ptr<LevelDBDatabase> leveldb = std::make_unique<LevelDBDatabase>(
+      std::move(ldb_state), nullptr, kDefaultMaxOpenIteratorsPerDatabase);
   ASSERT_TRUE(leveldb);
   leveldb->SetClockForTesting(std::move(test_clock));
   // Calling |Put| sets time modified.
   put_value = value;
   base::Time now_time = clock_ptr->Now();
-  leveldb::Status status = leveldb->Put(key, &put_value);
+  status = leveldb->Put(key, &put_value);
   EXPECT_TRUE(status.ok());
   EXPECT_EQ(now_time, leveldb->LastModified());
 
diff --git a/content/browser/indexed_db/leveldb/mock_leveldb_factory.cc b/content/browser/indexed_db/leveldb/mock_leveldb_factory.cc
deleted file mode 100644
index 61ff10b..0000000
--- a/content/browser/indexed_db/leveldb/mock_leveldb_factory.cc
+++ /dev/null
@@ -1,14 +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 "content/browser/indexed_db/leveldb/mock_leveldb_factory.h"
-
-namespace content {
-
-MockLevelDBFactory::MockLevelDBFactory() {
-}
-
-MockLevelDBFactory::~MockLevelDBFactory() {
-}
-
-}  // namespace content
diff --git a/content/browser/indexed_db/leveldb/mock_leveldb_factory.h b/content/browser/indexed_db/leveldb/mock_leveldb_factory.h
deleted file mode 100644
index b01f6e1..0000000
--- a/content/browser/indexed_db/leveldb/mock_leveldb_factory.h
+++ /dev/null
@@ -1,29 +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 CONTENT_BROWSER_INDEXED_DB_LEVELDB_MOCK_LEVELDB_FACTORY_H_
-#define CONTENT_BROWSER_INDEXED_DB_LEVELDB_MOCK_LEVELDB_FACTORY_H_
-
-#include "base/files/file_path.h"
-#include "content/browser/indexed_db/leveldb/leveldb_factory.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace content {
-
-class MockLevelDBFactory : public LevelDBFactory {
- public:
-  MockLevelDBFactory();
-  ~MockLevelDBFactory() override;
-  MOCK_METHOD4(OpenLevelDB,
-               leveldb::Status(const base::FilePath& file_name,
-                               const LevelDBComparator* comparator,
-                               std::unique_ptr<LevelDBDatabase>* db,
-                               bool* is_disk_full));
-  MOCK_METHOD1(DestroyLevelDB,
-               leveldb::Status(const base::FilePath& file_name));
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_INDEXED_DB_LEVELDB_MOCK_LEVELDB_FACTORY_H_
diff --git a/content/browser/indexed_db/mock_indexed_db_factory.h b/content/browser/indexed_db/mock_indexed_db_factory.h
index 53274cf..25c1d62 100644
--- a/content/browser/indexed_db/mock_indexed_db_factory.h
+++ b/content/browser/indexed_db/mock_indexed_db_factory.h
@@ -103,22 +103,13 @@
  protected:
   ~MockIndexedDBFactory() override;
 
-  MOCK_METHOD5(OpenBackingStore,
-               scoped_refptr<IndexedDBBackingStore>(
-                   const url::Origin& origin,
-                   const base::FilePath& data_directory,
-                   IndexedDBDataLossInfo* data_loss_info,
-                   bool* disk_full,
-                   leveldb::Status* s));
-
-  MOCK_METHOD6(OpenBackingStoreHelper,
-               scoped_refptr<IndexedDBBackingStore>(
-                   const url::Origin& origin,
-                   const base::FilePath& data_directory,
-                   IndexedDBDataLossInfo* data_loss_info,
-                   bool* disk_full,
-                   bool first_time,
-                   leveldb::Status* s));
+  MOCK_METHOD2(
+      OpenBackingStore,
+      std::tuple<scoped_refptr<IndexedDBBackingStore>,
+                 leveldb::Status,
+                 IndexedDBDataLossInfo,
+                 bool /* disk_full */>(const url::Origin& origin,
+                                       const base::FilePath& data_directory));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockIndexedDBFactory);
diff --git a/content/browser/indexed_db/scopes/leveldb_state.cc b/content/browser/indexed_db/scopes/leveldb_state.cc
new file mode 100644
index 0000000..868ff96
--- /dev/null
+++ b/content/browser/indexed_db/scopes/leveldb_state.cc
@@ -0,0 +1,58 @@
+// 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 <content/browser/indexed_db/scopes/leveldb_state.h>
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
+
+namespace content {
+
+// static
+scoped_refptr<LevelDBState> LevelDBState::CreateForDiskDB(
+    const leveldb::Comparator* comparator,
+    const LevelDBComparator* idb_comparator,
+    std::unique_ptr<leveldb::DB> database,
+    base::FilePath database_path) {
+  return base::WrapRefCounted(new LevelDBState(
+      nullptr, comparator, idb_comparator, std::move(database),
+      std::move(database_path), database_path.BaseName().AsUTF8Unsafe()));
+}
+
+// static
+scoped_refptr<LevelDBState> LevelDBState::CreateForInMemoryDB(
+    std::unique_ptr<leveldb::Env> in_memory_env,
+    const leveldb::Comparator* comparator,
+    const LevelDBComparator* idb_comparator,
+    std::unique_ptr<leveldb::DB> in_memory_database,
+    std::string name_for_tracing) {
+  return base::WrapRefCounted(
+      new LevelDBState(std::move(in_memory_env), comparator, idb_comparator,
+                       std::move(in_memory_database), base::FilePath(),
+                       std::move(name_for_tracing)));
+}
+
+LevelDBState::LevelDBState(std::unique_ptr<leveldb::Env> optional_in_memory_env,
+                           const leveldb::Comparator* comparator,
+                           const LevelDBComparator* idb_comparator,
+                           std::unique_ptr<leveldb::DB> database,
+                           base::FilePath database_path,
+                           std::string name_for_tracing)
+    : in_memory_env_(std::move(optional_in_memory_env)),
+      comparator_(comparator),
+      idb_comparator_(idb_comparator),
+      db_(std::move(database)),
+      database_path_(std::move(database_path)),
+      name_for_tracing_(std::move(name_for_tracing)) {}
+
+LevelDBState::~LevelDBState() {
+  if (!db_)
+    return;
+  base::TimeTicks begin_time = base::TimeTicks::Now();
+  const_cast<std::unique_ptr<leveldb::DB>*>(&db_)->reset();
+  UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.CloseTime",
+                             base::TimeTicks::Now() - begin_time);
+}
+
+}  // namespace content
diff --git a/content/browser/indexed_db/scopes/leveldb_state.h b/content/browser/indexed_db/scopes/leveldb_state.h
new file mode 100644
index 0000000..e3dcf5c3
--- /dev/null
+++ b/content/browser/indexed_db/scopes/leveldb_state.h
@@ -0,0 +1,70 @@
+// 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 CONTENT_BROWSER_INDEXED_DB_SCOPES_LEVELDB_STATE_H_
+#define CONTENT_BROWSER_INDEXED_DB_SCOPES_LEVELDB_STATE_H_
+
+#include "base/memory/ref_counted.h"
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
+#include "content/common/content_export.h"
+#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
+
+namespace content {
+
+// Encapsulates a leveldb database and comparator, allowing them to be used
+// safely across thread boundaries.
+class CONTENT_EXPORT LevelDBState
+    : public base::RefCountedThreadSafe<LevelDBState> {
+ public:
+  static scoped_refptr<LevelDBState> CreateForDiskDB(
+      const leveldb::Comparator* comparator,
+      const LevelDBComparator* idb_comparator,
+      std::unique_ptr<leveldb::DB> database,
+      base::FilePath database_path);
+
+  static scoped_refptr<LevelDBState> CreateForInMemoryDB(
+      std::unique_ptr<leveldb::Env> in_memory_env,
+      const leveldb::Comparator* comparator,
+      const LevelDBComparator* idb_comparator,
+      std::unique_ptr<leveldb::DB> in_memory_database,
+      std::string name_for_tracing);
+
+  const leveldb::Comparator* comparator() const { return comparator_; }
+  const LevelDBComparator* idb_comparator() const { return idb_comparator_; }
+  leveldb::DB* db() const { return db_.get(); }
+  const std::string& name_for_tracing() const { return name_for_tracing_; }
+
+  // Null for on-disk databases.
+  leveldb::Env* in_memory_env() const { return in_memory_env_.get(); }
+  // Empty for in-memory databases.
+  const base::FilePath& database_path() const { return database_path_; }
+
+ private:
+  friend class base::RefCountedThreadSafe<LevelDBState>;
+
+  LevelDBState(std::unique_ptr<leveldb::Env> optional_in_memory_env,
+               const leveldb::Comparator* comparator,
+               const LevelDBComparator* idb_comparator,
+               std::unique_ptr<leveldb::DB> database,
+               base::FilePath database_path,
+               std::string name_for_tracing);
+  ~LevelDBState();
+
+  const std::unique_ptr<leveldb::Env> in_memory_env_;
+  const leveldb::Comparator* comparator_;
+  const LevelDBComparator* idb_comparator_;
+  const std::unique_ptr<leveldb::DB> db_;
+  const base::FilePath database_path_;
+  const std::string name_for_tracing_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_INDEXED_DB_SCOPES_LEVELDB_STATE_H_
diff --git a/content/browser/loader/cross_site_document_resource_handler.cc b/content/browser/loader/cross_site_document_resource_handler.cc
index 8f434fc..7d5a3367 100644
--- a/content/browser/loader/cross_site_document_resource_handler.cc
+++ b/content/browser/loader/cross_site_document_resource_handler.cc
@@ -593,13 +593,12 @@
   //    (i.e. CanAccessDataForOrigin would only plug one hole in the
   //    non-NetworkService world)
   // 2) we hope that NetworkService will ship soon.
-  constexpr auto kInitiatorLockCompatibility =
-      network::InitiatorLockCompatibility::kCompatibleLock;
+  base::Optional<url::Origin> kInitiatorLock = base::nullopt;
 
   // Delegate most decisions to CrossOriginReadBlocking::ResponseAnalyzer.
   analyzer_ =
       std::make_unique<network::CrossOriginReadBlocking::ResponseAnalyzer>(
-          *request(), response, kInitiatorLockCompatibility);
+          *request(), response, kInitiatorLock);
   if (analyzer_->ShouldAllow())
     return false;
 
diff --git a/content/browser/net/net_command_line_flags_browsertest.cc b/content/browser/net/net_command_line_flags_browsertest.cc
new file mode 100644
index 0000000..e47cbbf0
--- /dev/null
+++ b/content/browser/net/net_command_line_flags_browsertest.cc
@@ -0,0 +1,52 @@
+// 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 "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/shell/browser/shell.h"
+#include "services/network/public/cpp/network_switches.h"
+
+namespace content {
+
+class CommandLineFlagsBrowserTest : public ContentBrowserTest {
+ protected:
+  network::mojom::NetworkContext* network_context() {
+    return content::BrowserContext::GetDefaultStoragePartition(
+               shell()->web_contents()->GetBrowserContext())
+        ->GetNetworkContext();
+  }
+};
+
+// Tests that when no special command line flags are passed, requests to port 79
+// (finger) fail with ERR_UNSAFE_PORT.
+IN_PROC_BROWSER_TEST_F(CommandLineFlagsBrowserTest, Port79DefaultBlocked) {
+  EXPECT_EQ(net::ERR_UNSAFE_PORT,
+            content::LoadBasicRequest(network_context(),
+                                      GURL("http://127.0.0.1:79"), 0, 0, 0));
+}
+
+class ExplicitlyAllowPort79BrowserTest : public CommandLineFlagsBrowserTest {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(network::switches::kExplicitlyAllowedPorts,
+                                    "79");
+  }
+};
+
+// Tests that when run with the flag --explicitly-allowed-ports=79, requests to
+// port 79 (finger) are permitted.
+//
+// The request may succeed or fail depending on the platform and what services
+// are running, so the test just verifies the reason for failure is not
+// ERR_UNSAFE_PORT.
+IN_PROC_BROWSER_TEST_F(ExplicitlyAllowPort79BrowserTest, Load) {
+  EXPECT_NE(net::ERR_UNSAFE_PORT,
+            content::LoadBasicRequest(network_context(),
+                                      GURL("http://127.0.0.1:79"), 0, 0, 0));
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index aac3f05..b8a0e79 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -2982,6 +2982,7 @@
   // with any associated values) if present in the browser command line.
   static const char* const kSwitchNames[] = {
     network::switches::kNoReferrers,
+    network::switches::kExplicitlyAllowedPorts,
     service_manager::switches::kDisableInProcessStackTraces,
     service_manager::switches::kDisableSeccompFilterSandbox,
     service_manager::switches::kNoSandbox,
@@ -3064,7 +3065,6 @@
     switches::kEnableWebGLDraftExtensions,
     switches::kEnableWebGLImageChromium,
     switches::kEnableWebVR,
-    switches::kExplicitlyAllowedPorts,
     switches::kFileUrlPathAlias,
     switches::kForceDisplayColorProfile,
     switches::kForceDeviceScaleFactor,
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index b775221..efe239e 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -34,6 +34,7 @@
 #include "content/browser/cookie_store/cookie_store_context.h"
 #include "content/browser/fileapi/browser_file_system_helper.h"
 #include "content/browser/gpu/shader_cache_factory.h"
+#include "content/browser/indexed_db/leveldb/leveldb_env.h"
 #include "content/browser/loader/prefetch_url_loader_service.h"
 #include "content/browser/notifications/platform_notification_context_impl.h"
 #include "content/common/dom_storage/dom_storage_types.h"
@@ -643,7 +644,8 @@
 
   base::FilePath path = in_memory ? base::FilePath() : partition_path;
   partition->indexed_db_context_ = new IndexedDBContextImpl(
-      path, context->GetSpecialStoragePolicy(), quota_manager_proxy);
+      path, context->GetSpecialStoragePolicy(), quota_manager_proxy,
+      indexed_db::GetDefaultLevelDBFactory());
 
   partition->cache_storage_context_ = new CacheStorageContextImpl(context);
   partition->cache_storage_context_->Init(path, quota_manager_proxy);
diff --git a/content/browser/utility_process_host.cc b/content/browser/utility_process_host.cc
index 074f2906..5e1c913 100644
--- a/content/browser/utility_process_host.cc
+++ b/content/browser/utility_process_host.cc
@@ -352,6 +352,7 @@
       network::switches::kIgnoreUrlFetcherCertRequests,
       network::switches::kLogNetLog,
       network::switches::kNoReferrers,
+      network::switches::kExplicitlyAllowedPorts,
       service_manager::switches::kNoSandbox,
 #if defined(OS_MACOSX)
       service_manager::switches::kEnableSandboxLogging,
diff --git a/content/browser/web_contents/web_contents_view_cocoa.mm b/content/browser/web_contents/web_contents_view_cocoa.mm
index 5cb0953..53bd394 100644
--- a/content/browser/web_contents/web_contents_view_cocoa.mm
+++ b/content/browser/web_contents/web_contents_view_cocoa.mm
@@ -14,10 +14,8 @@
 #include "content/public/browser/web_contents_view_delegate.h"
 #import "third_party/mozilla/NSPasteboard+Utils.h"
 #include "ui/base/clipboard/custom_data_helper.h"
-#include "ui/base/cocoa/cocoa_base_utils.h"
 #include "ui/base/dragdrop/cocoa_dnd_util.h"
 
-using content::DraggingInfo;
 using content::DropData;
 using content::WebContentsImpl;
 using content::WebContentsViewMac;
@@ -61,33 +59,6 @@
   [super dealloc];
 }
 
-- (void)populateDraggingInfo:(DraggingInfo*)info
-          fromNSDraggingInfo:(id<NSDraggingInfo>)nsInfo {
-  NSPoint windowPoint = [nsInfo draggingLocation];
-
-  NSPoint viewPoint = [self convertPoint:windowPoint fromView:nil];
-  NSRect viewFrame = [self frame];
-  info->location_in_view =
-      gfx::PointF(viewPoint.x, viewFrame.size.height - viewPoint.y);
-
-  NSPoint screenPoint =
-      ui::ConvertPointFromWindowToScreen([self window], windowPoint);
-  NSScreen* screen = [[self window] screen];
-  NSRect screenFrame = [screen frame];
-  info->location_in_screen =
-      gfx::PointF(screenPoint.x, screenFrame.size.height - screenPoint.y);
-
-  info->location_in_view = gfx::PointF(viewPoint.x, viewPoint.y);
-  info->location_in_screen = gfx::PointF(screenPoint.x, screenPoint.y);
-  NSPasteboard* pboard = [nsInfo draggingPasteboard];
-  if ([pboard containsURLDataConvertingTextToURL:YES]) {
-    GURL url;
-    ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES);
-    info->url.emplace(url);
-  }
-  info->operation_mask = [nsInfo draggingSourceOperationMask];
-}
-
 - (BOOL)allowsVibrancy {
   // Returning YES will allow rendering this view with vibrancy effect if it is
   // incorporated into a view hierarchy that uses vibrancy, it will have no
@@ -214,10 +185,7 @@
   content::PopulateDropDataFromPasteboard(&dropData,
                                           [sender draggingPasteboard]);
   [dragDest_ setDropData:dropData];
-
-  DraggingInfo draggingInfo;
-  [self populateDraggingInfo:&draggingInfo fromNSDraggingInfo:sender];
-  return [dragDest_ draggingEntered:draggingInfo];
+  return [dragDest_ draggingEntered:sender view:self];
 }
 
 - (void)draggingExited:(id<NSDraggingInfo>)sender {
@@ -225,15 +193,11 @@
 }
 
 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
-  DraggingInfo draggingInfo;
-  [self populateDraggingInfo:&draggingInfo fromNSDraggingInfo:sender];
-  return [dragDest_ draggingUpdated:draggingInfo];
+  return [dragDest_ draggingUpdated:sender view:self];
 }
 
 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
-  DraggingInfo draggingInfo;
-  [self populateDraggingInfo:&draggingInfo fromNSDraggingInfo:sender];
-  return [dragDest_ performDragOperation:draggingInfo];
+  return [dragDest_ performDragOperation:sender view:self];
 }
 
 - (void)cancelDeferredClose {
diff --git a/content/browser/web_contents/web_drag_dest_mac.h b/content/browser/web_contents/web_drag_dest_mac.h
index 0f51b9f..990cd2cd5 100644
--- a/content/browser/web_contents/web_drag_dest_mac.h
+++ b/content/browser/web_contents/web_drag_dest_mac.h
@@ -33,27 +33,6 @@
 void CONTENT_EXPORT PopulateDropDataFromPasteboard(content::DropData* data,
                                                    NSPasteboard* pboard);
 
-// The data extracted from an NSDraggingInfo needed by draggingEntered,
-// draggingUpdated, and performDragOperation.
-// TODO(https://crbug.com/898608): Change this to be a mojo structure.
-struct CONTENT_EXPORT DraggingInfo {
-  DraggingInfo();
-  ~DraggingInfo();
-
-  // The dragging location in the NSView, with the origin in the upper-left.
-  gfx::PointF location_in_view;
-
-  // The dragging location in the NSScreen, with the origin in the upper-left.
-  gfx::PointF location_in_screen;
-
-  // The URL data from the drag, if any. Note that this is redundant in that it
-  // is already present in DropData. It is here because it is used by methods
-  // that don't use DropData.
-  base::Optional<GURL> url;
-
-  // The operation mask.
-  uint32_t operation_mask;
-};
 }
 
 // A class that handles tracking and event processing for a drag and drop
@@ -116,15 +95,17 @@
 // Messages to send during the tracking of a drag, ususally upon receiving
 // calls from the view system. Communicates the drag messages to WebCore.
 - (void)setDropData:(const content::DropData&)dropData;
-- (NSDragOperation)draggingEntered:(const content::DraggingInfo&)info;
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
+                              view:(NSView*)view;
 - (void)draggingExited;
-- (NSDragOperation)draggingUpdated:(const content::DraggingInfo&)info;
-- (BOOL)performDragOperation:(const content::DraggingInfo&)info;
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
+                              view:(NSView*)view;
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)info view:(NSView*)view;
 
 // Helper to call WebWidgetHostInputEventRouter::GetRenderWidgetHostAtPoint().
 - (content::RenderWidgetHostImpl*)
-    GetRenderWidgetHostAtPoint:(const gfx::PointF&)viewPoint
-                 transformedPt:(gfx::PointF*)transformedPt;
+GetRenderWidgetHostAtPoint:(const NSPoint&)viewPoint
+             transformedPt:(gfx::PointF*)transformedPt;
 
 // Sets |dragStartProcessID_| and |dragStartViewID_|.
 - (void)setDragStartTrackersForProcess:(int)processID;
diff --git a/content/browser/web_contents/web_drag_dest_mac.mm b/content/browser/web_contents/web_drag_dest_mac.mm
index e8ae8e1f..e9d6a913 100644
--- a/content/browser/web_contents/web_drag_dest_mac.mm
+++ b/content/browser/web_contents/web_drag_dest_mac.mm
@@ -26,7 +26,6 @@
 #include "ui/gfx/geometry/point.h"
 
 using blink::WebDragOperationsMask;
-using content::DraggingInfo;
 using content::DropData;
 using content::OpenURLParams;
 using content::Referrer;
@@ -138,11 +137,17 @@
   dropDataUnfiltered_ = std::make_unique<DropData>(dropData);
 }
 
-- (NSDragOperation)draggingEntered:(const DraggingInfo&)info {
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
+                              view:(NSView*)view {
   // Save off the RVH so we can tell if it changes during a drag. If it does,
   // we need to send a new enter message in draggingUpdated:.
   currentRVH_ = webContents_->GetRenderViewHost();
 
+  // Create the appropriate mouse locations for WebCore. The draggingLocation
+  // is in window coordinates. Both need to be flipped.
+  NSPoint windowPoint = [info draggingLocation];
+  NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
+  NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
   gfx::PointF transformedPt;
   if (!webContents_->GetRenderWidgetHostView()) {
     // TODO(ekaramad, paulmeyer): Find a better way than toggling |canceled_|.
@@ -153,8 +158,7 @@
   }
 
   content::RenderWidgetHostImpl* targetRWH =
-      [self GetRenderWidgetHostAtPoint:info.location_in_view
-                         transformedPt:&transformedPt];
+      [self GetRenderWidgetHostAtPoint:viewPoint transformedPt:&transformedPt];
   if (![self isValidDragTarget:targetRWH])
     return NSDragOperationNone;
 
@@ -166,7 +170,7 @@
   currentRWHForDrag_ = targetRWH->GetWeakPtr();
   currentRWHForDrag_->FilterDropData(dropData.get());
 
-  NSDragOperation mask = info.operation_mask;
+  NSDragOperation mask = [info draggingSourceOperationMask];
 
   // Give the delegate an opportunity to cancel the drag.
   canceled_ = !webContents_->GetDelegate()->CanDragEnter(
@@ -177,7 +181,7 @@
     return NSDragOperationNone;
 
   if ([self onlyAllowsNavigation]) {
-    if (info.url)
+    if ([[info draggingPasteboard] containsURLDataConvertingTextToURL:YES])
       return NSDragOperationCopy;
     return NSDragOperationNone;
   }
@@ -190,7 +194,8 @@
   dropDataFiltered_.swap(dropData);
 
   currentRWHForDrag_->DragTargetDragEnter(
-      *dropDataFiltered_, transformedPt, info.location_in_screen,
+      *dropDataFiltered_, transformedPt,
+      gfx::PointF(screenPoint.x, screenPoint.y),
       static_cast<WebDragOperationsMask>(mask), GetModifierFlags());
 
   // We won't know the true operation (whether the drag is allowed) until we
@@ -221,17 +226,21 @@
   dropDataFiltered_.reset();
 }
 
-- (NSDragOperation)draggingUpdated:(const DraggingInfo&)info {
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info view:(NSView*)view {
   if (canceled_) {
     // TODO(ekaramad,paulmeyer): We probably shouldn't be checking for
     // |canceled_| twice in this method.
     return NSDragOperationNone;
   }
 
+  // Create the appropriate mouse locations for WebCore. The draggingLocation
+  // is in window coordinates. Both need to be flipped.
+  NSPoint windowPoint = [info draggingLocation];
+  NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
+  NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
   gfx::PointF transformedPt;
   content::RenderWidgetHostImpl* targetRWH =
-      [self GetRenderWidgetHostAtPoint:info.location_in_view
-                         transformedPt:&transformedPt];
+      [self GetRenderWidgetHostAtPoint:viewPoint transformedPt:&transformedPt];
 
   if (![self isValidDragTarget:targetRWH])
     return NSDragOperationNone;
@@ -240,8 +249,9 @@
   // per drag, even without the drag ever leaving the window.
   if (targetRWH != currentRWHForDrag_.get()) {
     if (currentRWHForDrag_) {
-      gfx::PointF transformedLeavePoint = info.location_in_view;
-      gfx::PointF transformedScreenPoint = info.location_in_screen;
+      gfx::PointF transformedLeavePoint = gfx::PointF(viewPoint.x, viewPoint.y);
+      gfx::PointF transformedScreenPoint =
+          gfx::PointF(screenPoint.x, screenPoint.y);
       content::RenderWidgetHostViewBase* rootView =
           static_cast<content::RenderWidgetHostViewBase*>(
               webContents_->GetRenderWidgetHostView());
@@ -255,22 +265,22 @@
       currentRWHForDrag_->DragTargetDragLeave(transformedLeavePoint,
                                               transformedScreenPoint);
     }
-    [self draggingEntered:info];
+    [self draggingEntered:info view:view];
   }
 
   if (canceled_)
     return NSDragOperationNone;
 
   if ([self onlyAllowsNavigation]) {
-    if (info.url)
+    if ([[info draggingPasteboard] containsURLDataConvertingTextToURL:YES])
       return NSDragOperationCopy;
     return NSDragOperationNone;
   }
 
-  NSDragOperation mask = info.operation_mask;
-  targetRWH->DragTargetDragOver(transformedPt, info.location_in_screen,
-                                static_cast<WebDragOperationsMask>(mask),
-                                GetModifierFlags());
+  NSDragOperation mask = [info draggingSourceOperationMask];
+  targetRWH->DragTargetDragOver(
+      transformedPt, gfx::PointF(screenPoint.x, screenPoint.y),
+      static_cast<WebDragOperationsMask>(mask), GetModifierFlags());
 
   if (delegate_)
     delegate_->OnDragOver();
@@ -278,28 +288,36 @@
   return currentOperation_;
 }
 
-- (BOOL)performDragOperation:(const DraggingInfo&)info {
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)info
+                              view:(NSView*)view {
+  // Create the appropriate mouse locations for WebCore. The draggingLocation
+  // is in window coordinates. Both need to be flipped.
+  NSPoint windowPoint = [info draggingLocation];
+  NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
+  NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
   gfx::PointF transformedPt;
   content::RenderWidgetHostImpl* targetRWH =
-      [self GetRenderWidgetHostAtPoint:info.location_in_view
-                         transformedPt:&transformedPt];
+      [self GetRenderWidgetHostAtPoint:viewPoint transformedPt:&transformedPt];
 
   if (![self isValidDragTarget:targetRWH])
     return NO;
 
   if (targetRWH != currentRWHForDrag_.get()) {
     if (currentRWHForDrag_)
-      currentRWHForDrag_->DragTargetDragLeave(transformedPt,
-                                              info.location_in_screen);
-    [self draggingEntered:info];
+      currentRWHForDrag_->DragTargetDragLeave(
+          transformedPt, gfx::PointF(screenPoint.x, screenPoint.y));
+    [self draggingEntered:info view:view];
   }
 
   // Check if we only allow navigation and navigate to a url on the pasteboard.
   if ([self onlyAllowsNavigation]) {
-    if (info.url) {
-      webContents_->OpenURL(OpenURLParams(
-          *info.url, Referrer(), WindowOpenDisposition::CURRENT_TAB,
-          ui::PAGE_TRANSITION_AUTO_BOOKMARK, false));
+    NSPasteboard* pboard = [info draggingPasteboard];
+    if ([pboard containsURLDataConvertingTextToURL:YES]) {
+      GURL url;
+      ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES);
+      webContents_->OpenURL(
+          OpenURLParams(url, Referrer(), WindowOpenDisposition::CURRENT_TAB,
+                        ui::PAGE_TRANSITION_AUTO_BOOKMARK, false));
       return YES;
     } else {
       return NO;
@@ -312,7 +330,8 @@
   currentRVH_ = NULL;
 
   targetRWH->DragTargetDrop(*dropDataFiltered_, transformedPt,
-                            info.location_in_screen, GetModifierFlags());
+                            gfx::PointF(screenPoint.x, screenPoint.y),
+                            GetModifierFlags());
 
   dropDataUnfiltered_.reset();
   dropDataFiltered_.reset();
@@ -321,11 +340,11 @@
 }
 
 - (content::RenderWidgetHostImpl*)
-    GetRenderWidgetHostAtPoint:(const gfx::PointF&)viewPoint
-                 transformedPt:(gfx::PointF*)transformedPt {
+GetRenderWidgetHostAtPoint:(const NSPoint&)viewPoint
+             transformedPt:(gfx::PointF*)transformedPt {
   return webContents_->GetInputEventRouter()->GetRenderWidgetHostAtPoint(
-      webContents_->GetRenderViewHost()->GetWidget()->GetView(), viewPoint,
-      transformedPt);
+      webContents_->GetRenderViewHost()->GetWidget()->GetView(),
+      gfx::PointF(viewPoint.x, viewPoint.y), transformedPt);
 }
 
 - (void)setDragStartTrackersForProcess:(int)processID {
@@ -406,7 +425,4 @@
   }
 }
 
-DraggingInfo::DraggingInfo() = default;
-DraggingInfo::~DraggingInfo() = default;
-
 }  // namespace content
diff --git a/content/browser/web_contents/web_drag_dest_mac_unittest.mm b/content/browser/web_contents/web_drag_dest_mac_unittest.mm
index b837cbd..3ecdcc8 100644
--- a/content/browser/web_contents/web_drag_dest_mac_unittest.mm
+++ b/content/browser/web_contents/web_drag_dest_mac_unittest.mm
@@ -66,6 +66,24 @@
   EXPECT_TRUE(drag_dest_);
 }
 
+// Test flipping of coordinates given a point in window coordinates.
+TEST_F(WebDragDestTest, Flip) {
+  NSPoint windowPoint = NSZeroPoint;
+  base::scoped_nsobject<NSWindow> window([[CocoaTestHelperWindow alloc] init]);
+  NSPoint viewPoint =
+      [drag_dest_ flipWindowPointToView:windowPoint
+                                   view:[window contentView]];
+  NSPoint screenPoint =
+      [drag_dest_ flipWindowPointToScreen:windowPoint
+                                     view:[window contentView]];
+  EXPECT_EQ(0, viewPoint.x);
+  EXPECT_EQ(600, viewPoint.y);
+  EXPECT_EQ(0, screenPoint.x);
+  // We can't put a value on the screen size since everyone will have a
+  // different one.
+  EXPECT_NE(0, screenPoint.y);
+}
+
 TEST_F(WebDragDestTest, URL) {
   NSString* url = nil;
   NSString* title = nil;
diff --git a/content/gpu/gpu_main.cc b/content/gpu/gpu_main.cc
index 1c96f168..0b9d501 100644
--- a/content/gpu/gpu_main.cc
+++ b/content/gpu/gpu_main.cc
@@ -64,6 +64,7 @@
 #endif
 
 #if defined(OS_WIN)
+#include "base/trace_event/trace_event_etw_export_win.h"
 #include "base/win/scoped_com_initializer.h"
 #include "base/win/windows_version.h"
 #include "media/gpu/windows/dxva_video_decode_accelerator_win.h"
@@ -211,6 +212,11 @@
   if (gpu_preferences.gpu_startup_dialog)
     WaitForDebugger("Gpu");
 
+#if defined(OS_WIN)
+  if (gpu_preferences.enable_trace_export_events_to_etw)
+    base::trace_event::TraceEventETWExport::EnableETWExport();
+#endif
+
   base::Time start_time = base::Time::Now();
 
 #if defined(OS_WIN)
diff --git a/content/public/browser/gpu_utils.cc b/content/public/browser/gpu_utils.cc
index d6162ed..267af69 100644
--- a/content/public/browser/gpu_utils.cc
+++ b/content/public/browser/gpu_utils.cc
@@ -95,6 +95,10 @@
       command_line->HasSwitch(switches::kDisableSoftwareRasterizer);
   gpu_preferences.log_gpu_control_list_decisions =
       command_line->HasSwitch(switches::kLogGpuControlListDecisions);
+#if defined(OS_WIN)
+  gpu_preferences.enable_trace_export_events_to_etw =
+      command_line->HasSwitch(switches::kTraceExportEventsToETW);
+#endif
   GetUintFromSwitch(command_line, switches::kMaxActiveWebGLContexts,
                     &gpu_preferences.max_active_webgl_contexts);
   gpu_preferences.gpu_startup_dialog =
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index eeb540b..512d0bb9 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -462,10 +462,6 @@
 // Enable rasterizer that writes directly to GPU memory associated with tiles.
 const char kEnableZeroCopy[]                = "enable-zero-copy";
 
-// Explicitly allows additional ports using a comma-separated list of port
-// numbers.
-const char kExplicitlyAllowedPorts[]        = "explicitly-allowed-ports";
-
 // Handle to the shared memory segment containing field trial state that is to
 // be shared between processes. The argument to this switch is the handle id
 // (pointer on Windows) as a string, followed by a comma, then the size of the
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index 2a08de6a..f420773 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -146,7 +146,6 @@
 CONTENT_EXPORT extern const char kEnableWebGLImageChromium[];
 CONTENT_EXPORT extern const char kEnableWebVR[];
 CONTENT_EXPORT extern const char kEnableZeroCopy[];
-CONTENT_EXPORT extern const char kExplicitlyAllowedPorts[];
 CONTENT_EXPORT extern const char kFieldTrialHandle[];
 CONTENT_EXPORT extern const char kFileUrlPathAlias[];
 CONTENT_EXPORT extern const char kForceDisplayList2dCanvas[];
diff --git a/content/renderer/compositor/layer_tree_view.cc b/content/renderer/compositor/layer_tree_view.cc
index f39f7cb..cbe23e1 100644
--- a/content/renderer/compositor/layer_tree_view.cc
+++ b/content/renderer/compositor/layer_tree_view.cc
@@ -428,22 +428,6 @@
   return false;
 }
 
-void LayerTreeView::LayoutAndPaintAsync(base::OnceClosure callback) {
-  DCHECK(layout_and_paint_async_callback_.is_null());
-  layout_and_paint_async_callback_ = std::move(callback);
-
-  if (CompositeIsSynchronous()) {
-    // The LayoutAndPaintAsyncCallback is invoked in WillCommit, which is
-    // dispatched after layout and paint for all compositing modes.
-    const bool raster = false;
-    layer_tree_host_->GetTaskRunnerProvider()->MainThreadTaskRunner()->PostTask(
-        FROM_HERE, base::BindOnce(&LayerTreeView::SynchronouslyComposite,
-                                  weak_factory_.GetWeakPtr(), raster, nullptr));
-  } else {
-    layer_tree_host_->SetNeedsCommit();
-  }
-}
-
 void LayerTreeView::SetLayerTreeFrameSink(
     std::unique_ptr<cc::LayerTreeFrameSink> layer_tree_frame_sink) {
   if (!layer_tree_frame_sink) {
@@ -453,14 +437,8 @@
   layer_tree_host_->SetLayerTreeFrameSink(std::move(layer_tree_frame_sink));
 }
 
-void LayerTreeView::InvokeLayoutAndPaintCallback() {
-  if (!layout_and_paint_async_callback_.is_null())
-    std::move(layout_and_paint_async_callback_).Run();
-}
-
 void LayerTreeView::CompositeAndReadbackAsync(
     base::OnceCallback<void(const SkBitmap&)> callback) {
-  DCHECK(layout_and_paint_async_callback_.is_null());
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner =
       layer_tree_host_->GetTaskRunnerProvider()->MainThreadTaskRunner();
   std::unique_ptr<viz::CopyOutputRequest> request =
@@ -712,9 +690,7 @@
                                 weak_factory_.GetWeakPtr()));
 }
 
-void LayerTreeView::WillCommit() {
-  InvokeLayoutAndPaintCallback();
-}
+void LayerTreeView::WillCommit() {}
 
 void LayerTreeView::DidCommit() {
   delegate_->DidCommitCompositorFrame();
diff --git a/content/renderer/compositor/layer_tree_view.h b/content/renderer/compositor/layer_tree_view.h
index 7590a49..3e01903 100644
--- a/content/renderer/compositor/layer_tree_view.h
+++ b/content/renderer/compositor/layer_tree_view.h
@@ -145,7 +145,6 @@
                                double duration_sec) override;
   bool HasPendingPageScaleAnimation() const override;
   void HeuristicsForGpuRasterizationUpdated(bool matches_heuristics) override;
-  void LayoutAndPaintAsync(base::OnceClosure callback) override;
   void CompositeAndReadbackAsync(
       base::OnceCallback<void(const SkBitmap&)> callback) override;
   // Synchronously performs the complete set of document lifecycle phases,
@@ -242,7 +241,6 @@
  private:
   void SetLayerTreeFrameSink(
       std::unique_ptr<cc::LayerTreeFrameSink> layer_tree_frame_sink);
-  void InvokeLayoutAndPaintCallback();
   bool CompositeIsSynchronous() const;
   void SynchronouslyComposite(bool raster,
                               std::unique_ptr<cc::SwapPromise> swap_promise);
@@ -259,7 +257,6 @@
   bool layer_tree_frame_sink_request_failed_while_invisible_ = false;
 
   bool in_synchronous_compositor_update_ = false;
-  base::OnceClosure layout_and_paint_async_callback_;
 
   viz::FrameSinkId frame_sink_id_;
   base::circular_deque<
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 2e17bf6..44df5d5 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -138,6 +138,7 @@
 #include "net/base/url_util.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "services/metrics/public/cpp/mojo_ukm_recorder.h"
+#include "services/network/public/cpp/network_switches.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "services/ws/public/cpp/gpu/context_provider_command_buffer.h"
@@ -1233,9 +1234,9 @@
   SkGraphics::SetImageGeneratorFromEncodedDataFactory(
       blink::WebImageGenerator::CreateAsSkImageGenerator);
 
-  if (command_line.HasSwitch(switches::kExplicitlyAllowedPorts)) {
-    std::string allowed_ports =
-        command_line.GetSwitchValueASCII(switches::kExplicitlyAllowedPorts);
+  if (command_line.HasSwitch(network::switches::kExplicitlyAllowedPorts)) {
+    std::string allowed_ports = command_line.GetSwitchValueASCII(
+        network::switches::kExplicitlyAllowedPorts);
     net::SetExplicitlyAllowedPorts(allowed_ports);
   }
 }
diff --git a/content/shell/renderer/web_test/blink_test_runner.cc b/content/shell/renderer/web_test/blink_test_runner.cc
index 444bbe5..f1441e3 100644
--- a/content/shell/renderer/web_test/blink_test_runner.cc
+++ b/content/shell/renderer/web_test/blink_test_runner.cc
@@ -48,7 +48,6 @@
 #include "content/shell/renderer/web_test/web_test_render_thread_observer.h"
 #include "content/shell/test_runner/app_banner_service.h"
 #include "content/shell/test_runner/gamepad_controller.h"
-#include "content/shell/test_runner/layout_and_paint_async_then.h"
 #include "content/shell/test_runner/pixel_dump.h"
 #include "content/shell/test_runner/web_test_interfaces.h"
 #include "content/shell/test_runner/web_test_runner.h"
diff --git a/content/shell/test_runner/BUILD.gn b/content/shell/test_runner/BUILD.gn
index f576a6ba..f3680117 100644
--- a/content/shell/test_runner/BUILD.gn
+++ b/content/shell/test_runner/BUILD.gn
@@ -31,8 +31,6 @@
     "gamepad_controller.h",
     "gc_controller.cc",
     "gc_controller.h",
-    "layout_and_paint_async_then.cc",
-    "layout_and_paint_async_then.h",
     "layout_dump.cc",
     "layout_dump.h",
     "mock_content_settings_client.cc",
diff --git a/content/shell/test_runner/layout_and_paint_async_then.cc b/content/shell/test_runner/layout_and_paint_async_then.cc
deleted file mode 100644
index 7e4dab4..0000000
--- a/content/shell/test_runner/layout_and_paint_async_then.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/shell/test_runner/layout_and_paint_async_then.h"
-
-#include "base/barrier_closure.h"
-#include "base/callback.h"
-#include "base/trace_event/trace_event.h"
-#include "third_party/blink/public/web/web_page_popup.h"
-#include "third_party/blink/public/web/web_widget.h"
-
-namespace test_runner {
-
-void LayoutAndPaintAsyncThen(blink::WebPagePopup* popup,
-                             blink::WebWidget* web_widget,
-                             base::OnceClosure callback) {
-  TRACE_EVENT0("shell", "LayoutAndPaintAsyncThen");
-
-  if (popup) {
-    auto barrier = base::BarrierClosure(2, std::move(callback));
-    web_widget->LayoutAndPaintAsync(barrier);
-    popup->LayoutAndPaintAsync(barrier);
-  } else {
-    web_widget->LayoutAndPaintAsync(std::move(callback));
-  }
-}
-
-}  // namespace test_runner
diff --git a/content/shell/test_runner/layout_and_paint_async_then.h b/content/shell/test_runner/layout_and_paint_async_then.h
deleted file mode 100644
index 798d2f1..0000000
--- a/content/shell/test_runner/layout_and_paint_async_then.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_SHELL_TEST_RUNNER_LAYOUT_AND_PAINT_ASYNC_THEN_H_
-#define CONTENT_SHELL_TEST_RUNNER_LAYOUT_AND_PAINT_ASYNC_THEN_H_
-
-#include "base/callback_forward.h"
-#include "content/shell/test_runner/test_runner_export.h"
-
-namespace blink {
-class WebPagePopup;
-class WebWidget;
-}  // namespace blink
-
-namespace test_runner {
-
-// Triggers a layout and paint of WebWidget and the active popup (if any).
-// Calls |callback| after the layout and paint happens for both the
-// widget and the popup.
-TEST_RUNNER_EXPORT void LayoutAndPaintAsyncThen(blink::WebPagePopup* popup,
-                                                blink::WebWidget* web_widget,
-                                                base::OnceClosure callback);
-
-}  // namespace test_runner
-
-#endif  // CONTENT_SHELL_TEST_RUNNER_LAYOUT_AND_PAINT_ASYNC_THEN_H_
diff --git a/content/shell/test_runner/test_runner.cc b/content/shell/test_runner/test_runner.cc
index b9ce921..faa25a84 100644
--- a/content/shell/test_runner/test_runner.cc
+++ b/content/shell/test_runner/test_runner.cc
@@ -20,7 +20,6 @@
 #include "build/build_config.h"
 #include "cc/paint/paint_canvas.h"
 #include "content/shell/common/web_test/web_test_switches.h"
-#include "content/shell/test_runner/layout_and_paint_async_then.h"
 #include "content/shell/test_runner/layout_dump.h"
 #include "content/shell/test_runner/mock_content_settings_client.h"
 #include "content/shell/test_runner/mock_screen_orientation_client.h"
@@ -194,8 +193,6 @@
   void UpdateAllLifecyclePhasesAndCompositeThen(
       v8::Local<v8::Function> callback);
   void SetAnimationRequiresRaster(bool do_raster);
-  void LayoutAndPaintAsync();
-  void LayoutAndPaintAsyncThen(v8::Local<v8::Function> callback);
   void LogToStderr(const std::string& output);
   void NotImplemented(const gin::Arguments& args);
   void NotifyDone();
@@ -482,10 +479,6 @@
                  &TestRunnerBindings::UpdateAllLifecyclePhasesAndCompositeThen)
       .SetMethod("setAnimationRequiresRaster",
                  &TestRunnerBindings::SetAnimationRequiresRaster)
-      .SetMethod("layoutAndPaintAsync",
-                 &TestRunnerBindings::LayoutAndPaintAsync)
-      .SetMethod("layoutAndPaintAsyncThen",
-                 &TestRunnerBindings::LayoutAndPaintAsyncThen)
       .SetMethod("logToStderr", &TestRunnerBindings::LogToStderr)
       .SetMethod("notifyDone", &TestRunnerBindings::NotifyDone)
       .SetMethod("overridePreference", &TestRunnerBindings::OverridePreference)
@@ -1346,17 +1339,6 @@
   runner_->SetAnimationRequiresRaster(do_raster);
 }
 
-void TestRunnerBindings::LayoutAndPaintAsync() {
-  if (view_runner_)
-    view_runner_->LayoutAndPaintAsync();
-}
-
-void TestRunnerBindings::LayoutAndPaintAsyncThen(
-    v8::Local<v8::Function> callback) {
-  if (view_runner_)
-    view_runner_->LayoutAndPaintAsyncThen(callback);
-}
-
 void TestRunnerBindings::GetManifestThen(v8::Local<v8::Function> callback) {
   if (view_runner_)
     view_runner_->GetManifestThen(callback);
diff --git a/content/shell/test_runner/test_runner_for_specific_view.cc b/content/shell/test_runner/test_runner_for_specific_view.cc
index e01f88e..f753784 100644
--- a/content/shell/test_runner/test_runner_for_specific_view.cc
+++ b/content/shell/test_runner/test_runner_for_specific_view.cc
@@ -16,7 +16,6 @@
 #include "build/build_config.h"
 #include "cc/paint/paint_canvas.h"
 #include "content/renderer/compositor/layer_tree_view.h"
-#include "content/shell/test_runner/layout_and_paint_async_then.h"
 #include "content/shell/test_runner/layout_dump.h"
 #include "content/shell/test_runner/mock_content_settings_client.h"
 #include "content/shell/test_runner/mock_screen_orientation_client.h"
@@ -217,26 +216,6 @@
       v8::UniquePersistent<v8::Function>(blink::MainThreadIsolate(), callback));
 }
 
-void TestRunnerForSpecificView::LayoutAndPaintAsync() {
-  // TODO(lfg, lukasza): TestRunnerForSpecificView assumes that there's a single
-  // WebWidget for the entire view, but with out-of-process iframes there may be
-  // multiple WebWidgets, one for each local root. We should look into making
-  // this structure more generic. Also the PagePopup for an OOPIF would be
-  // attached to a WebView without a main frame, which should be handled.
-  test_runner::LayoutAndPaintAsyncThen(
-      web_view()->GetPagePopup(),
-      web_view()->MainFrame()->ToWebLocalFrame()->FrameWidget(),
-      base::DoNothing());
-}
-
-void TestRunnerForSpecificView::LayoutAndPaintAsyncThen(
-    v8::Local<v8::Function> callback) {
-  test_runner::LayoutAndPaintAsyncThen(
-      web_view()->GetPagePopup(),
-      web_view()->MainFrame()->ToWebLocalFrame()->FrameWidget(),
-      CreateClosureThatPostsV8Callback(callback));
-}
-
 void TestRunnerForSpecificView::CapturePixelsAsyncThen(
     v8::Local<v8::Function> callback) {
   v8::UniquePersistent<v8::Function> persistent_callback(
diff --git a/content/shell/test_runner/test_runner_for_specific_view.h b/content/shell/test_runner/test_runner_for_specific_view.h
index 3b3ddcf..64621649 100644
--- a/content/shell/test_runner/test_runner_for_specific_view.h
+++ b/content/shell/test_runner/test_runner_for_specific_view.h
@@ -79,12 +79,9 @@
   void UpdateAllLifecyclePhasesAndCompositeThen(
       v8::Local<v8::Function> callback);
 
-  void LayoutAndPaintAsync();
-  void LayoutAndPaintAsyncThen(v8::Local<v8::Function> callback);
-
-  // Similar to LayoutAndPaintAsyncThen(), but pass parameters of the captured
-  // snapshot (width, height, snapshot) to the callback. The snapshot is in
-  // uint8_t RGBA format.
+  // The callback will be called after the next full frame update and raster,
+  // with the captured snapshot as the parameters (width, height, snapshot).
+  // The snapshot is in uint8_t RGBA format.
   void CapturePixelsAsyncThen(v8::Local<v8::Function> callback);
   void CapturePixelsCallback(v8::UniquePersistent<v8::Function> callback,
                              const SkBitmap& snapshot);
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 602018b..dab1cfc 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -827,6 +827,7 @@
     "../browser/mojo_sandbox_browsertest.cc",
     "../browser/navigation_browsertest.cc",
     "../browser/net/accept_header_browsertest.cc",
+    "../browser/net/net_command_line_flags_browsertest.cc",
     "../browser/net_info_browsertest.cc",
     "../browser/network_service_browsertest.cc",
     "../browser/network_service_restart_browsertest.cc",
@@ -1437,12 +1438,13 @@
     "../browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc",
     "../browser/indexed_db/indexed_db_transaction_unittest.cc",
     "../browser/indexed_db/indexed_db_unittest.cc",
+    "../browser/indexed_db/leveldb/fake_leveldb_factory.cc",
+    "../browser/indexed_db/leveldb/fake_leveldb_factory.h",
+    "../browser/indexed_db/leveldb/leveldb_env_unittest.cc",
     "../browser/indexed_db/leveldb/leveldb_transaction_unittest.cc",
     "../browser/indexed_db/leveldb/leveldb_unittest.cc",
     "../browser/indexed_db/leveldb/mock_level_db.cc",
     "../browser/indexed_db/leveldb/mock_level_db.h",
-    "../browser/indexed_db/leveldb/mock_leveldb_factory.cc",
-    "../browser/indexed_db/leveldb/mock_leveldb_factory.h",
     "../browser/indexed_db/list_set_unittest.cc",
     "../browser/indexed_db/mock_indexed_db_callbacks.cc",
     "../browser/indexed_db/mock_indexed_db_callbacks.h",
diff --git a/content/test/data/accessibility/aria/aria-listbox-expected-blink.txt b/content/test/data/accessibility/aria/aria-listbox-expected-blink.txt
index 6fee00c..dc833e6 100644
--- a/content/test/data/accessibility/aria/aria-listbox-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-listbox-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
-++listBox
-++++listBoxOption name='Item 1' setSize=2 posInSet=1 selected=false
-++++listBoxOption name='Item 2' setSize=2 posInSet=2 selected=false
+++listBox setSize=4
+++++listBoxOption name='Item 1' setSize=4 posInSet=1 selected=false
+++++listBoxOption name='Item 2' setSize=4 posInSet=2 selected=false
 ++++splitter horizontal
-++++listBoxOption name='Second group item 1' setSize=2 posInSet=1 selected=false
-++++listBoxOption name='Second group item 2' setSize=2 posInSet=2 selected=false
\ No newline at end of file
+++++listBoxOption name='Second group item 1' setSize=4 posInSet=3 selected=false
+++++listBoxOption name='Second group item 2' setSize=4 posInSet=4 selected=false
diff --git a/content/test/data/accessibility/aria/aria-posinset-expected-blink.txt b/content/test/data/accessibility/aria/aria-posinset-expected-blink.txt
index e4e8860..b3a039bb 100644
--- a/content/test/data/accessibility/aria/aria-posinset-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-posinset-expected-blink.txt
@@ -1,14 +1,14 @@
 rootWebArea
-++article setSize=2 posInSet=1
+++article setSize=0 posInSet=0
 ++++staticText name='This is an ARIA article 1.'
 ++++++inlineTextBox name='This is an ARIA article 1.'
-++article setSize=2 posInSet=2
+++article setSize=0 posInSet=0
 ++++staticText name='This is an ARIA article 2.'
 ++++++inlineTextBox name='This is an ARIA article 2.'
-++listBox
+++listBox setSize=2
 ++++listBoxOption name='Item 1' setSize=2 posInSet=1 selected=false
 ++++listBoxOption name='Item 2' setSize=2 posInSet=2 selected=false
-++listBox
+++listBox setSize=2
 ++++listBoxOption name='Item 1' setSize=2 posInSet=1 selected=false
 ++++listBoxOption name='Item 2' setSize=2 posInSet=2 selected=false
 ++form
@@ -28,7 +28,7 @@
 ++radioButton setSize=2 posInSet=2 checkedState=false
 ++staticText name='Banana'
 ++++inlineTextBox name='Banana'
-++group name='Cake'
+++group name='Cake' setSize=0
 ++++legend
 ++++++staticText name='Cake'
 ++++++++inlineTextBox name='Cake'
@@ -46,4 +46,4 @@
 ++++++++inlineTextBox name='<newline>'
 ++++++radioButton name='blue' setSize=1 posInSet=1 checkedState=false
 ++staticText name='Done'
-++++inlineTextBox name='Done'
\ No newline at end of file
+++++inlineTextBox name='Done'
diff --git a/content/test/data/accessibility/aria/aria-posinset-expected-win.txt b/content/test/data/accessibility/aria/aria-posinset-expected-win.txt
index 5b1bab1..f52889c 100644
--- a/content/test/data/accessibility/aria/aria-posinset-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-posinset-expected-win.txt
@@ -1,12 +1,12 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
-++ROLE_SYSTEM_DOCUMENT READONLY setsize:2 posinset:1
+++ROLE_SYSTEM_DOCUMENT READONLY setsize:0 posinset:0
 ++++ROLE_SYSTEM_STATICTEXT name='This is an ARIA article 1.'
-++ROLE_SYSTEM_DOCUMENT READONLY setsize:2 posinset:2
+++ROLE_SYSTEM_DOCUMENT READONLY setsize:0 posinset:0
 ++++ROLE_SYSTEM_STATICTEXT name='This is an ARIA article 2.'
-++ROLE_SYSTEM_LIST
+++ROLE_SYSTEM_LIST setsize:2
 ++++ROLE_SYSTEM_LISTITEM name='Item 1' FOCUSABLE setsize:2 posinset:1
 ++++ROLE_SYSTEM_LISTITEM name='Item 2' FOCUSABLE setsize:2 posinset:2
-++ROLE_SYSTEM_LIST
+++ROLE_SYSTEM_LIST setsize:2
 ++++ROLE_SYSTEM_LISTITEM name='Item 1' FOCUSABLE setsize:2 posinset:1
 ++++ROLE_SYSTEM_LISTITEM name='Item 2' FOCUSABLE setsize:2 posinset:2
 ++IA2_ROLE_FORM
@@ -20,7 +20,7 @@
 ++ROLE_SYSTEM_WHITESPACE name='<newline>'
 ++ROLE_SYSTEM_RADIOBUTTON FOCUSABLE IA2_STATE_CHECKABLE setsize:2 posinset:2 checkable:true
 ++ROLE_SYSTEM_STATICTEXT name='Banana'
-++ROLE_SYSTEM_GROUPING name='Cake'
+++ROLE_SYSTEM_GROUPING name='Cake' setsize:0
 ++++IA2_ROLE_LABEL
 ++++++ROLE_SYSTEM_STATICTEXT name='Cake'
 ++++ROLE_SYSTEM_RADIOBUTTON name='Chiffon cakes' CHECKED FOCUSABLE IA2_STATE_CHECKABLE setsize:2 posinset:1 checkable:true
@@ -33,4 +33,4 @@
 ++++++++ROLE_SYSTEM_STATICTEXT name='red'
 ++++++ROLE_SYSTEM_WHITESPACE name='<newline>'
 ++++++ROLE_SYSTEM_RADIOBUTTON name='blue' FOCUSABLE IA2_STATE_CHECKABLE setsize:1 posinset:1 checkable:true
-++ROLE_SYSTEM_STATICTEXT name='Done'
\ No newline at end of file
+++ROLE_SYSTEM_STATICTEXT name='Done'
diff --git a/content/test/data/accessibility/aria/aria-setsize-expected-blink.txt b/content/test/data/accessibility/aria/aria-setsize-expected-blink.txt
index bb8a597a..4459055 100644
--- a/content/test/data/accessibility/aria/aria-setsize-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-setsize-expected-blink.txt
@@ -1,12 +1,12 @@
 rootWebArea
-++listBox
+++listBox setSize=4
 ++++listBoxOption name='Item 1' setSize=4 posInSet=1 selected=false
 ++++listBoxOption name='Item 2' setSize=4 posInSet=2 selected=false
 ++++listBoxOption name='Item 3' setSize=4 posInSet=3 selected=false
 ++++listBoxOption name='Item 4' setSize=4 posInSet=4 selected=false
-++listBox
+++listBox setSize=5
 ++++listBoxOption name='Item 1' setSize=5 posInSet=1 selected=false
 ++++listBoxOption name='Item 2' setSize=5 posInSet=2 selected=false
 ++++listBoxOption name='Item 3' setSize=5 posInSet=3 selected=false
 ++++listBoxOption name='Item 4' setSize=5 posInSet=4 selected=false
-++++listBoxOption name='Item 5' setSize=5 posInSet=5 selected=false
\ No newline at end of file
+++++listBoxOption name='Item 5' setSize=5 posInSet=5 selected=false
diff --git a/content/test/data/accessibility/aria/aria-setsize-expected-win.txt b/content/test/data/accessibility/aria/aria-setsize-expected-win.txt
index 6cae42b..0874fd57 100644
--- a/content/test/data/accessibility/aria/aria-setsize-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-setsize-expected-win.txt
@@ -1,10 +1,10 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
-++ROLE_SYSTEM_LIST
+++ROLE_SYSTEM_LIST setsize:4
 ++++ROLE_SYSTEM_LISTITEM name='Item 1' FOCUSABLE setsize:4 posinset:1
 ++++ROLE_SYSTEM_LISTITEM name='Item 2' FOCUSABLE setsize:4 posinset:2
 ++++ROLE_SYSTEM_LISTITEM name='Item 3' FOCUSABLE setsize:4 posinset:3
 ++++ROLE_SYSTEM_LISTITEM name='Item 4' FOCUSABLE setsize:4 posinset:4
-++ROLE_SYSTEM_LIST
+++ROLE_SYSTEM_LIST setsize:5
 ++++ROLE_SYSTEM_LISTITEM name='Item 1' FOCUSABLE setsize:5 posinset:1
 ++++ROLE_SYSTEM_LISTITEM name='Item 2' FOCUSABLE setsize:5 posinset:2
 ++++ROLE_SYSTEM_LISTITEM name='Item 3' FOCUSABLE setsize:5 posinset:3
diff --git a/content/test/data/accessibility/aria/aria-tab-expected-blink.txt b/content/test/data/accessibility/aria/aria-tab-expected-blink.txt
index c92853c..e8cd121 100644
--- a/content/test/data/accessibility/aria/aria-tab-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-tab-expected-blink.txt
@@ -1,4 +1,4 @@
 rootWebArea
-++tabList horizontal
+++tabList horizontal setSize=2
 ++++tab name='Tab 1' setSize=2 posInSet=1 selected=false
-++++tab name='Tab 2' setSize=2 posInSet=2 selected=false
\ No newline at end of file
+++++tab name='Tab 2' setSize=2 posInSet=2 selected=false
diff --git a/content/test/data/accessibility/aria/aria-tree-expected-blink.txt b/content/test/data/accessibility/aria/aria-tree-expected-blink.txt
index d9ec7e3a..df2a948d 100644
--- a/content/test/data/accessibility/aria/aria-tree-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-tree-expected-blink.txt
@@ -1,15 +1,15 @@
 rootWebArea
-++tree
+++tree setSize=2
 ++++treeItem name='Animals' hierarchicalLevel=1 setSize=2 posInSet=1 checkedState=mixed selected=false
 ++++++link name='Animals'
 ++++++++staticText name='Animals'
 ++++++++++inlineTextBox name='Animals'
-++++++group
+++++++group setSize=2
 ++++++++treeItem name='Domesticated' hierarchicalLevel=2 setSize=2 posInSet=1 selected=false
 ++++++++++link name='Domesticated'
 ++++++++++++staticText name='Domesticated'
 ++++++++++++++inlineTextBox name='Domesticated'
-++++++++++group
+++++++++++group setSize=2
 ++++++++++++treeItem name='Dog' hierarchicalLevel=3 setSize=2 posInSet=1 checkedState=true selected=false
 ++++++++++++++link name='Dog'
 ++++++++++++++++staticText name='Dog'
diff --git a/content/test/data/accessibility/aria/aria-tree-expected-win.txt b/content/test/data/accessibility/aria/aria-tree-expected-win.txt
index 16010b8..c0f2ac17 100644
--- a/content/test/data/accessibility/aria/aria-tree-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-tree-expected-win.txt
@@ -1,13 +1,13 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
-++ROLE_SYSTEM_OUTLINE IA2_STATE_VERTICAL
+++ROLE_SYSTEM_OUTLINE IA2_STATE_VERTICAL setsize:2
 ++++ROLE_SYSTEM_OUTLINEITEM name='Animals' MIXED IA2_STATE_CHECKABLE level:1 setsize:2 posinset:1 checkable:true
 ++++++ROLE_SYSTEM_LINK name='Animals' FOCUSABLE
 ++++++++ROLE_SYSTEM_STATICTEXT name='Animals'
-++++++ROLE_SYSTEM_GROUPING
+++++++ROLE_SYSTEM_GROUPING setsize:2
 ++++++++ROLE_SYSTEM_OUTLINEITEM name='Domesticated' level:2 setsize:2 posinset:1
 ++++++++++ROLE_SYSTEM_LINK name='Domesticated' FOCUSABLE
 ++++++++++++ROLE_SYSTEM_STATICTEXT name='Domesticated'
-++++++++++ROLE_SYSTEM_GROUPING
+++++++++++ROLE_SYSTEM_GROUPING setsize:2
 ++++++++++++ROLE_SYSTEM_OUTLINEITEM name='Dog' CHECKED IA2_STATE_CHECKABLE level:3 setsize:2 posinset:1 checkable:true
 ++++++++++++++ROLE_SYSTEM_LINK name='Dog' FOCUSABLE
 ++++++++++++++++ROLE_SYSTEM_STATICTEXT name='Dog'
diff --git a/content/test/data/accessibility/event/add-subtree-expected-win.txt b/content/test/data/accessibility/event/add-subtree-expected-win.txt
index de590142..57b78be 100644
--- a/content/test/data/accessibility/event/add-subtree-expected-win.txt
+++ b/content/test/data/accessibility/event/add-subtree-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_REORDER on <ul> role=ROLE_SYSTEM_LIST SetSize=3
 EVENT_OBJECT_SHOW on <li> role=ROLE_SYSTEM_LISTITEM PosInSet=3 SetSize=3
-IA2_EVENT_TEXT_INSERTED on <ul> role=ROLE_SYSTEM_LIST SetSize=2 new_text={'<obj>' start=2 end=3}
+IA2_EVENT_TEXT_INSERTED on <ul> role=ROLE_SYSTEM_LIST SetSize=3 new_text={'<obj>' start=2 end=3}
diff --git a/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt b/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt
index 7c485e7..73cb5b6a 100644
--- a/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt
+++ b/content/test/data/accessibility/event/remove-hidden-attribute-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_LIST SetSize=3
 EVENT_OBJECT_SHOW on <div#item3> role=ROLE_SYSTEM_LISTITEM name="Item 3" PosInSet=3 SetSize=3
-IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_LIST SetSize=2 new_text={'<obj>' start=2 end=3}
+IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_LIST SetSize=3 new_text={'<obj>' start=2 end=3}
diff --git a/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt b/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt
index 1200268a..3dadf9f 100644
--- a/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt
+++ b/content/test/data/accessibility/event/remove-hidden-attribute-subtree-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_REORDER on <ul> role=ROLE_SYSTEM_LIST SetSize=3
 EVENT_OBJECT_SHOW on <li#item3> role=ROLE_SYSTEM_LISTITEM PosInSet=3 SetSize=3
-IA2_EVENT_TEXT_INSERTED on <ul> role=ROLE_SYSTEM_LIST SetSize=2 new_text={'<obj>' start=2 end=3}
+IA2_EVENT_TEXT_INSERTED on <ul> role=ROLE_SYSTEM_LIST SetSize=3 new_text={'<obj>' start=2 end=3}
diff --git a/content/test/data/accessibility/html/li-expected-auralinux.txt b/content/test/data/accessibility/html/li-expected-auralinux.txt
index ef3966a..8ddbd7ec 100644
--- a/content/test/data/accessibility/html/li-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/li-expected-auralinux.txt
@@ -1,5 +1,5 @@
 [document web]
-++[list] display:block
+++[list] display:block setsize:3
 ++++[list item] display:list-item posinset:1 setsize:3
 ++++++[static] name='• '
 ++++++[text] name='Item 1' display:list-item
diff --git a/content/test/data/accessibility/html/li-expected-blink.txt b/content/test/data/accessibility/html/li-expected-blink.txt
index 4cfbfc37..9895cd6 100644
--- a/content/test/data/accessibility/html/li-expected-blink.txt
+++ b/content/test/data/accessibility/html/li-expected-blink.txt
@@ -1,5 +1,5 @@
 rootWebArea
-++list display='block'
+++list display='block' setSize=3
 ++++listItem display='list-item' setSize=3 posInSet=1
 ++++++listMarker name='• '
 ++++++staticText display='list-item' name='Item 1'
diff --git a/content/test/data/accessibility/html/optgroup-expected-auralinux.txt b/content/test/data/accessibility/html/optgroup-expected-auralinux.txt
index 0d838b60..64b1d73 100644
--- a/content/test/data/accessibility/html/optgroup-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/optgroup-expected-auralinux.txt
@@ -1,15 +1,15 @@
 [document web] enabled
 ++[section] enabled
-++++[list box] enabled
-++++++[panel] name='Enabled' enabled xml-roles:group
+++++[list box] enabled setsize:8
+++++++[panel] name='Enabled' enabled setsize:0 xml-roles:group
 ++++++++[text] name='Enabled' enabled
-++++++[list item] name='One' enabled selectable posinset:1 setsize:4
-++++++[list item] name='Two' enabled selectable posinset:2 setsize:4
-++++++[list item] name='Three' enabled selectable posinset:3 setsize:4
-++++++[list item] name='Four' enabled selectable posinset:4 setsize:4
-++++++[panel] name='Disabled' enabled xml-roles:group
+++++++[list item] name='One' enabled selectable posinset:1 setsize:8
+++++++[list item] name='Two' enabled selectable posinset:2 setsize:8
+++++++[list item] name='Three' enabled selectable posinset:3 setsize:8
+++++++[list item] name='Four' enabled selectable posinset:4 setsize:8
+++++++[panel] name='Disabled' enabled setsize:0 xml-roles:group
 ++++++++[text] name='Disabled' enabled
-++++++[list item] name='One' posinset:1 setsize:4
-++++++[list item] name='Two' posinset:2 setsize:4
-++++++[list item] name='Three' posinset:3 setsize:4
-++++++[list item] name='Four' posinset:4 setsize:4
+++++++[list item] name='One' posinset:5 setsize:8
+++++++[list item] name='Two' posinset:6 setsize:8
+++++++[list item] name='Three' posinset:7 setsize:8
+++++++[list item] name='Four' posinset:8 setsize:8
diff --git a/content/test/data/accessibility/html/optgroup-expected-blink.txt b/content/test/data/accessibility/html/optgroup-expected-blink.txt
index ecbaeabc..7769636 100644
--- a/content/test/data/accessibility/html/optgroup-expected-blink.txt
+++ b/content/test/data/accessibility/html/optgroup-expected-blink.txt
@@ -1,17 +1,17 @@
 rootWebArea
 ++genericContainer
-++++listBox
-++++++group name='Enabled'
+++++listBox setSize=8
+++++++group name='Enabled' setSize=0
 ++++++++staticText name='Enabled'
 ++++++++++inlineTextBox name='Enabled'
-++++++listBoxOption name='One' setSize=4 posInSet=1 selected=false
-++++++listBoxOption name='Two' setSize=4 posInSet=2 selected=false
-++++++listBoxOption name='Three' setSize=4 posInSet=3 selected=false
-++++++listBoxOption name='Four' setSize=4 posInSet=4 selected=false
-++++++group name='Disabled'
+++++++listBoxOption name='One' setSize=8 posInSet=1 selected=false
+++++++listBoxOption name='Two' setSize=8 posInSet=2 selected=false
+++++++listBoxOption name='Three' setSize=8 posInSet=3 selected=false
+++++++listBoxOption name='Four' setSize=8 posInSet=4 selected=false
+++++++group name='Disabled' setSize=0
 ++++++++staticText name='Disabled'
 ++++++++++inlineTextBox name='Disabled'
-++++++listBoxOption name='One' restriction=disabled setSize=4 posInSet=1
-++++++listBoxOption name='Two' restriction=disabled setSize=4 posInSet=2
-++++++listBoxOption name='Three' restriction=disabled setSize=4 posInSet=3
-++++++listBoxOption name='Four' restriction=disabled setSize=4 posInSet=4
\ No newline at end of file
+++++++listBoxOption name='One' restriction=disabled setSize=8 posInSet=5
+++++++listBoxOption name='Two' restriction=disabled setSize=8 posInSet=6
+++++++listBoxOption name='Three' restriction=disabled setSize=8 posInSet=7
+++++++listBoxOption name='Four' restriction=disabled setSize=8 posInSet=8
diff --git a/content/test/data/accessibility/html/select-expected-auralinux.txt b/content/test/data/accessibility/html/select-expected-auralinux.txt
index 345a762f..596ea75 100644
--- a/content/test/data/accessibility/html/select-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/select-expected-auralinux.txt
@@ -1,25 +1,25 @@
 [document web]
 ++[section]
 ++++[combo box] expandable haspopup:menu
-++++++[menu]
+++++++[menu] setsize:3
 ++++++++[menu item] name='Placeholder option' selectable selected posinset:1 setsize:3
 ++++++++[menu item] name='Option 1' selectable posinset:2 setsize:3
 ++++++++[menu item] name='Option 2' selectable posinset:3 setsize:3
 ++++[combo box] expandable haspopup:menu
-++++++[menu]
+++++++[menu] setsize:3
 ++++++++[menu item] name='Option 1' selectable posinset:1 setsize:3
 ++++++++[menu item] name='Option 2' selectable selected posinset:2 setsize:3
 ++++++++[menu item] name='Option 3' selectable posinset:3 setsize:3
 ++++[combo box] expandable required haspopup:menu
-++++++[menu]
+++++++[menu] setsize:3
 ++++++++[menu item] name='Option 1' selectable selected posinset:1 setsize:3
 ++++++++[menu item] name='Option 2' selectable posinset:2 setsize:3
 ++++++++[menu item] name='Option 3' selectable posinset:3 setsize:3
-++++[list box] multiselectable
+++++[list box] multiselectable setsize:3
 ++++++[list item] name='Option 1' selectable posinset:1 setsize:3
 ++++++[list item] name='Option 2' selectable posinset:2 setsize:3
 ++++++[list item] name='Option 3' selectable posinset:3 setsize:3
-++++[list box]
+++++[list box] setsize:3
 ++++++[list item] name='Option 1' selectable posinset:1 setsize:3
 ++++++[list item] name='Option 2' selectable posinset:2 setsize:3
 ++++++[list item] name='Option 3' selectable posinset:3 setsize:3
diff --git a/content/test/data/accessibility/html/select-expected-blink.txt b/content/test/data/accessibility/html/select-expected-blink.txt
index 8508b8c..43677b6 100644
--- a/content/test/data/accessibility/html/select-expected-blink.txt
+++ b/content/test/data/accessibility/html/select-expected-blink.txt
@@ -1,25 +1,25 @@
 rootWebArea focusable
 ++genericContainer
 ++++popUpButton collapsed focusable value='Placeholder option' haspopup=menu
-++++++menuListPopup invisible
+++++++menuListPopup invisible setSize=3
 ++++++++menuListOption focusable name='Placeholder option' setSize=3 posInSet=1 selected=true
 ++++++++menuListOption focusable invisible name='Option 1' setSize=3 posInSet=2 selected=false
 ++++++++menuListOption focusable invisible name='Option 2' setSize=3 posInSet=3 selected=false
 ++++popUpButton collapsed focusable value='Option 2' haspopup=menu
-++++++menuListPopup invisible
+++++++menuListPopup invisible setSize=3
 ++++++++menuListOption focusable invisible name='Option 1' setSize=3 posInSet=1 selected=false
 ++++++++menuListOption focusable name='Option 2' setSize=3 posInSet=2 selected=true
 ++++++++menuListOption focusable invisible name='Option 3' setSize=3 posInSet=3 selected=false
 ++++popUpButton collapsed focusable required value='Option 1' haspopup=menu
-++++++menuListPopup invisible
+++++++menuListPopup invisible setSize=3
 ++++++++menuListOption focusable name='Option 1' setSize=3 posInSet=1 selected=true
 ++++++++menuListOption focusable invisible name='Option 2' setSize=3 posInSet=2 selected=false
 ++++++++menuListOption focusable invisible name='Option 3' setSize=3 posInSet=3 selected=false
-++++listBox focusable multiselectable
+++++listBox focusable multiselectable setSize=3
 ++++++listBoxOption focusable name='Option 1' setSize=3 posInSet=1 selected=false
 ++++++listBoxOption focusable name='Option 2' setSize=3 posInSet=2 selected=false
 ++++++listBoxOption focusable name='Option 3' setSize=3 posInSet=3 selected=false
-++++listBox focusable
+++++listBox focusable setSize=3
 ++++++listBoxOption focusable name='Option 1' setSize=3 posInSet=1 selected=false
 ++++++listBoxOption focusable name='Option 2' setSize=3 posInSet=2 selected=false
 ++++++listBoxOption focusable name='Option 3' setSize=3 posInSet=3 selected=false
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 9e5a03d..2508f192 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -95,9 +95,6 @@
         'integer-cubemap-specification-order-bug.html',
         bug=905003) # owner:cwallez, test might be buggy
 
-    # Need to implement new lifetime/deletion semantics.
-    self.Fail('conformance2/vertex_arrays/vertex-array-object.html', bug=739604)
-
     # The following actually passes on gl_passthrough and also Mac Intel with
     # command buffer.
     self.Fail('deqp/functional/gles3/shadertexturefunction/' +
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
index de61db9..3351133 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
@@ -95,9 +95,6 @@
     # ========================
     # Fails on all platforms
 
-    # Need to implement new lifetime/deletion semantics.
-    self.Fail('conformance/extensions/oes-vertex-array-object.html', bug=739604)
-
     # Need to add detection of feedback loops with multiple render targets.
     self.Fail('conformance/rendering/rendering-sampling-feedback-loop.html',
         bug=660844)
@@ -337,9 +334,8 @@
         ['win10', 'intel', 'opengl', 'no_passthrough'], bug=680797)
     self.Fail('conformance/extensions/oes-texture-half-float-with-canvas.html',
         ['win10', 'intel', 'opengl', 'no_passthrough'], bug=680797)
-    # TODO(kbr): re-enable after fixing lifetime semantics. crbug.com/739604
-    # self.Fail('conformance/extensions/oes-vertex-array-object.html',
-    #     ['win10', 'intel', 'opengl'], bug=680797)
+    self.Fail('conformance/extensions/oes-vertex-array-object.html',
+        ['win10', 'intel', 'opengl'], bug=680797)
     self.Fail('conformance/glsl/bugs/' +
         'array-of-struct-with-int-first-position.html',
         ['win10', 'intel', 'opengl'], bug=680797)
diff --git a/docs/how_to_add_your_feature_flag.md b/docs/how_to_add_your_feature_flag.md
index 39a54cb..a27e24d6 100644
--- a/docs/how_to_add_your_feature_flag.md
+++ b/docs/how_to_add_your_feature_flag.md
@@ -47,6 +47,8 @@
 autoninja -C out/Default unit_tests
 # Run AboutFlagsHistogramTest.CheckHistograms
 ./out/Default/unit_tests --gtest_filter=AboutFlagsHistogramTest.CheckHistograms
+# Run AboutFlagsHistogramTest.CheckHistograms on Android
+./out/Default/bin/run_unit_tests --gtest_filter=AboutFlagsHistogramTest.CheckHistograms
 ```
 
 That test will ask you to add several entries to enums.xml.
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index bdea2ea02..71533a0d 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1368,6 +1368,7 @@
   AUTOTESTPRIVATE_SENDASSISTANTTEXTQUERY = 1305,
   AUTOTESTPRIVATE_SETCROSTINIAPPSCALED = 1306,
   ACTIVITYLOGPRIVATE_DELETEACTIVITIESBYEXTENSION = 1307,
+  ACCESSIBILITY_PRIVATE_FORWARDKEYEVENTSTOSWITCHACCESS = 1308,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/url_loader_factory_manager.cc b/extensions/browser/url_loader_factory_manager.cc
index 3ad1140..c56ef73 100644
--- a/extensions/browser/url_loader_factory_manager.cc
+++ b/extensions/browser/url_loader_factory_manager.cc
@@ -66,7 +66,6 @@
     "0CD842E367BA8D9ECE3A2F68448B338AF5C84F88",
     "0EBA158DA6EC99B8E71FBDB7CA7CA4DF1C707B0F",
     "0EBCD86FC44873101E247C81BE81662AB1379738",
-    "133E65ABB919CEC935A54700136E6405BDF1FF25",
     "16A81AEA09A67B03F7AEA5B957D24A4095E764BE",
     "177508B365CBF1610CC2B53707749D79272F2F0B",
     "1AB9CC404876117F49135E67BAD813F935AAE9BA",
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 085fcdb..f44786505 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -11,7 +11,7 @@
 // https://crbug.com/846346 and DoContentScriptsDependOnRelaxedCorb function in
 // extensions/browser/url_loader_factory_manager.cc.
 const base::Feature kBypassCorbOnlyForExtensionsAllowlist{
-    "BypassCorbOnlyForExtensionsAllowlist", base::FEATURE_DISABLED_BY_DEFAULT};
+    "BypassCorbOnlyForExtensionsAllowlist", base::FEATURE_ENABLED_BY_DEFAULT};
 const char kBypassCorbAllowlistParamName[] = "BypassCorbExtensionsAllowlist";
 
 // Enables the use of C++-based extension bindings (instead of JS generation).
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index 17e168c..b44d3e1 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -32,6 +32,10 @@
 const char kContentCapabilities[] = "content_capabilities";
 const char kContentScripts[] = "content_scripts";
 const char kContentSecurityPolicy[] = "content_security_policy";
+const char kContentSecurityPolicy_ExtensionPagesPath[] =
+    "content_security_policy.extension_pages";
+const char kContentSecurityPolicy_SandboxedPagesPath[] =
+    "content_security_policy.sandbox";
 const char kConvertedFromUserScript[] = "converted_from_user_script";
 const char kCss[] = "css";
 const char kCtrlKey[] = "ctrlKey";
@@ -732,6 +736,9 @@
     "The \"plugins\" requirement is deprecated.";
 const char kReservedMessageFound[] =
     "Reserved key * found in message catalog.";
+const char kSandboxPagesCSPKeyNotAllowed[] =
+    "The Content Security Policy for sandboxed pages should be specified in "
+    "'content_security_policy.sandbox'.";
 const char kRulesFileIsInvalid[] =
     "Invalid value for key '*.*': The provided path is invalid.";
 const char kTtsGenderIsDeprecated[] =
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index 961423e..a9615a3 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -35,6 +35,8 @@
 extern const char kContentCapabilities[];
 extern const char kContentScripts[];
 extern const char kContentSecurityPolicy[];
+extern const char kContentSecurityPolicy_ExtensionPagesPath[];
+extern const char kContentSecurityPolicy_SandboxedPagesPath[];
 extern const char kConvertedFromUserScript[];
 extern const char kCss[];
 extern const char kCtrlKey[];
@@ -494,6 +496,7 @@
 extern const char kPermissionUnknownOrMalformed[];
 extern const char kPluginsRequirementDeprecated[];
 extern const char kReservedMessageFound[];
+extern const char kSandboxPagesCSPKeyNotAllowed[];
 extern const char kRulesFileIsInvalid[];
 extern const char kTtsGenderIsDeprecated[];
 extern const char kUnrecognizedManifestKey[];
diff --git a/extensions/common/manifest_handlers/csp_info.cc b/extensions/common/manifest_handlers/csp_info.cc
index 36c519c..4a641c4 100644
--- a/extensions/common/manifest_handlers/csp_info.cc
+++ b/extensions/common/manifest_handlers/csp_info.cc
@@ -35,9 +35,6 @@
     "sandbox allow-scripts allow-forms allow-popups allow-modals; "
     "script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';";
 
-const char kExtensionPagesKey[] = "extension_pages";
-const char kExtensionPagesPath[] = "content_security_policy.extension_pages";
-
 #define PLATFORM_APP_LOCAL_CSP_SOURCES \
     "'self' blob: filesystem: data: chrome-extension-resource:"
 
@@ -153,19 +150,35 @@
       extension->GetType() == Manifest::TYPE_EXTENSION &&
       GetCurrentChannel() == version_info::Channel::UNKNOWN;
   if (csp_dictionary_supported && csp && csp->is_dict())
-    return ParseCSPDictionary(extension, error, *csp);
+    return ParseCSPDictionary(extension, error);
 
+  // TODO(crbug.com/914224) Disallow the usages below in manifest v3 for
+  // extensions.
   return ParseExtensionPagesCSP(extension, error, key, csp) &&
          ParseSandboxCSP(extension, error, keys::kSandboxedPagesCSP,
                          GetManifestPath(extension, keys::kSandboxedPagesCSP));
 }
 
 bool CSPHandler::ParseCSPDictionary(Extension* extension,
-                                    base::string16* error,
-                                    const base::Value& csp_dict) {
-  DCHECK(csp_dict.is_dict());
-  return ParseExtensionPagesCSP(extension, error, kExtensionPagesPath,
-                                csp_dict.FindKey(kExtensionPagesKey));
+                                    base::string16* error) {
+  if (!ParseExtensionPagesCSP(
+          extension, error, keys::kContentSecurityPolicy_ExtensionPagesPath,
+          GetManifestPath(extension,
+                          keys::kContentSecurityPolicy_ExtensionPagesPath))) {
+    return false;
+  }
+
+  // keys::kSandboxedPagesCSP shouldn't be used when using
+  // keys::kContentSecurityPolicy as a dictionary.
+  if (extension->manifest()->HasPath(keys::kSandboxedPagesCSP)) {
+    *error = base::ASCIIToUTF16(errors::kSandboxPagesCSPKeyNotAllowed);
+    return false;
+  }
+
+  return ParseSandboxCSP(
+      extension, error, keys::kContentSecurityPolicy_SandboxedPagesPath,
+      GetManifestPath(extension,
+                      keys::kContentSecurityPolicy_SandboxedPagesPath));
 }
 
 bool CSPHandler::ParseExtensionPagesCSP(
diff --git a/extensions/common/manifest_handlers/csp_info.h b/extensions/common/manifest_handlers/csp_info.h
index c2ad9262..636bb6f 100644
--- a/extensions/common/manifest_handlers/csp_info.h
+++ b/extensions/common/manifest_handlers/csp_info.h
@@ -55,9 +55,7 @@
 
  private:
   // Parses the "content_security_policy" dictionary in the manifest.
-  bool ParseCSPDictionary(Extension* extension,
-                          base::string16* error,
-                          const base::Value& csp_dict);
+  bool ParseCSPDictionary(Extension* extension, base::string16* error);
 
   // Parses the content security policy specified in the manifest for extension
   // pages.
diff --git a/extensions/common/manifest_handlers/csp_info_unittest.cc b/extensions/common/manifest_handlers/csp_info_unittest.cc
index da050fe3..aefff22 100644
--- a/extensions/common/manifest_handlers/csp_info_unittest.cc
+++ b/extensions/common/manifest_handlers/csp_info_unittest.cc
@@ -110,7 +110,7 @@
               EXPECT_TYPE_ERROR);
 }
 
-TEST_F(CSPInfoUnitTest, CSPDictionaryKey) {
+TEST_F(CSPInfoUnitTest, CSPDictionary_ExtensionPages) {
   const char kDefaultCSP[] =
       "script-src 'self' blob: filesystem: chrome-extension-resource:; "
       "object-src 'self' blob: filesystem:;";
@@ -122,7 +122,7 @@
       {"csp_empty_valid.json", "script-src 'self'; object-src 'self';"},
       {"csp_empty_dictionary_valid.json", kDefaultCSP}};
 
-  // Verify that "content_security_policy" key can be used as a dictionary on
+  // Verify that keys::kContentSecurityPolicy key can be used as a dictionary on
   // trunk.
   {
     ScopedCurrentChannel channel(version_info::Channel::UNKNOWN);
@@ -137,8 +137,8 @@
     }
   }
 
-  // Verify that "content_security_policy" key can't be used as a dictionary on
-  // Stable.
+  // Verify that keys::kContentSecurityPolicy key can't be used as a dictionary
+  // on Stable.
   {
     ScopedCurrentChannel channel(version_info::Channel::STABLE);
     for (const auto& test_case : cases) {
@@ -152,14 +152,63 @@
 
   {
     ScopedCurrentChannel channel(version_info::Channel::UNKNOWN);
-    const char* kExtensionPagesKey = "content_security_policy.extension_pages";
     Testcase testcases[] = {
         Testcase("csp_invalid_2.json",
-                 GetInvalidManifestKeyError(kExtensionPagesKey)),
+                 GetInvalidManifestKeyError(
+                     keys::kContentSecurityPolicy_ExtensionPagesPath)),
         Testcase("csp_invalid_3.json",
-                 GetInvalidManifestKeyError(kExtensionPagesKey))};
+                 GetInvalidManifestKeyError(
+                     keys::kContentSecurityPolicy_ExtensionPagesPath))};
     RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
   }
 }
 
+TEST_F(CSPInfoUnitTest, CSPDictionary_Sandbox) {
+  ScopedCurrentChannel channel(version_info::Channel::UNKNOWN);
+
+  const char kCustomSandboxedCSP[] =
+      "sandbox; script-src 'self'; child-src 'self';";
+  const char kDefaultSandboxedPageCSP[] =
+      "sandbox allow-scripts allow-forms allow-popups allow-modals; "
+      "script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';";
+  const char kDefaultExtensionPagesCSP[] =
+      "script-src 'self' blob: filesystem: chrome-extension-resource:; "
+      "object-src 'self' blob: filesystem:;";
+  const char kCustomExtensionPagesCSP[] = "script-src; object-src;";
+
+  struct {
+    const char* file_name;
+    const char* resource_path;
+    const char* expected_csp;
+  } success_cases[] = {
+      {"sandbox_dictionary_1.json", "/test", kCustomSandboxedCSP},
+      {"sandbox_dictionary_1.json", "/index", kDefaultExtensionPagesCSP},
+      {"sandbox_dictionary_2.json", "/test", kDefaultSandboxedPageCSP},
+      {"sandbox_dictionary_2.json", "/index", kCustomExtensionPagesCSP},
+  };
+
+  for (const auto& test_case : success_cases) {
+    SCOPED_TRACE(base::StringPrintf("%s with path %s", test_case.file_name,
+                                    test_case.resource_path));
+    scoped_refptr<Extension> extension =
+        LoadAndExpectSuccess(test_case.file_name);
+    ASSERT_TRUE(extension);
+    EXPECT_EQ(test_case.expected_csp,
+              CSPInfo::GetResourceContentSecurityPolicy(
+                  extension.get(), test_case.resource_path));
+  }
+
+  Testcase testcases[] = {
+      {"sandbox_both_keys.json", errors::kSandboxPagesCSPKeyNotAllowed},
+      {"sandbox_csp_with_dictionary.json",
+       errors::kSandboxPagesCSPKeyNotAllowed},
+      {"sandbox_invalid_type.json",
+       GetInvalidManifestKeyError(
+           keys::kContentSecurityPolicy_SandboxedPagesPath)},
+      {"unsandboxed_csp.json",
+       GetInvalidManifestKeyError(
+           keys::kContentSecurityPolicy_SandboxedPagesPath)}};
+  RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
+}
+
 }  // namespace extensions
diff --git a/extensions/test/data/manifest_tests/sandbox_both_keys.json b/extensions/test/data/manifest_tests/sandbox_both_keys.json
new file mode 100644
index 0000000..347809d
--- /dev/null
+++ b/extensions/test/data/manifest_tests/sandbox_both_keys.json
@@ -0,0 +1,12 @@
+{
+  "name": "test",
+  "version": "0.1",
+  "manifest_version": 2,
+  "sandbox": {
+    "pages": ["test"],
+    "content_security_policy": "sandbox;"
+  },
+  "content_security_policy" : {
+    "sandbox" : "sandbox; script-src https://www.google.com"
+  }
+}
diff --git a/extensions/test/data/manifest_tests/sandbox_csp_with_dictionary.json b/extensions/test/data/manifest_tests/sandbox_csp_with_dictionary.json
new file mode 100644
index 0000000..ffceab8
--- /dev/null
+++ b/extensions/test/data/manifest_tests/sandbox_csp_with_dictionary.json
@@ -0,0 +1,12 @@
+{
+  "name": "test",
+  "version": "0.1",
+  "manifest_version": 2,
+  "sandbox": {
+    "pages": ["test"],
+    "content_security_policy": "sandbox;"
+  },
+  "content_security_policy" : {
+    "extension_pages" : "script-src google.com;"
+  }
+}
diff --git a/extensions/test/data/manifest_tests/sandbox_dictionary_1.json b/extensions/test/data/manifest_tests/sandbox_dictionary_1.json
new file mode 100644
index 0000000..daa54d4
--- /dev/null
+++ b/extensions/test/data/manifest_tests/sandbox_dictionary_1.json
@@ -0,0 +1,11 @@
+{
+  "name": "test",
+  "version": "0.1",
+  "manifest_version": 2,
+  "sandbox": {
+    "pages": ["test"]
+  },
+  "content_security_policy" : {
+    "sandbox" : "sandbox; script-src https://www.google.com"
+  }
+}
diff --git a/extensions/test/data/manifest_tests/sandbox_dictionary_2.json b/extensions/test/data/manifest_tests/sandbox_dictionary_2.json
new file mode 100644
index 0000000..0c5fff5
--- /dev/null
+++ b/extensions/test/data/manifest_tests/sandbox_dictionary_2.json
@@ -0,0 +1,11 @@
+{
+  "name": "test",
+  "version": "0.1",
+  "manifest_version": 2,
+  "sandbox": {
+    "pages": ["test"]
+  },
+  "content_security_policy" : {
+    "extension_pages" : "script-src; object-src;"
+  }
+}
diff --git a/extensions/test/data/manifest_tests/sandbox_invalid_type.json b/extensions/test/data/manifest_tests/sandbox_invalid_type.json
new file mode 100644
index 0000000..0f959d0
--- /dev/null
+++ b/extensions/test/data/manifest_tests/sandbox_invalid_type.json
@@ -0,0 +1,11 @@
+{
+  "name": "test",
+  "version": "0.1",
+  "manifest_version": 2,
+  "sandbox": {
+    "pages": ["test"]
+  },
+  "content_security_policy" : {
+    "sandbox" : []
+  }
+}
diff --git a/extensions/test/data/manifest_tests/unsandboxed_csp.json b/extensions/test/data/manifest_tests/unsandboxed_csp.json
new file mode 100644
index 0000000..35d70e6
--- /dev/null
+++ b/extensions/test/data/manifest_tests/unsandboxed_csp.json
@@ -0,0 +1,11 @@
+{
+  "name": "test",
+  "version": "0.1",
+  "manifest_version": 2,
+  "sandbox": {
+    "pages": ["test"]
+  },
+  "content_security_policy" : {
+    "sandbox" : "script-src https://www.google.com"
+  }
+}
diff --git a/gpu/command_buffer/client/client_transfer_cache.cc b/gpu/command_buffer/client/client_transfer_cache.cc
index 9e2e930d..b762dac 100644
--- a/gpu/command_buffer/client/client_transfer_cache.cc
+++ b/gpu/command_buffer/client/client_transfer_cache.cc
@@ -11,7 +11,7 @@
 ClientTransferCache::~ClientTransferCache() = default;
 
 void* ClientTransferCache::MapEntry(MappedMemoryManager* mapped_memory,
-                                    size_t size) {
+                                    uint32_t size) {
   DCHECK(!mapped_ptr_);
   DCHECK(!transfer_buffer_ptr_);
   mapped_ptr_.emplace(size, client_->cmd_buffer_helper(), mapped_memory);
@@ -24,7 +24,7 @@
 
 void* ClientTransferCache::MapTransferBufferEntry(
     TransferBufferInterface* transfer_buffer,
-    size_t size) {
+    uint32_t size) {
   DCHECK(!mapped_ptr_);
   DCHECK(!transfer_buffer_ptr_);
   transfer_buffer_ptr_.emplace(size, client_->cmd_buffer_helper(),
@@ -64,7 +64,7 @@
                                                 uint32_t id,
                                                 uint32_t shm_id,
                                                 uint32_t shm_offset,
-                                                size_t size) {
+                                                uint32_t size) {
   DCHECK(!mapped_ptr_);
   EntryKey key(type, id);
 
diff --git a/gpu/command_buffer/client/client_transfer_cache.h b/gpu/command_buffer/client/client_transfer_cache.h
index 45325e1..96f6a919 100644
--- a/gpu/command_buffer/client/client_transfer_cache.h
+++ b/gpu/command_buffer/client/client_transfer_cache.h
@@ -79,7 +79,7 @@
                              uint32_t id,
                              uint32_t shm_id,
                              uint32_t shm_offset,
-                             size_t size);
+                             uint32_t size);
 
   // Similar to AddTransferCacheEntry() but doesn't use |client_| to trigger the
   // creation of the service-side cache entry. Instead, it calls
@@ -97,9 +97,9 @@
       base::OnceCallback<void(ClientDiscardableHandle)> create_entry_cb);
 
   // Map(of either type) must always be followed by an Unmap.
-  void* MapEntry(MappedMemoryManager* mapped_memory, size_t size);
+  void* MapEntry(MappedMemoryManager* mapped_memory, uint32_t size);
   void* MapTransferBufferEntry(TransferBufferInterface* transfer_buffer,
-                               size_t size);
+                               uint32_t size);
   void UnmapAndCreateEntry(uint32_t type, uint32_t id);
   bool LockEntry(uint32_t type, uint32_t id);
   void UnlockEntries(const std::vector<std::pair<uint32_t, uint32_t>>& entries);
diff --git a/gpu/command_buffer/client/context_support.h b/gpu/command_buffer/client/context_support.h
index 3e4fed2..a3438f75 100644
--- a/gpu/command_buffer/client/context_support.h
+++ b/gpu/command_buffer/client/context_support.h
@@ -116,7 +116,7 @@
   // Maps a buffer that will receive serialized data for an entry to be created.
   // Returns nullptr on failure. If success, must be paired with a call to
   // UnmapAndCreateTransferCacheEntry.
-  virtual void* MapTransferCacheEntry(size_t serialized_size) = 0;
+  virtual void* MapTransferCacheEntry(uint32_t serialized_size) = 0;
 
   // Unmaps the buffer and creates a transfer cache entry with the serialized
   // data.
diff --git a/gpu/command_buffer/client/gles2_implementation.cc b/gpu/command_buffer/client/gles2_implementation.cc
index e857907..9a659301 100644
--- a/gpu/command_buffer/client/gles2_implementation.cc
+++ b/gpu/command_buffer/client/gles2_implementation.cc
@@ -6402,7 +6402,7 @@
   return manager->TextureIsDeletedForTracing(texture_id);
 }
 
-void* GLES2Implementation::MapTransferCacheEntry(size_t serialized_size) {
+void* GLES2Implementation::MapTransferCacheEntry(uint32_t serialized_size) {
   NOTREACHED();
   return nullptr;
 }
diff --git a/gpu/command_buffer/client/gles2_implementation.h b/gpu/command_buffer/client/gles2_implementation.h
index 0858127..174431b 100644
--- a/gpu/command_buffer/client/gles2_implementation.h
+++ b/gpu/command_buffer/client/gles2_implementation.h
@@ -136,7 +136,7 @@
       uint32_t texture_id) override;
   bool ThreadsafeDiscardableTextureIsDeletedForTracing(
       uint32_t texture_id) override;
-  void* MapTransferCacheEntry(size_t serialized_size) override;
+  void* MapTransferCacheEntry(uint32_t serialized_size) override;
   void UnmapAndCreateTransferCacheEntry(uint32_t type, uint32_t id) override;
   bool ThreadsafeLockTransferCacheEntry(uint32_t type, uint32_t id) override;
   void UnlockTransferCacheEntries(
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index 02db32f..4f99136 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -115,7 +115,7 @@
 
   size_t CreateEntryInternal(const cc::ClientTransferCacheEntry& entry,
                              char* memory) final {
-    size_t size = entry.SerializedSize();
+    uint32_t size = entry.SerializedSize();
     // Cap the entries inlined to a specific size.
     if (size <= ri_->max_inlined_entry_size_ && ri_->raster_mapped_buffer_) {
       size_t written = InlineEntry(entry, memory);
@@ -154,7 +154,7 @@
     DCHECK(buffer->BelongsToBuffer(memory));
 
     size_t memory_offset = memory - static_cast<char*>(buffer->address());
-    size_t bytes_to_write = entry.SerializedSize();
+    uint32_t bytes_to_write = entry.SerializedSize();
     size_t bytes_remaining = buffer->size() - memory_offset;
     DCHECK_GT(bytes_to_write, 0u);
 
@@ -481,7 +481,7 @@
   return false;
 }
 
-void* RasterImplementation::MapTransferCacheEntry(size_t serialized_size) {
+void* RasterImplementation::MapTransferCacheEntry(uint32_t serialized_size) {
   // Prefer to use transfer buffer when possible, since transfer buffer
   // allocations are much cheaper.
   if (raster_mapped_buffer_ ||
diff --git a/gpu/command_buffer/client/raster_implementation.h b/gpu/command_buffer/client/raster_implementation.h
index 681859f..d01b436 100644
--- a/gpu/command_buffer/client/raster_implementation.h
+++ b/gpu/command_buffer/client/raster_implementation.h
@@ -163,7 +163,7 @@
       uint32_t texture_id) override;
   bool ThreadsafeDiscardableTextureIsDeletedForTracing(
       uint32_t texture_id) override;
-  void* MapTransferCacheEntry(size_t serialized_size) override;
+  void* MapTransferCacheEntry(uint32_t serialized_size) override;
   void UnmapAndCreateTransferCacheEntry(uint32_t type, uint32_t id) override;
   bool ThreadsafeLockTransferCacheEntry(uint32_t type, uint32_t id) override;
   void UnlockTransferCacheEntries(
diff --git a/gpu/command_buffer/client/raster_implementation_gles_unittest.cc b/gpu/command_buffer/client/raster_implementation_gles_unittest.cc
index 4737c2f1..4dc4429 100644
--- a/gpu/command_buffer/client/raster_implementation_gles_unittest.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles_unittest.cc
@@ -206,7 +206,7 @@
       uint32_t texture_id) override {
     return false;
   }
-  void* MapTransferCacheEntry(size_t serialized_size) override {
+  void* MapTransferCacheEntry(uint32_t serialized_size) override {
     mapped_transfer_cache_entry_.reset(new char[serialized_size]);
     return mapped_transfer_cache_entry_.get();
   }
diff --git a/gpu/config/gpu_preferences.h b/gpu/config/gpu_preferences.h
index 1d5d6c0c..79965fe 100644
--- a/gpu/config/gpu_preferences.h
+++ b/gpu/config/gpu_preferences.h
@@ -101,6 +101,9 @@
 
   bool log_gpu_control_list_decisions = false;
 
+  // Enable exporting of events to ETW (on Windows).
+  bool enable_trace_export_events_to_etw = false;
+
   // ===================================
   // Settings from //gpu/command_buffer/service/gpu_switches.cc
 
diff --git a/gpu/config/gpu_preferences_unittest.cc b/gpu/config/gpu_preferences_unittest.cc
index d31ea56..260ef9ce3 100644
--- a/gpu/config/gpu_preferences_unittest.cc
+++ b/gpu/config/gpu_preferences_unittest.cc
@@ -35,6 +35,8 @@
             right.disable_software_rasterizer);
   EXPECT_EQ(left.log_gpu_control_list_decisions,
             right.log_gpu_control_list_decisions);
+  EXPECT_EQ(left.enable_trace_export_events_to_etw,
+            right.enable_trace_export_events_to_etw);
   EXPECT_EQ(left.compile_shader_always_succeeds,
             right.compile_shader_always_succeeds);
   EXPECT_EQ(left.disable_gl_error_limit, right.disable_gl_error_limit);
@@ -125,6 +127,7 @@
     GPU_PREFERENCES_FIELD(enable_media_foundation_vea_on_windows7, true)
     GPU_PREFERENCES_FIELD(disable_software_rasterizer, true)
     GPU_PREFERENCES_FIELD(log_gpu_control_list_decisions, true)
+    GPU_PREFERENCES_FIELD(enable_trace_export_events_to_etw, true)
     GPU_PREFERENCES_FIELD(compile_shader_always_succeeds, true)
     GPU_PREFERENCES_FIELD(disable_gl_error_limit, true)
     GPU_PREFERENCES_FIELD(disable_glsl_translator, true)
diff --git a/gpu/ipc/common/gpu_preferences.mojom b/gpu/ipc/common/gpu_preferences.mojom
index 61d6150a..fc33fa77 100644
--- a/gpu/ipc/common/gpu_preferences.mojom
+++ b/gpu/ipc/common/gpu_preferences.mojom
@@ -34,6 +34,7 @@
   bool enable_media_foundation_vea_on_windows7;
   bool disable_software_rasterizer;
   bool log_gpu_control_list_decisions;
+  bool enable_trace_export_events_to_etw;
 
   bool compile_shader_always_succeeds;
   bool disable_gl_error_limit;
diff --git a/gpu/ipc/common/gpu_preferences_struct_traits.h b/gpu/ipc/common/gpu_preferences_struct_traits.h
index 22e7524a..29056f6 100644
--- a/gpu/ipc/common/gpu_preferences_struct_traits.h
+++ b/gpu/ipc/common/gpu_preferences_struct_traits.h
@@ -76,6 +76,8 @@
     out->disable_software_rasterizer = prefs.disable_software_rasterizer();
     out->log_gpu_control_list_decisions =
         prefs.log_gpu_control_list_decisions();
+    out->enable_trace_export_events_to_etw =
+        prefs.enable_trace_export_events_to_etw();
     out->compile_shader_always_succeeds =
         prefs.compile_shader_always_succeeds();
     out->disable_gl_error_limit = prefs.disable_gl_error_limit();
@@ -177,6 +179,10 @@
   static bool log_gpu_control_list_decisions(const gpu::GpuPreferences& prefs) {
     return prefs.log_gpu_control_list_decisions;
   }
+  static bool enable_trace_export_events_to_etw(
+      const gpu::GpuPreferences& prefs) {
+    return prefs.enable_trace_export_events_to_etw;
+  }
   static bool compile_shader_always_succeeds(const gpu::GpuPreferences& prefs) {
     return prefs.compile_shader_always_succeeds;
   }
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 80a50114..2b6f0f7 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1775,6 +1775,9 @@
       <message name="IDS_IOS_TOOLS_MENU_REQUEST_MOBILE_SITE" desc="The iOS menu item for requesting the mobile site [iOS only]" meaning="Request the mobile version of the current web site. [Length: Unlimited]">
         Request Mobile Site
       </message>
+      <message name="IDS_IOS_TOOLS_MENU_SEARCH_COPIED_IMAGE" desc="The iOS menu item for searching for the image copied to the system clipboard">
+        Search Copied Image
+      </message>
       <message name="IDS_IOS_TOOLS_MENU_SEARCH_COPIED_TEXT" desc="The iOS menu item for pasting text in the omnibox and searching for the text.">
           Search Copied Text
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_SEARCH_COPIED_IMAGE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_SEARCH_COPIED_IMAGE.png.sha1
new file mode 100644
index 0000000..0fa4a2f4
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_SEARCH_COPIED_IMAGE.png.sha1
@@ -0,0 +1 @@
+e3362f58b1ce066591b4318ab118cfba6bfeda2a
\ No newline at end of file
diff --git a/ios/chrome/browser/find_in_page/find_tab_helper.h b/ios/chrome/browser/find_in_page/find_tab_helper.h
index 4c8aa98..1b7f9fb 100644
--- a/ios/chrome/browser/find_in_page/find_tab_helper.h
+++ b/ios/chrome/browser/find_in_page/find_tab_helper.h
@@ -77,9 +77,8 @@
   FindTabHelper(web::WebState* web_state);
 
   // web::WebStateObserver.
-  void NavigationItemCommitted(
-      web::WebState* web_state,
-      const web::LoadCommittedDetails& load_details) override;
+  void DidFinishNavigation(web::WebState* web_state,
+                           web::NavigationContext* navigation_context) override;
   void WebStateDestroyed(web::WebState* web_state) override;
 
   // The ObjC find in page controller.
diff --git a/ios/chrome/browser/find_in_page/find_tab_helper.mm b/ios/chrome/browser/find_in_page/find_tab_helper.mm
index c34c39d..ee34163 100644
--- a/ios/chrome/browser/find_in_page/find_tab_helper.mm
+++ b/ios/chrome/browser/find_in_page/find_tab_helper.mm
@@ -85,9 +85,9 @@
   [controller_ restoreSearchTerm];
 }
 
-void FindTabHelper::NavigationItemCommitted(
+void FindTabHelper::DidFinishNavigation(
     web::WebState* web_state,
-    const web::LoadCommittedDetails& load_details) {
+    web::NavigationContext* navigation_context) {
   StopFinding(nil);
 }
 
diff --git a/ios/chrome/browser/search_engines/search_engines_util.cc b/ios/chrome/browser/search_engines/search_engines_util.cc
index 7c47af1..70540dd 100644
--- a/ios/chrome/browser/search_engines/search_engines_util.cc
+++ b/ios/chrome/browser/search_engines/search_engines_util.cc
@@ -112,4 +112,10 @@
     new LoadedObserver(service);  // The observer manages its own lifetime.
 }
 
+bool SupportsSearchByImage(TemplateURLService* service) {
+  const TemplateURL* default_url = service->GetDefaultSearchProvider();
+  return default_url && !default_url->image_url().empty() &&
+         default_url->image_url_ref().IsValid(service->search_terms_data());
+}
+
 }  // namespace search_engines
diff --git a/ios/chrome/browser/search_engines/search_engines_util.h b/ios/chrome/browser/search_engines/search_engines_util.h
index 69e36c1..2ecd221 100644
--- a/ios/chrome/browser/search_engines/search_engines_util.h
+++ b/ios/chrome/browser/search_engines/search_engines_util.h
@@ -17,6 +17,10 @@
 void UpdateSearchEnginesIfNeeded(PrefService* preferences,
                                  TemplateURLService* service);
 
+// Checks whether the default url of the given template url supports searching
+// by image.
+bool SupportsSearchByImage(TemplateURLService* service);
+
 }  // namespace search_engines
 
 #endif  // IOS_CHROME_BROWSER_SEARCH_ENGINES_SEARCH_ENGINES_UTIL_H_
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index 5349a691..773f665 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -54,6 +54,7 @@
 #import "ios/chrome/browser/prerender/prerender_service_factory.h"
 #include "ios/chrome/browser/reading_list/offline_url_utils.h"
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
+#include "ios/chrome/browser/search_engines/search_engines_util.h"
 #include "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
 #import "ios/chrome/browser/signin/account_consistency_service_factory.h"
@@ -105,6 +106,7 @@
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
 #import "ios/chrome/browser/ui/image_util/image_copier.h"
 #import "ios/chrome/browser/ui/image_util/image_saver.h"
+#import "ios/chrome/browser/ui/image_util/image_util.h"
 #import "ios/chrome/browser/ui/infobars/infobar_container_coordinator.h"
 #import "ios/chrome/browser/ui/infobars/infobar_positioner.h"
 #import "ios/chrome/browser/ui/key_commands_provider.h"
@@ -193,6 +195,7 @@
 #include "ios/web/public/web_client.h"
 #import "ios/web/public/web_state/context_menu_params.h"
 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
+#import "ios/web/public/web_state/navigation_context.h"
 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
 #import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
 #import "ios/web/public/web_state/ui/crw_web_view_scroll_view_proxy.h"
@@ -202,7 +205,6 @@
 #include "ios/web/public/web_thread.h"
 #import "ios/web/web_state/ui/crw_web_controller.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 #include "ui/base/page_transition_types.h"
@@ -270,11 +272,6 @@
 // Duration of the toolbar animation.
 const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
 
-// Dimensions to use when downsizing an image for search-by-image.
-const CGFloat kSearchByImageMaxImageArea = 90000.0;
-const CGFloat kSearchByImageMaxImageWidth = 600.0;
-const CGFloat kSearchByImageMaxImageHeight = 400.0;
-
 // When the tab strip moves beyond this origin offset, switch the status bar
 // appearance from light to dark.
 const CGFloat kTabStripAppearanceOffset = -29;
@@ -3290,9 +3287,8 @@
 
     TemplateURLService* service =
         ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
-    const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
-    if (defaultURL && !defaultURL->image_url().empty() &&
-        defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
+    if (search_engines::SupportsSearchByImage(service)) {
+      const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
       title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
                                       defaultURL->short_name());
       action = ^{
@@ -3360,27 +3356,25 @@
   }
 }
 
-// Performs a search using |data| and |imageURL| as inputs.
+// Performs a search using |data| and |imageURL| as inputs. Opens the results in
+// a new tab based on |inNewTab|.
 - (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
   NSData* imageData = data;
-  UIImage* image = [UIImage imageWithData:imageData];
-  // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
-  // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
-  // kSearchByImageMaxImageHeight).
-  if (image &&
-      image.size.height * image.size.width > kSearchByImageMaxImageArea &&
-      (image.size.width > kSearchByImageMaxImageWidth ||
-       image.size.height > kSearchByImageMaxImageHeight)) {
-    CGSize newImageSize =
-        CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
-    image = [image gtm_imageByResizingToSize:newImageSize
-                         preserveAspectRatio:YES
-                                   trimToFit:NO];
-    imageData = UIImageJPEGRepresentation(image, 1.0);
+  UIImage* image = [UIImage imageWithData:data];
+  UIImage* resizedImage = ResizeImageForSearchByImage(image);
+  if (![image isEqual:resizedImage]) {
+    imageData = UIImageJPEGRepresentation(resizedImage, 1.0);
   }
+  [self searchByResizedImageData:imageData atURL:&imageURL inNewTab:YES];
+}
 
-  char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
-  std::string byteString(bytes, [imageData length]);
+// Performs a search with the given image data. The data should alread have
+// been scaled down in |ResizeImageForSearchByImage|.
+- (void)searchByResizedImageData:(NSData*)data
+                           atURL:(const GURL*)imageURL
+                        inNewTab:(BOOL)inNewTab {
+  char const* bytes = reinterpret_cast<const char*>([data bytes]);
+  std::string byteString(bytes, [data length]);
 
   TemplateURLService* templateUrlService =
       ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
@@ -3390,7 +3384,9 @@
   DCHECK(defaultURL->image_url_ref().IsValid(
       templateUrlService->search_terms_data()));
   TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
-  search_args.image_url = imageURL;
+  if (imageURL) {
+    search_args.image_url = *imageURL;
+  }
   search_args.image_thumbnail_content = byteString;
 
   // Generate the URL and populate |post_content| with the content type and
@@ -3398,24 +3394,26 @@
   TemplateURLRef::PostContent postContent;
   GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
       search_args, templateUrlService->search_terms_data(), &postContent));
-  [self.tabModel
-      insertTabWithLoadParams:web_navigation_util::CreateWebLoadParams(
-                                  result, ui::PAGE_TRANSITION_TYPED,
-                                  &postContent)
-                       opener:nil
-                  openedByDOM:NO
-                      atIndex:self.tabModel.count
-                 inBackground:NO];
+  web::NavigationManager::WebLoadParams loadParams =
+      web_navigation_util::CreateWebLoadParams(
+          result, ui::PAGE_TRANSITION_TYPED, &postContent);
+  if (inNewTab) {
+    [self.tabModel insertTabWithLoadParams:loadParams
+                                    opener:nil
+                               openedByDOM:NO
+                                   atIndex:self.tabModel.count
+                              inBackground:NO];
+  } else {
+    [self loadURLWithParams:ChromeLoadParams(loadParams)];
+  }
 }
 
 #pragma mark - CRWWebStateObserver methods.
 
-// TODO(crbug.com/918934): didCommitNavigationWithDetails is deprecated, and
-// this call to closeFindInPage incorrectly triggers for all navigations, not
-// just navigations in the active WebState.
+// TODO(crbug.com/918934): This call to closeFindInPage incorrectly triggers for
+// all navigations, not just navigations in the active WebState.
 - (void)webState:(web::WebState*)webState
-    didCommitNavigationWithDetails:
-        (const web::LoadCommittedDetails&)load_details {
+    didFinishNavigation:(web::NavigationContext*)navigation {
   // Stop any Find in Page searches and close the find bar when navigating to a
   // new page.
   [self closeFindInPage];
@@ -4394,6 +4392,12 @@
   [nativeController focusFakebox];
 }
 
+- (void)searchByImage:(UIImage*)image {
+  UIImage* resizedImage = ResizeImageForSearchByImage(image);
+  NSData* data = UIImageJPEGRepresentation(resizedImage, 1.0);
+  [self searchByResizedImageData:data atURL:nil inNewTab:NO];
+}
+
 #pragma mark - BrowserCommands helpers
 
 // Reloads the original url of the last non-redirect item (including non-history
diff --git a/ios/chrome/browser/ui/commands/browser_commands.h b/ios/chrome/browser/ui/commands/browser_commands.h
index 1315a9c..aec94190 100644
--- a/ios/chrome/browser/ui/commands/browser_commands.h
+++ b/ios/chrome/browser/ui/commands/browser_commands.h
@@ -6,6 +6,7 @@
 #define IOS_CHROME_BROWSER_UI_COMMANDS_BROWSER_COMMANDS_H_
 
 #import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
 
 #import "ios/chrome/browser/ui/commands/activity_service_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_coordinator_commands.h"
@@ -101,6 +102,9 @@
 // omnibox.
 - (void)focusFakebox;
 
+// Searches for an image in the current tab.
+- (void)searchByImage:(UIImage*)image;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_COMMANDS_BROWSER_COMMANDS_H_
diff --git a/ios/chrome/browser/ui/image_util/BUILD.gn b/ios/chrome/browser/ui/image_util/BUILD.gn
index 200b529..3fb922a 100644
--- a/ios/chrome/browser/ui/image_util/BUILD.gn
+++ b/ios/chrome/browser/ui/image_util/BUILD.gn
@@ -21,6 +21,7 @@
     "//ios/chrome/browser/web",
     "//ios/web",
     "//net",
+    "//third_party/google_toolbox_for_mac",
     "//ui/base",
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
diff --git a/ios/chrome/browser/ui/image_util/image_util.h b/ios/chrome/browser/ui/image_util/image_util.h
index 974d1de..f4a579e 100644
--- a/ios/chrome/browser/ui/image_util/image_util.h
+++ b/ios/chrome/browser/ui/image_util/image_util.h
@@ -39,4 +39,9 @@
 // is nil, empty, or cannot be recognized, nil will be returned.
 NSString* GetImageUTIFromData(NSData* data);
 
+// Downsizes the image if its area exceeds kSearchByImageMaxImageArea AND
+// (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
+// kSearchByImageMaxImageHeight) in preparation for searching.
+UIImage* ResizeImageForSearchByImage(UIImage* image);
+
 #endif  // IOS_CHROME_BROWSER_UI_IMAGE_UTIL_IMAGE_UTIL_H_
diff --git a/ios/chrome/browser/ui/image_util/image_util.mm b/ios/chrome/browser/ui/image_util/image_util.mm
index 521421b..05a7102 100644
--- a/ios/chrome/browser/ui/image_util/image_util.mm
+++ b/ios/chrome/browser/ui/image_util/image_util.mm
@@ -7,6 +7,7 @@
 
 #import "ios/chrome/browser/ui/image_util/image_util.h"
 
+#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
 #include "ui/gfx/color_analysis.h"
 #include "ui/gfx/image/image.h"
 
@@ -22,6 +23,11 @@
 NSString* kImageExtensionGIF = @"gif";
 NSString* kImageExtensionICO = @"ico";
 NSString* kImageExtensionWebP = @"webp";
+
+// Dimensions to use when downsizing an image for search-by-image.
+const CGFloat kSearchByImageMaxImageArea = 90000.0;
+const CGFloat kSearchByImageMaxImageWidth = 600.0;
+const CGFloat kSearchByImageMaxImageHeight = 400.0;
 }
 
 UIColor* DominantColorForImage(const gfx::Image& image, CGFloat opacity) {
@@ -107,3 +113,17 @@
   };
   return dict[GetImageExtensionFromData(data)];
 }
+
+UIImage* ResizeImageForSearchByImage(UIImage* image) {
+  if (image &&
+      image.size.height * image.size.width > kSearchByImageMaxImageArea &&
+      (image.size.width > kSearchByImageMaxImageWidth ||
+       image.size.height > kSearchByImageMaxImageHeight)) {
+    CGSize newImageSize =
+        CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
+    image = [image gtm_imageByResizingToSize:newImageSize
+                         preserveAspectRatio:YES
+                                   trimToFit:NO];
+  }
+  return image;
+}
diff --git a/ios/chrome/browser/ui/popup_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/BUILD.gn
index 5a6460a..d22edf9 100644
--- a/ios/chrome/browser/ui/popup_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/BUILD.gn
@@ -49,6 +49,7 @@
     "//ios/chrome/browser/feature_engagement",
     "//ios/chrome/browser/find_in_page",
     "//ios/chrome/browser/reading_list",
+    "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/ui",
     "//ios/chrome/browser/ui/activity_services",
     "//ios/chrome/browser/ui/bookmarks",
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_action_handler.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_action_handler.mm
index a40114d49..01469037a 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_action_handler.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_action_handler.mm
@@ -156,6 +156,17 @@
       RecordAction(UserMetricsAction("MobileMenuScanQRCode"));
       [self.dispatcher showQRScanner];
       break;
+    case PopupMenuActionSearchCopiedImage: {
+      DCHECK(base::FeatureList::IsEnabled(omnibox::kCopiedTextBehavior));
+      RecordAction(UserMetricsAction("MobileMenuSearchCopiedImage"));
+      ClipboardRecentContent* clipboardRecentContent =
+          ClipboardRecentContent::GetInstance();
+      if (base::Optional<gfx::Image> image =
+              clipboardRecentContent->GetRecentImageFromClipboard()) {
+        [self.dispatcher searchByImage:[image.value().ToUIImage() copy]];
+      }
+      break;
+    }
     default:
       NOTREACHED() << "Unexpected identifier";
       break;
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
index ac7dc1e..afb30795 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
@@ -60,5 +60,7 @@
 extern NSString* const kToolsMenuVoiceSearch;
 // QR Code Search item accessibility Identifier.
 extern NSString* const kToolsMenuQRCodeSearch;
+// Copied Image Search item accessibility Identifier.
+extern NSString* const kToolsMenuCopiedImageSearch;
 
 #endif  // IOS_CHROME_BROWSER_UI_POPUP_MENU_POPUP_MENU_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
index 91a1f7b..4a33b6f1 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
@@ -38,3 +38,4 @@
 NSString* const kToolsMenuPasteAndGo = @"kToolsMenuPasteAndGo";
 NSString* const kToolsMenuVoiceSearch = @"kToolsMenuVoiceSearch";
 NSString* const kToolsMenuQRCodeSearch = @"kToolsMenuQRCodeSearch";
+NSString* const kToolsMenuCopiedImageSearch = @"kToolsMenuCopiedImageSearch";
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
index 47eaba0..89e1dad 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
@@ -12,6 +12,7 @@
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/feature_engagement/tracker_factory.h"
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
+#import "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #import "ios/chrome/browser/ui/bubble/bubble_presenter.h"
 #import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
@@ -230,10 +231,12 @@
   self.mediator.engagementTracker =
       feature_engagement::TrackerFactory::GetForBrowserState(self.browserState);
   self.mediator.webStateList = self.webStateList;
-  self.mediator.popupMenu = tableViewController;
   self.mediator.dispatcher = static_cast<id<BrowserCommands>>(self.dispatcher);
   self.mediator.bookmarkModel =
       ios::BookmarkModelFactory::GetForBrowserState(self.browserState);
+  self.mediator.templateURLService =
+      ios::TemplateURLServiceFactory::GetForBrowserState(self.browserState);
+  self.mediator.popupMenu = tableViewController;
 
   self.actionHandler = [[PopupMenuActionHandler alloc] init];
   self.actionHandler.baseViewController = self.baseViewController;
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.h b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.h
index 01d7f183..f0ea9966 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.h
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.h
@@ -19,6 +19,7 @@
 @protocol BrowserCommands;
 @protocol PopupMenuConsumer;
 class ReadingListModel;
+class TemplateURLService;
 class WebStateList;
 
 // Mediator for the popup menu. This object is in charge of creating and
@@ -48,6 +49,9 @@
 @property(nonatomic, assign) feature_engagement::Tracker* engagementTracker;
 // The bookmarks model to know if the page is bookmarked.
 @property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel;
+// The template url service to use for checking whether search by image is
+// available.
+@property(nonatomic, assign) TemplateURLService* templateURLService;
 
 // Disconnect the mediator.
 - (void)disconnect;
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
index 6b88238..51065b5 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
@@ -12,8 +12,10 @@
 #include "components/feature_engagement/public/tracker.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/open_from_clipboard/clipboard_recent_content.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
 #import "ios/chrome/browser/find_in_page/find_tab_helper.h"
+#import "ios/chrome/browser/search_engines/search_engines_util.h"
 #import "ios/chrome/browser/ui/activity_services/canonical_url_retriever.h"
 #include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
@@ -590,18 +592,26 @@
   if (base::FeatureList::IsEnabled(omnibox::kCopiedTextBehavior)) {
     ClipboardRecentContent* clipboardRecentContent =
         ClipboardRecentContent::GetInstance();
-    NSNumber* titleID = nil;
-    if (clipboardRecentContent->GetRecentURLFromClipboard()) {
-      titleID = [NSNumber numberWithInt:IDS_IOS_TOOLS_MENU_VISIT_COPIED_LINK];
+    PopupMenuToolsItem* copiedContentItem = nil;
+
+    if (search_engines::SupportsSearchByImage(self.templateURLService) &&
+        clipboardRecentContent->GetRecentImageFromClipboard()) {
+      copiedContentItem = CreateTableViewItem(
+          IDS_IOS_TOOLS_MENU_SEARCH_COPIED_IMAGE,
+          PopupMenuActionSearchCopiedImage, @"popup_menu_paste_and_go",
+          kToolsMenuCopiedImageSearch);
+    } else if (clipboardRecentContent->GetRecentURLFromClipboard()) {
+      copiedContentItem = CreateTableViewItem(
+          IDS_IOS_TOOLS_MENU_VISIT_COPIED_LINK, PopupMenuActionPasteAndGo,
+          @"popup_menu_paste_and_go", kToolsMenuPasteAndGo);
     } else if (clipboardRecentContent->GetRecentTextFromClipboard()) {
-      titleID = [NSNumber numberWithInt:IDS_IOS_TOOLS_MENU_SEARCH_COPIED_TEXT];
+      copiedContentItem = CreateTableViewItem(
+          IDS_IOS_TOOLS_MENU_SEARCH_COPIED_TEXT, PopupMenuActionPasteAndGo,
+          @"popup_menu_paste_and_go", kToolsMenuPasteAndGo);
     }
 
-    if (titleID) {
-      PopupMenuToolsItem* pasteAndGo =
-          CreateTableViewItem(titleID.intValue, PopupMenuActionPasteAndGo,
-                              @"popup_menu_paste_and_go", kToolsMenuPasteAndGo);
-      [items addObject:pasteAndGo];
+    if (copiedContentItem) {
+      [items addObject:copiedContentItem];
     }
   } else {
     NSString* pasteboardString = [UIPasteboard generalPasteboard].string;
diff --git a/ios/chrome/browser/ui/popup_menu/public/cells/popup_menu_item.h b/ios/chrome/browser/ui/popup_menu/public/cells/popup_menu_item.h
index efbaf9c..18a1654 100644
--- a/ios/chrome/browser/ui/popup_menu/public/cells/popup_menu_item.h
+++ b/ios/chrome/browser/ui/popup_menu/public/cells/popup_menu_item.h
@@ -34,6 +34,7 @@
   PopupMenuActionPasteAndGo,
   PopupMenuActionVoiceSearch,
   PopupMenuActionQRCodeSearch,
+  PopupMenuActionSearchCopiedImage,
 };
 
 // Protocol defining a popup item.
diff --git a/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.h b/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.h
index 729fb5e..28cd217 100644
--- a/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.h
+++ b/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.h
@@ -59,9 +59,8 @@
                            bool user_action) override;
 
   // WebStateObserver:
-  void NavigationItemCommitted(
-      web::WebState* web_state,
-      const web::LoadCommittedDetails& load_details) override;
+  void DidFinishNavigation(web::WebState* web_state,
+                           web::NavigationContext* navigation_context) override;
   void WebStateDestroyed(web::WebState* web_state) override;
 
   // Helper object that listens for TTS notifications.
diff --git a/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.mm b/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.mm
index d3756f1e..2a8b710 100644
--- a/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.mm
+++ b/ios/chrome/browser/ui/voice/text_to_speech_playback_controller.mm
@@ -108,9 +108,9 @@
 
 #pragma mark WebStateObserver
 
-void TextToSpeechPlaybackController::NavigationItemCommitted(
+void TextToSpeechPlaybackController::DidFinishNavigation(
     web::WebState* web_state,
-    const web::LoadCommittedDetails& load_details) {
+    web::NavigationContext* navigation_context) {
   [notification_helper_ cancelPlayback];
 }
 
diff --git a/ios/chrome/browser/voice/voice_search_navigations_tab_helper.h b/ios/chrome/browser/voice/voice_search_navigations_tab_helper.h
index 79af113..aafe060 100644
--- a/ios/chrome/browser/voice/voice_search_navigations_tab_helper.h
+++ b/ios/chrome/browser/voice/voice_search_navigations_tab_helper.h
@@ -29,9 +29,8 @@
   explicit VoiceSearchNavigationTabHelper(web::WebState* web_state);
 
   // WebStateObserver:
-  void NavigationItemCommitted(
-      web::WebState* web_state,
-      const web::LoadCommittedDetails& load_details) override;
+  void DidFinishNavigation(web::WebState* web_state,
+                           web::NavigationContext* navigation_context) override;
   void WebStateDestroyed(web::WebState* web_state) override;
 
   // The WebState this instance is observing. Will be null after
diff --git a/ios/chrome/browser/voice/voice_search_navigations_tab_helper.mm b/ios/chrome/browser/voice/voice_search_navigations_tab_helper.mm
index 8a93b371..5d91c5471 100644
--- a/ios/chrome/browser/voice/voice_search_navigations_tab_helper.mm
+++ b/ios/chrome/browser/voice/voice_search_navigations_tab_helper.mm
@@ -7,26 +7,13 @@
 #include <memory>
 
 #include "base/logging.h"
-#include "ios/web/public/load_committed_details.h"
-#import "ios/web/public/navigation_item.h"
-#import "ios/web/public/navigation_manager.h"
+#import "ios/web/public/web_state/navigation_context.h"
 #import "ios/web/public/web_state/web_state.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-namespace {
-// The key under which the VoiceSearchNavigationMarkers are stored.
-const void* const kNavigationMarkerKey = &kNavigationMarkerKey;
-}
-
-#pragma mark - VoiceSearchNavigationMarker
-
-// A marker object that can be added to a NavigationItem.  Its presence
-// indicates that the navigation is the result of a voice search query.
-class VoiceSearchNavigationMarker : public base::SupportsUserData::Data {};
-
 #pragma mark - VoiceSearchNavigations
 
 VoiceSearchNavigationTabHelper::VoiceSearchNavigationTabHelper(
@@ -43,13 +30,11 @@
   return will_navigate_to_voice_search_result_;
 }
 
-void VoiceSearchNavigationTabHelper::NavigationItemCommitted(
+void VoiceSearchNavigationTabHelper::DidFinishNavigation(
     web::WebState* web_state,
-    const web::LoadCommittedDetails& load_details) {
+    web::NavigationContext* context) {
   DCHECK_EQ(web_state_, web_state);
-  if (will_navigate_to_voice_search_result_) {
-    web_state->GetNavigationManager()->GetLastCommittedItem()->SetUserData(
-        kNavigationMarkerKey, std::make_unique<VoiceSearchNavigationMarker>());
+  if (will_navigate_to_voice_search_result_ && context->HasCommitted()) {
     will_navigate_to_voice_search_result_ = false;
   }
 }
diff --git a/ios/web/navigation/error_retry_state_machine.mm b/ios/web/navigation/error_retry_state_machine.mm
index 3beaf057..02be34d 100644
--- a/ios/web/navigation/error_retry_state_machine.mm
+++ b/ios/web/navigation/error_retry_state_machine.mm
@@ -147,6 +147,18 @@
         return ErrorRetryCommand::kRewriteWebViewURL;
       }
 
+      if (wk_navigation_util::IsRestoreSessionUrl(web_view_url)) {
+        GURL target_url;
+        if (wk_navigation_util::ExtractTargetURL(web_view_url, &target_url) &&
+            target_url == url_) {
+          // (10) Back/forward navigation to a restored session entry in offline
+          // mode. It is OK to consider this load succeeded for now because the
+          // failure delegate will be triggered again if the load fails.
+          state_ = ErrorRetryState::kNoNavigationError;
+          return ErrorRetryCommand::kDoNothing;
+        }
+      }
+
       // (5) This is either a reload of the original URL that succeeded in
       // WebView (either because it was already in Page Cache or the network
       // load succeded), or a back/forward of a previous WebUI error that is
diff --git a/ios/web/navigation/error_retry_state_machine_unittest.mm b/ios/web/navigation/error_retry_state_machine_unittest.mm
index 1143bf2e..64986998 100644
--- a/ios/web/navigation/error_retry_state_machine_unittest.mm
+++ b/ios/web/navigation/error_retry_state_machine_unittest.mm
@@ -144,6 +144,17 @@
     ASSERT_EQ(ErrorRetryState::kReadyToDisplayErrorForFailedNavigation,
               clone.state());
   }
+
+  // Simulate back/forward navigation to a restored session entry that never
+  // succeeded in loading.
+  {
+    const GURL restore_session_url =
+        wk_navigation_util::CreateRedirectUrl(test_url);
+    ErrorRetryStateMachine clone(machine);
+    ASSERT_EQ(ErrorRetryCommand::kDoNothing,
+              clone.DidFinishNavigation(restore_session_url));
+    ASSERT_EQ(ErrorRetryState::kNoNavigationError, clone.state());
+  }
 }
 
 // Tests load failure after navigation is committed.
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 57fcf41f4..41e534d 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -782,6 +782,9 @@
 // used in the context of a URL KVO callback firing, and only if |isLoading| is
 // YES for the web view (since if it's not, no guesswork is needed).
 - (BOOL)isKVOChangePotentialSameDocumentNavigationToURL:(const GURL&)newURL;
+// Returns YES if a SafeBrowsing warning is currently displayed within
+// WKWebView.
+- (BOOL)isSafeBrowsingWarningDisplayedInWebView;
 // Called when a non-document-changing URL change occurs. Updates the
 // _documentURL, and informs the superclass of the change.
 - (void)URLDidChangeWithoutDocumentChange:(const GURL&)URL;
@@ -1082,6 +1085,27 @@
   return !_webProcessCrashed && [_containerView isViewAlive];
 }
 
+- (BOOL)isSafeBrowsingWarningDisplayedInWebView {
+  // A SafeBrowsing warning is a UIScrollView that is inserted on top of
+  // WKWebView's scroll view. This method uses heuristics to detect this view.
+  // It may break in the future if WebKit's implementation of SafeBrowsing
+  // warnings changes.
+  UIView* containingView = _webView.scrollView.superview;
+  if (!containingView)
+    return NO;
+
+  UIView* topView = containingView.subviews.lastObject;
+
+  if (topView == _webView.scrollView)
+    return NO;
+
+  return
+      [topView isKindOfClass:[UIScrollView class]] &&
+      [NSStringFromClass([topView class]) containsString:@"Warning"] &&
+      topView.subviews.count > 0 &&
+      [topView.subviews[0].subviews.lastObject isKindOfClass:[UIButton class]];
+}
+
 - (BOOL)contentIsHTML {
   if (!_webView)
     return NO;
@@ -5288,25 +5312,35 @@
 
   GURL webViewURL = net::GURLWithNSURL([_webView URL]);
 
+  if (![self isCurrentNavigationBackForward])
+    return;
+
+  web::NavigationContextImpl* existingContext =
+      [self contextForPendingMainFrameNavigationWithURL:webViewURL];
+
   // When traversing history restored from a previous session, WKWebView does
   // not fire 'pageshow', 'onload', 'popstate' or any of the
   // WKNavigationDelegate callbacks for back/forward navigation from an about:
-  // scheme placeholder URL to another entry. Loading state KVO is the only
-  // observable event in this scenario, so force a reload to trigger redirect
-  // from restore_session.html to the restored URL.
+  // scheme placeholder URL to another entry or if either of the redirect fails
+  // to load (e.g. in airplane mode). Loading state KVO is the only observable
+  // event in this scenario, so force a reload to trigger redirect from
+  // restore_session.html to the restored URL.
   bool previousURLHasAboutScheme =
       _documentURL.SchemeIs(url::kAboutScheme) ||
       IsPlaceholderUrl(_documentURL) ||
       web::GetWebClient()->IsAppSpecificURL(_documentURL);
+  bool is_back_forward_navigation =
+      existingContext &&
+      (existingContext->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK);
   if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
-      IsRestoreSessionUrl(webViewURL) && previousURLHasAboutScheme) {
-    [_webView reload];
-    return;
+      IsRestoreSessionUrl(webViewURL)) {
+    if (previousURLHasAboutScheme || is_back_forward_navigation) {
+      [_webView reload];
+      _loadPhase = web::LOAD_REQUESTED;
+      return;
+    }
   }
 
-  if (![self isCurrentNavigationBackForward])
-    return;
-
   // For failed navigations, WKWebView will sometimes revert to the previous URL
   // before committing the current navigation or resetting the web view's
   // |isLoading| property to NO.  If this is the first navigation for the web
@@ -5317,9 +5351,6 @@
     return;
   }
 
-  web::NavigationContextImpl* existingContext =
-      [self contextForPendingMainFrameNavigationWithURL:webViewURL];
-
   if (!navigationWasCommitted && ![_pendingNavigationInfo cancelled]) {
     // A fast back-forward navigation does not call |didCommitNavigation:|, so
     // signal page change explicitly.
@@ -5401,7 +5432,7 @@
     return;
   }
   GURL URL(net::GURLWithNSURL([_webView URL]));
-  // URL changes happen at three points:
+  // URL changes happen at four points:
   // 1) When a load starts; at this point, the load is provisional, and
   //    it should be ignored until it's committed, since the document/window
   //    objects haven't changed yet.
@@ -5410,11 +5441,15 @@
   //    be reported.
   // 3) When a navigation error occurs after provisional navigation starts,
   //    the URL reverts to the previous URL without triggering a new navigation.
+  // 4) When a SafeBrowsing warning is displayed after
+  //    decidePolicyForNavigationAction but before a provisional navigation
+  //    starts, and the user clicks the "Go Back" link on the warning page.
   //
-  // If |isLoading| is NO, then it must be case 2 or 3. If the last committed
-  // URL (_documentURL) matches the current URL, assume that it is a revert from
-  // navigation failure and do nothing. If the URL does not match, assume it is
-  // a non-document-changing URL change, and handle accordingly.
+  // If |isLoading| is NO, then it must be case 2, 3, or 4. If the last
+  // committed URL (_documentURL) matches the current URL, assume that it is
+  // case 4 if a SafeBrowsing warning is currently displayed and case 3
+  // otherwise. If the URL does not match, assume it is a non-document-changing
+  // URL change, and handle accordingly.
   //
   // If |isLoading| is YES, then it could either be case 1, or it could be case
   // 2 on a page that hasn't finished loading yet. If it's possible that it
@@ -5428,8 +5463,31 @@
   // window.location.href will match the previous URL at this stage, not the web
   // view's current URL.
   if (![_webView isLoading]) {
-    if (_documentURL == URL)
+    if (_documentURL == URL) {
+      if (![self isSafeBrowsingWarningDisplayedInWebView])
+        return;
+
+      self.navigationManagerImpl->DiscardNonCommittedItems();
+      _webStateImpl->SetIsLoading(false);
+      _pendingNavigationInfo = nil;
+      if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
+        // Right after a history navigation that gets cancelled by a tap on
+        // "Go Back", WKWebView's current back/forward list item will still be
+        // for the unsafe page; updating this is the responsibility of the
+        // WebProcess, so only happens after an IPC round-trip to and from the
+        // WebProcess with no notification to the embedder. This means that
+        // WKBasedNavigationManagerImpl::WKWebViewCache::GetCurrentItemIndex()
+        // will be the index of the unsafe page's item. To get back into a
+        // consistent state, force a reload.
+        [_webView reload];
+      } else {
+        // Tapping "Go Back" on a SafeBrowsing interstitial can change whether
+        // there are any forward or back items, e.g., by returning to or
+        // moving away from the forward-most or back-most item.
+        _webStateImpl->OnBackForwardStateChanged();
+      }
       return;
+    }
 
     // At this point, _webView, _webView.backForwardList.currentItem and its
     // associated NavigationItem should all have the same URL, except in two
diff --git a/ios/web/web_state/web_state_observer_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
index 6f3f943..16198da 100644
--- a/ios/web/web_state/web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -12,6 +12,7 @@
 #import "base/test/ios/wait_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "ios/testing/embedded_test_server_handlers.h"
+#include "ios/web/navigation/wk_navigation_util.h"
 #import "ios/web/public/crw_navigation_item_storage.h"
 #import "ios/web/public/crw_session_storage.h"
 #include "ios/web/public/features.h"
@@ -31,6 +32,7 @@
 #import "ios/web/test/web_int_test.h"
 #import "ios/web/web_state/ui/crw_web_controller.h"
 #import "ios/web/web_state/web_state_impl.h"
+#import "net/base/mac/url_conversions.h"
 #include "net/http/http_response_headers.h"
 #include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -52,6 +54,8 @@
 
 namespace {
 
+using wk_navigation_util::CreateRedirectUrl;
+
 const char kExpectedMimeType[] = "text/html";
 
 const char kFailedTitle[] = "failed_title";
@@ -629,7 +633,7 @@
 // A Google Mock matcher which matches |target_frame_is_main| member of
 // WebStatePolicyDecider::RequestInfo. This is needed because
 // WebStatePolicyDecider::RequestInfo doesn't support operator==.
-MATCHER_P(RequestInfoMatch, expected_request_info, /* argument_name = */ "") {
+MATCHER_P(RequestInfoMatch, expected_request_info, /*description=*/"") {
   return ui::PageTransitionTypeIncludingQualifiersIs(
              arg.transition_type, expected_request_info.transition_type) &&
          arg.target_frame_is_main ==
@@ -637,6 +641,12 @@
          arg.has_user_gesture == expected_request_info.has_user_gesture;
 }
 
+// A GMock matcher that matches |URL| member of |arg| with |expected_url|. |arg|
+// is expected to be either an NSURLRequest or NSURLResponse.
+MATCHER_P(URLMatch, expected_url, /*description=*/"") {
+  return expected_url == net::GURLWithNSURL(arg.URL);
+}
+
 // Mocks WebStateObserver navigation callbacks.
 class WebStateObserverMock : public WebStateObserver {
  public:
@@ -2154,6 +2164,9 @@
 
 // Verifies that WebState::CreateWithStorageSession does not call any
 // WebStateObserver callbacks.
+// TODO(crbug.com/738020): Remove this test after deprecating legacy navigation
+// manager. Restore session in slim navigation manager is better tested in
+// RestoreSessionOnline.
 TEST_P(WebStateObserverTest, RestoreSession) {
   // Create session storage.
   CRWNavigationItemStorage* item = [[CRWNavigationItemStorage alloc] init];
@@ -2205,6 +2218,116 @@
   ASSERT_TRUE(test::WaitForPageToFinishLoading(web_state.get()));
 }
 
+// Tests callbacks for restoring session and subsequently going back to
+// about:blank.
+TEST_P(WebStateObserverTest, RestoreSessionOnline) {
+  // LegacyNavigationManager doesn't trigger load in Restore.
+  if (!GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    return;
+  }
+
+  // Create a session of 3 items. Current item is at index 1.
+  const GURL url0("about:blank");
+  auto item0 = std::make_unique<NavigationItemImpl>();
+  item0->SetURL(url0);
+
+  const GURL url1 = test_server_->GetURL("/echo?1");
+  auto item1 = std::make_unique<NavigationItemImpl>();
+  item1->SetURL(url1);
+
+  const GURL url2 = test_server_->GetURL("/echo?2");
+  auto item2 = std::make_unique<NavigationItemImpl>();
+  item2->SetURL(url2);
+
+  __block std::vector<std::unique_ptr<NavigationItem>> restored_items;
+  restored_items.push_back(std::move(item0));
+  restored_items.push_back(std::move(item1));
+  restored_items.push_back(std::move(item2));
+
+  // Initiate session restoration.
+
+  EXPECT_CALL(*decider_, ShouldAllowRequest(_, _)).WillOnce(Return(true));
+  EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
+      .WillOnce(Return(true));
+
+  // Back/forward state changes due to History API calls during session
+  // restoration. Called once each for CanGoBack and CanGoForward.
+  EXPECT_CALL(observer_, DidChangeBackForwardState(web_state())).Times(2);
+
+  // Client-side redirect to restore_session.html?targetUrl=url1.
+  EXPECT_CALL(*decider_,
+              ShouldAllowRequest(URLMatch(CreateRedirectUrl(url1)), _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*decider_, ShouldAllowResponse(URLMatch(CreateRedirectUrl(url1)),
+                                             /*for_main_frame=*/true))
+      .WillOnce(Return(true));
+
+  // Client-side redirect to |url1|.
+  EXPECT_CALL(*decider_, ShouldAllowRequest(URLMatch(url1), _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(observer_, DidStartLoading(web_state()));
+  EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
+  EXPECT_CALL(*decider_,
+              ShouldAllowResponse(URLMatch(url1), /*for_main_frame=*/true))
+      .WillOnce(Return(true));
+  EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
+  EXPECT_CALL(observer_, TitleWasSet(web_state()));
+  EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  EXPECT_CALL(observer_,
+              PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
+
+  ASSERT_TRUE(ExecuteBlockAndWaitForLoad(url1, ^{
+    navigation_manager()->Restore(/*last_committed_item_index=*/1,
+                                  std::move(restored_items));
+  }));
+  ASSERT_EQ(url1, navigation_manager()->GetLastCommittedItem()->GetURL());
+  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
+  ASSERT_EQ(3, navigation_manager()->GetItemCount());
+  ASSERT_TRUE(navigation_manager()->CanGoBack());
+  ASSERT_TRUE(navigation_manager()->CanGoForward());
+
+  // Go back to |item0|.
+
+  EXPECT_CALL(observer_, DidStartLoading(web_state()));
+  // Only CanGoBackward changes state on this navigation.
+  EXPECT_CALL(observer_, DidChangeBackForwardState(web_state())).Times(1);
+
+  // Load restore_session.html?targetUrl=url0.
+  EXPECT_CALL(*decider_,
+              ShouldAllowRequest(URLMatch(CreateRedirectUrl(url0)), _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*decider_, ShouldAllowResponse(URLMatch(CreateRedirectUrl(url0)),
+                                             /*for_main_frame=*/true))
+      .WillOnce(Return(true));
+
+  // decide policy for restore_session.html?targetUrl=url0 again due to reload
+  // in onpopstate().
+  EXPECT_CALL(*decider_,
+              ShouldAllowRequest(URLMatch(CreateRedirectUrl(url0)), _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*decider_, ShouldAllowResponse(URLMatch(CreateRedirectUrl(url0)),
+                                             /*for_main_frame=*/true))
+      .WillOnce(Return(true));
+
+  // Client-side redirect to |url0|.
+  EXPECT_CALL(*decider_, ShouldAllowRequest(URLMatch(url0), _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
+  // No ShouldAllowResponse call because about:blank has no response.
+  EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _));
+  EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  EXPECT_CALL(observer_,
+              PageLoaded(web_state(), PageLoadCompletionStatus::SUCCESS));
+
+  ASSERT_TRUE(ExecuteBlockAndWaitForLoad(url0, ^{
+    navigation_manager()->GoBack();
+  }));
+  ASSERT_EQ(url0, navigation_manager()->GetLastCommittedItem()->GetURL());
+  EXPECT_EQ(0, navigation_manager()->GetLastCommittedItemIndex());
+  ASSERT_TRUE(navigation_manager()->CanGoForward());
+  ASSERT_FALSE(navigation_manager()->CanGoBack());
+}
+
 INSTANTIATE_TEST_CASE_P(
     ProgrammaticWebStateObserverTest,
     WebStateObserverTest,
diff --git a/media/gpu/video_decode_accelerator_unittest.cc b/media/gpu/video_decode_accelerator_unittest.cc
index d338d7d..3aa9e4d 100644
--- a/media/gpu/video_decode_accelerator_unittest.cc
+++ b/media/gpu/video_decode_accelerator_unittest.cc
@@ -1700,16 +1700,21 @@
  public:
   VDATestSuite(int argc, char** argv) : base::TestSuite(argc, argv) {}
 
-  int Run() {
+ private:
+  void Initialize() override {
+    base::TestSuite::Initialize();
+
 #if defined(OS_WIN) || defined(OS_CHROMEOS)
     // For windows the decoding thread initializes the media foundation decoder
     // which uses COM. We need the thread to be a UI thread.
     // On Ozone, the backend initializes the event system using a UI
     // thread.
-    base::test::ScopedTaskEnvironment scoped_task_environment(
-        base::test::ScopedTaskEnvironment::MainThreadType::UI);
+    scoped_task_environment_ =
+        std::make_unique<base::test::ScopedTaskEnvironment>(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI);
 #else
-    base::test::ScopedTaskEnvironment scoped_task_environment;
+    scoped_task_environment_ =
+        std::make_unique<base::test::ScopedTaskEnvironment>();
 #endif  // OS_WIN || OS_CHROMEOS
 
     media::g_env =
@@ -1729,8 +1734,14 @@
 #elif defined(OS_WIN)
     media::DXVAVideoDecodeAccelerator::PreSandboxInitialization();
 #endif
-    return base::TestSuite::Run();
   }
+
+  void Shutdown() override {
+    scoped_task_environment_.reset();
+    base::TestSuite::Shutdown();
+  }
+
+  std::unique_ptr<base::test::ScopedTaskEnvironment> scoped_task_environment_;
 };
 
 }  // namespace
diff --git a/net/disk_cache/simple/simple_entry_impl.cc b/net/disk_cache/simple/simple_entry_impl.cc
index e5096b4..ec225b0f 100644
--- a/net/disk_cache/simple/simple_entry_impl.cc
+++ b/net/disk_cache/simple/simple_entry_impl.cc
@@ -16,6 +16,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
 #include "base/task_runner.h"
 #include "base/task_runner_util.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -166,13 +167,17 @@
                                       net::NetLogSourceType::DISK_CACHE_ENTRY)),
       stream_0_data_(base::MakeRefCounted<net::GrowableIOBuffer>()),
       entry_priority_(entry_priority) {
-  static_assert(arraysize(data_size_) == arraysize(crc32s_end_offset_),
+  static_assert(std::extent<decltype(data_size_)>() ==
+                    std::extent<decltype(crc32s_end_offset_)>(),
                 "arrays should be the same size");
-  static_assert(arraysize(data_size_) == arraysize(crc32s_),
+  static_assert(
+      std::extent<decltype(data_size_)>() == std::extent<decltype(crc32s_)>(),
+      "arrays should be the same size");
+  static_assert(std::extent<decltype(data_size_)>() ==
+                    std::extent<decltype(have_written_)>(),
                 "arrays should be the same size");
-  static_assert(arraysize(data_size_) == arraysize(have_written_),
-                "arrays should be the same size");
-  static_assert(arraysize(data_size_) == arraysize(crc_check_state_),
+  static_assert(std::extent<decltype(data_size_)>() ==
+                    std::extent<decltype(crc_check_state_)>(),
                 "arrays should be the same size");
   ResetEntry();
   net_log_.BeginEvent(net::NetLogEventType::SIMPLE_CACHE_ENTRY,
@@ -614,7 +619,7 @@
   std::memset(crc32s_, 0, sizeof(crc32s_));
   std::memset(have_written_, 0, sizeof(have_written_));
   std::memset(data_size_, 0, sizeof(data_size_));
-  for (size_t i = 0; i < arraysize(crc_check_state_); ++i) {
+  for (size_t i = 0; i < base::size(crc_check_state_); ++i) {
     crc_check_state_[i] = CRC_CHECK_NEVER_READ_AT_ALL;
   }
 }
diff --git a/net/dns/dns_config_service_posix.cc b/net/dns/dns_config_service_posix.cc
index c51a632..31c564fa 100644
--- a/net/dns/dns_config_service_posix.cc
+++ b/net/dns/dns_config_service_posix.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <string>
+#include <type_traits>
 
 #include "base/bind.h"
 #include "base/files/file.h"
@@ -465,8 +466,8 @@
     dns_config->nameservers.push_back(ipe);
   }
 #elif defined(OS_LINUX)
-  static_assert(arraysize(res.nsaddr_list) >= MAXNS &&
-                    arraysize(res._u._ext.nsaddrs) >= MAXNS,
+  static_assert(std::extent<decltype(res.nsaddr_list)>() >= MAXNS &&
+                    std::extent<decltype(res._u._ext.nsaddrs)>() >= MAXNS,
                 "incompatible libresolv res_state");
   DCHECK_LE(res.nscount, MAXNS);
   // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
diff --git a/net/third_party/quic/test_tools/quic_stream_id_manager_peer.h b/net/third_party/quic/test_tools/quic_stream_id_manager_peer.h
index 9888cb4..2ceec2a 100644
--- a/net/third_party/quic/test_tools/quic_stream_id_manager_peer.h
+++ b/net/third_party/quic/test_tools/quic_stream_id_manager_peer.h
@@ -4,7 +4,7 @@
 #ifndef NET_THIRD_PARTY_QUIC_TEST_TOOLS_QUIC_STREAM_ID_MANAGER_PEER_H_
 #define NET_THIRD_PARTY_QUIC_TEST_TOOLS_QUIC_STREAM_ID_MANAGER_PEER_H_
 
-#include "base/macros.h"
+#include <stddef.h>
 
 namespace quic {
 
diff --git a/net/traffic_annotation/network_traffic_annotation.h b/net/traffic_annotation/network_traffic_annotation.h
index 796bc038..4a67c9b 100644
--- a/net/traffic_annotation/network_traffic_annotation.h
+++ b/net/traffic_annotation/network_traffic_annotation.h
@@ -153,7 +153,7 @@
     const char (&proto)[N3]) {
 #if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
   DCHECK(partial_annotation.completing_id_hash_code ==
-             COMPUTE_NETWORK_TRAFFIC_ANNOTATION_ID_HASH(unique_id) ||
+             COMPUTE_NETWORK_TRAFFIC_ANNOTATION_ID_HASH(group_id) ||
          partial_annotation.unique_id_hash_code ==
              COMPUTE_NETWORK_TRAFFIC_ANNOTATION_ID_HASH("test_partial") ||
          partial_annotation.unique_id_hash_code ==
diff --git a/services/media_session/audio_focus_manager.cc b/services/media_session/audio_focus_manager.cc
index c775811..85e95e74 100644
--- a/services/media_session/audio_focus_manager.cc
+++ b/services/media_session/audio_focus_manager.cc
@@ -17,6 +17,16 @@
 
 namespace media_session {
 
+namespace {
+
+mojom::EnforcementMode GetDefaultEnforcementMode() {
+  if (IsAudioFocusEnabled() && IsAudioFocusEnforcementEnabled())
+    return mojom::EnforcementMode::kSingleGroup;
+  return mojom::EnforcementMode::kNone;
+}
+
+}  // namespace
+
 class AudioFocusManager::StackRow : public mojom::AudioFocusRequestClient {
  public:
   StackRow(AudioFocusManager* owner,
@@ -128,6 +138,22 @@
     controller_->BindToInterface(std::move(request));
   }
 
+  void Suspend(bool transient) {
+    DCHECK(!session_info_->force_duck);
+    transient_suspend_ = transient_suspend_ || transient;
+    session_->Suspend(mojom::MediaSession::SuspendType::kSystem);
+  }
+
+  void MaybeResume() {
+    DCHECK(!session_info_->force_duck);
+
+    if (!transient_suspend_)
+      return;
+
+    transient_suspend_ = false;
+    session_->Resume(mojom::MediaSession::SuspendType::kSystem);
+  }
+
  private:
   void SetSessionInfo(mojom::MediaSessionInfoPtr session_info) {
     bool is_controllable_changed =
@@ -162,6 +188,7 @@
 
   AudioFocusManagerMetricsHelper metrics_helper_;
   bool encountered_error_ = false;
+  bool transient_suspend_ = false;
 
   std::unique_ptr<MediaController> controller_;
 
@@ -261,9 +288,7 @@
     return;
   }
 
-  if (IsAudioFocusEnforcementEnabled())
-    EnforceAudioFocusAbandon(row->audio_focus_type());
-
+  EnforceAudioFocusAbandon();
   MaybeUpdateActiveSession();
 
   // Notify observers that we lost audio focus.
@@ -288,6 +313,21 @@
   bindings_.dispatch_context()->source_name = name;
 }
 
+void AudioFocusManager::SetEnforcementMode(mojom::EnforcementMode mode) {
+  if (mode == mojom::EnforcementMode::kDefault)
+    mode = GetDefaultEnforcementMode();
+
+  if (mode == enforcement_mode_)
+    return;
+
+  enforcement_mode_ = mode;
+
+  if (audio_focus_stack_.empty())
+    return;
+
+  EnforceAudioFocus();
+}
+
 void AudioFocusManager::CreateActiveMediaController(
     mojom::MediaControllerRequest request) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -329,16 +369,10 @@
     std::unique_ptr<StackRow> row,
     mojom::AudioFocusType type,
     base::OnceCallback<void()> callback) {
-  // If audio focus is enabled then we should enforce this request and make sure
-  // the new active session is not ducking.
-  if (IsAudioFocusEnforcementEnabled()) {
-    EnforceAudioFocusRequest(type, row->group_id());
-    row->session()->StopDucking();
-  }
-
   row->SetAudioFocusType(type);
   audio_focus_stack_.push_back(std::move(row));
 
+  EnforceAudioFocus();
   MaybeUpdateActiveSession();
 
   // Notify observers that we were gained audio focus.
@@ -353,87 +387,49 @@
   std::move(callback).Run();
 }
 
-void AudioFocusManager::EnforceAudioFocusRequest(
-    mojom::AudioFocusType type,
-    const base::UnguessableToken& group_id) {
-  DCHECK(IsAudioFocusEnforcementEnabled());
-
-  for (auto& old_session : audio_focus_stack_) {
-    // If the session has the force duck flag set then we should always duck it.
-    if (old_session->info()->force_duck) {
-      old_session->session()->StartDucking();
-      continue;
-    }
-
-    switch (type) {
-      case mojom::AudioFocusType::kGain:
-      case mojom::AudioFocusType::kGainTransient:
-        // If the session has the same group id as the new session then we
-        // should not suspend that session.
-        if (old_session->group_id() == group_id)
-          break;
-
-        old_session->session()->Suspend(
-            mojom::MediaSession::SuspendType::kSystem);
-        break;
-      case mojom::AudioFocusType::kGainTransientMayDuck:
-        old_session->session()->StartDucking();
-        break;
-    }
-  }
-}
-
-void AudioFocusManager::EnforceAudioFocusAbandon(mojom::AudioFocusType type) {
-  DCHECK(IsAudioFocusEnforcementEnabled());
-
+void AudioFocusManager::EnforceAudioFocusAbandon() {
   // Allow the top-most MediaSession having force duck to unduck even if
   // it is not active.
-  for (auto iter = audio_focus_stack_.rbegin();
-       iter != audio_focus_stack_.rend(); ++iter) {
-    if (!(*iter)->info()->force_duck)
-      continue;
+  if (enforcement_mode_ != mojom::EnforcementMode::kNone) {
+    for (auto iter = audio_focus_stack_.rbegin();
+         iter != audio_focus_stack_.rend(); ++iter) {
+      if (!(*iter)->info()->force_duck)
+        continue;
 
-    // TODO(beccahughes): Replace with std::rotate.
-    auto duck_row = std::move(*iter);
-    duck_row->session()->StopDucking();
-    audio_focus_stack_.erase(std::next(iter).base());
-    audio_focus_stack_.push_back(std::move(duck_row));
-    return;
+      // TODO(beccahughes): Replace with std::rotate.
+      auto duck_row = std::move(*iter);
+      duck_row->session()->StopDucking();
+      audio_focus_stack_.erase(std::next(iter).base());
+      audio_focus_stack_.push_back(std::move(duck_row));
+      return;
+    }
   }
 
-  DCHECK(!audio_focus_stack_.empty());
-  StackRow* top = audio_focus_stack_.back().get();
+  EnforceAudioFocus();
+}
 
-  switch (type) {
-    case mojom::AudioFocusType::kGain:
-      // Do nothing. The abandoned session suspended all the media sessions and
-      // they should stay suspended to avoid surprising the user.
-      break;
-    case mojom::AudioFocusType::kGainTransient:
-      // The abandoned session suspended all the media sessions but we should
-      // start playing the top one again as the abandoned media was transient.
-      // This will also apply to any sessions that have the same group_id as the
-      // new top most session.
-      for (auto& session : audio_focus_stack_) {
-        if (session->group_id() != top->group_id())
-          continue;
+void AudioFocusManager::EnforceAudioFocus() {
+  DCHECK_NE(mojom::EnforcementMode::kDefault, enforcement_mode_);
+  if (audio_focus_stack_.empty())
+    return;
 
-        session->session()->Resume(mojom::MediaSession::SuspendType::kSystem);
-      }
-      break;
-    case mojom::AudioFocusType::kGainTransientMayDuck:
-      // The abandoned session ducked all the media sessions so we should unduck
-      // them. If they are not playing then they will not resume.
-      for (auto& session : base::Reversed(audio_focus_stack_)) {
-        session->session()->StopDucking();
+  EnforcementState state;
 
-        // If the new session is ducking then we should continue ducking all but
-        // the new session.
-        if (top->audio_focus_type() ==
-            mojom::AudioFocusType::kGainTransientMayDuck)
-          break;
-      }
-      break;
+  for (auto& session : base::Reversed(audio_focus_stack_)) {
+    EnforceSingleSession(session.get(), state);
+
+    // Update the flags based on the audio focus type of this session.
+    switch (session->audio_focus_type()) {
+      case mojom::AudioFocusType::kGain:
+        state.should_suspend = true;
+        break;
+      case mojom::AudioFocusType::kGainTransient:
+        state.should_transient_suspend = true;
+        break;
+      case mojom::AudioFocusType::kGainTransientMayDuck:
+        state.should_duck = true;
+        break;
+    }
   }
 }
 
@@ -462,7 +458,8 @@
   });
 }
 
-AudioFocusManager::AudioFocusManager() {
+AudioFocusManager::AudioFocusManager()
+    : enforcement_mode_(GetDefaultEnforcementMode()) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 }
 
@@ -496,4 +493,60 @@
          audio_focus_stack_.back()->audio_focus_type() == type;
 }
 
+bool AudioFocusManager::ShouldSessionBeSuspended(
+    const StackRow* session,
+    const EnforcementState& state) const {
+  bool should_suspend_any =
+      state.should_suspend || state.should_transient_suspend;
+
+  switch (enforcement_mode_) {
+    case mojom::EnforcementMode::kSingleSession:
+      return should_suspend_any;
+    case mojom::EnforcementMode::kSingleGroup:
+      return should_suspend_any &&
+             session->group_id() != audio_focus_stack_.back()->group_id();
+    case mojom::EnforcementMode::kNone:
+      return false;
+    case mojom::EnforcementMode::kDefault:
+      NOTIMPLEMENTED();
+      return false;
+  }
+}
+
+bool AudioFocusManager::ShouldSessionBeDucked(
+    const StackRow* session,
+    const EnforcementState& state) const {
+  switch (enforcement_mode_) {
+    case mojom::EnforcementMode::kSingleSession:
+    case mojom::EnforcementMode::kSingleGroup:
+      if (session->info()->force_duck)
+        return state.should_duck || ShouldSessionBeSuspended(session, state);
+      return state.should_duck;
+    case mojom::EnforcementMode::kNone:
+      return false;
+    case mojom::EnforcementMode::kDefault:
+      NOTIMPLEMENTED();
+      return false;
+  }
+}
+
+void AudioFocusManager::EnforceSingleSession(StackRow* session,
+                                             const EnforcementState& state) {
+  if (ShouldSessionBeDucked(session, state)) {
+    session->session()->StartDucking();
+  } else {
+    session->session()->StopDucking();
+  }
+
+  // If the session wants to be ducked instead of suspended we should stop now.
+  if (session->info()->force_duck)
+    return;
+
+  if (ShouldSessionBeSuspended(session, state)) {
+    session->Suspend(state.should_transient_suspend);
+  } else {
+    session->MaybeResume();
+  }
+}
+
 }  // namespace media_session
diff --git a/services/media_session/audio_focus_manager.h b/services/media_session/audio_focus_manager.h
index 810f5fd..04cbfed 100644
--- a/services/media_session/audio_focus_manager.h
+++ b/services/media_session/audio_focus_manager.h
@@ -55,6 +55,7 @@
   void GetFocusRequests(GetFocusRequestsCallback callback) override;
   void AddObserver(mojom::AudioFocusObserverPtr observer) override;
   void SetSourceName(const std::string& name) override;
+  void SetEnforcementMode(mojom::EnforcementMode mode) override;
 
   // mojom::AudioFocusManagerDebug.
   void GetDebugInfoForRequest(const RequestId& request_id,
@@ -95,14 +96,19 @@
     std::string source_name;
   };
 
+  struct EnforcementState {
+    bool should_duck = false;
+    bool should_suspend = false;
+    bool should_transient_suspend = false;
+  };
+
   void RequestAudioFocusInternal(std::unique_ptr<StackRow>,
                                  mojom::AudioFocusType,
                                  base::OnceCallback<void()>);
-  void EnforceAudioFocusRequest(mojom::AudioFocusType type,
-                                const base::UnguessableToken& group_id);
-
   void AbandonAudioFocusInternal(RequestId);
-  void EnforceAudioFocusAbandon(mojom::AudioFocusType);
+
+  void EnforceAudioFocusAbandon();
+  void EnforceAudioFocus();
 
   void MaybeUpdateActiveSession();
 
@@ -115,6 +121,13 @@
   bool IsSessionOnTopOfAudioFocusStack(RequestId id,
                                        mojom::AudioFocusType type) const;
 
+  bool ShouldSessionBeSuspended(const StackRow* session,
+                                const EnforcementState& state) const;
+  bool ShouldSessionBeDucked(const StackRow* session,
+                             const EnforcementState& state) const;
+
+  void EnforceSingleSession(StackRow* session, const EnforcementState& state);
+
   // This |MediaController| acts as a proxy for controlling the active
   // |MediaSession| over mojo.
   MediaController active_media_controller_;
@@ -137,6 +150,8 @@
   // A MediaSession must abandon audio focus before its destruction.
   std::list<std::unique_ptr<StackRow>> audio_focus_stack_;
 
+  mojom::EnforcementMode enforcement_mode_;
+
   // Adding observers should happen on the same thread that the service is
   // running on.
   THREAD_CHECKER(thread_checker_);
diff --git a/services/media_session/audio_focus_manager_unittest.cc b/services/media_session/audio_focus_manager_unittest.cc
index 1ea3fc4..bae9edd 100644
--- a/services/media_session/audio_focus_manager_unittest.cc
+++ b/services/media_session/audio_focus_manager_unittest.cc
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "base/callback.h"
-#include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_command_line.h"
@@ -18,7 +17,6 @@
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/media_session/audio_focus_manager_metrics_helper.h"
 #include "services/media_session/media_session_service.h"
-#include "services/media_session/public/cpp/switches.h"
 #include "services/media_session/public/cpp/test/audio_focus_test_util.h"
 #include "services/media_session/public/cpp/test/mock_media_session.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
@@ -38,18 +36,12 @@
 // This tests the Audio Focus Manager API. The parameter determines whether
 // audio focus is enabled or not. If it is not enabled it should track the media
 // sessions but not enforce single session focus.
-class AudioFocusManagerTest : public testing::TestWithParam<bool> {
+class AudioFocusManagerTest
+    : public testing::TestWithParam<mojom::EnforcementMode> {
  public:
   AudioFocusManagerTest() = default;
 
   void SetUp() override {
-    if (!GetParam()) {
-      command_line_.GetProcessCommandLine()->AppendSwitchASCII(
-          switches::kEnableAudioFocus, switches::kEnableAudioFocusNoEnforce);
-    }
-
-    ASSERT_EQ(GetParam(), IsAudioFocusEnforcementEnabled());
-
     // Create an instance of the MediaSessionService.
     service_ = std::make_unique<MediaSessionService>(
         connector_factory_.RegisterInstance(mojom::kServiceName));
@@ -57,6 +49,9 @@
                                                             &audio_focus_ptr_);
     connector_factory_.GetDefaultConnector()->BindInterface(
         mojom::kServiceName, &audio_focus_debug_ptr_);
+
+    audio_focus_ptr_->SetEnforcementMode(GetParam());
+    audio_focus_ptr_.FlushForTesting();
   }
 
   void TearDown() override {
@@ -126,7 +121,7 @@
       test::MockMediaSession* session) {
     mojom::MediaSessionInfo::SessionState state = session->GetState();
 
-    if (!GetParam()) {
+    if (!IsEnforcementEnabled()) {
       // If audio focus enforcement is disabled then we should never see these
       // states in the tests.
       EXPECT_NE(mojom::MediaSessionInfo::SessionState::kSuspended, state);
@@ -152,7 +147,7 @@
       mojom::MediaSessionInfo::SessionState state) {
     // If enforcement is enabled then returns the provided state, otherwise
     // returns kActive because without enforcement we did not change state.
-    if (GetParam())
+    if (IsEnforcementEnabled())
       return state;
     return mojom::MediaSessionInfo::SessionState::kActive;
   }
@@ -186,6 +181,15 @@
         .size();
   }
 
+  bool IsEnforcementEnabled() const {
+    return GetParam() == mojom::EnforcementMode::kSingleSession ||
+           GetParam() == mojom::EnforcementMode::kSingleGroup;
+  }
+
+  bool IsGroupingEnabled() const {
+    return GetParam() != mojom::EnforcementMode::kSingleSession;
+  }
+
  private:
   int GetCountForType(mojom::AudioFocusType type) {
     const auto audio_focus_requests = GetRequests();
@@ -220,7 +224,7 @@
   }
 
   void FlushForTestingIfEnabled() {
-    if (!GetParam())
+    if (!IsEnforcementEnabled())
       return;
 
     audio_focus_ptr_.FlushForTesting();
@@ -239,7 +243,13 @@
   DISALLOW_COPY_AND_ASSIGN(AudioFocusManagerTest);
 };
 
-INSTANTIATE_TEST_CASE_P(, AudioFocusManagerTest, testing::Bool());
+INSTANTIATE_TEST_CASE_P(
+    ,
+    AudioFocusManagerTest,
+    testing::Values(mojom::EnforcementMode::kDefault,
+                    mojom::EnforcementMode::kNone,
+                    mojom::EnforcementMode::kSingleGroup,
+                    mojom::EnforcementMode::kSingleSession));
 
 TEST_P(AudioFocusManagerTest, RequestAudioFocusGain_ReplaceFocusedEntry) {
   test::MockMediaSession media_session_1;
@@ -683,7 +693,8 @@
             GetState(&media_session_1));
 
   media_session_3.AbandonAudioFocusFromClient();
-  EXPECT_EQ(GetParam() ? request_id_1 : request_id_2, GetAudioFocusedSession());
+  EXPECT_EQ(IsEnforcementEnabled() ? request_id_1 : request_id_2,
+            GetAudioFocusedSession());
 }
 
 TEST_P(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) {
@@ -1105,7 +1116,9 @@
 
   media_session_4.AbandonAudioFocusFromClient();
 
-  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+  EXPECT_EQ(IsGroupingEnabled()
+                ? mojom::MediaSessionInfo::SessionState::kActive
+                : mojom::MediaSessionInfo::SessionState::kSuspended,
             GetState(&media_session_1));
   EXPECT_EQ(
       GetStateFromParam(mojom::MediaSessionInfo::SessionState::kSuspended),
@@ -1127,7 +1140,9 @@
 
   RequestGroupedAudioFocus(&media_session_2, mojom::AudioFocusType::kGain,
                            group_id);
-  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+  EXPECT_EQ(IsGroupingEnabled()
+                ? mojom::MediaSessionInfo::SessionState::kActive
+                : mojom::MediaSessionInfo::SessionState::kSuspended,
             GetState(&media_session_1));
   EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
             GetState(&media_session_2));
@@ -1163,13 +1178,12 @@
 
   RequestGroupedAudioFocus(&media_session_2,
                            mojom::AudioFocusType::kGainTransient, group_id);
-  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+  EXPECT_EQ(IsGroupingEnabled()
+                ? mojom::MediaSessionInfo::SessionState::kActive
+                : mojom::MediaSessionInfo::SessionState::kSuspended,
             GetState(&media_session_1));
   EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
             GetState(&media_session_2));
 }
 
-// TODO: Fix //content
-// TODO: Fix //ash
-
 }  // namespace media_session
diff --git a/services/media_session/media_controller_unittest.cc b/services/media_session/media_controller_unittest.cc
index c92e5c9..16a15db8 100644
--- a/services/media_session/media_controller_unittest.cc
+++ b/services/media_session/media_controller_unittest.cc
@@ -38,6 +38,10 @@
     controller_manager_ptr_->CreateActiveMediaController(
         mojo::MakeRequest(&media_controller_ptr_));
     controller_manager_ptr_.FlushForTesting();
+
+    audio_focus_ptr_->SetEnforcementMode(
+        mojom::EnforcementMode::kSingleSession);
+    audio_focus_ptr_.FlushForTesting();
   }
 
   void TearDown() override {
diff --git a/services/media_session/public/mojom/audio_focus.mojom b/services/media_session/public/mojom/audio_focus.mojom
index ead6a4c4..8c64774 100644
--- a/services/media_session/public/mojom/audio_focus.mojom
+++ b/services/media_session/public/mojom/audio_focus.mojom
@@ -7,7 +7,29 @@
 import "mojo/public/mojom/base/unguessable_token.mojom";
 import "services/media_session/public/mojom/media_session.mojom";
 
-// Next MinVersion: 5
+// Next MinVersion: 6
+
+// These are the different modes the AudioFocusManager can enforce audio focus.
+[Extensible]
+enum EnforcementMode {
+  // This will default to whatever enforcement mode is configured through
+  // feature flags.
+  kDefault,
+
+  // This will allow all sessions to play, without the enforcement of a single
+  // media session.
+  kNone,
+
+  // This will enforce that all media sessions playing have the same audio
+  // focus group id. If a session gains focus then all other sessions with
+  // a different group id will be ducked/paused. Any session with the same
+  // group id will be ignored.
+  kSingleGroup,
+
+  // This will enforce that only one media session can be playing at any
+  // one time.
+  kSingleSession,
+};
 
 // These are the different types of audio focus that can be requested.
 [Extensible]
@@ -72,7 +94,7 @@
 };
 
 // Controls audio focus across the entire system.
-// Next Method ID: 5
+// Next Method ID: 6
 interface AudioFocusManager {
   // Requests audio focus with |type| for the |media_session| with
   // |session_info|. Media sessions should provide a |request| that will
@@ -105,6 +127,9 @@
   // associating metrics to a source. If the source name is updated then
   // the audio focus requests will retain the previous source name.
   [MinVersion=2] SetSourceName@3(string name);
+
+  // Sets the enforcement mode for the Audio Focus Manager.
+  [MinVersion=5] SetEnforcementMode@5(EnforcementMode mode);
 };
 
 // Provides debug information about audio focus requests.
diff --git a/services/network/cross_origin_read_blocking.cc b/services/network/cross_origin_read_blocking.cc
index ee5f6e69e..25e6bf9b 100644
--- a/services/network/cross_origin_read_blocking.cc
+++ b/services/network/cross_origin_read_blocking.cc
@@ -565,11 +565,11 @@
 CrossOriginReadBlocking::ResponseAnalyzer::ResponseAnalyzer(
     const net::URLRequest& request,
     const ResourceResponse& response,
-    InitiatorLockCompatibility initiator_compatibility) {
+    base::Optional<url::Origin> request_initiator_site_lock) {
   content_length_ = response.head.content_length;
   http_response_code_ =
       response.head.headers ? response.head.headers->response_code() : 0;
-  initiator_compatibility_ = initiator_compatibility;
+  request_initiator_site_lock_ = request_initiator_site_lock;
 
   should_block_based_on_headers_ = ShouldBlockBasedOnHeaders(request, response);
   if (should_block_based_on_headers_ == kNeedToSniffMore)
@@ -587,9 +587,20 @@
   // that are inexpensive should be near the top.
   url::Origin target_origin = url::Origin::Create(request.url());
 
+  // Check if |target_origin| seems to match the factory lock in
+  // |request_initiator_site_lock_|.  If so, then treat this request as
+  // same-origin (even if |request.initiator()| might be cross-origin).  See
+  // also https://crbug.com/918660.
+  if (VerifyRequestInitiatorLock(request_initiator_site_lock_, target_origin) ==
+      InitiatorLockCompatibility::kCompatibleLock) {
+    return kAllow;
+  }
+
   // Treat a missing initiator as an empty origin to be safe, though we don't
   // expect this to happen.  Unfortunately, this requires a copy.
   url::Origin initiator;
+  initiator_compatibility_ = VerifyRequestInitiatorLock(
+      request_initiator_site_lock_, request.initiator());
   bool block_untrustworthy_initiator =
       ShouldEnforceInitiatorLock() &&
       initiator_compatibility_ == InitiatorLockCompatibility::kIncorrectLock;
diff --git a/services/network/cross_origin_read_blocking.h b/services/network/cross_origin_read_blocking.h
index 5103b17..6caca3c 100644
--- a/services/network/cross_origin_read_blocking.h
+++ b/services/network/cross_origin_read_blocking.h
@@ -69,7 +69,7 @@
     // ResponseAnalyzer will decide whether |response| needs to be blocked.
     ResponseAnalyzer(const net::URLRequest& request,
                      const ResourceResponse& response,
-                     InitiatorLockCompatibility initiator_compatibility);
+                     base::Optional<url::Origin> request_initiator_site_lock);
 
     ~ResponseAnalyzer();
 
@@ -163,6 +163,9 @@
     InitiatorLockCompatibility initiator_compatibility_ =
         InitiatorLockCompatibility::kIncorrectLock;
 
+    // Propagated from URLLoaderFactoryParams::request_initiator_site_lock;
+    base::Optional<url::Origin> request_initiator_site_lock_;
+
     // The sniffers to be used.
     std::vector<std::unique_ptr<ConfirmationSniffer>> sniffers_;
 
diff --git a/services/network/initiator_lock_compatibility.cc b/services/network/initiator_lock_compatibility.cc
index a352cad..7ca032b 100644
--- a/services/network/initiator_lock_compatibility.cc
+++ b/services/network/initiator_lock_compatibility.cc
@@ -4,8 +4,10 @@
 
 #include "services/network/initiator_lock_compatibility.h"
 
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "url/gurl.h"
@@ -15,20 +17,19 @@
 namespace network {
 
 InitiatorLockCompatibility VerifyRequestInitiatorLock(
-    const mojom::URLLoaderFactoryParams& factory_params,
-    const ResourceRequest& request) {
-  if (factory_params.process_id == mojom::kBrowserProcessId)
-    return InitiatorLockCompatibility::kBrowserProcess;
-
-  if (!factory_params.request_initiator_site_lock.has_value())
+    const base::Optional<url::Origin>& request_initiator_site_lock,
+    const base::Optional<url::Origin>& request_initiator) {
+  if (!request_initiator_site_lock.has_value())
     return InitiatorLockCompatibility::kNoLock;
-  const url::Origin& lock = factory_params.request_initiator_site_lock.value();
+  const url::Origin& lock = request_initiator_site_lock.value();
 
-  if (!request.request_initiator.has_value()) {
-    NOTREACHED();  // Should only happen for the browser process.
+  if (!request_initiator.has_value()) {
+    // This should only happen for the browser process (or if NetworkService is
+    // not enabled).
+    DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
     return InitiatorLockCompatibility::kNoInitiator;
   }
-  const url::Origin& initiator = request.request_initiator.value();
+  const url::Origin& initiator = request_initiator.value();
 
   // TODO(lukasza, nasko): Also consider equality of precursor origins (e.g. if
   // |initiator| is opaque, then it's precursor origin should match the |lock|
@@ -57,4 +58,14 @@
   return InitiatorLockCompatibility::kIncorrectLock;
 }
 
+InitiatorLockCompatibility VerifyRequestInitiatorLock(
+    const mojom::URLLoaderFactoryParams& factory_params,
+    const ResourceRequest& request) {
+  if (factory_params.process_id == mojom::kBrowserProcessId)
+    return InitiatorLockCompatibility::kBrowserProcess;
+
+  return VerifyRequestInitiatorLock(factory_params.request_initiator_site_lock,
+                                    request.request_initiator);
+}
+
 }  // namespace network
diff --git a/services/network/initiator_lock_compatibility.h b/services/network/initiator_lock_compatibility.h
index 5e1dba7..6b18910 100644
--- a/services/network/initiator_lock_compatibility.h
+++ b/services/network/initiator_lock_compatibility.h
@@ -6,6 +6,8 @@
 #define SERVICES_NETWORK_INITIATOR_LOCK_COMPATIBILITY_H_
 
 #include "base/component_export.h"
+#include "base/optional.h"
+#include "url/origin.h"
 
 namespace network {
 
@@ -52,6 +54,10 @@
 InitiatorLockCompatibility VerifyRequestInitiatorLock(
     const mojom::URLLoaderFactoryParams& factory_params,
     const ResourceRequest& request);
+COMPONENT_EXPORT(NETWORK_SERVICE)
+InitiatorLockCompatibility VerifyRequestInitiatorLock(
+    const base::Optional<url::Origin>& request_initiator_site_lock,
+    const base::Optional<url::Origin>& request_initiator);
 
 }  // namespace network
 
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index e4cf802..d22a74c 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -22,6 +22,7 @@
 #include "mojo/public/cpp/bindings/type_converter.h"
 #include "net/base/logging_network_change_observer.h"
 #include "net/base/network_change_notifier.h"
+#include "net/base/port_util.h"
 #include "net/cert/cert_database.h"
 #include "net/cert/ct_log_response_parser.h"
 #include "net/cert/signed_tree_head.h"
@@ -177,6 +178,13 @@
 
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
 
+  // Set-up the global port overrides.
+  if (command_line->HasSwitch(switches::kExplicitlyAllowedPorts)) {
+    std::string allowed_ports =
+        command_line->GetSwitchValueASCII(switches::kExplicitlyAllowedPorts);
+    net::SetExplicitlyAllowedPorts(allowed_ports);
+  }
+
   // Record this once per session, though the switch is appled on a
   // per-NetworkContext basis.
   UMA_HISTOGRAM_BOOLEAN(
diff --git a/services/network/public/cpp/network_switches.cc b/services/network/public/cpp/network_switches.cc
index 8b2d4ed..60859bc4 100644
--- a/services/network/public/cpp/network_switches.cc
+++ b/services/network/public/cpp/network_switches.cc
@@ -49,6 +49,10 @@
 // Don't send HTTP-Referer headers.
 const char kNoReferrers[] = "no-referrers";
 
+// Allows overriding the list of restricted ports by passing a comma-separated
+// list of port numbers.
+const char kExplicitlyAllowedPorts[] = "explicitly-allowed-ports";
+
 }  // namespace switches
 
 }  // namespace network
diff --git a/services/network/public/cpp/network_switches.h b/services/network/public/cpp/network_switches.h
index 83e78d0..db64e5a 100644
--- a/services/network/public/cpp/network_switches.h
+++ b/services/network/public/cpp/network_switches.h
@@ -20,6 +20,7 @@
 COMPONENT_EXPORT(NETWORK_CPP) extern const char kLogNetLog[];
 COMPONENT_EXPORT(NETWORK_CPP) extern const char kSSLKeyLogFile[];
 COMPONENT_EXPORT(NETWORK_CPP) extern const char kNoReferrers[];
+COMPONENT_EXPORT(NETWORK_CPP) extern const char kExplicitlyAllowedPorts[];
 
 }  // namespace switches
 
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index a4159d9..3b6253d4 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -401,11 +401,9 @@
   throttling_token_ = network::ScopedThrottlingToken::MaybeCreate(
       url_request_->net_log().source().id, request.throttling_profile_id);
 
-  initiator_lock_compatibility_ =
-      VerifyRequestInitiatorLock(*factory_params_, request);
   UMA_HISTOGRAM_ENUMERATION(
       "NetworkService.URLLoader.RequestInitiatorOriginLockCompatibility",
-      initiator_lock_compatibility_);
+      VerifyRequestInitiatorLock(*factory_params_, request));
   // TODO(lukasza): Enforce the origin lock.
   // - https://crbug.com/766694: In the long-term kIncorrectLock should trigger
   //   a renderer kill, but this can't be done until HTML Imports are gone.
@@ -799,7 +797,8 @@
 
     corb_analyzer_ =
         std::make_unique<CrossOriginReadBlocking::ResponseAnalyzer>(
-            *url_request_, *response_, initiator_lock_compatibility_);
+            *url_request_, *response_,
+            factory_params_->request_initiator_site_lock);
     is_more_corb_sniffing_needed_ = corb_analyzer_->needs_sniffing();
     if (corb_analyzer_->ShouldBlock()) {
       DCHECK(!is_more_corb_sniffing_needed_);
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index 277d8da..5692c241 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -247,7 +247,6 @@
 
   // Sniffing state.
   std::unique_ptr<CrossOriginReadBlocking::ResponseAnalyzer> corb_analyzer_;
-  InitiatorLockCompatibility initiator_lock_compatibility_;
   bool is_more_corb_sniffing_needed_ = false;
   bool is_more_mime_sniffing_needed_ = false;
 
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 34fb233..11b8dd3 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -144,8 +144,6 @@
 #define SK_USE_LEGACY_DISTANCE_FIELDS
 #endif
 
-#define SK_LEGACY_TESSELLATOR_CPU_COVERAGE
-
 // Skia is enabling this feature soon. Chrome probably does
 // not want it for M64
 #ifndef SK_DISABLE_EXPLICIT_GPU_RESOURCE_ALLOCATION
diff --git a/third_party/blink/perf_tests/resources/runner.js b/third_party/blink/perf_tests/resources/runner.js
index a79729a..d1a99ac 100644
--- a/third_party/blink/perf_tests/resources/runner.js
+++ b/third_party/blink/perf_tests/resources/runner.js
@@ -136,6 +136,17 @@
         finish();
     }
 
+    PerfTestRunner.formatException = function (text, exception) {
+        return "Got an exception while " + text +
+            " with name=" + exception.name +
+            ", message=" + exception.message +
+            "\n" + exception.stack;
+    }
+
+    PerfTestRunner.logException = function (text, exception) {
+      PerfTestRunner.logFatalError(PerfTestRunner.formatException(text, exception));
+    }
+
     PerfTestRunner.forceLayout = function(doc) {
         doc = doc || document;
         if (doc.body)
@@ -192,7 +203,7 @@
             try {
                 runner();
             } catch (exception) {
-                PerfTestRunner.logFatalError("Got an exception while running test.run with name=" + exception.name + ", message=" + exception.message);
+              PerfTestRunner.logException("running test.run", exception);
             }
             return;
         }
@@ -212,7 +223,7 @@
                 if (currentTest.teardown)
                     currentTest.teardown();
             } catch (exception) {
-                PerfTestRunner.logFatalError("Got an exception while running test.run with name=" + exception.name + ", message=" + exception.message);
+                PerfTestRunner.logException("running test.run", exception);
                 return;
             }
 
@@ -221,7 +232,7 @@
             try {
                 ignoreWarmUpAndLog(measuredValue);
             } catch (exception) {
-                PerfTestRunner.logFatalError("Got an exception while logging the result with name=" + exception.name + ", message=" + exception.message);
+                PerfTestRunner.logException("logging the result", exception);
                 return;
             }
 
@@ -260,7 +271,7 @@
             if (currentTest.done)
                 currentTest.done();
         } catch (exception) {
-            logInDocument("Got an exception while finalizing the test with name=" + exception.name + ", message=" + exception.message);
+            logInDocument(PerfTestRunner.formatException("finalizing the test", exception));
         }
 
         if (window.testRunner) {
diff --git a/third_party/blink/public/platform/web_layer_tree_view.h b/third_party/blink/public/platform/web_layer_tree_view.h
index 15dd069a..31758b7 100644
--- a/third_party/blink/public/platform/web_layer_tree_view.h
+++ b/third_party/blink/public/platform/web_layer_tree_view.h
@@ -138,9 +138,6 @@
 
   // Flow control and scheduling ---------------------------------------
 
-  // Run layout and paint of all pending document changes asynchronously.
-  virtual void LayoutAndPaintAsync(base::OnceClosure callback) {}
-
   virtual void CompositeAndReadbackAsync(
       base::OnceCallback<void(const SkBitmap&)> callback) {}
 
diff --git a/third_party/blink/public/web/web_widget.h b/third_party/blink/public/web/web_widget.h
index 981ffea..c5fb93d 100644
--- a/third_party/blink/public/web/web_widget.h
+++ b/third_party/blink/public/web/web_widget.h
@@ -139,9 +139,6 @@
   // transormations (e.g. pinch-zoom, dev tools emulation, etc.).
   virtual void PaintContent(cc::PaintCanvas*, const WebRect& view_port) {}
 
-  // Run layout and paint of all pending document changes asynchronously.
-  virtual void LayoutAndPaintAsync(base::OnceClosure callback) {}
-
   // This should only be called when isAcceleratedCompositingActive() is true.
   virtual void CompositeAndReadbackAsync(
       base::OnceCallback<void(const SkBitmap&)> callback) {}
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index b492c41..649913ba 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -1036,10 +1036,8 @@
 
 scoped_refptr<ComputedStyle> StyleResolver::StyleForText(Text* text_node) {
   DCHECK(text_node);
-
   Node* parent_node = LayoutTreeBuilderTraversal::Parent(*text_node);
-  if (!parent_node || !parent_node->GetComputedStyle())
-    return InitialStyleForElement(GetDocument());
+  DCHECK(parent_node);
   return parent_node->MutableComputedStyle();
 }
 
diff --git a/third_party/blink/renderer/core/editing/selection_controller.cc b/third_party/blink/renderer/core/editing/selection_controller.cc
index e4ca6842..a380f8b 100644
--- a/third_party/blink/renderer/core/editing/selection_controller.cc
+++ b/third_party/blink/renderer/core/editing/selection_controller.cc
@@ -174,11 +174,11 @@
   original_base_in_flat_tree_ = PositionInFlatTreeWithAffinity();
 }
 
-static PositionInFlatTree AdjustPositionRespectUserSelectAll(
+static PositionInFlatTreeWithAffinity AdjustPositionRespectUserSelectAll(
     Node* inner_node,
     const PositionInFlatTree& selection_start,
     const PositionInFlatTree& selection_end,
-    const PositionInFlatTree& position) {
+    const PositionInFlatTreeWithAffinity& position) {
   const VisibleSelectionInFlatTree& selection_in_user_select_all =
       CreateVisibleSelection(ExpandSelectionToRespectUserSelectAll(
           inner_node,
@@ -188,9 +188,10 @@
   if (!selection_in_user_select_all.IsRange())
     return position;
   if (selection_in_user_select_all.Start().CompareTo(selection_start) < 0)
-    return selection_in_user_select_all.Start();
+    return PositionInFlatTreeWithAffinity(selection_in_user_select_all.Start());
+  // TODO(xiaochengh): Do we need to use upstream affinity for end?
   if (selection_end.CompareTo(selection_in_user_select_all.End()) < 0)
-    return selection_in_user_select_all.End();
+    return PositionInFlatTreeWithAffinity(selection_in_user_select_all.End());
   return position;
 }
 
@@ -211,7 +212,7 @@
 }
 
 static SelectionInFlatTree ExtendSelectionAsDirectional(
-    const PositionInFlatTree& position,
+    const PositionInFlatTreeWithAffinity& position,
     const SelectionInFlatTree& selection,
     TextGranularity granularity) {
   DCHECK(!selection.IsNone());
@@ -219,7 +220,7 @@
   const PositionInFlatTree& start = selection.ComputeStartPosition();
   const PositionInFlatTree& end = selection.ComputeEndPosition();
   const PositionInFlatTree& base = selection.IsBaseFirst() ? start : end;
-  if (position < base) {
+  if (position.GetPosition() < base) {
     // Extend backward yields backward selection
     //  - forward selection:  *abc ^def ghi| => |abc def^ ghi
     //  - backward selection: *abc |def ghi^ => |abc def ghi^
@@ -230,9 +231,11 @@
             ? ComputeEndRespectingGranularity(
                   new_start, PositionInFlatTreeWithAffinity(start), granularity)
             : end;
-    return SelectionInFlatTree::Builder()
-        .SetBaseAndExtent(new_end, new_start)
-        .Build();
+    SelectionInFlatTree::Builder builder;
+    builder.SetBaseAndExtent(new_end, new_start);
+    if (new_start == new_end)
+      builder.SetAffinity(position.Affinity());
+    return builder.Build();
   }
 
   // Extend forward yields forward selection
@@ -244,9 +247,11 @@
           : ComputeStartFromEndForExtendForward(end, granularity);
   const PositionInFlatTree& new_end = ComputeEndRespectingGranularity(
       new_start, PositionInFlatTreeWithAffinity(position), granularity);
-  return SelectionInFlatTree::Builder()
-      .SetBaseAndExtent(new_start, new_end)
-      .Build();
+  SelectionInFlatTree::Builder builder;
+  builder.SetBaseAndExtent(new_start, new_end);
+  if (new_start == new_end)
+    builder.SetAffinity(position.Affinity());
+  return builder.Build();
 }
 
 static SelectionInFlatTree ExtendSelectionAsNonDirectional(
@@ -258,6 +263,8 @@
   // Shift+Click deselects when selection was created right-to-left
   const PositionInFlatTree& start = selection.ComputeStartPosition();
   const PositionInFlatTree& end = selection.ComputeEndPosition();
+  if (start == end && position == start)
+    return selection;
   if (position < start) {
     return SelectionInFlatTree::Builder()
         .SetBaseAndExtent(
@@ -336,10 +343,9 @@
   if (extend_selection && !selection.IsNone()) {
     // Note: "fast/events/shift-click-user-select-none.html" makes
     // |pos.isNull()| true.
-    const PositionInFlatTree& adjusted_position =
+    const PositionInFlatTreeWithAffinity adjusted_position =
         AdjustPositionRespectUserSelectAll(inner_node, selection.Start(),
-                                           selection.End(),
-                                           position_to_use.GetPosition());
+                                           selection.End(), position_to_use);
     const TextGranularity granularity = Selection().Granularity();
     if (adjusted_position.IsNull()) {
       UpdateSelectionForMouseDownDispatchingSelectStart(
@@ -352,8 +358,9 @@
         frame_->GetEditor().Behavior().ShouldConsiderSelectionAsDirectional()
             ? ExtendSelectionAsDirectional(adjusted_position,
                                            selection.AsSelection(), granularity)
-            : ExtendSelectionAsNonDirectional(
-                  adjusted_position, selection.AsSelection(), granularity),
+            : ExtendSelectionAsNonDirectional(adjusted_position.GetPosition(),
+                                              selection.AsSelection(),
+                                              granularity),
         SetSelectionOptions::Builder().SetGranularity(granularity).Build());
     return false;
   }
@@ -516,10 +523,10 @@
     return;
   }
 
-  const PositionInFlatTree& adjusted_position =
-      AdjustPositionRespectUserSelectAll(target, visible_selection.Start(),
-                                         visible_selection.End(),
-                                         target_position.DeepEquivalent());
+  const PositionInFlatTreeWithAffinity adjusted_position =
+      AdjustPositionRespectUserSelectAll(
+          target, visible_selection.Start(), visible_selection.End(),
+          target_position.ToPositionWithAffinity());
   const SelectionInFlatTree& adjusted_selection =
       should_extend_selection
           ? ExtendSelectionAsDirectional(adjusted_position,
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index d8b9b791..8b71a1fc 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -575,10 +575,6 @@
   return MainFrame().DomWindow();
 }
 
-void WebPagePopupImpl::LayoutAndPaintAsync(base::OnceClosure callback) {
-  layer_tree_view_->LayoutAndPaintAsync(std::move(callback));
-}
-
 void WebPagePopupImpl::CompositeAndReadbackAsync(
     base::OnceCallback<void(const SkBitmap&)> callback) {
   DCHECK(IsAcceleratedCompositingActive());
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.h b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
index efdb66a..f8b1ec81 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.h
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
@@ -68,7 +68,6 @@
     return other && popup_client_ == other->popup_client_;
   }
   LocalDOMWindow* Window();
-  void LayoutAndPaintAsync(base::OnceClosure callback) override;
   void CompositeAndReadbackAsync(
       base::OnceCallback<void(const SkBitmap&)> callback) override;
   WebPoint PositionRelativeToOwner() override;
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 6a695832..9f45d48 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1610,11 +1610,6 @@
                                    *page_->DeprecatedLocalMainFrame());
 }
 
-void WebViewImpl::LayoutAndPaintAsync(base::OnceClosure callback) {
-  if (layer_tree_view_)
-    layer_tree_view_->LayoutAndPaintAsync(std::move(callback));
-}
-
 void WebViewImpl::CompositeAndReadbackAsync(
     base::OnceCallback<void(const SkBitmap&)> callback) {
   if (layer_tree_view_)
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index a2b2c17..b6ba2a2 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -439,7 +439,6 @@
   void RequestPresentationCallbackForTesting(
       base::OnceClosure callback) override;
   void PaintContent(cc::PaintCanvas*, const WebRect&) override;
-  void LayoutAndPaintAsync(base::OnceClosure callback) override;
   void CompositeAndReadbackAsync(
       base::OnceCallback<void(const SkBitmap&)> callback) override;
   void ThemeChanged() override;
diff --git a/third_party/blink/renderer/core/frame/link_highlights.h b/third_party/blink/renderer/core/frame/link_highlights.h
index 075bd99f..fc58e13e 100644
--- a/third_party/blink/renderer/core/frame/link_highlights.h
+++ b/third_party/blink/renderer/core/frame/link_highlights.h
@@ -69,6 +69,7 @@
   FRIEND_TEST_ALL_PREFIXES(LinkHighlightImplTest, verifyWebViewImplIntegration);
   FRIEND_TEST_ALL_PREFIXES(LinkHighlightImplTest, resetDuringNodeRemoval);
   FRIEND_TEST_ALL_PREFIXES(LinkHighlightImplTest, resetLayerTreeView);
+  FRIEND_TEST_ALL_PREFIXES(LinkHighlightImplTest, HighlightInvalidation);
   FRIEND_TEST_ALL_PREFIXES(LinkHighlightImplTest, multipleHighlights);
   FRIEND_TEST_ALL_PREFIXES(LinkHighlightImplTest, HighlightLayerEffectNode);
   FRIEND_TEST_ALL_PREFIXES(LinkHighlightImplTest, MultiColumn);
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 53fd46d..f927f69 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -337,10 +337,6 @@
       1, View()->MinimumPageScaleFactor(), View()->MaximumPageScaleFactor());
 }
 
-void WebFrameWidgetImpl::LayoutAndPaintAsync(base::OnceClosure callback) {
-  layer_tree_view_->LayoutAndPaintAsync(std::move(callback));
-}
-
 void WebFrameWidgetImpl::CompositeAndReadbackAsync(
     base::OnceCallback<void(const SkBitmap&)> callback) {
   layer_tree_view_->CompositeAndReadbackAsync(std::move(callback));
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index c30359f0..622592b 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -89,7 +89,6 @@
   void UpdateLifecycle(LifecycleUpdate requested_update,
                        LifecycleUpdateReason reason) override;
   void PaintContent(cc::PaintCanvas*, const WebRect&) override;
-  void LayoutAndPaintAsync(base::OnceClosure callback) override;
   void CompositeAndReadbackAsync(
       base::OnceCallback<void(const SkBitmap&)> callback) override;
   void ThemeChanged() override;
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 6f2aa599..a762ca2c 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -70,10 +70,6 @@
   web_view_->PaintContent(canvas, view_port);
 }
 
-void WebViewFrameWidget::LayoutAndPaintAsync(base::OnceClosure callback) {
-  web_view_->LayoutAndPaintAsync(std::move(callback));
-}
-
 void WebViewFrameWidget::CompositeAndReadbackAsync(
     base::OnceCallback<void(const SkBitmap&)> callback) {
   web_view_->CompositeAndReadbackAsync(std::move(callback));
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.h b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
index 2d41ec8..2ec4c80 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.h
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
@@ -53,7 +53,6 @@
   void UpdateLifecycle(LifecycleUpdate requested_update,
                        LifecycleUpdateReason reason) override;
   void PaintContent(cc::PaintCanvas*, const WebRect& view_port) override;
-  void LayoutAndPaintAsync(base::OnceClosure callback) override;
   void CompositeAndReadbackAsync(
       base::OnceCallback<void(const SkBitmap&)>) override;
   void ThemeChanged() override;
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index 3b67df83..f9ecbe3a4 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -1051,6 +1051,24 @@
   BlockPaintInvalidator(*this).ClearPreviousVisualRects();
 }
 
+void LayoutBlock::ImageChanged(WrappedImagePtr image,
+                               CanDeferInvalidation defer) {
+  LayoutBox::ImageChanged(image, defer);
+
+  if (!StyleRef().HasPseudoStyle(kPseudoIdFirstLine))
+    return;
+
+  if (auto* first_line_container = NearestInnerBlockWithFirstLine()) {
+    for (const auto* layer = &FirstLineStyleRef().BackgroundLayers(); layer;
+         layer = layer->Next()) {
+      if (layer->GetImage() && image == layer->GetImage()->Data()) {
+        first_line_container->SetShouldDoFullPaintInvalidationForFirstLine();
+        break;
+      }
+    }
+  }
+}
+
 void LayoutBlock::RemovePositionedObjects(
     LayoutObject* o,
     ContainingBlockState containing_block_state) {
diff --git a/third_party/blink/renderer/core/layout/layout_block.h b/third_party/blink/renderer/core/layout/layout_block.h
index d5fe0eed..5422b712 100644
--- a/third_party/blink/renderer/core/layout/layout_block.h
+++ b/third_party/blink/renderer/core/layout/layout_block.h
@@ -518,9 +518,10 @@
 
  protected:
   void InvalidatePaint(const PaintInvalidatorContext&) const override;
-
   void ClearPreviousVisualRects() override;
 
+  void ImageChanged(WrappedImagePtr, CanDeferInvalidation) override;
+
  private:
   LayoutRect LocalCaretRect(
       const InlineBox*,
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 82962bd..9bf6348d 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -1965,39 +1965,6 @@
   SetStyle(std::move(pseudo_style));
 }
 
-void LayoutObject::FirstLineStyleDidChange(const ComputedStyle& old_style,
-                                           const ComputedStyle& new_style) {
-  StyleDifference diff =
-      old_style.VisualInvalidationDiff(GetDocument(), new_style);
-
-  if (diff.NeedsFullPaintInvalidation() ||
-      diff.TextDecorationOrColorChanged()) {
-    // We need to invalidate all inline boxes in the first line, because they
-    // need to be repainted with the new style, e.g. background, font style,
-    // etc.
-    LayoutBlockFlow* first_line_container = nullptr;
-    if (BehavesLikeBlockContainer()) {
-      // This object is a LayoutBlock having PseudoIdFirstLine pseudo style
-      // changed.
-      first_line_container =
-          ToLayoutBlock(this)->NearestInnerBlockWithFirstLine();
-    } else if (IsLayoutInline()) {
-      // This object is a LayoutInline having FIRST_LINE_INHERITED pesudo style
-      // changed. This method can be called even if the LayoutInline doesn't
-      // intersect the first line, but we only need to invalidate if it does.
-      if (InlineBox* first_line_box =
-              ToLayoutInline(this)->FirstLineBoxIncludingCulling()) {
-        if (first_line_box->IsFirstLineStyle())
-          first_line_container = ToLayoutBlockFlow(ContainingBlock());
-      }
-    }
-    if (first_line_container)
-      first_line_container->SetShouldDoFullPaintInvalidationForFirstLine();
-  }
-  if (diff.NeedsLayout())
-    SetNeedsLayoutAndPrefWidthsRecalc(layout_invalidation_reason::kStyleChange);
-}
-
 void LayoutObject::MarkContainerChainForOverflowRecalcIfNeeded() {
   LayoutObject* object = this;
   do {
@@ -2047,9 +2014,9 @@
   SetStyleInternal(std::move(style));
 
   UpdateFillImages(old_style ? &old_style->BackgroundLayers() : nullptr,
-                   style_->BackgroundLayers());
+                   &style_->BackgroundLayers());
   UpdateFillImages(old_style ? &old_style->MaskLayers() : nullptr,
-                   style_->MaskLayers());
+                   &style_->MaskLayers());
 
   UpdateImage(old_style ? old_style->BorderImage().GetImage() : nullptr,
               style_->BorderImage().GetImage());
@@ -2352,8 +2319,7 @@
       SetBackgroundNeedsFullPaintInvalidation();
   }
 
-  if (old_style && old_style->StyleType() == kPseudoIdNone)
-    ApplyPseudoStyleChanges(*old_style);
+  ApplyPseudoStyleChanges(old_style);
 
   if (old_style &&
       old_style->UsedTransformStyle3D() != StyleRef().UsedTransformStyle3D()) {
@@ -2363,30 +2329,57 @@
   }
 }
 
-void LayoutObject::ApplyPseudoStyleChanges(const ComputedStyle& old_style) {
-  if (old_style.HasPseudoStyle(kPseudoIdFirstLine) ||
+void LayoutObject::ApplyPseudoStyleChanges(const ComputedStyle* old_style) {
+  if ((old_style && old_style->HasPseudoStyle(kPseudoIdFirstLine)) ||
       StyleRef().HasPseudoStyle(kPseudoIdFirstLine))
     ApplyFirstLineChanges(old_style);
 
-  if (old_style.HasPseudoStyle(kPseudoIdSelection) ||
+  if ((old_style && old_style->HasPseudoStyle(kPseudoIdSelection)) ||
       StyleRef().HasPseudoStyle(kPseudoIdSelection))
     InvalidateSelectedChildrenOnStyleChange();
 }
 
-void LayoutObject::ApplyFirstLineChanges(const ComputedStyle& old_style) {
-  if (old_style.HasPseudoStyle(kPseudoIdFirstLine)) {
-    scoped_refptr<const ComputedStyle> old_pseudo_style =
-        old_style.GetCachedPseudoStyle(kPseudoIdFirstLine);
-    if (StyleRef().HasPseudoStyle(kPseudoIdFirstLine) && old_pseudo_style) {
-      scoped_refptr<const ComputedStyle> new_pseudo_style =
-          UncachedFirstLineStyle();
-      if (new_pseudo_style) {
-        FirstLineStyleDidChange(*old_pseudo_style, *new_pseudo_style);
-        return;
-      }
-    }
+void LayoutObject::ApplyFirstLineChanges(const ComputedStyle* old_style) {
+  DCHECK((old_style && old_style->HasPseudoStyle(kPseudoIdFirstLine)) ||
+         StyleRef().HasPseudoStyle(kPseudoIdFirstLine));
+
+  const ComputedStyle* old_first_line_style = nullptr;
+  if (old_style && old_style->HasPseudoStyle(kPseudoIdFirstLine))
+    old_first_line_style = old_style->GetCachedPseudoStyle(kPseudoIdFirstLine);
+  else
+    old_first_line_style = old_style;
+
+  auto new_first_line_style = UncachedFirstLineStyle();
+  if (!old_first_line_style && !new_first_line_style)
+    return;
+
+  UpdateFillImages(
+      old_first_line_style ? &old_first_line_style->BackgroundLayers()
+                           : nullptr,
+      new_first_line_style ? &new_first_line_style->BackgroundLayers()
+                           : nullptr);
+
+  StyleDifference diff;
+  bool has_diff = false;
+  if (old_first_line_style && new_first_line_style) {
+    diff = old_first_line_style->VisualInvalidationDiff(GetDocument(),
+                                                        *new_first_line_style);
+    has_diff = true;
   }
-  SetNeedsLayoutAndPrefWidthsRecalc(layout_invalidation_reason::kStyleChange);
+  if (!has_diff) {
+    diff.SetNeedsPaintInvalidationObject();
+    diff.SetNeedsFullLayout();
+  }
+
+  if (BehavesLikeBlockContainer() && (diff.NeedsFullPaintInvalidation() ||
+                                      diff.TextDecorationOrColorChanged())) {
+    if (auto* first_line_container =
+            ToLayoutBlock(this)->NearestInnerBlockWithFirstLine())
+      first_line_container->SetShouldDoFullPaintInvalidationForFirstLine();
+  }
+
+  if (diff.NeedsLayout())
+    SetNeedsLayoutAndPrefWidthsRecalc(layout_invalidation_reason::kStyleChange);
 }
 
 void LayoutObject::PropagateStyleToAnonymousChildren() {
@@ -2460,14 +2453,14 @@
 }
 
 void LayoutObject::UpdateFillImages(const FillLayer* old_layers,
-                                    const FillLayer& new_layers) {
+                                    const FillLayer* new_layers) {
   // Optimize the common case
-  if (FillLayer::ImagesIdentical(old_layers, &new_layers))
+  if (FillLayer::ImagesIdentical(old_layers, new_layers))
     return;
 
   // Go through the new layers and addClients first, to avoid removing all
   // clients of an image.
-  for (const FillLayer* curr_new = &new_layers; curr_new;
+  for (const FillLayer* curr_new = new_layers; curr_new;
        curr_new = curr_new->Next()) {
     if (curr_new->GetImage())
       curr_new->GetImage()->AddClient(this);
@@ -3550,6 +3543,12 @@
           PseudoStyleRequest(kPseudoIdFirstLine), style);
     }
   } else if (layout_object_for_first_line_style->IsLayoutInline()) {
+    if (!layout_object_for_first_line_style->Parent()) {
+      // The logic below applies only when this object has parent, while this
+      // function can be called from ApplyFirstLineChanges() when the object
+      // has not been set a parent yet.
+      return nullptr;
+    }
     if (layout_object_for_first_line_style->IsAnonymous()) {
       // Anonymous inline box for ::first-line should inherit background.
       if (ToLayoutInline(layout_object_for_first_line_style)
@@ -3586,11 +3585,9 @@
 
 scoped_refptr<const ComputedStyle> LayoutObject::UncachedFirstLineStyle()
     const {
-  if (!GetDocument().GetStyleEngine().UsesFirstLineRules())
-    return nullptr;
-
+  // Don't check GetDocument().GetStyleEngine().UsesFirstLineRules() because
+  // this object may not have access to GetDocument() yet during initialization.
   DCHECK(!IsText());
-
   return FirstLineStyleForCachedUncachedType(kUncached, this, style_.get());
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 1467118..f47806a 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -1327,9 +1327,6 @@
                                  LayoutObject* parent);
   void SetStyleWithWritingModeOfParent(scoped_refptr<ComputedStyle>);
 
-  void FirstLineStyleDidChange(const ComputedStyle& old_style,
-                               const ComputedStyle& new_style);
-
   void ClearBaseComputedStyle();
 
   // This function returns an enclosing non-anonymous LayoutBlock for this
@@ -2388,7 +2385,7 @@
 
   void UpdateShapeImage(const ShapeValue*, const ShapeValue*);
   void UpdateFillImages(const FillLayer* old_layers,
-                        const FillLayer& new_layers);
+                        const FillLayer* new_layers);
   void UpdateCursorImages(const CursorList* old_cursors,
                           const CursorList* new_cursors);
   void CheckCounterChanges(const ComputedStyle* old_style,
@@ -2434,8 +2431,8 @@
   // LayoutView to return the owning LayoutObject in the containing frame.
   inline LayoutObject* ParentCrossingFrames() const;
 
-  void ApplyPseudoStyleChanges(const ComputedStyle& old_style);
-  void ApplyFirstLineChanges(const ComputedStyle& old_style);
+  void ApplyPseudoStyleChanges(const ComputedStyle* old_style);
+  void ApplyFirstLineChanges(const ComputedStyle* old_style);
 
   LayoutRect VisualRectForInlineBox() const {
     return AdjustVisualRectForInlineBox(VisualRect());
diff --git a/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h b/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h
index 4a128618..94d59f3b 100644
--- a/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h
+++ b/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h
@@ -57,7 +57,7 @@
     return *this;
   }
 
-  NGBoxStrut operator+(const NGBoxStrut& other) {
+  NGBoxStrut operator+(const NGBoxStrut& other) const {
     NGBoxStrut result(*this);
     result += other;
     return result;
@@ -71,7 +71,7 @@
     return *this;
   }
 
-  NGBoxStrut operator-(const NGBoxStrut& other) {
+  NGBoxStrut operator-(const NGBoxStrut& other) const {
     NGBoxStrut result(*this);
     result -= other;
     return result;
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 79aa5c4..67245b0 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
@@ -45,6 +45,13 @@
   return !item_results.IsEmpty() && item_results.back().has_unpositioned_floats;
 }
 
+bool IsImage(const NGInlineItem& item) {
+  if (!item.GetLayoutObject() || !item.GetLayoutObject()->IsLayoutImage())
+    return false;
+  DCHECK(item.Type() == NGInlineItem::kAtomicInline);
+  return true;
+}
+
 }  // namespace
 
 NGLineBreaker::NGLineBreaker(NGInlineNode node,
@@ -86,6 +93,19 @@
     items_data_.AssertOffset(item_index_, offset_);
     ignore_floats_ = break_token->IgnoreFloats();
   }
+
+  // There's a special intrinsic size measure quirk for images that are direct
+  // children of table cells that have auto inline-size: When measuring
+  // intrinsic min/max inline sizes, we pretend that it's not possible to break
+  // between images, or between text and images. Note that this only applies
+  // when measuring. During actual layout, on the other hand, standard breaking
+  // rules are to be followed.
+  // See https://quirks.spec.whatwg.org/#the-table-cell-width-calculation-quirk
+  if (node.GetDocument().InQuirksMode() &&
+      node.Style().Display() == EDisplay::kTableCell &&
+      node.Style().LogicalWidth().IsIntrinsicOrAuto() &&
+      mode != NGLineBreakerMode::kContent)
+    sticky_images_quirk_ = true;
 }
 
 // Define the destructor here, so that we can forward-declare more in the
@@ -277,6 +297,15 @@
     // opportunity if we're trailing.
     if (state_ == LineBreakState::kTrailing &&
         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);
+        continue;
+      }
+
       line_info_->SetIsLastLine(false);
       return;
     }
@@ -878,6 +907,19 @@
   trailing_whitespace_ = WhitespaceState::kNone;
   position_ += item_result->inline_size;
   ComputeCanBreakAfter(item_result);
+
+  if (sticky_images_quirk_ && IsImage(item)) {
+    const auto& items = Items();
+    if (item_index_ + 1 < items.size()) {
+      DCHECK_EQ(&item, &items[item_index_]);
+      const auto& next_item = items[item_index_ + 1];
+      // This is an image, and we don't want to break after it, unless what
+      // comes after provides a break opportunity. Look ahead. We only want to
+      // break if the next item is an atomic inline that's not an image.
+      if (next_item.Type() != NGInlineItem::kAtomicInline || IsImage(next_item))
+        item_result->can_break_after = false;
+    }
+  }
   MoveToNextOf(item);
 }
 
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 04ed7efb..b483011 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
@@ -202,6 +202,10 @@
 
   bool ignore_floats_ = false;
 
+  // Set in quirks mode when we're not supposed to break inside table cells
+  // between images, and between text and images.
+  bool sticky_images_quirk_ = false;
+
   const NGInlineItemsData& items_data_;
 
   NGLineBreakerMode mode_;
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 a944c30..a60e9b1b 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
@@ -30,24 +30,6 @@
          LayoutNGMixin<LayoutBlockFlow>::IsOfType(type);
 }
 
-void LayoutNGBlockFlow::ComputeIntrinsicLogicalWidths(
-    LayoutUnit& min_logical_width,
-    LayoutUnit& max_logical_width) const {
-  NGBlockNode node(const_cast<LayoutNGBlockFlow*>(this));
-  if (!node.CanUseNewLayout()) {
-    LayoutBlockFlow::ComputeIntrinsicLogicalWidths(min_logical_width,
-                                                   max_logical_width);
-    return;
-  }
-  MinMaxSizeInput input;
-  // This function returns content-box plus scrollbar.
-  input.size_type = NGMinMaxSizeType::kContentBoxSize;
-  MinMaxSize sizes = node.ComputeMinMaxSize(StyleRef().GetWritingMode(), input);
-  sizes += LayoutUnit(ScrollbarLogicalWidth());
-  min_logical_width = sizes.min_size;
-  max_logical_width = sizes.max_size;
-}
-
 void LayoutNGBlockFlow::UpdateBlockLayout(bool relayout_children) {
   LayoutAnalyzer::BlockScope analyzer(*this);
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
index 4f552c1..f36533ea2 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
@@ -24,11 +24,6 @@
  protected:
   bool IsOfType(LayoutObjectType) const override;
 
- protected:
-  void ComputeIntrinsicLogicalWidths(
-      LayoutUnit& min_logical_width,
-      LayoutUnit& max_logical_width) const override;
-
  private:
   void UpdateOutOfFlowBlockLayout();
 };
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
index 7759f35..5935a2a 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
@@ -72,8 +72,40 @@
 }
 
 template <typename Base>
-void LayoutNGMixin<Base>::ComputeVisualOverflow(
-    bool recompute_floats) {
+void LayoutNGMixin<Base>::ComputeIntrinsicLogicalWidths(
+    LayoutUnit& min_logical_width,
+    LayoutUnit& max_logical_width) const {
+  NGBlockNode node(const_cast<LayoutNGMixin<Base>*>(this));
+  if (!node.CanUseNewLayout()) {
+    Base::ComputeIntrinsicLogicalWidths(min_logical_width, max_logical_width);
+    return;
+  }
+  MinMaxSizeInput input;
+  // This function returns content-box plus scrollbar.
+  input.size_type = NGMinMaxSizeType::kContentBoxSize;
+  MinMaxSize sizes =
+      node.ComputeMinMaxSize(node.Style().GetWritingMode(), input);
+
+  if (Base::IsTableCell()) {
+    // If a table cell, or the column that it belongs to, has a specified fixed
+    // positive inline-size, and the measured intrinsic max size is less than
+    // that, use specified size as max size.
+    LayoutTableCell* cell = ToLayoutTableCell(node.GetLayoutBox());
+    Length table_cell_width = cell->StyleOrColLogicalWidth();
+    if (table_cell_width.IsFixed() && table_cell_width.Value() > 0) {
+      sizes.max_size = std::max(sizes.min_size,
+                                Base::AdjustContentBoxLogicalWidthForBoxSizing(
+                                    LayoutUnit(table_cell_width.Value())));
+    }
+  }
+
+  sizes += LayoutUnit(Base::ScrollbarLogicalWidth());
+  min_logical_width = sizes.min_size;
+  max_logical_width = sizes.max_size;
+}
+
+template <typename Base>
+void LayoutNGMixin<Base>::ComputeVisualOverflow(bool recompute_floats) {
   LayoutRect previous_visual_overflow_rect = Base::VisualOverflowRect();
   Base::ClearVisualOverflow();
   Base::ComputeVisualOverflow(recompute_floats);
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
index 502ac6d3..ace5295 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
@@ -83,6 +83,10 @@
  protected:
   bool IsOfType(LayoutObject::LayoutObjectType) const override;
 
+  void ComputeIntrinsicLogicalWidths(
+      LayoutUnit& min_logical_width,
+      LayoutUnit& max_logical_width) const override;
+
   void ComputeVisualOverflow(bool recompute_floats) final;
 
   void AddVisualOverflowFromChildren();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
index 7afc944..743d54b 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
@@ -22,7 +22,12 @@
 NGFlexLayoutAlgorithm::NGFlexLayoutAlgorithm(NGBlockNode node,
                                              const NGConstraintSpace& space,
                                              const NGBreakToken* break_token)
-    : NGLayoutAlgorithm(node, space, ToNGBlockBreakToken(break_token)) {
+    : NGLayoutAlgorithm(node, space, ToNGBlockBreakToken(break_token)),
+      border_scrollbar_padding_(
+          CalculateBorderScrollbarPadding(ConstraintSpace(), Node())),
+      borders_(ComputeBorders(ConstraintSpace(), Style())),
+      padding_(ComputePadding(ConstraintSpace(), Style())),
+      is_column_(Style().IsColumnFlexDirection()) {
   container_builder_.SetIsNewFormattingContext(space.IsNewFormattingContext());
 }
 
@@ -55,22 +60,14 @@
 }
 
 scoped_refptr<NGLayoutResult> NGFlexLayoutAlgorithm::Layout() {
-  DCHECK(!NeedMinMaxSize(ConstraintSpace(), Style()))
-      << "Don't support that yet";
-
-  borders_ = ComputeBorders(ConstraintSpace(), Style());
-  padding_ = ComputePadding(ConstraintSpace(), Style());
   // TODO(dgrogan): Pass padding+borders as optimization.
   border_box_size_ = CalculateBorderBoxSize(ConstraintSpace(), Node());
-  border_scrollbar_padding_ =
-      CalculateBorderScrollbarPadding(ConstraintSpace(), Node());
   content_box_size_ =
       ShrinkAvailableSize(border_box_size_, border_scrollbar_padding_);
 
   const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max());
   FlexLayoutAlgorithm algorithm(&Style(), line_break_length);
-  bool is_column = Style().IsColumnFlexDirection();
-  bool is_horizontal_flow = algorithm.IsHorizontalFlow();
+  const bool is_horizontal_flow = algorithm.IsHorizontalFlow();
 
   for (NGLayoutInputNode generic_child = Node().FirstChild(); generic_child;
        generic_child = generic_child.NextSibling()) {
@@ -200,7 +197,7 @@
 
   LayoutUnit main_axis_offset = border_scrollbar_padding_.inline_start;
   LayoutUnit cross_axis_offset = border_scrollbar_padding_.block_start;
-  if (is_column) {
+  if (is_column_) {
     main_axis_offset = border_scrollbar_padding_.block_start;
     cross_axis_offset = border_scrollbar_padding_.inline_start;
   }
@@ -225,7 +222,7 @@
                                               &space_builder);
 
       NGLogicalSize available_size;
-      if (is_column) {
+      if (is_column_) {
         available_size.inline_size = content_box_size_.inline_size;
         available_size.block_size = flex_item.flexed_content_size +
                                     flex_item.main_axis_border_and_padding;
@@ -256,8 +253,8 @@
         std::max(max_main_axis_extent, line->main_axis_extent);
   }
   LayoutUnit intrinsic_block_content_size =
-      is_column ? max_main_axis_extent
-                : cross_axis_offset - border_scrollbar_padding_.block_start;
+      is_column_ ? max_main_axis_extent
+                 : cross_axis_offset - border_scrollbar_padding_.block_start;
   LayoutUnit intrinsic_block_size =
       intrinsic_block_content_size + border_scrollbar_padding_.BlockSum();
   LayoutUnit block_size = ComputeBlockSizeForFragment(
@@ -268,7 +265,7 @@
   // container-specific local variables into data members.
   LayoutUnit final_content_cross_size =
       block_size - border_scrollbar_padding_.BlockSum();
-  if (is_column) {
+  if (is_column_) {
     final_content_cross_size =
         border_box_size_.inline_size - border_scrollbar_padding_.InlineSum();
   }
@@ -293,7 +290,7 @@
         NGLogicalSize available_size(flex_item.flexed_content_size +
                                          flex_item.main_axis_border_and_padding,
                                      flex_item.cross_axis_size);
-        if (is_column)
+        if (is_column_)
           available_size.Flip();
         space_builder.SetAvailableSize(available_size);
         space_builder.SetPercentageResolutionSize(content_box_size_);
@@ -325,8 +322,55 @@
 
 base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize(
     const MinMaxSizeInput& input) const {
-  // TODO(dgrogan): Implement this.
-  return base::nullopt;
+  MinMaxSize sizes;
+  if (Node().ShouldApplySizeContainment()) {
+    // TODO(dgrogan): When this code was written it didn't make any more tests
+    // pass, so it may be wrong or untested.
+    if (input.size_type == NGMinMaxSizeType::kBorderBoxSize)
+      sizes = border_scrollbar_padding_.InlineSum();
+    return sizes;
+  }
+
+  for (NGLayoutInputNode generic_child = Node().FirstChild(); generic_child;
+       generic_child = generic_child.NextSibling()) {
+    NGBlockNode child = ToNGBlockNode(generic_child);
+    if (child.IsOutOfFlowPositioned())
+      continue;
+    // Use default MinMaxSizeInput:
+    //   - Children of flexbox ignore any specified float properties, so
+    //     children never have to take floated siblings into account, and
+    //     external floats don't make it through the new formatting context that
+    //     flexbox establishes.
+    //   - We want the child's border box MinMaxSize, which is the default.
+    MinMaxSize child_min_max_sizes =
+        ComputeMinAndMaxContentContribution(Style(), child, MinMaxSizeInput());
+    NGBoxStrut child_margins = ComputeMinMaxMargins(Style(), child);
+    child_min_max_sizes += child_margins.InlineSum();
+    if (is_column_) {
+      sizes.min_size = std::max(sizes.min_size, child_min_max_sizes.min_size);
+      sizes.max_size = std::max(sizes.max_size, child_min_max_sizes.max_size);
+    } else {
+      sizes.max_size += child_min_max_sizes.max_size;
+      if (IsMultiline())
+        sizes.min_size = std::max(sizes.min_size, child_min_max_sizes.min_size);
+      else
+        sizes.min_size += child_min_max_sizes.min_size;
+    }
+  }
+  sizes.max_size = std::max(sizes.max_size, sizes.min_size);
+
+  // Due to negative margins, it is possible that we calculated a negative
+  // intrinsic width. Make sure that we never return a negative width.
+  sizes.Encompass(LayoutUnit());
+
+  if (input.size_type == NGMinMaxSizeType::kBorderBoxSize)
+    sizes += border_scrollbar_padding_.InlineSum();
+
+  return sizes;
+}
+
+bool NGFlexLayoutAlgorithm::IsMultiline() const {
+  return Style().FlexWrap() != EFlexWrap::kNowrap;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h
index 04652cd..e10d18f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h
@@ -37,12 +37,17 @@
   LayoutUnit MainAxisContentExtent(LayoutUnit sum_hypothetical_main_size);
 
   void HandleOutOfFlowPositioned(NGBlockNode child);
+  // TODO(dgrogan): This is redundant with FlexLayoutAlgorithm.IsMultiline() but
+  // it's needed before the algorithm is instantiated. Figure out how to
+  // not reimplement.
+  bool IsMultiline() const;
 
+  const NGBoxStrut border_scrollbar_padding_;
+  const NGBoxStrut borders_;
+  const NGBoxStrut padding_;
+  const bool is_column_;
   NGLogicalSize border_box_size_;
-  NGBoxStrut border_scrollbar_padding_;
   NGLogicalSize content_box_size_;
-  NGBoxStrut borders_;
-  NGBoxStrut padding_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h
index ed19927..05dcdde 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h
@@ -180,6 +180,14 @@
 
   struct FrozenState;
 
+  // TODO(altimin): This is used when creating a URLLoader, and
+  // FetchContext::GetLoadingTaskRunner is used whenever asynchronous tasks
+  // around resource loading are posted. Modify the code so that all
+  // the tasks related to loading a resource use the resource loader handle's
+  // task runner.
+  std::unique_ptr<scheduler::WebResourceLoadingTaskRunnerHandle>
+  CreateResourceLoadingTaskRunnerHandle();
+
   // Convenient accessors below can be used to transparently access the
   // relevant document loader or frame in either cases without null-checks.
   //
@@ -191,8 +199,6 @@
 
   // FetchContext overrides:
   FrameScheduler* GetFrameScheduler() const override;
-  std::unique_ptr<scheduler::WebResourceLoadingTaskRunnerHandle>
-  CreateResourceLoadingTaskRunnerHandle() override;
 
   // BaseFetchContext overrides:
   KURL GetSiteForCookies() const override;
diff --git a/third_party/blink/renderer/core/loader/worker_fetch_context.h b/third_party/blink/renderer/core/loader/worker_fetch_context.h
index 7ba2a2f..4357aeb 100644
--- a/third_party/blink/renderer/core/loader/worker_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/worker_fetch_context.h
@@ -110,8 +110,13 @@
                                ResourceRequest&) override;
   bool DefersLoading() const override;
 
-  std::unique_ptr<scheduler::WebResourceLoadingTaskRunnerHandle>
-  CreateResourceLoadingTaskRunnerHandle() override;
+  // TODO(altimin): This is used when creating a URLLoader, and
+  // FetchContext::GetLoadingTaskRunner is used whenever asynchronous tasks
+  // around resource loading are posted. Modify the code so that all
+  // the tasks related to loading a resource use the resource loader handle's
+  // task runner.
+  std::unique_ptr<blink::scheduler::WebResourceLoadingTaskRunnerHandle>
+  CreateResourceLoadingTaskRunnerHandle();
 
   SecurityContext& GetSecurityContext() const;
   WorkerSettings* GetWorkerSettings() const;
diff --git a/third_party/blink/renderer/core/paint/README.md b/third_party/blink/renderer/core/paint/README.md
index ce3acf4d..dbf3dc08 100644
--- a/third_party/blink/renderer/core/paint/README.md
+++ b/third_party/blink/renderer/core/paint/README.md
@@ -574,7 +574,7 @@
 `::first-line` style and the style of the `LayoutInline` and apply the computed
 style to the first line part of the `LayoutInline`. In Blink's style
 implementation, the combined first line style of `LayoutInline` is identified
-with `FIRST_LINE_INHERITED` pseudo ID.
+with `kPseudoIdFirstLineInherited`.
 
 The normal paint invalidation of texts doesn't work for first line because
 *   `ComputedStyle::VisualInvalidationDiff()` can't detect first line style
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.cc b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
index a4ee479..93b5985 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
@@ -386,10 +386,9 @@
     Layer()->SetNeedsDisplay();
 
     if (current_graphics_layer_) {
-      gfx::Rect rect = gfx::ToEnclosingRect(
-          gfx::RectF(Layer()->position(), gfx::SizeF(Layer()->bounds())));
+      IntRect rect = IntRect(IntPoint(), IntSize(Layer()->bounds()));
       current_graphics_layer_->TrackRasterInvalidation(
-          *this, IntRect(rect), PaintInvalidationReason::kFullLayer);
+          *this, rect, PaintInvalidationReason::kFullLayer);
     }
   }
 }
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc b/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc
index 414f1c7..d3da4806 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc
@@ -248,6 +248,44 @@
   }
 }
 
+TEST_P(LinkHighlightImplTest, HighlightInvalidation) {
+  // This test requires GraphicsLayers which are not used in
+  // CompositeAfterPaint.
+  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+    return;
+
+  WebViewImpl* web_view_impl = web_view_helper_.GetWebView();
+  web_view_impl->MainFrameWidget()->Resize(WebSize(640, 480));
+  UpdateAllLifecyclePhases();
+
+  WebGestureEvent touch_event(WebInputEvent::kGestureShowPress,
+                              WebInputEvent::kNoModifiers,
+                              WebInputEvent::GetStaticTimeStampForTests(),
+                              kWebGestureDeviceTouchscreen);
+  touch_event.SetPositionInWidget(WebFloatPoint(20, 20));
+  GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event);
+  auto* touch_element = ToElement(web_view_impl->BestTapNode(targeted_event));
+  web_view_impl->EnableTapHighlightAtPoint(targeted_event);
+
+  web_view_helper_.LocalMainFrame()
+      ->GetFrameView()
+      ->SetTracksPaintInvalidations(true);
+
+  // Change the touched element's height to 12px.
+  auto& style = touch_element->getAttribute(html_names::kStyleAttr);
+  String new_style = style.GetString();
+  new_style.append("height: 12px;");
+  touch_element->setAttribute(html_names::kStyleAttr, AtomicString(new_style));
+  UpdateAllLifecyclePhases();
+
+  const auto& highlights =
+      web_view_impl->GetPage()->GetLinkHighlights().link_highlights_;
+  auto* highlight_layer = highlights.at(0)->CurrentGraphicsLayerForTesting();
+  const auto* tracking = highlight_layer->GetRasterInvalidationTracking();
+  // The invalidation rect should fully cover the layer.
+  EXPECT_EQ(tracking->Invalidations().back().rect, IntRect(0, 0, 200, 12));
+}
+
 TEST_P(LinkHighlightImplTest, HighlightLayerEffectNode) {
   // This is testing the blink->cc layer integration.
   if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
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 95f3227..bafc1fb51 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
@@ -553,8 +553,11 @@
 
   bool needs_end_layer = false;
   if (!painting_overflow_contents) {
+    bool skip_background = layout_box.BackgroundTransfersToView() ||
+                           (paint_info.SkipRootBackground() &&
+                            paint_info.PaintContainer() == layout_box);
     PaintNormalBoxShadow(paint_info, paint_rect, style, border_edges_.line_left,
-                         border_edges_.line_right);
+                         border_edges_.line_right, skip_background);
 
     if (box_fragment_.HasSelfPaintingLayer() && layout_box.IsTableCell() &&
         ToLayoutTableCell(layout_box).Table()->ShouldCollapseBorders()) {
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/HeapTimelineOverview.js b/third_party/blink/renderer/devtools/front_end/profiler/HeapTimelineOverview.js
index e60c72bf..09f2ed55 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/HeapTimelineOverview.js
+++ b/third_party/blink/renderer/devtools/front_end/profiler/HeapTimelineOverview.js
@@ -231,9 +231,9 @@
     const minIndex = timestamps.lowerBound(timeLeft);
     const maxIndex = timestamps.upperBound(timeRight);
     let size = 0;
-    for (let i = minIndex; i < maxIndex; ++i)
+    for (let i = minIndex; i <= maxIndex; ++i)
       size += sizes[i];
-    const minId = minIndex < ids.length ? ids[minIndex] : Infinity;
+    const minId = minIndex > 0 ? ids[minIndex - 1] : 0;
     const maxId = maxIndex < ids.length ? ids[maxIndex] : Infinity;
 
     this.dispatchEventToListeners(Profiler.HeapTimelineOverview.IdsRangeChanged, {minId, maxId, size});
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.cc b/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.cc
index 9757b584..0d48626 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.cc
@@ -53,6 +53,12 @@
 
 void DtlsTransportProxy::OnStateChange(webrtc::DtlsTransportInformation info) {
   DCHECK(host_thread_->BelongsToCurrentThread());
+  // Closed is the last state that can happen, so unregister when we see this.
+  // Unregistering allows us to safely delete the proxy independent of the
+  // state of the webrtc::DtlsTransport.
+  if (info.state() == webrtc::DtlsTransportState::kClosed) {
+    dtls_transport_->UnregisterObserver();
+  }
   PostCrossThreadTask(*proxy_thread_, FROM_HERE,
                       CrossThreadBind(&Delegate::OnStateChange,
                                       CrossThreadUnretained(delegate_), info));
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.h b/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.h
index c38cd2a9..0d837e8 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/dtls_transport_proxy.h
@@ -38,6 +38,9 @@
   // Constructs a DtlsTransportProxy.
   // The caller is responsible for keeping |dtls_transport| and |delegate|
   // alive until after the DtlsTransportProxy is deleted.
+  // The DtlsTransportProxy can be safely deleted after seeing the
+  // state |kClosed|, since this is the last event that can happen
+  // on the transport.
   static std::unique_ptr<DtlsTransportProxy> Create(
       LocalFrame& frame,
       scoped_refptr<base::SingleThreadTaskRunner> proxy_thread,
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
index 45baf4c..f4611fdb 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
@@ -194,8 +194,23 @@
   packet_writer->InitializeWithQuicConnection(quic_connection.get());
 
   // QUIC configurations for the session are specified here.
+  // TODO(shampson): Consider setting larger initial flow control window sizes
+  // so that the default limit doesn't cause initial undersending.
   quic::QuicConfig quic_config;
   quic_config.SetMaxIncomingDynamicStreamsToSend(kMaxIncomingDynamicStreams);
+  // The handshake network timeouts are configured to large values to prevent
+  // the QUIC connection from being closed on a slow connection. This can occur
+  // if signaling is slow and one side begins the handshake early.
+  // See ICE related bug: bugs.webrtc.org/9869.
+  //
+  // This timeout is from time of creation of the quic::QuicConnection object to
+  // the completion of the handshake. It must be larger than the idle time.
+  quic_config.set_max_time_before_crypto_handshake(
+      quic::QuicTime::Delta::FromSeconds(50));
+  // This is the timeout for idle time in the handshake. This value allows
+  // time for slow signaling to complete.
+  quic_config.set_max_idle_time_before_crypto_handshake(
+      quic::QuicTime::Delta::FromSeconds(30));
   return std::make_unique<P2PQuicTransportImpl>(
       delegate, packet_transport, std::move(config), std::move(helper),
       std::move(quic_connection), quic_config, clock_);
diff --git a/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc b/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc
index 4be645fb..c671a2a 100644
--- a/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc
+++ b/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc
@@ -49,7 +49,7 @@
 
 GLboolean EXTDisjointTimerQuery::isQueryEXT(WebGLTimerQueryEXT* query) {
   WebGLExtensionScopedContext scoped(this);
-  if (!query || scoped.IsLost() || query->IsDeleted() ||
+  if (!query || scoped.IsLost() || query->MarkedForDeletion() ||
       !query->Validate(nullptr, scoped.Context())) {
     return false;
   }
@@ -63,12 +63,8 @@
   if (scoped.IsLost())
     return;
 
-  DCHECK(query);
-  if (query->IsDeleted() || !query->Validate(nullptr, scoped.Context())) {
-    scoped.Context()->SynthesizeGLError(GL_INVALID_OPERATION, "beginQueryEXT",
-                                        "invalid query");
+  if (!scoped.Context()->ValidateWebGLObject("beginQueryEXT", query))
     return;
-  }
 
   if (target != GL_TIME_ELAPSED_EXT) {
     scoped.Context()->SynthesizeGLError(GL_INVALID_ENUM, "beginQueryEXT",
@@ -121,12 +117,8 @@
   if (scoped.IsLost())
     return;
 
-  DCHECK(query);
-  if (query->IsDeleted() || !query->Validate(nullptr, scoped.Context())) {
-    scoped.Context()->SynthesizeGLError(GL_INVALID_OPERATION, "queryCounterEXT",
-                                        "invalid query");
+  if (!scoped.Context()->ValidateWebGLObject("queryCounterEXT", query))
     return;
-  }
 
   if (target != GL_TIMESTAMP_EXT) {
     scoped.Context()->SynthesizeGLError(GL_INVALID_ENUM, "queryCounterEXT",
@@ -186,11 +178,12 @@
   if (scoped.IsLost())
     return ScriptValue::CreateNull(script_state);
 
-  DCHECK(query);
-  if (query->IsDeleted() || !query->Validate(nullptr, scoped.Context()) ||
-      current_elapsed_query_ == query) {
-    scoped.Context()->SynthesizeGLError(GL_INVALID_OPERATION,
-                                        "getQueryObjectEXT", "invalid query");
+  if (!scoped.Context()->ValidateWebGLObject("getQueryObjectEXT", query))
+    return ScriptValue::CreateNull(script_state);
+
+  if (current_elapsed_query_ == query) {
+    scoped.Context()->SynthesizeGLError(
+        GL_INVALID_OPERATION, "getQueryObjectEXT", "query is currently active");
     return ScriptValue::CreateNull(script_state);
   }
 
diff --git a/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query_webgl2.cc b/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query_webgl2.cc
index 8eba91c..7e94d11 100644
--- a/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query_webgl2.cc
+++ b/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query_webgl2.cc
@@ -37,13 +37,8 @@
   if (scoped.IsLost())
     return;
 
-  DCHECK(query);
-  if (query->IsDeleted() ||
-      !query->Validate(scoped.Context()->ContextGroup(), scoped.Context())) {
-    scoped.Context()->SynthesizeGLError(GL_INVALID_OPERATION, "queryCounterEXT",
-                                        "invalid query");
+  if (!scoped.Context()->ValidateWebGLObject("queryCounterEXT", query))
     return;
-  }
 
   if (target != GL_TIMESTAMP_EXT) {
     scoped.Context()->SynthesizeGLError(GL_INVALID_ENUM, "queryCounterEXT",
diff --git a/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc b/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc
index 359498a..bdb1fd9 100644
--- a/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc
+++ b/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc
@@ -77,6 +77,8 @@
 
   if (!array_object->HasEverBeenBound())
     return 0;
+  if (array_object->MarkedForDeletion())
+    return 0;
 
   return scoped.Context()->ContextGL()->IsVertexArrayOES(
       array_object->Object());
@@ -88,12 +90,9 @@
   if (scoped.IsLost())
     return;
 
-  if (array_object && (array_object->IsDeleted() ||
-                       !array_object->Validate(nullptr, scoped.Context()))) {
-    scoped.Context()->SynthesizeGLError(
-        GL_INVALID_OPERATION, "bindVertexArrayOES", "invalid arrayObject");
+  if (!scoped.Context()->ValidateNullableWebGLObject(
+          "OESVertexArrayObject.bindVertexArrayOES", array_object))
     return;
-  }
 
   if (array_object && !array_object->IsDefaultObject() &&
       array_object->Object()) {
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
index 25ece46..66d8748a 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
@@ -43,8 +43,8 @@
 // TODO(kainino): Change outByteLength to GLuint and change the associated
 // range checking (and all uses) - overflow becomes possible in cases below
 bool ValidateSubSourceAndGetData(DOMArrayBufferView* view,
-                                 GLuint sub_offset,
-                                 GLuint sub_length,
+                                 long long sub_offset,
+                                 long long sub_length,
                                  void** out_base_address,
                                  long long* out_byte_length) {
   // This is guaranteed to be non-null by DOM.
@@ -245,7 +245,8 @@
                       "srcOffset + length too large");
     return;
   }
-  BufferDataImpl(target, sub_byte_length, sub_base_address, usage);
+  BufferDataImpl(target, static_cast<GLsizeiptr>(sub_byte_length),
+                 sub_base_address, usage);
 }
 
 void WebGL2RenderingContextBase::bufferData(GLenum target,
@@ -269,7 +270,7 @@
 
 void WebGL2RenderingContextBase::bufferSubData(
     GLenum target,
-    GLintptr dst_byte_offset,
+    long long dst_byte_offset,
     MaybeShared<DOMArrayBufferView> src_data,
     GLuint src_offset,
     GLuint length) {
@@ -283,7 +284,8 @@
                       "srcOffset + length too large");
     return;
   }
-  BufferSubDataImpl(target, dst_byte_offset, sub_byte_length, sub_base_address);
+  BufferSubDataImpl(target, dst_byte_offset,
+                    static_cast<GLsizeiptr>(sub_byte_length), sub_base_address);
 }
 
 void WebGL2RenderingContextBase::bufferSubData(GLenum target,
@@ -373,13 +375,14 @@
   }
 
   void* mapped_data = ContextGL()->MapBufferRange(
-      target, static_cast<GLintptr>(src_byte_offset), destination_byte_length,
-      GL_MAP_READ_BIT);
+      target, static_cast<GLintptr>(src_byte_offset),
+      static_cast<GLsizeiptr>(destination_byte_length), GL_MAP_READ_BIT);
 
   if (!mapped_data)
     return;
 
-  memcpy(destination_data_ptr, mapped_data, destination_byte_length);
+  memcpy(destination_data_ptr, mapped_data,
+         static_cast<size_t>(destination_byte_length));
 
   ContextGL()->UnmapBuffer(target);
 }
@@ -439,14 +442,11 @@
                                                          WebGLTexture* texture,
                                                          GLint level,
                                                          GLint layer) {
-  if (isContextLost() || !ValidateFramebufferFuncParameters(
-                             "framebufferTextureLayer", target, attachment))
+  if (isContextLost() ||
+      !ValidateFramebufferFuncParameters("framebufferTextureLayer", target,
+                                         attachment) ||
+      !ValidateNullableWebGLObject("framebufferTextureLayer", texture))
     return;
-  if (texture && !texture->Validate(ContextGroup(), this)) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "framebufferTextureLayer",
-                      "texture does not belong to this context");
-    return;
-  }
   GLenum textarget = texture ? texture->GetTarget() : 0;
   if (texture) {
     if (textarget != GL_TEXTURE_3D && textarget != GL_TEXTURE_2D_ARRAY) {
@@ -1060,7 +1060,7 @@
                                             GLint border,
                                             GLenum format,
                                             GLenum type,
-                                            GLintptr offset) {
+                                            long long offset) {
   if (isContextLost())
     return;
   if (!ValidateTexture2DBinding("texImage2D", target))
@@ -1096,7 +1096,7 @@
                                                GLsizei height,
                                                GLenum format,
                                                GLenum type,
-                                               GLintptr offset) {
+                                               long long offset) {
   if (isContextLost())
     return;
   if (!ValidateTexture2DBinding("texSubImage2D", target))
@@ -1745,7 +1745,7 @@
                                             GLint border,
                                             GLenum format,
                                             GLenum type,
-                                            GLintptr offset) {
+                                            long long offset) {
   if (isContextLost())
     return;
   if (!ValidateTexture3DBinding("texImage3D", target))
@@ -1942,7 +1942,7 @@
                                                GLsizei depth,
                                                GLenum format,
                                                GLenum type,
-                                               GLintptr offset) {
+                                               long long offset) {
   if (isContextLost())
     return;
   if (!ValidateTexture3DBinding("texSubImage3D", target))
@@ -2197,7 +2197,7 @@
                                                       GLsizei height,
                                                       GLint border,
                                                       GLsizei image_size,
-                                                      GLintptr offset) {
+                                                      long long offset) {
   if (isContextLost())
     return;
   if (!bound_pixel_unpack_buffer_) {
@@ -2279,7 +2279,7 @@
                                                          GLsizei height,
                                                          GLenum format,
                                                          GLsizei image_size,
-                                                         GLintptr offset) {
+                                                         long long offset) {
   if (isContextLost())
     return;
   if (!bound_pixel_unpack_buffer_) {
@@ -2341,7 +2341,7 @@
                                                       GLsizei depth,
                                                       GLint border,
                                                       GLsizei image_size,
-                                                      GLintptr offset) {
+                                                      long long offset) {
   if (isContextLost())
     return;
   if (!bound_pixel_unpack_buffer_) {
@@ -2407,7 +2407,7 @@
                                                          GLsizei depth,
                                                          GLenum format,
                                                          GLsizei image_size,
-                                                         GLintptr offset) {
+                                                         long long offset) {
   if (isContextLost())
     return;
   if (!bound_pixel_unpack_buffer_) {
@@ -2422,7 +2422,7 @@
 
 GLint WebGL2RenderingContextBase::getFragDataLocation(WebGLProgram* program,
                                                       const String& name) {
-  if (isContextLost() || !ValidateWebGLObject("getFragDataLocation", program))
+  if (!ValidateWebGLProgramOrShader("getFragDataLocation", program))
     return -1;
 
   return ContextGL()->GetFragDataLocation(ObjectOrZero(program),
@@ -3817,22 +3817,18 @@
 }
 
 GLboolean WebGL2RenderingContextBase::isQuery(WebGLQuery* query) {
-  if (isContextLost() || !query)
+  if (!query || isContextLost() || !query->Validate(ContextGroup(), this))
+    return 0;
+
+  if (query->MarkedForDeletion())
     return 0;
 
   return ContextGL()->IsQueryEXT(query->Object());
 }
 
 void WebGL2RenderingContextBase::beginQuery(GLenum target, WebGLQuery* query) {
-  bool deleted;
-  DCHECK(query);
-  if (!CheckObjectToBeBound("beginQuery", query, deleted))
+  if (!ValidateWebGLObject("beginQuery", query))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "beginQuery",
-                      "attempted to begin a deleted query object");
-    return;
-  }
 
   if (query->GetTarget() && query->GetTarget() != target) {
     SynthesizeGLError(GL_INVALID_OPERATION, "beginQuery",
@@ -3986,15 +3982,8 @@
     ScriptState* script_state,
     WebGLQuery* query,
     GLenum pname) {
-  DCHECK(query);
-  bool deleted;
-  if (!CheckObjectToBeBound("getQueryParameter", query, deleted))
+  if (!ValidateWebGLObject("getQueryParameter", query))
     return ScriptValue::CreateNull(script_state);
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "getQueryParameter",
-                      "attempted to access to a deleted query object");
-    return ScriptValue::CreateNull(script_state);
-  }
 
   // Query is non-null at this point.
   if (!query->GetTarget()) {
@@ -4048,7 +4037,10 @@
 }
 
 GLboolean WebGL2RenderingContextBase::isSampler(WebGLSampler* sampler) {
-  if (isContextLost() || !sampler)
+  if (!sampler || isContextLost() || !sampler->Validate(ContextGroup(), this))
+    return 0;
+
+  if (sampler->MarkedForDeletion())
     return 0;
 
   return ContextGL()->IsSampler(sampler->Object());
@@ -4056,14 +4048,8 @@
 
 void WebGL2RenderingContextBase::bindSampler(GLuint unit,
                                              WebGLSampler* sampler) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindSampler", sampler, deleted))
+  if (!ValidateNullableWebGLObject("bindSampler", sampler))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindSampler",
-                      "attempted to bind a deleted sampler");
-    return;
-  }
 
   if (unit >= sampler_units_.size()) {
     SynthesizeGLError(GL_INVALID_VALUE, "bindSampler",
@@ -4081,7 +4067,7 @@
                                                   GLfloat paramf,
                                                   GLint parami,
                                                   bool is_float) {
-  if (isContextLost() || !ValidateWebGLObject("samplerParameter", sampler))
+  if (!ValidateWebGLObject("samplerParameter", sampler))
     return;
 
   GLint param;
@@ -4191,7 +4177,7 @@
     ScriptState* script_state,
     WebGLSampler* sampler,
     GLenum pname) {
-  if (isContextLost() || !ValidateWebGLObject("getSamplerParameter", sampler))
+  if (!ValidateWebGLObject("getSamplerParameter", sampler))
     return ScriptValue::CreateNull(script_state);
 
   switch (pname) {
@@ -4237,7 +4223,10 @@
 }
 
 GLboolean WebGL2RenderingContextBase::isSync(WebGLSync* sync) {
-  if (isContextLost() || !sync || !sync->Validate(ContextGroup(), this))
+  if (!sync || isContextLost() || !sync->Validate(ContextGroup(), this))
+    return 0;
+
+  if (sync->MarkedForDeletion())
     return 0;
 
   return sync->Object() != 0;
@@ -4250,7 +4239,7 @@
 GLenum WebGL2RenderingContextBase::clientWaitSync(WebGLSync* sync,
                                                   GLbitfield flags,
                                                   GLuint64 timeout) {
-  if (isContextLost() || !ValidateWebGLObject("clientWaitSync", sync))
+  if (!ValidateWebGLObject("clientWaitSync", sync))
     return GL_WAIT_FAILED;
 
   if (timeout > kMaxClientWaitTimeout) {
@@ -4283,7 +4272,7 @@
 void WebGL2RenderingContextBase::waitSync(WebGLSync* sync,
                                           GLbitfield flags,
                                           GLint64 timeout) {
-  if (isContextLost() || !ValidateWebGLObject("waitSync", sync))
+  if (!ValidateWebGLObject("waitSync", sync))
     return;
 
   if (flags) {
@@ -4303,7 +4292,7 @@
     ScriptState* script_state,
     WebGLSync* sync,
     GLenum pname) {
-  if (isContextLost() || !ValidateWebGLObject("getSyncParameter", sync))
+  if (!ValidateWebGLObject("getSyncParameter", sync))
     return ScriptValue::CreateNull(script_state);
 
   switch (pname) {
@@ -4330,34 +4319,44 @@
 
 void WebGL2RenderingContextBase::deleteTransformFeedback(
     WebGLTransformFeedback* feedback) {
+  // We have to short-circuit the deletion process if the transform feedback is
+  // active. This requires duplication of some validation logic.
+  if (!isContextLost() && feedback &&
+      feedback->Validate(ContextGroup(), this)) {
+    if (feedback->active()) {
+      SynthesizeGLError(
+          GL_INVALID_OPERATION, "deleteTransformFeedback",
+          "attempt to delete an active transform feedback object");
+      return;
+    }
+  }
+
+  if (!DeleteObject(feedback))
+    return;
+
   if (feedback == transform_feedback_binding_)
     transform_feedback_binding_ = default_transform_feedback_;
-
-  DeleteObject(feedback);
 }
 
 GLboolean WebGL2RenderingContextBase::isTransformFeedback(
     WebGLTransformFeedback* feedback) {
-  if (isContextLost() || !feedback || !feedback->Validate(ContextGroup(), this))
+  if (!feedback || isContextLost() || !feedback->Validate(ContextGroup(), this))
     return 0;
 
   if (!feedback->HasEverBeenBound())
     return 0;
 
+  if (feedback->MarkedForDeletion())
+    return 0;
+
   return ContextGL()->IsTransformFeedback(feedback->Object());
 }
 
 void WebGL2RenderingContextBase::bindTransformFeedback(
     GLenum target,
     WebGLTransformFeedback* feedback) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindTransformFeedback", feedback, deleted))
+  if (!ValidateNullableWebGLObject("bindTransformFeedback", feedback))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindTransformFeedback",
-                      "attempted to bind a deleted transform feedback object");
-    return;
-  }
 
   if (target != GL_TRANSFORM_FEEDBACK) {
     SynthesizeGLError(GL_INVALID_ENUM, "bindTransformFeedback",
@@ -4443,8 +4442,7 @@
     WebGLProgram* program,
     const Vector<String>& varyings,
     GLenum buffer_mode) {
-  if (isContextLost() ||
-      !ValidateWebGLObject("transformFeedbackVaryings", program))
+  if (!ValidateWebGLProgramOrShader("transformFeedbackVaryings", program))
     return;
 
   switch (buffer_mode) {
@@ -4482,8 +4480,7 @@
 WebGLActiveInfo* WebGL2RenderingContextBase::getTransformFeedbackVarying(
     WebGLProgram* program,
     GLuint index) {
-  if (isContextLost() ||
-      !ValidateWebGLObject("getTransformFeedbackVarying", program))
+  if (!ValidateWebGLProgramOrShader("getTransformFeedbackVarying", program))
     return nullptr;
 
   if (!program->LinkStatus(this)) {
@@ -4600,14 +4597,8 @@
                                                 WebGLBuffer* buffer) {
   if (isContextLost())
     return;
-  bool deleted;
-  if (!CheckObjectToBeBound("bindBufferBase", buffer, deleted))
+  if (!ValidateNullableWebGLObject("bindBufferBase", buffer))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindBufferBase",
-                      "attempt to bind a deleted buffer");
-    return;
-  }
   if (target == GL_TRANSFORM_FEEDBACK_BUFFER &&
       transform_feedback_binding_->active()) {
     SynthesizeGLError(GL_INVALID_OPERATION, "bindBufferBase",
@@ -4628,14 +4619,8 @@
                                                  long long size) {
   if (isContextLost())
     return;
-  bool deleted;
-  if (!CheckObjectToBeBound("bindBufferRange", buffer, deleted))
+  if (!ValidateNullableWebGLObject("bindBufferRange", buffer))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindBufferRange",
-                      "attempt to bind a deleted buffer");
-    return;
-  }
   if (target == GL_TRANSFORM_FEEDBACK_BUFFER &&
       transform_feedback_binding_->active()) {
     SynthesizeGLError(GL_INVALID_OPERATION, "bindBufferBase",
@@ -4742,7 +4727,7 @@
     WebGLProgram* program,
     const Vector<String>& uniform_names) {
   Vector<GLuint> result;
-  if (isContextLost() || !ValidateWebGLObject("getUniformIndices", program))
+  if (!ValidateWebGLProgramOrShader("getUniformIndices", program))
     return result;
 
   Vector<CString> keep_alive;  // Must keep these instances alive while looking
@@ -4764,7 +4749,7 @@
     WebGLProgram* program,
     const Vector<GLuint>& uniform_indices,
     GLenum pname) {
-  if (isContextLost() || !ValidateWebGLObject("getActiveUniforms", program))
+  if (!ValidateWebGLProgramOrShader("getActiveUniforms", program))
     return ScriptValue::CreateNull(script_state);
 
   enum ReturnType { kEnumType, kUnsignedIntType, kIntType, kBoolType };
@@ -4841,7 +4826,7 @@
 GLuint WebGL2RenderingContextBase::getUniformBlockIndex(
     WebGLProgram* program,
     const String& uniform_block_name) {
-  if (isContextLost() || !ValidateWebGLObject("getUniformBlockIndex", program))
+  if (!ValidateWebGLProgramOrShader("getUniformBlockIndex", program))
     return 0;
   if (!ValidateString("getUniformBlockIndex", uniform_block_name))
     return 0;
@@ -4876,8 +4861,7 @@
     WebGLProgram* program,
     GLuint uniform_block_index,
     GLenum pname) {
-  if (isContextLost() ||
-      !ValidateWebGLObject("getActiveUniformBlockParameter", program))
+  if (!ValidateWebGLProgramOrShader("getActiveUniformBlockParameter", program))
     return ScriptValue::CreateNull(script_state);
 
   if (!ValidateUniformBlockIndex("getActiveUniformBlockParameter", program,
@@ -4924,8 +4908,7 @@
 String WebGL2RenderingContextBase::getActiveUniformBlockName(
     WebGLProgram* program,
     GLuint uniform_block_index) {
-  if (isContextLost() ||
-      !ValidateWebGLObject("getActiveUniformBlockName", program))
+  if (!ValidateWebGLProgramOrShader("getActiveUniformBlockName", program))
     return String();
 
   if (!ValidateUniformBlockIndex("getActiveUniformBlockName", program,
@@ -4958,7 +4941,7 @@
     WebGLProgram* program,
     GLuint uniform_block_index,
     GLuint uniform_block_binding) {
-  if (isContextLost() || !ValidateWebGLObject("uniformBlockBinding", program))
+  if (!ValidateWebGLProgramOrShader("uniformBlockBinding", program))
     return;
 
   if (!ValidateUniformBlockIndex("uniformBlockBinding", program,
@@ -4979,8 +4962,16 @@
 
 void WebGL2RenderingContextBase::deleteVertexArray(
     WebGLVertexArrayObject* vertex_array) {
-  if (isContextLost() || !vertex_array ||
-      !ValidateWebGLObject("deleteVertexArray", vertex_array))
+  // ValidateWebGLObject generates an error if the object has already been
+  // deleted, so we must replicate most of its checks here.
+  if (isContextLost() || !vertex_array)
+    return;
+  if (!vertex_array->Validate(ContextGroup(), this)) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "deleteVertexArray",
+                      "object does not belong to this context");
+    return;
+  }
+  if (vertex_array->MarkedForDeletion())
     return;
 
   if (!vertex_array->IsDefaultObject() &&
@@ -4998,20 +4989,16 @@
 
   if (!vertex_array->HasEverBeenBound())
     return 0;
+  if (vertex_array->MarkedForDeletion())
+    return 0;
 
   return ContextGL()->IsVertexArrayOES(vertex_array->Object());
 }
 
 void WebGL2RenderingContextBase::bindVertexArray(
     WebGLVertexArrayObject* vertex_array) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindVertexArray", vertex_array, deleted))
+  if (!ValidateNullableWebGLObject("bindVertexArray", vertex_array))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindVertexArray",
-                      "attempt to bind a deleted vertex array");
-    return;
-  }
 
   if (vertex_array && !vertex_array->IsDefaultObject() &&
       vertex_array->Object()) {
@@ -5027,16 +5014,9 @@
 
 void WebGL2RenderingContextBase::bindFramebuffer(GLenum target,
                                                  WebGLFramebuffer* buffer) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindFramebuffer", buffer, deleted))
+  if (!ValidateNullableWebGLObject("bindFramebuffer", buffer))
     return;
 
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindFramebuffer",
-                      "attempt to bind a deleted framebuffer");
-    return;
-  }
-
   switch (target) {
     case GL_DRAW_FRAMEBUFFER:
       break;
@@ -5992,7 +5972,7 @@
 const char* WebGL2RenderingContextBase::ValidateGetBufferSubData(
     const char* function_name,
     GLenum target,
-    GLintptr source_byte_offset,
+    long long source_byte_offset,
     DOMArrayBufferView* destination_array_buffer_view,
     GLuint destination_offset,
     GLuint length,
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h
index f8e36e4..730aa33 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h
@@ -35,7 +35,7 @@
                   GLuint,
                   GLuint);
   void bufferSubData(GLenum,
-                     GLintptr,
+                     long long offset,
                      MaybeShared<DOMArrayBufferView>,
                      GLuint,
                      GLuint);
@@ -98,7 +98,7 @@
                   GLint,
                   GLenum,
                   GLenum,
-                  GLintptr);
+                  long long);
   void texImage2D(GLenum,
                   GLint,
                   GLint,
@@ -170,7 +170,7 @@
                      GLsizei,
                      GLenum,
                      GLenum,
-                     GLintptr);
+                     long long);
   void texSubImage2D(GLenum,
                      GLint,
                      GLint,
@@ -395,7 +395,7 @@
                   GLint,
                   GLenum,
                   GLenum,
-                  GLintptr);
+                  long long);
   void texSubImage3D(GLenum,
                      GLint,
                      GLint,
@@ -418,7 +418,7 @@
                      GLsizei,
                      GLenum,
                      GLenum,
-                     GLintptr);
+                     long long);
   void texSubImage3D(GLenum,
                      GLint,
                      GLint,
@@ -559,7 +559,7 @@
                             GLsizei height,
                             GLint border,
                             GLsizei image_size,
-                            GLintptr offset);
+                            long long offset);
   void compressedTexSubImage2D(GLenum target,
                                GLint level,
                                GLint xoffset,
@@ -568,7 +568,7 @@
                                GLsizei height,
                                GLenum format,
                                GLsizei image_size,
-                               GLintptr offset);
+                               long long offset);
   void compressedTexImage3D(GLenum target,
                             GLint level,
                             GLenum internalformat,
@@ -577,7 +577,7 @@
                             GLsizei depth,
                             GLint border,
                             GLsizei image_size,
-                            GLintptr offset);
+                            long long offset);
   void compressedTexSubImage3D(GLenum target,
                                GLint level,
                                GLint xoffset,
@@ -588,7 +588,7 @@
                                GLsizei depth,
                                GLenum format,
                                GLsizei image_size,
-                               GLintptr offset);
+                               long long offset);
 
   // Have to re-declare/re-define the following compressedTex{Sub}Image2D
   // functions from the base class. This is because the above
@@ -1088,7 +1088,7 @@
 
   const char* ValidateGetBufferSubData(const char* function_name,
                                        GLenum target,
-                                       GLintptr source_byte_offset,
+                                       long long source_byte_offset,
                                        DOMArrayBufferView*,
                                        GLuint destination_offset,
                                        GLuint length,
diff --git a/third_party/blink/renderer/modules/webgl/webgl_multiview.cc b/third_party/blink/renderer/modules/webgl/webgl_multiview.cc
index 8e36180..0419e82 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_multiview.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_multiview.cc
@@ -33,13 +33,9 @@
   WebGLExtensionScopedContext scoped(this);
   if (scoped.IsLost())
     return;
-  if (texture &&
-      !texture->Validate(scoped.Context()->ContextGroup(), scoped.Context())) {
-    scoped.Context()->SynthesizeGLError(
-        GL_INVALID_OPERATION, "framebufferTextureMultiviewWEBGL",
-        "texture does not belong to this context");
+  if (!scoped.Context()->ValidateNullableWebGLObject(
+          "framebufferTextureMultiviewWEBGL", texture))
     return;
-  }
   GLenum textarget = texture ? texture->GetTarget() : 0;
   if (texture) {
     if (textarget != GL_TEXTURE_2D_ARRAY) {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_object.cc b/third_party/blink/renderer/modules/webgl/webgl_object.cc
index f89c52a..b3db68f 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_object.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_object.cc
@@ -32,7 +32,7 @@
 WebGLObject::WebGLObject(WebGLRenderingContextBase* context)
     : cached_number_of_context_losses_(context->NumberOfContextLosses()),
       attachment_count_(0),
-      deleted_(false),
+      marked_for_deletion_(false),
       destruction_in_progress_(false) {}
 
 WebGLObject::~WebGLObject() = default;
@@ -42,7 +42,7 @@
 }
 
 void WebGLObject::DeleteObject(gpu::gles2::GLES2Interface* gl) {
-  deleted_ = true;
+  marked_for_deletion_ = true;
   if (!HasObject())
     return;
 
@@ -66,7 +66,7 @@
 }
 
 void WebGLObject::Detach() {
-  attachment_count_ = 0;  // Make sure OpenGL resource is deleted.
+  attachment_count_ = 0;  // Make sure OpenGL resource is eventually deleted.
 }
 
 void WebGLObject::DetachAndDeleteObject() {
@@ -92,7 +92,7 @@
 void WebGLObject::OnDetached(gpu::gles2::GLES2Interface* gl) {
   if (attachment_count_)
     --attachment_count_;
-  if (deleted_)
+  if (marked_for_deletion_)
     DeleteObject(gl);
 }
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_object.h b/third_party/blink/renderer/modules/webgl/webgl_object.h
index 0fc06c6b..08f3ccb 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_object.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_object.h
@@ -75,10 +75,10 @@
   void OnAttached() { ++attachment_count_; }
   void OnDetached(gpu::gles2::GLES2Interface*);
 
-  // This indicates whether the client side issue a delete call already, not
-  // whether the OpenGL resource is deleted.
-  // object()==0 indicates the OpenGL resource is deleted.
-  bool IsDeleted() { return deleted_; }
+  // This indicates whether the client side has already issued a delete call,
+  // not whether the OpenGL resource is deleted. Object()==0, or !HasObject(),
+  // indicates that the OpenGL resource has been deleted.
+  bool MarkedForDeletion() { return marked_for_deletion_; }
 
   // True if this object belongs to the group or context.
   virtual bool Validate(const WebGLContextGroup*,
@@ -130,9 +130,11 @@
 
   unsigned attachment_count_;
 
-  // Indicates whether the WebGL context's deletion function for this
-  // object (deleteBuffer, deleteTexture, etc.) has been called.
-  bool deleted_;
+  // Indicates whether the WebGL context's deletion function for this object
+  // (deleteBuffer, deleteTexture, etc.) has been called. It does *not* indicate
+  // whether the underlying OpenGL resource has been destroyed; !HasObject()
+  // indicates that.
+  bool marked_for_deletion_;
 
   // Indicates whether the destructor has been entered and we therefore
   // need to be careful in subclasses to not touch other on-heap objects.
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index 0ed3994..5332c24 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -1704,8 +1704,8 @@
 
 void WebGLRenderingContextBase::attachShader(WebGLProgram* program,
                                              WebGLShader* shader) {
-  if (isContextLost() || !ValidateWebGLObject("attachShader", program) ||
-      !ValidateWebGLObject("attachShader", shader))
+  if (!ValidateWebGLProgramOrShader("attachShader", program) ||
+      !ValidateWebGLProgramOrShader("attachShader", shader))
     return;
   if (!program->AttachShader(shader)) {
     SynthesizeGLError(GL_INVALID_OPERATION, "attachShader",
@@ -1719,7 +1719,7 @@
 void WebGLRenderingContextBase::bindAttribLocation(WebGLProgram* program,
                                                    GLuint index,
                                                    const String& name) {
-  if (isContextLost() || !ValidateWebGLObject("bindAttribLocation", program))
+  if (!ValidateWebGLObject("bindAttribLocation", program))
     return;
   if (!ValidateLocationLength("bindAttribLocation", name))
     return;
@@ -1732,23 +1732,6 @@
                                   name.Utf8().data());
 }
 
-bool WebGLRenderingContextBase::CheckObjectToBeBound(const char* function_name,
-                                                     WebGLObject* object,
-                                                     bool& deleted) {
-  deleted = false;
-  if (isContextLost())
-    return false;
-  if (object) {
-    if (!object->Validate(ContextGroup(), this)) {
-      SynthesizeGLError(GL_INVALID_OPERATION, function_name,
-                        "object not from this context");
-      return false;
-    }
-    deleted = object->IsDeleted();
-  }
-  return true;
-}
-
 bool WebGLRenderingContextBase::ValidateAndUpdateBufferBindTarget(
     const char* function_name,
     GLenum target,
@@ -1781,14 +1764,8 @@
 }
 
 void WebGLRenderingContextBase::bindBuffer(GLenum target, WebGLBuffer* buffer) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindBuffer", buffer, deleted))
+  if (!ValidateNullableWebGLObject("bindBuffer", buffer))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindBuffer",
-                      "attempt to bind a deleted buffer");
-    return;
-  }
   if (!ValidateAndUpdateBufferBindTarget("bindBuffer", target, buffer))
     return;
   ContextGL()->BindBuffer(target, ObjectOrZero(buffer));
@@ -1796,14 +1773,8 @@
 
 void WebGLRenderingContextBase::bindFramebuffer(GLenum target,
                                                 WebGLFramebuffer* buffer) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindFramebuffer", buffer, deleted))
+  if (!ValidateNullableWebGLObject("bindFramebuffer", buffer))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindFramebuffer",
-                      "attempt to bind a deleted framebuffer");
-    return;
-  }
 
   if (target != GL_FRAMEBUFFER) {
     SynthesizeGLError(GL_INVALID_ENUM, "bindFramebuffer", "invalid target");
@@ -1816,14 +1787,8 @@
 void WebGLRenderingContextBase::bindRenderbuffer(
     GLenum target,
     WebGLRenderbuffer* render_buffer) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindRenderbuffer", render_buffer, deleted))
+  if (!ValidateNullableWebGLObject("bindRenderbuffer", render_buffer))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindRenderbuffer",
-                      "attempt to bind a deleted renderbuffer");
-    return;
-  }
   if (target != GL_RENDERBUFFER) {
     SynthesizeGLError(GL_INVALID_ENUM, "bindRenderbuffer", "invalid target");
     return;
@@ -1836,14 +1801,8 @@
 
 void WebGLRenderingContextBase::bindTexture(GLenum target,
                                             WebGLTexture* texture) {
-  bool deleted;
-  if (!CheckObjectToBeBound("bindTexture", texture, deleted))
+  if (!ValidateNullableWebGLObject("bindTexture", texture))
     return;
-  if (deleted) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "bindTexture",
-                      "attempt to bind a deleted texture");
-    return;
-  }
   if (texture && texture->GetTarget() && texture->GetTarget() != target) {
     SynthesizeGLError(GL_INVALID_OPERATION, "bindTexture",
                       "textures can not be used with multiple targets");
@@ -2145,7 +2104,7 @@
 }
 
 void WebGLRenderingContextBase::compileShader(WebGLShader* shader) {
-  if (isContextLost() || !ValidateWebGLObject("compileShader", shader))
+  if (!ValidateWebGLProgramOrShader("compileShader", shader))
     return;
   ContextGL()->CompileShader(ObjectOrZero(shader));
 }
@@ -2358,6 +2317,11 @@
                       "object does not belong to this context");
     return false;
   }
+  if (object->MarkedForDeletion()) {
+    // This is specified to be a no-op, including skipping all unbinding from
+    // the context's attachment points that would otherwise happen.
+    return false;
+  }
   if (object->HasObject()) {
     // We need to pass in context here because we want
     // things in this context unbound.
@@ -2481,8 +2445,8 @@
 
 void WebGLRenderingContextBase::detachShader(WebGLProgram* program,
                                              WebGLShader* shader) {
-  if (isContextLost() || !ValidateWebGLObject("detachShader", program) ||
-      !ValidateWebGLObject("detachShader", shader))
+  if (!ValidateWebGLProgramOrShader("detachShader", program) ||
+      !ValidateWebGLProgramOrShader("detachShader", shader))
     return;
   if (!program->DetachShader(shader)) {
     SynthesizeGLError(GL_INVALID_OPERATION, "detachShader",
@@ -2531,12 +2495,55 @@
   return true;
 }
 
+bool WebGLRenderingContextBase::ValidateNullableWebGLObject(
+    const char* function_name,
+    WebGLObject* object) {
+  if (isContextLost())
+    return false;
+  if (!object) {
+    // This differs in behavior to ValidateWebGLObject; null objects are allowed
+    // in these entry points.
+    return true;
+  }
+  return ValidateWebGLObject(function_name, object);
+}
+
 bool WebGLRenderingContextBase::ValidateWebGLObject(const char* function_name,
                                                     WebGLObject* object) {
+  if (isContextLost())
+    return false;
   DCHECK(object);
+  if (object->MarkedForDeletion()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, function_name,
+                      "attempt to use a deleted object");
+    return false;
+  }
+  if (!object->Validate(ContextGroup(), this)) {
+    SynthesizeGLError(GL_INVALID_OPERATION, function_name,
+                      "object does not belong to this context");
+    return false;
+  }
+  return true;
+}
+
+bool WebGLRenderingContextBase::ValidateWebGLProgramOrShader(
+    const char* function_name,
+    WebGLObject* object) {
+  if (isContextLost())
+    return false;
+  DCHECK(object);
+  // OpenGL ES 3.0.5 p. 45:
+  // "Commands that accept shader or program object names will generate the
+  // error INVALID_VALUE if the provided name is not the name of either a shader
+  // or program object and INVALID_OPERATION if the provided name identifies an
+  // object that is not the expected type."
+  //
+  // Programs and shaders also have slightly different lifetime rules than other
+  // objects in the API; they continue to be usable after being marked for
+  // deletion.
   if (!object->HasObject()) {
     SynthesizeGLError(GL_INVALID_VALUE, function_name,
-                      "no object or object deleted");
+                      "attempt to use a deleted object");
     return false;
   }
   if (!object->Validate(ContextGroup(), this)) {
@@ -2678,10 +2685,11 @@
                       "invalid target");
     return;
   }
-  if (buffer && (!buffer->HasEverBeenBound() ||
-                 !buffer->Validate(ContextGroup(), this))) {
+  if (!ValidateNullableWebGLObject("framebufferRenderbuffer", buffer))
+    return;
+  if (buffer && (!buffer->HasEverBeenBound())) {
     SynthesizeGLError(GL_INVALID_OPERATION, "framebufferRenderbuffer",
-                      "buffer never bound or buffer not from this context");
+                      "renderbuffer has never been bound");
     return;
   }
   // Don't allow the default framebuffer to be mutated; all current
@@ -2712,11 +2720,10 @@
   if (isContextLost() || !ValidateFramebufferFuncParameters(
                              "framebufferTexture2D", target, attachment))
     return;
-  if (texture && !texture->Validate(ContextGroup(), this)) {
-    SynthesizeGLError(GL_INVALID_OPERATION, "framebufferTexture2D",
-                      "no texture or texture not from this context");
+  if (!ValidateNullableWebGLObject("framebufferTexture2D", texture))
     return;
-  }
+  // TODO(crbug.com/919711): validate texture's target against textarget.
+
   // Don't allow the default framebuffer to be mutated; all current
   // implementations use an FBO internally in place of the default
   // FBO.
@@ -2754,7 +2761,7 @@
 WebGLActiveInfo* WebGLRenderingContextBase::getActiveAttrib(
     WebGLProgram* program,
     GLuint index) {
-  if (isContextLost() || !ValidateWebGLObject("getActiveAttrib", program))
+  if (!ValidateWebGLProgramOrShader("getActiveAttrib", program))
     return nullptr;
   GLuint program_id = ObjectNonZero(program);
   GLint max_name_length = -1;
@@ -2784,7 +2791,7 @@
 WebGLActiveInfo* WebGLRenderingContextBase::getActiveUniform(
     WebGLProgram* program,
     GLuint index) {
-  if (isContextLost() || !ValidateWebGLObject("getActiveUniform", program))
+  if (!ValidateWebGLProgramOrShader("getActiveUniform", program))
     return nullptr;
   GLuint program_id = ObjectNonZero(program);
   GLint max_name_length = -1;
@@ -2813,7 +2820,7 @@
 
 base::Optional<HeapVector<Member<WebGLShader>>>
 WebGLRenderingContextBase::getAttachedShaders(WebGLProgram* program) {
-  if (isContextLost() || !ValidateWebGLObject("getAttachedShaders", program))
+  if (!ValidateWebGLProgramOrShader("getAttachedShaders", program))
     return base::nullopt;
 
   HeapVector<Member<WebGLShader>> shader_objects;
@@ -2829,7 +2836,7 @@
 
 GLint WebGLRenderingContextBase::getAttribLocation(WebGLProgram* program,
                                                    const String& name) {
-  if (isContextLost() || !ValidateWebGLObject("getAttribLocation", program))
+  if (!ValidateWebGLProgramOrShader("getAttribLocation", program))
     return -1;
   if (!ValidateLocationLength("getAttribLocation", name))
     return -1;
@@ -3385,13 +3392,14 @@
     ScriptState* script_state,
     WebGLProgram* program,
     GLenum pname) {
-  if (isContextLost() || !ValidateWebGLObject("getProgramParameter", program))
+  if (!ValidateWebGLProgramOrShader("getProgramParamter", program)) {
     return ScriptValue::CreateNull(script_state);
+  }
 
   GLint value = 0;
   switch (pname) {
     case GL_DELETE_STATUS:
-      return WebGLAny(script_state, program->IsDeleted());
+      return WebGLAny(script_state, program->MarkedForDeletion());
     case GL_VALIDATE_STATUS:
       ContextGL()->GetProgramiv(ObjectOrZero(program), pname, &value);
       return WebGLAny(script_state, static_cast<bool>(value));
@@ -3432,7 +3440,7 @@
 }
 
 String WebGLRenderingContextBase::getProgramInfoLog(WebGLProgram* program) {
-  if (isContextLost() || !ValidateWebGLObject("getProgramInfoLog", program))
+  if (!ValidateWebGLProgramOrShader("getProgramInfoLog", program))
     return String();
   GLStringQuery query(ContextGL());
   return query.Run<GLStringQuery::ProgramInfoLog>(ObjectNonZero(program));
@@ -3489,12 +3497,13 @@
     ScriptState* script_state,
     WebGLShader* shader,
     GLenum pname) {
-  if (isContextLost() || !ValidateWebGLObject("getShaderParameter", shader))
+  if (!ValidateWebGLProgramOrShader("getShaderParameter", shader)) {
     return ScriptValue::CreateNull(script_state);
+  }
   GLint value = 0;
   switch (pname) {
     case GL_DELETE_STATUS:
-      return WebGLAny(script_state, shader->IsDeleted());
+      return WebGLAny(script_state, shader->MarkedForDeletion());
     case GL_COMPILE_STATUS:
       ContextGL()->GetShaderiv(ObjectOrZero(shader), pname, &value);
       return WebGLAny(script_state, static_cast<bool>(value));
@@ -3517,7 +3526,7 @@
 }
 
 String WebGLRenderingContextBase::getShaderInfoLog(WebGLShader* shader) {
-  if (isContextLost() || !ValidateWebGLObject("getShaderInfoLog", shader))
+  if (!ValidateWebGLProgramOrShader("getShaderInfoLog", shader))
     return String();
   GLStringQuery query(ContextGL());
   return query.Run<GLStringQuery::ShaderInfoLog>(ObjectNonZero(shader));
@@ -3553,7 +3562,7 @@
 }
 
 String WebGLRenderingContextBase::getShaderSource(WebGLShader* shader) {
-  if (isContextLost() || !ValidateWebGLObject("getShaderSource", shader))
+  if (!ValidateWebGLProgramOrShader("getShaderSource", shader))
     return String();
   return EnsureNotNull(shader->Source());
 }
@@ -3616,7 +3625,7 @@
     ScriptState* script_state,
     WebGLProgram* program,
     const WebGLUniformLocation* uniform_location) {
-  if (isContextLost() || !ValidateWebGLObject("getUniform", program))
+  if (!ValidateWebGLProgramOrShader("getUniform", program))
     return ScriptValue::CreateNull(script_state);
   DCHECK(uniform_location);
   if (uniform_location->Program() != program) {
@@ -3886,7 +3895,7 @@
 WebGLUniformLocation* WebGLRenderingContextBase::getUniformLocation(
     WebGLProgram* program,
     const String& name) {
-  if (isContextLost() || !ValidateWebGLObject("getUniformLocation", program))
+  if (!ValidateWebGLProgramOrShader("getUniformLocation", program))
     return nullptr;
   if (!ValidateLocationLength("getUniformLocation", name))
     return nullptr;
@@ -4022,7 +4031,7 @@
 
   if (!buffer->HasEverBeenBound())
     return 0;
-  if (buffer->IsDeleted())
+  if (buffer->MarkedForDeletion())
     return 0;
 
   return ContextGL()->IsBuffer(buffer->Object());
@@ -4048,7 +4057,7 @@
 
   if (!framebuffer->HasEverBeenBound())
     return 0;
-  if (framebuffer->IsDeleted())
+  if (framebuffer->MarkedForDeletion())
     return 0;
 
   return ContextGL()->IsFramebuffer(framebuffer->Object());
@@ -4058,6 +4067,10 @@
   if (!program || isContextLost() || !program->Validate(ContextGroup(), this))
     return 0;
 
+  // OpenGL ES special-cases the behavior of program objects; if they're deleted
+  // while attached to the current context state, glIsProgram is supposed to
+  // still return true. For this reason, MarkedForDeletion is not checked here.
+
   return ContextGL()->IsProgram(program->Object());
 }
 
@@ -4069,7 +4082,7 @@
 
   if (!renderbuffer->HasEverBeenBound())
     return 0;
-  if (renderbuffer->IsDeleted())
+  if (renderbuffer->MarkedForDeletion())
     return 0;
 
   return ContextGL()->IsRenderbuffer(renderbuffer->Object());
@@ -4079,6 +4092,10 @@
   if (!shader || isContextLost() || !shader->Validate(ContextGroup(), this))
     return 0;
 
+  // OpenGL ES special-cases the behavior of shader objects; if they're deleted
+  // while attached to a program, glIsShader is supposed to still return true.
+  // For this reason, MarkedForDeletion is not checked here.
+
   return ContextGL()->IsShader(shader->Object());
 }
 
@@ -4088,7 +4105,7 @@
 
   if (!texture->HasEverBeenBound())
     return 0;
-  if (texture->IsDeleted())
+  if (texture->MarkedForDeletion())
     return 0;
 
   return ContextGL()->IsTexture(texture->Object());
@@ -4101,7 +4118,7 @@
 }
 
 void WebGLRenderingContextBase::linkProgram(WebGLProgram* program) {
-  if (isContextLost() || !ValidateWebGLObject("linkProgram", program))
+  if (!ValidateWebGLProgramOrShader("linkProgram", program))
     return;
 
   if (program->ActiveTransformFeedbackCount() > 0) {
@@ -4446,7 +4463,7 @@
 
 void WebGLRenderingContextBase::shaderSource(WebGLShader* shader,
                                              const String& string) {
-  if (isContextLost() || !ValidateWebGLObject("shaderSource", shader))
+  if (!ValidateWebGLProgramOrShader("shaderSource", shader))
     return;
   String string_without_comments = StripComments(string).Result();
   // TODO(danakj): Make validateShaderSource reject characters > 255 (or utf16
@@ -6225,11 +6242,8 @@
 }
 
 void WebGLRenderingContextBase::useProgram(WebGLProgram* program) {
-  bool deleted;
-  if (!CheckObjectToBeBound("useProgram", program, deleted))
+  if (!ValidateNullableWebGLObject("useProgram", program))
     return;
-  if (deleted)
-    program = nullptr;
   if (program && !program->LinkStatus(this)) {
     SynthesizeGLError(GL_INVALID_OPERATION, "useProgram", "program not valid");
     return;
@@ -6246,7 +6260,7 @@
 }
 
 void WebGLRenderingContextBase::validateProgram(WebGLProgram* program) {
-  if (isContextLost() || !ValidateWebGLObject("validateProgram", program))
+  if (!ValidateWebGLProgramOrShader("validateProgram", program))
     return;
   ContextGL()->ValidateProgram(ObjectOrZero(program));
 }
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index 7b6d15a7..a77c320 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -688,7 +688,23 @@
   // Check if each enabled vertex attribute is bound to a buffer.
   bool ValidateRenderingState(const char*);
 
-  bool ValidateWebGLObject(const char*, WebGLObject*);
+  // Helper function for APIs which can legally receive null objects, including
+  // the bind* calls (bindBuffer, bindTexture, etc.) and useProgram. Checks that
+  // the object belongs to this context and that it's not marked for deletion.
+  // Returns false if the caller should return without further processing.
+  // Performs a context loss check internally.
+  // This returns true for null WebGLObject arguments!
+  bool ValidateNullableWebGLObject(const char* function_name, WebGLObject*);
+
+  // Validates the incoming WebGL object, which is assumed to be non-null.
+  // Checks that the object belongs to this context and that it's not marked for
+  // deletion. Performs a context loss check internally.
+  bool ValidateWebGLObject(const char* function_name, WebGLObject*);
+
+  // Validates the incoming WebGL program or shader, which is assumed to be
+  // non-null. OpenGL ES's validation rules differ for these types of objetcts
+  // compared to others. Performs a context loss check internally.
+  bool ValidateWebGLProgramOrShader(const char* function_name, WebGLObject*);
 
   // Adds a compressed texture format.
   void AddCompressedTextureFormat(GLenum);
@@ -1496,13 +1512,6 @@
   // Return false if caller should return without further processing.
   bool DeleteObject(WebGLObject*);
 
-  // Helper function for bind* (bindBuffer, bindTexture, etc) and useProgram.
-  // If the object has already been deleted, set deleted to true upon return.
-  // Return false if caller should return without further processing.
-  bool CheckObjectToBeBound(const char* function_name,
-                            WebGLObject*,
-                            bool& deleted);
-
   void DispatchContextLostEvent(TimerBase*);
   // Helper for restoration after context lost.
   void MaybeRestoreContext(TimerBase*);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.cc b/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.cc
index 316d989c..2024fbf 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.cc
@@ -13,9 +13,10 @@
     : WebGLSharedObject(ctx), object_(0) {}
 
 void WebGLSharedPlatform3DObject::SetObject(GLuint object) {
-  // object==0 && deleted==false indicating an uninitialized state;
+  // SetObject may only be called when this container is in the
+  // uninitialized state: object==0 && marked_for_deletion==false.
   DCHECK(!object_);
-  DCHECK(!IsDeleted());
+  DCHECK(!MarkedForDeletion());
   object_ = object;
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
index 1106cd9..7b3cb90 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
@@ -247,16 +247,6 @@
     return task_runner_;
   }
 
-  // TODO(altimin): This is used when creating a URLLoader, and
-  // FetchContext::GetLoadingTaskRunner is used whenever asynchronous tasks
-  // around resource loading are posted. Modify the code so that all
-  // the tasks related to loading a resource use the resource loader handle's
-  // task runner.
-  virtual std::unique_ptr<blink::scheduler::WebResourceLoadingTaskRunnerHandle>
-  CreateResourceLoadingTaskRunnerHandle() {
-    return nullptr;
-  }
-
   // Called when the underlying context is detached. Note that some
   // FetchContexts continue working after detached (e.g., for fetch() operations
   // with "keepalive" specified).
diff --git a/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h b/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h
index 8ba84ca..6f8f648 100644
--- a/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h
@@ -115,7 +115,9 @@
     }
     WrappedResourceRequest wrapped(request);
     return url_loader_factory_->CreateURLLoader(
-        wrapped, CreateResourceLoadingTaskRunnerHandle());
+        wrapped,
+        scheduler::WebResourceLoadingTaskRunnerHandle::CreateUnprioritized(
+            GetLoadingTaskRunner()));
   }
 
   ResourceLoadScheduler::ThrottlingPolicy InitialLoadThrottlingPolicy()
@@ -127,12 +129,6 @@
     return frame_scheduler_.get();
   }
 
-  std::unique_ptr<blink::scheduler::WebResourceLoadingTaskRunnerHandle>
-  CreateResourceLoadingTaskRunnerHandle() override {
-    return scheduler::WebResourceLoadingTaskRunnerHandle::CreateUnprioritized(
-        GetLoadingTaskRunner());
-  }
-
  private:
   class MockFrameScheduler final : public scheduler::FakeFrameScheduler {
    public:
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
index f8b0236..f77a636 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
@@ -60,10 +60,10 @@
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html [ Pass ]
+crbug.com/613663 external/wpt/quirks/table-cell-width-calculation.html [ Pass ]
 
 # New failures are appended below by the script.
 crbug.com/728378 compositing/culling/tile-occlusion-boundaries.html [ Failure ]
-crbug.com/591099 compositing/overflow/border-radius-above-composited-subframe.html [ Failure ]
 crbug.com/591099 css3/filters/backdrop-filter-rendering.html [ Pass ]
 crbug.com/591099 css3/filters/composited-layer-child-bounds-after-composited-to-sw-shadow-change.html [ Failure ]
 crbug.com/591099 css3/flexbox/intrinsic-width-orthogonal-writing-mode.html [ Failure ]
@@ -199,10 +199,6 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/line-box-height-vlr-023.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/ortho-htb-alongside-vrl-floats-006.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/ortho-htb-alongside-vrl-floats-014.xht [ Pass ]
-crbug.com/591099 external/wpt/css/css-writing-modes/orthogonal-parent-shrink-to-fit-001q.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/orthogonal-parent-shrink-to-fit-001r.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/orthogonal-parent-shrink-to-fit-001s.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/orthogonal-parent-shrink-to-fit-001t.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-htb-in-vlr-001.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-prct-vrl-in-htb-001.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-vrl-in-htb-001.xht [ Pass ]
@@ -318,7 +314,7 @@
 crbug.com/591099 virtual/bidi-caret-affinity/editing/selection/select-text-overflow-ellipsis-mixed-in-rtl.html [ Crash ]
 crbug.com/591099 virtual/bidi-caret-affinity/editing/selection/select-text-overflow-ellipsis.html [ Crash ]
 crbug.com/591099 virtual/bidi-caret-affinity/editing/selection/word-granularity.html [ Crash ]
-crbug.com/916511 virtual/composite-after-paint/paint/background/scrolling-background-with-negative-z-child.html [ Failure ]
+crbug.com/916511 virtual/composite-after-paint/paint/background/scrolling-background-with-negative-z-child.html [ Failure Crash ]
 crbug.com/591099 virtual/composite-after-paint/paint/invalidation/box/margin.html [ Failure Pass ]
 crbug.com/591099 virtual/display-lock/display-lock/lock-before-append/acquire-update-measure-remove.html [ Failure ]
 crbug.com/591099 virtual/display-lock/display-lock/lock-before-append/measure-forced-layout.html [ Failure ]
diff --git a/third_party/blink/web_tests/PRESUBMIT.py b/third_party/blink/web_tests/PRESUBMIT.py
index c527a4dd..39cb587 100644
--- a/third_party/blink/web_tests/PRESUBMIT.py
+++ b/third_party/blink/web_tests/PRESUBMIT.py
@@ -24,7 +24,7 @@
         return []
 
     checker_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
-        '..', '..', 'blink', 'tools', 'check_testharness_expected_pass.py')
+        '..', 'tools', 'check_testharness_expected_pass.py')
 
     args = [input_api.python_executable, checker_path]
     args.extend(baseline_files)
@@ -75,7 +75,7 @@
 
 def _CheckTestExpectations(input_api, output_api):
     lint_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
-        '..', '..', 'blink', 'tools', 'lint_test_expectations.py')
+        '..', 'tools', 'lint_test_expectations.py')
     _, errs = input_api.subprocess.Popen(
         [input_api.python_executable, lint_path],
         stdout=input_api.subprocess.PIPE,
@@ -125,6 +125,22 @@
                 results.append(output_api.PresubmitError('Found an invalid preference %s in expected result %s:%s' % (error.group(1), f, line_num)))
     return results
 
+def _CheckRunAfterLayoutAndPaintJS(input_api, output_api):
+    """Checks if resources/run-after-layout-and-paint.js and
+       http/tests/resources/run-after-layout-and-paint.js are the same."""
+    js_file = input_api.os_path.join(input_api.PresubmitLocalPath(),
+        'resources', 'run-after-layout-and-paint.js')
+    http_tests_js_file = input_api.os_path.join(input_api.PresubmitLocalPath(),
+        'http', 'tests', 'resources', 'run-after-layout-and-paint.js')
+    for f in input_api.AffectedFiles():
+        path = f.AbsoluteLocalPath()
+        if path == js_file or path == http_tests_js_file:
+            if not filecmp.cmp(js_file, http_tests_js_file):
+                return [output_api.PresubmitError(
+                    '%s and %s must be kept exactly the same' %
+                    (js_file, http_tests_js_file))]
+            break
+    return []
 
 def CheckChangeOnUpload(input_api, output_api):
     results = []
@@ -133,6 +149,7 @@
     results.extend(_CheckTestExpectations(input_api, output_api))
     results.extend(_CheckForJSTest(input_api, output_api))
     results.extend(_CheckForInvalidPreferenceError(input_api, output_api))
+    results.extend(_CheckRunAfterLayoutAndPaintJS(input_api, output_api))
     return results
 
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 480c7f9f..9793aa5b 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -394,10 +394,6 @@
 crbug.com/882975 virtual/threaded/fast/events/pinch/scroll-visual-viewport-send-boundary-events.html [ Failure Pass ]
 ### See crbug.com/891427 comment near the top of this file:
 ###crbug.com/882975 virtual/threaded/synthetic_gestures/synthetic-pinch-zoom-gesture-touchscreen-desktop.html [ Failure Pass ]
-crbug.com/911664 fast/events/touch/compositor-touch-hit-rects-non-composited-scroll.html [ Failure ]
-crbug.com/911664 virtual/scroll_customization/fast/events/touch/compositor-touch-hit-rects-non-composited-scroll.html [ Failure ]
-crbug.com/911664 virtual/user-activation-v2/fast/events/touch/compositor-touch-hit-rects-non-composited-scroll.html [ Failure ]
-crbug.com/911664 virtual/mouseevent_fractional/fast/events/touch/compositor-touch-hit-rects-non-composited-scroll.html [ Failure ]
 crbug.com/882973 http/tests/devtools/layers/layer-tree-model.js [ Failure ]
 crbug.com/884239 virtual/threaded/animations/animationworklet/worklet-animation-local-time-undefined.html [ Failure ]
 # Subpixel rounding differences that are incorrect.
@@ -1525,10 +1521,8 @@
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/columns-auto-size.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/columns-height-set-via-top-bottom.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/content-height-with-scrollbars.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/crash-removing-out-of-flow-child.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/cross-axis-scrollbar.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/display-flexbox-set-get.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-align-baseline.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-align-baseline.html [ Crash ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-align-column.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-align-end.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-align-vertical-writing-mode.html [ Failure ]
@@ -1538,48 +1532,36 @@
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-auto-margins-no-available-space.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-auto-margins.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-border.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-margins-auto-size.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-margins-auto-size.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-margins.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-orientations.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-overflow.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow-padding.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-flow.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-item-contains-strict.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-one-sets-flex-basis-to-zero-px.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flex-order.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-baseline-margins.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-baseline.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-height-with-overflow-auto.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-ignore-firstLetter.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-baseline-margins.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-baseline.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-height-with-overflow-auto.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-lines-must-be-stretched-by-default.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-overflow-auto.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexbox-width-with-overflow-auto.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/flexitem.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/float-inside-flexitem.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/floated-flexbox.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/inline-flexbox-ignore-firstLine.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/inline-flexbox-wrap-vertically-width-calculation.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/intrinsic-min-width-applies-with-fixed-width.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/intrinsic-width-orthogonal-writing-mode.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/line-wrapping.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/min-size-auto.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/inline-flexbox-wrap-vertically-width-calculation.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/line-wrapping.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/min-size-auto.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/minimum-size-image.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-align-content-horizontal-column.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-align-self.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-align-self.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-column-auto.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-column-overflow.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-justify-content.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-justify-content.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-min-max.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-min-preferred-width.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-reverse-wrap-baseline.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-reverse-wrap-overflow.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-shrink-to-fit.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/negative-flex-rounding-assert.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline-shrink-to-fit.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/negative-overflow.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/nested-stretch.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/order-painting.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/orthogonal-flex-directions.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/orthogonal-writing-modes-and-intrinsic-sizing.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/overflow-and-padding.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/overflow-auto-resizes-correctly.html [ Failure ]
@@ -1587,14 +1569,14 @@
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/percentage-height-replaced-element.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/percentage-heights.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/percentage-sizes-quirks.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/position-absolute-child.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/preferred-widths-orthogonal.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/preferred-widths.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/position-absolute-child.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/preferred-widths-orthogonal.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/preferred-widths.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/relayout-align-items.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/relayout-image-load.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/relpos-with-percentage-top.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/scrollbars-auto.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/scrollbars.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/scrollbars-auto.html [ Crash ]
+crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/scrollbars.html [ Crash ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/stretch-input-in-column.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/stretched-child-shrink-on-relayout.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/stretching-orthogonal-flows.html [ Failure ]
@@ -1614,7 +1596,7 @@
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-004.htm [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-005.htm [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-006.htm [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-wrap-001.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-wrap-001.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-wrap-002.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-wrap-003.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/align-content-wrap-004.html [ Failure ]
@@ -1632,19 +1614,15 @@
 crbug.com/807497 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/anonymous-flex-item-005.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/anonymous-flex-item-006.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/auto-margins-001.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-reverse-wrap-reverse.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-reverse-wrap.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-reverse.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-wrap-reverse.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-wrap.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-reverse-wrap-reverse.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-reverse-wrap.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-reverse.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-wrap-reverse.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column-wrap.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-column.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-row-reverse-wrap-reverse.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-row-reverse.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-row-wrap-reverse.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-row.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/css-flexbox-test1.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/display_inline-flex_exist.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-align-items-center.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-align-items-center.html [ Failure ]
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-aspect-ratio-img-column-001.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-aspect-ratio-img-row-002.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-aspect-ratio-img-row-003.html [ Failure ]
@@ -1658,8 +1636,8 @@
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-flow-010.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-flow-011.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-flow-012.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-items-flexibility.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-margin-no-collapse.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-items-flexibility.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-margin-no-collapse.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-001.xht [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-002.xht [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-003.xht [ Failure ]
@@ -1715,30 +1693,28 @@
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_flow-column-wrap-reverse.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_flow-column-wrap.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_flow-row-wrap-reverse.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_inline-abspos.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_inline-float.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_inline.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_inline-abspos.html [ Failure ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_inline.html [ Failure ]
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_margin-collapse.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_order-box.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_order.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_quirks_body.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-direction.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-flow-reverse.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-flow.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-order.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-direction.html [ Crash ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-flow-reverse.html [ Crash ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-flow.html [ Crash ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_rtl-order.html [ Crash ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_stf-table-singleline-2.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_stf-table-singleline.html [ Failure ]
 crbug.com/336604 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_visibility-collapse-line-wrapping.html [ Failure ]
 crbug.com/336604 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_visibility-collapse.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flexbox_wrap-reverse.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/layout-algorithm_algo-cross-line-002.html [ Failure ]
-crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/negative-margins-001.html [ Skip ]
+crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/negative-margins-001.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/order_value.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/percentage-heights-000.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/percentage-heights-002.html [ Failure ]
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/percentage-heights-003.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/percentage-heights-004.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/percentage-heights-quirks-node.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/position-absolute-001.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/position-absolute-002.html [ Failure ]
@@ -1749,7 +1725,7 @@
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/ttwf-reftest-flex-align-content-space-between.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/ttwf-reftest-flex-direction-column-reverse.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/ttwf-reftest-flex-direction-column.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/ttwf-reftest-flex-inline.html [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/ttwf-reftest-flex-inline.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/ttwf-reftest-flex-order.html [ Failure ]
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/ttwf-reftest-flex-wrap-reverse.html [ Failure ]
 
@@ -1759,7 +1735,6 @@
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-lines/multi-line-wrap-with-column-reverse.html [ Failure ]
 
 ### virtual/layout_ng_experimental/external/wpt/css/css-flexbox/getcomputedstyle/
-crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_display-inline.html [ Skip ]
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-basis-0percent.html [ Failure ]
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_flex-shorthand-number.html [ Failure ]
 
diff --git a/third_party/blink/web_tests/accessibility/animation-policy.html b/third_party/blink/web_tests/accessibility/animation-policy.html
index 64c6f7e..f4bbb08 100644
--- a/third_party/blink/web_tests/accessibility/animation-policy.html
+++ b/third_party/blink/web_tests/accessibility/animation-policy.html
@@ -1,3 +1,4 @@
+<script src="../resources/run-after-layout-and-paint.js"></script>
 <script>
 var updated = false;
 var prevTime;
@@ -12,11 +13,6 @@
     updated = true;
 }
 
-function finishTest() {
-    if (window.testRunner)
-        testRunner.notifyDone();
-}
-
 function imageLoaded() {
     if (!updated)
         return;
@@ -24,11 +20,8 @@
 }
 
 function onTimeout() {
-    if (window.testRunner) {
-        testRunner.layoutAndPaintAsyncThen(function () {
-            window.requestAnimationFrame(finishTest);
-        });
-    }
+    if (window.testRunner)
+        testRunner.notifyDone();
 }
 </script>
 <body onload="changeImage()">
diff --git a/third_party/blink/web_tests/bluetooth/requestDevice/device-iframe.https.html b/third_party/blink/web_tests/bluetooth/requestDevice/device-iframe.https.html
new file mode 100644
index 0000000..3885941
--- /dev/null
+++ b/third_party/blink/web_tests/bluetooth/requestDevice/device-iframe.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../external/wpt/bluetooth/resources/bluetooth-helpers.js"></script>
+<body></body>
+<script>
+'use strict';
+
+function createIframe(iframe) {
+  return new Promise(resolve => {
+    iframe.src = '';
+    iframe.onload = () => {
+      resolve(iframe.contentWindow.navigator.bluetooth);
+    };
+    document.body.appendChild(iframe);
+  });
+}
+
+promise_test(async(t) => {
+  let iframe = document.createElement('iframe');
+  let iframeBluetoothObject = await createIframe(iframe);
+  document.body.removeChild(iframe);
+  // Set iframe to null to ensure that the GC cleans up as much as possible.
+  iframe = null;
+  GCController.collect();
+  return callWithTrustedClick(iframeBluetoothObject.requestDevice)
+      .catch(err => assert_equals(err.name, 'ReferenceError'));
+  }, 'detaching from iframe invalidates reference to the iframe bluetooth object');
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/bluetooth/requestLEScan/scan-iframe.https.html b/third_party/blink/web_tests/bluetooth/requestLEScan/scan-iframe.https.html
new file mode 100644
index 0000000..2a5c01b1
--- /dev/null
+++ b/third_party/blink/web_tests/bluetooth/requestLEScan/scan-iframe.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../external/wpt/bluetooth/resources/bluetooth-helpers.js"></script>
+<body></body>
+<script>
+'use strict';
+
+function createIframe(iframe) {
+  return new Promise(resolve => {
+    iframe.src = '';
+    iframe.onload = () => {
+      resolve(iframe.contentWindow.navigator.bluetooth);
+    };
+    document.body.appendChild(iframe);
+  });
+}
+
+promise_test(async(t) => {
+  let iframe = document.createElement('iframe');
+  let iframeBluetoothObject = await createIframe(iframe);
+  document.body.removeChild(iframe);
+  // Set iframe to null to ensure that the GC cleans up as much as possible.
+  iframe = null;
+  GCController.collect();
+  return callWithTrustedClick(iframeBluetoothObject.requestLEScan)
+      .catch(err => assert_equals(err.name, 'ReferenceError'));
+  }, 'detaching from iframe invalidates reference to the iframe bluetooth object');
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/css3/filters/background-image-blur-repaint.html b/third_party/blink/web_tests/css3/filters/background-image-blur-repaint.html
index 8e2f89b..696d0bb 100644
--- a/third_party/blink/web_tests/css3/filters/background-image-blur-repaint.html
+++ b/third_party/blink/web_tests/css3/filters/background-image-blur-repaint.html
@@ -3,24 +3,15 @@
 <!-- You should see a 50x50 green box over a blurred background, with no white 100x100 box bleeding into the background image.-->
 <html>
 <head>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-  if (window.testRunner)
-    testRunner.waitUntilDone();
-
   function runTest() {
     function shrinkBox() {
       var box = document.getElementsByClassName("box")[0];
       box.style.width = "50px";
       box.style.height = "50px";
-      if (window.testRunner) {
-        testRunner.notifyDone();
-      }
     }
-    if (window.testRunner) {
-      testRunner.layoutAndPaintAsyncThen(shrinkBox);
-    } else {
-      setTimeout(shrinkBox, 500);
-    }
+    runAfterLayoutAndPaint(shrinkBox, true);
   }
 </script>
 <style>
diff --git a/third_party/blink/web_tests/editing/caret/caret_affinity_mouse_perturbation.html b/third_party/blink/web_tests/editing/caret/caret_affinity_mouse_perturbation.html
new file mode 100644
index 0000000..0342afb
--- /dev/null
+++ b/third_party/blink/web_tests/editing/caret/caret_affinity_mouse_perturbation.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../assert_selection.js"></script>
+<script>
+const kStyle = `
+<style>
+div.test {
+  font: 10px/10px Ahem;
+  width: 30px;
+  word-break: break-all;
+  padding-right: 10px;
+}
+</style>`;
+
+// Regression tests for https://crbug.com/919146
+
+// Mouse down at the end of the first line. Without releasing, slightly move
+// the mouse around. Caret shouldn't be moved to the second line.
+selection_test(
+    `${kStyle}<div class="test" contenteditable>foobar</div>`,
+    selection => {
+      assert_own_property(window, 'eventSender');
+      assert_own_property(window, 'internals');
+
+      const div = selection.document.querySelector('div.test');
+      const x = selection.computeLeft(div) + 35;
+      const y = selection.computeTop(div) + 5;
+      eventSender.mouseMoveTo(x - 3, y);
+      eventSender.mouseDown();
+      eventSender.mouseMoveTo(x + 3, y);
+      eventSender.mouseUp();
+
+      assert_equals(internals.textAffinity, 'Upstream');
+    },
+    `${kStyle}<div class="test" contenteditable>foo|bar</div>`,
+    'Drag with upstream caret');
+
+// Set upstream caret at the end of the first line. Then shift+click around the
+// caret. Caret should remain upstream at the end of the first line.
+selection_test(
+    `${kStyle}<div class="test" contenteditable>foobar</div>`,
+    selection => {
+      assert_own_property(window, 'eventSender');
+      assert_own_property(window, 'internals');
+
+      const div = selection.document.querySelector('div.test');
+      const x = selection.computeLeft(div) + 35;
+      const y = selection.computeTop(div) + 5;
+      eventSender.mouseMoveTo(x - 3, y);
+      eventSender.mouseDown();
+      eventSender.mouseUp();
+
+      eventSender.mouseMoveTo(x + 3, y);
+      eventSender.mouseDown(0, ['shiftKey']);
+      eventSender.mouseUp(0, ['shiftKey']);
+
+      assert_equals(internals.textAffinity, 'Upstream');
+    },
+    `${kStyle}<div class="test" contenteditable>foo|bar</div>`,
+    'Shift+Click around upstream caret');
+</script>
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 e1b7622..1aef400 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
@@ -51835,6 +51835,18 @@
      {}
     ]
    ],
+   "css/css-masking/clip-path/clip-path-fixed-nested.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-fixed-nested.html",
+     [
+      [
+       "/css/css-masking/clip-path/clip-path-fixed-nested-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-masking/clip-path/clip-path-inline-001.html": [
     [
      "/css/css-masking/clip-path/clip-path-inline-001.html",
@@ -137924,6 +137936,11 @@
      {}
     ]
    ],
+   "css/css-masking/clip-path/clip-path-fixed-nested-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-masking/clip-path/reference/clip-path-circle-2-ref.html": [
     [
      {}
@@ -166799,6 +166816,11 @@
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/tables/table-attribute-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/tables/table-border-1-ref.html": [
     [
      {}
@@ -236229,6 +236251,12 @@
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/tables/table-attribute.html": [
+    [
+     "/html/rendering/non-replaced-elements/tables/table-attribute.html",
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/tables/table-vspace-hspace-s.html": [
     [
      "/html/rendering/non-replaced-elements/tables/table-vspace-hspace-s.html",
@@ -276553,6 +276581,12 @@
      {}
     ]
    ],
+   "svg/animations/scripted/end-element-on-inactive-element.svg": [
+    [
+     "/svg/animations/scripted/end-element-on-inactive-element.svg",
+     {}
+    ]
+   ],
    "svg/extensibility/foreignObject/containing-block.html": [
     [
      "/svg/extensibility/foreignObject/containing-block.html",
@@ -346852,6 +346886,14 @@
    "5806e75d536cc34a4610630782e5cdfd4b5e552d",
    "reftest"
   ],
+  "css/css-masking/clip-path/clip-path-fixed-nested-ref.html": [
+   "b860304f04fe22498eb5b43d9de00d7e261353bb",
+   "support"
+  ],
+  "css/css-masking/clip-path/clip-path-fixed-nested.html": [
+   "5090bc2aed43e0619eb7b966c2ed46cd43057e40",
+   "reftest"
+  ],
   "css/css-masking/clip-path/clip-path-inline-001.html": [
    "21acae0ee7e06da76a6b5830e5aaa6b5e0d2e6a6",
    "reftest"
@@ -402768,6 +402810,14 @@
    "f06c3dc9b4f101faa253b8b1d980510475894992",
    "testharness"
   ],
+  "html/rendering/non-replaced-elements/tables/table-attribute-expected.txt": [
+   "d5a1fc32f10bab297a27a8d557737452b3e6718b",
+   "support"
+  ],
+  "html/rendering/non-replaced-elements/tables/table-attribute.html": [
+   "54acff0350eaee7fced7869c566bfb780b26cfd0",
+   "testharness"
+  ],
   "html/rendering/non-replaced-elements/tables/table-border-1-ref.html": [
    "ceac88e9a3c82013165b1a64e7acd3d3841271fb",
    "support"
@@ -443556,6 +443606,10 @@
    "ee86b537ae987483687cc8ba6181db82f99ab162",
    "support"
   ],
+  "svg/animations/scripted/end-element-on-inactive-element.svg": [
+   "34be9b9781f707d488e284c3285b82009557366b",
+   "testharness"
+  ],
   "svg/coordinate-systems/abspos.html": [
    "fb37bbe7f3ae4a61d1c216970c8a263673aed0dc",
    "reftest"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-image-first-line.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-image-first-line.html
new file mode 100644
index 0000000..c8dee70
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/background-image-first-line.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>CSS Test: background-image applicability to ::first-letter</title>
+<link rel="help" href="http://www.w3.org/TR/css3-background/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo">
+<link rel="match" href="reference/background-image-first-line-ref.html">
+<meta name="flags" content="ahem image">
+<meta name="assert" content="background-image applicability to ::first-line">
+<style type="text/css">
+#content {
+  color: transparent;
+  font: 100px Ahem;
+}
+#content::first-line {
+  background-image: url("support/cat.png");  /* 98 w. by 99px h. */
+  background-repeat: no-repeat;
+}
+</style>
+<p>Test passes if cat image is visible.</p>
+<div id="content">X</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/background-image-first-line-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/background-image-first-line-ref.html
new file mode 100644
index 0000000..82fd9a2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/background-image-first-line-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta name="flags" content="ahem image">
+<style type="text/css">
+#content {
+  color: transparent;
+  font: 100px Ahem;
+  background-image: url("../support/cat.png");  /* 98 w. by 99px h. */
+  background-repeat: no-repeat;
+}
+</style>
+<p>Test passes if cat image is visible.</p>
+<div id="content">X</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-fixed-nested-ref.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-fixed-nested-ref.html
new file mode 100644
index 0000000..b860304f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-fixed-nested-ref.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<style>
+  body, html {
+    margin: 0;
+    padding: 0;
+    background: green;
+  }
+
+  .purple-square {
+    background: purple;
+    width: 50px;
+    height: 50px;
+  }
+</style>
+<div class="purple-square"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-fixed-nested.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-fixed-nested.html
new file mode 100644
index 0000000..5090bc2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-fixed-nested.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<title>CSS Test: nested clip-path() inside the same reference frame with position: fixed</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1501111">
+<link rel="help" href="https://drafts.fxtf.org/css-masking/#the-clip-path">
+<link rel="match" href="clip-path-fixed-nested-ref.html">
+<style>
+  body, html {
+    margin: 0;
+    padding: 0;
+  }
+  .outer-clip {
+    height: 100vh;
+    clip-path: inset(0 0 0 0);
+    background: green;
+  }
+  .fixed {
+    position: fixed;
+  }
+  .inner-clip {
+    height: 50px;
+    width: 50px;
+    clip-path: inset(0 0 0 0);
+  }
+  .inner-clip-contents {
+    height: 100px;
+    width: 100px;
+    background: purple;
+  }
+</style>
+<div class="outer-clip">
+  <div class="fixed">
+    <div class="inner-clip">
+      <div class="inner-clip-contents"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/corb/img-mime-types-coverage.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/corb/img-mime-types-coverage.tentative.sub.html
index 65c5b84..223a0a3 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/corb/img-mime-types-coverage.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/corb/img-mime-types-coverage.tentative.sub.html
@@ -45,7 +45,12 @@
 
   const get_url = (mime) => {
     // www1 is cross-origin, so the HTTP response is CORB-eligible -->
-    url = "http://{{domains[www1]}}:{{ports[http][0]}}"
+    //
+    // TODO(lukasza@chromium.org): Once https://crbug.com/888079 and
+    // https://crbug.com/891872 are fixed, we should use a cross-*origin*
+    // rather than cross-*site* URL below (e.g. s/hosts[alt]/domains/g).
+    // See also https://crbug.com/918660 for more context.
+    url = "http://{{hosts[alt][www1]}}:{{ports[http][0]}}"
     url = url + "/fetch/nosniff/resources/image.py"
     if (mime != null) {
         url += "?type=" + encodeURIComponent(mime)
diff --git a/third_party/blink/web_tests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
index 82adc47..46403b0 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
@@ -7,5 +7,11 @@
 <meta charset="utf-8">
 <!-- Reference page uses same-origin resources, which are not CORB-eligible. -->
 <link rel="match" href="img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html">
-<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
-<img src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html-nosniff.png">
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible
+
+TODO(lukasza@chromium.org): Once https://crbug.com/888079 and
+https://crbug.com/891872 are fixed, we should use a cross-*origin*
+rather than cross-*site* URL below (e.g. s/hosts[alt]/domains/g).
+See also https://crbug.com/918660 for more context.
+-->
+<img src="http://{{hosts[alt][www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html-nosniff.png">
diff --git a/third_party/blink/web_tests/external/wpt/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html
index cea80f2..2fc93f8 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html
@@ -17,8 +17,14 @@
 });
 </script>
 
-<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible
+
+TODO(lukasza@chromium.org): Once https://crbug.com/888079 and
+https://crbug.com/891872 are fixed, we should use a cross-*origin*
+rather than cross-*site* URL below (e.g. s/hosts[alt]/domains/g).
+See also https://crbug.com/918660 for more context.
+-->
 <link rel="preload" as="image"
       onerror="window.preloadErrorEvent()"
       onload="window.preloadLoadEvent()"
-      href="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html-nosniff.png">
+      href="http://{{hosts[alt][www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html-nosniff.png">
diff --git a/third_party/blink/web_tests/external/wpt/fetch/corb/script-html-correctly-labeled.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/corb/script-html-correctly-labeled.tentative.sub.html
index 8f4d767..407cef9 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/corb/script-html-correctly-labeled.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/corb/script-html-correctly-labeled.tentative.sub.html
@@ -24,7 +24,12 @@
   });
 
   // www1 is cross-origin, so the HTTP response is CORB-eligible.
-  script.src = 'http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/html-correctly-labeled.html';
+  //
+  // TODO(lukasza@chromium.org): Once https://crbug.com/888079 and
+  // https://crbug.com/891872 are fixed, we should use a cross-*origin*
+  // rather than cross-*site* URL below (e.g. s/hosts[alt]/domains/g).
+  // See also https://crbug.com/918660 for more context.
+  script.src = 'http://{{hosts[alt][www1]}}:{{ports[http][0]}}/fetch/corb/resources/html-correctly-labeled.html';
   document.body.appendChild(script)
 }, "CORB-blocked script has no syntax errors");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html
index cabc7b0..03924cd 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html
@@ -68,7 +68,12 @@
     });
 
     // www1 is cross-origin, so the HTTP response is CORB-eligible.
-    var src_prefix = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/sniffable-resource.py";
+    //
+    // TODO(lukasza@chromium.org): Once https://crbug.com/888079 and
+    // https://crbug.com/891872 are fixed, we should use a cross-*origin*
+    // rather than cross-*site* URL below (e.g. s/hosts[alt]/domains/g).
+    // See also https://crbug.com/918660 for more context.
+    var src_prefix = "http://{{hosts[alt][www1]}}:{{ports[http][0]}}/fetch/corb/resources/sniffable-resource.py";
     script.src = src_prefix + "?type=" + mime_type + "&body=" + encodeURIComponent(body);
     document.body.appendChild(script)
   }, "CORB-blocks '" + mime_type + "' that starts with the following JSON parser breaker: " + body);
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/tables/table-attribute-expected.txt b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/tables/table-attribute-expected.txt
new file mode 100644
index 0000000..d5a1fc3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/tables/table-attribute-expected.txt
@@ -0,0 +1,62 @@
+This is a testharness.js-based test.
+Found 58 tests; 34 PASS, 24 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS table bordercolor attribute is correct
+PASS table cellspacing attribute is correct
+PASS table background attribute is correct
+PASS table bgcolor attribute is correct
+PASS thead background attribute is correct
+PASS thead bgcolor attribute is correct
+PASS tbody background attribute is correct
+PASS tbody bgcolor attribute is correct
+PASS tfoot background attribute is correct
+PASS tfoot bgcolor attribute is correct
+PASS tr background attribute is correct
+PASS tr bgcolor attribute is correct
+PASS td background attribute is correct
+PASS td bgcolor attribute is correct
+PASS th background attribute is correct
+PASS th bgcolor attribute is correct
+FAIL table thead align attribute center is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table thead align attribute middle is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table thead align attribute left is correct assert_equals: expected "left" but got "-webkit-left"
+FAIL table thead align attribute right is correct assert_equals: expected "right" but got "-webkit-right"
+PASS table thead align attribute justify is correct
+FAIL table tbody align attribute center is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table tbody align attribute middle is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table tbody align attribute left is correct assert_equals: expected "left" but got "-webkit-left"
+FAIL table tbody align attribute right is correct assert_equals: expected "right" but got "-webkit-right"
+PASS table tbody align attribute justify is correct
+FAIL table tfoot align attribute center is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table tfoot align attribute middle is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table tfoot align attribute left is correct assert_equals: expected "left" but got "-webkit-left"
+FAIL table tfoot align attribute right is correct assert_equals: expected "right" but got "-webkit-right"
+PASS table tfoot align attribute justify is correct
+FAIL table tr align attribute center is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table tr align attribute middle is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table tr align attribute left is correct assert_equals: expected "left" but got "-webkit-left"
+FAIL table tr align attribute right is correct assert_equals: expected "right" but got "-webkit-right"
+PASS table tr align attribute justify is correct
+FAIL table td align attribute center is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table td align attribute middle is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table td align attribute left is correct assert_equals: expected "left" but got "-webkit-left"
+FAIL table td align attribute right is correct assert_equals: expected "right" but got "-webkit-right"
+PASS table td align attribute justify is correct
+FAIL table th align attribute center is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table th align attribute middle is correct assert_equals: expected "center" but got "-webkit-center"
+FAIL table th align attribute left is correct assert_equals: expected "left" but got "-webkit-left"
+FAIL table th align attribute right is correct assert_equals: expected "right" but got "-webkit-right"
+PASS table th align attribute justify is correct
+PASS tr height attribute pixel is correct
+PASS td height attribute pixel is correct
+PASS th height attribute pixel is correct
+PASS table_tr height attribute percentage is correct
+PASS table_td height attribute percentage is correct
+PASS table_th height attribute percentage is correct
+PASS table height attribute pixel is correct
+PASS table height attribute 90% is correct
+PASS table height attribute 110% is correct
+PASS table cellpadding attribute is correct
+PASS th default align attribute is center
+PASS table col width attribute is correct
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/tables/table-attribute.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/tables/table-attribute.html
new file mode 100644
index 0000000..54acff0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/tables/table-attribute.html
@@ -0,0 +1,194 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Table attribute test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#tables-2">
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+  .div_tbl table {
+    width: 400px;
+    height: 300px;
+    border-spacing: 0px;
+  }
+  .div_tbl td {
+    padding: 0px;
+  }
+  .div_tbl th {
+    padding: 0px;
+  }
+  .div_200 {
+    height: 200px;
+  }
+</style>
+
+<div id="div">
+  <table id="table">
+    <thead id="thead">
+      <tr>
+        <th id="th">Month</th>
+        <th>Savings</th>
+      </tr>
+    </thead>
+    <tbody id="tbody">
+      <tr id="tr">
+        <td>January</td>
+        <td>$60</td>
+      </tr>
+      <tr>
+        <td id="td">February</td>
+        <td>$80</td>
+      </tr>
+    </tbody>
+    <tfoot id="tfoot">
+      <tr>
+        <td>Sum</td>
+        <td>$140</td>
+      </tr>
+    </tfoot>
+  </table>
+</div>
+
+<script>
+
+const ids = ["table", "thead", "tbody", "tfoot", "tr", "td", "th"];
+const alignIds = ["thead", "tbody", "tfoot", "tr", "td", "th"];
+const heightIds = ["tr", "td", "th"];
+const div = document.getElementById("div");
+const table = document.getElementById("table");
+const aligns = [
+  ["center", "center"],
+  ["middle", "center"],
+  ["left", "left"],
+  ["right", "right"],
+  ["justify", "justify"]
+];
+
+function commonTest(id, attr, value, cssProp, expected) {
+  test(t => {
+    let elem = document.getElementById(id);
+    t.add_cleanup(() => {
+      elem.removeAttribute(attr);
+    });
+    elem.setAttribute(attr, value);
+    let css = window.getComputedStyle(elem, null).getPropertyValue(cssProp);
+    assert_equals(css, expected);
+  }, `${id} ${attr} attribute is correct`);
+}
+
+function commonAlignTest(id, attr, value, cssProp, expected) {
+  test(t => {
+    let elem = document.getElementById(id);
+    t.add_cleanup(() => {
+      elem.removeAttribute(attr);
+    });
+    elem.setAttribute(attr, value);
+    let css = window.getComputedStyle(elem, null).getPropertyValue(cssProp);
+    assert_equals(css, expected);
+  }, `table ${id} align attribute ${value} is correct`);
+}
+
+function commonHeightTest(id, attr, value, cssProp, expected, type="", divClass) {
+  test(t => {
+    let elem = document.getElementById(id);
+    t.add_cleanup(() => {
+      elem.removeAttribute(attr);
+      div.classList.remove(divClass);
+    });
+    elem.setAttribute(attr, value);
+    div.classList.add(divClass);
+    let css = window.getComputedStyle(elem, null).getPropertyValue(cssProp);
+    assert_equals(css, expected);
+  }, `${id} ${attr} attribute ${type} is correct`);
+}
+
+// table#bordercolor
+commonTest("table", "bordercolor", "red", "border-color", "rgb(255, 0, 0)");
+// table#cellspacing
+commonTest("table", "cellspacing", "10", "border-spacing", "10px 10px", "10");
+
+// {table, thead, body, tfoot, tr, td, th}#background
+// {table, thead, body, tfoot, tr, td, th}#bgcolor
+const url = new URL('/images/threecolors.png', window.location.href).href;
+for (let id of ids) {
+  commonTest(id, "background", "/images/threecolors.png", "background-image", `url(\"${url}\")`);
+
+  commonTest(id, "bgcolor", "red", "background-color", "rgb(255, 0, 0)");
+}
+
+// {thead, body, tfoot, tr, td, th}#align#{center, middle, left, right, justify}
+for (let id of alignIds) {
+  for (let [value, expected] of aligns) {
+    commonAlignTest(id, "align", value, "text-align", expected);
+  }
+}
+
+// {tr, td, th}#height#pixel
+for (let id of heightIds) {
+  commonHeightTest(id, "height", "60", "height", "60px", "pixel", "div_tbl");
+}
+
+// {tr, td, th}#height#percentage
+let tbl = document.createElement("table");
+tbl.innerHTML = '<tr id="table_tr"><th id="table_th"></th></tr><tr><td id="table_td"></td></tr>';
+div.appendChild(tbl);
+const heightPercIds = ["table_tr", "table_td", "table_th"];
+for (let id of heightPercIds) {
+  commonHeightTest(id, "height", "20%", "height", "60px", "percentage", "div_tbl");
+}
+div.removeChild(tbl);
+
+// table#height#{pixel, percentage}
+commonHeightTest("table", "height", "180", "height", "180px", "pixel", "div_200");
+commonHeightTest("table", "height", "90%", "height", "180px", "90%", "div_200");
+commonHeightTest("table", "height", "110%", "height", "220px", "110%", "div_200");
+
+// table#cellpadding
+test(t => {
+  t.add_cleanup(() => {
+    table.removeAttribute("cellpadding");
+  });
+  table.setAttribute("cellpadding", "10");
+
+  let th = document.getElementById("th");
+  let th_css = window.getComputedStyle(th, null).getPropertyValue("padding");
+  assert_equals(th_css, "10px");
+
+  let td = document.getElementById("td");
+  let td_css = window.getComputedStyle(td, null).getPropertyValue("padding");
+  assert_equals(td_css, "10px");
+}, "table cellpadding attribute is correct");
+
+// th default text-align property is center
+test(t => {
+  let elem = document.getElementById("th");
+  let css = window.getComputedStyle(elem, null).getPropertyValue("text-align");
+  assert_equals(css, "center");
+}, "th default align attribute is center");
+
+// col#width#{pixel, percentage}
+test(t => {
+  let colgroup = document.createElement("colgroup");
+  let col1 = document.createElement("col");
+  let col2 = document.createElement("col");
+  t.add_cleanup(() => {
+    table.removeChild(colgroup);
+    div.classList.remove("div_tbl");
+  });
+  colgroup.appendChild(col1);
+  colgroup.appendChild(col2);
+  table.insertBefore(colgroup, table.firstChild);
+  div.classList.add("div_tbl");
+
+  col1.setAttribute("width", "100");
+  let td = document.getElementById("td");
+  let css = window.getComputedStyle(td, null).getPropertyValue("width");
+  assert_equals(css, "100px");
+
+  col1.setAttribute("width", "50%");
+  css = window.getComputedStyle(td, null).getPropertyValue("width");
+  assert_equals(css, "200px");
+}, "table col width attribute is correct");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/resources/link-style-error.js b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/resources/link-style-error.js
index d1fa5ac..7ebc39b 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/resources/link-style-error.js
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/resources/link-style-error.js
@@ -1,7 +1,13 @@
 ["<link>", "@import"].forEach(linkType => {
   [
    ["same-origin", "resources/css.py"],
-   ["cross-origin", get_host_info().HTTP_REMOTE_ORIGIN + "/html/semantics/document-metadata/the-link-element/resources/css.py"]
+
+   // TODO(lukasza@chromium.org): Once https://crbug.com/888079 and
+   // https://crbug.com/891872 are fixed, we should use a cross-*origin* rather
+   // than cross-*site* URL below (e.g.  s/ HTTP_NOTSAMESITE_ORIGIN /
+   // HTTP_REMOTE_ORIGIN /g).  See also https://crbug.com/918660 for more
+   // context.
+   ["cross-origin", get_host_info().HTTP_NOTSAMESITE_ORIGIN + "/html/semantics/document-metadata/the-link-element/resources/css.py"]
   ].forEach(originType => {
     ["no Content-Type", "wrong Content-Type", "broken Content-Type"].forEach(contentType => {
       ["no nosniff", "nosniff"].forEach(nosniff => {
diff --git a/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background-rounded.html b/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background-rounded.html
index b278082..a83fe4d1 100644
--- a/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background-rounded.html
+++ b/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background-rounded.html
@@ -9,13 +9,14 @@
       width: 200px;
     }
   </style>
+  <script src="../../resources/run-after-layout-and-paint.js"></script>
   <script type="text/javascript" charset="utf-8">
     if (window.testRunner)
       testRunner.waitUntilDone();
     
     function pageLoaded() 
     {
-      testRunner.layoutAndPaintAsyncThen(function () {
+      runAfterLayoutAndPaint(function () {
         window.setTimeout(function() {
           if (window.testRunner)
             testRunner.notifyDone();
diff --git a/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background.html b/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background.html
index 2868a08..d6054a1 100644
--- a/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background.html
+++ b/third_party/blink/web_tests/fast/backgrounds/animated-gif-as-background.html
@@ -8,13 +8,14 @@
       width: 200px;
     }
   </style>
+  <script src="../../resources/run-after-layout-and-paint.js"></script>
   <script type="text/javascript" charset="utf-8">
     if (window.testRunner)
       testRunner.waitUntilDone();
     
     function pageLoaded() 
     {
-      testRunner.layoutAndPaintAsyncThen(function () {
+      runAfterLayoutAndPaint(function () {
         window.setTimeout(function() {
           if (window.testRunner)
             testRunner.notifyDone();
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-webgl.html b/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-webgl.html
index 7508cebc..be7acb89 100644
--- a/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-webgl.html
+++ b/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-webgl.html
@@ -1,6 +1,7 @@
 <html>
 <head>
 <script src="../../resources/js-test.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body onload="start();">
 <canvas id="webgl" width="200" height="200"></canvas>
@@ -40,11 +41,7 @@
         gl.clearColor(0.0, 1.0, 0.0, 1.0);
         gl.clear(gl.COLOR_BUFFER_BIT);
         createImageBitmapAndCheck(webgl_canvas, false).then(function() {
-            if (window.testRunner) {
-                testRunner.layoutAndPaintAsyncThen(asyncTest);
-            } else {
-                window.requestAnimationFrame(asyncTest);
-            }
+            runAfterLayoutAndPaint(asyncTest);
         });
     }
 
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-no-alpha-invalidation.html b/third_party/blink/web_tests/fast/canvas/canvas-no-alpha-invalidation.html
index 0427b4c..7c101ff 100644
--- a/third_party/blink/web_tests/fast/canvas/canvas-no-alpha-invalidation.html
+++ b/third_party/blink/web_tests/fast/canvas/canvas-no-alpha-invalidation.html
@@ -1,10 +1,11 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <p>Expect to see a black square below this line.</p>
 <canvas id = 'c' width='100' height='100'></canvas>
 <script>
 if (window.testRunner) {
   testRunner.waitUntilDone();
-  testRunner.layoutAndPaintAsyncThen(function(){
+  runAfterLayoutAndPaint(function(){
     // Verify that creating a context with alpha:false trigger a graphics
     // update even though nothing is drawn to the canvas.
     document.getElementById('c').getContext('2d', { alpha:false });
@@ -13,4 +14,4 @@
 } else {
   console.log('ERROR: This test requires the testRunner interface.')
 }
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d-after-to-data-url-without-context.html b/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d-after-to-data-url-without-context.html
index 359649a..9bf67c2 100644
--- a/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d-after-to-data-url-without-context.html
+++ b/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d-after-to-data-url-without-context.html
@@ -13,6 +13,7 @@
 <canvas id="nonpreserve-canvas3d" width="100" height="100"></canvas>
 <canvas id="nonpreserve-canvas2d" width="100" height="100"></canvas>
 <script src="../../../resources/js-test.js"></script>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="resources/draw-webgl-to-canvas-2d.js"></script>
 <script>
 function createContexts() {
diff --git a/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d.html b/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d.html
index 3b90d53..14cbfa3a 100644
--- a/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d.html
+++ b/third_party/blink/web_tests/fast/canvas/webgl/draw-webgl-to-canvas-2d.html
@@ -27,6 +27,7 @@
 <canvas id="nonpreserve-canvas3d" width="100" height="100"></canvas>
 <canvas id="nonpreserve-canvas2d" width="100" height="100"></canvas>
 <script src="../../../resources/js-test.js"></script>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="resources/draw-webgl-to-canvas-2d.js"></script>
 <script>
 function createContexts() {
diff --git a/third_party/blink/web_tests/fast/canvas/webgl/resources/draw-webgl-to-canvas-2d.js b/third_party/blink/web_tests/fast/canvas/webgl/resources/draw-webgl-to-canvas-2d.js
index 28d2247..eddb57f3 100644
--- a/third_party/blink/web_tests/fast/canvas/webgl/resources/draw-webgl-to-canvas-2d.js
+++ b/third_party/blink/web_tests/fast/canvas/webgl/resources/draw-webgl-to-canvas-2d.js
@@ -68,14 +68,9 @@
     debug("2) when drawingBuffer is not preserved.")
     drawWebGLToCanvas2D(nonpreserve_ctx2D, nonpreserve_canvas3D, false);
 
-    if (window.testRunner) {
-        testRunner.waitUntilDone();
-        testRunner.layoutAndPaintAsyncThen(asyncTest);
-    } else {
-        window.requestAnimationFrame(asyncTest);
-    }
+    runAfterLayoutAndPaint(asyncTest);
 }
 
 window.onload = function () {
-    window.requestAnimationFrame(startTestAfterFirstPaint);
+    runAfterLayoutAndPaint(startTestAfterFirstPaint);
 }
diff --git a/third_party/blink/web_tests/fast/deprecated-flexbox/stretch-align-svg.html b/third_party/blink/web_tests/fast/deprecated-flexbox/stretch-align-svg.html
index 6cb1cdb4..9baa0aa4 100644
--- a/third_party/blink/web_tests/fast/deprecated-flexbox/stretch-align-svg.html
+++ b/third_party/blink/web_tests/fast/deprecated-flexbox/stretch-align-svg.html
@@ -19,9 +19,6 @@
   <div id="spanner"></div>
 </div>
 <script>
-if (window.testRunner)
-  testRunner.waitUntilDone();
-
 runAfterLayoutAndPaint(function() {
   document.body.appendChild(document.createElement('div'));
   document.body.offsetLeft;
@@ -29,8 +26,5 @@
   var c = document.querySelector('rect');
   c.setAttribute('height', '100%');
   c.getScreenCTM();
-
-  if (window.testRunner)
-    testRunner.layoutAndPaintAsyncThen(function() { testRunner.notifyDone(); });
-});
+}, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/events/middleClickAutoscroll-drag-scrollable-iframe-div.html b/third_party/blink/web_tests/fast/events/middleClickAutoscroll-drag-scrollable-iframe-div.html
index a870a1e..49c4f19 100644
--- a/third_party/blink/web_tests/fast/events/middleClickAutoscroll-drag-scrollable-iframe-div.html
+++ b/third_party/blink/web_tests/fast/events/middleClickAutoscroll-drag-scrollable-iframe-div.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <script src="../../resources/js-test.js"> </script>
+<script src="../../resources/run-after-layout-and-paint.js"> </script>
 <script>
 var button_was_clicked = false;
 
@@ -7,14 +8,14 @@
     if (window.testRunner) {
         testRunner.waitUntilDone();
         testRunner.dumpAsText();
-        testRunner.layoutAndPaintAsyncThen(
+        runAfterLayoutAndPaint(
             function() {
                 var iframe = document.getElementById('ScrollIFrame');
                 var iframeDocument = iframe.contentDocument;
                 var textInIFrame = iframeDocument.getElementById('textInFrame');
                 if (window.eventSender) {
                     debug("Starting Autoscroll test on iframe");
-                    testRunner.layoutAndPaintAsyncThen(
+                    runAfterLayoutAndPaint(
                         function() {
                             var x = iframe.offsetLeft + textInIFrame.offsetLeft + 7;
                             var y = iframe.offsetTop + textInIFrame.offsetTop + 7;
@@ -23,7 +24,7 @@
                             eventSender.mouseDown(1);
                             eventSender.mouseMoveTo(x + 220, y + 220);
                             eventSender.mouseUp(1);
-                            testRunner.layoutAndPaintAsyncThen(autoscrollTestPart2);                            
+                            runAfterLayoutAndPaint(autoscrollTestPart2);
                         });
                 }
             });
@@ -43,7 +44,7 @@
     // This click actually clicks the button.
     eventSender.mouseDown();
     eventSender.mouseUp();
-    testRunner.runAfterLayoutAndPaint(checkButtonClicked);
+    runAfterLayoutAndPaint(checkButtonClicked);
 }
 
 function checkButtonClicked() {
@@ -73,4 +74,4 @@
 
     <button id="testCompletedButton" type="button" onclick="testCompleted()"> Click me </button>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/fast/events/middleClickAutoscroll-modal-scrollable-iframe-div.html b/third_party/blink/web_tests/fast/events/middleClickAutoscroll-modal-scrollable-iframe-div.html
index bfcd0b1..ccb3b7a 100644
--- a/third_party/blink/web_tests/fast/events/middleClickAutoscroll-modal-scrollable-iframe-div.html
+++ b/third_party/blink/web_tests/fast/events/middleClickAutoscroll-modal-scrollable-iframe-div.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <script src="../../resources/js-test.js"> </script>
+<script src="../../resources/run-after-layout-and-paint.js"> </script>
 <script>
 var button_was_clicked = false;
 
@@ -7,14 +8,14 @@
     if (window.testRunner) {
         testRunner.waitUntilDone();
         testRunner.dumpAsText();
-        testRunner.layoutAndPaintAsyncThen(
+        runAfterLayoutAndPaint(
             function() {
                 var iframe = document.getElementById('ScrollIFrame');
                 var iframeDocument = iframe.contentDocument;
                 var textInIFrame = iframeDocument.getElementById('textInFrame');
                 if (window.eventSender) {
                     debug("Starting Autoscroll test on iframe");
-                    testRunner.layoutAndPaintAsyncThen(
+                    runAfterLayoutAndPaint(
                         function() {
                             var x = iframe.offsetLeft + textInIFrame.offsetLeft + 7;
                             var y = iframe.offsetTop + textInIFrame.offsetTop + 7;
@@ -23,7 +24,7 @@
                             eventSender.mouseDown(1);
                             eventSender.mouseUp(1);
                             eventSender.mouseMoveTo(x + 220, y + 220);
-                            testRunner.layoutAndPaintAsyncThen(autoscrollTestPart2);                            
+                            runAfterLayoutAndPaint(autoscrollTestPart2);                            
                         });
                 }
             });
@@ -43,7 +44,7 @@
     // This click actually clicks the button.
     eventSender.mouseDown();
     eventSender.mouseUp();
-    testRunner.runAfterLayoutAndPaint(checkButtonClicked);
+    runAfterLayoutAndPaint(checkButtonClicked);
 }
 
 function checkButtonClicked() {
@@ -73,4 +74,4 @@
 
     <button id="testCompletedButton" type="button" onclick="testCompleted()"> Click me </button>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-non-composited-scroll-expected.txt b/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-non-composited-scroll-expected.txt
index 6df2158..ce49af64 100644
--- a/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-non-composited-scroll-expected.txt
+++ b/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-non-composited-scroll-expected.txt
@@ -13,7 +13,7 @@
 
 divInsideNonScrollableLayer: layer(0,0 785x671) has hit test rect (14,273 273x10)
 
-divInsideCompositedLayer: layer(0,10 273x22) has hit test rect (0,10 273x12)
+divInsideCompositedLayer: layer(14,305 273x22) has hit test rect (0,10 273x12)
 
 overflowwithborder: layer(0,0 785x767) has hit test rect (13,336 290x50)
 
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover-background-color.html b/third_party/blink/web_tests/fast/history/visited-link-hover-background-color.html
index 1610463..1420316 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover-background-color.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover-background-color.html
@@ -1,9 +1,8 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
     testRunner.keepWebHistory();
-    testRunner.waitUntilDone();
-}
 </script>
 <style>
 a { background-color: red; text-decoration: none; color: initial }
@@ -11,11 +10,8 @@
 </style>
 <a id="anchor" href="">Should have a green background when hovered</a>
 <script>
-if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 1, anchor.offsetTop + 1);
-        testRunner.notifyDone();
-    });
-}
+    }, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover-border-color.html b/third_party/blink/web_tests/fast/history/visited-link-hover-border-color.html
index 353aea7..a59611bd 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover-border-color.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover-border-color.html
@@ -1,9 +1,8 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
     testRunner.keepWebHistory();
-    testRunner.waitUntilDone();
-}
 </script>
 <style>
 a { border: solid red 10px; text-decoration: none; color: initial }
@@ -11,11 +10,8 @@
 </style>
 <a id="anchor" href="">Should have a green border when hovered</a>
 <script>
-if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 10, anchor.offsetTop + 10);
-        testRunner.notifyDone();
-    });
-}
+    }, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover-emphasis-color.html b/third_party/blink/web_tests/fast/history/visited-link-hover-emphasis-color.html
index 84077a6..368a40a0 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover-emphasis-color.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover-emphasis-color.html
@@ -1,9 +1,8 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
-    testRunner.keepWebHistory();
+if (window.testRunner)
     testRunner.waitUntilDone();
-}
 </script>
 <style>
 a { -webkit-text-emphasis-style: filled }
@@ -15,11 +14,8 @@
     <a id="anchor" href="">Emphasis should be green when hovered</a>
 </div>
 <script>
-if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 1, anchor.offsetTop + 1);
-        testRunner.notifyDone();
-    });
-}
+    }, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover-outline-color.html b/third_party/blink/web_tests/fast/history/visited-link-hover-outline-color.html
index e748c1a6..d031640a 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover-outline-color.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover-outline-color.html
@@ -1,9 +1,8 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
     testRunner.keepWebHistory();
-    testRunner.waitUntilDone();
-}
 </script>
 <style>
 a { outline: solid red 10px; text-decoration: none; color: initial }
@@ -11,11 +10,8 @@
 </style>
 <a id="anchor" href="">Should have a green outline when hovered</a>
 <script>
-if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 1, anchor.offsetTop + 1);
-        testRunner.notifyDone();
-    });
-}
+    }, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover-text-decoration-color.html b/third_party/blink/web_tests/fast/history/visited-link-hover-text-decoration-color.html
index 27f9f9a..969cd5a 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover-text-decoration-color.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover-text-decoration-color.html
@@ -1,9 +1,8 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
     testRunner.keepWebHistory();
-    testRunner.waitUntilDone();
-}
 </script>
 <style>
 div { text-decoration-color: red; }
@@ -14,11 +13,8 @@
     <a id="anchor" href="">Should be green (including underline) when hovered</a>
 </div>
 <script>
-if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 1, anchor.offsetTop + 1);
-        testRunner.notifyDone();
-    });
-}
+    }, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover-text-fill-color.html b/third_party/blink/web_tests/fast/history/visited-link-hover-text-fill-color.html
index 8dd6e23f..fb3680a 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover-text-fill-color.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover-text-fill-color.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 if (window.testRunner) {
     testRunner.keepWebHistory();
@@ -15,7 +16,7 @@
 </div>
 <script>
 if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 1, anchor.offsetTop + 1);
         testRunner.notifyDone();
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover-text-stroke-color.html b/third_party/blink/web_tests/fast/history/visited-link-hover-text-stroke-color.html
index ca567cc..8794d32 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover-text-stroke-color.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover-text-stroke-color.html
@@ -1,9 +1,8 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
     testRunner.keepWebHistory();
-    testRunner.waitUntilDone();
-}
 </script>
 <style>
 div { -webkit-text-stroke-width: 1px; -webkit-text-stroke-color: red }
@@ -14,11 +13,8 @@
     <a id="anchor" href="">Should have green stroke when hovered</a>
 </div>
 <script>
-if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 1, anchor.offsetTop + 1);
-        testRunner.notifyDone();
-    });
-}
+    }, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/history/visited-link-hover.html b/third_party/blink/web_tests/fast/history/visited-link-hover.html
index 5e3053a..46af269 100644
--- a/third_party/blink/web_tests/fast/history/visited-link-hover.html
+++ b/third_party/blink/web_tests/fast/history/visited-link-hover.html
@@ -1,9 +1,8 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
     testRunner.keepWebHistory();
-    testRunner.waitUntilDone();
-}
 </script>
 <style>
 div { color: red; }
@@ -14,11 +13,8 @@
     <a id="anchor" href="">Should be green when hovered</a>
 </div>
 <script>
-if (window.testRunner) {
-    testRunner.layoutAndPaintAsyncThen(function(){
+    runAfterLayoutAndPaint(function(){
         if (window.eventSender);
             eventSender.mouseMoveTo(anchor.offsetLeft + 1, anchor.offsetTop + 1);
-        testRunner.notifyDone();
-    });
-}
+    }, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/layers/layer-status-change-crash.html b/third_party/blink/web_tests/fast/layers/layer-status-change-crash.html
index 92719f6..0d4f312 100644
--- a/third_party/blink/web_tests/fast/layers/layer-status-change-crash.html
+++ b/third_party/blink/web_tests/fast/layers/layer-status-change-crash.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <body>
 <div id="div">This test passes if it does not crash.</div>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 if (window.testRunner) {
   testRunner.waitUntilDone();
   testRunner.dumpAsText();
 
   var div = document.getElementById('div');
-  testRunner.layoutAndPaintAsyncThen(function() {
+  runAfterLayoutAndPaint(function() {
     div.style.position = 'relative';
     div.style.backgroundColor = 'blue';
-    testRunner.layoutAndPaintAsyncThen(function() {
+    runAfterLayoutAndPaint(function() {
       div.textContent += ' PASS';
       testRunner.notifyDone();
     });
diff --git a/third_party/blink/web_tests/fast/layers/layer-visibility-sublayer.html b/third_party/blink/web_tests/fast/layers/layer-visibility-sublayer.html
index 7209217..8de8f32 100644
--- a/third_party/blink/web_tests/fast/layers/layer-visibility-sublayer.html
+++ b/third_party/blink/web_tests/fast/layers/layer-visibility-sublayer.html
@@ -8,18 +8,13 @@
 .abstop { position: absolute; left:0; right:0; height:30px   }
 .abs { position: absolute; left:0; right:0; top:0; bottom:0 }
 </style>
-
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 var node1;
 var node2;
 
 function startTest() {
-    if (window.testRunner) {
-        testRunner.waitUntilDone();
-        testRunner.layoutAndPaintAsyncThen(doTest)
-    } else {
-        requestAnimationFrame(doTest)
-    }
+    runAfterLayoutAndPaint(doTest, true);
 }
 
 function doTest()
@@ -35,12 +30,6 @@
     //26
     document.getElementById('26c').style.setProperty('visibility','hidden','');
     document.getElementById('26a').removeChild(document.getElementById('26b'));
-
-    if (window.testRunner) {
-    	testRunner.layoutAndPaintAsyncThen(function() {
-      	    testRunner.notifyDone();
-        });
-    }
 }
 
 
diff --git a/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-animates.html b/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-animates.html
index c90ccd6..7ada4dd 100644
--- a/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-animates.html
+++ b/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-animates.html
@@ -15,6 +15,7 @@
 
 </style>
 <script src="../../resources/js-test.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <div id="container">
   <div id="content"></div>
 </div>
@@ -32,7 +33,7 @@
 
     element.addEventListener("scroll", onElementScroll);
 
-    testRunner.layoutAndPaintAsyncThen(function() {
+    runAfterLayoutAndPaint(function() {
         // Give the container focus.
         eventSender.mouseMoveTo(100, 100);
         eventSender.mouseDown();
diff --git a/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-precise-deltas-dont-animate.html b/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-precise-deltas-dont-animate.html
index a71993b..4a17147 100644
--- a/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-precise-deltas-dont-animate.html
+++ b/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-precise-deltas-dont-animate.html
@@ -15,6 +15,7 @@
 
 </style>
 <script src="../../resources/js-test.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <div id="container">
   <div id="content"></div>
 </div>
@@ -32,7 +33,7 @@
 
     element.addEventListener("scroll", onElementScroll);
 
-    testRunner.layoutAndPaintAsyncThen(function() {
+    runAfterLayoutAndPaint(function() {
         // Give the container focus.
         eventSender.mouseMoveTo(100, 100);
         eventSender.mouseScrollBy(0, -20, /* paged */ false,
diff --git a/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-root-frame-animates.html b/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-root-frame-animates.html
index 2105aab..39aeffaf 100644
--- a/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-root-frame-animates.html
+++ b/third_party/blink/web_tests/fast/scroll-behavior/overflow-scroll-root-frame-animates.html
@@ -7,6 +7,7 @@
 }
 </style>
 <script src="../../resources/js-test.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <div id="content"></div>
 <div id="console"></div>
 <script>
@@ -21,7 +22,7 @@
 
     window.addEventListener("scroll", onWindowScroll);
 
-    testRunner.layoutAndPaintAsyncThen(function() {
+    runAfterLayoutAndPaint(function() {
         eventSender.keyDown('End');
     });
 }
diff --git a/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-local-background.html b/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-local-background.html
index 188a4c1..14a1b0e 100644
--- a/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-local-background.html
+++ b/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-local-background.html
@@ -16,17 +16,9 @@
 <div id="container">
     <div id="bloat"></div>
 </div>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-    if (window.testRunner) {
-        testRunner.waitUntilDone();
-        testRunner.layoutAndPaintAsyncThen(function() {
-            document.getElementById('container').scrollTop = 1000;
-            testRunner.notifyDone();
-        });
-    } else {
-        // For manual test.
-        setTimeout(function() {
-            document.getElementById('container').scrollTop = 1000;
-        }, 500);
-    }
+runAfterLayoutAndPaint(function() {
+    document.getElementById('container').scrollTop = 1000;
+}, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-to-ancestor-backing.html b/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-to-ancestor-backing.html
index caed95fe..5269e8e 100644
--- a/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-to-ancestor-backing.html
+++ b/third_party/blink/web_tests/fast/scrolling/non-composited-scrolling-repaint-to-ancestor-backing.html
@@ -26,17 +26,9 @@
         <div id="clipped"></div>
     </div>
 </div>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-    if (window.testRunner) {
-        testRunner.waitUntilDone();
-        testRunner.layoutAndPaintAsyncThen(function() {
-            document.getElementById('container').scrollTop = 1000;
-            testRunner.notifyDone();
-        });
-    } else {
-        // For manual test.
-        setTimeout(function() {
-            document.getElementById('container').scrollTop = 1000;
-        }, 500);
-    }
+runAfterLayoutAndPaint(function() {
+    document.getElementById('container').scrollTop = 1000;
+}, true);
 </script>
diff --git a/third_party/blink/web_tests/fast/webgl/canvas-to-data-url.html b/third_party/blink/web_tests/fast/webgl/canvas-to-data-url.html
index 63cdfaf5..2a66980 100644
--- a/third_party/blink/web_tests/fast/webgl/canvas-to-data-url.html
+++ b/third_party/blink/web_tests/fast/webgl/canvas-to-data-url.html
@@ -4,6 +4,7 @@
 <canvas id="preserve-canvas3d" width="10" height="10"></canvas>
 <canvas id="nonpreserve-canvas3d" width="10" height="10"></canvas>
 <script src="../../resources/js-test.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 if (window.testRunner) {
     testRunner.dumpAsText();
@@ -44,12 +45,7 @@
     debug("2) when drawingBuffer is not preserved.")
     shouldBeTrue("nonpreserve_canvas3D.toDataURL('image/png') == internals.getImageSourceURL(nonpreserve_canvas3D)");
 
-    if (window.testRunner) {
-        testRunner.waitUntilDone();
-        testRunner.layoutAndPaintAsyncThen(asyncTest);
-    } else {
-        window.requestAnimationFrame(asyncTest);
-    }
+    runAfterLayoutAndPaint(asyncTest);
 }
 
 window.onload = function () {
diff --git a/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-repaint.html b/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-repaint.html
index 54a537f..a2b8cb3 100644
--- a/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-repaint.html
+++ b/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-repaint.html
@@ -42,6 +42,7 @@
 }
 </script>
 <script src="resources/webgl-test-utils.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 
 var ctxs = []
@@ -116,17 +117,9 @@
 
 function repaintTest() {
     drawAll(50);
-    if (window.testRunner) {
-        testRunner.notifyDone();
-    }
 }
 
 function runRepaintTest() {
-    if (window.testRunner) {
-        testRunner.waitUntilDone();
-        testRunner.layoutAndPaintAsyncThen(repaintTest);
-    } else {
-        setTimeout(repaintTest, 100);
-    }
+    runAfterLayoutAndPaint(repaintTest, true);
 }
 </script>
diff --git a/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-tabswitching.html b/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-tabswitching.html
index fc1eb58d..800295a 100644
--- a/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-tabswitching.html
+++ b/third_party/blink/web_tests/fast/webgl/webgl-composite-modes-tabswitching.html
@@ -42,6 +42,7 @@
 }
 </script>
 <script src="resources/webgl-test-utils.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 
 var ctxs = []
@@ -121,32 +122,27 @@
     }
 }
 
-function setPageVisible() {
-    if (window.testRunner) {
-        testRunner.setPageVisibility("visible");
-        testRunner.layoutAndPaintAsyncThen(repaintOnVisiblePage);
-    } else {
-        setTimeout(repaintOnVisiblePage, 50);
-    }
-}
-
 function repaintOnHiddenPage() {
     if (window.testRunner) {
         testRunner.setPageVisibility("hidden");
     }
     // Although page is hidden, WebGL must draw this frame.
     drawAll(30);
-    // testRunner.layoutAndPaintAsyncThen doesn't work when page is hidden.
-    setTimeout(setPageVisible, 50);
+
+    // TODO(crbug.com/919536): Ideally this should be wrapped in
+    // runAfterLayoutAndPaint() to ensure test coverage, but for now that will
+    // crash.
+    if (window.testRunner) {
+        testRunner.setPageVisibility("visible");
+    }
+    runAfterLayoutAndPaint(repaintOnVisiblePage);
 }
 
 function runRepaintTest() {
     drawAll(0);
     if (window.testRunner) {
         testRunner.waitUntilDone();
-        testRunner.layoutAndPaintAsyncThen(repaintOnHiddenPage);
-    } else {
-        setTimeout(repaintOnHiddenPage, 50);
     }
+    runAfterLayoutAndPaint(repaintOnHiddenPage);
 }
 </script>
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
index 12127c1..78b2d92 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -21,6 +21,11 @@
           "object": "InlineTextBox 'test test test'",
           "rect": [0, 0, 74, 16],
           "reason": "subtree"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [26, 0, 26, 16],
+          "reason": "selection"
         }
       ],
       "transform": 2
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
index d13cd0b0..09ad592 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -21,6 +21,11 @@
           "object": "InlineTextBox 'test test test'",
           "rect": [0, 0, 74, 16],
           "reason": "subtree"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [26, 0, 26, 16],
+          "reason": "selection"
         }
       ],
       "transform": 2
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-inserted-listitem-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-inserted-listitem-expected.txt
index 05cdce6..21b0436c 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-inserted-listitem-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-inserted-listitem-expected.txt
@@ -9,10 +9,20 @@
         {
           "object": "LayoutSVGRect rect id='move'",
           "rect": [28, 38, 10, 10],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRect rect id='move'",
+          "rect": [28, 18, 10, 10],
           "reason": "chunk appeared"
         },
         {
           "object": "LayoutSVGRect rect id='move'",
+          "rect": [28, 18, 10, 10],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRect rect id='move'",
           "rect": [18, 18, 10, 10],
           "reason": "disappeared"
         }
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
index 1ba2fa3..779ace7 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
@@ -12,11 +12,31 @@
           "reason": "appeared"
         },
         {
+          "object": "InlineTextBox ' B C'",
+          "rect": [23, 8, 85, 23],
+          "reason": "appeared"
+        },
+        {
+          "object": "InlineTextBox ' B C'",
+          "rect": [23, 8, 85, 23],
+          "reason": "disappeared"
+        },
+        {
           "object": "InlineTextBox 'A'",
           "rect": [23, 8, 85, 23],
           "reason": "appeared"
         },
         {
+          "object": "InlineTextBox 'A'",
+          "rect": [23, 8, 85, 23],
+          "reason": "appeared"
+        },
+        {
+          "object": "InlineTextBox 'A'",
+          "rect": [23, 8, 85, 23],
+          "reason": "disappeared"
+        },
+        {
           "object": "InlineTextBox ' B C'",
           "rect": [23, 8, 80, 23],
           "reason": "disappeared"
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-expected.txt
index 047e65c..4cb073e 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/modify-transferred-listitem-expected.txt
@@ -10,6 +10,11 @@
           "object": "LayoutSVGPath polygon id='target'",
           "rect": [18, 18, 30, 20],
           "reason": "full"
+        },
+        {
+          "object": "LayoutSVGPath polygon id='target'",
+          "rect": [18, 18, 20, 20],
+          "reason": "full"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt
index d552b0c..b8d8eae 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt
@@ -93,6 +93,16 @@
         },
         {
           "object": "LayoutBlockFlow (positioned) div id='html' class='outerBox'",
+          "rect": [415, 125, 150, 150],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) div id='html' class='outerBox'",
+          "rect": [415, 125, 150, 150],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) div id='html' class='outerBox'",
           "rect": [400, 100, 150, 150],
           "reason": "geometry"
         },
@@ -183,6 +193,16 @@
         },
         {
           "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [115, 125, 150, 150],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [115, 125, 150, 150],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
           "rect": [100, 100, 150, 150],
           "reason": "paint property change"
         },
@@ -273,6 +293,16 @@
         },
         {
           "object": "LayoutBlockFlow div class='innerBox'",
+          "rect": [440, 150, 100, 100],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow div class='innerBox'",
+          "rect": [440, 150, 100, 100],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow div class='innerBox'",
           "rect": [425, 125, 100, 100],
           "reason": "geometry"
         },
@@ -363,6 +393,16 @@
         },
         {
           "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [140, 150, 100, 100],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [140, 150, 100, 100],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
           "rect": [125, 125, 100, 100],
           "reason": "paint property change"
         }
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/css1/box_properties/float_elements_in_series-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/css1/box_properties/float_elements_in_series-expected.png
index 918c197..bfb60b3 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/css1/box_properties/float_elements_in_series-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/css1/box_properties/float_elements_in_series-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/css/content/content-quotes-01-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/css/content/content-quotes-01-expected.txt
new file mode 100644
index 0000000..7eb8ae2
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/css/content/content-quotes-01-expected.txt
@@ -0,0 +1,13 @@
+The texts between the markers should be identical.
+
+========Marker1========
+
+ab	1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
+
+========Marker2========
+
+WWaWWbWWWWWWWW	1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
+
+========Marker3========
+
+PASSED
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/css/content/content-quotes-05-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/css/content/content-quotes-05-expected.txt
new file mode 100644
index 0000000..e9c2297
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/css/content/content-quotes-05-expected.txt
@@ -0,0 +1,11 @@
+========Marker1========
+
+abc	1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
+
+========Marker2========
+
+WWaWWWbWWWWWcWWWW	1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
+
+========Marker3========
+
+PASSED
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-big-endian-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-big-endian-expected.png
index 701be09..fdc06b8 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-big-endian-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-big-endian-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-little-endian-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-little-endian-expected.png
index 701be09..fdc06b8 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-little-endian-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/encoding/utf-16-little-endian-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/lists/ordered-list-with-no-ol-tag-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/lists/ordered-list-with-no-ol-tag-expected.png
index ac07f77..67097f19 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/lists/ordered-list-with-no-ol-tag-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/lists/ordered-list-with-no-ol-tag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-collapsed-border-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-collapsed-border-expected.png
index 6a0e191..b535785 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-collapsed-border-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-collapsed-border-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-expected.png
index 6a0e191..b535785 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-opacity-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-collapsed-border-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-collapsed-border-expected.png
index 8299aab8..af18a818 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-collapsed-border-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-collapsed-border-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-expected.png
index a0c0b9f..2e5b6e7c 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/table/backgr_layers-show-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/text/basic/007-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/text/basic/007-expected.png
index 4cf62d0..be271c6 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/text/basic/007-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/text/basic/007-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/text/international/003-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/text/international/003-expected.png
new file mode 100644
index 0000000..04dcf573
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/fast/text/international/003-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/html/tabular_data/td_colspan_rendering-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/html/tabular_data/td_colspan_rendering-expected.png
index 3d1b89d4..1636877 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/html/tabular_data/td_colspan_rendering-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/html/tabular_data/td_colspan_rendering-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
new file mode 100644
index 0000000..334b04e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -0,0 +1,44 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutTextControl INPUT id='target'",
+          "rect": [7, 7, 63, 24],
+          "reason": "subtree"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [10, 11, 57, 16],
+          "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [10, 11, 56, 16],
+          "reason": "selection"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [10, 11, 56, 16],
+          "reason": "paint property change"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
new file mode 100644
index 0000000..91fc6664
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutTextControl INPUT id='target'",
+          "rect": [7, 7, 63, 24],
+          "reason": "subtree"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [10, 11, 57, 16],
+          "reason": "selection"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [10, 11, 57, 16],
+          "reason": "paint property change"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug106158-1-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug106158-1-expected.png
index 0ade942b..51ca9ad 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug106158-1-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug106158-1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug2267-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug2267-expected.png
index 4af0433..abe95de 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug2267-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug2267-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug38916-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug38916-expected.png
index 807a0a02..5c546b68 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug38916-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug38916-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug44523-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug44523-expected.png
index dc336e9..b38d335 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug44523-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug44523-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-1-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-1-expected.png
index 54f8a993..dda3d871 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-1-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-2-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-2-expected.png
index 54f8a993..dda3d871 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-2-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-5-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-5-expected.png
index 2f32213d..045f6118 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-5-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-5-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-expected.png
index 2f32213d..045f6118 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46268-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-1-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-1-expected.png
index 1a1f1c6..b261af6 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-1-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-2-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-2-expected.png
index d1368bb..896f1c1 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-2-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug46480-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug57828-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug57828-expected.png
index eb0a525..1049910 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug57828-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug57828-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug59354-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug59354-expected.png
index a88c517..c918880 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug59354-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug59354-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug69382-2-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug69382-2-expected.png
index 95ef6063..99a64ac 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug69382-2-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/bugs/bug69382-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/marvin/x_tbody_align_char-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/marvin/x_tbody_align_char-expected.png
index 1d2addfb..c915189c 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/marvin/x_tbody_align_char-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla/marvin/x_tbody_align_char-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla_expected_failures/bugs/bug32205-4-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla_expected_failures/bugs/bug32205-4-expected.png
index 557383c..24be6b6 100644
--- a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla_expected_failures/bugs/bug32205-4-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla_expected_failures/bugs/bug32205-4-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla_expected_failures/bugs/bug8499-expected.png b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla_expected_failures/bugs/bug8499-expected.png
new file mode 100644
index 0000000..42d26e6b
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=LayoutNG/tables/mozilla_expected_failures/bugs/bug8499-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/fullscreen/enter-exit-full-screen-hover.html b/third_party/blink/web_tests/fullscreen/enter-exit-full-screen-hover.html
index 47249365..141d6a2 100644
--- a/third_party/blink/web_tests/fullscreen/enter-exit-full-screen-hover.html
+++ b/third_party/blink/web_tests/fullscreen/enter-exit-full-screen-hover.html
@@ -7,6 +7,7 @@
 }
 </style>
 <script src="../resources/js-test.js"></script>
+<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="full-screen-test.js"></script>
 <script src="../fast/events/touch/resources/touch-hover-active-tests.js"></script>
 <link rel="stylesheet" href="../fast/events/touch/resources/touch-hover-active-tests.css">
@@ -32,13 +33,13 @@
     // After entering fullscreen + layout, the button should lose hover.
     waitForEventOnce(document, 'webkitfullscreenchange', function() {
         shouldBeTrue("document.webkitIsFullScreen");
-        testRunner.layoutAndPaintAsyncThen(function() {
+        runAfterLayoutAndPaint(function() {
             shouldBeDefault("getHoverActiveState(enterButton)");
 
             // After exiting fullscreen + layout, the button should lose hover.
             waitForEventOnce(document, 'webkitfullscreenchange', function() {
                 shouldBeFalse("document.webkitIsFullScreen");
-                testRunner.layoutAndPaintAsyncThen(function() {
+                runAfterLayoutAndPaint(function() {
                     shouldBeDefault("getHoverActiveState(exitButton)");
                     endTest();
                 });
diff --git a/third_party/blink/web_tests/fullscreen/full-screen-stacking-context.html b/third_party/blink/web_tests/fullscreen/full-screen-stacking-context.html
index 80f5c53..f01119ce 100644
--- a/third_party/blink/web_tests/fullscreen/full-screen-stacking-context.html
+++ b/third_party/blink/web_tests/fullscreen/full-screen-stacking-context.html
@@ -1,12 +1,13 @@
 <!DOCTYPE html>
 <html>
     <head>
+        <script src="../resources/run-after-layout-and-paint.js"></script>
         <script>
             var runPixelTests = true;
 
             function init() {
                 waitForEventOnce(document, 'webkitfullscreenchange', function() {
-                    testRunner.layoutAndPaintAsyncThen(endTest);
+                    runAfterLayoutAndPaint(endTest);
                 });
                 runWithKeyDown(goFullScreen);
             }
diff --git a/third_party/blink/web_tests/fullscreen/full-screen-test.js b/third_party/blink/web_tests/fullscreen/full-screen-test.js
index 800322b..729dcb2b 100644
--- a/third_party/blink/web_tests/fullscreen/full-screen-test.js
+++ b/third_party/blink/web_tests/fullscreen/full-screen-test.js
@@ -142,7 +142,7 @@
     consoleWrite("END OF TEST");
     testEnded = true;
     if (window.testRunner)
-        testRunner.layoutAndPaintAsyncThen(() => testRunner.notifyDone());
+        testRunner.notifyDone();
 }
 
 function logResult(success, text)
diff --git a/third_party/blink/web_tests/http/tests/csspaint/border-color.html b/third_party/blink/web_tests/http/tests/csspaint/border-color.html
index 2520a9d..3a1a4e7 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/border-color.html
+++ b/third_party/blink/web_tests/http/tests/csspaint/border-color.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="resources/test-runner-paint-worklet.js"></script>
 
 <style>
@@ -159,7 +158,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importPaintWorkletThenEndTest(document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/csspaint/geometry-background-image-zoom.html b/third_party/blink/web_tests/http/tests/csspaint/geometry-background-image-zoom.html
index f5c1351..5bd64cf8f 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/geometry-background-image-zoom.html
+++ b/third_party/blink/web_tests/http/tests/csspaint/geometry-background-image-zoom.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="./resources/test-runner-paint-worklet.js"></script>
 <style>
 html, body { margin: 0; padding: 0; }
@@ -28,7 +27,7 @@
 
 <script>
     document.body.style.zoom = "200%"
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importPaintWorkletThenEndTest(document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/csspaint/geometry-border-image-zoom.html b/third_party/blink/web_tests/http/tests/csspaint/geometry-border-image-zoom.html
index 96c1ff0..c2f1113 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/geometry-border-image-zoom.html
+++ b/third_party/blink/web_tests/http/tests/csspaint/geometry-border-image-zoom.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="./resources/test-runner-paint-worklet.js"></script>
 <style>
 html, body { margin: 0; padding: 0; }
@@ -34,7 +33,7 @@
 <script>
     document.getElementById('canvas-geometry').style.borderImageOutset = '10px';
     document.body.style.zoom = "200%";
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importPaintWorkletThenEndTest(document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/csspaint/hidpi/geometry-with-hidpi-zoom.html b/third_party/blink/web_tests/http/tests/csspaint/hidpi/geometry-with-hidpi-zoom.html
index 7e083af..81d912d0 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/hidpi/geometry-with-hidpi-zoom.html
+++ b/third_party/blink/web_tests/http/tests/csspaint/hidpi/geometry-with-hidpi-zoom.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html>
-<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/test-runner-paint-worklet.js"></script>
 <style>
 html, body { margin: 0; padding: 0; }
@@ -35,7 +34,7 @@
 
 <script>
     document.body.style.zoom = "33%";
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importPaintWorkletThenEndTest(document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/csspaint/line-dash-scale-with-page-zoom.html b/third_party/blink/web_tests/http/tests/csspaint/line-dash-scale-with-page-zoom.html
index cd8d2097..6a25362 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/line-dash-scale-with-page-zoom.html
+++ b/third_party/blink/web_tests/http/tests/csspaint/line-dash-scale-with-page-zoom.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="./resources/test-runner-paint-worklet.js"></script>
 <style>
 html, body { margin: 0; padding: 0; }
@@ -38,7 +37,7 @@
 
 <script>
     document.body.style.zoom = "200%"
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importPaintWorkletThenEndTest(document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/csspaint/paint2d-zoom.html b/third_party/blink/web_tests/http/tests/csspaint/paint2d-zoom.html
index d4bf63e..9015592 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/paint2d-zoom.html
+++ b/third_party/blink/web_tests/http/tests/csspaint/paint2d-zoom.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="./resources/test-runner-paint-worklet.js"></script>
 <style>
 html, body { margin: 0; padding: 0; }
@@ -26,7 +25,7 @@
 
 <script>
     document.body.style.zoom = "300%";
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importPaintWorkletThenEndTest(document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/csspaint/resources/test-runner-paint-worklet.js b/third_party/blink/web_tests/http/tests/csspaint/resources/test-runner-paint-worklet.js
index 8fd3d1b2..b8dc099c 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/resources/test-runner-paint-worklet.js
+++ b/third_party/blink/web_tests/http/tests/csspaint/resources/test-runner-paint-worklet.js
@@ -1,20 +1,18 @@
-// Given a piece of 'code', runs it in the worklet, then once loaded waits for
-// layout and paint, before finishing the test.
+// Given a piece of 'code', runs it in the worklet, then once loaded finish the
+// test (content_shell will ensure a full frame update before exit).
 //
 // Usage:
-//   importPaintWorkletAndTerminateTestAfterAsyncPaint('/* worklet code goes here. */');
+//   importPaintWorkletThenEndTest('/* worklet code goes here. */');
 
-function importPaintWorkletAndTerminateTestAfterAsyncPaint(code) {
+function importPaintWorkletThenEndTest(code) {
     if (window.testRunner) {
       testRunner.waitUntilDone();
     }
 
     var blob = new Blob([code], {type: 'text/javascript'});
     CSS.paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
-        runAfterLayoutAndPaint(function() {
-            if (window.testRunner) {
-                testRunner.notifyDone();
-            }
-        });
+        if (window.testRunner) {
+            testRunner.notifyDone();
+        }
     });
 }
diff --git a/third_party/blink/web_tests/http/tests/csspaint/shadow-scale-with-page-zoom.html b/third_party/blink/web_tests/http/tests/csspaint/shadow-scale-with-page-zoom.html
index 5f08bf9..d98a875 100644
--- a/third_party/blink/web_tests/http/tests/csspaint/shadow-scale-with-page-zoom.html
+++ b/third_party/blink/web_tests/http/tests/csspaint/shadow-scale-with-page-zoom.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="./resources/test-runner-paint-worklet.js"></script>
 <style>
 html, body { margin: 0; padding: 0; }
@@ -37,7 +36,7 @@
 
 <script>
     document.body.style.zoom = "200%"
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importPaintWorkletThenEndTest(document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/anonymous-image-object.js b/third_party/blink/web_tests/http/tests/devtools/tracing/anonymous-image-object.js
index 2c1ab8d6..70556fa8 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/anonymous-image-object.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/anonymous-image-object.js
@@ -8,6 +8,7 @@
   await TestRunner.loadModule('performance_test_runner');
   await TestRunner.showPanel('timeline');
   await TestRunner.loadHTML(`
+      <script src="../../resources/run-after-layout-and-paint.js"></script>
       <style>
       div.marker::before {
           content: url(resources/test.bmp);
@@ -26,7 +27,7 @@
           img.addEventListener("load", onImageLoaded, false);
           function onImageLoaded()
           {
-              testRunner.layoutAndPaintAsyncThen(callback);
+              runAfterLayoutAndPaint(callback);
           }
           return promise;
       }
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/decode-resize.js b/third_party/blink/web_tests/http/tests/devtools/tracing/decode-resize.js
index 92a5dd31..a83bd44 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/decode-resize.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/decode-resize.js
@@ -7,6 +7,7 @@
   await TestRunner.loadModule('performance_test_runner');
   await TestRunner.showPanel('timeline');
   await TestRunner.loadHTML(`
+    <script src="../../resources/run-after-layout-and-paint.js"></script>
     <style>
     div {
         display: inline-block;
@@ -47,7 +48,7 @@
     {
         for (let image of images) {
             await addImage(image);
-            await new Promise(fulfill => testRunner.layoutAndPaintAsyncThen(fulfill));
+            await new Promise(fulfill => runAfterLayoutAndPaint(fulfill));
         }
         return generateFrames(3);
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/scroll-invalidations.js b/third_party/blink/web_tests/http/tests/devtools/tracing/scroll-invalidations.js
index 1c92ca4e..d4a5c50 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/scroll-invalidations.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/scroll-invalidations.js
@@ -7,13 +7,14 @@
   await TestRunner.loadModule('performance_test_runner');
   await TestRunner.showPanel('timeline');
   await TestRunner.loadHTML(`
+    <script src="../../resources/run-after-layout-and-paint.js"></script>
     <div style="width: 400px; height: 2000px; background-color: grey"></div>
     <div style="position: fixed; left: 50px; top: 100px; width: 50px; height: 50px; background-color: rgba(255, 100, 100, 0.6)"></div>
   `);
   await TestRunner.evaluateInPagePromise(`
     function scrollAndDisplay() {
       scrollTo(0, 200);
-      return new Promise(fulfill => testRunner.layoutAndPaintAsyncThen(fulfill));
+      return new Promise(fulfill => runAfterLayoutAndPaint(fulfill));
     }
   `);
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-gc-event.js b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-gc-event.js
index ad49930..ba20d38e 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-gc-event.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-gc-event.js
@@ -6,11 +6,14 @@
   TestRunner.addResult(`Tests the Timeline API instrumentation of a gc event\n`);
   await TestRunner.loadModule('performance_test_runner');
   await TestRunner.showPanel('timeline');
+  await TestRunner.loadHTML(`
+    <script src="../../../resources/run-after-layout-and-paint.js"></script>
+  `);
   await TestRunner.evaluateInPagePromise(`
       function produceGarbageForGCEvents()
       {
           window.gc();
-          return new Promise(fulfill => testRunner.layoutAndPaintAsyncThen(fulfill));
+          return new Promise(fulfill => runAfterLayoutAndPaint(fulfill));
       }
   `);
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-dom-gc.js b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-dom-gc.js
index a3ac236..24b74ecb 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-dom-gc.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-dom-gc.js
@@ -7,11 +7,15 @@
       `Tests the Timeline API instrumentation of a DOM GC event\n`);
   await TestRunner.loadModule('performance_test_runner');
   await TestRunner.showPanel('timeline');
+  await TestRunner.loadHTML(`
+    <script src="../../../resources/run-after-layout-and-paint.js"></script>
+  `);
+
   await TestRunner.evaluateInPagePromise(`
         function produceGarbageForGCEvents()
         {
             window.gc();
-            return new Promise(fulfill => testRunner.layoutAndPaintAsyncThen(fulfill));
+            return new Promise(fulfill => runAfterLayoutAndPaint(fulfill));
         }
     `);
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations-expected.txt b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations-expected.txt
index 6a8db3f..1aeae29 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations-expected.txt
@@ -2,7 +2,18 @@
 
 paint invalidations[
     {
-        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:21}
+        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:22}
+        changedAttribute : undefined
+        changedClass : undefined
+        changedId : undefined
+        changedPseudo : undefined
+        extraData : ""
+        nodeName : "DIV class='testElement'"
+        selectorPart : undefined
+        type : "StyleRecalcInvalidationTracking"
+    }
+    {
+        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:23}
         changedAttribute : undefined
         changedClass : undefined
         changedId : undefined
@@ -24,7 +35,7 @@
         type : "StyleRecalcInvalidationTracking"
     }
     {
-        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:21}
+        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:23}
         changedAttribute : undefined
         changedClass : undefined
         changedId : undefined
@@ -46,7 +57,7 @@
         type : "StyleRecalcInvalidationTracking"
     }
     {
-        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:21}
+        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:23}
         changedAttribute : undefined
         changedClass : undefined
         changedId : undefined
@@ -68,18 +79,7 @@
         type : "StyleRecalcInvalidationTracking"
     }
     {
-        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:21}
-        changedAttribute : undefined
-        changedClass : undefined
-        changedId : undefined
-        changedPseudo : undefined
-        extraData : ""
-        nodeName : "DIV class='testElement'"
-        selectorPart : undefined
-        type : "StyleRecalcInvalidationTracking"
-    }
-    {
-        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:22}
+        cause : {reason: Inline CSS style declaration was mutated, stackTrace: test://evaluations/0/timeline-grouped-invalidations.js:23}
         changedAttribute : undefined
         changedClass : undefined
         changedId : undefined
@@ -90,6 +90,6 @@
         type : "StyleRecalcInvalidationTracking"
     }
 ]
-PASS - record contained Inline CSS style declaration was mutated for [ DIV class='testElement' ], [ DIV class='testElement' ], and 2 others. (anonymous) @ timeline-grouped-invalidations.js:21
 PASS - record contained Inline CSS style declaration was mutated for [ DIV class='testElement' ], [ DIV class='testElement' ], and 2 others. (anonymous) @ timeline-grouped-invalidations.js:22
+PASS - record contained Inline CSS style declaration was mutated for [ DIV class='testElement' ], [ DIV class='testElement' ], and 2 others. (anonymous) @ timeline-grouped-invalidations.js:23
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations.js b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations.js
index 8fa05b8..3316923d 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations.js
@@ -8,6 +8,7 @@
   await TestRunner.showPanel('timeline');
   await TestRunner.loadHTML(`
     <!DOCTYPE HTML>
+    <script src="../../../resources/run-after-layout-and-paint.js"></script>
     <div class="testElement">P</div><div class="testElement">A</div>
     <div class="testElement">S</div><div class="testElement">S</div>
   `);
@@ -21,7 +22,7 @@
             testElements[i].style.color = "red";
             testElements[i].style.backgroundColor = "blue";
           }
-          testRunner.layoutAndPaintAsyncThen(resolve);
+          runAfterLayoutAndPaint(resolve);
         });
       });
     }
@@ -43,10 +44,10 @@
   var invalidations = invalidationsTree.shadowRoot.textContent;
   checkStringContains(
       invalidations,
-      `Inline CSS style declaration was mutated for [ DIV class='testElement' ], [ DIV class='testElement' ], and 2 others. (anonymous) @ timeline-grouped-invalidations.js:21`);
+      `Inline CSS style declaration was mutated for [ DIV class='testElement' ], [ DIV class='testElement' ], and 2 others. (anonymous) @ timeline-grouped-invalidations.js:22`);
   checkStringContains(
       invalidations,
-      `Inline CSS style declaration was mutated for [ DIV class='testElement' ], [ DIV class='testElement' ], and 2 others. (anonymous) @ timeline-grouped-invalidations.js:22`);
+      `Inline CSS style declaration was mutated for [ DIV class='testElement' ], [ DIV class='testElement' ], and 2 others. (anonymous) @ timeline-grouped-invalidations.js:23`);
   TestRunner.completeTest();
 
   function checkStringContains(string, contains) {
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/paint-profiler-update.js b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/paint-profiler-update.js
index a8f5818..4fac56a5 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/paint-profiler-update.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/paint-profiler-update.js
@@ -7,8 +7,9 @@
   await TestRunner.loadModule('performance_test_runner');
   await TestRunner.showPanel('timeline');
   await TestRunner.loadHTML(`
-      <div id="square" style="width: 40px; height: 40px"></div>
-    `);
+    <script src="../../../resources/run-after-layout-and-paint.js"></script>
+    <div id="square" style="width: 40px; height: 40px"></div>
+  `);
   await TestRunner.evaluateInPagePromise(`
       function performActions()
       {
@@ -20,13 +21,13 @@
           function step1()
           {
               square.style.backgroundColor = "red";
-              testRunner.layoutAndPaintAsyncThen(step2);
+              runAfterLayoutAndPaint(step2);
           }
 
           function step2()
           {
               square.style.backgroundColor = "black";
-              testRunner.layoutAndPaintAsyncThen(callback);
+              runAfterLayoutAndPaint(callback);
           }
           return promise;
       }
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/update-layer-tree.js b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/update-layer-tree.js
index 2abe03c..561b46c 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/update-layer-tree.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-paint/update-layer-tree.js
@@ -7,6 +7,7 @@
   await TestRunner.loadModule('performance_test_runner');
   await TestRunner.showPanel('timeline');
   await TestRunner.loadHTML(`
+      <script src="../../../resources/run-after-layout-and-paint.js"></script>
       <style>
       .layer {
           position: absolute;
@@ -23,7 +24,7 @@
           var layer = document.createElement("div");
           layer.classList.add("layer");
           document.getElementById("parent-layer").appendChild(layer);
-          return new Promise((fulfill) => testRunner.layoutAndPaintAsyncThen(fulfill));
+          return new Promise((fulfill) => runAfterLayoutAndPaint(fulfill));
       }
   `);
 
diff --git a/third_party/blink/web_tests/http/tests/media/controls/controls-list-add-hide.html b/third_party/blink/web_tests/http/tests/media/controls/controls-list-add-hide.html
index 5866f91..f77903c 100644
--- a/third_party/blink/web_tests/http/tests/media/controls/controls-list-add-hide.html
+++ b/third_party/blink/web_tests/http/tests/media/controls/controls-list-add-hide.html
@@ -2,6 +2,7 @@
 <title>Test adding keywords to controlsList hides buttons</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../../media-resources/media-controls.js"></script>
 <video controls id="enabled-controls" width="500px"></video>
 <script>
@@ -14,12 +15,12 @@
 
     v.controlsList.add('nodownload');
 
-    testRunner.layoutAndPaintAsyncThen(t.step_func(() => {
+    runAfterLayoutAndPaint(t.step_func(() => {
       assert_true(isFullscreenButtonEnabled(v));
       assert_false(isDownloadsButtonEnabled(v));
       v.controlsList.add('nofullscreen');
 
-      testRunner.layoutAndPaintAsyncThen(t.step_func_done(() => {
+      runAfterLayoutAndPaint(t.step_func_done(() => {
         assert_false(isFullscreenButtonEnabled(v));
         assert_false(isDownloadsButtonEnabled(v));
       }));
diff --git a/third_party/blink/web_tests/http/tests/media/controls/controls-list-remove-show.html b/third_party/blink/web_tests/http/tests/media/controls/controls-list-remove-show.html
index bb58312..5b25665 100644
--- a/third_party/blink/web_tests/http/tests/media/controls/controls-list-remove-show.html
+++ b/third_party/blink/web_tests/http/tests/media/controls/controls-list-remove-show.html
@@ -2,6 +2,7 @@
 <title>Test removing keywords from controlsList shows buttons</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../../media-resources/media-controls.js"></script>
 <video controlslist="nodownload nofullscreen" id="disabled-controls" width="500px"></video>
 <script>
@@ -14,13 +15,13 @@
 
     v.controlsList.remove('nodownload');
 
-    testRunner.layoutAndPaintAsyncThen(t.step_func(() => {
+    runAfterLayoutAndPaint(t.step_func(() => {
       assert_false(isFullscreenButtonEnabled(v));
       assert_true(isDownloadsButtonEnabled(v));
 
       v.controlsList.remove('nofullscreen');
 
-      testRunner.layoutAndPaintAsyncThen(t.step_func_done(() => {
+      runAfterLayoutAndPaint(t.step_func_done(() => {
         assert_true(isFullscreenButtonEnabled(v));
         assert_true(isDownloadsButtonEnabled(v));
       }));
diff --git a/third_party/blink/web_tests/http/tests/resources/run-after-layout-and-paint.js b/third_party/blink/web_tests/http/tests/resources/run-after-layout-and-paint.js
index e317341..bf2ed0c 100644
--- a/third_party/blink/web_tests/http/tests/resources/run-after-layout-and-paint.js
+++ b/third_party/blink/web_tests/http/tests/resources/run-after-layout-and-paint.js
@@ -1,26 +1,34 @@
-// Run a callback after layout and paint of all pending document changes.
+// Run a callback after a frame update.
 //
-// It has two modes:
-// - traditional mode, for existing tests, and tests needing customized notifyDone timing:
-//   Usage:
+// Note that this file has two copies:
+//   resources/run-after-layout-and-paint.js
+// and
+//   http/tests/resources/run-after-layout-and-paint.js.
+// They should be kept always the same.
+//
+// The function runAfterLayoutAndPaint() has two modes:
+// - traditional mode, for existing tests, and tests needing customized
+//   notifyDone timing:
 //     if (window.testRunner)
 //       testRunner.waitUntilDone();
 //     runAfterLayoutAndPaint(function() {
 //       ... // some code which modifies style/layout
 //       if (window.testRunner)
 //         testRunner.notifyDone();
-//       // Or to ensure the next paint is executed before the test finishes:
-//       // if (window.testRunner)
-//       //   runAfterAfterLayoutAndPaint(function() { testRunner.notifyDone() });
 //       // Or notifyDone any time later if needed.
 //     });
 //
-// - autoNotifyDone mode, for new tests which just need to change style/layout and finish:
-//   Usage:
+// - autoNotifyDone mode, for new tests which just need to change style/layout
+//   and finish:
 //     runAfterLayoutAndPaint(function() {
 //       ... // some code which modifies style/layout
 //     }, true);
-
+//
+// Note that because we always update a frame before finishing a test,
+// we don't need
+//     runAfterLayoutAndPaint(function() { testRunner.notifyDone(); })
+// to ensure the test finish after a frame update.
+//
 if (window.internals)
     internals.runtimeFlags.paintUnderInvalidationCheckingEnabled = true;
 
@@ -35,9 +43,18 @@
     if (autoNotifyDone)
         testRunner.waitUntilDone();
 
-    testRunner.layoutAndPaintAsyncThen(function() {
-        callback();
-        if (autoNotifyDone)
-            testRunner.notifyDone();
+    // We do requestAnimationFrame and setTimeout to ensure a frame has started
+    // and layout and paint have run. The requestAnimationFrame fires after the
+    // frame has started but before layout and paint. The setTimeout fires
+    // at the beginning of the next frame, meaning that the previous frame has
+    // completed layout and paint.
+    // See http://crrev.com/c/1395193/10/third_party/blink/web_tests/http/tests/resources/run-after-layout-and-paint.js
+    // for more discussions.
+    requestAnimationFrame(function() {
+        setTimeout(function() {
+            callback();
+            if (autoNotifyDone)
+                testRunner.notifyDone();
+        }, 0);
     });
 }
diff --git a/third_party/blink/web_tests/images/animated-gif-fast-crash.html b/third_party/blink/web_tests/images/animated-gif-fast-crash.html
index 2214ad9..379aa1f 100644
--- a/third_party/blink/web_tests/images/animated-gif-fast-crash.html
+++ b/third_party/blink/web_tests/images/animated-gif-fast-crash.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 Tests fast animating gif (10ms per frame). Passes if it doesn't crash.<br>
 <img src="resources/animated-10color-fast.gif">
+<script src="../resources/run-after-layout-and-paint.js"></script>
 <script>
 
 // Busy-wait 50ms to trigger the bug of invalidation during paint.
@@ -16,7 +17,7 @@
 function displayOneFrame() {
   delay();
   if (--count >= 0)
-    testRunner.layoutAndPaintAsyncThen(displayOneFrame);
+    runAfterLayoutAndPaint(displayOneFrame);
   else
     testRunner.notifyDone();
 }
diff --git a/third_party/blink/web_tests/images/animated-gif-with-offsets.html b/third_party/blink/web_tests/images/animated-gif-with-offsets.html
index 26db055..3d118de 100644
--- a/third_party/blink/web_tests/images/animated-gif-with-offsets.html
+++ b/third_party/blink/web_tests/images/animated-gif-with-offsets.html
@@ -4,6 +4,7 @@
 doesn't crash, we're good.
 <img src="resources/animated-gif-with-offsets.gif">
 </body>
+<script src="../resources/run-after-layout-and-paint.js"></script>
 <script>
 
 // Delay 50ms for each frame.
@@ -17,7 +18,7 @@
 function displayOneFrame() {
   delay();
   if (--count >= 0)
-    testRunner.layoutAndPaintAsyncThen(displayOneFrame);
+    runAfterLayoutAndPaint(displayOneFrame);
   else
     testRunner.notifyDone();
 }
diff --git a/third_party/blink/web_tests/images/resources/color-checker-munsell-chart.js b/third_party/blink/web_tests/images/resources/color-checker-munsell-chart.js
index 4b213b4..951389b 100644
--- a/third_party/blink/web_tests/images/resources/color-checker-munsell-chart.js
+++ b/third_party/blink/web_tests/images/resources/color-checker-munsell-chart.js
@@ -6,7 +6,7 @@
   var image = document.querySelector('img');
 
   image.onload = function() {
-    runAfterLayoutAndPaint(function () { setTimeout(drawImageToCanvas, 0) });
+    runAfterLayoutAndPaint(drawImageToCanvas);
   };
 
   image.src = source;
diff --git a/third_party/blink/web_tests/inspector-protocol/animation/animation-pause-infinite.js b/third_party/blink/web_tests/inspector-protocol/animation/animation-pause-infinite.js
index 837fa28..e928d32 100644
--- a/third_party/blink/web_tests/inspector-protocol/animation/animation-pause-infinite.js
+++ b/third_party/blink/web_tests/inspector-protocol/animation/animation-pause-infinite.js
@@ -1,5 +1,6 @@
 (async function(testRunner) {
   var {page, session, dp} = await testRunner.startHTML(`
+    <script src='../../resources/run-after-layout-and-paint.js'></script>
     <div id='node' style='background-color: red; height: 100px'></div>
   `, 'Tests that the animation is correctly paused.');
 
@@ -17,8 +18,7 @@
     (function rafWidth() {
         var callback;
         var promise = new Promise((fulfill) => callback = fulfill);
-        if (window.testRunner)
-            testRunner.layoutAndPaintAsyncThen(() => callback(node.offsetWidth));
+        runAfterLayoutAndPaint(() => callback(node.offsetWidth));
         return promise;
     })()
   `);
diff --git a/third_party/blink/web_tests/inspector-protocol/animation/animation-pause.js b/third_party/blink/web_tests/inspector-protocol/animation/animation-pause.js
index 37cfa58..238c974a 100644
--- a/third_party/blink/web_tests/inspector-protocol/animation/animation-pause.js
+++ b/third_party/blink/web_tests/inspector-protocol/animation/animation-pause.js
@@ -1,5 +1,6 @@
 (async function(testRunner) {
   var {page, session, dp} = await testRunner.startHTML(`
+    <script src='../../resources/run-after-layout-and-paint.js'></script>
     <div id='node' style='background-color: red; height: 100px'></div>
   `, 'Tests that the animation is correctly paused.');
 
@@ -17,8 +18,7 @@
     (function rafWidth() {
         var callback;
         var promise = new Promise((fulfill) => callback = fulfill);
-        if (window.testRunner)
-            testRunner.layoutAndPaintAsyncThen(() => callback(node.offsetWidth));
+        runAfterLayoutAndPaint(() => callback(node.offsetWidth));
         return promise;
     })()
   `);
diff --git a/third_party/blink/web_tests/inspector-protocol/animation/animation-seek-past-end.js b/third_party/blink/web_tests/inspector-protocol/animation/animation-seek-past-end.js
index 37dc66c..34206650 100644
--- a/third_party/blink/web_tests/inspector-protocol/animation/animation-seek-past-end.js
+++ b/third_party/blink/web_tests/inspector-protocol/animation/animation-seek-past-end.js
@@ -1,5 +1,6 @@
 (async function(testRunner) {
   var {page, session, dp} = await testRunner.startHTML(`
+    <script src='../../resources/run-after-layout-and-paint.js'></script>
     <div id='node' style='background-color: red; height: 100px; width: 100px'></div>
   `, 'Tests seeking animation past end time.');
 
@@ -16,8 +17,7 @@
     (function rafWidth() {
         var callback;
         var promise = new Promise((fulfill) => callback = fulfill);
-        if (window.testRunner)
-            testRunner.layoutAndPaintAsyncThen(() => callback(node.offsetWidth));
+        runAfterLayoutAndPaint(() => callback(node.offsetWidth));
         return promise;
     })()
   `);
diff --git a/third_party/blink/web_tests/media/controls-slider-appearance-crash.html b/third_party/blink/web_tests/media/controls-slider-appearance-crash.html
index e398a65..ff43cf30 100644
--- a/third_party/blink/web_tests/media/controls-slider-appearance-crash.html
+++ b/third_party/blink/web_tests/media/controls-slider-appearance-crash.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 
 <script src="../resources/js-test.js"></script>
+<script src="../resources/run-after-layout-and-paint.js"></script>
 <script>
 description("This test ensures that applying media control slider thumb appearance to pseudo-elements does not cause a crash.");
 </script>
@@ -25,6 +26,6 @@
 <script>
 if (window.testRunner) {
     testRunner.waitUntilDone();
-    testRunner.layoutAndPaintAsyncThen(function() { testRunner.notifyDone(); });
+    runAfterLayoutAndPaint(function() { testRunner.notifyDone(); });
 }
 </script>
diff --git a/third_party/blink/web_tests/media/controls/buttons-after-reset.html b/third_party/blink/web_tests/media/controls/buttons-after-reset.html
index 9bca350..496e966 100644
--- a/third_party/blink/web_tests/media/controls/buttons-after-reset.html
+++ b/third_party/blink/web_tests/media/controls/buttons-after-reset.html
@@ -2,6 +2,7 @@
 <html>
 <title>Test that resetting the controls after load is a no-op.</title>
 <script src="../media-controls.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <video controls width=400>
   <track kind=subtitles src=fake-en-sub.vtt srclang=en label=English>
   <track kind=subtitles src=fake-fr-sub.vtt srclang=fr label=French>
@@ -15,10 +16,10 @@
   video.src = '../content/test.ogv';
   video.addEventListener('loadedmetadata', () => {
     video.controls = false;
-    testRunner.layoutAndPaintAsyncThen(() => {
+    runAfterLayoutAndPaint(() => {
       video.controls = true;
 
-      testRunner.layoutAndPaintAsyncThen(() => {
+      runAfterLayoutAndPaint(() => {
         testRunner.notifyDone();
       });
     });
diff --git a/third_party/blink/web_tests/media/controls/captions-menu-always-visible-expected.html b/third_party/blink/web_tests/media/controls/captions-menu-always-visible-expected.html
index ce92e5c..b3473a9 100644
--- a/third_party/blink/web_tests/media/controls/captions-menu-always-visible-expected.html
+++ b/third_party/blink/web_tests/media/controls/captions-menu-always-visible-expected.html
@@ -28,8 +28,7 @@
     // :hover sometimes applying.
     textTrackMenu(video).style = 'pointer-events: none;';
 
-    testRunner.layoutAndPaintAsyncThen(() => {
+    if (window.testRunner)
       testRunner.notifyDone();
-    });
   });
 </script>
diff --git a/third_party/blink/web_tests/media/controls/captions-menu-always-visible.html b/third_party/blink/web_tests/media/controls/captions-menu-always-visible.html
index c361949..7507a2c5 100644
--- a/third_party/blink/web_tests/media/controls/captions-menu-always-visible.html
+++ b/third_party/blink/web_tests/media/controls/captions-menu-always-visible.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <title>Captions menu always visible</title>
 <script src="../media-controls.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <style>
   .container {
     overflow: hidden;
@@ -31,7 +32,7 @@
     // :hover sometimes applying.
     textTrackMenu(video).style = 'pointer-events: none;';
 
-    testRunner.layoutAndPaintAsyncThen(() => {
+    runAfterLayoutAndPaint(() => {
       testRunner.notifyDone();
     });
   });
diff --git a/third_party/blink/web_tests/media/controls/controls-layout-in-different-size.html b/third_party/blink/web_tests/media/controls/controls-layout-in-different-size.html
index 143b16a..7d0b0898 100644
--- a/third_party/blink/web_tests/media/controls/controls-layout-in-different-size.html
+++ b/third_party/blink/web_tests/media/controls/controls-layout-in-different-size.html
@@ -3,6 +3,7 @@
 <title>Media Controls: Test controls layout correctly in different small sizes.</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <video controls></video>
 <script>
@@ -23,7 +24,7 @@
     function runTestCase(index) {
       let test = testCases[index];
       video.width = test;
-      testRunner.layoutAndPaintAsyncThen(t.step_func(() => {
+      runAfterLayoutAndPaint(t.step_func(() => {
         expectLayoutCorrectly();
 
         assert_not_equals(getComputedStyle(overflowBtn), 'none',
diff --git a/third_party/blink/web_tests/media/controls/download-button-displays-with-preload-none.html b/third_party/blink/web_tests/media/controls/download-button-displays-with-preload-none.html
index e1e6c46..c4fd9d4 100644
--- a/third_party/blink/web_tests/media/controls/download-button-displays-with-preload-none.html
+++ b/third_party/blink/web_tests/media/controls/download-button-displays-with-preload-none.html
@@ -2,13 +2,14 @@
 <title>media controls download button preload none</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <video controls preload="none" src="https://someexample.example/example.mp4"></video>
 <script>
 async_test(function(t) {
   var video = document.querySelector("video");
 
-  testRunner.layoutAndPaintAsyncThen(t.step_func_done(function() {
+  runAfterLayoutAndPaint(t.step_func_done(function() {
     assert_true(isDownloadsButtonEnabled(video));
   }));
 
diff --git a/third_party/blink/web_tests/media/controls/large-overflow-menu-when-pip-enabled.html b/third_party/blink/web_tests/media/controls/large-overflow-menu-when-pip-enabled.html
index ced98b1..c272e2a 100644
--- a/third_party/blink/web_tests/media/controls/large-overflow-menu-when-pip-enabled.html
+++ b/third_party/blink/web_tests/media/controls/large-overflow-menu-when-pip-enabled.html
@@ -2,6 +2,7 @@
 <title>Large overflow menu when pip enabled</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <body>
 <video controls></video>
@@ -20,11 +21,11 @@
     expectOverflowMenuAndTrackListContainsPip();
 
     video.setAttribute('disablepictureinpicture', '');
-    testRunner.layoutAndPaintAsyncThen(t.step_func(() => {
+    runAfterLayoutAndPaint(t.step_func(() => {
       expectOverflowMenuAndTrackListNotContainsPip();
 
       video.removeAttribute('disablepictureinpicture');
-      testRunner.layoutAndPaintAsyncThen(t.step_func_done(() => {
+      runAfterLayoutAndPaint(t.step_func_done(() => {
         expectOverflowMenuAndTrackListContainsPip();
       }));
     }));
diff --git a/third_party/blink/web_tests/media/controls/modern/immersive-mode-adds-css-class.html b/third_party/blink/web_tests/media/controls/modern/immersive-mode-adds-css-class.html
index 61a49242..9dd1f748 100644
--- a/third_party/blink/web_tests/media/controls/modern/immersive-mode-adds-css-class.html
+++ b/third_party/blink/web_tests/media/controls/modern/immersive-mode-adds-css-class.html
@@ -3,6 +3,7 @@
 <title>Test that enabling immersive mode adds the immersive mode CSS class.</title>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../../media-controls.js"></script>
 <video controls width=400 preload=metadata src="../../content/60_sec_video.webm"></video>
 <script>
@@ -19,7 +20,7 @@
 
     // Make sure that we still respect immersive mode when enlarged.
     video.width = 1000;
-    testRunner.layoutAndPaintAsyncThen(t.step_func_done(() => {
+    runAfterLayoutAndPaint(t.step_func_done(() => {
       assert_true(controls.classList.contains(immersiveModeCSSClass));
       checkThatVRCSSRulesAreRespected();
     }));
diff --git a/third_party/blink/web_tests/media/controls/overflow-menu-always-visible-expected.html b/third_party/blink/web_tests/media/controls/overflow-menu-always-visible-expected.html
index 4a69e0f..327ea430 100644
--- a/third_party/blink/web_tests/media/controls/overflow-menu-always-visible-expected.html
+++ b/third_party/blink/web_tests/media/controls/overflow-menu-always-visible-expected.html
@@ -10,9 +10,6 @@
 </style>
 <video controls muted></video>
 <script>
-  if (window.testRunner)
-    testRunner.waitUntilDone();
-
   var video = document.querySelector('video');
   enableTestMode(video);
   video.src = '../content/test.ogv';
@@ -26,9 +23,5 @@
     // Disabling pointer events on the overflow menu to avoid a flakyness with
     // :hover sometimes applying.
     overflowMenu(video).style = 'pointer-events: none;';
-
-    testRunner.layoutAndPaintAsyncThen(() => {
-      testRunner.notifyDone();
-    });
   });
 </script>
diff --git a/third_party/blink/web_tests/media/controls/overflow-menu-always-visible.html b/third_party/blink/web_tests/media/controls/overflow-menu-always-visible.html
index ffcc86f..602edd8 100644
--- a/third_party/blink/web_tests/media/controls/overflow-menu-always-visible.html
+++ b/third_party/blink/web_tests/media/controls/overflow-menu-always-visible.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <title>Overflow menu always visible</title>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <style>
   .container {
@@ -30,7 +31,7 @@
     // :hover sometimes applying.
     overflowMenu(video).style = 'pointer-events: none;';
 
-    testRunner.layoutAndPaintAsyncThen(() => {
+    runAfterLayoutAndPaint(() => {
       testRunner.notifyDone();
     });
   });
diff --git a/third_party/blink/web_tests/media/controls/overflow-menu-animation.html b/third_party/blink/web_tests/media/controls/overflow-menu-animation.html
index 69cc4a2..3d5c070 100644
--- a/third_party/blink/web_tests/media/controls/overflow-menu-animation.html
+++ b/third_party/blink/web_tests/media/controls/overflow-menu-animation.html
@@ -2,6 +2,7 @@
 <title>Media Controls: overflow menu item list animation.</title>
 <script src='../../resources/testharness.js'></script>
 <script src='../../resources/testharnessreport.js'></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src='../media-controls.js'></script>
 <video controls width=400></video>
 <script>
@@ -18,7 +19,7 @@
 
     function runTestCase(index) {
       video.width = testCasesWidth[index];
-      testRunner.layoutAndPaintAsyncThen(t.step_func(() => {
+      runAfterLayoutAndPaint(t.step_func(() => {
 
         // Go through item list, check 'animated-##' class is presented if item is displayed
         // and check if the items' classes are in sequential order
diff --git a/third_party/blink/web_tests/media/controls/overlay-play-button-document-move.html b/third_party/blink/web_tests/media/controls/overlay-play-button-document-move.html
index cacba37..f39d8fc 100644
--- a/third_party/blink/web_tests/media/controls/overlay-play-button-document-move.html
+++ b/third_party/blink/web_tests/media/controls/overlay-play-button-document-move.html
@@ -2,6 +2,7 @@
 <title>media controls overlay play button document move</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <script src="overlay-play-button.js"></script>
 <body>
@@ -19,22 +20,22 @@
 
     // If the width goes under the minimum, the button should be hidden.
     video.width = NARROW_VIDEO_WIDTH;
-    testRunner.layoutAndPaintAsyncThen(t.step_func(function() {
+    runAfterLayoutAndPaint(t.step_func(function() {
       assertOverlayPlayButtonNotVisible(video);
 
       // Re-widening the video should display the button.
       video.width = NORMAL_VIDEO_WIDTH;
-      testRunner.layoutAndPaintAsyncThen(t.step_func(function() {
+      runAfterLayoutAndPaint(t.step_func(function() {
         assertOverlayPlayButtonVisible(video);
 
         // If the height goes under the minimum, the button should be hidden.
         video.height = NARROW_VIDEO_HEIGHT;
-        testRunner.layoutAndPaintAsyncThen(t.step_func(function() {
+        runAfterLayoutAndPaint(t.step_func(function() {
           assertOverlayPlayButtonNotVisible(video);
 
           // Re-heightening the video should display the button.
           video.height = NORMAL_VIDEO_HEIGHT;
-          testRunner.layoutAndPaintAsyncThen(t.step_func_done(function() {
+          runAfterLayoutAndPaint(t.step_func_done(function() {
             assertOverlayPlayButtonVisible(video);
           }));
         }));
diff --git a/third_party/blink/web_tests/media/controls/overlay-play-button-narrow.html b/third_party/blink/web_tests/media/controls/overlay-play-button-narrow.html
index 248a6a4..66d4b80 100644
--- a/third_party/blink/web_tests/media/controls/overlay-play-button-narrow.html
+++ b/third_party/blink/web_tests/media/controls/overlay-play-button-narrow.html
@@ -2,6 +2,7 @@
 <title>media controls overlay play button narrow</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <script src="overlay-play-button.js"></script>
 <body>
@@ -24,22 +25,22 @@
 
     // If the width goes under the minimum, the button should be hidden.
     video.width = NARROW_VIDEO_WIDTH;
-    testRunner.layoutAndPaintAsyncThen(t.step_func(function() {
+    runAfterLayoutAndPaint(t.step_func(function() {
       assertOverlayPlayButtonNotVisible(video);
 
       // Re-widening the video should display the button.
       video.width = NORMAL_VIDEO_WIDTH;
-      testRunner.layoutAndPaintAsyncThen(t.step_func(function() {
+      runAfterLayoutAndPaint(t.step_func(function() {
         assertOverlayPlayButtonVisible(video);
 
         // If the height goes under the minimum, the button should be hidden.
         video.height = NARROW_VIDEO_HEIGHT;
-        testRunner.layoutAndPaintAsyncThen(t.step_func(function() {
+        runAfterLayoutAndPaint(t.step_func(function() {
           assertOverlayPlayButtonNotVisible(video);
 
           // Re-heightening the video should display the button.
           video.height = NORMAL_VIDEO_HEIGHT;
-          testRunner.layoutAndPaintAsyncThen(t.step_func_done(function() {
+          runAfterLayoutAndPaint(t.step_func_done(function() {
             assertOverlayPlayButtonVisible(video);
           }));
         }));
diff --git a/third_party/blink/web_tests/media/controls/resizing-changes-css-class.html b/third_party/blink/web_tests/media/controls/resizing-changes-css-class.html
index 741625d..0d1592f0 100644
--- a/third_party/blink/web_tests/media/controls/resizing-changes-css-class.html
+++ b/third_party/blink/web_tests/media/controls/resizing-changes-css-class.html
@@ -3,6 +3,7 @@
 <title>Test that sizing changes are reflected in CSS classes.</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <video controls width=200 preload=none></video>
 <script>
@@ -26,7 +27,7 @@
   function runTestCase(index) {
     let test = testCases[index];
     video.width = test.width;
-    testRunner.layoutAndPaintAsyncThen(t.step_func(() => {
+    runAfterLayoutAndPaint(t.step_func(() => {
       test.expect();
       let nextIndex = index + 1;
       if (nextIndex === testCases.length) {
diff --git a/third_party/blink/web_tests/media/controls/settings-disable-controls.html b/third_party/blink/web_tests/media/controls/settings-disable-controls.html
index d753eb1..63735381 100644
--- a/third_party/blink/web_tests/media/controls/settings-disable-controls.html
+++ b/third_party/blink/web_tests/media/controls/settings-disable-controls.html
@@ -2,6 +2,7 @@
 <title>Test that 'mediaControlsEnabled' properly toggles the native controls</title>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script src="../media-controls.js"></script>
 <video controls></video>
 <script>
@@ -23,12 +24,12 @@
     assert_not_equals(mediaControlsButton(video, "panel").style.display, "none");
 
     internals.settings.setMediaControlsEnabled(false);
-    testRunner.layoutAndPaintAsyncThen(t.step_func(() => {
+    runAfterLayoutAndPaint(t.step_func(() => {
       assert_equals(mediaControlsButton(video, "panel").style.display, "none");
       assert_equals(overlayCastButton(video).style.display, "none");
 
       internals.settings.setMediaControlsEnabled(true);
-      testRunner.layoutAndPaintAsyncThen(t.step_func_done(() => {
+      runAfterLayoutAndPaint(t.step_func_done(() => {
         assert_not_equals(mediaControlsButton(video, "panel").style.display, "none");
         assert_equals(overlayCastButton(video).style.display, "none");
       }));
diff --git a/third_party/blink/web_tests/media/resources/munsell-video-chart.js b/third_party/blink/web_tests/media/resources/munsell-video-chart.js
index 8efbe02..2f7fce82 100644
--- a/third_party/blink/web_tests/media/resources/munsell-video-chart.js
+++ b/third_party/blink/web_tests/media/resources/munsell-video-chart.js
@@ -5,7 +5,7 @@
 
   var image = document.querySelector('img');
   image.onload = function() {
-    runAfterLayoutAndPaint(function () { setTimeout(drawImageToCanvas, 0) });
+    runAfterLayoutAndPaint(drawImageToCanvas);
   };
 
   image.src = source;
diff --git a/third_party/blink/web_tests/media/video-controls-overflow-menu-fullscreen-button.html b/third_party/blink/web_tests/media/video-controls-overflow-menu-fullscreen-button.html
index 8982077..47e3be2c 100644
--- a/third_party/blink/web_tests/media/video-controls-overflow-menu-fullscreen-button.html
+++ b/third_party/blink/web_tests/media/video-controls-overflow-menu-fullscreen-button.html
@@ -2,6 +2,7 @@
 <title>Clicking on the overflow fullscreen button opens the video in fullscreen.</title>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
+<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="media-controls.js"></script>
 <script src="overflow-menu.js"></script>
 
@@ -33,7 +34,7 @@
     document.onwebkitfullscreenchange = t.step_func(() => {
       assert_equals(document.fullscreenElement, video);
       // Hiding the overflow menu is triggered by layout.
-      testRunner.layoutAndPaintAsyncThen(t.step_func_done(() => {
+      runAfterLayoutAndPaint(t.step_func_done(() => {
         assert_equals(getComputedStyle(overflowMenu).display, "none");
       }));
     });
diff --git a/third_party/blink/web_tests/media/video-persistence-expected.html b/third_party/blink/web_tests/media/video-persistence-expected.html
index 6d52a37..bdb3365f 100644
--- a/third_party/blink/web_tests/media/video-persistence-expected.html
+++ b/third_party/blink/web_tests/media/video-persistence-expected.html
@@ -55,9 +55,7 @@
     });
 
     document.addEventListener('webkitfullscreenchange', e => {
-      testRunner.layoutAndPaintAsyncThen(() => {
-        testRunner.notifyDone();
-      });
+      testRunner.notifyDone();
     });
   }
 </script>
diff --git a/third_party/blink/web_tests/media/video-persistence.html b/third_party/blink/web_tests/media/video-persistence.html
index a9112eb..4f31080 100644
--- a/third_party/blink/web_tests/media/video-persistence.html
+++ b/third_party/blink/web_tests/media/video-persistence.html
@@ -50,10 +50,7 @@
 
     document.addEventListener('webkitfullscreenchange', e => {
       internals.setPersistent(video, true);
-
-      testRunner.layoutAndPaintAsyncThen(() => {
-        testRunner.notifyDone();
-      });
+      testRunner.notifyDone();
     });
   }
 </script>
diff --git a/third_party/blink/web_tests/paint/background/scrolling-background-with-negative-z-child.html b/third_party/blink/web_tests/paint/background/scrolling-background-with-negative-z-child.html
index 89b02050..0f4634f 100644
--- a/third_party/blink/web_tests/paint/background/scrolling-background-with-negative-z-child.html
+++ b/third_party/blink/web_tests/paint/background/scrolling-background-with-negative-z-child.html
@@ -1,4 +1,5 @@
 <!doctype html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <style>
 #container {
   overflow: scroll;
@@ -22,16 +23,7 @@
   <div id="compositedChild"></div>
 </div>
 <script>
-  if (window.testRunner) {
-    testRunner.waitUntilDone();
-    testRunner.layoutAndPaintAsyncThen(function() {
-      document.getElementById('container').scrollTop = 1000;
-      testRunner.notifyDone();
-    });
-  } else {
-    // For manual testing.
-    setTimeout(function() {
-      document.getElementById('container').scrollTop = 1000;
-    }, 500);
-  }
+  runAfterLayoutAndPaint(function() {
+    document.getElementById('container').scrollTop = 1000;
+  }, true);
 </script>
diff --git a/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html b/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html
index 2b7e28c..399fd65 100644
--- a/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html
+++ b/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html
@@ -1,5 +1,6 @@
 <html>
 <head>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <style type="text/css">
     #test1 div {
         height: 100px;
@@ -74,12 +75,12 @@
         // Ensure the deferred decoder has decoded ../resources/apple.jpg.
         testRunner.updateAllLifecyclePhasesAndCompositeThen(function() {
             internals.advanceImageAnimation(imgForAdvanceImageAnimation);
-            testRunner.layoutAndPaintAsyncThen(function() {
+            runAfterLayoutAndPaint(function() {
                 internals.startTrackingRepaints(document);
                 internals.advanceImageAnimation(imgForAdvanceImageAnimation);
-                testRunner.layoutAndPaintAsyncThen(function() {
+                runAfterLayoutAndPaint(function() {
                     internals.advanceImageAnimation(imgForAdvanceImageAnimation);
-                    testRunner.layoutAndPaintAsyncThen(finish);
+                    runAfterLayoutAndPaint(finish);
                 });
             });
         });
diff --git a/third_party/blink/web_tests/paint/invalidation/resources/text-based-repaint.js b/third_party/blink/web_tests/paint/invalidation/resources/text-based-repaint.js
index a9966490..f55bd95 100644
--- a/third_party/blink/web_tests/paint/invalidation/resources/text-based-repaint.js
+++ b/third_party/blink/web_tests/paint/invalidation/resources/text-based-repaint.js
@@ -28,11 +28,16 @@
     else
         testRunner.dumpAsText();
 
-    testRunner.layoutAndPaintAsyncThen(function() {
-        internals.startTrackingRepaints(top.document);
-        repaintTest();
-        if (!window.testIsAsync)
-            finishRepaintTest();
+    // This is equivalent to runAfterLayoutAndPaint() in
+    // ../../resources/run-after-layout-and-paint.js. Duplicate it here so that
+    // the callers don't need to include that file.
+    requestAnimationFrame(() => {
+        setTimeout(() => {
+            internals.startTrackingRepaints(top.document);
+            repaintTest();
+            if (!window.testIsAsync)
+                finishRepaintTest();
+        }, 0);
     });
 }
 
@@ -42,22 +47,11 @@
     runRepaintTest();
 }
 
-function forceStyleRecalc()
-{
-    if (document.body)
-        document.body.clientTop;
-    else if (document.documentElement)
-        document.documentElement.clientTop;
-}
-
 function finishRepaintTest()
 {
     if (!window.testRunner || !window.internals)
         return;
 
-    // Force a style recalc.
-    forceStyleRecalc();
-
     var flags = internals.LAYER_TREE_INCLUDES_PAINT_INVALIDATIONS;
 
     if (window.layerTreeAsTextAdditionalFlags)
diff --git a/third_party/blink/web_tests/paint/invalidation/resources/window-resize-repaint.js b/third_party/blink/web_tests/paint/invalidation/resources/window-resize-repaint.js
index ae0d641a..c5ef19e 100644
--- a/third_party/blink/web_tests/paint/invalidation/resources/window-resize-repaint.js
+++ b/third_party/blink/web_tests/paint/invalidation/resources/window-resize-repaint.js
@@ -21,7 +21,7 @@
     if (sizeIndex < testSizes.length) {
         internals.startTrackingRepaints(document);
         window.resizeTo(testSizes[sizeIndex].width, testSizes[sizeIndex].height);
-        testRunner.layoutAndPaintAsyncThen(doTest);
+        runAfterLayoutAndPaint(doTest);
     } else if (window.testRunner) {
         testRunner.setCustomTextOutput(repaintRects);
         testRunner.notifyDone();
@@ -33,6 +33,6 @@
     testRunner.waitUntilDone();
     onload = function() {
         window.resizeTo(testSizes[0].width, testSizes[0].height);
-        testRunner.layoutAndPaintAsyncThen(doTest);
+        runAfterLayoutAndPaint(doTest);
     };
 }
diff --git a/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container.html b/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container.html
index 4a0d229..c9c73d8 100644
--- a/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container.html
+++ b/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html> 
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <script src="../../../editing/editing.js"></script>
 <input id="root" style="will-change: transform" size="5"  value="test test test">
@@ -11,7 +12,7 @@
 function repaintTest() {
   root.focus();
   root.scrollLeft = 200;
-  requestAnimationFrame(function() {
+  runAfterLayoutAndPaint(function() {
     execMoveSelectionForwardByWordCommand();
     execMoveSelectionForwardByWordCommand();
     execMoveSelectionForwardByWordCommand();
diff --git a/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container.html b/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container.html
index dcd95ff..4210de6 100644
--- a/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container.html
+++ b/third_party/blink/web_tests/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html> 
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <script src="../../../editing/editing.js"></script>
 <input id="root" style="will-change: transform" size="5"  value="test test test">
@@ -11,7 +12,7 @@
 function repaintTest() {
   root.focus();
   root.scrollLeft = 200;
-  requestAnimationFrame(function() {
+  runAfterLayoutAndPaint(function() {
     execMoveSelectionForwardByWordCommand();
     execMoveSelectionForwardByWordCommand();
     execMoveSelectionForwardByWordCommand();
diff --git a/third_party/blink/web_tests/paint/invalidation/selection/selection-in-composited-scrolling-container.html b/third_party/blink/web_tests/paint/invalidation/selection/selection-in-composited-scrolling-container.html
index 0e886f7..35ca562 100644
--- a/third_party/blink/web_tests/paint/invalidation/selection/selection-in-composited-scrolling-container.html
+++ b/third_party/blink/web_tests/paint/invalidation/selection/selection-in-composited-scrolling-container.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <input id="target" size="5"  value="test test test">
 <script>
@@ -10,7 +11,7 @@
 function repaintTest() {
   target.focus();
   target.scrollLeft = 200;
-  requestAnimationFrame(function() {
+  runAfterLayoutAndPaint(function() {
       target.setSelectionRange(5, 10);
       finishRepaintTest();
   });
diff --git a/third_party/blink/web_tests/paint/invalidation/selection/selection-in-non-composited-scrolling-container.html b/third_party/blink/web_tests/paint/invalidation/selection/selection-in-non-composited-scrolling-container.html
index 9f4151bd..69ca757 100644
--- a/third_party/blink/web_tests/paint/invalidation/selection/selection-in-non-composited-scrolling-container.html
+++ b/third_party/blink/web_tests/paint/invalidation/selection/selection-in-non-composited-scrolling-container.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <input id="target"size="5"  value="test test test">
 <script>
@@ -10,7 +11,7 @@
 function repaintTest() {
   target.focus();
   target.scrollLeft = 5;
-  requestAnimationFrame(function() {
+  runAfterLayoutAndPaint(function() {
       target.setSelectionRange(5, 10);
       finishRepaintTest();
   });
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/feImage-target-reappend-to-document.svg b/third_party/blink/web_tests/paint/invalidation/svg/feImage-target-reappend-to-document.svg
index a6a2bc8..ca2ac50 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/feImage-target-reappend-to-document.svg
+++ b/third_party/blink/web_tests/paint/invalidation/svg/feImage-target-reappend-to-document.svg
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="runRepaintAndPixelTest()">
+    <script xlink:href="../../../resources/run-after-layout-and-paint.js"></script>
     <script xlink:href="../resources/text-based-repaint.js"></script>
     <title>There should be a single green 100x100 square.</title>
     <defs>
@@ -20,7 +21,7 @@
             var greenImage = document.getElementById("feimage-green");
             document.getElementById("filter").removeChild(greenImage);
 
-            testRunner.layoutAndPaintAsyncThen(function() {
+            runAfterLayoutAndPaint(function() {
                 document.getElementById("filter").appendChild(greenImage);
                 finishRepaintTest();
             });
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/js-late-mask-and-object-creation.svg b/third_party/blink/web_tests/paint/invalidation/svg/js-late-mask-and-object-creation.svg
index bd34aaca..e5861f1e 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/js-late-mask-and-object-creation.svg
+++ b/third_party/blink/web_tests/paint/invalidation/svg/js-late-mask-and-object-creation.svg
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">  
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  id="svg-root" width="100%" height="100%" viewBox="0 0 800 600" onload="runRepaintAndPixelTest()">
+<script xlink:href="../../../resources/run-after-layout-and-paint.js"/>
 <script xlink:href="../resources/text-based-repaint.js"/>
 
 <g id="content"/>
@@ -10,7 +11,7 @@
     var content = document.getElementById("content");
 
     function repaintTest() {
-        testRunner.layoutAndPaintAsyncThen(createObject);
+        createObject();
     }
 
     function createObject()
@@ -23,7 +24,7 @@
         rect.setAttribute("mask", "url(#dynMask)");
 
         content.appendChild(rect);
-        testRunner.layoutAndPaintAsyncThen(createMask);
+        runAfterLayoutAndPaint(createMask);
     }
 
     function createMask()
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation-expected.txt
index 30a8bab7..257c371 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation-expected.txt
@@ -29,6 +29,26 @@
         },
         {
           "object": "LayoutSVGRect rect",
+          "rect": [200, 100, 403, 249],
+          "reason": "chunk appeared"
+        },
+        {
+          "object": "LayoutSVGRect rect",
+          "rect": [200, 100, 403, 249],
+          "reason": "chunk appeared"
+        },
+        {
+          "object": "LayoutSVGRect rect",
+          "rect": [200, 100, 403, 249],
+          "reason": "chunk disappeared"
+        },
+        {
+          "object": "LayoutSVGRect rect",
+          "rect": [200, 100, 403, 249],
+          "reason": "chunk disappeared"
+        },
+        {
+          "object": "LayoutSVGRect rect",
           "rect": [100, 100, 403, 249],
           "reason": "chunk appeared"
         },
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation.svg b/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation.svg
index 47b1034..df86666 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation.svg
+++ b/third_party/blink/web_tests/paint/invalidation/svg/mask-invalidation.svg
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="runRepaintAndPixelTest()">
+<script xlink:href="../../../resources/run-after-layout-and-paint.js"></script>
 <script xlink:href="../resources/text-based-repaint.js"/>
 <script>
 window.testIsAsync = true;
@@ -24,7 +25,7 @@
 
 function repaintTest() {
     draw(150, 50);
-    requestAnimationFrame(function() {
+    runAfterLayoutAndPaint(function() {
         draw(50, 50);
         finishRepaintTest();
     });
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem-expected.txt
index 2ccb818..4f41439 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem-expected.txt
@@ -20,10 +20,20 @@
         {
           "object": "LayoutSVGRect rect id='move'",
           "rect": [28, 38, 10, 10],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRect rect id='move'",
+          "rect": [28, 18, 10, 10],
           "reason": "chunk appeared"
         },
         {
           "object": "LayoutSVGRect rect id='move'",
+          "rect": [28, 18, 10, 10],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRect rect id='move'",
           "rect": [18, 18, 10, 10],
           "reason": "disappeared"
         }
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem.html b/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem.html
index b0c3d165..895e5b7 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem.html
+++ b/third_party/blink/web_tests/paint/invalidation/svg/modify-inserted-listitem.html
@@ -3,6 +3,7 @@
   <rect id="ref" x="20" y="30" width="10" height="10" fill="red"></rect>
   <rect id="move" x="10" y="10" width="10" height="10" fill="green"></rect>
 </svg>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <script>
 testIsAsync = true;
@@ -13,10 +14,9 @@
     transform.matrix.e = 10;
     document.querySelector('#move').transform.baseVal.appendItem(transform);
 
-    requestAnimationFrame(function() {
+    runAfterLayoutAndPaint(function() {
         transform.matrix.f = 20;
-        if (window.testRunner)
-            finishRepaintTest();
+        finishRepaintTest();
     });
 }
 </script>
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-different-attr.html b/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-different-attr.html
index 86737dbc..02a6c215 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-different-attr.html
+++ b/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-different-attr.html
@@ -13,6 +13,7 @@
   <text id="target" x="15" y="10 20" fill="green">A B C</text>
   <text id="source" x="50" y="50 60" fill="blue">X Y Z</text>
 </svg>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <script>
 testIsAsync = true;
@@ -22,10 +23,9 @@
     var moved = document.querySelector('#source').y.baseVal.removeItem(1);
     document.querySelector('#target').x.baseVal.appendItem(moved);
 
-    requestAnimationFrame(function() {
+    runAfterLayoutAndPaint(function() {
         moved.value = 65;
-        if (window.testRunner)
-            finishRepaintTest();
+        finishRepaintTest();
     });
 }
 </script>
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-expected.txt
index 04c85d1f..cda0c1a3 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem-expected.txt
@@ -21,6 +21,11 @@
           "object": "LayoutSVGPath polygon id='target'",
           "rect": [18, 18, 30, 20],
           "reason": "full"
+        },
+        {
+          "object": "LayoutSVGPath polygon id='target'",
+          "rect": [18, 18, 20, 20],
+          "reason": "full"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem.html b/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem.html
index 52ab1488..7b4f493 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem.html
+++ b/third_party/blink/web_tests/paint/invalidation/svg/modify-transferred-listitem.html
@@ -4,6 +4,7 @@
   <polygon id="target" points="20,10 10,30 30,30" fill="green"></polygon>
   <polygon id="source" points="20,20" fill="blue"></polygon>
 </svg>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <script>
 testIsAsync = true;
@@ -13,11 +14,10 @@
     var moved = document.querySelector('#source').points.removeItem(0);
     document.querySelector('#target').points.appendItem(moved);
 
-    requestAnimationFrame(function() {
+    runAfterLayoutAndPaint(function() {
         moved.x = 40;
         moved.y = 10;
-        if (window.testRunner)
-            finishRepaintTest();
+        finishRepaintTest();
     });
 }
 </script>
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt
index 6ba0ad1..b12e613 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div-expected.txt
@@ -104,6 +104,16 @@
         },
         {
           "object": "LayoutBlockFlow (positioned) div id='html' class='outerBox'",
+          "rect": [415, 125, 150, 150],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) div id='html' class='outerBox'",
+          "rect": [415, 125, 150, 150],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) div id='html' class='outerBox'",
           "rect": [400, 100, 150, 150],
           "reason": "geometry"
         },
@@ -194,6 +204,16 @@
         },
         {
           "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [115, 125, 150, 150],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [115, 125, 150, 150],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
           "rect": [100, 100, 150, 150],
           "reason": "paint property change"
         },
@@ -284,6 +304,16 @@
         },
         {
           "object": "LayoutBlockFlow div class='innerBox'",
+          "rect": [440, 150, 100, 100],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow div class='innerBox'",
+          "rect": [440, 150, 100, 100],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow div class='innerBox'",
           "rect": [425, 125, 100, 100],
           "reason": "geometry"
         },
@@ -374,6 +404,16 @@
         },
         {
           "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [140, 150, 100, 100],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
+          "rect": [140, 150, 100, 100],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutSVGRoot (positioned) svg id='svg'",
           "rect": [125, 125, 100, 100],
           "reason": "paint property change"
         }
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div.xhtml b/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div.xhtml
index 3850746..b5fac673 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div.xhtml
+++ b/third_party/blink/web_tests/paint/invalidation/svg/repaint-moving-svg-and-div.xhtml
@@ -1,5 +1,6 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/text-based-repaint.js"></script>
 <style>
 
@@ -86,7 +87,7 @@
         htmlStyle.top = htmlTop + "px";
 
         ++iterations;
-        requestAnimationFrame(repaintTest);
+        runAfterLayoutAndPaint(repaintTest);
     } else {
         finishRepaintTest();
     }
diff --git a/third_party/blink/web_tests/paint/invalidation/text-decoration-invalidation.html b/third_party/blink/web_tests/paint/invalidation/text-decoration-invalidation.html
index 9d62333..feddcfc 100644
--- a/third_party/blink/web_tests/paint/invalidation/text-decoration-invalidation.html
+++ b/third_party/blink/web_tests/paint/invalidation/text-decoration-invalidation.html
@@ -22,15 +22,12 @@
 <span>When</span> <span>not hovered, </span><span>there</span> <span>should</span>   <span>  be  </span> <span>no</span> <span>underlines.</span>
 </div>
 
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
-if (window.testRunner) {
-    testRunner.waitUntilDone();
-    if (window.eventSender);
-        eventSender.mouseMoveTo(underlineNoHover.offsetLeft + 10, underlineNoHover.offsetTop + 10);
-    testRunner.layoutAndPaintAsyncThen(function(){
-        if (window.eventSender);
-            eventSender.mouseMoveTo(underlineHover.offsetLeft + 10, underlineHover.offsetTop + 10);
-        testRunner.notifyDone();
-    });
-}
-</script>
\ No newline at end of file
+if (window.eventSender)
+    eventSender.mouseMoveTo(underlineNoHover.offsetLeft + 10, underlineNoHover.offsetTop + 10);
+runAfterLayoutAndPaint(function() {
+    if (window.eventSender)
+        eventSender.mouseMoveTo(underlineHover.offsetLeft + 10, underlineHover.offsetTop + 10);
+}, true);
+</script>
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited.html
index 9f8e48b..b44f303 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <script>
     if (window.internals)
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered.html
index 062f9ac..6ba82ac 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
 body {
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-generated.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-generated.html
index e0158c8..cfc9cff5 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-generated.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-generated.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
 body {
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents.html
index 0307d0af..9def798 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
 body {
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-non-fixed.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-non-fixed.html
index 046c9c6..32b682e 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-non-fixed.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-non-fixed.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
 body {
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos.html
index 96f0ab0..12cd01f3 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
 .container {
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-centered.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-centered.html
index 97b67ba8..a4573a1 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-centered.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-centered.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
 #target {
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-frameset.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-frameset.html
index 88066c15..192249a0 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-frameset.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-frameset.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <frameset cols="25%,*,25%">
   <frame> 
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-media-query.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-media-query.html
index 35b681fc..fb6a549 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-media-query.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-media-query.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style media="(min-height: 201px)">
 body { background-color: blue; }
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change1.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change1.html
index 0f3c882..b93d6b0d 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change1.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change1.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
   html { overflow: hidden; }
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change2.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change2.html
index ed04a5c..1be0a5da 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change2.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-no-layout-change2.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <style>
   html { overflow: hidden; }
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-html.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-html.html
index 873a28c5..f462890 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-html.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-html.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <html style="height: 50%">
 <body style="height: 100%; margin: 0">
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-width-height.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-width-height.html
index 6a1e1f0..f3d5d9c 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-width-height.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-percent-width-height.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <body style="margin: 0">
     <div style="position: absolute; width: 50%; height: 50%; background-color: blue"></div>
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-bottom.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-bottom.html
index 74872c5..5b781c9 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-bottom.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-bottom.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <body style="margin: 0">
     <div style="position: absolute; width: 20px; height: 20px; bottom: 20px; background-color: blue"></div>
-</body>
\ No newline at end of file
+</body>
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-percent-top.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-percent-top.html
index 6f613906..79097ac 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-percent-top.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-positioned-percent-top.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <body style="margin: 0">
     <div style="position: absolute; top: 50%; width: 20px; height: 20px; background-color: blue"></div>
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-vertical-writing-mode.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-vertical-writing-mode.html
index 2af6f0c6..64e25e1ff 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-vertical-writing-mode.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-vertical-writing-mode.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <html style="-webkit-writing-mode: vertical-rl">
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <body style="font-size: 60px">
 AAAA BBBB CCCC DDDD EEEE FFFF GGGG HHHH IIII JJJJ KKKK LLLL MMMM NNNN
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-viewport-percent.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-viewport-percent.html
index 18bf896..d216e7d 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-viewport-percent.html
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-viewport-percent.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <script src="../resources/window-resize-repaint.js"></script>
 <body style="margin: 0">
     <div style="position: absolute">
diff --git a/third_party/blink/web_tests/paint/selection/resources/selection.js b/third_party/blink/web_tests/paint/selection/resources/selection.js
index bfc2a447..292e26d 100644
--- a/third_party/blink/web_tests/paint/selection/resources/selection.js
+++ b/third_party/blink/web_tests/paint/selection/resources/selection.js
@@ -1,7 +1,7 @@
 function selectRangeAfterLayoutAndPaint(startElement, startIndex, endElement, endIndex) {
     runAfterLayoutAndPaint(function() {
         selectRange(startElement, startIndex, endElement, endIndex);
-      }, true);
+    }, true);
 }
 
 function selectRange(startElement, startIndex, endElement, endIndex) {
@@ -15,4 +15,4 @@
   var range = document.createRange();
   range.selectNode(element);
   window.getSelection().addRange(range);
-}
\ No newline at end of file
+}
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
index f5c64f2..83570e7 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
@@ -32,6 +32,11 @@
           "object": "LayoutTextControl INPUT id='root'",
           "rect": [0, 0, 61, 22],
           "reason": "full layer"
+        },
+        {
+          "object": "Caret",
+          "rect": [58, 4, 1, 16],
+          "reason": "caret"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
index f5c64f2..83570e7 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
@@ -32,6 +32,11 @@
           "object": "LayoutTextControl INPUT id='root'",
           "rect": [0, 0, 61, 22],
           "reason": "full layer"
+        },
+        {
+          "object": "Caret",
+          "rect": [58, 4, 1, 16],
+          "reason": "caret"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
index cc0c514..09a0829d 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -31,6 +31,11 @@
           "object": "LayoutBlockFlow DIV",
           "rect": [10, 11, 56, 16],
           "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [18, 11, 26, 16],
+          "reason": "selection"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
index f7a54884..e144691 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -26,6 +26,11 @@
           "object": "LayoutBlockFlow DIV",
           "rect": [10, 11, 57, 16],
           "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [31, 11, 26, 16],
+          "reason": "selection"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.10/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
index ae51837..f1d0681 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.10/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -26,6 +26,11 @@
           "object": "LayoutBlockFlow DIV",
           "rect": [11, 11, 35, 13],
           "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [11, 11, 15, 13],
+          "reason": "selection"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.10/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
new file mode 100644
index 0000000..6b51b7d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutTextControl INPUT id='target'",
+          "rect": [5, 5, 47, 25],
+          "reason": "subtree"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [11, 11, 35, 13],
+          "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [28, 11, 18, 13],
+          "reason": "selection"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
new file mode 100644
index 0000000..764dc14
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
new file mode 100644
index 0000000..4afd29cd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
new file mode 100644
index 0000000..d82dd1c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
new file mode 100644
index 0000000..1ca9543
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
new file mode 100644
index 0000000..1027048
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..aae62e6e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..db9466d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
new file mode 100644
index 0000000..764dc14
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
new file mode 100644
index 0000000..4afd29cd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
new file mode 100644
index 0000000..d82dd1c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
new file mode 100644
index 0000000..1ca9543
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
new file mode 100644
index 0000000..1027048
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..aae62e6e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..db9466d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
new file mode 100644
index 0000000..764dc14
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
new file mode 100644
index 0000000..4afd29cd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
new file mode 100644
index 0000000..d82dd1c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
new file mode 100644
index 0000000..1ca9543
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
new file mode 100644
index 0000000..1027048
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..aae62e6e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..db9466d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
new file mode 100644
index 0000000..764dc14
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
new file mode 100644
index 0000000..4afd29cd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
new file mode 100644
index 0000000..d82dd1c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
new file mode 100644
index 0000000..1ca9543
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
new file mode 100644
index 0000000..1027048
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..aae62e6e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..db9466d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
index c0ef892..b5b2bfb9 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
@@ -32,6 +32,11 @@
           "object": "LayoutTextControl INPUT id='root'",
           "rect": [0, 0, 41, 19],
           "reason": "full layer"
+        },
+        {
+          "object": "Caret",
+          "rect": [39, 6, 1, 13],
+          "reason": "caret"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
index c0ef892..b5b2bfb9 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
@@ -32,6 +32,11 @@
           "object": "LayoutTextControl INPUT id='root'",
           "rect": [0, 0, 41, 19],
           "reason": "full layer"
+        },
+        {
+          "object": "Caret",
+          "rect": [39, 6, 1, 13],
+          "reason": "caret"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
index 674f7f3..1b536cb 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -31,6 +31,11 @@
           "object": "LayoutBlockFlow DIV",
           "rect": [11, 11, 34, 13],
           "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [11, 11, 14, 13],
+          "reason": "selection"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
index ae51837..cecda7d 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -26,6 +26,11 @@
           "object": "LayoutBlockFlow DIV",
           "rect": [11, 11, 35, 13],
           "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [29, 11, 17, 13],
+          "reason": "selection"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
index f912a02..a9f775b 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
@@ -23,11 +23,31 @@
           "reason": "appeared"
         },
         {
+          "object": "InlineTextBox ' B C'",
+          "rect": [23, 8, 85, 24],
+          "reason": "appeared"
+        },
+        {
+          "object": "InlineTextBox ' B C'",
+          "rect": [23, 8, 85, 24],
+          "reason": "disappeared"
+        },
+        {
           "object": "InlineTextBox 'A'",
           "rect": [23, 8, 85, 24],
           "reason": "appeared"
         },
         {
+          "object": "InlineTextBox 'A'",
+          "rect": [23, 8, 85, 24],
+          "reason": "appeared"
+        },
+        {
+          "object": "InlineTextBox 'A'",
+          "rect": [23, 8, 85, 24],
+          "reason": "disappeared"
+        },
+        {
           "object": "InlineTextBox ' B C'",
           "rect": [23, 8, 80, 24],
           "reason": "disappeared"
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
index bf57eca..764dc14 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
new file mode 100644
index 0000000..4afd29cd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-in-positioned-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
index fa4f8c7..d82dd1c 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
index 1566267..1ca9543 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-with-paint-root-offset-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
index e311764..1027048 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-focus-ring-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
index 25c4a6a..aae62e6e 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
index e86ef63d..db9466d 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
index 1a1be903..b2c5d38 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-composited-scrolling-container-expected.txt
@@ -32,6 +32,11 @@
           "object": "LayoutTextControl INPUT id='root'",
           "rect": [0, 0, 68, 22],
           "reason": "full layer"
+        },
+        {
+          "object": "Caret",
+          "rect": [65, 4, 1, 16],
+          "reason": "caret"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
index 1a1be903..b2c5d38 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/invalidate-caret-in-non-composited-scrolling-container-expected.txt
@@ -32,6 +32,11 @@
           "object": "LayoutTextControl INPUT id='root'",
           "rect": [0, 0, 68, 22],
           "reason": "full layer"
+        },
+        {
+          "object": "Caret",
+          "rect": [65, 4, 1, 16],
+          "reason": "caret"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
index 671c110..986b7789 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -31,6 +31,11 @@
           "object": "LayoutBlockFlow DIV",
           "rect": [10, 11, 63, 16],
           "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [25, 11, 26, 16],
+          "reason": "selection"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
index 3ee964e..5def703 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -26,6 +26,11 @@
           "object": "LayoutBlockFlow DIV",
           "rect": [10, 11, 64, 16],
           "reason": "paint property change"
+        },
+        {
+          "object": "InlineTextBox 'test test test'",
+          "rect": [31, 11, 26, 16],
+          "reason": "selection"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
index 5dea806fb..59f8f6dc 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/svg/modify-transferred-listitem-different-attr-expected.txt
@@ -23,11 +23,31 @@
           "reason": "appeared"
         },
         {
+          "object": "InlineTextBox ' B C'",
+          "rect": [23, 8, 85, 23],
+          "reason": "appeared"
+        },
+        {
+          "object": "InlineTextBox ' B C'",
+          "rect": [23, 8, 85, 23],
+          "reason": "disappeared"
+        },
+        {
           "object": "InlineTextBox 'A'",
           "rect": [23, 8, 85, 23],
           "reason": "appeared"
         },
         {
+          "object": "InlineTextBox 'A'",
+          "rect": [23, 8, 85, 23],
+          "reason": "appeared"
+        },
+        {
+          "object": "InlineTextBox 'A'",
+          "rect": [23, 8, 85, 23],
+          "reason": "disappeared"
+        },
+        {
           "object": "InlineTextBox ' B C'",
           "rect": [23, 8, 80, 23],
           "reason": "disappeared"
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
new file mode 100644
index 0000000..7bad532
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-arc-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
new file mode 100644
index 0000000..982bd43
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-ellipse-circumference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
index 540a17bf..1464101 100644
--- a/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
+++ b/third_party/blink/web_tests/platform/win7/virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/resources/run-after-layout-and-paint.js b/third_party/blink/web_tests/resources/run-after-layout-and-paint.js
index e317341..bf2ed0c 100644
--- a/third_party/blink/web_tests/resources/run-after-layout-and-paint.js
+++ b/third_party/blink/web_tests/resources/run-after-layout-and-paint.js
@@ -1,26 +1,34 @@
-// Run a callback after layout and paint of all pending document changes.
+// Run a callback after a frame update.
 //
-// It has two modes:
-// - traditional mode, for existing tests, and tests needing customized notifyDone timing:
-//   Usage:
+// Note that this file has two copies:
+//   resources/run-after-layout-and-paint.js
+// and
+//   http/tests/resources/run-after-layout-and-paint.js.
+// They should be kept always the same.
+//
+// The function runAfterLayoutAndPaint() has two modes:
+// - traditional mode, for existing tests, and tests needing customized
+//   notifyDone timing:
 //     if (window.testRunner)
 //       testRunner.waitUntilDone();
 //     runAfterLayoutAndPaint(function() {
 //       ... // some code which modifies style/layout
 //       if (window.testRunner)
 //         testRunner.notifyDone();
-//       // Or to ensure the next paint is executed before the test finishes:
-//       // if (window.testRunner)
-//       //   runAfterAfterLayoutAndPaint(function() { testRunner.notifyDone() });
 //       // Or notifyDone any time later if needed.
 //     });
 //
-// - autoNotifyDone mode, for new tests which just need to change style/layout and finish:
-//   Usage:
+// - autoNotifyDone mode, for new tests which just need to change style/layout
+//   and finish:
 //     runAfterLayoutAndPaint(function() {
 //       ... // some code which modifies style/layout
 //     }, true);
-
+//
+// Note that because we always update a frame before finishing a test,
+// we don't need
+//     runAfterLayoutAndPaint(function() { testRunner.notifyDone(); })
+// to ensure the test finish after a frame update.
+//
 if (window.internals)
     internals.runtimeFlags.paintUnderInvalidationCheckingEnabled = true;
 
@@ -35,9 +43,18 @@
     if (autoNotifyDone)
         testRunner.waitUntilDone();
 
-    testRunner.layoutAndPaintAsyncThen(function() {
-        callback();
-        if (autoNotifyDone)
-            testRunner.notifyDone();
+    // We do requestAnimationFrame and setTimeout to ensure a frame has started
+    // and layout and paint have run. The requestAnimationFrame fires after the
+    // frame has started but before layout and paint. The setTimeout fires
+    // at the beginning of the next frame, meaning that the previous frame has
+    // completed layout and paint.
+    // See http://crrev.com/c/1395193/10/third_party/blink/web_tests/http/tests/resources/run-after-layout-and-paint.js
+    // for more discussions.
+    requestAnimationFrame(function() {
+        setTimeout(function() {
+            callback();
+            if (autoNotifyDone)
+                testRunner.notifyDone();
+        }, 0);
     });
 }
diff --git a/third_party/blink/web_tests/svg/animations/animate-restart-never.html b/third_party/blink/web_tests/svg/animations/animate-restart-never.html
index 7f8127fc..743df85 100644
--- a/third_party/blink/web_tests/svg/animations/animate-restart-never.html
+++ b/third_party/blink/web_tests/svg/animations/animate-restart-never.html
@@ -13,11 +13,7 @@
 
 function animationEnded() {
     click(50,50);
-    if (window.testRunner) {
-      testRunner.layoutAndPaintAsyncThen(function() {
-        testRunner.notifyDone();
-      });
-    }
+    testRunner.notifyDone();
 }
 </script>
 <svg>
diff --git a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-1.html b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-1.html
index dbe549f6..14b80064 100644
--- a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-1.html
+++ b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-1.html
@@ -1,18 +1,11 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 if (window.testRunner) {
   testRunner.dumpAsText();
-  testRunner.waitUntilDone();
   window.onload = function() {
-    testRunner.layoutAndPaintAsyncThen(function() {
-      mutateTree();
-      testRunner.layoutAndPaintAsyncThen(function() {
-        testRunner.notifyDone();
-      });
-    });
+    runAfterLayoutAndPaint(mutateTree, true);
   };
-} else {
-  window.onload = function() { setTimeout(mutateTree, 100); };
 }
 function mutateTree() {
   // A reference from the 'rect' to the pattern cycle.
diff --git a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-2.html b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-2.html
index 84175b1..5060ecd6 100644
--- a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-2.html
+++ b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-2.html
@@ -1,18 +1,11 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 if (window.testRunner) {
   testRunner.dumpAsText();
-  testRunner.waitUntilDone();
   window.onload = function() {
-    testRunner.layoutAndPaintAsyncThen(function() {
-      mutateTree();
-      testRunner.layoutAndPaintAsyncThen(function() {
-        testRunner.notifyDone();
-      });
-    });
+    runAfterLayoutAndPaint(mutateTree, true);
   };
-} else {
-  window.onload = function() { setTimeout(mutateTree, 100); };
 }
 function mutateTree() {
   // Add a reference from the rect in pattern#p3 to form a cycle.
diff --git a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-3.html b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-3.html
index 58325935..ad7cb352 100644
--- a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-3.html
+++ b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-3.html
@@ -1,18 +1,11 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 if (window.testRunner) {
   testRunner.dumpAsText();
-  testRunner.waitUntilDone();
   window.onload = function() {
-    testRunner.layoutAndPaintAsyncThen(function() {
-      mutateTree();
-      testRunner.layoutAndPaintAsyncThen(function() {
-        testRunner.notifyDone();
-      });
-    });
+    runAfterLayoutAndPaint(mutateTree, true);
   };
-} else {
-  window.onload = function() { setTimeout(mutateTree, 100); };
 }
 const svgNs = 'http://www.w3.org/2000/svg';
 function buildPattern(patternId, refId) {
diff --git a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-4.html b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-4.html
index b053b48..5308106 100644
--- a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-4.html
+++ b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle-dynamic-4.html
@@ -1,18 +1,11 @@
 <!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
 <script>
 if (window.testRunner) {
   testRunner.dumpAsText();
-  testRunner.waitUntilDone();
   window.onload = function() {
-    testRunner.layoutAndPaintAsyncThen(function() {
-      mutateTree();
-      testRunner.layoutAndPaintAsyncThen(function() {
-        testRunner.notifyDone();
-      });
-    });
+    runAfterLayoutAndPaint(mutateTree, true);
   };
-} else {
-  window.onload = function() { setTimeout(mutateTree, 100); };
 }
 const svgNs = 'http://www.w3.org/2000/svg';
 function buildPattern(patternId, refId) {
diff --git a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle.html b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle.html
index 6356e35..619ce00 100644
--- a/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle.html
+++ b/third_party/blink/web_tests/svg/custom/pattern-3-step-cycle.html
@@ -1,12 +1,7 @@
 <!DOCTYPE html>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
   testRunner.dumpAsText();
-  testRunner.waitUntilDone();
-  window.onload = function() {
-    testRunner.layoutAndPaintAsyncThen(function() { testRunner.notifyDone(); });
-  };
-}
 </script>
 <p>PASS if no crash (stack overflow).</p>
 <svg width="100" height="100">
diff --git a/third_party/blink/web_tests/svg/custom/pattern-content-cycle-w-resourceless-container.html b/third_party/blink/web_tests/svg/custom/pattern-content-cycle-w-resourceless-container.html
index b15db7065..53c91cd 100644
--- a/third_party/blink/web_tests/svg/custom/pattern-content-cycle-w-resourceless-container.html
+++ b/third_party/blink/web_tests/svg/custom/pattern-content-cycle-w-resourceless-container.html
@@ -1,12 +1,7 @@
 <!DOCTYPE html>
 <script>
-if (window.testRunner) {
+if (window.testRunner)
   testRunner.dumpAsText();
-  testRunner.waitUntilDone();
-  window.onload = function() {
-    testRunner.layoutAndPaintAsyncThen(function() { testRunner.notifyDone(); });
-  };
-}
 </script>
 <p>PASS if no crash (stack overflow).</p>
 <svg width="100" height="100">
diff --git a/third_party/blink/web_tests/svg/custom/svg-image-container-size.html b/third_party/blink/web_tests/svg/custom/svg-image-container-size.html
index 772220b..d896df6 100644
--- a/third_party/blink/web_tests/svg/custom/svg-image-container-size.html
+++ b/third_party/blink/web_tests/svg/custom/svg-image-container-size.html
@@ -19,10 +19,7 @@
 }
 
 function startTest() {
-  if (window.testRunner)
-    testRunner.layoutAndPaintAsyncThen(insertSVGImage);
-  else
-    requestAnimationFrame(insertSVGImage);
+  runAfterLayoutAndPaint(insertSVGImage);
 }
 </script>
 <svg width="384" height="128" style="display: block">
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index bf119ed..380b987 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -180,6 +180,12 @@
 chrome.accessibilityPrivate.setSwitchAccessMenuState = function(show, element_bounds) {};
 
 /**
+ * When enabled, forwards key events to the Switch Access extension.
+ * @param {boolean} should_forward
+ */
+chrome.accessibilityPrivate.forwardKeyEventsToSwitchAccess = function(should_forward) {};
+
+/**
  * Sets current ARC app to use native ARC support.
  * @param {boolean} enabled True for ChromeVox (native), false for TalkBack.
  */
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index fda5e99..3c1138fd 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -11516,6 +11516,11 @@
   <description>User pressed 'Scan QR Code' in the app menu.</description>
 </action>
 
+<action name="MobileMenuSearchCopiedImage">
+  <owner>rkgibson@google.com</owner>
+  <description>User pressed 'Search Copied Image' in the app menu.</description>
+</action>
+
 <action name="MobileMenuSettings">
   <owner>aurimas@chromium.org</owner>
   <description>User pressed 'Settings' in the app menu.</description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4f67b80e..f6d79bb 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -3534,6 +3534,7 @@
   <int value="15" label="Component update task"/>
   <int value="16" label="Deprecated Explore Sites refresh task"/>
   <int value="17" label="Explore Sites refresh task"/>
+  <int value="18" label="Download auto-resumption task"/>
 </enum>
 
 <enum name="BackgroundTracingState">
@@ -17624,6 +17625,8 @@
   <int value="1305" label="AUTOTESTPRIVATE_SENDASSISTANTTEXTQUERY"/>
   <int value="1306" label="AUTOTESTPRIVATE_SETCROSTINIAPPSCALED"/>
   <int value="1307" label="ACTIVITYLOGPRIVATE_DELETEACTIVITIESBYEXTENSION"/>
+  <int value="1308"
+      label="ACCESSIBILITY_PRIVATE_FORWARDKEYEVENTSTOSWITCHACCESS"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -26702,7 +26705,7 @@
 </enum>
 
 <enum name="IDBLevelDBBackingStoreOpenResult">
-  <int value="0" label="OpenMemorySuccess">
+  <int value="0" label="OpenMemorySuccess (Deprecated)">
     An in-memory backing store was opened successfully.
   </int>
   <int value="1" label="OpenSuccess">
@@ -26734,7 +26737,7 @@
     version.
   </int>
   <int value="8" label="OpenFailedUnknownErr"/>
-  <int value="9" label="OpenMemoryFailed">
+  <int value="9" label="OpenMemoryFailed (Deprecated)">
     An in-memory backing store failed to open.
   </int>
   <int value="10" label="OpenNonASCII">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index e061c24..953f3360 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -123650,6 +123650,9 @@
 
 <histogram name="WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure"
     enum="LevelDBErrorCount">
+  <obsolete>
+    Deprecated 1/19. Metric never actually worked, as -1 / 1024 yields 0.
+  </obsolete>
   <owner>dgrogan@chromium.org</owner>
   <summary>
     Count of how many times LevelDBDatabase got an error trying to check free
@@ -123658,6 +123661,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDB.OpenFailureFreeDiskSpace" units="Kb">
+  <obsolete>
+    Deprecated 1/19. Use Quota.AvailableDiskSpace instead.
+  </obsolete>
   <owner>dgrogan@chromium.org</owner>
   <summary>
     Amount of free disk space on the partition/volume/etc where LevelDB failed
@@ -123666,6 +123672,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDB.OpenSuccessFreeDiskSpace" units="Kb">
+  <obsolete>
+    Deprecated 1/19. Use Quota.AvailableDiskSpace instead.
+  </obsolete>
   <owner>dgrogan@chromium.org</owner>
   <summary>
     Amount of free disk space on the partition/volume/etc where LevelDB was
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 13f5765..ce1088f 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -357,6 +357,33 @@
   return 0;
 }
 
+#if defined(OS_MACOSX)
+//
+// Table column-like nodes. These nodes are only present on macOS.
+//
+
+bool AXNode::IsTableColumn() const {
+  return data().role == ax::mojom::Role::kColumn;
+}
+
+int32_t AXNode::GetTableColColIndex() const {
+  if (!IsTableColumn())
+    return 0;
+
+  AXTableInfo* table_info = GetAncestorTableInfo();
+  if (!table_info)
+    return 0;
+
+  int32_t index = 0;
+  for (const AXNode* node : table_info->extra_mac_nodes) {
+    if (node == this)
+      break;
+    index++;
+  }
+  return index;
+}
+#endif  // defined(OS_MACOSX)
+
 //
 // Table cell-like nodes.
 //
@@ -519,12 +546,21 @@
   }
 }
 
+// Uses function in ax_role_properties to check if node is item-like.
+bool AXNode::IsOrderedSetItem() const {
+  return ui::IsItemLike(data().role);
+}
+// Uses function in ax_role_properties to check if node is oredered-set-like.
+bool AXNode::IsOrderedSet() const {
+  return ui::IsSetLike(data().role);
+}
+
 // pos_in_set and set_size related functions.
 // Uses AXTree's cache to calculate node's pos_in_set.
 int32_t AXNode::GetPosInSet() {
   // Only allow this to be called on nodes that can hold pos_in_set values,
   // which are defined in the ARIA spec.
-  if (!IsItemLike(data().role)) {
+  if (!IsOrderedSetItem()) {
     return 0;
   }
 
@@ -541,7 +577,7 @@
 int32_t AXNode::GetSetSize() {
   // Only allow this to be called on nodes that can hold set_size values, which
   // are defined in the ARIA spec.
-  if (!(IsItemLike(data().role) || IsSetLike(data().role)))
+  if (!(IsOrderedSetItem() || IsOrderedSet()))
     return 0;
 
   // If node is item-like, find its outerlying ordered set. Otherwise,
diff --git a/ui/accessibility/ax_node.h b/ui/accessibility/ax_node.h
index 5443831..d23a52d 100644
--- a/ui/accessibility/ax_node.h
+++ b/ui/accessibility/ax_node.h
@@ -11,6 +11,7 @@
 #include <ostream>
 #include <vector>
 
+#include "build/build_config.h"
 #include "ui/accessibility/ax_export.h"
 #include "ui/accessibility/ax_node_data.h"
 
@@ -188,7 +189,9 @@
     return data().GetHtmlAttribute(attribute, value);
   }
 
-  // PosInSet and SetSize public methods
+  // PosInSet and SetSize public methods.
+  bool IsOrderedSetItem() const;
+  bool IsOrderedSet() const;
   int32_t GetPosInSet();
   int32_t GetSetSize();
 
@@ -249,6 +252,12 @@
   bool IsTableRow() const;
   int32_t GetTableRowRowIndex() const;
 
+#if defined(OS_MACOSX)
+  // Table column-like nodes. These nodes are only present on macOS.
+  bool IsTableColumn() const;
+  int32_t GetTableColColIndex() const;
+#endif  // defined(OS_MACOSX)
+
   // Table cell-like nodes.
   bool IsTableCellOrHeader() const;
   int32_t GetTableCellIndex() const;
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
index 9c53b19e..b277f70 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -589,11 +589,6 @@
                             ax::mojom::IntAttribute::kHierarchicalLevel,
                             "level");
   TestAtkObjectIntAttribute(root_node, root_atk_object,
-                            ax::mojom::IntAttribute::kSetSize, "setsize");
-  TestAtkObjectIntAttribute(root_node, root_atk_object,
-                            ax::mojom::IntAttribute::kPosInSet, "posinset");
-
-  TestAtkObjectIntAttribute(root_node, root_atk_object,
                             ax::mojom::IntAttribute::kAriaColumnCount,
                             "colcount", ax::mojom::Role::kTable);
   TestAtkObjectIntAttribute(root_node, root_atk_object,
@@ -1277,4 +1272,55 @@
   g_object_unref(root_atk_object);
 }
 
+// Tests GetPosInSet() and GetSetSize() functions of AXPlatformNodeBase.
+// PosInSet and SetSize must be tested separately from other IntAttributes
+// because they can be either assigned values or calculated dynamically.
+TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectSetSizePosInSet) {
+  AXTreeUpdate update;
+  update.root_id = 1;
+  update.nodes.resize(4);
+  update.nodes[0].id = 1;
+  update.nodes[0].role = ax::mojom::Role::kRadioGroup;
+  update.nodes[0].child_ids = {2, 3, 4};
+  update.nodes[1].id = 2;
+  update.nodes[1].role =
+      ax::mojom::Role::kRadioButton;  // kRadioButton posinset = 2, setsize = 5.
+  update.nodes[1].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 2);
+  update.nodes[2].id = 3;
+  update.nodes[2].role =
+      ax::mojom::Role::kRadioButton;  // kRadioButton posinset = 3, setsize = 5.
+  update.nodes[3].id = 4;
+  update.nodes[3].role =
+      ax::mojom::Role::kRadioButton;  // kRadioButton posinset = 5, stesize = 5
+  update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 5);
+  Init(update);
+
+  AXNode* radiobutton1 = GetRootNode()->children()[0];
+  AtkObject* radiobutton1_atk_object(AtkObjectFromNode(radiobutton1));
+  EXPECT_TRUE(ATK_IS_OBJECT(radiobutton1_atk_object));
+  g_object_ref(radiobutton1_atk_object);
+
+  AXNode* radiobutton2 = GetRootNode()->children()[1];
+  AtkObject* radiobutton2_atk_object(AtkObjectFromNode(radiobutton2));
+  EXPECT_TRUE(ATK_IS_OBJECT(radiobutton2_atk_object));
+  g_object_ref(radiobutton2_atk_object);
+
+  AXNode* radiobutton3 = GetRootNode()->children()[2];
+  AtkObject* radiobutton3_atk_object(AtkObjectFromNode(radiobutton3));
+  EXPECT_TRUE(ATK_IS_OBJECT(radiobutton3_atk_object));
+  g_object_ref(radiobutton3_atk_object);
+
+  // Notice that setsize was never assigned to any of the kRadioButtons, but was
+  // inferred.
+  EnsureAtkObjectHasAttributeWithValue(radiobutton1_atk_object, "posinset",
+                                       "2");
+  EnsureAtkObjectHasAttributeWithValue(radiobutton1_atk_object, "setsize", "5");
+  EnsureAtkObjectHasAttributeWithValue(radiobutton2_atk_object, "posinset",
+                                       "3");
+  EnsureAtkObjectHasAttributeWithValue(radiobutton2_atk_object, "setsize", "5");
+  EnsureAtkObjectHasAttributeWithValue(radiobutton3_atk_object, "posinset",
+                                       "5");
+  EnsureAtkObjectHasAttributeWithValue(radiobutton3_atk_object, "setsize", "5");
+}
+
 }  // namespace ui
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 24f9d1e..7d9ffb2 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -915,6 +915,7 @@
     const char* name,
     PlatformAttributeList* attributes) {
   DCHECK(attributes);
+
   auto maybe_value = ComputeAttribute(delegate_, attribute);
   if (maybe_value.has_value()) {
     std::string str_value = base::IntToString(maybe_value.value());
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.h b/ui/accessibility/platform/ax_platform_node_delegate.h
index 0b40d2a..f569559 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -146,7 +146,9 @@
   virtual int32_t GetCellId(int32_t row_index, int32_t col_index) const = 0;
   virtual int32_t CellIndexToId(int32_t cell_index) const = 0;
 
-  // Only called on ordered-set-like elements and item-like elements.
+  // Ordered-set-like and item-like nodes.
+  virtual bool IsOrderedSetItem() const = 0;
+  virtual bool IsOrderedSet() const = 0;
   virtual int32_t GetPosInSet() const = 0;
   virtual int32_t GetSetSize() const = 0;
 
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.cc b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
index 2314a13..d1aa8dbd 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -193,6 +193,14 @@
   return *dummy_unique_id;
 }
 
+bool AXPlatformNodeDelegateBase::IsOrderedSetItem() const {
+  return false;
+}
+
+bool AXPlatformNodeDelegateBase::IsOrderedSet() const {
+  return false;
+}
+
 int32_t AXPlatformNodeDelegateBase::GetPosInSet() const {
   return 0;
 }
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.h b/ui/accessibility/platform/ax_platform_node_delegate_base.h
index 80ab923..20a2c999 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -121,7 +121,9 @@
   int32_t GetCellId(int32_t row_index, int32_t col_index) const override;
   int32_t CellIndexToId(int32_t cell_index) const override;
 
-  // Only called on ordered-set-like elements and item-like elements.
+  // Ordered-set-like and item-like nodes.
+  bool IsOrderedSetItem() const override;
+  bool IsOrderedSet() const override;
   int32_t GetPosInSet() const override;
   int32_t GetSetSize() const override;
 
diff --git a/ui/accessibility/platform/compute_attributes.cc b/ui/accessibility/platform/compute_attributes.cc
index 3531a2e..1b04e0e 100644
--- a/ui/accessibility/platform/compute_attributes.cc
+++ b/ui/accessibility/platform/compute_attributes.cc
@@ -61,6 +61,35 @@
   }
 }
 
+base::Optional<int32_t> GetOrderedSetItemAttribute(
+    const ui::AXPlatformNodeDelegate* delegate,
+    ax::mojom::IntAttribute attribute) {
+  int value = 0;
+  switch (attribute) {
+    case ax::mojom::IntAttribute::kPosInSet:
+      value = delegate->GetPosInSet();
+      return value;
+    case ax::mojom::IntAttribute::kSetSize:
+      value = delegate->GetSetSize();
+      return value;
+    default:
+      return base::nullopt;
+  }
+}
+
+base::Optional<int32_t> GetOrderedSetAttribute(
+    const ui::AXPlatformNodeDelegate* delegate,
+    ax::mojom::IntAttribute attribute) {
+  int value = 0;
+  switch (attribute) {
+    case ax::mojom::IntAttribute::kSetSize:
+      value = delegate->GetSetSize();
+      return value;
+    default:
+      return base::nullopt;
+  }
+}
+
 base::Optional<int32_t> GetFromData(const ui::AXPlatformNodeDelegate* delegate,
                                     ax::mojom::IntAttribute attribute) {
   int32_t value;
@@ -76,12 +105,18 @@
     const ui::AXPlatformNodeDelegate* delegate,
     ax::mojom::IntAttribute attribute) {
   base::Optional<int32_t> maybe_value = base::nullopt;
+  // Table-related nodes.
   if (delegate->IsTableCellOrHeader())
     maybe_value = GetCellAttribute(delegate, attribute);
   else if (delegate->IsTableRow())
     maybe_value = GetRowAttribute(delegate, attribute);
   else if (delegate->IsTable())
     maybe_value = GetTableAttribute(delegate, attribute);
+  // Ordered-set-related nodes.
+  else if (delegate->IsOrderedSetItem())
+    maybe_value = GetOrderedSetItemAttribute(delegate, attribute);
+  else if (delegate->IsOrderedSet())
+    maybe_value = GetOrderedSetAttribute(delegate, attribute);
 
   if (!maybe_value.has_value()) {
     return GetFromData(delegate, attribute);
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 292c019..f3462d8 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -307,6 +307,14 @@
       platform_node_(AXPlatformNode::Create(this)) {
 }
 
+bool TestAXNodeWrapper::IsOrderedSetItem() const {
+  return node_->IsOrderedSetItem();
+}
+
+bool TestAXNodeWrapper::IsOrderedSet() const {
+  return node_->IsOrderedSet();
+}
+
 int32_t TestAXNodeWrapper::GetPosInSet() const {
   return node_->GetPosInSet();
 }
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.h b/ui/accessibility/platform/test_ax_node_wrapper.h
index eb29fb0..726bb41 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -57,7 +57,8 @@
                                         int32_t dst_id) override;
   std::set<int32_t> GetReverseRelations(ax::mojom::IntListAttribute attr,
                                         int32_t dst_id) override;
-
+  bool IsOrderedSetItem() const override;
+  bool IsOrderedSet() const override;
   int32_t GetPosInSet() const override;
   int32_t GetSetSize() const override;
 
diff --git a/ui/base/material_design/material_design_controller.cc b/ui/base/material_design/material_design_controller.cc
index e9700d21..da5ffa6 100644
--- a/ui/base/material_design/material_design_controller.cc
+++ b/ui/base/material_design/material_design_controller.cc
@@ -20,8 +20,6 @@
 #include "ui/base/ui_base_switches.h"
 #include "ui/base/ui_features.h"
 #include "ui/gfx/animation/linear_animation.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/color_utils.h"
 
 #if defined(OS_WIN)
 #include "base/win/win_util.h"
@@ -87,11 +85,8 @@
   }
   SetTouchUi(touch);
 
-  // Ideally, there would be a more general, "initialize random stuff here"
-  // function into which these things and a call to this function can be placed.
-  // TODO(crbug.com/864544)
-  color_utils::SetDarkestColor(gfx::kGoogleGrey900);
-
+  // TODO(crbug.com/864544): Ideally, there would be a more general, "initialize
+  // random stuff here" function into which this sort of thing can be placed.
   double animation_duration_scale;
   if (base::StringToDouble(
           command_line->GetSwitchValueASCII(switches::kAnimationDurationScale),
diff --git a/ui/gfx/color_utils.cc b/ui/gfx/color_utils.cc
index 9edd09b..afcd749 100644
--- a/ui/gfx/color_utils.cc
+++ b/ui/gfx/color_utils.cc
@@ -28,10 +28,14 @@
 namespace {
 
 // The darkest reference color in color_utils.
-SkColor g_color_utils_darkest = SK_ColorBLACK;
+SkColor g_darkest_color = gfx::kGoogleGrey900;
 
-// The luma midpoint for determining if a color is light or dark.
-int g_color_utils_luma_midpoint = 128;
+// The luminance midpoint for determining if a color is light or dark.  This is
+// the value where white and g_darkest_color contrast equally.  This default
+// value is the midpoint given kGoogleGrey900 as the darkest color.
+float g_luminance_midpoint = 0.211692036f;
+
+constexpr float kWhiteLuminance = 1.0f;
 
 int calcHue(float temp1, float temp2, float hue) {
   if (hue < 0.0f)
@@ -300,12 +304,12 @@
 }
 
 bool IsDark(SkColor color) {
-  return GetLuma(color) < g_color_utils_luma_midpoint;
+  return GetRelativeLuminance(color) < g_luminance_midpoint;
 }
 
 SkColor BlendTowardOppositeLuma(SkColor color, SkAlpha alpha) {
-  return AlphaBlend(IsDark(color) ? SK_ColorWHITE : g_color_utils_darkest,
-                    color, alpha);
+  return AlphaBlend(IsDark(color) ? SK_ColorWHITE : g_darkest_color, color,
+                    alpha);
 }
 
 SkColor GetThemedAssetColor(SkColor theme_color) {
@@ -343,7 +347,7 @@
 SkColor GetColorWithMinimumContrast(SkColor default_foreground,
                                     SkColor background) {
   const SkColor blend_direction =
-      IsDark(background) ? SK_ColorWHITE : g_color_utils_darkest;
+      BlendTowardOppositeLuma(background, SK_AlphaOPAQUE);
   const SkAlpha alpha = GetBlendValueWithMinimumContrast(
       default_foreground, blend_direction, background,
       kMinimumReadableContrastRatio);
@@ -438,13 +442,24 @@
                             SkColorGetB(color));
 }
 
-void SetDarkestColor(SkColor color) {
-  g_color_utils_darkest = color;
-  g_color_utils_luma_midpoint = (GetLuma(color) + 255) / 2;
+SkColor SetDarkestColorForTesting(SkColor color) {
+  const SkColor previous_darkest_color = g_darkest_color;
+  g_darkest_color = color;
+
+  const float dark_luminance = GetRelativeLuminance(color);
+  // We want to compute |g_luminance_midpoint| such that
+  // GetContrastRatio(dark_luminance, g_luminance_midpoint) ==
+  // GetContrastRatio(kWhiteLuminance, g_luminance_midpoint).  The formula below
+  // can be verified by plugging it into how GetContrastRatio() operates.
+  g_luminance_midpoint =
+      std::sqrtf((dark_luminance + 0.05f) * (kWhiteLuminance + 0.05f)) - 0.05f;
+
+  return previous_darkest_color;
 }
 
-SkColor GetDarkestColor() {
-  return g_color_utils_darkest;
+std::tuple<float, float, float> GetLuminancesForTesting() {
+  return std::make_tuple(GetRelativeLuminance(g_darkest_color),
+                         g_luminance_midpoint, kWhiteLuminance);
 }
 
 }  // namespace color_utils
diff --git a/ui/gfx/color_utils.h b/ui/gfx/color_utils.h
index 80b3504..7e5612c 100644
--- a/ui/gfx/color_utils.h
+++ b/ui/gfx/color_utils.h
@@ -6,6 +6,7 @@
 #define UI_GFX_COLOR_UTILS_H_
 
 #include <string>
+#include <tuple>
 
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/gfx_export.h"
@@ -105,7 +106,7 @@
 GFX_EXPORT SkColor GetResultingPaintColor(SkColor foreground,
                                           SkColor background);
 
-// Returns true if the luma of |color| is closer to black than white.
+// Returns true if |color| contrasts more with white than the darkest color.
 GFX_EXPORT bool IsDark(SkColor color);
 
 // Makes a dark color lighter or a light color darker by blending |color| with
@@ -185,11 +186,12 @@
 // Creates an rgb string for an SkColor. For example: '255,0,255'.
 GFX_EXPORT std::string SkColorToRgbString(SkColor color);
 
-// Sets the color_utils darkest color to |color| from the SK_ColorBLACK default.
-GFX_EXPORT void SetDarkestColor(SkColor color);
+// Sets the darkest available color to |color|.  Returns the previous darkest
+// color.
+GFX_EXPORT SkColor SetDarkestColorForTesting(SkColor color);
 
-// Returns the current color_utils darkest color so tests can clean up.
-GFX_EXPORT SkColor GetDarkestColor();
+// Returns the luminance of the darkest, midpoint, and lightest colors.
+GFX_EXPORT std::tuple<float, float, float> GetLuminancesForTesting();
 
 }  // namespace color_utils
 
diff --git a/ui/gfx/color_utils_unittest.cc b/ui/gfx/color_utils_unittest.cc
index bb39cacb..3e026be 100644
--- a/ui/gfx/color_utils_unittest.cc
+++ b/ui/gfx/color_utils_unittest.cc
@@ -205,13 +205,25 @@
 }
 
 TEST(ColorUtils, IsDarkDarkestColorChange) {
-  SkColor old_black_color = GetDarkestColor();
-
   ASSERT_FALSE(IsDark(SkColorSetARGB(255, 200, 200, 200)));
-  SetDarkestColor(SkColorSetARGB(255, 200, 200, 200));
+  const SkColor old_darkest_color =
+      SetDarkestColorForTesting(SkColorSetARGB(255, 200, 200, 200));
   EXPECT_TRUE(IsDark(SkColorSetARGB(255, 200, 200, 200)));
 
-  SetDarkestColor(old_black_color);
+  SetDarkestColorForTesting(old_darkest_color);
+}
+
+TEST(ColorUtils, MidpointLuminanceMatches) {
+  const SkColor old_darkest_color = SetDarkestColorForTesting(SK_ColorBLACK);
+  float darkest, midpoint, lightest;
+  std::tie(darkest, midpoint, lightest) = GetLuminancesForTesting();
+  EXPECT_FLOAT_EQ(GetContrastRatio(darkest, midpoint),
+                  GetContrastRatio(midpoint, lightest));
+
+  SetDarkestColorForTesting(old_darkest_color);
+  std::tie(darkest, midpoint, lightest) = GetLuminancesForTesting();
+  EXPECT_FLOAT_EQ(GetContrastRatio(darkest, midpoint),
+                  GetContrastRatio(midpoint, lightest));
 }
 
 TEST(ColorUtils, GetColorWithMinimumContrast_ForegroundAlreadyMeetsMinimum) {
@@ -236,15 +248,13 @@
 }
 
 TEST(ColorUtils, GetColorWithMinimumContrast_StopsAtDarkestColor) {
-  SkColor old_black_color = GetDarkestColor();
-
   const SkColor darkest_color = SkColorSetRGB(0x44, 0x44, 0x44);
-  SetDarkestColor(darkest_color);
+  const SkColor old_darkest_color = SetDarkestColorForTesting(darkest_color);
   EXPECT_EQ(darkest_color,
             GetColorWithMinimumContrast(SkColorSetRGB(0x55, 0x55, 0x55),
                                         SkColorSetRGB(0xAA, 0xAA, 0xAA)));
 
-  SetDarkestColor(old_black_color);
+  SetDarkestColorForTesting(old_darkest_color);
 }
 
 TEST(ColorUtils, GetBlendValueWithMinimumContrast_ComputesExpectedOpacities) {
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 868136f..7757bdf 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -679,6 +679,7 @@
       "widget/desktop_aura/desktop_native_widget_aura.cc",
       "widget/desktop_aura/desktop_screen.cc",
       "widget/desktop_aura/desktop_screen_position_client.cc",
+      "widget/desktop_aura/desktop_window_tree_host.cc",
       "widget/focus_manager_event_handler.cc",
       "widget/native_widget_aura.cc",
       "widget/tooltip_manager_aura.cc",
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index 150ca0d..01813e6 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -385,14 +385,6 @@
       .device_scale_factor();
 }
 
-void DesktopWindowTreeHostMus::SetBoundsInDIP(const gfx::Rect& bounds_in_dip) {
-  // Do not use ConvertRectToPixel, enclosing rects cause problems.
-  const gfx::Rect rect(
-      gfx::ScaleToFlooredPoint(bounds_in_dip.origin(), GetScaleFactor()),
-      gfx::ScaleToCeiledSize(bounds_in_dip.size(), GetScaleFactor()));
-  SetBoundsInPixels(rect, viz::LocalSurfaceIdAllocation());
-}
-
 bool DesktopWindowTreeHostMus::IsWaitingForRestoreToComplete() const {
   return window_tree_host_window_observer_->is_waiting_for_restore();
 }
@@ -1015,6 +1007,14 @@
   return false;
 }
 
+void DesktopWindowTreeHostMus::SetBoundsInDIP(const gfx::Rect& bounds_in_dip) {
+  // Do not use ConvertRectToPixel, enclosing rects cause problems.
+  const gfx::Rect rect(
+      gfx::ScaleToFlooredPoint(bounds_in_dip.origin(), GetScaleFactor()),
+      gfx::ScaleToCeiledSize(bounds_in_dip.size(), GetScaleFactor()));
+  SetBoundsInPixels(rect, viz::LocalSurfaceIdAllocation());
+}
+
 void DesktopWindowTreeHostMus::OnWindowManagerFrameValuesChanged() {
   NonClientView* non_client_view =
       native_widget_delegate_->AsWidget()->non_client_view();
diff --git a/ui/views/mus/desktop_window_tree_host_mus.h b/ui/views/mus/desktop_window_tree_host_mus.h
index 6d61f260..4a58e2d 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.h
+++ b/ui/views/mus/desktop_window_tree_host_mus.h
@@ -59,8 +59,6 @@
   // Helper function to get the scale factor.
   float GetScaleFactor() const;
 
-  void SetBoundsInDIP(const gfx::Rect& bounds_in_dip);
-
   // Returns true if the client area should be set on this.
   bool ShouldSendClientAreaToServer() const;
 
@@ -149,6 +147,7 @@
   bool ShouldUpdateWindowTransparency() const override;
   bool ShouldUseDesktopNativeCursorManager() const override;
   bool ShouldCreateVisibilityController() const override;
+  void SetBoundsInDIP(const gfx::Rect& bounds_in_dip) override;
 
   // MusClientObserver:
   void OnWindowManagerFrameValuesChanged() override;
diff --git a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
index 9959576..d266b9e 100644
--- a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
@@ -916,4 +916,41 @@
   transient_child->RemoveObserver(&observer);
 }
 
+// DesktopWindowTreeHostMusTest with --force-device-scale-factor=1.25.
+class DesktopWindowTreeHostMusTestFractionalDPI
+    : public DesktopWindowTreeHostMusTest {
+ public:
+  DesktopWindowTreeHostMusTestFractionalDPI() = default;
+  ~DesktopWindowTreeHostMusTestFractionalDPI() override = default;
+
+  // DesktopWindowTreeHostMusTest:
+  void SetUp() override {
+    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+        switches::kForceDeviceScaleFactor, "1.25");
+    DesktopWindowTreeHostMusTest::SetUp();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostMusTestFractionalDPI);
+};
+
+TEST_F(DesktopWindowTreeHostMusTestFractionalDPI,
+       SetBoundsInDipWithFractionalScale) {
+  std::unique_ptr<Widget> widget(CreateWidget());
+  // These numbers are carefully chosen such that if enclosing rect is used
+  // the pixel values differ between the two. The WindowServcie assumes ceiling
+  // is used on the size, which is not impacted by the location.
+  const gfx::Rect bounds1(408, 48, 339, 296);
+  const int expected_pixel_height =
+      gfx::ScaleToCeiledSize(bounds1.size(), 1.25f).height();
+  widget->SetBounds(bounds1);
+  EXPECT_EQ(expected_pixel_height,
+            widget->GetNativeWindow()->GetHost()->GetBoundsInPixels().height());
+
+  const gfx::Rect bounds2(gfx::Point(408, 49), bounds1.size());
+  widget->SetBounds(bounds2);
+  EXPECT_EQ(expected_pixel_height,
+            widget->GetNativeWindow()->GetHost()->GetBoundsInPixels().height());
+}
+
 }  // namespace views
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 afd5488c..f7144e6 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -23,7 +23,6 @@
 #include "ui/base/hit_test.h"
 #include "ui/base/ime/input_method.h"
 #include "ui/compositor/layer.h"
-#include "ui/display/screen.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/rect.h"
@@ -728,11 +727,7 @@
 void DesktopNativeWidgetAura::SetBounds(const gfx::Rect& bounds) {
   if (!content_window_)
     return;
-  aura::Window* root = host_->window();
-  display::Screen* screen = display::Screen::GetScreen();
-  gfx::Rect bounds_in_pixels = screen->DIPToScreenRectInWindow(root, bounds);
-  desktop_window_tree_host_->AsWindowTreeHost()->SetBoundsInPixels(
-      bounds_in_pixels);
+  desktop_window_tree_host_->SetBoundsInDIP(bounds);
 }
 
 void DesktopNativeWidgetAura::SetBoundsConstrained(const gfx::Rect& bounds) {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host.cc
new file mode 100644
index 0000000..6125dbe
--- /dev/null
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host.cc
@@ -0,0 +1,20 @@
+// 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/views/widget/desktop_aura/desktop_window_tree_host.h"
+
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/display/screen.h"
+
+namespace views {
+
+void DesktopWindowTreeHost::SetBoundsInDIP(const gfx::Rect& bounds) {
+  aura::Window* root = AsWindowTreeHost()->window();
+  const gfx::Rect bounds_in_pixels =
+      display::Screen::GetScreen()->DIPToScreenRectInWindow(root, bounds);
+  AsWindowTreeHost()->SetBoundsInPixels(bounds_in_pixels);
+}
+
+}  // namespace views
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host.h b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
index 8a909991..a0ec589 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
@@ -187,6 +187,10 @@
 
   // Returns whether a VisibilityController should be created.
   virtual bool ShouldCreateVisibilityController() const = 0;
+
+  // Sets the bounds in screen coordinate DIPs (WindowTreeHost generally
+  // operates in pixels). This function is implemented in terms of Screen.
+  virtual void SetBoundsInDIP(const gfx::Rect& bounds);
 };
 
 }  // namespace views
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index f615231..24017c9 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -72,14 +72,6 @@
   DestroyDispatcher();
 }
 
-void DesktopWindowTreeHostPlatform::SetBoundsInDIP(
-    const gfx::Rect& bounds_in_dip) {
-  DCHECK_NE(0, device_scale_factor());
-  SetBoundsInPixels(
-      gfx::ConvertRectToPixel(device_scale_factor(), bounds_in_dip),
-      viz::LocalSurfaceIdAllocation());
-}
-
 void DesktopWindowTreeHostPlatform::Init(const Widget::InitParams& params) {
   ui::PlatformWindowInitProperties properties =
       ConvertWidgetInitParamsToInitProperties(params);
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
index fa10671..039683e7 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
@@ -24,8 +24,6 @@
       DesktopNativeWidgetAura* desktop_native_widget_aura);
   ~DesktopWindowTreeHostPlatform() override;
 
-  void SetBoundsInDIP(const gfx::Rect& bounds_in_dip);
-
   // DesktopWindowTreeHost:
   void Init(const Widget::InitParams& params) override;
   void OnNativeWidgetCreated(const Widget::InitParams& params) override;
diff --git a/ui/webui/resources/images/error_badge.svg b/ui/webui/resources/images/error_badge.svg
new file mode 100644
index 0000000..4367b025
--- /dev/null
+++ b/ui/webui/resources/images/error_badge.svg
@@ -0,0 +1,9 @@
+<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <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-8zm.8 12H9.2v-1.6h1.6V14zm0-3.2H9.2V6h1.6v4.8z" id="a"/>
+  </defs>
+  <g fill="none" fill-rule="evenodd">
+    <circle fill="#FFF" cx="10" cy="10" r="10"/>
+    <use fill="#D93025" fill-rule="nonzero" xlink:href="#a"/>
+  </g>
+</svg>
diff --git a/ui/webui/resources/webui_resources.grd b/ui/webui/resources/webui_resources.grd
index b22111a..2e2aec3 100644
--- a/ui/webui/resources/webui_resources.grd
+++ b/ui/webui/resources/webui_resources.grd
@@ -185,6 +185,9 @@
                file="images/disabled_select.png" type="BINDATA" />
       <include name="IDR_WEBUI_IMAGES_ERROR"
                file="images/error.svg" type="BINDATA" compress="gzip" />
+      <!-- Similar to IDR_WEBUI_IMAGES_ERROR except that it is white-filled instead of transparent-filled. Useful for badging images where the background may be red. -->
+      <include name="IDR_WEBUI_IMAGES_ERROR_BADGE"
+               file="images/error_badge.svg" type="BINDATA" compress="gzip" />
       <include name="IDR_WEBUI_IMAGES_GOOGLE_LOGO"
                file="images/google_logo.svg" type="BINDATA" compress="gzip" />
       <include name="IDR_WEBUI_IMAGES_SELECT"