diff --git a/BUILD.gn b/BUILD.gn
index 607e439..b722743 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -171,7 +171,7 @@
       "//net:net_perftests",
       "//third_party/angle/src/tests:angle_end2end_tests",
       "//third_party/angle/src/tests:angle_unittests",
-      "//third_party/blink/renderer/controller:webkit_unit_tests",
+      "//third_party/blink/renderer/controller:blink_unittests",
       "//third_party/blink/renderer/platform/wtf:wtf_unittests",
       "//ui/gl:gl_unittests",
       "//url/ipc:url_ipc_unittests",
@@ -920,17 +920,17 @@
     testonly = true
 
     deps = [
-      ":webkit_layout_tests",
+      ":blink_web_tests",
       "//third_party/blink/public:all_blink",
     ]
   }
 
-  # Layout tests runner
+  # Web tests runner
   # third_party/blink/tools/run_web_tests.py
-  group("run_webkit_tests") {
+  group("run_web_tests") {
     testonly = true
     deps = [
-      ":webkit_layout_tests",
+      ":blink_web_tests",
     ]
   }
 
@@ -960,21 +960,21 @@
   # The _exparchive at the end of the name indicates to the isolate recipe
   # that the isolate should be archived separately using the `exparchive`
   # command, rather than as part of the normal `batcharchive` command.
-  group("webkit_layout_tests_exparchive") {
+  group("blink_web_tests_exparchive") {
     testonly = true
     deps = [
-      ":webkit_layout_tests",
+      ":blink_web_tests",
     ]
     data_deps = [
-      ":webkit_layout_tests",
+      ":blink_web_tests",
     ]
   }
 
-  # This target contains only a small subset of the layout tests,
+  # This target contains only a small subset of the web tests,
   # and is useful for testing with the regular isolate mechanism.
-  # To run the full layout test suite you need to use
-  # :webkit_layout_tests_exparchive, above, instead.
-  generated_script_test("webkit_layout_tests") {
+  # To run the full web test suite you need to use
+  # :blink_web_tests_exparchive, above, instead.
+  generated_script_test("blink_web_tests") {
     generator_script =
         "//testing/scripts/generators/gen_run_web_tests_script.py"
     extra_args = []
@@ -1101,7 +1101,7 @@
     ]
   }
 
-  group("webkit_python_tests") {
+  group("blink_python_tests") {
     data = [
       "//build/android/",
       "//components/crash/content/tools/generate_breakpad_symbols.py",
diff --git a/DEPS b/DEPS
index 8df51b5..c1a1885 100644
--- a/DEPS
+++ b/DEPS
@@ -126,11 +126,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '5105d68f93e9c294d9cfa56247ca2119ec8da8d4',
+  'skia_revision': '6152470dc69ee96172e5f4c3270f98e47ff9693d',
   # 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': '9c2fa40d2db3eb2f7e2f15196c918e4dfd2b8710',
+  'v8_revision': 'd34a141dae9be2c1f66509c7c449992ea34d058a',
   # 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.
@@ -138,7 +138,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': '3805122b0297b8208ff734a3bcdaa12850b60a00',
+  'angle_revision': '52047de4d41f6861e35b7073dd180edffd64c540',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -146,7 +146,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': 'bf42afa0df142896bea074f0d4d1d4fd6d3309ab',
+  'pdfium_revision': 'e6fcdfa5bb6e1fca1bb833cbace54b754ca395a6',
   # 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.
@@ -189,7 +189,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': '63f9b85be556ed82b32e4b9f30b266152d523cdc',
+  'catapult_revision': '8ba5518f87cc9179d81af3df7eaad5fb15a34acf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -253,7 +253,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '1809ff74233de497ad2b14a06eaddde54ecba7fd',
+  'dawn_revision': 'b2207737abd32e20e39a1ac00987179626f050c7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -732,7 +732,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'e67fcf9ed6c6bff5ea5a202cd278601f95f9a7c1',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '499f9dd2f00ec1c72fd7f2322d756db28e17eb9d',
       'condition': 'checkout_linux',
   },
 
@@ -1092,7 +1092,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '635129336c5010e0a6324fde2ba680ca7404c5e9',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '3271875cb274dbfb0ada8d2cec976bf4e1e7d0ca',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1255,7 +1255,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'a0f51b2e123f39c9ff12e621b0b47dd28dd64424',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f13c2cd9ee41f4ca572232a4e397b05449474632',
+    Var('webrtc_git') + '/src.git' + '@' + 'b1ea48c2e8682e9c2f67ff3cfa4e9dbaebbb8cc7',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1296,7 +1296,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7ec7019349934f9b0cee81701c180fac9652282f',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@116ba2e6c585afa1507e806e162cae6c098bf9ef',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/system/network/vpn_list_view.cc b/ash/system/network/vpn_list_view.cc
index 2ac950b..a2c4813 100644
--- a/ash/system/network/vpn_list_view.cc
+++ b/ash/system/network/vpn_list_view.cc
@@ -324,6 +324,10 @@
   list_empty_ = false;
 }
 
+void VPNListView::AddProviderAndNetworks(const VPNProvider& vpn_provider) {
+  AddProviderAndNetworks(vpn_provider, NetworkStateHandler::NetworkStateList());
+}
+
 void VPNListView::AddProviderAndNetworks(
     const VPNProvider& vpn_provider,
     const NetworkStateHandler::NetworkStateList& networks) {
@@ -351,6 +355,21 @@
   }
 }
 
+bool VPNListView::ProcessProviderForNetwork(
+    const NetworkState* network,
+    const NetworkStateHandler::NetworkStateList& networks,
+    std::vector<VPNProvider>* providers) {
+  for (auto provider_iter = providers->begin();
+       provider_iter != providers->end(); ++provider_iter) {
+    if (!VpnProviderMatchesNetwork(*provider_iter, *network))
+      continue;
+    AddProviderAndNetworks(*provider_iter, networks);
+    providers->erase(provider_iter);
+    return true;
+  }
+  return false;
+}
+
 void VPNListView::AddProvidersAndNetworks(
     const NetworkStateHandler::NetworkStateList& networks) {
   // Get the list of VPN providers enabled in the primary user's profile.
@@ -364,56 +383,33 @@
 
   // Add connected ARCVPN network. If we can find the correct provider, nest
   // the network under the provider. Otherwise list it unnested.
-  for (const NetworkState* const& network : networks) {
+  for (const NetworkState* network : networks) {
     if (!network->IsConnectingOrConnected())
       break;
     if (network->GetVpnProviderType() != shill::kProviderArcVpn)
       continue;
 
-    bool found_provider = false;
-    for (auto arc_provider_iter = arc_providers.begin();
-         arc_provider_iter != arc_providers.end(); ++arc_provider_iter) {
-      if (!VpnProviderMatchesNetwork(*arc_provider_iter, *network))
-        continue;
-      AddProviderAndNetworks(*arc_provider_iter, networks);
-      arc_providers.erase(arc_provider_iter);
-      found_provider = true;
-      break;
-    }
-    // No matched provider found for this network. Show it unnested.
+    // If no matched provider found for this network. Show it unnested.
     // TODO(lgcheng@) add UMA status to track this.
-    if (!found_provider)
+    if (!ProcessProviderForNetwork(network, networks, &arc_providers))
       AddNetwork(network);
   }
 
   // Add providers with at least one configured network along with their
   // networks. Providers are added in the order of their highest priority
   // network.
-  for (const NetworkState* const& network : networks) {
-    for (auto extension_provider_iter = extension_providers.begin();
-         extension_provider_iter != extension_providers.end();
-         ++extension_provider_iter) {
-      if (!VpnProviderMatchesNetwork(*extension_provider_iter, *network))
-        continue;
-      AddProviderAndNetworks(*extension_provider_iter, networks);
-      extension_providers.erase(extension_provider_iter);
-      break;
-    }
-  }
-
-  // Create a local networkstate list. Help AddProviderAndNetworks() by passing
-  // empty list of network states.
-  NetworkStateHandler::NetworkStateList networkstate_empty_list;
+  for (const NetworkState* network : networks)
+    ProcessProviderForNetwork(network, networks, &extension_providers);
 
   // Add providers without any configured networks, in the order that the
   // providers were returned by the extensions system.
-  for (const VPNProvider& provider : extension_providers)
-    AddProviderAndNetworks(provider, networkstate_empty_list);
+  for (const VPNProvider& extension_provider : extension_providers)
+    AddProviderAndNetworks(extension_provider);
 
   // Add Arc VPN providers without any connected or connecting networks. These
   // providers are sorted by last launch time.
   for (const VPNProvider& arc_provider : arc_providers) {
-    AddProviderAndNetworks(arc_provider, networkstate_empty_list);
+    AddProviderAndNetworks(arc_provider);
   }
 }
 
diff --git a/ash/system/network/vpn_list_view.h b/ash/system/network/vpn_list_view.h
index 6e40f73..676dd36 100644
--- a/ash/system/network/vpn_list_view.h
+++ b/ash/system/network/vpn_list_view.h
@@ -59,11 +59,25 @@
   void AddNetwork(const chromeos::NetworkState* network);
 
   // Adds the VPN provider identified by |vpn_provider| to the list, along with
+  // no networks that belong to this provider.
+  void AddProviderAndNetworks(const VPNProvider& vpn_provider);
+
+  // Adds the VPN provider identified by |vpn_provider| to the list, along with
   // any networks that belong to this provider.
   void AddProviderAndNetworks(
       const VPNProvider& vpn_provider,
       const chromeos::NetworkStateHandler::NetworkStateList& networks);
 
+  // Finds VPN provider from |providers| that matches given |network|. Then adds
+  // the VPN provider along with any networks that belong to this provider. Will
+  // also remove the match from |providers| to avoid showing duplicate provider
+  // entry in VPN list view.
+  // Returns true if finds a match, returns false otherwise.
+  bool ProcessProviderForNetwork(
+      const chromeos::NetworkState* network,
+      const chromeos::NetworkStateHandler::NetworkStateList& networks,
+      std::vector<VPNProvider>* providers);
+
   // Adds all available VPN providers and networks to the list.
   void AddProvidersAndNetworks(
       const chromeos::NetworkStateHandler::NetworkStateList& networks);
diff --git a/base/memory/weak_ptr.cc b/base/memory/weak_ptr.cc
index c993fcb..64fd499 100644
--- a/base/memory/weak_ptr.cc
+++ b/base/memory/weak_ptr.cc
@@ -34,6 +34,10 @@
   return !invalidated_.IsSet();
 }
 
+void WeakReference::Flag::DetachFromSequence() {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
 WeakReference::Flag::~Flag() = default;
 
 WeakReference::WeakReference() = default;
@@ -54,25 +58,24 @@
   return flag_ && flag_->MaybeValid();
 }
 
-WeakReferenceOwner::WeakReferenceOwner() = default;
+WeakReferenceOwner::WeakReferenceOwner()
+    : flag_(MakeRefCounted<WeakReference::Flag>()) {}
 
 WeakReferenceOwner::~WeakReferenceOwner() {
-  Invalidate();
+  flag_->Invalidate();
 }
 
 WeakReference WeakReferenceOwner::GetRef() const {
-  // If we hold the last reference to the Flag then create a new one.
+  // If we hold the last reference to the Flag then detach the SequenceChecker.
   if (!HasRefs())
-    flag_ = new WeakReference::Flag();
+    flag_->DetachFromSequence();
 
   return WeakReference(flag_);
 }
 
 void WeakReferenceOwner::Invalidate() {
-  if (flag_) {
-    flag_->Invalidate();
-    flag_ = nullptr;
-  }
+  flag_->Invalidate();
+  flag_ = MakeRefCounted<WeakReference::Flag>();
 }
 
 WeakPtrBase::WeakPtrBase() : ptr_(0) {}
diff --git a/base/memory/weak_ptr.h b/base/memory/weak_ptr.h
index 6b94b0e..af2bd38 100644
--- a/base/memory/weak_ptr.h
+++ b/base/memory/weak_ptr.h
@@ -102,6 +102,8 @@
 
     bool MaybeValid() const;
 
+    void DetachFromSequence();
+
    private:
     friend class base::RefCountedThreadSafe<Flag>;
 
@@ -134,7 +136,7 @@
 
   WeakReference GetRef() const;
 
-  bool HasRefs() const { return flag_ && !flag_->HasOneRef(); }
+  bool HasRefs() const { return !flag_->HasOneRef(); }
 
   void Invalidate();
 
@@ -223,7 +225,6 @@
 class WeakPtr : public internal::WeakPtrBase {
  public:
   WeakPtr() = default;
-
   WeakPtr(std::nullptr_t) {}
 
   // Allow conversion from U to T provided U "is a" T. Note that this
diff --git a/build/android/pylib/gtest/gtest_config.py b/build/android/pylib/gtest/gtest_config.py
index 332a31d..3ac1955 100644
--- a/build/android/pylib/gtest/gtest_config.py
+++ b/build/android/pylib/gtest/gtest_config.py
@@ -21,6 +21,7 @@
 STABLE_TEST_SUITES = [
     'android_webview_unittests',
     'base_unittests',
+    'blink_unittests',
     'breakpad_unittests',
     'cc_unittests',
     'components_unittests',
@@ -42,7 +43,6 @@
     'ui_base_unittests',
     'ui_touch_selection_unittests',
     'unit_tests_apk',
-    'webkit_unit_tests',
 ]
 
 # Tests fail in component=shared_library build, which is required for ASan.
diff --git a/build/android/resource_sizes.py b/build/android/resource_sizes.py
index c986962..301acf3 100755
--- a/build/android/resource_sizes.py
+++ b/build/android/resource_sizes.py
@@ -617,14 +617,11 @@
 
 
 def _Analyze(apk_path, chartjson, args):
-  metric_prefix = os.path.basename(args.input) + '_'
-  metric_prefix = metric_prefix.replace('.minimal.apks', '.apk')
 
-  def report_func(title, *args):
+  def report_func(*args):
     # Do not add any new metrics without also documenting them in:
     # //docs/speed/binary_size/metrics.md.
-    title = metric_prefix + title
-    perf_tests_results_helper.ReportPerfResult(chartjson, title, *args)
+    perf_tests_results_helper.ReportPerfResult(chartjson, *args)
 
   out_dir, tool_prefix = _ConfigOutDirAndToolsPrefix(args.out_dir)
   is_bundle = args.input.endswith('.apks')
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 21a83da..7f9d51d 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1120,7 +1120,7 @@
           # aggregates enabling subsequent optimizations) leads to
           # invalid code generation when using the Android NDK's
           # compiler (r5-r7). This can be verified using
-          # webkit_unit_tests' WTF.Checked_int8_t test.
+          # blink_unittests' WTF.Checked_int8_t test.
           "-fno-tree-sra",
 
           # The following option is disabled to improve binary
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 999f15b..a879d52 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-1c91c8faa342f73db94a3973fd9759497f1f3c27
\ No newline at end of file
+4236f65568daabc3872ae42c603099873e984bec
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 85bd396..3d14acc 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-f42d146adf49641d73ba7e4a8901578fa2114663
\ No newline at end of file
+2f6532e63eeaa8e3bed08ce65d551310a075fd9d
\ No newline at end of file
diff --git a/cc/layers/video_layer_impl.cc b/cc/layers/video_layer_impl.cc
index 84c9913..eab1e6a 100644
--- a/cc/layers/video_layer_impl.cc
+++ b/cc/layers/video_layer_impl.cc
@@ -104,8 +104,11 @@
 
   if (!updater_) {
     const LayerTreeSettings& settings = layer_tree_impl()->settings();
+    // TODO(sergeyu): Pass RasterContextProvider when it's available. Then
+    // remove ContextProvider parameter from VideoResourceUpdater.
     updater_ = std::make_unique<media::VideoResourceUpdater>(
         layer_tree_impl()->context_provider(),
+        /*raster_context_provider=*/nullptr,
         layer_tree_impl()->layer_tree_frame_sink(),
         layer_tree_impl()->resource_provider(),
         settings.use_stream_video_draw_quad,
diff --git a/cc/resources/resource_pool_unittest.cc b/cc/resources/resource_pool_unittest.cc
index 5f15954..370b7750 100644
--- a/cc/resources/resource_pool_unittest.cc
+++ b/cc/resources/resource_pool_unittest.cc
@@ -244,8 +244,9 @@
 
   std::vector<viz::ResourceId> export_ids = {resource.resource_id_for_export()};
   std::vector<viz::TransferableResource> transferable_resources;
-  resource_provider_->PrepareSendToParent(export_ids, &transferable_resources,
-                                          context_provider_.get());
+  resource_provider_->PrepareSendToParent(
+      export_ids, &transferable_resources,
+      static_cast<viz::RasterContextProvider*>(context_provider_.get()));
   auto returned_resources =
       viz::TransferableResource::ReturnResources(transferable_resources);
   ASSERT_EQ(1u, returned_resources.size());
@@ -276,8 +277,9 @@
   EXPECT_TRUE(resource_pool_->PrepareForExport(resource));
 
   std::vector<viz::TransferableResource> transfers;
-  resource_provider_->PrepareSendToParent({resource.resource_id_for_export()},
-                                          &transfers, context_provider_.get());
+  resource_provider_->PrepareSendToParent(
+      {resource.resource_id_for_export()}, &transfers,
+      static_cast<viz::RasterContextProvider*>(context_provider_.get()));
 
   resource_pool_->ReleaseResource(std::move(resource));
   EXPECT_EQ(40000u, resource_pool_->GetTotalMemoryUsageForTesting());
@@ -316,8 +318,9 @@
   SetBackingOnResource(resource);
   EXPECT_TRUE(resource_pool_->PrepareForExport(resource));
   std::vector<viz::TransferableResource> transfers;
-  resource_provider_->PrepareSendToParent({resource.resource_id_for_export()},
-                                          &transfers, context_provider_.get());
+  resource_provider_->PrepareSendToParent(
+      {resource.resource_id_for_export()}, &transfers,
+      static_cast<viz::RasterContextProvider*>(context_provider_.get()));
 
   resource_pool_->ReleaseResource(std::move(resource));
   EXPECT_EQ(40000u, resource_pool_->GetTotalMemoryUsageForTesting());
@@ -534,8 +537,9 @@
   // Export the resource to the display compositor, so it will be busy once
   // released.
   std::vector<viz::TransferableResource> transfers;
-  resource_provider_->PrepareSendToParent({resource.resource_id_for_export()},
-                                          &transfers, context_provider_.get());
+  resource_provider_->PrepareSendToParent(
+      {resource.resource_id_for_export()}, &transfers,
+      static_cast<viz::RasterContextProvider*>(context_provider_.get()));
 
   // Release the resource making it busy.
   resource_pool_->ReleaseResource(std::move(resource));
@@ -603,7 +607,7 @@
   std::vector<viz::TransferableResource> transfers;
   resource_provider_->PrepareSendToParent(
       {busy_resource.resource_id_for_export()}, &transfers,
-      context_provider_.get());
+      static_cast<viz::RasterContextProvider*>(context_provider_.get()));
 
   // Release the resource making it busy.
   resource_pool_->ReleaseResource(std::move(busy_resource));
@@ -695,8 +699,9 @@
   EXPECT_TRUE(resource_pool_->PrepareForExport(resource));
 
   std::vector<viz::TransferableResource> transfer;
-  resource_provider_->PrepareSendToParent({resource.resource_id_for_export()},
-                                          &transfer, context_provider_.get());
+  resource_provider_->PrepareSendToParent(
+      {resource.resource_id_for_export()}, &transfer,
+      static_cast<viz::RasterContextProvider*>(context_provider_.get()));
 
   // The verified_flush flag will be set by the ResourceProvider when it exports
   // the resource.
diff --git a/chrome/OWNERS b/chrome/OWNERS
index 7672069..f656e8d 100644
--- a/chrome/OWNERS
+++ b/chrome/OWNERS
@@ -1,11 +1,17 @@
 # This OWNERS list is a last resort. Please prefer to use more specific OWNERS
 # where possible.
 
-# Reviewers:
+# Reviewers (in CET):
+droger@chromium.org
+treib@chromium.org
+vasilii@chromium.org
+
+# Reviewers (in EST):
 avi@chromium.org
-jochen@chromium.org
-sky@chromium.org
 thakis@chromium.org
+
+# Reviewers (in PST):
+sky@chromium.org
 thestig@chromium.org
 
 # per-file rules:
diff --git a/chrome/VERSION b/chrome/VERSION
index 90773828..a3beede 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=74
 MINOR=0
-BUILD=3705
+BUILD=3706
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncher.java
index aaafb8e..0356ec6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabLauncher.java
@@ -13,9 +13,11 @@
 import android.support.annotation.Nullable;
 
 import org.chromium.base.StrictModeContext;
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordUserAction;
-import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeVersionInfo;
 import org.chromium.chrome.browser.IntentHandler;
@@ -89,7 +91,7 @@
                         ChromeFeatureList.ALLOW_NEW_INCOGNITO_TAB_INTENTS)
                 && PrefServiceBridge.getInstance().isIncognitoModeEnabled();
 
-        AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> setComponentEnabled(context, enable));
+        PostTask.postTask(TaskTraits.USER_VISIBLE, () -> setComponentEnabled(context, enable));
     }
 
     /**
@@ -98,6 +100,8 @@
      */
     @VisibleForTesting
     static void setComponentEnabled(Context context, boolean enabled) {
+        ThreadUtils.assertOnBackgroundThread();
+
         PackageManager packageManager = context.getPackageManager();
         ComponentName componentName = new ComponentName(context, IncognitoTabLauncher.class);
 
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 6dc36e3..db8e4de 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-74.0.3704.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-74.0.3705.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index d695899..dba863f 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7106,9 +7106,6 @@
         <message name="IDS_SYNC_SETTINGS_NOT_CONFIRMED" desc="The message that appears in the settings page indicating that user is signed in, but sync hasn't started because the sync settings haven't been confirmed.">
           Confirm sync settings to start sync.
         </message>
-        <message name="IDS_SYNC_AUTHENTICATING_LABEL" desc="Label to display while the user is being authenticated to use sync.">
-          Authenticating...
-        </message>
         <message name="IDS_SYNC_ERROR_SIGNING_IN" desc="An error was encountered while signing the user in.">
           Error signing in.
         </message>
@@ -9332,6 +9329,33 @@
       <message name="IDS_WEBAUTHN_CABLE_ACTIVATE_DESCRIPTION" desc="Contents of the dialog shown when the user tries to sign-in using a phone as a security key.">
         A notification was sent to your phone. Follow the prompts to confirm it's you.
       </message>
+      <message name="IDS_WEBAUTHN_PIN_ENTRY_TITLE" desc="Title of the dialog shown when instructing the user to enter the PIN code to use a security key (an external physical device for user authentication) with their computer.">
+        PIN required
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_ENTRY_DESCRIPTION" desc="Description in the dialog shown when instructing the user to enter the PIN code to use a security key (an external physical device for user authentication) with their computer.">
+        Enter the PIN for your security key
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_ENTRY_PIN_LABEL" desc="Label text. Displayed next to a text box where the user enters the PIN code for their security key (an external physical device for user authentication).">
+        PIN
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_ENTRY_NEXT" desc="Button text. The user clicks this button once they have entered the PIN code into a text box in order to continue.">
+        Next
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_SETUP_DESCRIPTION" desc="Description in the dialog shown when instructing the user to set up a PIN code to use with their security key (an external physical device for user authentication) with their computer.">
+        Set up a new PIN for your security key
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_SETUP_CONFIRMATION_LABEL" desc="Label text. Displayed next to a text box where the user re-enters the PIN code for their security key (an external physical device for user authentication).">
+        Confirm PIN
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_ENTRY_ERROR_INVALID_CHARACTERS" desc="Error message. Displayed when the user attempts to set a PIN code for their security key (an external physical device for user authentication), and the PIN chosen by them contains invalid characters.">
+        PIN contains invalid characters
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_ENTRY_ERROR_TOO_SHORT" desc="Error message. Displayed when the user attempts to set a PIN code for their security key (an external physical device for user authentication), and the PIN chosen by them is too short.">
+        PIN is too short.
+      </message>
+      <message name="IDS_WEBAUTHN_PIN_ENTRY_ERROR_MISMATCH" desc="Error message. Displayed when the user attempts to set a PIN code for their security key (an external physical device for user authentication), and the confirmation value does not match the first value they entered.">
+        Confirmation does not match.
+      </message>
    </if>
    <if expr="is_macosx">
      <message name="IDS_WEBAUTHN_TOUCH_ID_TITLE" desc="Title of the dialog shown when the user tries to sign in with Touch ID." meaning="'Touch ID' is the fingerprint recognition feature in macOS. Try to refer Apple support documentation in the target language for the appropriate product name translation.">
@@ -9343,10 +9367,10 @@
     <message name="IDS_INCOGNITO_WINDOW_COUNTER_TITLE" desc="The title of the incognito window counter dialog.">
       Incognito
     </message>
-    <message name="IDS_INCOGNITO_WINDOW_COUNTER_MESSAGE" desc="The message showing the number of open incogntio windows. [ICU Syntax]">
+    <message name="IDS_INCOGNITO_WINDOW_COUNTER_MESSAGE" desc="The message showing the number of open incogntio windows. This is not used for zero windows.[ICU Syntax]">
       {0, plural,
-        =1 {}
-        other {# windows are open}
+        =1 {# open window}
+        other {# open windows}
       }
     </message>
     <message name="IDS_INCOGNITO_WINDOW_COUNTER_CLOSE_BUTTON" desc="The text of the button offering to close all incognito windows.">
diff --git a/chrome/app/onboarding_welcome_strings.grdp b/chrome/app/onboarding_welcome_strings.grdp
index fa158c0..adee72d 100644
--- a/chrome/app/onboarding_welcome_strings.grdp
+++ b/chrome/app/onboarding_welcome_strings.grdp
@@ -61,14 +61,14 @@
   <message name="IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_ART_TITLE" desc="Label for choosing a background/wallpaper from the 'Art' category">
     Art
   </message>
-  <message name="IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_LANDSCAPE_TITLE" desc="Label for choosing a background/wallpaper from the 'Landscape' category, as in a category of photos of nature">
+  <message name="IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_LANDSCAPE_TITLE" desc="Label for choosing a background/wallpaper from the 'Landscape' category, as in a category of photos of scenery and large outdoor spaces.">
     Landscape
   </message>
   <message name="IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_CITYSCAPE_TITLE" desc="Label for choosing a background/wallpaper from the 'Cityscape' category">
     Cityscape
   </message>
-  <message name="IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_SEASCAPE_TITLE" desc="Label for choosing a background/wallpaper from the 'Seascape' category">
-    Seascape
+  <message name="IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_LIFE_TITLE" desc="Label for choosing a background/wallpaper from the 'Life' category, as in a category of photos of nature, flowers, and buildings.">
+    Life
   </message>
   <message name="IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_GEOMETRIC_SHAPES_TITLE" desc="Label for choosing a background/wallpaper from the 'Geometric Shapes' category">
     Geometric shapes
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 2261758..6da9663 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -306,6 +306,7 @@
         <structure type="chrome_scaled_image" name="IDR_WEBAUTHN_ILLUSTRATION_PHONE" file="common/webauthn/phone.png" />
         <structure type="chrome_scaled_image" name="IDR_WEBAUTHN_ILLUSTRATION_USB" file="common/webauthn/usb.png" />
         <structure type="chrome_scaled_image" name="IDR_WEBAUTHN_ILLUSTRATION_WELCOME" file="common/webauthn/welcome.png" />
+        <structure type="chrome_scaled_image" name="IDR_WEBAUTHN_ILLUSTRATION_PIN" file="common/webauthn/usb.png" />
       </if>
       <if expr="is_macosx">
         <structure type="chrome_scaled_image" name="IDR_WEBAUTHN_ILLUSTRATION_TOUCHID" file="common/webauthn/touchid.png" />
diff --git a/chrome/browser/apps/app_service/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon_factory.cc
index 3b4769a6..15b26863 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory.cc
@@ -49,6 +49,7 @@
 // Runs |callback| passing an IconValuePtr with a compressed image: a
 // std::vector<uint8_t>.
 void RunCallbackWithCompressedData(
+    bool is_placeholder_icon,
     apps::mojom::Publisher::LoadIconCallback callback,
     std::vector<uint8_t> data) {
   apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
@@ -56,12 +57,14 @@
                              ? apps::mojom::IconCompression::kUnknown
                              : apps::mojom::IconCompression::kCompressed;
   iv->compressed = std::move(data);
+  iv->is_placeholder_icon = is_placeholder_icon;
   std::move(callback).Run(std::move(iv));
 }
 
 // Like RunCallbackWithCompressedData, but calls "fallback(callback)" if the
 // data is empty.
 void RunCallbackWithCompressedDataWithFallback(
+    bool is_placeholder_icon,
     apps::mojom::Publisher::LoadIconCallback callback,
     base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)> fallback,
     std::vector<uint8_t> data) {
@@ -69,17 +72,20 @@
     std::move(fallback).Run(std::move(callback));
     return;
   }
-  RunCallbackWithCompressedData(std::move(callback), std::move(data));
+  RunCallbackWithCompressedData(is_placeholder_icon, std::move(callback),
+                                std::move(data));
 }
 
 // Runs |callback| passing an IconValuePtr with an uncompressed image: a
 // SkBitmap.
 void RunCallbackWithUncompressedSkBitmap(
+    bool is_placeholder_icon,
     apps::mojom::Publisher::LoadIconCallback callback,
     const SkBitmap& bitmap) {
   apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
   iv->icon_compression = apps::mojom::IconCompression::kUncompressed;
   iv->uncompressed = gfx::ImageSkia(gfx::ImageSkiaRep(bitmap, 0.0f));
+  iv->is_placeholder_icon = is_placeholder_icon;
   std::move(callback).Run(std::move(iv));
 }
 
@@ -87,6 +93,7 @@
 // std::vector<uint8_t> to a SkBitmap. It calls "fallback(callback)" if the
 // data is empty.
 void RunCallbackWithCompressedDataToUncompressWithFallback(
+    bool is_placeholder_icon,
     apps::mojom::Publisher::LoadIconCallback callback,
     base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)> fallback,
     std::vector<uint8_t> data) {
@@ -98,18 +105,20 @@
       content::ServiceManagerConnection::GetForProcess()->GetConnector(), data,
       data_decoder::mojom::ImageCodec::DEFAULT, false,
       data_decoder::kDefaultMaxSizeInBytes, gfx::Size(),
-      base::BindOnce(&RunCallbackWithUncompressedSkBitmap,
+      base::BindOnce(&RunCallbackWithUncompressedSkBitmap, is_placeholder_icon,
                      std::move(callback)));
 }
 
 // Runs |callback| passing an IconValuePtr with an uncompressed image: an
 // ImageSkia.
 void RunCallbackWithUncompressedImageSkia(
+    bool is_placeholder_icon,
     apps::mojom::Publisher::LoadIconCallback callback,
     const gfx::ImageSkia image) {
   apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
   iv->icon_compression = apps::mojom::IconCompression::kUncompressed;
   iv->uncompressed = image;
+  iv->is_placeholder_icon = is_placeholder_icon;
   std::move(callback).Run(std::move(iv));
 }
 
@@ -117,11 +126,13 @@
 // an Image.
 void RunCallbackWithUncompressedImage(
     base::OnceCallback<void(gfx::ImageSkia*)> image_filter,
+    bool is_placeholder_icon,
     apps::mojom::Publisher::LoadIconCallback callback,
     const gfx::Image& image) {
   gfx::ImageSkia image_skia = image.AsImageSkia();
   std::move(image_filter).Run(&image_skia);
-  RunCallbackWithUncompressedImageSkia(std::move(callback), image_skia);
+  RunCallbackWithUncompressedImageSkia(is_placeholder_icon, std::move(callback),
+                                       image_skia);
 }
 
 // Forwards to extensions::ChromeAppIcon::ApplyEffects, with subtle differences
@@ -148,6 +159,7 @@
                            content::BrowserContext* context,
                            const std::string& extension_id,
                            apps::mojom::Publisher::LoadIconCallback callback) {
+  constexpr bool is_placeholder_icon = false;
   int size_hint_in_px = apps_util::ConvertDipToPx(size_hint_in_dip);
 
   const extensions::Extension* extension =
@@ -183,7 +195,7 @@
                     resize_function, apply_chrome_badge,
                     extensions::util::IsAppLaunchable(extension_id, context),
                     extension->from_bookmark()),
-                std::move(callback)));
+                is_placeholder_icon, std::move(callback)));
         return;
       }
 
@@ -194,7 +206,7 @@
         base::PostTaskWithTraitsAndReplyWithResult(
             FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
             base::BindOnce(&CompressedDataFromResource, ext_resource),
-            base::BindOnce(&RunCallbackWithCompressedData,
+            base::BindOnce(&RunCallbackWithCompressedData, is_placeholder_icon,
                            std::move(callback)));
         return;
       }
@@ -211,6 +223,7 @@
     apps::mojom::Publisher::LoadIconCallback callback,
     base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)>
         fallback) {
+  constexpr bool is_placeholder_icon = false;
   // TODO(crbug.com/826982): pass size_hint_in_dip (or _in_px) on to the
   // callbacks, and re-size the decoded-from-file image)?
 
@@ -223,7 +236,8 @@
           FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
           base::BindOnce(&ReadFileAsCompressedData, path),
           base::BindOnce(&RunCallbackWithCompressedDataToUncompressWithFallback,
-                         std::move(callback), std::move(fallback)));
+                         is_placeholder_icon, std::move(callback),
+                         std::move(fallback)));
 
       return;
     }
@@ -233,7 +247,8 @@
           FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
           base::BindOnce(&ReadFileAsCompressedData, path),
           base::BindOnce(&RunCallbackWithCompressedDataWithFallback,
-                         std::move(callback), std::move(fallback)));
+                         is_placeholder_icon, std::move(callback),
+                         std::move(fallback)));
       return;
     }
   }
@@ -244,6 +259,7 @@
 void LoadIconFromResource(apps::mojom::IconCompression icon_compression,
                           int size_hint_in_dip,
                           int resource_id,
+                          bool is_placeholder_icon,
                           apps::mojom::Publisher::LoadIconCallback callback) {
   if (resource_id != 0) {
     switch (icon_compression) {
@@ -255,7 +271,7 @@
             ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
                 resource_id);
         RunCallbackWithUncompressedImageSkia(
-            std::move(callback),
+            is_placeholder_icon, std::move(callback),
             gfx::ImageSkiaOperations::CreateResizedImage(
                 *unscaled, skia::ImageOperations::RESIZE_BEST,
                 gfx::Size(size_hint_in_dip, size_hint_in_dip)));
@@ -267,7 +283,7 @@
             ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
                 resource_id);
         RunCallbackWithCompressedData(
-            std::move(callback),
+            is_placeholder_icon, std::move(callback),
             std::vector<uint8_t>(data.begin(), data.end()));
         return;
       }
diff --git a/chrome/browser/apps/app_service/app_icon_factory.h b/chrome/browser/apps/app_service/app_icon_factory.h
index 5f28e46..86e0970 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.h
+++ b/chrome/browser/apps/app_service/app_icon_factory.h
@@ -39,6 +39,7 @@
 void LoadIconFromResource(apps::mojom::IconCompression icon_compression,
                           int size_hint_in_dip,
                           int resource_id,
+                          bool is_placeholder_icon,
                           apps::mojom::Publisher::LoadIconCallback callback);
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/app_icon_source.cc b/chrome/browser/apps/app_service/app_icon_source.cc
index 655ae95..88ae25b 100644
--- a/chrome/browser/apps/app_service/app_icon_source.cc
+++ b/chrome/browser/apps/app_service/app_icon_source.cc
@@ -97,8 +97,9 @@
     return;
   }
 
+  constexpr bool allow_placeholder_icon = false;
   app_service_proxy->LoadIcon(app_id, apps::mojom::IconCompression::kCompressed,
-                              size_in_dip,
+                              size_in_dip, allow_placeholder_icon,
                               base::BindOnce(&RunCallback, callback));
 }
 
diff --git a/chrome/browser/apps/app_service/app_service_proxy.cc b/chrome/browser/apps/app_service/app_service_proxy.cc
index f736bcc..6a4d6af 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy.cc
@@ -58,18 +58,20 @@
     const std::string& app_id,
     apps::mojom::IconCompression icon_compression,
     int32_t size_hint_in_dip,
+    bool allow_placeholder_icon,
     apps::mojom::Publisher::LoadIconCallback callback) {
   bool found = false;
   cache_.ForOneApp(app_id, [this, &icon_compression, &size_hint_in_dip,
-                            &callback, &found](const apps::AppUpdate& update) {
+                            &allow_placeholder_icon, &callback,
+                            &found](const apps::AppUpdate& update) {
     apps::mojom::IconKeyPtr icon_key = update.IconKey();
     if (icon_key.is_null()) {
       return;
     }
     found = true;
-    app_service_->LoadIcon(update.AppType(), update.AppId(),
-                           std::move(icon_key), icon_compression,
-                           size_hint_in_dip, std::move(callback));
+    app_service_->LoadIcon(update.AppType(), std::move(icon_key),
+                           icon_compression, size_hint_in_dip,
+                           allow_placeholder_icon, std::move(callback));
   });
 
   if (!found) {
diff --git a/chrome/browser/apps/app_service/app_service_proxy.h b/chrome/browser/apps/app_service/app_service_proxy.h
index 0c8c266..3f703e8 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.h
+++ b/chrome/browser/apps/app_service/app_service_proxy.h
@@ -42,6 +42,7 @@
   void LoadIcon(const std::string& app_id,
                 apps::mojom::IconCompression icon_compression,
                 int32_t size_hint_in_dip,
+                bool allow_placeholder_icon,
                 apps::mojom::Publisher::LoadIconCallback callback);
 
   void Launch(const std::string& app_id,
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 387d6f0..40eb0f0 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -25,6 +25,7 @@
 #include "components/arc/common/app.mojom.h"
 #include "components/arc/common/app_permissions.mojom.h"
 #include "content/public/common/service_manager_connection.h"
+#include "extensions/grit/extensions_browser_resources.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/data_decoder/public/cpp/decode_image.h"
 #include "ui/display/display.h"
@@ -168,10 +169,10 @@
   subscribers_.AddPtr(std::move(subscriber));
 }
 
-void ArcApps::LoadIcon(const std::string& app_id,
-                       apps::mojom::IconKeyPtr icon_key,
+void ArcApps::LoadIcon(apps::mojom::IconKeyPtr icon_key,
                        apps::mojom::IconCompression icon_compression,
                        int32_t size_hint_in_dip,
+                       bool allow_placeholder_icon,
                        LoadIconCallback callback) {
   if (!icon_key.is_null() &&
       (icon_key->icon_type == apps::mojom::IconType::kArc) &&
@@ -195,7 +196,8 @@
         GetCachedIconFilePath(icon_key->s_key, size_hint_in_dip),
         std::move(callback),
         base::BindOnce(&ArcApps::LoadIconFromVM, weak_ptr_factory_.GetWeakPtr(),
-                       icon_key->s_key, icon_compression, size_hint_in_dip));
+                       icon_key->s_key, icon_compression, size_hint_in_dip,
+                       allow_placeholder_icon));
     return;
   }
 
@@ -387,7 +389,16 @@
 void ArcApps::LoadIconFromVM(const std::string icon_key_s_key,
                              apps::mojom::IconCompression icon_compression,
                              int32_t size_hint_in_dip,
+                             bool allow_placeholder_icon,
                              LoadIconCallback callback) {
+  if (allow_placeholder_icon) {
+    constexpr bool is_placeholder_icon = true;
+    LoadIconFromResource(icon_compression, size_hint_in_dip,
+                         IDR_APP_DEFAULT_ICON, is_placeholder_icon,
+                         std::move(callback));
+    return;
+  }
+
   std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
       prefs_->GetApp(icon_key_s_key);
   if (app_info) {
@@ -418,8 +429,9 @@
   int size_hint_in_px = apps_util::ConvertDipToPx(size_hint_in_dip);
   int resource_id = (size_hint_in_px <= 32) ? IDR_ARC_SUPPORT_ICON_32
                                             : IDR_ARC_SUPPORT_ICON_192;
+  constexpr bool is_placeholder_icon = false;
   LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
-                       std::move(callback));
+                       is_placeholder_icon, std::move(callback));
 }
 
 apps::mojom::AppPtr ArcApps::Convert(const std::string& app_id,
diff --git a/chrome/browser/apps/app_service/arc_apps.h b/chrome/browser/apps/app_service/arc_apps.h
index 3f428fe..22a17769 100644
--- a/chrome/browser/apps/app_service/arc_apps.h
+++ b/chrome/browser/apps/app_service/arc_apps.h
@@ -44,10 +44,10 @@
   // apps::mojom::Publisher overrides.
   void Connect(apps::mojom::SubscriberPtr subscriber,
                apps::mojom::ConnectOptionsPtr opts) override;
-  void LoadIcon(const std::string& app_id,
-                apps::mojom::IconKeyPtr icon_key,
+  void LoadIcon(apps::mojom::IconKeyPtr icon_key,
                 apps::mojom::IconCompression icon_compression,
                 int32_t size_hint_in_dip,
+                bool allow_placeholder_icon,
                 LoadIconCallback callback) override;
   void Launch(const std::string& app_id,
               int32_t event_flags,
@@ -83,6 +83,7 @@
   void LoadIconFromVM(const std::string icon_key_s_key,
                       apps::mojom::IconCompression icon_compression,
                       int32_t size_hint_in_dip,
+                      bool allow_placeholder_icon,
                       LoadIconCallback callback);
   void LoadPlayStoreIcon(apps::mojom::IconCompression icon_compression,
                          int32_t size_hint_in_dip,
diff --git a/chrome/browser/apps/app_service/built_in_chromeos_apps.cc b/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
index aa10f84..156d590 100644
--- a/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
+++ b/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
@@ -107,17 +107,18 @@
 }
 
 void BuiltInChromeOsApps::LoadIcon(
-    const std::string& app_id,
     apps::mojom::IconKeyPtr icon_key,
     apps::mojom::IconCompression icon_compression,
     int32_t size_hint_in_dip,
+    bool allow_placeholder_icon,
     LoadIconCallback callback) {
+  constexpr bool is_placeholder_icon = false;
   if (!icon_key.is_null() &&
       (icon_key->icon_type == apps::mojom::IconType::kResource) &&
       (icon_key->u_key != 0) && (icon_key->u_key <= INT_MAX)) {
     int resource_id = static_cast<int>(icon_key->u_key);
     LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
-                         std::move(callback));
+                         is_placeholder_icon, std::move(callback));
     return;
   }
   // On failure, we still run the callback, with the zero IconValue.
diff --git a/chrome/browser/apps/app_service/built_in_chromeos_apps.h b/chrome/browser/apps/app_service/built_in_chromeos_apps.h
index 7b5c173..6bc59ef 100644
--- a/chrome/browser/apps/app_service/built_in_chromeos_apps.h
+++ b/chrome/browser/apps/app_service/built_in_chromeos_apps.h
@@ -28,10 +28,10 @@
   // apps::mojom::Publisher overrides.
   void Connect(apps::mojom::SubscriberPtr subscriber,
                apps::mojom::ConnectOptionsPtr opts) override;
-  void LoadIcon(const std::string& app_id,
-                apps::mojom::IconKeyPtr icon_key,
+  void LoadIcon(apps::mojom::IconKeyPtr icon_key,
                 apps::mojom::IconCompression icon_compression,
                 int32_t size_hint_in_dip,
+                bool allow_placeholder_icon,
                 LoadIconCallback callback) override;
   void Launch(const std::string& app_id,
               int32_t event_flags,
diff --git a/chrome/browser/apps/app_service/crostini_apps.cc b/chrome/browser/apps/app_service/crostini_apps.cc
index a9c6cdd..449942b 100644
--- a/chrome/browser/apps/app_service/crostini_apps.cc
+++ b/chrome/browser/apps/app_service/crostini_apps.cc
@@ -62,17 +62,18 @@
   subscribers_.AddPtr(std::move(subscriber));
 }
 
-void CrostiniApps::LoadIcon(const std::string& app_id,
-                            apps::mojom::IconKeyPtr icon_key,
+void CrostiniApps::LoadIcon(apps::mojom::IconKeyPtr icon_key,
                             apps::mojom::IconCompression icon_compression,
                             int32_t size_hint_in_dip,
+                            bool allow_placeholder_icon,
                             LoadIconCallback callback) {
   if (!icon_key.is_null()) {
     if ((icon_key->icon_type == apps::mojom::IconType::kResource) &&
         (icon_key->u_key != 0) && (icon_key->u_key <= INT_MAX)) {
       int resource_id = static_cast<int>(icon_key->u_key);
+      constexpr bool is_placeholder_icon = false;
       LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
-                           std::move(callback));
+                           is_placeholder_icon, std::move(callback));
       return;
     }
 
@@ -87,7 +88,8 @@
           std::move(callback),
           base::BindOnce(&CrostiniApps::LoadIconFromVM,
                          weak_ptr_factory_.GetWeakPtr(), icon_key->s_key,
-                         scale_factor));
+                         icon_compression, size_hint_in_dip,
+                         allow_placeholder_icon, scale_factor));
       return;
     }
   }
@@ -142,10 +144,23 @@
 }
 
 void CrostiniApps::LoadIconFromVM(const std::string icon_key_s_key,
+                                  apps::mojom::IconCompression icon_compression,
+                                  int32_t size_hint_in_dip,
+                                  bool allow_placeholder_icon,
                                   ui::ScaleFactor scale_factor,
                                   LoadIconCallback callback) {
-  // Treat this as failure. We still run the callback, with the zero IconValue.
-  std::move(callback).Run(apps::mojom::IconValue::New());
+  if (!allow_placeholder_icon) {
+    // Treat this as failure. We still run the callback, with the zero
+    // IconValue.
+    std::move(callback).Run(apps::mojom::IconValue::New());
+    return;
+  }
+
+  // Provide a placeholder icon.
+  constexpr bool is_placeholder_icon = true;
+  LoadIconFromResource(icon_compression, size_hint_in_dip,
+                       IDR_LOGO_CROSTINI_DEFAULT_192, is_placeholder_icon,
+                       std::move(callback));
 
   // Ask the VM to load the icon (and write a cached copy to the file system).
   // The "Maybe" is because multiple requests for the same icon will be merged,
diff --git a/chrome/browser/apps/app_service/crostini_apps.h b/chrome/browser/apps/app_service/crostini_apps.h
index 148c6316..290663d 100644
--- a/chrome/browser/apps/app_service/crostini_apps.h
+++ b/chrome/browser/apps/app_service/crostini_apps.h
@@ -43,10 +43,10 @@
   // apps::mojom::Publisher overrides.
   void Connect(apps::mojom::SubscriberPtr subscriber,
                apps::mojom::ConnectOptionsPtr opts) override;
-  void LoadIcon(const std::string& app_id,
-                apps::mojom::IconKeyPtr icon_key,
+  void LoadIcon(apps::mojom::IconKeyPtr icon_key,
                 apps::mojom::IconCompression icon_compression,
                 int32_t size_hint_in_dip,
+                bool allow_placeholder_icon,
                 LoadIconCallback callback) override;
   void Launch(const std::string& app_id,
               int32_t event_flags,
@@ -67,6 +67,9 @@
                         ui::ScaleFactor scale_factor) override;
 
   void LoadIconFromVM(const std::string icon_key_s_key,
+                      apps::mojom::IconCompression icon_compression,
+                      int32_t size_hint_in_dip,
+                      bool allow_placeholder_icon,
                       ui::ScaleFactor scale_factor,
                       LoadIconCallback callback);
 
diff --git a/chrome/browser/apps/app_service/extension_apps.cc b/chrome/browser/apps/app_service/extension_apps.cc
index 2c70697..843de94 100644
--- a/chrome/browser/apps/app_service/extension_apps.cc
+++ b/chrome/browser/apps/app_service/extension_apps.cc
@@ -124,10 +124,10 @@
   subscribers_.AddPtr(std::move(subscriber));
 }
 
-void ExtensionApps::LoadIcon(const std::string& app_id,
-                             apps::mojom::IconKeyPtr icon_key,
+void ExtensionApps::LoadIcon(apps::mojom::IconKeyPtr icon_key,
                              apps::mojom::IconCompression icon_compression,
                              int32_t size_hint_in_dip,
+                             bool allow_placeholder_icon,
                              LoadIconCallback callback) {
   if (!icon_key.is_null() &&
       (icon_key->icon_type == apps::mojom::IconType::kExtension) &&
diff --git a/chrome/browser/apps/app_service/extension_apps.h b/chrome/browser/apps/app_service/extension_apps.h
index 0f093be..26d66fe 100644
--- a/chrome/browser/apps/app_service/extension_apps.h
+++ b/chrome/browser/apps/app_service/extension_apps.h
@@ -50,10 +50,10 @@
   // apps::mojom::Publisher overrides.
   void Connect(apps::mojom::SubscriberPtr subscriber,
                apps::mojom::ConnectOptionsPtr opts) override;
-  void LoadIcon(const std::string& app_id,
-                apps::mojom::IconKeyPtr icon_key,
+  void LoadIcon(apps::mojom::IconKeyPtr icon_key,
                 apps::mojom::IconCompression icon_compression,
                 int32_t size_hint_in_dip,
+                bool allow_placeholder_icon,
                 LoadIconCallback callback) override;
   void Launch(const std::string& app_id,
               int32_t event_flags,
diff --git a/chrome/browser/chromeos/arc/auth/arc_robot_auth_code_fetcher_browsertest.cc b/chrome/browser/chromeos/arc/auth/arc_robot_auth_code_fetcher_browsertest.cc
index 4fcf9fd..a22e6c8 100644
--- a/chrome/browser/chromeos/arc/auth/arc_robot_auth_code_fetcher_browsertest.cc
+++ b/chrome/browser/chromeos/arc/auth/arc_robot_auth_code_fetcher_browsertest.cc
@@ -105,19 +105,9 @@
             cloud_policy_manager->core()->client());
     cloud_policy_client->SetDMToken("fake-dm-token");
     cloud_policy_client->client_id_ = "client-id";
-
-    test_url_loader_factory_ =
-        std::make_unique<network::TestURLLoaderFactory>();
-    test_shared_loader_factory_ =
-        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-            test_url_loader_factory_.get());
   }
 
-  void TearDownOnMainThread() override {
-    if (test_shared_loader_factory_)
-      test_shared_loader_factory_->Detach();
-    user_manager_enabler_.reset();
-  }
+  void TearDownOnMainThread() override { user_manager_enabler_.reset(); }
 
   chromeos::FakeChromeUserManager* GetFakeUserManager() const {
     return static_cast<chromeos::FakeChromeUserManager*>(
@@ -128,7 +118,8 @@
                      bool* output_fetch_success,
                      std::string* output_auth_code) {
     base::RunLoop run_loop;
-    fetcher->SetURLLoaderFactoryForTesting(test_shared_loader_factory_);
+    fetcher->SetURLLoaderFactoryForTesting(
+        test_url_loader_factory_.GetSafeWeakWrapper());
     fetcher->Fetch(base::Bind(
         [](bool* output_fetch_success, std::string* output_auth_code,
            base::RunLoop* run_loop, bool fetch_success,
@@ -145,16 +136,14 @@
   }
 
   network::TestURLLoaderFactory* test_url_loader_factory() {
-    return test_url_loader_factory_.get();
+    return &test_url_loader_factory_;
   }
 
  private:
   // Whether to connect the CloudPolicyClient.
   CloudPolicyClientSetup cloud_policy_client_setup_;
 
-  std::unique_ptr<network::TestURLLoaderFactory> test_url_loader_factory_;
-  scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
-      test_shared_loader_factory_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
   std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
 
   DISALLOW_COPY_AND_ASSIGN(ArcRobotAuthCodeFetcherBrowserTest);
diff --git a/chrome/browser/chromeos/arc/process/arc_process_service.cc b/chrome/browser/chromeos/arc/process/arc_process_service.cc
index 143e318..684d328 100644
--- a/chrome/browser/chromeos/arc/process/arc_process_service.cc
+++ b/chrome/browser/chromeos/arc/process/arc_process_service.cc
@@ -32,6 +32,7 @@
 #include "base/trace_event/trace_event.h"
 #include "components/arc/arc_bridge_service.h"
 #include "components/arc/arc_browser_context_keyed_service_factory_base.h"
+#include "components/arc/arc_util.h"
 #include "content/public/browser/browser_thread.h"
 #include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h"
 #include "services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom.h"
@@ -68,6 +69,12 @@
 
 std::vector<ArcProcess> GetArcSystemProcessList() {
   std::vector<ArcProcess> ret_processes;
+
+  if (arc::IsArcVmEnabled()) {
+    // TODO(b/122992194): Fix this for ARCVM.
+    return ret_processes;
+  }
+
   const base::ProcessIterator::ProcessEntries& entry_list =
       base::ProcessIterator(nullptr).Snapshot();
   const base::ProcessId arc_init_pid = GetArcInitProcessId(entry_list);
@@ -152,17 +159,29 @@
     std::vector<mojom::RunningAppProcessInfoPtr> processes) {
   std::vector<ArcProcess> ret_processes;
   for (const auto& entry : processes) {
-    const auto it = pid_map.find(entry->pid);
-    // The nspid could be missing due to race condition. For example, the
-    // process is still running when we get the process snapshot and ends when
-    // we update the nspid to pid mapping.
-    if (it == pid_map.end() || it->second == base::kNullProcessId) {
-      continue;
+    base::ProcessId pid;
+    if (arc::IsArcVmEnabled()) {
+      // When VM is enabled, there is no external pid. Set the pid here to the
+      // guest pid. Setting the pid to zero was considered but the task manager
+      // groups tasks by pid and this can cause incorrect aggregated stats to
+      // be displayed. The task manager will handle these cases by checking if
+      // the task is in VM (via Task::IsRunningInVM) and know to partition off
+      // these processes.
+      pid = entry->pid;
+    } else {
+      const auto it = pid_map.find(entry->pid);
+      // The nspid could be missing due to race condition. For example, the
+      // process is still running when we get the process snapshot and ends when
+      // we update the nspid to pid mapping.
+      if (it == pid_map.end() || it->second == base::kNullProcessId) {
+        continue;
+      }
+      pid = it->second;
     }
     // Constructs the ArcProcess instance if the mapping is found.
-    ArcProcess arc_process(entry->pid, pid_map.at(entry->pid),
-                           entry->process_name, entry->process_state,
-                           entry->is_focused, entry->last_activity_time);
+    ArcProcess arc_process(entry->pid, pid, entry->process_name,
+                           entry->process_state, entry->is_focused,
+                           entry->last_activity_time);
     // |entry->packages| is provided only when process.mojom's verion is >=4.
     if (entry->packages) {
       for (const auto& package : *entry->packages) {
@@ -178,26 +197,28 @@
     scoped_refptr<ArcProcessService::NSPidToPidMap> nspid_map,
     std::vector<mojom::RunningAppProcessInfoPtr> processes) {
   ArcProcessService::NSPidToPidMap& pid_map = *nspid_map;
-  // Cleanup dead pids in the cache |pid_map|.
-  std::unordered_set<ProcessId> nspid_to_remove;
-  for (const auto& entry : pid_map) {
-    nspid_to_remove.insert(entry.first);
-  }
-  bool unmapped_nspid = false;
-  for (const auto& entry : processes) {
-    // erase() returns 0 if coudln't find the key. It means a new process.
-    if (nspid_to_remove.erase(entry->pid) == 0) {
-      pid_map[entry->pid] = base::kNullProcessId;
-      unmapped_nspid = true;
+  if (!arc::IsArcVmEnabled()) {
+    // Cleanup dead pids in the cache |pid_map|.
+    std::unordered_set<ProcessId> nspid_to_remove;
+    for (const auto& entry : pid_map) {
+      nspid_to_remove.insert(entry.first);
     }
-  }
-  for (const auto& entry : nspid_to_remove) {
-    pid_map.erase(entry);
-  }
+    bool unmapped_nspid = false;
+    for (const auto& entry : processes) {
+      // erase() returns 0 if coudln't find the key. It means a new process.
+      if (nspid_to_remove.erase(entry->pid) == 0) {
+        pid_map[entry->pid] = base::kNullProcessId;
+        unmapped_nspid = true;
+      }
+    }
+    for (const auto& entry : nspid_to_remove) {
+      pid_map.erase(entry);
+    }
 
-  // The operation is costly so avoid calling it when possible.
-  if (unmapped_nspid) {
-    UpdateNspidToPidMap(nspid_map);
+    // The operation is costly so avoid calling it when possible.
+    if (unmapped_nspid) {
+      UpdateNspidToPidMap(nspid_map);
+    }
   }
 
   return FilterProcessList(pid_map, std::move(processes));
@@ -207,34 +228,36 @@
 UpdateAndReturnMemoryInfo(
     scoped_refptr<ArcProcessService::NSPidToPidMap> nspid_map,
     memory_instrumentation::mojom::GlobalMemoryDumpPtr dump) {
-  ArcProcessService::NSPidToPidMap& pid_map = *nspid_map;
-  // Cleanup dead processes in pid_map
-  // TODO(wvk) should we be cleaning dead processes here too ?
-  base::flat_set<ProcessId> nspid_to_remove;
-  for (const auto& entry : pid_map)
-    nspid_to_remove.insert(entry.first);
+  if (!arc::IsArcVmEnabled()) {
+    ArcProcessService::NSPidToPidMap& pid_map = *nspid_map;
+    // Cleanup dead processes in pid_map
+    // TODO(wvk) should we be cleaning dead processes here too ?
+    base::flat_set<ProcessId> nspid_to_remove;
+    for (const auto& entry : pid_map)
+      nspid_to_remove.insert(entry.first);
 
-  bool unmapped_nspid = false;
-  for (const auto& proc : dump->process_dumps) {
-    // erase() returns 0 if couldn't find the key (new process)
-    if (nspid_to_remove.erase(proc->pid) == 0) {
-      pid_map[proc->pid] = base::kNullProcessId;
-      unmapped_nspid = true;
+    bool unmapped_nspid = false;
+    for (const auto& proc : dump->process_dumps) {
+      // erase() returns 0 if couldn't find the key (new process)
+      if (nspid_to_remove.erase(proc->pid) == 0) {
+        pid_map[proc->pid] = base::kNullProcessId;
+        unmapped_nspid = true;
+      }
     }
-  }
-  for (const auto& old_nspid : nspid_to_remove)
-    pid_map.erase(old_nspid);
+    for (const auto& old_nspid : nspid_to_remove)
+      pid_map.erase(old_nspid);
 
-  if (unmapped_nspid)
-    UpdateNspidToPidMap(nspid_map);
+    if (unmapped_nspid)
+      UpdateNspidToPidMap(nspid_map);
 
-  // Return memory info only for processes that have a mapping nspid->pid
-  for (auto& proc : dump->process_dumps) {
-    auto it = pid_map.find(proc->pid);
-    proc->pid = it == pid_map.end() ? kNullProcessId : it->second;
+    // Return memory info only for processes that have a mapping nspid->pid
+    for (auto& proc : dump->process_dumps) {
+      auto it = pid_map.find(proc->pid);
+      proc->pid = it == pid_map.end() ? kNullProcessId : it->second;
+    }
+    base::EraseIf(dump->process_dumps,
+                  [](const auto& proc) { return proc->pid == kNullProcessId; });
   }
-  base::EraseIf(dump->process_dumps,
-                [](const auto& proc) { return proc->pid == kNullProcessId; });
   return memory_instrumentation::GlobalMemoryDump::MoveFrom(std::move(dump));
 }
 
@@ -393,8 +416,11 @@
     return;
   }
   std::vector<uint32_t> nspids;
-  for (const auto& proc : procs)
-    nspids.push_back(proc.nspid());
+  if (!arc::IsArcVmEnabled()) {
+    for (const auto& proc : procs)
+      nspids.push_back(proc.nspid());
+  }
+  // TODO(b/122992194): Fix this for ARCVM.
   process_instance->RequestSystemProcessMemoryInfo(
       nspids,
       base::BindOnce(&ArcProcessService::OnReceiveMemoryInfo,
diff --git a/chrome/browser/chromeos/drive/file_system_util.cc b/chrome/browser/chromeos/drive/file_system_util.cc
index a80838c..b23a782 100644
--- a/chrome/browser/chromeos/drive/file_system_util.cc
+++ b/chrome/browser/chromeos/drive/file_system_util.cc
@@ -32,7 +32,7 @@
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_paths_internal.h"
 #include "chromeos/constants/chromeos_constants.h"
-#include "components/browser_sync/profile_sync_service.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/drive/chromeos/file_system_interface.h"
 #include "components/drive/drive.pb.h"
 #include "components/drive/drive_pref_names.h"
@@ -223,7 +223,7 @@
 
   // Disable drive if sync is disabled by command line flag. Outside tests, this
   // only occurs in cases already handled by the gaia account check above.
-  if (!browser_sync::ProfileSyncService::IsSyncAllowedByFlag())
+  if (!switches::IsSyncAllowedByFlag())
     return false;
 
   return true;
diff --git a/chrome/browser/chromeos/events/event_rewriter_unittest.cc b/chrome/browser/chromeos/events/event_rewriter_unittest.cc
index c7b1d1a..dd1fb78 100644
--- a/chrome/browser/chromeos/events/event_rewriter_unittest.cc
+++ b/chrome/browser/chromeos/events/event_rewriter_unittest.cc
@@ -1276,21 +1276,21 @@
   rewriter_->set_ime_keyboard_for_testing(&ime_keyboard);
   EXPECT_FALSE(ime_keyboard.caps_lock_is_enabled_);
 
-  // On Chrome OS, CapsLock is mapped to F16 with Mod3Mask.
+  // On Chrome OS, CapsLock is mapped to CapsLock with Mod3Mask.
   EXPECT_EQ(GetExpectedResultAsString(
                 ui::ET_KEY_PRESSED, ui::VKEY_CAPITAL, ui::DomCode::CAPS_LOCK,
                 ui::EF_CAPS_LOCK_ON | ui::EF_MOD3_DOWN, ui::DomKey::CAPS_LOCK),
             GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED,
-                                      ui::VKEY_F16, ui::DomCode::F16,
-                                      ui::EF_MOD3_DOWN, ui::DomKey::F16));
+                                      ui::VKEY_CAPITAL, ui::DomCode::CAPS_LOCK,
+                                      ui::EF_MOD3_DOWN, ui::DomKey::CAPS_LOCK));
   EXPECT_FALSE(ime_keyboard.caps_lock_is_enabled_);
 
   EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_RELEASED, ui::VKEY_CAPITAL,
                                       ui::DomCode::CAPS_LOCK, ui::EF_NONE,
                                       ui::DomKey::CAPS_LOCK),
             GetRewrittenEventAsString(rewriter_, ui::ET_KEY_RELEASED,
-                                      ui::VKEY_F16, ui::DomCode::F16,
-                                      ui::EF_MOD3_DOWN, ui::DomKey::F16));
+                                      ui::VKEY_CAPITAL, ui::DomCode::CAPS_LOCK,
+                                      ui::EF_MOD3_DOWN, ui::DomKey::CAPS_LOCK));
   EXPECT_TRUE(ime_keyboard.caps_lock_is_enabled_);
 
   // Remap Caps Lock to Control.
@@ -1314,22 +1314,6 @@
             GetRewrittenEventAsString(rewriter_, ui::ET_KEY_RELEASED,
                                       ui::VKEY_CAPITAL, ui::DomCode::CAPS_LOCK,
                                       ui::EF_NONE, ui::DomKey::CAPS_LOCK));
-
-  // Press F16.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_CONTROL,
-                                      ui::DomCode::CONTROL_LEFT,
-                                      ui::EF_CONTROL_DOWN, ui::DomKey::CONTROL),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED,
-                                      ui::VKEY_F16, ui::DomCode::F16,
-                                      ui::EF_MOD3_DOWN, ui::DomKey::F16));
-
-  // Release F16.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL,
-                                      ui::DomCode::CONTROL_LEFT, ui::EF_NONE,
-                                      ui::DomKey::CONTROL),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_RELEASED,
-                                      ui::VKEY_F16, ui::DomCode::F16,
-                                      ui::EF_MOD3_DOWN, ui::DomKey::F16));
 }
 
 TEST_F(EventRewriterTest, TestRewriteDiamondKey) {
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 588c07c..ac13ccd 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -477,7 +477,10 @@
         TestCase("checkPlayFilesContextMenu"),
         TestCase("checkPlayFilesContextMenu").EnableMyFilesVolume(),
         TestCase("checkLinuxFilesContextMenu"),
-        TestCase("checkLinuxFilesContextMenu").EnableMyFilesVolume()));
+        TestCase("checkLinuxFilesContextMenu").EnableMyFilesVolume(),
+        TestCase("checkRemovableRootContextMenu"),
+        TestCase("checkUsbContextMenu"),
+        TestCase("checkPartitionContextMenu")));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Delete, /* delete.js */
diff --git a/chrome/browser/chromeos/file_manager/path_util.cc b/chrome/browser/chromeos/file_manager/path_util.cc
index aec6e53..2484657 100644
--- a/chrome/browser/chromeos/file_manager/path_util.cc
+++ b/chrome/browser/chromeos/file_manager/path_util.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
+#include "chrome/browser/chromeos/drive/file_system_util.h"
 #include "chrome/browser/chromeos/fileapi/external_file_url_util.h"
 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -205,6 +206,22 @@
   return false;
 }
 
+bool MigrateToDriveFs(Profile* profile,
+                      const base::FilePath& old_path,
+                      base::FilePath* new_path) {
+  const auto* user = chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
+  auto* integration_service =
+      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
+  if (!base::FeatureList::IsEnabled(chromeos::features::kDriveFs) ||
+      !integration_service || !integration_service->is_enabled() || !user ||
+      !user->GetAccountId().HasAccountIdKey()) {
+    return false;
+  }
+  *new_path = integration_service->GetMountPointPath();
+  return drive::util::GetDriveMountPointPath(profile).AppendRelativePath(
+      old_path, new_path);
+}
+
 std::string GetDownloadsMountPointName(Profile* profile) {
   // To distinguish profiles in multi-profile session, we append user name hash
   // to "Downloads". Note that some profiles (like login or test profiles)
@@ -500,6 +517,33 @@
                                .Append(l10n_util::GetStringUTF8(
                                    IDS_FILE_BROWSER_DRIVE_COMPUTERS_LABEL))
                                .value())) {
+  } else if (drive_integration_service &&
+             ReplacePrefix(&result,
+                           drive::util::GetDriveMountPointPath(profile)
+                               .Append(kDriveFsDirRoot)
+                               .value(),
+                           base::FilePath(kDisplayNameGoogleDrive)
+                               .Append(l10n_util::GetStringUTF8(
+                                   IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL))
+                               .value())) {
+  } else if (drive_integration_service &&
+             ReplacePrefix(&result,
+                           drive::util::GetDriveMountPointPath(profile)
+                               .Append(kDriveFsDirTeamDrives)
+                               .value(),
+                           base::FilePath(kDisplayNameGoogleDrive)
+                               .Append(l10n_util::GetStringUTF8(
+                                   IDS_FILE_BROWSER_DRIVE_TEAM_DRIVES_LABEL))
+                               .value())) {
+  } else if (drive_integration_service &&
+             ReplacePrefix(&result,
+                           drive::util::GetDriveMountPointPath(profile)
+                               .Append(kDriveFsDirComputers)
+                               .value(),
+                           base::FilePath(kDisplayNameGoogleDrive)
+                               .Append(l10n_util::GetStringUTF8(
+                                   IDS_FILE_BROWSER_DRIVE_COMPUTERS_LABEL))
+                               .value())) {
   } else if (ReplacePrefix(&result, kAndroidFilesPath,
                            l10n_util::GetStringUTF8(
                                IDS_FILE_BROWSER_ANDROID_FILES_ROOT_LABEL))) {
diff --git a/chrome/browser/chromeos/file_manager/path_util.h b/chrome/browser/chromeos/file_manager/path_util.h
index f082d4a0..89067af 100644
--- a/chrome/browser/chromeos/file_manager/path_util.h
+++ b/chrome/browser/chromeos/file_manager/path_util.h
@@ -61,6 +61,14 @@
                                    const base::FilePath& old_path,
                                    base::FilePath* new_path);
 
+// Convers |old_path| in /special/drive-<hash> to |new_path| in
+// /media/fuse/drivefs-<id>. Returns true if path is changed else
+// returns false if |old_path| was not inside Drive, and |new_path| is
+// undefined.
+bool MigrateToDriveFs(Profile* profile,
+                      const base::FilePath& old_path,
+                      base::FilePath* new_path);
+
 // The canonical mount point name for "Downloads" folder.
 std::string GetDownloadsMountPointName(Profile* profile);
 
diff --git a/chrome/browser/chromeos/file_manager/path_util_unittest.cc b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
index 9000c4a..fa85091 100644
--- a/chrome/browser/chromeos/file_manager/path_util_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
@@ -238,6 +238,21 @@
             "/media/fuse/drivefs-84675c855b63e12f384d45f033826980/"
             "Computers/My Other Computer/bar"));
 
+    EXPECT_EQ("Google Drive \u203a My Drive \u203a foo",
+              GetPathDisplayTextForSettings(
+                  &profile2, "/special/drive-0123456789abcdef/root/foo"));
+    EXPECT_EQ(
+        "Google Drive \u203a Team Drives \u203a A Team Drive \u203a foo",
+        GetPathDisplayTextForSettings(
+            &profile2,
+            "/special/drive-0123456789abcdef/team_drives/A Team Drive/foo"));
+
+    EXPECT_EQ(
+        "Google Drive \u203a Computers \u203a My Other Computer \u203a bar",
+        GetPathDisplayTextForSettings(
+            &profile2,
+            "/special/drive-0123456789abcdef/Computers/My Other Computer/bar"));
+
     TestingProfile guest_profile(base::FilePath("/home/chronos/guest"));
     guest_profile.SetGuestSession(true);
     guest_profile.set_profile_name("$guest");
@@ -370,6 +385,49 @@
       FilePath::FromUTF8Unsafe("/home/chronos/user/dl"), &path));
 }
 
+TEST_F(FileManagerPathUtilTest, MigrateToDriveFs) {
+  base::FilePath home("/home/chronos/u-0123456789abcdef");
+  base::FilePath other("/some/other/path");
+  base::FilePath old_drive("/special/drive-0123456789abcdef");
+  base::FilePath my_drive = old_drive.Append("root");
+  base::FilePath file_in_my_drive = old_drive.Append("root").Append("file.txt");
+
+  // DriveFS disabled, no changes.
+  base::FilePath result;
+  {
+    drive::DriveIntegrationServiceFactory::GetForProfile(profile_.get())
+        ->SetEnabled(true);
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(chromeos::features::kDriveFs);
+    EXPECT_FALSE(MigrateToDriveFs(profile_.get(), other, &result));
+    EXPECT_FALSE(MigrateToDriveFs(profile_.get(), my_drive, &result));
+  }
+  // MyFilesVolume enabled, migrate paths under Downloads.
+  {
+    TestingProfile profile2(base::FilePath("/home/chronos/u-0123456789abcdef"));
+    chromeos::FakeChromeUserManager user_manager;
+    user_manager.AddUser(
+        AccountId::FromUserEmailGaiaId(profile2.GetProfileUserName(), "12345"));
+    PrefService* prefs = profile2.GetPrefs();
+    prefs->SetString(drive::prefs::kDriveFsProfileSalt, "a");
+    drive::DriveIntegrationServiceFactory::GetForProfile(&profile2)->SetEnabled(
+        true);
+
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(chromeos::features::kDriveFs);
+    EXPECT_FALSE(MigrateToDriveFs(&profile2, other, &result));
+    EXPECT_TRUE(MigrateToDriveFs(&profile2, my_drive, &result));
+    EXPECT_EQ(base::FilePath(
+                  "/media/fuse/drivefs-84675c855b63e12f384d45f033826980/root"),
+              result);
+    EXPECT_TRUE(MigrateToDriveFs(&profile2, file_in_my_drive, &result));
+    EXPECT_EQ(
+        base::FilePath("/media/fuse/drivefs-84675c855b63e12f384d45f033826980/"
+                       "root/file.txt"),
+        result);
+  }
+}
+
 TEST_F(FileManagerPathUtilTest, ConvertFileSystemURLToPathInsideCrostini) {
   content::TestServiceManagerContext service_manager_context;
 
diff --git a/chrome/browser/chromeos/scheduler_configuration_manager.cc b/chrome/browser/chromeos/scheduler_configuration_manager.cc
index f80cfc4..ca6ef4a 100644
--- a/chrome/browser/chromeos/scheduler_configuration_manager.cc
+++ b/chrome/browser/chromeos/scheduler_configuration_manager.cc
@@ -7,6 +7,8 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/metrics/field_trial_params.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/dbus/debug_daemon_client.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -15,6 +17,11 @@
 
 namespace chromeos {
 
+namespace {
+constexpr base::FeatureParam<std::string> kSchedulerConfigurationParam{
+    &features::kSchedulerConfiguration, "config", ""};
+}  // namespace
+
 SchedulerConfigurationManager::SchedulerConfigurationManager(
     DebugDaemonClient* debug_daemon_client,
     PrefService* local_state)
@@ -34,6 +41,10 @@
 // static
 void SchedulerConfigurationManager::RegisterLocalStatePrefs(
     PrefRegistrySimple* registry) {
+  // Ideally the pref would be registered specifying the default provided via
+  // the feature parameter. This is unfortunately not possible though because
+  // the feature API initialization depends on the local state PrefService, so
+  // this function runs before feature parameters are available.
   registry->RegisterStringPref(prefs::kSchedulerConfiguration, std::string());
 }
 
@@ -55,10 +66,16 @@
     return;
   }
 
+  // Determine the effective configuration name. Prefer the value from local
+  // state if present, feature parameter if otherwise, hard-coded default if
+  // no configuration is present.
   std::string config_name;
   PrefService* local_state = observer_.prefs();
+  std::string feature_param_value = kSchedulerConfigurationParam.Get();
   if (local_state->HasPrefPath(prefs::kSchedulerConfiguration)) {
     config_name = local_state->GetString(prefs::kSchedulerConfiguration);
+  } else if (!feature_param_value.empty()) {
+    config_name = feature_param_value;
   } else {
     config_name = debugd::scheduler_configuration::kPerformanceScheduler;
   }
diff --git a/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc b/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc
index e1507d6..d614f591 100644
--- a/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc
+++ b/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc
@@ -6,7 +6,9 @@
 
 #include <memory>
 
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/dbus/fake_debug_daemon_client.h"
 #include "components/prefs/testing_pref_service.h"
@@ -79,4 +81,20 @@
             debug_daemon_client_.scheduler_configuration_name());
 }
 
+TEST_F(SchedulerConfigurationManagerTest, FinchDefault) {
+  auto feature_list = std::make_unique<base::test::ScopedFeatureList>();
+  feature_list->InitAndEnableFeatureWithParameters(
+      features::kSchedulerConfiguration, {{"config", "finch"}});
+
+  // Finch parameter selects the default.
+  SchedulerConfigurationManager manager(&debug_daemon_client_, &local_state_);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ("finch", debug_daemon_client_.scheduler_configuration_name());
+
+  // Config values override finch default.
+  local_state_.SetString(prefs::kSchedulerConfiguration, "config");
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ("config", debug_daemon_client_.scheduler_configuration_name());
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/download/download_prefs.cc b/chrome/browser/download/download_prefs.cc
index 53d7b76..f1c854a 100644
--- a/chrome/browser/download/download_prefs.cc
+++ b/chrome/browser/download/download_prefs.cc
@@ -143,6 +143,9 @@
     } else if (file_manager::util::MigrateFromDownloadsToMyFiles(
                    profile_, current, &migrated)) {
       prefs->SetFilePath(path_pref[i], migrated);
+    } else if (file_manager::util::MigrateToDriveFs(profile_, current,
+                                                    &migrated)) {
+      prefs->SetFilePath(path_pref[i], migrated);
     }
   }
 
@@ -450,6 +453,14 @@
 base::FilePath DownloadPrefs::SanitizeDownloadTargetPath(
     const base::FilePath& path) const {
 #if defined(OS_CHROMEOS)
+  base::FilePath migrated_drive_path;
+  // Managed prefs may force a legacy Drive path as the download path. Ensure
+  // the path is valid when DriveFS is enabled.
+  if (file_manager::util::MigrateToDriveFs(profile_, path,
+                                           &migrated_drive_path)) {
+    return SanitizeDownloadTargetPath(migrated_drive_path);
+  }
+
   // If |path| isn't absolute, fall back to the default directory.
   base::FilePath profile_myfiles_path =
       file_manager::util::GetMyFilesFolderForProfile(profile_);
diff --git a/chrome/browser/drive/drive_notification_manager_factory.cc b/chrome/browser/drive/drive_notification_manager_factory.cc
index e413a78..a4ffda5 100644
--- a/chrome/browser/drive/drive_notification_manager_factory.cc
+++ b/chrome/browser/drive/drive_notification_manager_factory.cc
@@ -8,7 +8,7 @@
 #include "chrome/browser/invalidation/deprecated_profile_invalidation_provider_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
-#include "components/browser_sync/profile_sync_service.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/drive/drive_notification_manager.h"
 #include "components/invalidation/impl/profile_invalidation_provider.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -27,7 +27,7 @@
 DriveNotificationManager*
 DriveNotificationManagerFactory::GetForBrowserContext(
     content::BrowserContext* context) {
-  if (!browser_sync::ProfileSyncService::IsSyncAllowedByFlag())
+  if (!switches::IsSyncAllowedByFlag())
     return NULL;
   if (!invalidation::DeprecatedProfileInvalidationProviderFactory::
           GetForProfile(Profile::FromBrowserContext(context))) {
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc
index db22228..b5659c4 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc
@@ -50,4 +50,8 @@
   ASSERT_TRUE(RunExtensionTest("extension_with_no_ruleset")) << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(DeclarativeNetRequestAPItest, DynamicRules) {
+  ASSERT_TRUE(RunExtensionTest("dynamic_rules")) << message_;
+}
+
 }  // namespace
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index 05f2028..16eb061 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -152,8 +152,6 @@
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-
   DISALLOW_COPY_AND_ASSIGN(ContentScriptApiTest);
 };
 
@@ -931,6 +929,51 @@
   EXPECT_EQ(123, content::EvalJs(web_contents, "123"));
 }
 
+namespace {
+
+enum class BindingsType { kNative, kJavaScript };
+
+// Test fixture for testing messaging APIs. Is parameterized to allow testing
+// with and without native (C++-based) extension bindings.
+class ContentScriptMessagingApiTest
+    : public ContentScriptApiTest,
+      public ::testing::WithParamInterface<BindingsType> {
+ protected:
+  ContentScriptMessagingApiTest() {
+    if (GetParam() == BindingsType::kNative) {
+      scoped_feature_list_.InitAndEnableFeature(
+          extensions_features::kNativeCrxBindings);
+    } else {
+      scoped_feature_list_.InitAndDisableFeature(
+          extensions_features::kNativeCrxBindings);
+    }
+  }
+
+  ~ContentScriptMessagingApiTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+}  // namespace
+
+// Verifies how the messaging API works with content scripts.
+IN_PROC_BROWSER_TEST_P(ContentScriptMessagingApiTest, Test) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+      "content_scripts/other_extensions/message_echoer_allows_by_default")));
+  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+      "content_scripts/other_extensions/message_echoer_allows")));
+  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+      "content_scripts/other_extensions/message_echoer_denies")));
+  ASSERT_TRUE(RunExtensionTest("content_scripts/messaging")) << message_;
+}
+
+INSTANTIATE_TEST_CASE_P(,
+                        ContentScriptMessagingApiTest,
+                        testing::Values(BindingsType::kNative,
+                                        BindingsType::kJavaScript));
+
 // Test fixture which sets a custom NTP Page.
 // TODO(karandeepb): Similar logic to set up a custom NTP is used elsewhere as
 // well. Abstract this away into a reusable test fixture class.
diff --git a/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc b/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc
index 7e7cd95..c2c2b7a 100644
--- a/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc
+++ b/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc
@@ -48,6 +48,7 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
 #include "services/identity/public/cpp/identity_manager.h"
+#include "services/identity/public/cpp/identity_test_utils.h"
 #include "services/identity/public/cpp/primary_account_mutator.h"
 #endif
 
@@ -182,12 +183,9 @@
 #else
     // Mock a signed-in user. This is used by the UserCloudPolicyStore to pass
     // the account id to the UserCloudPolicyValidator.
-    auto* primary_account_mutator =
-        IdentityManagerFactory::GetForProfile(browser()->profile())
-            ->GetPrimaryAccountMutator();
-    primary_account_mutator->LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-        "", "account_id", PolicyBuilder::kFakeUsername,
-        base::OnceCallback<void(const std::string&)>());
+    identity::SetPrimaryAccount(
+        IdentityManagerFactory::GetForProfile(browser()->profile()),
+        PolicyBuilder::kFakeUsername);
 
     UserCloudPolicyManager* policy_manager =
         UserCloudPolicyManagerFactory::GetForBrowserContext(
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc
index a2a8aa3..f23c4a7 100644
--- a/chrome/browser/profiles/profile.cc
+++ b/chrome/browser/profiles/profile.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/pref_names.h"
-#include "components/browser_sync/profile_sync_service.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_prefs.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/language/core/browser/pref_names.h"
@@ -24,6 +24,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/sync/base/sync_prefs.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/storage_partition.h"
@@ -280,8 +281,7 @@
   // No ProfileSyncService created yet - we don't want to create one, so just
   // infer the accessible state by looking at prefs/command line flags.
   syncer::SyncPrefs prefs(GetPrefs());
-  return browser_sync::ProfileSyncService::IsSyncAllowedByFlag() &&
-         !prefs.IsManaged();
+  return switches::IsSyncAllowedByFlag() && !prefs.IsManaged();
 }
 
 void Profile::MaybeSendDestroyedNotification() {
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index ba7b435..db1989f 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -122,6 +122,7 @@
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/cors_origin_pattern_setter.h"
 #include "content/public/browser/dom_storage_context.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/permission_controller.h"
@@ -227,6 +228,7 @@
 using base::TimeDelta;
 using bookmarks::BookmarkModel;
 using content::BrowserThread;
+using content::CorsOriginPatternSetter;
 using content::DownloadManagerDelegate;
 
 namespace {
@@ -325,52 +327,6 @@
 }
 #endif  // defined(OS_CHROMEOS)
 
-// A class used to make an asynchronous Mojo call with cloned patterns for each
-// StoragePartition iteration. |this| instance will be destructed when all
-// existing asynchronous Mojo calls made in SetLists() are done, and |closure|
-// will be invoked on destructing |this|.
-class CorsOriginPatternSetter
-    : public base::RefCounted<CorsOriginPatternSetter> {
- public:
-  CorsOriginPatternSetter(
-      const url::Origin& source_origin,
-      std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns,
-      std::vector<network::mojom::CorsOriginPatternPtr> block_patterns,
-      base::OnceClosure closure)
-      : source_origin_(source_origin),
-        allow_patterns_(std::move(allow_patterns)),
-        block_patterns_(std::move(block_patterns)),
-        closure_(std::move(closure)) {}
-
-  void SetLists(content::StoragePartition* partition) {
-    partition->GetNetworkContext()->SetCorsOriginAccessListsForOrigin(
-        source_origin_, ClonePatterns(allow_patterns_),
-        ClonePatterns(block_patterns_),
-        base::BindOnce([](scoped_refptr<CorsOriginPatternSetter> setter) {},
-                       base::RetainedRef(this)));
-  }
-
-  static std::vector<network::mojom::CorsOriginPatternPtr> ClonePatterns(
-      const std::vector<network::mojom::CorsOriginPatternPtr>& patterns) {
-    std::vector<network::mojom::CorsOriginPatternPtr> cloned_patterns;
-    cloned_patterns.reserve(patterns.size());
-    for (const auto& item : patterns)
-      cloned_patterns.push_back(item.Clone());
-    return cloned_patterns;
-  }
-
- private:
-  friend class base::RefCounted<CorsOriginPatternSetter>;
-
-  ~CorsOriginPatternSetter() { std::move(closure_).Run(); }
-
-  const url::Origin source_origin_;
-  const std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns_;
-  const std::vector<network::mojom::CorsOriginPatternPtr> block_patterns_;
-
-  base::OnceClosure closure_;
-};
-
 }  // namespace
 
 // static
diff --git a/chrome/browser/resources/app_management/router.js b/chrome/browser/resources/app_management/router.js
index 88fb59b..06532b0 100644
--- a/chrome/browser/resources/app_management/router.js
+++ b/chrome/browser/resources/app_management/router.js
@@ -45,6 +45,8 @@
     },
   },
 
+  urlParsed_: false,
+
   observers: [
     'onUrlChanged_(path_, queryParams_)',
     'onStateChanged_(currentPageType_, selectedAppId_, searchTerm_)',
@@ -81,6 +83,9 @@
 
   /** @private */
   onStateChanged_: function() {
+    if (!this.urlParsed_) {
+      return;
+    }
     this.debounce('publishUrl', this.publishUrl_);
   },
 
@@ -149,5 +154,6 @@
     } else {
       this.dispatch(app_management.actions.changePage(newPage));
     }
+    this.urlParsed_ = true;
   },
 });
diff --git a/chrome/browser/resources/chromeos/camera/src/css/main.css b/chrome/browser/resources/chromeos/camera/src/css/main.css
index 6e9318d..38217d7 100644
--- a/chrome/browser/resources/chromeos/camera/src/css/main.css
+++ b/chrome/browser/resources/chromeos/camera/src/css/main.css
@@ -83,8 +83,8 @@
   -webkit-appearance: none;
 }
 
-button:focus:after,
-input:focus:after {
+button:focus::after,
+input:focus::after {
   border: 2px solid rgba(37, 129, 223, 0.7);
   border-radius: 4px;
   bottom: -3px;
@@ -101,8 +101,8 @@
 }
 
 .circle button,
-.circle button:focus:after,
-.circle input:focus:after {
+.circle button:focus::after,
+.circle input:focus::after {
   border-radius: 50%;
 }
 
@@ -558,7 +558,7 @@
   transform: translateY(-50%);
 }
 
-body._4x4 #preview-grid-horizontal:before {
+body._4x4 #preview-grid-horizontal::before {
   border-bottom: 1px solid white;
   content: '';
   height: 0;
@@ -579,9 +579,9 @@
 }
 
 body.grid._4x4 #preview-grid-horizontal,
-body.grid._4x4 #preview-grid-horizontal:before,
+body.grid._4x4 #preview-grid-horizontal::before,
 body.gridsettings._4x4 #preview-grid-horizontal,
-body.gridsettings._4x4 #preview-grid-horizontal:before {
+body.gridsettings._4x4 #preview-grid-horizontal::before {
   height: 50%;
 }
 
@@ -596,7 +596,7 @@
   width: 100%;
 }
 
-body._4x4 #preview-grid-vertical:before {
+body._4x4 #preview-grid-vertical::before {
   border-right: 1px solid white;
   bottom: 0;
   content: '';
@@ -617,26 +617,26 @@
 }
 
 body.grid._4x4 #preview-grid-vertical,
-body.grid._4x4 #preview-grid-vertical:before,
+body.grid._4x4 #preview-grid-vertical::before,
 body.gridsettings._4x4 #preview-grid-vertical,
-body.gridsettings._4x4 #preview-grid-vertical:before {
+body.gridsettings._4x4 #preview-grid-vertical::before {
   width: 50%;
 }
 
 #preview-grid-horizontal.animate,
-#preview-grid-horizontal.animate:before {
+#preview-grid-horizontal.animate::before {
   transition: height 500ms, visibility 500ms;
 }
 
 #preview-grid-vertical.animate,
-#preview-grid-vertical.animate:before {
+#preview-grid-vertical.animate::before {
   transition: width 500ms, visibility 500ms;
 }
 
 body:not(.grid):not(.gridsettings) #preview-grid-horizontal,
-body:not(.grid):not(.gridsettings) #preview-grid-horizontal:before,
+body:not(.grid):not(.gridsettings) #preview-grid-horizontal::before,
 body:not(.grid):not(.gridsettings) #preview-grid-vertical,
-body:not(.grid):not(.gridsettings) #preview-grid-vertical:before {
+body:not(.grid):not(.gridsettings) #preview-grid-vertical::before {
   visibility: hidden;
 }
 
@@ -657,7 +657,7 @@
 }
 
 #record-time .icon {
-  background-color: #ea4335;
+  background-color: rgb(234, 67, 53);
   border-radius: 50%;
   flex-shrink: 0;
   height: 6px;
@@ -776,7 +776,7 @@
 #tooltip {
   background: rgba(241, 243, 244, 0.8);
   border-radius: 2px;
-  color: #202124;
+  color: rgb(32, 33, 36);
   font-family: 'Roboto', sans-serif;
   font-size: 12px;
   left: 0;
@@ -796,7 +796,7 @@
 }
 
 #toast {
-  background: #1e1e23;
+  background: rgb(30, 30, 35);
   border-radius: 16px;
   color: white;
   font-family: 'Roboto', sans-serif;
@@ -817,7 +817,8 @@
 }
 
 @keyframes toast-spoken {
-  0%, 100% {
+  0%,
+  100% {
     opacity: 0;
   }
 }
@@ -826,7 +827,8 @@
   0% {
     opacity: 0;
   }
-  10%, 90% {
+  10%,
+  90% {
     opacity: 0.9;
   }
   100% {
@@ -839,17 +841,17 @@
   display: flex;
   flex-direction: column;
   height: 100%;
-  left: 0px;
+  left: 0;
   min-width: 360px;
   opacity: 0.9;
   position: absolute;
-  top: 0px;
+  top: 0;
 }
 
 .menu-header,
 .menu-item {
   align-items: center;
-  color: #f1f3f4;
+  color: rgb(241, 243, 244);
   display: flex;
   flex-shrink: 0;
   font-family: 'Roboto', sans-serif;
@@ -876,7 +878,7 @@
 }
 
 .menu-item .description {
-  color: #bdc1c6;
+  color: rgb(189, 193, 198);
   margin-top: 5px;
 }
 
@@ -892,10 +894,10 @@
   display: inline;
 }
 
-.menu-item input:before {
+.menu-item input::before {
   border-radius: 50%;
   bottom: 13px;
-  box-shadow: 0px 0px 0px 2px #f1f3f4;
+  box-shadow: 0 0 0 2px rgb(241, 243, 244);
   content: '';
   left: 13px;
   position: absolute;
@@ -903,19 +905,19 @@
   top: 13px;
 }
 
-.menu-item input:checked:before {
+.menu-item input:checked::before {
   background-clip: padding-box;
-  background-color: #f1f3f4;
+  background-color: rgb(241, 243, 244);
   border: 4px solid transparent;
   bottom: 12px;
-  box-shadow: 0px 0px 0px 1px #f1f3f4;
+  box-shadow: 0 0 0 1px rgb(241, 243, 244);
   left: 12px;
   right: 12px;
   top: 12px;
   transition: border-width 100ms ease-in;
 }
 
-.menu-item:focus:after {
+.menu-item:focus::after {
   left: 2px;
   right: 2px;
 }
@@ -985,7 +987,7 @@
 }
 
 #dialog #dialog-msg {
-  color: #202124;
+  color: rgb(32, 33, 36);
   cursor: text;
   font-family: 'Roboto', sans-serif;
   font-size: 14px;
@@ -1022,7 +1024,7 @@
 #dialog-buttons button {
   background-color: white;
   border-style: solid;
-  color: #2581df;
+  color: rgb(37, 129, 223);
   font-family: 'Roboto', sans-serif;
   font-size: 12px;
   margin: 4px;
@@ -1030,12 +1032,12 @@
 }
 
 #dialog-buttons button:focus {
-  background-color: #2581df;
-  border-color: #2581df;
+  background-color: rgb(37, 129, 223);
+  border-color: rgb(37, 129, 223);
   color: white;
 }
 
-#dialog-buttons button:focus:after {
+#dialog-buttons button:focus::after {
   border: none;
 }
 
diff --git a/chrome/browser/resources/print_preview/new/pages_settings.js b/chrome/browser/resources/print_preview/new/pages_settings.js
index 245d22e..c2d4bfd 100644
--- a/chrome/browser/resources/print_preview/new/pages_settings.js
+++ b/chrome/browser/resources/print_preview/new/pages_settings.js
@@ -113,8 +113,13 @@
     'input-change': 'onInputChange_',
   },
 
-  /** @private {string} */
-  lastInput_: '',
+  /**
+   * True if the user's last valid input should be restored to the custom input
+   * field. Cleared when the input is set automatically, or the user manually
+   * clears the field.
+   * @private {boolean}
+   */
+  restoreLastInput_: true,
 
   /**
    * Initialize |selectedValue| in attached() since this doesn't observe
@@ -135,6 +140,9 @@
    * @private
    */
   onInputChange_: function(e) {
+    if (this.inputString_ !== e.detail) {
+      this.restoreLastInput_ = true;
+    }
     this.inputString_ = e.detail;
   },
 
@@ -253,7 +261,6 @@
         }
       }
     }
-    this.lastInput_ = this.inputString_;
     this.errorState_ = PagesInputErrorState.NO_ERROR;
     this.pagesToPrint_ = pages;
   },
@@ -358,7 +365,11 @@
   onCustomInputBlur_: function() {
     this.resetAndUpdate();
     if (this.errorState_ === PagesInputErrorState.EMPTY) {
-      this.$$('cr-input').value = this.lastInput_;
+      // Update with all pages.
+      this.$$('cr-input').value = this.getAllPagesString_();
+      this.inputString_ = this.getAllPagesString_();
+      this.resetString();
+      this.restoreLastInput_ = false;
     }
   },
 
@@ -411,13 +422,22 @@
     return !this.customSelected_ || this.controlsDisabled_;
   },
 
+  /**
+   * @return {string} A string representing the full page range.
+   * @private
+   */
+  getAllPagesString_: function() {
+    return this.pageCount === 1 ? '1' : `1-${this.pageCount}`;
+  },
+
   /** @private */
   onCustomSelectedChange_: function() {
-    if (!this.customSelected_ &&
-        (this.errorState_ == PagesInputErrorState.INVALID_SYNTAX ||
-         this.errorState_ == PagesInputErrorState.OUT_OF_BOUNDS)) {
-      this.$$('cr-input').value = '';
+    if ((this.customSelected_ && !this.restoreLastInput_) ||
+        this.errorState_ !== PagesInputErrorState.NO_ERROR) {
+      this.restoreLastInput_ = true;
       this.inputString_ = '';
+      this.$$('cr-input').value = '';
+      this.resetString();
     }
     this.updatePagesToPrint_();
   }
diff --git a/chrome/browser/resources/print_preview/new/scaling_settings.html b/chrome/browser/resources/print_preview/new/scaling_settings.html
index 3d1b792..58c12da 100644
--- a/chrome/browser/resources/print_preview/new/scaling_settings.html
+++ b/chrome/browser/resources/print_preview/new/scaling_settings.html
@@ -30,7 +30,8 @@
         </select>
       </div>
     </print-preview-settings-section>
-    <iron-collapse opened="[[customSelected_]]">
+    <iron-collapse opened="[[customSelected_]]"
+        on-transitionend="onCollapseChanged_">
       <print-preview-number-settings-section
           max-value="200" min-value="10" default-value="100"
           disabled$="[[inputDisabled_(disabled, inputValid_, customSelected_)]]"
diff --git a/chrome/browser/resources/print_preview/new/scaling_settings.js b/chrome/browser/resources/print_preview/new/scaling_settings.js
index a891b32..4d2e396 100644
--- a/chrome/browser/resources/print_preview/new/scaling_settings.js
+++ b/chrome/browser/resources/print_preview/new/scaling_settings.js
@@ -162,4 +162,11 @@
   computeCustomSelected_: function() {
     return this.selectedValue === ScalingValue.CUSTOM.toString();
   },
+
+  /** @private */
+  onCollapseChanged_: function() {
+    if (this.customSelected_) {
+      this.$$('print-preview-number-settings-section').getInput().focus();
+    }
+  },
 });
diff --git a/chrome/browser/resources/usb_internals/BUILD.gn b/chrome/browser/resources/usb_internals/BUILD.gn
new file mode 100644
index 0000000..763a0fa
--- /dev/null
+++ b/chrome/browser/resources/usb_internals/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_type_check("closure_compile") {
+  deps = [
+    ":usb_internals",
+  ]
+}
+
+js_library("usb_internals") {
+  sources = [
+    "usb_internals.js",
+  ]
+
+  deps = [
+    "//chrome/browser/ui/webui/usb_internals:mojo_bindings_js_externs",
+    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:util",
+    "//ui/webui/resources/js/cr/ui:tabs",
+  ]
+}
diff --git a/chrome/browser/resources/usb_internals/usb_internals.css b/chrome/browser/resources/usb_internals/usb_internals.css
index c11885b..af12290b 100644
--- a/chrome/browser/resources/usb_internals/usb_internals.css
+++ b/chrome/browser/resources/usb_internals/usb_internals.css
@@ -3,6 +3,7 @@
  * found in the LICENSE file.
  */
 
+/* Devices Tab */
 table.styled-table {
   border-collapse: collapse;
 }
@@ -11,12 +12,12 @@
 .styled-table th,
 .styled-table td {
   border: 1px solid #777;
-  padding-left: 4px;
-  padding-right: 4px;
+  padding-inline-end: 4px;
+  padding-inline-start: 4px;
 }
 
 .styled-table th {
-  text-align: left;
+  text-align: start;
 }
 
 .page-section {
diff --git a/chrome/browser/resources/usb_internals/usb_internals.html b/chrome/browser/resources/usb_internals/usb_internals.html
index 2bcc483..1c84c37 100644
--- a/chrome/browser/resources/usb_internals/usb_internals.html
+++ b/chrome/browser/resources/usb_internals/usb_internals.html
@@ -1,55 +1,88 @@
 <!doctype html>
 <html lang="en">
+
 <head>
   <meta charset="utf-8">
   <title>USB Internals</title>
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+  <link rel="stylesheet" href="chrome://resources/css/tabs.css">
   <link rel="stylesheet" href="usb_internals.css">
+
+  <script src="chrome://resources/js/assert.js"></script>
   <script src="chrome://resources/js/cr.js"></script>
+  <script src="chrome://resources/js/promise_resolver.js"></script>
+  <script src="chrome://resources/js/cr/ui.js"></script>
+  <script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
+  <script src="chrome://resources/js/cr/ui/tabs.js"></script>
   <script src="chrome://resources/js/mojo_bindings.js"></script>
   <script src="chrome://resources/js/util.js"></script>
   <script src="device/usb/public/mojom/device_manager_test.mojom.js"></script>
   <script src="chrome/browser/ui/webui/usb_internals/usb_internals.mojom.js">
   </script>
-  <script src="usb_internals.js"></script>
 </head>
-<body>
-  <p>
-    <table class="styled-table">
-      <thead>
-        <tr>
-          <th>Name</th>
-          <th>Serial number</th>
-          <th>Landing page</th>
-          <th>
-        </tr>
-      </thead>
-      <tbody id="test-device-list">
-      </tbody>
-    </table>
-  </p>
 
-  <div class="page-section">
-    <strong>Add a test device:</strong>
-    <form id="add-test-device-form" action="">
-      <p>
-        <label>
-          Name: <input id="test-device-name" type="text" size="40">
-        </label>
-      </p>
-      <p>
-        <label>
-          Serial number: <input id="test-device-serial" type="text" size="40">
-        </label>
-      </p>
-      <p>
-        <label>
-          Landing page:
-          <input id="test-device-landing-page" type="text" size="40">
-        </label>
-      </p>
-      <button type="submit">Add</button> <span id="add-test-device-result"></span>
-    </form>
-  </div>
+<body>
+  <tabbox>
+    <tabs>
+      <tab>Test Devices</tab>
+    </tabs>
+    <tabpanels>
+      <tabpanel>
+        <!-- Test Devices -->
+        <h2>Test Devices</h2>
+        <p>
+          <table class="styled-table">
+            <thead>
+              <tr>
+                <th>Name</th>
+                <th>Serial number</th>
+                <th>Landing page</th>
+                <th>
+              </tr>
+            </thead>
+            <tbody id="test-device-list">
+            </tbody>
+
+            <template id="test-device-row">
+              <tr>
+                <td></td>
+                <td></td>
+                <td></td>
+                <td><button>Remove</button></td>
+              </tr>
+            </template>
+
+          </table>
+        </p>
+        <div class="page-section">
+          <strong>Add a test device:</strong>
+          <form id="add-test-device-form" action="">
+            <p>
+              <label>
+                Name: <input id="test-device-name" type="text" size="40">
+              </label>
+            </p>
+            <p>
+              <label>
+                Serial number:
+                <input id="test-device-serial" type="text" size="40">
+              </label>
+            </p>
+            <p>
+              <label>
+                Landing page:
+                <input id="test-device-landing-page" type="text" size="40">
+              </label>
+            </p>
+            <button type="submit">Add</button>
+            <span id="add-test-device-result"></span>
+          </form>
+        </div>
+      </tabpanel>
+    </tabpanels>
+  </tabbox>
+
+  <script src="usb_internals.js"></script>
 </body>
-</html>
+
+</html>
\ No newline at end of file
diff --git a/chrome/browser/resources/usb_internals/usb_internals.js b/chrome/browser/resources/usb_internals/usb_internals.js
index b9ea066..67e63a8 100644
--- a/chrome/browser/resources/usb_internals/usb_internals.js
+++ b/chrome/browser/resources/usb_internals/usb_internals.js
@@ -5,67 +5,75 @@
 /**
  * Javascript for usb_internals.html, served from chrome://usb-internals/.
  */
+cr.define('usb_internals', function() {
+  class UsbInternals {
+    constructor() {
+      // Connection to the UsbInternalsPageHandler instance running in the
+      // browser process.
+      this.usbManagerTest = null;
 
-(function() {
-// Connection to the UsbInternalsPageHandler instance running in the browser
-// process.
-let usbManagerTest = null;
+      const pageHandler = new mojom.UsbInternalsPageHandlerPtr;
+      Mojo.bindInterface(
+          mojom.UsbInternalsPageHandler.name,
+          mojo.makeRequest(pageHandler).handle);
 
-function refreshDeviceList() {
-  usbManagerTest.getTestDevices().then(function(response) {
-    const tableBody = $('test-device-list');
-    tableBody.innerHTML = '';
-    for (const device of response.devices) {
-      const row = document.createElement('tr');
-      const name = document.createElement('td');
-      const serialNumber = document.createElement('td');
-      const landingPage = document.createElement('td');
-      const remove = document.createElement('td');
-      const removeButton = document.createElement('button');
-      name.textContent = device.name;
-      serialNumber.textContent = device.serialNumber;
-      landingPage.textContent = device.landingPage.url;
-      removeButton.addEventListener('click', async function() {
-        await usbManagerTest.removeDeviceForTesting(device.guid);
-        refreshDeviceList();
+      this.usbManagerTest = new device.mojom.UsbDeviceManagerTestPtr;
+      pageHandler.bindTestInterface(mojo.makeRequest(this.usbManagerTest));
+
+      cr.ui.decorate('tabbox', cr.ui.TabBox);
+      $('add-test-device-form').addEventListener('submit', (event) => {
+        this.addTestDevice(event);
       });
-      removeButton.textContent = 'Remove';
-      row.appendChild(name);
-      row.appendChild(serialNumber);
-      row.appendChild(landingPage);
-      remove.appendChild(removeButton);
-      row.appendChild(remove);
-      tableBody.appendChild(row);
+      this.refreshDeviceList();
     }
-  });
-}
 
-function addTestDevice(event) {
-  usbManagerTest
-      .addDeviceForTesting(
+    async refreshDeviceList() {
+      const response = await this.usbManagerTest.getTestDevices();
+
+      const tableBody = $('test-device-list');
+      tableBody.innerHTML = '';
+
+      const rowTemplate = document.querySelector('#test-device-row');
+      const td = rowTemplate.content.querySelectorAll('td');
+
+      for (const device of response.devices) {
+        td[0].textContent = device.name;
+        td[1].textContent = device.serialNumber;
+        td[2].textContent = device.landingPage.url;
+
+        const clone = document.importNode(rowTemplate.content, true);
+
+        const removeButton = clone.querySelector('button');
+        removeButton.addEventListener('click', async () => {
+          await this.usbManagerTest.removeDeviceForTesting(device.guid);
+          this.refreshDeviceList();
+        });
+
+        tableBody.appendChild(clone);
+      }
+    }
+
+    async addTestDevice(event) {
+      event.preventDefault();
+
+      const response = await this.usbManagerTest.addDeviceForTesting(
           $('test-device-name').value, $('test-device-serial').value,
-          $('test-device-landing-page').value)
-      .then(function(response) {
-        if (response.success) {
-          refreshDeviceList();
-        }
+          $('test-device-landing-page').value);
+      if (response.success) {
+        this.refreshDeviceList();
+      }
 
-        $('add-test-device-result').textContent = response.message;
-        $('add-test-device-result').className =
-            response.success ? 'action-success' : 'action-failure';
-      });
-  event.preventDefault();
-}
+      $('add-test-device-result').textContent = response.message;
+      $('add-test-device-result').className =
+          response.success ? 'action-success' : 'action-failure';
+    }
+  }
 
-document.addEventListener('DOMContentLoaded', function() {
-  const pageHandler = new mojom.UsbInternalsPageHandlerPtr;
-  Mojo.bindInterface(
-      mojom.UsbInternalsPageHandler.name, mojo.makeRequest(pageHandler).handle);
-
-  usbManagerTest = new device.mojom.UsbDeviceManagerTestPtr;
-  pageHandler.bindTestInterface(mojo.makeRequest(usbManagerTest));
-
-  $('add-test-device-form').addEventListener('submit', addTestDevice);
-  refreshDeviceList();
+  return {
+    UsbInternals,
+  };
 });
-})();
+
+document.addEventListener('DOMContentLoaded', () => {
+  new usb_internals.UsbInternals();
+});
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/art.jpg b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/art.jpg
new file mode 100644
index 0000000..2514b17
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/art.jpg
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/cityscape.jpg b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/cityscape.jpg
new file mode 100644
index 0000000..4c36cf1
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/cityscape.jpg
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/geometric_shapes.jpg b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/geometric_shapes.jpg
new file mode 100644
index 0000000..6092436
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/geometric_shapes.jpg
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/landscape.jpg b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/landscape.jpg
new file mode 100644
index 0000000..340ddff
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/landscape.jpg
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/life.jpg b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/life.jpg
new file mode 100644
index 0000000..58c9aa4
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/images/ntp_thumbnails/life.jpg
Binary files differ
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
index 6df82b4..250fb51 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
@@ -7,6 +7,7 @@
    * @typedef {{
    *   id: number,
    *   imageUrl: string,
+   *   thumbnailClass: string,
    *   title: string,
    * }}
    */
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
index 5a136ea..8bea421 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
@@ -69,6 +69,26 @@
         flex: 1;
       }
 
+      .art {
+        background-image: url(../images/ntp_thumbnails/art.jpg);
+      }
+
+      .cityscape {
+        background-image: url(../images/ntp_thumbnails/cityscape.jpg);
+      }
+
+      .geometric-shapes {
+        background-image: url(../images/ntp_thumbnails/geometric_shapes.jpg);
+      }
+
+      .landscape {
+        background-image: url(../images/ntp_thumbnails/landscape.jpg);
+      }
+
+      .life {
+        background-image: url(../images/ntp_thumbnails/life.jpg);
+      }
+
       .ntp-background-title {
         border-top: var(--cr-separator-line);
         font-size: 14px;
@@ -109,7 +129,7 @@
             on-click="onBackgroundClick_"
             on-keyup="onBackgroundKeyUp_"
             on-pointerdown="onBackgroundPointerDown_">
-          <div class="ntp-background-thumbnail"></div>
+          <div class$="ntp-background-thumbnail [[item.thumbnailClass]]"></div>
           <div class="ntp-background-title">[[item.title]]</div>
         </button>
       </template>
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
index c2b39ae..75579b8 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
@@ -35,6 +35,7 @@
     const defaultBackground = {
       id: -1,
       imageUrl: '',
+      thumbnailClass: '',
       title: this.i18n('ntpBackgroundDefault'),
     };
     this.selectedBackground_ = defaultBackground;
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd b/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd
index 0ba3e7f..55b4f6a 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd
+++ b/chrome/browser/resources/welcome/onboarding_welcome/onboarding_welcome_resources.grd
@@ -38,6 +38,11 @@
       <include name="IDR_NUX_GOOGLE_APPS_YOUTUBE_2X" file="images\youtube_2x.png" type="BINDATA" />
       <include name="IDR_NUX_NTP_BACKGROUND_LOGO_1X" file="images\ntp_background_1x.png" type="BINDATA" />
       <include name="IDR_NUX_NTP_BACKGROUND_LOGO_2X" file="images\ntp_background_2x.png" type="BINDATA" />
+      <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_ART" file="images\ntp_thumbnails\art.jpg" type="BINDATA" />
+      <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_CITYSCAPE" file="images\ntp_thumbnails\cityscape.jpg" type="BINDATA" />
+      <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_GEOMETRIC_SHAPES" file="images\ntp_thumbnails\geometric_shapes.jpg" type="BINDATA" />
+      <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_LANDSCAPE" file="images\ntp_thumbnails\landscape.jpg" type="BINDATA" />
+      <include name="IDR_NUX_NTP_BACKGROUND_THUMBNAIL_LIFE" file="images\ntp_thumbnails\life.jpg" type="BINDATA" />
       <include name="IDR_NUX_SET_AS_DEFAULT_ILLUSTRATION_1X" file="images\set_as_default_illustration_1x.png" type="BINDATA" />
       <include name="IDR_NUX_SET_AS_DEFAULT_ILLUSTRATION_2X" file="images\set_as_default_illustration_2x.png" type="BINDATA" />
       <include name="IDR_NUX_SET_AS_DEFAULT_LOGO_1X" file="images\set_as_default_1x.png" type="BINDATA" />
diff --git a/chrome/browser/signin/chrome_signin_client.cc b/chrome/browser/signin/chrome_signin_client.cc
index b8763a3..22ee0f5 100644
--- a/chrome/browser/signin/chrome_signin_client.cc
+++ b/chrome/browser/signin/chrome_signin_client.cc
@@ -338,15 +338,6 @@
 #endif
 }
 
-void ChromeSigninClient::AfterCredentialsCopied() {
-  if (signin_util::IsForceSigninEnabled()) {
-    // The signout after credential copy won't open UserManager after all
-    // browser window are closed. Because the browser window will be opened for
-    // the new profile soon.
-    should_display_user_manager_ = false;
-  }
-}
-
 void ChromeSigninClient::SetReadyForDiceMigration(bool is_ready) {
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
   AccountConsistencyModeManager::GetForProfile(profile_)
diff --git a/chrome/browser/signin/chrome_signin_client.h b/chrome/browser/signin/chrome_signin_client.h
index 4080fb0..bf666eb 100644
--- a/chrome/browser/signin/chrome_signin_client.h
+++ b/chrome/browser/signin/chrome_signin_client.h
@@ -82,7 +82,6 @@
   void OnConnectionChanged(network::mojom::ConnectionType type) override;
 #endif
 
-  void AfterCredentialsCopied() override;
   void SetReadyForDiceMigration(bool is_ready) override;
 
   // Used in tests to override the URLLoaderFactory returned by
diff --git a/chrome/browser/signin/chrome_signin_client_unittest.cc b/chrome/browser/signin/chrome_signin_client_unittest.cc
index 4110a6c..c9b0da4 100644
--- a/chrome/browser/signin/chrome_signin_client_unittest.cc
+++ b/chrome/browser/signin/chrome_signin_client_unittest.cc
@@ -190,42 +190,6 @@
   PreSignOut(source_metric, delete_metric);
 }
 
-TEST_F(ChromeSigninClientSignoutTest, SignOutWithoutManager) {
-  signin_metrics::ProfileSignout source_metric =
-      signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS;
-  signin_metrics::SignoutDelete delete_metric =
-      signin_metrics::SignoutDelete::IGNORE_METRIC;
-
-  // Call the method below instead calling SigninManager::CopyCredentialsFrom,
-  // keeping the same behavior.
-  client_->AfterCredentialsCopied();
-
-  EXPECT_CALL(*client_, ShowUserManager(browser()->profile()->GetPath()))
-      .Times(0);
-  EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
-      .Times(1);
-  EXPECT_CALL(
-      *client_,
-      SignOutCallback(source_metric, delete_metric,
-                      SigninClient::SignoutDecision::ALLOW_SIGNOUT))
-      .Times(1);
-
-  PreSignOut(source_metric, delete_metric);
-
-  ::testing::Mock::VerifyAndClearExpectations(client_.get());
-
-  EXPECT_CALL(*client_, ShowUserManager(browser()->profile()->GetPath()))
-      .Times(1);
-  EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
-      .Times(1);
-  EXPECT_CALL(
-      *client_,
-      SignOutCallback(source_metric, delete_metric,
-                      SigninClient::SignoutDecision::ALLOW_SIGNOUT))
-      .Times(1);
-  PreSignOut(source_metric, delete_metric);
-}
-
 TEST_F(ChromeSigninClientSignoutTest, SignOutWithoutForceSignin) {
   signin_util::SetForceSigninForTesting(false);
   CreateClient(browser()->profile());
diff --git a/chrome/browser/signin/dice_browsertest.cc b/chrome/browser/signin/dice_browsertest.cc
index 6e0128e..85c8f2e 100644
--- a/chrome/browser/signin/dice_browsertest.cc
+++ b/chrome/browser/signin/dice_browsertest.cc
@@ -659,10 +659,6 @@
       GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
   // Sync should not be enabled.
   EXPECT_TRUE(GetIdentityManager()->GetPrimaryAccountId().empty());
-  EXPECT_TRUE(GetIdentityManager()
-                  ->GetPrimaryAccountMutator()
-                  ->LegacyPrimaryAccountForAuthInProgress()
-                  .account_id.empty());
 
   EXPECT_EQ(1, reconcilor_blocked_count_);
   WaitForReconcilorUnblockedCount(1);
@@ -906,10 +902,6 @@
       browser()->profile()));
 
   EXPECT_FALSE(GetIdentityManager()->GetPrimaryAccountId().empty());
-  EXPECT_TRUE(GetIdentityManager()
-                  ->GetPrimaryAccountMutator()
-                  ->LegacyPrimaryAccountForAuthInProgress()
-                  .account_id.empty());
   EXPECT_TRUE(
       GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
   EXPECT_FALSE(GetIdentityManager()->GetAccountsWithRefreshTokens().empty());
@@ -929,10 +921,6 @@
       browser()->profile()));
 
   EXPECT_TRUE(GetIdentityManager()->GetPrimaryAccountId().empty());
-  EXPECT_TRUE(GetIdentityManager()
-                  ->GetPrimaryAccountMutator()
-                  ->LegacyPrimaryAccountForAuthInProgress()
-                  .account_id.empty());
   EXPECT_FALSE(
       GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
   EXPECT_TRUE(GetIdentityManager()->GetAccountsWithRefreshTokens().empty());
diff --git a/chrome/browser/signin/force_signin_verifier.cc b/chrome/browser/signin/force_signin_verifier.cc
index 5cc5185..971e5aa 100644
--- a/chrome/browser/signin/force_signin_verifier.cc
+++ b/chrome/browser/signin/force_signin_verifier.cc
@@ -142,8 +142,7 @@
   // Do not close window if there is ongoing reauth. If it fails later, the
   // signin process should take care of the signout.
   auto* primary_account_mutator = identity_manager_->GetPrimaryAccountMutator();
-  if (!primary_account_mutator ||
-      primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress())
+  if (!primary_account_mutator)
     return;
   primary_account_mutator->ClearPrimaryAccount(
       identity::PrimaryAccountMutator::ClearAccountsAction::kRemoveAll,
diff --git a/chrome/browser/signin/signin_promo_util.cc b/chrome/browser/signin/signin_promo_util.cc
index 7013b71..745aae4 100644
--- a/chrome/browser/signin/signin_promo_util.cc
+++ b/chrome/browser/signin/signin_promo_util.cc
@@ -41,13 +41,7 @@
   // Display the signin promo if the user is not signed in.
   identity::IdentityManager* identity_manager =
       IdentityManagerFactory::GetForProfile(original_profile);
-  if (identity_manager->HasPrimaryAccount() ||
-      identity_manager->GetPrimaryAccountMutator()
-          ->LegacyIsPrimaryAccountAuthInProgress()) {
-    return false;
-  }
-
-  return true;
+  return !identity_manager->HasPrimaryAccount();
 #endif
 }
 
diff --git a/chrome/browser/sync/profile_sync_service_factory.cc b/chrome/browser/sync/profile_sync_service_factory.cc
index 7ac80d4..444e0f3 100644
--- a/chrome/browser/sync/profile_sync_service_factory.cc
+++ b/chrome/browser/sync/profile_sync_service_factory.cc
@@ -41,6 +41,7 @@
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
 #include "chrome/browser/web_data_service_factory.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/browser_sync/profile_sync_components_factory_impl.h"
 #include "components/browser_sync/profile_sync_service.h"
 #include "components/invalidation/impl/invalidation_switches.h"
@@ -115,7 +116,7 @@
 // static
 syncer::SyncService* ProfileSyncServiceFactory::GetSyncServiceForProfile(
     Profile* profile) {
-  if (!ProfileSyncService::IsSyncAllowedByFlag()) {
+  if (!switches::IsSyncAllowedByFlag()) {
     return nullptr;
   }
 
diff --git a/chrome/browser/sync/sync_ui_util.cc b/chrome/browser/sync/sync_ui_util.cc
index 87322ea..38f673d 100644
--- a/chrome/browser/sync/sync_ui_util.cc
+++ b/chrome/browser/sync/sync_ui_util.cc
@@ -20,7 +20,6 @@
 #include "components/sync/protocol/sync_protocol_error.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "services/identity/public/cpp/identity_manager.h"
-#include "services/identity/public/cpp/primary_account_mutator.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace sync_ui_util {
@@ -138,13 +137,14 @@
 }
 
 // status_label and link_label must either be both null or both non-null.
-MessageType GetStatusLabelsImpl(const syncer::SyncService* service,
-                                identity::IdentityManager* identity_manager,
-                                const bool is_user_signout_allowed,
-                                const GoogleServiceAuthError& auth_error,
-                                base::string16* status_label,
-                                base::string16* link_label,
-                                ActionType* action_type) {
+MessageType GetStatusLabelsImpl(
+    const syncer::SyncService* service,
+    const identity::IdentityManager* identity_manager,
+    bool is_user_signout_allowed,
+    const GoogleServiceAuthError& auth_error,
+    base::string16* status_label,
+    base::string16* link_label,
+    ActionType* action_type) {
   DCHECK(service);
   DCHECK_EQ(status_label == nullptr, link_label == nullptr);
 
@@ -152,10 +152,6 @@
     return PRE_SYNCED;
   }
 
-  // Needed to check the state of the authentication process below.
-  const auto* primary_account_mutator =
-      identity_manager->GetPrimaryAccountMutator();
-
   syncer::SyncStatus status;
   service->QueryDetailedSyncStatus(&status);
 
@@ -176,16 +172,6 @@
       return SYNC_ERROR;
     }
 
-    // For auth errors first check if an auth is in progress.
-    if (primary_account_mutator &&
-        primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress()) {
-      if (status_label) {
-        *status_label =
-            l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL);
-      }
-      return PRE_SYNCED;
-    }
-
     // Since there is no auth in progress, check for an auth error first.
     if (auth_error.state() != GoogleServiceAuthError::NONE) {
       if (status_label && link_label) {
@@ -241,14 +227,8 @@
       *status_label = l10n_util::GetStringUTF16(IDS_SYNC_NTP_SETUP_IN_PROGRESS);
     }
 
-    if (primary_account_mutator &&
-        primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress()) {
-      if (status_label) {
-        *status_label =
-            l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL);
-      }
-    } else if (auth_error.state() != GoogleServiceAuthError::NONE &&
-               auth_error.state() != GoogleServiceAuthError::TWO_FACTOR) {
+    if (auth_error.state() != GoogleServiceAuthError::NONE &&
+        auth_error.state() != GoogleServiceAuthError::TWO_FACTOR) {
       if (status_label && link_label) {
         GetStatusForAuthError(auth_error, status_label, link_label,
                               action_type);
diff --git a/chrome/browser/sync/sync_ui_util_unittest.cc b/chrome/browser/sync/sync_ui_util_unittest.cc
index d073398..4d72e7b 100644
--- a/chrome/browser/sync/sync_ui_util_unittest.cc
+++ b/chrome/browser/sync/sync_ui_util_unittest.cc
@@ -22,7 +22,6 @@
 #include "services/identity/public/cpp/identity_manager.h"
 #include "services/identity/public/cpp/identity_test_environment.h"
 #include "services/identity/public/cpp/identity_test_utils.h"
-#include "services/identity/public/cpp/primary_account_mutator.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -34,7 +33,6 @@
 enum DistinctState {
   STATUS_CASE_SETUP_IN_PROGRESS,
   STATUS_CASE_SETUP_ERROR,
-  STATUS_CASE_AUTHENTICATING,
   STATUS_CASE_AUTH_ERROR,
   STATUS_CASE_PROTOCOL_ERROR,
   STATUS_CASE_PASSPHRASE_ERROR,
@@ -44,9 +42,7 @@
   NUMBER_OF_STATUS_CASES
 };
 
-const char kTestGaiaId[] = "gaia-id-test_user@test.com";
 const char kTestUser[] = "test_user@test.com";
-const char kRefreshToken[] = "refresh_token";
 
 }  // namespace
 
@@ -78,30 +74,6 @@
       service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
       return;
     }
-    case STATUS_CASE_AUTHENTICATING: {
-      service->SetFirstSetupComplete(true);
-      service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
-      service->SetPassphraseRequired(false);
-      service->SetDisableReasons(syncer::SyncService::DISABLE_REASON_NONE);
-      service->SetDetailedSyncStatus(false, syncer::SyncEngine::Status());
-
-      // This case will be run in platforms supporting mutation of the primary
-      // account (i.e., not ChromeOS) only, so we can assume mutator != nullptr.
-      auto* primary_account_mutator =
-          identity_manager->GetPrimaryAccountMutator();
-      DCHECK(primary_account_mutator);
-
-      // Starting the auth process and not completing it will make the mutator
-      // report "auth in progress" when checked from the test later on.
-      primary_account_mutator
-          ->LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-              kRefreshToken, kTestGaiaId, kTestUser,
-              base::BindOnce([](const std::string& refresh_token) {
-                // Check that the token is properly passed along.
-                EXPECT_EQ(kRefreshToken, refresh_token);
-              }));
-      return;
-    }
     case STATUS_CASE_AUTH_ERROR: {
       service->SetFirstSetupComplete(true);
       service->SetTransportState(syncer::SyncService::TransportState::ACTIVE);
@@ -174,8 +146,6 @@
       return sync_ui_util::NO_ACTION;
     case STATUS_CASE_SETUP_ERROR:
       return sync_ui_util::REAUTHENTICATE;
-    case STATUS_CASE_AUTHENTICATING:
-      return sync_ui_util::NO_ACTION;
     case STATUS_CASE_AUTH_ERROR:
       return sync_ui_util::REAUTHENTICATE;
     case STATUS_CASE_PROTOCOL_ERROR:
@@ -226,13 +196,6 @@
     identity::IdentityManager* identity_manager =
         environment->identity_manager();
 
-    // We can't check the "Authenticating" case in platforms that don't support
-    // mutation of the primary account (e.g. ChromeOS), so skip those cases.
-    if (idx == STATUS_CASE_AUTHENTICATING &&
-        !identity_manager->GetPrimaryAccountMutator()) {
-      continue;
-    }
-
     // Need a primary account signed in before calling GetDistinctCase().
     environment->MakePrimaryAccountAvailable(kTestUser);
 
diff --git a/chrome/browser/task_manager/providers/arc/arc_process_task.cc b/chrome/browser/task_manager/providers/arc/arc_process_task.cc
index ec12563..a7e4611 100644
--- a/chrome/browser/task_manager/providers/arc/arc_process_task.cc
+++ b/chrome/browser/task_manager/providers/arc/arc_process_task.cc
@@ -12,6 +12,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/arc/arc_bridge_service.h"
 #include "components/arc/arc_service_manager.h"
+#include "components/arc/arc_util.h"
 #include "components/arc/common/process.mojom.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -130,6 +131,10 @@
   return !arc_process_.IsPersistent();
 }
 
+bool ArcProcessTask::IsRunningInVM() const {
+  return arc::IsArcVmEnabled();
+}
+
 void ArcProcessTask::Kill() {
   auto* process_instance = ARC_GET_INSTANCE_FOR_METHOD(
       arc::ArcServiceManager::Get()->arc_bridge_service()->process(),
diff --git a/chrome/browser/task_manager/providers/arc/arc_process_task.h b/chrome/browser/task_manager/providers/arc/arc_process_task.h
index adebbdf..c4b4d6a 100644
--- a/chrome/browser/task_manager/providers/arc/arc_process_task.h
+++ b/chrome/browser/task_manager/providers/arc/arc_process_task.h
@@ -33,6 +33,7 @@
   int GetChildProcessUniqueID() const override;
   bool IsKillable() override;
   void Kill() override;
+  bool IsRunningInVM() const override;
 
   // arc::ConnectionObserver<arc::mojom::IntentHelperInstance>:
   void OnConnectionReady() override;
diff --git a/chrome/browser/task_manager/providers/task.cc b/chrome/browser/task_manager/providers/task.cc
index 9ff36e9..74f3cb2 100644
--- a/chrome/browser/task_manager/providers/task.cc
+++ b/chrome/browser/task_manager/providers/task.cc
@@ -191,6 +191,10 @@
   return -1;
 }
 
+bool Task::IsRunningInVM() const {
+  return false;
+}
+
 // static
 gfx::ImageSkia* Task::FetchIcon(int id, gfx::ImageSkia** result_image) {
   if (!*result_image && ui::ResourceBundle::HasSharedInstance()) {
diff --git a/chrome/browser/task_manager/providers/task.h b/chrome/browser/task_manager/providers/task.h
index 2eefac6..6a6f9e6 100644
--- a/chrome/browser/task_manager/providers/task.h
+++ b/chrome/browser/task_manager/providers/task.h
@@ -154,6 +154,9 @@
   // Returns the keep-alive counter if the Task is an event page, -1 otherwise.
   virtual int GetKeepaliveCount() const;
 
+  // Returns true if the task is running inside a VM.
+  virtual bool IsRunningInVM() const;
+
   int64_t task_id() const { return task_id_; }
 
   // Returns the instantaneous rate, in bytes per second, of network usage
diff --git a/chrome/browser/task_manager/sampling/task_group.cc b/chrome/browser/task_manager/sampling/task_group.cc
index 43f4fed..9407ea1 100644
--- a/chrome/browser/task_manager/sampling/task_group.cc
+++ b/chrome/browser/task_manager/sampling/task_group.cc
@@ -83,11 +83,13 @@
 TaskGroup::TaskGroup(
     base::ProcessHandle proc_handle,
     base::ProcessId proc_id,
+    bool is_running_in_vm,
     const base::Closure& on_background_calculations_done,
     const scoped_refptr<SharedSampler>& shared_sampler,
     const scoped_refptr<base::SequencedTaskRunner>& blocking_pool_runner)
     : process_handle_(proc_handle),
       process_id_(proc_id),
+      is_running_in_vm_(is_running_in_vm),
       on_background_calculations_done_(on_background_calculations_done),
       worker_thread_sampler_(nullptr),
       shared_sampler_(shared_sampler),
@@ -119,7 +121,7 @@
       gpu_memory_has_duplicates_(false),
       is_backgrounded_(false),
       weak_ptr_factory_(this) {
-  if (process_id_ != base::kNullProcessId) {
+  if (process_id_ != base::kNullProcessId && !is_running_in_vm_) {
     worker_thread_sampler_ = base::MakeRefCounted<TaskGroupSampler>(
         base::Process::Open(process_id_), blocking_pool_runner,
         base::Bind(&TaskGroup::OnCpuRefreshDone,
@@ -164,6 +166,9 @@
                         int64_t refresh_flags) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(!empty());
+  if (is_running_in_vm_)
+    refresh_flags &= ~kUnsupportedVMRefreshFlags;
+
   expected_on_bg_done_flags_ = refresh_flags & kBackgroundRefreshTypesMask;
   // If a refresh type was recently disabled, we need to account for that too.
   current_on_bg_done_flags_ &= expected_on_bg_done_flags_;
diff --git a/chrome/browser/task_manager/sampling/task_group.h b/chrome/browser/task_manager/sampling/task_group.h
index 9ada18b..e6a7de8 100644
--- a/chrome/browser/task_manager/sampling/task_group.h
+++ b/chrome/browser/task_manager/sampling/task_group.h
@@ -29,6 +29,18 @@
 
 namespace task_manager {
 
+// A mask for refresh flags that are not supported by VM tasks.
+constexpr int kUnsupportedVMRefreshFlags =
+    REFRESH_TYPE_CPU | REFRESH_TYPE_SWAPPED_MEM | REFRESH_TYPE_GPU_MEMORY |
+    REFRESH_TYPE_V8_MEMORY | REFRESH_TYPE_SQLITE_MEMORY |
+    REFRESH_TYPE_WEBCACHE_STATS | REFRESH_TYPE_NETWORK_USAGE |
+    REFRESH_TYPE_NACL | REFRESH_TYPE_IDLE_WAKEUPS | REFRESH_TYPE_HANDLES |
+    REFRESH_TYPE_START_TIME | REFRESH_TYPE_CPU_TIME | REFRESH_TYPE_PRIORITY |
+#if defined(OS_LINUX) || defined(OS_MACOSX)
+    REFRESH_TYPE_FD_COUNT |
+#endif
+    REFRESH_TYPE_HARD_FAULTS;
+
 class SharedSampler;
 
 // Defines a group of tasks tracked by the task manager which belong to the same
@@ -38,6 +50,7 @@
   TaskGroup(
       base::ProcessHandle proc_handle,
       base::ProcessId proc_id,
+      bool is_running_in_vm,
       const base::Closure& on_background_calculations_done,
       const scoped_refptr<SharedSampler>& shared_sampler,
       const scoped_refptr<base::SequencedTaskRunner>& blocking_pool_runner);
@@ -143,6 +156,7 @@
   // The process' handle and ID.
   base::ProcessHandle process_handle_;
   base::ProcessId process_id_;
+  bool is_running_in_vm_;
 
   // This is a callback into the TaskManagerImpl to inform it that the
   // background calculations for this TaskGroup has finished.
diff --git a/chrome/browser/task_manager/sampling/task_group_unittest.cc b/chrome/browser/task_manager/sampling/task_group_unittest.cc
index 5c0fa62..c8b20d9 100644
--- a/chrome/browser/task_manager/sampling/task_group_unittest.cc
+++ b/chrome/browser/task_manager/sampling/task_group_unittest.cc
@@ -28,13 +28,14 @@
 
 class FakeTask : public Task {
  public:
-  FakeTask(base::ProcessId process_id, Type type)
+  FakeTask(base::ProcessId process_id, Type type, bool is_running_in_vm)
       : Task(base::string16(),
              "FakeTask",
              nullptr,
              base::kNullProcessHandle,
              process_id),
-        type_(type) {}
+        type_(type),
+        is_running_in_vm_(is_running_in_vm) {}
 
   Type GetType() const override { return type_; }
 
@@ -44,8 +45,11 @@
 
   SessionID GetTabId() const override { return SessionID::InvalidValue(); }
 
+  bool IsRunningInVM() const override { return is_running_in_vm_; }
+
  private:
   Type type_;
+  bool is_running_in_vm_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeTask);
 };
@@ -57,17 +61,7 @@
   TaskGroupTest()
       : io_task_runner_(base::CreateSingleThreadTaskRunnerWithTraits(
             {content::BrowserThread::IO})),
-        run_loop_(std::make_unique<base::RunLoop>()),
-        task_group_(base::Process::Current().Handle(),
-                    base::Process::Current().Pid(),
-                    base::Bind(&TaskGroupTest::OnBackgroundCalculationsDone,
-                               base::Unretained(this)),
-                    new SharedSampler(io_task_runner_),
-                    io_task_runner_),
-        fake_task_(base::Process::Current().Pid(), Task::UNKNOWN) {
-    // Refresh() is only valid on non-empty TaskGroups, so add a fake Task.
-    task_group_.AddTask(&fake_task_);
-  }
+        run_loop_(std::make_unique<base::RunLoop>()) {}
 
  protected:
   void OnBackgroundCalculationsDone() {
@@ -76,11 +70,24 @@
     run_loop_->QuitWhenIdle();
   }
 
+  void CreateTaskGroup(bool is_running_in_vm) {
+    task_group_ = std::make_unique<TaskGroup>(
+        base::Process::Current().Handle(), base::Process::Current().Pid(),
+        is_running_in_vm,
+        base::Bind(&TaskGroupTest::OnBackgroundCalculationsDone,
+                   base::Unretained(this)),
+        new SharedSampler(io_task_runner_), io_task_runner_);
+    // Refresh() is only valid on non-empty TaskGroups, so add a fake Task.
+    fake_task_ = std::make_unique<FakeTask>(base::Process::Current().Pid(),
+                                            Task::UNKNOWN, is_running_in_vm);
+    task_group_->AddTask(fake_task_.get());
+  }
+
   content::TestBrowserThreadBundle browser_threads_;
   scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
   std::unique_ptr<base::RunLoop> run_loop_;
-  TaskGroup task_group_;
-  FakeTask fake_task_;
+  std::unique_ptr<TaskGroup> task_group_;
+  std::unique_ptr<FakeTask> fake_task_;
   bool background_refresh_complete_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TaskGroupTest);
@@ -90,26 +97,29 @@
 // refresh trivially completes, without crashing or leaving things in a weird
 // state.
 TEST_F(TaskGroupTest, NullRefresh) {
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(), 0);
-  EXPECT_TRUE(task_group_.AreBackgroundCalculationsDone());
+  CreateTaskGroup(false);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(), 0);
+  EXPECT_TRUE(task_group_->AreBackgroundCalculationsDone());
   EXPECT_FALSE(background_refresh_complete_);
 }
 
 // Ensure that refreshing an empty TaskGroup causes a DCHECK (if enabled).
 TEST_F(TaskGroupTest, RefreshZeroTasksDeathTest) {
+  CreateTaskGroup(false);
   // Remove the fake Task from the group.
-  task_group_.RemoveTask(&fake_task_);
+  task_group_->RemoveTask(fake_task_.get());
 
   EXPECT_DCHECK_DEATH(
-      task_group_.Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(), 0));
+      task_group_->Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(), 0));
 }
 
 // Verify that Refresh() for a field which can be refreshed synchronously
 // completes immediately, without leaving any background calculations pending.
 TEST_F(TaskGroupTest, SyncRefresh) {
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
-                      REFRESH_TYPE_NETWORK_USAGE);
-  EXPECT_TRUE(task_group_.AreBackgroundCalculationsDone());
+  CreateTaskGroup(false);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
+                       REFRESH_TYPE_NETWORK_USAGE);
+  EXPECT_TRUE(task_group_->AreBackgroundCalculationsDone());
   EXPECT_FALSE(background_refresh_complete_);
 }
 
@@ -117,14 +127,15 @@
 // work (e.g. on another thread) to complete. Cpu is such a field, so verify
 // that it is correctly reported as requiring background calculations.
 TEST_F(TaskGroupTest, AsyncRefresh) {
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
-                      REFRESH_TYPE_CPU);
-  EXPECT_FALSE(task_group_.AreBackgroundCalculationsDone());
+  CreateTaskGroup(false);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
+                       REFRESH_TYPE_CPU);
+  EXPECT_FALSE(task_group_->AreBackgroundCalculationsDone());
 
   ASSERT_FALSE(background_refresh_complete_);
   run_loop_->Run();
 
-  EXPECT_TRUE(task_group_.AreBackgroundCalculationsDone());
+  EXPECT_TRUE(task_group_->AreBackgroundCalculationsDone());
   EXPECT_TRUE(background_refresh_complete_);
 }
 
@@ -133,29 +144,32 @@
 // and via asynchronous refresh on others, so we just test that that field
 // requires background calculations, similarly to the AsyncRefresh test above.
 TEST_F(TaskGroupTest, SharedAsyncRefresh) {
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
-                      REFRESH_TYPE_IDLE_WAKEUPS);
-  EXPECT_FALSE(task_group_.AreBackgroundCalculationsDone());
+  CreateTaskGroup(false);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
+                       REFRESH_TYPE_IDLE_WAKEUPS);
+  EXPECT_FALSE(task_group_->AreBackgroundCalculationsDone());
 
   ASSERT_FALSE(background_refresh_complete_);
   run_loop_->Run();
 
   EXPECT_TRUE(background_refresh_complete_);
 
-  EXPECT_TRUE(task_group_.AreBackgroundCalculationsDone());
+  EXPECT_TRUE(task_group_->AreBackgroundCalculationsDone());
 }
 
 // Ensure that if NaCl is enabled then calling Refresh with a NaCl Task active
 // results in asynchronous completion. Also verifies that if NaCl is disabled
 // then completion is synchronous.
 TEST_F(TaskGroupTest, NaclRefreshWithTask) {
-  FakeTask fake_task(base::Process::Current().Pid(), Task::NACL);
-  task_group_.AddTask(&fake_task);
+  CreateTaskGroup(false);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::NACL,
+                     false /* is_running_in_vm */);
+  task_group_->AddTask(&fake_task);
 
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
-                      REFRESH_TYPE_NACL);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
+                       REFRESH_TYPE_NACL);
 #if BUILDFLAG(ENABLE_NACL)
-  EXPECT_FALSE(task_group_.AreBackgroundCalculationsDone());
+  EXPECT_FALSE(task_group_->AreBackgroundCalculationsDone());
 
   ASSERT_FALSE(background_refresh_complete_);
   run_loop_->Run();
@@ -163,13 +177,15 @@
   EXPECT_TRUE(background_refresh_complete_);
 #endif  // BUILDFLAG(ENABLE_NACL)
 
-  EXPECT_TRUE(task_group_.AreBackgroundCalculationsDone());
+  EXPECT_TRUE(task_group_->AreBackgroundCalculationsDone());
 }
 
 // Test the task has correct network usage rate when zero bytes read and sent.
 TEST_F(TaskGroupTest, NetworkBytesSentReadZero) {
+  CreateTaskGroup(false);
   const int zero_bytes = 0;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesRead(zero_bytes);
   fake_task.Refresh(base::TimeDelta::FromSeconds(1),
                     REFRESH_TYPE_NETWORK_USAGE);
@@ -182,8 +198,10 @@
 
 // Test the task has correct network usage rate when only having read bytes.
 TEST_F(TaskGroupTest, NetworkBytesRead) {
+  CreateTaskGroup(false);
   const int read_bytes = 1024;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesRead(read_bytes);
   EXPECT_EQ(0, fake_task.network_usage_rate());
   EXPECT_EQ(read_bytes, fake_task.cumulative_network_usage());
@@ -195,8 +213,10 @@
 
 // Test the task has correct network usage rate when only having sent bytes.
 TEST_F(TaskGroupTest, NetworkBytesSent) {
+  CreateTaskGroup(false);
   const int sent_bytes = 1023;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesSent(sent_bytes);
   EXPECT_EQ(0, fake_task.network_usage_rate());
   EXPECT_EQ(sent_bytes, fake_task.cumulative_network_usage());
@@ -209,9 +229,11 @@
 // Test the task has correct network usage rate when only having read bytes and
 // having a non 1s refresh time.
 TEST_F(TaskGroupTest, NetworkBytesRead2SecRefresh) {
+  CreateTaskGroup(false);
   const int refresh_secs = 2;
   const int read_bytes = 1024 * refresh_secs;  // for integer division
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesRead(read_bytes);
   EXPECT_EQ(0, fake_task.network_usage_rate());
   EXPECT_EQ(read_bytes, fake_task.cumulative_network_usage());
@@ -224,9 +246,11 @@
 // Test the task has correct network usage rate when only having sent bytes and
 // having a non 1s refresh time.
 TEST_F(TaskGroupTest, NetworkBytesSent2SecRefresh) {
+  CreateTaskGroup(false);
   const int refresh_secs = 2;
   const int sent_bytes = 1023 * refresh_secs;  // for integer division
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesSent(sent_bytes);
   EXPECT_EQ(0, fake_task.network_usage_rate());
   EXPECT_EQ(sent_bytes, fake_task.cumulative_network_usage());
@@ -238,9 +262,11 @@
 
 // Tests the task has correct usage on receiving and then sending bytes.
 TEST_F(TaskGroupTest, NetworkBytesReadThenSent) {
+  CreateTaskGroup(false);
   const int read_bytes = 124;
   const int sent_bytes = 1027;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesRead(read_bytes);
   EXPECT_EQ(read_bytes, fake_task.cumulative_network_usage());
   fake_task.OnNetworkBytesSent(sent_bytes);
@@ -252,9 +278,11 @@
 
 // Tests the task has correct usage rate on sending and then receiving bytes.
 TEST_F(TaskGroupTest, NetworkBytesSentThenRead) {
+  CreateTaskGroup(false);
   const int read_bytes = 1025;
   const int sent_bytes = 10;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesSent(sent_bytes);
   fake_task.OnNetworkBytesRead(read_bytes);
   fake_task.Refresh(base::TimeDelta::FromSeconds(1),
@@ -265,8 +293,10 @@
 // Tests that the network usage rate goes to 0 after reading bytes then a
 // refresh with no traffic and that cumulative is still correct.
 TEST_F(TaskGroupTest, NetworkBytesReadRefreshNone) {
+  CreateTaskGroup(false);
   const int read_bytes = 1024;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesRead(read_bytes);
   fake_task.Refresh(base::TimeDelta::FromSeconds(1),
                     REFRESH_TYPE_NETWORK_USAGE);
@@ -280,8 +310,10 @@
 // Tests that the network usage rate goes to 0 after sending bytes then a
 // refresh with no traffic and that cumulative is still correct.
 TEST_F(TaskGroupTest, NetworkBytesSentRefreshNone) {
+  CreateTaskGroup(false);
   const int sent_bytes = 1024;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   fake_task.OnNetworkBytesSent(sent_bytes);
   fake_task.Refresh(base::TimeDelta::FromSeconds(1),
                     REFRESH_TYPE_NETWORK_USAGE);
@@ -295,10 +327,12 @@
 // Tests that the network usage rate goes to 0 after a refresh with no traffic
 // and that cumulative is still correct.
 TEST_F(TaskGroupTest, NetworkBytesTransferredRefreshNone) {
+  CreateTaskGroup(false);
   const int read_bytes = 1024;
   const int sent_bytes = 1;
   const int number_of_cycles = 2;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   for (int i = 0; i < number_of_cycles; i++) {
     fake_task.OnNetworkBytesRead(read_bytes);
     fake_task.Refresh(base::TimeDelta::FromSeconds(1),
@@ -318,37 +352,42 @@
 // Tests that 2 tasks in 1 task group that both read bytes have correct usage
 // rates and correct cumulative network usage.
 TEST_F(TaskGroupTest, NetworkBytesReadAsGroup) {
+  CreateTaskGroup(false);
   const int read_bytes1 = 1024;
   const int read_bytes2 = 789;
   const int number_of_cycles = 2;
-  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER);
-  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
+  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
 
-  task_group_.AddTask(&fake_task1);
-  task_group_.AddTask(&fake_task2);
+  task_group_->AddTask(&fake_task1);
+  task_group_->AddTask(&fake_task2);
 
   for (int i = 0; i < number_of_cycles; i++) {
     fake_task1.OnNetworkBytesRead(read_bytes1);
     fake_task2.OnNetworkBytesRead(read_bytes2);
-    task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                        base::TimeDelta::FromSeconds(1),
-                        REFRESH_TYPE_NETWORK_USAGE);
+    task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                         base::TimeDelta::FromSeconds(1),
+                         REFRESH_TYPE_NETWORK_USAGE);
     EXPECT_EQ(read_bytes1 + read_bytes2,
-              task_group_.per_process_network_usage_rate());
+              task_group_->per_process_network_usage_rate());
   }
 
   EXPECT_EQ((read_bytes1 + read_bytes2) * number_of_cycles,
-            task_group_.cumulative_per_process_network_usage());
+            task_group_->cumulative_per_process_network_usage());
 }
 
 // Tests that the network usage rate does not get affected until a refresh is
 // called and that the cumulative is as up to date as possible.
 TEST_F(TaskGroupTest, NetworkBytesTransferredRefreshOutOfOrder) {
+  CreateTaskGroup(false);
   const int read_bytes = 1024;
   const int sent_bytes = 1;
   const int number_of_cycles = 4;
   int number_of_bytes_transferred = 0;
-  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task(base::Process::Current().Pid(), Task::RENDERER,
+                     false /* is_running_in_vm */);
   for (int i = 0; i < number_of_cycles; i++) {
     fake_task.OnNetworkBytesRead(read_bytes * i);
     number_of_bytes_transferred += read_bytes * i;
@@ -376,151 +415,178 @@
 // Tests that 2 tasks in 1 task group that both sent bytes have correct usage
 // rates and correct cumulative network usage.
 TEST_F(TaskGroupTest, NetworkBytesSentAsGroup) {
+  CreateTaskGroup(false);
   const int sent_bytes1 = 1123;
   const int sent_bytes2 = 778;
-  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER);
-  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
+  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
 
-  task_group_.AddTask(&fake_task1);
-  task_group_.AddTask(&fake_task2);
+  task_group_->AddTask(&fake_task1);
+  task_group_->AddTask(&fake_task2);
 
   fake_task1.OnNetworkBytesSent(sent_bytes1);
   fake_task2.OnNetworkBytesSent(sent_bytes2);
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                      base::TimeDelta::FromSeconds(1),
-                      REFRESH_TYPE_NETWORK_USAGE);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                       base::TimeDelta::FromSeconds(1),
+                       REFRESH_TYPE_NETWORK_USAGE);
   EXPECT_EQ(sent_bytes1 + sent_bytes2,
-            task_group_.per_process_network_usage_rate());
+            task_group_->per_process_network_usage_rate());
 
   fake_task1.OnNetworkBytesSent(sent_bytes1);
   fake_task2.OnNetworkBytesSent(sent_bytes2);
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                      base::TimeDelta::FromSeconds(1),
-                      REFRESH_TYPE_NETWORK_USAGE);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                       base::TimeDelta::FromSeconds(1),
+                       REFRESH_TYPE_NETWORK_USAGE);
 
   EXPECT_EQ((sent_bytes1 + sent_bytes2) * 2,
-            task_group_.cumulative_per_process_network_usage());
+            task_group_->cumulative_per_process_network_usage());
 }
 
 // Tests that 2 tasks in 1  task group that have one sending and one reading
 // have correct usage rates for the group and correct cumulative network usage.
 TEST_F(TaskGroupTest, NetworkBytesTransferredAsGroup) {
+  CreateTaskGroup(false);
   const int sent_bytes = 1023;
   const int read_bytes = 678;
   const int number_of_cycles = 2;
-  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER);
-  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
+  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
 
-  task_group_.AddTask(&fake_task1);
-  task_group_.AddTask(&fake_task2);
+  task_group_->AddTask(&fake_task1);
+  task_group_->AddTask(&fake_task2);
   for (int i = 0; i < number_of_cycles; i++) {
     fake_task1.OnNetworkBytesSent(sent_bytes);
     fake_task2.OnNetworkBytesRead(read_bytes);
-    task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                        base::TimeDelta::FromSeconds(1),
-                        REFRESH_TYPE_NETWORK_USAGE);
+    task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                         base::TimeDelta::FromSeconds(1),
+                         REFRESH_TYPE_NETWORK_USAGE);
     EXPECT_EQ(sent_bytes + read_bytes,
-              task_group_.per_process_network_usage_rate());
+              task_group_->per_process_network_usage_rate());
   }
 
   EXPECT_EQ((read_bytes + sent_bytes) * number_of_cycles,
-            task_group_.cumulative_per_process_network_usage());
+            task_group_->cumulative_per_process_network_usage());
 }
 
 // Tests that after two tasks in a task group read bytes that a refresh will
 // zero out network usage rate while maintaining the correct cumulative network
 // usage.
 TEST_F(TaskGroupTest, NetworkBytesReadAsGroupThenNone) {
+  CreateTaskGroup(false);
   const int read_bytes1 = 1013;
   const int read_bytes2 = 679;
   const int number_of_cycles = 2;
-  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER);
-  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
+  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
 
-  task_group_.AddTask(&fake_task1);
-  task_group_.AddTask(&fake_task2);
+  task_group_->AddTask(&fake_task1);
+  task_group_->AddTask(&fake_task2);
 
   for (int i = 0; i < number_of_cycles; i++) {
     fake_task1.OnNetworkBytesRead(read_bytes1);
     fake_task2.OnNetworkBytesRead(read_bytes2);
-    task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                        base::TimeDelta::FromSeconds(1),
-                        REFRESH_TYPE_NETWORK_USAGE);
+    task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                         base::TimeDelta::FromSeconds(1),
+                         REFRESH_TYPE_NETWORK_USAGE);
     EXPECT_EQ(read_bytes1 + read_bytes2,
-              task_group_.per_process_network_usage_rate());
+              task_group_->per_process_network_usage_rate());
   }
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                      base::TimeDelta::FromSeconds(1),
-                      REFRESH_TYPE_NETWORK_USAGE);
-  EXPECT_EQ(0, task_group_.per_process_network_usage_rate());
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                       base::TimeDelta::FromSeconds(1),
+                       REFRESH_TYPE_NETWORK_USAGE);
+  EXPECT_EQ(0, task_group_->per_process_network_usage_rate());
   EXPECT_EQ((read_bytes1 + read_bytes2) * number_of_cycles,
-            task_group_.cumulative_per_process_network_usage());
+            task_group_->cumulative_per_process_network_usage());
 }
 
 // Tests that after two tasks in a task group send bytes that a refresh will
 // zero out network usage rate while maintaining the correct cumulative network
 // usage.
 TEST_F(TaskGroupTest, NetworkBytesSentAsGroupThenNone) {
+  CreateTaskGroup(false);
   const int sent_bytes1 = 1023;
   const int sent_bytes2 = 678;
   const int number_of_cycles = 2;
-  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER);
-  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
+  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
 
-  task_group_.AddTask(&fake_task1);
-  task_group_.AddTask(&fake_task2);
+  task_group_->AddTask(&fake_task1);
+  task_group_->AddTask(&fake_task2);
 
   for (int i = 0; i < number_of_cycles; i++) {
     fake_task1.OnNetworkBytesSent(sent_bytes1);
     fake_task2.OnNetworkBytesSent(sent_bytes2);
-    task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                        base::TimeDelta::FromSeconds(1),
-                        REFRESH_TYPE_NETWORK_USAGE);
+    task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                         base::TimeDelta::FromSeconds(1),
+                         REFRESH_TYPE_NETWORK_USAGE);
     EXPECT_EQ(sent_bytes1 + sent_bytes2,
-              task_group_.per_process_network_usage_rate());
+              task_group_->per_process_network_usage_rate());
   }
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                      base::TimeDelta::FromSeconds(1),
-                      REFRESH_TYPE_NETWORK_USAGE);
-  EXPECT_EQ(0, task_group_.per_process_network_usage_rate());
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                       base::TimeDelta::FromSeconds(1),
+                       REFRESH_TYPE_NETWORK_USAGE);
+  EXPECT_EQ(0, task_group_->per_process_network_usage_rate());
   EXPECT_EQ((sent_bytes1 + sent_bytes2) * number_of_cycles,
-            task_group_.cumulative_per_process_network_usage());
+            task_group_->cumulative_per_process_network_usage());
 }
 
 // Tests that after two tasks in a task group transferred bytes that a refresh
 // will zero out network usage rate while maintaining the correct cumulative
 // network usage.
 TEST_F(TaskGroupTest, NetworkBytesTransferredAsGroupThenNone) {
+  CreateTaskGroup(false);
   const int read_bytes = 321;
   const int sent_bytes = 987;
   const int number_of_cycles = 3;
-  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER);
-  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER);
+  FakeTask fake_task1(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
+  FakeTask fake_task2(base::Process::Current().Pid(), Task::RENDERER,
+                      false /* is_running_in_vm */);
 
-  task_group_.AddTask(&fake_task1);
-  task_group_.AddTask(&fake_task2);
+  task_group_->AddTask(&fake_task1);
+  task_group_->AddTask(&fake_task2);
 
   for (int i = 0; i < number_of_cycles; i++) {
     fake_task1.OnNetworkBytesRead(read_bytes);
     fake_task2.OnNetworkBytesSent(sent_bytes);
-    task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                        base::TimeDelta::FromSeconds(1),
-                        REFRESH_TYPE_NETWORK_USAGE);
+    task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                         base::TimeDelta::FromSeconds(1),
+                         REFRESH_TYPE_NETWORK_USAGE);
     EXPECT_EQ(read_bytes + sent_bytes,
-              task_group_.per_process_network_usage_rate());
+              task_group_->per_process_network_usage_rate());
   }
-  task_group_.Refresh(gpu::VideoMemoryUsageStats(),
-                      base::TimeDelta::FromSeconds(1),
-                      REFRESH_TYPE_NETWORK_USAGE);
-  EXPECT_EQ(0, task_group_.per_process_network_usage_rate());
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(),
+                       base::TimeDelta::FromSeconds(1),
+                       REFRESH_TYPE_NETWORK_USAGE);
+  EXPECT_EQ(0, task_group_->per_process_network_usage_rate());
   EXPECT_EQ((read_bytes + sent_bytes) * number_of_cycles,
-            task_group_.cumulative_per_process_network_usage());
+            task_group_->cumulative_per_process_network_usage());
 }
 
 // Test the task can't be killed with a PID of base::kNullProcessId.
 TEST_F(TaskGroupTest, TaskWithPidZero) {
-  FakeTask fake_task(base::kNullProcessId, Task::RENDERER);
+  CreateTaskGroup(false);
+  FakeTask fake_task(base::kNullProcessId, Task::RENDERER,
+                     false /* is_running_in_vm */);
   EXPECT_FALSE(fake_task.IsKillable());
 }
 
+// Verify that calling TaskGroup::Refresh() on a VM task group with no supported
+// refresh flags trivially completes.
+TEST_F(TaskGroupTest, UnsupportedVMRefreshFlags) {
+  CreateTaskGroup(true);
+  task_group_->Refresh(gpu::VideoMemoryUsageStats(), base::TimeDelta(),
+                       task_manager::kUnsupportedVMRefreshFlags);
+  EXPECT_TRUE(task_group_->AreBackgroundCalculationsDone());
+  EXPECT_FALSE(background_refresh_complete_);
+}
+
 }  // namespace task_manager
diff --git a/chrome/browser/task_manager/sampling/task_manager_impl.cc b/chrome/browser/task_manager/sampling/task_manager_impl.cc
index 1c50a9b..6af3d41b7 100644
--- a/chrome/browser/task_manager/sampling/task_manager_impl.cc
+++ b/chrome/browser/task_manager/sampling/task_manager_impl.cc
@@ -335,7 +335,10 @@
                              b->task_id());
     };
 
-    const size_t num_groups = task_groups_by_proc_id_.size();
+    const size_t num_groups =
+        task_groups_by_proc_id_.size() + arc_vm_task_groups_by_proc_id_.size();
+
+    // |task_groups_by_task_id_| contains all tasks, both VM and non-VM.
     const size_t num_tasks = task_groups_by_task_id_.size();
 
     // Populate |tasks_to_visit| with one task from each group.
@@ -359,6 +362,13 @@
       }
     }
 
+    for (const auto& groups_pair : arc_vm_task_groups_by_proc_id_) {
+      const std::vector<Task*>& tasks = groups_pair.second->tasks();
+      Task* group_task =
+          *std::min_element(tasks.begin(), tasks.end(), comparator);
+      tasks_to_visit.push_back(group_task);
+    }
+
     // Now sort |tasks_to_visit| in reverse order (putting the browser process
     // at back()). We will treat it as a stack from now on.
     std::sort(tasks_to_visit.rbegin(), tasks_to_visit.rend(), comparator);
@@ -429,6 +439,10 @@
   return GetTaskGroupByTaskId(task_id)->num_tasks();
 }
 
+bool TaskManagerImpl::IsRunningInVM(TaskId task_id) const {
+  return GetTaskByTaskId(task_id)->IsRunningInVM();
+}
+
 TaskId TaskManagerImpl::GetTaskIdForWebContents(
     content::WebContents* web_contents) const {
   if (!web_contents)
@@ -445,10 +459,16 @@
 
   const base::ProcessId proc_id = task->process_id();
   const TaskId task_id = task->task_id();
+  const bool is_running_in_vm = task->IsRunningInVM();
 
-  std::unique_ptr<TaskGroup>& task_group = task_groups_by_proc_id_[proc_id];
+  TaskManagerImpl::PidToTaskGroupMap& task_group_map =
+      is_running_in_vm ? arc_vm_task_groups_by_proc_id_
+                       : task_groups_by_proc_id_;
+
+  std::unique_ptr<TaskGroup>& task_group = task_group_map[proc_id];
   if (!task_group) {
     task_group.reset(new TaskGroup(task->process_handle(), proc_id,
+                                   is_running_in_vm,
                                    on_background_data_ready_callback_,
                                    shared_sampler_, blocking_pool_runner_));
 #if defined(OS_CHROMEOS)
@@ -472,8 +492,13 @@
 
   const base::ProcessId proc_id = task->process_id();
   const TaskId task_id = task->task_id();
+  const bool is_running_in_vm = task->IsRunningInVM();
 
-  DCHECK(task_groups_by_proc_id_.count(proc_id));
+  TaskManagerImpl::PidToTaskGroupMap& task_group_map =
+      is_running_in_vm ? arc_vm_task_groups_by_proc_id_
+                       : task_groups_by_proc_id_;
+
+  DCHECK(task_group_map.count(proc_id));
 
   NotifyObserversOnTaskToBeRemoved(task_id);
 
@@ -482,7 +507,7 @@
   task_groups_by_task_id_.erase(task_id);
 
   if (task_group->empty())
-    task_groups_by_proc_id_.erase(proc_id);  // Deletes |task_group|.
+    task_group_map.erase(proc_id);  // Deletes |task_group|.
 
   // Invalidate the cached sorted IDs by clearing the list.
   sorted_task_ids_.clear();
@@ -612,6 +637,11 @@
                                enabled_resources_flags());
   }
 
+  for (auto& groups_itr : arc_vm_task_groups_by_proc_id_) {
+    groups_itr.second->Refresh(gpu_memory_stats_, GetCurrentRefreshTime(),
+                               enabled_resources_flags());
+  }
+
 #if defined(OS_CHROMEOS)
   if (TaskManagerObserver::IsResourceRefreshEnabled(
           REFRESH_TYPE_MEMORY_FOOTPRINT, enabled_resources_flags())) {
@@ -652,6 +682,7 @@
     provider->ClearObserver();
 
   task_groups_by_proc_id_.clear();
+  arc_vm_task_groups_by_proc_id_.clear();
   task_groups_by_task_id_.clear();
   sorted_task_ids_.clear();
 }
diff --git a/chrome/browser/task_manager/sampling/task_manager_impl.h b/chrome/browser/task_manager/sampling/task_manager_impl.h
index 7ef9949..9dc4cb3 100644
--- a/chrome/browser/task_manager/sampling/task_manager_impl.h
+++ b/chrome/browser/task_manager/sampling/task_manager_impl.h
@@ -89,6 +89,7 @@
   const TaskIdList& GetTaskIdsList() const override;
   TaskIdList GetIdsOfTasksSharingSameProcess(TaskId task_id) const override;
   size_t GetNumberOfTasksOnSameProcess(TaskId task_id) const override;
+  bool IsRunningInVM(TaskId task_id) const override;
   TaskId GetTaskIdForWebContents(
       content::WebContents* web_contents) const override;
 
@@ -109,6 +110,9 @@
       std::vector<network::mojom::NetworkUsagePtr> total_network_usages);
 
  private:
+  using PidToTaskGroupMap =
+      std::map<base::ProcessId, std::unique_ptr<TaskGroup>>;
+
   friend struct base::LazyInstanceTraitsBase<TaskManagerImpl>;
 
   TaskManagerImpl();
@@ -135,6 +139,7 @@
   bool UpdateTasksWithBytesTransferred(const BytesTransferredKey& key,
                                        const BytesTransferredParam& param);
 
+  PidToTaskGroupMap* GetVmPidToTaskGroupMap(Task::Type type);
   TaskGroup* GetTaskGroupByTaskId(TaskId task_id) const;
   Task* GetTaskByTaskId(TaskId task_id) const;
 
@@ -145,7 +150,12 @@
   const base::Closure on_background_data_ready_callback_;
 
   // Map TaskGroups by the IDs of the processes they represent.
-  std::map<base::ProcessId, std::unique_ptr<TaskGroup>> task_groups_by_proc_id_;
+  PidToTaskGroupMap task_groups_by_proc_id_;
+
+  // Map ARC VM PidToTaskGroupMaps by the task type. This should be separate
+  // from the non-VM map |task_groups_by_proc_id_| as there can be conflicting
+  // PIDs.
+  PidToTaskGroupMap arc_vm_task_groups_by_proc_id_;
 
   // Map each task by its ID to the TaskGroup on which it resides.
   // Keys are unique but values will have duplicates (i.e. multiple tasks
diff --git a/chrome/browser/task_manager/task_manager_interface.h b/chrome/browser/task_manager/task_manager_interface.h
index 1207c1b..73a108f 100644
--- a/chrome/browser/task_manager/task_manager_interface.h
+++ b/chrome/browser/task_manager/task_manager_interface.h
@@ -234,6 +234,9 @@
   // the Task with |task_id| is running.
   virtual size_t GetNumberOfTasksOnSameProcess(TaskId task_id) const = 0;
 
+  // Returns true if the task is running inside a VM.
+  virtual bool IsRunningInVM(TaskId task_id) const = 0;
+
   // Returns the TaskId associated with the main task for |web_contents|.
   // Returns -1 if |web_contents| is nullptr or does not currently have an
   // associated Task.
diff --git a/chrome/browser/task_manager/test_task_manager.cc b/chrome/browser/task_manager/test_task_manager.cc
index a2e0cd8..7438cf7 100644
--- a/chrome/browser/task_manager/test_task_manager.cc
+++ b/chrome/browser/task_manager/test_task_manager.cc
@@ -177,6 +177,10 @@
   return 1;
 }
 
+bool TestTaskManager::IsRunningInVM(TaskId task_id) const {
+  return false;
+}
+
 TaskId TestTaskManager::GetTaskIdForWebContents(
     content::WebContents* web_contents) const {
   return -1;
diff --git a/chrome/browser/task_manager/test_task_manager.h b/chrome/browser/task_manager/test_task_manager.h
index 5097b225..788ee92 100644
--- a/chrome/browser/task_manager/test_task_manager.h
+++ b/chrome/browser/task_manager/test_task_manager.h
@@ -71,6 +71,7 @@
   const TaskIdList& GetTaskIdsList() const override;
   TaskIdList GetIdsOfTasksSharingSameProcess(TaskId task_id) const override;
   size_t GetNumberOfTasksOnSameProcess(TaskId task_id) const override;
+  bool IsRunningInVM(TaskId task_id) const override;
   TaskId GetTaskIdForWebContents(
       content::WebContents* web_contents) const override;
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index c0e2dab..465e923 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2858,6 +2858,10 @@
       "views/update_recommended_message_box.h",
       "views/webauthn/authenticator_ble_pin_entry_sheet_view.cc",
       "views/webauthn/authenticator_ble_pin_entry_sheet_view.h",
+      "views/webauthn/authenticator_client_pin_entry_sheet_view.cc",
+      "views/webauthn/authenticator_client_pin_entry_sheet_view.h",
+      "views/webauthn/authenticator_client_pin_entry_view.cc",
+      "views/webauthn/authenticator_client_pin_entry_view.h",
       "views/webauthn/authenticator_request_dialog_view.cc",
       "views/webauthn/authenticator_request_dialog_view.h",
       "views/webauthn/authenticator_request_sheet_view.cc",
diff --git a/chrome/browser/ui/app_list/app_service_app_item.cc b/chrome/browser/ui/app_list/app_service_app_item.cc
index a9d9e32..e158a87 100644
--- a/chrome/browser/ui/app_list/app_service_app_item.cc
+++ b/chrome/browser/ui/app_list/app_service_app_item.cc
@@ -78,14 +78,8 @@
   }
 
   if (in_constructor || app_update.IconKeyChanged()) {
-    apps::AppServiceProxy* proxy = apps::AppServiceProxy::Get(profile());
-    if (proxy) {
-      proxy->LoadIcon(app_update.AppId(),
-                      apps::mojom::IconCompression::kUncompressed,
-                      app_list::AppListConfig::instance().grid_icon_dimension(),
-                      base::BindOnce(&AppServiceAppItem::OnLoadIcon,
-                                     weak_ptr_factory_.GetWeakPtr()));
-    }
+    constexpr bool allow_placeholder_icon = true;
+    CallLoadIcon(allow_placeholder_icon);
   }
 
   if (in_constructor || app_update.IsPlatformAppChanged()) {
@@ -131,10 +125,26 @@
   }
 }
 
+void AppServiceAppItem::CallLoadIcon(bool allow_placeholder_icon) {
+  apps::AppServiceProxy* proxy = apps::AppServiceProxy::Get(profile());
+  if (proxy) {
+    proxy->LoadIcon(id(), apps::mojom::IconCompression::kUncompressed,
+                    app_list::AppListConfig::instance().grid_icon_dimension(),
+                    allow_placeholder_icon,
+                    base::BindOnce(&AppServiceAppItem::OnLoadIcon,
+                                   weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
 void AppServiceAppItem::OnLoadIcon(apps::mojom::IconValuePtr icon_value) {
   if (icon_value->icon_compression !=
       apps::mojom::IconCompression::kUncompressed) {
     return;
   }
   SetIcon(icon_value->uncompressed);
+
+  if (icon_value->is_placeholder_icon) {
+    constexpr bool allow_placeholder_icon = false;
+    CallLoadIcon(allow_placeholder_icon);
+  }
 }
diff --git a/chrome/browser/ui/app_list/app_service_app_item.h b/chrome/browser/ui/app_list/app_service_app_item.h
index 47be350..6aa1334 100644
--- a/chrome/browser/ui/app_list/app_service_app_item.h
+++ b/chrome/browser/ui/app_list/app_service_app_item.h
@@ -48,6 +48,8 @@
   void ExecuteLaunchCommand(int event_flags) override;
 
   void Launch(int event_flags, apps::mojom::LaunchSource launch_source);
+
+  void CallLoadIcon(bool allow_placeholder_icon);
   void OnLoadIcon(apps::mojom::IconValuePtr icon_value);
 
   apps::mojom::AppType app_type_;
diff --git a/chrome/browser/ui/app_list/search/app_service_app_result.cc b/chrome/browser/ui/app_list/search/app_service_app_result.cc
index 4239f0f..7df3d82 100644
--- a/chrome/browser/ui/app_list/search/app_service_app_result.cc
+++ b/chrome/browser/ui/app_list/search/app_service_app_result.cc
@@ -24,6 +24,7 @@
       is_platform_app_(false),
       show_in_launcher_(false),
       weak_ptr_factory_(this) {
+  constexpr bool allow_placeholder_icon = true;
   apps::AppServiceProxy* proxy = apps::AppServiceProxy::Get(profile);
 
   if (proxy) {
@@ -38,6 +39,7 @@
     proxy->LoadIcon(
         app_id, apps::mojom::IconCompression::kUncompressed,
         AppListConfig::instance().GetPreferredIconDimension(display_type()),
+        allow_placeholder_icon,
         base::BindOnce(&AppServiceAppResult::OnLoadIcon,
                        weak_ptr_factory_.GetWeakPtr(), false));
 
@@ -45,6 +47,7 @@
       proxy->LoadIcon(
           app_id, apps::mojom::IconCompression::kUncompressed,
           AppListConfig::instance().suggestion_chip_icon_dimension(),
+          allow_placeholder_icon,
           base::BindOnce(&AppServiceAppResult::OnLoadIcon,
                          weak_ptr_factory_.GetWeakPtr(), true));
     }
@@ -123,6 +126,10 @@
   } else {
     SetIcon(icon_value->uncompressed);
   }
+
+  if (icon_value->is_placeholder_icon) {
+    // TODO(crbug.com/826982): watch for new (non-placeholder) icons.
+  }
 }
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index 7213f85..3dfc447 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -16,6 +16,7 @@
 #include "base/strings/string_split.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
@@ -46,6 +47,8 @@
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/app_menu_model.h"
+#include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
+#include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
@@ -1302,14 +1305,30 @@
   ASSERT_TRUE(https_server()->Start());
   NavigateToURLAndWait(browser(), GetInstallableAppURL());
 
-  chrome::SetAutoAcceptPWAInstallDialogForTesting(true);
+  chrome::SetAutoAcceptPWAInstallDialogForTesting(/*auto_accept*/ true);
+
+  web_app::AppId app_id;
+
+  base::RunLoop run_loop;
+  web_app::SetInstalledCallbackForTesting(
+      base::BindLambdaForTesting([&](const web_app::AppId& installed_app_id,
+                                     web_app::InstallResultCode code) {
+        EXPECT_EQ(web_app::InstallResultCode::kSuccess, code);
+        app_id = installed_app_id;
+        run_loop.Quit();
+      }));
+
   chrome::ExecuteCommand(browser(), IDC_INSTALL_PWA);
+  run_loop.Run();
+
+  chrome::SetAutoAcceptPWAInstallDialogForTesting(/*auto_accept*/ false);
+
   const extensions::Extension* app =
-      extensions::TestExtensionRegistryObserver(
-          extensions::ExtensionRegistry::Get(browser()->profile()))
-          .WaitForExtensionInstalled();
+      extensions::ExtensionRegistry::Get(browser()->profile())
+          ->enabled_extensions()
+          .GetByID(app_id);
+  ASSERT_TRUE(app);
   EXPECT_EQ(app->name(), GetInstallableAppName());
-  chrome::SetAutoAcceptPWAInstallDialogForTesting(false);
 
   // Installed PWAs should launch in their own window.
   EXPECT_EQ(extensions::GetLaunchContainer(
diff --git a/chrome/browser/ui/startup/startup_tab_provider.cc b/chrome/browser/ui/startup/startup_tab_provider.cc
index b47c854..5100310 100644
--- a/chrome/browser/ui/startup/startup_tab_provider.cc
+++ b/chrome/browser/ui/startup/startup_tab_provider.cc
@@ -76,10 +76,6 @@
   standard_params.is_signin_allowed = profile->IsSyncAllowed();
   if (auto* identity_manager = IdentityManagerFactory::GetForProfile(profile)) {
     standard_params.is_signed_in = identity_manager->HasPrimaryAccount();
-    if (auto* account_mutator = identity_manager->GetPrimaryAccountMutator()) {
-      standard_params.is_signin_in_progress =
-          account_mutator->LegacyIsPrimaryAccountAuthInProgress();
-    }
   }
   standard_params.is_supervised_user = profile->IsSupervised();
   standard_params.is_force_signin_enabled = signin_util::IsForceSigninEnabled();
@@ -204,9 +200,8 @@
 // static
 bool StartupTabProviderImpl::ShouldShowWelcomeForOnboarding(
     bool has_seen_welcome_page,
-    bool is_signed_in,
-    bool is_signin_in_progress) {
-  return !has_seen_welcome_page && !is_signed_in && !is_signin_in_progress;
+    bool is_signed_in) {
+  return !has_seen_welcome_page && !is_signed_in;
 }
 
 // static
@@ -216,8 +211,7 @@
   if (CanShowWelcome(params.is_signin_allowed, params.is_supervised_user,
                      params.is_force_signin_enabled) &&
       ShouldShowWelcomeForOnboarding(params.has_seen_welcome_page,
-                                     params.is_signed_in,
-                                     params.is_signin_in_progress)) {
+                                     params.is_signed_in)) {
     tabs.emplace_back(GetWelcomePageUrl(!params.is_first_run), false);
   }
   return tabs;
diff --git a/chrome/browser/ui/startup/startup_tab_provider.h b/chrome/browser/ui/startup/startup_tab_provider.h
index c7874ca78..e338d9a 100644
--- a/chrome/browser/ui/startup/startup_tab_provider.h
+++ b/chrome/browser/ui/startup/startup_tab_provider.h
@@ -69,7 +69,6 @@
     bool has_seen_welcome_page = false;
     bool is_signin_allowed = false;
     bool is_signed_in = false;
-    bool is_signin_in_progress = false;
     bool is_supervised_user = false;
     bool is_force_signin_enabled = false;
   };
@@ -95,8 +94,7 @@
   // Returns true if the standard welcome page should be shown in a tab. This
   // should only be used following a positive result from CanShowWelcome.
   static bool ShouldShowWelcomeForOnboarding(bool has_seen_welcome_page,
-                                             bool is_signed_in,
-                                             bool is_signin_in_progress);
+                                             bool is_signed_in);
 
   // Determines which tabs should be shown according to onboarding/first
   // run policy.
diff --git a/chrome/browser/ui/startup/startup_tab_provider_unittest.cc b/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
index e6cceaf..56c0937 100644
--- a/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
+++ b/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
@@ -66,7 +66,7 @@
     StandardOnboardingTabsParams params;
     params.is_first_run = true;
     params.is_signin_allowed = true;
-    params.is_signin_in_progress = true;
+    params.is_signed_in = true;
     StartupTabs output =
         StartupTabProviderImpl::GetStandardOnboardingTabsForState(params);
     EXPECT_TRUE(output.empty());
@@ -262,22 +262,6 @@
     EXPECT_TRUE(output.empty());
   }
   {
-    // If sign-in is in progress, block showing the standard Welcome page.
-    StandardOnboardingTabsParams standard_params;
-    standard_params.is_first_run = true;
-    standard_params.is_signin_allowed = true;
-    standard_params.is_signin_in_progress = true;
-
-    Win10OnboardingTabsParams win10_params;
-    win10_params.has_seen_win10_promo = true;
-    win10_params.set_default_browser_allowed = true;
-
-    StartupTabs output = StartupTabProviderImpl::GetWin10OnboardingTabsForState(
-        standard_params, win10_params);
-
-    EXPECT_TRUE(output.empty());
-  }
-  {
     // If sign-in is disabled, block showing the standard Welcome page.
     StandardOnboardingTabsParams standard_params;
     standard_params.is_first_run = true;
diff --git a/chrome/browser/ui/task_manager/task_manager_table_model.cc b/chrome/browser/ui/task_manager/task_manager_table_model.cc
index 40a28b9..1025020 100644
--- a/chrome/browser/ui/task_manager/task_manager_table_model.cc
+++ b/chrome/browser/ui/task_manager/task_manager_table_model.cc
@@ -353,6 +353,13 @@
           observed_task_manager()->GetSwappedMemoryUsage(tasks_[row]), false);
 
     case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
+      if (observed_task_manager()->IsRunningInVM(tasks_[row])) {
+        // Don't show the process ID if running inside a VM to avoid confusion
+        // over conflicting pids.
+        // TODO(b/122992194): Figure out if we need to change this to display
+        // something for VM processes.
+        return base::string16();
+      }
       return stringifier_->GetProcessIdText(
           observed_task_manager()->GetProcessId(tasks_[row]));
 
@@ -502,9 +509,15 @@
           observed_task_manager()->GetNaClDebugStubPort(tasks_[row1]),
           observed_task_manager()->GetNaClDebugStubPort(tasks_[row2]));
 
-    case IDS_TASK_MANAGER_PROCESS_ID_COLUMN:
+    case IDS_TASK_MANAGER_PROCESS_ID_COLUMN: {
+      bool vm1 = observed_task_manager()->IsRunningInVM(tasks_[row1]);
+      bool vm2 = observed_task_manager()->IsRunningInVM(tasks_[row2]);
+      if (vm1 != vm2) {
+        return ValueCompare(vm1, vm2);
+      }
       return ValueCompare(observed_task_manager()->GetProcessId(tasks_[row1]),
                           observed_task_manager()->GetProcessId(tasks_[row2]));
+    }
 
     case IDS_TASK_MANAGER_GDI_HANDLES_COLUMN: {
       int64_t current1, peak1, current2, peak2;
@@ -611,17 +624,21 @@
 void TaskManagerTableModel::GetRowsGroupRange(int row_index,
                                               int* out_start,
                                               int* out_length) {
-  const base::ProcessId process_id =
-      observed_task_manager()->GetProcessId(tasks_[row_index]);
   int i = row_index;
   int limit = row_index + 1;
-  while (i > 0 &&
-         observed_task_manager()->GetProcessId(tasks_[i - 1]) == process_id) {
-    --i;
-  }
-  while (limit < RowCount() &&
-         observed_task_manager()->GetProcessId(tasks_[limit]) == process_id) {
-    ++limit;
+  if (!observed_task_manager()->IsRunningInVM(tasks_[row_index])) {
+    const base::ProcessId process_id =
+        observed_task_manager()->GetProcessId(tasks_[row_index]);
+    while (i > 0 &&
+           observed_task_manager()->GetProcessId(tasks_[i - 1]) == process_id &&
+           !observed_task_manager()->IsRunningInVM(tasks_[i - 1])) {
+      --i;
+    }
+    while (limit < RowCount() &&
+           observed_task_manager()->GetProcessId(tasks_[limit]) == process_id &&
+           !observed_task_manager()->IsRunningInVM(tasks_[limit])) {
+      ++limit;
+    }
   }
   *out_start = i;
   *out_length = limit - i;
@@ -897,8 +914,15 @@
   if (row_index == 0)
     return true;
 
-  return observed_task_manager()->GetProcessId(tasks_[row_index - 1]) !=
-      observed_task_manager()->GetProcessId(tasks_[row_index]);
+  if (observed_task_manager()->GetProcessId(tasks_[row_index - 1]) !=
+      observed_task_manager()->GetProcessId(tasks_[row_index]))
+    return true;
+
+  if (observed_task_manager()->IsRunningInVM(tasks_[row_index - 1]) !=
+      observed_task_manager()->IsRunningInVM(tasks_[row_index]))
+    return true;
+
+  return false;
 }
 
 }  // namespace task_manager
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
index 3e35be61..5f2a62d 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
@@ -162,10 +162,14 @@
   // call ExecuteCommandWithDisposition on their behalf. Unfortunately, it's not
   // possible to plumb IsKeyEvent through, so this has to be a special case.
   if (IsIncognitoCounterActive()) {
-    IncognitoWindowCountView::ShowBubble(
-        this, browser_,
-        BrowserList::GetIncognitoSessionsActiveForProfile(profile_));
+    if (!IncognitoWindowCountView::IsShowing()) {
+      IncognitoWindowCountView::ShowBubble(
+          this, browser_,
+          BrowserList::GetIncognitoSessionsActiveForProfile(profile_));
+    }
   } else {
+    // TODO(https://crbug.com/896235): Call IncognitoWindowCountView::ShowBubble
+    // from this ShowAvatarBubbleFromAvatarButton.
     browser_->window()->ShowAvatarBubbleFromAvatarButton(
         BrowserWindow::AVATAR_BUBBLE_MODE_DEFAULT,
         signin::ManageAccountsParams(),
diff --git a/chrome/browser/ui/views/profiles/incognito_window_count_view.cc b/chrome/browser/ui/views/profiles/incognito_window_count_view.cc
index 380689a..6b4bdc9 100644
--- a/chrome/browser/ui/views/profiles/incognito_window_count_view.cc
+++ b/chrome/browser/ui/views/profiles/incognito_window_count_view.cc
@@ -8,7 +8,6 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/themes/theme_properties.h"
-#include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -20,12 +19,24 @@
 #include "ui/views/layout/box_layout.h"
 
 // static
+IncognitoWindowCountView*
+    IncognitoWindowCountView::incognito_window_counter_bubble_ = nullptr;
+
+// static
 void IncognitoWindowCountView::ShowBubble(views::Button* anchor_button,
                                           Browser* browser,
                                           int incognito_window_count) {
   // The IncognitoWindowCountView is self-owned, it deletes itself when the
   // widget is closed or the parent browser is destroyed.
-  new IncognitoWindowCountView(anchor_button, browser, incognito_window_count);
+  if (!IsShowing()) {
+    new IncognitoWindowCountView(anchor_button, browser,
+                                 incognito_window_count);
+  }
+}
+
+// static
+bool IncognitoWindowCountView::IsShowing() {
+  return incognito_window_counter_bubble_ != nullptr;
 }
 
 IncognitoWindowCountView::IncognitoWindowCountView(views::Button* anchor_button,
@@ -36,6 +47,8 @@
       browser_(browser),
       browser_list_observer_(this),
       weak_ptr_factory_(this) {
+  DCHECK(incognito_window_counter_bubble_ == nullptr);
+  incognito_window_counter_bubble_ = this;
   browser_list_observer_.Add(BrowserList::GetInstance());
 
   // The lifetime of this bubble is tied to the lifetime of the browser.
@@ -48,7 +61,9 @@
       chrome::DialogIdentifier::INCOGNITO_WINDOW_COUNTER);
 }
 
-IncognitoWindowCountView::~IncognitoWindowCountView() {}
+IncognitoWindowCountView::~IncognitoWindowCountView() {
+  incognito_window_counter_bubble_ = nullptr;
+}
 
 void IncognitoWindowCountView::OnBrowserRemoved(Browser* browser) {
   if (browser_ == browser)
@@ -64,10 +79,8 @@
       views::BoxLayout::Orientation::kVertical));
 
   auto incognito_icon = std::make_unique<views::ImageView>();
-  const ui::ThemeProvider& theme_provider =
-      ThemeService::GetThemeProviderForProfile(browser_->profile());
-  const SkColor icon_color =
-      theme_provider.GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
+  const SkColor icon_color = ThemeProperties::GetDefaultColor(
+      ThemeProperties::COLOR_NTP_BACKGROUND, true /* incognito */);
   incognito_icon->SetImage(
       gfx::CreateVectorIcon(kIncognitoIcon, 40, icon_color));
 
diff --git a/chrome/browser/ui/views/profiles/incognito_window_count_view.h b/chrome/browser/ui/views/profiles/incognito_window_count_view.h
index 4ef8747e..abfa622 100644
--- a/chrome/browser/ui/views/profiles/incognito_window_count_view.h
+++ b/chrome/browser/ui/views/profiles/incognito_window_count_view.h
@@ -25,6 +25,8 @@
                          Browser* browser,
                          int incognito_window_count);
 
+  static bool IsShowing();
+
   // BubbleDialogDelegateView:
   int GetDialogButtons() const override;
   void Init() override;
@@ -46,6 +48,8 @@
   int incognito_window_count_;
   Browser* const browser_;
 
+  static IncognitoWindowCountView* incognito_window_counter_bubble_;
+
   ScopedObserver<BrowserList, BrowserListObserver> browser_list_observer_;
   base::WeakPtrFactory<IncognitoWindowCountView> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/ui/views/select_file_dialog_extension.cc b/chrome/browser/ui/views/select_file_dialog_extension.cc
index 7acbb09..9683129 100644
--- a/chrome/browser/ui/views/select_file_dialog_extension.cc
+++ b/chrome/browser/ui/views/select_file_dialog_extension.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/chromeos/file_manager/select_file_dialog_util.h"
 #include "chrome/browser/chromeos/file_manager/url_util.h"
 #include "chrome/browser/chromeos/login/ui/login_web_dialog.h"
+#include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_view_host.h"
 #include "chrome/browser/profiles/profile.h"
@@ -31,7 +32,6 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views/extensions/extension_dialog.h"
 #include "chrome/common/pref_names.h"
-#include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/native_app_window.h"
@@ -369,11 +369,8 @@
   if (PendingExists(routing_id))
     return;
 
-  const PrefService* pref_service = profile_->GetPrefs();
-  DCHECK(pref_service);
-
   base::FilePath download_default_path(
-      pref_service->GetFilePath(prefs::kDownloadDefaultDirectory));
+      DownloadPrefs::FromBrowserContext(profile_)->DownloadPath());
 
   base::FilePath selection_path = default_path.IsAbsolute() ?
       default_path : download_default_path.Append(default_path.BaseName());
diff --git a/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.cc b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.cc
new file mode 100644
index 0000000..d5b8045
--- /dev/null
+++ b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.cc
@@ -0,0 +1,52 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h"
+
+#include <memory>
+#include <utility>
+
+AuthenticatorClientPinEntrySheetView::AuthenticatorClientPinEntrySheetView(
+    std::unique_ptr<AuthenticatorClientPinEntrySheetModel> sheet_model)
+    : AuthenticatorRequestSheetView(std::move(sheet_model)) {
+  pin_entry_sheet_model()->SetDelegate(this);
+}
+
+AuthenticatorClientPinEntrySheetView::~AuthenticatorClientPinEntrySheetView() =
+    default;
+
+AuthenticatorClientPinEntrySheetModel*
+AuthenticatorClientPinEntrySheetView::pin_entry_sheet_model() {
+  return static_cast<AuthenticatorClientPinEntrySheetModel*>(model());
+}
+
+std::unique_ptr<views::View>
+AuthenticatorClientPinEntrySheetView::BuildStepSpecificContent() {
+  DCHECK(!pin_entry_view_);
+  auto view = std::make_unique<AuthenticatorClientPinEntryView>(
+      this, pin_entry_sheet_model()->mode() ==
+                AuthenticatorClientPinEntrySheetModel::Mode::
+                    kPinSetup /* show_confirmation_text_field */);
+  pin_entry_view_ = view.get();
+  return view;
+}
+
+void AuthenticatorClientPinEntrySheetView::OnPincodeChanged(
+    base::string16 pincode) {
+  pin_entry_sheet_model()->SetPinCode(std::move(pincode));
+}
+
+void AuthenticatorClientPinEntrySheetView::OnConfirmationChanged(
+    base::string16 pincode) {
+  pin_entry_sheet_model()->SetPinConfirmation(std::move(pincode));
+}
+
+void AuthenticatorClientPinEntrySheetView::ShowPinError(
+    const base::string16& error) {
+  if (!pin_entry_view_) {
+    DCHECK(false);
+    return;
+  }
+  pin_entry_view_->UpdateError(error);
+}
diff --git a/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h
new file mode 100644
index 0000000..dadca87
--- /dev/null
+++ b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_CLIENT_PIN_ENTRY_SHEET_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_CLIENT_PIN_ENTRY_SHEET_VIEW_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.h"
+#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
+#include "chrome/browser/ui/webauthn/sheet_models.h"
+
+// Web Authentication request dialog sheet view for entering an authenticator
+// PIN.
+class AuthenticatorClientPinEntrySheetView
+    : public AuthenticatorRequestSheetView,
+      public AuthenticatorClientPinEntrySheetModel::Delegate,
+      public AuthenticatorClientPinEntryView::Delegate {
+ public:
+  explicit AuthenticatorClientPinEntrySheetView(
+      std::unique_ptr<AuthenticatorClientPinEntrySheetModel> model);
+  ~AuthenticatorClientPinEntrySheetView() override;
+
+ private:
+  AuthenticatorClientPinEntrySheetModel* pin_entry_sheet_model();
+
+  // AuthenticatorRequestSheetView:
+  std::unique_ptr<views::View> BuildStepSpecificContent() override;
+
+  // AuthenticatorClientPinEntrySheetModel::Delegate:
+  void ShowPinError(const base::string16& error) override;
+
+  // AuthenticatorClientPinEntryView::Delegate:
+  void OnPincodeChanged(base::string16 pincode) override;
+  void OnConfirmationChanged(base::string16 pincode) override;
+
+  AuthenticatorClientPinEntryView* pin_entry_view_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(AuthenticatorClientPinEntrySheetView);
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_CLIENT_PIN_ENTRY_SHEET_VIEW_H_
diff --git a/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.cc b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.cc
new file mode 100644
index 0000000..61c301c
--- /dev/null
+++ b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.cc
@@ -0,0 +1,140 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/layout_provider.h"
+#include "ui/views/style/typography.h"
+
+namespace {
+
+constexpr int kExpectedPincodeCharLength = 6;
+constexpr int kPreferredTextfieldCharLength = 20;
+constexpr int kTextfieldBottomBorderThickness = 2;
+
+}  // namespace
+
+AuthenticatorClientPinEntryView::AuthenticatorClientPinEntryView(
+    Delegate* delegate,
+    bool show_confirmation_text_field)
+    : delegate_(delegate),
+      show_confirmation_text_field_(show_confirmation_text_field) {
+  views::GridLayout* layout =
+      SetLayoutManager(std::make_unique<views::GridLayout>(this));
+  views::ColumnSet* columns = layout->AddColumnSet(0);
+
+  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING,
+                     views::GridLayout::kFixedSize, views::GridLayout::USE_PREF,
+                     0, 0);
+  columns->AddPaddingColumn(views::GridLayout::kFixedSize, 10);
+  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING,
+                     views::GridLayout::kFixedSize, views::GridLayout::USE_PREF,
+                     0, 0);
+
+  layout->StartRow(views::GridLayout::kFixedSize, 0);
+
+  auto pin_label = std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_ENTRY_PIN_LABEL),
+      views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY);
+  pin_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  pin_label->SetEnabledColor(gfx::kGoogleBlue500);
+  auto* pin_label_ptr = pin_label.get();
+  layout->AddView(pin_label.release());
+
+  views::View* confirmation_label_ptr = nullptr;
+  if (show_confirmation_text_field_) {
+    auto confirmation_label = std::make_unique<views::Label>(
+        l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_SETUP_CONFIRMATION_LABEL),
+        views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY);
+    confirmation_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    confirmation_label->SetEnabledColor(gfx::kGoogleBlue500);
+    confirmation_label_ptr = confirmation_label.get();
+    layout->AddView(confirmation_label.release());
+  }
+
+  layout->StartRow(views::GridLayout::kFixedSize, 0);
+
+  auto pin_text_field = std::make_unique<views::Textfield>();
+  pin_text_field->SetTextInputType(ui::TextInputType::TEXT_INPUT_TYPE_PASSWORD);
+  pin_text_field->SetBackgroundColor(gfx::kGoogleGrey100);
+  pin_text_field->SetMinimumWidthInChars(kExpectedPincodeCharLength);
+  pin_text_field->SetDefaultWidthInChars(kPreferredTextfieldCharLength);
+  pin_text_field->SetBorder(views::CreateSolidSidedBorder(
+      0, 0, kTextfieldBottomBorderThickness, 0, gfx::kGoogleBlue500));
+  pin_text_field->set_controller(this);
+  pin_text_field->SetAssociatedLabel(pin_label_ptr);
+  pin_text_field_ = pin_text_field.get();
+  layout->AddView(pin_text_field.release());
+
+  if (show_confirmation_text_field_) {
+    DCHECK(confirmation_label_ptr);
+    auto confirmation_text_field = std::make_unique<views::Textfield>();
+    confirmation_text_field->SetTextInputType(
+        ui::TextInputType::TEXT_INPUT_TYPE_PASSWORD);
+    confirmation_text_field->SetBackgroundColor(gfx::kGoogleGrey100);
+    confirmation_text_field->SetMinimumWidthInChars(kExpectedPincodeCharLength);
+    confirmation_text_field->SetDefaultWidthInChars(
+        kPreferredTextfieldCharLength);
+    confirmation_text_field->SetBorder(views::CreateSolidSidedBorder(
+        0, 0, kTextfieldBottomBorderThickness, 0, gfx::kGoogleBlue500));
+    confirmation_text_field->set_controller(this);
+    confirmation_text_field->SetAssociatedLabel(confirmation_label_ptr);
+    confirmation_text_field_ = confirmation_text_field.get();
+    layout->AddView(confirmation_text_field.release());
+  }
+
+  layout->StartRow(views::GridLayout::kFixedSize, 0);
+
+  auto error_label = std::make_unique<views::Label>(
+      base::string16(), views::style::CONTEXT_LABEL,
+      views::style::STYLE_PRIMARY);
+  error_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  error_label->SetEnabledColor(gfx::kGoogleRed500);
+  error_label_ = error_label.get();
+  layout->AddView(error_label.release(), 3 /* col_span */, 1 /* row_span */);
+}
+
+AuthenticatorClientPinEntryView::~AuthenticatorClientPinEntryView() = default;
+
+void AuthenticatorClientPinEntryView::UpdateError(const base::string16& text) {
+  error_label_->SetVisible(true);
+  error_label_->SetText(text);
+  error_label_->SizeToPreferredSize();
+}
+
+void AuthenticatorClientPinEntryView::RequestFocus() {
+  pin_text_field_->RequestFocus();
+}
+
+void AuthenticatorClientPinEntryView::ContentsChanged(
+    views::Textfield* sender,
+    const base::string16& new_contents) {
+  DCHECK(sender == pin_text_field_ || sender == confirmation_text_field_);
+
+  if (sender == pin_text_field_) {
+    delegate_->OnPincodeChanged(new_contents);
+  } else {
+    delegate_->OnConfirmationChanged(new_contents);
+  }
+}
+
+bool AuthenticatorClientPinEntryView::HandleKeyEvent(
+    views::Textfield* sender,
+    const ui::KeyEvent& key_event) {
+  // As WebAuthN UI views do not intercept any key events, the key event must
+  // be further processed.
+  return false;
+}
diff --git a/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.h b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.h
new file mode 100644
index 0000000..47eee05
--- /dev/null
+++ b/chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_view.h
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_CLIENT_PIN_ENTRY_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_CLIENT_PIN_ENTRY_VIEW_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/view.h"
+
+namespace views {
+class Textfield;
+class Label;
+}  // namespace views
+
+// View showing a label and text field for entering an authenticator PIN.
+//
+// TODO(martinkr): Reuse for BLE PIN or fold into
+// AuthenticatorClientPinEntrySheetModel.
+class AuthenticatorClientPinEntryView : public views::View,
+                                        public views::TextfieldController {
+ public:
+  class Delegate {
+   public:
+    virtual void OnPincodeChanged(base::string16 pin_code) = 0;
+    virtual void OnConfirmationChanged(base::string16 pin_confirmation) = 0;
+  };
+
+  explicit AuthenticatorClientPinEntryView(Delegate* delegate,
+                                           bool show_confirmation_text_field);
+  ~AuthenticatorClientPinEntryView() override;
+
+  void UpdateError(const base::string16& value);
+
+ private:
+  // views::View:
+  void RequestFocus() override;
+
+  // views::TextFieldController:
+  void ContentsChanged(views::Textfield* sender,
+                       const base::string16& new_contents) override;
+  bool HandleKeyEvent(views::Textfield* sender,
+                      const ui::KeyEvent& key_event) override;
+
+  Delegate* const delegate_;
+  views::Textfield* pin_text_field_ = nullptr;
+  views::Textfield* confirmation_text_field_ = nullptr;
+  views::Label* error_label_ = nullptr;
+  const bool show_confirmation_text_field_;
+
+  DISALLOW_COPY_AND_ASSIGN(AuthenticatorClientPinEntryView);
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_CLIENT_PIN_ENTRY_VIEW_H_
diff --git a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
index 03053d1..bdff790 100644
--- a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
+++ b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
@@ -6,6 +6,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_ble_pin_entry_sheet_view.h"
+#include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_transport_selector_sheet_view.h"
 #include "chrome/browser/ui/views/webauthn/ble_device_selection_sheet_view.h"
@@ -118,6 +119,18 @@
       sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
           std::make_unique<AuthenticatorPaaskSheetModel>(dialog_model));
       break;
+    case Step::kClientPinEntry:
+      sheet_view = std::make_unique<AuthenticatorClientPinEntrySheetView>(
+          std::make_unique<AuthenticatorClientPinEntrySheetModel>(
+              dialog_model,
+              AuthenticatorClientPinEntrySheetModel::Mode::kPinEntry));
+      break;
+    case Step::kClientPinSetup:
+      sheet_view = std::make_unique<AuthenticatorClientPinEntrySheetView>(
+          std::make_unique<AuthenticatorClientPinEntrySheetModel>(
+              dialog_model,
+              AuthenticatorClientPinEntrySheetModel::Mode::kPinSetup));
+      break;
     case Step::kNotStarted:
     case Step::kClosed:
       sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_utils.cc b/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
index a683380..1d979a2 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
+++ b/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/feature_list.h"
+#include "base/no_destructor.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/web_applications/components/install_manager.h"
@@ -54,6 +55,16 @@
   }
 }
 
+WebAppInstalledCallbackForTesting& GetInstalledCallbackForTesting() {
+  static base::NoDestructor<WebAppInstalledCallbackForTesting> instance;
+  return *instance;
+}
+
+void OnWebAppInstalled(const AppId& installed_app_id, InstallResultCode code) {
+  if (GetInstalledCallbackForTesting())
+    std::move(GetInstalledCallbackForTesting()).Run(installed_app_id, code);
+}
+
 }  // namespace
 
 bool CanCreateWebApp(const Browser* browser) {
@@ -77,7 +88,13 @@
 
   provider->install_manager().InstallWebApp(
       web_contents, force_shortcut_app,
-      base::BindOnce(WebAppInstallDialogCallback), base::DoNothing());
+      base::BindOnce(WebAppInstallDialogCallback),
+      base::BindOnce(OnWebAppInstalled));
+}
+
+void SetInstalledCallbackForTesting(
+    WebAppInstalledCallbackForTesting callback) {
+  GetInstalledCallbackForTesting() = std::move(callback);
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_utils.h b/chrome/browser/ui/web_applications/web_app_dialog_utils.h
index e47c16f..f77d132 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_utils.h
+++ b/chrome/browser/ui/web_applications/web_app_dialog_utils.h
@@ -5,10 +5,15 @@
 #ifndef CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_DIALOG_UTILS_H_
 #define CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_DIALOG_UTILS_H_
 
+#include "base/callback_forward.h"
+#include "chrome/browser/web_applications/components/web_app_helpers.h"
+
 class Browser;
 
 namespace web_app {
 
+enum class InstallResultCode;
+
 // Returns true if a WebApp installation is allowed for the current page.
 bool CanCreateWebApp(const Browser* browser);
 
@@ -16,6 +21,10 @@
 void CreateWebAppFromCurrentWebContents(Browser* browser,
                                         bool force_shortcut_app);
 
+using WebAppInstalledCallbackForTesting =
+    base::OnceCallback<void(const AppId& app_id, InstallResultCode code)>;
+void SetInstalledCallbackForTesting(WebAppInstalledCallbackForTesting callback);
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_DIALOG_UTILS_H_
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index bb9f298..fca6f7a 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/webauthn/other_transports_menu_model.h"
@@ -662,3 +663,100 @@
 ui::MenuModel* AuthenticatorPaaskSheetModel::GetOtherTransportsMenuModel() {
   return other_transports_menu_model_.get();
 }
+
+// AuthenticatorClientPinEntrySheetModel
+// -----------------------------------------
+
+AuthenticatorClientPinEntrySheetModel::AuthenticatorClientPinEntrySheetModel(
+    AuthenticatorRequestDialogModel* dialog_model,
+    Mode mode)
+    : AuthenticatorSheetModelBase(dialog_model), mode_(mode) {}
+
+AuthenticatorClientPinEntrySheetModel::
+    ~AuthenticatorClientPinEntrySheetModel() = default;
+
+void AuthenticatorClientPinEntrySheetModel::SetDelegate(Delegate* delegate) {
+  DCHECK(!delegate_);
+  delegate_ = delegate;
+}
+
+void AuthenticatorClientPinEntrySheetModel::SetPinCode(
+    base::string16 pin_code) {
+  pin_code_ = std::move(pin_code);
+}
+
+void AuthenticatorClientPinEntrySheetModel::SetPinConfirmation(
+    base::string16 pin_confirmation) {
+  DCHECK(mode_ == AuthenticatorClientPinEntrySheetModel::Mode::kPinSetup);
+  pin_confirmation_ = std::move(pin_confirmation);
+}
+
+gfx::ImageSkia* AuthenticatorClientPinEntrySheetModel::GetStepIllustration()
+    const {
+  return GetImage(IDR_WEBAUTHN_ILLUSTRATION_PIN);
+}
+
+base::string16 AuthenticatorClientPinEntrySheetModel::GetStepTitle() const {
+  return l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_ENTRY_TITLE);
+}
+
+base::string16 AuthenticatorClientPinEntrySheetModel::GetStepDescription()
+    const {
+  return l10n_util::GetStringUTF16(
+      mode_ == AuthenticatorClientPinEntrySheetModel::Mode::kPinEntry
+          ? IDS_WEBAUTHN_PIN_ENTRY_DESCRIPTION
+          : IDS_WEBAUTHN_PIN_SETUP_DESCRIPTION);
+}
+
+bool AuthenticatorClientPinEntrySheetModel::IsAcceptButtonVisible() const {
+  return true;
+}
+
+bool AuthenticatorClientPinEntrySheetModel::IsAcceptButtonEnabled() const {
+  return true;
+}
+
+base::string16 AuthenticatorClientPinEntrySheetModel::GetAcceptButtonLabel()
+    const {
+  return l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_ENTRY_NEXT);
+}
+
+static bool IsValidUTF16(const base::string16& str16) {
+  std::string unused_str8;
+  return base::UTF16ToUTF8(str16.c_str(), str16.size(), &unused_str8);
+}
+
+void AuthenticatorClientPinEntrySheetModel::OnAccept() {
+  // TODO(martinkr): use device::pin::kMinLength once landed.
+  constexpr size_t kMinPinLength = 4;
+  if (!delegate_) {
+    NOTREACHED();
+    return;
+  }
+  if (mode_ == AuthenticatorClientPinEntrySheetModel::Mode::kPinSetup) {
+    // Validate a new PIN.
+    base::Optional<base::string16> error;
+    if (!pin_code_.empty() && !IsValidUTF16(pin_code_)) {
+      error = l10n_util::GetStringUTF16(
+          IDS_WEBAUTHN_PIN_ENTRY_ERROR_INVALID_CHARACTERS);
+    } else if (pin_code_.size() < kMinPinLength) {
+      error = l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_ENTRY_ERROR_TOO_SHORT);
+    } else if (pin_code_ != pin_confirmation_) {
+      error = l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_ENTRY_ERROR_MISMATCH);
+    }
+    if (error) {
+      delegate_->ShowPinError(*error);
+      return;
+    }
+  } else {
+    // Submit PIN to authenticator for verification.
+    DCHECK(mode_ == AuthenticatorClientPinEntrySheetModel::Mode::kPinEntry);
+    if (pin_code_.size() < kMinPinLength) {
+      delegate_->ShowPinError(
+          l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_ENTRY_ERROR_TOO_SHORT));
+      return;
+    }
+  }
+  // TODO(martinkr): Actually set the PIN/request the PIN token and continue
+  // the request.
+}
diff --git a/chrome/browser/ui/webauthn/sheet_models.h b/chrome/browser/ui/webauthn/sheet_models.h
index 4ba545c..8ababa8 100644
--- a/chrome/browser/ui/webauthn/sheet_models.h
+++ b/chrome/browser/ui/webauthn/sheet_models.h
@@ -344,4 +344,43 @@
   std::unique_ptr<OtherTransportsMenuModel> other_transports_menu_model_;
 };
 
+class AuthenticatorClientPinEntrySheetModel
+    : public AuthenticatorSheetModelBase {
+ public:
+  class Delegate {
+   public:
+    virtual void ShowPinError(const base::string16& error) = 0;
+  };
+  // Indicates whether the view should accommodate setting up a new PIN or
+  // entering an existing one.
+  enum class Mode { kPinEntry, kPinSetup };
+  AuthenticatorClientPinEntrySheetModel(
+      AuthenticatorRequestDialogModel* dialog_model,
+      Mode mode);
+  ~AuthenticatorClientPinEntrySheetModel() override;
+
+  using AuthenticatorSheetModelBase::AuthenticatorSheetModelBase;
+
+  void SetDelegate(Delegate* delegate);
+  void SetPinCode(base::string16 pin_code);
+  void SetPinConfirmation(base::string16 pin_confirmation);
+
+  Mode mode() const { return mode_; }
+
+ private:
+  // AuthenticatorSheetModelBase:
+  gfx::ImageSkia* GetStepIllustration() const override;
+  base::string16 GetStepTitle() const override;
+  base::string16 GetStepDescription() const override;
+  bool IsAcceptButtonVisible() const override;
+  bool IsAcceptButtonEnabled() const override;
+  base::string16 GetAcceptButtonLabel() const override;
+  void OnAccept() override;
+
+  base::string16 pin_code_;
+  base::string16 pin_confirmation_;
+  const Mode mode_;
+  Delegate* delegate_ = nullptr;
+};
+
 #endif  // CHROME_BROWSER_UI_WEBAUTHN_SHEET_MODELS_H_
diff --git a/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc b/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
index 0ee734c..33867b0 100644
--- a/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
+++ b/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
@@ -4,8 +4,6 @@
 
 #include "chrome/browser/ui/webui/usb_internals/usb_internals_ui.h"
 
-#include <utility>
-
 #include "base/bind.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/usb_internals/usb_internals_page_handler.h"
diff --git a/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc b/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc
index a202a5db..8974b66 100644
--- a/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc
@@ -20,10 +20,10 @@
 
 enum class NtpBackgrounds {
   kArt = 0,
-  kLandscape = 1,
-  kCityscape = 2,
-  kSeascape = 3,
-  kGeometricShapes = 4,
+  kCityscape = 1,
+  kGeometricShapes = 2,
+  kLandscape = 3,
+  kLife = 4,
 };
 
 NtpBackgroundHandler::NtpBackgroundHandler() {}
@@ -47,12 +47,13 @@
       GetOnboardingNtpBackgrounds();
 
   auto element = std::make_unique<base::DictionaryValue>();
-  int id = static_cast<int>(NtpBackgrounds::kArt);
+  int id = static_cast<int>(NtpBackgrounds::kCityscape);
   element->SetInteger("id", id);
-  element->SetString("title",
-                     l10n_util::GetStringUTF8(
-                         IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_ART_TITLE));
+  element->SetString(
+      "title", l10n_util::GetStringUTF8(
+                   IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_CITYSCAPE_TITLE));
   element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("thumbnailClass", "cityscape");
   list_value.Append(std::move(element));
 
   element = std::make_unique<base::DictionaryValue>();
@@ -62,24 +63,17 @@
       "title", l10n_util::GetStringUTF8(
                    IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_LANDSCAPE_TITLE));
   element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("thumbnailClass", "landscape");
   list_value.Append(std::move(element));
 
   element = std::make_unique<base::DictionaryValue>();
-  id = static_cast<int>(NtpBackgrounds::kCityscape);
-  element->SetInteger("id", id);
-  element->SetString(
-      "title", l10n_util::GetStringUTF8(
-                   IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_CITYSCAPE_TITLE));
-  element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
-  list_value.Append(std::move(element));
-
-  element = std::make_unique<base::DictionaryValue>();
-  id = static_cast<int>(NtpBackgrounds::kSeascape);
+  id = static_cast<int>(NtpBackgrounds::kArt);
   element->SetInteger("id", id);
   element->SetString("title",
                      l10n_util::GetStringUTF8(
-                         IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_SEASCAPE_TITLE));
+                         IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_ART_TITLE));
   element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("thumbnailClass", "art");
   list_value.Append(std::move(element));
 
   element = std::make_unique<base::DictionaryValue>();
@@ -90,6 +84,17 @@
       l10n_util::GetStringUTF8(
           IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_GEOMETRIC_SHAPES_TITLE));
   element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("thumbnailClass", "geometric-shapes");
+  list_value.Append(std::move(element));
+
+  element = std::make_unique<base::DictionaryValue>();
+  id = static_cast<int>(NtpBackgrounds::kLife);
+  element->SetInteger("id", id);
+  element->SetString("title",
+                     l10n_util::GetStringUTF8(
+                         IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_LIFE_TITLE));
+  element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("thumbnailClass", "life");
   list_value.Append(std::move(element));
 
   ResolveJavascriptCallback(*callback_id, list_value);
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
index be5869f..9519633 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
@@ -9,9 +9,11 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
+#include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "chrome/common/web_application_info.h"
 #include "extensions/common/extension.h"
@@ -28,6 +30,9 @@
 void BookmarkAppInstallFinalizer::FinalizeInstall(
     std::unique_ptr<WebApplicationInfo> web_app_info,
     InstallFinalizedCallback callback) {
+  // Concurrent calls are not allowed.
+  DCHECK(!web_app_info_);
+
   if (!crx_installer_) {
     ExtensionService* extension_service =
         ExtensionSystem::Get(profile_)->extension_service();
@@ -35,10 +40,13 @@
     crx_installer_ = CrxInstaller::CreateSilent(extension_service);
   }
 
-  crx_installer_->set_installer_callback(base::BindOnce(
-      &BookmarkAppInstallFinalizer::OnInstall, weak_ptr_factory_.GetWeakPtr(),
-      std::move(callback), web_app_info->app_url));
-  crx_installer_->InstallWebApp(*web_app_info);
+  web_app_info_ = std::move(web_app_info);
+
+  crx_installer_->set_installer_callback(
+      base::BindOnce(&BookmarkAppInstallFinalizer::OnExtensionInstalled,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     web_app_info_->app_url));
+  crx_installer_->InstallWebApp(*web_app_info_);
 }
 
 void BookmarkAppInstallFinalizer::SetCrxInstallerForTesting(
@@ -46,22 +54,36 @@
   crx_installer_ = crx_installer;
 }
 
-void BookmarkAppInstallFinalizer::OnInstall(
+void BookmarkAppInstallFinalizer::OnExtensionInstalled(
     InstallFinalizedCallback callback,
     const GURL& app_url,
     const base::Optional<CrxInstallError>& error) {
+  DCHECK(web_app_info_);
+
   if (error) {
     std::move(callback).Run(web_app::AppId(),
                             web_app::InstallResultCode::kFailedUnknownReason);
-    return;
+  } else {
+    auto* extension = crx_installer_->extension();
+    DCHECK(extension);
+    DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension), app_url);
+
+    LaunchType launch_type = web_app_info_->open_as_window
+                                 ? LAUNCH_TYPE_WINDOW
+                                 : LAUNCH_TYPE_REGULAR;
+
+    // Set the launcher type for the app.
+    SetLaunchType(profile_, extension->id(), launch_type);
+
+    // Set this app to be locally installed, as it was installed from this
+    // machine.
+    SetBookmarkAppIsLocallyInstalled(profile_, extension, true);
+
+    std::move(callback).Run(extension->id(),
+                            web_app::InstallResultCode::kSuccess);
   }
 
-  auto* installed_extension = crx_installer_->extension();
-  DCHECK(installed_extension);
-  DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(installed_extension), app_url);
-
-  std::move(callback).Run(installed_extension->id(),
-                          web_app::InstallResultCode::kSuccess);
+  web_app_info_ = nullptr;
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
index 6c5db547..9b1afb1 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
@@ -36,10 +36,11 @@
   void SetCrxInstallerForTesting(scoped_refptr<CrxInstaller> crx_installer);
 
  private:
-  void OnInstall(InstallFinalizedCallback callback,
-                 const GURL& app_url,
-                 const base::Optional<CrxInstallError>& error);
+  void OnExtensionInstalled(InstallFinalizedCallback callback,
+                            const GURL& app_url,
+                            const base::Optional<CrxInstallError>& error);
 
+  std::unique_ptr<WebApplicationInfo> web_app_info_;
   scoped_refptr<CrxInstaller> crx_installer_;
   Profile* profile_;
 
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index 160c93e..fbd7957 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -79,6 +79,10 @@
 
     // Phone as a security key.
     kCableActivate,
+
+    // Authenticator Client PIN.
+    kClientPinEntry,
+    kClientPinSetup,
   };
 
   // Implemented by the dialog to observe this model and show the UI panels
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 67547b5..f7ff67f 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -682,6 +682,10 @@
 const base::Feature kShillSandboxing{"ShillSandboxing",
                                      base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enable support for multiple scheduler configurations.
+const base::Feature kSchedulerConfiguration{"SchedulerConfiguration",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
+
 #endif  // defined(OS_CHROMEOS)
 
 // Enable showing a tab-modal dialog while a Web Authentication API request is
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index b97c853c..323e8fb 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -453,6 +453,9 @@
 COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kUsbbouncer;
 
 COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kShillSandboxing;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kSchedulerConfiguration;
 #endif  // defined(OS_CHROMEOS)
 
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/common/extensions/api/automation.idl b/chrome/common/extensions/api/automation.idl
index 4ac4c91..e965dab 100644
--- a/chrome/common/extensions/api/automation.idl
+++ b/chrome/common/extensions/api/automation.idl
@@ -302,9 +302,9 @@
     scrollForward,
     scrollLeft,
     scrollRight,
+    scrollUp,
     scrollToMakeVisible,
     scrollToPoint,
-    scrollUp,
     setAccessibilityFocus,
     setScrollOffset,
     setSelection,
diff --git a/chrome/services/app_service/README.md b/chrome/services/app_service/README.md
index e29a7e3..75a593b 100644
--- a/chrome/services/app_service/README.md
+++ b/chrome/services/app_service/README.md
@@ -280,10 +280,10 @@
       // App Icon Factory methods.
       LoadIcon(
           AppType app_type,
-          string app_id,
           IconKey icon_key,
           IconCompression icon_compression,
-          int32 size_hint_in_dip) => (IconValue icon_value);
+          int32 size_hint_in_dip,
+          bool allow_placeholder_icon) => (IconValue icon_value);
 
       // Some additional methods; not App Icon Factory related.
     };
@@ -291,16 +291,18 @@
     interface Publisher {
       // App Icon Factory methods.
       LoadIcon(
-          string app_id,
           IconKey icon_key,
           IconCompression icon_compression,
-          int32 size_hint_in_dip) => (IconValue icon_value);
+          int32 size_hint_in_dip,
+          bool allow_placeholder_icon) => (IconValue icon_value);
 
       // Some additional methods; not App Icon Factory related.
     };
 
     enum IconType {
       kUnknown,
+      kArc,
+      kCrostini,
       kExtension,
       kResource,
     };
@@ -322,12 +324,44 @@
       IconCompression icon_compression;
       gfx.mojom.ImageSkia? uncompressed;
       array<uint8>? compressed;
+      bool is_placeholder_icon;
     };
 
 TBD: post-processing effects like rounded corners, badges or grayed out (for
 disabled apps) icons.
 
 
+## Placeholder Icons
+
+It can take some time for `Publisher`s to provide an icon. For example, loading
+the canonical icon for an ARC++ or Crostini app might require waiting for a VM
+to start. Such icons are often cached on the file system, but on a cache miss,
+there may be a number of seconds before the system can present an icon. In this
+case, we might want to present a `Publisher`-specific placeholder, typically
+loaded from a resource (an asset statically compiled into the binary).
+
+There are two boolean fields that facilitate this: `allow_placeholder_icon` is
+sent from a `Subsciber` to a `Publisher` and `is_placeholder_icon` is sent in
+the response.
+
+`LoadIcon`'s `allow_placeholder_icon` states whether the the caller will accept
+a placeholder if the real icon can not be provided quickly. Native user
+interfaces like the app launcher will probably set this to true. On the other
+hand, serving Web-UI URLs such as `chrome://app-icon/app_id/icon_size` will set
+this to false, as that URL should identify a particular icon, not one that
+changes over time. Web-UI that wants to display placeholder icons and be
+notified of when real icons are ready will require some mechanism other than a
+`chrome:://app-icon/etc` URL.
+
+`IconValue`'s `is_placeholder_icon` states whether the icon provided is a
+placeholder. That field should only be true if the corresponding `LoadIcon`
+call had `allow_placeholder_icon` true. When the `LoadIcon` caller receives a
+placeholder icon, it is up to the caller to issue a new `LoadIcon` call, this
+time with `allow_placeholder_icon` false. A new Mojo call is necessary, because
+a Mojo callback is a `base::OnceCallback`, so the same callback can't be used
+for both the placeholder and the real icon.
+
+
 # App Runner
 
 Each `Publisher` has (`Publisher`-specific) implementations of e.g. launching an
@@ -383,4 +417,4 @@
 
 ---
 
-Updated on 2018-11-22.
+Updated on 2019-02-09.
diff --git a/chrome/services/app_service/app_service_impl.cc b/chrome/services/app_service/app_service_impl.cc
index 5c62ace7..33c083f 100644
--- a/chrome/services/app_service/app_service_impl.cc
+++ b/chrome/services/app_service/app_service_impl.cc
@@ -64,18 +64,19 @@
 }
 
 void AppServiceImpl::LoadIcon(apps::mojom::AppType app_type,
-                              const std::string& app_id,
                               apps::mojom::IconKeyPtr icon_key,
                               apps::mojom::IconCompression icon_compression,
                               int32_t size_hint_in_dip,
+                              bool allow_placeholder_icon,
                               LoadIconCallback callback) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     std::move(callback).Run(apps::mojom::IconValue::New());
     return;
   }
-  iter->second->LoadIcon(app_id, std::move(icon_key), icon_compression,
-                         size_hint_in_dip, std::move(callback));
+  iter->second->LoadIcon(std::move(icon_key), icon_compression,
+                         size_hint_in_dip, allow_placeholder_icon,
+                         std::move(callback));
 }
 
 void AppServiceImpl::Launch(apps::mojom::AppType app_type,
diff --git a/chrome/services/app_service/app_service_impl.h b/chrome/services/app_service/app_service_impl.h
index b9f239b..8984481 100644
--- a/chrome/services/app_service/app_service_impl.h
+++ b/chrome/services/app_service/app_service_impl.h
@@ -32,10 +32,10 @@
   void RegisterSubscriber(apps::mojom::SubscriberPtr subscriber,
                           apps::mojom::ConnectOptionsPtr opts) override;
   void LoadIcon(apps::mojom::AppType app_type,
-                const std::string& app_id,
                 apps::mojom::IconKeyPtr icon_key,
                 apps::mojom::IconCompression icon_compression,
                 int32_t size_hint_in_dip,
+                bool allow_placeholder_icon,
                 LoadIconCallback callback) override;
   void Launch(apps::mojom::AppType app_type,
               const std::string& app_id,
diff --git a/chrome/services/app_service/app_service_impl_unittest.cc b/chrome/services/app_service/app_service_impl_unittest.cc
index 529a8e8..50d38f98 100644
--- a/chrome/services/app_service/app_service_impl_unittest.cc
+++ b/chrome/services/app_service/app_service_impl_unittest.cc
@@ -37,7 +37,7 @@
     }
   }
 
-  std::string load_icon_app_id_;
+  std::string load_icon_s_key;
 
  private:
   void Connect(apps::mojom::SubscriberPtr subscriber,
@@ -46,12 +46,12 @@
     subscribers_.AddPtr(std::move(subscriber));
   }
 
-  void LoadIcon(const std::string& app_id,
-                apps::mojom::IconKeyPtr icon_key,
+  void LoadIcon(apps::mojom::IconKeyPtr icon_key,
                 apps::mojom::IconCompression icon_compression,
                 int32_t size_hint_in_dip,
+                bool allow_placeholder_icon,
                 LoadIconCallback callback) override {
-    load_icon_app_id_ = app_id;
+    load_icon_s_key = icon_key->s_key;
     std::move(callback).Run(apps::mojom::IconValue::New());
   }
 
@@ -193,20 +193,24 @@
                            : apps::mojom::AppType::kUnknown;
 
     bool callback_ran = false;
-    pub0.load_icon_app_id_ = "-";
-    pub1.load_icon_app_id_ = "-";
-    pub2.load_icon_app_id_ = "-";
+    pub0.load_icon_s_key = "-";
+    pub1.load_icon_s_key = "-";
+    pub2.load_icon_s_key = "-";
+    auto icon_key = apps::mojom::IconKey::New();
+    icon_key->s_key = "o";
+    constexpr bool allow_placeholder_icon = false;
     impl.LoadIcon(
-        app_type, "o", apps::mojom::IconKey::New(),
+        app_type, std::move(icon_key),
         apps::mojom::IconCompression::kUncompressed, size_hint_in_dip,
+        allow_placeholder_icon,
         base::BindOnce(
             [](bool* ran, apps::mojom::IconValuePtr iv) { *ran = true; },
             &callback_ran));
     base::RunLoop().RunUntilIdle();
     EXPECT_TRUE(callback_ran);
-    EXPECT_EQ("-", pub0.load_icon_app_id_);
-    EXPECT_EQ(i == 0 ? "o" : "-", pub1.load_icon_app_id_);
-    EXPECT_EQ("-", pub2.load_icon_app_id_);
+    EXPECT_EQ("-", pub0.load_icon_s_key);
+    EXPECT_EQ(i == 0 ? "o" : "-", pub1.load_icon_s_key);
+    EXPECT_EQ("-", pub2.load_icon_s_key);
   }
 }
 
diff --git a/chrome/services/app_service/public/mojom/app_service.mojom b/chrome/services/app_service/public/mojom/app_service.mojom
index 9cd98937..c957fe5 100644
--- a/chrome/services/app_service/public/mojom/app_service.mojom
+++ b/chrome/services/app_service/public/mojom/app_service.mojom
@@ -21,10 +21,10 @@
   // App Icon Factory methods.
   LoadIcon(
       AppType app_type,
-      string app_id,
       IconKey icon_key,
       IconCompression icon_compression,
-      int32 size_hint_in_dip) => (IconValue icon_value);
+      int32 size_hint_in_dip,
+      bool allow_placeholder_icon) => (IconValue icon_value);
 
   // App Runner methods.
   Launch(
@@ -54,10 +54,10 @@
 
   // App Icon Factory methods.
   LoadIcon(
-      string app_id,
       IconKey icon_key,
       IconCompression icon_compression,
-      int32 size_hint_in_dip) => (IconValue icon_value);
+      int32 size_hint_in_dip,
+      bool allow_placeholder_icon) => (IconValue icon_value);
 
   // App Runner methods.
   Launch(
diff --git a/chrome/services/app_service/public/mojom/types.mojom b/chrome/services/app_service/public/mojom/types.mojom
index 237a99a..6fd7f39 100644
--- a/chrome/services/app_service/public/mojom/types.mojom
+++ b/chrome/services/app_service/public/mojom/types.mojom
@@ -108,6 +108,7 @@
   IconCompression icon_compression;
   gfx.mojom.ImageSkia? uncompressed;
   array<uint8>? compressed;
+  bool is_placeholder_icon;
 };
 
 enum LaunchSource {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 86bc8b3..742fd02 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5471,6 +5471,7 @@
       "//components/sync:test_support_testserver",
       "//content/test:test_support",
       "//net",
+      "//services/identity/public/cpp:test_support",
       "//skia",
     ]
 
diff --git a/chrome/test/base/js2gtest.js b/chrome/test/base/js2gtest.js
index fa35d87..bd16af8 100644
--- a/chrome/test/base/js2gtest.js
+++ b/chrome/test/base/js2gtest.js
@@ -187,7 +187,7 @@
     if (this[testFixture].prototype.commandLineSwitches)
       output('#include "base/command_line.h"');
     if (this[testFixture].prototype.featureList ||
-        this[testFixture].prototype.featureWithParameters)
+        this[testFixture].prototype.featuresWithParameters)
       output('#include "base/test/scoped_feature_list.h"');
   }
   output();
@@ -395,9 +395,9 @@
     var switches = this[testFixture].prototype.commandLineSwitches;
     var hasSwitches = switches && switches.length;
     var featureList = this[testFixture].prototype.featureList;
-    var featureWithParameters =
-        this[testFixture].prototype.featureWithParameters;
-    if ((!hasSwitches && !featureList && !featureWithParameters) ||
+    var featuresWithParameters =
+        this[testFixture].prototype.featuresWithParameters;
+    if ((!hasSwitches && !featureList && !featuresWithParameters) ||
         typedefCppFixture == 'V8UnitTest') {
       output(`
 typedef ${typedefCppFixture} ${testFixture};
@@ -407,7 +407,7 @@
       output(`
 class ${testFixture} : public ${typedefCppFixture} {
  protected:`);
-      if (featureList || featureWithParameters) {
+      if (featureList || featuresWithParameters) {
         output(`
   ${testFixture}() {`);
         if (featureList) {
@@ -415,20 +415,23 @@
     scoped_feature_list_.InitWithFeatures({${featureList[0]}},
                                           {${featureList[1]}});`);
         }
-        if (featureWithParameters) {
-          var feature = featureWithParameters[0];
-          var parameters = featureWithParameters[1];
+        if (featuresWithParameters) {
+          for (var i = 0; i < featuresWithParameters.length; ++i) {
+            var feature = featuresWithParameters[i];
+            var featureName = feature[0];
+            var parameters = feature[1];
           output(`
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        ${feature}, {`);
-          for (var parameter of parameters) {
-            var parameterName = parameter[0];
-            var parameterValue = parameter[1];
-            output(`
+    scoped_feature_list${i}_.InitAndEnableFeatureWithParameters(
+        ${featureName}, {`);
+            for (var parameter of parameters) {
+              var parameterName = parameter[0];
+              var parameterValue = parameter[1];
+              output(`
             {"${parameterName}", "${parameterValue}"},`);
-          }
-          output(`
+            }
+            output(`
     });`);
+          }
         }
         output(`
   }`);
@@ -453,9 +456,17 @@
       output(`
   }`);
       }
-      if (featureList || featureWithParameters) {
+      if (featureList || featuresWithParameters) {
+        if (featureList) {
         output(`
   base::test::ScopedFeatureList scoped_feature_list_;`);
+        }
+        if (featuresWithParameters) {
+          for (var i = 0; i < featuresWithParameters.length; ++i) {
+            output(`
+  base::test::ScopedFeatureList scoped_feature_list${i}_;`);
+          }
+        }
       }
       output(`
 };
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index c264918..bc81d58 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -150,7 +150,13 @@
             chrome_processes = chromedriver_process.children()
             if len(chrome_processes) == 1:
               # Remove core file size limit, then use SIGABRT to dump core.
-              chrome_processes[0].rlimit(
+              # Newer versions of psutil.Process have rlimit method, while older
+              # versions have set_rlimit method.
+              if hasattr(chrome_processes[0], 'rlimit'):
+                rlimit_method = chrome_processes[0].rlimit
+              else:
+                rlimit_method = chrome_processes[0].set_rlimit
+              rlimit_method(
                   psutil.RLIMIT_CORE,
                   (psutil.RLIM_INFINITY, psutil.RLIM_INFINITY))
               chrome_processes[0].send_signal(signal.SIGABRT)
diff --git a/chrome/test/data/extensions/api_test/content_scripts/messaging/background.js b/chrome/test/data/extensions/api_test/content_scripts/messaging/background.js
new file mode 100644
index 0000000..c6a3506
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/messaging/background.js
@@ -0,0 +1,9 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.getConfig(function(config) {
+  var testUrl =
+      `http://localhost:${config.testServer.port}/extensions/test_file.html`;
+  chrome.tabs.create({url: testUrl});
+});
diff --git a/chrome/test/data/extensions/api_test/content_scripts/messaging/content_script.js b/chrome/test/data/extensions/api_test/content_scripts/messaging/content_script.js
new file mode 100644
index 0000000..534889e
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/messaging/content_script.js
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var TEST_MESSAGE = 'test_message';
+
+function assertValidExternalMessageSender(sender) {
+  chrome.test.assertEq(chrome.runtime.id, sender.id);
+  chrome.test.assertEq(undefined, sender.tlsChannelId);
+  // TODO(crbug.com/931962): Enable after the API implementation gets fixed:
+  // chrome.test.assertEq(undefined, sender.tab);
+  // chrome.test.assertEq(undefined, sender.frameId);
+  // chrome.test.assertEq(undefined, sender.url);
+}
+
+chrome.test.runTests([
+  // Test that the API that allows to directly communicate with other content
+  // scripts is unavailable.
+  function testNoTabsMessagingApi() {
+    chrome.test.assertTrue(!chrome.tabs);
+    chrome.test.succeed();
+  },
+  // Test that we can successfully send a message to an extension that doesn't
+  // have custom externally_connectable manifest property.
+  function testMessageToExtensionAllowingByDefault() {
+    chrome.runtime.sendMessage(
+        'badpbjaedophlnacllhobhnbcgomhbcd',
+        TEST_MESSAGE,
+        function(response) {
+          chrome.test.assertEq(TEST_MESSAGE, response.receivedMessage);
+          assertValidExternalMessageSender(response.receivedSender);
+          chrome.test.succeed();
+        });
+  },
+  // Test that we can successfully send a message to an extension that specifies
+  // our extension ID in its externally_connectable manifest property.
+  function testMessageToAllowingExtension() {
+    chrome.runtime.sendMessage(
+        'pmnfaklgffejbafjijfofbcianldmhci',
+        TEST_MESSAGE,
+        function(response) {
+          chrome.test.assertEq(TEST_MESSAGE, response.receivedMessage);
+          assertValidExternalMessageSender(response.receivedSender);
+          chrome.test.succeed();
+        });
+  },
+  // Test that we cannot send a message to an extension that has custom
+  // externally_connectable manifest property, but doesn't contain our extension
+  // ID there.
+  function testMessageToDenyingExtension() {
+    chrome.runtime.sendMessage(
+        'gcdagggcealpldjgchchljhjlhikcmco',
+        TEST_MESSAGE,
+        function(response) {
+          chrome.test.assertLastError('Could not establish connection. ' +
+                                      'Receiving end does not exist.');
+          chrome.test.succeed();
+        });
+  },
+]);
diff --git a/chrome/test/data/extensions/api_test/content_scripts/messaging/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/messaging/manifest.json
new file mode 100644
index 0000000..ea44c723
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/messaging/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "content_script_messaging (ID iehnoephgajegjjclojodjindjofbkcb)",
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzdFXpwj4YHkWuwNA6fp+7GdrSjXcDvDxTPWNeQ1rfINDvirEZ6a1GRi/i29I0mW9xFNCQ7zaM/EGWqDfyEoWg2AHxGEK4VXA1aseQFNfhF7oGPWCu8v9QdwI940TTSN7rZWHj2GezTzToTl9QJ5VKekYf5wWlnbZ1t60OuWlPRxKVEeG5a8JPYpkFcOCuxfAm4fJ+es0lYBsSDPqCOTNyBBTQmuh9hoAfBaU1LZzbWqFfJUITHx9N7qNiC1ksGuJQ4zn8xhRNngSKO/L0I7am9/J8imA3aW6H7eLmVJxgRG0USCwlmR+1rsDepD9gzy4bmf9GcOh3C2qVVC4FvttXQIDAQAB",
+  "version": "1.0",
+  "manifest_version": 2,
+  "description": "Tests the message passing behavior from a content script.",
+  "background": {
+    "persistent": true,
+    "scripts": ["background.js"]
+  },
+  "content_scripts": [
+    {
+      "matches": ["http://localhost:*/extensions/test_file.html"],
+      "js": ["content_script.js"]
+    }
+  ]
+}
diff --git a/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows/background.js b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows/background.js
new file mode 100644
index 0000000..3c30766
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows/background.js
@@ -0,0 +1,8 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.runtime.onMessageExternal.addListener(
+    function(message, sender, sendResponse) {
+      sendResponse({receivedMessage: message, receivedSender: sender});
+    });
diff --git a/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows/manifest.json
new file mode 100644
index 0000000..3280542
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows/manifest.json
@@ -0,0 +1,9 @@
+{
+  "name": "content_script_message_echoer_allows (ID pmnfaklgffejbafjijfofbcianldmhci)",
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp2mL/rBp2YqOLYcBkXOPdsONV3q13ra7IpnBu4c5v6bzbZ98FljOoBPhxOGHCEIC9xAaENLwQjV0dfVmbJhKJFmEj/HylOZW1t/YZfO7EMqFjOk2eJ6bH2tVHIJpH5jR1LJxpAsJJkkaG7H7eHBMl9FO96Scrs5js+t9WpuDRK8vBBU/6bl7nGhvvHHVn6EjasLXf3Dk/D/0pBmXcqkyF/PXl0qQjguwd32nSipqZJQiepnL47gie2ILLC/87aa7rntk+j1i59y5tFMb+OGtbh930phP68oARgRvBPrVKQ7npn22Bwn5Bt4nmeT41P2k0MGG3wklCEzlv9vfs+eDdQIDAQAB",
+  "version": "1.0",
+  "manifest_version": 2,
+  "description": "Is used in tests to verify that a content script can talk to an extension that has it listed in the external_connectible value.",
+  "background": { "scripts": ["background.js"] },
+  "externally_connectable": {"ids": ["iehnoephgajegjjclojodjindjofbkcb"]}
+}
diff --git a/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows_by_default/background.js b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows_by_default/background.js
new file mode 100644
index 0000000..3c30766
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows_by_default/background.js
@@ -0,0 +1,8 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.runtime.onMessageExternal.addListener(
+    function(message, sender, sendResponse) {
+      sendResponse({receivedMessage: message, receivedSender: sender});
+    });
diff --git a/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows_by_default/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows_by_default/manifest.json
new file mode 100644
index 0000000..ab18c55
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_allows_by_default/manifest.json
@@ -0,0 +1,8 @@
+{
+  "name": "content_script_message_echoer_allows_by_default (ID badpbjaedophlnacllhobhnbcgomhbcd)",
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp3+k29aqv3R+3awyoTtSKln7fJhzsmn1u9RD47fixNZW8PPweiuRKmg30Ctm2hrB0y+UU0EJpUkjSPqxUkW+2NnNAABhsdb75BMJffuQhTyOJGw60IPTLE31kHOoGsSS6wTUGI6+qUS/3PyTL+/eW+pHlGgxH4+Zjyp8UgdOIaitbKwDL6kJjXXiCBx3RmhyLr+xPSHfzrg/BQ8bQfNMOt2s0leINuBds605BJjWXZ6Yu3D96jecSCy1C7MtV4+WY6f/4TLCD0ZhmQhYsvOKZ9hPC6pV3Ui1KDqeVp58JZfsPP9/bpVqz1jdg384wJs4QXjrZNzHxRKDBPw/G1EeuQIDAQAB",
+  "version": "1.0",
+  "manifest_version": 2,
+  "description": "Is used in tests to verify that a content script can talk to an extension that has no custom external_connectible value.",
+  "background": { "scripts": ["background.js"] }
+}
diff --git a/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_denies/background.js b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_denies/background.js
new file mode 100644
index 0000000..3c30766
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_denies/background.js
@@ -0,0 +1,8 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.runtime.onMessageExternal.addListener(
+    function(message, sender, sendResponse) {
+      sendResponse({receivedMessage: message, receivedSender: sender});
+    });
diff --git a/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_denies/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_denies/manifest.json
new file mode 100644
index 0000000..2c4ea37
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/other_extensions/message_echoer_denies/manifest.json
@@ -0,0 +1,9 @@
+{
+  "name": "content_script_message_echoer_denies (ID gcdagggcealpldjgchchljhjlhikcmco)",
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArQY26TQt78EYprMLn1l2zanD6RNSVG6Eky7bIzg6qpNEt/fT9ghIHAjs0SxpcCnhEfGPbNvA80fmK9Lq0eMBcqW76JA99sFwiG/WgyLDnAuiabuMJ56WNoNi37xjbPjPvLQHGb+06fny6s+SMpm76+B/hMK8BvySd7d/EOgcFxSXNrhWwxPXKx2uaFU6gza+rqCiVxHt/pjoKaas1mTlvPsOiW6DPvtgDujxpPUum5RmusUa8c/jtPFY0Rhe32h7NoxJtYD6nDGC1KzJ9lHaPsNtjGaFdyhRihyMtr+IVK68WnelTI9GYLJU1fbrfIYBlP0D2dTDH8sg+iiVj4BKMwIDAQAB",
+  "version": "1.0",
+  "manifest_version": 2,
+  "description": "Is used in tests to verify that a content script can NOT talk to an extension that has it missing from the non-defaulted external_connectible value.",
+  "background": { "scripts": ["background.js"] },
+  "externally_connectable": {"ids": []}
+}
diff --git a/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/background.js b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/background.js
new file mode 100644
index 0000000..5be1ee8
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/background.js
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var callbackFail = chrome.test.callbackFail;
+
+var emptyCallback = function() {};
+var expectedError = 'Not implemented';
+
+chrome.test.runTests([
+  function addDynamicRules() {
+    chrome.declarativeNetRequest.addDynamicRules(
+        [], callbackFail(expectedError, emptyCallback));
+  },
+  function removeDynamicRules() {
+    chrome.declarativeNetRequest.removeDynamicRules(
+        [], callbackFail(expectedError, emptyCallback));
+  },
+  function getDynamicRules() {
+    chrome.declarativeNetRequest.getDynamicRules(
+        callbackFail(expectedError, function(rules) {}));
+  }
+]);
diff --git a/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/manifest.json b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/manifest.json
new file mode 100644
index 0000000..b494386
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "Test extension",
+  "declarative_net_request": {
+    "rule_resources": [
+      "rules_file_empty.json"
+    ]
+  },
+  "manifest_version": 2,
+  "permissions": [
+    "declarativeNetRequest"
+  ],
+  "version": "1.0",
+  "background": {
+    "scripts": [
+      "background.js"
+    ]
+  }
+}
diff --git a/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/rules_file_empty.json b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/rules_file_empty.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/rules_file_empty.json
@@ -0,0 +1 @@
+[]
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index e13e316..c37e6d9 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -56,6 +56,7 @@
     "cr_elements/cr_elements_browsertest.js",
     "find_shortcut_behavior_browsertest.js",
     "history/history_browsertest.js",
+    "js2gtest_browsertest.js",
     "load_time_data_browsertest.js",
     "media_router/media_router_elements_browsertest.js",
     "mock4js_browsertest.js",
diff --git a/chrome/test/data/webui/app_management/app_management_browsertest.js b/chrome/test/data/webui/app_management/app_management_browsertest.js
index bed944d..1f22924 100644
--- a/chrome/test/data/webui/app_management/app_management_browsertest.js
+++ b/chrome/test/data/webui/app_management/app_management_browsertest.js
@@ -19,7 +19,9 @@
   browsePreload: 'chrome://apps',
 
   extraLibraries: PolymerTest.getLibraries(ROOT_PATH).concat([
+    '../test_store.js',
     'test_util.js',
+    'test_store.js',
   ]),
 
   featureList: ['features::kAppManagement', ''],
@@ -98,6 +100,20 @@
   mocha.run();
 });
 
+function AppManagementRouterTest() {}
+
+AppManagementRouterTest.prototype = {
+  __proto__: AppManagementBrowserTest.prototype,
+
+  extraLibraries: AppManagementBrowserTest.prototype.extraLibraries.concat([
+    'router_test.js',
+  ]),
+};
+
+TEST_F('AppManagementRouterTest', 'All', function() {
+  mocha.run();
+});
+
 function AppManagementPwaPermissionViewTest() {}
 
 AppManagementPwaPermissionViewTest.prototype = {
diff --git a/chrome/test/data/webui/app_management/main_view_test.js b/chrome/test/data/webui/app_management/main_view_test.js
index 379591e..f14d9c0 100644
--- a/chrome/test/data/webui/app_management/main_view_test.js
+++ b/chrome/test/data/webui/app_management/main_view_test.js
@@ -21,13 +21,11 @@
   setup(function() {
     appIdCounter = 0;
 
-    mainView = document.createElement('app-management-main-view');
-    PolymerTest.clearBody();
-
     fakeHandler = setupFakeHandler();
     replaceStore();
 
-    document.body.appendChild(mainView);
+    mainView = document.createElement('app-management-main-view');
+    replaceBody(mainView);
   });
 
   test('simple app addition', async function() {
diff --git a/chrome/test/data/webui/app_management/metadata_view_test.js b/chrome/test/data/webui/app_management/metadata_view_test.js
index c337bc5f..8c18b4a 100644
--- a/chrome/test/data/webui/app_management/metadata_view_test.js
+++ b/chrome/test/data/webui/app_management/metadata_view_test.js
@@ -11,9 +11,6 @@
   const APP_ID = '1';
 
   setup(async function() {
-    metadataView = document.createElement('app-management-metadata-view');
-
-    PolymerTest.clearBody();
     fakeHandler = setupFakeHandler();
     replaceStore();
 
@@ -22,7 +19,8 @@
     app_management.Store.getInstance().dispatch(
         app_management.actions.changePage(PageType.DETAIL, APP_ID));
 
-    document.body.appendChild(metadataView);
+    metadataView = document.createElement('app-management-metadata-view');
+    replaceBody(metadataView);
   });
 
   test(
diff --git a/chrome/test/data/webui/app_management/pwa_permission_view_test.js b/chrome/test/data/webui/app_management/pwa_permission_view_test.js
index c69f3979..93198f2 100644
--- a/chrome/test/data/webui/app_management/pwa_permission_view_test.js
+++ b/chrome/test/data/webui/app_management/pwa_permission_view_test.js
@@ -27,10 +27,6 @@
   }
 
   setup(async function() {
-    pwaPermissionView =
-        document.createElement('app-management-pwa-permission-view');
-    PolymerTest.clearBody();
-
     fakeHandler = setupFakeHandler();
     replaceStore();
 
@@ -38,7 +34,9 @@
     await fakeHandler.addApp(TEST_APP_ID);
     app_management.Store.getInstance().dispatch(
         app_management.actions.changePage(PageType.DETAIL, TEST_APP_ID));
-    document.body.appendChild(pwaPermissionView);
+    pwaPermissionView =
+        document.createElement('app-management-pwa-permission-view');
+    replaceBody(pwaPermissionView);
   });
 
   test('App is rendered correctly', function() {
diff --git a/chrome/test/data/webui/app_management/reducers_test.js b/chrome/test/data/webui/app_management/reducers_test.js
index 9ba3f21..bdb1040 100644
--- a/chrome/test/data/webui/app_management/reducers_test.js
+++ b/chrome/test/data/webui/app_management/reducers_test.js
@@ -6,6 +6,7 @@
 
 suite('app state', function() {
   let apps;
+  let state;
 
   setup(function() {
     apps = {
diff --git a/chrome/test/data/webui/app_management/router_test.js b/chrome/test/data/webui/app_management/router_test.js
new file mode 100644
index 0000000..a03bcc1
--- /dev/null
+++ b/chrome/test/data/webui/app_management/router_test.js
@@ -0,0 +1,87 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+suite('<app-management-router>', function() {
+  let store;
+  let router;
+  let fakeHandler;
+
+  async function navigateTo(route) {
+    window.history.replaceState({}, '', route);
+    window.dispatchEvent(new CustomEvent('location-changed'));
+    await PolymerTest.flushTasks();
+  }
+
+  function getCurrentUrlSuffix() {
+    return window.location.href.slice(window.location.origin.length);
+  }
+
+  setup(async function() {
+    fakeHandler = setupFakeHandler();
+    store = new app_management.TestStore();
+    await fakeHandler.addApp('1');
+    store.replaceSingleton();
+    router = document.createElement('app-management-router');
+    replaceBody(router);
+  });
+
+  test('search updates from route', async function() {
+    await navigateTo('/?q=beep');
+    const expected = app_management.actions.setSearchTerm('beep');
+    assertDeepEquals(expected, store.lastAction);
+  });
+
+  test('selected app updates from route', async function() {
+    await navigateTo('/detail?id=1');
+    const expected = app_management.actions.changePage(PageType.DETAIL, '1');
+
+    assertDeepEquals(expected, store.lastAction);
+  });
+
+  test('notifications view appears from route', async function() {
+    await navigateTo('/notifications');
+    const expected = app_management.actions.changePage(PageType.NOTIFICATIONS);
+    assertDeepEquals(expected, store.lastAction);
+  });
+
+  test('route updates from state change', async function() {
+    // The application needs an initial url to start with.
+    await navigateTo('/');
+
+    store.data.currentPage = {
+      pageType: PageType.DETAIL,
+      selectedAppId: '1',
+    };
+    store.notifyObservers();
+
+    await PolymerTest.flushTasks();
+    assertEquals('/detail?id=1', getCurrentUrlSuffix());
+
+    // Returning main page clears the route.
+    store.data.currentPage = {
+      pageType: PageType.MAIN,
+      selectedAppId: null,
+    };
+    store.notifyObservers();
+    await PolymerTest.flushTasks();
+    assertEquals('/', getCurrentUrlSuffix());
+
+    store.data.currentPage = {
+      pageType: PageType.NOTIFICATIONS,
+      selectedAppId: null,
+    };
+    store.notifyObservers();
+    await PolymerTest.flushTasks();
+    assertEquals('/notifications', getCurrentUrlSuffix());
+  });
+
+  test('route updates from search', async function() {
+    await navigateTo('/');
+    store.data.search = {term: 'bloop'};
+    store.notifyObservers();
+    await PolymerTest.flushTasks();
+
+    assertEquals('/?q=bloop', getCurrentUrlSuffix());
+  });
+});
diff --git a/chrome/test/data/webui/app_management/test_store.js b/chrome/test/data/webui/app_management/test_store.js
new file mode 100644
index 0000000..159af67
--- /dev/null
+++ b/chrome/test/data/webui/app_management/test_store.js
@@ -0,0 +1,19 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+suiteSetup(function() {
+  cr.define('app_management', function() {
+    class TestStore extends cr.ui.TestStore {
+      constructor(data) {
+        super(
+            data, app_management.Store, app_management.util.createEmptyState(),
+            app_management.reduceAction);
+      }
+    }
+
+    return {
+      TestStore: TestStore,
+    };
+  });
+});
diff --git a/chrome/test/data/webui/app_management/test_util.js b/chrome/test/data/webui/app_management/test_util.js
index f0482197..703cb32 100644
--- a/chrome/test/data/webui/app_management/test_util.js
+++ b/chrome/test/data/webui/app_management/test_util.js
@@ -44,3 +44,25 @@
   const rect = element.getBoundingClientRect();
   return rect.height === 0 && rect.width === 0;
 }
+
+/**
+ * Create an app for testing purpose.
+ * @param {string} id
+ * @param {Object=} optConfig
+ * @return {!App}
+ */
+function createApp(id, config) {
+  return app_management.FakePageHandler.createApp(id, config);
+}
+
+/**
+ * Replace the current body of the test with a new element.
+ * @param {Element} element
+ */
+function replaceBody(element) {
+  PolymerTest.clearBody();
+
+  window.history.replaceState({}, '', '/');
+
+  document.body.appendChild(element);
+}
diff --git a/chrome/test/data/webui/js2gtest_browsertest.js b/chrome/test/data/webui/js2gtest_browsertest.js
new file mode 100644
index 0000000..b060cec
--- /dev/null
+++ b/chrome/test/data/webui/js2gtest_browsertest.js
@@ -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.
+
+GEN(`
+#include "base/metrics/field_trial_params.h"
+
+const base::Feature kTestFeature{"TestFeature",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kTestFeatureWithParam{"TestFeatureWithParam",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+const base::FeatureParam<int> kTestFeatureWithParamCount{
+    &kTestFeatureWithParam, "count", 5};
+`);
+
+/**
+ * @constructor
+ * @extends {testing.Test}
+ */
+function JSToGtestBrowserTest() {}
+
+JSToGtestBrowserTest.prototype = {
+  __proto__: testing.Test.prototype,
+
+  browsePreload: 'chrome://dummyurl',
+
+  /** @override */
+  testGenPostamble() {
+    GEN(`
+  EXPECT_TRUE(base::FeatureList::IsEnabled(kTestFeature));
+  EXPECT_TRUE(base::FeatureList::IsEnabled(kTestFeatureWithParam));
+  EXPECT_EQ(5,
+      base::GetFieldTrialParamByFeatureAsInt(kTestFeatureWithParam,
+      kTestFeatureWithParamCount.name, 0));`);
+  },
+
+  /** @override */
+  featureList: ['kTestFeature', ''],
+
+  /** @override */
+  featuresWithParameters: [
+    ['kTestFeatureWithParam', [['count', '5']]],
+  ],
+};
+
+TEST_F('JSToGtestBrowserTest', 'TestFeatureEnabling', function() {
+  // Just to be generated.
+});
diff --git a/chrome/test/data/webui/print_preview/pages_settings_test.js b/chrome/test/data/webui/print_preview/pages_settings_test.js
index 11012e1..a82f06b 100644
--- a/chrome/test/data/webui/print_preview/pages_settings_test.js
+++ b/chrome/test/data/webui/print_preview/pages_settings_test.js
@@ -244,8 +244,8 @@
 
     // Tests that the clearing a valid input has no effect, clearing an invalid
     // input does not show an error message but does not reset the preview, and
-    // changing focus from an empty input in either case automatically reselects
-    // the "all" radio button.
+    // changing focus from an empty input in either case fills in the dropdown
+    // with the full page range.
     test(assert(TestNames.ClearInput), function() {
       const input = pagesSection.$.pageSettingsCustomInput.inputElement;
       const select = pagesSection.$$('select');
@@ -266,28 +266,56 @@
             return whenBlurred;
           })
           .then(function() {
-            // Blurring does not change the state.
+            // Blurring a blank field sets the full page range.
             assertEquals(customValue, select.value);
-            validateState([1, 2], '', false);
-            assertEquals('1-2', input.value);
+            validateState([1, 2, 3], '', false);
+            assertEquals('1-3', input.value);
             return setupInput('5', 3);
           })
           .then(function() {
             assertEquals(customValue, select.value);
-            validateState([1, 2], limitError + '3', true);
+            // Invalid input doesn't change the preview.
+            validateState([1, 2, 3], limitError + '3', true);
             return setupInput('', 3);
           })
           .then(function() {
             assertEquals(customValue, select.value);
-            validateState([1, 2], '', false);
+            validateState([1, 2, 3], '', false);
             const whenBlurred = test_util.eventToPromise('blur', input);
             input.blur();
+            // Blurring an invalid value that has been cleared should reset the
+            // value to all pages.
             return whenBlurred;
           })
           .then(function() {
             assertEquals(customValue, select.value);
-            validateState([1, 2], '', false);
-            assertEquals('1-2', input.value);
+            validateState([1, 2, 3], '', false);
+            assertEquals('1-3', input.value);
+
+            // Clear the input and then select "All" in the dropdown.
+            input.focus();
+            return setupInput('', 3);
+          })
+          .then(function() {
+            select.focus();
+            select.value = pagesSection.pagesValueEnum_.ALL.toString();
+            select.dispatchEvent(new CustomEvent('change'));
+            return test_util.eventToPromise(
+                'process-select-change', pagesSection);
+          })
+          .then(function() {
+            Polymer.dom.flush();
+            assertEquals(allValue, select.value);
+            validateState([1, 2, 3], '', false);
+            // Reselect custom.
+            select.value = pagesSection.pagesValueEnum_.CUSTOM.toString();
+            select.dispatchEvent(new CustomEvent('change'));
+            return test_util.eventToPromise('focus', input);
+          })
+          .then(function() {
+            // Input has been cleared.
+            assertEquals('', input.value);
+            validateState([1, 2, 3], '', false);
           });
     });
 
diff --git a/chrome/test/data/webui/welcome/nux_ntp_background_test.js b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
index bb08006..5073adc 100644
--- a/chrome/test/data/webui/welcome/nux_ntp_background_test.js
+++ b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
@@ -10,11 +10,13 @@
         id: 0,
         title: 'Cat',
         imageUrl: 'some/cute/photo/of/a/cat',
+        thumbnailClass: 'cat',
       },
       {
         id: 1,
         title: 'Venice',
         imageUrl: 'some/scenic/photo/of/a/beach',
+        thumbnailClass: 'venice',
       },
     ];
 
diff --git a/chromecast/browser/cast_browser_context.cc b/chromecast/browser/cast_browser_context.cc
index 9827c6da..950d8e5 100644
--- a/chromecast/browser/cast_browser_context.cc
+++ b/chromecast/browser/cast_browser_context.cc
@@ -5,8 +5,11 @@
 #include "chromecast/browser/cast_browser_context.h"
 
 #include <memory>
+#include <utility>
 
+#include "base/barrier_closure.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/macros.h"
 #include "base/path_service.h"
@@ -16,21 +19,26 @@
 #include "chromecast/browser/cast_permission_manager.h"
 #include "chromecast/browser/url_request_context_factory.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/cors_origin_pattern_setter.h"
 #include "content/public/browser/resource_context.h"
+#include "content/public/browser/shared_cors_origin_access_list.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/content_switches.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
+#include "services/network/public/cpp/features.h"
 
 namespace chromecast {
 namespace shell {
 
 namespace {
 const void* const kDownloadManagerDelegateKey = &kDownloadManagerDelegateKey;
-}
+}  // namespace
 
-class CastBrowserContext::CastResourceContext :
-    public content::ResourceContext {
+using content::CorsOriginPatternSetter;
+
+class CastBrowserContext::CastResourceContext
+    : public content::ResourceContext {
  public:
   CastResourceContext() {}
   ~CastResourceContext() override {}
@@ -42,17 +50,17 @@
 CastBrowserContext::CastBrowserContext(
     URLRequestContextFactory* url_request_context_factory)
     : url_request_context_factory_(url_request_context_factory),
-      resource_context_(new CastResourceContext) {
+      resource_context_(new CastResourceContext),
+      shared_cors_origin_access_list_(
+          content::SharedCorsOriginAccessList::Create()) {
   InitWhileIOAllowed();
 }
 
 CastBrowserContext::~CastBrowserContext() {
   BrowserContext::NotifyWillBeDestroyed(this);
   ShutdownStoragePartitions();
-  content::BrowserThread::DeleteSoon(
-      content::BrowserThread::IO,
-      FROM_HERE,
-      resource_context_.release());
+  content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE,
+                                     resource_context_.release());
 }
 
 void CastBrowserContext::InitWhileIOAllowed() {
@@ -171,9 +179,45 @@
 
 net::URLRequestContextGetter*
 CastBrowserContext::CreateMediaRequestContextForStoragePartition(
-    const base::FilePath& partition_path, bool in_memory) {
+    const base::FilePath& partition_path,
+    bool in_memory) {
   return nullptr;
 }
 
+void CastBrowserContext::SetCorsOriginAccessListForOrigin(
+    const url::Origin& source_origin,
+    std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns,
+    std::vector<network::mojom::CorsOriginPatternPtr> block_patterns,
+    base::OnceClosure closure) {
+  if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    shared_cors_origin_access_list_->SetForOrigin(
+        source_origin, std::move(allow_patterns), std::move(block_patterns),
+        std::move(closure));
+  } else {
+    auto barrier_closure = BarrierClosure(2, std::move(closure));
+
+    // Keep profile storage partitions' NetworkContexts synchronized.
+    auto profile_setter = base::MakeRefCounted<CorsOriginPatternSetter>(
+        source_origin, CorsOriginPatternSetter::ClonePatterns(allow_patterns),
+        CorsOriginPatternSetter::ClonePatterns(block_patterns),
+        barrier_closure);
+    ForEachStoragePartition(
+        this, base::BindRepeating(&CorsOriginPatternSetter::SetLists,
+                                  base::RetainedRef(profile_setter.get())));
+
+    // Keep the per-profile access list up to date so that we can use this to
+    // restore NetworkContext settings at anytime, e.g. on restarting the
+    // network service.
+    shared_cors_origin_access_list_->SetForOrigin(
+        source_origin, std::move(allow_patterns), std::move(block_patterns),
+        barrier_closure);
+  }
+}
+
+const content::SharedCorsOriginAccessList*
+CastBrowserContext::GetSharedCorsOriginAccessList() const {
+  return shared_cors_origin_access_list_.get();
+}
+
 }  // namespace shell
 }  // namespace chromecast
diff --git a/chromecast/browser/cast_browser_context.h b/chromecast/browser/cast_browser_context.h
index aa9d6896..f77f0d2 100644
--- a/chromecast/browser/cast_browser_context.h
+++ b/chromecast/browser/cast_browser_context.h
@@ -5,6 +5,8 @@
 #ifndef CHROMECAST_BROWSER_CAST_BROWSER_CONTEXT_H_
 #define CHROMECAST_BROWSER_CAST_BROWSER_CONTEXT_H_
 
+#include <vector>
+
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "content/public/browser/browser_context.h"
@@ -60,6 +62,14 @@
 
   net::URLRequestContextGetter* GetSystemRequestContext();
 
+  void SetCorsOriginAccessListForOrigin(
+      const url::Origin& source_origin,
+      std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns,
+      std::vector<network::mojom::CorsOriginPatternPtr> block_patterns,
+      base::OnceClosure closure) override;
+  const content::SharedCorsOriginAccessList* GetSharedCorsOriginAccessList()
+      const override;
+
  private:
   class CastResourceContext;
 
@@ -71,6 +81,8 @@
   base::FilePath path_;
   std::unique_ptr<CastResourceContext> resource_context_;
   std::unique_ptr<content::PermissionControllerDelegate> permission_manager_;
+  scoped_refptr<content::SharedCorsOriginAccessList>
+      shared_cors_origin_access_list_;
 
   DISALLOW_COPY_AND_ASSIGN(CastBrowserContext);
 };
diff --git a/chromeos/components/drivefs/drivefs_host_unittest.cc b/chromeos/components/drivefs/drivefs_host_unittest.cc
index b3fd27f..5754435 100644
--- a/chromeos/components/drivefs/drivefs_host_unittest.cc
+++ b/chromeos/components/drivefs/drivefs_host_unittest.cc
@@ -433,6 +433,10 @@
 };
 
 TEST_F(DriveFsHostTest, Basic) {
+  MockDriveFsHostObserver observer;
+  ScopedObserver<DriveFsHost, DriveFsHostObserver> observer_scoper(&observer);
+  observer_scoper.Add(host_.get());
+
   EXPECT_FALSE(host_->IsMounted());
 
   EXPECT_EQ(base::FilePath("/path/to/profile/GCache/v2/salt-g-ID"),
@@ -444,7 +448,12 @@
   EXPECT_EQ(base::FilePath("/media/drivefsroot/salt-g-ID"),
             host_->GetMountPath());
 
-  DoUnmount();
+  EXPECT_CALL(observer, OnUnmounted());
+  EXPECT_CALL(*host_delegate_, OnUnmounted(_)).Times(0);
+  base::RunLoop run_loop;
+  delegate_ptr_.set_connection_error_handler(run_loop.QuitClosure());
+  host_->Unmount();
+  run_loop.Run();
 }
 
 TEST_F(DriveFsHostTest, GetMountPathWhileUnmounted) {
@@ -452,31 +461,6 @@
             host_->GetMountPath());
 }
 
-TEST_F(DriveFsHostTest, OnMountedBeforeMountEvent) {
-  auto token = StartMount();
-  ASSERT_TRUE(PendingConnectionManager::Get().OpenIpcChannel(token, {}));
-  SendOnMounted();
-  EXPECT_CALL(*host_delegate_, OnMounted(_)).Times(0);
-  delegate_ptr_.FlushForTesting();
-
-  testing::Mock::VerifyAndClear(host_delegate_.get());
-
-  EXPECT_FALSE(host_->IsMounted());
-
-  EXPECT_CALL(*host_delegate_,
-              OnMounted(base::FilePath("/media/drivefsroot/salt-g-ID")));
-  EXPECT_CALL(*disk_manager_, UnmountPath("/media/drivefsroot/salt-g-ID",
-                                          chromeos::UNMOUNT_OPTIONS_NONE, _));
-
-  DispatchMountSuccessEvent(token);
-
-  ASSERT_TRUE(host_->IsMounted());
-  EXPECT_EQ(base::FilePath("/media/drivefsroot/salt-g-ID"),
-            host_->GetMountPath());
-
-  DoUnmount();
-}
-
 TEST_F(DriveFsHostTest, OnMountFailedFromMojo) {
   ASSERT_FALSE(host_->IsMounted());
 
@@ -492,6 +476,7 @@
 
 TEST_F(DriveFsHostTest, OnMountFailedFromDbus) {
   ASSERT_FALSE(host_->IsMounted());
+  EXPECT_CALL(*disk_manager_, UnmountPath(_, _, _)).Times(0);
 
   auto token = StartMount();
 
@@ -508,79 +493,6 @@
   run_loop.Run();
 
   ASSERT_FALSE(host_->IsMounted());
-}
-
-TEST_F(DriveFsHostTest, OnMountFailed_UnmountInObserver) {
-  ASSERT_FALSE(host_->IsMounted());
-
-  auto token = StartMount();
-
-  base::RunLoop run_loop;
-  base::OnceClosure quit_closure = run_loop.QuitClosure();
-  EXPECT_CALL(*host_delegate_, OnMountFailed(MountFailure::kInvocation, _))
-      .WillOnce(testing::InvokeWithoutArgs([&]() {
-        std::move(quit_closure).Run();
-        host_->Unmount();
-      }));
-  DispatchMountEvent(chromeos::disks::DiskMountManager::MOUNTING,
-                     chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS,
-                     {base::StrCat({"drivefs://", token}),
-                      "/media/drivefsroot/salt-g-ID",
-                      chromeos::MOUNT_TYPE_NETWORK_STORAGE,
-                      {}});
-  run_loop.Run();
-
-  ASSERT_FALSE(host_->IsMounted());
-}
-
-TEST_F(DriveFsHostTest, UnmountAfterMountComplete) {
-  MockDriveFsHostObserver observer;
-  ScopedObserver<DriveFsHost, DriveFsHostObserver> observer_scoper(&observer);
-  observer_scoper.Add(host_.get());
-
-  ASSERT_NO_FATAL_FAILURE(DoMount());
-
-  EXPECT_CALL(observer, OnUnmounted());
-  base::RunLoop run_loop;
-  delegate_ptr_.set_connection_error_handler(run_loop.QuitClosure());
-  host_->Unmount();
-  run_loop.Run();
-}
-
-TEST_F(DriveFsHostTest, UnmountBeforeMountEvent) {
-  MockDriveFsHostObserver observer;
-  ScopedObserver<DriveFsHost, DriveFsHostObserver> observer_scoper(&observer);
-  observer_scoper.Add(host_.get());
-  EXPECT_CALL(observer, OnUnmounted()).Times(0);
-
-  auto token = StartMount();
-  EXPECT_FALSE(host_->IsMounted());
-  host_->Unmount();
-  EXPECT_FALSE(PendingConnectionManager::Get().OpenIpcChannel(token, {}));
-}
-
-TEST_F(DriveFsHostTest, UnmountBeforeMojoConnection) {
-  MockDriveFsHostObserver observer;
-  ScopedObserver<DriveFsHost, DriveFsHostObserver> observer_scoper(&observer);
-  observer_scoper.Add(host_.get());
-  EXPECT_CALL(observer, OnUnmounted()).Times(0);
-
-  auto token = StartMount();
-  DispatchMountSuccessEvent(token);
-
-  EXPECT_FALSE(host_->IsMounted());
-  EXPECT_CALL(*disk_manager_, UnmountPath("/media/drivefsroot/salt-g-ID",
-                                          chromeos::UNMOUNT_OPTIONS_NONE, _));
-
-  host_->Unmount();
-  EXPECT_FALSE(PendingConnectionManager::Get().OpenIpcChannel(token, {}));
-}
-
-TEST_F(DriveFsHostTest, DestroyBeforeMountEvent) {
-  auto token = StartMount();
-  EXPECT_CALL(*disk_manager_, UnmountPath(_, _, _)).Times(0);
-
-  host_.reset();
   EXPECT_FALSE(PendingConnectionManager::Get().OpenIpcChannel(token, {}));
 }
 
@@ -594,93 +506,11 @@
   EXPECT_FALSE(PendingConnectionManager::Get().OpenIpcChannel(token, {}));
 }
 
-TEST_F(DriveFsHostTest, ObserveOtherMount) {
-  auto token = StartMount();
-  EXPECT_CALL(*disk_manager_, UnmountPath(_, _, _)).Times(0);
-
-  DispatchMountEvent(chromeos::disks::DiskMountManager::MOUNTING,
-                     chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED,
-                     {"some/other/mount/event",
-                      "/some/other/mount/point",
-                      chromeos::MOUNT_TYPE_DEVICE,
-                      {}});
-  DispatchMountEvent(chromeos::disks::DiskMountManager::UNMOUNTING,
-                     chromeos::MOUNT_ERROR_NONE,
-                     {base::StrCat({"drivefs://", token}),
-                      "/media/drivefsroot/salt-g-ID",
-                      chromeos::MOUNT_TYPE_NETWORK_STORAGE,
-                      {}});
-  EXPECT_FALSE(host_->IsMounted());
-  host_->Unmount();
-}
-
-TEST_F(DriveFsHostTest, MountError) {
-  auto token = StartMount();
-  EXPECT_CALL(*disk_manager_, UnmountPath(_, _, _)).Times(0);
-  EXPECT_CALL(*host_delegate_, OnMountFailed(MountFailure::kInvocation, _));
-
-  DispatchMountEvent(chromeos::disks::DiskMountManager::MOUNTING,
-                     chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED,
-                     {base::StrCat({"drivefs://", token}),
-                      "/media/drivefsroot/g-ID",
-                      chromeos::MOUNT_TYPE_NETWORK_STORAGE,
-                      {}});
-  EXPECT_FALSE(host_->IsMounted());
-  EXPECT_FALSE(PendingConnectionManager::Get().OpenIpcChannel(token, {}));
-}
-
 TEST_F(DriveFsHostTest, MountWhileAlreadyMounted) {
   DoMount();
   EXPECT_FALSE(host_->Mount());
 }
 
-TEST_F(DriveFsHostTest, UnmountByRemote) {
-  ASSERT_NO_FATAL_FAILURE(DoMount());
-  base::Optional<base::TimeDelta> delay = base::TimeDelta::FromSeconds(5);
-  EXPECT_CALL(*host_delegate_, OnUnmounted(delay));
-  SendOnUnmounted(delay);
-  base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(DriveFsHostTest, BreakConnectionAfterMount) {
-  ASSERT_NO_FATAL_FAILURE(DoMount());
-  base::Optional<base::TimeDelta> empty;
-  EXPECT_CALL(*host_delegate_, OnUnmounted(empty));
-  delegate_ptr_.reset();
-  base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(DriveFsHostTest, BreakConnectionBeforeMount) {
-  ASSERT_NO_FATAL_FAILURE(EstablishConnection());
-  EXPECT_FALSE(host_->IsMounted());
-
-  base::Optional<base::TimeDelta> empty;
-  EXPECT_CALL(*host_delegate_,
-              OnMountFailed(MountFailure::kIpcDisconnect, empty));
-  delegate_ptr_.reset();
-  base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(DriveFsHostTest, MountTimeout) {
-  auto token = StartMount();
-  DispatchMountSuccessEvent(token);
-  EXPECT_FALSE(host_->IsMounted());
-
-  base::Optional<base::TimeDelta> empty;
-  EXPECT_CALL(*host_delegate_, OnMountFailed(MountFailure::kTimeout, empty));
-  timer_->Fire();
-}
-
-// DiskMountManager sometimes sends mount events for all existing mount points.
-// Mount events beyond the first should be ignored.
-TEST_F(DriveFsHostTest, MultipleMountNotifications) {
-  ASSERT_NO_FATAL_FAILURE(DoMount());
-
-  // That is event is ignored is verified the the expectations set in DoMount().
-  // OnMounted() should only be invoked once.
-  DispatchMountSuccessEvent(token_);
-}
-
 TEST_F(DriveFsHostTest, UnsupportedAccountTypes) {
   EXPECT_CALL(*disk_manager_, MountPath(_, _, _, _, _, _)).Times(0);
   const AccountId unsupported_accounts[] = {
diff --git a/chromeos/network/network_state.cc b/chromeos/network/network_state.cc
index 1e5a170..8e71e93 100644
--- a/chromeos/network/network_state.cc
+++ b/chromeos/network/network_state.cc
@@ -540,8 +540,10 @@
 
 // static
 bool NetworkState::ErrorIsValid(const std::string& error) {
-  // Shill uses "Unknown" to indicate an unset or cleared error state.
-  return !error.empty() && error != kErrorUnknown;
+  // Pre M-74 Shill uses "Unknown" to indicate an unset or cleared error state.
+  // TODO(stevenjb): Remove kErrorUnknown once 74 has shipped.
+  return !error.empty() && error != kErrorUnknown &&
+         error != shill::kErrorNoFailure;
 }
 
 // static
diff --git a/components/assist_ranker/base_predictor_unittest.cc b/components/assist_ranker/base_predictor_unittest.cc
index 6b330ba..9339229 100644
--- a/components/assist_ranker/base_predictor_unittest.cc
+++ b/components/assist_ranker/base_predictor_unittest.cc
@@ -71,7 +71,7 @@
   // |predictor_config|.
   static std::unique_ptr<FakePredictor> Create(
       PredictorConfig predictor_config);
-  ~FakePredictor() override{};
+  ~FakePredictor() override {}
   // Validation will always succeed.
   static RankerModelStatus ValidateModel(const RankerModel& model) {
     return RankerModelStatus::OK;
@@ -79,7 +79,7 @@
 
  protected:
   // Not implementing any inference logic.
-  bool Initialize() override { return true; };
+  bool Initialize() override { return true; }
 
  private:
   FakePredictor(const PredictorConfig& config) : BasePredictor(config) {}
diff --git a/components/assist_ranker/binary_classifier_predictor.cc b/components/assist_ranker/binary_classifier_predictor.cc
index 402aa59..54e1eb98 100644
--- a/components/assist_ranker/binary_classifier_predictor.cc
+++ b/components/assist_ranker/binary_classifier_predictor.cc
@@ -19,8 +19,8 @@
 
 BinaryClassifierPredictor::BinaryClassifierPredictor(
     const PredictorConfig& config)
-    : BasePredictor(config){};
-BinaryClassifierPredictor::~BinaryClassifierPredictor(){};
+    : BasePredictor(config) {}
+BinaryClassifierPredictor::~BinaryClassifierPredictor() {}
 
 // static
 std::unique_ptr<BinaryClassifierPredictor> BinaryClassifierPredictor::Create(
diff --git a/components/assist_ranker/classifier_predictor.cc b/components/assist_ranker/classifier_predictor.cc
index 31e90be..80fa065 100644
--- a/components/assist_ranker/classifier_predictor.cc
+++ b/components/assist_ranker/classifier_predictor.cc
@@ -21,8 +21,8 @@
 namespace assist_ranker {
 
 ClassifierPredictor::ClassifierPredictor(const PredictorConfig& config)
-    : BasePredictor(config){};
-ClassifierPredictor::~ClassifierPredictor(){};
+    : BasePredictor(config) {}
+ClassifierPredictor::~ClassifierPredictor() {}
 
 // static
 std::unique_ptr<ClassifierPredictor> ClassifierPredictor::Create(
diff --git a/components/browser_sync/browser_sync_switches.cc b/components/browser_sync/browser_sync_switches.cc
index e970f0a0..39d8763 100644
--- a/components/browser_sync/browser_sync_switches.cc
+++ b/components/browser_sync/browser_sync_switches.cc
@@ -4,6 +4,8 @@
 
 #include "components/browser_sync/browser_sync_switches.h"
 
+#include "base/command_line.h"
+
 namespace switches {
 
 // Disables syncing browser data to a Google Account.
@@ -24,4 +26,9 @@
 // flag is present.
 const char kLocalSyncBackendDir[] = "local-sync-backend-dir";
 
+bool IsSyncAllowedByFlag() {
+  return !base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kDisableSync);
+}
+
 }  // namespace switches
diff --git a/components/browser_sync/browser_sync_switches.h b/components/browser_sync/browser_sync_switches.h
index fa70667..de0323c 100644
--- a/components/browser_sync/browser_sync_switches.h
+++ b/components/browser_sync/browser_sync_switches.h
@@ -14,6 +14,12 @@
 extern const char kEnableLocalSyncBackend[];
 extern const char kLocalSyncBackendDir[];
 
+// Returns whether sync is allowed to run based on command-line switches.
+// Profile::IsSyncAllowed() is probably a better signal than this function.
+// This function can be called from any thread, and the implementation doesn't
+// assume it's running on the UI thread.
+bool IsSyncAllowedByFlag();
+
 }  // namespace switches
 
 #endif  // COMPONENTS_BROWSER_SYNC_BROWSER_SYNC_SWITCHES_H_
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc
index dbd27d7..259b581 100644
--- a/components/browser_sync/profile_sync_service.cc
+++ b/components/browser_sync/profile_sync_service.cc
@@ -201,7 +201,7 @@
 
   // If Sync is disabled via command line flag, then ProfileSyncService
   // shouldn't be instantiated.
-  DCHECK(IsSyncAllowedByFlag());
+  DCHECK(switches::IsSyncAllowedByFlag());
 
   std::string last_version = sync_prefs_.GetLastRunVersion();
   std::string current_version = PRODUCT_VERSION;
@@ -706,7 +706,7 @@
 
   // If Sync is disabled via command line flag, then ProfileSyncService
   // shouldn't even be instantiated.
-  DCHECK(IsSyncAllowedByFlag());
+  DCHECK(switches::IsSyncAllowedByFlag());
 
   int result = DISABLE_REASON_NONE;
   if (!user_settings_->IsSyncAllowedByPlatform()) {
@@ -1885,12 +1885,6 @@
   return sync_js_controller_.AsWeakPtr();
 }
 
-// static
-bool ProfileSyncService::IsSyncAllowedByFlag() {
-  return !base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kDisableSync);
-}
-
 void ProfileSyncService::StopAndClear() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
diff --git a/components/browser_sync/profile_sync_service.h b/components/browser_sync/profile_sync_service.h
index ad19786..6c680db7 100644
--- a/components/browser_sync/profile_sync_service.h
+++ b/components/browser_sync/profile_sync_service.h
@@ -287,12 +287,6 @@
     return crypto_.passphrase_required_reason();
   }
 
-  // Returns whether sync is allowed to run based on command-line switches.
-  // Profile::IsSyncAllowed() is probably a better signal than this function.
-  // This function can be called from any thread, and the implementation doesn't
-  // assume it's running on the UI thread.
-  static bool IsSyncAllowedByFlag();
-
   // syncer::UnrecoverableErrorHandler implementation.
   void OnUnrecoverableError(const base::Location& from_here,
                             const std::string& message) override;
diff --git a/components/browser_sync/profile_sync_service_unittest.cc b/components/browser_sync/profile_sync_service_unittest.cc
index 525d07b7..7c54c0d 100644
--- a/components/browser_sync/profile_sync_service_unittest.cc
+++ b/components/browser_sync/profile_sync_service_unittest.cc
@@ -1111,12 +1111,12 @@
 // Verify that the disable sync flag disables sync.
 TEST_F(ProfileSyncServiceTest, DisableSyncFlag) {
   base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kDisableSync);
-  EXPECT_FALSE(ProfileSyncService::IsSyncAllowedByFlag());
+  EXPECT_FALSE(switches::IsSyncAllowedByFlag());
 }
 
 // Verify that no disable sync flag enables sync.
 TEST_F(ProfileSyncServiceTest, NoDisableSyncFlag) {
-  EXPECT_TRUE(ProfileSyncService::IsSyncAllowedByFlag());
+  EXPECT_TRUE(switches::IsSyncAllowedByFlag());
 }
 
 // Test Sync will stop after receive memory pressure
diff --git a/components/exo/wayland/zcr_keyboard_configuration.cc b/components/exo/wayland/zcr_keyboard_configuration.cc
index 1aee392..d986c54 100644
--- a/components/exo/wayland/zcr_keyboard_configuration.cc
+++ b/components/exo/wayland/zcr_keyboard_configuration.cc
@@ -24,7 +24,7 @@
 // Send a keyboard layout name instead of XKB contents.
 // TODO(tetsui): Remove when the change becomes default.
 const base::Feature kSendKeyboardLayoutNameFeature{
-    "ExoSendKeyboardLayoutName", base::FEATURE_DISABLED_BY_DEFAULT};
+    "ExoSendKeyboardLayoutName", base::FEATURE_ENABLED_BY_DEFAULT};
 
 ////////////////////////////////////////////////////////////////////////////////
 // keyboard_device_configuration interface:
diff --git a/components/plugins/renderer/webview_plugin.cc b/components/plugins/renderer/webview_plugin.cc
index 4bed940..af53aae 100644
--- a/components/plugins/renderer/webview_plugin.cc
+++ b/components/plugins/renderer/webview_plugin.cc
@@ -69,7 +69,10 @@
   WebViewPlugin* plugin = new WebViewPlugin(render_view, delegate, preferences);
   // Loading may synchronously access |delegate| which could be
   // uninitialized just yet, so load in another task.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+      plugin->web_view_helper_.main_frame()->GetTaskRunner(
+          blink::TaskType::kInternalDefault);
+  task_runner->PostTask(
       FROM_HERE,
       base::BindOnce(&WebViewPlugin::LoadHTML,
                      plugin->weak_factory_.GetWeakPtr(), html_data, url));
diff --git a/components/signin/core/browser/account_investigator_unittest.cc b/components/signin/core/browser/account_investigator_unittest.cc
index 15c5d5c..e6f5b19 100644
--- a/components/signin/core/browser/account_investigator_unittest.cc
+++ b/components/signin/core/browser/account_investigator_unittest.cc
@@ -267,7 +267,8 @@
       /*accounts_are_fresh=*/true, just_one, no_accounts};
   GoogleServiceAuthError error(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
   investigator()->OnAccountsInCookieUpdated(accounts_in_cookie_jar_info, error);
-  EXPECT_EQ(0u, histogram_tester.GetTotalCountsForPrefix("Signin.").size());
+  EXPECT_EQ(
+      0u, histogram_tester.GetTotalCountsForPrefix("Signin.CookieJar.").size());
 }
 
 TEST_F(AccountInvestigatorTest, OnGaiaAccountsInCookieUpdatedOnChange) {
@@ -294,7 +295,8 @@
       /*accounts_are_fresh=*/true, just_one, no_accounts};
   investigator()->OnAccountsInCookieUpdated(
       accounts_in_cookie_jar_info, GoogleServiceAuthError::AuthErrorNone());
-  EXPECT_EQ(1u, histogram_tester.GetTotalCountsForPrefix("Signin.").size());
+  EXPECT_EQ(
+      1u, histogram_tester.GetTotalCountsForPrefix("Signin.CookieJar.").size());
   ExpectRelationReport(ReportingType::ON_CHANGE, histogram_tester,
                        AccountRelation::SINGLE_SIGNED_IN_MATCH_NO_SIGNED_OUT);
 }
@@ -348,7 +350,8 @@
   const HistogramTester histogram_tester;
   TryPeriodicReport();
   EXPECT_TRUE(*periodic_pending());
-  EXPECT_EQ(0u, histogram_tester.GetTotalCountsForPrefix("Signin.").size());
+  EXPECT_EQ(
+      0u, histogram_tester.GetTotalCountsForPrefix("Signin.CookieJar.").size());
 
   std::string email("f@bar.com");
   identity_test_env()->SetCookieAccounts(
diff --git a/components/signin/core/browser/fake_signin_manager.cc b/components/signin/core/browser/fake_signin_manager.cc
index 50a187a..953ff28 100644
--- a/components/signin/core/browser/fake_signin_manager.cc
+++ b/components/signin/core/browser/fake_signin_manager.cc
@@ -52,33 +52,12 @@
 
 FakeSigninManager::~FakeSigninManager() {}
 
-void FakeSigninManager::StartSignInWithRefreshToken(
-    const std::string& refresh_token,
-    const std::string& gaia_id,
-    const std::string& username,
-    OAuthTokenFetchedCallback oauth_fetched_callback) {
-  set_auth_in_progress(
-      account_tracker_service()->SeedAccountInfo(gaia_id, username));
-  username_ = username;
-
-  possibly_invalid_gaia_id_.assign(gaia_id);
-  possibly_invalid_email_.assign(username);
-
-  if (!oauth_fetched_callback.is_null())
-    std::move(oauth_fetched_callback).Run(refresh_token);
-}
-
-void FakeSigninManager::CompletePendingSignin() {
-  SetAuthenticatedAccountId(GetAccountIdForAuthInProgress());
-  set_auth_in_progress(std::string());
-  FireGoogleSigninSucceeded();
-}
-
 void FakeSigninManager::SignIn(const std::string& gaia_id,
                                const std::string& username) {
-  StartSignInWithRefreshToken(std::string(), gaia_id, username,
-                              OAuthTokenFetchedCallback());
-  CompletePendingSignin();
+  std::string account_id =
+      account_tracker_service()->SeedAccountInfo(gaia_id, username);
+  token_service()->UpdateCredentials(account_id, "test_refresh_token");
+  OnExternalSigninCompleted(username);
 }
 
 void FakeSigninManager::ForceSignOut() {
@@ -99,18 +78,6 @@
     RemoveAccountsOption remove_option,
     SigninClient::SignoutDecision signout_decision) {
   if (!IsAuthenticated()) {
-    if (AuthInProgress()) {
-      // If the user is in the process of signing in, then treat a call to
-      // SignOut as a cancellation request.
-      GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
-      HandleAuthError(error);
-    } else {
-      // Clean up our transient data and exit if we aren't signed in.
-      // This avoids a perf regression from clearing out the TokenDB if
-      // SignOut() is invoked on startup to clean up any incomplete previous
-      // signin attempts.
-      ClearTransientSigninData();
-    }
     return;
   }
 
@@ -119,7 +86,6 @@
   if (signout_decision == SigninClient::SignoutDecision::DISALLOW_SIGNOUT)
     return;
 
-  set_auth_in_progress(std::string());
   AccountInfo account_info = GetAuthenticatedAccountInfo();
   const std::string account_id = GetAuthenticatedAccountId();
   const std::string username = account_info.email;
diff --git a/components/signin/core/browser/fake_signin_manager.h b/components/signin/core/browser/fake_signin_manager.h
index 0c5239e..a04d04e 100644
--- a/components/signin/core/browser/fake_signin_manager.h
+++ b/components/signin/core/browser/fake_signin_manager.h
@@ -44,24 +44,12 @@
 
   ~FakeSigninManager() override;
 
-  void set_auth_in_progress(const std::string& account_id) {
-    possibly_invalid_account_id_ = account_id;
-  }
-
   void SignIn(const std::string& gaia_id, const std::string& username);
 
   void ForceSignOut();
 
   void FailSignin(const GoogleServiceAuthError& error);
 
-  void StartSignInWithRefreshToken(
-      const std::string& refresh_token,
-      const std::string& gaia_id,
-      const std::string& username,
-      OAuthTokenFetchedCallback oauth_fetched_callback) override;
-
-  void CompletePendingSignin() override;
-
  protected:
   void OnSignoutDecisionReached(
       signin_metrics::ProfileSignout signout_source_metric,
diff --git a/components/signin/core/browser/signin_client.h b/components/signin/core/browser/signin_client.h
index 202cfda..0b288a6 100644
--- a/components/signin/core/browser/signin_client.h
+++ b/components/signin/core/browser/signin_client.h
@@ -95,9 +95,6 @@
       gaia::GaiaSource source,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) = 0;
 
-  // Called once the credentials has been copied to another SigninManager.
-  virtual void AfterCredentialsCopied() {}
-
   // Schedules migration to happen at next startup.
   virtual void SetReadyForDiceMigration(bool is_ready) {}
 };
diff --git a/components/signin/core/browser/signin_manager.cc b/components/signin/core/browser/signin_manager.cc
index e0cfbbf..4250086 100644
--- a/components/signin/core/browser/signin_manager.cc
+++ b/components/signin/core/browser/signin_manager.cc
@@ -32,107 +32,13 @@
     GaiaCookieManagerService* cookie_manager_service,
     signin::AccountConsistencyMethod account_consistency)
     : SigninManagerBase(client, token_service, account_tracker_service),
-      type_(SIGNIN_TYPE_NONE),
       cookie_manager_service_(cookie_manager_service),
       account_consistency_(account_consistency),
-      signin_manager_signed_in_(false),
-      user_info_fetched_by_account_tracker_(false),
       weak_pointer_factory_(this) {}
 
 SigninManager::~SigninManager() {}
 
-std::string SigninManager::SigninTypeToString(SigninManager::SigninType type) {
-  switch (type) {
-    case SIGNIN_TYPE_NONE:
-      return "No Signin";
-    case SIGNIN_TYPE_WITH_REFRESH_TOKEN:
-      return "With refresh token";
-    case SIGNIN_TYPE_WITHOUT_REFRESH_TOKEN:
-      return "Without refresh token";
-  }
-
-  NOTREACHED();
-  return std::string();
-}
-
-bool SigninManager::PrepareForSignin(SigninType type,
-                                     const std::string& gaia_id,
-                                     const std::string& username) {
-  std::string account_id =
-      account_tracker_service()->PickAccountIdForAccount(gaia_id, username);
-  DCHECK(!account_id.empty());
-  DCHECK(possibly_invalid_account_id_.empty() ||
-         possibly_invalid_account_id_ == account_id);
-
-  if (!IsAllowedUsername(username)) {
-    // Account is not allowed by admin policy.
-    HandleAuthError(
-        GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED));
-    return false;
-  }
-
-  // This attempt is either 1) the user trying to establish initial sync, or
-  // 2) trying to refresh credentials for an existing username.  If it is 2, we
-  // need to try again, but take care to leave state around tracking that the
-  // user has successfully signed in once before with this username, so that on
-  // restart we don't think sync setup has never completed.
-  ClearTransientSigninData();
-  type_ = type;
-  possibly_invalid_account_id_.assign(account_id);
-  possibly_invalid_gaia_id_.assign(gaia_id);
-  possibly_invalid_email_.assign(username);
-  signin_manager_signed_in_ = false;
-  user_info_fetched_by_account_tracker_ = false;
-  return true;
-}
-
-void SigninManager::StartSignInWithRefreshToken(
-    const std::string& refresh_token,
-    const std::string& gaia_id,
-    const std::string& username,
-    OAuthTokenFetchedCallback callback) {
-  DCHECK(!IsAuthenticated());
-  SigninType signin_type = refresh_token.empty()
-                               ? SIGNIN_TYPE_WITHOUT_REFRESH_TOKEN
-                               : SIGNIN_TYPE_WITH_REFRESH_TOKEN;
-  if (!PrepareForSignin(signin_type, gaia_id, username)) {
-    return;
-  }
-
-  // Store the refresh token.
-  temp_refresh_token_ = refresh_token;
-
-  if (!callback.is_null()) {
-    // Callback present, let the caller complete the pending sign-in.
-    std::move(callback).Run(temp_refresh_token_);
-  } else {
-    // No callback, so just complete the pending signin.
-    CompletePendingSignin();
-  }
-}
-
-void SigninManager::CopyCredentialsFrom(const SigninManager& source) {
-  DCHECK_NE(this, &source);
-  possibly_invalid_account_id_ = source.possibly_invalid_account_id_;
-  possibly_invalid_gaia_id_ = source.possibly_invalid_gaia_id_;
-  possibly_invalid_email_ = source.possibly_invalid_email_;
-  temp_refresh_token_ = source.temp_refresh_token_;
-  source.signin_client()->AfterCredentialsCopied();
-}
-
-void SigninManager::ClearTransientSigninData() {
-  DCHECK(IsInitialized());
-
-  possibly_invalid_account_id_.clear();
-  possibly_invalid_gaia_id_.clear();
-  possibly_invalid_email_.clear();
-  type_ = SIGNIN_TYPE_NONE;
-  temp_refresh_token_.clear();
-}
-
 void SigninManager::HandleAuthError(const GoogleServiceAuthError& error) {
-  ClearTransientSigninData();
-
   for (auto& observer : observer_list_)
     observer.GoogleSigninFailed(error);
 }
@@ -181,18 +87,6 @@
 
   signin_metrics::LogSignout(signout_source_metric, signout_delete_metric);
   if (!IsAuthenticated()) {
-    if (AuthInProgress()) {
-      // If the user is in the process of signing in, then treat a call to
-      // SignOut as a cancellation request.
-      GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
-      HandleAuthError(error);
-    } else {
-      // Clean up our transient data and exit if we aren't signed in.
-      // This avoids a perf regression from clearing out the TokenDB if
-      // SignOut() is invoked on startup to clean up any incomplete previous
-      // signin attempts.
-      ClearTransientSigninData();
-    }
     return;
   }
 
@@ -203,8 +97,6 @@
     return;
   }
 
-  ClearTransientSigninData();
-
   AccountInfo account_info = GetAuthenticatedAccountInfo();
   const std::string account_id = GetAuthenticatedAccountId();
   const std::string username = account_info.email;
@@ -289,8 +181,6 @@
                               signin_metrics::SignoutDelete::IGNORE_METRIC);
   }
 
-  account_tracker_service()->AddObserver(this);
-
   // It is important to only load credentials after starting to observe the
   // token service.
   token_service()->AddObserver(this);
@@ -298,7 +188,6 @@
 
 void SigninManager::Shutdown() {
   token_service()->RemoveObserver(this);
-  account_tracker_service()->RemoveObserver(this);
   local_state_pref_registrar_.RemoveAll();
   SigninManagerBase::Shutdown();
 }
@@ -322,7 +211,7 @@
 }
 
 void SigninManager::OnSigninAllowedPrefChanged() {
-  if (!IsSigninAllowed() && (IsAuthenticated() || AuthInProgress()))
+  if (!IsSigninAllowed() && IsAuthenticated())
     SignOut(signin_metrics::SIGNOUT_PREF_CHANGED,
             signin_metrics::SignoutDelete::IGNORE_METRIC);
 }
@@ -343,22 +232,6 @@
   return identity::IsUsernameAllowedByPattern(username, pattern);
 }
 
-bool SigninManager::AuthInProgress() const {
-  return !possibly_invalid_account_id_.empty();
-}
-
-const std::string& SigninManager::GetAccountIdForAuthInProgress() const {
-  return possibly_invalid_account_id_;
-}
-
-const std::string& SigninManager::GetGaiaIdForAuthInProgress() const {
-  return possibly_invalid_gaia_id_;
-}
-
-const std::string& SigninManager::GetUsernameForAuthInProgress() const {
-  return possibly_invalid_email_;
-}
-
 void SigninManager::MergeSigninCredentialIntoCookieJar() {
   if (account_consistency_ == signin::AccountConsistencyMethod::kMirror)
     return;
@@ -370,49 +243,19 @@
                                               gaia::GaiaSource::kSigninManager);
 }
 
-void SigninManager::CompletePendingSignin() {
-  DCHECK(!possibly_invalid_account_id_.empty());
-  OnSignedIn();
-
-  DCHECK(IsAuthenticated());
-
-  if (!temp_refresh_token_.empty()) {
-    std::string account_id = GetAuthenticatedAccountId();
-    token_service()->UpdateCredentials(
-        account_id, temp_refresh_token_,
-        signin_metrics::SourceForRefreshTokenOperation::
-            kSigninManager_LegacyPreDiceSigninFlow);
-    temp_refresh_token_.clear();
-  }
-  MergeSigninCredentialIntoCookieJar();
-}
-
 void SigninManager::OnExternalSigninCompleted(const std::string& username) {
   AccountInfo info =
       account_tracker_service()->FindAccountInfoByEmail(username);
   DCHECK(!info.gaia.empty());
   DCHECK(!info.email.empty());
-  possibly_invalid_account_id_ = info.account_id;
-  possibly_invalid_gaia_id_ = info.gaia;
-  possibly_invalid_email_ = info.email;
-  OnSignedIn();
-}
 
-void SigninManager::OnSignedIn() {
   bool reauth_in_progress = IsAuthenticated();
 
   signin_client()->GetPrefs()->SetInt64(
       prefs::kSignedInTime,
       base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
 
-  SetAuthenticatedAccountInfo(possibly_invalid_gaia_id_,
-                              possibly_invalid_email_);
-  const std::string gaia_id = possibly_invalid_gaia_id_;
-
-  possibly_invalid_account_id_.clear();
-  possibly_invalid_gaia_id_.clear();
-  possibly_invalid_email_.clear();
-  signin_manager_signed_in_ = true;
+  SetAuthenticatedAccountInfo(info.gaia, info.email);
 
   if (!reauth_in_progress)
     FireGoogleSigninSucceeded();
@@ -433,17 +276,6 @@
   }
 }
 
-void SigninManager::OnAccountUpdated(const AccountInfo& info) {
-  if (!info.IsValid())
-    return;
-
-  user_info_fetched_by_account_tracker_ = true;
-}
-
-void SigninManager::OnAccountUpdateFailed(const std::string& account_id) {
-  user_info_fetched_by_account_tracker_ = true;
-}
-
 void SigninManager::OnRefreshTokensLoaded() {
   token_service()->RemoveObserver(this);
 
diff --git a/components/signin/core/browser/signin_manager.h b/components/signin/core/browser/signin_manager.h
index 0141fef..2efe1c19 100644
--- a/components/signin/core/browser/signin_manager.h
+++ b/components/signin/core/browser/signin_manager.h
@@ -48,16 +48,8 @@
 }  // namespace identity
 
 class SigninManager : public SigninManagerBase,
-                      public AccountTrackerService::Observer,
                       public OAuth2TokenService::Observer {
  public:
-  // The callback invoked once the OAuth token has been fetched during signin,
-  // but before the profile transitions to the "signed-in" state. This allows
-  // callers to load policy and prompt the user appropriately before completing
-  // signin. The callback is passed the just-fetched OAuth login refresh token.
-  using OAuthTokenFetchedCallback =
-      base::OnceCallback<void(const std::string&)>;
-
   // Used to remove accounts from the token service and the account tracker.
   enum class RemoveAccountsOption {
     // Do not remove accounts.
@@ -87,24 +79,6 @@
   // are actually SigninManager instances.
   static SigninManager* FromSigninManagerBase(SigninManagerBase* manager);
 
-  // Attempt to sign in this user with a refresh token.
-  // If |refresh_token| is not empty, then SigninManager will add it to the
-  // |token_service_| when the sign-in flow is completed.
-  // If non-null, the passed |oauth_fetched_callback| callback is invoked once
-  // sign-in has been completed.
-  // The callback should invoke SignOut() or CompletePendingSignin() to either
-  // continue or cancel the in-process signin.
-  virtual void StartSignInWithRefreshToken(
-      const std::string& refresh_token,
-      const std::string& gaia_id,
-      const std::string& username,
-      OAuthTokenFetchedCallback oauth_fetched_callback);
-
-  // Copies auth credentials from one SigninManager to this one. This is used
-  // when creating a new profile during the signin process to transfer the
-  // in-progress credentials to the new profile.
-  virtual void CopyCredentialsFrom(const SigninManager& source);
-
   // Signs a user out, removing the preference, erasing all keys
   // associated with the authenticated user, and canceling all auth in progress.
   // On mobile and on desktop pre-DICE, this also removes all accounts from
@@ -140,18 +114,12 @@
   // If applicable, merge the signed in account into the cookie jar.
   void MergeSigninCredentialIntoCookieJar();
 
-  // Invoked from an OAuthTokenFetchedCallback to complete user signin.
-  virtual void CompletePendingSignin();
-
   // Invoked from SigninManagerAndroid to indicate that the sign-in process
   // has completed for the email |username|.  SigninManager assumes that
   // |username| can be used to look up the corresponding account_id and gaia_id
   // for this email.
   void OnExternalSigninCompleted(const std::string& username);
 
-  // Returns true if there's a signin in progress.
-  bool AuthInProgress() const override;
-
   // Returns whether sign-in is allowed.
   // TODO(crbug.com/806778): Remove method in super-class.
   bool IsSigninAllowed() const override;
@@ -159,18 +127,6 @@
   // Sets whether sign-in is allowed or not.
   void SetSigninAllowed(bool allowed);
 
-  // If an authentication is in progress, return the account id being
-  // authenticated. Returns an empty string if no auth is in progress.
-  const std::string& GetAccountIdForAuthInProgress() const;
-
-  // If an authentication is in progress, return the gaia id being
-  // authenticated. Returns an empty string if no auth is in progress.
-  const std::string& GetGaiaIdForAuthInProgress() const;
-
-  // If an authentication is in progress, return the username being
-  // authenticated. Returns an empty string if no auth is in progress.
-  const std::string& GetUsernameForAuthInProgress() const;
-
  protected:
   // The sign out process which is started by SigninClient::PreSignOut()
   virtual void OnSignoutDecisionReached(
@@ -180,50 +136,20 @@
       SigninClient::SignoutDecision signout_decision);
 
  private:
-  enum SigninType {
-    SIGNIN_TYPE_NONE,
-    SIGNIN_TYPE_WITH_REFRESH_TOKEN,
-    SIGNIN_TYPE_WITHOUT_REFRESH_TOKEN
-  };
-
-  std::string SigninTypeToString(SigninType type);
   friend class FakeSigninManager;
   friend class identity::IdentityManager;
   FRIEND_TEST_ALL_PREFIXES(SigninManagerTest, Prohibited);
   FRIEND_TEST_ALL_PREFIXES(SigninManagerTest, TestAlternateWildcard);
 
-  // Called to setup the transient signin data during one of the
-  // StartSigninXXX methods.  |type| indicates which of the methods is being
-  // used to perform the signin while |username| identifies the account to be
-  // signed in. Returns false and generates an auth error if the passed
-  // |username| is not allowed by policy.  |gaia_id| is the obfuscated gaia id
-  // corresponding to |username|.
-  bool PrepareForSignin(SigninType type,
-                        const std::string& gaia_id,
-                        const std::string& username);
-
-  // Persists |account_id| as the currently signed-in account, and triggers
-  // a sign-in success notification.
-  void OnSignedIn();
-
   // Send all observers |GoogleSigninSucceeded| notifications.
   void FireGoogleSigninSucceeded();
 
   // Send all observers |GoogleSignedOut| notifications.
   void FireGoogleSignedOut(const AccountInfo& account_info);
 
-  // AccountTrackerService::Observer:
-  void OnAccountUpdated(const AccountInfo& info) override;
-  void OnAccountUpdateFailed(const std::string& account_id) override;
-
   // OAuth2TokenService::Observer:
   void OnRefreshTokensLoaded() override;
 
-  // Called when a new request to re-authenticate a user is in progress.
-  // Will clear in memory data but leaves the db as such so when the browser
-  // restarts we can use the old token(which might throw a password error).
-  void ClearTransientSigninData();
-
   // Called to handle an error from a GAIA auth fetch.  Sets the last error
   // to |error|, sends out a notification of login failure and clears the
   // transient signin data.
@@ -240,19 +166,6 @@
   // Returns true if the passed username is allowed by policy.
   bool IsAllowedUsername(const std::string& username) const;
 
-  std::string possibly_invalid_account_id_;
-  std::string possibly_invalid_gaia_id_;
-  std::string possibly_invalid_email_;
-
-  // The type of sign being performed.  This value is valid only between a call
-  // to one of the StartSigninXXX methods and when the sign in is either
-  // successful or not.
-  SigninType type_;
-
-  // Temporarily saves the oauth2 refresh token.  It will be passed to the
-  // token service so that it does not need to mint new ones.
-  std::string temp_refresh_token_;
-
   // Object used to use the token to push a GAIA cookie into the cookie jar.
   GaiaCookieManagerService* cookie_manager_service_;
 
@@ -265,12 +178,6 @@
 
   signin::AccountConsistencyMethod account_consistency_;
 
-  // Two gate conditions for when PostSignedIn should be called. Verify
-  // that the SigninManager has reached OnSignedIn() and the AccountTracker
-  // has completed calling GetUserInfo.
-  bool signin_manager_signed_in_;
-  bool user_info_fetched_by_account_tracker_;
-
   base::WeakPtrFactory<SigninManager> weak_pointer_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(SigninManager);
diff --git a/components/signin/core/browser/signin_manager_base.cc b/components/signin/core/browser/signin_manager_base.cc
index 4eaecf6..1e22abb 100644
--- a/components/signin/core/browser/signin_manager_base.cc
+++ b/components/signin/core/browser/signin_manager_base.cc
@@ -229,11 +229,6 @@
   return !authenticated_account_id_.empty();
 }
 
-bool SigninManagerBase::AuthInProgress() const {
-  // SigninManagerBase never kicks off auth processes itself.
-  return false;
-}
-
 void SigninManagerBase::Shutdown() {
   on_shutdown_callback_list_.Notify();
 }
diff --git a/components/signin/core/browser/signin_manager_base.h b/components/signin/core/browser/signin_manager_base.h
index 5a80e91..f618edd 100644
--- a/components/signin/core/browser/signin_manager_base.h
+++ b/components/signin/core/browser/signin_manager_base.h
@@ -126,9 +126,6 @@
   // Returns true if there is an authenticated user.
   bool IsAuthenticated() const;
 
-  // Returns true if there's a signin in progress.
-  virtual bool AuthInProgress() const;
-
   // KeyedService implementation.
   void Shutdown() override;
 
diff --git a/components/signin/core/browser/signin_manager_unittest.cc b/components/signin/core/browser/signin_manager_unittest.cc
index 8d5711d..517a5738 100644
--- a/components/signin/core/browser/signin_manager_unittest.cc
+++ b/components/signin/core/browser/signin_manager_unittest.cc
@@ -141,11 +141,6 @@
     EXPECT_EQ(0, test_observer_.num_failed_signins_);
   }
 
-  void CompleteSigninCallback(const std::string& oauth_token) {
-    oauth_tokens_fetched_.push_back(oauth_token);
-    manager_->CompletePendingSignin();
-  }
-
   base::test::ScopedTaskEnvironment task_environment_;
   sync_preferences::TestingPrefServiceSyncable user_prefs_;
   TestingPrefServiceSimple local_state_;
@@ -161,63 +156,11 @@
   signin::AccountConsistencyMethod account_consistency_;
 };
 
-TEST_F(SigninManagerTest, SignInWithRefreshToken) {
-  CreateSigninManager();
-  EXPECT_FALSE(manager_->IsAuthenticated());
-
-  std::string account_id = AddToAccountTracker("gaia_id", "user@gmail.com");
-  manager_->StartSignInWithRefreshToken(
-      "rt", "gaia_id", "user@gmail.com",
-      SigninManager::OAuthTokenFetchedCallback());
-
-  ExpectSignInWithRefreshTokenSuccess();
-
-  // Should persist across resets.
-  ShutDownManager();
-  CreateSigninManager();
-  EXPECT_EQ(account_id, manager_->GetAuthenticatedAccountId());
-}
-
-TEST_F(SigninManagerTest, SignInWithRefreshTokenCallbackComplete) {
-  CreateSigninManager();
-  EXPECT_FALSE(manager_->IsAuthenticated());
-
-  manager_->StartSignInWithRefreshToken(
-      "rt", "gaia_id", "user@gmail.com",
-      base::BindOnce(&SigninManagerTest::CompleteSigninCallback,
-                     base::Unretained(this)));
-
-  ExpectSignInWithRefreshTokenSuccess();
-  ASSERT_EQ(1U, oauth_tokens_fetched_.size());
-  EXPECT_EQ(oauth_tokens_fetched_[0], "rt");
-}
-
-TEST_F(SigninManagerTest, SignInWithRefreshTokenCallsPostSignout) {
-  CreateSigninManager();
-  EXPECT_FALSE(manager_->IsAuthenticated());
-
-  std::string gaia_id = "12345";
-  std::string email = "user@google.com";
-
-  account_tracker()->SeedAccountInfo(gaia_id, email);
-  account_fetcher()->OnRefreshTokensLoaded();
-
-  manager_->StartSignInWithRefreshToken(
-      "rt1", gaia_id, email, SigninManager::OAuthTokenFetchedCallback());
-
-  account_fetcher()->FakeUserInfoFetchSuccess(
-      account_tracker()->PickAccountIdForAccount(gaia_id, email), email,
-      gaia_id, "google.com", "full_name", "given_name", "locale",
-      "http://www.google.com");
-
-  ExpectSignInWithRefreshTokenSuccess();
-}
-
 TEST_F(SigninManagerTest, SignOut) {
   CreateSigninManager();
-  manager_->StartSignInWithRefreshToken(
-      "rt", "gaia_id", "user@gmail.com",
-      SigninManager::OAuthTokenFetchedCallback());
+  std::string main_account_id =
+      AddToAccountTracker("account_id", "user@gmail.com");
+  manager_->OnExternalSigninCompleted("user@gmail.com");
   manager_->SignOut(signin_metrics::SIGNOUT_TEST,
                     signin_metrics::SignoutDelete::IGNORE_METRIC);
   EXPECT_FALSE(manager_->IsAuthenticated());
diff --git a/components/viz/client/client_resource_provider.cc b/components/viz/client/client_resource_provider.cc
index 7110be6..efca8bb 100644
--- a/components/viz/client/client_resource_provider.cc
+++ b/components/viz/client/client_resource_provider.cc
@@ -10,6 +10,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "components/viz/common/gpu/context_provider.h"
+#include "components/viz/common/gpu/raster_context_provider.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "components/viz/common/resources/resource_sizes.h"
 #include "components/viz/common/resources/returned_resource.h"
@@ -94,6 +95,35 @@
     const std::vector<ResourceId>& export_ids,
     std::vector<TransferableResource>* list,
     ContextProvider* context_provider) {
+  auto cb = base::BindOnce(
+      [](scoped_refptr<ContextProvider> context_provider,
+         std::vector<GLbyte*>* tokens) {
+        context_provider->ContextGL()->VerifySyncTokensCHROMIUM(tokens->data(),
+                                                                tokens->size());
+      },
+      base::WrapRefCounted(context_provider));
+  PrepareSendToParentInternal(export_ids, list, std::move(cb));
+}
+
+void ClientResourceProvider::PrepareSendToParent(
+    const std::vector<ResourceId>& export_ids,
+    std::vector<TransferableResource>* list,
+    RasterContextProvider* context_provider) {
+  PrepareSendToParentInternal(
+      export_ids, list,
+      base::BindOnce(
+          [](scoped_refptr<RasterContextProvider> context_provider,
+             std::vector<GLbyte*>* tokens) {
+            context_provider->RasterInterface()->VerifySyncTokensCHROMIUM(
+                tokens->data(), tokens->size());
+          },
+          base::WrapRefCounted(context_provider)));
+}
+
+void ClientResourceProvider::PrepareSendToParentInternal(
+    const std::vector<ResourceId>& export_ids,
+    std::vector<TransferableResource>* list,
+    base::OnceCallback<void(std::vector<GLbyte*>* tokens)> verify_sync_tokens) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   // This function goes through the array multiple times, store the resources
@@ -122,9 +152,8 @@
 
   if (!unverified_sync_tokens.empty()) {
     DCHECK(verified_sync_tokens_required_);
-    DCHECK(context_provider);
-    context_provider->ContextGL()->VerifySyncTokensCHROMIUM(
-        unverified_sync_tokens.data(), unverified_sync_tokens.size());
+    DCHECK(verify_sync_tokens);
+    std::move(verify_sync_tokens).Run(&unverified_sync_tokens);
   }
 
   for (ImportedResource* imported : imports) {
diff --git a/components/viz/client/client_resource_provider.h b/components/viz/client/client_resource_provider.h
index 6de5121..f3965af 100644
--- a/components/viz/client/client_resource_provider.h
+++ b/components/viz/client/client_resource_provider.h
@@ -32,6 +32,7 @@
 
 namespace viz {
 class ContextProvider;
+class RasterContextProvider;
 
 // This class is used to give an integer name (ResourceId) to a gpu or software
 // resource (shipped as a TransferableResource), in order to use that name in
@@ -58,6 +59,13 @@
   void PrepareSendToParent(
       const std::vector<ResourceId>& resource_ids,
       std::vector<TransferableResource>* transferable_resources,
+      RasterContextProvider* context_provider);
+
+  // TODO(sergeyu): Remove after updating all callers to use the above version
+  // of this method.
+  void PrepareSendToParent(
+      const std::vector<ResourceId>& resource_ids,
+      std::vector<TransferableResource>* transferable_resources,
       ContextProvider* context_provider);
 
   // Receives resources from the parent, moving them from mailboxes. ResourceIds
@@ -126,6 +134,12 @@
  private:
   struct ImportedResource;
 
+  void PrepareSendToParentInternal(
+      const std::vector<ResourceId>& export_ids,
+      std::vector<TransferableResource>* list,
+      base::OnceCallback<void(std::vector<GLbyte*>* tokens)>
+          verify_sync_tokens);
+
   THREAD_CHECKER(thread_checker_);
   const bool verified_sync_tokens_required_;
 
diff --git a/components/viz/service/display/display_resource_provider_unittest.cc b/components/viz/service/display/display_resource_provider_unittest.cc
index cdc51d5..52c6bb9 100644
--- a/components/viz/service/display/display_resource_provider_unittest.cc
+++ b/components/viz/service/display/display_resource_provider_unittest.cc
@@ -465,8 +465,9 @@
 
   // Transfer some resources to the parent.
   std::vector<TransferableResource> list;
-  child_resource_provider_->PrepareSendToParent({id1}, &list,
-                                                child_context_provider_.get());
+  child_resource_provider_->PrepareSendToParent(
+      {id1}, &list,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   ASSERT_EQ(1u, list.size());
   EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1));
 
@@ -519,7 +520,8 @@
     // Transfer some resources to the parent.
     std::vector<TransferableResource> list;
     child_resource_provider_->PrepareSendToParent(
-        {id1}, &list, child_context_provider_.get());
+        {id1}, &list,
+        static_cast<RasterContextProvider*>(child_context_provider_.get()));
     ASSERT_EQ(1u, list.size());
     EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1));
 
@@ -583,8 +585,9 @@
 
   // Transfer some resources to the parent.
   std::vector<TransferableResource> list;
-  child_resource_provider_->PrepareSendToParent({id1}, &list,
-                                                child_context_provider_.get());
+  child_resource_provider_->PrepareSendToParent(
+      {id1}, &list,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   ASSERT_EQ(1u, list.size());
   EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1));
   EXPECT_TRUE(list[0].read_lock_fences_enabled);
@@ -642,8 +645,9 @@
 
   // Transfer resources to the parent.
   std::vector<TransferableResource> list;
-  child_resource_provider_->PrepareSendToParent({id1, id2}, &list,
-                                                child_context_provider_.get());
+  child_resource_provider_->PrepareSendToParent(
+      {id1, id2}, &list,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   ASSERT_EQ(2u, list.size());
   EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1));
   EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id2));
@@ -703,8 +707,9 @@
 
   // Transfer resources to the parent.
   std::vector<TransferableResource> list;
-  child_resource_provider_->PrepareSendToParent({id1, id2}, &list,
-                                                child_context_provider_.get());
+  child_resource_provider_->PrepareSendToParent(
+      {id1, id2}, &list,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   ASSERT_EQ(2u, list.size());
   EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1));
   EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id2));
@@ -770,7 +775,8 @@
     // Transfer some resources to the parent.
     std::vector<TransferableResource> list;
     no_token_resource_provider->PrepareSendToParent(
-        {id}, &list, child_context_provider_.get());
+        {id}, &list,
+        static_cast<RasterContextProvider*>(child_context_provider_.get()));
     ASSERT_EQ(1u, list.size());
     // A given sync point should be passed through.
     EXPECT_EQ(external_sync_token, list[0].mailbox_holder.sync_token);
@@ -832,8 +838,9 @@
   std::vector<ResourceId> resource_ids_to_transfer(ids, ids + kTotalResources);
 
   std::vector<TransferableResource> list;
-  child_resource_provider_->PrepareSendToParent(resource_ids_to_transfer, &list,
-                                                child_context_provider_.get());
+  child_resource_provider_->PrepareSendToParent(
+      resource_ids_to_transfer, &list,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   ASSERT_EQ(kTotalResources, list.size());
   for (const auto& id : ids)
     EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id));
@@ -951,8 +958,9 @@
   std::vector<ReturnedResource> returned_to_child;
   int child_id = resource_provider_->CreateChild(
       base::BindRepeating(&CollectResources, &returned_to_child), true);
-  child_resource_provider_->PrepareSendToParent({resource_id}, &send_to_parent,
-                                                child_context_provider_.get());
+  child_resource_provider_->PrepareSendToParent(
+      {resource_id}, &send_to_parent,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   resource_provider_->ReceiveFromChild(child_id, send_to_parent);
 
   // In DisplayResourceProvider's namespace, use the mapped resource id.
@@ -1070,8 +1078,9 @@
     std::vector<ReturnedResource> returned_to_child;
     int child_id = resource_provider->CreateChild(
         base::BindRepeating(&CollectResources, &returned_to_child), true);
-    child_resource_provider->PrepareSendToParent({resource_id}, &send_to_parent,
-                                                 child_context_provider.get());
+    child_resource_provider->PrepareSendToParent(
+        {resource_id}, &send_to_parent,
+        static_cast<RasterContextProvider*>(child_context_provider.get()));
     resource_provider->ReceiveFromChild(child_id, send_to_parent);
 
     // In DisplayResourceProvider's namespace, use the mapped resource id.
@@ -1223,8 +1232,9 @@
   std::vector<ReturnedResource> returned_to_child;
   int child_id = resource_provider->CreateChild(
       base::BindRepeating(&CollectResources, &returned_to_child), true);
-  child_resource_provider->PrepareSendToParent({resource_id}, &send_to_parent,
-                                               child_context_provider_.get());
+  child_resource_provider->PrepareSendToParent(
+      {resource_id}, &send_to_parent,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   resource_provider->ReceiveFromChild(child_id, send_to_parent);
 
   // Before create DrawQuad in DisplayResourceProvider's namespace, get the
@@ -1357,8 +1367,9 @@
 
   // Transfer some resources to the parent.
   std::vector<TransferableResource> list;
-  child_resource_provider_->PrepareSendToParent({id1, id2}, &list,
-                                                child_context_provider_.get());
+  child_resource_provider_->PrepareSendToParent(
+      {id1, id2}, &list,
+      static_cast<RasterContextProvider*>(child_context_provider_.get()));
   ASSERT_EQ(2u, list.size());
   resource_provider_->ReceiveFromChild(child_id, list);
   std::unordered_map<ResourceId, ResourceId> resource_map =
diff --git a/components/viz/service/display/gl_renderer_unittest.cc b/components/viz/service/display/gl_renderer_unittest.cc
index 2c36d6f..4aae5a9 100644
--- a/components/viz/service/display/gl_renderer_unittest.cc
+++ b/components/viz/service/display/gl_renderer_unittest.cc
@@ -2226,8 +2226,9 @@
   std::vector<ResourceId> resource_ids_to_transfer;
   resource_ids_to_transfer.push_back(resource_id);
   std::vector<TransferableResource> list;
-  child_resource_provider->PrepareSendToParent(resource_ids_to_transfer, &list,
-                                               child_context_provider.get());
+  child_resource_provider->PrepareSendToParent(
+      resource_ids_to_transfer, &list,
+      static_cast<RasterContextProvider*>(child_context_provider.get()));
   parent_resource_provider->ReceiveFromChild(child_id, list);
 
   // In DisplayResourceProvider's namespace, use the mapped resource id.
@@ -2438,8 +2439,9 @@
   std::vector<ResourceId> resource_ids_to_transfer;
   resource_ids_to_transfer.push_back(resource_id);
   std::vector<TransferableResource> list;
-  child_resource_provider->PrepareSendToParent(resource_ids_to_transfer, &list,
-                                               child_context_provider.get());
+  child_resource_provider->PrepareSendToParent(
+      resource_ids_to_transfer, &list,
+      static_cast<RasterContextProvider*>(child_context_provider.get()));
   parent_resource_provider->ReceiveFromChild(child_id, list);
 
   // In DisplayResourceProvider's namespace, use the mapped resource id.
@@ -2824,8 +2826,9 @@
   std::vector<ResourceId> resource_ids_to_transfer;
   resource_ids_to_transfer.push_back(resource_id);
   std::vector<TransferableResource> list;
-  child_resource_provider->PrepareSendToParent(resource_ids_to_transfer, &list,
-                                               child_context_provider.get());
+  child_resource_provider->PrepareSendToParent(
+      resource_ids_to_transfer, &list,
+      static_cast<RasterContextProvider*>(child_context_provider.get()));
   parent_resource_provider->ReceiveFromChild(child_id, list);
   // In DisplayResourceProvider's namespace, use the mapped resource id.
   std::unordered_map<ResourceId, ResourceId> resource_map =
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index d5d5546..bedffa1 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -1249,11 +1249,13 @@
     constexpr int kMaxResourceSize = 10000;
 
     video_resource_updater_ = std::make_unique<media::VideoResourceUpdater>(
-        this->child_context_provider_.get(), nullptr,
+        this->child_context_provider_.get(),
+        /*raster_context_provider=*/nullptr, nullptr,
         this->child_resource_provider_.get(), kUseStreamVideoDrawQuad,
         kUseGpuMemoryBufferResources, kUseR16Texture, kMaxResourceSize);
     video_resource_updater2_ = std::make_unique<media::VideoResourceUpdater>(
-        this->child_context_provider_.get(), nullptr,
+        this->child_context_provider_.get(),
+        /*raster_context_provider=*/nullptr, nullptr,
         this->child_resource_provider_.get(), kUseStreamVideoDrawQuad,
         kUseGpuMemoryBufferResources, kUseR16Texture, kMaxResourceSize);
   }
@@ -1652,9 +1654,9 @@
     constexpr bool kUseR16Texture = false;
     constexpr int kMaxResourceSize = 10000;
     video_resource_updater_ = std::make_unique<media::VideoResourceUpdater>(
-        child_context_provider_.get(), nullptr, child_resource_provider_.get(),
-        kUseStreamVideoDrawQuad, kUseGpuMemoryBufferResources, kUseR16Texture,
-        kMaxResourceSize);
+        child_context_provider_.get(), nullptr, nullptr,
+        child_resource_provider_.get(), kUseStreamVideoDrawQuad,
+        kUseGpuMemoryBufferResources, kUseR16Texture, kMaxResourceSize);
   }
 
   void TearDown() override {
diff --git a/components/viz/service/display_embedder/gl_output_surface.cc b/components/viz/service/display_embedder/gl_output_surface.cc
index 35cee0d..49b3cb0 100644
--- a/components/viz/service/display_embedder/gl_output_surface.cc
+++ b/components/viz/service/display_embedder/gl_output_surface.cc
@@ -4,7 +4,8 @@
 
 #include "components/viz/service/display_embedder/gl_output_surface.h"
 
-#include <stdint.h>
+#include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -107,6 +108,10 @@
   if (frame.sub_buffer_rect) {
     HandlePartialSwap(*frame.sub_buffer_rect, flags, std::move(swap_callback),
                       std::move(presentation_callback));
+  } else if (!frame.content_bounds.empty()) {
+    context_provider_->ContextSupport()->SwapWithBounds(
+        frame.content_bounds, flags, std::move(swap_callback),
+        std::move(presentation_callback));
   } else {
     context_provider_->ContextSupport()->Swap(flags, std::move(swap_callback),
                                               std::move(presentation_callback));
diff --git a/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc b/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc
index 7f0ae29..99ec733 100644
--- a/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc
+++ b/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc
@@ -37,15 +37,13 @@
   // implementation.
   capabilities_.max_frames_pending = 2;
 
-  buffer_queue_.reset(new BufferQueue(
+  buffer_queue_ = std::make_unique<BufferQueue>(
       context_provider->ContextGL(), target, internalformat, buffer_format,
-      gpu_memory_buffer_manager, surface_handle));
+      gpu_memory_buffer_manager, surface_handle);
   buffer_queue_->Initialize();
 }
 
-GLOutputSurfaceBufferQueue::~GLOutputSurfaceBufferQueue() {
-  // TODO(rjkroege): Support cleanup.
-}
+GLOutputSurfaceBufferQueue::~GLOutputSurfaceBufferQueue() = default;
 
 void GLOutputSurfaceBufferQueue::BindFramebuffer() {
   DCHECK(buffer_queue_);
@@ -89,7 +87,6 @@
 }
 
 bool GLOutputSurfaceBufferQueue::IsDisplayedAsOverlayPlane() const {
-  // TODO(rjkroege): implement remaining overlay functionality.
   return true;
 }
 
diff --git a/components/viz/service/display_embedder/gl_output_surface_ozone.cc b/components/viz/service/display_embedder/gl_output_surface_ozone.cc
index 8c42482..6a563a5 100644
--- a/components/viz/service/display_embedder/gl_output_surface_ozone.cc
+++ b/components/viz/service/display_embedder/gl_output_surface_ozone.cc
@@ -23,6 +23,12 @@
                                  internal_format,
                                  display::DisplaySnapshot::PrimaryFormat()) {}
 
-GLOutputSurfaceOzone::~GLOutputSurfaceOzone() {}
+GLOutputSurfaceOzone::~GLOutputSurfaceOzone() = default;
+
+OverlayCandidateValidator* GLOutputSurfaceOzone::GetOverlayCandidateValidator()
+    const {
+  // TODO(crbug.com/930173): Implement overlay functionality.
+  return nullptr;
+}
 
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/gl_output_surface_ozone.h b/components/viz/service/display_embedder/gl_output_surface_ozone.h
index 47a7cff..3dc0919 100644
--- a/components/viz/service/display_embedder/gl_output_surface_ozone.h
+++ b/components/viz/service/display_embedder/gl_output_surface_ozone.h
@@ -20,6 +20,9 @@
       uint32_t internal_format);
   ~GLOutputSurfaceOzone() override;
 
+  // OutputSurface implementation.
+  OverlayCandidateValidator* GetOverlayCandidateValidator() const override;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(GLOutputSurfaceOzone);
 };
diff --git a/components/viz/service/gl/gpu_service_impl.h b/components/viz/service/gl/gpu_service_impl.h
index 691f2e2..aa3dfcf 100644
--- a/components/viz/service/gl/gpu_service_impl.h
+++ b/components/viz/service/gl/gpu_service_impl.h
@@ -98,6 +98,66 @@
   // accordingly. This can safely be called from any thread.
   void DisableGpuCompositing();
 
+  // mojom::GpuService:
+  void EstablishGpuChannel(int32_t client_id,
+                           uint64_t client_tracing_id,
+                           bool is_gpu_host,
+                           bool cache_shaders_on_disk,
+                           EstablishGpuChannelCallback callback) override;
+  void CloseChannel(int32_t client_id) override;
+#if defined(OS_CHROMEOS)
+  void CreateArcVideoDecodeAccelerator(
+      arc::mojom::VideoDecodeAcceleratorRequest vda_request) override;
+  void CreateArcVideoEncodeAccelerator(
+      arc::mojom::VideoEncodeAcceleratorRequest vea_request) override;
+  void CreateArcVideoProtectedBufferAllocator(
+      arc::mojom::VideoProtectedBufferAllocatorRequest pba_request) override;
+  void CreateArcProtectedBufferManager(
+      arc::mojom::ProtectedBufferManagerRequest pbm_request) override;
+#endif  // defined(OS_CHROMEOS)
+  void CreateJpegDecodeAccelerator(
+      media::mojom::JpegDecodeAcceleratorRequest jda_request) override;
+  void CreateJpegEncodeAccelerator(
+      media::mojom::JpegEncodeAcceleratorRequest jea_request) override;
+  void CreateVideoEncodeAcceleratorProvider(
+      media::mojom::VideoEncodeAcceleratorProviderRequest vea_provider_request)
+      override;
+  void CreateGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
+                             const gfx::Size& size,
+                             gfx::BufferFormat format,
+                             gfx::BufferUsage usage,
+                             int client_id,
+                             gpu::SurfaceHandle surface_handle,
+                             CreateGpuMemoryBufferCallback callback) override;
+  void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
+                              int client_id,
+                              const gpu::SyncToken& sync_token) override;
+  void GetVideoMemoryUsageStats(
+      GetVideoMemoryUsageStatsCallback callback) override;
+#if defined(OS_WIN)
+  void RequestCompleteGpuInfo(RequestCompleteGpuInfoCallback callback) override;
+  void GetGpuSupportedRuntimeVersion(
+      GetGpuSupportedRuntimeVersionCallback callback) override;
+#endif
+  void RequestHDRStatus(RequestHDRStatusCallback callback) override;
+  void LoadedShader(int32_t client_id,
+                    const std::string& key,
+                    const std::string& data) override;
+  void WakeUpGpu() override;
+  void GpuSwitched() override;
+  void DestroyAllChannels() override;
+  void OnBackgroundCleanup() override;
+  void OnBackgrounded() override;
+  void OnForegrounded() override;
+#if defined(OS_MACOSX)
+  void BeginCATransaction() override;
+  void CommitCATransaction(CommitCATransactionCallback callback) override;
+#endif
+  void Crash() override;
+  void Hang() override;
+  void ThrowJavaException() override;
+  void Stop(StopCallback callback) override;
+
   bool is_initialized() const { return !!gpu_host_; }
 
   media::MediaGpuChannelManager* media_gpu_channel_manager() {
@@ -189,66 +249,6 @@
 #endif
   void SetActiveURL(const GURL& url) override;
 
-  // mojom::GpuService:
-  void EstablishGpuChannel(int32_t client_id,
-                           uint64_t client_tracing_id,
-                           bool is_gpu_host,
-                           bool cache_shaders_on_disk,
-                           EstablishGpuChannelCallback callback) override;
-  void CloseChannel(int32_t client_id) override;
-#if defined(OS_CHROMEOS)
-  void CreateArcVideoDecodeAccelerator(
-      arc::mojom::VideoDecodeAcceleratorRequest vda_request) override;
-  void CreateArcVideoEncodeAccelerator(
-      arc::mojom::VideoEncodeAcceleratorRequest vea_request) override;
-  void CreateArcVideoProtectedBufferAllocator(
-      arc::mojom::VideoProtectedBufferAllocatorRequest pba_request) override;
-  void CreateArcProtectedBufferManager(
-      arc::mojom::ProtectedBufferManagerRequest pbm_request) override;
-#endif  // defined(OS_CHROMEOS)
-  void CreateJpegDecodeAccelerator(
-      media::mojom::JpegDecodeAcceleratorRequest jda_request) override;
-  void CreateJpegEncodeAccelerator(
-      media::mojom::JpegEncodeAcceleratorRequest jea_request) override;
-  void CreateVideoEncodeAcceleratorProvider(
-      media::mojom::VideoEncodeAcceleratorProviderRequest vea_provider_request)
-      override;
-  void CreateGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
-                             const gfx::Size& size,
-                             gfx::BufferFormat format,
-                             gfx::BufferUsage usage,
-                             int client_id,
-                             gpu::SurfaceHandle surface_handle,
-                             CreateGpuMemoryBufferCallback callback) override;
-  void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
-                              int client_id,
-                              const gpu::SyncToken& sync_token) override;
-  void GetVideoMemoryUsageStats(
-      GetVideoMemoryUsageStatsCallback callback) override;
-#if defined(OS_WIN)
-  void RequestCompleteGpuInfo(RequestCompleteGpuInfoCallback callback) override;
-  void GetGpuSupportedRuntimeVersion(
-      GetGpuSupportedRuntimeVersionCallback callback) override;
-#endif
-  void RequestHDRStatus(RequestHDRStatusCallback callback) override;
-  void LoadedShader(int32_t client_id,
-                    const std::string& key,
-                    const std::string& data) override;
-  void WakeUpGpu() override;
-  void GpuSwitched() override;
-  void DestroyAllChannels() override;
-  void OnBackgroundCleanup() override;
-  void OnBackgrounded() override;
-  void OnForegrounded() override;
-#if defined(OS_MACOSX)
-  void BeginCATransaction() override;
-  void CommitCATransaction(CommitCATransactionCallback callback) override;
-#endif
-  void Crash() override;
-  void Hang() override;
-  void ThrowJavaException() override;
-  void Stop(StopCallback callback) override;
-
 #if defined(OS_CHROMEOS)
   void CreateArcVideoDecodeAcceleratorOnMainThread(
       arc::mojom::VideoDecodeAcceleratorRequest vda_request);
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index a7673ea..db9dae3e 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1131,8 +1131,6 @@
     "media/android/media_player_renderer_web_contents_observer.h",
     "media/android/media_resource_getter_impl.cc",
     "media/android/media_resource_getter_impl.h",
-    "media/android/media_web_contents_observer_android.cc",
-    "media/android/media_web_contents_observer_android.h",
     "media/audible_metrics.cc",
     "media/audible_metrics.h",
     "media/audio_input_stream_broker.cc",
diff --git a/content/browser/loader/source_stream_to_data_pipe.cc b/content/browser/loader/source_stream_to_data_pipe.cc
index 56934492..c3923e59 100644
--- a/content/browser/loader/source_stream_to_data_pipe.cc
+++ b/content/browser/loader/source_stream_to_data_pipe.cc
@@ -65,16 +65,12 @@
 
 void SourceStreamToDataPipe::DidRead(int result) {
   DCHECK(pending_write_);
-  if (result < 0) {
-    // An error case.
+  if (result <= 0) {
+    // An error, or end of the stream.
+    pending_write_->Complete(0);  // Closes the data pipe.
     OnComplete(result);
     return;
   }
-  if (result == 0) {
-    pending_write_->Complete(0);
-    OnComplete(net::OK);
-    return;
-  }
   dest_ = pending_write_->Complete(result);
   pending_write_ = nullptr;
 
@@ -97,7 +93,7 @@
   // Resets the watchers, pipes and the exchange handler, so that
   // we will never be called back.
   writable_handle_watcher_.Cancel();
-  pending_write_ = nullptr;  // Closes the data pipe if this was holding it.
+  pending_write_ = nullptr;
   dest_.reset();
 
   std::move(completion_callback_).Run(result);
diff --git a/content/browser/media/android/media_web_contents_observer_android.cc b/content/browser/media/android/media_web_contents_observer_android.cc
deleted file mode 100644
index 1a2d7b8..0000000
--- a/content/browser/media/android/media_web_contents_observer_android.cc
+++ /dev/null
@@ -1,43 +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/browser/media/android/media_web_contents_observer_android.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/memory/ptr_util.h"
-#include "content/browser/web_contents/web_contents_impl.h"
-#include "content/common/media/media_player_delegate_messages.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-#include "ipc/ipc_message_macros.h"
-
-namespace content {
-
-static void SuspendAllMediaPlayersInRenderFrame(
-    RenderFrameHost* render_frame_host) {
-  render_frame_host->Send(new MediaPlayerDelegateMsg_SuspendAllMediaPlayers(
-      render_frame_host->GetRoutingID()));
-}
-
-MediaWebContentsObserverAndroid::MediaWebContentsObserverAndroid(
-    WebContents* web_contents)
-    : MediaWebContentsObserver(web_contents) {}
-
-MediaWebContentsObserverAndroid::~MediaWebContentsObserverAndroid() {}
-
-// static
-MediaWebContentsObserverAndroid*
-MediaWebContentsObserverAndroid::FromWebContents(WebContents* web_contents) {
-  return static_cast<MediaWebContentsObserverAndroid*>(
-      static_cast<WebContentsImpl*>(web_contents)
-          ->media_web_contents_observer());
-}
-
-void MediaWebContentsObserverAndroid::SuspendAllMediaPlayers() {
-  web_contents()->ForEachFrame(
-      base::BindRepeating(&SuspendAllMediaPlayersInRenderFrame));
-}
-}  // namespace content
diff --git a/content/browser/media/android/media_web_contents_observer_android.h b/content/browser/media/android/media_web_contents_observer_android.h
deleted file mode 100644
index f09839ade..0000000
--- a/content/browser/media/android/media_web_contents_observer_android.h
+++ /dev/null
@@ -1,45 +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_BROWSER_MEDIA_ANDROID_MEDIA_WEB_CONTENTS_OBSERVER_ANDROID_H_
-#define CONTENT_BROWSER_MEDIA_ANDROID_MEDIA_WEB_CONTENTS_OBSERVER_ANDROID_H_
-
-#include <stdint.h>
-
-#include <memory>
-#include <unordered_map>
-
-#include "base/macros.h"
-#include "content/browser/media/media_web_contents_observer.h"
-#include "content/common/content_export.h"
-
-namespace media {
-enum class MediaContentType;
-}  // namespace media
-
-namespace content {
-
-// This class adds Android specific extensions to the MediaWebContentsObserver.
-class CONTENT_EXPORT MediaWebContentsObserverAndroid
-    : public MediaWebContentsObserver {
- public:
-  explicit MediaWebContentsObserverAndroid(WebContents* web_contents);
-  ~MediaWebContentsObserverAndroid() override;
-
-  // Returns the android specific observer for a given web contents.
-  static MediaWebContentsObserverAndroid* FromWebContents(
-      WebContents* web_contents);
-
-  // Called by the WebContents when a tab has been closed but may still be
-  // available for "undo" -- indicates that all media players (even audio only
-  // players typically allowed background audio) bound to this WebContents must
-  // be suspended.
-  void SuspendAllMediaPlayers();
- private:
-  DISALLOW_COPY_AND_ASSIGN(MediaWebContentsObserverAndroid);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_MEDIA_ANDROID_MEDIA_WEB_CONTENTS_OBSERVER_ANDROID_H_
diff --git a/content/browser/media/media_web_contents_observer.cc b/content/browser/media/media_web_contents_observer.cc
index 740bdc6..b2b1f74 100644
--- a/content/browser/media/media_web_contents_observer.cc
+++ b/content/browser/media/media_web_contents_observer.cc
@@ -54,6 +54,14 @@
   return players->second.find(player_id.delegate_id) != players->second.end();
 }
 
+#if defined(OS_ANDROID)
+static void SuspendAllMediaPlayersInRenderFrame(
+    RenderFrameHost* render_frame_host) {
+  render_frame_host->Send(new MediaPlayerDelegateMsg_SuspendAllMediaPlayers(
+      render_frame_host->GetRoutingID()));
+}
+#endif  // defined(OS_ANDROID)
+
 }  // anonymous namespace
 
 MediaWebContentsObserver::MediaWebContentsObserver(WebContents* web_contents)
@@ -379,4 +387,11 @@
   return static_cast<WebContentsImpl*>(web_contents());
 }
 
+#if defined(OS_ANDROID)
+void MediaWebContentsObserver::SuspendAllMediaPlayers() {
+  web_contents()->ForEachFrame(
+      base::BindRepeating(&SuspendAllMediaPlayersInRenderFrame));
+}
+#endif  // defined(OS_ANDROID)
+
 }  // namespace content
diff --git a/content/browser/media/media_web_contents_observer.h b/content/browser/media/media_web_contents_observer.h
index 788c018..aaffbc3 100644
--- a/content/browser/media/media_web_contents_observer.h
+++ b/content/browser/media/media_web_contents_observer.h
@@ -12,6 +12,7 @@
 #include <set>
 
 #include "base/macros.h"
+#include "build/build_config.h"
 #include "content/browser/media/session/media_session_controllers_manager.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -92,6 +93,13 @@
     audible_metrics_ = audible_metrics;
   }
 
+#if defined(OS_ANDROID)
+  // Called by the WebContents when a tab has been closed but may still be
+  // available for "undo" -- indicates that all media players (even audio only
+  // players typically allowed background audio) bound to this WebContents must
+  // be suspended.
+  void SuspendAllMediaPlayers();
+#endif  // defined(OS_ANDROID)
  protected:
   MediaSessionControllersManager* session_controllers_manager() {
     return &session_controllers_manager_;
diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc
index f26efe9..04ee7dd 100644
--- a/content/browser/renderer_host/input/input_router_impl.cc
+++ b/content/browser/renderer_host/input/input_router_impl.cc
@@ -382,12 +382,9 @@
     touch_action_filter_.IncreaseActiveTouches();
     // In certain corner cases, the ack for the touch start may not come with a
     // touch action, then we should set the touch actions to Auto.
-    if ((compositor_touch_action_enabled_ &&
-         !touch_action_filter_.white_listed_touch_action().has_value()) ||
-        (!compositor_touch_action_enabled_ &&
-         !touch_action_filter_.allowed_touch_action().has_value())) {
+    if (!compositor_touch_action_enabled_ &&
+        !touch_action_filter_.allowed_touch_action().has_value()) {
       touch_action_filter_.OnSetTouchAction(cc::kTouchActionAuto);
-      touch_action_filter_.OnSetWhiteListedTouchAction(cc::kTouchActionAuto);
       if (compositor_touch_action_enabled_)
         touch_event_queue_.StopTimeoutMonitor();
       UpdateTouchAckTimeoutEnabled();
@@ -712,13 +709,12 @@
   // to page functionality, so the timeout could do more harm than good.
   base::Optional<cc::TouchAction> allowed_touch_action =
       touch_action_filter_.allowed_touch_action();
-  base::Optional<cc::TouchAction> white_listed_touch_action =
+  cc::TouchAction white_listed_touch_action =
       touch_action_filter_.white_listed_touch_action();
   const bool touch_ack_timeout_disabled =
       (allowed_touch_action.has_value() &&
        allowed_touch_action.value() == cc::kTouchActionNone) ||
-      (white_listed_touch_action.has_value() &&
-       white_listed_touch_action.value() == cc::kTouchActionNone);
+      (white_listed_touch_action == cc::kTouchActionNone);
   touch_event_queue_.SetAckTimeoutEnabled(!touch_ack_timeout_disabled);
 }
 
diff --git a/content/browser/renderer_host/input/input_router_impl_unittest.cc b/content/browser/renderer_host/input/input_router_impl_unittest.cc
index 6754d94..cee66ad 100644
--- a/content/browser/renderer_host/input/input_router_impl_unittest.cc
+++ b/content/browser/renderer_host/input/input_router_impl_unittest.cc
@@ -480,7 +480,7 @@
                                    source, ack_state);
     EXPECT_EQ(input_router_->AllowedTouchAction(), expected_touch_action);
     EXPECT_EQ(input_router_->touch_action_filter_.white_listed_touch_action(),
-              expected_white_listed_touch_action);
+              expected_white_listed_touch_action.value());
   }
 
   const float radius_x_ = 20.0f;
@@ -512,7 +512,7 @@
     return input_router_->touch_action_filter_.allowed_touch_action_;
   }
 
-  base::Optional<cc::TouchAction> WhiteListedTouchAction() {
+  cc::TouchAction WhiteListedTouchAction() {
     return input_router_->touch_action_filter_.white_listed_touch_action_;
   }
 
@@ -714,58 +714,79 @@
   InputEventAckSource source = compositor_touch_action_enabled_
                                    ? InputEventAckSource::COMPOSITOR_THREAD
                                    : InputEventAckSource::MAIN_THREAD;
+  base::Optional<cc::TouchAction> expected_touch_action;
+  if (!compositor_touch_action_enabled_)
+    expected_touch_action = cc::kTouchActionAuto;
   OnTouchEventAckWithAckState(source, INPUT_EVENT_ACK_STATE_CONSUMED,
-                              cc::kTouchActionAuto, cc::kTouchActionAuto);
+                              expected_touch_action, cc::kTouchActionAuto);
 }
 
 TEST_P(InputRouterImplTest, TouchActionAutoWithAckStateNotConsumed) {
   InputEventAckSource source = compositor_touch_action_enabled_
                                    ? InputEventAckSource::COMPOSITOR_THREAD
                                    : InputEventAckSource::MAIN_THREAD;
+  base::Optional<cc::TouchAction> expected_touch_action;
+  if (!compositor_touch_action_enabled_)
+    expected_touch_action = cc::kTouchActionAuto;
   OnTouchEventAckWithAckState(source, INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
-                              cc::kTouchActionAuto, cc::kTouchActionAuto);
+                              expected_touch_action, cc::kTouchActionAuto);
 }
 
 TEST_P(InputRouterImplTest, TouchActionAutoWithAckStateConsumedShouldBubble) {
   InputEventAckSource source = compositor_touch_action_enabled_
                                    ? InputEventAckSource::COMPOSITOR_THREAD
                                    : InputEventAckSource::MAIN_THREAD;
+  base::Optional<cc::TouchAction> expected_touch_action;
+  if (!compositor_touch_action_enabled_)
+    expected_touch_action = cc::kTouchActionAuto;
   OnTouchEventAckWithAckState(source,
                               INPUT_EVENT_ACK_STATE_CONSUMED_SHOULD_BUBBLE,
-                              cc::kTouchActionAuto, cc::kTouchActionAuto);
+                              expected_touch_action, cc::kTouchActionAuto);
 }
 
 TEST_P(InputRouterImplTest, TouchActionAutoWithAckStateNoConsumerExists) {
   InputEventAckSource source = compositor_touch_action_enabled_
                                    ? InputEventAckSource::COMPOSITOR_THREAD
                                    : InputEventAckSource::MAIN_THREAD;
+  base::Optional<cc::TouchAction> expected_touch_action;
+  if (!compositor_touch_action_enabled_)
+    expected_touch_action = cc::kTouchActionAuto;
   OnTouchEventAckWithAckState(source, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
-                              cc::kTouchActionAuto, cc::kTouchActionAuto);
+                              expected_touch_action, cc::kTouchActionAuto);
 }
 
 TEST_P(InputRouterImplTest, TouchActionAutoWithAckStateIgnored) {
   InputEventAckSource source = compositor_touch_action_enabled_
                                    ? InputEventAckSource::COMPOSITOR_THREAD
                                    : InputEventAckSource::MAIN_THREAD;
+  base::Optional<cc::TouchAction> expected_touch_action;
+  if (!compositor_touch_action_enabled_)
+    expected_touch_action = cc::kTouchActionAuto;
   OnTouchEventAckWithAckState(source, INPUT_EVENT_ACK_STATE_IGNORED,
-                              cc::kTouchActionAuto, cc::kTouchActionAuto);
+                              expected_touch_action, cc::kTouchActionAuto);
 }
 
 TEST_P(InputRouterImplTest, TouchActionAutoWithAckStateNonBlocking) {
   InputEventAckSource source = compositor_touch_action_enabled_
                                    ? InputEventAckSource::COMPOSITOR_THREAD
                                    : InputEventAckSource::MAIN_THREAD;
+  base::Optional<cc::TouchAction> expected_touch_action;
+  if (!compositor_touch_action_enabled_)
+    expected_touch_action = cc::kTouchActionAuto;
   OnTouchEventAckWithAckState(source, INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING,
-                              cc::kTouchActionAuto, cc::kTouchActionAuto);
+                              expected_touch_action, cc::kTouchActionAuto);
 }
 
 TEST_P(InputRouterImplTest, TouchActionAutoWithAckStateNonBlockingDueToFling) {
   InputEventAckSource source = compositor_touch_action_enabled_
                                    ? InputEventAckSource::COMPOSITOR_THREAD
                                    : InputEventAckSource::MAIN_THREAD;
+  base::Optional<cc::TouchAction> expected_touch_action;
+  if (!compositor_touch_action_enabled_)
+    expected_touch_action = cc::kTouchActionAuto;
   OnTouchEventAckWithAckState(
       source, INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING_DUE_TO_FLING,
-      cc::kTouchActionAuto, cc::kTouchActionAuto);
+      expected_touch_action, cc::kTouchActionAuto);
 }
 
 // Tests that touch-events are sent properly.
@@ -2087,13 +2108,11 @@
       expected_touch_action);
   ASSERT_EQ(1U, disposition_handler_->GetAndResetAckCount());
   base::Optional<cc::TouchAction> allowed_touch_action = AllowedTouchAction();
-  base::Optional<cc::TouchAction> white_listed_touch_action =
-      WhiteListedTouchAction();
+  cc::TouchAction white_listed_touch_action = WhiteListedTouchAction();
   if (compositor_touch_action_enabled_) {
     EXPECT_FALSE(allowed_touch_action.has_value());
-    EXPECT_EQ(expected_touch_action, white_listed_touch_action);
+    EXPECT_EQ(expected_touch_action.value(), white_listed_touch_action);
   } else {
-    EXPECT_FALSE(white_listed_touch_action.has_value());
     EXPECT_EQ(expected_touch_action, allowed_touch_action);
   }
 }
diff --git a/content/browser/renderer_host/input/touch_action_filter.cc b/content/browser/renderer_host/input/touch_action_filter.cc
index 6cacb57..8234734 100644
--- a/content/browser/renderer_host/input/touch_action_filter.cc
+++ b/content/browser/renderer_host/input/touch_action_filter.cc
@@ -137,16 +137,9 @@
     return FilterGestureEventResult::kFilterGestureEventDelayed;
   }
 
-  base::Optional<cc::TouchAction> touch_action;
-  if (gesture_event->GetType() == WebInputEvent::kGestureTapDown) {
-    touch_action = allowed_touch_action_.has_value()
-                       ? allowed_touch_action_
-                       : white_listed_touch_action_;
-  } else {
-    touch_action = active_touch_action_.has_value()
-                       ? active_touch_action_
-                       : white_listed_touch_action_;
-  }
+  cc::TouchAction touch_action = active_touch_action_.has_value()
+                                     ? active_touch_action_.value()
+                                     : white_listed_touch_action_;
 
   // Filter for allowable touch actions first (eg. before the TouchEventQueue
   // can decide to send a touch cancel event).
@@ -156,9 +149,13 @@
       // GestureScrollBegin could come without GestureTapDown.
       if (!gesture_sequence_in_progress_) {
         gesture_sequence_in_progress_ = true;
-        if (!active_touch_action_.has_value()) {
-          SetTouchAction(cc::kTouchActionAuto);
-          touch_action = cc::kTouchActionAuto;
+        if (allowed_touch_action_.has_value()) {
+          active_touch_action_ = allowed_touch_action_;
+          touch_action = allowed_touch_action_.value();
+        } else {
+          touch_action = compositor_touch_action_enabled_
+                             ? white_listed_touch_action_
+                             : active_touch_action_.value();
         }
       }
       gesture_sequence_.append("B");
@@ -169,16 +166,8 @@
         base::debug::SetCrashKeyString(crash_key, gesture_sequence_);
         gesture_sequence_.clear();
       }
-      if (compositor_touch_action_enabled_ && !touch_action.has_value()) {
-        static auto* crash_key = base::debug::AllocateCrashKeyString(
-            "scrollbegin1-gestures", base::debug::CrashKeySize::Size256);
-        base::debug::SetCrashKeyString(crash_key, gesture_sequence_);
-        base::debug::DumpWithoutCrashing();
-        gesture_sequence_.clear();
-      }
-      DCHECK(touch_action.has_value());
       drop_scroll_events_ =
-          ShouldSuppressScrolling(*gesture_event, touch_action.value());
+          ShouldSuppressScrolling(*gesture_event, touch_action);
       FilterGestureEventResult res;
       if (!drop_scroll_events_) {
         res = FilterGestureEventResult::kFilterGestureEventAllowed;
@@ -218,7 +207,7 @@
         base::debug::SetCrashKeyString(crash_key, gesture_sequence_);
         gesture_sequence_.clear();
       }
-      if (IsYAxisActionDisallowed(touch_action.value())) {
+      if (IsYAxisActionDisallowed(touch_action)) {
         if (compositor_touch_action_enabled_ &&
             !active_touch_action_.has_value() &&
             gesture_event->data.scroll_update.delta_y != 0) {
@@ -230,7 +219,7 @@
         }
         gesture_event->data.scroll_update.delta_y = 0;
         gesture_event->data.scroll_update.velocity_y = 0;
-      } else if (IsXAxisActionDisallowed(touch_action.value())) {
+      } else if (IsXAxisActionDisallowed(touch_action)) {
         if (compositor_touch_action_enabled_ &&
             !active_touch_action_.has_value() &&
             gesture_event->data.scroll_update.delta_x != 0) {
@@ -259,18 +248,18 @@
       if (gesture_sequence_.size() >= 1000)
         gesture_sequence_.erase(gesture_sequence_.begin(),
                                 gesture_sequence_.end() - 250);
+      // Do not reset |white_listed_touch_action_|. In the fling cancel case,
+      // the ack for the second touch sequence start, which sets the white
+      // listed touch action, could arrive before the GSE of the first fling
+      // sequence, we do not want to reset the white listed touch action.
       gesture_sequence_in_progress_ = false;
-      // Whenever there is a new touch start, white listed touch action will be
-      // set. So it is fine to reset at GSE.
-      white_listed_touch_action_.reset();
       ReportGestureEventFiltered(drop_scroll_events_);
       return FilterScrollEventAndResetState();
 
     // Evaluate the |drop_pinch_events_| here instead of GSB because pinch
     // events could arrive without GSB, e.g. double-tap-drag.
     case WebInputEvent::kGesturePinchBegin:
-      drop_pinch_events_ =
-          (touch_action.value() & cc::kTouchActionPinchZoom) == 0;
+      drop_pinch_events_ = (touch_action & cc::kTouchActionPinchZoom) == 0;
       FALLTHROUGH;
     case WebInputEvent::kGesturePinchUpdate:
       gesture_sequence_.append("P");
@@ -313,7 +302,7 @@
         gesture_sequence_.clear();
       }
       allow_current_double_tap_event_ =
-          (touch_action.value() & cc::kTouchActionDoubleTapZoom) != 0;
+          (touch_action & cc::kTouchActionDoubleTapZoom) != 0;
       if (!allow_current_double_tap_event_) {
         gesture_event->SetType(WebInputEvent::kGestureTap);
         drop_current_tap_ending_event_ = true;
@@ -344,29 +333,10 @@
         gesture_sequence_.append("AY");
       else
         gesture_sequence_.append("AN");
-      if (white_listed_touch_action_.has_value())
-        gesture_sequence_.append("WY");
-      else
-        gesture_sequence_.append("WN");
       if (active_touch_action_.has_value())
         gesture_sequence_.append("OY");
       else
         gesture_sequence_.append("ON");
-      if (compositor_touch_action_enabled_ &&
-          !allowed_touch_action_.has_value() &&
-          !white_listed_touch_action_.has_value()) {
-        static auto* crash_key = base::debug::AllocateCrashKeyString(
-            "tapdown-gestures", base::debug::CrashKeySize::Size256);
-        if (gesture_sequence_.size() >= 256)
-          gesture_sequence_.erase(gesture_sequence_.begin(),
-                                  gesture_sequence_.end() - 256);
-        base::debug::SetCrashKeyString(crash_key, gesture_sequence_);
-        base::debug::DumpWithoutCrashing();
-        gesture_sequence_.clear();
-      }
-      // TODO(xidachen): investigate why the touch action has no value.
-      if (compositor_touch_action_enabled_ && !touch_action.has_value())
-        SetTouchAction(cc::kTouchActionAuto);
       // In theory, the num_of_active_touches_ should be > 0 at this point. But
       // crash reports suggest otherwise.
       if (num_of_active_touches_ <= 0)
@@ -481,11 +451,9 @@
   // Report how often the effective touch action computed by blink is or is
   // not equivalent to the whitelisted touch action computed by the
   // compositor.
-  if (white_listed_touch_action_.has_value()) {
-    UMA_HISTOGRAM_BOOLEAN(
-        "TouchAction.EquivalentEffectiveAndWhiteListed",
-        active_touch_action_.value() == white_listed_touch_action_.value());
-  }
+  UMA_HISTOGRAM_BOOLEAN(
+      "TouchAction.EquivalentEffectiveAndWhiteListed",
+      active_touch_action_.value() == white_listed_touch_action_);
 }
 
 void TouchActionFilter::AppendToGestureSequenceForDebugging(const char* str) {
@@ -498,6 +466,7 @@
   // sequenceo.
   if (has_touch_event_handler_) {
     allowed_touch_action_.reset();
+    white_listed_touch_action_ = cc::kTouchActionAuto;
   } else {
     // Lack of a touch handler indicates that the page either has no
     // touch-action modifiers or that all its touch-action modifiers are auto.
@@ -511,12 +480,8 @@
     cc::TouchAction white_listed_touch_action) {
   // We use '&' here to account for the multiple-finger case, which is the same
   // as OnSetTouchAction.
-  if (white_listed_touch_action_.has_value()) {
-    white_listed_touch_action_ =
-        white_listed_touch_action_.value() & white_listed_touch_action;
-  } else {
-    white_listed_touch_action_ = white_listed_touch_action;
-  }
+  white_listed_touch_action_ =
+      white_listed_touch_action_ & white_listed_touch_action;
 }
 
 bool TouchActionFilter::ShouldSuppressScrolling(
@@ -577,7 +542,6 @@
     if (has_touch_event_handler_) {
       gesture_sequence_.append("H");
       active_touch_action_.reset();
-      white_listed_touch_action_.reset();
     }
   }
 }
diff --git a/content/browser/renderer_host/input/touch_action_filter.h b/content/browser/renderer_host/input/touch_action_filter.h
index e048fa76..f1d5f6a 100644
--- a/content/browser/renderer_host/input/touch_action_filter.h
+++ b/content/browser/renderer_host/input/touch_action_filter.h
@@ -59,7 +59,7 @@
     return allowed_touch_action_;
   }
 
-  base::Optional<cc::TouchAction> white_listed_touch_action() const {
+  cc::TouchAction white_listed_touch_action() const {
     return white_listed_touch_action_;
   }
 
@@ -136,10 +136,8 @@
   // sequence due to fling.
   base::Optional<cc::TouchAction> active_touch_action_;
 
-  // TODO(xidachen): consider giving this a default value of Auto, instead of
-  // Optional.
   // Whitelisted touch action received from the compositor.
-  base::Optional<cc::TouchAction> white_listed_touch_action_;
+  cc::TouchAction white_listed_touch_action_;
 
   // Debugging only.
   std::string gesture_sequence_;
diff --git a/content/browser/renderer_host/input/touch_action_filter_unittest.cc b/content/browser/renderer_host/input/touch_action_filter_unittest.cc
index a9ce1d2..7d3b4983 100644
--- a/content/browser/renderer_host/input/touch_action_filter_unittest.cc
+++ b/content/browser/renderer_host/input/touch_action_filter_unittest.cc
@@ -44,7 +44,7 @@
   void ResetTouchAction() { filter_.ResetTouchAction(); }
   void ResetActiveTouchAction() { filter_.active_touch_action_.reset(); }
   void ResetWhiteListedTouchAction() {
-    filter_.white_listed_touch_action_.reset();
+    filter_.white_listed_touch_action_ = cc::kTouchActionAuto;
   }
   void SetNoDeferredEvents() { filter_.has_deferred_events_ = false; }
   void SetGestureSequenceInProgress() {
@@ -1202,7 +1202,6 @@
   filter_.OnHasTouchEventHandlers(true);
   EXPECT_FALSE(ActiveTouchAction().has_value());
   EXPECT_FALSE(filter_.allowed_touch_action().has_value());
-  EXPECT_FALSE(filter_.white_listed_touch_action().has_value());
 
   int dx = 2, dy = 5;
   // Test gestures that are allowed.
@@ -1217,7 +1216,7 @@
   filter_.OnSetWhiteListedTouchAction(cc::kTouchActionPan);
   if (!compositor_touch_action_enabled_)
     filter_.OnSetTouchAction(cc::kTouchActionPan);
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionPan);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPan);
   SetGestureSequenceInProgress();
   EXPECT_EQ(filter_.FilterGestureEvent(&scroll_begin),
             FilterGestureEventResult::kFilterGestureEventAllowed);
@@ -1233,7 +1232,7 @@
   filter_.OnSetWhiteListedTouchAction(cc::kTouchActionPan);
   if (!compositor_touch_action_enabled_)
     filter_.OnSetTouchAction(cc::kTouchActionPan);
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionPan);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPan);
   WebGestureEvent pinch_begin = SyntheticWebGestureEventBuilder::Build(
       WebInputEvent::kGesturePinchBegin, kSourceDevice);
   WebGestureEvent pinch_update =
@@ -1266,7 +1265,7 @@
   if (!compositor_touch_action_enabled_)
     filter_.OnSetTouchAction(cc::kTouchActionPanY);
   SetNoDeferredEvents();
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionPanY);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPanY);
   SetGestureSequenceInProgress();
   EXPECT_EQ(filter_.FilterGestureEvent(&scroll_begin),
             FilterGestureEventResult::kFilterGestureEventAllowed);
@@ -1289,7 +1288,7 @@
   if (!compositor_touch_action_enabled_)
     filter_.OnSetTouchAction(cc::kTouchActionPanX);
   SetNoDeferredEvents();
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionPanX);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPanX);
 
   dy = 0;
   scroll_begin =
@@ -1311,7 +1310,7 @@
   scroll_update = SyntheticWebGestureEventBuilder::BuildScrollUpdate(
       dx, dy, 0, kSourceDevice);
   filter_.OnSetWhiteListedTouchAction(cc::kTouchActionPanX);
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionPanX);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPanX);
   SetGestureSequenceInProgress();
   if (compositor_touch_action_enabled_) {
     EXPECT_EQ(filter_.FilterGestureEvent(&scroll_begin),
@@ -1330,22 +1329,21 @@
   }
 }
 
-TEST_P(TouchActionFilterTest, WhiteListedTouchActionNotResetHasHandlers) {
+TEST_P(TouchActionFilterTest, WhiteListedTouchActionResetToAuto) {
   filter_.OnHasTouchEventHandlers(true);
-  EXPECT_FALSE(filter_.white_listed_touch_action().has_value());
 
   filter_.OnSetWhiteListedTouchAction(cc::kTouchActionPan);
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionPan);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPan);
   ResetTouchAction();
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionPan);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionAuto);
 }
 
 TEST_P(TouchActionFilterTest, WhiteListedTouchActionAutoNoHasHandlers) {
   filter_.OnHasTouchEventHandlers(false);
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionAuto);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionAuto);
 
   ResetTouchAction();
-  EXPECT_EQ(filter_.white_listed_touch_action().value(), cc::kTouchActionAuto);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionAuto);
 }
 
 TEST_P(TouchActionFilterTest, ResetBeforeHasHandlerSet) {
@@ -1357,6 +1355,35 @@
             FilterGestureEventResult::kFilterGestureEventAllowed);
 }
 
+TEST_P(TouchActionFilterTest,
+       WhiteListedTouchActionNotResetAtGestureScrollEnd) {
+  if (!compositor_touch_action_enabled_)
+    return;
+  filter_.OnHasTouchEventHandlers(true);
+
+  filter_.OnSetWhiteListedTouchAction(cc::kTouchActionPan);
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPan);
+
+  int dx = 2, dy = 5;
+  WebGestureEvent scroll_begin =
+      SyntheticWebGestureEventBuilder::BuildScrollBegin(dx, dy, kSourceDevice);
+  WebGestureEvent scroll_update =
+      SyntheticWebGestureEventBuilder::BuildScrollUpdate(dx, dy, 0,
+                                                         kSourceDevice);
+  WebGestureEvent scroll_end = SyntheticWebGestureEventBuilder::Build(
+      WebInputEvent::kGestureScrollEnd, kSourceDevice);
+
+  SetGestureSequenceInProgress();
+  EXPECT_EQ(filter_.FilterGestureEvent(&scroll_begin),
+            FilterGestureEventResult::kFilterGestureEventAllowed);
+  EXPECT_EQ(filter_.FilterGestureEvent(&scroll_update),
+            FilterGestureEventResult::kFilterGestureEventAllowed);
+  EXPECT_EQ(filter_.FilterGestureEvent(&scroll_end),
+            FilterGestureEventResult::kFilterGestureEventAllowed);
+
+  EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPan);
+}
+
 // Having a gesture scroll begin without tap down should set touch action to
 // Auto.
 TEST_P(TouchActionFilterTest, ScrollBeginWithoutTapDown) {
@@ -1364,12 +1391,20 @@
   EXPECT_FALSE(ActiveTouchAction().has_value());
   EXPECT_FALSE(filter_.allowed_touch_action().has_value());
 
+  if (compositor_touch_action_enabled_)
+    filter_.OnSetWhiteListedTouchAction(cc::kTouchActionPan);
+  else
+    filter_.OnSetTouchAction(cc::kTouchActionPan);
   WebGestureEvent scroll_begin =
       SyntheticWebGestureEventBuilder::BuildScrollBegin(5, 0, kSourceDevice);
   EXPECT_EQ(filter_.FilterGestureEvent(&scroll_begin),
             FilterGestureEventResult::kFilterGestureEventAllowed);
-  EXPECT_EQ(ActiveTouchAction().value(), cc::kTouchActionAuto);
-  EXPECT_EQ(filter_.allowed_touch_action().value(), cc::kTouchActionAuto);
+  if (compositor_touch_action_enabled_) {
+    EXPECT_EQ(filter_.white_listed_touch_action(), cc::kTouchActionPan);
+  } else {
+    EXPECT_EQ(ActiveTouchAction().value(), cc::kTouchActionPan);
+    EXPECT_EQ(filter_.allowed_touch_action().value(), cc::kTouchActionPan);
+  }
 }
 
 // This tests a gesture tap down with |num_of_active_touches_| == 0
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 2ccad70..a8df14b 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -364,7 +364,8 @@
   params->opener_frame_route_id = opener_frame_route_id;
   params->replicated_frame_state = replicated_frame_state;
   params->proxy_routing_id = proxy_route_id;
-  params->hidden = GetWidget()->delegate()->IsHidden();
+  params->hidden = is_active() ? GetWidget()->is_hidden()
+                               : GetWidget()->delegate()->IsHidden();
   params->never_visible = delegate_->IsNeverVisible();
   params->window_was_created_with_opener = window_was_created_with_opener;
   if (main_rfh) {
diff --git a/content/browser/renderer_host/render_widget_host_delegate.h b/content/browser/renderer_host/render_widget_host_delegate.h
index 25d59c5..688a332 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.h
+++ b/content/browser/renderer_host/render_widget_host_delegate.h
@@ -249,7 +249,6 @@
   // the RenderWidgetHost to ask the delegate if it can be shown in the event of
   // something other than the WebContents attempting to enable visibility of
   // this RenderWidgetHost.
-  // TODO(nasko): Move this to RenderViewHostDelegate.
   virtual bool IsHidden();
 
   // Returns the associated RenderViewHostDelegateView*, if possible.
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 93a418b..2baa5daf 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -46,7 +46,6 @@
 #include "content/browser/compositor/surface_utils.h"
 #include "content/browser/devtools/render_frame_devtools_agent_host.h"
 #include "content/browser/gpu/gpu_process_host.h"
-#include "content/browser/media/android/media_web_contents_observer_android.h"
 #include "content/browser/renderer_host/compositor_impl_android.h"
 #include "content/browser/renderer_host/delegated_frame_host_client_android.h"
 #include "content/browser/renderer_host/dip_util.h"
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 30360e3..2bc40d1 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -12412,7 +12412,6 @@
             ->input_router());
     // Clear the touch actions that were set by previous touches.
     input_router->touch_action_filter_.allowed_touch_action_.reset();
-    input_router->touch_action_filter_.white_listed_touch_action_.reset();
     // Send a touch start event to child to get the TAF filled with child
     // frame's touch action.
     ack_observer.Reset();
diff --git a/content/browser/web_contents/web_contents_android.cc b/content/browser/web_contents/web_contents_android.cc
index 777bee8..b724462 100644
--- a/content/browser/web_contents/web_contents_android.cc
+++ b/content/browser/web_contents/web_contents_android.cc
@@ -26,7 +26,7 @@
 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
 #include "content/browser/android/java/gin_java_bridge_dispatcher_host.h"
 #include "content/browser/frame_host/interstitial_page_impl.h"
-#include "content/browser/media/android/media_web_contents_observer_android.h"
+#include "content/browser/media/media_web_contents_observer.h"
 #include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/browser/web_contents/web_contents_view_android.h"
@@ -426,8 +426,7 @@
 void WebContentsAndroid::SuspendAllMediaPlayers(
     JNIEnv* env,
     const JavaParamRef<jobject>& jobj) {
-  MediaWebContentsObserverAndroid::FromWebContents(web_contents_)
-      ->SuspendAllMediaPlayers();
+  web_contents_->media_web_contents_observer()->SuspendAllMediaPlayers();
 }
 
 void WebContentsAndroid::SetAudioMuted(JNIEnv* env,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 613d5ff..e87aa15 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -170,7 +170,6 @@
 #if defined(OS_ANDROID)
 #include "content/browser/android/date_time_chooser_android.h"
 #include "content/browser/android/java_interfaces_impl.h"
-#include "content/browser/media/android/media_web_contents_observer_android.h"
 #include "content/browser/web_contents/web_contents_android.h"
 #include "services/device/public/mojom/nfc.mojom.h"
 #else  // !OS_ANDROID
@@ -592,6 +591,8 @@
               browser_context)),
       audio_stream_monitor_(this),
       bluetooth_connected_device_count_(0),
+      media_web_contents_observer_(
+          std::make_unique<MediaWebContentsObserver>(this)),
       media_device_group_id_salt_base_(
           BrowserContext::CreateRandomMediaDeviceIDSalt()),
 #if !defined(OS_ANDROID)
@@ -604,11 +605,6 @@
   frame_tree_.SetFrameRemoveListener(
       base::Bind(&WebContentsImpl::OnFrameRemoved,
                  base::Unretained(this)));
-#if defined(OS_ANDROID)
-  media_web_contents_observer_.reset(new MediaWebContentsObserverAndroid(this));
-#else
-  media_web_contents_observer_.reset(new MediaWebContentsObserver(this));
-#endif
 #if BUILDFLAG(ENABLE_PLUGINS)
   pepper_playback_observer_.reset(new PepperPlaybackObserver(this));
 #endif
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 35fe0e6..a5153dc 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -3242,17 +3242,4 @@
   EXPECT_EQ(new_web_contents1, new_web_contents2);
 }
 
-IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, SetVisibilityBeforeLoad) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL url(embedded_test_server()->GetURL("/hello.html"));
-
-  WebContentsImpl* web_contents =
-      static_cast<WebContentsImpl*>(shell()->web_contents());
-  web_contents->WasHidden();
-  TestNavigationObserver same_tab_observer(web_contents);
-  shell()->LoadURL(url);
-  same_tab_observer.Wait();
-  EXPECT_TRUE(EvalJs(shell(), "document.hidden").ExtractBool());
-}
-
 }  // namespace content
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index dfde858..61b18ad 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -107,6 +107,8 @@
     "cookie_store_factory.h",
     "cors_exempt_headers.cc",
     "cors_exempt_headers.h",
+    "cors_origin_pattern_setter.cc",
+    "cors_origin_pattern_setter.h",
     "delegate_to_browser_gpu_service_accelerator_factory.h",
     "desktop_capture.cc",
     "desktop_capture.h",
diff --git a/content/public/browser/cors_origin_pattern_setter.cc b/content/public/browser/cors_origin_pattern_setter.cc
new file mode 100644
index 0000000..27b4798
--- /dev/null
+++ b/content/public/browser/cors_origin_pattern_setter.cc
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/browser/cors_origin_pattern_setter.h"
+#include "content/public/browser/storage_partition.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+
+namespace content {
+
+CorsOriginPatternSetter::CorsOriginPatternSetter(
+    const url::Origin& source_origin,
+    std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns,
+    std::vector<network::mojom::CorsOriginPatternPtr> block_patterns,
+    base::OnceClosure closure)
+    : source_origin_(source_origin),
+      allow_patterns_(std::move(allow_patterns)),
+      block_patterns_(std::move(block_patterns)),
+      closure_(std::move(closure)) {}
+
+CorsOriginPatternSetter::~CorsOriginPatternSetter() {
+  std::move(closure_).Run();
+}
+
+void CorsOriginPatternSetter::SetLists(StoragePartition* partition) {
+  partition->GetNetworkContext()->SetCorsOriginAccessListsForOrigin(
+      source_origin_, ClonePatterns(allow_patterns_),
+      ClonePatterns(block_patterns_),
+      base::BindOnce([](scoped_refptr<CorsOriginPatternSetter> setter) {},
+                     base::RetainedRef(this)));
+}
+
+// static
+std::vector<network::mojom::CorsOriginPatternPtr>
+CorsOriginPatternSetter::ClonePatterns(
+    const std::vector<network::mojom::CorsOriginPatternPtr>& patterns) {
+  std::vector<network::mojom::CorsOriginPatternPtr> cloned_patterns;
+  cloned_patterns.reserve(patterns.size());
+  for (const auto& item : patterns)
+    cloned_patterns.push_back(item.Clone());
+  return cloned_patterns;
+}
+
+}  // namespace content
diff --git a/content/public/browser/cors_origin_pattern_setter.h b/content/public/browser/cors_origin_pattern_setter.h
new file mode 100644
index 0000000..da2b156
--- /dev/null
+++ b/content/public/browser/cors_origin_pattern_setter.h
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_CORS_ORIGIN_PATTERN_SETTER_H_
+#define CONTENT_PUBLIC_BROWSER_CORS_ORIGIN_PATTERN_SETTER_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/barrier_closure.h"
+#include "base/callback.h"
+#include "content/common/content_export.h"
+#include "services/network/public/mojom/cors_origin_pattern.mojom.h"
+#include "url/origin.h"
+
+namespace content {
+
+class StoragePartition;
+
+// A class used to make an asynchronous Mojo call with cloned patterns for each
+// StoragePartition iteration. |this| instance will be destructed when all
+// existing asynchronous Mojo calls made in SetLists() are done, and |closure|
+// will be invoked on destructing |this|.
+//
+// Typically this would be used to implement
+// BrowserContext::SetCorsOriginAccessListForOrigin, and would use
+// ForEachStoragePartition with SetLists as the StoragePartitionCallback.
+class CONTENT_EXPORT CorsOriginPatternSetter
+    : public base::RefCounted<CorsOriginPatternSetter> {
+ public:
+  CorsOriginPatternSetter(
+      const url::Origin& source_origin,
+      std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns,
+      std::vector<network::mojom::CorsOriginPatternPtr> block_patterns,
+      base::OnceClosure closure);
+
+  void SetLists(StoragePartition* partition);
+
+  static std::vector<network::mojom::CorsOriginPatternPtr> ClonePatterns(
+      const std::vector<network::mojom::CorsOriginPatternPtr>& patterns);
+
+ private:
+  friend class base::RefCounted<CorsOriginPatternSetter>;
+
+  ~CorsOriginPatternSetter();
+
+  const url::Origin source_origin_;
+  const std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns_;
+  const std::vector<network::mojom::CorsOriginPatternPtr> block_patterns_;
+
+  base::OnceClosure closure_;
+
+  DISALLOW_COPY_AND_ASSIGN(CorsOriginPatternSetter);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_CORS_ORIGIN_PATTERN_SETTER_H_
diff --git a/content/public/test/blink_test_environment.h b/content/public/test/blink_test_environment.h
index da56a64..c973147 100644
--- a/content/public/test/blink_test_environment.h
+++ b/content/public/test/blink_test_environment.h
@@ -5,7 +5,7 @@
 #ifndef CONTENT_PUBLIC_TEST_BLINK_TEST_ENVIRONMENT_H_
 #define CONTENT_PUBLIC_TEST_BLINK_TEST_ENVIRONMENT_H_
 
-// This package provides functions used by webkit_unit_tests.
+// This package provides functions used by blink_unittests.
 namespace content {
 
 // Initializes Blink test environment for unit tests.
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.cc b/content/renderer/loader/web_worker_fetch_context_impl.cc
index ac9541f..72b0f02 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.cc
+++ b/content/renderer/loader/web_worker_fetch_context_impl.cc
@@ -262,7 +262,7 @@
       mojo::MakeRequest(&service_worker_worker_client_registry_ptr_info));
 
   blink::mojom::ServiceWorkerContainerHostPtrInfo host_ptr_info;
-  if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
+  if (service_worker_container_host_) {
     service_worker_container_host_->CloneContainerHost(
         mojo::MakeRequest(&host_ptr_info));
   }
@@ -317,8 +317,10 @@
     preference_watcher_binding_.Bind(std::move(preference_watcher_request_));
 
   if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
-    service_worker_container_host_.Bind(
-        std::move(service_worker_container_host_info_));
+    if (service_worker_container_host_info_) {
+      service_worker_container_host_.Bind(
+          std::move(service_worker_container_host_info_));
+    }
 
     blink::mojom::BlobRegistryPtr blob_registry_ptr;
     service_manager_connection_->BindInterface(
@@ -533,6 +535,8 @@
     web_loader_factory_->SetServiceWorkerURLLoaderFactory(nullptr);
     return;
   }
+  if (!service_worker_container_host_)
+    return;
 
   network::mojom::URLLoaderFactoryPtr service_worker_url_loader_factory;
   blink::mojom::ServiceWorkerContainerHostPtrInfo host_ptr_info;
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.h b/content/renderer/loader/web_worker_fetch_context_impl.h
index 7bebcc5..b4beeb8 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.h
+++ b/content/renderer/loader/web_worker_fetch_context_impl.h
@@ -203,6 +203,8 @@
 
   // S13nServiceWorker:
   // Initialized on the worker thread when InitializeOnWorkerThread() is called.
+  // May be nullptr if the creating context was already being destructed, see
+  // ServiceWorkerProviderContext::OnNetworkProviderDestroyed().
   blink::mojom::ServiceWorkerContainerHostPtr service_worker_container_host_;
 
   // S13nServiceWorker:
diff --git a/content/renderer/media/media_factory.cc b/content/renderer/media/media_factory.cc
index 2d52883..6b0a942 100644
--- a/content/renderer/media/media_factory.cc
+++ b/content/renderer/media/media_factory.cc
@@ -110,7 +110,7 @@
 // establishing a GPUChannelHost, which must be done on the main thread.
 void PostContextProviderToCallback(
     scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
-    scoped_refptr<viz::ContextProvider> unwanted_context_provider,
+    scoped_refptr<viz::RasterContextProvider> unwanted_context_provider,
     blink::WebSubmitterConfigurationCallback set_context_provider_callback) {
   // |unwanted_context_provider| needs to be destroyed on the current thread.
   // Therefore, post a reply-callback that retains a reference to it, so that it
@@ -118,7 +118,8 @@
   main_task_runner->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(
-          [](scoped_refptr<viz::ContextProvider> unwanted_context_provider,
+          [](scoped_refptr<viz::RasterContextProvider>
+                 unwanted_context_provider,
              blink::WebSubmitterConfigurationCallback cb) {
             auto* rti = content::RenderThreadImpl::current();
             auto context_provider = rti->GetVideoFrameCompositorContextProvider(
@@ -128,9 +129,9 @@
           },
           unwanted_context_provider,
           media::BindToCurrentLoop(std::move(set_context_provider_callback))),
-      base::BindOnce(
-          [](scoped_refptr<viz::ContextProvider> unwanted_context_provider) {},
-          unwanted_context_provider));
+      base::BindOnce([](scoped_refptr<viz::RasterContextProvider>
+                            unwanted_context_provider) {},
+                     unwanted_context_provider));
 }
 
 }  // namespace
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index f426c57..b5669f9 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1370,9 +1370,9 @@
   return gpu_factories_.back().get();
 }
 
-scoped_refptr<viz::ContextProvider>
+scoped_refptr<viz::RasterContextProvider>
 RenderThreadImpl::GetVideoFrameCompositorContextProvider(
-    scoped_refptr<viz::ContextProvider> unwanted_context_provider) {
+    scoped_refptr<viz::RasterContextProvider> unwanted_context_provider) {
   DCHECK(video_frame_compositor_task_runner_);
   if (video_frame_compositor_context_provider_ &&
       video_frame_compositor_context_provider_ != unwanted_context_provider) {
@@ -1396,7 +1396,7 @@
 
   bool support_locking = false;
   bool support_gles2_interface = true;
-  bool support_raster_interface = false;
+  bool support_raster_interface = true;
   bool support_oop_rasterization = false;
   bool support_grcontext = false;
   bool automatic_flushes = false;
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index 5a5b774..579131f 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -385,8 +385,9 @@
   // video frame compositing. The ContextProvider given as an argument is
   // one that has been lost, and is a hint to the RenderThreadImpl to clear
   // it's |video_frame_compositor_context_provider_| if it matches.
-  scoped_refptr<viz::ContextProvider> GetVideoFrameCompositorContextProvider(
-      scoped_refptr<viz::ContextProvider>);
+  scoped_refptr<viz::RasterContextProvider>
+      GetVideoFrameCompositorContextProvider(
+          scoped_refptr<viz::RasterContextProvider>);
 
   // Returns a worker context provider that will be bound on the compositor
   // thread.
@@ -676,7 +677,8 @@
 
   base::ObserverList<RenderThreadObserver>::Unchecked observers_;
 
-  scoped_refptr<viz::ContextProvider> video_frame_compositor_context_provider_;
+  scoped_refptr<viz::RasterContextProvider>
+      video_frame_compositor_context_provider_;
 
   scoped_refptr<viz::RasterContextProvider> shared_worker_context_provider_;
 
diff --git a/content/renderer/service_worker/service_worker_network_provider_for_frame.cc b/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
index 357104eb..9025ebe 100644
--- a/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
+++ b/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
@@ -144,10 +144,6 @@
     // ServiceWorkerContainerHost since we couldn't set it up correctly due to
     // this test limitation. This way we don't crash when the associated
     // interface ptr is used.
-    //
-    // TODO(falken): Just give ServiceWorkerProviderContext a null interface ptr
-    // and make the callsites deal with it. They are supposed to anyway because
-    // OnNetworkProviderDestroyed() can reset the ptr to null at any time.
     mojo::AssociateWithDisconnectedPipe(host_info->host_request.PassHandle());
   }
   return provider;
diff --git a/content/renderer/service_worker/service_worker_provider_context.cc b/content/renderer/service_worker/service_worker_provider_context.cc
index 6f166df..f6a2649 100644
--- a/content/renderer/service_worker/service_worker_provider_context.cc
+++ b/content/renderer/service_worker/service_worker_provider_context.cc
@@ -131,6 +131,12 @@
   if (!state->subresource_loader_factory) {
     DCHECK(!state->controller_connector);
     DCHECK(state->controller_endpoint);
+
+    blink::mojom::ServiceWorkerContainerHostPtrInfo host_ptr_info =
+        CloneContainerHostPtrInfo();
+    if (!host_ptr_info)
+      return nullptr;
+
     // Create a SubresourceLoaderFactory on a background thread to avoid
     // extra contention on the main thread.
     auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
@@ -138,7 +144,7 @@
     task_runner->PostTask(
         FROM_HERE,
         base::BindOnce(&CreateSubresourceLoaderFactoryForProviderContext,
-                       CloneContainerHostPtrInfo(),
+                       std::move(host_ptr_info),
                        std::move(state->controller_endpoint), state->client_id,
                        state->fallback_loader_factory->Clone(),
                        mojo::MakeRequest(&state->controller_connector),
@@ -201,6 +207,8 @@
   DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
   DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(state_for_client_);
+  if (!container_host_)
+    return nullptr;
   blink::mojom::ServiceWorkerContainerHostPtrInfo container_host_ptr_info;
   container_host_->CloneContainerHost(
       mojo::MakeRequest(&container_host_ptr_info));
@@ -214,6 +222,8 @@
 void ServiceWorkerProviderContext::PingContainerHost(
     base::OnceClosure callback) {
   DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
+  if (!container_host_)
+    return;
   container_host_->Ping(std::move(callback));
 }
 
@@ -232,6 +242,9 @@
     return;
   }
 
+  if (!container_host_)
+    return;
+
   container_host_->HintToUpdateServiceWorker();
 }
 
diff --git a/content/renderer/service_worker/service_worker_provider_context.h b/content/renderer/service_worker/service_worker_provider_context.h
index b41175e..a1b4951 100644
--- a/content/renderer/service_worker/service_worker_provider_context.h
+++ b/content/renderer/service_worker/service_worker_provider_context.h
@@ -136,7 +136,8 @@
 
   // S13nServiceWorker:
   // Returns a ServiceWorkerContainerHostPtrInfo to this context's container
-  // host.
+  // host. This can return null after OnNetworkProviderDestroyed() is called
+  // (in which case |this| will be destroyed soon).
   blink::mojom::ServiceWorkerContainerHostPtrInfo CloneContainerHostPtrInfo();
 
   // Called when WebServiceWorkerNetworkProvider is destructed. This function
@@ -147,6 +148,9 @@
   // ServiceWorkerProviderHost must destruct quickly in order to remove the
   // ServiceWorkerClient from the system (thus allowing unregistration/update to
   // occur and ensuring the Clients API doesn't return the client).
+  //
+  // TODO(https://crbug.com/931497): Remove this weird partially destroyed
+  // state.
   void OnNetworkProviderDestroyed();
 
   // Gets the blink::mojom::ServiceWorkerContainerHost* for sending requests to
@@ -210,7 +214,9 @@
 
   // The |container_host_| interface represents the connection to the
   // browser-side ServiceWorkerProviderHost, whose lifetime is bound to
-  // |container_host_| via the Mojo connection.
+  // |container_host_| via the Mojo connection. This may be nullptr if the Mojo
+  // connection was broken in OnNetworkProviderDestroyed().
+  //
   // The |container_host_| interface also implements functions for
   // navigator.serviceWorker, but all the methods that correspond to
   // navigator.serviceWorker.* can be used only if |this| is a provider
diff --git a/content/renderer/service_worker/service_worker_provider_context_unittest.cc b/content/renderer/service_worker/service_worker_provider_context_unittest.cc
index 7ec251f..78bbda5 100644
--- a/content/renderer/service_worker/service_worker_provider_context_unittest.cc
+++ b/content/renderer/service_worker/service_worker_provider_context_unittest.cc
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
@@ -710,5 +711,47 @@
             *(++(client->used_features().begin())));
 }
 
+TEST_F(ServiceWorkerProviderContextTest, OnNetworkProviderDestroyed) {
+  // Make the object host for .controller.
+  auto object_host =
+      std::make_unique<MockServiceWorkerObjectHost>(200 /* version_id */);
+  blink::mojom::ServiceWorkerObjectInfoPtr object_info =
+      object_host->CreateObjectInfo();
+
+  // Make the ControllerServiceWorkerInfo.
+  FakeControllerServiceWorker fake_controller;
+  auto controller_info = blink::mojom::ControllerServiceWorkerInfo::New();
+  blink::mojom::ControllerServiceWorkerPtr controller_ptr;
+  fake_controller.Clone(mojo::MakeRequest(&controller_ptr));
+  controller_info->mode =
+      blink::mojom::ControllerServiceWorkerMode::kControlled;
+  controller_info->object_info = std::move(object_info);
+  controller_info->endpoint = controller_ptr.PassInterface();
+
+  // Make the container host and container pointers.
+  blink::mojom::ServiceWorkerContainerHostAssociatedPtr host_ptr;
+  blink::mojom::ServiceWorkerContainerHostAssociatedRequest host_request =
+      mojo::MakeRequestAssociatedWithDedicatedPipe(&host_ptr);
+  blink::mojom::ServiceWorkerContainerAssociatedPtr container_ptr;
+  blink::mojom::ServiceWorkerContainerAssociatedRequest container_request =
+      mojo::MakeRequestAssociatedWithDedicatedPipe(&container_ptr);
+
+  // Make the provider context.
+  auto provider_context = base::MakeRefCounted<ServiceWorkerProviderContext>(
+      11 /* provider_id */, blink::mojom::ServiceWorkerProviderType::kForWindow,
+      std::move(container_request), host_ptr.PassInterface(),
+      std::move(controller_info), loader_factory_);
+
+  // Put it in the weird state to test.
+  provider_context->OnNetworkProviderDestroyed();
+
+  // Calling these in the weird state shouldn't crash.
+  EXPECT_FALSE(provider_context->container_host());
+  EXPECT_FALSE(provider_context->CloneContainerHostPtrInfo());
+  provider_context->PingContainerHost(base::DoNothing());
+  provider_context->DispatchNetworkQuiet();
+  provider_context->NotifyExecutionReady();
+}
+
 }  // namespace service_worker_provider_context_unittest
 }  // namespace content
diff --git a/content/renderer/worker/service_worker_network_provider_for_worker.cc b/content/renderer/worker/service_worker_network_provider_for_worker.cc
index 8e4fe41..1106d554 100644
--- a/content/renderer/worker/service_worker_network_provider_for_worker.cc
+++ b/content/renderer/worker/service_worker_network_provider_for_worker.cc
@@ -68,11 +68,6 @@
       // ServiceWorkerContainerHost since we couldn't set it up correctly due
       // to this test limitation. This way we don't crash when the associated
       // interface ptr is used.
-      //
-      // TODO(falken): Just give ServiceWorkerProviderContext a null interface
-      // ptr and make the callsites deal with it. They are supposed to anyway
-      // because OnNetworkProviderDestroyed() can reset the ptr to null at any
-      // time.
       mojo::AssociateWithDisconnectedPipe(host_info->host_request.PassHandle());
     }
   }
diff --git a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
index 524e7ae..59dd927 100644
--- a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
+++ b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
@@ -25,8 +25,13 @@
 
 namespace {
 
+namespace dnr_api = api::declarative_net_request;
+
 // Returns true if the given |extension| has a registered ruleset. If it
 // doesn't, returns false and populates |error|.
+// TODO(crbug.com/931967): Using HasRegisteredRuleset for PreRunValidation means
+// that the extension function will fail if the ruleset for the extension is
+// currently being indexed. Fix this.
 bool HasRegisteredRuleset(content::BrowserContext* context,
                           const ExtensionId& extension_id,
                           std::string* error) {
@@ -87,11 +92,10 @@
       break;
   }
 
-  if (static_cast<int>(new_set.size()) >
-      api::declarative_net_request::MAX_NUMBER_OF_ALLOWED_PAGES) {
+  if (static_cast<int>(new_set.size()) > dnr_api::MAX_NUMBER_OF_ALLOWED_PAGES) {
     return RespondNow(Error(base::StringPrintf(
         "The number of allowed page patterns can't exceed %d",
-        api::declarative_net_request::MAX_NUMBER_OF_ALLOWED_PAGES)));
+        dnr_api::MAX_NUMBER_OF_ALLOWED_PAGES)));
   }
 
   // Persist |new_set| as part of preferences.
@@ -130,7 +134,7 @@
 
 ExtensionFunction::ResponseAction
 DeclarativeNetRequestAddAllowedPagesFunction::Run() {
-  using Params = api::declarative_net_request::AddAllowedPages::Params;
+  using Params = dnr_api::AddAllowedPages::Params;
 
   base::string16 error;
   std::unique_ptr<Params> params(Params::Create(*args_, &error));
@@ -149,7 +153,7 @@
 
 ExtensionFunction::ResponseAction
 DeclarativeNetRequestRemoveAllowedPagesFunction::Run() {
-  using Params = api::declarative_net_request::AddAllowedPages::Params;
+  using Params = dnr_api::AddAllowedPages::Params;
 
   base::string16 error;
   std::unique_ptr<Params> params(Params::Create(*args_, &error));
@@ -177,9 +181,59 @@
   const ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
   URLPatternSet current_set = prefs->GetDNRAllowedPages(extension_id());
 
-  return RespondNow(ArgumentList(
-      api::declarative_net_request::GetAllowedPages::Results::Create(
-          *current_set.ToStringVector())));
+  return RespondNow(ArgumentList(dnr_api::GetAllowedPages::Results::Create(
+      *current_set.ToStringVector())));
+}
+
+DeclarativeNetRequestAddDynamicRulesFunction::
+    DeclarativeNetRequestAddDynamicRulesFunction() = default;
+DeclarativeNetRequestAddDynamicRulesFunction::
+    ~DeclarativeNetRequestAddDynamicRulesFunction() = default;
+
+bool DeclarativeNetRequestAddDynamicRulesFunction::PreRunValidation(
+    std::string* error) {
+  return UIThreadExtensionFunction::PreRunValidation(error) &&
+         HasRegisteredRuleset(browser_context(), extension_id(), error);
+}
+
+ExtensionFunction::ResponseAction
+DeclarativeNetRequestAddDynamicRulesFunction::Run() {
+  // TODO(crbug.com/930961): Implement this.
+  return RespondNow(Error("Not implemented"));
+}
+
+DeclarativeNetRequestRemoveDynamicRulesFunction::
+    DeclarativeNetRequestRemoveDynamicRulesFunction() = default;
+DeclarativeNetRequestRemoveDynamicRulesFunction::
+    ~DeclarativeNetRequestRemoveDynamicRulesFunction() = default;
+
+bool DeclarativeNetRequestRemoveDynamicRulesFunction::PreRunValidation(
+    std::string* error) {
+  return UIThreadExtensionFunction::PreRunValidation(error) &&
+         HasRegisteredRuleset(browser_context(), extension_id(), error);
+}
+
+ExtensionFunction::ResponseAction
+DeclarativeNetRequestRemoveDynamicRulesFunction::Run() {
+  // TODO(crbug.com/930961): Implement this.
+  return RespondNow(Error("Not implemented"));
+}
+
+DeclarativeNetRequestGetDynamicRulesFunction::
+    DeclarativeNetRequestGetDynamicRulesFunction() = default;
+DeclarativeNetRequestGetDynamicRulesFunction::
+    ~DeclarativeNetRequestGetDynamicRulesFunction() = default;
+
+bool DeclarativeNetRequestGetDynamicRulesFunction::PreRunValidation(
+    std::string* error) {
+  return UIThreadExtensionFunction::PreRunValidation(error) &&
+         HasRegisteredRuleset(browser_context(), extension_id(), error);
+}
+
+ExtensionFunction::ResponseAction
+DeclarativeNetRequestGetDynamicRulesFunction::Run() {
+  // TODO(crbug.com/930961): Implement this.
+  return RespondNow(Error("Not implemented"));
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/api/declarative_net_request/declarative_net_request_api.h b/extensions/browser/api/declarative_net_request/declarative_net_request_api.h
index 67f28531..b698069 100644
--- a/extensions/browser/api/declarative_net_request/declarative_net_request_api.h
+++ b/extensions/browser/api/declarative_net_request/declarative_net_request_api.h
@@ -96,6 +96,60 @@
   DISALLOW_COPY_AND_ASSIGN(DeclarativeNetRequestGetAllowedPagesFunction);
 };
 
+class DeclarativeNetRequestAddDynamicRulesFunction
+    : public UIThreadExtensionFunction {
+ public:
+  DeclarativeNetRequestAddDynamicRulesFunction();
+  DECLARE_EXTENSION_FUNCTION("declarativeNetRequest.addDynamicRules",
+                             DECLARATIVENETREQUEST_ADDDYNAMICRULES);
+
+ protected:
+  ~DeclarativeNetRequestAddDynamicRulesFunction() override;
+
+  // ExtensionFunction override:
+  bool PreRunValidation(std::string* error) override;
+  ExtensionFunction::ResponseAction Run() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DeclarativeNetRequestAddDynamicRulesFunction);
+};
+
+class DeclarativeNetRequestRemoveDynamicRulesFunction
+    : public UIThreadExtensionFunction {
+ public:
+  DeclarativeNetRequestRemoveDynamicRulesFunction();
+  DECLARE_EXTENSION_FUNCTION("declarativeNetRequest.removeDynamicRules",
+                             DECLARATIVENETREQUEST_REMOVEDYNAMICRULES);
+
+ protected:
+  ~DeclarativeNetRequestRemoveDynamicRulesFunction() override;
+
+  // ExtensionFunction override:
+  bool PreRunValidation(std::string* error) override;
+  ExtensionFunction::ResponseAction Run() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DeclarativeNetRequestRemoveDynamicRulesFunction);
+};
+
+class DeclarativeNetRequestGetDynamicRulesFunction
+    : public UIThreadExtensionFunction {
+ public:
+  DeclarativeNetRequestGetDynamicRulesFunction();
+  DECLARE_EXTENSION_FUNCTION("declarativeNetRequest.getDynamicRules",
+                             DECLARATIVENETREQUEST_GETDYNAMICRULES);
+
+ protected:
+  ~DeclarativeNetRequestGetDynamicRulesFunction() override;
+
+  // ExtensionFunction override:
+  bool PreRunValidation(std::string* error) override;
+  ExtensionFunction::ResponseAction Run() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DeclarativeNetRequestGetDynamicRulesFunction);
+};
+
 }  // namespace extensions
 
 #endif  // EXTENSIONS_BROWSER_API_DECLARATIVE_NET_REQUEST_DECLARATIVE_NET_REQUEST_API_H_
diff --git a/extensions/browser/api/declarative_net_request/indexed_rule.cc b/extensions/browser/api/declarative_net_request/indexed_rule.cc
index b33543c..cfdc48a 100644
--- a/extensions/browser/api/declarative_net_request/indexed_rule.cc
+++ b/extensions/browser/api/declarative_net_request/indexed_rule.cc
@@ -263,74 +263,73 @@
 IndexedRule& IndexedRule::operator=(IndexedRule&& other) = default;
 
 // static
-ParseResult IndexedRule::CreateIndexedRule(
-    std::unique_ptr<dnr_api::Rule> parsed_rule,
-    IndexedRule* indexed_rule) {
+ParseResult IndexedRule::CreateIndexedRule(dnr_api::Rule parsed_rule,
+                                           IndexedRule* indexed_rule) {
   DCHECK(indexed_rule);
   DCHECK(IsAPIAvailable());
 
-  if (parsed_rule->id < kMinValidID)
+  if (parsed_rule.id < kMinValidID)
     return ParseResult::ERROR_INVALID_RULE_ID;
 
   const bool is_redirect_rule =
-      parsed_rule->action.type == dnr_api::RULE_ACTION_TYPE_REDIRECT;
+      parsed_rule.action.type == dnr_api::RULE_ACTION_TYPE_REDIRECT;
   if (is_redirect_rule) {
-    if (!parsed_rule->action.redirect_url ||
-        parsed_rule->action.redirect_url->empty()) {
+    if (!parsed_rule.action.redirect_url ||
+        parsed_rule.action.redirect_url->empty()) {
       return ParseResult::ERROR_EMPTY_REDIRECT_URL;
     }
-    if (!GURL(*parsed_rule->action.redirect_url).is_valid())
+    if (!GURL(*parsed_rule.action.redirect_url).is_valid())
       return ParseResult::ERROR_INVALID_REDIRECT_URL;
-    if (!parsed_rule->priority)
+    if (!parsed_rule.priority)
       return ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY;
-    if (*parsed_rule->priority < kMinValidPriority)
+    if (*parsed_rule.priority < kMinValidPriority)
       return ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY;
   }
 
-  if (parsed_rule->condition.domains && parsed_rule->condition.domains->empty())
+  if (parsed_rule.condition.domains && parsed_rule.condition.domains->empty())
     return ParseResult::ERROR_EMPTY_DOMAINS_LIST;
 
-  if (parsed_rule->condition.resource_types &&
-      parsed_rule->condition.resource_types->empty()) {
+  if (parsed_rule.condition.resource_types &&
+      parsed_rule.condition.resource_types->empty()) {
     return ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST;
   }
 
-  if (parsed_rule->condition.url_filter) {
-    if (parsed_rule->condition.url_filter->empty())
+  if (parsed_rule.condition.url_filter) {
+    if (parsed_rule.condition.url_filter->empty())
       return ParseResult::ERROR_EMPTY_URL_FILTER;
-    if (!base::IsStringASCII(*parsed_rule->condition.url_filter))
+    if (!base::IsStringASCII(*parsed_rule.condition.url_filter))
       return ParseResult::ERROR_NON_ASCII_URL_FILTER;
   }
 
-  indexed_rule->id = base::checked_cast<uint32_t>(parsed_rule->id);
+  indexed_rule->id = base::checked_cast<uint32_t>(parsed_rule.id);
   indexed_rule->priority = base::checked_cast<uint32_t>(
-      is_redirect_rule ? *parsed_rule->priority : kDefaultPriority);
-  indexed_rule->options = GetOptionsMask(*parsed_rule);
-  indexed_rule->activation_types = GetActivationTypes(*parsed_rule);
+      is_redirect_rule ? *parsed_rule.priority : kDefaultPriority);
+  indexed_rule->options = GetOptionsMask(parsed_rule);
+  indexed_rule->activation_types = GetActivationTypes(parsed_rule);
 
   {
-    ParseResult result = ComputeElementTypes(parsed_rule->condition,
+    ParseResult result = ComputeElementTypes(parsed_rule.condition,
                                              &indexed_rule->element_types);
     if (result != ParseResult::SUCCESS)
       return result;
   }
 
-  if (!CanonicalizeDomains(std::move(parsed_rule->condition.domains),
+  if (!CanonicalizeDomains(std::move(parsed_rule.condition.domains),
                            &indexed_rule->domains)) {
     return ParseResult::ERROR_NON_ASCII_DOMAIN;
   }
 
-  if (!CanonicalizeDomains(std::move(parsed_rule->condition.excluded_domains),
+  if (!CanonicalizeDomains(std::move(parsed_rule.condition.excluded_domains),
                            &indexed_rule->excluded_domains)) {
     return ParseResult::ERROR_NON_ASCII_EXCLUDED_DOMAIN;
   }
 
   if (is_redirect_rule)
-    indexed_rule->redirect_url = std::move(*parsed_rule->action.redirect_url);
+    indexed_rule->redirect_url = std::move(*parsed_rule.action.redirect_url);
 
   // Parse the |anchor_left|, |anchor_right|, |url_pattern_type| and
   // |url_pattern| fields.
-  UrlFilterParser::Parse(std::move(parsed_rule->condition.url_filter),
+  UrlFilterParser::Parse(std::move(parsed_rule.condition.url_filter),
                          indexed_rule);
 
   // Lower-case case-insensitive patterns as required by url pattern index.
diff --git a/extensions/browser/api/declarative_net_request/indexed_rule.h b/extensions/browser/api/declarative_net_request/indexed_rule.h
index 6240322..710d415 100644
--- a/extensions/browser/api/declarative_net_request/indexed_rule.h
+++ b/extensions/browser/api/declarative_net_request/indexed_rule.h
@@ -6,7 +6,6 @@
 #define EXTENSIONS_BROWSER_API_DECLARATIVE_NET_REQUEST_INDEXED_RULE_H_
 
 #include <stdint.h>
-#include <memory>
 #include <string>
 #include <vector>
 
@@ -35,8 +34,7 @@
   IndexedRule& operator=(IndexedRule&& other);
 
   static ParseResult CreateIndexedRule(
-      std::unique_ptr<extensions::api::declarative_net_request::Rule>
-          parsed_rule,
+      extensions::api::declarative_net_request::Rule parsed_rule,
       IndexedRule* indexed_rule);
 
   // These fields correspond to the attributes of a flatbuffer UrlRule, as
diff --git a/extensions/browser/api/declarative_net_request/indexed_rule_unittest.cc b/extensions/browser/api/declarative_net_request/indexed_rule_unittest.cc
index c36dc61..fbf780f 100644
--- a/extensions/browser/api/declarative_net_request/indexed_rule_unittest.cc
+++ b/extensions/browser/api/declarative_net_request/indexed_rule_unittest.cc
@@ -26,11 +26,11 @@
 namespace flat_rule = url_pattern_index::flat;
 namespace dnr_api = extensions::api::declarative_net_request;
 
-std::unique_ptr<dnr_api::Rule> CreateGenericParsedRule() {
-  auto rule = std::make_unique<dnr_api::Rule>();
-  rule->id = kMinValidID;
-  rule->condition.url_filter = std::make_unique<std::string>("filter");
-  rule->action.type = dnr_api::RULE_ACTION_TYPE_BLOCK;
+dnr_api::Rule CreateGenericParsedRule() {
+  dnr_api::Rule rule;
+  rule.id = kMinValidID;
+  rule.condition.url_filter = std::make_unique<std::string>("filter");
+  rule.action.type = dnr_api::RULE_ACTION_TYPE_BLOCK;
   return rule;
 }
 
@@ -55,8 +55,8 @@
   };
   for (size_t i = 0; i < base::size(cases); ++i) {
     SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->id = cases[i].id;
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.id = cases[i].id;
 
     IndexedRule indexed_rule;
     ParseResult result =
@@ -87,10 +87,10 @@
 
   for (size_t i = 0; i < base::size(cases); ++i) {
     SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->priority = std::move(cases[i].priority);
-    rule->action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
-    rule->action.redirect_url =
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.priority = std::move(cases[i].priority);
+    rule.action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
+    rule.action.redirect_url =
         std::make_unique<std::string>("http://google.com");
 
     IndexedRule indexed_rule;
@@ -104,8 +104,8 @@
 
   // Ensure priority is ignored for non-redirect rules.
   {
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->priority = std::make_unique<int>(5);
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.priority = std::make_unique<int>(5);
     IndexedRule indexed_rule;
     ParseResult result =
         IndexedRule::CreateIndexedRule(std::move(rule), &indexed_rule);
@@ -137,10 +137,10 @@
 
   for (size_t i = 0; i < base::size(cases); ++i) {
     SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->condition.domain_type = cases[i].domain_type;
-    rule->action.type = cases[i].action_type;
-    rule->condition.is_url_filter_case_sensitive =
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.condition.domain_type = cases[i].domain_type;
+    rule.action.type = cases[i].action_type;
+    rule.condition.is_url_filter_case_sensitive =
         std::move(cases[i].is_url_filter_case_sensitive);
 
     IndexedRule indexed_rule;
@@ -202,9 +202,9 @@
 
   for (size_t i = 0; i < base::size(cases); ++i) {
     SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->condition.resource_types = std::move(cases[i].resource_types);
-    rule->condition.excluded_resource_types =
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.condition.resource_types = std::move(cases[i].resource_types);
+    rule.condition.excluded_resource_types =
         std::move(cases[i].excluded_resource_types);
 
     IndexedRule indexed_rule;
@@ -273,8 +273,8 @@
 
   for (size_t i = 0; i < base::size(cases); ++i) {
     SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->condition.url_filter = std::move(cases[i].input_url_filter);
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.condition.url_filter = std::move(cases[i].input_url_filter);
 
     IndexedRule indexed_rule;
     ParseResult result =
@@ -305,9 +305,9 @@
   };
 
   for (auto& test_case : test_cases) {
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->condition.url_filter = std::make_unique<std::string>(kPattern);
-    rule->condition.is_url_filter_case_sensitive =
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.condition.url_filter = std::make_unique<std::string>(kPattern);
+    rule.condition.is_url_filter_case_sensitive =
         std::move(test_case.is_url_filter_case_sensitive);
     IndexedRule indexed_rule;
     ASSERT_EQ(ParseResult::SUCCESS,
@@ -369,9 +369,9 @@
 
   for (size_t i = 0; i < base::size(cases); ++i) {
     SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->condition.domains = std::move(cases[i].domains);
-    rule->condition.excluded_domains = std::move(cases[i].excluded_domains);
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.condition.domains = std::move(cases[i].domains);
+    rule.condition.excluded_domains = std::move(cases[i].excluded_domains);
 
     IndexedRule indexed_rule;
     ParseResult result =
@@ -402,10 +402,10 @@
 
   for (size_t i = 0; i < base::size(cases); ++i) {
     SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
-    std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
-    rule->action.redirect_url = std::move(cases[i].redirect_url);
-    rule->action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
-    rule->priority = std::make_unique<int>(kMinValidPriority);
+    dnr_api::Rule rule = CreateGenericParsedRule();
+    rule.action.redirect_url = std::move(cases[i].redirect_url);
+    rule.action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
+    rule.priority = std::make_unique<int>(kMinValidPriority);
 
     IndexedRule indexed_rule;
     ParseResult result =
diff --git a/extensions/browser/api/declarative_net_request/utils.cc b/extensions/browser/api/declarative_net_request/utils.cc
index 9b2fa30..599eba3 100644
--- a/extensions/browser/api/declarative_net_request/utils.cc
+++ b/extensions/browser/api/declarative_net_request/utils.cc
@@ -171,17 +171,17 @@
   base::ElapsedTimer timer;
   {
     std::set<int> id_set;  // Ensure all ids are distinct.
-    std::unique_ptr<dnr_api::Rule> parsed_rule;
 
     const auto& rules_list = rules.GetList();
     for (size_t i = 0; i < rules_list.size(); i++) {
+      dnr_api::Rule parsed_rule;
       base::string16 parse_error;
-      parsed_rule = dnr_api::Rule::FromValue(rules_list[i], &parse_error);
 
       // Ignore rules which can't be successfully parsed and show an install
       // warning for them. A hard error is not thrown to maintain backwards
       // compatibility.
-      if (!parsed_rule || !parse_error.empty()) {
+      if (!dnr_api::Rule::Populate(rules_list[i], &parsed_rule, &parse_error) ||
+          !parse_error.empty()) {
         if (unparsed_warning_count < kMaxUnparsedRulesWarnings) {
           ++unparsed_warning_count;
           std::string rule_location;
@@ -205,7 +205,7 @@
         continue;
       }
 
-      int rule_id = parsed_rule->id;
+      int rule_id = parsed_rule.id;
       bool inserted = id_set.insert(rule_id).second;
       if (!inserted)
         return ParseInfo(ParseResult::ERROR_DUPLICATE_IDS, rule_id);
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 376ee04..ab529fc 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1378,6 +1378,9 @@
   PASSWORDSPRIVATE_CHANGESAVEDPASSWORD = 1315,
   AUTOTESTPRIVATE_SETWHITELISTEDPREF = 1316,
   SAFEBROWSINGPRIVATE_GETREFERRERCHAIN = 1317,
+  DECLARATIVENETREQUEST_ADDDYNAMICRULES = 1318,
+  DECLARATIVENETREQUEST_REMOVEDYNAMICRULES = 1319,
+  DECLARATIVENETREQUEST_GETDYNAMICRULES = 1320,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 63a47d9..b0b1f04 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -126,6 +126,21 @@
     "dependencies": ["permission:declarativeNetRequest"],
     "contexts": ["blessed_extension"]
   },
+  "declarativeNetRequest.addDynamicRules": {
+    "dependencies": ["permission:declarativeNetRequest"],
+    "contexts": ["blessed_extension"],
+    "channel": "trunk"
+  },
+  "declarativeNetRequest.removeDynamicRules": {
+    "dependencies": ["permission:declarativeNetRequest"],
+    "contexts": ["blessed_extension"],
+    "channel": "trunk"
+  },
+  "declarativeNetRequest.getDynamicRules": {
+    "dependencies": ["permission:declarativeNetRequest"],
+    "contexts": ["blessed_extension"],
+    "channel": "trunk"
+  },
   "declarativeWebRequest": {
     "dependencies": ["permission:declarativeWebRequest"],
     "contexts": ["blessed_extension"]
diff --git a/extensions/common/api/declarative_net_request.idl b/extensions/common/api/declarative_net_request.idl
index 337c7ad..de1c1a1 100644
--- a/extensions/common/api/declarative_net_request.idl
+++ b/extensions/common/api/declarative_net_request.idl
@@ -137,8 +137,16 @@
 
   callback EmptyCallback = void();
   callback GetAllowedPagesCallback = void(DOMString[] result);
+  callback GetRulesCallback = void(Rule[] rules);
 
   interface Functions {
+
+    // TODO(crbug.com/930961): Enable documentation for these functions once
+    // they are implemented.
+    [nodoc] static void addDynamicRules(Rule[] rules, optional EmptyCallback callback);
+    [nodoc] static void removeDynamicRules(long[] rule_ids, optional EmptyCallback callback);
+    [nodoc] static void getDynamicRules(GetRulesCallback callback);
+
     // Adds <code>page_patterns</code> to the set of allowed pages. Requests
     // from these pages are not intercepted by the extension. These are
     // persisted across browser sessions.
diff --git a/extensions/renderer/ipc_message_sender.cc b/extensions/renderer/ipc_message_sender.cc
index a641db3..2a0357a 100644
--- a/extensions/renderer/ipc_message_sender.cc
+++ b/extensions/renderer/ipc_message_sender.cc
@@ -176,6 +176,8 @@
       }
       case MessageTarget::TAB: {
         DCHECK(extension);
+        DCHECK_NE(script_context->context_type(),
+                  Feature::CONTENT_SCRIPT_CONTEXT);
         ExtensionMsg_TabTargetConnectionInfo info;
         info.tab_id = *target.tab_id;
         info.frame_id = *target.frame_id;
diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc
index 1a38ac07..7bfe76a 100644
--- a/extensions/renderer/messaging_bindings.cc
+++ b/extensions/renderer/messaging_bindings.cc
@@ -254,6 +254,8 @@
   if (!render_frame)
     return;
 
+  DCHECK_NE(context()->context_type(), Feature::CONTENT_SCRIPT_CONTEXT);
+
   // tabs_custom_bindings.js unwraps arguments to tabs.connect/sendMessage and
   // passes them to OpenChannelToTab, in the following order:
   // - |tab_id| - Positive number that specifies the destination of the channel.
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc b/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
index 0acdc97..848ce5e 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
+++ b/google_apis/gaia/oauth2_access_token_fetcher_impl_unittest.cc
@@ -74,20 +74,15 @@
 class OAuth2AccessTokenFetcherImplTest : public testing::Test {
  public:
   OAuth2AccessTokenFetcherImplTest()
-      : shared_url_loader_factory_(
-            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-                &url_loader_factory_)),
-        fetcher_(&consumer_, shared_url_loader_factory_, "refresh_token") {
+      : fetcher_(&consumer_,
+                 url_loader_factory_.GetSafeWeakWrapper(),
+                 "refresh_token") {
     url_loader_factory_.SetInterceptor(base::BindRepeating(
         &URLLoaderFactoryInterceptor::Intercept,
         base::Unretained(&url_loader_factory_interceptor_)));
     base::RunLoop().RunUntilIdle();
   }
 
-  ~OAuth2AccessTokenFetcherImplTest() override {
-    shared_url_loader_factory_->Detach();
-  }
-
   void SetupGetAccessToken(int net_error_code,
                            net::HttpStatusCode http_response_code,
                            const std::string& body) {
@@ -124,8 +119,6 @@
   MockOAuth2AccessTokenConsumer consumer_;
   URLLoaderFactoryInterceptor url_loader_factory_interceptor_;
   network::TestURLLoaderFactory url_loader_factory_;
-  scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
-      shared_url_loader_factory_;
   OAuth2AccessTokenFetcherImpl fetcher_;
 };
 
diff --git a/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json b/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
index 017274e..b3f0031 100644
--- a/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
@@ -29,7 +29,8 @@
       "os": "12.1",
       "host os": "Mac-10.13.6",
       "pool": "Chrome",
-      "shards": 4
+      "shards": 4,
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -37,7 +38,8 @@
       "device type": "iPhone X",
       "os": "12.1",
       "host os": "Mac-10.13.6",
-      "pool": "Chrome"
+      "pool": "Chrome",
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -46,7 +48,8 @@
       "os": "12.1",
       "host os": "Mac-10.13.6",
       "pool": "Chrome",
-      "shards": 4
+      "shards": 4,
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -54,7 +57,8 @@
       "device type": "iPad Air 2",
       "os": "12.1",
       "host os": "Mac-10.13.6",
-      "pool": "Chrome"
+      "pool": "Chrome",
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -63,7 +67,8 @@
       "os": "12.1",
       "host os": "Mac-10.13.6",
       "pool": "Chrome",
-      "shards": 4
+      "shards": 4,
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -71,7 +76,8 @@
       "device type": "iPhone 7",
       "os": "12.1",
       "host os": "Mac-10.13.6",
-      "pool": "Chrome"
+      "pool": "Chrome",
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -80,7 +86,8 @@
       "os": "11.4",
       "host os": "Mac-10.13.6",
       "pool": "Chrome",
-      "shards": 4
+      "shards": 4,
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -88,7 +95,8 @@
       "device type": "iPhone 7",
       "os": "11.4",
       "host os": "Mac-10.13.6",
-      "pool": "Chrome"
+      "pool": "Chrome",
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -97,7 +105,8 @@
       "os": "11.4",
       "host os": "Mac-10.13.6",
       "pool": "Chrome",
-      "shards": 4
+      "shards": 4,
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -105,7 +114,8 @@
       "device type": "iPad Air 2",
       "os": "11.4",
       "host os": "Mac-10.13.6",
-      "pool": "Chrome"
+      "pool": "Chrome",
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -114,7 +124,8 @@
       "os": "11.4",
       "host os": "Mac-10.13.6",
       "pool": "Chrome",
-      "shards": 4
+      "shards": 4,
+      "priority": 35
     },
     {
       "xcode parallelization": true,
@@ -122,7 +133,8 @@
       "device type": "iPhone X",
       "os": "11.4",
       "host os": "Mac-10.13.6",
-      "pool": "Chrome"
+      "pool": "Chrome",
+      "priority": 35
     }
   ]
 }
diff --git a/ios/build/bots/scripts/test_runner.py b/ios/build/bots/scripts/test_runner.py
index 51f3ffa..5ea5823 100644
--- a/ios/build/bots/scripts/test_runner.py
+++ b/ios/build/bots/scripts/test_runner.py
@@ -837,15 +837,22 @@
 
   def tear_down(self):
     """Performs cleanup actions which must occur after every test launch."""
+    LOGGER.debug('Extracting test data.')
     self.extract_test_data()
+    LOGGER.debug('Retrieving crash reports.')
     self.retrieve_crash_reports()
+    LOGGER.debug('Retrieving derived data.')
     self.retrieve_derived_data()
+    LOGGER.debug('Making desktop screenshots.')
     self.screenshot_desktop()
+    LOGGER.debug('Killing simulators.')
     self.kill_simulators()
+    LOGGER.debug('Wiping simulator.')
     self.wipe_simulator()
     if os.path.exists(self.homedir):
       shutil.rmtree(self.homedir, ignore_errors=True)
       self.homedir = ''
+    LOGGER.debug('End of tear_down.')
 
   def run_tests(self, test_shard=None):
     """Runs passed-in tests. Builds a command and create a simulator to
diff --git a/ios/build/bots/scripts/xcodebuild_runner.py b/ios/build/bots/scripts/xcodebuild_runner.py
index c4ca40c..6e4760b 100644
--- a/ios/build/bots/scripts/xcodebuild_runner.py
+++ b/ios/build/bots/scripts/xcodebuild_runner.py
@@ -8,16 +8,21 @@
 
 import collections
 import json
+import logging
 import multiprocessing
 import os
 import plistlib
 import re
 import shutil
 import subprocess
+import threading
 import time
 
 import test_runner
 
+LOGGER = logging.getLogger(__name__)
+READLINE_TIMEOUT = 300
+
 
 class LaunchCommandCreationError(test_runner.TestRunnerError):
   """One of launch command parameters was not set properly."""
@@ -33,6 +38,20 @@
     super(LaunchCommandPoolCreationError, self).__init__(message)
 
 
+def terminate_process(proc):
+  """Terminates the process.
+
+  If an error occurs ignore it, just print out a message.
+
+  Args:
+    proc: A subprocess.
+  """
+  try:
+    proc.terminate()
+  except OSError as ex:
+    print 'Error while killing a process: %s' % ex
+
+
 def test_status_summary(summary_plist):
   """Gets status summary from TestSummaries.plist.
 
@@ -355,8 +374,18 @@
         stdout=subprocess.PIPE,
         stderr=subprocess.STDOUT,
     )
+
     while True:
+      # It seems that subprocess.stdout.readline() is stuck from time to time
+      # and tests fail because of TIMEOUT.
+      # Try to fix the issue by adding timer-thread for 5 mins
+      # that will kill `frozen` running process if no new line is read
+      # and will finish test attempt.
+      # If new line appears in 5 mins, just cancel timer.
+      timer = threading.Timer(READLINE_TIMEOUT, terminate_process, [proc])
+      timer.start()
       line = proc.stdout.readline()
+      timer.cancel()
       if not line:
         break
       line = line.rstrip()
@@ -598,8 +627,8 @@
       self.logs[' '.join(result['cmd'])] = result['test_results']
       self.test_results['commands'].append(
           {'cmd': ' '.join(result['cmd']), 'logs': result['logs']})
-
     self.test_results['end_run'] = int(time.time())
+    LOGGER.debug('Test ended.')
     # Test is failed if there are failures for the last run.
     return not self.test_results['commands'][-1]['logs']['failed']
 
diff --git a/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc b/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc
index 0848537..f7f99ac 100644
--- a/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc
+++ b/ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
+#include "components/signin/core/browser/test_signin_client.h"
 #include "components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/signin/account_fetcher_service_factory.h"
@@ -71,9 +72,16 @@
   return account_fetcher_service;
 }
 
+std::unique_ptr<KeyedService> BuildTestSigninClient(web::BrowserState* state) {
+  return std::make_unique<TestSigninClient>(
+      ios::ChromeBrowserState::FromBrowserState(state)->GetPrefs());
+}
+
 TestChromeBrowserState::TestingFactories GetIdentityTestEnvironmentFactories(
     bool use_ios_token_service_delegate) {
-  return {{ios::AccountFetcherServiceFactory::GetInstance(),
+  return {{SigninClientFactory::GetInstance(),
+           base::BindRepeating(&BuildTestSigninClient)},
+          {ios::AccountFetcherServiceFactory::GetInstance(),
            base::BindRepeating(&BuildFakeAccountFetcherService)},
           {ProfileOAuth2TokenServiceFactory::GetInstance(),
            base::BindRepeating(use_ios_token_service_delegate
diff --git a/ios/chrome/browser/sync/profile_sync_service_factory.cc b/ios/chrome/browser/sync/profile_sync_service_factory.cc
index 9bb22a1..6e3c021 100644
--- a/ios/chrome/browser/sync/profile_sync_service_factory.cc
+++ b/ios/chrome/browser/sync/profile_sync_service_factory.cc
@@ -10,6 +10,7 @@
 #include "base/no_destructor.h"
 #include "base/task/post_task.h"
 #include "base/time/time.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/browser_sync/profile_sync_service.h"
 #include "components/invalidation/impl/invalidation_switches.h"
 #include "components/invalidation/impl/profile_invalidation_provider.h"
@@ -76,7 +77,7 @@
 // static
 syncer::SyncService* ProfileSyncServiceFactory::GetForBrowserState(
     ios::ChromeBrowserState* browser_state) {
-  if (!browser_sync::ProfileSyncService::IsSyncAllowedByFlag())
+  if (!switches::IsSyncAllowedByFlag())
     return nullptr;
 
   return static_cast<syncer::SyncService*>(
@@ -86,7 +87,7 @@
 // static
 syncer::SyncService* ProfileSyncServiceFactory::GetForBrowserStateIfExists(
     ios::ChromeBrowserState* browser_state) {
-  if (!browser_sync::ProfileSyncService::IsSyncAllowedByFlag())
+  if (!switches::IsSyncAllowedByFlag())
     return nullptr;
 
   return static_cast<syncer::SyncService*>(
diff --git a/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm b/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm
index 5558326..57cacfd 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm
@@ -8,7 +8,7 @@
 
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
-#include "components/browser_sync/profile_sync_service.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/google/core/common/google_util.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/sync/base/sync_prefs.h"
@@ -109,7 +109,7 @@
 
 // Returns an account item.
 - (TableViewItem*)accountItem {
-  DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag());
+  DCHECK(switches::IsSyncAllowedByFlag());
   NSString* text = l10n_util::GetNSString(IDS_SYNC_BASIC_ENCRYPTION_DATA);
   return [self itemWithType:ItemTypeAccount
                        text:text
@@ -119,7 +119,7 @@
 
 // Returns a passphrase item.
 - (TableViewItem*)passphraseItem {
-  DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag());
+  DCHECK(switches::IsSyncAllowedByFlag());
   NSString* text = l10n_util::GetNSString(IDS_SYNC_FULL_ENCRYPTION_DATA);
   return [self itemWithType:ItemTypePassphrase
                        text:text
@@ -164,7 +164,7 @@
   TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
   switch (item.type) {
     case ItemTypePassphrase: {
-      DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag());
+      DCHECK(switches::IsSyncAllowedByFlag());
       syncer::SyncService* service =
           ProfileSyncServiceFactory::GetForBrowserState(_browserState);
       if (service->IsEngineInitialized() &&
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index c4023bb..7bcda9d 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -645,6 +645,7 @@
       "test:frame_validator",
       "test:video_player",
       "test:video_player_test_environment",
+      "test:video_player_thumbnail_renderer",
       "//media:test_support",
       "//mojo/core/embedder",
       "//testing/gtest",
diff --git a/media/gpu/test/BUILD.gn b/media/gpu/test/BUILD.gn
index 05f0f8c..afc8688 100644
--- a/media/gpu/test/BUILD.gn
+++ b/media/gpu/test/BUILD.gn
@@ -127,6 +127,23 @@
 
 # TODO(dstaessens@) Make this work on other platforms too.
 if (is_chromeos) {
+  static_library("video_player_thumbnail_renderer") {
+    testonly = true
+    sources = [
+      "video_player/frame_renderer.h",
+      "video_player/frame_renderer_thumbnail.cc",
+      "video_player/frame_renderer_thumbnail.h",
+    ]
+    deps = [
+      ":decode_helpers",
+      "//gpu/command_buffer/common",
+      "//media/gpu",
+      "//ui/gfx/codec:codec",
+      "//ui/gl:gl",
+      "//ui/gl/init:init",
+    ]
+  }
+
   static_library("video_player") {
     testonly = true
     sources = [
diff --git a/media/gpu/test/rendering_helper.cc b/media/gpu/test/rendering_helper.cc
index 2664108..ecfe1a0 100644
--- a/media/gpu/test/rendering_helper.cc
+++ b/media/gpu/test/rendering_helper.cc
@@ -39,38 +39,6 @@
 
 namespace media {
 
-namespace {
-
-// Helper for Shader creation.
-void CreateShader(GLuint program, GLenum type, const char* source, int size) {
-  GLuint shader = glCreateShader(type);
-  glShaderSource(shader, 1, &source, &size);
-  glCompileShader(shader);
-  int result = GL_FALSE;
-  glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
-  if (!result) {
-    char log[4096];
-    glGetShaderInfoLog(shader, base::size(log), NULL, log);
-    LOG(FATAL) << log;
-  }
-  glAttachShader(program, shader);
-  glDeleteShader(shader);
-  CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
-}
-
-void DeleteTexture(uint32_t texture_id) {
-  glDeleteTextures(1, &texture_id);
-  CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
-}
-
-// Helper function to set GL viewport.
-void GLSetViewPort(const gfx::Rect& area) {
-  glViewport(area.x(), area.y(), area.width(), area.height());
-  glScissor(area.x(), area.y(), area.width(), area.height());
-}
-
-}  // namespace
-
 bool RenderingHelper::use_gl_ = false;
 
 VideoFrameTexture::VideoFrameTexture(uint32_t texture_target,
@@ -436,6 +404,39 @@
   }
 }
 
+// static
+void RenderingHelper::DeleteTexture(uint32_t texture_id) {
+  glDeleteTextures(1, &texture_id);
+  CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
+}
+
+// static
+void RenderingHelper::GLSetViewPort(const gfx::Rect& area) {
+  glViewport(area.x(), area.y(), area.width(), area.height());
+  glScissor(area.x(), area.y(), area.width(), area.height());
+}
+
+// static
+void RenderingHelper::CreateShader(GLuint program,
+                                   GLenum type,
+                                   const char* source,
+                                   int size) {
+  GLuint shader = glCreateShader(type);
+  glShaderSource(shader, 1, &source, &size);
+  glCompileShader(shader);
+  int result = GL_FALSE;
+  glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
+  if (!result) {
+    char log[4096];
+    glGetShaderInfoLog(shader, base::size(log), NULL, log);
+    LOG(FATAL) << log;
+  }
+  glAttachShader(program, shader);
+  glDeleteShader(shader);
+  CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
+}
+
+// static
 void RenderingHelper::RenderTexture(uint32_t texture_target,
                                     uint32_t texture_id) {
   // The ExternalOES sampler is bound to GL_TEXTURE1 and the Texture2D sampler
diff --git a/media/gpu/test/rendering_helper.h b/media/gpu/test/rendering_helper.h
index e89ccff..5ff09db 100644
--- a/media/gpu/test/rendering_helper.h
+++ b/media/gpu/test/rendering_helper.h
@@ -35,6 +35,9 @@
 class TextureRef;
 }  // namespace test
 
+// TODO(dstaessens@) Most functionality can be removed from this file when the
+// video_decode_accelerator_unittests are deprecated in favor of the new
+// video_decode_accelerator_test.
 class VideoFrameTexture : public base::RefCounted<VideoFrameTexture> {
  public:
   uint32_t texture_id() const { return texture_id_; }
@@ -120,9 +123,25 @@
   void GetThumbnailsAsRGBA(std::vector<unsigned char>* rgba,
                            base::WaitableEvent* done);
 
+  // Delete the texture with specified |texture_id|.
+  static void DeleteTexture(uint32_t texture_id);
+
+  // Set the GL viewport to the specified |area|.
+  static void GLSetViewPort(const gfx::Rect& area);
+
+  // Create a shader with specified |program| id and |type| by compiling the
+  // shader |source| code with length |size|.
+  static void CreateShader(GLuint program,
+                           GLenum type,
+                           const char* source,
+                           int size);
+
+  // Render |texture_id| to the current view port of the screen using target
+  // |texture_target|.
+  static void RenderTexture(uint32_t texture_target, uint32_t texture_id);
+
  private:
   struct RenderedVideo {
-
     // True if there won't be any new video frames comming.
     bool is_flushing = false;
 
@@ -157,10 +176,6 @@
   void DropOneFrameForAllVideos();
   void ScheduleNextRenderContent();
 
-  // Render |texture_id| to the current view port of the screen using target
-  // |texture_target|.
-  void RenderTexture(uint32_t texture_target, uint32_t texture_id);
-
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
   scoped_refptr<gl::GLContext> gl_context_;
diff --git a/media/gpu/test/video_player/frame_renderer.h b/media/gpu/test/video_player/frame_renderer.h
index 81ccacf2..c56e066 100644
--- a/media/gpu/test/video_player/frame_renderer.h
+++ b/media/gpu/test/video_player/frame_renderer.h
@@ -9,6 +9,7 @@
 
 #include "base/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
+#include "media/base/video_frame.h"
 #include "media/base/video_types.h"
 #include "media/video/picture.h"
 #include "ui/gfx/geometry/size.h"
@@ -42,6 +43,15 @@
   // Render the specified video frame. Once rendering is done the reference to
   // the |video_frame| should be dropped so the video frame can be reused.
   virtual void RenderFrame(scoped_refptr<VideoFrame> video_frame) = 0;
+
+  // Create a texture-backed video frame with specified |pixel_format|, |size|
+  // and |texture_target|. The texture's id will be put in |texture_id|.
+  // TODO(dstaessens@) Remove when allocate mode is removed.
+  virtual scoped_refptr<VideoFrame> CreateVideoFrame(
+      VideoPixelFormat pixel_format,
+      const gfx::Size& size,
+      uint32_t texture_target,
+      uint32_t* texture_id) = 0;
 };
 
 }  // namespace test
diff --git a/media/gpu/test/video_player/frame_renderer_dummy.cc b/media/gpu/test/video_player/frame_renderer_dummy.cc
index 3cb5b18..13d14eb 100644
--- a/media/gpu/test/video_player/frame_renderer_dummy.cc
+++ b/media/gpu/test/video_player/frame_renderer_dummy.cc
@@ -48,5 +48,13 @@
 
 void FrameRendererDummy::RenderFrame(scoped_refptr<VideoFrame> video_frame) {}
 
+scoped_refptr<VideoFrame> FrameRendererDummy::CreateVideoFrame(
+    VideoPixelFormat pixel_format,
+    const gfx::Size& size,
+    uint32_t texture_target,
+    uint32_t* texture_id) {
+  return nullptr;
+}
+
 }  // namespace test
 }  // namespace media
diff --git a/media/gpu/test/video_player/frame_renderer_dummy.h b/media/gpu/test/video_player/frame_renderer_dummy.h
index ce14e5b..10943a4 100644
--- a/media/gpu/test/video_player/frame_renderer_dummy.h
+++ b/media/gpu/test/video_player/frame_renderer_dummy.h
@@ -29,6 +29,10 @@
   void ReleaseGLContext() override;
   gl::GLContext* GetGLContext() override;
   void RenderFrame(scoped_refptr<VideoFrame> video_frame) override;
+  scoped_refptr<VideoFrame> CreateVideoFrame(VideoPixelFormat pixel_format,
+                                             const gfx::Size& size,
+                                             uint32_t texture_target,
+                                             uint32_t* texture_id) override;
 
  private:
   FrameRendererDummy();
diff --git a/media/gpu/test/video_player/frame_renderer_thumbnail.cc b/media/gpu/test/video_player/frame_renderer_thumbnail.cc
new file mode 100644
index 0000000..0463f6d
--- /dev/null
+++ b/media/gpu/test/video_player/frame_renderer_thumbnail.cc
@@ -0,0 +1,430 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/test/video_player/frame_renderer_thumbnail.h"
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "build/build_config.h"
+#include "media/gpu/test/rendering_helper.h"
+#include "media/gpu/test/video_decode_accelerator_unittest_helpers.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/init/gl_factory.h"
+
+namespace media {
+namespace test {
+
+namespace {
+
+// Size of the large image to which the thumbnails will be rendered.
+constexpr gfx::Size kThumbnailsPageSize(1600, 1200);
+// Size of the individual thumbnails that will be rendered.
+constexpr gfx::Size kThumbnailSize(160, 120);
+
+// Default file path used to store the thumbnail image.
+constexpr const base::FilePath::CharType* kDefaultOutputPath =
+    FILE_PATH_LITERAL("thumbnail.png");
+
+// Vertex shader used to render thumbnails.
+constexpr char kVertexShader[] =
+    "varying vec2 interp_tc;\n"
+    "attribute vec4 in_pos;\n"
+    "attribute vec2 in_tc;\n"
+    "uniform bool tex_flip; void main() {\n"
+    "  if (tex_flip)\n"
+    "    interp_tc = vec2(in_tc.x, 1.0 - in_tc.y);\n"
+    "  else\n"
+    "   interp_tc = in_tc;\n"
+    "  gl_Position = in_pos;\n"
+    "}\n";
+
+// Fragment shader used to render thumbnails.
+#if !defined(OS_WIN)
+constexpr char kFragmentShader[] =
+    "#extension GL_OES_EGL_image_external : enable\n"
+    "precision mediump float;\n"
+    "varying vec2 interp_tc;\n"
+    "uniform sampler2D tex;\n"
+    "#ifdef GL_OES_EGL_image_external\n"
+    "uniform samplerExternalOES tex_external;\n"
+    "#endif\n"
+    "void main() {\n"
+    "  vec4 color = texture2D(tex, interp_tc);\n"
+    "#ifdef GL_OES_EGL_image_external\n"
+    "  color += texture2D(tex_external, interp_tc);\n"
+    "#endif\n"
+    "  gl_FragColor = color;\n"
+    "}\n";
+#else
+constexpr char kFragmentShader[] =
+    "#ifdef GL_ES\n"
+    "precision mediump float;\n"
+    "#endif\n"
+    "varying vec2 interp_tc;\n"
+    "uniform sampler2D tex;\n"
+    "void main() {\n"
+    "  gl_FragColor = texture2D(tex, interp_tc);\n"
+    "}\n";
+#endif
+
+GLuint CreateTexture(GLenum texture_target, const gfx::Size& size) {
+  GLuint texture_id;
+  glGenTextures(1, &texture_id);
+  glBindTexture(texture_target, texture_id);
+  if (texture_target == GL_TEXTURE_2D) {
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
+                 GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+  }
+
+  glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  // OpenGLES2.0.25 section 3.8.2 requires CLAMP_TO_EDGE for NPOT textures.
+  glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+  CHECK_EQ(static_cast<int>(glGetError()), GL_NO_ERROR);
+  return texture_id;
+}
+
+// Helper class to automatically acquire and release the GL context.
+class AutoGLContext {
+ public:
+  explicit AutoGLContext(FrameRenderer* const frame_renderer)
+      : frame_renderer_(frame_renderer) {
+    frame_renderer_->AcquireGLContext();
+  }
+  ~AutoGLContext() { frame_renderer_->ReleaseGLContext(); }
+
+ private:
+  FrameRenderer* const frame_renderer_;
+};
+
+}  // namespace
+
+bool FrameRendererThumbnail::gl_initialized_ = false;
+
+FrameRendererThumbnail::FrameRendererThumbnail(
+    const std::vector<std::string>& thumbnail_checksums)
+    : frame_count_(0),
+      thumbnail_checksums_(thumbnail_checksums),
+      thumbnails_fbo_id_(0),
+      thumbnails_texture_id_(0),
+      vertex_buffer_(0),
+      program_(0) {
+  DETACH_FROM_SEQUENCE(renderer_sequence_checker_);
+}
+
+FrameRendererThumbnail::~FrameRendererThumbnail() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  base::AutoLock auto_lock(renderer_lock_);
+  DestroyThumbnailImage();
+  gl_context_ = nullptr;
+  gl_surface_ = nullptr;
+
+  CHECK(mailbox_texture_map_.empty());
+}
+
+// static
+std::unique_ptr<FrameRendererThumbnail> FrameRendererThumbnail::Create(
+    const base::FilePath& video_file_path) {
+  // Read thumbnail checksums from file.
+  std::vector<std::string> thumbnail_checksums =
+      media::test::ReadGoldenThumbnailMD5s(
+          video_file_path.AddExtension(FILE_PATH_LITERAL(".md5")));
+
+  auto frame_renderer =
+      base::WrapUnique(new FrameRendererThumbnail(thumbnail_checksums));
+  frame_renderer->Initialize(video_file_path);
+  return frame_renderer;
+}
+
+void FrameRendererThumbnail::AcquireGLContext() {
+  gl_context_lock_.Acquire();
+  CHECK(gl_context_->MakeCurrent(gl_surface_.get()));
+}
+
+void FrameRendererThumbnail::ReleaseGLContext() {
+  gl_context_lock_.AssertAcquired();
+  gl_context_->ReleaseCurrent(gl_surface_.get());
+  gl_context_lock_.Release();
+}
+
+gl::GLContext* FrameRendererThumbnail::GetGLContext() {
+  gl_context_lock_.AssertAcquired();
+  return gl_context_.get();
+}
+
+void FrameRendererThumbnail::RenderFrame(
+    scoped_refptr<VideoFrame> video_frame) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(renderer_sequence_checker_);
+
+  // Find the texture associated with the video frame's mailbox.
+  base::AutoLock auto_lock(renderer_lock_);
+  const gpu::MailboxHolder& mailbox_holder = video_frame->mailbox_holder(0);
+  const gpu::Mailbox& mailbox = mailbox_holder.mailbox;
+  auto it = mailbox_texture_map_.find(mailbox);
+  ASSERT_NE(it, mailbox_texture_map_.end());
+
+  RenderThumbnail(mailbox_holder.texture_target, it->second);
+}
+
+scoped_refptr<VideoFrame> FrameRendererThumbnail::CreateVideoFrame(
+    VideoPixelFormat pixel_format,
+    const gfx::Size& texture_size,
+    uint32_t texture_target,
+    uint32_t* texture_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(renderer_sequence_checker_);
+
+  // Create a mailbox.
+  gpu::Mailbox mailbox = gpu::Mailbox::Generate();
+  gpu::MailboxHolder mailbox_holders[media::VideoFrame::kMaxPlanes];
+  mailbox_holders[0] =
+      gpu::MailboxHolder(mailbox, gpu::SyncToken(), texture_target);
+
+  // Create a new video frame associated with the mailbox.
+  base::OnceCallback<void(const gpu::SyncToken&)> mailbox_holder_release_cb =
+      base::BindOnce(&FrameRendererThumbnail::DeleteTexture,
+                     base::Unretained(this), mailbox);
+  scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
+      pixel_format, mailbox_holders, std::move(mailbox_holder_release_cb),
+      texture_size, gfx::Rect(texture_size), texture_size, base::TimeDelta());
+
+  // Create a texture and associate it with the mailbox.
+  {
+    AutoGLContext auto_gl_context(this);
+    *texture_id = CreateTexture(texture_target, texture_size);
+  }
+
+  base::AutoLock auto_lock(renderer_lock_);
+  mailbox_texture_map_.insert(std::make_pair(mailbox, *texture_id));
+
+  return frame;
+}
+
+bool FrameRendererThumbnail::ValidateThumbnail() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  base::AutoLock auto_lock(renderer_lock_);
+  const std::vector<uint8_t> rgba = ConvertThumbnailToRGBA();
+
+  // Convert the thumbnail from RGBA to RGB.
+  std::vector<uint8_t> rgb;
+  EXPECT_EQ(media::test::ConvertRGBAToRGB(rgba, &rgb), true)
+      << "RGBA frame has incorrect alpha";
+
+  // Calculate the thumbnail's checksum and compare it to golden values.
+  std::string md5_string = base::MD5String(
+      base::StringPiece(reinterpret_cast<char*>(&rgb[0]), rgb.size()));
+  bool is_valid_thumbnail =
+      base::ContainsValue(thumbnail_checksums_, md5_string);
+
+  return is_valid_thumbnail;
+}
+
+void FrameRendererThumbnail::SaveThumbnail() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  base::AutoLock auto_lock(renderer_lock_);
+  const std::vector<uint8_t> rgba = ConvertThumbnailToRGBA();
+
+  // Convert raw RGBA into PNG for export.
+  std::vector<unsigned char> png;
+  gfx::PNGCodec::Encode(&rgba[0], gfx::PNGCodec::FORMAT_RGBA,
+                        kThumbnailsPageSize, kThumbnailsPageSize.width() * 4,
+                        true, std::vector<gfx::PNGCodec::Comment>(), &png);
+
+  base::FilePath filepath(kDefaultOutputPath);
+  int num_bytes =
+      base::WriteFile(filepath, reinterpret_cast<char*>(&png[0]), png.size());
+  ASSERT_NE(-1, num_bytes);
+  EXPECT_EQ(static_cast<size_t>(num_bytes), png.size());
+}
+
+void FrameRendererThumbnail::Initialize(const base::FilePath& video_file_path) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  // Initialize GL rendering and create GL context.
+  if (!gl_initialized_) {
+    if (!gl::init::InitializeGLOneOff())
+      LOG(FATAL) << "Could not initialize GL";
+    gl_initialized_ = true;
+  }
+  gl_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  gl_context_ = gl::init::CreateGLContext(nullptr, gl_surface_.get(),
+                                          gl::GLContextAttribs());
+
+  base::AutoLock auto_lock(renderer_lock_);
+  InitializeThumbnailImage();
+}
+
+// TODO(dstaessens@) This code is mostly duplicated from
+// RenderingHelper::Initialize(), as that code is unfortunately too inflexible
+// to reuse here. But most of the code in rendering helper can be removed soon
+// when the video_decoder_accelerator_unittests get deprecated.
+void FrameRendererThumbnail::InitializeThumbnailImage() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  AutoGLContext auto_gl_context(this);
+  GLint max_texture_size;
+  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
+  CHECK_GE(max_texture_size, kThumbnailsPageSize.width());
+  CHECK_GE(max_texture_size, kThumbnailsPageSize.height());
+
+  thumbnails_fbo_size_ = kThumbnailsPageSize;
+  thumbnail_size_ = kThumbnailSize;
+
+  glGenFramebuffersEXT(1, &thumbnails_fbo_id_);
+  glGenTextures(1, &thumbnails_texture_id_);
+  glBindTexture(GL_TEXTURE_2D, thumbnails_texture_id_);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, thumbnails_fbo_size_.width(),
+               thumbnails_fbo_size_.height(), 0, GL_RGB,
+               GL_UNSIGNED_SHORT_5_6_5, NULL);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glBindTexture(GL_TEXTURE_2D, 0);
+
+  glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
+  glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                            thumbnails_texture_id_, 0);
+
+  GLenum fb_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
+  CHECK(fb_status == GL_FRAMEBUFFER_COMPLETE) << fb_status;
+  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+  glClear(GL_COLOR_BUFFER_BIT);
+  glBindFramebufferEXT(GL_FRAMEBUFFER,
+                       gl_surface_->GetBackingFramebufferObject());
+
+  // These vertices and texture coords map (0,0) in the texture to the bottom
+  // left of the viewport. Since we get the video frames with the the top left
+  // at (0,0) we need to flip the texture y coordinate in the vertex shader for
+  // this to be rendered the right way up. In the case of thumbnail rendering we
+  // use the same vertex shader to render the FBO to the screen, where we do not
+  // want this flipping. Vertices are 2 floats for position and 2 floats for
+  // texcoord each.
+  const float kVertices[] = {
+      -1, 1,  0, 1,  // Vertex 0
+      -1, -1, 0, 0,  // Vertex 1
+      1,  1,  1, 1,  // Vertex 2
+      1,  -1, 1, 0,  // Vertex 3
+  };
+  const GLvoid* kVertexPositionOffset = 0;
+  const GLvoid* kVertexTexcoordOffset =
+      reinterpret_cast<GLvoid*>(sizeof(float) * 2);
+  const GLsizei kVertexStride = sizeof(float) * 4;
+
+  glGenBuffersARB(1, &vertex_buffer_);
+  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
+  glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW);
+
+  program_ = glCreateProgram();
+  RenderingHelper::CreateShader(program_, GL_VERTEX_SHADER, kVertexShader,
+                                base::size(kVertexShader));
+  RenderingHelper::CreateShader(program_, GL_FRAGMENT_SHADER, kFragmentShader,
+                                base::size(kFragmentShader));
+  glLinkProgram(program_);
+  GLint result = GL_FALSE;
+  glGetProgramiv(program_, GL_LINK_STATUS, &result);
+  if (!result) {
+    constexpr GLsizei kLogBufferSize = 4096;
+    char log[kLogBufferSize];
+    glGetShaderInfoLog(program_, kLogBufferSize, NULL, log);
+    LOG(FATAL) << log;
+  }
+  glUseProgram(program_);
+  glDeleteProgram(program_);
+
+  glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
+  glUniform1i(glGetUniformLocation(program_, "tex"), 0);
+  GLint tex_external = glGetUniformLocation(program_, "tex_external");
+  if (tex_external != -1) {
+    glUniform1i(tex_external, 1);
+  }
+  GLint pos_location = glGetAttribLocation(program_, "in_pos");
+  glEnableVertexAttribArray(pos_location);
+  glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, kVertexStride,
+                        kVertexPositionOffset);
+  GLint tc_location = glGetAttribLocation(program_, "in_tc");
+  glEnableVertexAttribArray(tc_location);
+  glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, kVertexStride,
+                        kVertexTexcoordOffset);
+
+  // Unbind the vertex buffer
+  glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+void FrameRendererThumbnail::DestroyThumbnailImage() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  AutoGLContext auto_gl_context(this);
+  glDeleteTextures(1, &thumbnails_texture_id_);
+  glDeleteFramebuffersEXT(1, &thumbnails_fbo_id_);
+  glDeleteBuffersARB(1, &vertex_buffer_);
+}
+
+void FrameRendererThumbnail::RenderThumbnail(uint32_t texture_target,
+                                             uint32_t texture_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(renderer_sequence_checker_);
+
+  const int width = thumbnail_size_.width();
+  const int height = thumbnail_size_.height();
+  const int thumbnails_in_row = thumbnails_fbo_size_.width() / width;
+  const int thumbnails_in_column = thumbnails_fbo_size_.height() / height;
+  const int row = (frame_count_ / thumbnails_in_row) % thumbnails_in_column;
+  const int col = frame_count_ % thumbnails_in_row;
+  gfx::Rect area(col * width, row * height, width, height);
+
+  AutoGLContext auto_gl_context(this);
+  glUniform1i(glGetUniformLocation(program_, "tex_flip"), 0);
+  glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
+  RenderingHelper::GLSetViewPort(area);
+  RenderingHelper::RenderTexture(texture_target, texture_id);
+  glBindFramebufferEXT(GL_FRAMEBUFFER,
+                       gl_surface_->GetBackingFramebufferObject());
+  // We need to flush the GL commands before returning the thumbnail texture to
+  // the decoder.
+  glFlush();
+
+  ++frame_count_;
+}
+
+const std::vector<uint8_t> FrameRendererThumbnail::ConvertThumbnailToRGBA() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  AutoGLContext auto_gl_context(this);
+  std::vector<uint8_t> rgba;
+  const size_t num_pixels = thumbnails_fbo_size_.GetArea();
+  rgba.resize(num_pixels * 4);
+  glBindFramebufferEXT(GL_FRAMEBUFFER, thumbnails_fbo_id_);
+  glPixelStorei(GL_PACK_ALIGNMENT, 1);
+  // We can only count on GL_RGBA/GL_UNSIGNED_BYTE support.
+  glReadPixels(0, 0, thumbnails_fbo_size_.width(),
+               thumbnails_fbo_size_.height(), GL_RGBA, GL_UNSIGNED_BYTE,
+               &(rgba)[0]);
+  glBindFramebufferEXT(GL_FRAMEBUFFER,
+                       gl_surface_->GetBackingFramebufferObject());
+
+  return rgba;
+}
+
+void FrameRendererThumbnail::DeleteTexture(const gpu::Mailbox& mailbox,
+                                           const gpu::SyncToken&) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+
+  base::AutoLock auto_lock(renderer_lock_);
+  auto it = mailbox_texture_map_.find(mailbox);
+  ASSERT_NE(it, mailbox_texture_map_.end());
+  uint32_t texture_id = it->second;
+  mailbox_texture_map_.erase(mailbox);
+
+  RenderingHelper::DeleteTexture(texture_id);
+}
+
+}  // namespace test
+}  // namespace media
diff --git a/media/gpu/test/video_player/frame_renderer_thumbnail.h b/media/gpu/test/video_player/frame_renderer_thumbnail.h
new file mode 100644
index 0000000..b3a7437
--- /dev/null
+++ b/media/gpu/test/video_player/frame_renderer_thumbnail.h
@@ -0,0 +1,130 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_GPU_TEST_VIDEO_PLAYER_FRAME_RENDERER_THUMBNAIL_H_
+#define MEDIA_GPU_TEST_VIDEO_PLAYER_FRAME_RENDERER_THUMBNAIL_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/sequence_checker.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/thread_annotations.h"
+#include "base/threading/thread.h"
+#include "gpu/command_buffer/common/gl2_types.h"
+#include "media/gpu/test/video_player/frame_renderer.h"
+
+namespace gl {
+
+class GLContext;
+class GLSurface;
+
+}  // namespace gl
+
+namespace media {
+namespace test {
+
+// The thumbnail frame renderer draws a thumbnail of each frame into a large
+// image containing 10x10 thumbnails. The checksum of this image can then be
+// compared to a golden value.
+// Rendering introduces small platform-dependant differences, so multiple golden
+// values need to be maintained. The thumbnail frame renderer should only be
+// used on older platforms that are not supported by the FrameValidator, and
+// will be deprecated soon.
+class FrameRendererThumbnail : public FrameRenderer {
+ public:
+  ~FrameRendererThumbnail() override;
+
+  // Create an instance of the thumbnail frame renderer. The |video_file_path|
+  // should point to a file containing all golden thumbnail hashes for the video
+  // being rendered.
+  static std::unique_ptr<FrameRendererThumbnail> Create(
+      const base::FilePath& video_file_path);
+
+  // FrameRenderer implementation
+  // Acquire the active GL context. This will claim |gl_context_lock_|.
+  void AcquireGLContext() override;
+  // Release the active GL context. This will release |gl_context_lock_|.
+  void ReleaseGLContext() override;
+  // Get the active GL context. This requires holding |gl_context_lock_|.
+  gl::GLContext* GetGLContext() override;
+  void RenderFrame(scoped_refptr<VideoFrame> video_frame) override;
+  scoped_refptr<VideoFrame> CreateVideoFrame(VideoPixelFormat pixel_format,
+                                             const gfx::Size& texture_size,
+                                             uint32_t texture_target,
+                                             uint32_t* texture_id) override;
+
+  // Validate the thumbnail image by comparing it against known golden values.
+  bool ValidateThumbnail();
+  // Save the thumbnail image to disk.
+  void SaveThumbnail();
+
+ private:
+  explicit FrameRendererThumbnail(
+      const std::vector<std::string>& thumbnail_checksums);
+
+  // Initialize the frame renderer, performs all rendering-related setup.
+  void Initialize(const base::FilePath& video_file_path);
+
+  // Initialize the thumbnail image the frame thumbnails will be rendered to.
+  void InitializeThumbnailImage() EXCLUSIVE_LOCKS_REQUIRED(renderer_lock_);
+  // Destroy the thumbnail image.
+  void DestroyThumbnailImage() EXCLUSIVE_LOCKS_REQUIRED(renderer_lock_);
+  // Render the texture with specified |texture_id| to the thumbnail image.
+  void RenderThumbnail(uint32_t texture_target, uint32_t texture_id)
+      EXCLUSIVE_LOCKS_REQUIRED(renderer_lock_);
+  // Convert the thumbnail image to RGBA.
+  const std::vector<uint8_t> ConvertThumbnailToRGBA()
+      EXCLUSIVE_LOCKS_REQUIRED(renderer_lock_);
+
+  // Destroy the texture associated with the specified |mailbox|.
+  void DeleteTexture(const gpu::Mailbox& mailbox, const gpu::SyncToken&);
+
+  // The number of frames rendered so far.
+  size_t frame_count_ GUARDED_BY(renderer_lock_);
+
+  // The list of thumbnail checksums for all platforms.
+  const std::vector<std::string> thumbnail_checksums_;
+  // Map between mailboxes and texture id's.
+  std::map<gpu::Mailbox, uint32_t> mailbox_texture_map_
+      GUARDED_BY(renderer_lock_);
+
+  // Id of the frame buffer used to render the thumbnails image.
+  GLuint thumbnails_fbo_id_ GUARDED_BY(renderer_lock_);
+  // Size of the frame buffer used to render the thumbnails image.
+  gfx::Size thumbnails_fbo_size_ GUARDED_BY(renderer_lock_);
+  // Texture id of the thumbnails image.
+  GLuint thumbnails_texture_id_ GUARDED_BY(renderer_lock_);
+  // Size of the individual thumbnails rendered to the thumbnails image.
+  gfx::Size thumbnail_size_ GUARDED_BY(renderer_lock_);
+  // Vertex buffer used to render thumbnails to the thumbnails image.
+  GLuint vertex_buffer_ GUARDED_BY(renderer_lock_);
+  // Shader program used to render thumbnails to the thumbnails image.
+  GLuint program_ GUARDED_BY(renderer_lock_);
+
+  // Lock protecting variables accessed on both renderer and client thread.
+  mutable base::Lock renderer_lock_;
+
+  // Lock protecting access to the gl context.
+  mutable base::Lock gl_context_lock_;
+  // GL context used for rendering.
+  scoped_refptr<gl::GLContext> gl_context_;
+  // GL surface used for rendering.
+  scoped_refptr<gl::GLSurface> gl_surface_;
+
+  // Whether GL was initialized, as it should only happen once.
+  static bool gl_initialized_;
+
+  SEQUENCE_CHECKER(client_sequence_checker_);
+  SEQUENCE_CHECKER(renderer_sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(FrameRendererThumbnail);
+};
+
+}  // namespace test
+}  // namespace media
+
+#endif  // MEDIA_GPU_TEST_VIDEO_PLAYER_FRAME_RENDERER_THUMBNAIL_H_
diff --git a/media/gpu/test/video_player/video.cc b/media/gpu/test/video_player/video.cc
index dc01d68..47a8e3f 100644
--- a/media/gpu/test/video_player/video.cc
+++ b/media/gpu/test/video_player/video.cc
@@ -78,6 +78,10 @@
   return data_.size() > 0;
 }
 
+const base::FilePath& Video::FilePath() const {
+  return file_path_;
+}
+
 const std::vector<uint8_t>& Video::Data() const {
   return data_;
 }
diff --git a/media/gpu/test/video_player/video.h b/media/gpu/test/video_player/video.h
index b5ae6ff..155355a 100644
--- a/media/gpu/test/video_player/video.h
+++ b/media/gpu/test/video_player/video.h
@@ -29,6 +29,8 @@
   // Returns true if the video file was loaded.
   bool IsLoaded() const;
 
+  // Get the video file path.
+  const base::FilePath& FilePath() const;
   // Get the video data, will be empty if the video hasn't been loaded yet.
   const std::vector<uint8_t>& Data() const;
 
diff --git a/media/gpu/test/video_player/video_decoder_client.cc b/media/gpu/test/video_player/video_decoder_client.cc
index 69fe89b..c179f59 100644
--- a/media/gpu/test/video_player/video_decoder_client.cc
+++ b/media/gpu/test/video_player/video_decoder_client.cc
@@ -45,6 +45,12 @@
   DestroyDecoder();
   decoder_client_thread_.Stop();
 
+  // Clear video frames, triggering associated destruction callbacks while we
+  // still have a GLcontext.
+  frame_renderer_->AcquireGLContext();
+  video_frames_.clear();
+  frame_renderer_->ReleaseGLContext();
+
   // Wait until the frame processors are done, before destroying them. As the
   // decoder has been destroyed no new frames will be sent to the processors.
   WaitForFrameProcessors();
@@ -114,6 +120,10 @@
   return success;
 }
 
+FrameRenderer* VideoDecoderClient::GetFrameRenderer() const {
+  return frame_renderer_.get();
+}
+
 void VideoDecoderClient::Play() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
 
@@ -148,23 +158,48 @@
             << " picture buffers with size " << size.height() << "x"
             << size.height();
 
-  // Create a set of picture buffers and give them to the decoder.
-  std::vector<PictureBuffer> picture_buffers;
-  for (uint32_t i = 0; i < requested_num_of_buffers; ++i)
-    picture_buffers.emplace_back(GetNextPictureBufferId(), size);
-  decoder_->AssignPictureBuffers(picture_buffers);
+  // If using import mode, create a set of DMABuf-backed video frames.
+  if (decoder_client_config_.allocation_mode == AllocationMode::kImport) {
+    std::vector<PictureBuffer> picture_buffers;
+    for (uint32_t i = 0; i < requested_num_of_buffers; ++i) {
+      picture_buffers.emplace_back(GetNextPictureBufferId(), size);
+    }
+    decoder_->AssignPictureBuffers(picture_buffers);
 
-  // Create a video frame for each of the picture buffers and provide memory
-  // handles to the video frame's data to the decoder.
-  for (const PictureBuffer& picture_buffer : picture_buffers) {
-    scoped_refptr<VideoFrame> video_frame =
-        CreatePlatformVideoFrame(pixel_format, size);
-    LOG_ASSERT(video_frame) << "Failed to create video frame";
-    video_frames_.emplace(picture_buffer.id(), video_frame);
-    gfx::GpuMemoryBufferHandle handle =
-        CreateGpuMemoryBufferHandle(video_frame);
-    LOG_ASSERT(!handle.is_null()) << "Failed to create GPU memory handle";
-    decoder_->ImportBufferForPicture(picture_buffer.id(), pixel_format, handle);
+    // Create a video frame for each of the picture buffers and provide memory
+    // handles to the video frame's data to the decoder.
+    for (const PictureBuffer& picture_buffer : picture_buffers) {
+      scoped_refptr<VideoFrame> video_frame =
+          CreatePlatformVideoFrame(pixel_format, size);
+      LOG_ASSERT(video_frame) << "Failed to create video frame";
+      video_frames_.emplace(picture_buffer.id(), video_frame);
+      gfx::GpuMemoryBufferHandle handle =
+          CreateGpuMemoryBufferHandle(video_frame);
+      LOG_ASSERT(!handle.is_null()) << "Failed to create GPU memory handle";
+      decoder_->ImportBufferForPicture(picture_buffer.id(), pixel_format,
+                                       handle);
+    }
+  }
+
+  // If using allocate mode, request a set of texture-backed video frames from
+  // the renderer.
+  if (decoder_client_config_.allocation_mode == AllocationMode::kAllocate) {
+    std::vector<PictureBuffer> picture_buffers;
+    for (uint32_t i = 0; i < requested_num_of_buffers; ++i) {
+      uint32_t texture_id;
+      auto video_frame = frame_renderer_->CreateVideoFrame(
+          pixel_format, size, texture_target, &texture_id);
+      LOG_ASSERT(video_frame) << "Failed to create video frame";
+      int32_t picture_buffer_id = GetNextPictureBufferId();
+      PictureBuffer::TextureIds texture_ids(1, texture_id);
+      picture_buffers.emplace_back(picture_buffer_id, size, texture_ids,
+                                   texture_ids, texture_target, pixel_format);
+      video_frames_.emplace(picture_buffer_id, std::move(video_frame));
+    }
+    // The decoder requires an active GL context to allocate memory.
+    frame_renderer_->AcquireGLContext();
+    decoder_->AssignPictureBuffers(picture_buffers);
+    frame_renderer_->ReleaseGLContext();
   }
 }
 
@@ -183,24 +218,38 @@
   LOG_ASSERT(it != video_frames_.end());
   scoped_refptr<VideoFrame> video_frame = it->second;
 
-  // Wrap the video frame in another video frame that calls
-  // ReusePictureBufferTask() upon destruction. When the renderer is done using
-  // the video frame, the associated picture buffer will automatically be
-  // flagged for reuse.
-  base::OnceClosure delete_cb = BindToCurrentLoop(
-      base::BindOnce(&VideoDecoderClient::ReusePictureBufferTask,
-                     base::Unretained(this), picture.picture_buffer_id()));
+  // When using import mode, we wrap the video frame in another video frame that
+  // calls ReusePictureBufferTask() upon destruction. When the renderer and
+  // video frame processors are done using the video frame, the associated
+  // picture buffer will automatically be flagged for reuse.
+  if (decoder_client_config_.allocation_mode == AllocationMode::kImport) {
+    base::OnceClosure delete_cb = BindToCurrentLoop(
+        base::BindOnce(&VideoDecoderClient::ReusePictureBufferTask,
+                       base::Unretained(this), picture.picture_buffer_id()));
 
-  scoped_refptr<VideoFrame> wrapped_video_frame = VideoFrame::WrapVideoFrame(
-      video_frame, video_frame->format(), video_frame->visible_rect(),
-      video_frame->visible_rect().size());
-  wrapped_video_frame->AddDestructionObserver(std::move(delete_cb));
+    scoped_refptr<VideoFrame> wrapped_video_frame = VideoFrame::WrapVideoFrame(
+        video_frame, video_frame->format(), video_frame->visible_rect(),
+        video_frame->visible_rect().size());
+    wrapped_video_frame->AddDestructionObserver(std::move(delete_cb));
 
-  frame_renderer_->RenderFrame(wrapped_video_frame);
+    frame_renderer_->RenderFrame(wrapped_video_frame);
 
-  for (auto& frame_processor : frame_processors_)
-    frame_processor->ProcessVideoFrame(wrapped_video_frame,
-                                       current_frame_index_);
+    for (auto& frame_processor : frame_processors_)
+      frame_processor->ProcessVideoFrame(wrapped_video_frame,
+                                         current_frame_index_);
+  }
+
+  // When using allocate mode, direct texture memory access is not supported.
+  // Since this is required by the video frame processors we can't use these
+  // here. Wrapping a video frame inside another video frame is also not
+  // supported, so we have to render the frame and return the picture buffer
+  // synchronously here. See http://crbug/362521.
+  if (decoder_client_config_.allocation_mode == AllocationMode::kAllocate) {
+    frame_renderer_->RenderFrame(video_frame);
+    ReusePictureBufferTask(picture.picture_buffer_id());
+    return;
+  }
+
   current_frame_index_++;
 }
 
diff --git a/media/gpu/test/video_player/video_decoder_client.h b/media/gpu/test/video_player/video_decoder_client.h
index b4e432a..0c153d1 100644
--- a/media/gpu/test/video_player/video_decoder_client.h
+++ b/media/gpu/test/video_player/video_decoder_client.h
@@ -27,11 +27,20 @@
 class FrameRenderer;
 class VideoFrameProcessor;
 
+// TODO(dstaessens@) Remove allocation mode, temporary added here so we can
+// support the thumbnail test for older platforms that don't support import.
+enum class AllocationMode {
+  kImport,    // Client allocates video frame memory.
+  kAllocate,  // Video decoder allocates video frame memory.
+};
+
 // Video decoder client configuration.
 struct VideoDecoderClientConfig {
   // The maximum number of bitstream buffer decodes that can be requested
   // without waiting for the result of the previous decode requests.
   size_t max_outstanding_decode_requests = 1;
+  // How the pictures buffers should be allocated.
+  AllocationMode allocation_mode = AllocationMode::kImport;
 };
 
 // The video decoder client is responsible for the communication between the
@@ -70,6 +79,8 @@
   // Wait until all frame processors have finished processing. Returns whether
   // processing was successful.
   bool WaitForFrameProcessors();
+  // Get the frame renderer associated with the video decoder client.
+  FrameRenderer* GetFrameRenderer() const;
 
   // Start decoding the video stream, decoder should be idle when this function
   // is called. This function is non-blocking, for each frame decoded a
diff --git a/media/gpu/test/video_player/video_player.cc b/media/gpu/test/video_player/video_player.cc
index 2fb37a3..87b014c3 100644
--- a/media/gpu/test/video_player/video_player.cc
+++ b/media/gpu/test/video_player/video_player.cc
@@ -57,12 +57,15 @@
       event_cb, std::move(frame_renderer), std::move(frame_processors), config);
   CHECK(decoder_client_) << "Failed to create decoder client";
 
-  // Create a decoder for the specified video. We'll always use import mode as
-  // this is the only mode supported by the media::VideoDecoder interface, which
-  // the video decoders are being migrated to.
+  // Create a decoder for the specified video.
+  // TODO(dstaessens@) Remove support for allocate mode, and always use import
+  // mode. Support for allocate mode is temporary maintained for older platforms
+  // that don't support import mode.
   VideoDecodeAccelerator::Config decoder_config(video->Profile());
   decoder_config.output_mode =
-      VideoDecodeAccelerator::Config::OutputMode::IMPORT;
+      config.allocation_mode == AllocationMode::kImport
+          ? VideoDecodeAccelerator::Config::OutputMode::IMPORT
+          : VideoDecodeAccelerator::Config::OutputMode::ALLOCATE;
   decoder_client_->CreateDecoder(decoder_config, video->Data());
 
   video_ = video;
@@ -129,6 +132,10 @@
   return video_player_state_;
 }
 
+FrameRenderer* VideoPlayer::GetFrameRenderer() const {
+  return decoder_client_->GetFrameRenderer();
+}
+
 bool VideoPlayer::WaitForEvent(VideoPlayerEvent event,
                                size_t times,
                                base::TimeDelta max_wait) {
diff --git a/media/gpu/test/video_player/video_player.h b/media/gpu/test/video_player/video_player.h
index bdfeb22..da5a611 100644
--- a/media/gpu/test/video_player/video_player.h
+++ b/media/gpu/test/video_player/video_player.h
@@ -84,6 +84,8 @@
   size_t GetCurrentFrame() const;
   // Get the current state of the video player.
   VideoPlayerState GetState() const;
+  // Get the frame renderer associated with the video player.
+  FrameRenderer* GetFrameRenderer() const;
 
   // Wait for an event to occur the specified number of times. All events that
   // occurred since last calling this function will be taken into account. All
diff --git a/media/gpu/v4l2/v4l2_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
index 6cdc015..4ba7f87 100644
--- a/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
@@ -1610,14 +1610,14 @@
               << " not in use (anymore?).";
     return;
   }
-  V4L2ReadableBufferRef buffer = std::move(iter->second);
-  DCHECK_EQ(buffers_at_client_.count(picture_buffer_id), 1u);
-  buffers_at_client_.erase(iter);
 
-  // Take ownership of the EGL fence.
+  // Take ownership of the EGL fence and keep the buffer out of the game until
+  // the fence signals.
   if (egl_fence)
     buffers_awaiting_fence_.emplace(
-        std::make_pair(std::move(egl_fence), std::move(buffer)));
+        std::make_pair(std::move(egl_fence), std::move(iter->second)));
+
+  buffers_at_client_.erase(iter);
 
   // We got a buffer back, so enqueue it back.
   Enqueue();
@@ -1885,6 +1885,9 @@
     decoder_input_queue_.pop_front();
   decoder_flushing_ = false;
 
+  // First liberate all the frames held by the client.
+  buffers_at_client_.clear();
+
   image_processor_ = nullptr;
   while (!buffers_at_ip_.empty())
     buffers_at_ip_.pop();
@@ -1996,6 +1999,8 @@
     return;
   }
 
+  buffers_at_client_.clear();
+
   image_processor_ = nullptr;
 
   if (!DestroyOutputBuffers()) {
@@ -2584,10 +2589,6 @@
   while (!buffers_awaiting_fence_.empty())
     buffers_awaiting_fence_.pop();
 
-  // TODO(acourbot@) the client should properly drop all references to the
-  // frames it holds instead!
-  buffers_at_client_.clear();
-
   if (!output_queue_->DeallocateBuffers()) {
     NOTIFY_ERROR(PLATFORM_FAILURE);
     success = false;
@@ -2601,7 +2602,8 @@
 void V4L2VideoDecodeAccelerator::SendBufferToClient(
     size_t output_buffer_index,
     int32_t bitstream_buffer_id,
-    V4L2ReadableBufferRef vda_buffer) {
+    V4L2ReadableBufferRef vda_buffer,
+    scoped_refptr<VideoFrame> frame) {
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
   DCHECK_GE(bitstream_buffer_id, 0);
   OutputRecord& output_record = output_buffer_map_[output_buffer_index];
@@ -2610,7 +2612,9 @@
   // We need to keep the VDA buffer for now, as the IP still needs to be told
   // which buffer to use so we cannot use this buffer index before the client
   // has returned the corresponding IP buffer.
-  buffers_at_client_.emplace(output_record.picture_id, std::move(vda_buffer));
+  buffers_at_client_.emplace(
+      output_record.picture_id,
+      std::make_pair(std::move(vda_buffer), std::move(frame)));
   // TODO(hubbe): Insert correct color space. http://crbug.com/647725
   const Picture picture(output_record.picture_id, bitstream_buffer_id,
                         gfx::Rect(visible_size_), gfx::ColorSpace(), false);
@@ -2719,8 +2723,7 @@
     child_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&V4L2VideoDecodeAccelerator::CreateEGLImageFor,
-                       weak_this_, ip_buffer_index,
-                       ip_output_record.picture_id,
+                       weak_this_, ip_buffer_index, ip_output_record.picture_id,
                        media::DuplicateFDs(frame->DmabufFds()),
                        ip_output_record.texture_id, egl_image_size_,
                        egl_image_format_fourcc_));
@@ -2734,7 +2737,7 @@
   buffers_at_ip_.pop();
 
   SendBufferToClient(ip_buffer_index, bitstream_buffer_id,
-                     std::move(vda_buffer));
+                     std::move(vda_buffer), std::move(frame));
   // Flush or resolution change may be waiting image processor to finish.
   if (buffers_at_ip_.empty()) {
     NotifyFlushDoneIfNeeded();
diff --git a/media/gpu/v4l2/v4l2_video_decode_accelerator.h b/media/gpu/v4l2/v4l2_video_decode_accelerator.h
index ab740e5..6a22d9c 100644
--- a/media/gpu/v4l2/v4l2_video_decode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_video_decode_accelerator.h
@@ -13,6 +13,7 @@
 #include <stdint.h>
 
 #include <list>
+#include <map>
 #include <memory>
 #include <queue>
 #include <utility>
@@ -394,9 +395,15 @@
   // output buffer is |output_buffer_index| and its id is |bitstream_buffer_id|.
   bool ProcessFrame(int32_t bitstream_buffer_id, V4L2ReadableBufferRef buf);
 
+  // Send a buffer to the client.
+  // |buffer_index| is the output buffer index of the buffer to be sent.
+  // |bitstream_buffer_id| is the bitstream ID from which the buffer results.
+  // |vda_buffer| is the output VDA buffer containing the decoded frame.
+  // |frame| is the IP frame that will be sent to the client, if IP is used.
   void SendBufferToClient(size_t buffer_index,
                           int32_t bitstream_buffer_id,
-                          V4L2ReadableBufferRef vda_buffer);
+                          V4L2ReadableBufferRef vda_buffer,
+                          scoped_refptr<VideoFrame> frame = nullptr);
 
   //
   // Methods run on child thread.
@@ -518,10 +525,14 @@
   // Bitstream IDs and VDA buffers currently being processed by the IP.
   std::queue<std::pair<int32_t, V4L2ReadableBufferRef>> buffers_at_ip_;
   // Keeps decoded buffers out of the free list until the client returns them.
-  std::map<int32_t, V4L2ReadableBufferRef> buffers_at_client_;
+  // First element is the VDA buffer, second is the (optional) IP buffer.
+  std::map<int32_t, std::pair<V4L2ReadableBufferRef, scoped_refptr<VideoFrame>>>
+      buffers_at_client_;
   // Queue of buffers that have been returned by the client, but which fence
-  // hasn't been signaled yet.
-  std::queue<std::pair<std::unique_ptr<gl::GLFenceEGL>, V4L2ReadableBufferRef>>
+  // hasn't been signaled yet. Keeps both the VDA and (optional) IP buffer.
+  std::queue<
+      std::pair<std::unique_ptr<gl::GLFenceEGL>,
+                std::pair<V4L2ReadableBufferRef, scoped_refptr<VideoFrame>>>>
       buffers_awaiting_fence_;
 
   // Mapping of int index to output buffer record.
diff --git a/media/gpu/video_decode_accelerator_tests.cc b/media/gpu/video_decode_accelerator_tests.cc
index 6503249..90741a4 100644
--- a/media/gpu/video_decode_accelerator_tests.cc
+++ b/media/gpu/video_decode_accelerator_tests.cc
@@ -7,6 +7,7 @@
 #include "media/gpu/test/video_frame_file_writer.h"
 #include "media/gpu/test/video_frame_validator.h"
 #include "media/gpu/test/video_player/frame_renderer_dummy.h"
+#include "media/gpu/test/video_player/frame_renderer_thumbnail.h"
 #include "media/gpu/test/video_player/video.h"
 #include "media/gpu/test/video_player/video_collection.h"
 #include "media/gpu/test/video_player/video_decoder_client.h"
@@ -41,18 +42,20 @@
  public:
   std::unique_ptr<VideoPlayer> CreateVideoPlayer(
       const Video* video,
-      const VideoDecoderClientConfig& config = VideoDecoderClientConfig()) {
+      const VideoDecoderClientConfig& config = VideoDecoderClientConfig(),
+      std::unique_ptr<FrameRenderer> frame_renderer =
+          FrameRendererDummy::Create()) {
     LOG_ASSERT(video);
     std::vector<std::unique_ptr<VideoFrameProcessor>> frame_processors;
 
     // Validate decoded video frames.
-    if (g_env->output_frames_) {
+    if (g_env->enable_validator_) {
       frame_processors.push_back(
           media::test::VideoFrameValidator::Create(video->FrameChecksums()));
     }
 
     // Write decoded video frames to the 'video_frames/<test_name/>' folder.
-    if (g_env->enable_validator_) {
+    if (g_env->output_frames_) {
       const ::testing::TestInfo* const test_info =
           ::testing::UnitTest::GetInstance()->current_test_info();
       base::FilePath output_folder =
@@ -61,7 +64,7 @@
       frame_processors.push_back(VideoFrameFileWriter::Create(output_folder));
     }
 
-    return VideoPlayer::Create(video, FrameRendererDummy::Create(),
+    return VideoPlayer::Create(video, std::move(frame_renderer),
                                std::move(frame_processors), config);
   }
 };
@@ -253,6 +256,32 @@
   }
 }
 
+// Play a video from start to finish. Thumbnails of the decoded frames will be
+// rendered into a image, whose checksum is compared to a golden value. This
+// test is only needed on older platforms that don't support the video frame
+// validator, which requires direct access to the video frame's memory. This
+// test is only ran when --disable_validator is specified, and will be
+// deprecated in the future.
+TEST_F(VideoDecoderTest, FlushAtEndOfStream_RenderThumbnails) {
+  if (g_env->enable_validator_)
+    GTEST_SKIP();
+
+  VideoDecoderClientConfig config;
+  config.allocation_mode = AllocationMode::kAllocate;
+  auto tvp = CreateVideoPlayer(
+      g_env->video_, config,
+      FrameRendererThumbnail::Create(g_env->video_->FilePath()));
+
+  tvp->Play();
+  EXPECT_TRUE(tvp->WaitForFlushDone());
+
+  EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
+  EXPECT_EQ(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
+  EXPECT_TRUE(tvp->WaitForFrameProcessors());
+  EXPECT_TRUE(static_cast<FrameRendererThumbnail*>(tvp->GetFrameRenderer())
+                  ->ValidateThumbnail());
+}
+
 }  // namespace test
 }  // namespace media
 
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index 563a35c..1f1ac59 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -25,6 +25,7 @@
 #include "components/viz/client/client_resource_provider.h"
 #include "components/viz/client/shared_bitmap_reporter.h"
 #include "components/viz/common/gpu/context_provider.h"
+#include "components/viz/common/gpu/raster_context_provider.h"
 #include "components/viz/common/quads/render_pass.h"
 #include "components/viz/common/quads/stream_video_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
@@ -332,11 +333,16 @@
                         viz::ResourceFormat format,
                         const gfx::ColorSpace& color_space,
                         bool use_gpu_memory_buffer_resources,
-                        viz::ContextProvider* context_provider)
+                        viz::ContextProvider* context_provider,
+                        viz::RasterContextProvider* raster_context_provider)
       : PlaneResource(plane_resource_id, size, format, /*is_software=*/false),
-        context_provider_(context_provider) {
-    DCHECK(context_provider_);
-    const gpu::Capabilities& caps = context_provider_->ContextCapabilities();
+        context_provider_(context_provider),
+        raster_context_provider_(raster_context_provider) {
+    DCHECK(context_provider_ || raster_context_provider_);
+    const gpu::Capabilities& caps =
+        raster_context_provider_
+            ? raster_context_provider_->ContextCapabilities()
+            : context_provider_->ContextCapabilities();
     overlay_candidate_ = use_gpu_memory_buffer_resources &&
                          caps.texture_storage_image &&
                          IsGpuMemoryBufferFormatSupported(format);
@@ -347,24 +353,17 @@
       texture_target_ = gpu::GetBufferTextureTarget(gfx::BufferUsage::SCANOUT,
                                                     BufferFormat(format), caps);
     }
-    auto* sii = context_provider_->SharedImageInterface();
-    DCHECK(sii);
-    auto* gl = context_provider_->ContextGL();
-    DCHECK(gl);
-
+    auto* sii = SharedImageInterface();
     mailbox_ =
         sii->CreateSharedImage(format, size, color_space, shared_image_usage);
-    gl->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
+    ContextGL()->WaitSyncTokenCHROMIUM(
+        sii->GenUnverifiedSyncToken().GetConstData());
   }
 
   ~HardwarePlaneResource() override {
-    auto* sii = context_provider_->SharedImageInterface();
-    DCHECK(sii);
-    auto* gl = context_provider_->ContextGL();
-    DCHECK(gl);
     gpu::SyncToken sync_token;
-    gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
-    sii->DestroySharedImage(sync_token, mailbox_);
+    ContextGL()->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
+    SharedImageInterface()->DestroySharedImage(sync_token, mailbox_);
   }
 
   const gpu::Mailbox& mailbox() const { return mailbox_; }
@@ -373,7 +372,23 @@
   bool overlay_candidate() const { return overlay_candidate_; }
 
  private:
+  gpu::SharedImageInterface* SharedImageInterface() {
+    auto* sii = raster_context_provider_
+                    ? raster_context_provider_->SharedImageInterface()
+                    : context_provider_->SharedImageInterface();
+    DCHECK(sii);
+    return sii;
+  }
+
+  gpu::gles2::GLES2Interface* ContextGL() {
+    auto* gl = raster_context_provider_ ? raster_context_provider_->ContextGL()
+                                        : context_provider_->ContextGL();
+    DCHECK(gl);
+    return gl;
+  }
+
   viz::ContextProvider* const context_provider_;
+  viz::RasterContextProvider* const raster_context_provider_;
   gpu::Mailbox mailbox_;
   GLenum texture_target_ = GL_TEXTURE_2D;
   bool overlay_candidate_ = false;
@@ -395,6 +410,7 @@
 
 VideoResourceUpdater::VideoResourceUpdater(
     viz::ContextProvider* context_provider,
+    viz::RasterContextProvider* raster_context_provider,
     viz::SharedBitmapReporter* shared_bitmap_reporter,
     viz::ClientResourceProvider* resource_provider,
     bool use_stream_video_draw_quad,
@@ -402,6 +418,7 @@
     bool use_r16_texture,
     int max_resource_size)
     : context_provider_(context_provider),
+      raster_context_provider_(raster_context_provider),
       shared_bitmap_reporter_(shared_bitmap_reporter),
       resource_provider_(resource_provider),
       use_stream_video_draw_quad_(use_stream_video_draw_quad),
@@ -410,7 +427,8 @@
       max_resource_size_(max_resource_size),
       tracing_id_(g_next_video_resource_updater_id.GetNext()),
       weak_ptr_factory_(this) {
-  DCHECK(context_provider_ || shared_bitmap_reporter_);
+  DCHECK(context_provider_ || raster_context_provider_ ||
+         shared_bitmap_reporter_);
 
   base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
       this, "media::VideoResourceUpdater", base::ThreadTaskRunnerHandle::Get());
@@ -619,8 +637,10 @@
 
 viz::ResourceFormat VideoResourceUpdater::YuvResourceFormat(
     int bits_per_channel) {
-  DCHECK(context_provider_);
-  const auto& caps = context_provider_->ContextCapabilities();
+  DCHECK(raster_context_provider_ || context_provider_);
+  const auto& caps = raster_context_provider_
+                         ? raster_context_provider_->ContextCapabilities()
+                         : context_provider_->ContextCapabilities();
   if (caps.disable_one_component_textures)
     return viz::RGBA_8888;
   if (bits_per_channel <= 8)
@@ -684,7 +704,8 @@
   } else {
     all_resources_.push_back(std::make_unique<HardwarePlaneResource>(
         plane_resource_id, plane_size, format, color_space,
-        use_gpu_memory_buffer_resources_, context_provider_));
+        use_gpu_memory_buffer_resources_, context_provider_,
+        raster_context_provider_));
   }
   return all_resources_.back().get();
 }
@@ -711,7 +732,8 @@
   DCHECK_EQ(hardware_resource->texture_target(),
             static_cast<GLenum>(GL_TEXTURE_2D));
 
-  gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL();
+  auto* gl = raster_context_provider_ ? raster_context_provider_->ContextGL()
+                                      : context_provider_->ContextGL();
 
   gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
   // TODO(piman): convert to CreateAndTexStorage2DSharedImageCHROMIUM once
@@ -746,7 +768,7 @@
     scoped_refptr<VideoFrame> video_frame) {
   TRACE_EVENT0("cc", "VideoResourceUpdater::CreateForHardwarePlanes");
   DCHECK(video_frame->HasTextures());
-  if (!context_provider_)
+  if (!context_provider_ && !raster_context_provider_)
     return VideoFrameExternalResources();
 
   VideoFrameExternalResources external_resources;
@@ -944,7 +966,9 @@
             video_frame.get(), upload_pixels_.get(), bytes_per_row);
 
         // Copy pixels into texture.
-        auto* gl = context_provider_->ContextGL();
+        auto* gl = raster_context_provider_
+                       ? raster_context_provider_->ContextGL()
+                       : context_provider_->ContextGL();
 
         const gfx::Size& plane_size = hardware_resource->resource_size();
         {
@@ -972,7 +996,10 @@
       HardwarePlaneResource* hardware_resource = plane_resource->AsHardware();
       external_resources.type = VideoFrameResourceType::RGBA;
       gpu::SyncToken sync_token;
-      GenerateCompositorSyncToken(context_provider_->ContextGL(), &sync_token);
+      auto* gl = raster_context_provider_
+                     ? raster_context_provider_->ContextGL()
+                     : context_provider_->ContextGL();
+      GenerateCompositorSyncToken(gl, &sync_token);
       transferable_resource = viz::TransferableResource::MakeGLOverlay(
           hardware_resource->mailbox(), GL_LINEAR,
           hardware_resource->texture_target(), sync_token,
@@ -1103,7 +1130,8 @@
 
     // Copy pixels into texture. TexSubImage2D() is applicable because
     // |yuv_resource_format| is LUMINANCE_F16, R16_EXT, LUMINANCE_8 or RED_8.
-    auto* gl = context_provider_->ContextGL();
+    auto* gl = raster_context_provider_ ? raster_context_provider_->ContextGL()
+                                        : context_provider_->ContextGL();
     DCHECK(GLSupportsFormat(plane_resource_format));
     {
       HardwarePlaneResource::ScopedTexture scope(gl, plane_resource);
@@ -1120,7 +1148,9 @@
 
   // Set the sync token otherwise resource is assumed to be synchronized.
   gpu::SyncToken sync_token;
-  GenerateCompositorSyncToken(context_provider_->ContextGL(), &sync_token);
+  auto* gl = raster_context_provider_ ? raster_context_provider_->ContextGL()
+                                      : context_provider_->ContextGL();
+  GenerateCompositorSyncToken(gl, &sync_token);
 
   for (size_t i = 0; i < plane_resources.size(); ++i) {
     HardwarePlaneResource* plane_resource = plane_resources[i]->AsHardware();
@@ -1149,7 +1179,9 @@
     return;
 
   // The video frame will insert a wait on the previous release sync token.
-  SyncTokenClientImpl client(context_provider_->ContextGL(), sync_token);
+  auto* gl = raster_context_provider_ ? raster_context_provider_->ContextGL()
+                                      : context_provider_->ContextGL();
+  SyncTokenClientImpl client(gl, sync_token);
   video_frame->UpdateReleaseSyncToken(&client);
 }
 
@@ -1166,8 +1198,9 @@
     return;
 
   if (context_provider_ && sync_token.HasData()) {
-    context_provider_->ContextGL()->WaitSyncTokenCHROMIUM(
-        sync_token.GetConstData());
+    auto* gl = raster_context_provider_ ? raster_context_provider_->ContextGL()
+                                        : context_provider_->ContextGL();
+    gl->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
   }
 
   if (lost_resource) {
diff --git a/media/renderers/video_resource_updater.h b/media/renderers/video_resource_updater.h
index 795a42f..39c3ed8 100644
--- a/media/renderers/video_resource_updater.h
+++ b/media/renderers/video_resource_updater.h
@@ -33,6 +33,7 @@
 namespace viz {
 class ClientResourceProvider;
 class ContextProvider;
+class RasterContextProvider;
 class RenderPass;
 class SharedBitmapReporter;
 }  // namespace viz
@@ -79,6 +80,7 @@
   // compositing |shared_bitmap_reporter| should be provided. If there is a
   // non-null |context_provider| we assume GPU compositing.
   VideoResourceUpdater(viz::ContextProvider* context_provider,
+                       viz::RasterContextProvider* raster_context_provider,
                        viz::SharedBitmapReporter* shared_bitmap_reporter,
                        viz::ClientResourceProvider* resource_provider,
                        bool use_stream_video_draw_quad,
@@ -131,7 +133,9 @@
     gfx::Size size_in_pixels;
   };
 
-  bool software_compositor() const { return context_provider_ == nullptr; }
+  bool software_compositor() const {
+    return context_provider_ == nullptr && raster_context_provider_ == nullptr;
+  }
 
   // Obtain a resource of the right format by either recycling an
   // unreferenced but appropriately formatted resource, or by
@@ -183,6 +187,7 @@
                     base::trace_event::ProcessMemoryDump* pmd) override;
 
   viz::ContextProvider* const context_provider_;
+  viz::RasterContextProvider* const raster_context_provider_;
   viz::SharedBitmapReporter* const shared_bitmap_reporter_;
   viz::ClientResourceProvider* const resource_provider_;
   const bool use_stream_video_draw_quad_;
diff --git a/media/renderers/video_resource_updater_unittest.cc b/media/renderers/video_resource_updater_unittest.cc
index ff8b426..fb39b4d 100644
--- a/media/renderers/video_resource_updater_unittest.cc
+++ b/media/renderers/video_resource_updater_unittest.cc
@@ -86,14 +86,16 @@
   std::unique_ptr<VideoResourceUpdater> CreateUpdaterForHardware(
       bool use_stream_video_draw_quad = false) {
     return std::make_unique<VideoResourceUpdater>(
-        context_provider_.get(), nullptr, resource_provider_.get(),
-        use_stream_video_draw_quad, /*use_gpu_memory_buffer_resources=*/false,
+        context_provider_.get(), /*raster_context_provider=*/nullptr, nullptr,
+        resource_provider_.get(), use_stream_video_draw_quad,
+        /*use_gpu_memory_buffer_resources=*/false,
         /*use_r16_texture=*/use_r16_texture_, /*max_resource_size=*/10000);
   }
 
   std::unique_ptr<VideoResourceUpdater> CreateUpdaterForSoftware() {
     return std::make_unique<VideoResourceUpdater>(
-        nullptr, &shared_bitmap_reporter_, resource_provider_.get(),
+        /*context_provider=*/nullptr, /*raster_context_provider=*/nullptr,
+        &shared_bitmap_reporter_, resource_provider_.get(),
         /*use_stream_video_draw_quad=*/false,
         /*use_gpu_memory_buffer_resources=*/false,
         /*use_r16_texture=*/false,
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index 8e1afa8..d6b1d9d 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -3620,7 +3620,7 @@
   if (!annot)
     return false;
 
-  int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get());
+  int flags = FPDFAnnot_GetFormFieldFlags(form(), page, annot.get());
   return CheckIfEditableFormTextArea(flags, form_type);
 }
 
diff --git a/remoting/base/oauth_helper.cc b/remoting/base/oauth_helper.cc
index 0e83109..62f4066f 100644
--- a/remoting/base/oauth_helper.cc
+++ b/remoting/base/oauth_helper.cc
@@ -29,11 +29,6 @@
          "https://www.googleapis.com/auth/userinfo.email ";
 }
 
-std::string GetDefaultOauthRedirectUrl() {
-  return "https://chromoting-oauth.talkgadget.google.com/talkgadget/oauth/"
-         "chrome-remote-desktop/rel/kgngmbheleoaphbjbaiobfdepmghbfah";
-}
-
 std::string GetOauthStartUrl(const std::string& redirect_url) {
   return base::StringPrintf(
       "https://accounts.google.com/o/oauth2/auth"
diff --git a/remoting/base/oauth_helper.h b/remoting/base/oauth_helper.h
index c0d5ec7..c659e1a 100644
--- a/remoting/base/oauth_helper.h
+++ b/remoting/base/oauth_helper.h
@@ -12,9 +12,6 @@
 // Gets the OAuth scope of the host's refresh token.
 std::string GetOauthScope();
 
-// Gets the default redirect URL for the OAuth dance.
-std::string GetDefaultOauthRedirectUrl();
-
 // Gets a URL at which the OAuth dance starts.
 std::string GetOauthStartUrl(const std::string& redirect_url);
 
diff --git a/remoting/base/oauth_token_getter_impl.cc b/remoting/base/oauth_token_getter_impl.cc
index 9c8e3ed..f095ccb 100644
--- a/remoting/base/oauth_token_getter_impl.cc
+++ b/remoting/base/oauth_token_getter_impl.cc
@@ -230,16 +230,10 @@
           ? google_apis::CLIENT_REMOTING_HOST
           : google_apis::CLIENT_REMOTING;
 
-  std::string redirect_uri;
-  if (intermediate_credentials_->oauth_redirect_uri.empty()) {
-    if (intermediate_credentials_->is_service_account) {
-      redirect_uri = "oob";
-    } else {
-      redirect_uri = GetDefaultOauthRedirectUrl();
-    }
-  } else {
-    redirect_uri = intermediate_credentials_->oauth_redirect_uri;
-  }
+  // For the case of fetching an OAuth token from a one-time-use code, the
+  // caller should provide a redirect URI.
+  std::string redirect_uri = intermediate_credentials_->oauth_redirect_uri;
+  DCHECK(!redirect_uri.empty());
 
   gaia::OAuthClientInfo client_info = {
       google_apis::GetOAuth2ClientID(oauth2_client),
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index d862c70..05e37e3 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -669,6 +669,8 @@
 // Test that the user signing in results in firing of the IdentityManager
 // observer callback and the IdentityManager's state being updated.
 TEST_F(IdentityManagerTest, PrimaryAccountInfoAfterSignin) {
+  signin_manager()->ForceSignOut();
+
   base::RunLoop run_loop;
   identity_manager_observer()->set_on_primary_account_set_callback(
       run_loop.QuitClosure());
@@ -694,6 +696,7 @@
 // Test that the user signing out results in firing of the IdentityManager
 // observer callback and the IdentityManager's state being updated.
 TEST_F(IdentityManagerTest, PrimaryAccountInfoAfterSigninAndSignout) {
+  signin_manager()->ForceSignOut();
   // First ensure that the user is signed in from the POV of the
   // IdentityManager.
   base::RunLoop run_loop;
@@ -729,6 +732,7 @@
 // Test that the primary account's ID remains tracked by the IdentityManager
 // after signing in even after having removed the account without signing out.
 TEST_F(IdentityManagerTest, PrimaryAccountInfoAfterSigninAndAccountRemoval) {
+  signin_manager()->ForceSignOut();
   // First ensure that the user is signed in from the POV of the
   // IdentityManager.
   base::RunLoop run_loop;
diff --git a/services/identity/public/cpp/identity_test_utils.cc b/services/identity/public/cpp/identity_test_utils.cc
index 8ea39df..41d88a03 100644
--- a/services/identity/public/cpp/identity_test_utils.cc
+++ b/services/identity/public/cpp/identity_test_utils.cc
@@ -176,11 +176,21 @@
 CoreAccountInfo SetPrimaryAccount(IdentityManager* identity_manager,
                                   const std::string& email) {
   DCHECK(!identity_manager->HasPrimaryAccount());
-
   SigninManagerBase* signin_manager = identity_manager->GetSigninManager();
   DCHECK(!signin_manager->IsAuthenticated());
 
-  std::string gaia_id = GetTestGaiaIdForEmail(email);
+  AccountTrackerService* account_tracker_service =
+      identity_manager->GetAccountTrackerService();
+  AccountInfo account_info =
+      account_tracker_service->FindAccountInfoByEmail(email);
+  if (account_info.account_id.empty()) {
+    std::string gaia_id = GetTestGaiaIdForEmail(email);
+    account_tracker_service->SeedAccountInfo(gaia_id, email);
+    account_info = account_tracker_service->FindAccountInfoByEmail(email);
+  }
+
+  std::string gaia_id = account_info.gaia;
+  DCHECK(!gaia_id.empty());
 
 #if defined(OS_CHROMEOS)
   // ChromeOS has no real notion of signin, so just plumb the information
@@ -189,26 +199,9 @@
   identity_manager->SetPrimaryAccountSynchronously(gaia_id, email,
                                                    /*refresh_token=*/"");
 #else
-
-  base::RunLoop run_loop;
-  OneShotIdentityManagerObserver signin_observer(
-      identity_manager, run_loop.QuitClosure(),
-      IdentityManagerEvent::PRIMARY_ACCOUNT_SET);
-
   SigninManager* real_signin_manager =
       SigninManager::FromSigninManagerBase(signin_manager);
-  // Note: It's important to pass base::DoNothing() (rather than a null
-  // callback) to make this work with both SigninManager and FakeSigninManager.
-  // If we would pass a null callback, then SigninManager would call
-  // CompletePendingSignin directly, but FakeSigninManager never does that.
-  // Note: pass an empty string as the refresh token so that no refresh token is
-  // set.
-  real_signin_manager->StartSignInWithRefreshToken(
-      /*refresh_token=*/"", gaia_id, email,
-      /*oauth_fetched_callback=*/base::DoNothing());
-  real_signin_manager->CompletePendingSignin();
-
-  run_loop.Run();
+  real_signin_manager->OnExternalSigninCompleted(email);
 #endif
 
   DCHECK(signin_manager->IsAuthenticated());
@@ -303,6 +296,10 @@
   DCHECK(account_tracker_service);
   DCHECK(account_tracker_service->FindAccountInfoByEmail(email).IsEmpty());
 
+  // Wait until tokens are loaded, otherwise the account will be removed as soon
+  // as tokens finish loading.
+  WaitForLoadCredentialsToComplete(identity_manager);
+
   std::string gaia_id = GetTestGaiaIdForEmail(email);
   account_tracker_service->SeedAccountInfo(gaia_id, email);
 
diff --git a/services/identity/public/cpp/primary_account_mutator.h b/services/identity/public/cpp/primary_account_mutator.h
index c2ef177..322a27b 100644
--- a/services/identity/public/cpp/primary_account_mutator.h
+++ b/services/identity/public/cpp/primary_account_mutator.h
@@ -10,8 +10,6 @@
 #include "base/callback_forward.h"
 #include "components/signin/core/browser/signin_metrics.h"
 
-struct AccountInfo;
-
 namespace identity {
 
 // PrimaryAccountMutator is the interface to set and clear the primary account
@@ -72,37 +70,9 @@
   // and should not be used when writing new code. They will be removed when the
   // old sign-in workflow has been turned down.
 
-  // Attempts to sign-in user with a given refresh token and account. If it is
-  // defined, |callback| should invoke either ClearPrimaryAccount() or
-  // LegacyCompletePendingPrimaryAccountSignin() to either cancel or continue
-  // the in progress sign-in (legacy, pre-DICE workflow).
-  virtual void LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-      const std::string& refresh_token,
-      const std::string& gaia_id,
-      const std::string& username,
-      base::OnceCallback<void(const std::string&)> callback) = 0;
-
-  // Complete the in-process sign-in (legacy, pre-DICE workflow).
-  virtual void LegacyCompletePendingPrimaryAccountSignin() = 0;
-
   // If applicable, merges the signed-in account into the cookie jar (legacy,
   // pre-DICE workflow).
   virtual void LegacyMergeSigninCredentialIntoCookieJar() = 0;
-
-  // Returns true if there is a sign-in in progress (legacy, pre-DICE workflow).
-  virtual bool LegacyIsPrimaryAccountAuthInProgress() const = 0;
-
-  // If an authentication is in progress, returns the AccountInfo for the
-  // account being authenticated. Returns an empty AccountInfo if no auth is
-  // in progress (legacy, pre-DICE workflow).
-  virtual AccountInfo LegacyPrimaryAccountForAuthInProgress() const = 0;
-
-  // Copy auth credentials from the other PrimaryAccountMutator to this one.
-  // Used when creating a new profile during the sign-in process to transfer
-  // the in-progress credential information to the new profile (legacy, pre-
-  // DICE workflow).
-  virtual void LegacyCopyCredentialsFrom(
-      const PrimaryAccountMutator& source) = 0;
 };
 
 }  // namespace identity
diff --git a/services/identity/public/cpp/primary_account_mutator_impl.cc b/services/identity/public/cpp/primary_account_mutator_impl.cc
index db9edf3..de9412b1 100644
--- a/services/identity/public/cpp/primary_account_mutator_impl.cc
+++ b/services/identity/public/cpp/primary_account_mutator_impl.cc
@@ -43,10 +43,7 @@
     ClearAccountsAction action,
     signin_metrics::ProfileSignout source_metric,
     signin_metrics::SignoutDelete delete_metric) {
-  // Check if and auth process is ongoing before reporting failure to support
-  // the legacy workflow of cancelling it by clearing the primary account.
-  if (!signin_manager_->IsAuthenticated() &&
-      !LegacyIsPrimaryAccountAuthInProgress())
+  if (!signin_manager_->IsAuthenticated())
     return false;
 
   switch (action) {
@@ -78,45 +75,8 @@
   NOTIMPLEMENTED();
 }
 
-void PrimaryAccountMutatorImpl::
-    LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-        const std::string& refresh_token,
-        const std::string& gaia_id,
-        const std::string& username,
-        base::OnceCallback<void(const std::string&)> callback) {
-  signin_manager_->StartSignInWithRefreshToken(refresh_token, gaia_id, username,
-                                               std::move(callback));
-}
-
-void PrimaryAccountMutatorImpl::LegacyCompletePendingPrimaryAccountSignin() {
-  signin_manager_->CompletePendingSignin();
-}
-
 void PrimaryAccountMutatorImpl::LegacyMergeSigninCredentialIntoCookieJar() {
   signin_manager_->MergeSigninCredentialIntoCookieJar();
 }
 
-bool PrimaryAccountMutatorImpl::LegacyIsPrimaryAccountAuthInProgress() const {
-  return signin_manager_->AuthInProgress();
-}
-
-AccountInfo PrimaryAccountMutatorImpl::LegacyPrimaryAccountForAuthInProgress()
-    const {
-  if (!LegacyIsPrimaryAccountAuthInProgress())
-    return AccountInfo{};
-
-  AccountInfo account_info;
-  account_info.account_id = signin_manager_->GetAccountIdForAuthInProgress();
-  account_info.gaia = signin_manager_->GetGaiaIdForAuthInProgress();
-  account_info.email = signin_manager_->GetUsernameForAuthInProgress();
-
-  return account_info;
-}
-
-void PrimaryAccountMutatorImpl::LegacyCopyCredentialsFrom(
-    const PrimaryAccountMutator& source) {
-  signin_manager_->CopyCredentialsFrom(
-      *static_cast<const PrimaryAccountMutatorImpl&>(source).signin_manager_);
-}
-
 }  // namespace identity
diff --git a/services/identity/public/cpp/primary_account_mutator_impl.h b/services/identity/public/cpp/primary_account_mutator_impl.h
index 54de8cc..7807fa1 100644
--- a/services/identity/public/cpp/primary_account_mutator_impl.h
+++ b/services/identity/public/cpp/primary_account_mutator_impl.h
@@ -29,16 +29,7 @@
   bool IsSettingPrimaryAccountAllowed() const override;
   void SetSettingPrimaryAccountAllowed(bool allowed) override;
   void SetAllowedPrimaryAccountPattern(const std::string& pattern) override;
-  void LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-      const std::string& refresh_token,
-      const std::string& gaia_id,
-      const std::string& username,
-      base::OnceCallback<void(const std::string&)> callback) override;
-  void LegacyCompletePendingPrimaryAccountSignin() override;
   void LegacyMergeSigninCredentialIntoCookieJar() override;
-  bool LegacyIsPrimaryAccountAuthInProgress() const override;
-  AccountInfo LegacyPrimaryAccountForAuthInProgress() const override;
-  void LegacyCopyCredentialsFrom(const PrimaryAccountMutator& source) override;
 
  private:
   // Pointers to the services used by the PrimaryAccountMutatorImpl. They
diff --git a/services/identity/public/cpp/primary_account_mutator_unittest.cc b/services/identity/public/cpp/primary_account_mutator_unittest.cc
index 81ed22f..0937dda 100644
--- a/services/identity/public/cpp/primary_account_mutator_unittest.cc
+++ b/services/identity/public/cpp/primary_account_mutator_unittest.cc
@@ -20,7 +20,6 @@
 const char kUnknownAccountId[] = "{unknown account id}";
 const char kPrimaryAccountEmail[] = "primary.account@example.com";
 const char kAnotherAccountEmail[] = "another.account@example.com";
-const char kRefreshToken[] = "refresh_token";
 
 // All account consistency methods that are tested by those unit tests when
 // testing ClearPrimaryAccount method.
@@ -520,426 +519,3 @@
       identity::PrimaryAccountMutator::ClearAccountsAction::kDefault,
       RemoveAccountExpectation::kRemovePrimary, AuthExpectation::kAuthError);
 }
-
-// Test that ClearPrimaryAccount(...) with authentication in progress notifies
-// Observers that sign-in is canceled and does not remove any tokens.
-TEST_F(PrimaryAccountMutatorTest, ClearPrimaryAccount_AuthInProgress) {
-  base::test::ScopedTaskEnvironment task_environment;
-  identity::IdentityTestEnvironment environment;
-
-  identity::IdentityManager* identity_manager = environment.identity_manager();
-  identity::PrimaryAccountMutator* primary_account_mutator =
-      identity_manager->GetPrimaryAccountMutator();
-
-  // Abort the test if the current platform does not support mutation of the
-  // primary account (the returned PrimaryAccountMutator* will be null).
-  if (!primary_account_mutator)
-    return;
-
-  AccountInfo account_info =
-      environment.MakeAccountAvailable(kPrimaryAccountEmail);
-  EXPECT_TRUE(
-      identity_manager->HasAccountWithRefreshToken(account_info.account_id));
-
-  // Account available in the tracker service but still not authenticated means
-  // there's neither a primary account nor an authentication process ongoing.
-  EXPECT_FALSE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  // Add a secondary account to verify that its refresh token survives the
-  // call to ClearPrimaryAccount(...) below.
-  AccountInfo secondary_account_info =
-      MakeAccountAvailable(identity_manager, kAnotherAccountEmail);
-  EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(
-      secondary_account_info.account_id));
-
-  // Start a signin process for the account we just made available and check
-  // that it's reported to be in progress before the process is completed.
-  base::RunLoop run_loop;
-  std::string signed_account_refresh_token;
-  primary_account_mutator->LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-      kRefreshToken, account_info.gaia, account_info.email,
-      base::BindOnce(
-          [](std::string* out_refresh_token, const std::string& refresh_token) {
-            *out_refresh_token = refresh_token;
-          },
-          base::Unretained(&signed_account_refresh_token)));
-
-  EXPECT_TRUE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  AccountInfo auth_in_progress_account_info =
-      primary_account_mutator->LegacyPrimaryAccountForAuthInProgress();
-
-  // No primary account to "clear", so no callback.
-  PrimaryAccountClearedCallback primary_account_cleared_callback =
-      base::BindRepeating([](const CoreAccountInfo&) {
-        FAIL() << "no primary account is set, so nothing should be cleared";
-      });
-
-  // Capture the authentication error and make sure we exit the run loop.
-  GoogleServiceAuthError captured_auth_error;
-  PrimaryAccountSigninFailedCallback primary_account_signin_failed_callback =
-      base::BindRepeating(
-          [](base::RepeatingClosure quit_closure,
-             GoogleServiceAuthError* out_auth_error,
-             const GoogleServiceAuthError& auth_error) {
-            *out_auth_error = auth_error;
-            quit_closure.Run();
-          },
-          run_loop.QuitClosure(), &captured_auth_error);
-
-  // Observer should not be notified of any token removals.
-  RefreshTokenRemovedCallback refresh_token_removed_callback =
-      base::BindRepeating([](const std::string& removed_account) {
-        FAIL() << "no token removal should happen";
-      });
-
-  ClearPrimaryAccountTestObserver scoped_observer(
-      identity_manager, primary_account_cleared_callback,
-      primary_account_signin_failed_callback, refresh_token_removed_callback);
-
-  EXPECT_TRUE(primary_account_mutator->ClearPrimaryAccount(
-      identity::PrimaryAccountMutator::ClearAccountsAction::kRemoveAll,
-      signin_metrics::SIGNOUT_TEST,
-      signin_metrics::SignoutDelete::IGNORE_METRIC));
-  run_loop.Run();
-
-  // Verify in-progress authentication was canceled.
-  EXPECT_EQ(captured_auth_error.state(),
-            GoogleServiceAuthError::State::REQUEST_CANCELED);
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  // We didn't have a primary account to start with, we shouldn't have one now
-  // either.
-  EXPECT_FALSE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(identity_manager->HasPrimaryAccountWithRefreshToken());
-
-  // Secondary account token still exists.
-  EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(
-      secondary_account_info.account_id));
-}
-
-// Checks that checking whether an authentication process is in progress reports
-// true before starting and after successfully completing the signin process.
-TEST_F(PrimaryAccountMutatorTest, SigninWithRefreshToken) {
-  base::test::ScopedTaskEnvironment task_environment;
-  identity::IdentityTestEnvironment environment;
-
-  identity::IdentityManager* identity_manager = environment.identity_manager();
-  identity::PrimaryAccountMutator* primary_account_mutator =
-      identity_manager->GetPrimaryAccountMutator();
-
-  // Abort the test if the current platform does not support mutation of the
-  // primary account (the returned PrimaryAccountMutator* will be null).
-  if (!primary_account_mutator)
-    return;
-
-  // We'll sign in the same account twice, using SetPrimaryAccount() and
-  // LegacyStartSigninWithRefreshTokenForPrimaryAccount(), and check that
-  // in both cases the end result is the same.
-  AccountInfo account_info =
-      environment.MakeAccountAvailable(kPrimaryAccountEmail);
-
-  EXPECT_FALSE(identity_manager->HasPrimaryAccount());
-  EXPECT_TRUE(
-      primary_account_mutator->SetPrimaryAccount(account_info.account_id));
-
-  const std::string primary_account_id_1 =
-      identity_manager->GetPrimaryAccountId();
-
-  EXPECT_TRUE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_id_1.empty());
-  EXPECT_EQ(primary_account_id_1, account_info.account_id);
-
-  EXPECT_TRUE(
-      identity_manager->HasAccountWithRefreshToken(primary_account_id_1));
-  EXPECT_FALSE(identity_manager->GetPrimaryAccountInfo().email.empty());
-
-  // Sign out the account to try to sign in again with the other mechanism, but
-  // using kKeepAll so we can use the same account we made available before.
-  EXPECT_TRUE(primary_account_mutator->ClearPrimaryAccount(
-      identity::PrimaryAccountMutator::ClearAccountsAction::kKeepAll,
-      signin_metrics::SIGNOUT_TEST,
-      signin_metrics::SignoutDelete::IGNORE_METRIC));
-
-  EXPECT_FALSE(identity_manager->HasPrimaryAccount());
-  EXPECT_TRUE(identity_manager->GetPrimaryAccountId().empty());
-  EXPECT_TRUE(identity_manager->GetPrimaryAccountInfo().email.empty());
-
-  // Start a signin process for the account and complete it right away to check
-  // whether we end up with a similar result than with SetPrimaryAccount().
-  std::string signed_account_refresh_token;
-  primary_account_mutator->LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-      kRefreshToken, account_info.gaia, account_info.email,
-      base::BindOnce(
-          [](std::string* out_refresh_token, const std::string& refresh_token) {
-            *out_refresh_token = refresh_token;
-          },
-          base::Unretained(&signed_account_refresh_token)));
-
-  primary_account_mutator->LegacyCompletePendingPrimaryAccountSignin();
-
-  // The refresh token assigned to the account should match the one passed.
-  EXPECT_EQ(signed_account_refresh_token, kRefreshToken);
-
-  EXPECT_TRUE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_id_1.empty());
-  EXPECT_EQ(primary_account_id_1, account_info.account_id);
-
-  EXPECT_TRUE(
-      identity_manager->HasAccountWithRefreshToken(primary_account_id_1));
-  EXPECT_FALSE(identity_manager->GetPrimaryAccountInfo().email.empty());
-
-  const std::string primary_account_id_2 =
-      identity_manager->GetPrimaryAccountId();
-
-  EXPECT_TRUE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_id_2.empty());
-  EXPECT_EQ(primary_account_id_2, account_info.account_id);
-
-  EXPECT_TRUE(
-      identity_manager->HasAccountWithRefreshToken(primary_account_id_2));
-  EXPECT_FALSE(identity_manager->GetPrimaryAccountInfo().email.empty());
-
-  // Information retrieved via the IdentityManager now for the current primary
-  // account should match the data of the account we signed in before.
-  EXPECT_EQ(primary_account_id_1, primary_account_id_2);
-}
-
-// Checks that checking whether an authentication process is in progress reports
-// true before starting and after successfully completing the signin process.
-TEST_F(PrimaryAccountMutatorTest, AuthInProgress_SigninCompleted) {
-  base::test::ScopedTaskEnvironment task_environment;
-  identity::IdentityTestEnvironment environment;
-
-  identity::IdentityManager* identity_manager = environment.identity_manager();
-  identity::PrimaryAccountMutator* primary_account_mutator =
-      identity_manager->GetPrimaryAccountMutator();
-
-  // Abort the test if the current platform does not support mutation of the
-  // primary account (the returned PrimaryAccountMutator* will be null).
-  if (!primary_account_mutator)
-    return;
-
-  AccountInfo account_info =
-      environment.MakeAccountAvailable(kPrimaryAccountEmail);
-
-  // Account available in the tracker service but still not authenticated means
-  // there's neither a primary account nor an authentication process ongoing.
-  EXPECT_FALSE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  // Start a signin process for the account we just made available and check
-  // that it's reported to be in progress before the process is completed.
-  base::RunLoop run_loop;
-  std::string signed_account_refresh_token;
-  primary_account_mutator->LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-      kRefreshToken, account_info.gaia, account_info.email,
-      base::BindOnce(
-          [](std::string* out_refresh_token, const std::string& refresh_token) {
-            *out_refresh_token = refresh_token;
-          },
-          base::Unretained(&signed_account_refresh_token)));
-
-  EXPECT_TRUE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  AccountInfo auth_in_progress_account_info =
-      primary_account_mutator->LegacyPrimaryAccountForAuthInProgress();
-
-  // The data from the AccountInfo related to the authentication process still
-  // in progress should match the data of the account being signed in.
-  EXPECT_EQ(auth_in_progress_account_info.account_id, account_info.account_id);
-  EXPECT_EQ(auth_in_progress_account_info.gaia, account_info.gaia);
-  EXPECT_EQ(auth_in_progress_account_info.email, account_info.email);
-
-  // Finally, complete the signin process so that we can do further checks.
-  primary_account_mutator->LegacyCompletePendingPrimaryAccountSignin();
-  run_loop.RunUntilIdle();
-
-  // The refresh token assigned to the account should match the one passed.
-  EXPECT_EQ(signed_account_refresh_token, kRefreshToken);
-
-  // An account has been authenticated now, so there should be a primary account
-  // authenticated and no authentication process reported as in progress now.
-  EXPECT_TRUE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  // Information retrieved via the IdentityManager now for the current primary
-  // account should match the data of the account being signed in.
-  EXPECT_EQ(identity_manager->GetPrimaryAccountId(), account_info.account_id);
-  AccountInfo identity_manager_account_info =
-      identity_manager->GetPrimaryAccountInfo();
-  EXPECT_EQ(identity_manager_account_info.account_id, account_info.account_id);
-  EXPECT_EQ(identity_manager_account_info.gaia, account_info.gaia);
-  EXPECT_EQ(identity_manager_account_info.email, account_info.email);
-}
-
-// Checks that checking whether an authentication process is in progress reports
-// true before starting and after cancelling and ongoing signin process.
-TEST_F(PrimaryAccountMutatorTest, AuthInProgress_SigninCancelled) {
-  base::test::ScopedTaskEnvironment task_environment;
-  identity::IdentityTestEnvironment environment;
-
-  identity::IdentityManager* identity_manager = environment.identity_manager();
-  identity::PrimaryAccountMutator* primary_account_mutator =
-      identity_manager->GetPrimaryAccountMutator();
-
-  // Abort the test if the current platform does not support mutation of the
-  // primary account (the returned PrimaryAccountMutator* will be null).
-  if (!primary_account_mutator)
-    return;
-
-  AccountInfo account_info =
-      environment.MakeAccountAvailable(kPrimaryAccountEmail);
-
-  // Account available in the tracker service but still not authenticated means
-  // there's neither a primary account nor an authentication process ongoing.
-  EXPECT_FALSE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  // Start a signin process for the account we just made available and check
-  // that it's reported to be in progress before the process is completed.
-  base::RunLoop run_loop;
-  std::string signed_account_refresh_token;
-  primary_account_mutator->LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-      kRefreshToken, account_info.gaia, account_info.email,
-      base::BindOnce(
-          [](std::string* out_refresh_token, const std::string& refresh_token) {
-            *out_refresh_token = refresh_token;
-          },
-          base::Unretained(&signed_account_refresh_token)));
-
-  EXPECT_TRUE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  AccountInfo auth_in_progress_account_info =
-      primary_account_mutator->LegacyPrimaryAccountForAuthInProgress();
-
-  // The data from the AccountInfo related to the authentication process still
-  // in progress should match the data of the account being signed in.
-  EXPECT_EQ(auth_in_progress_account_info.account_id, account_info.account_id);
-  EXPECT_EQ(auth_in_progress_account_info.gaia, account_info.gaia);
-  EXPECT_EQ(auth_in_progress_account_info.email, account_info.email);
-
-  // Now cancel the signin process (by attempting to clear the primary account
-  // we were trying to sign in so far), so that we can do further checks.
-  EXPECT_TRUE(primary_account_mutator->ClearPrimaryAccount(
-      identity::PrimaryAccountMutator::ClearAccountsAction::kRemoveAll,
-      signin_metrics::SIGNOUT_TEST,
-      signin_metrics::SignoutDelete::IGNORE_METRIC));
-  run_loop.RunUntilIdle();
-
-  // The refresh token assigned to the account should match the one passed.
-  EXPECT_EQ(signed_account_refresh_token, kRefreshToken);
-
-  // The request has been cancelled, so there should not be a primary account
-  // signed in, the refresh we just received should not be valid for the primary
-  // account (even if it's been fetched and stored for the account already) and
-  // no authentication process reported as in progress now.
-  EXPECT_FALSE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(identity_manager->HasPrimaryAccountWithRefreshToken());
-  EXPECT_TRUE(
-      identity_manager->HasAccountWithRefreshToken(account_info.account_id));
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  // Information retrieved via the IdentityManager confirms the cancelation.
-  EXPECT_EQ(identity_manager->GetPrimaryAccountId(), std::string());
-  EXPECT_TRUE(identity_manager->GetPrimaryAccountInfo().IsEmpty());
-}
-
-// Checks that copying the credentials from another PrimaryAccountMutator works.
-TEST_F(PrimaryAccountMutatorTest, CopyCredentialsFrom) {
-  base::test::ScopedTaskEnvironment task_environment;
-  identity::IdentityTestEnvironment environment;
-
-  identity::IdentityManager* identity_manager = environment.identity_manager();
-  identity::PrimaryAccountMutator* primary_account_mutator =
-      identity_manager->GetPrimaryAccountMutator();
-
-  // Abort the test if the current platform does not support mutation of the
-  // primary account (the returned PrimaryAccountMutator* will be null).
-  if (!primary_account_mutator)
-    return;
-
-  // We will need another PrimaryAccountMutator to copy the credentials from the
-  // one used previously and check that they match later on.
-  identity::IdentityTestEnvironment other_environment;
-  identity::IdentityManager* other_identity_manager =
-      other_environment.identity_manager();
-  identity::PrimaryAccountMutator* other_primary_account_mutator =
-      other_identity_manager->GetPrimaryAccountMutator();
-
-  AccountInfo account_info =
-      environment.MakeAccountAvailable(kPrimaryAccountEmail);
-
-  // Start a signin process for the account we just made available so that we
-  // can check whether the credentials copied to another PrimaryAccountMutator.
-  base::RunLoop run_loop;
-  std::string signed_account_refresh_token;
-  primary_account_mutator->LegacyStartSigninWithRefreshTokenForPrimaryAccount(
-      kRefreshToken, account_info.gaia, account_info.email,
-      base::BindOnce(
-          [](std::string* out_refresh_token, const std::string& refresh_token) {
-            *out_refresh_token = refresh_token;
-          },
-          base::Unretained(&signed_account_refresh_token)));
-  run_loop.RunUntilIdle();
-
-  // The refresh token assigned to the account should match the one passed.
-  EXPECT_EQ(signed_account_refresh_token, kRefreshToken);
-
-  // This is a good moment to copy the credentials from one mutator to the other
-  // since internal transient data hold by the SigninManager in this state will
-  // be non-empty while the authentication process is ongoing (e.g. possibly
-  // invalid account ID, Gaia ID and email), allowing us to compare values.
-  base::RunLoop run_loop2;
-  other_primary_account_mutator->LegacyCopyCredentialsFrom(
-      *primary_account_mutator);
-  run_loop2.RunUntilIdle();
-
-  EXPECT_TRUE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-  EXPECT_TRUE(
-      other_primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  AccountInfo auth_in_progress_account_info =
-      primary_account_mutator->LegacyPrimaryAccountForAuthInProgress();
-  AccountInfo other_auth_in_progress_account_info =
-      other_primary_account_mutator->LegacyPrimaryAccountForAuthInProgress();
-
-  EXPECT_FALSE(auth_in_progress_account_info.IsEmpty());
-  EXPECT_FALSE(other_auth_in_progress_account_info.IsEmpty());
-
-  EXPECT_EQ(auth_in_progress_account_info.account_id,
-            other_auth_in_progress_account_info.account_id);
-  EXPECT_EQ(auth_in_progress_account_info.gaia,
-            other_auth_in_progress_account_info.gaia);
-  EXPECT_EQ(auth_in_progress_account_info.email,
-            other_auth_in_progress_account_info.email);
-
-  // Finally, complete the signin process so that we can do further checks.
-  base::RunLoop run_loop3;
-  primary_account_mutator->LegacyCompletePendingPrimaryAccountSignin();
-  run_loop3.RunUntilIdle();
-
-  // An account has been authenticated now, so there should be a primary account
-  // authenticated and no authentication process reported as in progress now.
-  EXPECT_TRUE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  // Query again the information for each of the two different environments now
-  // that the original one has completed the authentication process and compare
-  // them one more time: they should not match as the original one is no longer
-  // in the middle of the authentication process.
-  EXPECT_TRUE(identity_manager->HasPrimaryAccount());
-  EXPECT_FALSE(other_identity_manager->HasPrimaryAccount());
-
-  EXPECT_FALSE(primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-  EXPECT_TRUE(
-      other_primary_account_mutator->LegacyIsPrimaryAccountAuthInProgress());
-
-  auth_in_progress_account_info =
-      primary_account_mutator->LegacyPrimaryAccountForAuthInProgress();
-  other_auth_in_progress_account_info =
-      other_primary_account_mutator->LegacyPrimaryAccountForAuthInProgress();
-  EXPECT_TRUE(auth_in_progress_account_info.IsEmpty());
-  EXPECT_FALSE(other_auth_in_progress_account_info.IsEmpty());
-}
diff --git a/services/image_annotation/annotator.cc b/services/image_annotation/annotator.cc
index 95b6969..9fd9d54 100644
--- a/services/image_annotation/annotator.cc
+++ b/services/image_annotation/annotator.cc
@@ -29,9 +29,9 @@
 // Returns nullopt if there is any unexpected structure to the annotations
 // message.
 base::Optional<std::string> ParseJsonOcrAnnotation(
-    const base::Value& annotations,
+    const base::Value& ocr_engine,
     const double min_ocr_confidence) {
-  const base::Value* const ocr_regions = annotations.FindKey("ocrRegions");
+  const base::Value* const ocr_regions = ocr_engine.FindKey("ocrRegions");
   // No OCR regions is valid - it just means there is no text.
   if (!ocr_regions)
     return std::string();
@@ -107,12 +107,21 @@
     if (!image_id || !image_id->is_string())
       continue;
 
-    const base::Value* const annotations = result.FindKey("annotations");
-    if (!annotations || !annotations->is_dict())
+    const base::Value* const engine_results = result.FindKey("engineResults");
+    if (!engine_results || !engine_results->is_list() ||
+        engine_results->GetList().size() != 1)
+      continue;
+
+    const base::Value& engine_result = engine_results->GetList()[0];
+    if (!engine_result.is_dict())
+      continue;
+
+    const base::Value* const ocr_engine = engine_result.FindKey("ocrEngine");
+    if (!ocr_engine || !ocr_engine->is_dict())
       continue;
 
     const base::Optional<std::string> ocr_text =
-        ParseJsonOcrAnnotation(*annotations, min_ocr_confidence);
+        ParseJsonOcrAnnotation(*ocr_engine, min_ocr_confidence);
     if (!ocr_text.has_value())
       continue;
 
@@ -199,22 +208,25 @@
                           it->second.size()),
         &base64_data);
 
+    // TODO(crbug.com/916420): accept and propagate page language info to
+    //                         improve OCR accuracy.
+    base::Value engine_params(base::Value::Type::DICTIONARY);
+    engine_params.SetKey("ocrParameters",
+                         base::Value(base::Value::Type::DICTIONARY));
+
+    base::Value engine_params_list(base::Value::Type::LIST);
+    engine_params_list.GetList().push_back(std::move(engine_params));
+
     base::Value image_request(base::Value::Type::DICTIONARY);
     image_request.SetKey("imageId", base::Value(it->first));
     image_request.SetKey("imageBytes", base::Value(std::move(base64_data)));
+    image_request.SetKey("engineParameters", std::move(engine_params_list));