diff --git a/DEPS b/DEPS
index e846dca4..39019dd 100644
--- a/DEPS
+++ b/DEPS
@@ -126,7 +126,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '249a2573052167220fdb90828a5df43bfcaa3a07',
+  'skia_revision': 'ca145bec7f3387bede9e2b5e33dd0196501534ec',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -142,7 +142,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '0a60818466f1efc7f142d6f267d113ecdc6591a5',
+  'swiftshader_revision': '8f79d388784c4870b87c3e87753d5e054af07011',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -737,7 +737,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd7810fd2d7b31fe57f8a155653d94976223d68d0',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'fd080e04f1e41186d0fe25463cf7371764d460d6',
       'condition': 'checkout_linux',
   },
 
@@ -762,7 +762,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9d686bcef57534e3474cbe93e29b65a965aef877',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9e3080f52cbc7e5c2fb42f4410fd5923780ae01c',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1275,7 +1275,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'a0f51b2e123f39c9ff12e621b0b47dd28dd64424',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '599d59237abf0dcb6b92774595316bfda84ab7f5',
+    Var('webrtc_git') + '/src.git' + '@' + '0d4869c2b9c2fdb536217209a4a88700fd121dd5',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1316,7 +1316,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f94d08d7c6f4c712ce9456425d2e1450927f82a4',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@274ad0c9456bbb4682b79566ec496c6b8a8f9e60',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
index dc410eaa..2c41a03 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
@@ -6,6 +6,7 @@
 
 import org.chromium.android_webview.AwContents.VisualStateCallback;
 import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
@@ -90,38 +91,38 @@
     }
 
     @Override
-    public void didFinishNavigation(final String url, boolean isInMainFrame, boolean isErrorPage,
-            boolean hasCommitted, boolean isSameDocument, boolean isFragmentNavigation,
-            boolean isRendererInitiated, boolean isDownload, Integer pageTransition, int errorCode,
-            String errorDescription, int httpStatusCode) {
-        if (errorCode != 0 && !isDownload) {
-            didFailLoad(isInMainFrame, errorCode, errorDescription, url);
+    public void didFinishNavigation(NavigationHandle navigation) {
+        String url = navigation.getUrl();
+        if (navigation.errorCode() != 0 && !navigation.isDownload()) {
+            didFailLoad(navigation.isInMainFrame(), navigation.errorCode(),
+                    navigation.errorDescription(), url);
         }
 
-        if (!hasCommitted) return;
+        if (!navigation.hasCommitted()) return;
 
         mCommittedNavigation = true;
 
-        if (!isInMainFrame) return;
+        if (!navigation.isInMainFrame()) return;
 
         AwContentsClient client = mAwContentsClient.get();
         if (client != null) {
             // OnPageStarted is not called for in-page navigations, which include fragment
             // navigations and navigation from history.push/replaceState.
             // Error page is handled by AwContentsClientBridge.onReceivedError.
-            if (!isSameDocument && !isErrorPage
-                    && AwFeatureList.pageStartedOnCommitEnabled(isRendererInitiated)) {
+            if (!navigation.isSameDocument() && !navigation.isErrorPage()
+                    && AwFeatureList.pageStartedOnCommitEnabled(navigation.isRendererInitiated())) {
                 client.getCallbackHelper().postOnPageStarted(url);
             }
 
-            boolean isReload = pageTransition != null
-                    && ((pageTransition & PageTransition.CORE_MASK) == PageTransition.RELOAD);
+            boolean isReload = navigation.pageTransition() != null
+                    && ((navigation.pageTransition() & PageTransition.CORE_MASK)
+                            == PageTransition.RELOAD);
             client.getCallbackHelper().postDoUpdateVisitedHistory(url, isReload);
         }
 
         // Only invoke the onPageCommitVisible callback when navigating to a different document,
         // but not when navigating to a different fragment within the same document.
-        if (!isSameDocument) {
+        if (!navigation.isSameDocument()) {
             PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                 AwContents awContents = mAwContents.get();
                 if (awContents != null) {
@@ -138,7 +139,7 @@
             });
         }
 
-        if (client != null && isFragmentNavigation) {
+        if (client != null && navigation.isFragmentNavigation()) {
             // Note fragment navigations do not have a matching onPageStarted.
             client.getCallbackHelper().postOnPageFinished(url);
         }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java
index 46c4310..4df10b52 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwWebContentsObserverTest.java
@@ -16,6 +16,7 @@
 import org.chromium.android_webview.AwContentsStatics;
 import org.chromium.android_webview.AwWebContentsObserver;
 import org.chromium.base.test.util.Feature;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
 import org.chromium.ui.base.PageTransition;
 
@@ -34,7 +35,6 @@
     private static final String EXAMPLE_URL = "http://www.example.com/";
     private static final String EXAMPLE_URL_WITH_FRAGMENT = "http://www.example.com/#anchor";
     private static final String SYNC_URL = "http://example.org/";
-    private static final String ERROR_DESCRIPTION = "description";
     private String mUnreachableWebDataUrl;
 
     @Before
@@ -96,21 +96,16 @@
         String baseUrl = null;
         boolean isInMainFrame = true;
         boolean isErrorPage = false;
-        boolean hasCommitted = true;
         boolean isSameDocument = true;
         boolean fragmentNavigation = true;
         boolean isRendererInitiated = true;
-        boolean isDownload = false;
         int errorCode = 0;
-        String errorDescription = "";
         int httpStatusCode = 200;
         callCount = onPageFinishedHelper.getCallCount();
-        mWebContentsObserver.didFinishNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage,
-                hasCommitted, !isSameDocument, !fragmentNavigation, !isRendererInitiated,
-                isDownload, PageTransition.TYPED, errorCode, errorDescription, httpStatusCode);
-        mWebContentsObserver.didFinishNavigation(EXAMPLE_URL_WITH_FRAGMENT, isInMainFrame,
-                isErrorPage, hasCommitted, isSameDocument, fragmentNavigation, isRendererInitiated,
-                isDownload, PageTransition.TYPED, errorCode, errorDescription, httpStatusCode);
+        simulateNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage, !isSameDocument,
+                !fragmentNavigation, !isRendererInitiated, PageTransition.TYPED);
+        simulateNavigation(EXAMPLE_URL_WITH_FRAGMENT, isInMainFrame, isErrorPage, isSameDocument,
+                fragmentNavigation, isRendererInitiated, PageTransition.TYPED);
         onPageFinishedHelper.waitForCallback(callCount);
         Assert.assertEquals("onPageFinished should be called for main frame fragment navigations.",
                 callCount + 1, onPageFinishedHelper.getCallCount());
@@ -118,9 +113,8 @@
                 EXAMPLE_URL_WITH_FRAGMENT, onPageFinishedHelper.getUrl());
 
         callCount = onPageFinishedHelper.getCallCount();
-        mWebContentsObserver.didFinishNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage,
-                hasCommitted, !isSameDocument, !fragmentNavigation, !isRendererInitiated,
-                isDownload, PageTransition.TYPED, errorCode, errorDescription, httpStatusCode);
+        simulateNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage, !isSameDocument,
+                !fragmentNavigation, !isRendererInitiated, PageTransition.TYPED);
         mWebContentsObserver.didFinishLoad(frameId, SYNC_URL, mainFrame);
         mWebContentsObserver.didStopLoading(SYNC_URL);
         onPageFinishedHelper.waitForCallback(callCount);
@@ -141,21 +135,15 @@
         String baseUrl = null;
         boolean isInMainFrame = true;
         boolean isErrorPage = false;
-        boolean hasCommitted = true;
         boolean isSameDocument = true;
         boolean fragmentNavigation = false;
         boolean isRendererInitiated = false;
-        boolean isDownload = false;
-        int errorCode = 0;
-        String errorDescription = "";
-        int httpStatusCode = 200;
         TestAwContentsClient.DoUpdateVisitedHistoryHelper doUpdateVisitedHistoryHelper =
                 mContentsClient.getDoUpdateVisitedHistoryHelper();
 
         int callCount = doUpdateVisitedHistoryHelper.getCallCount();
-        mWebContentsObserver.didFinishNavigation(nullUrl, isInMainFrame, !isErrorPage, hasCommitted,
-                !isSameDocument, fragmentNavigation, isRendererInitiated, isDownload,
-                PageTransition.TYPED, errorCode, errorDescription, httpStatusCode);
+        simulateNavigation(nullUrl, isInMainFrame, !isErrorPage, !isSameDocument,
+                fragmentNavigation, isRendererInitiated, PageTransition.TYPED);
         doUpdateVisitedHistoryHelper.waitForCallback(callCount);
         Assert.assertEquals("doUpdateVisitedHistory should be called for any url.", callCount + 1,
                 doUpdateVisitedHistoryHelper.getCallCount());
@@ -164,9 +152,8 @@
         Assert.assertEquals(false, doUpdateVisitedHistoryHelper.getIsReload());
 
         callCount = doUpdateVisitedHistoryHelper.getCallCount();
-        mWebContentsObserver.didFinishNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage,
-                hasCommitted, !isSameDocument, fragmentNavigation, isRendererInitiated, isDownload,
-                PageTransition.TYPED, errorCode, errorDescription, httpStatusCode);
+        simulateNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage, !isSameDocument,
+                fragmentNavigation, isRendererInitiated, PageTransition.TYPED);
         doUpdateVisitedHistoryHelper.waitForCallback(callCount);
         Assert.assertEquals("doUpdateVisitedHistory should be called for any url.", callCount + 1,
                 doUpdateVisitedHistoryHelper.getCallCount());
@@ -175,12 +162,10 @@
         Assert.assertEquals(false, doUpdateVisitedHistoryHelper.getIsReload());
 
         callCount = doUpdateVisitedHistoryHelper.getCallCount();
-        mWebContentsObserver.didFinishNavigation(nullUrl, isInMainFrame, isErrorPage, hasCommitted,
-                !isSameDocument, fragmentNavigation, isRendererInitiated, isDownload,
-                PageTransition.TYPED, errorCode, errorDescription, httpStatusCode);
-        mWebContentsObserver.didFinishNavigation(EXAMPLE_URL, !isInMainFrame, isErrorPage,
-                hasCommitted, !isSameDocument, fragmentNavigation, isRendererInitiated, isDownload,
-                PageTransition.TYPED, errorCode, errorDescription, httpStatusCode);
+        simulateNavigation(nullUrl, isInMainFrame, isErrorPage, !isSameDocument, fragmentNavigation,
+                isRendererInitiated, PageTransition.TYPED);
+        simulateNavigation(EXAMPLE_URL, !isInMainFrame, isErrorPage, !isSameDocument,
+                fragmentNavigation, isRendererInitiated, PageTransition.TYPED);
         doUpdateVisitedHistoryHelper.waitForCallback(callCount);
         Assert.assertEquals("doUpdateVisitedHistory should only be called for the main frame.",
                 callCount + 1, doUpdateVisitedHistoryHelper.getCallCount());
@@ -189,9 +174,8 @@
         Assert.assertEquals(false, doUpdateVisitedHistoryHelper.getIsReload());
 
         callCount = doUpdateVisitedHistoryHelper.getCallCount();
-        mWebContentsObserver.didFinishNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage,
-                hasCommitted, isSameDocument, !fragmentNavigation, !isRendererInitiated, isDownload,
-                PageTransition.RELOAD, errorCode, errorDescription, httpStatusCode);
+        simulateNavigation(EXAMPLE_URL, isInMainFrame, isErrorPage, isSameDocument,
+                !fragmentNavigation, !isRendererInitiated, PageTransition.RELOAD);
         doUpdateVisitedHistoryHelper.waitForCallback(callCount);
         Assert.assertEquals("doUpdateVisitedHistory should be called for reloads.", callCount + 1,
                 doUpdateVisitedHistoryHelper.getCallCount());
@@ -199,4 +183,16 @@
                 doUpdateVisitedHistoryHelper.getUrl());
         Assert.assertEquals(true, doUpdateVisitedHistoryHelper.getIsReload());
     }
+
+    private void simulateNavigation(String url, boolean isInMainFrame, boolean isErrorPage,
+            boolean isSameDocument, boolean isFragmentNavigation, boolean isRendererInitiated,
+            int transition) {
+        NavigationHandle navigation = new NavigationHandle(0 /* navigationHandleProxy */, url,
+                isInMainFrame, isSameDocument, isRendererInitiated);
+        mWebContentsObserver.didStartNavigation(navigation);
+
+        navigation.didFinish(url, isErrorPage, true /* hasCommitted */, isFragmentNavigation,
+                false /* isDownload */, transition, 0 /* errorCode*/, 200 /* httpStatusCode*/);
+        mWebContentsObserver.didFinishNavigation(navigation);
+    }
 }
diff --git a/apps/saved_files_service_unittest.cc b/apps/saved_files_service_unittest.cc
index d0679e9..7f9f1de 100644
--- a/apps/saved_files_service_unittest.cc
+++ b/apps/saved_files_service_unittest.cc
@@ -38,7 +38,7 @@
  protected:
   void SetUp() override {
     testing::Test::SetUp();
-    extension_ = env_.MakeExtension(*base::test::ParseJson(
+    extension_ = env_.MakeExtension(*base::test::ParseJsonDeprecated(
         "{"
         "  \"app\": {"
         "    \"background\": {"
@@ -147,7 +147,7 @@
 }
 
 TEST_F(SavedFilesServiceUnitTest, NoRetainEntriesPermissionTest) {
-  extension_ = env_.MakeExtension(*base::test::ParseJson(
+  extension_ = env_.MakeExtension(*base::test::ParseJsonDeprecated(
       "{\"app\": {\"background\": {\"scripts\": [\"background.js\"]}},"
       "\"permissions\": [\"fileSystem\"]}"));
   service_->RegisterFileEntry(extension_->id(), GenerateId(1), path_, true);
diff --git a/ash/assistant/assistant_ui_controller.cc b/ash/assistant/assistant_ui_controller.cc
index f03d8478..a0def9d 100644
--- a/ash/assistant/assistant_ui_controller.cc
+++ b/ash/assistant/assistant_ui_controller.cc
@@ -251,8 +251,12 @@
   // navigation was initiated by a server response. Otherwise the navigation
   // was user initiated so we only hide the UI to retain session state. That way
   // the user can choose to resume their session if they are so inclined.
+  // However, we close the UI if it is in the |kLauncherEmbeddedUi| mode, where
+  // we only maintain |kVisible| and |kClosed| two states.
   if (from_server)
     CloseUi(AssistantExitPoint::kNewBrowserTabFromServer);
+  else if (model_.ui_mode() == AssistantUiMode::kLauncherEmbeddedUi)
+    CloseUi(AssistantExitPoint::kNewBrowserTabFromUser);
   else
     HideUi(AssistantExitPoint::kNewBrowserTabFromUser);
 }
diff --git a/base/BUILD.gn b/base/BUILD.gn
index c54a54c..2134595f 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1308,6 +1308,8 @@
       "android/path_service_android.cc",
       "android/path_utils.cc",
       "android/path_utils.h",
+      "android/reached_addresses_bitset.cc",
+      "android/reached_addresses_bitset.h",
       "android/reached_code_profiler.cc",
       "android/reached_code_profiler.h",
       "android/record_histogram.cc",
@@ -2287,6 +2289,7 @@
     "android/library_loader/library_loader_hooks_unittest.cc",
     "android/library_loader/library_prefetcher_unittest.cc",
     "android/path_utils_unittest.cc",
+    "android/reached_addresses_bitset_unittest.cc",
     "android/scoped_java_ref_unittest.cc",
     "android/sys_utils_unittest.cc",
     "android/unguessable_token_android_unittest.cc",
diff --git a/base/android/library_loader/anchor_functions.cc b/base/android/library_loader/anchor_functions.cc
index 0865d9d..9914c34 100644
--- a/base/android/library_loader/anchor_functions.cc
+++ b/base/android/library_loader/anchor_functions.cc
@@ -56,8 +56,12 @@
 const size_t kEndOfOrderedText =
     reinterpret_cast<size_t>(dummy_function_end_of_ordered_text);
 
-bool IsOrderingSane() {
+bool AreAnchorsSane() {
   size_t here = reinterpret_cast<size_t>(&IsOrderingSane);
+  return kStartOfText < here && here < kEndOfText;
+}
+
+bool IsOrderingSane() {
   // The symbols linker_script_start_of_text and linker_script_end_of_text
   // should cover all of .text, and dummy_function_start_of_ordered_text and
   // dummy_function_end_of_ordered_text should cover the ordered part of it.
@@ -68,8 +72,7 @@
   // different, but linker-defined symbols have zero size and therefore the
   // start address could be the same as the address of
   // dummy_function_start_of_ordered_text.
-  return kStartOfText < here && here < kEndOfText &&
-         kStartOfOrderedText < kEndOfOrderedText &&
+  return AreAnchorsSane() && kStartOfOrderedText < kEndOfOrderedText &&
          kStartOfText <= kStartOfOrderedText && kEndOfOrderedText < kEndOfText;
 }
 
diff --git a/base/android/library_loader/anchor_functions.h b/base/android/library_loader/anchor_functions.h
index 9894583..fe95d5fb 100644
--- a/base/android/library_loader/anchor_functions.h
+++ b/base/android/library_loader/anchor_functions.h
@@ -22,6 +22,9 @@
 BASE_EXPORT extern const size_t kStartOfOrderedText;
 BASE_EXPORT extern const size_t kEndOfOrderedText;
 
+// Returns true if anchors are sane.
+BASE_EXPORT bool AreAnchorsSane();
+
 // Returns true if the ordering looks sane.
 BASE_EXPORT bool IsOrderingSane();
 
diff --git a/base/android/reached_addresses_bitset.cc b/base/android/reached_addresses_bitset.cc
new file mode 100644
index 0000000..9be0b10
--- /dev/null
+++ b/base/android/reached_addresses_bitset.cc
@@ -0,0 +1,99 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/reached_addresses_bitset.h"
+
+#include "base/android/library_loader/anchor_functions.h"
+#include "base/android/library_loader/anchor_functions_buildflags.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+
+namespace base {
+namespace android {
+
+namespace {
+constexpr size_t kBitsPerElement = sizeof(uint32_t) * 8;
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+// Enough for 1 << 29 bytes of code, 512MB.
+constexpr size_t kTextBitfieldSize = 1 << 20;
+std::atomic<uint32_t> g_text_bitfield[kTextBitfieldSize];
+#endif
+}  // namespace
+
+// static
+ReachedAddressesBitset* ReachedAddressesBitset::GetTextBitset() {
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+  static base::NoDestructor<ReachedAddressesBitset> text_bitset(
+      kStartOfText, kEndOfText, g_text_bitfield, kTextBitfieldSize);
+  return text_bitset.get();
+#else
+  return nullptr;
+#endif
+}
+
+void ReachedAddressesBitset::RecordAddress(uintptr_t address) {
+  // |address| is outside of the range.
+  if (address < start_address_ || address >= end_address_)
+    return;
+
+  uint32_t offset = address - start_address_;
+  uint32_t offset_index = offset / kBytesGranularity;
+
+  // Atomically set the corresponding bit in the array.
+  std::atomic<uint32_t>* element = reached_ + (offset_index / kBitsPerElement);
+
+  // First, a racy check. This saves a CAS if the bit is already set, and
+  // allows the cache line to remain shared across CPUs in this case.
+  uint32_t value = element->load(std::memory_order_relaxed);
+  uint32_t mask = 1 << (offset_index % kBitsPerElement);
+  if (value & mask)
+    return;
+  element->fetch_or(mask, std::memory_order_relaxed);
+}
+
+std::vector<uint32_t> ReachedAddressesBitset::GetReachedOffsets() const {
+  std::vector<uint32_t> offsets;
+  const size_t elements = NumberOfReachableElements();
+
+  for (size_t i = 0; i < elements; ++i) {
+    uint32_t element = reached_[i].load(std::memory_order_relaxed);
+    // No reached addresses at this element.
+    if (element == 0)
+      continue;
+
+    for (size_t j = 0; j < 32; ++j) {
+      if (!((element >> j) & 1))
+        continue;
+
+      uint32_t offset_index = i * 32 + j;
+      uint32_t offset = offset_index * kBytesGranularity;
+      offsets.push_back(offset);
+    }
+  }
+
+  return offsets;
+}
+
+ReachedAddressesBitset::ReachedAddressesBitset(
+    uintptr_t start_address,
+    uintptr_t end_address,
+    std::atomic<uint32_t>* storage_ptr,
+    size_t storage_size)
+    : start_address_(start_address),
+      end_address_(end_address),
+      reached_(storage_ptr) {
+  DCHECK_LE(start_address_, end_address_);
+  DCHECK_LE(NumberOfReachableElements(), storage_size * kBitsPerElement);
+}
+
+size_t ReachedAddressesBitset::NumberOfReachableElements() const {
+  size_t reachable_bits =
+      (end_address_ + kBytesGranularity - 1) / kBytesGranularity -
+      start_address_ / kBytesGranularity;
+  return (reachable_bits + kBitsPerElement - 1) / kBitsPerElement;
+}
+
+}  // namespace android
+}  // namespace base
diff --git a/base/android/reached_addresses_bitset.h b/base/android/reached_addresses_bitset.h
new file mode 100644
index 0000000..1f4b8927
--- /dev/null
+++ b/base/android/reached_addresses_bitset.h
@@ -0,0 +1,75 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_REACHED_ADDRESSES_BITSET_H_
+#define BASE_ANDROID_REACHED_ADDRESSES_BITSET_H_
+
+#include <atomic>
+#include <vector>
+
+#include "base/base_export.h"
+
+namespace base {
+
+template <typename T>
+class NoDestructor;
+
+namespace android {
+
+// ReachedAddressesBitset is a set that stores addresses for the
+// ReachedCodeProfiler in compact form. Its main features are lock-free
+// thread-safety and fast adding of elements.
+//
+// The addresses are kept with |kBytesGranularity| to save the storage space.
+//
+// Once insterted, elements cannot be erased from the set.
+//
+// All methods can be called from any thread.
+class BASE_EXPORT ReachedAddressesBitset {
+ public:
+  // Returns an instance of ReachedAddressesBitset having enough storage space
+  // to keep all addresses from the .text section.
+  // Returns nullptr if SUPPORTS_CODE_ORDERING isn't defined.
+  // This instance is stored in the .bss section of the binary, meaning that it
+  // doesn't incur the binary size overhead and it doesn't increase the resident
+  // memory footprint when not used.
+  static ReachedAddressesBitset* GetTextBitset();
+
+  // Inserts |address| into the bitset iff |address| lies in the range between
+  // |start_address_| and |end_address_|.
+  void RecordAddress(uintptr_t address);
+
+  // Returns a list of recorded addresses in the form of offsets from
+  // |start_address_|.
+  std::vector<uint32_t> GetReachedOffsets() const;
+
+ private:
+  friend class ReachedAddressesBitsetTest;
+  friend class NoDestructor<ReachedAddressesBitset>;
+
+  // Represents the number of bytes that are mapped into the same bit in the
+  // bitset.
+  static constexpr size_t kBytesGranularity = 4;
+
+  // Constructs a ReachedAddressesBitset on top of an external storage of
+  // |storage_size| pointed by |storage_ptr|. This external storage must outlive
+  // the constructed bitset instance. The size of storage must be large enough
+  // to fit all addresses in the range between |start_address| and
+  // |end_address|.
+  ReachedAddressesBitset(uintptr_t start_address,
+                         uintptr_t end_address,
+                         std::atomic<uint32_t>* storage_ptr,
+                         size_t storage_size);
+
+  size_t NumberOfReachableElements() const;
+
+  uintptr_t start_address_;
+  uintptr_t end_address_;
+  std::atomic<uint32_t>* reached_;
+};
+
+}  // namespace android
+}  // namespace base
+
+#endif  // BASE_ANDROID_REACHED_ADDRESSES_BITSET_H_
diff --git a/base/android/reached_addresses_bitset_unittest.cc b/base/android/reached_addresses_bitset_unittest.cc
new file mode 100644
index 0000000..32fb4f2
--- /dev/null
+++ b/base/android/reached_addresses_bitset_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/reached_addresses_bitset.h"
+
+#include <utility>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+using testing::ElementsAre;
+using testing::ElementsAreArray;
+
+constexpr uintptr_t kStartAddress = 0x1000;
+constexpr uintptr_t kEndAddress = 0x2000;
+constexpr size_t kStorageSize = 512;
+
+class ReachedAddressesBitsetTest : public testing::Test {
+ public:
+  ReachedAddressesBitsetTest()
+      : bitset_(kStartAddress, kEndAddress, storage_, kStorageSize) {
+    memset(storage_, 0, kStorageSize * sizeof(uint32_t));
+    EXPECT_TRUE(bitset()->GetReachedOffsets().empty());
+  }
+
+  ReachedAddressesBitset* bitset() { return &bitset_; }
+
+ private:
+  std::atomic<uint32_t> storage_[kStorageSize];
+  ReachedAddressesBitset bitset_;
+};
+
+TEST_F(ReachedAddressesBitsetTest, RecordStartAddress) {
+  bitset()->RecordAddress(kStartAddress);
+  EXPECT_THAT(bitset()->GetReachedOffsets(), ElementsAre(0));
+}
+
+TEST_F(ReachedAddressesBitsetTest, RecordLastAddress) {
+  bitset()->RecordAddress(kEndAddress - 4);
+  EXPECT_THAT(bitset()->GetReachedOffsets(),
+              ElementsAre(kEndAddress - 4 - kStartAddress));
+}
+
+TEST_F(ReachedAddressesBitsetTest, RecordAddressOutsideOfRange_Small) {
+  bitset()->RecordAddress(kStartAddress - 4);
+  EXPECT_THAT(bitset()->GetReachedOffsets(), ElementsAre());
+}
+
+TEST_F(ReachedAddressesBitsetTest, RecordAddressOutsideOfRange_Large) {
+  bitset()->RecordAddress(kEndAddress);
+  EXPECT_THAT(bitset()->GetReachedOffsets(), ElementsAre());
+}
+
+TEST_F(ReachedAddressesBitsetTest, RecordUnalignedAddresses) {
+  constexpr uint32_t aligned_offset = 0x100;
+  bitset()->RecordAddress(kStartAddress + aligned_offset + 1);
+  bitset()->RecordAddress(kStartAddress + aligned_offset + 2);
+  bitset()->RecordAddress(kStartAddress + aligned_offset + 3);
+  EXPECT_THAT(bitset()->GetReachedOffsets(), ElementsAre(aligned_offset));
+}
+
+TEST_F(ReachedAddressesBitsetTest, FillBitsetOneByOne) {
+  std::vector<uint32_t> expected_offsets;
+  for (uintptr_t address = kStartAddress; address < kEndAddress; address += 4) {
+    bitset()->RecordAddress(address);
+    expected_offsets.push_back(address - kStartAddress);
+    ASSERT_THAT(bitset()->GetReachedOffsets(),
+                ElementsAreArray(expected_offsets))
+        << "Last added: " << address;
+  }
+}
+
+}  // namespace android
+}  // namespace base
diff --git a/base/android/reached_code_profiler.cc b/base/android/reached_code_profiler.cc
index 07e6d5e..93100fa 100644
--- a/base/android/reached_code_profiler.cc
+++ b/base/android/reached_code_profiler.cc
@@ -13,6 +13,7 @@
 
 #include "base/android/library_loader/anchor_functions.h"
 #include "base/android/orderfile/orderfile_buildflags.h"
+#include "base/android/reached_addresses_bitset.h"
 #include "base/base_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -57,10 +58,6 @@
 
 constexpr const char kDumpToFileFlag[] = "reached-code-profiler-dump-to-file";
 
-// Enough for 1 << 29 bytes of code, 512MB.
-constexpr size_t kBitfieldSize = 1 << 20;
-constexpr size_t kBitsPerElement = 4 * 32;
-
 constexpr uint64_t kIterationsBeforeSkipping = 50;
 constexpr uint64_t kIterationsBetweenUpdates = 100;
 constexpr int kProfilerSignal = SIGURG;
@@ -69,45 +66,13 @@
     base::TimeDelta::FromMilliseconds(10);
 constexpr base::TimeDelta kDumpInterval = base::TimeDelta::FromSeconds(30);
 
-std::atomic<uint32_t> g_reached[kBitfieldSize];
-std::atomic<std::atomic<uint32_t>*> g_enabled_and_reached(g_reached);
-
-size_t NumberOfReachableElements() {
-  return (kEndOfText - kStartOfText) / kBitsPerElement + 1;
-}
-
-void RecordAddress(uint32_t address) {
-  auto* reached = g_enabled_and_reached.load(std::memory_order_relaxed);
-  if (!reached)
-    return;
-
-  // Stopped in libc, third-party, or Java code.
-  if (address < kStartOfText || address > kEndOfText)
-    return;
-
-  size_t offset = address - kStartOfText;
-  static_assert(sizeof(int) == 4,
-                "Collection and processing code assumes that sizeof(int) == 4");
-  size_t offset_index = offset / 4;
-
-  // Atomically set the corresponding bit in the array.
-  std::atomic<uint32_t>* element = reached + (offset_index / 32);
-  // First, a racy check. This saves a CAS if the bit is already set, and
-  // allows the cache line to remain shared acoss CPUs in this case.
-  uint32_t value = element->load(std::memory_order_relaxed);
-  uint32_t mask = 1 << (offset_index % 32);
-  if (value & mask)
-    return;
-  element->fetch_or(mask, std::memory_order_relaxed);
-}
-
 void HandleSignal(int signal, siginfo_t* info, void* context) {
   if (signal != kProfilerSignal)
     return;
 
   ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context);
   uint32_t address = ucontext->uc_mcontext.arm_pc;
-  RecordAddress(address);
+  ReachedAddressesBitset::GetTextBitset()->RecordAddress(address);
 }
 
 struct ScopedTimerCloseTraits {
@@ -120,30 +85,9 @@
 using ScopedTimer =
     base::ScopedGeneric<base::Optional<timer_t>, ScopedTimerCloseTraits>;
 
-std::vector<uint8_t> SnapshotReachedCodeBitset() {
-  std::vector<uint8_t> buf;
-  size_t elements = NumberOfReachableElements();
-  buf.resize(elements * sizeof(uint32_t));
-  // Copy the reached array into a buffer with atomic loads with the explicit
-  // memory ordering flag. In practice this is likely not necessary because:
-  // a) integrity of the data across individual elements of |g_reached| is not
-  //    maintained anyway
-  // b) write(2) will not take the data in smaller chunks than 4 bytes
-  // c) it would be bizarre for mojo initialization code to cause the compiler
-  //    to spill stuff into the array..
-  // Anyway .. come to the Safe Side, we have CPUs to spin.
-  for (size_t i = 0; i < elements; i++) {
-    uint32_t word = g_reached[i].load(std::memory_order_relaxed);
-    for (int j = 0; j < 4; j++) {
-      buf[4 * i + j] = static_cast<uint8_t>((word >> (j * 8)) & 0xFF);
-    }
-  }
-  return buf;
-}
-
 void DumpToFile(const base::FilePath& path,
                 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
-  CHECK(task_runner->BelongsToCurrentThread());
+  DCHECK(task_runner->BelongsToCurrentThread());
 
   auto dir_path = path.DirName();
   if (!base::DirectoryExists(dir_path) && !base::CreateDirectory(dir_path)) {
@@ -151,9 +95,11 @@
     return;
   }
 
-  std::vector<uint8_t> buf = SnapshotReachedCodeBitset();
-  base::StringPiece contents(reinterpret_cast<const char*>(buf.data()),
-                             buf.size());
+  std::vector<uint32_t> reached_offsets =
+      ReachedAddressesBitset::GetTextBitset()->GetReachedOffsets();
+  base::StringPiece contents(
+      reinterpret_cast<const char*>(reached_offsets.data()),
+      reached_offsets.size());
   if (!base::ImportantFileWriter::WriteFileAtomically(path, contents,
                                                       "ReachedDump")) {
     LOG(ERROR) << "Could not write reached dump into " << path;
diff --git a/base/json/json_reader.h b/base/json/json_reader.h
index 738f72e..fb458cfe 100644
--- a/base/json/json_reader.h
+++ b/base/json/json_reader.h
@@ -110,8 +110,7 @@
   ~JSONReader();
 
   // Reads and parses |json|, returning a Value.
-  // If |json| is not a properly formed JSON string, returns a Value of type
-  // NONE.
+  // If |json| is not a properly formed JSON string, returns base::nullopt.
   static Optional<Value> Read(StringPiece json,
                               int options = JSON_PARSE_RFC,
                               int max_depth = kStackMaxDepth);
diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc
index ac1796cf..5023f33 100644
--- a/base/json/json_reader_unittest.cc
+++ b/base/json/json_reader_unittest.cc
@@ -6,7 +6,7 @@
 
 #include <stddef.h>
 
-#include <memory>
+#include <utility>
 
 #include "base/base_paths.h"
 #include "base/files/file_util.h"
@@ -53,31 +53,29 @@
   std::string value;
   EXPECT_TRUE(root->GetAsString(&value));
   EXPECT_EQ("sample string", value);
-  std::unique_ptr<ListValue> list = ListValue::From(
-      JSONReader::ReadDeprecated("[1, /* comment, 2 ] */ \n 3]"));
-  ASSERT_TRUE(list);
-  EXPECT_EQ(2u, list->GetSize());
-  int int_val = 0;
-  EXPECT_TRUE(list->GetInteger(0, &int_val));
-  EXPECT_EQ(1, int_val);
-  EXPECT_TRUE(list->GetInteger(1, &int_val));
-  EXPECT_EQ(3, int_val);
-  list = ListValue::From(JSONReader::ReadDeprecated("[1, /*a*/2, 3]"));
-  ASSERT_TRUE(list);
-  EXPECT_EQ(3u, list->GetSize());
+  root = JSONReader::Read("[1, /* comment, 2 ] */ \n 3]");
+  ASSERT_TRUE(root);
+  ASSERT_TRUE(root->is_list());
+  ASSERT_EQ(2u, root->GetList().size());
+  ASSERT_TRUE(root->GetList()[0].is_int());
+  EXPECT_EQ(1, root->GetList()[0].GetInt());
+  ASSERT_TRUE(root->GetList()[1].is_int());
+  EXPECT_EQ(3, root->GetList()[1].GetInt());
+  root = JSONReader::Read("[1, /*a*/2, 3]");
+  ASSERT_TRUE(root);
+  ASSERT_TRUE(root->is_list());
+  EXPECT_EQ(3u, root->GetList().size());
   root = JSONReader::Read("/* comment **/42");
   ASSERT_TRUE(root);
-  EXPECT_TRUE(root->is_int());
-  EXPECT_TRUE(root->GetAsInteger(&int_val));
-  EXPECT_EQ(42, int_val);
+  ASSERT_TRUE(root->is_int());
+  EXPECT_EQ(42, root->GetInt());
   root = JSONReader::Read(
       "/* comment **/\n"
       "// */ 43\n"
       "44");
   ASSERT_TRUE(root);
   EXPECT_TRUE(root->is_int());
-  EXPECT_TRUE(root->GetAsInteger(&int_val));
-  EXPECT_EQ(44, int_val);
+  EXPECT_EQ(44, root->GetInt());
 }
 
 TEST(JSONReaderTest, Ints) {
@@ -478,12 +476,12 @@
   EXPECT_TRUE(root->GetAsString(&str_val));
   EXPECT_EQ(L"\x7f51\x9875", UTF8ToWide(str_val));
 
-  std::unique_ptr<DictionaryValue> dict_val =
-      DictionaryValue::From(JSONReader::ReadDeprecated(
-          "{\"path\": \"/tmp/\xc3\xa0\xc3\xa8\xc3\xb2.png\"}"));
-  ASSERT_TRUE(dict_val);
-  EXPECT_TRUE(dict_val->GetString("path", &str_val));
-  EXPECT_EQ("/tmp/\xC3\xA0\xC3\xA8\xC3\xB2.png", str_val);
+  root = JSONReader::Read("{\"path\": \"/tmp/\xc3\xa0\xc3\xa8\xc3\xb2.png\"}");
+  ASSERT_TRUE(root);
+  ASSERT_TRUE(root->is_dict());
+  const std::string* maybe_string = root->FindStringKey("path");
+  ASSERT_TRUE(maybe_string);
+  EXPECT_EQ("/tmp/\xC3\xA0\xC3\xA8\xC3\xB2.png", *maybe_string);
 }
 
 TEST(JSONReaderTest, InvalidUTF8Input) {
@@ -572,15 +570,15 @@
 // Tests that the root of a JSON object can be deleted safely while its
 // children outlive it.
 TEST(JSONReaderTest, StringOptimizations) {
-  std::unique_ptr<Value> dict_literal_0;
-  std::unique_ptr<Value> dict_literal_1;
-  std::unique_ptr<Value> dict_string_0;
-  std::unique_ptr<Value> dict_string_1;
-  std::unique_ptr<Value> list_value_0;
-  std::unique_ptr<Value> list_value_1;
+  Value dict_literal_0;
+  Value dict_literal_1;
+  Value dict_string_0;
+  Value dict_string_1;
+  Value list_value_0;
+  Value list_value_1;
 
   {
-    std::unique_ptr<Value> root = JSONReader::ReadDeprecated(
+    Optional<Value> root = JSONReader::Read(
         "{"
         "  \"test\": {"
         "    \"foo\": true,"
@@ -595,46 +593,52 @@
         "}",
         JSON_PARSE_RFC);
     ASSERT_TRUE(root);
+    ASSERT_TRUE(root->is_dict());
 
-    DictionaryValue* root_dict = nullptr;
-    ASSERT_TRUE(root->GetAsDictionary(&root_dict));
+    Value* dict = root->FindKeyOfType("test", Value::Type::DICTIONARY);
+    ASSERT_TRUE(dict);
+    Value* list = root->FindKeyOfType("list", Value::Type::LIST);
+    ASSERT_TRUE(list);
 
-    DictionaryValue* dict = nullptr;
-    ListValue* list = nullptr;
+    Value* to_move = dict->FindKey("foo");
+    ASSERT_TRUE(to_move);
+    dict_literal_0 = std::move(*to_move);
+    to_move = dict->FindKey("bar");
+    ASSERT_TRUE(to_move);
+    dict_literal_1 = std::move(*to_move);
+    to_move = dict->FindKey("baz");
+    ASSERT_TRUE(to_move);
+    dict_string_0 = std::move(*to_move);
+    to_move = dict->FindKey("moo");
+    ASSERT_TRUE(to_move);
+    dict_string_1 = std::move(*to_move);
+    ASSERT_TRUE(dict->RemoveKey("foo"));
+    ASSERT_TRUE(dict->RemoveKey("bar"));
+    ASSERT_TRUE(dict->RemoveKey("baz"));
+    ASSERT_TRUE(dict->RemoveKey("moo"));
 
-    ASSERT_TRUE(root_dict->GetDictionary("test", &dict));
-    ASSERT_TRUE(root_dict->GetList("list", &list));
-
-    ASSERT_TRUE(dict->Remove("foo", &dict_literal_0));
-    ASSERT_TRUE(dict->Remove("bar", &dict_literal_1));
-    ASSERT_TRUE(dict->Remove("baz", &dict_string_0));
-    ASSERT_TRUE(dict->Remove("moo", &dict_string_1));
-
-    ASSERT_EQ(2u, list->GetSize());
-    ASSERT_TRUE(list->Remove(0, &list_value_0));
-    ASSERT_TRUE(list->Remove(0, &list_value_1));
+    ASSERT_EQ(2u, list->GetList().size());
+    list_value_0 = std::move(list->GetList()[0]);
+    list_value_1 = std::move(list->GetList()[1]);
+    list->GetList().clear();
   }
 
-  bool b = false;
-  double d = 0;
-  std::string s;
+  ASSERT_TRUE(dict_literal_0.is_bool());
+  EXPECT_TRUE(dict_literal_0.GetBool());
 
-  EXPECT_TRUE(dict_literal_0->GetAsBoolean(&b));
-  EXPECT_TRUE(b);
+  ASSERT_TRUE(dict_literal_1.is_double());
+  EXPECT_EQ(3.14, dict_literal_1.GetDouble());
 
-  EXPECT_TRUE(dict_literal_1->GetAsDouble(&d));
-  EXPECT_EQ(3.14, d);
+  ASSERT_TRUE(dict_string_0.is_string());
+  EXPECT_EQ("bat", dict_string_0.GetString());
 
-  EXPECT_TRUE(dict_string_0->GetAsString(&s));
-  EXPECT_EQ("bat", s);
+  ASSERT_TRUE(dict_string_1.is_string());
+  EXPECT_EQ("cow", dict_string_1.GetString());
 
-  EXPECT_TRUE(dict_string_1->GetAsString(&s));
-  EXPECT_EQ("cow", s);
-
-  EXPECT_TRUE(list_value_0->GetAsString(&s));
-  EXPECT_EQ("a", s);
-  EXPECT_TRUE(list_value_1->GetAsString(&s));
-  EXPECT_EQ("b", s);
+  ASSERT_TRUE(list_value_0.is_string());
+  EXPECT_EQ("a", list_value_0.GetString());
+  ASSERT_TRUE(list_value_1.is_string());
+  EXPECT_EQ("b", list_value_1.GetString());
 }
 
 // A smattering of invalid JSON designed to test specific portions of the
diff --git a/base/test/values_test_util.cc b/base/test/values_test_util.cc
index 80e0a36..007e6f6 100644
--- a/base/test/values_test_util.cc
+++ b/base/test/values_test_util.cc
@@ -4,7 +4,6 @@
 
 #include "base/test/values_test_util.h"
 
-#include <memory>
 #include <ostream>
 #include <utility>
 
@@ -63,7 +62,7 @@
 namespace test {
 
 IsJsonMatcher::IsJsonMatcher(base::StringPiece json)
-    : expected_value_(std::move(*test::ParseJson(json))) {}
+    : expected_value_(test::ParseJson(json)) {}
 
 IsJsonMatcher::IsJsonMatcher(const base::Value& value)
     : expected_value_(value.Clone()) {}
@@ -102,16 +101,19 @@
   *os << "is not the JSON value " << expected_value_;
 }
 
-std::unique_ptr<Value> ParseJson(base::StringPiece json) {
-  std::string error_msg;
-  std::unique_ptr<Value> result =
-      base::JSONReader::ReadAndReturnErrorDeprecated(
-          json, base::JSON_ALLOW_TRAILING_COMMAS, nullptr, &error_msg);
-  if (!result) {
-    ADD_FAILURE() << "Failed to parse \"" << json << "\": " << error_msg;
-    result = std::make_unique<Value>();
+Value ParseJson(StringPiece json) {
+  JSONReader::ValueWithError result =
+      JSONReader::ReadAndReturnValueWithError(json, JSON_ALLOW_TRAILING_COMMAS);
+  if (!result.value) {
+    ADD_FAILURE() << "Failed to parse \"" << json
+                  << "\": " << result.error_message;
+    return Value();
   }
-  return result;
+  return std::move(result.value.value());
+}
+
+std::unique_ptr<Value> ParseJsonDeprecated(StringPiece json) {
+  return Value::ToUniquePtrValue(ParseJson(json));
 }
 
 }  // namespace test
diff --git a/base/test/values_test_util.h b/base/test/values_test_util.h
index 73483bf..c7d5509 100644
--- a/base/test/values_test_util.h
+++ b/base/test/values_test_util.h
@@ -77,10 +77,16 @@
   return testing::MakePolymorphicMatcher(IsJsonMatcher(value));
 }
 
+// Parses |json| as JSON, allowing trailing commas, and returns the resulting
+// value.  If |json| fails to parse, causes an EXPECT failure and returns the
+// Null Value.
+Value ParseJson(StringPiece json);
+
+// DEPRECATED.
 // Parses |json| as JSON, allowing trailing commas, and returns the
 // resulting value.  If the json fails to parse, causes an EXPECT
 // failure and returns the Null Value (but never a NULL pointer).
-std::unique_ptr<Value> ParseJson(base::StringPiece json);
+std::unique_ptr<Value> ParseJsonDeprecated(StringPiece json);
 
 }  // namespace test
 }  // namespace base
diff --git a/base/trace_event/trace_config.cc b/base/trace_event/trace_config.cc
index 6c9dc03..e28456bd 100644
--- a/base/trace_event/trace_config.cc
+++ b/base/trace_event/trace_config.cc
@@ -33,6 +33,7 @@
 // String parameters that can be used to parse the trace config string.
 const char kRecordModeParam[] = "record_mode";
 const char kTraceBufferSizeInEvents[] = "trace_buffer_size_in_events";
+const char kTraceBufferSizeInKb[] = "trace_buffer_size_in_kb";
 const char kEnableSystraceParam[] = "enable_systrace";
 const char kEnableArgumentFilterParam[] = "enable_argument_filter";
 
@@ -292,6 +293,7 @@
 
   record_mode_ = rhs.record_mode_;
   trace_buffer_size_in_events_ = rhs.trace_buffer_size_in_events_;
+  trace_buffer_size_in_kb_ = rhs.trace_buffer_size_in_kb_;
   enable_systrace_ = rhs.enable_systrace_;
   enable_argument_filter_ = rhs.enable_argument_filter_;
   category_filter_ = rhs.category_filter_;
@@ -334,6 +336,8 @@
   }
   DCHECK_EQ(trace_buffer_size_in_events_, config.trace_buffer_size_in_events_)
       << "Cannot change trace buffer size";
+  DCHECK_EQ(trace_buffer_size_in_kb_, config.trace_buffer_size_in_kb_)
+      << "Cannot change trace buffer size";
 
   category_filter_.Merge(config.category_filter_);
   memory_dump_config_.Merge(config.memory_dump_config_);
@@ -348,6 +352,7 @@
 void TraceConfig::Clear() {
   record_mode_ = RECORD_UNTIL_FULL;
   trace_buffer_size_in_events_ = 0;
+  trace_buffer_size_in_kb_ = 0;
   enable_systrace_ = false;
   enable_argument_filter_ = false;
   category_filter_.Clear();
@@ -360,6 +365,7 @@
 void TraceConfig::InitializeDefault() {
   record_mode_ = RECORD_UNTIL_FULL;
   trace_buffer_size_in_events_ = 0;
+  trace_buffer_size_in_kb_ = 0;
   enable_systrace_ = false;
   enable_argument_filter_ = false;
 }
@@ -381,6 +387,8 @@
   int buffer_size = 0;
   trace_buffer_size_in_events_ =
       dict.GetInteger(kTraceBufferSizeInEvents, &buffer_size) ? buffer_size : 0;
+  trace_buffer_size_in_kb_ =
+      dict.GetInteger(kTraceBufferSizeInKb, &buffer_size) ? buffer_size : 0;
 
   bool val;
   enable_systrace_ = dict.GetBoolean(kEnableSystraceParam, &val) ? val : false;
@@ -423,6 +431,7 @@
 
   record_mode_ = RECORD_UNTIL_FULL;
   trace_buffer_size_in_events_ = 0;
+  trace_buffer_size_in_kb_ = 0;
   enable_systrace_ = false;
   enable_argument_filter_ = false;
   if (!trace_options_string.empty()) {
@@ -564,6 +573,8 @@
   dict->SetBoolean(kEnableArgumentFilterParam, enable_argument_filter_);
   if (trace_buffer_size_in_events_ > 0)
     dict->SetInteger(kTraceBufferSizeInEvents, trace_buffer_size_in_events_);
+  if (trace_buffer_size_in_kb_ > 0)
+    dict->SetInteger(kTraceBufferSizeInKb, trace_buffer_size_in_kb_);
 
   category_filter_.ToDict(dict.get());
   process_filter_config_.ToDict(dict.get());
diff --git a/base/trace_event/trace_config.h b/base/trace_event/trace_config.h
index 09bdd60..30f647f 100644
--- a/base/trace_event/trace_config.h
+++ b/base/trace_event/trace_config.h
@@ -116,7 +116,7 @@
 
   class BASE_EXPORT EventFilterConfig {
    public:
-    EventFilterConfig(const std::string& predicate_name);
+    explicit EventFilterConfig(const std::string& predicate_name);
     EventFilterConfig(const EventFilterConfig& tc);
 
     ~EventFilterConfig();
@@ -229,6 +229,7 @@
   size_t GetTraceBufferSizeInEvents() const {
     return trace_buffer_size_in_events_;
   }
+  size_t GetTraceBufferSizeInKb() const { return trace_buffer_size_in_kb_; }
   bool IsSystraceEnabled() const { return enable_systrace_; }
   bool IsArgumentFilterEnabled() const { return enable_argument_filter_; }
 
@@ -236,6 +237,7 @@
   void SetTraceBufferSizeInEvents(size_t size) {
     trace_buffer_size_in_events_ = size;
   }
+  void SetTraceBufferSizeInKb(size_t size) { trace_buffer_size_in_kb_ = size; }
   void EnableSystrace() { enable_systrace_ = true; }
   void EnableArgumentFilter() { enable_argument_filter_ = true; }
   void EnableHistogram(const std::string& histogram_name);
@@ -317,6 +319,7 @@
 
   TraceRecordMode record_mode_;
   size_t trace_buffer_size_in_events_ = 0;  // 0 specifies default size
+  size_t trace_buffer_size_in_kb_ = 0;      // 0 specifies default size
   bool enable_systrace_ : 1;
   bool enable_argument_filter_ : 1;
 
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index df4c101..f8c9a30 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-4e667b3998b2dcc3e611ed26003fbafc7abaeb30
\ No newline at end of file
+81b54a7e2b2fff7f5dad246801c3c4b34486abdf
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 3c5cdb1..eec3a7a4 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-3da35f1d08e145c7e9589ab286562d97b23e940c
\ No newline at end of file
+f80285fb56fc829316c85929d34f5fbe9b15b4a9
\ No newline at end of file
diff --git a/cc/test/layer_tree_json_parser.cc b/cc/test/layer_tree_json_parser.cc
index d23dfb4..8d600d5 100644
--- a/cc/test/layer_tree_json_parser.cc
+++ b/cc/test/layer_tree_json_parser.cc
@@ -138,7 +138,7 @@
 
 scoped_refptr<Layer> ParseTreeFromJson(std::string json,
                                        ContentLayerClient* content_client) {
-  std::unique_ptr<base::Value> val = base::test::ParseJson(json);
+  std::unique_ptr<base::Value> val = base::test::ParseJsonDeprecated(json);
   return ParseTreeFromValue(*val, content_client);
 }
 
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index a1c0c47..1f18015 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -147,8 +147,8 @@
   enum MainOrder : int {
     MAIN_START = 1,
     MAIN_LAYOUT,
-    MAIN_DID_BEGIN_FRAME,
     MAIN_COMMIT_COMPLETE,
+    MAIN_DID_BEGIN_FRAME,
     MAIN_END,
   };
 
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index 7218721..e0683e5 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -236,8 +236,6 @@
   skip_commit |= defer_main_frame_update_ || defer_commits_;
 
   if (skip_commit) {
-    current_pipeline_stage_ = NO_PIPELINE_STAGE;
-    layer_tree_host_->DidBeginMainFrame();
     TRACE_EVENT_INSTANT0("cc", "EarlyOut_DeferCommit_InsideBeginMainFrame",
                          TRACE_EVENT_SCOPE_THREAD);
     layer_tree_host_->RecordEndOfFrameMetrics(begin_main_frame_start_time);
@@ -248,8 +246,10 @@
                                   CommitEarlyOutReason::ABORTED_DEFERRED_COMMIT,
                                   begin_main_frame_start_time,
                                   base::Passed(&empty_swap_promises)));
+    current_pipeline_stage_ = NO_PIPELINE_STAGE;
     // We intentionally don't report CommitComplete() here since it was aborted
     // prematurely and we're waiting to do another commit in the future.
+    layer_tree_host_->DidBeginMainFrame();
     // When we stop deferring commits, we should resume any previously requested
     // pipeline stages.
     deferred_final_pipeline_stage_ = final_pipeline_stage_;
@@ -284,8 +284,6 @@
 
   current_pipeline_stage_ = COMMIT_PIPELINE_STAGE;
   if (final_pipeline_stage_ < COMMIT_PIPELINE_STAGE) {
-    current_pipeline_stage_ = NO_PIPELINE_STAGE;
-    layer_tree_host_->DidBeginMainFrame();
     TRACE_EVENT_INSTANT0("cc", "EarlyOut_NoUpdates", TRACE_EVENT_SCOPE_THREAD);
     layer_tree_host_->RecordEndOfFrameMetrics(begin_main_frame_start_time);
     std::vector<std::unique_ptr<SwapPromise>> swap_promises =
@@ -300,7 +298,9 @@
     // Although the commit is internally aborted, this is because it has been
     // detected to be a no-op.  From the perspective of an embedder, this commit
     // went through, and input should no longer be throttled, etc.
+    current_pipeline_stage_ = NO_PIPELINE_STAGE;
     layer_tree_host_->CommitComplete();
+    layer_tree_host_->DidBeginMainFrame();
     return;
   }
 
@@ -314,9 +314,6 @@
   layer_tree_host_->QueueSwapPromise(
       std::make_unique<LatencyInfoSwapPromise>(new_latency_info));
 
-  current_pipeline_stage_ = NO_PIPELINE_STAGE;
-  layer_tree_host_->DidBeginMainFrame();
-
   // Notify the impl thread that the main thread is ready to commit. This will
   // begin the commit process, which is blocking from the main thread's
   // point of view, but asynchronously performed on the impl thread,
@@ -339,7 +336,9 @@
     completion.Wait();
   }
 
+  current_pipeline_stage_ = NO_PIPELINE_STAGE;
   layer_tree_host_->CommitComplete();
+  layer_tree_host_->DidBeginMainFrame();
 }
 
 void ProxyMain::DidPresentCompositorFrame(
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 056cfe70..874079dc 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -231,8 +231,8 @@
       << "Activation is expected to have synchronously occurred by now.";
 
   DebugScopedSetMainThread main(task_runner_provider_);
-  layer_tree_host_->DidBeginMainFrame();
   layer_tree_host_->CommitComplete();
+  layer_tree_host_->DidBeginMainFrame();
 
   next_frame_is_newly_committed_frame_ = true;
 }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedAppLifecycle.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedAppLifecycle.java
index e6d5be9c..55de18d 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedAppLifecycle.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedAppLifecycle.java
@@ -15,6 +15,9 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.signin.SigninManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Aggregation point for application lifecycle events that the Feed cares about. Events that
  * originate in Java flow directly to FeedAppLifecycle, while native-originating events arrive
@@ -22,12 +25,13 @@
  */
 public class FeedAppLifecycle
         implements SigninManager.SignInStateObserver, ApplicationStatus.ActivityStateListener {
-    @IntDef({AppLifecycleEvent.ENTER_FOREGROUND, AppLifecycleEvent.ENTER_BACKGROUND,
-            AppLifecycleEvent.CLEAR_ALL, AppLifecycleEvent.INITIALIZE,
-            AppLifecycleEvent.NUM_ENTRIES})
-
     // Intdef used to assign each event a number for metrics logging purposes. This maps directly to
     // the AppLifecycleEvent enum defined in tools/metrics/enums.xml
+    @IntDef({AppLifecycleEvent.ENTER_FOREGROUND, AppLifecycleEvent.ENTER_BACKGROUND,
+            AppLifecycleEvent.CLEAR_ALL, AppLifecycleEvent.INITIALIZE, AppLifecycleEvent.SIGN_IN,
+            AppLifecycleEvent.SIGN_OUT, AppLifecycleEvent.HISTORY_DELETED,
+            AppLifecycleEvent.CACHED_DATA_CLEARED})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface AppLifecycleEvent {
         int ENTER_FOREGROUND = 0;
         int ENTER_BACKGROUND = 1;
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/StreamLifecycleManager.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/StreamLifecycleManager.java
index bda37b4..f2ad96e 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/StreamLifecycleManager.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/StreamLifecycleManager.java
@@ -29,21 +29,22 @@
  */
 class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener {
     /** The different states that the Stream can be in its lifecycle. */
-    @IntDef({NOT_SPECIFIED, CREATED, SHOWN, ACTIVE, INACTIVE, HIDDEN, DESTROYED})
+    @IntDef({StreamState.NOT_SPECIFIED, StreamState.CREATED, StreamState.SHOWN, StreamState.ACTIVE,
+            StreamState.INACTIVE, StreamState.HIDDEN, StreamState.DESTROYED})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface StreamState {}
+    private @interface StreamState {
+        int NOT_SPECIFIED = -1;
+        int CREATED = 0;
+        int SHOWN = 1;
+        int ACTIVE = 2;
+        int INACTIVE = 3;
+        int HIDDEN = 4;
+        int DESTROYED = 5;
+    }
 
     /** Key for the Stream instance state that may be stored in a navigation entry. */
     private static final String STREAM_SAVED_INSTANCE_STATE_KEY = "StreamSavedInstanceState";
 
-    private static final int NOT_SPECIFIED = -1;
-    private static final int CREATED = 0;
-    private static final int SHOWN = 1;
-    private static final int ACTIVE = 2;
-    private static final int INACTIVE = 3;
-    private static final int HIDDEN = 4;
-    private static final int DESTROYED = 5;
-
     /** The {@link Stream} that this class manages. */
     private final Stream mStream;
 
@@ -60,7 +61,7 @@
     private final TabObserver mTabObserver;
 
     /** The current state the Stream is in its lifecycle. */
-    private @StreamState int mStreamState = NOT_SPECIFIED;
+    private @StreamState int mStreamState = StreamState.NOT_SPECIFIED;
 
     /**
      * @param stream The {@link Stream} that this class manages.
@@ -100,7 +101,7 @@
             }
         };
 
-        mStreamState = CREATED;
+        mStreamState = StreamState.CREATED;
         mStream.onCreate(restoreInstanceState());
         show();
         activate();
@@ -138,7 +139,8 @@
         final int state = ApplicationStatus.getStateForActivity(mActivity);
         // We don't call Stream#onShow to prevent feed services from being warmed up if the user
         // has opted out from article suggestions during the previous session.
-        return (mStreamState == CREATED || mStreamState == HIDDEN) && !mTab.isHidden()
+        return (mStreamState == StreamState.CREATED || mStreamState == StreamState.HIDDEN)
+                && !mTab.isHidden()
                 && (state == ActivityState.STARTED || state == ActivityState.RESUMED)
                 && FeedProcessScopeFactory.areArticlesVisibleDuringSession();
     }
@@ -147,13 +149,14 @@
     private void show() {
         if (!canShow()) return;
 
-        mStreamState = SHOWN;
+        mStreamState = StreamState.SHOWN;
         mStream.onShow();
     }
 
     /** @return Whether the {@link Stream} can be activated. */
     private boolean canActivate() {
-        return (mStreamState == SHOWN || mStreamState == INACTIVE) && mTab.isUserInteractable()
+        return (mStreamState == StreamState.SHOWN || mStreamState == StreamState.INACTIVE)
+                && mTab.isUserInteractable()
                 && ApplicationStatus.getStateForActivity(mActivity) == ActivityState.RESUMED
                 && FeedProcessScopeFactory.areArticlesVisibleDuringSession();
     }
@@ -164,25 +167,27 @@
         show();
         if (!canActivate()) return;
 
-        mStreamState = ACTIVE;
+        mStreamState = StreamState.ACTIVE;
         mStream.onActive();
     }
 
     /** Calls {@link Stream#onInactive()}. */
     private void deactivate() {
-        if (mStreamState != ACTIVE) return;
+        if (mStreamState != StreamState.ACTIVE) return;
 
-        mStreamState = INACTIVE;
+        mStreamState = StreamState.INACTIVE;
         mStream.onInactive();
     }
 
     /** Calls {@link Stream#onHide()}. */
     private void hide() {
-        if (mStreamState == HIDDEN || mStreamState == CREATED || mStreamState == DESTROYED) return;
+        if (mStreamState == StreamState.HIDDEN || mStreamState == StreamState.CREATED
+                || mStreamState == StreamState.DESTROYED)
+            return;
 
         // Make sure the Stream is inactive before setting it to hidden state.
         deactivate();
-        mStreamState = HIDDEN;
+        mStreamState = StreamState.HIDDEN;
         // Save instance state as the Stream begins to hide. This matches the activity lifecycle
         // that instance state is saved as the activity begins to stop.
         saveInstanceState();
@@ -194,11 +199,11 @@
      * anymore.
      */
     void destroy() {
-        if (mStreamState == DESTROYED) return;
+        if (mStreamState == StreamState.DESTROYED) return;
 
         // Make sure the Stream is hidden before setting it to destroyed state.
         hide();
-        mStreamState = DESTROYED;
+        mStreamState = StreamState.DESTROYED;
         mTab.removeObserver(mTabObserver);
         ApplicationStatus.unregisterActivityStateListener(this);
         mStream.onDestroy();
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_header.xml b/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_header.xml
index 91f6bbb..bd934c70 100644
--- a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_header.xml
+++ b/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_header.xml
@@ -8,7 +8,8 @@
     xmlns:tools="http://schemas.android.com/tools">
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="56dp"
+        android:layout_height="wrap_content"
+        android:minHeight="56dp"
         android:gravity="center_vertical"
         android:orientation="horizontal"
         android:paddingStart="24dp"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabTaskDescriptionHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabTaskDescriptionHelper.java
index 4624ea20..01d4bef 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabTaskDescriptionHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabTaskDescriptionHelper.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.content_public.browser.NavigationHandle;
 
 import java.util.List;
 
@@ -90,11 +91,9 @@
             }
 
             @Override
-            public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                    boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                    int httpStatusCode) {
-                if (hasCommitted && isInMainFrame && !isSameDocument) {
+            public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+                if (navigation.hasCommitted() && navigation.isInMainFrame()
+                        && !navigation.isSameDocument()) {
                     mLargestFavicon = null;
                     updateTaskDescription();
                 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index f79c87a3..af0e418 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -151,6 +151,7 @@
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsAccessibility;
@@ -1545,14 +1546,11 @@
             }
 
             @Override
-            public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                    boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                    int httpStatusCode) {
-                if (hasCommitted && isInMainFrame) {
+            public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+                if (navigation.hasCommitted() && navigation.isInMainFrame()) {
                     DataReductionPromoInfoBar.maybeLaunchPromoInfoBar(ChromeTabbedActivity.this,
-                            tab.getWebContents(), url, tab.isShowingErrorPage(),
-                            isFragmentNavigation, httpStatusCode);
+                            tab.getWebContents(), navigation.getUrl(), tab.isShowingErrorPage(),
+                            navigation.isFragmentNavigation(), navigation.httpStatusCode());
                 }
             }
         };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java
index 3d50a330..9deadb5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.MessagePort;
 import org.chromium.content_public.browser.MessagePort.MessageCallback;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
@@ -68,12 +69,9 @@
             private boolean mNavigatedOnce;
 
             @Override
-            public void didFinishNavigation(String url, boolean isInMainFrame, boolean isErrorPage,
-                    boolean hasCommitted, boolean isSameDocument, boolean isFragmentNavigation,
-                    boolean isRendererInitiated, boolean isDownload, Integer pageTransition,
-                    int errorCode, String errorDescription, int httpStatusCode) {
-                if (mNavigatedOnce && hasCommitted && isInMainFrame && !isSameDocument
-                        && mChannel != null) {
+            public void didFinishNavigation(NavigationHandle navigation) {
+                if (mNavigatedOnce && navigation.hasCommitted() && navigation.isInMainFrame()
+                        && !navigation.isSameDocument() && mChannel != null) {
                     webContents.removeObserver(this);
                     disconnectChannel();
                     return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java
index bfe36cb8..ef411bb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifier.java
@@ -23,6 +23,7 @@
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.content_public.browser.NavigationHandle;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -81,16 +82,13 @@
     /** A {@link TabObserver} that checks whether we are on a verified Origin on page navigation. */
     private final TabObserver mVerifyOnPageLoadObserver = new EmptyTabObserver() {
         @Override
-        public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                int httpStatusCode) {
-            if (!hasCommitted || !isInMainFrame) return;
+        public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+            if (!navigation.hasCommitted() || !navigation.isInMainFrame()) return;
             if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TRUSTED_WEB_ACTIVITY)) {
                 assert false : "Shouldn't observe navigation when TWAs are disabled";
                 return;
             }
-            verify(new Origin(url));
+            verify(new Origin(navigation.getUrl()));
         }
     };
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
index d27ed854..cc4f08b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
@@ -24,6 +24,7 @@
 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
 import org.chromium.components.navigation_interception.NavigationParams;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.RenderCoordinates;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
@@ -333,25 +334,21 @@
                     }
 
                     @Override
-                    public void didStartNavigation(String url, boolean isInMainFrame,
-                            boolean isSameDocument, long navigationHandleProxy) {
-                        if (isInMainFrame && !isSameDocument) {
+                    public void didStartNavigation(NavigationHandle navigation) {
+                        if (navigation.isInMainFrame() && !navigation.isSameDocument()) {
+                            String url = navigation.getUrl();
                             mContentDelegate.onMainFrameLoadStarted(
                                     url, !TextUtils.equals(url, mLoadedUrl));
                         }
                     }
 
                     @Override
-                    public void didFinishNavigation(String url, boolean isInMainFrame,
-                            boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                            boolean isFragmentNavigation, boolean isRendererInitiated,
-                            boolean isDownload, Integer pageTransition, int errorCode,
-                            String errorDescription, int httpStatusCode) {
-                        if (hasCommitted && isInMainFrame) {
+                    public void didFinishNavigation(NavigationHandle navigation) {
+                        if (navigation.hasCommitted() && navigation.isInMainFrame()) {
                             mIsProcessingPendingNavigation = false;
-                            mContentDelegate.onMainFrameNavigation(url,
-                                    !TextUtils.equals(url, mLoadedUrl),
-                                    isHttpFailureCode(httpStatusCode));
+                            mContentDelegate.onMainFrameNavigation(navigation.getUrl(),
+                                    !TextUtils.equals(navigation.getUrl(), mLoadedUrl),
+                                    isHttpFailureCode(navigation.httpStatusCode()));
                         }
                     }
                 };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
index ecf159bc..a8224ac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
@@ -26,6 +26,7 @@
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -186,13 +187,11 @@
     }
 
     @Override
-    public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-            boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-            boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-            int httpStatusCode) {
+    public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
         boolean firstNavigation = mFirstCommitTimestamp == 0;
-        boolean isFirstMainFrameCommit = firstNavigation && hasCommitted && !isErrorPage
-                && isInMainFrame && !isSameDocument && !isFragmentNavigation;
+        boolean isFirstMainFrameCommit = firstNavigation && navigation.hasCommitted()
+                && !navigation.isErrorPage() && navigation.isInMainFrame()
+                && !navigation.isSameDocument() && !navigation.isFragmentNavigation();
         if (isFirstMainFrameCommit) mFirstCommitTimestamp = SystemClock.elapsedRealtime();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java
index 5a6862f..3d71630 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java
@@ -44,7 +44,7 @@
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.content_public.browser.LoadUrlParams;
-import org.chromium.content_public.browser.NavigationHandleProxy;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 
@@ -108,49 +108,43 @@
 
     private final EmptyTabObserver mHeaderVisibilityObserver = new EmptyTabObserver() {
         @Override
-        public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                                          boolean isErrorPage, boolean hasCommitted,
-                                          boolean isSameDocument, boolean isFragmentNavigation,
-                                          @Nullable Integer pageTransition, int errorCode,
-                                          int httpStatusCode) {
-            if (!isInMainFrame || !hasCommitted) return;
-            maybeCustomizeCctHeader(url);
+        public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+            if (!navigation.isInMainFrame() || !navigation.hasCommitted()) return;
+            maybeCustomizeCctHeader(navigation.getUrl());
         }
     };
 
     // Update the request's header on module managed URLs.
     private final EmptyTabObserver mCustomRequestHeaderModifier = new EmptyTabObserver() {
         @Override
-        public void onDidStartNavigation(Tab tab, String url, boolean isInMainFrame,
-                boolean isSameDocument, long navigationHandleProxy) {
-            if (!isInMainFrame || isSameDocument) return;
-
-            updateCustomRequestHeader(url, navigationHandleProxy, false /* isRedirect */);
+        public void onDidStartNavigation(Tab tab, NavigationHandle navigation) {
+            updateCustomRequestHeader(navigation, /* isRedirect */ false);
         }
 
         @Override
-        public void onDidRedirectNavigation(
-                Tab tab, String url, boolean isInMainFrame, long navigationHandleProxy) {
-            if (!isInMainFrame) return;
-
-            updateCustomRequestHeader(url, navigationHandleProxy, true /* isRedirect */);
+        public void onDidRedirectNavigation(Tab tab, NavigationHandle navigation) {
+            updateCustomRequestHeader(navigation, /* is_redirect */ true);
         }
 
-        private void updateCustomRequestHeader(
-                String url, long navigationHandleProxy, boolean isRedirect) {
-            if (!ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_MODULE_CUSTOM_REQUEST_HEADER))
+        private void updateCustomRequestHeader(NavigationHandle navigation, boolean isRedirect) {
+            // Update an header only when the navigation emit a network request.²
+            if (!navigation.isInMainFrame() || navigation.isSameDocument()
+                    || navigation.isErrorPage()
+                    || !ChromeFeatureList.isEnabled(
+                            ChromeFeatureList.CCT_MODULE_CUSTOM_REQUEST_HEADER)) {
                 return;
+            }
+
             try (TraceEvent e = TraceEvent.scoped(
                          "DynamicModuleCoordinator.updateCustomRequestHeader")) {
-                if (isModuleManagedUrl(url)) {
+                if (isModuleManagedUrl(navigation.getUrl())) {
                     String headerValue = mIntentDataProvider.getExtraModuleManagedUrlsHeaderValue();
                     if (headerValue != null) {
-                        NavigationHandleProxy.nativeSetRequestHeader(navigationHandleProxy,
+                        navigation.setRequestHeader(
                                 DynamicModuleConstants.MANAGED_URL_HEADER, headerValue);
                     }
                 } else if (isRedirect) {
-                    NavigationHandleProxy.nativeRemoveRequestHeader(
-                            navigationHandleProxy, DynamicModuleConstants.MANAGED_URL_HEADER);
+                    navigation.removeRequestHeader(DynamicModuleConstants.MANAGED_URL_HEADER);
                 }
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
index 5a4860c..91c40b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
@@ -36,6 +36,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.ui.KeyboardVisibilityDelegate;
@@ -313,9 +314,8 @@
             private int mLastDistillerPageIndex;
 
             @Override
-            public void didStartNavigation(String url, boolean isInMainFrame,
-                    boolean isSameDocument, long navigationHandleProxy) {
-                if (!isInMainFrame || isSameDocument) return;
+            public void didStartNavigation(NavigationHandle navigation) {
+                if (!navigation.isInMainFrame() || navigation.isSameDocument()) return;
 
                 // Reader Mode should not pollute the navigation stack. To avoid this, watch for
                 // navigations and prepare to remove any that are "chrome-distiller" urls.
@@ -332,21 +332,20 @@
                 ReaderModeTabInfo tabInfo = mTabStatusMap.get(readerTabId);
                 if (tabInfo == null) return;
 
-                tabInfo.setUrl(url);
-                if (DomDistillerUrlUtils.isDistilledPage(url)) {
+                tabInfo.setUrl(navigation.getUrl());
+                if (DomDistillerUrlUtils.isDistilledPage(navigation.getUrl())) {
                     tabInfo.setStatus(STARTED);
-                    mReaderModePageUrl = url;
+                    mReaderModePageUrl = navigation.getUrl();
                 }
             }
 
             @Override
-            public void didFinishNavigation(String url, boolean isInMainFrame, boolean isErrorPage,
-                    boolean hasCommitted, boolean isSameDocument, boolean isFragmentNavigation,
-                    boolean isRendererInitiated, boolean isDownload, Integer pageTransition,
-                    int errorCode, String errorDescription, int httpStatusCode) {
+            public void didFinishNavigation(NavigationHandle navigation) {
                 // TODO(cjhopman): This should possibly ignore navigations that replace the entry
                 // (like those from history.replaceState()).
-                if (!hasCommitted || !isInMainFrame || isSameDocument) return;
+                if (!navigation.hasCommitted() || !navigation.isInMainFrame()
+                        || navigation.isSameDocument())
+                    return;
 
                 if (mShouldRemovePreviousNavigation) {
                     mShouldRemovePreviousNavigation = false;
@@ -361,7 +360,7 @@
                 if (tabInfo == null) return;
 
                 tabInfo.setStatus(POSSIBLE);
-                if (!TextUtils.equals(url,
+                if (!TextUtils.equals(navigation.getUrl(),
                             DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(
                                     mReaderModePageUrl))) {
                     tabInfo.setStatus(NOT_POSSIBLE);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
index 629b170..af7db4d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainer.java
@@ -30,6 +30,7 @@
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver;
 import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.display.DisplayAndroid;
@@ -115,11 +116,8 @@
     /** Resets the state of the InfoBarContainer when the user navigates. */
     private final TabObserver mTabObserver = new EmptyTabObserver() {
         @Override
-        public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                int httpStatusCode) {
-            if (hasCommitted && isInMainFrame) {
+        public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+            if (navigation.hasCommitted() && navigation.isInMainFrame()) {
                 setHidden(false);
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
index a79d0f6b3..e861be4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
@@ -27,6 +27,7 @@
 import org.chromium.components.url_formatter.UrlFormatter;
 import org.chromium.content_public.browser.MediaSession;
 import org.chromium.content_public.browser.MediaSessionObserver;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.media_session.mojom.MediaSessionAction;
 import org.chromium.services.media_session.MediaImage;
@@ -297,13 +298,12 @@
         }
 
         @Override
-        public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                int httpStatusCode) {
+        public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
             assert tab == mTab;
 
-            if (!hasCommitted || !isInMainFrame || isSameDocument) return;
+            if (!navigation.hasCommitted() || !navigation.isInMainFrame()
+                    || navigation.isSameDocument())
+                return;
 
             String origin = mTab.getUrl();
             try {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
index 4715fe74..4aec465ac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.metrics;
 
 import android.os.SystemClock;
-import android.support.annotation.Nullable;
 
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.metrics.RecordHistogram;
@@ -14,6 +13,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
 import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.content_public.browser.BrowserStartupController;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 
 /**
@@ -68,13 +68,12 @@
                     }
 
                     @Override
-                    public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                            boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                            boolean isFragmentNavigation, @Nullable Integer pageTransition,
-                            int errorCode, int httpStatusCode) {
-                        boolean isTrackedPage = hasCommitted && isInMainFrame && !isErrorPage
-                                && !isSameDocument && !isFragmentNavigation
-                                && UrlUtilities.isHttpOrHttps(url);
+                    public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+                        boolean isTrackedPage = navigation.hasCommitted()
+                                && navigation.isInMainFrame() && !navigation.isErrorPage()
+                                && !navigation.isSameDocument()
+                                && !navigation.isFragmentNavigation()
+                                && UrlUtilities.isHttpOrHttps(navigation.getUrl());
                         registerFinishNavigation(isTrackedPage);
                     }
                 };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandler.java
index 721a326..dd28aca3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandler.java
@@ -12,7 +12,6 @@
 import android.os.Bundle;
 import android.speech.RecognizerIntent;
 import android.support.annotation.IntDef;
-import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 
@@ -24,6 +23,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
 import org.chromium.chrome.browser.util.FeatureUtilities;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.RenderFrameHost;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
@@ -193,11 +193,11 @@
         }
 
         @Override
-        public void didFinishNavigation(String url, boolean isInMainFrame, boolean isErrorPage,
-                boolean hasCommitted, boolean isSameDocument, boolean isFragmentNavigation,
-                boolean isRendererInitiated, boolean isDownload, @Nullable Integer pageTransition,
-                int errorCode, String errorDescription, int httpStatusCode) {
-            if (hasCommitted && isInMainFrame && !isErrorPage) setReceivedUserGesture(url);
+        public void didFinishNavigation(NavigationHandle navigation) {
+            if (navigation.hasCommitted() && navigation.isInMainFrame()
+                    && !navigation.isErrorPage()) {
+                setReceivedUserGesture(navigation.getUrl());
+            }
             destroy();
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
index fa9c0a4..b961ce7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
@@ -1,10 +1,13 @@
+include_rules = {
+  "+content/public/android/java/src/org/chromium/content_public",
+}
+
 specific_include_rules = {
   'Tab\.java': [
     "-chrome",
     "+chrome/android/java/src/org/chromium/chrome/browser/tab",
     "+components/embedder_support/android/java/src/org/chromium/components/embedder_support/view",
     "+components/navigation_interception/android/java/src/org/chromium/components/navigation_interception",
-    "+content/public/android/java/src/org/chromium/content_public",
     "+ui/android/java/src/org/chromium/ui/base",
     "+ui/android/java/src/org/chromium/ui/mojom",
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java
index e4ff56c..9e5fcc9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.tab.Tab.TabHidingType;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.BrowserControlsState;
 
@@ -98,18 +99,13 @@
             Tab tab, boolean isMainFrame, int errorCode, String description, String failingUrl) {}
 
     @Override
-    public void onDidStartNavigation(Tab tab, String url, boolean isInMainFrame,
-            boolean isSameDocument, long navigationHandleProxy) {}
+    public void onDidStartNavigation(Tab tab, NavigationHandle navigationHandle) {}
 
     @Override
-    public void onDidRedirectNavigation(
-            Tab tab, String url, boolean isInMainFrame, long navigationHandleProxy) {}
+    public void onDidRedirectNavigation(Tab tab, NavigationHandle navigationHandle) {}
 
     @Override
-    public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-            boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-            boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-            int httpStatusCode) {}
+    public void onDidFinishNavigation(Tab tab, NavigationHandle navigationHandle) {}
 
     @Override
     public void didFirstVisuallyNonEmptyPaint(Tab tab) {}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabObserver.java
index 49b8b87..0c9948fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabObserver.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.tab;
 
 import android.graphics.Bitmap;
-import android.support.annotation.Nullable;
 import android.view.ContextMenu;
 
 import org.chromium.chrome.browser.TabLoadStatus;
@@ -13,6 +12,7 @@
 import org.chromium.chrome.browser.tab.Tab.TabHidingType;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.BrowserControlsState;
 
@@ -222,52 +222,26 @@
     /**
      * Called when a navigation is started in the WebContents.
      * @param tab The notifying {@link Tab}.
-     * @param url The validated URL for the loading page.
-     * @param isInMainFrame Whether the navigation is for the main frame.
-     * @param isSameDocument Whether the main frame navigation did not cause changes to the
-     *                   document (for example scrolling to a named anchor or PopState).
-     * @param navigationHandleProxy Pointer to a NavigationHandleProxy representing the navigation.
-     *                              Its lifetime is bound to this function. Do not store it. It can
-     *                              be used to modify headers.
+     * @param navigationHandle Pointer to a NavigationHandle representing the navigation.
+     *                         Its lifetime end at the end of onDidFinishNavigation().
      */
-    public void onDidStartNavigation(Tab tab, String url, boolean isInMainFrame,
-            boolean isSameDocument, long navigationHandleProxy);
+    public void onDidStartNavigation(Tab tab, NavigationHandle navigationHandle);
 
     /**
      * Called when a navigation is redirected in the WebContents.
      * @param tab The notifying {@link Tab}.
-     * @param url The validated URL for the loading page.
-     * @param isInMainFrame Whether the navigation is for the main frame.
-     * @param navigationHandleProxy Pointer to a NavigationHandleProxy representing the navigation.
-     *                              Its lifetime is bound to this function. Do not store it. It can
-     *                              be used to modify headers.
+     * @param navigationHandle Pointer to a NavigationHandle representing the navigation.
+     *                         Its lifetime end at the end of onDidFinishNavigation().
      */
-    public void onDidRedirectNavigation(
-            Tab tab, String url, boolean isInMainFrame, long navigationHandleProxy);
+    public void onDidRedirectNavigation(Tab tab, NavigationHandle navigationHandle);
 
     /**
      * Called when a navigation is finished i.e. committed, aborted or replaced by a new one.
      * @param tab The notifying {@link Tab}.
-     * @param url The validated URL for the loading page.
-     * @param isInMainFrame Whether the navigation is for the main frame.
-     * @param isErrorPage Whether the navigation shows an error page.
-     * @param hasCommitted Whether the navigation has committed. This returns true for either
-     *                     successful commits or error pages that replace the previous page
-     *                     (distinguished by |isErrorPage|), and false for errors that leave the
-     *                     user on the previous page.
-     * @param isSameDocument Whether the main frame navigation did not cause changes to the
-     *                   document (for example scrolling to a named anchor or PopState).
-     * @param isFragmentNavigation Whether the main frame navigation did not cause changes
-     *                             to the document (for example scrolling to a named anchor
-     *                             or PopState).
-     * @param pageTransition The page transition type associated with this navigation.
-     * @param errorCode The net error code if an error occurred prior to commit, otherwise net::OK.
-     * @param httpStatusCode The HTTP status code of the navigation.
+     * @param navigationHandle Pointer to a NavigationHandle representing the navigation.
+     *                         Its lifetime end at the end of this function.
      */
-    public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-            boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-            boolean isFragmentNavigation, @Nullable Integer pageTransition, int errorCode,
-            int httpStatusCode);
+    public void onDidFinishNavigation(Tab tab, NavigationHandle navigation);
 
     /**
      * Called when the page has painted something non-empty.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java
index c6684ebe..b44ca18 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.util.AccessibilityUtil;
 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.browser.WebContents;
 
@@ -86,11 +87,8 @@
             }
 
             @Override
-            public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                    boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                    int httpStatusCode) {
-                if (!hasCommitted || !isInMainFrame) return;
+            public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+                if (!navigation.hasCommitted() || !navigation.isInMainFrame()) return;
                 mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD);
                 mHandler.sendEmptyMessageDelayed(
                         MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD, getLoadDelayMs());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java
index 23f714be..e02f7f6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabThemeColorHelper.java
@@ -12,6 +12,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.content_public.browser.NavigationHandle;
 
 /**
  * Manages theme color used for {@link Tab}. Destroyed together with the tab.
@@ -162,11 +163,8 @@
     }
 
     @Override
-    public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-            boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-            boolean isFragmentNavigation, @Nullable Integer pageTransition, int errorCode,
-            int httpStatusCode) {
-        if (errorCode != 0) updateIfNeeded(true);
+    public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+        if (navigation.errorCode() != 0) updateIfNeeded(true);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
index 46a3034..6c66988 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
 import org.chromium.chrome.browser.policy.PolicyAuditor;
 import org.chromium.chrome.browser.policy.PolicyAuditor.AuditEvent;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 
@@ -216,54 +217,46 @@
         }
 
         @Override
-        public void didStartNavigation(String url, boolean isInMainFrame, boolean isSameDocument,
-                long navigationHandleProxy) {
-            if (isInMainFrame && !isSameDocument) {
-                mTab.didStartPageLoad(url);
+        public void didStartNavigation(NavigationHandle navigation) {
+            if (navigation.isInMainFrame() && !navigation.isSameDocument()) {
+                mTab.didStartPageLoad(navigation.getUrl());
             }
 
             RewindableIterator<TabObserver> observers = mTab.getTabObservers();
             while (observers.hasNext()) {
-                observers.next().onDidStartNavigation(
-                        mTab, url, isInMainFrame, isSameDocument, navigationHandleProxy);
+                observers.next().onDidStartNavigation(mTab, navigation);
             }
         }
 
         @Override
-        public void didRedirectNavigation(
-                String url, boolean isInMainFrame, long navigationHandleProxy) {
+        public void didRedirectNavigation(NavigationHandle navigation) {
             RewindableIterator<TabObserver> observers = mTab.getTabObservers();
             while (observers.hasNext()) {
-                observers.next().onDidRedirectNavigation(
-                        mTab, url, isInMainFrame, navigationHandleProxy);
+                observers.next().onDidRedirectNavigation(mTab, navigation);
             }
         }
 
         @Override
-        public void didFinishNavigation(String url, boolean isInMainFrame, boolean isErrorPage,
-                boolean hasCommitted, boolean isSameDocument, boolean isFragmentNavigation,
-                boolean isRendererInitiated, boolean isDownload, Integer pageTransition,
-                int errorCode, String errorDescription, int httpStatusCode) {
+        public void didFinishNavigation(NavigationHandle navigation) {
             RewindableIterator<TabObserver> observers = mTab.getTabObservers();
             while (observers.hasNext()) {
-                observers.next().onDidFinishNavigation(mTab, url, isInMainFrame, isErrorPage,
-                        hasCommitted, isSameDocument, isFragmentNavigation, pageTransition,
-                        errorCode, httpStatusCode);
+                observers.next().onDidFinishNavigation(mTab, navigation);
             }
 
-            if (errorCode != 0) {
-                if (isInMainFrame) mTab.didFailPageLoad(errorCode);
+            if (navigation.errorCode() != 0) {
+                if (navigation.isInMainFrame()) mTab.didFailPageLoad(navigation.errorCode());
 
-                recordErrorInPolicyAuditor(url, errorDescription, errorCode);
+                recordErrorInPolicyAuditor(
+                        navigation.getUrl(), navigation.errorDescription(), navigation.errorCode());
             }
 
-            if (!hasCommitted) return;
+            if (!navigation.hasCommitted()) return;
 
-            if (isInMainFrame) {
+            if (navigation.isInMainFrame()) {
                 mTab.setIsTabStateDirty(true);
                 mTab.updateTitle();
-                mTab.handleDidFinishNavigation(url, pageTransition);
-                mTab.setIsShowingErrorPage(isErrorPage);
+                mTab.handleDidFinishNavigation(navigation.getUrl(), navigation.pageTransition());
+                mTab.setIsShowingErrorPage(navigation.isErrorPage());
 
                 observers.rewind();
                 while (observers.hasNext()) {
@@ -272,11 +265,12 @@
             }
 
             FullscreenManager fullscreenManager = mTab.getFullscreenManager();
-            if (isInMainFrame && !isSameDocument && fullscreenManager != null) {
+            if (navigation.isInMainFrame() && !navigation.isSameDocument()
+                    && fullscreenManager != null) {
                 fullscreenManager.exitPersistentFullscreenMode();
             }
 
-            if (isInMainFrame) {
+            if (navigation.isInMainFrame()) {
                 // Stop swipe-to-refresh animation.
                 SwipeRefreshHandler handler = SwipeRefreshHandler.get(mTab);
                 if (handler != null) handler.didStopRefreshing();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index a1f189f..11d30c4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -109,6 +109,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.AsyncViewProvider;
@@ -566,9 +567,8 @@
             }
 
             @Override
-            public void onDidStartNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isSameDocument, long navigationHandleProxy) {
-                if (!isInMainFrame) return;
+            public void onDidStartNavigation(Tab tab, NavigationHandle navigation) {
+                if (!navigation.isInMainFrame()) return;
                 // Update URL as soon as it becomes available when it's a new tab.
                 // But we want to update only when it's a new tab. So we check whether the current
                 // navigation entry is initial, meaning whether it has the same target URL as the
@@ -579,14 +579,14 @@
                     mLocationBar.setUrlToPageUrl();
                 }
 
-                if (isSameDocument) return;
+                if (navigation.isSameDocument()) return;
                 // This event is used as the primary trigger for the progress bar because it
                 // is the earliest indication that a load has started for a particular frame. In
                 // the case of the progress bar, it should only traverse the screen a single time
                 // per page load. So if this event states the main frame has started loading the
                 // progress bar is started.
 
-                if (NativePageFactory.isNativePageUrl(url, tab.isIncognito())) {
+                if (NativePageFactory.isNativePageUrl(navigation.getUrl(), tab.isIncognito())) {
                     finishLoadProgress(false);
                     return;
                 }
@@ -597,15 +597,13 @@
             }
 
             @Override
-            public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                    boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                    int httpStatusCode) {
-                if (hasCommitted && isInMainFrame && !isSameDocument) {
+            public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+                if (navigation.hasCommitted() && navigation.isInMainFrame()
+                        && !navigation.isSameDocument()) {
                     mToolbar.onNavigatedToDifferentPage();
                 }
 
-                if (hasCommitted && tab.isPreview()) {
+                if (navigation.hasCommitted() && tab.isPreview()) {
                     // Some previews are not fully decided until the page commits. If this
                     // is a preview, update the security icon which will also update the verbose
                     // status view to make sure the "Lite" badge is displayed.
@@ -613,12 +611,13 @@
                     PreviewsUma.recordLitePageAtCommit(
                             PreviewsAndroidBridge.getInstance().getPreviewsType(
                                     tab.getWebContents()),
-                            isInMainFrame);
+                            navigation.isInMainFrame());
                 }
 
                 // If the load failed due to a different navigation, there is no need to reset the
                 // location bar animations.
-                if (errorCode != 0 && isInMainFrame && !hasPendingNonNtpNavigation(tab)) {
+                if (navigation.errorCode() != 0 && navigation.isInMainFrame()
+                        && !hasPendingNonNtpNavigation(tab)) {
                     NewTabPage ntp = mLocationBarModel.getNewTabPageForCurrentTab();
                     if (ntp == null) return;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkSplashNetworkErrorObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkSplashNetworkErrorObserver.java
index 86e28e1..3e58a11 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkSplashNetworkErrorObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkSplashNetworkErrorObserver.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.metrics.WebApkUma;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.net.NetError;
 import org.chromium.net.NetworkChangeNotifier;
 
@@ -37,13 +38,10 @@
     }
 
     @Override
-    public void onDidFinishNavigation(final Tab tab, final String url, boolean isInMainFrame,
-            boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-            boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-            int httpStatusCode) {
-        if (!isInMainFrame) return;
+    public void onDidFinishNavigation(final Tab tab, NavigationHandle navigation) {
+        if (!navigation.isInMainFrame()) return;
 
-        switch (errorCode) {
+        switch (navigation.errorCode()) {
             case ERROR_OK:
                 mDelegate.hideWebApkNetworkErrorDialog();
                 break;
@@ -51,10 +49,10 @@
                 onNetworkChanged(tab);
                 break;
             default:
-                onNetworkError(tab, errorCode);
+                onNetworkError(tab, navigation.errorCode());
                 break;
         }
-        WebApkUma.recordNetworkErrorWhenLaunch(-errorCode);
+        WebApkUma.recordNetworkErrorWhenLaunch(-navigation.errorCode());
     }
 
     private void onNetworkChanged(Tab tab) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index 01c0bb6..bfdd9e4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -57,6 +57,7 @@
 import org.chromium.chrome.browser.widget.TintedDrawable;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.ScreenOrientationProvider;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
@@ -631,23 +632,20 @@
 
     protected TabObserver createTabObserver() {
         return new EmptyTabObserver() {
-
             @Override
-            public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                    boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                    int httpStatusCode) {
-                if (hasCommitted && isInMainFrame) {
+            public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+                if (navigation.hasCommitted() && navigation.isInMainFrame()) {
                     // Notify the renderer to permanently hide the top controls since they do
                     // not apply to fullscreen content views.
                     tab.updateBrowserControlsState(tab.getBrowserControlsStateConstraints(), true);
 
                     RecordHistogram.recordBooleanHistogram(
-                            HISTOGRAM_NAVIGATION_STATUS, !isErrorPage);
+                            HISTOGRAM_NAVIGATION_STATUS, !navigation.isErrorPage());
 
                     updateToolbarCloseButtonVisibility();
 
-                    if (!WebappScopePolicy.isUrlInScope(scopePolicy(), mWebappInfo, url)) {
+                    if (!WebappScopePolicy.isUrlInScope(
+                                scopePolicy(), mWebappInfo, navigation.getUrl())) {
                         // Briefly show the toolbar for off-scope navigations.
                         getFullscreenManager()
                                 .getBrowserVisibilityDelegate()
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index 71b9f62..96ce5d2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -38,6 +38,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TouchCommon;
@@ -96,11 +97,8 @@
         }
 
         @Override
-        public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                int httpStatusCode) {
-            if (errorCode == 0) return;
+        public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
+            if (navigation.errorCode() == 0) return;
             mFailCallback.notifyCalled();
         }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java
index c942710a..eefed199 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/InfoBarAppearanceTest.java
@@ -25,6 +25,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.InfoBarTestAnimationListener;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 
 import java.util.List;
@@ -102,8 +103,7 @@
         CallbackHelper callbackHelper = new CallbackHelper();
         EmptyTabObserver navigationWaiter = new EmptyTabObserver() {
             @Override
-            public void onDidStartNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isSameDocument, long navigationHandleProxy) {
+            public void onDidStartNavigation(Tab tab, NavigationHandle navigation) {
                 callbackHelper.notifyCalled();
             }
         };
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java
index f1fec86..3a47793a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java
@@ -26,6 +26,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
@@ -239,10 +240,7 @@
         }
 
         @Override
-        public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                int httpStatusCode) {
+        public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
             mNavigationOccurred = true;
         }
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
index 76b87a4c..d00dbb80 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 
 /**
@@ -63,10 +64,7 @@
         final CallbackHelper callbackHelper = new CallbackHelper();
         EmptyTabObserver navigationWaiter = new EmptyTabObserver() {
             @Override
-            public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
-                    boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
-                    boolean isFragmentNavigation, Integer pageTransition, int errorCode,
-                    int httpStatusCode) {
+            public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
                 callbackHelper.notifyCalled();
             }
         };
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifierTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifierTest.java
index 86dab1b..969745f2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifierTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifierTest.java
@@ -43,6 +43,7 @@
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
+import org.chromium.content_public.browser.NavigationHandle;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -228,8 +229,18 @@
 
     private void navigateToUrl(String url) {
         when(mTab.getUrl()).thenReturn(url);
+        NavigationHandle navigation =
+                new NavigationHandle(0 /* navigationHandleProxy */, url, true /* isMainFrame */,
+                        false /* isSameDocument */, false /* isRendererInitiated */);
         for (TabObserver tabObserver : mTabObserverCaptor.getAllValues()) {
-            tabObserver.onDidFinishNavigation(mTab, url, true, false, true, true, false, 0, 0, 0);
+            tabObserver.onDidStartNavigation(mTab, navigation);
+        }
+
+        navigation.didFinish(url, false /* isErrorPage */, true /* hasCommitted */,
+                false /* isFragmentNavigation */, false /* isDownload */, 0 /* pageTransition */,
+                0 /* errorCode*/, 200 /* httpStatusCode*/);
+        for (TabObserver tabObserver : mTabObserverCaptor.getAllValues()) {
+            tabObserver.onDidFinishNavigation(mTab, navigation);
         }
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
index b6ac764..93ebf3e 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.favicon.LargeIconBridge;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.MediaSession;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.services.media_session.MediaMetadata;
 
@@ -97,9 +98,14 @@
 
     public void simulateNavigation(String url, boolean isSameDocument) {
         mUrl = url;
-        mMediaSessionTabHelper.mTabObserver.onDidFinishNavigation(mTab, url,
-                true /* isInMainFrame */, false /* isErrorPage */, true /* hasCommitted */,
-                isSameDocument, false /* isFragmentNavigation */, 0 /* pageTransition */,
+
+        NavigationHandle navigation = new NavigationHandle(0 /* navigationHandleProxy */, url,
+                true /* isInMainFrame */, isSameDocument, false /* isRendererInitiated */);
+        mMediaSessionTabHelper.mTabObserver.onDidStartNavigation(mTab, navigation);
+
+        navigation.didFinish(url, false /* isErrorPage */, true /* hasCommitted */,
+                false /* isFragmentNavigation */, false /* isDownload */, 0 /* pageTransition */,
                 0 /* errorCode */, 200 /* httpStatusCode */);
+        mMediaSessionTabHelper.mTabObserver.onDidFinishNavigation(mTab, navigation);
     }
 }
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index ec4bc38..09832433 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -459,6 +459,7 @@
       "//chromeos/services/media_perception/public/mojom",
       "//chromeos/services/multidevice_setup/public/cpp:manifest",
       "//chromeos/services/multidevice_setup/public/mojom",
+      "//media/capture/video/chromeos/mojo:cros_camera",
       "//ui/accessibility/mojom",
     ]
 
diff --git a/chrome/app/DEPS b/chrome/app/DEPS
index fb87c9d..7ea4725 100644
--- a/chrome/app/DEPS
+++ b/chrome/app/DEPS
@@ -62,6 +62,7 @@
     "+components/translate/content/common",
     "+extensions/buildflags",
     "+extensions/common",
+    "+media/capture/video/chromeos/mojo",
     "+services/identity",
     "+services/image_annotation/public",
     "+services/resource_coordinator/public",
diff --git a/chrome/app/chrome_content_browser_overlay_manifest.cc b/chrome/app/chrome_content_browser_overlay_manifest.cc
index 4eb0aa1..0679ebba 100644
--- a/chrome/app/chrome_content_browser_overlay_manifest.cc
+++ b/chrome/app/chrome_content_browser_overlay_manifest.cc
@@ -59,6 +59,7 @@
 #include "chromeos/services/media_perception/public/mojom/media_perception.mojom.h"
 #include "chromeos/services/multidevice_setup/public/cpp/manifest.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
 #include "services/ws/common/switches.h"
 #include "ui/accessibility/mojom/ax_host.mojom.h"  // nogncheck
 #if BUILDFLAG(ENABLE_CROS_ASSISTANT)
@@ -178,6 +179,7 @@
             .RequireCapability("unzip", "unzip_file")
             .RequireCapability("util_win", "util_win")
             .RequireCapability("wifi_util_win", "wifi_credentials")
+            .RequireCapability("video_capture", "capture")
             .RequireCapability("xr_device_service", "xr_device_provider")
             .RequireCapability("xr_device_service", "xr_device_test_hook")
 #if defined(OS_CHROMEOS)
@@ -206,6 +208,7 @@
 #if defined(OS_CHROMEOS)
                     chromeos::ime::mojom::InputEngineManager,
                     chromeos::media_perception::mojom::MediaPerception,
+                    cros::mojom::CrosImageCapture,
 #endif
                     contextual_search::mojom::ContextualSearchJsApiService,
                     dom_distiller::mojom::DistillabilityService,
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 4ac5ee1..b201111 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -3369,6 +3369,9 @@
   <message name="IDS_CROSTINI_INSTALLER_INSTALLING" desc="Text shown in the Crostini installer dialog while  Linux is installing.">
     Installing Linux...
   </message>
+  <message name="IDS_CROSTINI_INSTALLER_CANCELING" desc="Text shown in the Crostini installer dialog when the install is canceled.">
+    Canceling...
+  </message>
   <message name="IDS_CROSTINI_INSTALLER_ERROR_TITLE" desc="Title of the Crostini installer when an error occurs.">
     Error installing Linux...
   </message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 21d46d6..2c4b8b61 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4215,6 +4215,14 @@
      FEATURE_VALUE_TYPE(chromeos::features::kUserActivityPredictionMlService)},
 #endif  // defined(OS_CHROMEOS)
 
+#if defined(OS_ANDROID)
+    {"manual-password-generation-android",
+     flag_descriptions::kManualPasswordGenerationAndroidName,
+     flag_descriptions::kManualPasswordGenerationAndroidDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(
+         password_manager::features::kManualPasswordGenerationAndroid)},
+#endif  // defined(OS_ANDROID)
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/android/browsing_data/browsing_data_bridge.cc b/chrome/browser/android/browsing_data/browsing_data_bridge.cc
index 16dc93ab..4091cdf 100644
--- a/chrome/browser/android/browsing_data/browsing_data_bridge.cc
+++ b/chrome/browser/android/browsing_data/browsing_data_bridge.cc
@@ -28,10 +28,10 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_features.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/browsing_data/core/history_notice_utils.h"
 #include "components/browsing_data/core/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
 #include "content/public/browser/browsing_data_remover.h"
diff --git a/chrome/browser/app_controller_mac_unittest.mm b/chrome/browser/app_controller_mac_unittest.mm
index 565c194a..3327cc8 100644
--- a/chrome/browser/app_controller_mac_unittest.mm
+++ b/chrome/browser/app_controller_mac_unittest.mm
@@ -13,16 +13,11 @@
 #import "chrome/browser/app_controller_mac.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/signin/signin_error_controller_factory.h"
-#include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "components/browser_sync/profile_sync_service.h"
-#include "components/browser_sync/profile_sync_service_mock.h"
-#include "components/signin/core/browser/signin_error_controller.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/platform_test.h"
 
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index f15d9d1..c116945 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -475,6 +475,7 @@
       <include name="IDR_SITE_ENGAGEMENT_JS" file="resources\engagement\site_engagement.js" flattenhtml="true" type="BINDATA" compress="gzip" />
       <include name="IDR_SITE_ENGAGEMENT_MOJO_JS" file="${root_gen_dir}\chrome\browser\engagement\site_engagement_details.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" />
       <include name="IDR_URL_MOJO_JS" file="${root_gen_dir}\url\mojom\url.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" />
+      <include name="IDR_URL_MOJO_LITE_JS" file="${root_gen_dir}\url\mojom\url.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
       <include name="IDR_SYNC_CONFIRMATION_CSS" file="resources\signin\sync_confirmation\sync_confirmation.css" type="BINDATA" />
       <include name="IDR_SYNC_CONFIRMATION_HTML" file="resources\signin\sync_confirmation\sync_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
       <include name="IDR_SYNC_CONFIRMATION_JS" file="resources\signin\sync_confirmation\sync_confirmation.js" type="BINDATA" />
@@ -491,8 +492,8 @@
       <include name="IDR_USB_INTERNALS_CSS" file="resources\usb_internals\usb_internals.css" type="BINDATA" compress="gzip" />
       <include name="IDR_USB_INTERNALS_HTML" file="resources\usb_internals\usb_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
       <include name="IDR_USB_INTERNALS_JS" file="resources\usb_internals\usb_internals.js" type="BINDATA" compress="gzip" />
-      <include name="IDR_USB_INTERNALS_MOJO_JS" file="${root_gen_dir}\chrome\browser\ui\webui\usb_internals\usb_internals.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" />
-      <include name="IDR_USB_DEVICE_MANAGER_TEST_MOJO_JS" file="${root_gen_dir}\device\usb\public\mojom\device_manager_test.mojom.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_INTERNALS_MOJO_JS" file="${root_gen_dir}\chrome\browser\ui\webui\usb_internals\usb_internals.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
+      <include name="IDR_USB_DEVICE_MANAGER_TEST_MOJO_JS" file="${root_gen_dir}\device\usb\public\mojom\device_manager_test.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
       <include name="IDR_WEBRTC_LOGS_HTML" file="resources\media\webrtc_logs.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
       <include name="IDR_WEBRTC_LOGS_JS" file="resources\media\webrtc_logs.js" type="BINDATA" />
       <include name="IDR_WEBSTORE_MANIFEST" file="resources\webstore_app\manifest.json" type="BINDATA" />
diff --git a/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc b/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc
index 29c25a0..69fa7f2 100644
--- a/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc
+++ b/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc
@@ -22,7 +22,6 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/web_data_service_factory.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/browsing_data/core/counters/autofill_counter.h"
 #include "components/browsing_data/core/counters/browsing_data_counter.h"
 #include "components/browsing_data/core/counters/history_counter.h"
@@ -30,6 +29,7 @@
 #include "components/browsing_data/core/pref_names.h"
 #include "components/history/core/browser/web_history_service.h"
 #include "components/password_manager/core/browser/password_store.h"
+#include "components/sync/driver/sync_service.h"
 #include "extensions/buildflags/buildflags.h"
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc b/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc
index 9cbcf094..76970685 100644
--- a/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc
+++ b/chrome/browser/browsing_data/counters/browsing_data_counter_utils_unittest.cc
@@ -15,8 +15,8 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/password_manager/core/browser/test_password_store.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "extensions/buildflags/buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/browsing_data/counters/site_data_counter.cc b/chrome/browser/browsing_data/counters/site_data_counter.cc
index a1e4647..6d251567 100644
--- a/chrome/browser/browsing_data/counters/site_data_counter.cc
+++ b/chrome/browser/browsing_data/counters/site_data_counter.cc
@@ -8,8 +8,8 @@
 #include "chrome/browser/browsing_data/counters/browsing_data_counter_utils.h"
 #include "chrome/browser/browsing_data/counters/site_data_counting_helper.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/browsing_data/core/pref_names.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/browser_thread.h"
 
 using content::BrowserThread;
diff --git a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
index d66c703..8e77dfe2 100644
--- a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
+++ b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
@@ -24,6 +24,7 @@
 #include "base/task/task_traits.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_paths.h"
@@ -46,6 +47,11 @@
 constexpr base::FilePath::CharType kAndroidDownloadDir[] =
     FILE_PATH_LITERAL("/storage/emulated/0/Download");
 
+// The MyFiles path inside ARC container. This will be the path that is used in
+// MediaScanner.scanFile request.
+constexpr base::FilePath::CharType kAndroidMyFilesDir[] =
+    FILE_PATH_LITERAL("/storage/MyFiles");
+
 // The removable media path in ChromeOS. This is the actual directory to be
 // watched.
 constexpr base::FilePath::CharType kCrosRemovableMediaDir[] =
@@ -400,6 +406,7 @@
 
   StopWatchingFileSystem();
   DCHECK(!downloads_watcher_);
+  DCHECK(!myfiles_watcher_);
   DCHECK(!removable_media_watcher_);
 
   arc_bridge_service_->file_system()->RemoveObserver(this);
@@ -418,40 +425,52 @@
 void ArcFileSystemWatcherService::StartWatchingFileSystem() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   StopWatchingFileSystem();
+  Profile* profile = Profile::FromBrowserContext(context_);
+
   DCHECK(!downloads_watcher_);
-  downloads_watcher_ = std::make_unique<FileSystemWatcher>(
-      context_,
-      base::Bind(&ArcFileSystemWatcherService::OnFileSystemChanged,
-                 weak_ptr_factory_.GetWeakPtr()),
-      DownloadPrefs(Profile::FromBrowserContext(context_))
+  downloads_watcher_ = CreateAndStartFileSystemWatcher(
+      DownloadPrefs(profile)
           .GetDefaultDownloadDirectoryForProfile()
           .StripTrailingSeparators(),
       base::FilePath(kAndroidDownloadDir));
-  file_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&FileSystemWatcher::Start,
-                                base::Unretained(downloads_watcher_.get())));
+
+  DCHECK(!myfiles_watcher_);
+  myfiles_watcher_ = CreateAndStartFileSystemWatcher(
+      file_manager::util::GetMyFilesFolderForProfile(profile),
+      base::FilePath(kAndroidMyFilesDir));
+
   DCHECK(!removable_media_watcher_);
-  removable_media_watcher_ = std::make_unique<FileSystemWatcher>(
-      context_,
-      base::Bind(&ArcFileSystemWatcherService::OnFileSystemChanged,
-                 weak_ptr_factory_.GetWeakPtr()),
+  removable_media_watcher_ = CreateAndStartFileSystemWatcher(
       base::FilePath(kCrosRemovableMediaDir),
       base::FilePath(kAndroidRemovableMediaDir));
-  file_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&FileSystemWatcher::Start,
-                     base::Unretained(removable_media_watcher_.get())));
 }
 
 void ArcFileSystemWatcherService::StopWatchingFileSystem() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (downloads_watcher_)
     file_task_runner_->DeleteSoon(FROM_HERE, downloads_watcher_.release());
+  if (myfiles_watcher_)
+    file_task_runner_->DeleteSoon(FROM_HERE, myfiles_watcher_.release());
   if (removable_media_watcher_)
     file_task_runner_->DeleteSoon(FROM_HERE,
                                   removable_media_watcher_.release());
 }
 
+std::unique_ptr<ArcFileSystemWatcherService::FileSystemWatcher>
+ArcFileSystemWatcherService::CreateAndStartFileSystemWatcher(
+    const base::FilePath& cros_path,
+    const base::FilePath& android_path) {
+  auto watcher = std::make_unique<FileSystemWatcher>(
+      context_,
+      base::BindRepeating(&ArcFileSystemWatcherService::OnFileSystemChanged,
+                          weak_ptr_factory_.GetWeakPtr()),
+      base::FilePath(cros_path), base::FilePath(android_path));
+  file_task_runner_->PostTask(FROM_HERE,
+                              base::BindOnce(&FileSystemWatcher::Start,
+                                             base::Unretained(watcher.get())));
+  return watcher;
+}
+
 void ArcFileSystemWatcherService::OnFileSystemChanged(
     const std::vector<std::string>& paths) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
diff --git a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.h b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.h
index d68f8919..1367ba78 100644
--- a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.h
+++ b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.h
@@ -62,12 +62,16 @@
   void StartWatchingFileSystem();
   void StopWatchingFileSystem();
 
+  std::unique_ptr<FileSystemWatcher> CreateAndStartFileSystemWatcher(
+      const base::FilePath& cros_path,
+      const base::FilePath& android_path);
   void OnFileSystemChanged(const std::vector<std::string>& paths);
 
   content::BrowserContext* const context_;
   ArcBridgeService* const arc_bridge_service_;  // Owned by ArcServiceManager.
 
   std::unique_ptr<FileSystemWatcher> downloads_watcher_;
+  std::unique_ptr<FileSystemWatcher> myfiles_watcher_;
   std::unique_ptr<FileSystemWatcher> removable_media_watcher_;
 
   scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
diff --git a/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.cc b/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.cc
index 5340715..6f1f6e0 100644
--- a/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.cc
+++ b/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.cc
@@ -343,8 +343,7 @@
 
   if (state_ != State::kDisabled) {
     DLOG(WARNING) << "Cannot start tracing, it is already enabled.";
-    if (callback)
-      std::move(callback).Run(false /*success*/);
+    std::move(callback).Run(false /*success*/);
     return;
   }
   state_ = State::kStarting;
@@ -391,8 +390,7 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK_EQ(State::kStarting, state_);
   state_ = success ? State::kEnabled : State::kDisabled;
-  if (callback)
-    std::move(callback).Run(success);
+  std::move(callback).Run(success);
 }
 
 void ArcTracingBridge::StopAndFlush(TraceDataCallback callback) {
@@ -463,9 +461,10 @@
 
 void ArcTracingBridge::ArcTracingAgent::StartTracing(
     const std::string& config,
-    base::TimeTicks coordinator_time) {
+    base::TimeTicks coordinator_time,
+    Agent::StartTracingCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  bridge_->StartTracing(config, SuccessCallback());
+  bridge_->StartTracing(config, std::move(callback));
 }
 
 void ArcTracingBridge::ArcTracingAgent::StopAndFlush(
diff --git a/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.h b/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.h
index e77f8a4..dc321c2 100644
--- a/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.h
+++ b/chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.h
@@ -78,7 +78,8 @@
 
     // tracing::mojom::Agent.
     void StartTracing(const std::string& config,
-                      base::TimeTicks coordinator_time) override;
+                      base::TimeTicks coordinator_time,
+                      Agent::StartTracingCallback callback) override;
     void StopAndFlush(tracing::mojom::RecorderPtr recorder) override;
 
     void OnTraceData(const std::string& data);
diff --git a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
index f780e696..61f576e 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
@@ -197,3 +197,7 @@
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ListContainer) {
   RunGeneratedTest("/foreground/js/ui/list_container_unittest.html");
 }
+
+IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTableList) {
+  RunGeneratedTest("/foreground/js/ui/file_table_list_unittest.html");
+}
diff --git a/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc b/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc
index 7fc9ffa..bd19f8b 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc
+++ b/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc
@@ -22,7 +22,8 @@
     : delegate_(delegate),
       identity_manager_(identity_manager),
       primary_account_id_(primary_account_id),
-      access_token_(oauthlogin_access_token) {
+      access_token_(oauthlogin_access_token),
+      weak_ptr_factory_(this) {
   DCHECK(delegate);
   identity_manager_->AddObserver(this);
 }
@@ -47,13 +48,19 @@
 
 void OAuth2LoginVerifier::VerifyProfileTokens() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  GaiaCookieManagerService::AddAccountToCookieCompletedCallback
+      completion_callback =
+          base::BindOnce(&OAuth2LoginVerifier::OnAddAccountToCookieCompleted,
+                         weak_ptr_factory_.GetWeakPtr());
   if (access_token_.empty()) {
     identity_manager_->GetAccountsCookieMutator()->AddAccountToCookie(
-        primary_account_id_, gaia::GaiaSource::kOAuth2LoginVerifier);
+        primary_account_id_, gaia::GaiaSource::kOAuth2LoginVerifier,
+        std::move(completion_callback));
   } else {
     identity_manager_->GetAccountsCookieMutator()->AddAccountToCookieWithToken(
         primary_account_id_, access_token_,
-        gaia::GaiaSource::kOAuth2LoginVerifier);
+        gaia::GaiaSource::kOAuth2LoginVerifier, std::move(completion_callback));
   }
 }
 
diff --git a/chrome/browser/chromeos/login/signin/oauth2_login_verifier.h b/chrome/browser/chromeos/login/signin/oauth2_login_verifier.h
index e2f2d92a..6eb4ae1 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_login_verifier.h
+++ b/chrome/browser/chromeos/login/signin/oauth2_login_verifier.h
@@ -11,6 +11,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile.h"
 #include "services/identity/public/cpp/identity_manager.h"
 
@@ -53,18 +54,20 @@
 
  private:
   // IdentityManager::Observer
-  void OnAddAccountToCookieCompleted(
-      const std::string& account_id,
-      const GoogleServiceAuthError& error) override;
   void OnAccountsInCookieUpdated(
       const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
       const GoogleServiceAuthError& error) override;
 
+  void OnAddAccountToCookieCompleted(const std::string& account_id,
+                                     const GoogleServiceAuthError& error);
+
   OAuth2LoginVerifier::Delegate* delegate_;
   identity::IdentityManager* identity_manager_;
   const std::string primary_account_id_;
   const std::string access_token_;
 
+  base::WeakPtrFactory<OAuth2LoginVerifier> weak_ptr_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(OAuth2LoginVerifier);
 };
 
diff --git a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc
index 18fab36..2e8d9e8 100644
--- a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc
+++ b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc
@@ -137,8 +137,6 @@
   user_manager::ScopedUserManager user_manager_enabler_;
   chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
   network::TestURLLoaderFactory test_url_loader_factory_;
-  scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
-      test_shared_loader_factory_;
   TestingProfileManager profile_manager_;
 };
 
@@ -184,8 +182,8 @@
   return invalidation_service_set_count;
 }
 
-const invalidation::InvalidationService*
-FakeConsumer::GetInvalidationService() const {
+const invalidation::InvalidationService* FakeConsumer::GetInvalidationService()
+    const {
   return invalidation_service_;
 }
 
@@ -195,9 +193,6 @@
       profile_invalidation_service_(nullptr),
       fake_user_manager_(new chromeos::FakeChromeUserManager),
       user_manager_enabler_(base::WrapUnique(fake_user_manager_)),
-      test_shared_loader_factory_(
-          base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-              &test_url_loader_factory_)),
       profile_manager_(TestingBrowserProcess::GetGlobal()) {
   cros_settings_test_helper_.InstallAttributes()->SetCloudManaged("example.com",
                                                                   "device_id");
@@ -209,7 +204,7 @@
   ASSERT_TRUE(profile_manager_.SetUp());
 
   chromeos::DeviceOAuth2TokenServiceFactory::Initialize(
-      test_shared_loader_factory_,
+      test_url_loader_factory_.GetSafeWeakWrapper(),
       TestingBrowserProcess::GetGlobal()->local_state());
 
   invalidation::DeprecatedProfileInvalidationProviderFactory::GetInstance()
@@ -223,7 +218,6 @@
   consumer_.reset();
   provider_->Shutdown();
   provider_.reset();
-  test_shared_loader_factory_->Detach();
 
   invalidation::DeprecatedProfileInvalidationProviderFactory::GetInstance()
       ->RegisterTestingFactory(
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 9f157580..548dbe0 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -421,6 +421,8 @@
     "blacklist_state_fetcher.h",
     "bookmark_app_experimental_navigation_throttle.cc",
     "bookmark_app_experimental_navigation_throttle.h",
+    "bookmark_app_extension_util.cc",
+    "bookmark_app_extension_util.h",
     "bookmark_app_helper.cc",
     "bookmark_app_helper.h",
     "bookmark_app_navigation_throttle.cc",
@@ -876,6 +878,7 @@
     "//services/network/public/mojom",
     "//services/service_manager/public/cpp",
     "//services/service_manager/public/mojom",
+    "//services/video_capture/public/mojom:constants",
     "//skia",
     "//sql",
     "//storage/browser",
@@ -982,10 +985,10 @@
       "//components/constrained_window",
       "//components/drive",
       "//components/user_manager",
+      "//media/capture/video/chromeos/mojo:cros_camera",
       "//remoting/base",
       "//remoting/host",
       "//remoting/host/it2me:chrome_os_host",
-      "//services/video_capture/public/mojom:constants",
       "//third_party/protobuf:protobuf_lite",
       "//ui/chromeos",
       "//ui/chromeos/events",
diff --git a/chrome/browser/extensions/api/debugger/debugger_api.cc b/chrome/browser/extensions/api/debugger/debugger_api.cc
index e6c793b..529a9277 100644
--- a/chrome/browser/extensions/api/debugger/debugger_api.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_api.cc
@@ -525,7 +525,7 @@
       GetProfile(), agent_host_.get(), extension(), debuggee_);
 
   if (!host->Attach()) {
-    FormatErrorMessage(debugger_api_constants::kRestrictedError);
+    error_ = debugger_api_constants::kRestrictedError;
     return false;
   }
 
diff --git a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc
index 945c5a1..b60f7e4 100644
--- a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc
@@ -162,7 +162,7 @@
   // Add a rule.
   api::events::Rule rule;
   api::events::Rule::Populate(
-      *base::test::ParseJson(
+      *base::test::ParseJsonDeprecated(
           "{\n"
           "  \"id\": \"rule1\",\n"
           "  \"priority\": 100,\n"
@@ -178,8 +178,8 @@
       &rule);
   std::vector<const api::events::Rule*> rules({&rule});
 
-  const Extension* extension = env()->MakeExtension(*base::test::ParseJson(
-      "{\"page_action\": {}}"));
+  const Extension* extension = env()->MakeExtension(
+      *base::test::ParseJsonDeprecated("{\"page_action\": {}}"));
   registry->AddRulesImpl(extension->id(), rules);
 
   registry->DidFinishNavigation(tab.get(), &navigation_handle);
diff --git a/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
index b6d947e..42e49361 100644
--- a/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
@@ -33,7 +33,7 @@
 namespace extensions {
 namespace {
 
-using base::test::ParseJson;
+using base::test::ParseJsonDeprecated;
 using testing::HasSubstr;
 
 std::unique_ptr<base::DictionaryValue> SimpleManifest() {
@@ -75,13 +75,15 @@
 
   // Test wrong data type passed.
   error.clear();
-  result = ContentAction::Create(&profile, nullptr, *ParseJson("[]"), &error);
+  result = ContentAction::Create(&profile, nullptr, *ParseJsonDeprecated("[]"),
+                                 &error);
   EXPECT_THAT(error, HasSubstr("missing instanceType"));
   EXPECT_FALSE(result.get());
 
   // Test missing instanceType element.
   error.clear();
-  result = ContentAction::Create(&profile, nullptr, *ParseJson("{}"), &error);
+  result = ContentAction::Create(&profile, nullptr, *ParseJsonDeprecated("{}"),
+                                 &error);
   EXPECT_THAT(error, HasSubstr("missing instanceType"));
   EXPECT_FALSE(result.get());
 
@@ -89,9 +91,10 @@
   error.clear();
   result = ContentAction::Create(
       &profile, nullptr,
-      *ParseJson("{\n"
-                 "  \"instanceType\": \"declarativeContent.UnknownType\",\n"
-                 "}"),
+      *ParseJsonDeprecated(
+          "{\n"
+          "  \"instanceType\": \"declarativeContent.UnknownType\",\n"
+          "}"),
       &error);
   EXPECT_THAT(error, HasSubstr("invalid instanceType"));
   EXPECT_FALSE(result.get());
@@ -118,9 +121,10 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       &profile, extension.get(),
-      *ParseJson("{\n"
-                 "  \"instanceType\": \"declarativeContent.ShowAction\",\n"
-                 "}"),
+      *ParseJsonDeprecated(
+          "{\n"
+          "  \"instanceType\": \"declarativeContent.ShowAction\",\n"
+          "}"),
       &error);
   EXPECT_THAT(error, testing::HasSubstr("without an action"));
   ASSERT_FALSE(result.get());
@@ -145,7 +149,8 @@
   TestingProfile profile;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       nullptr, extension.get(),
-      *ParseJson(R"({"instanceType": "declarativeContent.ShowAction"})"),
+      *ParseJsonDeprecated(
+          R"({"instanceType": "declarativeContent.ShowAction"})"),
       &error);
   EXPECT_TRUE(error.empty()) << error;
   ASSERT_TRUE(result.get());
@@ -219,8 +224,8 @@
           .Set("imageData", DictionaryBuilder().Set("19", data64).Build())
           .Build();
 
-  const Extension* extension = env.MakeExtension(
-      *ParseJson("{\"page_action\": { \"default_title\": \"Extension\" } }"));
+  const Extension* extension = env.MakeExtension(*ParseJsonDeprecated(
+      "{\"page_action\": { \"default_title\": \"Extension\" } }"));
   base::HistogramTester histogram_tester;
   TestingProfile profile;
   std::string error;
@@ -278,8 +283,8 @@
           .Build();
 
   // Expect an error and no instance to be created.
-  const Extension* extension = env.MakeExtension(
-      *ParseJson(R"({"page_action": {"default_title": "Extension"}})"));
+  const Extension* extension = env.MakeExtension(*ParseJsonDeprecated(
+      R"({"page_action": {"default_title": "Extension"}})"));
   base::HistogramTester histogram_tester;
   TestingProfile profile;
   std::string error;
@@ -302,7 +307,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"allFrames\": true,\n"
@@ -318,7 +323,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"css\": [\"style.css\"]\n"
@@ -333,7 +338,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"js\": [\"script.js\"]\n"
@@ -348,7 +353,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"css\": \"style.css\"\n"
@@ -362,7 +367,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"js\": \"script.js\"\n"
@@ -376,7 +381,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"js\": [\"script.js\"],\n"
@@ -392,7 +397,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"js\": [\"script.js\"],\n"
@@ -408,7 +413,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"js\": [\"script.js\"],\n"
@@ -423,7 +428,7 @@
   std::string error;
   std::unique_ptr<const ContentAction> result = ContentAction::Create(
       profile(), extension(),
-      *ParseJson(
+      *ParseJsonDeprecated(
           "{\n"
           "  \"instanceType\": \"declarativeContent.RequestContentScript\",\n"
           "  \"js\": [\"script.js\"],\n"
diff --git a/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc b/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc
index 758dc9f..a53f33a 100644
--- a/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc
@@ -81,7 +81,7 @@
   std::string error;
   std::unique_ptr<ContentCondition> condition = CreateContentCondition(
       nullptr, std::map<std::string, ContentPredicateFactory*>(),
-      *base::test::ParseJson(
+      *base::test::ParseJsonDeprecated(
           "{\n"
           "  \"invalid\": \"foobar\",\n"
           "  \"instanceType\": \"declarativeContent.PageStateMatcher\",\n"
@@ -99,7 +99,7 @@
   std::string error;
   std::unique_ptr<ContentCondition> condition = CreateContentCondition(
       nullptr, predicate_factories,
-      *base::test::ParseJson(
+      *base::test::ParseJsonDeprecated(
           "{\n"
           "  \"test_predicate\": \"\",\n"
           "  \"instanceType\": \"declarativeContent.PageStateMatcher\",\n"
@@ -117,7 +117,7 @@
   std::string error;
   std::unique_ptr<ContentCondition> condition = CreateContentCondition(
       nullptr, predicate_factories,
-      *base::test::ParseJson(
+      *base::test::ParseJsonDeprecated(
           "{\n"
           "  \"test_predicate1\": {},\n"
           "  \"test_predicate2\": [],\n"
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc
index ae8d03c9..0f5446c1 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc
@@ -108,9 +108,7 @@
                            std::unique_ptr<const ContentPredicate>* predicate) {
     std::string error;
     *predicate = tracker_.CreatePredicate(
-        nullptr,
-        *base::test::ParseJson(value),
-        &error);
+        nullptr, *base::test::ParseJsonDeprecated(value), &error);
     EXPECT_EQ("", error);
     ASSERT_TRUE(*predicate);
   }
@@ -122,7 +120,7 @@
   std::string error;
   std::unique_ptr<DeclarativeContentCssPredicate> predicate =
       DeclarativeContentCssPredicate::Create(
-          nullptr, *base::test::ParseJson("\"selector\""), &error);
+          nullptr, *base::test::ParseJsonDeprecated("\"selector\""), &error);
   EXPECT_THAT(error, HasSubstr("invalid type"));
   EXPECT_FALSE(predicate);
 }
@@ -131,7 +129,8 @@
   std::string error;
   std::unique_ptr<DeclarativeContentCssPredicate> predicate =
       DeclarativeContentCssPredicate::Create(
-          nullptr, *base::test::ParseJson("[\"input\", \"a\"]"), &error);
+          nullptr, *base::test::ParseJsonDeprecated("[\"input\", \"a\"]"),
+          &error);
   EXPECT_EQ("", error);
   ASSERT_TRUE(predicate);
 
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc
index 3b74e76..8c8917c2 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc
@@ -55,7 +55,8 @@
   std::unique_ptr<DeclarativeContentIsBookmarkedPredicate> predicate =
       DeclarativeContentIsBookmarkedPredicate::Create(
           evaluator, extension,
-          *base::test::ParseJson(is_bookmarked ? "true" : "false"), &error);
+          *base::test::ParseJsonDeprecated(is_bookmarked ? "true" : "false"),
+          &error);
   EXPECT_EQ("", error);
   EXPECT_TRUE(predicate);
   EXPECT_EQ(is_bookmarked, predicate->is_bookmarked());
@@ -168,7 +169,8 @@
   std::string error;
   std::unique_ptr<DeclarativeContentIsBookmarkedPredicate> predicate =
       DeclarativeContentIsBookmarkedPredicate::Create(
-          nullptr, extension.get(), *base::test::ParseJson("true"), &error);
+          nullptr, extension.get(), *base::test::ParseJsonDeprecated("true"),
+          &error);
   EXPECT_THAT(error, HasSubstr("requires 'bookmarks' permission"));
   EXPECT_FALSE(predicate);
 }
@@ -181,7 +183,8 @@
   std::string error;
   std::unique_ptr<DeclarativeContentIsBookmarkedPredicate> predicate =
       DeclarativeContentIsBookmarkedPredicate::Create(
-          nullptr, extension.get(), *base::test::ParseJson("[]"), &error);
+          nullptr, extension.get(), *base::test::ParseJsonDeprecated("[]"),
+          &error);
   EXPECT_THAT(error, HasSubstr("invalid type"));
   EXPECT_FALSE(predicate);
 }
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc
index cea7cf1..075716ea 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc
@@ -80,9 +80,7 @@
                            std::unique_ptr<const ContentPredicate>* predicate) {
     std::string error;
     *predicate = tracker_.CreatePredicate(
-        nullptr,
-        *base::test::ParseJson(value),
-        &error);
+        nullptr, *base::test::ParseJsonDeprecated(value), &error);
     EXPECT_EQ("", error);
     ASSERT_TRUE(*predicate);
   }
@@ -95,8 +93,8 @@
   std::string error;
   std::unique_ptr<DeclarativeContentPageUrlPredicate> predicate =
       DeclarativeContentPageUrlPredicate::Create(
-          nullptr, matcher.condition_factory(), *base::test::ParseJson("[]"),
-          &error);
+          nullptr, matcher.condition_factory(),
+          *base::test::ParseJsonDeprecated("[]"), &error);
   EXPECT_THAT(error, HasSubstr("invalid type"));
   EXPECT_FALSE(predicate);
 
@@ -109,7 +107,8 @@
   std::unique_ptr<DeclarativeContentPageUrlPredicate> predicate =
       DeclarativeContentPageUrlPredicate::Create(
           nullptr, matcher.condition_factory(),
-          *base::test::ParseJson("{\"hostSuffix\": \"example.com\"}"), &error);
+          *base::test::ParseJsonDeprecated("{\"hostSuffix\": \"example.com\"}"),
+          &error);
   EXPECT_EQ("", error);
   ASSERT_TRUE(predicate);
 
diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
index 7cd7aba..b82f4ee 100644
--- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
@@ -51,7 +51,8 @@
 const char kUnknownActionType[] = "unknownType";
 
 std::unique_ptr<WebRequestActionSet> CreateSetOfActions(const char* json) {
-  std::unique_ptr<base::Value> parsed_value(base::test::ParseJson(json));
+  std::unique_ptr<base::Value> parsed_value(
+      base::test::ParseJsonDeprecated(json));
   const base::ListValue* parsed_list;
   CHECK(parsed_value->GetAsList(&parsed_list));
 
diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc
index c8f2b9b7d..4bb494b 100644
--- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc
@@ -206,7 +206,7 @@
     json_description += attributes;
     json_description += "}";
 
-    return base::test::ParseJson(json_description);
+    return base::test::ParseJsonDeprecated(json_description);
   }
 
   // Create a rule with the ID |rule_id| and with conditions created from the
diff --git a/chrome/browser/extensions/api/device_permissions_manager_unittest.cc b/chrome/browser/extensions/api/device_permissions_manager_unittest.cc
index e81b634..1beb223 100644
--- a/chrome/browser/extensions/api/device_permissions_manager_unittest.cc
+++ b/chrome/browser/extensions/api/device_permissions_manager_unittest.cc
@@ -55,16 +55,15 @@
   void SetUp() override {
     testing::Test::SetUp();
     env_.reset(new extensions::TestExtensionEnvironment());
-    extension_ =
-        env_->MakeExtension(*base::test::ParseJson(
-                                "{"
-                                "  \"app\": {"
-                                "    \"background\": {"
-                                "      \"scripts\": [\"background.js\"]"
-                                "    }"
-                                "  },"
-                                "  \"permissions\": [ \"hid\", \"usb\" ]"
-                                "}"));
+    extension_ = env_->MakeExtension(*base::test::ParseJsonDeprecated(
+        "{"
+        "  \"app\": {"
+        "    \"background\": {"
+        "      \"scripts\": [\"background.js\"]"
+        "    }"
+        "  },"
+        "  \"permissions\": [ \"hid\", \"usb\" ]"
+        "}"));
 
     HidDeviceManager::GetFactoryInstance()->SetTestingFactory(
         env_->profile(), base::BindRepeating(&CreateHidDeviceManager));
@@ -337,7 +336,7 @@
 }
 
 TEST_F(DevicePermissionsManagerTest, LoadPrefs) {
-  std::unique_ptr<base::Value> prefs_value = base::test::ParseJson(
+  std::unique_ptr<base::Value> prefs_value = base::test::ParseJsonDeprecated(
       "["
       "  {"
       "    \"manufacturer_string\": \"Test Manufacturer\","
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
index f35c291..fa32e0d 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
@@ -17,9 +17,9 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/common/extensions/api/passwords_private.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/password_manager/core/browser/manage_passwords_referrer.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_function_registry.h"
 
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.cc b/chrome/browser/extensions/bookmark_app_extension_util.cc
new file mode 100644
index 0000000..84418902
--- /dev/null
+++ b/chrome/browser/extensions/bookmark_app_extension_util.cc
@@ -0,0 +1,74 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/bookmark_app_extension_util.h"
+
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "extensions/common/extension.h"
+
+#if defined(OS_MACOSX)
+#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut_mac.h"
+#include "chrome/common/chrome_switches.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+// gn check complains on Linux Ozone.
+#include "ash/public/cpp/shelf_model.h"  // nogncheck
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+#endif
+
+namespace extensions {
+
+void BookmarkAppCreateOsShortcuts(Profile* profile,
+                                  const Extension* extension) {
+#if !defined(OS_CHROMEOS)
+  web_app::ShortcutLocations creation_locations;
+#if defined(OS_LINUX) || defined(OS_WIN)
+  creation_locations.on_desktop = true;
+#else
+  creation_locations.on_desktop = false;
+#endif
+  creation_locations.applications_menu_location =
+      web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
+  creation_locations.in_quick_launch_bar = false;
+
+  Profile* current_profile = profile->GetOriginalProfile();
+  web_app::CreateShortcuts(web_app::SHORTCUT_CREATION_BY_USER,
+                           creation_locations, current_profile, extension);
+#else
+  // ChromeLauncherController does not exist in unit tests.
+  if (ChromeLauncherController::instance()) {
+    ChromeLauncherController::instance()->shelf_model()->PinAppWithID(
+        extension->id());
+  }
+#endif  // !defined(OS_CHROMEOS)
+}
+
+void BookmarkAppReparentTab(Profile* profile,
+                            content::WebContents* contents,
+                            const Extension* extension,
+                            LaunchType launch_type) {
+#if defined(OS_MACOSX)
+  // TODO(https://crbug.com/915571): Reparent the tab on Mac just like the
+  // other platforms.
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+          ::switches::kDisableHostedAppShimCreation)) {
+    Profile* current_profile = profile->GetOriginalProfile();
+    web_app::RevealAppShimInFinderForApp(current_profile, extension);
+  }
+#else
+  // Reparent the tab into an app window immediately when opening as a window.
+  if (base::FeatureList::IsEnabled(::features::kDesktopPWAWindowing) &&
+      launch_type == LAUNCH_TYPE_WINDOW && !profile->IsOffTheRecord()) {
+    ReparentWebContentsIntoAppBrowser(contents, extension);
+  }
+#endif  // !defined(OS_MACOSX)
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.h b/chrome/browser/extensions/bookmark_app_extension_util.h
new file mode 100644
index 0000000..3d1e5f4
--- /dev/null
+++ b/chrome/browser/extensions/bookmark_app_extension_util.h
@@ -0,0 +1,29 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_EXTENSION_UTIL_H_
+#define CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_EXTENSION_UTIL_H_
+
+#include "extensions/common/constants.h"
+
+class Profile;
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+
+class Extension;
+
+void BookmarkAppCreateOsShortcuts(Profile* profile, const Extension* extension);
+
+void BookmarkAppReparentTab(Profile* profile,
+                            content::WebContents* contents,
+                            const Extension* extension,
+                            LaunchType launch_type);
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_EXTENSION_UTIL_H_
diff --git a/chrome/browser/extensions/bookmark_app_helper.cc b/chrome/browser/extensions/bookmark_app_helper.cc
index 03bccc3..853efea 100644
--- a/chrome/browser/extensions/bookmark_app_helper.cc
+++ b/chrome/browser/extensions/bookmark_app_helper.cc
@@ -16,12 +16,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/browser/banners/app_banner_manager.h"
-#include "chrome/browser/banners/app_banner_manager_desktop.h"
-#include "chrome/browser/banners/app_banner_settings_helper.h"
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_delegate.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/bookmark_app_extension_util.h"
 #include "chrome/browser/extensions/convert_web_app.h"
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -36,11 +34,9 @@
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/web_applications/components/web_app_icon_downloader.h"
 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
-#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
 #include "chrome/browser/webshare/share_target_pref_helper.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h"
@@ -63,17 +59,6 @@
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
-#if defined(OS_MACOSX)
-#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut_mac.h"
-#include "chrome/common/chrome_switches.h"
-#endif
-
-#if defined(OS_CHROMEOS)
-// gn check complains on Linux Ozone.
-#include "ash/public/cpp/shelf_model.h"  // nogncheck
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#endif
-
 namespace extensions {
 
 namespace {
@@ -483,62 +468,19 @@
     return;
   }
 
-  // Record an app banner added to homescreen event to ensure banners are not
-  // shown for this app.
-  AppBannerSettingsHelper::RecordBannerEvent(
-      contents_, web_app_info_.app_url, web_app_info_.app_url.spec(),
-      AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN,
-      base::Time::Now());
+  web_app::RecordAppBanner(contents_, web_app_info_.app_url);
 
-#if !defined(OS_CHROMEOS)
-  // Pin the app to the relevant launcher depending on the OS.
-  Profile* current_profile = profile_->GetOriginalProfile();
-#endif  // !defined(OS_CHROMEOS)
+  if (create_shortcuts_)
+    BookmarkAppCreateOsShortcuts(profile_, extension);
 
-  // If there is no browser, it means that the app is being installed in the
-  // background. We skip some steps in this case.
-  const bool silent_install =
-      (chrome::FindBrowserWithWebContents(contents_) == nullptr);
-
-  if (create_shortcuts_) {
-#if !defined(OS_CHROMEOS)
-    web_app::ShortcutLocations creation_locations;
-#if defined(OS_LINUX) || defined(OS_WIN)
-    creation_locations.on_desktop = true;
-#else
-    creation_locations.on_desktop = false;
-#endif
-    creation_locations.applications_menu_location =
-        web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
-    creation_locations.in_quick_launch_bar = false;
-
-    web_app::CreateShortcuts(web_app::SHORTCUT_CREATION_BY_USER,
-                             creation_locations, current_profile, extension);
-#else
-    // ChromeLauncherController does not exist in unit tests.
-    if (ChromeLauncherController::instance()) {
-      ChromeLauncherController::instance()->shelf_model()->PinAppWithID(
-          extension->id());
-    }
-#endif  // !defined(OS_CHROMEOS)
-  }
-
-  if (!silent_install) {
-#if defined(OS_MACOSX)
-    // TODO(https://crbug.com/915571): Reparent the tab on Mac just like the
-    // other platforms.
-    if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
-            ::switches::kDisableHostedAppShimCreation)) {
-      web_app::RevealAppShimInFinderForApp(current_profile, extension);
-    }
-#else
-    // Reparent the tab into an app window immediately when opening as a window.
-    if (base::FeatureList::IsEnabled(::features::kDesktopPWAWindowing) &&
-        launch_type == LAUNCH_TYPE_WINDOW && !profile_->IsOffTheRecord()) {
-      ReparentWebContentsIntoAppBrowser(contents_, extension);
-    }
-#endif  // !defined(OS_MACOSX)
-  }
+  // If there is a browser, it means that the app is being installed in the
+  // foreground: window reparenting needed.
+  const bool reparent_tab =
+      (chrome::FindBrowserWithWebContents(contents_) != nullptr);
+  // TODO(loyso): Reparenting must be implemented in
+  // chrome/browser/ui/web_applications/ UI layer as a post-install step.
+  if (reparent_tab)
+    BookmarkAppReparentTab(profile_, contents_, extension, launch_type);
 
   callback_.Run(extension, web_app_info_);
 }
diff --git a/chrome/browser/extensions/chrome_extensions_interface_registration.cc b/chrome/browser/extensions/chrome_extensions_interface_registration.cc
index f5742714..a1dd82b 100644
--- a/chrome/browser/extensions/chrome_extensions_interface_registration.cc
+++ b/chrome/browser/extensions/chrome_extensions_interface_registration.cc
@@ -9,6 +9,7 @@
 #include "base/logging.h"
 #include "chrome/browser/media/router/media_router_feature.h"  // nogncheck
 #include "chrome/browser/media/router/mojo/media_router_desktop.h"  // nogncheck
+#include "chrome/common/extensions/extension_constants.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "extensions/common/extension.h"
@@ -24,7 +25,9 @@
 #include "content/public/common/service_manager_connection.h"
 #include "extensions/browser/api/extensions_api_client.h"
 #include "extensions/browser/api/media_perception_private/media_perception_api_delegate.h"
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
+#include "services/video_capture/public/mojom/constants.mojom.h"
 #endif
 
 namespace extensions {
@@ -87,6 +90,11 @@
                               base::Unretained(delegate)));
     }
   }
+  if (extension->id().compare(extension_misc::kChromeCameraAppId) == 0) {
+    registry->AddInterface(
+        base::BindRepeating(&ForwardRequest<cros::mojom::CrosImageCapture>,
+                            video_capture::mojom::kServiceName));
+  }
 #endif
 }
 
diff --git a/chrome/browser/extensions/extension_protocols_unittest.cc b/chrome/browser/extensions/extension_protocols_unittest.cc
index b860393..708c8f0b5 100644
--- a/chrome/browser/extensions/extension_protocols_unittest.cc
+++ b/chrome/browser/extensions/extension_protocols_unittest.cc
@@ -768,7 +768,7 @@
       })";
   test_dir.WriteManifest(kManifest);
   std::unique_ptr<base::DictionaryValue> manifest =
-      base::DictionaryValue::From(base::test::ParseJson(kManifest));
+      base::DictionaryValue::From(base::test::ParseJsonDeprecated(kManifest));
   ASSERT_TRUE(manifest);
 
   test_dir.WriteFile(FILE_PATH_LITERAL("json_file.json"), "{}");
diff --git a/chrome/browser/extensions/extension_service_sync_unittest.cc b/chrome/browser/extensions/extension_service_sync_unittest.cc
index 20b7147..3b27aa2b 100644
--- a/chrome/browser/extensions/extension_service_sync_unittest.cc
+++ b/chrome/browser/extensions/extension_service_sync_unittest.cc
@@ -37,12 +37,14 @@
 #include "chrome/common/extensions/extension_test_util.h"
 #include "chrome/common/extensions/sync_helper.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/crx_file/id_util.h"
+#include "components/sync/driver/sync_service.h"
+#include "components/sync/driver/sync_user_settings.h"
 #include "components/sync/model/fake_sync_change_processor.h"
 #include "components/sync/model/sync_change_processor_wrapper_for_test.h"
 #include "components/sync/model/sync_data.h"
 #include "components/sync/model/sync_error_factory_mock.h"
+#include "components/sync/protocol/sync.pb.h"
 #include "components/variations/variations_associated_data.h"
 #include "extensions/browser/api_test_utils.h"
 #include "extensions/browser/app_sorting.h"
diff --git a/chrome/browser/extensions/permission_message_combinations_unittest.cc b/chrome/browser/extensions/permission_message_combinations_unittest.cc
index 3d87493..5ebe1e2e 100644
--- a/chrome/browser/extensions/permission_message_combinations_unittest.cc
+++ b/chrome/browser/extensions/permission_message_combinations_unittest.cc
@@ -44,7 +44,7 @@
     std::replace(json_manifest_with_double_quotes.begin(),
                  json_manifest_with_double_quotes.end(), '\'', '"');
     app_ = env_.MakeExtension(
-        *base::test::ParseJson(json_manifest_with_double_quotes),
+        *base::test::ParseJsonDeprecated(json_manifest_with_double_quotes),
         kAllowlistedExtensionID);
   }
 
diff --git a/chrome/browser/extensions/updater/update_service_browsertest.cc b/chrome/browser/extensions/updater/update_service_browsertest.cc
index 1b080db..a5755d4 100644
--- a/chrome/browser/extensions/updater/update_service_browsertest.cc
+++ b/chrome/browser/extensions/updater/update_service_browsertest.cc
@@ -117,7 +117,8 @@
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(update_request);
+    const auto root = base::JSONReader::Read(update_request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.10", app.FindKey("version")->GetString());
@@ -189,7 +190,8 @@
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(update_request);
+    const auto root = base::JSONReader::Read(update_request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.10", app.FindKey("version")->GetString());
@@ -352,7 +354,8 @@
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(update_request);
+    const auto root = base::JSONReader::Read(update_request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.10", app.FindKey("version")->GetString());
@@ -457,7 +460,8 @@
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(update_request);
+    const auto root = base::JSONReader::Read(update_request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.0.0.0", app.FindKey("version")->GetString());
@@ -703,7 +707,8 @@
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(update_request);
+    const auto root = base::JSONReader::Read(update_request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(id_, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.0.0.0", app.FindKey("version")->GetString());
@@ -834,7 +839,8 @@
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(update_request);
+    const auto root = base::JSONReader::Read(update_request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(id_, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.0.0.0", app.FindKey("version")->GetString());
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index a3cbe93..9ce56ff 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2286,6 +2286,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "manual-password-generation-android",
+    "owners": [ "ioanap" ],
+    "expiry_milestone": 76
+  },
+  {
     "name": "mash",
     "owners": [ "mustash-team@google.com" ],
     "expiry_milestone": 79
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ce91e0f..8550012 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2449,14 +2449,20 @@
     "Whether to use the Google Play Services Location Settings Dialog "
     "permission dialog.";
 
-const char kModalPermissionDialogViewName[] = "Modal Permission Dialog";
-const char kModalPermissionDialogViewDescription[] =
-    "Enable this option to use ModalDialogManager for permission Dialogs.";
+const char kManualPasswordGenerationAndroidName[] =
+    "Manual password generation";
+const char kManualPasswordGenerationAndroidDescription[] =
+    "Whether Chrome should offer users the option to manually request to "
+    "generate passwords on Android.";
 
 const char kMediaScreenCaptureName[] = "Experimental ScreenCapture.";
 const char kMediaScreenCaptureDescription[] =
     "Enable this option for experimental ScreenCapture feature on Android.";
 
+const char kModalPermissionDialogViewName[] = "Modal Permission Dialog";
+const char kModalPermissionDialogViewDescription[] =
+    "Enable this option to use ModalDialogManager for permission Dialogs.";
+
 const char kModalPermissionPromptsName[] = "Modal Permission Prompts";
 const char kModalPermissionPromptsDescription[] =
     "Whether to use permission dialogs in place of permission infobars.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 87806e9..7f0f2aef 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1449,12 +1449,15 @@
 extern const char kLsdPermissionPromptName[];
 extern const char kLsdPermissionPromptDescription[];
 
-extern const char kModalPermissionDialogViewName[];
-extern const char kModalPermissionDialogViewDescription[];
+extern const char kManualPasswordGenerationAndroidName[];
+extern const char kManualPasswordGenerationAndroidDescription[];
 
 extern const char kMediaScreenCaptureName[];
 extern const char kMediaScreenCaptureDescription[];
 
+extern const char kModalPermissionDialogViewName[];
+extern const char kModalPermissionDialogViewDescription[];
+
 extern const char kModalPermissionPromptsName[];
 extern const char kModalPermissionPromptsDescription[];
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
index 88a59958..d5ca539 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
@@ -26,7 +26,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::test::IsJson;
-using base::test::ParseJson;
+using base::test::ParseJsonDeprecated;
 using testing::_;
 using testing::IsEmpty;
 using testing::Not;
@@ -198,7 +198,7 @@
   }
 
   cast_channel::LaunchSessionResponse GetSuccessLaunchResponse() {
-    auto receiver_status = ParseJson(kReceiverStatus);
+    auto receiver_status = ParseJsonDeprecated(kReceiverStatus);
     cast_channel::LaunchSessionResponse response;
     response.result = cast_channel::LaunchSessionResponse::Result::kOk;
     response.receiver_status = std::move(*receiver_status);
@@ -404,7 +404,8 @@
 }
 
 TEST_F(CastActivityManagerTest, AddRemoveNonLocalActivity) {
-  auto session = CastSession::From(sink_, *ParseJson(kReceiverStatus));
+  auto session =
+      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
   ASSERT_TRUE(session);
 
   MediaRoute route;
@@ -422,7 +423,8 @@
   LaunchSession();
   LaunchSessionResponseSuccess();
 
-  auto session = CastSession::From(sink_, *ParseJson(kReceiverStatus));
+  auto session =
+      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
   ASSERT_TRUE(session);
 
   MediaRoute route;
@@ -438,7 +440,8 @@
 
 TEST_F(CastActivityManagerTest, UpdateExistingSession) {
   // Create and add the session to be updated, and verify it was added.
-  auto session = CastSession::From(sink_, *ParseJson(kReceiverStatus));
+  auto session =
+      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
   ASSERT_TRUE(session);
   MediaRoute route;
   ExpectSingleRouteUpdate(&route);
@@ -448,7 +451,8 @@
   auto old_route_id = route.media_route_id();
 
   // Description change should be reflect in route update.
-  auto updated_session = CastSession::From(sink_, *ParseJson(kReceiverStatus2));
+  auto updated_session =
+      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus2));
   ASSERT_TRUE(updated_session);
 
   ExpectSingleRouteUpdate(&route);
@@ -462,7 +466,8 @@
 
 TEST_F(CastActivityManagerTest, ReplaceExistingSession) {
   // Create and add the session to be replaced, and verify it was added.
-  auto session = CastSession::From(sink_, *ParseJson(kReceiverStatus));
+  auto session =
+      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
   ASSERT_TRUE(session);
   MediaRoute route;
   ExpectSingleRouteUpdate(&route);
@@ -472,7 +477,8 @@
   EXPECT_EQ(route.description(), session->GetRouteDescription());
 
   // Different session.
-  auto new_session = CastSession::From(sink_, *ParseJson(kReceiverStatus3));
+  auto new_session =
+      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus3));
   ASSERT_TRUE(new_session);
 
   ExpectSingleRouteUpdate(&route);
@@ -590,7 +596,8 @@
     "timeoutMillis": 0,
     "type": "v2_message"
   })")));
-  manager_->OnMediaStatusUpdated(sink_, *ParseJson(R"({"foo": "bar"})"), 345);
+  manager_->OnMediaStatusUpdated(
+      sink_, *ParseJsonDeprecated(R"({"foo": "bar"})"), 345);
 }
 
 TEST_F(CastActivityManagerTest, OnMediaStatusUpdatedWithPendingRequest) {
@@ -619,7 +626,8 @@
     "timeoutMillis": 0,
     "type": "v2_message"
   })")));
-  manager_->OnMediaStatusUpdated(sink_, *ParseJson(R"({"foo": "bar"})"), 345);
+  manager_->OnMediaStatusUpdated(
+      sink_, *ParseJsonDeprecated(R"({"foo": "bar"})"), 345);
 }
 
 TEST_F(CastActivityManagerTest, SendVolumeCommandToReceiver) {
diff --git a/chrome/browser/media/router/providers/cast/cast_internal_message_util_unittest.cc b/chrome/browser/media/router/providers/cast/cast_internal_message_util_unittest.cc
index 5b7c6c5..3f1dffa 100644
--- a/chrome/browser/media/router/providers/cast/cast_internal_message_util_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/cast_internal_message_util_unittest.cc
@@ -40,14 +40,14 @@
 void ExpectNoCastSession(const MediaSinkInternal& sink,
                          const std::string& receiver_status_str,
                          const std::string& reason) {
-  auto session = CastSession::From(sink, *ParseJson(receiver_status_str));
+  auto session = CastSession::From(sink, ParseJson(receiver_status_str));
   EXPECT_FALSE(session) << "Shouldn't have created session because of "
                         << reason;
 }
 
 void ExpectInvalidCastInternalMessage(const std::string& message_str,
                                       const std::string& invalid_reason) {
-  EXPECT_FALSE(CastInternalMessage::From(std::move(*ParseJson(message_str))))
+  EXPECT_FALSE(CastInternalMessage::From(ParseJson(message_str)))
       << "message expected to be invlaid: " << invalid_reason;
 }
 
@@ -73,7 +73,7 @@
     }
   })";
 
-  auto message = CastInternalMessage::From(std::move(*ParseJson(message_str)));
+  auto message = CastInternalMessage::From(ParseJson(message_str));
   ASSERT_TRUE(message);
   EXPECT_EQ(CastInternalMessage::Type::kAppMessage, message->type);
   EXPECT_EQ("12345", message->client_id);
@@ -101,7 +101,7 @@
     }
   })";
 
-  auto message = CastInternalMessage::From(std::move(*ParseJson(message_str)));
+  auto message = CastInternalMessage::From(ParseJson(message_str));
   ASSERT_TRUE(message);
   EXPECT_EQ(CastInternalMessage::Type::kV2Message, message->type);
   EXPECT_EQ("12345", message->client_id);
@@ -113,7 +113,7 @@
       "sessionId": "sessionId",
       "foo": "bar"
     })");
-  EXPECT_EQ(*v2_body, message->v2_message_body());
+  EXPECT_EQ(v2_body, message->v2_message_body());
 
   EXPECT_DCHECK_DEATH(message->app_message_namespace());
   EXPECT_DCHECK_DEATH(message->app_message_body());
@@ -127,7 +127,7 @@
       "message": {}
     })";
 
-  auto message = CastInternalMessage::From(std::move(*ParseJson(message_str)));
+  auto message = CastInternalMessage::From(ParseJson(message_str));
   ASSERT_TRUE(message);
   EXPECT_EQ(CastInternalMessage::Type::kClientConnect, message->type);
   EXPECT_EQ("12345", message->client_id);
@@ -211,7 +211,7 @@
         "transportId":"transportId"
       }]
   })";
-  auto session = CastSession::From(sink, *ParseJson(receiver_status_str));
+  auto session = CastSession::From(sink, ParseJson(receiver_status_str));
   ASSERT_TRUE(session);
   EXPECT_EQ("sessionId", session->session_id());
   EXPECT_EQ("ABCDEFGH", session->app_id());
diff --git a/chrome/browser/media/router/providers/cast/cast_session_tracker_unittest.cc b/chrome/browser/media/router/providers/cast/cast_session_tracker_unittest.cc
index 9e4494a..96d5f16 100644
--- a/chrome/browser/media/router/providers/cast/cast_session_tracker_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/cast_session_tracker_unittest.cc
@@ -15,7 +15,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::test::IsJson;
-using base::test::ParseJson;
+using base::test::ParseJsonDeprecated;
 using testing::_;
 using testing::ByRef;
 using testing::Eq;
@@ -99,7 +99,7 @@
         sink_.cast_data().cast_channel_id,
         cast_channel::InternalMessage(
             cast_channel::CastMessageType::kReceiverStatus,
-            std::move(*ParseJson(kReceiverStatus))));
+            std::move(*ParseJsonDeprecated(kReceiverStatus))));
 
     session_ = session_tracker_.GetSessions().begin()->second.get();
     ASSERT_TRUE(session_);
@@ -145,7 +145,7 @@
       sink_.cast_data().cast_channel_id,
       cast_channel::InternalMessage(
           cast_channel::CastMessageType::kReceiverStatus,
-          std::move(*ParseJson(kIdleReceiverStatus))));
+          std::move(*ParseJsonDeprecated(kIdleReceiverStatus))));
 }
 
 TEST_F(CastSessionTrackerTest, GetSessions) {
@@ -192,7 +192,7 @@
   session_tracker_.OnInternalMessage(
       sink_.cast_data().cast_channel_id,
       cast_channel::InternalMessage(cast_channel::CastMessageType::kMediaStatus,
-                                    std::move(*ParseJson(R"({
+                                    std::move(*ParseJsonDeprecated(R"({
     "status": [{
         "playerState": "anything but IDLE",
         "supportedMediaRequests": 0,
@@ -247,7 +247,7 @@
   session_tracker_.OnInternalMessage(
       sink_.cast_data().cast_channel_id,
       cast_channel::InternalMessage(cast_channel::CastMessageType::kMediaStatus,
-                                    std::move(*ParseJson(R"({
+                                    std::move(*ParseJsonDeprecated(R"({
     "requestId": 12345,
     "status": [{
         "playerState": "anything but IDLE",
@@ -278,7 +278,7 @@
   session_tracker_.OnInternalMessage(
       sink_.cast_data().cast_channel_id,
       cast_channel::InternalMessage(cast_channel::CastMessageType::kMediaStatus,
-                                    std::move(*ParseJson(R"({
+                                    std::move(*ParseJsonDeprecated(R"({
     "status": [{
         "media": "theMedia",
         "mediaSessionId": 345,
@@ -324,7 +324,7 @@
   session_tracker_.OnInternalMessage(
       sink_.cast_data().cast_channel_id,
       cast_channel::InternalMessage(cast_channel::CastMessageType::kMediaStatus,
-                                    std::move(*ParseJson(R"({
+                                    std::move(*ParseJsonDeprecated(R"({
     "status": [{
         "mediaSessionId": 345,
         "playerState": "anything but IDLE",
diff --git a/chrome/browser/net/reporting_browsertest.cc b/chrome/browser/net/reporting_browsertest.cc
index 1575744..d672a4d 100644
--- a/chrome/browser/net/reporting_browsertest.cc
+++ b/chrome/browser/net/reporting_browsertest.cc
@@ -116,7 +116,7 @@
 }
 
 std::unique_ptr<base::Value> ParseReportUpload(const std::string& payload) {
-  auto parsed_payload = base::test::ParseJson(payload);
+  auto parsed_payload = base::test::ParseJsonDeprecated(payload);
   // Clear out any non-reproducible fields.
   for (auto& report : parsed_payload->GetList()) {
     report.RemoveKey("age");
@@ -151,7 +151,7 @@
 
   // Verify the contents of the report that we received.
   EXPECT_TRUE(actual != nullptr);
-  auto expected = base::test::ParseJson(base::StringPrintf(
+  auto expected = base::test::ParseJsonDeprecated(base::StringPrintf(
       R"json(
         [
           {
diff --git a/chrome/browser/password_manager/password_store_factory.cc b/chrome/browser/password_manager/password_store_factory.cc
index 84f3b75f..cd1d05d4 100644
--- a/chrome/browser/password_manager/password_store_factory.cc
+++ b/chrome/browser/password_manager/password_store_factory.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/web_data_service_factory.h"
 #include "chrome/common/chrome_paths_internal.h"
 #include "chrome/common/chrome_switches.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/os_crypt/os_crypt_switches.h"
 #include "components/password_manager/core/browser/login_database.h"
@@ -35,6 +34,7 @@
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/storage_partition.h"
@@ -112,7 +112,7 @@
   if (!password_store)
     return;
   syncer::SyncService* sync_service =
-      ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
+      ProfileSyncServiceFactory::GetForProfile(profile);
 
   password_manager::ToggleAffiliationBasedMatchingBasedOnPasswordSyncedState(
       password_store.get(), sync_service,
diff --git a/chrome/browser/password_manager/save_password_infobar_delegate_android.cc b/chrome/browser/password_manager/save_password_infobar_delegate_android.cc
index d723390..9e16482 100644
--- a/chrome/browser/password_manager/save_password_infobar_delegate_android.cc
+++ b/chrome/browser/password_manager/save_password_infobar_delegate_android.cc
@@ -15,11 +15,11 @@
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/infobars/core/infobar.h"
 #include "components/infobars/core/infobar_manager.h"
 #include "components/password_manager/core/browser/password_bubble_experiment.h"
 #include "components/password_manager/core/browser/password_form_metrics_recorder.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/origin.h"
diff --git a/chrome/browser/password_manager/update_password_infobar_delegate_android.cc b/chrome/browser/password_manager/update_password_infobar_delegate_android.cc
index b7bf90a..95ef3c3 100644
--- a/chrome/browser/password_manager/update_password_infobar_delegate_android.cc
+++ b/chrome/browser/password_manager/update_password_infobar_delegate_android.cc
@@ -14,11 +14,11 @@
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/infobars/core/infobar.h"
 #include "components/password_manager/core/browser/password_bubble_experiment.h"
 #include "components/password_manager/core/browser/password_form_metrics_recorder.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 
diff --git a/chrome/browser/previews/previews_browsertest.cc b/chrome/browser/previews/previews_browsertest.cc
index f876c11..84c2546dc0 100644
--- a/chrome/browser/previews/previews_browsertest.cc
+++ b/chrome/browser/previews/previews_browsertest.cc
@@ -522,7 +522,7 @@
 };
 
 std::unique_ptr<base::Value> ParseReportUpload(const std::string& payload) {
-  auto parsed_payload = base::test::ParseJson(payload);
+  auto parsed_payload = base::test::ParseJsonDeprecated(payload);
   // Clear out any non-reproducible fields.
   for (auto& report : parsed_payload->GetList()) {
     report.RemoveKey("age");
@@ -560,7 +560,7 @@
 
   // Verify the contents of the report that we received.
   EXPECT_TRUE(actual != nullptr);
-  auto expected = base::test::ParseJson(base::StringPrintf(
+  auto expected = base::test::ParseJsonDeprecated(base::StringPrintf(
       R"text(
         [
           {
diff --git a/chrome/browser/resources/chromeos/switch_access/menu_panel.html b/chrome/browser/resources/chromeos/switch_access/menu_panel.html
index 987e907..7c17135 100644
--- a/chrome/browser/resources/chromeos/switch_access/menu_panel.html
+++ b/chrome/browser/resources/chromeos/switch_access/menu_panel.html
@@ -7,7 +7,6 @@
   <meta charset="utf-8">
   <title>Switch Access Menu</title>
   <script type="text/javascript" src="closure/base.js"></script>
-  <script type="text/javascript" src="message_handler.js"></script>
   <script type="text/javascript" src="menu_panel.js"></script>
   <link rel="stylesheet" href="menu_panel.css">
 </head>
diff --git a/chrome/browser/resources/chromeos/switch_access/menu_panel.js b/chrome/browser/resources/chromeos/switch_access/menu_panel.js
index e23777e..cfca914 100644
--- a/chrome/browser/resources/chromeos/switch_access/menu_panel.js
+++ b/chrome/browser/resources/chromeos/switch_access/menu_panel.js
@@ -45,10 +45,10 @@
    * @private
    */
   setupButton_(button) {
-    let id = button.id;
-    button.addEventListener('click', function() {
-      MessageHandler.sendMessage(MessageHandler.Destination.BACKGROUND, id);
-    }.bind(id));
+    let action = button.id;
+    button.addEventListener('click', function(action) {
+      this.menuManager_.performAction(action);
+    }.bind(this, action));
   }
 
   /**
diff --git a/chrome/browser/resources/pdf/BUILD.gn b/chrome/browser/resources/pdf/BUILD.gn
index ac96b75..214f03a 100644
--- a/chrome/browser/resources/pdf/BUILD.gn
+++ b/chrome/browser/resources/pdf/BUILD.gn
@@ -76,10 +76,15 @@
   ]
 }
 
+js_library("metrics") {
+  externs_list = [ "$externs_path/metrics_private.js" ]
+}
+
 js_type_check("pdf_resources") {
   deps = [
     ":browser_api",
     ":gesture_detector",
+    ":metrics",
     ":open_pdf_params_parser",
     ":pdf_fitting_type",
     ":pdf_scripting_api",
diff --git a/chrome/browser/resources/pdf/elements/icons.html b/chrome/browser/resources/pdf/elements/icons.html
index f71953e..a5976b1 100644
--- a/chrome/browser/resources/pdf/elements/icons.html
+++ b/chrome/browser/resources/pdf/elements/icons.html
@@ -12,12 +12,14 @@
       <g id="bookmark"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"></path></g>
       <g id="bookmark-border"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"></path></g>
       <g id="create"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g>
-      <g id="eraser"><path d="m15.543 4.9863c-0.32193 0.019835-0.65088 0.1626-0.91016 0.42188l-4.8867 4.8867 5.5332 5.5332h-2.1211l-4.4727-4.4727-0.65234 0.65234c-0.51854 0.51855-0.56773 1.319-0.10938 1.7773l4.166 4.166c0.45836 0.45836 1.2588 0.40917 1.7773-0.10938l6.5996-6.5996c0.51854-0.51855 0.56773-1.319 0.10938-1.7773l-4.166-4.166c-0.22918-0.22918-0.54525-0.33233-0.86719-0.3125zm-12.543 13.764v1.5h0.75 6 0.75v-1.5h-0.75-6-0.75z"></path></g>
+      <g id="eraser"><path d="M21.41,11.33 L13.04,20 L4.73,20 L2.58,17.86 C1.8,17.08 1.8,15.83 2.58,15.04 L13.62,3.58 C14.4,2.81 15.68,2.81 16.46,3.58 L21.41,8.51 C22.2,9.29 22.2,10.55 21.41,11.33 L21.41,11.33 Z"></path><polygon points="17.26 18 15.26 20 21.96 20 21.96 18"></polygon></g>
       <g id="fullscreen-exit"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></g>
-      <g id="highlighter"><path d="M15.7498169,15.9885 L18.69405,19.7475 C18.75405,19.8165 18.79455,19.89975 18.8148,19.98825 C17.22405,20.30025 14.37105,20.25 12.0648,20.25 C9.72705,20.25 6.61248962,20.295 5.01423962,19.9755 C5.03523962,19.8915 5.07348962,19.8135 5.13123962,19.7475 L8.25498962,15.9885 L8.25498962,11.3135317 C8.25498962,11.2722817 8.26398962,11.2317817 8.28123962,11.1957817 L8.59698962,10.5297817 C8.59698962,10.5 15.4078169,10.49925 15.4078169,10.5 L15.7235669,11.1957817 C15.7400669,11.2317817 15.7498169,11.2722817 15.7498169,11.3135317 L15.7498169,15.9885"></path><path d="M13.846962,4.03912354 L14.04,4.09664481 C14.163,4.13093052 14.25,4.25693052 14.25,4.40178767 L14.25,5.43243216 L14.25,5.7872893 L14.25,8.82657296 C13.6995,8.91057296 12.79575,9 12,9 C11.20275,9 10.3005,8.91057296 9.75,8.82571582 L9.75,4.41586073 L9.75,3.85871787 L9.75,3.3152893 C9.75,3.11043216 9.91725,2.96043216 10.09125,3.0092893 L13.846962,4.03912354 Z" style="fill: var(--pen-tip-fill)"></g>
-      <g id="marker"><path d="M14.25,8.91853902 L14.25,6.53925711 C14.25,5.97869512 14.1161242,5.42520919 13.8582163,4.92039274 L12.7090497,2.67089612 C12.4225948,2.10981636 11.5774052,2.10964378 11.2909503,2.67072353 C10.911176,3.41422755 10.4575732,4.30184157 10.1413899,4.92108308 C9.88348208,5.42589953 9.75,5.97869512 9.75,6.53925711 L9.75,8.91940195 C10.4758827,8.97169576 11.2816971,9 11.9916328,9 C12.7058997,9 13.5195892,8.97152317 14.25,8.91853902" style="fill: var(--pen-tip-fill)"></path><path d="M15.7498169,15.9885 L15.7498169,11.3135317 C15.7498169,11.2722817 15.7400669,11.2317817 15.7235669,11.1957817 L15.4078169,10.5 C15.4078169,10.49925 8.59698962,10.5 8.59698962,10.5297817 L8.28123962,11.1957817 C8.26398962,11.2317817 8.25498962,11.2722817 8.25498962,11.3135317 L8.25498962,15.9885 L5.13123962,19.7475 C5.07348962,19.8135 5.03523962,19.8915 5.01423962,19.9755 C6.61248962,20.295 9.72705,20.25 12.0648,20.25 C14.37105,20.25 17.22405,20.30025 18.8148,19.98825 C18.79455,19.89975 18.75405,19.8165 18.69405,19.7475 L15.7498169,15.9885 Z"></path></g>
+      <g id="highlighter"><path d="M10.22,9.49 L4.31,15.49 C3.54,16.29 3.61,17.54 4.39,18.34 L0.77,22 L6.45,22 L7.19,21.25 C7.97,22.06 9.14,22.11 9.92,21.3 L15.88,15.25 L10.22,9.49 L10.22,9.49 Z"></path><path style="fill: var(--pen-tip-fill)"  d="M22.68,5.49 L19.86,2.62 C19.08,1.82 17.79,1.78 17.02,2.58 L11.27,8.43 L16.93,14.18 L22.62,8.4 C23.39,7.59 23.45,6.29 22.68,5.49 L22.68,5.49 Z"></path><path style="fill: var(--pen-tip-border)" d="M18.4,3c0.3,0,0.5,0.1,0.7,0.3L22,6.2c0.4,0.4,0.4,1.1-0.1,1.5l-5,5.1l-4.3-4.3l5.1-5.2 C17.9,3.1,18.1,3,18.4,3 M18.4,2c-0.5,0-1,0.2-1.4,0.6l-5.8,5.9l5.7,5.8l5.7-5.8c0.8-0.8,0.8-2.1,0.1-2.9l-2.8-2.9 C19.5,2.2,18.9,2,18.4,2L18.4,2z"></path></g>
+      <g id="marker"><polygon points="3 17.25 3 21 6.74 21 14.28 13.47 10.53 9.72"></polygon><path style="fill: var(--pen-tip-fill)" d="M18.37,3.3 L20.71,5.63 C21.1,6.02 21.11,6.66 20.72,7.05 L15.35,12.41 L11.59,8.65 L14.12,6.12 L13.39,5.39 L7.73,11.05 L6.33,9.65 L12.7,3.29 C13.09,2.9 13.74,2.91 14.12,3.3 L15.54,4.71 L16.96,3.3 C17.34,2.91 17.98,2.91 18.37,3.3 L18.37,3.3 Z"></path><path style="fill: var(--pen-tip-border)" d="M17.7,4L20,6.3L15.4,11L13,8.6l1.8-1.8l0.7-0.7l-0.7-0.7l-0.2-0.2l0.2,0.2l0.7,0.7l0.7-0.7L17.7,4 M13.4,3 c-0.3,0-0.5,0.1-0.7,0.3L6.3,9.6l1.4,1.4l5.7-5.7l0.7,0.7l-2.5,2.5l3.8,3.8L20.7,7c0.4-0.4,0.4-1,0-1.4l-2.3-2.3 C18.2,3.1,17.9,3,17.7,3S17.2,3.1,17,3.3l-1.4,1.4l-1.4-1.4C13.9,3.1,13.7,3,13.4,3L13.4,3z"></path></g>
+      <g id="redo"><path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"></path></g>
       <g id="remove"><path d="M19 13H5v-2h14v2z"></path></g>
       <g id="rotate-right"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"></path></g>
+      <g id="undo"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"></path></g>
     </defs>
   </svg>
 </iron-iconset-svg>
diff --git a/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn b/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn
index a2cb5af..32ca2b0 100644
--- a/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn
+++ b/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn
@@ -12,6 +12,7 @@
 
 js_library("viewer-ink-host") {
   deps = [
+    "//chrome/browser/resources/pdf:metrics",
     "//chrome/browser/resources/pdf:viewport",
     "//chrome/browser/resources/pdf/ink:ink_api",
   ]
diff --git a/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js b/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js
index d4e26340..d8002d4 100644
--- a/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js
+++ b/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js
@@ -50,6 +50,9 @@
   /** @type {Viewport} */
   viewport: null,
 
+  /** @type {?AnnotationTool} */
+  tool_: null,
+
   /**
    * Whether we should suppress pointer events due to a gesture,
    * eg. pinch-zoom.
@@ -168,6 +171,24 @@
     this.activePointer_ = null;
     if (!this.pointerGesture_) {
       this.dispatchPointerEvent_(e);
+      // If the stroke was not cancelled, record metrics.
+      if (e.type == 'pointerup') {
+        if (e.pointerType == 'mouse') {
+          PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_MOUSE);
+        } else if (e.pointerType == 'pen') {
+          PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_PEN);
+        } else if (e.pointerType == 'touch') {
+          PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_TOUCH);
+        }
+        if (this.tool_.tool == 'eraser') {
+          PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_ERASER);
+        } else if (this.tool_.tool == 'pen') {
+          PDFMetrics.record(PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_PEN);
+        } else if (this.tool_.tool == 'highlighter') {
+          PDFMetrics.record(
+              PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_HIGHLIGHTER);
+        }
+      }
     }
     this.pointerGesture_ = false;
   },
@@ -248,6 +269,16 @@
     this.ink_.setCamera(camera);
   },
 
+  /** Undo the last edit action. */
+  undo() {
+    this.ink_.undo();
+  },
+
+  /** Redo the last undone edit action. */
+  redo() {
+    this.ink_.redo();
+  },
+
   /**
    * @return {!Promise<{fileName: string, dataToSave: ArrayBuffer}>}
    *     The serialized PDF document including any annotations that were made.
diff --git a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
index 1d0b87a..d8e6c6f 100644
--- a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
+++ b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
@@ -103,7 +103,8 @@
       #eraser:not([selected]),
       #pen:not([selected]),
       #highlighter:not([selected]) {
-        filter: contrast(30%);
+        --pen-tip-fill: currentcolor !important;
+        --pen-tip-border: currentcolor !important;
       }
 
       #pen,
@@ -116,6 +117,13 @@
         border-radius: 4px;
       }
 
+      #annotation-separator {
+        background: rgb(151, 152, 152);
+        height: 30px;
+        margin-inline-end: 12px;
+        width: 1px;
+      }
+
       :host([annotation-mode]) #annotate {
         background-color: rgb(25, 27, 29);
         border-radius: 4px;
@@ -208,14 +216,6 @@
     </div>
 
     <div id="annotations-bar" class="invisible">
-      <paper-icon-button id="eraser"
-          selected$="[[equal_('eraser', annotationTool.tool)]]"
-          on-click="annotationToolClicked_"
-          icon="pdf:eraser"
-          aria-label$="{{strings.annotationEraser}}"
-          title$="{{strings.annotationEraser}}">
-      </paper-icon-button>
-
       <viewer-toolbar-dropdown id="pen"
           selected$="[[equal_('pen', annotationTool.tool)]]"
           open-after-select
@@ -253,6 +253,30 @@
           on-selected-color-changed="annotationToolOptionChanged_">
         </viewer-pen-options>
       </viewer-toolbar-dropdown>
+
+      <paper-icon-button id="eraser"
+          selected$="[[equal_('eraser', annotationTool.tool)]]"
+          on-click="annotationToolClicked_"
+          icon="pdf:eraser"
+          aria-label$="{{strings.annotationEraser}}"
+          title$="{{strings.annotationEraser}}">
+      </paper-icon-button>
+
+      <div id="annotation-separator"></div>
+
+      <paper-icon-button id="undo"
+          icon="pdf:undo"
+          on-click="undo"
+          aria-label$="{{strings.annotationUndo}}"
+          title$="{{strings.annotationUndo}}">
+      </paper-icon-button>
+
+      <paper-icon-button id="redo"
+          icon="pdf:redo"
+          on-click="redo"
+          aria-label$="{{strings.annotationRedo}}"
+          title$="{{strings.annotationRedo}}">
+      </paper-icon-button>
     </div>
   </template>
   <script src="viewer-pdf-toolbar.js"></script>
diff --git a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js
index 5c61b3b8..3f3524ff 100644
--- a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js
+++ b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.js
@@ -163,6 +163,14 @@
     this.fire('print');
   },
 
+  undo: function() {
+    this.fire('undo');
+  },
+
+  redo: function() {
+    this.fire('redo');
+  },
+
   toggleAnnotation: function() {
     this.annotationMode = !this.annotationMode;
     if (this.annotationMode) {
@@ -193,6 +201,10 @@
       selectedColor: null,
     };
     element.attributeStyleMap.set('--pen-tip-fill', options.selectedColor);
+    element.attributeStyleMap.set(
+        '--pen-tip-border',
+        options.selectedColor == '#000000' ? 'currentcolor' :
+                                             options.selectedColor);
     this.annotationTool = {
       tool: tool,
       size: options.selectedSize,
diff --git a/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html b/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html
index 9401afc..1568f0c 100644
--- a/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html
+++ b/chrome/browser/resources/pdf/elements/viewer-toolbar-dropdown/viewer-toolbar-dropdown.html
@@ -55,7 +55,7 @@
       }
 
       #arrow {
-        margin-inline-start: -12px;
+        margin-inline-start: -8px;
         padding-inline-end: 4px;
       }
 
diff --git a/chrome/browser/resources/pdf/ink/ink_api.js b/chrome/browser/resources/pdf/ink/ink_api.js
index bec99a7..c530d57 100644
--- a/chrome/browser/resources/pdf/ink/ink_api.js
+++ b/chrome/browser/resources/pdf/ink/ink_api.js
@@ -90,6 +90,14 @@
     const event = new PointerEvent(type, init);
     document.querySelector('#ink-engine').dispatchEvent(event);
   }
+
+  undo() {
+    this.embed_.undo();
+  }
+
+  redo() {
+    this.embed_.redo();
+  }
 }
 
 /** @return {Promise<InkAPI>} */
diff --git a/chrome/browser/resources/pdf/metrics.js b/chrome/browser/resources/pdf/metrics.js
index d965d30..d55c0d1 100644
--- a/chrome/browser/resources/pdf/metrics.js
+++ b/chrome/browser/resources/pdf/metrics.js
@@ -57,7 +57,7 @@
   }
 }
 
-/** @private {Object} */
+/** @private {?chrome.metricsPrivate.MetricType} */
 PDFMetrics.actionsMetric_ = null;
 
 /** @private {Set} */
@@ -80,24 +80,75 @@
    * denominator to determine percentages of documents in which an action was
    * taken as well as average number of each action per document.
    */
-  DOCUMENT_OPENED: 0,  // Baseline to use as denominator for all formulas.
-  ROTATE_FIRST: 1,
+  DOCUMENT_OPENED: 0,
+
   /** Recorded when the document is rotated clockwise or counter-clockwise. */
+  ROTATE_FIRST: 1,
   ROTATE: 2,
+
   FIT_TO_WIDTH_FIRST: 3,
   FIT_TO_WIDTH: 4,
+
   FIT_TO_PAGE_FIRST: 5,
   FIT_TO_PAGE: 6,
-  OPEN_BOOKMARKS_PANEL_FIRST: 7,
+
   /** Recorded when the bookmarks panel is opened. */
+  OPEN_BOOKMARKS_PANEL_FIRST: 7,
   OPEN_BOOKMARKS_PANEL: 8,
-  FOLLOW_BOOKMARK_FIRST: 9,
+
   /** Recorded when a bookmark is followed. */
+  FOLLOW_BOOKMARK_FIRST: 9,
   FOLLOW_BOOKMARK: 10,
-  PAGE_SELECTOR_NAVIGATE_FIRST: 11,
+
   /** Recorded when the page selection is used to navigate to another page. */
+  PAGE_SELECTOR_NAVIGATE_FIRST: 11,
   PAGE_SELECTOR_NAVIGATE: 12,
-  NUMBER_OF_ACTIONS: 13
+
+  /** Recorded when the user triggers a save of the document. */
+  SAVE_FIRST: 13,
+  SAVE: 14,
+
+  /**
+   * Recorded when the user triggers a save of the document and the document
+   * has been modified by annotations.
+   */
+  SAVE_WITH_ANNOTATION_FIRST: 15,
+  SAVE_WITH_ANNOTATION: 16,
+
+  PRINT_FIRST: 17,
+  PRINT: 18,
+
+  ENTER_ANNOTATION_MODE_FIRST: 19,
+  ENTER_ANNOTATION_MODE: 20,
+
+  EXIT_ANNOTATION_MODE_FIRST: 21,
+  EXIT_ANNOTATION_MODE: 22,
+
+  /** Recorded when a pen stroke is made. */
+  ANNOTATE_STROKE_TOOL_PEN_FIRST: 23,
+  ANNOTATE_STROKE_TOOL_PEN: 24,
+
+  /** Recorded when an eraser stroke is made. */
+  ANNOTATE_STROKE_TOOL_ERASER_FIRST: 25,
+  ANNOTATE_STROKE_TOOL_ERASER: 26,
+
+  /** Recorded when a highlighter stroke is made. */
+  ANNOTATE_STROKE_TOOL_HIGHLIGHTER_FIRST: 27,
+  ANNOTATE_STROKE_TOOL_HIGHLIGHTER: 28,
+
+  /** Recorded when a stroke is made using touch. */
+  ANNOTATE_STROKE_DEVICE_TOUCH_FIRST: 29,
+  ANNOTATE_STROKE_DEVICE_TOUCH: 30,
+
+  /** Recorded when a stroke is made using mouse. */
+  ANNOTATE_STROKE_DEVICE_MOUSE_FIRST: 31,
+  ANNOTATE_STROKE_DEVICE_MOUSE: 32,
+
+  /** Recorded when a stroke is made using pen. */
+  ANNOTATE_STROKE_DEVICE_PEN_FIRST: 33,
+  ANNOTATE_STROKE_DEVICE_PEN: 34,
+
+  NUMBER_OF_ACTIONS: 35,
 };
 
 // Map from UserAction to the 'FIRST' action. These metrics are recorded
@@ -128,4 +179,48 @@
     PDFMetrics.UserAction.PAGE_SELECTOR_NAVIGATE,
     PDFMetrics.UserAction.PAGE_SELECTOR_NAVIGATE_FIRST,
   ],
+  [
+    PDFMetrics.UserAction.SAVE,
+    PDFMetrics.UserAction.SAVE_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.SAVE_WITH_ANNOTATION,
+    PDFMetrics.UserAction.SAVE_WITH_ANNOTATION_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.PRINT,
+    PDFMetrics.UserAction.PRINT_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.ENTER_ANNOTATION_MODE,
+    PDFMetrics.UserAction.ENTER_ANNOTATION_MODE_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.EXIT_ANNOTATION_MODE,
+    PDFMetrics.UserAction.EXIT_ANNOTATION_MODE_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_PEN,
+    PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_PEN_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_ERASER,
+    PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_ERASER_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_HIGHLIGHTER,
+    PDFMetrics.UserAction.ANNOTATE_STROKE_TOOL_HIGHLIGHTER_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_TOUCH,
+    PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_TOUCH_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_MOUSE,
+    PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_MOUSE_FIRST,
+  ],
+  [
+    PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_PEN,
+    PDFMetrics.UserAction.ANNOTATE_STROKE_DEVICE_PEN_FIRST,
+  ],
 ]);
diff --git a/chrome/browser/resources/pdf/pdf_viewer.js b/chrome/browser/resources/pdf/pdf_viewer.js
index 0a4a071b..0208d63 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.js
+++ b/chrome/browser/resources/pdf/pdf_viewer.js
@@ -250,6 +250,10 @@
     this.toolbar_.addEventListener('save', () => this.save());
     this.toolbar_.addEventListener('print', () => this.print());
     this.toolbar_.addEventListener(
+        'undo', () => this.currentController_.undo());
+    this.toolbar_.addEventListener(
+        'redo', () => this.currentController_.redo());
+    this.toolbar_.addEventListener(
         'rotate-right', () => this.currentController_.rotateClockwise());
     this.toolbar_.addEventListener(
         'annotation-mode-changed', e => this.annotationModeChanged_(e));
@@ -499,9 +503,10 @@
   annotationModeChanged_: async function(e) {
     const annotationMode = e.detail.value;
     if (annotationMode) {
+      // Enter annotation mode.
+      PDFMetrics.record(PDFMetrics.UserAction.ENTER_ANNOTATION_MODE);
       this.hasEnteredAnnotationMode_ = true;
       assert(this.currentController_ == this.pluginController_);
-      // Enter annotation mode.
       // TODO(dstockwell): set plugin read-only, begin transition
       this.updateProgress(0);
       // TODO(dstockwell): handle save failure
@@ -515,6 +520,7 @@
       this.updateProgress(100);
     } else {
       // Exit annotation mode.
+      PDFMetrics.record(PDFMetrics.UserAction.EXIT_ANNOTATION_MODE);
       assert(this.currentController_ == this.inkController_);
       // TODO(dstockwell): set ink read-only, begin transition
       this.updateProgress(0);
@@ -1147,6 +1153,10 @@
    * Saves the current PDF document to disk.
    */
   save: async function() {
+    PDFMetrics.record(PDFMetrics.UserAction.SAVE);
+    if (this.hasEnteredAnnotationMode_) {
+      PDFMetrics.record(PDFMetrics.UserAction.SAVE_WITH_ANNOTATION);
+    }
     // If we have entered annotation mode we must require the local
     // contents to ensure annotations are saved. Otherwise we would
     // save the cached or remote copy without annotatios.
@@ -1185,6 +1195,7 @@
   },
 
   print: async function() {
+    PDFMetrics.record(PDFMetrics.UserAction.PRINT);
     await this.exitAnnotationMode_();
     this.currentController_.print();
   }
@@ -1227,6 +1238,16 @@
   print() {}
 
   /**
+   * Undo an edit action.
+   */
+  undo() {}
+
+  /**
+   * Redo an edit action.
+   */
+  redo() {}
+
+  /**
    * Requests that the current document be saved.
    * @param {boolean} requireResult whether a response is required, otherwise
    *     the controller may save the document to disk internally.
@@ -1294,6 +1315,16 @@
   }
 
   /** @override */
+  undo() {
+    this.inkHost_.undo();
+  }
+
+  /** @override */
+  redo() {
+    this.inkHost_.redo();
+  }
+
+  /** @override */
   load(filename, data) {
     if (!this.inkHost_) {
       this.inkHost_ = document.createElement('viewer-ink-host');
diff --git a/chrome/browser/resources/print_preview/new/scaling_settings.html b/chrome/browser/resources/print_preview/new/scaling_settings.html
index 58c12da..ca6a9ea 100644
--- a/chrome/browser/resources/print_preview/new/scaling_settings.html
+++ b/chrome/browser/resources/print_preview/new/scaling_settings.html
@@ -15,8 +15,7 @@
       <span slot="title" id="scaling-label">$i18n{scalingLabel}</span>
       <div slot="controls">
         <select class="md-select" aria-labelledby="scaling-label"
-            disabled$="[[dropdownDisabled_(disabled, inputValid_)]]"
-            value="{{selectedValue::change}}">
+            disabled$="[[dropdownDisabled_]]" value="{{selectedValue::change}}">
           <option value="[[scalingValueEnum_.DEFAULT]]">
             $i18n{optionDefaultScaling}
           </option>
@@ -34,7 +33,7 @@
         on-transitionend="onCollapseChanged_">
       <print-preview-number-settings-section
           max-value="200" min-value="10" default-value="100"
-          disabled$="[[inputDisabled_(disabled, inputValid_, customSelected_)]]"
+          disabled$="[[inputDisabled_(dropdownDisabled_, customSelected_)]]"
           current-value="{{currentValue_}}" input-valid="{{inputValid_}}"
           hint-message="$i18n{scalingInstruction}">
       </print-preview-number-settings-section>
diff --git a/chrome/browser/resources/print_preview/new/scaling_settings.js b/chrome/browser/resources/print_preview/new/scaling_settings.js
index 4d2e396..ea05eef 100644
--- a/chrome/browser/resources/print_preview/new/scaling_settings.js
+++ b/chrome/browser/resources/print_preview/new/scaling_settings.js
@@ -25,7 +25,10 @@
   behaviors: [SettingsBehavior, print_preview_new.SelectBehavior],
 
   properties: {
-    disabled: Boolean,
+    disabled: {
+      type: Boolean,
+      observer: 'onDisabledChanged_',
+    },
 
     /** @private {string} */
     currentValue_: {
@@ -42,6 +45,12 @@
     /** @private {boolean} */
     inputValid_: Boolean,
 
+    /** @private {boolean} */
+    dropdownDisabled_: {
+      type: Boolean,
+      value: false,
+    },
+
     /**
      * Mirroring the enum so that it can be used from HTML bindings.
      * @private
@@ -130,21 +139,16 @@
    * @private
    */
   onInputChanged_: function() {
-    if (this.currentValue_ !== '') {
-      this.setSettingValid('scaling', this.inputValid_);
-    }
+    this.setSettingValid('scaling', this.inputValid_);
 
     if (this.currentValue_ !== '' && this.inputValid_) {
       this.setSetting('scaling', this.currentValue_);
     }
   },
 
-  /**
-   * @return {boolean} Whether the dropdown should be disabled.
-   * @private
-   */
-  dropdownDisabled_: function() {
-    return this.disabled && this.inputValid_;
+  /** @private */
+  onDisabledChanged_: function() {
+    this.dropdownDisabled_ = this.disabled && this.inputValid_;
   },
 
   /**
@@ -152,7 +156,7 @@
    * @private
    */
   inputDisabled_: function() {
-    return !this.customSelected_ || (this.disabled && this.inputValid_);
+    return !this.customSelected_ || this.dropdownDisabled_;
   },
 
   /**
diff --git a/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js b/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
index e584065c..cb850b8 100644
--- a/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
+++ b/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
@@ -210,6 +210,8 @@
       this.unpairedDeviceList_,
       this.deviceList_.filter(d => !(d.paired || d.connecting)));
 
+    this.$.pairedDevices.fire('iron-resize');
+    this.$.unpairedDevices.fire('iron-resize');
     this.updateScrollableContents();
     this.restoreScroll(this.$.unpairedDevices);
     this.restoreScroll(this.$.pairedDevices);
diff --git a/chrome/browser/resources/usb_internals/usb_internals.html b/chrome/browser/resources/usb_internals/usb_internals.html
index 1c84c37..cc025f8d 100644
--- a/chrome/browser/resources/usb_internals/usb_internals.html
+++ b/chrome/browser/resources/usb_internals/usb_internals.html
@@ -14,10 +14,13 @@
   <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/mojo_bindings_lite.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 src="url/mojom/url.mojom-lite.js"></script>
+  <script src="device/usb/public/mojom/device_manager_test.mojom-lite.js">
+  </script>
+  <script
+    src="chrome/browser/ui/webui/usb_internals/usb_internals.mojom-lite.js">
   </script>
 </head>
 
diff --git a/chrome/browser/resources/usb_internals/usb_internals.js b/chrome/browser/resources/usb_internals/usb_internals.js
index 67e63a8..0315f13 100644
--- a/chrome/browser/resources/usb_internals/usb_internals.js
+++ b/chrome/browser/resources/usb_internals/usb_internals.js
@@ -12,13 +12,9 @@
       // browser process.
       this.usbManagerTest = null;
 
-      const pageHandler = new mojom.UsbInternalsPageHandlerPtr;
-      Mojo.bindInterface(
-          mojom.UsbInternalsPageHandler.name,
-          mojo.makeRequest(pageHandler).handle);
-
-      this.usbManagerTest = new device.mojom.UsbDeviceManagerTestPtr;
-      pageHandler.bindTestInterface(mojo.makeRequest(this.usbManagerTest));
+      const pageHandler = mojom.UsbInternalsPageHandler.getProxy();
+      this.usbManagerTest = new device.mojom.UsbDeviceManagerTestProxy;
+      pageHandler.bindTestInterface(this.usbManagerTest.createRequest());
 
       cr.ui.decorate('tabbox', cr.ui.TabBox);
       $('add-test-device-form').addEventListener('submit', (event) => {
diff --git a/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc b/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc
index f3ec5f8..e977bbd 100644
--- a/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc
@@ -56,6 +56,17 @@
     const bool should_enable_uss =
         index == 0 ? std::get<0>(GetParam()) : std::get<1>(GetParam());
 
+    // In order to avoid test flakiness, for any client other than the first, we
+    // need to make sure the feature toggle has been fully read by PasswordStore
+    // before overriding it again. The way to achieve that, for the current
+    // implementation of PasswordStore, is to make a round trip to the backend
+    // sequence, which guarantees that initialization has completed.
+    if (index != 0) {
+      // We ignore the returned value since all we want is to wait for the
+      // round trip to be completed.
+      GetPasswordCount(index - 1);
+    }
+
     // The value of the feature kSyncUSSPasswords only matters during the
     // setup of each client, when the profile is created, ProfileSyncService
     // instantiated as well as the datatype controllers. By overriding the
diff --git a/chrome/browser/sync/user_event_service_factory.cc b/chrome/browser/sync/user_event_service_factory.cc
index b2a3182..b0cf20a 100644
--- a/chrome/browser/sync/user_event_service_factory.cc
+++ b/chrome/browser/sync/user_event_service_factory.cc
@@ -14,10 +14,10 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/sync/session_sync_service_factory.h"
 #include "chrome/common/channel_info.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/sync/base/model_type.h"
 #include "components/sync/base/report_unrecoverable_error.h"
+#include "components/sync/driver/sync_service.h"
 #include "components/sync/model/model_type_store_service.h"
 #include "components/sync/model_impl/client_tag_based_model_type_processor.h"
 #include "components/sync/user_events/no_op_user_event_service.h"
diff --git a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
index 948fa68..a8a34e5 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
@@ -57,10 +57,11 @@
 #include "components/arc/arc_util.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
 #include "components/arc/test/fake_app_instance.h"
-#include "components/browser_sync/profile_sync_service.h"
+#include "components/sync/driver/sync_service.h"
 #include "components/sync/model/fake_sync_change_processor.h"
 #include "components/sync/model/sync_data.h"
 #include "components/sync/model/sync_error_factory_mock.h"
+#include "components/sync/protocol/sync.pb.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_utils.h"
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 911977c..943a0a3 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -47,7 +47,6 @@
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
 #include "components/autofill/core/common/autofill_switches.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/browser/password_requirements_service.h"
@@ -56,6 +55,7 @@
 #include "components/signin/core/browser/signin_buildflags.h"
 #include "components/signin/core/browser/signin_header_helper.h"
 #include "components/signin/core/browser/signin_metrics.h"
+#include "components/sync/driver/sync_service.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/ukm/content/source_url_recorder.h"
 #include "components/user_prefs/user_prefs.h"
@@ -125,7 +125,7 @@
 syncer::SyncService* ChromeAutofillClient::GetSyncService() {
   Profile* profile =
       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
-  return ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
+  return ProfileSyncServiceFactory::GetForProfile(profile);
 }
 
 identity::IdentityManager* ChromeAutofillClient::GetIdentityManager() {
diff --git a/chrome/browser/ui/passwords/settings/password_manager_presenter.cc b/chrome/browser/ui/passwords/settings/password_manager_presenter.cc
index 70a232d..1625e5d 100644
--- a/chrome/browser/ui/passwords/settings/password_manager_presenter.cc
+++ b/chrome/browser/ui/passwords/settings/password_manager_presenter.cc
@@ -30,13 +30,13 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "components/autofill/core/common/password_form.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/password_manager/core/browser/password_list_sorter.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/browser/password_sync_util.h"
 #include "components/password_manager/core/browser/password_ui_utils.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/sync/driver/sync_service.h"
 #include "components/undo/undo_operation.h"
 #include "content/public/browser/browser_thread.h"
 
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.cc b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
index 83dc9ed..6615368 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
@@ -133,6 +133,14 @@
   return l10n_util::GetStringUTF16(IDS_APP_CANCEL);
 }
 
+bool CrostiniInstallerView::IsDialogButtonEnabled(
+    ui::DialogButton button) const {
+  if (button == ui::DIALOG_BUTTON_CANCEL &&
+      (state_ == State::CLEANUP || state_ == State::CLEANUP_FINISHED)) {
+    return false;
+  }
+  return true;
+}
 
 bool CrostiniInstallerView::ShouldShowCloseButton() const {
   return false;
@@ -157,14 +165,10 @@
   profile_->GetPrefs()->Set(crostini::prefs::kCrostiniContainers,
                             base::Value(base::Value::Type::LIST));
 
-  progress_bar_->SetVisible(true);
-
   // |learn_more_link_| should only be present in State::PROMPT.
   delete learn_more_link_;
   learn_more_link_ = nullptr;
 
-  StepProgress();
-
   // HandleError needs the |progress_bar_|, so we delay our Offline check until
   // it exists.
   if (content::GetNetworkConnectionTracker()->IsOffline()) {
@@ -250,7 +254,6 @@
   }
   VLOG(1) << "cros-termina install success";
   UpdateState(State::START_CONCIERGE);
-  StepProgress();
 }
 
 void CrostiniInstallerView::OnConciergeStarted(CrostiniResult result) {
@@ -265,7 +268,6 @@
   }
   VLOG(1) << "Concierge service started";
   UpdateState(State::CREATE_DISK_IMAGE);
-  StepProgress();
 }
 
 void CrostiniInstallerView::OnDiskImageCreated(
@@ -290,7 +292,6 @@
   }
   VLOG(1) << "Created crostini disk image";
   UpdateState(State::START_TERMINA_VM);
-  StepProgress();
 }
 
 void CrostiniInstallerView::OnVmStarted(CrostiniResult result) {
@@ -305,7 +306,6 @@
   }
   VLOG(1) << "Started Termina VM successfully";
   UpdateState(State::CREATE_CONTAINER);
-  StepProgress();
 }
 
 void CrostiniInstallerView::OnContainerDownloading(int32_t download_percent) {
@@ -319,7 +319,6 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK_EQ(state_, State::CREATE_CONTAINER);
   UpdateState(State::START_CONTAINER);
-  StepProgress();
 }
 
 void CrostiniInstallerView::OnContainerStarted(CrostiniResult result) {
@@ -336,7 +335,6 @@
   }
   VLOG(1) << "Started container successfully";
   UpdateState(State::SETUP_CONTAINER);
-  StepProgress();
 }
 
 void CrostiniInstallerView::OnContainerSetup(CrostiniResult result) {
@@ -353,7 +351,6 @@
   }
   VLOG(1) << "Set up container successfully";
   UpdateState(State::FETCH_SSH_KEYS);
-  StepProgress();
 }
 
 void CrostiniInstallerView::OnSshKeysFetched(CrostiniResult result) {
@@ -370,7 +367,6 @@
   }
   VLOG(1) << "Fetched ssh keys successfully";
   UpdateState(State::MOUNT_CONTAINER);
-  StepProgress();
 }
 
 // static
@@ -490,8 +486,6 @@
   UpdateState(State::ERROR);
   message_label_->SetVisible(true);
   message_label_->SetText(error_message);
-  SetBigMessageLabel();
-  progress_bar_->SetVisible(false);
 
   // Remove the buttons so they get recreated with correct color and
   // highlighting. Without this it is possible for both buttons to be styled as
@@ -512,7 +506,6 @@
         SetupResult::kErrorMountingContainer);
     return;
   }
-  StepProgress();
   ShowLoginShell();
 }
 
@@ -526,7 +519,6 @@
       crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
       std::vector<std::string>());
 
-  StepProgress();
   RecordSetupResultHistogram(SetupResult::kSuccess);
   crostini_manager->UpdateLaunchMetricsForEnterpriseReporting();
   RecordTimeFromDeviceSetupToInstallMetric();
@@ -628,6 +620,8 @@
       state_progress_timer_->AbandonAndStop();
     }
   }
+
+  StepProgress();
 }
 
 void CrostiniInstallerView::SetMessageLabel() {
@@ -662,6 +656,9 @@
     case State::MOUNT_CONTAINER:
       message_id = IDS_CROSTINI_INSTALLER_MOUNT_CONTAINER_MESSAGE;
       break;
+    case State::CLEANUP:
+      message_id = IDS_CROSTINI_INSTALLER_CANCELING;
+      break;
     default:
       break;
   }
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.h b/chrome/browser/ui/views/crostini/crostini_installer_view.h
index 9be1784..9a1c3b4 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.h
@@ -72,6 +72,7 @@
   // views::DialogDelegateView:
   int GetDialogButtons() const override;
   base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
+  bool IsDialogButtonEnabled(ui::DialogButton button) const override;
   bool ShouldShowCloseButton() const override;
   bool ShouldShowWindowTitle() const override;
   bool Accept() override;
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
index a5acd2a1f..e2483158 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view.cc
@@ -107,17 +107,15 @@
 // page.
 class CustomTabBarTitleOriginView : public views::View {
  public:
-  explicit CustomTabBarTitleOriginView(SkColor foreground_color,
-                                       SkColor background_color) {
-    title_label_ = new views::Label(
-        base::string16(), views::style::TextContext::CONTEXT_DIALOG_TITLE);
-    location_label_ = new views::Label(base::string16());
+  explicit CustomTabBarTitleOriginView(SkColor background_color) {
+    title_label_ = new views::Label(base::string16(), CONTEXT_BODY_TEXT_LARGE,
+                                    views::style::TextStyle::STYLE_PRIMARY);
+    location_label_ = new views::Label(
+        base::string16(), CONTEXT_BODY_TEXT_SMALL, STYLE_SECONDARY);
 
-    title_label_->SetEnabledColor(foreground_color);
     title_label_->SetBackgroundColor(background_color);
     title_label_->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
 
-    location_label_->SetEnabledColor(foreground_color);
     location_label_->SetBackgroundColor(background_color);
     location_label_->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
 
@@ -171,19 +169,20 @@
   title_bar_color_ = optional_theme_color.value_or(GetDefaultFrameColor());
   SetBackground(views::CreateSolidBackground(kCustomTabBarViewBackgroundColor));
 
-  constexpr SkColor kForegroundColor = gfx::kGoogleGrey900;
+  const SkColor foreground_color =
+      color_utils::GetColorWithMaxContrast(kCustomTabBarViewBackgroundColor);
 
   const gfx::FontList& font_list = views::style::GetFont(
       CONTEXT_OMNIBOX_PRIMARY, views::style::STYLE_PRIMARY);
 
-  close_button_ = CreateCloseButton(this, kForegroundColor);
+  close_button_ = CreateCloseButton(this, foreground_color);
   AddChildView(close_button_);
 
   location_icon_view_ = new LocationIconView(font_list, this);
   AddChildView(location_icon_view_);
 
-  title_origin_view_ = new CustomTabBarTitleOriginView(
-      kForegroundColor, kCustomTabBarViewBackgroundColor);
+  title_origin_view_ =
+      new CustomTabBarTitleOriginView(kCustomTabBarViewBackgroundColor);
   AddChildView(title_origin_view_);
 
   auto layout = std::make_unique<views::FlexLayout>();
diff --git a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
index 871c69f..029f640 100644
--- a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
@@ -25,9 +25,9 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_info.h"
+#include "components/sync/driver/sync_service.h"
 #include "content/public/browser/web_ui.h"
 #include "extensions/common/constants.h"
 #include "services/identity/public/cpp/identity_manager.h"
diff --git a/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc b/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc
index 5e5186e..08df873 100644
--- a/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc
+++ b/chrome/browser/ui/webui/print_preview/extension_printer_handler_unittest.cc
@@ -599,10 +599,12 @@
       base::MakeRefCounted<MockUsbDevice>(0, 1, "Google", "USB Printer", "");
   usb_service().AddDevice(device1);
 
-  const Extension* extension_1 = env_.MakeExtension(
-      *base::test::ParseJson(kExtension1), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-  const Extension* extension_2 = env_.MakeExtension(
-      *base::test::ParseJson(kExtension2), "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
+  const Extension* extension_1 =
+      env_.MakeExtension(*base::test::ParseJsonDeprecated(kExtension1),
+                         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+  const Extension* extension_2 =
+      env_.MakeExtension(*base::test::ParseJsonDeprecated(kExtension2),
+                         "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
 
   extensions::DevicePermissionsManager* permissions_manager =
       extensions::DevicePermissionsManager::Get(env_.profile());
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 33867b01..8b61315 100644
--- a/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
+++ b/chrome/browser/ui/webui/usb_internals/usb_internals_ui.cc
@@ -19,12 +19,12 @@
   source->AddResourcePath("usb_internals.css", IDR_USB_INTERNALS_CSS);
   source->AddResourcePath("usb_internals.js", IDR_USB_INTERNALS_JS);
   source->AddResourcePath(
-      "device/usb/public/mojom/device_manager_test.mojom.js",
+      "device/usb/public/mojom/device_manager_test.mojom-lite.js",
       IDR_USB_DEVICE_MANAGER_TEST_MOJO_JS);
   source->AddResourcePath(
-      "chrome/browser/ui/webui/usb_internals/usb_internals.mojom.js",
+      "chrome/browser/ui/webui/usb_internals/usb_internals.mojom-lite.js",
       IDR_USB_INTERNALS_MOJO_JS);
-  source->AddResourcePath("url/mojom/url.mojom.js", IDR_URL_MOJO_JS);
+  source->AddResourcePath("url/mojom/url.mojom-lite.js", IDR_URL_MOJO_LITE_JS);
   source->SetDefaultResource(IDR_USB_INTERNALS_HTML);
   source->UseGzip();
 
diff --git a/chrome/browser/web_applications/components/web_app_install_utils.cc b/chrome/browser/web_applications/components/web_app_install_utils.cc
index d6295571..39230d6 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/components/web_app_install_utils.cc
@@ -7,6 +7,10 @@
 #include <utility>
 
 #include "base/stl_util.h"
+#include "base/time/time.h"
+#include "chrome/browser/banners/app_banner_manager.h"
+#include "chrome/browser/banners/app_banner_manager_desktop.h"
+#include "chrome/browser/banners/app_banner_settings_helper.h"
 #include "chrome/browser/installable/installable_data.h"
 #include "chrome/browser/web_applications/components/web_app_icon_generator.h"
 #include "chrome/common/web_application_info.h"
@@ -163,4 +167,11 @@
   ReplaceWebAppIcons(size_to_icons, web_app_info);
 }
 
+void RecordAppBanner(content::WebContents* contents, const GURL& app_url) {
+  AppBannerSettingsHelper::RecordBannerEvent(
+      contents, app_url, app_url.spec(),
+      AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN,
+      base::Time::Now());
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_install_utils.h b/chrome/browser/web_applications/components/web_app_install_utils.h
index edda0a86..488134c 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils.h
+++ b/chrome/browser/web_applications/components/web_app_install_utils.h
@@ -19,6 +19,10 @@
 struct Manifest;
 }
 
+namespace content {
+class WebContents;
+}
+
 namespace web_app {
 
 struct BitmapAndSource;
@@ -65,6 +69,10 @@
     std::vector<BitmapAndSource> downloaded_icons,
     WebApplicationInfo* web_app_info);
 
+// Record an app banner added to homescreen event to ensure banners are not
+// shown for this app.
+void RecordAppBanner(content::WebContents* contents, const GURL& app_url);
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_INSTALL_UTILS_H_
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_action_key_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_action_key_unittest.cc
index 6e92c97..cc45977 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_action_key_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_action_key_unittest.cc
@@ -16,11 +16,12 @@
  protected:
   ManifestData CreateManifest(const std::string& action_json) {
     std::unique_ptr<base::DictionaryValue> manifest =
-        base::DictionaryValue::From(base::test::ParseJson(R"json({
+        base::DictionaryValue::From(
+            base::test::ParseJsonDeprecated(R"json({
                                     "name": "test",
                                     "version": "1",
                                     "manifest_version": 2, )json" +
-                                                          action_json + "}"));
+                                            action_json + "}"));
     EXPECT_TRUE(manifest);
     return ManifestData(std::move(manifest), "test");
   }
diff --git a/chrome/common/extensions/permissions/permissions_data_unittest.cc b/chrome/common/extensions/permissions/permissions_data_unittest.cc
index 37cabda..6a1c3b3 100644
--- a/chrome/common/extensions/permissions/permissions_data_unittest.cc
+++ b/chrome/common/extensions/permissions/permissions_data_unittest.cc
@@ -129,10 +129,7 @@
             extension->permissions_data()->IsRestrictedUrl(invalid_url, &error))
       << name;
   if (!allow_on_other_schemes) {
-    EXPECT_EQ(ErrorUtils::FormatErrorMessage(
-                  manifest_errors::kCannotAccessPage,
-                  invalid_url.spec()),
-              error) << name;
+    EXPECT_EQ(manifest_errors::kCannotAccessPage, error) << name;
   } else {
     EXPECT_TRUE(error.empty());
   }
diff --git a/chrome/installer/gcapi/BUILD.gn b/chrome/installer/gcapi/BUILD.gn
index 297c55a..a6a5018 100644
--- a/chrome/installer/gcapi/BUILD.gn
+++ b/chrome/installer/gcapi/BUILD.gn
@@ -54,7 +54,6 @@
     "//base",
     "//chrome/installer/launcher_support",
     "//chrome/installer/util:with_no_strings",
-    "//components/variations",
     "//google_update",
   ]
 }
@@ -75,7 +74,6 @@
     "//chrome/install_static:install_static_util",
     "//chrome/install_static/test:test_support",
     "//chrome/installer/util:with_no_strings",
-    "//components/variations",
     "//testing/gtest",
   ]
 
diff --git a/chrome/installer/gcapi/gcapi_omaha_experiment.cc b/chrome/installer/gcapi/gcapi_omaha_experiment.cc
index 286d22f..68c50c34 100644
--- a/chrome/installer/gcapi/gcapi_omaha_experiment.cc
+++ b/chrome/installer/gcapi/gcapi_omaha_experiment.cc
@@ -4,18 +4,27 @@
 
 #include "chrome/installer/gcapi/gcapi_omaha_experiment.h"
 
+#include "base/logging.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/time/time.h"
 #include "chrome/installer/gcapi/gcapi.h"
 #include "chrome/installer/gcapi/google_update_util.h"
 #include "chrome/installer/util/google_update_constants.h"
-#include "components/variations/variations_experiment_util.h"
 
 namespace {
 
+constexpr const base::char16* kDays[] = {
+    STRING16_LITERAL("Sun"), STRING16_LITERAL("Mon"), STRING16_LITERAL("Tue"),
+    STRING16_LITERAL("Wed"), STRING16_LITERAL("Thu"), STRING16_LITERAL("Fri"),
+    STRING16_LITERAL("Sat")};
+
+constexpr const base::char16* kMonths[] = {
+    STRING16_LITERAL("Jan"), STRING16_LITERAL("Feb"), STRING16_LITERAL("Mar"),
+    STRING16_LITERAL("Apr"), STRING16_LITERAL("May"), STRING16_LITERAL("Jun"),
+    STRING16_LITERAL("Jul"), STRING16_LITERAL("Aug"), STRING16_LITERAL("Sep"),
+    STRING16_LITERAL("Oct"), STRING16_LITERAL("Nov"), STRING16_LITERAL("Dec")};
+
 // Returns the number of weeks since 2/3/2003.
 int GetCurrentRlzWeek(const base::Time& current_time) {
   const base::Time::Exploded february_third_2003_exploded =
@@ -43,8 +52,7 @@
 
   // Split the original labels by the label separator.
   std::vector<base::string16> entries = base::SplitString(
-      original_labels,
-      base::string16(1, variations::kExperimentLabelSeparator),
+      original_labels, base::string16(1, kExperimentLabelSeparator),
       base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
 
   // Keep all labels, but the one we want to add/replace.
@@ -56,7 +64,7 @@
         !base::StartsWith(entry, label_and_separator,
                           base::CompareCase::SENSITIVE)) {
       new_labels += entry;
-      new_labels += variations::kExperimentLabelSeparator;
+      new_labels += kExperimentLabelSeparator;
     }
   }
 
@@ -86,17 +94,16 @@
 
   base::string16 gcapi_experiment_label;
   base::SStringPrintf(&gcapi_experiment_label,
-                      L"%ls=%ls_%d|%ls",
-                      label.c_str(),
-                      brand_code,
-                      GetCurrentRlzWeek(instance_time),
-                      variations::BuildExperimentDateString(
-                          instance_time).c_str());
+                      STRING16_LITERAL("%ls=%ls_%d|%ls"), label.c_str(),
+                      brand_code, GetCurrentRlzWeek(instance_time),
+                      BuildExperimentDateString(instance_time).c_str());
   return gcapi_experiment_label;
 }
 
 }  // namespace gcapi_internals
 
+const base::char16 kExperimentLabelSeparator = ';';
+
 bool SetReactivationExperimentLabels(const wchar_t* brand_code,
                                      int shell_mode) {
   return SetExperimentLabel(brand_code, gcapi_internals::kReactivationLabel,
@@ -107,3 +114,25 @@
   return SetExperimentLabel(brand_code, gcapi_internals::kRelaunchLabel,
                             shell_mode);
 }
+
+base::string16 BuildExperimentDateString(base::Time current_time) {
+  // The Google Update experiment_labels timestamp format is:
+  // "DAY, DD0 MON YYYY HH0:MI0:SE0 TZ"
+  //  DAY = 3 character day of week,
+  //  DD0 = 2 digit day of month,
+  //  MON = 3 character month of year,
+  //  YYYY = 4 digit year,
+  //  HH0 = 2 digit hour,
+  //  MI0 = 2 digit minute,
+  //  SE0 = 2 digit second,
+  //  TZ = 3 character timezone
+  base::Time::Exploded then = {};
+  current_time.UTCExplode(&then);
+  then.year += 1;
+  DCHECK(then.HasValidValues());
+
+  return base::StringPrintf(
+      STRING16_LITERAL("%s, %02d %s %d %02d:%02d:%02d GMT"),
+      kDays[then.day_of_week], then.day_of_month, kMonths[then.month - 1],
+      then.year, then.hour, then.minute, then.second);
+}
diff --git a/chrome/installer/gcapi/gcapi_omaha_experiment.h b/chrome/installer/gcapi/gcapi_omaha_experiment.h
index 8e5d79f0..5e423ccc 100644
--- a/chrome/installer/gcapi/gcapi_omaha_experiment.h
+++ b/chrome/installer/gcapi/gcapi_omaha_experiment.h
@@ -6,6 +6,7 @@
 #define CHROME_INSTALLER_GCAPI_GCAPI_OMAHA_EXPERIMENT_H_
 
 #include "base/strings/string16.h"
+#include "base/time/time.h"
 
 namespace gcapi_internals {
 
@@ -19,6 +20,9 @@
 
 }  // namespace gcapi_internals
 
+// The separator used to separate items in experiment labels.
+extern const base::char16 kExperimentLabelSeparator;
+
 // Writes a reactivation brand code experiment label in the Chrome product and
 // binaries registry keys for |brand_code|. This experiment label will have a
 // expiration date of now plus one year. If |shell_mode| is set to
@@ -35,4 +39,8 @@
 // at the same time (they are mutually exclusive).
 bool SetRelaunchExperimentLabels(const wchar_t* brand_code, int shell_mode);
 
+// Constructs a date string in the format understood by Google Update for the
+// |current_time| plus one year.
+base::string16 BuildExperimentDateString(base::Time current_time);
+
 #endif  // CHROME_INSTALLER_GCAPI_GCAPI_OMAHA_EXPERIMENT_H_
diff --git a/chrome/installer/gcapi/gcapi_omaha_experiment_test.cc b/chrome/installer/gcapi/gcapi_omaha_experiment_test.cc
index 038c7fe4..00ce5297 100644
--- a/chrome/installer/gcapi/gcapi_omaha_experiment_test.cc
+++ b/chrome/installer/gcapi/gcapi_omaha_experiment_test.cc
@@ -6,17 +6,13 @@
 
 #include <stdint.h>
 
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/test_reg_util_win.h"
 #include "chrome/install_static/test/scoped_install_details.h"
 #include "chrome/installer/gcapi/gcapi.h"
 #include "chrome/installer/util/google_update_constants.h"
 #include "chrome/installer/util/google_update_settings.h"
-#include "components/variations/variations_experiment_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::ASCIIToUTF16;
-
 namespace {
 
 const wchar_t kBrand[] = L"ABCD";
@@ -83,7 +79,7 @@
   ASSERT_TRUE(SetReactivationExperimentLabels(kBrand, shell_mode()));
 
   base::string16 expected_labels(kSomeExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(reactivation_label_);
   VerifyExperimentLabels(expected_labels);
 }
@@ -91,18 +87,18 @@
 TEST_P(GCAPIOmahaExperimentTest,
        SetReactivationLabelWithExistingIdenticalExperiment) {
   base::string16 previous_labels(kSomeExperiments);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(reactivation_label_);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(kSomeOtherExperiments);
   GoogleUpdateSettings::SetExperimentLabels(previous_labels);
 
   ASSERT_TRUE(SetReactivationExperimentLabels(kBrand, shell_mode()));
 
   base::string16 expected_labels(kSomeExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(kSomeOtherExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(reactivation_label_);
   VerifyExperimentLabels(expected_labels);
 }
@@ -110,14 +106,14 @@
 TEST_P(GCAPIOmahaExperimentTest,
        SetReactivationLabelWithExistingIdenticalAtBeginning) {
   base::string16 previous_labels(reactivation_label_);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(kSomeExperiments);
   GoogleUpdateSettings::SetExperimentLabels(previous_labels);
 
   ASSERT_TRUE(SetReactivationExperimentLabels(kBrand, shell_mode()));
 
   base::string16 expected_labels(kSomeExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(reactivation_label_);
   VerifyExperimentLabels(expected_labels);
 }
@@ -125,30 +121,30 @@
 TEST_P(GCAPIOmahaExperimentTest,
        SetReactivationLabelWithFakeMatchInAnExperiment) {
   base::string16 previous_labels(kSomeExperiments);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(L"blah_");
   // Shouldn't match deletion criteria.
   previous_labels.append(reactivation_label_);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(kSomeOtherExperiments);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   // Should match the deletion criteria.
   previous_labels.append(reactivation_label_);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(kSomeMoreExperiments);
   GoogleUpdateSettings::SetExperimentLabels(previous_labels);
 
   ASSERT_TRUE(SetReactivationExperimentLabels(kBrand, shell_mode()));
 
   base::string16 expected_labels(kSomeExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(L"blah_");
   expected_labels.append(reactivation_label_);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(kSomeOtherExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(kSomeMoreExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(reactivation_label_);
   VerifyExperimentLabels(expected_labels);
 }
@@ -156,23 +152,23 @@
 TEST_P(GCAPIOmahaExperimentTest,
        SetReactivationLabelWithFakeMatchInAnExperimentAndNoRealMatch) {
   base::string16 previous_labels(kSomeExperiments);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(L"blah_");
   // Shouldn't match deletion criteria.
   previous_labels.append(reactivation_label_);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   previous_labels.append(kSomeOtherExperiments);
   GoogleUpdateSettings::SetExperimentLabels(previous_labels);
 
   ASSERT_TRUE(SetReactivationExperimentLabels(kBrand, shell_mode()));
 
   base::string16 expected_labels(kSomeExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(L"blah_");
   expected_labels.append(reactivation_label_);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(kSomeOtherExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(reactivation_label_);
   VerifyExperimentLabels(expected_labels);
 }
@@ -180,7 +176,7 @@
 TEST_P(GCAPIOmahaExperimentTest,
        SetReactivationLabelWithExistingEntryWithLabelAsPrefix) {
   base::string16 previous_labels(kSomeExperiments);
-  previous_labels += variations::kExperimentLabelSeparator;
+  previous_labels += kExperimentLabelSeparator;
   // Append prefix matching the label, but not followed by '='.
   previous_labels.append(gcapi_internals::kReactivationLabel);
   // Shouldn't match deletion criteria.
@@ -190,14 +186,113 @@
   ASSERT_TRUE(SetReactivationExperimentLabels(kBrand, shell_mode()));
 
   base::string16 expected_labels(kSomeExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(gcapi_internals::kReactivationLabel);
   expected_labels.append(kSomeOtherExperiments);
-  expected_labels += variations::kExperimentLabelSeparator;
+  expected_labels += kExperimentLabelSeparator;
   expected_labels.append(reactivation_label_);
   VerifyExperimentLabels(expected_labels);
 }
 
+TEST_P(GCAPIOmahaExperimentTest, BuildExperimentDateString) {
+  // Sat, 1 Jan 2000 00:00:00 UTC
+  base::Time::Exploded kTestTimeExploded = {2000, 1, 6, 1, 0, 0, 0, 0};
+  base::Time kTestTime;
+  EXPECT_TRUE(base::Time::FromUTCExploded(kTestTimeExploded, &kTestTime));
+  EXPECT_EQ(STRING16_LITERAL("Sat, 01 Jan 2001 00:00:00 GMT"),
+            BuildExperimentDateString(kTestTime));
+  EXPECT_EQ(STRING16_LITERAL("Sat, 01 Jan 2001 00:00:00 GMT"),
+            BuildExperimentDateString(kTestTime +
+                                      base::TimeDelta::FromMilliseconds(1)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 00:00:01 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromSeconds(1)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 00:00:59 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromSeconds(59)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 00:01:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromSeconds(60)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 00:01:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromMinutes(1)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 00:59:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromMinutes(59)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 01:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromMinutes(60)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 01:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromHours(1)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jan 2001 23:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromHours(23)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sun, 02 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromHours(24)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sun, 02 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(1)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Mon, 03 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(2)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Tue, 04 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(3)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Wed, 05 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(4)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Thu, 06 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(5)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Fri, 07 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(6)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 08 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(7)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Mon, 31 Jan 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(30)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Tue, 01 Feb 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(31)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Wed, 01 Mar 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(60)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Apr 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(91)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Mon, 01 May 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(121)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Thu, 01 Jun 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(152)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sat, 01 Jul 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(182)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Tue, 01 Aug 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(213)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Fri, 01 Sep 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(244)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Sun, 01 Oct 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(274)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Wed, 01 Nov 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(305)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Fri, 01 Dec 2001 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(335)));
+  EXPECT_EQ(
+      STRING16_LITERAL("Mon, 01 Jan 2002 00:00:00 GMT"),
+      BuildExperimentDateString(kTestTime + base::TimeDelta::FromDays(366)));
+}
+
 INSTANTIATE_TEST_SUITE_P(, GCAPIOmahaExperimentTest, ::testing::Bool());
 
 }  // namespace
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 65faf7c..5cea213 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -382,6 +382,9 @@
     "//media/test/data/four-colors-rot-180.mp4",
     "//media/test/data/four-colors-rot-270.mp4",
     "//media/test/data/four-colors-vp9.webm",
+
+    # For power
+    "//media/test/data/bear-1280x720.mp4",
   ]
 
   # For pixel_test and maps_pixel_test. Because this links to a CIPD
diff --git a/chrome/test/data/autofill/captured_sites/cipd.yaml b/chrome/test/data/autofill/captured_sites/cipd.yaml
index 839a8b88..505c396 100644
--- a/chrome/test/data/autofill/captured_sites/cipd.yaml
+++ b/chrome/test/data/autofill/captured_sites/cipd.yaml
@@ -13,7 +13,7 @@
 # Captured Sites Test Framework Eng doc:
 # https://docs.google.com/document/d/12ZLoGmBK9kc5C5ComHkJWWJUXOda5K0uFr-hfSt6ZUg
 description: captured sites test archives and recipes.
-install_mode: "copy"
+install_mode: copy
 data:
   - file: amazon
   - file: amazon.test
diff --git a/chrome/test/data/extensions/api_test/permissions/file_access_no/background.js b/chrome/test/data/extensions/api_test/permissions/file_access_no/background.js
index bfc3c0e..bc54ecb 100644
--- a/chrome/test/data/extensions/api_test/permissions/file_access_no/background.js
+++ b/chrome/test/data/extensions/api_test/permissions/file_access_no/background.js
@@ -5,7 +5,7 @@
 var callbackFail = chrome.test.callbackFail;
 var callbackPass = chrome.test.callbackPass;
 var expectedError =
-    "Invalid value for origin pattern file:///Invalid scheme.: *";
+    "Invalid value for origin pattern file:///*: Invalid scheme.";
 
 function test() {
   chrome.permissions.request({"origins": ["file:///*"]},
diff --git a/chrome/test/data/webui/cr_elements/cr_action_menu_test.js b/chrome/test/data/webui/cr_elements/cr_action_menu_test.js
index 76800e7a..e5b5a6d 100644
--- a/chrome/test/data/webui/cr_elements/cr_action_menu_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_action_menu_test.js
@@ -166,6 +166,13 @@
     assertEquals(items[2], getDeepActiveElement());
   });
 
+  test('close on click away', function() {
+    menu.showAt(dots);
+    assertTrue(dialog.open);
+    menu.click();
+    assertFalse(dialog.open);
+  });
+
   test('close on resize', function() {
     menu.showAt(dots);
     assertTrue(dialog.open);
diff --git a/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
index 92a5fd7..c364aa6 100644
--- a/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
@@ -1256,3 +1256,31 @@
       this.runMochaTest(
           destination_settings_test.TestNames.TwoAccountsRecentDestinations);
     });
+
+PrintPreviewScalingSettingsTest = class extends NewPrintPreviewTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://print/new/scaling_settings.html';
+  }
+
+  /** @override */
+  get extraLibraries() {
+    return super.extraLibraries.concat([
+      '../settings/test_util.js',
+      'print_preview_test_utils.js',
+      'scaling_settings_test.js',
+    ]);
+  }
+
+  /** @override */
+  get suiteName() {
+    return scaling_settings_test.suiteName;
+  }
+};
+
+TEST_F(
+    'PrintPreviewScalingSettingsTest', 'InputNotDisabledOnValidityChange',
+    function() {
+      this.runMochaTest(
+          scaling_settings_test.TestNames.InputNotDisabledOnValidityChange);
+    });
diff --git a/chrome/test/data/webui/print_preview/scaling_settings_test.js b/chrome/test/data/webui/print_preview/scaling_settings_test.js
new file mode 100644
index 0000000..7a7299b
--- /dev/null
+++ b/chrome/test/data/webui/print_preview/scaling_settings_test.js
@@ -0,0 +1,148 @@
+// 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.
+
+cr.define('scaling_settings_test', function() {
+  /** @enum {string} */
+  const TestNames = {
+    InputNotDisabledOnValidityChange: 'input not disabled on validity change',
+  };
+
+  const suiteName = 'ScalingSettingsTest';
+  suite(suiteName, function() {
+    /** @type {?PrintPreviewScalingSettingsElement} */
+    let scalingSection = null;
+
+    /** @override */
+    setup(function() {
+      PolymerTest.clearBody();
+      scalingSection = document.createElement('print-preview-scaling-settings');
+      scalingSection.settings = {
+        scaling: {
+          value: '100',
+          unavailableValue: '100',
+          valid: true,
+          available: true,
+          key: 'scaleFactor',
+        },
+        customScaling: {
+          value: false,
+          unavailableValue: false,
+          valid: true,
+          available: true,
+          key: 'customScaling',
+        },
+        fitToPage: {
+          value: false,
+          unavailableValue: false,
+          valid: true,
+          available: false,
+          key: 'isFitToPageEnabled',
+        },
+      };
+      scalingSection.disabled = false;
+      document.body.appendChild(scalingSection);
+    });
+
+    /**
+     * Sets up the scaling section to use the custom input with the input string
+     * given by |inputString|.
+     * @param {string} inputString
+     * @param {number} pageCount
+     * @return {!Promise} Promise that resolves when the input-change event
+     *     has fired.
+     */
+    function setupInput(inputString) {
+      const numberSection =
+          scalingSection.$$('print-preview-number-settings-section');
+      const input = numberSection.getInput();
+      const scalingSelect = scalingSection.$$('select');
+      const isCustomSelected = scalingSelect.value ===
+          scalingSection.scalingValueEnum_.CUSTOM.toString();
+      const readyForInput = isCustomSelected ?
+          Promise.resolve() :
+          test_util.eventToPromise('process-select-change', scalingSection);
+
+      // Select custom
+      if (!isCustomSelected) {
+        scalingSelect.value =
+            scalingSection.scalingValueEnum_.CUSTOM.toString();
+        scalingSelect.dispatchEvent(new CustomEvent('change'));
+      }
+      return readyForInput.then(() => {
+        // Set input string
+        input.value = inputString;
+        input.dispatchEvent(
+            new CustomEvent('input', {composed: true, bubbles: true}));
+
+        // Validate results
+        return test_util.eventToPromise('input-change', numberSection);
+      });
+    }
+
+    /**
+     * @param {string} expectedScaling The expected scaling value.
+     * @param {boolean} invalid Whether the scaling setting should be invalid.
+     */
+    function validateState(expectedScaling, invalid) {
+      assertEquals(expectedScaling, scalingSection.getSettingValue('scaling'));
+      assertEquals(!invalid, scalingSection.getSetting('scaling').valid);
+      assertEquals(
+          invalid,
+          scalingSection.$$('print-preview-number-settings-section')
+              .getInput()
+              .invalid);
+    }
+
+    // Verifies that the input is never disabled when the validity of the
+    // setting changes.
+    test(assert(TestNames.InputNotDisabledOnValidityChange), function() {
+      // In the real UI, the print preview app listens for this event from this
+      // section and others and sets disabled to true if any change from true to
+      // false is detected. Imitate this here. Since we are only interacting
+      // with the scaling input, at no point should the input be disabled, as it
+      // will lose focus.
+      scalingSection.addEventListener('setting-valid-changed', function(e) {
+        const numberSection =
+            scalingSection.$$('print-preview-number-settings-section');
+        assertFalse(numberSection.getInput().disabled);
+      });
+
+      // Set a valid input
+      return setupInput('90')
+          .then(function() {
+            validateState('90', false);
+            // Set invalid input
+            return setupInput('9');
+          })
+          .then(function() {
+            validateState('90', true);
+            // Restore valid input
+            return setupInput('90');
+          })
+          .then(function() {
+            validateState('90', false);
+            // Invalid input again
+            return setupInput('9');
+          })
+          .then(function() {
+            validateState('90', true);
+            // Clear input
+            return setupInput('');
+          })
+          .then(function() {
+            validateState('90', false);
+            // Set valid input
+            return setupInput('50');
+          })
+          .then(function() {
+            validateState('50', false);
+          });
+    });
+  });
+
+  return {
+    suiteName: suiteName,
+    TestNames: TestNames,
+  };
+});
diff --git a/chrome/test/data/webui/settings/bluetooth_page_tests.js b/chrome/test/data/webui/settings/bluetooth_page_tests.js
index da9a6bc7..544b347b 100644
--- a/chrome/test/data/webui/settings/bluetooth_page_tests.js
+++ b/chrome/test/data/webui/settings/bluetooth_page_tests.js
@@ -18,6 +18,25 @@
   };
 }
 
+/**
+ * @param {number} numPairedDevices Number of paired devices to generate.
+ * @param {number} numUnpairedDevices Number of unparied devices to generate.
+ * @return {!Array<!chrome.bluetooth.Device>} An array of fake bluetooth
+ *     devices.
+ */
+function generateFakeDevices(numPairedDevices, numUnpairedDevices) {
+  let devices = [];
+  for (let i = 0; i < numPairedDevices + numUnpairedDevices; ++i) {
+    devices.push({
+      address: '00:00:00:00:01:' + i.toString().padStart(2, '0'),
+      name: 'FakeDevice' + i,
+      paired: i < numPairedDevices,
+      connected: false,
+    });
+  }
+  return devices;
+}
+
 suite('Bluetooth', function() {
   let bluetoothPage = null;
 
@@ -161,14 +180,17 @@
       assertFalse(bluetoothPage.bluetoothToggleState_);
     });
 
-    // listUpdateFrequencyMs is set to 0 for tests, but we still need to wait
-    // for the callback of setTimeout(0) to be processed in the message queue.
-    // Add another setTimeout(0) to the end of message queue and wait for it to
-    // complete ensures the previous callback has been executed.
-    function waitForListUpdateTimeout() {
-      return new Promise(function(resolve) {
+    async function waitForListUpdateTimeout() {
+      // listUpdateFrequencyMs is set to 0 for tests, but we still need to wait
+      // for the callback of setTimeout(0) to be processed in the message queue.
+      await new Promise(function(resolve) {
         setTimeout(resolve, 0);
       });
+
+      // Adding two flushTasks ensures that all events are fully handled after
+      // being fired.
+      await PolymerTest.flushTasks();
+      await PolymerTest.flushTasks();
     }
 
     test('pair device', async function() {
@@ -178,8 +200,9 @@
       ]);
 
       await waitForListUpdateTimeout();
-      Polymer.dom.flush();
 
+      // TODO(jlklein): Stop referencing private state in these tests. Only use
+      // public observable state.
       assertEquals(4, subpage.deviceList_.length);
       assertEquals(2, subpage.pairedDeviceList_.length);
       assertEquals(2, subpage.unpairedDeviceList_.length);
@@ -189,7 +212,6 @@
           resolve => bluetoothPrivateApi.connect(address, resolve));
 
       await waitForListUpdateTimeout();
-      Polymer.dom.flush();
 
       assertEquals(3, subpage.pairedDeviceList_.length);
       assertEquals(1, subpage.unpairedDeviceList_.length);
@@ -202,7 +224,6 @@
       ]);
       await waitForListUpdateTimeout();
 
-      Polymer.dom.flush();
       const dialog = subpage.$.deviceDialog;
       assertTrue(!!dialog);
       assertFalse(dialog.$.dialog.open);
@@ -251,7 +272,6 @@
         bluetoothApi.simulateDevicesAddedForTest(
             [fakeUnpairedDevice1, fakeUnpairedDevice2]);
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(2, deviceList().length);
         assertEquals(2, unpairedDeviceList().length);
@@ -264,7 +284,6 @@
         assertEquals(
             unpairedDeviceList()[1].address, fakeUnpairedDevice2.address);
 
-        unpairedDeviceIronList.notifyResize();
         Polymer.dom.flush();
 
         const devices = unpairedDeviceIronList.querySelectorAll(
@@ -294,7 +313,6 @@
         bluetoothApi.simulateDevicesAddedForTest([fakeUnpairedDevice1]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(2, deviceList().length);
         assertEquals(2, unpairedDeviceList().length);
@@ -312,7 +330,6 @@
             [fakeUnpairedDevice1.address, fakeUnpairedDevice2.address]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(0, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -327,7 +344,6 @@
             [fakeUnpairedDevice1, fakeUnpairedDevice2, fakeUnpairedDevice3]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(3, deviceList().length);
         assertEquals(3, unpairedDeviceList().length);
@@ -341,7 +357,6 @@
         bluetoothApi.simulateDeviceUpdatedForTest(updatedDevice);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(3, deviceList().length);
         assertEquals(3, unpairedDeviceList().length);
@@ -360,7 +375,6 @@
         bluetoothApi.simulateDevicesAddedForTest(
             [fakePairedDevice1, fakePairedDevice2]);
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(2, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -368,7 +382,6 @@
         assertFalse(subpage.$.noUnpairedDevices.hidden);
         assertTrue(subpage.$.noPairedDevices.hidden);
 
-        pairedDeviceIronList.notifyResize();
         Polymer.dom.flush();
 
         const devices =
@@ -383,7 +396,6 @@
         bluetoothApi.simulateDevicesRemovedForTest([fakePairedDevice1.address]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(1, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -398,7 +410,6 @@
         bluetoothApi.simulateDevicesAddedForTest([fakePairedDevice1]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(2, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -414,7 +425,6 @@
             [fakePairedDevice1.address, fakePairedDevice2.address]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(0, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -429,7 +439,6 @@
             [fakePairedDevice1, fakePairedDevice2, fakePairedDevice3]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(3, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -443,7 +452,6 @@
         bluetoothApi.simulateDeviceUpdatedForTest(updatedDevice);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(3, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -462,7 +470,6 @@
         bluetoothApi.simulateDevicesAddedForTest([fakeUnpairedDevice1]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(1, deviceList().length);
         assertEquals(1, unpairedDeviceList().length);
@@ -477,7 +484,6 @@
         bluetoothApi.simulateDeviceUpdatedForTest(nowPairedDevice);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(1, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -495,7 +501,6 @@
         bluetoothApi.simulateDevicesAddedForTest([fakePairedDevice1]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(1, deviceList().length);
         assertEquals(0, unpairedDeviceList().length);
@@ -510,7 +515,6 @@
         bluetoothApi.simulateDeviceUpdatedForTest(nowUnpairedDevice);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(1, deviceList().length);
         assertEquals(1, unpairedDeviceList().length);
@@ -530,7 +534,6 @@
         ]);
 
         await waitForListUpdateTimeout();
-        Polymer.dom.flush();
 
         assertEquals(4, deviceList().length);
         assertEquals(2, unpairedDeviceList().length);
@@ -538,8 +541,6 @@
         assertTrue(subpage.$.noUnpairedDevices.hidden);
         assertTrue(subpage.$.noPairedDevices.hidden);
 
-        pairedDeviceIronList.notifyResize();
-        unpairedDeviceIronList.notifyResize();
         Polymer.dom.flush();
 
         const unpairedDevices = unpairedDeviceIronList.querySelectorAll(
@@ -556,6 +557,26 @@
         assertTrue(pairedDevices[1].device.paired);
         assertFalse(pairedDevices[1].device.connected);
       });
+
+      test('Unpaired and paired devices: many devices added', async function() {
+        bluetoothApi.simulateDevicesAddedForTest(generateFakeDevices(5, 15));
+
+        await waitForListUpdateTimeout();
+
+        assertEquals(20, deviceList().length);
+        assertEquals(15, unpairedDeviceList().length);
+        assertEquals(5, pairedDeviceList().length);
+        assertTrue(subpage.$.noUnpairedDevices.hidden);
+        assertTrue(subpage.$.noPairedDevices.hidden);
+
+        const unpairedDevices = unpairedDeviceIronList.querySelectorAll(
+            'bluetooth-device-list-item');
+        assertEquals(15, unpairedDevices.length);
+
+        const pairedDevices =
+            pairedDeviceIronList.querySelectorAll('bluetooth-device-list-item');
+        assertEquals(5, pairedDevices.length);
+      });
     });
   });
 });
diff --git a/chrome/test/remoting/it2me_browsertest.cc b/chrome/test/remoting/it2me_browsertest.cc
index 6a9123a..5efd795 100644
--- a/chrome/test/remoting/it2me_browsertest.cc
+++ b/chrome/test/remoting/it2me_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/string_number_conversions.h"
 #include "chrome/test/remoting/remote_desktop_browsertest.h"
+#include "chrome/test/remoting/remote_test_helper.h"
 
 namespace remoting {
 
diff --git a/chrome/test/remoting/remote_desktop_browsertest.cc b/chrome/test/remoting/remote_desktop_browsertest.cc
index a1f563c1..2cf56e3 100644
--- a/chrome/test/remoting/remote_desktop_browsertest.cc
+++ b/chrome/test/remoting/remote_desktop_browsertest.cc
@@ -18,8 +18,10 @@
 #include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/webui/signin/login_ui_test_utils.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/ui_test_utils.h"
 #include "chrome/test/remoting/key_code_conv.h"
 #include "chrome/test/remoting/page_load_notification_observer.h"
+#include "chrome/test/remoting/remote_test_helper.h"
 #include "chrome/test/remoting/waiter.h"
 #include "content/public/browser/native_web_keyboard_event.h"
 #include "content/public/browser/render_view_host.h"
@@ -30,11 +32,33 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/switches.h"
+#include "net/dns/mock_host_resolver.h"
 #include "ui/base/window_open_disposition.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
 
+using extensions::Extension;
+
 namespace remoting {
 
+namespace {
+
+// Command line arguments specific to the chromoting browser tests.
+const char kOverrideUserDataDir[] = "override-user-data-dir";
+const char kNoCleanup[] = "no-cleanup";
+const char kNoInstall[] = "no-install";
+const char kWebAppCrx[] = "webapp-crx";
+const char kWebAppUnpacked[] = "webapp-unpacked";
+const char kUserName[] = "username";
+const char kUserPassword[] = "password";
+const char kAccountsFile[] = "accounts-file";
+const char kAccountType[] = "account-type";
+const char kMe2MePin[] = "me2me-pin";
+const char kRemoteHostName[] = "remote-host-name";
+const char kExtensionName[] = "extension-name";
+const char kHttpServer[] = "http-server";
+
+}  // namespace
+
 RemoteDesktopBrowserTest::RemoteDesktopBrowserTest()
     : remote_test_helper_(nullptr), extension_(nullptr) {
 }
@@ -712,6 +736,28 @@
   observer.Wait();
 }
 
+bool RemoteDesktopBrowserTest::ExecuteScriptAndExtractBool(
+    const std::string& script) {
+  return RemoteTestHelper::ExecuteScriptAndExtractBool(active_web_contents(),
+                                                       script);
+}
+
+// Helper to execute a JavaScript code snippet in the active WebContents
+// and extract the int result.
+int RemoteDesktopBrowserTest::ExecuteScriptAndExtractInt(
+    const std::string& script) {
+  return RemoteTestHelper::ExecuteScriptAndExtractInt(active_web_contents(),
+                                                      script);
+}
+
+// Helper to execute a JavaScript code snippet in the active WebContents
+// and extract the string result.
+std::string RemoteDesktopBrowserTest::ExecuteScriptAndExtractString(
+    const std::string& script) {
+  return RemoteTestHelper::ExecuteScriptAndExtractString(active_web_contents(),
+                                                         script);
+}
+
 // static
 bool RemoteDesktopBrowserTest::LoadScript(
     content::WebContents* web_contents,
@@ -746,9 +792,9 @@
       content::ExecuteScriptAndExtractString(web_contents, script, &result));
 
   // Read in the JSON
-  base::JSONReader reader;
-  std::unique_ptr<base::Value> value =
-      reader.ReadDeprecated(result, base::JSON_ALLOW_TRAILING_COMMAS);
+  base::Optional<base::Value> value =
+      base::JSONReader::Read(result, base::JSON_ALLOW_TRAILING_COMMAS);
+  ASSERT_TRUE(value);
 
   // Convert to dictionary
   base::DictionaryValue* dict_value = NULL;
@@ -836,10 +882,9 @@
 }
 
 bool RemoteDesktopBrowserTest::IsHostOnline(const std::string& host_id) {
-
   ExecuteScript("remoting.hostList.refreshAndDisplay()");
 
- // Verify the host is online.
+  // Verify the host is online.
   std::string element_id = "host_" + host_id;
   std::string host_div_class = ExecuteScriptAndExtractString(
       "document.getElementById('" + element_id + "').parentNode.className");
@@ -890,11 +935,12 @@
   ASSERT_TRUE(base::ReadFileToString(absolute_path, &accounts_info));
 
   // Get the root dictionary from the input json file contents.
-  std::unique_ptr<base::Value> root = base::JSONReader::ReadDeprecated(
-      accounts_info, base::JSON_ALLOW_TRAILING_COMMAS);
+  base::Optional<base::Value> root =
+      base::JSONReader::Read(accounts_info, base::JSON_ALLOW_TRAILING_COMMAS);
 
   const base::DictionaryValue* root_dict = NULL;
-  ASSERT_TRUE(root.get() && root->GetAsDictionary(&root_dict));
+  ASSERT_TRUE(root);
+  ASSERT_TRUE(root->GetAsDictionary(&root_dict));
 
   // Now get the dictionary for the specified account type.
   const base::DictionaryValue* account_dict = NULL;
diff --git a/chrome/test/remoting/remote_desktop_browsertest.h b/chrome/test/remoting/remote_desktop_browsertest.h
index 7d58c23a..903b2b1 100644
--- a/chrome/test/remoting/remote_desktop_browsertest.h
+++ b/chrome/test/remoting/remote_desktop_browsertest.h
@@ -5,40 +5,22 @@
 #ifndef CHROME_TEST_REMOTING_REMOTE_DESKTOP_BROWSERTEST_H_
 #define CHROME_TEST_REMOTING_REMOTE_DESKTOP_BROWSERTEST_H_
 
-#include "base/debug/stack_trace.h"
+#include <memory>
+#include <string>
+#include <vector>
+
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "chrome/test/remoting/remote_test_helper.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/test/browser_test_utils.h"
-#include "net/dns/mock_host_resolver.h"
+#include "third_party/blink/public/platform/web_mouse_event.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/dom_key.h"
-
-namespace {
-// Command line arguments specific to the chromoting browser tests.
-const char kOverrideUserDataDir[] = "override-user-data-dir";
-const char kNoCleanup[] = "no-cleanup";
-const char kNoInstall[] = "no-install";
-const char kWebAppCrx[] = "webapp-crx";
-const char kWebAppUnpacked[] = "webapp-unpacked";
-const char kUserName[] = "username";
-const char kUserPassword[] = "password";
-const char kAccountsFile[] = "accounts-file";
-const char kAccountType[] = "account-type";
-const char kMe2MePin[] = "me2me-pin";
-const char kRemoteHostName[] = "remote-host-name";
-const char kExtensionName[] = "extension-name";
-const char kHttpServer[] = "http-server";
-
-}  // namespace
-
-using extensions::Extension;
+#include "ui/events/keycodes/keyboard_codes.h"
 
 namespace remoting {
 
+class RemoteTestHelper;
+
 class RemoteDesktopBrowserTest : public extensions::PlatformAppBrowserTest {
  public:
   RemoteDesktopBrowserTest();
@@ -82,7 +64,7 @@
   // to set up appropriate mocks.
   // |window_open_disposition| controls where the app will be launched.  For v2
   // app, the value of |window_open_disposition| will always be NEW_WINDOW.
-  // Returns the content::Webconetns of the launched app. The lifetime of the
+  // Returns the content::WebContents of the launched app. The lifetime of the
   // returned value is managed by LaunchChromotingApp().
   content::WebContents* LaunchChromotingApp(bool defer_start);
   content::WebContents* LaunchChromotingApp(
@@ -256,24 +238,15 @@
 
   // Helper to execute a JavaScript code snippet in the active WebContents
   // and extract the boolean result.
-  bool ExecuteScriptAndExtractBool(const std::string& script) {
-    return RemoteTestHelper::ExecuteScriptAndExtractBool(
-        active_web_contents(), script);
-  }
+  bool ExecuteScriptAndExtractBool(const std::string& script);
 
   // Helper to execute a JavaScript code snippet in the active WebContents
   // and extract the int result.
-  int ExecuteScriptAndExtractInt(const std::string& script) {
-    return RemoteTestHelper::ExecuteScriptAndExtractInt(
-        active_web_contents(), script);
-  }
+  int ExecuteScriptAndExtractInt(const std::string& script);
 
   // Helper to execute a JavaScript code snippet in the active WebContents
   // and extract the string result.
-  std::string ExecuteScriptAndExtractString(const std::string& script) {
-    return RemoteTestHelper::ExecuteScriptAndExtractString(
-        active_web_contents(), script);
-  }
+  std::string ExecuteScriptAndExtractString(const std::string& script);
 
   // Helper to load a JavaScript file from |path| and inject it to
   // current web_content.  The variable |path| is relative to the directory of
@@ -390,7 +363,7 @@
 
   bool no_cleanup_;
   bool no_install_;
-  const Extension* extension_;
+  const extensions::Extension* extension_;
   base::FilePath webapp_crx_;
   base::FilePath webapp_unpacked_;
   std::string username_;
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index 472fd8c..9c45f39 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -625,16 +625,19 @@
 
   AutofillProfile* profile = GetProfileByGUID(data_model.guid());
   if (profile) {
-    if (profile->record_type() == AutofillProfile::LOCAL_PROFILE) {
-      profile->RecordAndLogUse();
-      UpdateProfileInDB(*profile, /*enforced=*/true);
-    } else if (profile->record_type() == AutofillProfile::SERVER_PROFILE) {
-      profile->RecordAndLogUse();
-      // TODO(crbug.com/864519): Update this once addresses support account
-      // storage, and also use the server database.
-      database_helper_->GetLocalDatabase()->UpdateServerAddressMetadata(
-          *profile);
-      Refresh();
+    profile->RecordAndLogUse();
+
+    switch (profile->record_type()) {
+      case AutofillProfile::LOCAL_PROFILE:
+        UpdateProfileInDB(*profile, /*enforced=*/true);
+        break;
+      case AutofillProfile::SERVER_PROFILE:
+        DCHECK(database_helper_->GetServerDatabase())
+            << "Recording use of server address without server storage.";
+        database_helper_->GetServerDatabase()->UpdateServerAddressMetadata(
+            *profile);
+        Refresh();
+        break;
     }
   }
 }
@@ -2355,12 +2358,9 @@
     if (!wallet_address->has_converted()) {
       // Try to merge the server address into a similar local profile, or create
       // a new local profile if no similar profile is found.
-      // TODO(crbug.com/864519): Use GetAccountInfoForPaymentsServer instead of
-      // going to IdentityManager directly. This will be necessary to properly
-      // support Wallet addresses with Butter.
       std::string address_guid = MergeServerAddressesIntoProfiles(
           *wallet_address, local_profiles, app_locale_,
-          identity_manager_->GetPrimaryAccountInfo().email);
+          GetAccountInfoForPaymentsServer().email);
 
       // Update the map to transfer the billing address relationship from the
       // server address to the converted/merged local profile.
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index 4ff7eb6..7258415 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -66,6 +66,9 @@
 namespace autofill {
 namespace {
 
+const char kPrimaryAccountEmail[] = "syncuser@example.com";
+const char kSyncTransportAccountEmail[] = "transport@example.com";
+
 enum UserMode { USER_MODE_NORMAL, USER_MODE_INCOGNITO };
 
 const base::Time kArbitraryTime = base::Time::FromDoubleT(25);
@@ -202,7 +205,8 @@
 
     personal_data->AddObserver(&personal_data_observer_);
     AccountInfo account_info;
-    account_info.email = "sync@account";
+    account_info.email = use_sync_transport_mode ? kSyncTransportAccountEmail
+                                                 : kPrimaryAccountEmail;
     sync_service_.SetAuthenticatedAccountInfo(account_info);
     sync_service_.SetIsAuthenticatedAccountPrimary(!use_sync_transport_mode);
     personal_data->OnSyncServiceInitialized(&sync_service_);
@@ -222,7 +226,7 @@
   }
 
   void EnableWalletCardImport() {
-    identity_test_env_.MakePrimaryAccountAvailable("syncuser@example.com");
+    identity_test_env_.MakePrimaryAccountAvailable(kPrimaryAccountEmail);
     base::CommandLine::ForCurrentProcess()->AppendSwitch(
         switches::kEnableOfferStoreUnmaskedWalletCards);
   }
@@ -274,7 +278,7 @@
 
   AccountInfo SetActiveSecondaryAccount() {
     AccountInfo account_info;
-    account_info.email = "signed_in_account@email.com";
+    account_info.email = kSyncTransportAccountEmail;
     account_info.account_id = "account_id";
     sync_service_.SetAuthenticatedAccountInfo(account_info);
     sync_service_.SetIsAuthenticatedAccountPrimary(false);
@@ -337,12 +341,6 @@
     return PersonalDataManagerTestBase::TurnOnSyncFeature(personal_data_.get());
   }
 
-  void EnableWalletCardImport() {
-    identity_test_env_.MakePrimaryAccountAvailable("syncuser@example.com");
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        switches::kEnableOfferStoreUnmaskedWalletCards);
-  }
-
   void EnableAutofillProfileCleanup() {
     personal_data_->is_autofill_profile_cleanup_pending_ = true;
   }
@@ -5427,7 +5425,7 @@
 
   // Make sure that the added address has the email address of the currently
   // signed-in user.
-  EXPECT_EQ(base::UTF8ToUTF16("syncuser@example.com"),
+  EXPECT_EQ(base::UTF8ToUTF16(kPrimaryAccountEmail),
             profiles[0]->GetRawInfo(EMAIL_ADDRESS));
 }
 
@@ -7317,18 +7315,15 @@
 }
 
 TEST_F(PersonalDataManagerTest, GetAccountInfoForPaymentsServer) {
-  const std::string kIdentityManagerAccountEmail = "identity_account@email.com";
-  const std::string kSyncServiceAccountEmail = "active_sync_account@email.com";
-
   // Make the IdentityManager return a non-empty AccountInfo when
   // GetPrimaryAccountInfo() is called.
-  identity_test_env_.SetPrimaryAccount(kIdentityManagerAccountEmail);
+  identity_test_env_.SetPrimaryAccount(kPrimaryAccountEmail);
   ResetPersonalDataManager(USER_MODE_NORMAL);
 
   // Make the sync service return a non-empty AccountInfo when
   // GetAuthenticatedAccountInfo() is called.
   AccountInfo active_info;
-  active_info.email = kSyncServiceAccountEmail;
+  active_info.email = kSyncTransportAccountEmail;
   sync_service_.SetAuthenticatedAccountInfo(active_info);
 
   // The IdentityManager's AccountInfo should be returned by default.
@@ -7339,7 +7334,7 @@
         /*disabled_features=*/{features::kAutofillEnableAccountWalletStorage,
                                features::kAutofillGetPaymentsIdentityFromSync});
 
-    EXPECT_EQ(kIdentityManagerAccountEmail,
+    EXPECT_EQ(kPrimaryAccountEmail,
               personal_data_->GetAccountInfoForPaymentsServer().email);
   }
 
@@ -7351,7 +7346,7 @@
         /*enabled_features=*/{features::kAutofillEnableAccountWalletStorage},
         /*disabled_features=*/{features::kAutofillGetPaymentsIdentityFromSync});
 
-    EXPECT_EQ(kSyncServiceAccountEmail,
+    EXPECT_EQ(kSyncTransportAccountEmail,
               personal_data_->GetAccountInfoForPaymentsServer().email);
   }
 
@@ -7363,7 +7358,7 @@
         /*enabled_features=*/{features::kAutofillGetPaymentsIdentityFromSync},
         /*disabled_features=*/{features::kAutofillEnableAccountWalletStorage});
 
-    EXPECT_EQ(kSyncServiceAccountEmail,
+    EXPECT_EQ(kSyncTransportAccountEmail,
               personal_data_->GetAccountInfoForPaymentsServer().email);
   }
 }
@@ -7396,7 +7391,7 @@
   // Set everything up so that the proposition should be shown.
   // Set an an active secondary account.
   AccountInfo active_info;
-  active_info.email = "signed_in_account@email.com";
+  active_info.email = kPrimaryAccountEmail;
   active_info.account_id = "account_id";
   sync_service_.SetAuthenticatedAccountInfo(active_info);
   sync_service_.SetIsAuthenticatedAccountPrimary(false);
@@ -7521,7 +7516,7 @@
   // Set everything up so that the proposition should be shown on Desktop.
   // Set an an active secondary account.
   AccountInfo active_info;
-  active_info.email = "signed_in_account@email.com";
+  active_info.email = kPrimaryAccountEmail;
   active_info.account_id = "account_id";
   sync_service_.SetAuthenticatedAccountInfo(active_info);
   sync_service_.SetIsAuthenticatedAccountPrimary(false);
@@ -7654,7 +7649,7 @@
 
   // Simulate that the user has enabled the sync feature.
   AccountInfo primary_account_info;
-  primary_account_info.email = "active_sync_account@email.com";
+  primary_account_info.email = kPrimaryAccountEmail;
   sync_service_.SetAuthenticatedAccountInfo(primary_account_info);
   sync_service_.SetIsAuthenticatedAccountPrimary(true);
 // MakePrimaryAccountAvailable is not supported on CrOS.
@@ -7688,8 +7683,9 @@
   // Make a non-primary account available with both a refresh token and cookie
   // to be in Sync Transport for Wallet mode.
   AccountInfo active_info;
-  active_info.email = "test@gmail.com";
+  active_info.email = kSyncTransportAccountEmail;
   active_info.account_id = "account_id";
+  // TODO(treib): This seems wrong, we want a NON-primary account here.
   identity_test_env_.SetPrimaryAccount(active_info.email);
   sync_service_.SetAuthenticatedAccountInfo(active_info);
   sync_service_.SetIsAuthenticatedAccountPrimary(false);
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc
index b4d1154..6fe3627 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc
@@ -254,8 +254,7 @@
   return false;
 }
 
-AutofillWalletSyncBridge::StopSyncResponse
-AutofillWalletSyncBridge::ApplyStopSyncChanges(
+void AutofillWalletSyncBridge::ApplyStopSyncChanges(
     std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
   // If a metadata change list gets passed in, that means sync is actually
   // disabled, so we want to delete the payments data.
@@ -282,7 +281,6 @@
 
     initial_sync_done_ = false;
   }
-  return StopSyncResponse::kModelStillReadyToSync;
 }
 
 void AutofillWalletSyncBridge::GetAllDataForTesting(DataCallback callback) {
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h
index 17ea21c9..3f91a3c 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h
+++ b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h
@@ -66,9 +66,8 @@
   std::string GetClientTag(const syncer::EntityData& entity_data) override;
   std::string GetStorageKey(const syncer::EntityData& entity_data) override;
   bool SupportsIncrementalUpdates() const override;
-  StopSyncResponse ApplyStopSyncChanges(
-      std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list)
-      override;
+  void ApplyStopSyncChanges(std::unique_ptr<syncer::MetadataChangeList>
+                                delete_metadata_change_list) override;
 
   // Sends all Wallet Data to the |callback| and keeps all the strings in their
   // original format (whereas GetAllDataForDebugging() has to make them UTF-8).
diff --git a/components/cast_channel/cast_message_handler_unittest.cc b/components/cast_channel/cast_message_handler_unittest.cc
index 89f67e7..cab1fdbe 100644
--- a/components/cast_channel/cast_message_handler_unittest.cc
+++ b/components/cast_channel/cast_message_handler_unittest.cc
@@ -21,7 +21,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::test::IsJson;
-using base::test::ParseJson;
+using base::test::ParseJsonDeprecated;
 using testing::_;
 using testing::AnyNumber;
 using testing::InSequence;
@@ -140,7 +140,7 @@
           Result::kOk,
           handler_.SendSetVolumeRequest(
               channel_id_,
-              *ParseJson(
+              *ParseJsonDeprecated(
                   R"({"sessionId": "theSessionId", "type": "SET_VOLUME"})"),
               "theSourceId", set_volume_callback_.Get()));
     }
@@ -376,8 +376,9 @@
             "requestId": 1,
             "type": "PLAY",
           })";
-          auto expected = CreateMediaRequest(*ParseJson(expected_body), 1,
-                                             "theSourceId", "theDestinationId");
+          auto expected =
+              CreateMediaRequest(*ParseJsonDeprecated(expected_body), 1,
+                                 "theSourceId", "theDestinationId");
           EXPECT_EQ(expected.namespace_(), message.namespace_());
           EXPECT_EQ(expected.source_id(), message.source_id());
           EXPECT_EQ(expected.destination_id(), message.destination_id());
@@ -392,8 +393,9 @@
   std::string message_str = R"({
     "type": "PLAY",
   })";
-  base::Optional<int> request_id = handler_.SendMediaRequest(
-      channel_id_, *ParseJson(message_str), "theSourceId", "theDestinationId");
+  base::Optional<int> request_id =
+      handler_.SendMediaRequest(channel_id_, *ParseJsonDeprecated(message_str),
+                                "theSourceId", "theDestinationId");
   EXPECT_EQ(1, request_id);
 }
 
@@ -409,8 +411,8 @@
             "requestId": 1,
             "type": "SET_VOLUME",
           })";
-          auto expected = CreateSetVolumeRequest(*ParseJson(expected_body), 1,
-                                                 "theSourceId");
+          auto expected = CreateSetVolumeRequest(
+              *ParseJsonDeprecated(expected_body), 1, "theSourceId");
           EXPECT_EQ(expected.namespace_(), message.namespace_());
           EXPECT_EQ(expected.source_id(), message.source_id());
           EXPECT_EQ(expected.destination_id(), message.destination_id());
@@ -427,7 +429,7 @@
     "type": "SET_VOLUME",
   })";
   EXPECT_EQ(Result::kOk, handler_.SendSetVolumeRequest(
-                             channel_id_, *ParseJson(message_str),
+                             channel_id_, *ParseJsonDeprecated(message_str),
                              "theSourceId", base::DoNothing::Once<Result>()));
 }
 
@@ -472,7 +474,7 @@
 
   // Handle pending launch session request.
   handler_.HandleCastInternalMessage(channel_id_, "theSourceId",
-                                     "theDestinationId", ParseJson(R"(
+                                     "theDestinationId", ParseJsonDeprecated(R"(
       {
         "requestId": 1,
         "type": "RECEIVER_STATUS",
@@ -481,29 +483,29 @@
 
   // Handle both pending get app availability requests.
   handler_.HandleCastInternalMessage(channel_id_, "theSourceId",
-                                     "theDestinationId", ParseJson(R"(
+                                     "theDestinationId", ParseJsonDeprecated(R"(
       {
         "requestId": 2,
         "availability": {"theAppId": "APP_AVAILABLE"},
       })"));
 
   // Handle pending set volume request (1 of 2).
-  handler_.HandleCastInternalMessage(channel_id_, "theSourceId",
-                                     "theDestinationId",
-                                     ParseJson(R"({"requestId": 3})"));
+  handler_.HandleCastInternalMessage(
+      channel_id_, "theSourceId", "theDestinationId",
+      ParseJsonDeprecated(R"({"requestId": 3})"));
 
   // Skip request_id == 4, since it was used by the second get app availability
   // request.
 
   // Handle pending set volume request (2 of 2).
-  handler_.HandleCastInternalMessage(channel_id_, "theSourceId",
-                                     "theDestinationId",
-                                     ParseJson(R"({"requestId": 5})"));
+  handler_.HandleCastInternalMessage(
+      channel_id_, "theSourceId", "theDestinationId",
+      ParseJsonDeprecated(R"({"requestId": 5})"));
 
   // Handle pending stop session request.
-  handler_.HandleCastInternalMessage(channel_id_, "theSourceId",
-                                     "theDestinationId",
-                                     ParseJson(R"({"requestId": 6})"));
+  handler_.HandleCastInternalMessage(
+      channel_id_, "theSourceId", "theDestinationId",
+      ParseJsonDeprecated(R"({"requestId": 6})"));
 }
 
 // Check that set volume requests time out correctly.
@@ -515,9 +517,9 @@
     "type": "SET_VOLUME",
   })";
   base::MockCallback<ResultCallback> callback;
-  EXPECT_EQ(Result::kOk,
-            handler_.SendSetVolumeRequest(channel_id_, *ParseJson(message_str),
-                                          "theSourceId", callback.Get()));
+  EXPECT_EQ(Result::kOk, handler_.SendSetVolumeRequest(
+                             channel_id_, *ParseJsonDeprecated(message_str),
+                             "theSourceId", callback.Get()));
   EXPECT_CALL(callback, Run(Result::kFailed));
   thread_bundle_.FastForwardBy(kRequestTimeout);
 }
diff --git a/components/cast_channel/cast_message_util_unittest.cc b/components/cast_channel/cast_message_util_unittest.cc
index 7bc1027..24e96aea 100644
--- a/components/cast_channel/cast_message_util_unittest.cc
+++ b/components/cast_channel/cast_message_util_unittest.cc
@@ -10,7 +10,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::test::IsJson;
-using base::test::ParseJson;
+using base::test::ParseJsonDeprecated;
 
 namespace cast_channel {
 
@@ -39,7 +39,7 @@
   )";
 
   LaunchSessionResponse response =
-      GetLaunchSessionResponse(*ParseJson(payload));
+      GetLaunchSessionResponse(*ParseJsonDeprecated(payload));
   EXPECT_EQ(LaunchSessionResponse::Result::kOk, response.result);
   EXPECT_TRUE(response.receiver_status);
 }
@@ -53,7 +53,7 @@
   )";
 
   LaunchSessionResponse response =
-      GetLaunchSessionResponse(*ParseJson(payload));
+      GetLaunchSessionResponse(*ParseJsonDeprecated(payload));
   EXPECT_EQ(LaunchSessionResponse::Result::kError, response.result);
   EXPECT_FALSE(response.receiver_status);
 }
@@ -69,7 +69,7 @@
   )";
 
   LaunchSessionResponse response =
-      GetLaunchSessionResponse(*ParseJson(payload));
+      GetLaunchSessionResponse(*ParseJsonDeprecated(payload));
   EXPECT_EQ(LaunchSessionResponse::Result::kUnknown, response.result);
   EXPECT_FALSE(response.receiver_status);
 }
@@ -110,8 +110,8 @@
        "requestId": 123,
     })";
 
-  CastMessage message = CreateMediaRequest(*ParseJson(body), 123, "theSourceId",
-                                           "theDestinationId");
+  CastMessage message = CreateMediaRequest(*ParseJsonDeprecated(body), 123,
+                                           "theSourceId", "theDestinationId");
   ASSERT_TRUE(IsCastMessageValid(message));
   EXPECT_EQ(kMediaNamespace, message.namespace_());
   EXPECT_EQ("theSourceId", message.source_id());
@@ -130,7 +130,7 @@
     })";
 
   CastMessage message =
-      CreateSetVolumeRequest(*ParseJson(body), 123, "theSourceId");
+      CreateSetVolumeRequest(*ParseJsonDeprecated(body), 123, "theSourceId");
   ASSERT_TRUE(IsCastMessageValid(message));
   EXPECT_EQ(kReceiverNamespace, message.namespace_());
   EXPECT_EQ("theSourceId", message.source_id());
diff --git a/components/consent_auditor/consent_sync_bridge_impl.cc b/components/consent_auditor/consent_sync_bridge_impl.cc
index ea3c403..4338d25f 100644
--- a/components/consent_auditor/consent_sync_bridge_impl.cc
+++ b/components/consent_auditor/consent_sync_bridge_impl.cc
@@ -135,8 +135,7 @@
   return GetStorageKeyFromSpecifics(entity_data.specifics.user_consent());
 }
 
-ModelTypeSyncBridge::StopSyncResponse
-ConsentSyncBridgeImpl::ApplyStopSyncChanges(
+void ConsentSyncBridgeImpl::ApplyStopSyncChanges(
     std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
   // Sync can only be stopped after initialization.
   DCHECK(deferred_consents_while_initializing_.empty());
@@ -155,8 +154,6 @@
                              base::BindOnce(&ConsentSyncBridgeImpl::OnCommit,
                                             weak_ptr_factory_.GetWeakPtr()));
   }
-
-  return StopSyncResponse::kModelStillReadyToSync;
 }
 
 void ConsentSyncBridgeImpl::ReadAllDataAndResubmit() {
diff --git a/components/consent_auditor/consent_sync_bridge_impl.h b/components/consent_auditor/consent_sync_bridge_impl.h
index 47132d7..be0ace2 100644
--- a/components/consent_auditor/consent_sync_bridge_impl.h
+++ b/components/consent_auditor/consent_sync_bridge_impl.h
@@ -42,9 +42,8 @@
   void GetAllDataForDebugging(DataCallback callback) override;
   std::string GetClientTag(const syncer::EntityData& entity_data) override;
   std::string GetStorageKey(const syncer::EntityData& entity_data) override;
-  StopSyncResponse ApplyStopSyncChanges(
-      std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list)
-      override;
+  void ApplyStopSyncChanges(std::unique_ptr<syncer::MetadataChangeList>
+                                delete_metadata_change_list) override;
 
   // ConsentSyncBridge implementation.
   void RecordConsent(
diff --git a/components/consent_auditor/consent_sync_bridge_impl_unittest.cc b/components/consent_auditor/consent_sync_bridge_impl_unittest.cc
index 750de5c..1e258365 100644
--- a/components/consent_auditor/consent_sync_bridge_impl_unittest.cc
+++ b/components/consent_auditor/consent_sync_bridge_impl_unittest.cc
@@ -197,9 +197,7 @@
       std::make_unique<UserConsentSpecifics>(user_consent_specifics));
   ASSERT_THAT(GetAllData(), SizeIs(1));
 
-  EXPECT_THAT(
-      bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList()),
-      Eq(ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync));
+  bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList());
   // The bridge may asynchronously query the store to choose what to delete.
   base::RunLoop().RunUntilIdle();
 
@@ -314,9 +312,7 @@
 
   // User disables sync, hovewer, the consent hasn't been submitted yet. It is
   // preserved to be submitted when sync is re-enabled.
-  EXPECT_THAT(
-      bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList()),
-      Eq(ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync));
+  bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList());
   // The bridge may asynchronously query the store to choose what to delete.
   base::RunLoop().RunUntilIdle();
 
@@ -385,9 +381,7 @@
       std::make_unique<UserConsentSpecifics>(user_consent_specifics));
   ASSERT_THAT(GetAllData(), SizeIs(1));
 
-  EXPECT_THAT(
-      bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList()),
-      Eq(ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync));
+  bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList());
   // The bridge may asynchronously query the store to choose what to delete.
   base::RunLoop().RunUntilIdle();
 
@@ -404,9 +398,7 @@
                           EntityChangeList());
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_THAT(
-      bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList()),
-      Eq(ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync));
+  bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList());
   base::RunLoop().RunUntilIdle();
 
   // This time their consent should be resubmitted, because it is for the same
diff --git a/components/cronet/url_request_context_config_unittest.cc b/components/cronet/url_request_context_config_unittest.cc
index 132fd13..cb62f84 100644
--- a/components/cronet/url_request_context_config_unittest.cc
+++ b/components/cronet/url_request_context_config_unittest.cc
@@ -32,10 +32,6 @@
 
 namespace {
 
-base::Value ParseJson(base::StringPiece json) {
-  return std::move(*base::test::ParseJson(json));
-}
-
 std::string WrapJsonHeader(base::StringPiece value) {
   std::string result;
   result.reserve(value.size() + 2);
@@ -48,8 +44,8 @@
 // Returns whether two JSON-encoded headers contain the same content, ignoring
 // irrelevant encoding issues like whitespace and map element ordering.
 bool JsonHeaderEquals(base::StringPiece expected, base::StringPiece actual) {
-  return ParseJson(WrapJsonHeader(expected)) ==
-         ParseJson(WrapJsonHeader(actual));
+  return base::test::ParseJson(WrapJsonHeader(expected)) ==
+         base::test::ParseJson(WrapJsonHeader(actual));
 }
 
 }  // namespace
@@ -71,7 +67,7 @@
   options.SetPath({"AsyncDNS", "enable"}, base::Value(true));
   options.SetPath({"NetworkErrorLogging", "enable"}, base::Value(true));
   options.SetPath({"NetworkErrorLogging", "preloaded_report_to_headers"},
-                  ParseJson(R"json(
+                  base::test::ParseJson(R"json(
                   [
                     {
                       "origin": "https://test-origin/",
@@ -119,7 +115,7 @@
                   ]
                   )json"));
   options.SetPath({"NetworkErrorLogging", "preloaded_nel_headers"},
-                  ParseJson(R"json(
+                  base::test::ParseJson(R"json(
                   [
                     {
                       "origin": "https://test-origin/",
diff --git a/components/exo/surface.cc b/components/exo/surface.cc
index 1817c3e3..ec94dbf 100644
--- a/components/exo/surface.cc
+++ b/components/exo/surface.cc
@@ -942,13 +942,15 @@
       viz::TextureDrawQuad* texture_quad =
           render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>();
       float vertex_opacity[4] = {1.0, 1.0, 1.0, 1.0};
-
+      SkColor background_color = SK_ColorTRANSPARENT;
+      if (current_resource_has_alpha_ && are_contents_opaque)
+        background_color = SK_ColorBLACK;  // Avoid writing alpha < 1
       texture_quad->SetNew(
-          quad_state, quad_rect, quad_rect, !are_contents_opaque,
-          current_resource_.id, true /* premultiplied_alpha */,
-          uv_crop.origin(), uv_crop.bottom_right(),
-          SK_ColorTRANSPARENT /* background_color */, vertex_opacity,
-          false /* y_flipped */, false /* nearest_neighbor */,
+          quad_state, quad_rect, quad_rect,
+          /* needs_blending=*/!are_contents_opaque, current_resource_.id,
+          /* premultiplied_alpha=*/true, uv_crop.origin(),
+          uv_crop.bottom_right(), background_color, vertex_opacity,
+          /* y_flipped=*/false, /* nearest_neighbor=*/false,
           state_.only_visible_on_secure_output, ui::ProtectedVideoType::kClear);
       if (current_resource_.is_overlay_candidate)
         texture_quad->set_resource_size_in_pixels(current_resource_.size);
diff --git a/components/exo/surface_unittest.cc b/components/exo/surface_unittest.cc
index 3670188..7e1cecc 100644
--- a/components/exo/surface_unittest.cc
+++ b/components/exo/surface_unittest.cc
@@ -221,9 +221,11 @@
         GetFrameFromSurface(shell_surface.get());
     ASSERT_EQ(1u, frame.render_pass_list.size());
     ASSERT_EQ(1u, frame.render_pass_list.back()->quad_list.size());
-    EXPECT_FALSE(frame.render_pass_list.back()
-                     ->quad_list.back()
-                     ->ShouldDrawWithBlending());
+    auto* texture_draw_quad = viz::TextureDrawQuad::MaterialCast(
+        frame.render_pass_list.back()->quad_list.back());
+
+    EXPECT_FALSE(texture_draw_quad->ShouldDrawWithBlending());
+    EXPECT_EQ(SK_ColorBLACK, texture_draw_quad->background_color);
     EXPECT_EQ(ToPixel(gfx::Rect(0, 0, 1, 1)),
               frame.render_pass_list.back()->damage_rect);
   }
@@ -238,9 +240,10 @@
         GetFrameFromSurface(shell_surface.get());
     ASSERT_EQ(1u, frame.render_pass_list.size());
     ASSERT_EQ(1u, frame.render_pass_list.back()->quad_list.size());
-    EXPECT_TRUE(frame.render_pass_list.back()
-                    ->quad_list.back()
-                    ->ShouldDrawWithBlending());
+    auto* texture_draw_quad = viz::TextureDrawQuad::MaterialCast(
+        frame.render_pass_list.back()->quad_list.back());
+    EXPECT_TRUE(texture_draw_quad->ShouldDrawWithBlending());
+    EXPECT_EQ(SK_ColorTRANSPARENT, texture_draw_quad->background_color);
     EXPECT_EQ(ToPixel(gfx::Rect(0, 0, 1, 1)),
               frame.render_pass_list.back()->damage_rect);
   }
diff --git a/components/keyed_service/content/browser_context_dependency_manager.cc b/components/keyed_service/content/browser_context_dependency_manager.cc
index e9cf9d39..3179ba48 100644
--- a/components/keyed_service/content/browser_context_dependency_manager.cc
+++ b/components/keyed_service/content/browser_context_dependency_manager.cc
@@ -85,7 +85,7 @@
 
 #ifndef NDEBUG
 void BrowserContextDependencyManager::DumpContextDependencies(
-    base::SupportsUserData* context) const {
+    void* context) const {
   // Whenever we try to build a destruction ordering, we should also dump a
   // dependency graph to "/path/to/context/context-dependencies.dot".
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
diff --git a/components/keyed_service/content/browser_context_dependency_manager.h b/components/keyed_service/content/browser_context_dependency_manager.h
index 8320e2b..bf434adec 100644
--- a/components/keyed_service/content/browser_context_dependency_manager.h
+++ b/components/keyed_service/content/browser_context_dependency_manager.h
@@ -96,7 +96,7 @@
 
 #ifndef NDEBUG
   // DependencyManager:
-  void DumpContextDependencies(base::SupportsUserData* context) const final;
+  void DumpContextDependencies(void* context) const final;
 #endif  // NDEBUG
 
   // A list of callbacks to call just before executing
diff --git a/components/keyed_service/content/browser_context_keyed_base_factory.cc b/components/keyed_service/content/browser_context_keyed_base_factory.cc
index 79d1ed0..8076ee3d 100644
--- a/components/keyed_service/content/browser_context_keyed_base_factory.cc
+++ b/components/keyed_service/content/browser_context_keyed_base_factory.cc
@@ -42,8 +42,7 @@
   KeyedServiceBaseFactory::ContextDestroyed(context);
 }
 
-base::SupportsUserData* BrowserContextKeyedBaseFactory::GetContextToUse(
-    base::SupportsUserData* context) const {
+void* BrowserContextKeyedBaseFactory::GetContextToUse(void* context) const {
   AssertContextWasntDestroyed(context);
   return GetBrowserContextToUse(static_cast<content::BrowserContext*>(context));
 }
@@ -52,13 +51,11 @@
   return ServiceIsCreatedWithBrowserContext();
 }
 
-void BrowserContextKeyedBaseFactory::ContextShutdown(
-    base::SupportsUserData* context) {
+void BrowserContextKeyedBaseFactory::ContextShutdown(void* context) {
   BrowserContextShutdown(static_cast<content::BrowserContext*>(context));
 }
 
-void BrowserContextKeyedBaseFactory::ContextDestroyed(
-    base::SupportsUserData* context) {
+void BrowserContextKeyedBaseFactory::ContextDestroyed(void* context) {
   BrowserContextDestroyed(static_cast<content::BrowserContext*>(context));
 }
 
@@ -67,17 +64,14 @@
   RegisterProfilePrefs(registry);
 }
 
-void BrowserContextKeyedBaseFactory::SetEmptyTestingFactory(
-    base::SupportsUserData* context) {
+void BrowserContextKeyedBaseFactory::SetEmptyTestingFactory(void* context) {
   SetEmptyTestingFactory(static_cast<content::BrowserContext*>(context));
 }
 
-bool BrowserContextKeyedBaseFactory::HasTestingFactory(
-    base::SupportsUserData* context) {
+bool BrowserContextKeyedBaseFactory::HasTestingFactory(void* context) {
   return HasTestingFactory(static_cast<content::BrowserContext*>(context));
 }
 
-void BrowserContextKeyedBaseFactory::CreateServiceNow(
-    base::SupportsUserData* context) {
+void BrowserContextKeyedBaseFactory::CreateServiceNow(void* context) {
   CreateServiceNow(static_cast<content::BrowserContext*>(context));
 }
diff --git a/components/keyed_service/content/browser_context_keyed_base_factory.h b/components/keyed_service/content/browser_context_keyed_base_factory.h
index 940fad4..69556d8 100644
--- a/components/keyed_service/content/browser_context_keyed_base_factory.h
+++ b/components/keyed_service/content/browser_context_keyed_base_factory.h
@@ -94,15 +94,14 @@
   virtual void CreateServiceNow(content::BrowserContext* context) = 0;
 
   // KeyedServiceBaseFactory:
-  base::SupportsUserData* GetContextToUse(
-      base::SupportsUserData* context) const final;
+  void* GetContextToUse(void* context) const final;
   bool ServiceIsCreatedWithContext() const final;
-  void ContextShutdown(base::SupportsUserData* context) final;
-  void ContextDestroyed(base::SupportsUserData* context) final;
+  void ContextShutdown(void* context) final;
+  void ContextDestroyed(void* context) final;
   void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) final;
-  void SetEmptyTestingFactory(base::SupportsUserData* context) final;
-  bool HasTestingFactory(base::SupportsUserData* context) final;
-  void CreateServiceNow(base::SupportsUserData* context) final;
+  void SetEmptyTestingFactory(void* context) final;
+  bool HasTestingFactory(void* context) final;
+  void CreateServiceNow(void* context) final;
 };
 
 #endif  // COMPONENTS_KEYED_SERVICE_CONTENT_BROWSER_CONTEXT_KEYED_BASE_FACTORY_H_
diff --git a/components/keyed_service/content/browser_context_keyed_service_factory.cc b/components/keyed_service/content/browser_context_keyed_service_factory.cc
index 21e9611..67cbd36 100644
--- a/components/keyed_service/content/browser_context_keyed_service_factory.cc
+++ b/components/keyed_service/content/browser_context_keyed_service_factory.cc
@@ -4,6 +4,8 @@
 
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
@@ -18,8 +20,7 @@
   KeyedServiceFactory::TestingFactory wrapped_factory;
   if (testing_factory) {
     wrapped_factory = base::BindRepeating(
-        [](const TestingFactory& testing_factory,
-           base::SupportsUserData* context) {
+        [](const TestingFactory& testing_factory, void* context) {
           return testing_factory.Run(
               static_cast<content::BrowserContext*>(context));
         },
@@ -33,13 +34,13 @@
     TestingFactory testing_factory) {
   DCHECK(testing_factory);
   return KeyedServiceFactory::SetTestingFactoryAndUse(
-      context, base::BindRepeating(
-                   [](const TestingFactory& testing_factory,
-                      base::SupportsUserData* context) {
-                     return testing_factory.Run(
-                         static_cast<content::BrowserContext*>(context));
-                   },
-                   std::move(testing_factory)));
+      context, nullptr /* side_parameter */,
+      base::BindRepeating(
+          [](const TestingFactory& testing_factory, void* context) {
+            return testing_factory.Run(
+                static_cast<content::BrowserContext*>(context));
+          },
+          std::move(testing_factory)));
 }
 
 BrowserContextKeyedServiceFactory::BrowserContextKeyedServiceFactory(
@@ -54,7 +55,8 @@
 KeyedService* BrowserContextKeyedServiceFactory::GetServiceForBrowserContext(
     content::BrowserContext* context,
     bool create) {
-  return KeyedServiceFactory::GetServiceForContext(context, create);
+  return KeyedServiceFactory::GetServiceForContext(
+      context, nullptr /* side_parameter */, create);
 }
 
 content::BrowserContext*
@@ -91,20 +93,19 @@
 
 std::unique_ptr<KeyedService>
 BrowserContextKeyedServiceFactory::BuildServiceInstanceFor(
-    base::SupportsUserData* context) const {
+    void* context,
+    void* side_parameter) const {
   // TODO(isherman): The wrapped BuildServiceInstanceFor() should return a
   // scoped_ptr as well.
   return base::WrapUnique(
       BuildServiceInstanceFor(static_cast<content::BrowserContext*>(context)));
 }
 
-bool BrowserContextKeyedServiceFactory::IsOffTheRecord(
-    base::SupportsUserData* context) const {
+bool BrowserContextKeyedServiceFactory::IsOffTheRecord(void* context) const {
   return static_cast<content::BrowserContext*>(context)->IsOffTheRecord();
 }
 
-base::SupportsUserData* BrowserContextKeyedServiceFactory::GetContextToUse(
-    base::SupportsUserData* context) const {
+void* BrowserContextKeyedServiceFactory::GetContextToUse(void* context) const {
   AssertContextWasntDestroyed(context);
   return GetBrowserContextToUse(static_cast<content::BrowserContext*>(context));
 }
@@ -113,13 +114,11 @@
   return ServiceIsCreatedWithBrowserContext();
 }
 
-void BrowserContextKeyedServiceFactory::ContextShutdown(
-    base::SupportsUserData* context) {
+void BrowserContextKeyedServiceFactory::ContextShutdown(void* context) {
   BrowserContextShutdown(static_cast<content::BrowserContext*>(context));
 }
 
-void BrowserContextKeyedServiceFactory::ContextDestroyed(
-    base::SupportsUserData* context) {
+void BrowserContextKeyedServiceFactory::ContextDestroyed(void* context) {
   BrowserContextDestroyed(static_cast<content::BrowserContext*>(context));
 }
 
@@ -127,3 +126,8 @@
     user_prefs::PrefRegistrySyncable* registry) {
   RegisterProfilePrefs(registry);
 }
+
+void BrowserContextKeyedServiceFactory::CreateServiceNow(void* context) {
+  KeyedServiceFactory::GetServiceForContext(context,
+                                            nullptr /* side_parameter */, true);
+}
diff --git a/components/keyed_service/content/browser_context_keyed_service_factory.h b/components/keyed_service/content/browser_context_keyed_service_factory.h
index ab605769..e6ec079b 100644
--- a/components/keyed_service/content/browser_context_keyed_service_factory.h
+++ b/components/keyed_service/content/browser_context_keyed_service_factory.h
@@ -122,16 +122,17 @@
 
   // KeyedServiceFactory:
   std::unique_ptr<KeyedService> BuildServiceInstanceFor(
-      base::SupportsUserData* context) const final;
-  bool IsOffTheRecord(base::SupportsUserData* context) const final;
+      void* context,
+      void* side_parameter) const final;
+  bool IsOffTheRecord(void* context) const final;
 
   // KeyedServiceBaseFactory:
-  base::SupportsUserData* GetContextToUse(
-      base::SupportsUserData* context) const final;
+  void* GetContextToUse(void* context) const final;
   bool ServiceIsCreatedWithContext() const final;
-  void ContextShutdown(base::SupportsUserData* context) final;
-  void ContextDestroyed(base::SupportsUserData* context) final;
+  void ContextShutdown(void* context) final;
+  void ContextDestroyed(void* context) final;
   void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) final;
+  void CreateServiceNow(void* context) final;
 
   DISALLOW_COPY_AND_ASSIGN(BrowserContextKeyedServiceFactory);
 };
diff --git a/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.cc b/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.cc
index cab1652..e6095171 100644
--- a/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.cc
+++ b/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.cc
@@ -16,8 +16,7 @@
   RefcountedKeyedServiceFactory::TestingFactory wrapped_factory;
   if (testing_factory) {
     wrapped_factory = base::BindRepeating(
-        [](const TestingFactory& testing_factory,
-           base::SupportsUserData* context) {
+        [](const TestingFactory& testing_factory, void* context) {
           return testing_factory.Run(
               static_cast<content::BrowserContext*>(context));
         },
@@ -33,13 +32,13 @@
     TestingFactory testing_factory) {
   DCHECK(testing_factory);
   return RefcountedKeyedServiceFactory::SetTestingFactoryAndUse(
-      context, base::BindRepeating(
-                   [](const TestingFactory& testing_factory,
-                      base::SupportsUserData* context) {
-                     return testing_factory.Run(
-                         static_cast<content::BrowserContext*>(context));
-                   },
-                   std::move(testing_factory)));
+      context, nullptr /* side_parameter */,
+      base::BindRepeating(
+          [](const TestingFactory& testing_factory, void* context) {
+            return testing_factory.Run(
+                static_cast<content::BrowserContext*>(context));
+          },
+          std::move(testing_factory)));
 }
 
 RefcountedBrowserContextKeyedServiceFactory::
@@ -57,7 +56,8 @@
 RefcountedBrowserContextKeyedServiceFactory::GetServiceForBrowserContext(
     content::BrowserContext* context,
     bool create) {
-  return RefcountedKeyedServiceFactory::GetServiceForContext(context, create);
+  return RefcountedKeyedServiceFactory::GetServiceForContext(
+      context, nullptr /* side_parameter */, create);
 }
 
 content::BrowserContext*
@@ -95,19 +95,19 @@
 
 scoped_refptr<RefcountedKeyedService>
 RefcountedBrowserContextKeyedServiceFactory::BuildServiceInstanceFor(
-    base::SupportsUserData* context) const {
+    void* context,
+    void* side_parameter) const {
   return BuildServiceInstanceFor(
       static_cast<content::BrowserContext*>(context));
 }
 
 bool RefcountedBrowserContextKeyedServiceFactory::IsOffTheRecord(
-    base::SupportsUserData* context) const {
+    void* context) const {
   return static_cast<content::BrowserContext*>(context)->IsOffTheRecord();
 }
 
-base::SupportsUserData*
-RefcountedBrowserContextKeyedServiceFactory::GetContextToUse(
-    base::SupportsUserData* context) const {
+void* RefcountedBrowserContextKeyedServiceFactory::GetContextToUse(
+    void* context) const {
   AssertContextWasntDestroyed(context);
   return GetBrowserContextToUse(static_cast<content::BrowserContext*>(context));
 }
@@ -118,12 +118,12 @@
 }
 
 void RefcountedBrowserContextKeyedServiceFactory::ContextShutdown(
-    base::SupportsUserData* context) {
+    void* context) {
   BrowserContextShutdown(static_cast<content::BrowserContext*>(context));
 }
 
 void RefcountedBrowserContextKeyedServiceFactory::ContextDestroyed(
-    base::SupportsUserData* context) {
+    void* context) {
   BrowserContextDestroyed(static_cast<content::BrowserContext*>(context));
 }
 
diff --git a/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.h b/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.h
index 3d2deb80..0e9090cc 100644
--- a/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.h
+++ b/components/keyed_service/content/refcounted_browser_context_keyed_service_factory.h
@@ -127,15 +127,15 @@
 
   // RefcountedKeyedServiceFactory:
   scoped_refptr<RefcountedKeyedService> BuildServiceInstanceFor(
-      base::SupportsUserData* context) const final;
-  bool IsOffTheRecord(base::SupportsUserData* context) const final;
+      void* context,
+      void* side_parameter) const final;
+  bool IsOffTheRecord(void* context) const final;
 
   // KeyedServiceBaseFactory:
-  base::SupportsUserData* GetContextToUse(
-      base::SupportsUserData* context) const final;
+  void* GetContextToUse(void* context) const final;
   bool ServiceIsCreatedWithContext() const final;
-  void ContextShutdown(base::SupportsUserData* context) final;
-  void ContextDestroyed(base::SupportsUserData* context) final;
+  void ContextShutdown(void* context) final;
+  void ContextDestroyed(void* context) final;
   void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) final;
 
   DISALLOW_COPY_AND_ASSIGN(RefcountedBrowserContextKeyedServiceFactory);
diff --git a/components/keyed_service/core/dependency_manager.cc b/components/keyed_service/core/dependency_manager.cc
index a55ca5c..9395b86 100644
--- a/components/keyed_service/core/dependency_manager.cc
+++ b/components/keyed_service/core/dependency_manager.cc
@@ -35,7 +35,7 @@
 }
 
 void DependencyManager::RegisterPrefsForServices(
-    base::SupportsUserData* context,
+    void* context,
     user_prefs::PrefRegistrySyncable* pref_registry) {
   std::vector<DependencyNode*> construction_order;
   if (!dependency_graph_.GetConstructionOrder(&construction_order)) {
@@ -49,7 +49,7 @@
   }
 }
 
-void DependencyManager::CreateContextServices(base::SupportsUserData* context,
+void DependencyManager::CreateContextServices(void* context,
                                               bool is_testing_context) {
   MarkContextLive(context);
 
@@ -74,8 +74,7 @@
   }
 }
 
-void DependencyManager::DestroyContextServices(
-    base::SupportsUserData* context) {
+void DependencyManager::DestroyContextServices(void* context) {
   std::vector<DependencyNode*> destruction_order;
   if (!dependency_graph_.GetDestructionOrder(&destruction_order)) {
     NOTREACHED();
@@ -101,8 +100,7 @@
   }
 }
 
-void DependencyManager::AssertContextWasntDestroyed(
-    base::SupportsUserData* context) const {
+void DependencyManager::AssertContextWasntDestroyed(void* context) const {
   if (dead_context_pointers_.find(context) != dead_context_pointers_.end()) {
 #if DCHECK_IS_ON()
     NOTREACHED() << "Attempted to access a context that was ShutDown(). "
@@ -116,7 +114,7 @@
   }
 }
 
-void DependencyManager::MarkContextLive(base::SupportsUserData* context) {
+void DependencyManager::MarkContextLive(void* context) {
   dead_context_pointers_.erase(context);
 }
 
diff --git a/components/keyed_service/core/dependency_manager.h b/components/keyed_service/core/dependency_manager.h
index 32c1f64..0f881a9 100644
--- a/components/keyed_service/core/dependency_manager.h
+++ b/components/keyed_service/core/dependency_manager.h
@@ -15,7 +15,6 @@
 
 namespace base {
 class FilePath;
-class SupportsUserData;
 }
 
 namespace user_prefs {
@@ -42,7 +41,7 @@
   // Registers preferences for all services via |registry| associated with
   // |context| (the association is managed by the embedder). The |context|
   // is used as a key to prevent multiple registration during tests.
-  void RegisterPrefsForServices(base::SupportsUserData* context,
+  void RegisterPrefsForServices(void* context,
                                 user_prefs::PrefRegistrySyncable* registry);
 
   // Called upon creation of |context| to create services that want to be
@@ -55,24 +54,23 @@
   //
   // If |is_testing_context| then the service will not be started unless the
   // method KeyedServiceBaseFactory::ServiceIsNULLWhileTesting() return false.
-  void CreateContextServices(base::SupportsUserData* context,
-                             bool is_testing_context);
+  void CreateContextServices(void* context, bool is_testing_context);
 
   // Called upon destruction of |context| to destroy all services associated
   // with it.
-  void DestroyContextServices(base::SupportsUserData* context);
+  void DestroyContextServices(void* context);
 
   // Runtime assertion called as a part of GetServiceForContext() to check if
   // |context| is considered stale. This will NOTREACHED() or
   // base::debug::DumpWithoutCrashing() depending on the DCHECK_IS_ON() value.
-  void AssertContextWasntDestroyed(base::SupportsUserData* context) const;
+  void AssertContextWasntDestroyed(void* context) const;
 
   // Marks |context| as live (i.e., not stale). This method can be called as a
   // safeguard against |AssertContextWasntDestroyed()| checks going off due to
   // |context| aliasing an instance from a prior construction (i.e., 0xWhatever
   // might be created, be destroyed, and then a new object might be created at
   // 0xWhatever).
-  void MarkContextLive(base::SupportsUserData* context);
+  void MarkContextLive(void* context);
 
 #ifndef NDEBUG
   // Dumps service dependency graph as a Graphviz dot file |dot_file| with a
@@ -86,8 +84,7 @@
 
 #ifndef NDEBUG
   // Hook for subclass to dump the dependency graph of service for |context|.
-  virtual void DumpContextDependencies(
-      base::SupportsUserData* context) const = 0;
+  virtual void DumpContextDependencies(void* context) const = 0;
 #endif  // NDEBUG
 
   DependencyGraph dependency_graph_;
@@ -96,7 +93,7 @@
   // These pointers are most likely invalid, but we keep track of their
   // locations in memory so we can nicely assert if we're asked to do anything
   // with them.
-  std::set<base::SupportsUserData*> dead_context_pointers_;
+  std::set<void*> dead_context_pointers_;
 };
 
 #endif  // COMPONENTS_KEYED_SERVICE_CORE_DEPENDENCY_MANAGER_H_
diff --git a/components/keyed_service/core/keyed_service_base_factory.cc b/components/keyed_service/core/keyed_service_base_factory.cc
index b9cf09c..fe02e58 100644
--- a/components/keyed_service/core/keyed_service_base_factory.cc
+++ b/components/keyed_service/core/keyed_service_base_factory.cc
@@ -25,15 +25,14 @@
   dependency_manager_->AddEdge(rhs, this);
 }
 
-void KeyedServiceBaseFactory::AssertContextWasntDestroyed(
-    base::SupportsUserData* context) const {
+void KeyedServiceBaseFactory::AssertContextWasntDestroyed(void* context) const {
   // TODO(crbug.com/701326): We should DCHECK(CalledOnValidThread()) here, but
   // currently some code doesn't do service getting on the main thread.
   // This needs to be fixed and DCHECK should be restored here.
   dependency_manager_->AssertContextWasntDestroyed(context);
 }
 
-void KeyedServiceBaseFactory::MarkContextLive(base::SupportsUserData* context) {
+void KeyedServiceBaseFactory::MarkContextLive(void* context) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   dependency_manager_->MarkContextLive(context);
 }
@@ -46,8 +45,7 @@
   return false;
 }
 
-void KeyedServiceBaseFactory::ContextDestroyed(
-    base::SupportsUserData* context) {
+void KeyedServiceBaseFactory::ContextDestroyed(void* context) {
   // While object destruction can be customized in ways where the object is
   // only dereferenced, this still must run on the UI thread.
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/keyed_service/core/keyed_service_base_factory.h b/components/keyed_service/core/keyed_service_base_factory.h
index 612395e2b..f603dcc 100644
--- a/components/keyed_service/core/keyed_service_base_factory.h
+++ b/components/keyed_service/core/keyed_service_base_factory.h
@@ -13,17 +13,13 @@
 
 class DependencyManager;
 
-namespace base {
-class SupportsUserData;
-}
-
 namespace user_prefs {
 class PrefRegistrySyncable;
 }
 
-// Base class for factories that take a base::SupportsUserData and return some
-// service. Not for direct usage, instead use descendent classes that deal with
-// more specific context objects.
+// Base class for factories that take an opaque pointer and return some service.
+// Not for direct usage, instead use descendent classes that deal with more
+// specific context objects.
 //
 // This object describes general dependency management between factories while
 // direct subclasses react to lifecycle events and implement memory management.
@@ -42,18 +38,17 @@
 
   // Runtime assertion to check if |context| is considered stale. Should be used
   // by subclasses when accessing |context|.
-  void AssertContextWasntDestroyed(base::SupportsUserData* context) const;
+  void AssertContextWasntDestroyed(void* context) const;
 
   // Marks |context| as live (i.e., not stale). This method can be called as a
   // safeguard against |AssertContextWasntDestroyed()| checks going off due to
   // |context| aliasing an instance from a prior construction (i.e., 0xWhatever
   // might be created, be destroyed, and then a new object might be created at
   // 0xWhatever).
-  void MarkContextLive(base::SupportsUserData* context);
+  void MarkContextLive(void* context);
 
   // Finds which context (if any) to use.
-  virtual base::SupportsUserData* GetContextToUse(
-      base::SupportsUserData* context) const = 0;
+  virtual void* GetContextToUse(void* context) const = 0;
 
   // By default, instance of a service are created lazily when GetForContext()
   // is called by the subclass. Some services need to be created as soon as the
@@ -76,8 +71,8 @@
   //   service with GetForContext() will NOTREACHED() and code should delete/
   //   deref/do other final memory management during this phase. The base class
   //   method *must* be called as the last thing.
-  virtual void ContextShutdown(base::SupportsUserData* context) = 0;
-  virtual void ContextDestroyed(base::SupportsUserData* context);
+  virtual void ContextShutdown(void* context) = 0;
+  virtual void ContextDestroyed(void* context);
 
   SEQUENCE_CHECKER(sequence_checker_);
 
@@ -94,13 +89,13 @@
 
   // Used by DependencyManager to disable creation of the service when the
   // method ServiceIsNULLWhileTesting() returns true.
-  virtual void SetEmptyTestingFactory(base::SupportsUserData* context) = 0;
+  virtual void SetEmptyTestingFactory(void* context) = 0;
 
   // Returns true if a testing factory function has been set for |context|.
-  virtual bool HasTestingFactory(base::SupportsUserData* context) = 0;
+  virtual bool HasTestingFactory(void* context) = 0;
 
   // Create the service associated with |context|.
-  virtual void CreateServiceNow(base::SupportsUserData* context) = 0;
+  virtual void CreateServiceNow(void* context) = 0;
 
   // A static string passed in to the constructor. Should be unique across all
   // services.
diff --git a/components/keyed_service/core/keyed_service_factory.cc b/components/keyed_service/core/keyed_service_factory.cc
index f3629a59..f490446 100644
--- a/components/keyed_service/core/keyed_service_factory.cc
+++ b/components/keyed_service/core/keyed_service_factory.cc
@@ -21,7 +21,7 @@
   DCHECK(mapping_.empty());
 }
 
-void KeyedServiceFactory::SetTestingFactory(base::SupportsUserData* context,
+void KeyedServiceFactory::SetTestingFactory(void* context,
                                             TestingFactory testing_factory) {
   // Ensure that |context| is not marked as stale (e.g., due to it aliasing an
   // instance that was destroyed in an earlier test) in order to avoid accesses
@@ -39,16 +39,17 @@
 }
 
 KeyedService* KeyedServiceFactory::SetTestingFactoryAndUse(
-    base::SupportsUserData* context,
+    void* context,
+    void* side_parameter,
     TestingFactory testing_factory) {
   DCHECK(testing_factory);
   SetTestingFactory(context, std::move(testing_factory));
-  return GetServiceForContext(context, true);
+  return GetServiceForContext(context, side_parameter, true);
 }
 
-KeyedService* KeyedServiceFactory::GetServiceForContext(
-    base::SupportsUserData* context,
-    bool create) {
+KeyedService* KeyedServiceFactory::GetServiceForContext(void* context,
+                                                        void* side_parameter,
+                                                        bool create) {
   TRACE_EVENT1("browser,startup", "KeyedServiceFactory::GetServiceForContext",
                "service_name", name());
   context = GetContextToUse(context);
@@ -75,33 +76,33 @@
       service = factory_iterator->second.Run(context);
     }
   } else {
-    service = BuildServiceInstanceFor(context);
+    service = BuildServiceInstanceFor(context, side_parameter);
   }
 
   return Associate(context, std::move(service));
 }
 
 KeyedService* KeyedServiceFactory::Associate(
-    base::SupportsUserData* context,
+    void* context,
     std::unique_ptr<KeyedService> service) {
   DCHECK(!base::ContainsKey(mapping_, context));
   auto iterator = mapping_.emplace(context, std::move(service)).first;
   return iterator->second.get();
 }
 
-void KeyedServiceFactory::Disassociate(base::SupportsUserData* context) {
+void KeyedServiceFactory::Disassociate(void* context) {
   auto iterator = mapping_.find(context);
   if (iterator != mapping_.end())
     mapping_.erase(iterator);
 }
 
-void KeyedServiceFactory::ContextShutdown(base::SupportsUserData* context) {
+void KeyedServiceFactory::ContextShutdown(void* context) {
   auto iterator = mapping_.find(context);
   if (iterator != mapping_.end() && iterator->second)
     iterator->second->Shutdown();
 }
 
-void KeyedServiceFactory::ContextDestroyed(base::SupportsUserData* context) {
+void KeyedServiceFactory::ContextDestroyed(void* context) {
   Disassociate(context);
 
   // For unit tests, we also remove the factory function both so we don't
@@ -113,15 +114,10 @@
   KeyedServiceBaseFactory::ContextDestroyed(context);
 }
 
-void KeyedServiceFactory::SetEmptyTestingFactory(
-    base::SupportsUserData* context) {
+void KeyedServiceFactory::SetEmptyTestingFactory(void* context) {
   SetTestingFactory(context, TestingFactory());
 }
 
-bool KeyedServiceFactory::HasTestingFactory(base::SupportsUserData* context) {
+bool KeyedServiceFactory::HasTestingFactory(void* context) {
   return base::ContainsKey(testing_factories_, context);
 }
-
-void KeyedServiceFactory::CreateServiceNow(base::SupportsUserData* context) {
-  GetServiceForContext(context, true);
-}
diff --git a/components/keyed_service/core/keyed_service_factory.h b/components/keyed_service/core/keyed_service_factory.h
index cc82b15..b6d8e300 100644
--- a/components/keyed_service/core/keyed_service_factory.h
+++ b/components/keyed_service/core/keyed_service_factory.h
@@ -17,9 +17,9 @@
 class DependencyManager;
 class KeyedService;
 
-// Base class for Factories that take a base::SupportsUserData object and return
-// some service on a one-to-one mapping. Each concrete factory that derives from
-// this class *must* be a Singleton (only unit tests don't do that).
+// Base class for Factories that take an opaque pointer and return some service
+// on a one-to-one mapping. Each concrete factory that derives from this class
+// *must* be a Singleton (only unit tests don't do that).
 //
 // We do this because services depend on each other and we need to control
 // shutdown/destruction order. In each derived classes' constructors, the
@@ -33,61 +33,64 @@
   // A callback that supplies the instance of a KeyedService for a given
   // |context|. This is used primarily for testing, where we want to feed
   // a specific test double into the KeyedServiceFactory system.
-  using TestingFactory = base::RepeatingCallback<std::unique_ptr<KeyedService>(
-      base::SupportsUserData* context)>;
+  using TestingFactory =
+      base::RepeatingCallback<std::unique_ptr<KeyedService>(void* context)>;
 
   // Associates |testing_factory| with |context| so that |testing_factory| is
   // used to create the KeyedService when requested.  |testing_factory| can be
   // empty to signal that KeyedService should be null.  Multiple calls to
   // SetTestingFactory() are allowed; previous services will be shut down.
-  void SetTestingFactory(base::SupportsUserData* context,
-                         TestingFactory testing_factory);
+  void SetTestingFactory(void* context, TestingFactory testing_factory);
 
   // Associates |testing_factory| with |context| and immediately returns the
   // created KeyedService. Since the factory will be used immediately, it may
   // not be empty.
-  KeyedService* SetTestingFactoryAndUse(base::SupportsUserData* context,
+  KeyedService* SetTestingFactoryAndUse(void* context,
+                                        void* side_parameter,
                                         TestingFactory testing_factory);
 
   // Common implementation that maps |context| to some service object. Deals
   // with incognito contexts per subclass instructions with GetContextToUse()
   // method on the base.  If |create| is true, the service will be created
-  // using BuildServiceInstanceFor() if it doesn't already exist.
-  KeyedService* GetServiceForContext(base::SupportsUserData* context,
+  // using BuildServiceInstanceFor() if it doesn't already exist. Subclasses
+  // could pass |side_parameters| object if needed to create a service object.
+  KeyedService* GetServiceForContext(void* context,
+                                     void* side_parameter,
                                      bool create);
 
   // Maps |context| to |service| with debug checks to prevent duplication and
   // returns a raw pointer to |service|.
-  KeyedService* Associate(base::SupportsUserData* context,
-                          std::unique_ptr<KeyedService> service);
+  KeyedService* Associate(void* context, std::unique_ptr<KeyedService> service);
 
   // Removes the mapping from |context| to a service.
-  void Disassociate(base::SupportsUserData* context);
+  void Disassociate(void* context);
 
-  // Returns a new KeyedService that will be associated with |context|.
+  // Returns a new KeyedService that will be associated with |context|. The
+  // |side_parameter| could be nullptr or some object required to create a
+  // service instance.
   virtual std::unique_ptr<KeyedService> BuildServiceInstanceFor(
-      base::SupportsUserData* context) const = 0;
+      void* context,
+      void* side_parameter) const = 0;
 
   // Returns whether the |context| is off-the-record or not.
-  virtual bool IsOffTheRecord(base::SupportsUserData* context) const = 0;
+  virtual bool IsOffTheRecord(void* context) const = 0;
 
   // KeyedServiceBaseFactory:
-  void ContextShutdown(base::SupportsUserData* context) override;
-  void ContextDestroyed(base::SupportsUserData* context) override;
+  void ContextShutdown(void* context) override;
+  void ContextDestroyed(void* context) override;
 
-  void SetEmptyTestingFactory(base::SupportsUserData* context) override;
-  bool HasTestingFactory(base::SupportsUserData* context) override;
-  void CreateServiceNow(base::SupportsUserData* context) override;
+  void SetEmptyTestingFactory(void* context) override;
+  bool HasTestingFactory(void* context) override;
 
  private:
   friend class DependencyManager;
   friend class DependencyManagerUnittests;
 
   // The mapping between a context and its service.
-  std::map<base::SupportsUserData*, std::unique_ptr<KeyedService>> mapping_;
+  std::map<void*, std::unique_ptr<KeyedService>> mapping_;
 
   // The mapping between a context and its overridden TestingFactory.
-  std::map<base::SupportsUserData*, TestingFactory> testing_factories_;
+  std::map<void*, TestingFactory> testing_factories_;
 
   DISALLOW_COPY_AND_ASSIGN(KeyedServiceFactory);
 };
diff --git a/components/keyed_service/core/refcounted_keyed_service_factory.cc b/components/keyed_service/core/refcounted_keyed_service_factory.cc
index 2e7355d3..a40650f 100644
--- a/components/keyed_service/core/refcounted_keyed_service_factory.cc
+++ b/components/keyed_service/core/refcounted_keyed_service_factory.cc
@@ -20,7 +20,7 @@
 }
 
 void RefcountedKeyedServiceFactory::SetTestingFactory(
-    base::SupportsUserData* context,
+    void* context,
     TestingFactory testing_factory) {
   // Ensure that |context| is not marked as stale (e.g., due to it aliasing an
   // instance that was destroyed in an earlier test) in order to avoid accesses
@@ -39,17 +39,18 @@
 
 scoped_refptr<RefcountedKeyedService>
 RefcountedKeyedServiceFactory::SetTestingFactoryAndUse(
-    base::SupportsUserData* context,
+    void* context,
+    void* side_parameter,
     TestingFactory testing_factory) {
   DCHECK(testing_factory);
   SetTestingFactory(context, std::move(testing_factory));
-  return GetServiceForContext(context, true);
+  return GetServiceForContext(context, side_parameter, true);
 }
 
 scoped_refptr<RefcountedKeyedService>
-RefcountedKeyedServiceFactory::GetServiceForContext(
-    base::SupportsUserData* context,
-    bool create) {
+RefcountedKeyedServiceFactory::GetServiceForContext(void* context,
+                                                    void* side_parameter,
+                                                    bool create) {
   context = GetContextToUse(context);
   if (!context)
     return nullptr;
@@ -74,22 +75,21 @@
       service = factory_iterator->second.Run(context);
     }
   } else {
-    service = BuildServiceInstanceFor(context);
+    service = BuildServiceInstanceFor(context, side_parameter);
   }
 
   return Associate(context, std::move(service));
 }
 
 scoped_refptr<RefcountedKeyedService> RefcountedKeyedServiceFactory::Associate(
-    base::SupportsUserData* context,
+    void* context,
     scoped_refptr<RefcountedKeyedService> service) {
   DCHECK(!base::ContainsKey(mapping_, context));
   auto iterator = mapping_.emplace(context, std::move(service)).first;
   return iterator->second;
 }
 
-void RefcountedKeyedServiceFactory::Disassociate(
-    base::SupportsUserData* context) {
+void RefcountedKeyedServiceFactory::Disassociate(void* context) {
   // We "merely" drop our reference to the service. Hopefully this will cause
   // the service to be destroyed. If not, oh well.
   auto iterator = mapping_.find(context);
@@ -97,15 +97,13 @@
     mapping_.erase(iterator);
 }
 
-void RefcountedKeyedServiceFactory::ContextShutdown(
-    base::SupportsUserData* context) {
+void RefcountedKeyedServiceFactory::ContextShutdown(void* context) {
   auto iterator = mapping_.find(context);
   if (iterator != mapping_.end() && iterator->second.get())
     iterator->second->ShutdownOnUIThread();
 }
 
-void RefcountedKeyedServiceFactory::ContextDestroyed(
-    base::SupportsUserData* context) {
+void RefcountedKeyedServiceFactory::ContextDestroyed(void* context) {
   Disassociate(context);
 
   // For unit tests, we also remove the factory function both so we don't
@@ -117,17 +115,14 @@
   KeyedServiceBaseFactory::ContextDestroyed(context);
 }
 
-void RefcountedKeyedServiceFactory::SetEmptyTestingFactory(
-    base::SupportsUserData* context) {
+void RefcountedKeyedServiceFactory::SetEmptyTestingFactory(void* context) {
   SetTestingFactory(context, TestingFactory());
 }
 
-bool RefcountedKeyedServiceFactory::HasTestingFactory(
-    base::SupportsUserData* context) {
+bool RefcountedKeyedServiceFactory::HasTestingFactory(void* context) {
   return base::ContainsKey(testing_factories_, context);
 }
 
-void RefcountedKeyedServiceFactory::CreateServiceNow(
-    base::SupportsUserData* context) {
-  GetServiceForContext(context, true);
+void RefcountedKeyedServiceFactory::CreateServiceNow(void* context) {
+  GetServiceForContext(context, nullptr /* side_parameter */, true);
 }
diff --git a/components/keyed_service/core/refcounted_keyed_service_factory.h b/components/keyed_service/core/refcounted_keyed_service_factory.h
index 7641934..07839e5 100644
--- a/components/keyed_service/core/refcounted_keyed_service_factory.h
+++ b/components/keyed_service/core/refcounted_keyed_service_factory.h
@@ -34,62 +34,63 @@
   // a specific test double into the KeyedServiceFactory system.
   using TestingFactory =
       base::RepeatingCallback<scoped_refptr<RefcountedKeyedService>(
-          base::SupportsUserData* context)>;
+          void* context)>;
 
   // Associates |testing_factory| with |context| so that |testing_factory| is
   // used to create the KeyedService when requested.  |testing_factory| can be
   // empty to signal that KeyedService should be null.  Multiple calls to
   // SetTestingFactory() are allowed; previous services will be shut down.
-  void SetTestingFactory(base::SupportsUserData* context,
-                         TestingFactory testing_factory);
+  void SetTestingFactory(void* context, TestingFactory testing_factory);
 
   // Associates |testing_factory| with |context| and immediately returns the
   // created KeyedService. Since the factory will be used immediately, it may
   // not be empty.
   scoped_refptr<RefcountedKeyedService> SetTestingFactoryAndUse(
-      base::SupportsUserData* context,
+      void* context,
+      void* side_parameter,
       TestingFactory testing_factory);
 
   // Common implementation that maps |context| to some service object. Deals
   // with incognito contexts per subclass instructions with GetContextToUse()
   // method on the base.  If |create| is true, the service will be created
-  // using BuildServiceInstanceFor() if it doesn't already exist.
-  scoped_refptr<RefcountedKeyedService> GetServiceForContext(
-      base::SupportsUserData* context,
-      bool create);
+  // using BuildServiceInstanceFor() if it doesn't already exist. Subclasses
+  // could pass |side_parameters| object if needed to create a service object.
+  scoped_refptr<RefcountedKeyedService>
+  GetServiceForContext(void* context, void* side_parameter, bool create);
 
   // Maps |context| to |service| with debug checks to prevent duplication and
   // returns |service|.
   scoped_refptr<RefcountedKeyedService> Associate(
-      base::SupportsUserData* context,
+      void* context,
       scoped_refptr<RefcountedKeyedService> service);
 
   // Removes the mapping from |context| to a service.
-  void Disassociate(base::SupportsUserData* context);
+  void Disassociate(void* context);
 
   // Returns a new RefcountedKeyedService that will be associated with
-  // |context|.
+  // |context|. The |side_parameter| could be nullptr or some object required
+  // to create a service instance.
   virtual scoped_refptr<RefcountedKeyedService> BuildServiceInstanceFor(
-      base::SupportsUserData* context) const = 0;
+      void* context,
+      void* side_parameter) const = 0;
 
   // Returns whether the |context| is off-the-record or not.
-  virtual bool IsOffTheRecord(base::SupportsUserData* context) const = 0;
+  virtual bool IsOffTheRecord(void* context) const = 0;
 
   // KeyedServiceBaseFactory:
-  void ContextShutdown(base::SupportsUserData* context) override;
-  void ContextDestroyed(base::SupportsUserData* context) override;
+  void ContextShutdown(void* context) override;
+  void ContextDestroyed(void* context) override;
 
-  void SetEmptyTestingFactory(base::SupportsUserData* context) override;
-  bool HasTestingFactory(base::SupportsUserData* context) override;
-  void CreateServiceNow(base::SupportsUserData* context) override;
+  void SetEmptyTestingFactory(void* context) override;
+  bool HasTestingFactory(void* context) override;
+  void CreateServiceNow(void* context) override;
 
  private:
   // The mapping between a context and its refcounted service.
-  std::map<base::SupportsUserData*, scoped_refptr<RefcountedKeyedService>>
-      mapping_;
+  std::map<void*, scoped_refptr<RefcountedKeyedService>> mapping_;
 
   // The mapping between a context and its overridden TestingFactory.
-  std::map<base::SupportsUserData*, TestingFactory> testing_factories_;
+  std::map<void*, TestingFactory> testing_factories_;
 
   DISALLOW_COPY_AND_ASSIGN(RefcountedKeyedServiceFactory);
 };
diff --git a/components/keyed_service/ios/browser_state_dependency_manager.cc b/components/keyed_service/ios/browser_state_dependency_manager.cc
index 7dcc8d9..d9c8577 100644
--- a/components/keyed_service/ios/browser_state_dependency_manager.cc
+++ b/components/keyed_service/ios/browser_state_dependency_manager.cc
@@ -60,6 +60,5 @@
 
 #ifndef NDEBUG
 void BrowserStateDependencyManager::DumpContextDependencies(
-    base::SupportsUserData* context) const {
-}
+    void* context) const {}
 #endif  // NDEBUG
diff --git a/components/keyed_service/ios/browser_state_dependency_manager.h b/components/keyed_service/ios/browser_state_dependency_manager.h
index 927f9a4..96c6743 100644
--- a/components/keyed_service/ios/browser_state_dependency_manager.h
+++ b/components/keyed_service/ios/browser_state_dependency_manager.h
@@ -80,7 +80,7 @@
 
 #ifndef NDEBUG
   // DependencyManager:
-  void DumpContextDependencies(base::SupportsUserData* context) const final;
+  void DumpContextDependencies(void* context) const final;
 #endif  // NDEBUG
 
   DISALLOW_COPY_AND_ASSIGN(BrowserStateDependencyManager);
diff --git a/components/keyed_service/ios/browser_state_keyed_service_factory.cc b/components/keyed_service/ios/browser_state_keyed_service_factory.cc
index da2937af..7f5b11ba 100644
--- a/components/keyed_service/ios/browser_state_keyed_service_factory.cc
+++ b/components/keyed_service/ios/browser_state_keyed_service_factory.cc
@@ -16,8 +16,7 @@
   KeyedServiceFactory::TestingFactory wrapped_factory;
   if (testing_factory) {
     wrapped_factory = base::BindRepeating(
-        [](const TestingFactory& testing_factory,
-           base::SupportsUserData* context) {
+        [](const TestingFactory& testing_factory, void* context) {
           return testing_factory.Run(static_cast<web::BrowserState*>(context));
         },
         std::move(testing_factory));
@@ -30,13 +29,13 @@
     TestingFactory testing_factory) {
   DCHECK(testing_factory);
   return KeyedServiceFactory::SetTestingFactoryAndUse(
-      context, base::BindRepeating(
-                   [](const TestingFactory& testing_factory,
-                      base::SupportsUserData* context) {
-                     return testing_factory.Run(
-                         static_cast<web::BrowserState*>(context));
-                   },
-                   std::move(testing_factory)));
+      context, nullptr /* side_parameter */,
+      base::BindRepeating(
+          [](const TestingFactory& testing_factory, void* context) {
+            return testing_factory.Run(
+                static_cast<web::BrowserState*>(context));
+          },
+          std::move(testing_factory)));
 }
 
 BrowserStateKeyedServiceFactory::BrowserStateKeyedServiceFactory(
@@ -51,7 +50,8 @@
 KeyedService* BrowserStateKeyedServiceFactory::GetServiceForBrowserState(
     web::BrowserState* context,
     bool create) {
-  return KeyedServiceFactory::GetServiceForContext(context, create);
+  return KeyedServiceFactory::GetServiceForContext(
+      context, nullptr /* side_parameter */, create);
 }
 
 web::BrowserState* BrowserStateKeyedServiceFactory::GetBrowserStateToUse(
@@ -86,17 +86,16 @@
 
 std::unique_ptr<KeyedService>
 BrowserStateKeyedServiceFactory::BuildServiceInstanceFor(
-    base::SupportsUserData* context) const {
+    void* context,
+    void* side_parameter) const {
   return BuildServiceInstanceFor(static_cast<web::BrowserState*>(context));
 }
 
-bool BrowserStateKeyedServiceFactory::IsOffTheRecord(
-    base::SupportsUserData* context) const {
+bool BrowserStateKeyedServiceFactory::IsOffTheRecord(void* context) const {
   return static_cast<web::BrowserState*>(context)->IsOffTheRecord();
 }
 
-base::SupportsUserData* BrowserStateKeyedServiceFactory::GetContextToUse(
-    base::SupportsUserData* context) const {
+void* BrowserStateKeyedServiceFactory::GetContextToUse(void* context) const {
   AssertContextWasntDestroyed(context);
   return GetBrowserStateToUse(static_cast<web::BrowserState*>(context));
 }
@@ -105,13 +104,11 @@
   return ServiceIsCreatedWithBrowserState();
 }
 
-void BrowserStateKeyedServiceFactory::ContextShutdown(
-    base::SupportsUserData* context) {
+void BrowserStateKeyedServiceFactory::ContextShutdown(void* context) {
   BrowserStateShutdown(static_cast<web::BrowserState*>(context));
 }
 
-void BrowserStateKeyedServiceFactory::ContextDestroyed(
-    base::SupportsUserData* context) {
+void BrowserStateKeyedServiceFactory::ContextDestroyed(void* context) {
   BrowserStateDestroyed(static_cast<web::BrowserState*>(context));
 }
 
@@ -119,3 +116,8 @@
     user_prefs::PrefRegistrySyncable* registry) {
   RegisterBrowserStatePrefs(registry);
 }
+
+void BrowserStateKeyedServiceFactory::CreateServiceNow(void* context) {
+  KeyedServiceFactory::GetServiceForContext(context,
+                                            nullptr /* side_parameter */, true);
+}
diff --git a/components/keyed_service/ios/browser_state_keyed_service_factory.h b/components/keyed_service/ios/browser_state_keyed_service_factory.h
index d891c113..95711d32 100644
--- a/components/keyed_service/ios/browser_state_keyed_service_factory.h
+++ b/components/keyed_service/ios/browser_state_keyed_service_factory.h
@@ -117,16 +117,17 @@
 
   // KeyedServiceFactory:
   std::unique_ptr<KeyedService> BuildServiceInstanceFor(
-      base::SupportsUserData* context) const final;
-  bool IsOffTheRecord(base::SupportsUserData* context) const final;
+      void* context,
+      void* side_parameter) const final;
+  bool IsOffTheRecord(void* context) const final;
 
   // KeyedServiceBaseFactory:
-  base::SupportsUserData* GetContextToUse(
-      base::SupportsUserData* context) const final;
+  void* GetContextToUse(void* context) const final;
   bool ServiceIsCreatedWithContext() const final;
-  void ContextShutdown(base::SupportsUserData* context) final;
-  void ContextDestroyed(base::SupportsUserData* context) final;
+  void ContextShutdown(void* context) final;
+  void ContextDestroyed(void* context) final;
   void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) final;
+  void CreateServiceNow(void* context) final;
 
   DISALLOW_COPY_AND_ASSIGN(BrowserStateKeyedServiceFactory);
 };
diff --git a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc
index b179954..1f838a54 100644
--- a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc
+++ b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc
@@ -16,8 +16,7 @@
   RefcountedKeyedServiceFactory::TestingFactory wrapped_factory;
   if (testing_factory) {
     wrapped_factory = base::BindRepeating(
-        [](const TestingFactory& testing_factory,
-           base::SupportsUserData* context) {
+        [](const TestingFactory& testing_factory, void* context) {
           return testing_factory.Run(static_cast<web::BrowserState*>(context));
         },
         std::move(testing_factory));
@@ -32,13 +31,13 @@
     TestingFactory testing_factory) {
   DCHECK(testing_factory);
   return RefcountedKeyedServiceFactory::SetTestingFactoryAndUse(
-      context, base::BindRepeating(
-                   [](const TestingFactory& testing_factory,
-                      base::SupportsUserData* context) {
-                     return testing_factory.Run(
-                         static_cast<web::BrowserState*>(context));
-                   },
-                   std::move(testing_factory)));
+      context, nullptr /* side_parameter*/,
+      base::BindRepeating(
+          [](const TestingFactory& testing_factory, void* context) {
+            return testing_factory.Run(
+                static_cast<web::BrowserState*>(context));
+          },
+          std::move(testing_factory)));
 }
 
 RefcountedBrowserStateKeyedServiceFactory::
@@ -56,7 +55,8 @@
 RefcountedBrowserStateKeyedServiceFactory::GetServiceForBrowserState(
     web::BrowserState* context,
     bool create) {
-  return RefcountedKeyedServiceFactory::GetServiceForContext(context, create);
+  return RefcountedKeyedServiceFactory::GetServiceForContext(
+      context, nullptr /* side_parameter*/, create);
 }
 
 web::BrowserState*
@@ -94,18 +94,18 @@
 
 scoped_refptr<RefcountedKeyedService>
 RefcountedBrowserStateKeyedServiceFactory::BuildServiceInstanceFor(
-    base::SupportsUserData* context) const {
+    void* context,
+    void* side_parameter) const {
   return BuildServiceInstanceFor(static_cast<web::BrowserState*>(context));
 }
 
 bool RefcountedBrowserStateKeyedServiceFactory::IsOffTheRecord(
-    base::SupportsUserData* context) const {
+    void* context) const {
   return static_cast<web::BrowserState*>(context)->IsOffTheRecord();
 }
 
-base::SupportsUserData*
-RefcountedBrowserStateKeyedServiceFactory::GetContextToUse(
-    base::SupportsUserData* context) const {
+void* RefcountedBrowserStateKeyedServiceFactory::GetContextToUse(
+    void* context) const {
   AssertContextWasntDestroyed(context);
   return GetBrowserStateToUse(static_cast<web::BrowserState*>(context));
 }
@@ -115,13 +115,12 @@
   return ServiceIsCreatedWithBrowserState();
 }
 
-void RefcountedBrowserStateKeyedServiceFactory::ContextShutdown(
-    base::SupportsUserData* context) {
+void RefcountedBrowserStateKeyedServiceFactory::ContextShutdown(void* context) {
   BrowserStateShutdown(static_cast<web::BrowserState*>(context));
 }
 
 void RefcountedBrowserStateKeyedServiceFactory::ContextDestroyed(
-    base::SupportsUserData* context) {
+    void* context) {
   BrowserStateDestroyed(static_cast<web::BrowserState*>(context));
 }
 
diff --git a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h
index 56afdfe..a0da0c05 100644
--- a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h
+++ b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h
@@ -122,15 +122,15 @@
 
   // RefcountedKeyedServiceFactory:
   scoped_refptr<RefcountedKeyedService> BuildServiceInstanceFor(
-      base::SupportsUserData* context) const final;
-  bool IsOffTheRecord(base::SupportsUserData* context) const final;
+      void* context,
+      void* side_parameter) const final;
+  bool IsOffTheRecord(void* context) const final;
 
   // KeyedServiceBaseFactory:
-  base::SupportsUserData* GetContextToUse(
-      base::SupportsUserData* context) const final;
+  void* GetContextToUse(void* context) const final;
   bool ServiceIsCreatedWithContext() const final;
-  void ContextShutdown(base::SupportsUserData* context) final;
-  void ContextDestroyed(base::SupportsUserData* context) final;
+  void ContextShutdown(void* context) final;
+  void ContextDestroyed(void* context) final;
   void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) final;
 
   DISALLOW_COPY_AND_ASSIGN(RefcountedBrowserStateKeyedServiceFactory);
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.cc b/components/password_manager/core/browser/sync/password_sync_bridge.cc
index 09cf825b..6122d6e 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.cc
@@ -447,12 +447,11 @@
   return false;
 }
 
-syncer::ModelTypeSyncBridge::StopSyncResponse
-PasswordSyncBridge::ApplyStopSyncChanges(
+void PasswordSyncBridge::ApplyStopSyncChanges(
     std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
   // TODO(crbug.com/902349): Implement disable-sync case by a more robust
   // implementation, via a dedicated method in PasswordStoreSync.
-  return ModelTypeSyncBridge::ApplyStopSyncChanges(
+  ModelTypeSyncBridge::ApplyStopSyncChanges(
       std::move(delete_metadata_change_list));
 }
 
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.h b/components/password_manager/core/browser/sync/password_sync_bridge.h
index 7772f2ed..7838ddbc 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.h
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.h
@@ -54,9 +54,8 @@
   std::string GetClientTag(const syncer::EntityData& entity_data) override;
   std::string GetStorageKey(const syncer::EntityData& entity_data) override;
   bool SupportsGetStorageKey() const override;
-  ModelTypeSyncBridge::StopSyncResponse ApplyStopSyncChanges(
-      std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list)
-      override;
+  void ApplyStopSyncChanges(std::unique_ptr<syncer::MetadataChangeList>
+                                delete_metadata_change_list) override;
 
  private:
   // Password store responsible for persistence.
diff --git a/components/password_manager/core/common/password_manager_features.cc b/components/password_manager/core/common/password_manager_features.cc
index 14f1336..12842831 100644
--- a/components/password_manager/core/common/password_manager_features.cc
+++ b/components/password_manager/core/common/password_manager_features.cc
@@ -6,6 +6,8 @@
 
 namespace password_manager {
 
+// NOTE: It is strongly recommended to use UpperCamelCase style for feature
+//       names, e.g. "MyGreatFeature".
 namespace features {
 
 // Enable affiliation based matching, so that credentials stored for an Android
@@ -85,6 +87,11 @@
 const base::Feature kOnlyNewParser = {"only-new-password-form-parsing",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Controls whether to offer manual password generation in the accessory sheet
+// on Android.
+const base::Feature kManualPasswordGenerationAndroid{
+    "ManualPasswordGenerationAndroid", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Performs a one-off migration (with retries) from a native backend into
 // logindb. Passwords are served from the new location.
 const base::Feature kMigrateLinuxToLoginDB = {
diff --git a/components/password_manager/core/common/password_manager_features.h b/components/password_manager/core/common/password_manager_features.h
index 55e8109..2196b89d 100644
--- a/components/password_manager/core/common/password_manager_features.h
+++ b/components/password_manager/core/common/password_manager_features.h
@@ -25,6 +25,7 @@
 extern const base::Feature kFillOnAccountSelect;
 extern const base::Feature kFillOnAccountSelectHttp;
 extern const base::Feature kGooglePasswordManager;
+extern const base::Feature kManualPasswordGenerationAndroid;
 extern const base::Feature kMigrateLinuxToLoginDB;
 extern const base::Feature kNewPasswordFormParsing;
 extern const base::Feature kNewPasswordFormParsingForSaving;
diff --git a/components/signin/core/browser/account_reconcilor.cc b/components/signin/core/browser/account_reconcilor.cc
index 4d7d458..b3cc6478 100644
--- a/components/signin/core/browser/account_reconcilor.cc
+++ b/components/signin/core/browser/account_reconcilor.cc
@@ -389,8 +389,10 @@
     return;
   }
   VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
-  cookie_manager_service_->AddAccountToCookie(account_id,
-                                              delegate_->GetGaiaApiSource());
+  cookie_manager_service_->AddAccountToCookie(
+      account_id, delegate_->GetGaiaApiSource(),
+      base::BindOnce(&AccountReconcilor::OnAddAccountToCookieCompleted,
+                     weak_factory_.GetWeakPtr()));
 }
 
 void AccountReconcilor::PerformSetCookiesAction(
diff --git a/components/signin/core/browser/account_reconcilor.h b/components/signin/core/browser/account_reconcilor.h
index 2d46144f..bb1ac48 100644
--- a/components/signin/core/browser/account_reconcilor.h
+++ b/components/signin/core/browser/account_reconcilor.h
@@ -281,9 +281,6 @@
   void OnErrorStateOfRefreshTokenUpdatedForAccount(
       const CoreAccountInfo& account_info,
       const GoogleServiceAuthError& error) override;
-  void OnAddAccountToCookieCompleted(
-      const std::string& account_id,
-      const GoogleServiceAuthError& error) override;
   void OnAccountsInCookieUpdated(
       const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
       const GoogleServiceAuthError& error) override;
@@ -294,6 +291,8 @@
       const std::vector<std::string>& chrome_accounts,
       std::vector<gaia::ListedAccount>&& gaia_accounts);
 
+  void OnAddAccountToCookieCompleted(const std::string& account_id,
+                                     const GoogleServiceAuthError& error);
   void OnSetAccountsInCookieCompleted(const GoogleServiceAuthError& error);
 
   // Lock related methods.
diff --git a/components/signin/core/browser/account_reconcilor_unittest.cc b/components/signin/core/browser/account_reconcilor_unittest.cc
index a29985d..82648e6 100644
--- a/components/signin/core/browser/account_reconcilor_unittest.cc
+++ b/components/signin/core/browser/account_reconcilor_unittest.cc
@@ -276,10 +276,9 @@
   std::string SeedAccountInfo(const std::string& gaia_id,
                               const std::string& username);
 
-  void SimulateAddAccountToCookieCompleted(
-      identity::IdentityManager::Observer* observer,
-      const std::string& account_id,
-      const GoogleServiceAuthError& error);
+  void SimulateAddAccountToCookieCompleted(AccountReconcilor* reconcilor,
+                                           const std::string& account_id,
+                                           const GoogleServiceAuthError& error);
 
   void SimulateCookieContentSettingsChanged(
       content_settings::Observer* observer,
@@ -450,10 +449,10 @@
 }
 
 void AccountReconcilorTest::SimulateAddAccountToCookieCompleted(
-    identity::IdentityManager::Observer* observer,
+    AccountReconcilor* reconcilor,
     const std::string& account_id,
     const GoogleServiceAuthError& error) {
-  observer->OnAddAccountToCookieCompleted(account_id, error);
+  reconcilor->OnAddAccountToCookieCompleted(account_id, error);
 }
 
 void AccountReconcilorTest::SimulateSetAccountsInCookieCompleted(
diff --git a/components/signin/core/browser/gaia_cookie_manager_service.cc b/components/signin/core/browser/gaia_cookie_manager_service.cc
index 6166afaf..94ab355c 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service.cc
+++ b/components/signin/core/browser/gaia_cookie_manager_service.cc
@@ -164,6 +164,16 @@
       source_(source),
       set_accounts_in_cookie_completed_callback_(std::move(callback)) {}
 
+GaiaCookieManagerService::GaiaCookieRequest::GaiaCookieRequest(
+    GaiaCookieRequestType request_type,
+    const std::vector<std::string>& account_ids,
+    gaia::GaiaSource source,
+    AddAccountToCookieCompletedCallback callback)
+    : request_type_(request_type),
+      account_ids_(account_ids),
+      source_(source),
+      add_account_to_cookie_completed_callback_(std::move(callback)) {}
+
 GaiaCookieManagerService::GaiaCookieRequest::~GaiaCookieRequest() {}
 
 GaiaCookieManagerService::GaiaCookieRequest::GaiaCookieRequest(
@@ -186,13 +196,23 @@
     std::move(set_accounts_in_cookie_completed_callback_).Run(error);
 }
 
+void GaiaCookieManagerService::GaiaCookieRequest::
+    RunAddAccountToCookieCompletedCallback(
+        const std::string& account_id,
+        const GoogleServiceAuthError& error) {
+  if (add_account_to_cookie_completed_callback_)
+    std::move(add_account_to_cookie_completed_callback_).Run(account_id, error);
+}
+
 // static
 GaiaCookieManagerService::GaiaCookieRequest
 GaiaCookieManagerService::GaiaCookieRequest::CreateAddAccountRequest(
     const std::string& account_id,
-    gaia::GaiaSource source) {
+    gaia::GaiaSource source,
+    AddAccountToCookieCompletedCallback callback) {
   return GaiaCookieManagerService::GaiaCookieRequest(
-      GaiaCookieRequestType::ADD_ACCOUNT, {account_id}, source);
+      GaiaCookieRequestType::ADD_ACCOUNT, {account_id}, source,
+      std::move(callback));
 }
 
 // static
@@ -541,16 +561,19 @@
 
 void GaiaCookieManagerService::AddAccountToCookieInternal(
     const std::string& account_id,
-    gaia::GaiaSource source) {
+    gaia::GaiaSource source,
+    AddAccountToCookieCompletedCallback completion_callback) {
   DCHECK(!account_id.empty());
+  requests_.push_back(GaiaCookieRequest::CreateAddAccountRequest(
+      account_id, source, std::move(completion_callback)));
+
   if (!signin_client_->AreSigninCookiesAllowed()) {
-    SignalComplete(account_id, GoogleServiceAuthError(
-                                   GoogleServiceAuthError::REQUEST_CANCELED));
+    SignalAddToCookieComplete(
+        requests_.begin(),
+        GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED));
     return;
   }
 
-  requests_.push_back(
-      GaiaCookieRequest::CreateAddAccountRequest(account_id, source));
   if (requests_.size() == 1) {
     signin_client_->DelayNetworkCall(
         base::BindOnce(&GaiaCookieManagerService::StartFetchingUbertoken,
@@ -558,22 +581,27 @@
   }
 }
 
-void GaiaCookieManagerService::AddAccountToCookie(const std::string& account_id,
-                                                  gaia::GaiaSource source) {
+void GaiaCookieManagerService::AddAccountToCookie(
+    const std::string& account_id,
+    gaia::GaiaSource source,
+    AddAccountToCookieCompletedCallback completion_callback) {
   VLOG(1) << "GaiaCookieManagerService::AddAccountToCookie: " << account_id;
   access_token_ = std::string();
-  AddAccountToCookieInternal(account_id, source);
+  AddAccountToCookieInternal(account_id, source,
+                             std::move(completion_callback));
 }
 
 void GaiaCookieManagerService::AddAccountToCookieWithToken(
     const std::string& account_id,
     const std::string& access_token,
-    gaia::GaiaSource source) {
+    gaia::GaiaSource source,
+    AddAccountToCookieCompletedCallback completion_callback) {
   VLOG(1) << "GaiaCookieManagerService::AddAccountToCookieWithToken: "
           << account_id;
   DCHECK(!access_token.empty());
   access_token_ = access_token;
-  AddAccountToCookieInternal(account_id, source);
+  AddAccountToCookieInternal(account_id, source,
+                             std::move(completion_callback));
 }
 
 bool GaiaCookieManagerService::ListAccounts(
@@ -634,7 +662,7 @@
         // We have a pending log in request for an account followed by
         // a signout.
         GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
-        SignalComplete(it->GetAccountID(), error);
+        SignalAddToCookieComplete(it, error);
       }
 
       // Keep all requests except for ADD_ACCOUNTS.
@@ -735,14 +763,20 @@
   InitCookieListener();
 }
 
-void GaiaCookieManagerService::SignalComplete(
-    const std::string& account_id,
+void GaiaCookieManagerService::SignalAddToCookieComplete(
+    const base::circular_deque<GaiaCookieRequest>::iterator& request,
     const GoogleServiceAuthError& error) {
-  // Its possible for the observer to delete |this| object.  Don't access
-  // access any members after this calling the observer.  This method should
-  // be the last call in any other method.
-  for (auto& observer : observer_list_)
-    observer.OnAddAccountToCookieCompleted(account_id, error);
+  // SignalAddToCookieComplete is called in two circumstances:
+  //
+  // - normal flow: this happens when SignalAddToCookieComplete is called at
+  // the end of processing a ADD_ACCOUNT request.
+  //
+  // - during a LogOut operation: When logging out, any queue request to
+  // ADD_ACCOUNT is canceled (which implies that it is possible to run the
+  // completion callback of a request that it not front of the queue).
+
+  request->RunAddAccountToCookieCompletedCallback(request->GetAccountID(),
+                                                  error);
 }
 
 void GaiaCookieManagerService::SignalSetAccountsComplete(
@@ -760,8 +794,8 @@
     const std::string account_id = requests_.front().GetAccountID();
     VLOG(1) << "Failed to retrieve ubertoken"
             << " account=" << account_id << " error=" << error.ToString();
+    SignalAddToCookieComplete(requests_.begin(), error);
     HandleNextRequest();
-    SignalComplete(account_id, error);
     return;
   }
 
@@ -837,8 +871,9 @@
          GaiaCookieRequestType::ADD_ACCOUNT);
 
   MarkListAccountsStale();
+  SignalAddToCookieComplete(requests_.begin(),
+                            GoogleServiceAuthError::AuthErrorNone());
   HandleNextRequest();
-  SignalComplete(account_id, GoogleServiceAuthError::AuthErrorNone());
 
   fetcher_backoff_.InformOfRequest(true);
   uber_token_ = std::string();
@@ -869,8 +904,8 @@
 
   UMA_HISTOGRAM_ENUMERATION("OAuth2Login.MergeSessionFailure", error.state(),
                             GoogleServiceAuthError::NUM_STATES);
+  SignalAddToCookieComplete(requests_.begin(), error);
   HandleNextRequest();
-  SignalComplete(account_id, error);
 }
 
 void GaiaCookieManagerService::OnOAuthMultiloginFinished(
diff --git a/components/signin/core/browser/gaia_cookie_manager_service.h b/components/signin/core/browser/gaia_cookie_manager_service.h
index 26038ed..ee2a8c5 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service.h
+++ b/components/signin/core/browser/gaia_cookie_manager_service.h
@@ -83,6 +83,9 @@
 
   typedef base::OnceCallback<void(const GoogleServiceAuthError& error)>
       SetAccountsInCookieCompletedCallback;
+  typedef base::OnceCallback<void(const std::string& account_id,
+                                  const GoogleServiceAuthError& error)>
+      AddAccountToCookieCompletedCallback;
 
   // Contains the information and parameters for any request.
   class GaiaCookieRequest {
@@ -101,10 +104,14 @@
 
     void RunSetAccountsInCookieCompletedCallback(
         const GoogleServiceAuthError& error);
+    void RunAddAccountToCookieCompletedCallback(
+        const std::string& account_id,
+        const GoogleServiceAuthError& error);
 
     static GaiaCookieRequest CreateAddAccountRequest(
         const std::string& account_id,
-        gaia::GaiaSource source);
+        gaia::GaiaSource source,
+        AddAccountToCookieCompletedCallback callback);
     static GaiaCookieRequest CreateLogOutRequest(gaia::GaiaSource source);
     static GaiaCookieRequest CreateListAccountsRequest();
     static GaiaCookieRequest CreateSetAccountsRequest(
@@ -120,6 +127,10 @@
                       const std::vector<std::string>& account_ids,
                       gaia::GaiaSource source,
                       SetAccountsInCookieCompletedCallback callback);
+    GaiaCookieRequest(GaiaCookieRequestType request_type,
+                      const std::vector<std::string>& account_ids,
+                      gaia::GaiaSource source,
+                      AddAccountToCookieCompletedCallback callback);
 
     GaiaCookieRequestType request_type_;
     std::vector<std::string> account_ids_;
@@ -127,19 +138,14 @@
 
     SetAccountsInCookieCompletedCallback
         set_accounts_in_cookie_completed_callback_;
+    AddAccountToCookieCompletedCallback
+        add_account_to_cookie_completed_callback_;
 
     DISALLOW_COPY_AND_ASSIGN(GaiaCookieRequest);
   };
 
   class Observer {
    public:
-    // Called whenever a merge session is completed.  The account that was
-    // merged is given by |account_id|.  If |error| is equal to
-    // GoogleServiceAuthError::AuthErrorNone() then the merge succeeded.
-    virtual void OnAddAccountToCookieCompleted(
-        const std::string& account_id,
-        const GoogleServiceAuthError& error) {}
-
     // Called whenever the GaiaCookieManagerService's list of GAIA accounts is
     // updated. The GCMS monitors the APISID cookie and triggers a /ListAccounts
     // call on change. The GCMS will also call ListAccounts upon the first call
@@ -241,11 +247,15 @@
   void InitCookieListener();
   void Shutdown() override;
 
-  void AddAccountToCookie(const std::string& account_id,
-                          gaia::GaiaSource source);
-  void AddAccountToCookieWithToken(const std::string& account_id,
-                                   const std::string& access_token,
-                                   gaia::GaiaSource source);
+  void AddAccountToCookie(
+      const std::string& account_id,
+      gaia::GaiaSource source,
+      AddAccountToCookieCompletedCallback completion_callback);
+  void AddAccountToCookieWithToken(
+      const std::string& account_id,
+      const std::string& access_token,
+      gaia::GaiaSource source,
+      AddAccountToCookieCompletedCallback completion_callback);
 
   // Takes list of account_ids and sets the cookie for these accounts regardless
   // of the current cookie state. Removes the accounts that are not in
@@ -327,8 +337,9 @@
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory();
 
   // Calls the AddAccountToCookie completion callback.
-  void SignalComplete(const std::string& account_id,
-                      const GoogleServiceAuthError& error);
+  void SignalAddToCookieComplete(
+      const base::circular_deque<GaiaCookieRequest>::iterator& request,
+      const GoogleServiceAuthError& error);
 
   // Marks the list account being staled, and for iOS only, it triggers to fetch
   // the list of accounts (on iOS there is no OnCookieChange() notification).
@@ -369,8 +380,10 @@
   virtual void OnSetAccountsFinished(const GoogleServiceAuthError& error);
 
   // Helper method for AddAccountToCookie* methods.
-  void AddAccountToCookieInternal(const std::string& account_id,
-                                  gaia::GaiaSource source);
+  void AddAccountToCookieInternal(
+      const std::string& account_id,
+      gaia::GaiaSource source,
+      AddAccountToCookieCompletedCallback completion_callback);
 
   // Helper function to trigger fetching retry in case of failure for only
   // failed account id. Virtual for testing purposes.
diff --git a/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc b/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
index 93a15c2..04b96f42 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
+++ b/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -34,6 +35,9 @@
 
 namespace {
 
+using MockAddAccountToCookieCompletedCallback = base::MockCallback<
+    GaiaCookieManagerService::AddAccountToCookieCompletedCallback>;
+
 class MockObserver : public GaiaCookieManagerService::Observer {
  public:
   explicit MockObserver(GaiaCookieManagerService* helper) : helper_(helper) {
@@ -42,8 +46,6 @@
 
   ~MockObserver() override { helper_->RemoveObserver(this); }
 
-  MOCK_METHOD2(OnAddAccountToCookieCompleted,
-               void(const std::string&, const GoogleServiceAuthError&));
   MOCK_METHOD3(OnGaiaAccountsInCookieUpdated,
                void(const std::vector<gaia::ListedAccount>&,
                     const std::vector<gaia::ListedAccount>&,
@@ -254,10 +256,13 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", no_error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc1@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateMergeSessionSuccess(&helper, "token");
 }
 
@@ -267,10 +272,12 @@
   base::HistogramTester histograms;
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed, Run("acc1@gmail.com", error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateMergeSessionFailure(&helper, error());
   // Persistent error incurs no further retries.
   DCHECK(!helper.is_running());
@@ -283,10 +290,12 @@
   MockObserver observer(&helper);
   signin_client()->set_are_signin_cookies_allowed(false);
 
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", canceled()));
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc1@gmail.com", canceled()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
 }
 
 TEST_F(GaiaCookieManagerServiceTest, MergeSessionRetried) {
@@ -299,10 +308,13 @@
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
   EXPECT_CALL(helper, StartFetchingMergeSession());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", no_error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc1@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateMergeSessionFailure(&helper, canceled());
   DCHECK(helper.is_running());
   Advance(test_task_runner, helper.GetBackoffEntry()->GetTimeUntilRelease());
@@ -321,10 +333,13 @@
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
   EXPECT_CALL(helper, StartFetchingMergeSession()).Times(2);
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", no_error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc1@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateMergeSessionFailure(&helper, canceled());
   DCHECK(helper.is_running());
   Advance(test_task_runner, helper.GetBackoffEntry()->GetTimeUntilRelease());
@@ -342,10 +357,12 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed, Run("acc1@gmail.com", error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateUbertokenFailure(&helper, error());
 }
 
@@ -897,13 +914,18 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2);
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", no_error()));
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed1,
+      add_account_to_cookie_completed2;
+  EXPECT_CALL(add_account_to_cookie_completed1,
+              Run("acc1@gmail.com", no_error()));
+  EXPECT_CALL(add_account_to_cookie_completed2,
+              Run("acc2@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed1.Get());
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed2.Get());
   SimulateMergeSessionSuccess(&helper, "token1");
   SimulateMergeSessionSuccess(&helper, "token2");
 }
@@ -913,13 +935,17 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2);
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", error()));
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed1,
+      add_account_to_cookie_completed2;
+  EXPECT_CALL(add_account_to_cookie_completed1, Run("acc1@gmail.com", error()));
+  EXPECT_CALL(add_account_to_cookie_completed2,
+              Run("acc2@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed1.Get());
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed2.Get());
   SimulateMergeSessionFailure(&helper, error());
   SimulateMergeSessionSuccess(&helper, "token2");
 }
@@ -929,13 +955,17 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2);
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", error()));
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed1,
+      add_account_to_cookie_completed2;
+  EXPECT_CALL(add_account_to_cookie_completed1, Run("acc1@gmail.com", error()));
+  EXPECT_CALL(add_account_to_cookie_completed2,
+              Run("acc2@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed1.Get());
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed2.Get());
   SimulateUbertokenFailure(&helper, error());
   SimulateMergeSessionSuccess(&helper, "token2");
 }
@@ -945,19 +975,25 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken()).Times(4);
-  EXPECT_CALL(observer, OnAddAccountToCookieCompleted(_, no_error())).Times(4);
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed, Run(_, no_error())).Times(4);
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
 
   SimulateMergeSessionSuccess(&helper, "token1");
 
-  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
 
   SimulateMergeSessionSuccess(&helper, "token2");
   SimulateMergeSessionSuccess(&helper, "token3");
 
-  helper.AddAccountToCookie("acc4@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc4@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
 
   SimulateMergeSessionSuccess(&helper, "token4");
 }
@@ -967,11 +1003,14 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
   EXPECT_CALL(helper, StartFetchingLogOut());
 
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc2@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateMergeSessionSuccess(&helper, "token1");
 
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
@@ -984,11 +1023,14 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
   EXPECT_CALL(helper, StartFetchingLogOut());
 
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc2@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateMergeSessionSuccess(&helper, "token1");
 
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
@@ -1002,11 +1044,14 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
   EXPECT_CALL(helper, StartFetchingLogOut());
 
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc2@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
 
   SimulateMergeSessionSuccess(&helper, "token1");
@@ -1018,15 +1063,20 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", no_error()));
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", canceled()));
   EXPECT_CALL(helper, StartFetchingLogOut());
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed1,
+      add_account_to_cookie_completed2;
+  EXPECT_CALL(add_account_to_cookie_completed1,
+              Run("acc1@gmail.com", no_error()));
+  EXPECT_CALL(add_account_to_cookie_completed2,
+              Run("acc2@gmail.com", canceled()));
+
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed1.Get());
   // The Log Out should prevent this AddAccount from being fetched.
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed2.Get());
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
 
   SimulateMergeSessionSuccess(&helper, "token1");
@@ -1038,11 +1088,14 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
   EXPECT_CALL(helper, StartFetchingLogOut());
 
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed;
+  EXPECT_CALL(add_account_to_cookie_completed,
+              Run("acc2@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed.Get());
   SimulateMergeSessionSuccess(&helper, "token1");
 
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
@@ -1056,16 +1109,22 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2);
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
   EXPECT_CALL(helper, StartFetchingLogOut());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc3@gmail.com", no_error()));
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed2,
+      add_account_to_cookie_completed3;
+  EXPECT_CALL(add_account_to_cookie_completed2,
+              Run("acc2@gmail.com", no_error()));
+  EXPECT_CALL(add_account_to_cookie_completed3,
+              Run("acc3@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed2.Get());
   SimulateMergeSessionSuccess(&helper, "token1");
 
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
-  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed3.Get());
 
   SimulateLogOutSuccess(&helper);
   // After LogOut the MergeSession should be fetched.
@@ -1077,19 +1136,24 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2);
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", no_error()));
   EXPECT_CALL(helper, StartFetchingLogOut());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc3@gmail.com", no_error()));
 
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed2,
+      add_account_to_cookie_completed3;
+  EXPECT_CALL(add_account_to_cookie_completed2,
+              Run("acc2@gmail.com", no_error()));
+  EXPECT_CALL(add_account_to_cookie_completed3,
+              Run("acc3@gmail.com", no_error()));
+
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed2.Get());
   SimulateMergeSessionSuccess(&helper, "token1");
 
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
   // Second LogOut will never be fetched.
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
-  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed3.Get());
 
   SimulateLogOutSuccess(&helper);
   // After LogOut the MergeSession should be fetched.
@@ -1101,24 +1165,29 @@
   MockObserver observer(&helper);
 
   // From the first Signin.
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", no_error()));
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed1;
+  EXPECT_CALL(add_account_to_cookie_completed1,
+              Run("acc1@gmail.com", no_error()));
 
   // From the sign out and then re-sign in.
   EXPECT_CALL(helper, StartFetchingLogOut());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc3@gmail.com", no_error()));
+
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed3;
+  EXPECT_CALL(add_account_to_cookie_completed3,
+              Run("acc3@gmail.com", no_error()));
 
   // Total sign in 2 times, not enforcing ordered sequences.
   EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2);
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed1.Get());
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
 
   SimulateMergeSessionSuccess(&helper, "token1");
   SimulateLogOutSuccess(&helper);
 
-  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc3@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed3.Get());
   SimulateMergeSessionSuccess(&helper, "token3");
 }
 
@@ -1127,14 +1196,18 @@
   MockObserver observer(&helper);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc2@gmail.com", canceled()));
-  EXPECT_CALL(observer,
-              OnAddAccountToCookieCompleted("acc1@gmail.com", no_error()));
+  MockAddAccountToCookieCompletedCallback add_account_to_cookie_completed1,
+      add_account_to_cookie_completed2;
+  EXPECT_CALL(add_account_to_cookie_completed1,
+              Run("acc1@gmail.com", no_error()));
+  EXPECT_CALL(add_account_to_cookie_completed2,
+              Run("acc2@gmail.com", canceled()));
   EXPECT_CALL(helper, StartFetchingLogOut());
 
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed1.Get());
+  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome,
+                            add_account_to_cookie_completed2.Get());
   helper.LogOutAllAccounts(gaia::GaiaSource::kChrome);
 
   SimulateMergeSessionSuccess(&helper, "token1");
@@ -1374,7 +1447,9 @@
   InstrumentedGaiaCookieManagerService helper(token_service(), signin_client());
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie(
+      "acc1@gmail.com", gaia::GaiaSource::kChrome,
+      GaiaCookieManagerService::AddAccountToCookieCompletedCallback());
 
   ASSERT_FALSE(IsLoadPending());
   SimulateUbertokenSuccess(&helper, "token");
@@ -1396,7 +1471,9 @@
   helper.external_cc_result_fetcher_for_testing()->Start();
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
-  helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome);
+  helper.AddAccountToCookie(
+      "acc2@gmail.com", gaia::GaiaSource::kChrome,
+      GaiaCookieManagerService::AddAccountToCookieCompletedCallback());
   // There is already a ExternalCCResultFetch underway. This will trigger
   // StartFetchingMergeSession.
   EXPECT_CALL(helper, StartFetchingMergeSession());
diff --git a/components/signin/core/browser/signin_manager.cc b/components/signin/core/browser/signin_manager.cc
index 4250086..dbe01004 100644
--- a/components/signin/core/browser/signin_manager.cc
+++ b/components/signin/core/browser/signin_manager.cc
@@ -239,8 +239,9 @@
   if (!IsAuthenticated())
     return;
 
-  cookie_manager_service_->AddAccountToCookie(GetAuthenticatedAccountId(),
-                                              gaia::GaiaSource::kSigninManager);
+  cookie_manager_service_->AddAccountToCookie(
+      GetAuthenticatedAccountId(), gaia::GaiaSource::kSigninManager,
+      GaiaCookieManagerService::AddAccountToCookieCompletedCallback());
 }
 
 void SigninManager::OnExternalSigninCompleted(const std::string& username) {
diff --git a/components/sync/device_info/device_info_sync_bridge.cc b/components/sync/device_info/device_info_sync_bridge.cc
index f28e009..ce147ff 100644
--- a/components/sync/device_info/device_info_sync_bridge.cc
+++ b/components/sync/device_info/device_info_sync_bridge.cc
@@ -234,8 +234,7 @@
   return entity_data.specifics.device_info().cache_guid();
 }
 
-ModelTypeSyncBridge::StopSyncResponse
-DeviceInfoSyncBridge::ApplyStopSyncChanges(
+void DeviceInfoSyncBridge::ApplyStopSyncChanges(
     std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
   // TODO(skym, crbug.com/659263): Would it be reasonable to pulse_timer_.Stop()
   // or subscription_.reset() here?
@@ -249,7 +248,6 @@
       NotifyObservers();
     }
   }
-  return StopSyncResponse::kModelStillReadyToSync;
 }
 
 bool DeviceInfoSyncBridge::IsSyncing() const {
diff --git a/components/sync/device_info/device_info_sync_bridge.h b/components/sync/device_info/device_info_sync_bridge.h
index 7134399..293b0f2 100644
--- a/components/sync/device_info/device_info_sync_bridge.h
+++ b/components/sync/device_info/device_info_sync_bridge.h
@@ -52,7 +52,7 @@
   void GetAllDataForDebugging(DataCallback callback) override;
   std::string GetClientTag(const EntityData& entity_data) override;
   std::string GetStorageKey(const EntityData& entity_data) override;
-  StopSyncResponse ApplyStopSyncChanges(
+  void ApplyStopSyncChanges(
       std::unique_ptr<MetadataChangeList> delete_metadata_change_list) override;
 
   // DeviceInfoTracker implementation.
diff --git a/components/sync/model/fake_model_type_sync_bridge.cc b/components/sync/model/fake_model_type_sync_bridge.cc
index 86fe0ae..860a127 100644
--- a/components/sync/model/fake_model_type_sync_bridge.cc
+++ b/components/sync/model/fake_model_type_sync_bridge.cc
@@ -361,12 +361,10 @@
   return std::move(*conflict_resolution_);
 }
 
-ModelTypeSyncBridge::StopSyncResponse
-FakeModelTypeSyncBridge::ApplyStopSyncChanges(
+void FakeModelTypeSyncBridge::ApplyStopSyncChanges(
     std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
   ModelTypeSyncBridge::ApplyStopSyncChanges(
       std::move(delete_metadata_change_list));
-  return stop_sync_response_;
 }
 
 void FakeModelTypeSyncBridge::SetConflictResolution(
@@ -375,10 +373,6 @@
       std::make_unique<ConflictResolution>(std::move(resolution));
 }
 
-void FakeModelTypeSyncBridge::SetStopSyncResponse(StopSyncResponse response) {
-  stop_sync_response_ = response;
-}
-
 void FakeModelTypeSyncBridge::ErrorOnNextCall() {
   EXPECT_FALSE(error_next_);
   error_next_ = true;
diff --git a/components/sync/model/fake_model_type_sync_bridge.h b/components/sync/model/fake_model_type_sync_bridge.h
index 10a8aac..82e37fa 100644
--- a/components/sync/model/fake_model_type_sync_bridge.h
+++ b/components/sync/model/fake_model_type_sync_bridge.h
@@ -126,16 +126,13 @@
   ConflictResolution ResolveConflict(
       const EntityData& local_data,
       const EntityData& remote_data) const override;
-  StopSyncResponse ApplyStopSyncChanges(
+  void ApplyStopSyncChanges(
       std::unique_ptr<MetadataChangeList> delete_metadata_change_list) override;
 
   // Stores a resolution for the next call to ResolveConflict. Note that if this
   // is a USE_NEW resolution, the data will only exist for one resolve call.
   void SetConflictResolution(ConflictResolution resolution);
 
-  // Stores the value returned by future calls to ApplyStopSyncChanges().
-  void SetStopSyncResponse(StopSyncResponse response);
-
   // Sets an error that the next fallible call to the bridge will generate.
   void ErrorOnNextCall();
 
@@ -163,9 +160,6 @@
   // The conflict resolution to use for calls to ResolveConflict.
   std::unique_ptr<ConflictResolution> conflict_resolution_;
 
-  StopSyncResponse stop_sync_response_ =
-      StopSyncResponse::kModelStillReadyToSync;
-
   // The storage keys which bridge will ignore.
   std::unordered_set<std::string> keys_to_ignore_;
 
diff --git a/components/sync/model/model_type_sync_bridge.cc b/components/sync/model/model_type_sync_bridge.cc
index 6ad1900..f1e94ab 100644
--- a/components/sync/model/model_type_sync_bridge.cc
+++ b/components/sync/model/model_type_sync_bridge.cc
@@ -46,14 +46,13 @@
   return ConflictResolution::UseRemote();
 }
 
-ModelTypeSyncBridge::StopSyncResponse ModelTypeSyncBridge::ApplyStopSyncChanges(
+void ModelTypeSyncBridge::ApplyStopSyncChanges(
     std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
   if (delete_metadata_change_list) {
     // Nothing to do if this fails, so just ignore the error it might return.
     ApplySyncChanges(std::move(delete_metadata_change_list),
                      EntityChangeList());
   }
-  return StopSyncResponse::kModelStillReadyToSync;
 }
 
 size_t ModelTypeSyncBridge::EstimateSyncOverheadMemoryUsage() const {
diff --git a/components/sync/model/model_type_sync_bridge.h b/components/sync/model/model_type_sync_bridge.h
index f650aac9..2247c85 100644
--- a/components/sync/model/model_type_sync_bridge.h
+++ b/components/sync/model/model_type_sync_bridge.h
@@ -36,13 +36,6 @@
   using DataCallback = base::OnceCallback<void(std::unique_ptr<DataBatch>)>;
   using StorageKeyList = std::vector<std::string>;
 
-  // TODO(crbug.com/863870): Remove this enum now the sessions has migrated
-  // away.
-  enum class StopSyncResponse {
-    kModelStillReadyToSync,
-    kModelNoLongerReadyToSync
-  };
-
   ModelTypeSyncBridge(
       std::unique_ptr<ModelTypeChangeProcessor> change_processor);
 
@@ -166,7 +159,7 @@
   // was disabled), and |*delete_metadata_change_list| contains a change list to
   // remove all metadata that the processor knows about (the bridge may decide
   // to implement deletion by other means).
-  virtual StopSyncResponse ApplyStopSyncChanges(
+  virtual void ApplyStopSyncChanges(
       std::unique_ptr<MetadataChangeList> delete_metadata_change_list);
 
   // Returns an estimate of memory usage attributed to sync (that is, excludes
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.cc b/components/sync/model_impl/client_tag_based_model_type_processor.cc
index 4e6d16c..2489a35 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.cc
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.cc
@@ -193,27 +193,13 @@
     // and the one received from sync and stored it |activation_request_|. This
     // indicates that the stored metadata are invalid (e.g. has been
     // manipulated) and don't belong to the current syncing client.
-    const ModelTypeSyncBridge::StopSyncResponse response =
-        ClearMetadataAndResetState();
+    ClearMetadataAndResetState();
 
-    switch (response) {
-      case ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync:
-        // The model is still ready to sync (with the same |bridge_|) - replay
-        // the initialization.
-        model_ready_to_sync_ = true;
-        // Notify the bridge sync is starting to simulate an enable event.
-        bridge_->OnSyncStarting(activation_request_);
-        break;
-      case ModelTypeSyncBridge::StopSyncResponse::kModelNoLongerReadyToSync:
-        // Model not ready to sync, so wait until the bridge calls
-        // ModelReadyToSync().
-        DCHECK(!model_ready_to_sync_);
-        // Notify the bridge sync is starting to simulate an enable event.
-        bridge_->OnSyncStarting(activation_request_);
-        // Return early to avoid replying to OnSyncStarting() immediately. This
-        // will be handled in ModelReadyToSync().
-        return;
-    }
+    // The model is still ready to sync (with the same |bridge_|) - replay
+    // the initialization.
+    model_ready_to_sync_ = true;
+    // Notify the bridge sync is starting to simulate an enable event.
+    bridge_->OnSyncStarting(activation_request_);
   }
 
   // Cache GUID verification earlier above guarantees the user is the same.
@@ -252,38 +238,21 @@
 
   switch (metadata_fate) {
     case KEEP_METADATA: {
-      switch (bridge_->ApplyStopSyncChanges(
-          /*delete_metadata_change_list=*/nullptr)) {
-        case ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync:
-          // The model is still ready to sync (with the same |bridge_|) and same
-          // sync metadata.
-          ResetState(KEEP_METADATA);
-          DCHECK(model_ready_to_sync_);
-          break;
-        case ModelTypeSyncBridge::StopSyncResponse::kModelNoLongerReadyToSync:
-          // Model not ready to sync, so wait until the bridge calls
-          // ModelReadyToSync(), and meanwhile throw away all metadata.
-          ResetState(CLEAR_METADATA);
-          DCHECK(!model_ready_to_sync_);
-          break;
-      }
+      bridge_->ApplyStopSyncChanges(
+          /*delete_metadata_change_list=*/nullptr);
+      // The model is still ready to sync (with the same |bridge_|) and same
+      // sync metadata.
+      ResetState(KEEP_METADATA);
+      DCHECK(model_ready_to_sync_);
       break;
     }
 
     case CLEAR_METADATA: {
-      switch (ClearMetadataAndResetState()) {
-        case ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync:
-          // The model is still ready to sync (with the same |bridge_|) - replay
-          // the initialization.
-          ModelReadyToSync(std::make_unique<MetadataBatch>());
-          DCHECK(model_ready_to_sync_);
-          break;
-        case ModelTypeSyncBridge::StopSyncResponse::kModelNoLongerReadyToSync:
-          // Model not ready to sync, so wait until the bridge calls
-          // ModelReadyToSync().
-          DCHECK(!model_ready_to_sync_);
-          break;
-      }
+      ClearMetadataAndResetState();
+      // The model is still ready to sync (with the same |bridge_|) - replay
+      // the initialization.
+      ModelReadyToSync(std::make_unique<MetadataBatch>());
+      DCHECK(model_ready_to_sync_);
       break;
     }
   }
@@ -291,8 +260,7 @@
   DCHECK(!IsConnected());
 }
 
-ModelTypeSyncBridge::StopSyncResponse
-ClientTagBasedModelTypeProcessor::ClearMetadataAndResetState() {
+void ClientTagBasedModelTypeProcessor::ClearMetadataAndResetState() {
   std::unique_ptr<MetadataChangeList> change_list;
 
   // Clear metadata if MergeSyncData() was called before.
@@ -309,13 +277,10 @@
     DCHECK(entities_.empty());
   }
 
-  const ModelTypeSyncBridge::StopSyncResponse response =
-      bridge_->ApplyStopSyncChanges(std::move(change_list));
+  bridge_->ApplyStopSyncChanges(std::move(change_list));
 
   // Reset all the internal state of the processor.
   ResetState(CLEAR_METADATA);
-
-  return response;
 }
 
 bool ClientTagBasedModelTypeProcessor::IsTrackingMetadata() {
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.h b/components/sync/model_impl/client_tag_based_model_type_processor.h
index 5ee7c4a..d9a7888 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.h
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.h
@@ -116,7 +116,7 @@
   // Clears all metadata and directs the bridge to clear the persisted metadata
   // as well. In addition, it resets the state of the processor and clears all
   // tracking maps such as |entities_| and |storage_key_to_tag_hash_|.
-  ModelTypeSyncBridge::StopSyncResponse ClearMetadataAndResetState();
+  void ClearMetadataAndResetState();
 
   // Returns true if the model is ready or encountered an error.
   bool IsModelReadyOrError() const;
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc b/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc
index 5b90834..c47c1ec6 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc
+++ b/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc
@@ -2200,43 +2200,6 @@
 }
 
 TEST_F(ClientTagBasedModelTypeProcessorTest,
-       ShouldNotConnectImmediatelyAfterGuidMismatchIfNotReadyToSync) {
-  // Commit item.
-  InitializeToReadyState();
-  WriteItemAndAck(kKey1, kValue1);
-  // Reset the processor to simulate a restart.
-  ResetState(/*keep_db=*/true);
-
-  // Force future stops cause the model to become unready.
-  bridge()->SetStopSyncResponse(
-      ModelTypeSyncBridge::StopSyncResponse::kModelNoLongerReadyToSync);
-
-  // A new processor loads the metadata after changing the cache GUID.
-  bridge()->SetInitialSyncDone(true);
-
-  std::unique_ptr<MetadataBatch> metadata_batch = db()->CreateMetadataBatch();
-  sync_pb::ModelTypeState model_type_state(metadata_batch->GetModelTypeState());
-  model_type_state.set_cache_guid("WRONG_CACHE_GUID");
-  metadata_batch->SetModelTypeState(model_type_state);
-
-  type_processor()->ModelReadyToSync(std::move(metadata_batch));
-  ASSERT_TRUE(type_processor()->IsModelReadyToSyncForTest());
-
-  OnSyncStarting();
-
-  // Model should not be ready to sync.
-  ASSERT_FALSE(type_processor()->IsModelReadyToSyncForTest());
-  // OnSyncStarting() should NOT have completed.
-  EXPECT_EQ(nullptr, worker());
-  // Upon a mismatch, metadata should have been cleared.
-  EXPECT_EQ(0U, db()->metadata_count());
-
-  // Calling ModelReadyToSync() should complete OnSyncStarting().
-  type_processor()->ModelReadyToSync(std::make_unique<MetadataBatch>());
-  EXPECT_NE(nullptr, worker());
-}
-
-TEST_F(ClientTagBasedModelTypeProcessorTest,
        ShouldClearOrphanMetadataInGetLocalChangesWhenDataIsMissing) {
   InitializeToReadyState();
   bridge()->WriteItem(kKey1, kValue1);
diff --git a/components/sync/model_impl/syncable_service_based_bridge.cc b/components/sync/model_impl/syncable_service_based_bridge.cc
index 8225fe3..6fb89ab1 100644
--- a/components/sync/model_impl/syncable_service_based_bridge.cc
+++ b/components/sync/model_impl/syncable_service_based_bridge.cc
@@ -423,8 +423,7 @@
   return ConflictResolution::UseLocal();
 }
 
-ModelTypeSyncBridge::StopSyncResponse
-SyncableServiceBasedBridge::ApplyStopSyncChanges(
+void SyncableServiceBasedBridge::ApplyStopSyncChanges(
     std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(store_);
@@ -438,8 +437,6 @@
     syncable_service_->StopSyncing(type_);
     syncable_service_started_ = false;
   }
-
-  return StopSyncResponse::kModelStillReadyToSync;
 }
 
 size_t SyncableServiceBasedBridge::EstimateSyncOverheadMemoryUsage() const {
diff --git a/components/sync/model_impl/syncable_service_based_bridge.h b/components/sync/model_impl/syncable_service_based_bridge.h
index 04dda28..1576aab8 100644
--- a/components/sync/model_impl/syncable_service_based_bridge.h
+++ b/components/sync/model_impl/syncable_service_based_bridge.h
@@ -64,7 +64,7 @@
   ConflictResolution ResolveConflict(
       const EntityData& local_data,
       const EntityData& remote_data) const override;
-  StopSyncResponse ApplyStopSyncChanges(
+  void ApplyStopSyncChanges(
       std::unique_ptr<MetadataChangeList> delete_metadata_change_list) override;
   size_t EstimateSyncOverheadMemoryUsage() const override;
 
diff --git a/components/sync/user_events/user_event_sync_bridge.cc b/components/sync/user_events/user_event_sync_bridge.cc
index ae12693..5f8be12 100644
--- a/components/sync/user_events/user_event_sync_bridge.cc
+++ b/components/sync/user_events/user_event_sync_bridge.cc
@@ -145,14 +145,12 @@
   return GetStorageKeyFromSpecifics(entity_data.specifics.user_event());
 }
 
-ModelTypeSyncBridge::StopSyncResponse UserEventSyncBridge::ApplyStopSyncChanges(
+void UserEventSyncBridge::ApplyStopSyncChanges(
     std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
   if (delete_metadata_change_list) {
     store_->DeleteAllDataAndMetadata(base::BindOnce(
         &UserEventSyncBridge::OnCommit, weak_ptr_factory_.GetWeakPtr()));
   }
-
-  return StopSyncResponse::kModelStillReadyToSync;
 }
 
 void UserEventSyncBridge::RecordUserEvent(
diff --git a/components/sync/user_events/user_event_sync_bridge.h b/components/sync/user_events/user_event_sync_bridge.h
index 2979732a..a3300dc 100644
--- a/components/sync/user_events/user_event_sync_bridge.h
+++ b/components/sync/user_events/user_event_sync_bridge.h
@@ -42,7 +42,7 @@
   void GetAllDataForDebugging(DataCallback callback) override;
   std::string GetClientTag(const EntityData& entity_data) override;
   std::string GetStorageKey(const EntityData& entity_data) override;
-  StopSyncResponse ApplyStopSyncChanges(
+  void ApplyStopSyncChanges(
       std::unique_ptr<MetadataChangeList> delete_metadata_change_list) override;
 
   void RecordUserEvent(std::unique_ptr<sync_pb::UserEventSpecifics> specifics);
diff --git a/components/sync/user_events/user_event_sync_bridge_unittest.cc b/components/sync/user_events/user_event_sync_bridge_unittest.cc
index a15f370..41f8967 100644
--- a/components/sync/user_events/user_event_sync_bridge_unittest.cc
+++ b/components/sync/user_events/user_event_sync_bridge_unittest.cc
@@ -216,9 +216,7 @@
   bridge()->RecordUserEvent(std::make_unique<UserEventSpecifics>(specifics));
   ASSERT_THAT(GetAllData(), SizeIs(1));
 
-  EXPECT_THAT(
-      bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList()),
-      Eq(ModelTypeSyncBridge::StopSyncResponse::kModelStillReadyToSync));
+  bridge()->ApplyStopSyncChanges(WriteBatch::CreateMetadataChangeList());
   // The bridge may asynchronously query the store to choose what to delete.
   base::RunLoop().RunUntilIdle();
 
diff --git a/components/sync_sessions/session_sync_bridge.cc b/components/sync_sessions/session_sync_bridge.cc
index c05c5647..4f5fead 100644
--- a/components/sync_sessions/session_sync_bridge.cc
+++ b/components/sync_sessions/session_sync_bridge.cc
@@ -292,7 +292,7 @@
   return SessionStore::GetStorageKey(entity_data.specifics.session());
 }
 
-ModelTypeSyncBridge::StopSyncResponse SessionSyncBridge::ApplyStopSyncChanges(
+void SessionSyncBridge::ApplyStopSyncChanges(
     std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
   DCHECK(store_);
   local_session_event_router_->Stop();
@@ -300,7 +300,6 @@
     store_->DeleteAllDataAndMetadata();
   }
   syncing_.reset();
-  return StopSyncResponse::kModelStillReadyToSync;
 }
 
 std::unique_ptr<LocalSessionEventHandlerImpl::WriteBatch>
diff --git a/components/sync_sessions/session_sync_bridge.h b/components/sync_sessions/session_sync_bridge.h
index 7f9e142..aa14ae3 100644
--- a/components/sync_sessions/session_sync_bridge.h
+++ b/components/sync_sessions/session_sync_bridge.h
@@ -64,9 +64,8 @@
   void GetAllDataForDebugging(DataCallback callback) override;
   std::string GetClientTag(const syncer::EntityData& entity_data) override;
   std::string GetStorageKey(const syncer::EntityData& entity_data) override;
-  StopSyncResponse ApplyStopSyncChanges(
-      std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list)
-      override;
+  void ApplyStopSyncChanges(std::unique_ptr<syncer::MetadataChangeList>
+                                delete_metadata_change_list) override;
 
   // LocalSessionEventHandlerImpl::Delegate implementation.
   std::unique_ptr<LocalSessionEventHandlerImpl::WriteBatch>
diff --git a/components/tracing/common/trace_startup_config.cc b/components/tracing/common/trace_startup_config.cc
index 4376acb3..6c6392c 100644
--- a/components/tracing/common/trace_startup_config.cc
+++ b/components/tracing/common/trace_startup_config.cc
@@ -31,6 +31,9 @@
 const size_t kTraceConfigFileSizeLimit = 64 * 1024;
 const int kDefaultStartupDuration = 5;
 
+// 95th percentile size of current startup traces size uploaded.
+const size_t kMaxStartupTraceSizeInKb = 300;
+
 // Trace config file path:
 // - Android: /data/local/chrome-trace-config.json
 // - Others: specified by --trace-config-file flag.
@@ -70,6 +73,7 @@
       {base::GetCurrentProcId()});
   // First 10k events at start are sufficient to debug startup traces.
   trace_config.SetTraceBufferSizeInEvents(10000);
+  trace_config.SetTraceBufferSizeInKb(kMaxStartupTraceSizeInKb);
   trace_config.SetProcessFilterConfig(process_config);
   // Enable argument filter since we could be background tracing.
   trace_config.EnableArgumentFilter();
diff --git a/components/update_client/ping_manager_unittest.cc b/components/update_client/ping_manager_unittest.cc
index 8520616..b48a905e 100644
--- a/components/update_client/ping_manager_unittest.cc
+++ b/components/update_client/ping_manager_unittest.cc
@@ -139,7 +139,8 @@
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(msg);
+      const auto root = base::JSONReader::Read(msg);
+      ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
       ASSERT_TRUE(request);
       EXPECT_TRUE(request->FindKey("@os"));
@@ -219,7 +220,8 @@
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(msg);
+      const auto root = base::JSONReader::Read(msg);
+      ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
       const auto& app = request->FindKey("app")->GetList()[0];
       EXPECT_EQ("abc", app.FindKey("appid")->GetString());
@@ -266,7 +268,8 @@
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(msg);
+      const auto root = base::JSONReader::Read(msg);
+      ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
       const auto& app = request->FindKey("app")->GetList()[0];
       EXPECT_EQ("abc", app.FindKey("appid")->GetString());
@@ -316,7 +319,8 @@
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(msg);
+      const auto root = base::JSONReader::Read(msg);
+      ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
       const auto& app = request->FindKey("app")->GetList()[0];
       EXPECT_EQ("abc", app.FindKey("appid")->GetString());
@@ -350,7 +354,8 @@
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(msg);
+      const auto root = base::JSONReader::Read(msg);
+      ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
       const auto& app = request->FindKey("app")->GetList()[0];
       EXPECT_EQ("abc", app.FindKey("appid")->GetString());
@@ -414,7 +419,8 @@
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(msg);
+      const auto root = base::JSONReader::Read(msg);
+      ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
       const auto& app = request->FindKey("app")->GetList()[0];
       EXPECT_EQ("abc", app.FindKey("appid")->GetString());
diff --git a/components/update_client/protocol_parser_json.cc b/components/update_client/protocol_parser_json.cc
index d7d0037..09dd08f6 100644
--- a/components/update_client/protocol_parser_json.cc
+++ b/components/update_client/protocol_parser_json.cc
@@ -282,7 +282,7 @@
     ParseError("Missing secure JSON prefix.");
     return false;
   }
-  const auto doc = base::JSONReader().ReadDeprecated(
+  const auto doc = base::JSONReader::Read(
       {response_json.begin() + std::char_traits<char>::length(kJSONPrefix),
        response_json.end()});
   if (!doc) {
diff --git a/components/update_client/update_checker_unittest.cc b/components/update_client/update_checker_unittest.cc
index 4ab14f6..c74a51f 100644
--- a/components/update_client/update_checker_unittest.cc
+++ b/components/update_client/update_checker_unittest.cc
@@ -284,7 +284,8 @@
   // Sanity check the request.
   const auto& request = post_interceptor_->GetRequestBody(0);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(request);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
     const auto* request = root->FindKey("request");
     ASSERT_TRUE(request);
     EXPECT_TRUE(request->FindKey("@os"));
@@ -431,7 +432,8 @@
 
   const auto request = post_interceptor_->GetRequestBody(0);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(request);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.9", app.FindKey("version")->GetString());
@@ -488,7 +490,8 @@
   const auto request = post_interceptor_->GetRequestBody(0);
 
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(request);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.9", app.FindKey("version")->GetString());
@@ -570,7 +573,8 @@
   // The request must contain dlpref="cacheable".
   const auto request = post_interceptor_->GetRequestBody(0);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(request);
+    const auto root = base::JSONReader().Read(request);
+    ASSERT_TRUE(root);
     EXPECT_EQ("cacheable",
               root->FindKey("request")->FindKey("dlpref")->GetString());
   } else {
@@ -608,7 +612,8 @@
   // Sanity check the request.
   const auto& request = post_interceptor_->GetRequestBody(0);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(request);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.9", app.FindKey("version")->GetString());
@@ -711,11 +716,13 @@
 
   if (use_JSON_) {
     const auto root1 =
-        base::JSONReader().ReadDeprecated(post_interceptor_->GetRequestBody(0));
+        base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+    ASSERT_TRUE(root1);
     const auto& app1 = root1->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(5, app1.FindPath({"ping", "r"})->GetInt());
     const auto root2 =
-        base::JSONReader().ReadDeprecated(post_interceptor_->GetRequestBody(1));
+        base::JSONReader::Read(post_interceptor_->GetRequestBody(1));
+    ASSERT_TRUE(root2);
     const auto& app2 = root2->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(3383, app2.FindPath({"ping", "rd"})->GetInt());
     EXPECT_TRUE(app2.FindPath({"ping", "ping_freshness"})->is_string());
@@ -783,23 +790,26 @@
 
   if (use_JSON_) {
     {
-      const auto root = base::JSONReader().ReadDeprecated(
-          post_interceptor_->GetRequestBody(0));
+      const auto root =
+          base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(10, app.FindPath({"ping", "a"})->GetInt());
       EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
     }
     {
-      const auto root = base::JSONReader().ReadDeprecated(
-          post_interceptor_->GetRequestBody(1));
+      const auto root =
+          base::JSONReader::Read(post_interceptor_->GetRequestBody(1));
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(3383, app.FindPath({"ping", "ad"})->GetInt());
       EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
       EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
     }
     {
-      const auto root = base::JSONReader().ReadDeprecated(
-          post_interceptor_->GetRequestBody(2));
+      const auto root =
+          base::JSONReader::Read(post_interceptor_->GetRequestBody(2));
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
       EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
@@ -839,7 +849,8 @@
       RunThreads();
       const auto& request = post_interceptor->GetRequestBody(0);
       if (use_JSON_) {
-        const auto root = base::JSONReader().ReadDeprecated(request);
+        const auto root = base::JSONReader::Read(request);
+        ASSERT_TRUE(root);
         const auto& app =
             root->FindKey("request")->FindKey("app")->GetList()[0];
         EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
@@ -867,7 +878,8 @@
       RunThreads();
       const auto& request = post_interceptor->GetRequestBody(0);
       if (use_JSON_) {
-        const auto root = base::JSONReader().ReadDeprecated(request);
+        const auto root = base::JSONReader::Read(request);
+        ASSERT_TRUE(root);
         const auto& app =
             root->FindKey("request")->FindKey("app")->GetList()[0];
         EXPECT_EQ("sideload", app.FindKey("installsource")->GetString());
@@ -895,7 +907,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_FALSE(app.FindKey("installsource"));
     } else {
@@ -919,7 +932,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ("webstore", app.FindKey("installsource")->GetString());
       EXPECT_EQ("external", app.FindKey("installedby")->GetString());
@@ -953,7 +967,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_FALSE(app.FindKey("disabled"));
@@ -979,7 +994,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_FALSE(app.FindKey("disabled"));
@@ -1005,7 +1021,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
       const auto& disabled = app.FindKey("disabled")->GetList();
@@ -1032,7 +1049,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
       const auto& disabled = app.FindKey("disabled")->GetList();
@@ -1060,7 +1078,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
       const auto& disabled = app.FindKey("disabled")->GetList();
@@ -1092,7 +1111,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
       const auto& disabled = app.FindKey("disabled")->GetList();
@@ -1143,7 +1163,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
@@ -1176,7 +1197,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
@@ -1209,7 +1231,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
@@ -1242,7 +1265,8 @@
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
     if (use_JSON_) {
-      const auto root = base::JSONReader().ReadDeprecated(request);
+      const auto root = base::JSONReader::Read(request);
+      ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
@@ -1317,7 +1341,8 @@
 
   const auto& request = post_interceptor_->GetRequestBody(0);
   if (use_JSON_) {
-    const auto root = base::JSONReader().ReadDeprecated(request);
+    const auto root = base::JSONReader::Read(request);
+    ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.9", app.FindKey("version")->GetString());
diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn
index c764b7d..5caa15ce 100644
--- a/components/variations/BUILD.gn
+++ b/components/variations/BUILD.gn
@@ -21,8 +21,6 @@
     "client_filterable_state.h",
     "entropy_provider.cc",
     "entropy_provider.h",
-    "experiment_labels.cc",
-    "experiment_labels.h",
     "hashing.cc",
     "hashing.h",
     "metrics.cc",
@@ -49,8 +47,6 @@
     "variations_associated_data.h",
     "variations_crash_keys.cc",
     "variations_crash_keys.h",
-    "variations_experiment_util.cc",
-    "variations_experiment_util.h",
     "variations_http_header_provider.cc",
     "variations_http_header_provider.h",
     "variations_id_collection.cc",
@@ -103,12 +99,16 @@
   }
 
   android_library("load_seed_result_enum_java") {
-    deps = [ "//base:base_java" ]
+    deps = [
+      "//base:base_java",
+    ]
     srcjar_deps = [ ":load_seed_result_enum_srcjar" ]
   }
 
   java_cpp_enum("load_seed_result_enum_srcjar") {
-    sources = [ "metrics.h" ]
+    sources = [
+      "metrics.h",
+    ]
   }
 }
 
@@ -135,7 +135,6 @@
     "active_field_trials_unittest.cc",
     "child_process_field_trial_syncer_unittest.cc",
     "entropy_provider_unittest.cc",
-    "experiment_labels_unittest.cc",
     "hashing_unittest.cc",
     "net/variations_command_line_unittest.cc",
     "net/variations_http_headers_unittest.cc",
diff --git a/components/variations/experiment_labels.cc b/components/variations/experiment_labels.cc
deleted file mode 100644
index 1b0d85a..0000000
--- a/components/variations/experiment_labels.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/variations/experiment_labels.h"
-
-#include <vector>
-
-#include "base/logging.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/variations/variations_associated_data.h"
-#include "components/variations/variations_experiment_util.h"
-
-namespace variations {
-namespace {
-
-const char kVariationPrefix[] = "CrVar";
-
-}  // namespace
-
-base::string16 ExtractNonVariationLabels(const base::string16& labels) {
-  // First, split everything by the label separator.
-  std::vector<base::StringPiece16> entries = base::SplitStringPiece(
-      labels, base::StringPiece16(&kExperimentLabelSeparator, 1),
-      base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-
-  // For each label, keep the ones that do not look like a Variations label.
-  base::string16 non_variation_labels;
-  for (const base::StringPiece16& entry : entries) {
-    if (entry.empty() ||
-        base::StartsWith(entry,
-                         base::ASCIIToUTF16(kVariationPrefix),
-                         base::CompareCase::INSENSITIVE_ASCII)) {
-      continue;
-    }
-
-    // Dump the whole thing, including the timestamp.
-    if (!non_variation_labels.empty())
-      non_variation_labels += kExperimentLabelSeparator;
-    entry.AppendToString(&non_variation_labels);
-  }
-
-  return non_variation_labels;
-}
-
-}  // namespace variations
diff --git a/components/variations/experiment_labels.h b/components/variations/experiment_labels.h
deleted file mode 100644
index cd10825..0000000
--- a/components/variations/experiment_labels.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_
-#define COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_
-
-#include "base/metrics/field_trial.h"
-#include "base/strings/string16.h"
-
-namespace variations {
-
-// Takes the value of experiment_labels from the registry and returns a valid
-// experiment_labels string value containing only the labels that are not
-// associated with Chrome Variations.
-base::string16 ExtractNonVariationLabels(const base::string16& labels);
-
-}  // namespace variations
-
-#endif  // COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_
diff --git a/components/variations/experiment_labels_unittest.cc b/components/variations/experiment_labels_unittest.cc
deleted file mode 100644
index 797f57a..0000000
--- a/components/variations/experiment_labels_unittest.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/variations/experiment_labels.h"
-
-#include <stddef.h>
-
-#include <set>
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-#include "base/metrics/field_trial.h"
-#include "base/stl_util.h"
-#include "base/strings/string_split.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/variations/variations_associated_data.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace variations {
-
-TEST(ExperimentLabelsTest, ExtractNonVariationLabels) {
-  struct {
-    const char* input_label;
-    const char* expected_output;
-  } test_cases[] = {
-      // Empty
-      {"", ""},
-      // One
-      {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT",
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
-      // Three
-      {"CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT",
-       "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT"},
-      // One and one Variation
-      {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT",
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
-      // One and one Variation, flipped
-      {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT",
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
-      // Sandwiched
-      {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT",
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
-      // Only Variations
-      {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT",
-       ""},
-      // Empty values
-      {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT",
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
-      // Trailing semicolon
-      {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;",  // Note the semi here.
-       "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
-      // Semis
-      {";;;;", ""},
-      // Three non-Variation labels
-      // Testing that the order is preserved.
-      {"experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "experiment3=123|Tue, 21 Jan 2014 15:30:21 GMT",
-       "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT;"
-       "experiment3=123|Tue, 21 Jan 2014 15:30:21 GMT"},
-  };
-
-  for (size_t i = 0; i < base::size(test_cases); ++i) {
-    std::string non_variation_labels = base::UTF16ToUTF8(
-        ExtractNonVariationLabels(
-            base::ASCIIToUTF16(test_cases[i].input_label)));
-    EXPECT_EQ(test_cases[i].expected_output, non_variation_labels);
-  }
-}
-
-}  // namespace variations
diff --git a/components/variations/variations_experiment_util.cc b/components/variations/variations_experiment_util.cc
deleted file mode 100644
index 9cf78bba..0000000
--- a/components/variations/variations_experiment_util.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/variations/variations_experiment_util.h"
-
-#include <vector>
-
-#include "base/logging.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/time/time.h"
-
-namespace variations {
-
-const base::char16 kExperimentLabelSeparator = ';';
-
-namespace {
-
-const char* const kDays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
-
-const char* const kMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
-                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
-
-}  // namespace
-
-base::string16 BuildExperimentDateString(const base::Time& current_time) {
-  // The Google Update experiment_labels timestamp format is:
-  // "DAY, DD0 MON YYYY HH0:MI0:SE0 TZ"
-  //  DAY = 3 character day of week,
-  //  DD0 = 2 digit day of month,
-  //  MON = 3 character month of year,
-  //  YYYY = 4 digit year,
-  //  HH0 = 2 digit hour,
-  //  MI0 = 2 digit minute,
-  //  SE0 = 2 digit second,
-  //  TZ = 3 character timezone
-  base::Time::Exploded then = {};
-  current_time.UTCExplode(&then);
-  then.year += 1;
-  DCHECK(then.HasValidValues());
-
-  return base::UTF8ToUTF16(
-      base::StringPrintf("%s, %02d %s %d %02d:%02d:%02d GMT",
-                         kDays[then.day_of_week],
-                         then.day_of_month,
-                         kMonths[then.month - 1],
-                         then.year,
-                         then.hour,
-                         then.minute,
-                         then.second));
-}
-
-}  // namespace variations
diff --git a/components/variations/variations_experiment_util.h b/components/variations/variations_experiment_util.h
deleted file mode 100644
index c9c23f40..0000000
--- a/components/variations/variations_experiment_util.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_VARIATIONS_VARIATIONS_EXPERIMENT_UTIL_H_
-#define COMPONENTS_VARIATIONS_VARIATIONS_EXPERIMENT_UTIL_H_
-
-#include "base/strings/string16.h"
-
-namespace base {
-class Time;
-}
-
-namespace variations {
-
-// The separator used to separate items in experiment labels.
-extern const base::char16 kExperimentLabelSeparator;
-
-// Constructs a date string in the format understood by Google Update for the
-// |current_time| plus one year.
-base::string16 BuildExperimentDateString(const base::Time& current_time);
-
-}  // namespace variations
-
-#endif  // COMPONENTS_VARIATIONS_VARIATIONS_EXPERIMENT_UTIL_H_
diff --git a/components/viz/service/display/overlay_candidate.cc b/components/viz/service/display/overlay_candidate.cc
index 2ec72ea..f1a25012 100644
--- a/components/viz/service/display/overlay_candidate.cc
+++ b/components/viz/service/display/overlay_candidate.cc
@@ -274,7 +274,9 @@
     DisplayResourceProvider* resource_provider,
     const TextureDrawQuad* quad,
     OverlayCandidate* candidate) {
-  if (quad->background_color != SK_ColorTRANSPARENT)
+  if (quad->background_color != SK_ColorTRANSPARENT &&
+      (quad->background_color != SK_ColorBLACK ||
+       quad->ShouldDrawWithBlending()))
     return false;
   if (!FromDrawQuadResource(resource_provider, quad, quad->resource_id(),
                             quad->y_flipped, candidate)) {
diff --git a/components/viz/service/display/overlay_unittest.cc b/components/viz/service/display/overlay_unittest.cc
index 645fee87..e7a9569 100644
--- a/components/viz/service/display/overlay_unittest.cc
+++ b/components/viz/service/display/overlay_unittest.cc
@@ -1025,6 +1025,25 @@
   TextureDrawQuad* quad = CreateFullscreenCandidateQuad(
       resource_provider_.get(), child_resource_provider_.get(),
       child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
+  quad->background_color = SK_ColorRED;
+
+  OverlayCandidateList candidate_list;
+  OverlayProcessor::FilterOperationsMap render_pass_filters;
+  OverlayProcessor::FilterOperationsMap render_pass_backdrop_filters;
+  RenderPassList pass_list;
+  pass_list.push_back(std::move(pass));
+  overlay_processor_->ProcessForOverlays(
+      resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
+      render_pass_filters, render_pass_backdrop_filters, &candidate_list,
+      nullptr, nullptr, &damage_rect_, &content_bounds_);
+  EXPECT_EQ(0U, candidate_list.size());
+}
+
+TEST_F(SingleOverlayOnTopTest, AcceptBlackBackgroundColor) {
+  std::unique_ptr<RenderPass> pass = CreateRenderPass();
+  TextureDrawQuad* quad = CreateFullscreenCandidateQuad(
+      resource_provider_.get(), child_resource_provider_.get(),
+      child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
   quad->background_color = SK_ColorBLACK;
 
   OverlayCandidateList candidate_list;
@@ -1036,6 +1055,26 @@
       resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
       render_pass_filters, render_pass_backdrop_filters, &candidate_list,
       nullptr, nullptr, &damage_rect_, &content_bounds_);
+  EXPECT_EQ(1U, candidate_list.size());
+}
+
+TEST_F(SingleOverlayOnTopTest, RejectBlackBackgroundColorWithBlending) {
+  std::unique_ptr<RenderPass> pass = CreateRenderPass();
+  TextureDrawQuad* quad = CreateFullscreenCandidateQuad(
+      resource_provider_.get(), child_resource_provider_.get(),
+      child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
+  quad->background_color = SK_ColorBLACK;
+  quad->needs_blending = true;
+
+  OverlayCandidateList candidate_list;
+  OverlayProcessor::FilterOperationsMap render_pass_filters;
+  OverlayProcessor::FilterOperationsMap render_pass_backdrop_filters;
+  RenderPassList pass_list;
+  pass_list.push_back(std::move(pass));
+  overlay_processor_->ProcessForOverlays(
+      resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
+      render_pass_filters, render_pass_backdrop_filters, &candidate_list,
+      nullptr, nullptr, &damage_rect_, &content_bounds_);
   EXPECT_EQ(0U, candidate_list.size());
 }
 
diff --git a/content/browser/android/navigation_handle_proxy.cc b/content/browser/android/navigation_handle_proxy.cc
index ec48e73e6..286cddb0 100644
--- a/content/browser/android/navigation_handle_proxy.cc
+++ b/content/browser/android/navigation_handle_proxy.cc
@@ -4,29 +4,98 @@
 
 #include "content/browser/android/navigation_handle_proxy.h"
 
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
 #include "content/public/browser/navigation_handle.h"
-#include "jni/NavigationHandleProxy_jni.h"
+#include "jni/NavigationHandle_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
 
 namespace content {
 
+NavigationHandleProxy::NavigationHandleProxy(
+    NavigationHandle* cpp_navigation_handle)
+    : cpp_navigation_handle_(cpp_navigation_handle) {
+  JNIEnv* env = AttachCurrentThread();
+  java_navigation_handle_ = Java_NavigationHandle_Constructor(
+      env, reinterpret_cast<jlong>(this),
+      ConvertUTF8ToJavaString(env, cpp_navigation_handle_->GetURL().spec()),
+      cpp_navigation_handle_->IsInMainFrame(),
+      cpp_navigation_handle_->IsSameDocument(),
+      cpp_navigation_handle_->IsRendererInitiated());
+}
+
+void NavigationHandleProxy::DidRedirect() {
+  JNIEnv* env = AttachCurrentThread();
+  Java_NavigationHandle_didRedirect(
+      env, java_navigation_handle_,
+      ConvertUTF8ToJavaString(env, cpp_navigation_handle_->GetURL().spec()));
+}
+
+void NavigationHandleProxy::DidFinish() {
+  JNIEnv* env = AttachCurrentThread();
+  // Matches logic in
+  // components/navigation_interception/navigation_params_android.cc
+  ScopedJavaLocalRef<jstring> jstring_url(ConvertUTF8ToJavaString(
+      env, cpp_navigation_handle_->GetBaseURLForDataURL().is_empty()
+               ? cpp_navigation_handle_->GetURL().spec()
+               : cpp_navigation_handle_->GetBaseURLForDataURL()
+                     .possibly_invalid_spec()));
+
+  bool is_fragment_navigation = cpp_navigation_handle_->IsSameDocument();
+
+  if (cpp_navigation_handle_->HasCommitted()) {
+    // See http://crbug.com/251330 for why it's determined this way.
+    url::Replacements<char> replacements;
+    replacements.ClearRef();
+    bool urls_same_ignoring_fragment =
+        cpp_navigation_handle_->GetURL().ReplaceComponents(replacements) ==
+        cpp_navigation_handle_->GetPreviousURL().ReplaceComponents(
+            replacements);
+    is_fragment_navigation &= urls_same_ignoring_fragment;
+  }
+
+  Java_NavigationHandle_didFinish(
+      env, java_navigation_handle_, jstring_url,
+      cpp_navigation_handle_->IsErrorPage(),
+      cpp_navigation_handle_->HasCommitted(), is_fragment_navigation,
+      cpp_navigation_handle_->IsDownload(),
+      cpp_navigation_handle_->HasCommitted()
+          ? cpp_navigation_handle_->GetPageTransition()
+          : -1,
+      cpp_navigation_handle_->GetNetErrorCode(),
+      // TODO(shaktisahu): Change default status to -1 after fixing
+      // crbug/690041.
+      cpp_navigation_handle_->GetResponseHeaders()
+          ? cpp_navigation_handle_->GetResponseHeaders()->response_code()
+          : 200);
+}
+
+NavigationHandleProxy::~NavigationHandleProxy() {
+  JNIEnv* env = AttachCurrentThread();
+  Java_NavigationHandle_release(env, java_navigation_handle_);
+}
+
 // Called from Java.
 void NavigationHandleProxy::SetRequestHeader(
     JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& obj,
-    const base::android::JavaParamRef<jstring>& name,
-    const base::android::JavaParamRef<jstring>& value) {
-  navigation_handle_->SetRequestHeader(
-      base::android::ConvertJavaStringToUTF8(name),
-      base::android::ConvertJavaStringToUTF8(value));
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jstring>& name,
+    const JavaParamRef<jstring>& value) {
+  cpp_navigation_handle_->SetRequestHeader(ConvertJavaStringToUTF8(name),
+                                           ConvertJavaStringToUTF8(value));
 }
 
 // Called from Java.
 void NavigationHandleProxy::RemoveRequestHeader(
     JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& obj,
-    const base::android::JavaParamRef<jstring>& name) {
-  navigation_handle_->RemoveRequestHeader(
-      base::android::ConvertJavaStringToUTF8(name));
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jstring>& name) {
+  cpp_navigation_handle_->RemoveRequestHeader(ConvertJavaStringToUTF8(name));
 }
 
 }  // namespace content
diff --git a/content/browser/android/navigation_handle_proxy.h b/content/browser/android/navigation_handle_proxy.h
index ec2e4a0..af3797c 100644
--- a/content/browser/android/navigation_handle_proxy.h
+++ b/content/browser/android/navigation_handle_proxy.h
@@ -16,11 +16,25 @@
 
 class NavigationHandle;
 
-// JNI bridge for using a content::NavigationHandle from Java.
-class NavigationHandleProxy {
+// JNI Bridge in between:
+// - [C++] NavigationHandle
+// - [Java] NavigationHandle
+class NavigationHandleProxy final {
  public:
-  explicit NavigationHandleProxy(content::NavigationHandle* navigation_handle)
-      : navigation_handle_(navigation_handle) {}
+  explicit NavigationHandleProxy(NavigationHandle* cpp_navigation_handle);
+  ~NavigationHandleProxy();
+
+  NavigationHandle* cpp_navigation_handle() const {
+    return cpp_navigation_handle_;
+  }
+  base::android::ScopedJavaGlobalRef<jobject> java_navigation_handle() const {
+    return java_navigation_handle_;
+  }
+
+  // |DidRedirect| and |DidFinish| updates the NavigationHandle on the java side
+  // with the state from the C++ side.
+  void DidRedirect();
+  void DidFinish();
 
   // Called from Java.
   void SetRequestHeader(JNIEnv* env,
@@ -33,10 +47,9 @@
                            const base::android::JavaParamRef<jobject>& obj,
                            const base::android::JavaParamRef<jstring>& name);
 
-  jlong JavaThis() const { return reinterpret_cast<jlong>(this); }
-
  private:
-  content::NavigationHandle* navigation_handle_;
+  base::android::ScopedJavaGlobalRef<jobject> java_navigation_handle_;
+  NavigationHandle* cpp_navigation_handle_ = nullptr;
 };
 
 }  // namespace content
diff --git a/content/browser/android/web_contents_observer_proxy.cc b/content/browser/android/web_contents_observer_proxy.cc
index 1bca9e7..b965c98 100644
--- a/content/browser/android/web_contents_observer_proxy.cc
+++ b/content/browser/android/web_contents_observer_proxy.cc
@@ -10,9 +10,11 @@
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/optional.h"
+#include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "content/browser/android/navigation_handle_proxy.h"
+#include "content/browser/frame_host/navigation_handle_impl.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/navigation_details.h"
@@ -127,68 +129,29 @@
 
 void WebContentsObserverProxy::DidStartNavigation(
     NavigationHandle* navigation_handle) {
-  JNIEnv* env = AttachCurrentThread();
-  ScopedJavaLocalRef<jstring> jstring_url(
-      ConvertUTF8ToJavaString(env, navigation_handle->GetURL().spec()));
-  NavigationHandleProxy navigation_handle_proxy(navigation_handle);
   Java_WebContentsObserverProxy_didStartNavigation(
-      env, java_observer_, jstring_url, navigation_handle->IsInMainFrame(),
-      navigation_handle->IsSameDocument(), navigation_handle_proxy.JavaThis());
+      AttachCurrentThread(), java_observer_,
+      static_cast<NavigationHandleImpl*>(navigation_handle)
+          ->java_navigation_handle());
 }
 
 void WebContentsObserverProxy::DidRedirectNavigation(
     NavigationHandle* navigation_handle) {
-  JNIEnv* env = AttachCurrentThread();
-  ScopedJavaLocalRef<jstring> jstring_url(
-      ConvertUTF8ToJavaString(env, navigation_handle->GetURL().spec()));
-  NavigationHandleProxy navigation_handle_proxy(navigation_handle);
   Java_WebContentsObserverProxy_didRedirectNavigation(
-      env, java_observer_, jstring_url, navigation_handle->IsInMainFrame(),
-      navigation_handle_proxy.JavaThis());
+      AttachCurrentThread(), java_observer_,
+      static_cast<NavigationHandleImpl*>(navigation_handle)
+          ->java_navigation_handle());
 }
 
 void WebContentsObserverProxy::DidFinishNavigation(
     NavigationHandle* navigation_handle) {
-  JNIEnv* env = AttachCurrentThread();
-  // Matches logic in
-  // components/navigation_interception/navigation_params_android.cc
-  ScopedJavaLocalRef<jstring> jstring_url(ConvertUTF8ToJavaString(
-      env,
-      navigation_handle->GetBaseURLForDataURL().is_empty()
-          ? navigation_handle->GetURL().spec()
-          : navigation_handle->GetBaseURLForDataURL().possibly_invalid_spec()));
-
-  bool is_fragment_navigation = navigation_handle->IsSameDocument();
-
-  if (navigation_handle->HasCommitted()) {
-    // See http://crbug.com/251330 for why it's determined this way.
-    url::Replacements<char> replacements;
-    replacements.ClearRef();
-    bool urls_same_ignoring_fragment =
-        navigation_handle->GetURL().ReplaceComponents(replacements) ==
-        navigation_handle->GetPreviousURL().ReplaceComponents(replacements);
-    is_fragment_navigation &= urls_same_ignoring_fragment;
-  }
-
-  // TODO(shaktisahu): Provide appropriate error description (crbug/690784).
-  ScopedJavaLocalRef<jstring> jerror_description =
-      ConvertUTF8ToJavaString(env, "");
-
   // Remove after fixing https://crbug/905461.
   TRACE_EVENT0("browser", "Java_WebContentsObserverProxy_didFinishNavigation");
+
   Java_WebContentsObserverProxy_didFinishNavigation(
-      env, java_observer_, jstring_url, navigation_handle->IsInMainFrame(),
-      navigation_handle->IsErrorPage(), navigation_handle->HasCommitted(),
-      navigation_handle->IsSameDocument(), is_fragment_navigation,
-      navigation_handle->IsRendererInitiated(), navigation_handle->IsDownload(),
-      navigation_handle->HasCommitted() ? navigation_handle->GetPageTransition()
-                                        : -1,
-      navigation_handle->GetNetErrorCode(), jerror_description,
-      // TODO(shaktisahu): Change default status to -1 after fixing
-      // crbug/690041.
-      navigation_handle->GetResponseHeaders()
-          ? navigation_handle->GetResponseHeaders()->response_code()
-          : 200);
+      AttachCurrentThread(), java_observer_,
+      static_cast<NavigationHandleImpl*>(navigation_handle)
+          ->java_navigation_handle());
 }
 
 void WebContentsObserverProxy::DidFinishLoad(RenderFrameHost* render_frame_host,
diff --git a/content/browser/android/web_contents_observer_proxy.h b/content/browser/android/web_contents_observer_proxy.h
index e558c5c4..16eb7752 100644
--- a/content/browser/android/web_contents_observer_proxy.h
+++ b/content/browser/android/web_contents_observer_proxy.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_ANDROID_WEB_CONTENTS_OBSERVER_PROXY_H_
 
 #include <jni.h>
+#include <memory>
 
 #include "base/android/jni_weak_ref.h"
 #include "base/macros.h"
diff --git a/content/browser/frame_host/navigation_handle_impl.cc b/content/browser/frame_host/navigation_handle_impl.cc
index 48b56d9..38238ad 100644
--- a/content/browser/frame_host/navigation_handle_impl.cc
+++ b/content/browser/frame_host/navigation_handle_impl.cc
@@ -218,6 +218,10 @@
     }
   }
 
+#if defined(OS_ANDROID)
+  navigation_handle_proxy_ = std::make_unique<NavigationHandleProxy>(this);
+#endif
+
   GetDelegate()->DidStartNavigation(this);
 
   if (IsInMainFrame()) {
@@ -245,6 +249,10 @@
     }
   }
 
+#if defined(OS_ANDROID)
+  navigation_handle_proxy_->DidFinish();
+#endif
+
   GetDelegate()->DidFinishNavigation(this);
 
   if (IsInMainFrame()) {
@@ -459,6 +467,13 @@
   throttle_runner_.AddThrottle(std::move(navigation_throttle));
 }
 
+#if defined(OS_ANDROID)
+base::android::ScopedJavaGlobalRef<jobject>
+NavigationHandleImpl::java_navigation_handle() {
+  return navigation_handle_proxy_->java_navigation_handle();
+}
+#endif
+
 bool NavigationHandleImpl::IsDeferredForTesting() {
   return throttle_runner_.GetDeferringThrottle() != nullptr;
 }
@@ -939,6 +954,10 @@
   if (result.action() == NavigationThrottle::PROCEED) {
     state_ = WILL_REDIRECT_REQUEST;
 
+#if defined(OS_ANDROID)
+    navigation_handle_proxy_->DidRedirect();
+#endif
+
     // Notify the delegate that a redirect was encountered and will be followed.
     if (GetDelegate())
       GetDelegate()->DidRedirectNavigation(this);
diff --git a/content/browser/frame_host/navigation_handle_impl.h b/content/browser/frame_host/navigation_handle_impl.h
index 02a4c83..cb51e06 100644
--- a/content/browser/frame_host/navigation_handle_impl.h
+++ b/content/browser/frame_host/navigation_handle_impl.h
@@ -20,6 +20,7 @@
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "build/build_config.h"
 #include "content/browser/frame_host/frame_tree_node.h"
 #include "content/browser/frame_host/navigation_request.h"
 #include "content/browser/frame_host/navigation_throttle_runner.h"
@@ -34,6 +35,11 @@
 #include "third_party/blink/public/platform/web_mixed_content_context_type.h"
 #include "url/gurl.h"
 
+#if defined(OS_ANDROID)
+#include "base/android/scoped_java_ref.h"
+#include "content/browser/android/navigation_handle_proxy.h"
+#endif
+
 struct FrameHostMsg_DidCommitProvisionalLoad_Params;
 
 namespace content {
@@ -155,6 +161,12 @@
   void RegisterSubresourceOverride(
       mojom::TransferrableURLLoaderPtr transferrable_loader) override;
 
+#if defined(OS_ANDROID)
+  // Returns a reference to this NavigationHandle Java counterpart. It is used
+  // by Java WebContentsObservers.
+  base::android::ScopedJavaGlobalRef<jobject> java_navigation_handle();
+#endif
+
   // Used in tests.
   State state_for_testing() const { return state_; }
 
@@ -554,6 +566,12 @@
   // responsible for notifying them about the various navigation events.
   NavigationThrottleRunner throttle_runner_;
 
+#if defined(OS_ANDROID)
+  // For each C++ NavigationHandle, there is a Java counterpart. It is the JNI
+  // bridge in between the two.
+  std::unique_ptr<NavigationHandleProxy> navigation_handle_proxy_;
+#endif
+
   base::WeakPtrFactory<NavigationHandleImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(NavigationHandleImpl);
diff --git a/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc
index 3006f21..9e167cdd 100644
--- a/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc
+++ b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc
@@ -331,7 +331,8 @@
       hang_event_for_testing->Wait();
     }
 
-    DCHECK(extracted_names.size());
+    if (!extracted_names.size())
+      continue;
 
     family_result.push_back(
         DWriteFontLookupTableBuilder::FontFileWithUniqueNames(
diff --git a/content/browser/service_worker/service_worker_database.cc b/content/browser/service_worker/service_worker_database.cc
index c4a75cf5..adaed7d 100644
--- a/content/browser/service_worker/service_worker_database.cc
+++ b/content/browser/service_worker/service_worker_database.cc
@@ -1245,8 +1245,12 @@
 
 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ClearPurgeableResourceIds(
     const std::set<int64_t>& ids) {
+  Status status = LazyOpen(false);
+  if (IsNewOrNonexistentDatabase(status))
+    return STATUS_OK;
+
   leveldb::WriteBatch batch;
-  Status status = DeleteResourceIdsInBatch(
+  status = DeleteResourceIdsInBatch(
       service_worker_internals::kPurgeableResIdKeyPrefix, ids, &batch);
   if (status != STATUS_OK)
     return status;
@@ -1256,8 +1260,12 @@
 ServiceWorkerDatabase::Status
 ServiceWorkerDatabase::PurgeUncommittedResourceIds(
     const std::set<int64_t>& ids) {
+  Status status = LazyOpen(false);
+  if (IsNewOrNonexistentDatabase(status))
+    return STATUS_OK;
+
   leveldb::WriteBatch batch;
-  Status status = DeleteResourceIdsInBatch(
+  status = DeleteResourceIdsInBatch(
       service_worker_internals::kUncommittedResIdKeyPrefix, ids, &batch);
   if (status != STATUS_OK)
     return status;
diff --git a/content/browser/service_worker/service_worker_file_upload_browsertest.cc b/content/browser/service_worker/service_worker_file_upload_browsertest.cc
index 2fd4645e..43119d99b 100644
--- a/content/browser/service_worker/service_worker_file_upload_browsertest.cc
+++ b/content/browser/service_worker/service_worker_file_upload_browsertest.cc
@@ -184,7 +184,8 @@
     std::string result;
     RunTest(BuildTargetUrl("/service_worker/upload", target_query),
             TargetOrigin::kSameOrigin, out_filename, &result);
-    *out_result = base::DictionaryValue::From(base::test::ParseJson(result));
+    *out_result =
+        base::DictionaryValue::From(base::test::ParseJsonDeprecated(result));
     ASSERT_TRUE(*out_result);
   }
 
@@ -250,7 +251,8 @@
     base::ReplaceFirstSubstringAfterOffset(&expectation, 0, "@SIZE@",
                                            base::NumberToString(kFileSize));
 
-    return base::DictionaryValue::From(base::test::ParseJson(expectation));
+    return base::DictionaryValue::From(
+        base::test::ParseJsonDeprecated(expectation));
   }
 
  private:
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index f094ae0..65ea05d 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -505,10 +505,16 @@
     config.EnableArgumentFilter();
 #if defined(OS_ANDROID)
   // Set low trace buffer size on Android in order to upload small trace files.
-  if (config_->tracing_mode() == BackgroundTracingConfigImpl::PREEMPTIVE)
+  if (config_->tracing_mode() == BackgroundTracingConfigImpl::PREEMPTIVE) {
     config.SetTraceBufferSizeInEvents(20000);
+    config.SetTraceBufferSizeInKb(500);
+  }
 #endif
 
+  is_tracing_ = TracingControllerImpl::GetInstance()->StartTracing(
+      config, base::BindOnce(&BackgroundTracingManagerImpl::OnStartTracingDone,
+                             base::Unretained(this), preset));
+
   // Activate the categories immediately. StartTracing eventually does this
   // itself, but asynchronously via PostTask, and in the meantime events will be
   // dropped. This ensures that we start recording events for those categories
@@ -520,10 +526,6 @@
     base::trace_event::TraceLog::GetInstance()->SetEnabled(config, modes);
   }
 
-  is_tracing_ = TracingControllerImpl::GetInstance()->StartTracing(
-      config, base::BindOnce(&BackgroundTracingManagerImpl::OnStartTracingDone,
-                             base::Unretained(this), preset));
-
   RecordBackgroundTracingMetric(RECORDING_ENABLED);
 }
 
diff --git a/content/browser/tracing/cast_tracing_agent.cc b/content/browser/tracing/cast_tracing_agent.cc
index fa4ae658..770f64c 100644
--- a/content/browser/tracing/cast_tracing_agent.cc
+++ b/content/browser/tracing/cast_tracing_agent.cc
@@ -279,13 +279,14 @@
 
 // tracing::mojom::Agent. Called by Mojo internals on the UI thread.
 void CastTracingAgent::StartTracing(const std::string& config,
-                                    base::TimeTicks coordinator_time) {
+                                    base::TimeTicks coordinator_time,
+                                    Agent::StartTracingCallback callback) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   DCHECK(!session_);
   session_ = std::make_unique<CastSystemTracingSession>(worker_task_runner_);
   session_->StartTracing(
       config, base::BindOnce(&CastTracingAgent::StartTracingCallbackProxy,
-                             base::Unretained(this)));
+                             base::Unretained(this), std::move(callback)));
 }
 
 void CastTracingAgent::StopAndFlush(tracing::mojom::RecorderPtr recorder) {
@@ -305,10 +306,12 @@
 }
 
 void CastTracingAgent::StartTracingCallbackProxy(
+    Agent::StartTracingCallback callback,
     bool success) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   if (!success)
     session_.reset();
+  std::move(callback).Run(success);
 }
 
 void CastTracingAgent::HandleTraceData(chromecast::SystemTracer::Status status,
diff --git a/content/browser/tracing/cast_tracing_agent.h b/content/browser/tracing/cast_tracing_agent.h
index 8d59d1c..7b90f2a 100644
--- a/content/browser/tracing/cast_tracing_agent.h
+++ b/content/browser/tracing/cast_tracing_agent.h
@@ -33,10 +33,12 @@
 
   // tracing::mojom::Agent. Called by Mojo internals on the UI thread.
   void StartTracing(const std::string& config,
-                    base::TimeTicks coordinator_time) override;
+                    base::TimeTicks coordinator_time,
+                    Agent::StartTracingCallback callback) override;
   void StopAndFlush(tracing::mojom::RecorderPtr recorder) override;
 
-  void StartTracingCallbackProxy(bool success);
+  void StartTracingCallbackProxy(Agent::StartTracingCallback callback,
+                                 bool success);
   void HandleTraceData(chromecast::SystemTracer::Status status,
                        std::string trace_data);
 
diff --git a/content/browser/tracing/cros_tracing_agent.cc b/content/browser/tracing/cros_tracing_agent.cc
index 427f6d0..a28b7c56 100644
--- a/content/browser/tracing/cros_tracing_agent.cc
+++ b/content/browser/tracing/cros_tracing_agent.cc
@@ -227,12 +227,13 @@
 
 // tracing::mojom::Agent. Called by Mojo internals on the UI thread.
 void CrOSTracingAgent::StartTracing(const std::string& config,
-                                    base::TimeTicks coordinator_time) {
+                                    base::TimeTicks coordinator_time,
+                                    Agent::StartTracingCallback callback) {
   DCHECK(!session_);
   session_ = std::make_unique<CrOSSystemTracingSession>();
   session_->StartTracing(
       config, base::BindOnce(&CrOSTracingAgent::StartTracingCallbackProxy,
-                             base::Unretained(this)));
+                             base::Unretained(this), std::move(callback)));
 }
 
 void CrOSTracingAgent::StopAndFlush(tracing::mojom::RecorderPtr recorder) {
@@ -245,9 +246,11 @@
 }
 
 void CrOSTracingAgent::StartTracingCallbackProxy(
+    Agent::StartTracingCallback callback,
     bool success) {
   if (!success)
     session_.reset();
+  std::move(callback).Run(success);
 }
 
 void CrOSTracingAgent::RecorderProxy(
diff --git a/content/browser/tracing/cros_tracing_agent.h b/content/browser/tracing/cros_tracing_agent.h
index 91ae13c..289db7c 100644
--- a/content/browser/tracing/cros_tracing_agent.h
+++ b/content/browser/tracing/cros_tracing_agent.h
@@ -32,10 +32,12 @@
 
   // tracing::mojom::Agent. Called by Mojo internals on the UI thread.
   void StartTracing(const std::string& config,
-                    base::TimeTicks coordinator_time) override;
+                    base::TimeTicks coordinator_time,
+                    Agent::StartTracingCallback callback) override;
   void StopAndFlush(tracing::mojom::RecorderPtr recorder) override;
 
-  void StartTracingCallbackProxy(bool success);
+  void StartTracingCallbackProxy(Agent::StartTracingCallback callback,
+                                 bool success);
   void RecorderProxy(const scoped_refptr<base::RefCountedString>& events);
 
   std::unique_ptr<CrOSSystemTracingSession> session_;
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc
index 558c2c5..4812441 100644
--- a/content/browser/tracing/tracing_controller_impl.cc
+++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -143,22 +143,16 @@
 }
 
 TracingControllerImpl::TracingControllerImpl()
-    : delegate_(GetContentClient()->browser()->GetTracingDelegate()),
-      weak_ptr_factory_(this) {
+    : delegate_(GetContentClient()->browser()->GetTracingDelegate()) {
   DCHECK(!g_tracing_controller);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // Deliberately leaked, like this class.
   base::FileTracing::SetProvider(new FileTracingProviderImpl);
   AddAgents();
-  base::trace_event::TraceLog::GetInstance()->AddAsyncEnabledStateObserver(
-      weak_ptr_factory_.GetWeakPtr());
   g_tracing_controller = this;
 }
 
-TracingControllerImpl::~TracingControllerImpl() {
-  base::trace_event::TraceLog::GetInstance()->RemoveAsyncEnabledStateObserver(
-      this);
-}
+TracingControllerImpl::~TracingControllerImpl() = default;
 
 void TracingControllerImpl::AddAgents() {
   tracing::TracedProcessImpl::GetInstance()->SetTaskRunner(
@@ -364,32 +358,20 @@
   trace_config_ =
       std::make_unique<base::trace_event::TraceConfig>(trace_config);
 
-  start_tracing_done_ = std::move(callback);
   ConnectToServiceIfNeeded();
-  coordinator_->StartTracing(trace_config.ToString());
-
-  if (start_tracing_done_ &&
-      (base::trace_event::TraceLog::GetInstance()->IsEnabled() ||
-       !trace_config.process_filter_config().IsEnabled(
-           base::Process::Current().Pid()))) {
-    // If we're already tracing, or if the current process is excluded from the
-    // process filter, we'll never receive a callback from the TraceLog, so then
-    // we just run the callback right away.
-    std::move(start_tracing_done_).Run();
-  }
-
+  coordinator_->StartTracing(
+      trace_config.ToString(),
+      base::BindOnce(
+          [](StartTracingDoneCallback callback, bool success) {
+            if (!callback.is_null())
+              std::move(callback).Run();
+          },
+          std::move(callback)));
   // TODO(chiniforooshan): The actual success value should be sent by the
   // callback asynchronously.
   return true;
 }
 
-void TracingControllerImpl::OnTraceLogEnabled() {
-  if (start_tracing_done_)
-    std::move(start_tracing_done_).Run();
-}
-
-void TracingControllerImpl::OnTraceLogDisabled() {}
-
 bool TracingControllerImpl::StopTracing(
     const scoped_refptr<TraceDataEndpoint>& trace_data_endpoint) {
   return StopTracing(std::move(trace_data_endpoint), "");
diff --git a/content/browser/tracing/tracing_controller_impl.h b/content/browser/tracing/tracing_controller_impl.h
index 5a6f9e6..dbb0b86e 100644
--- a/content/browser/tracing/tracing_controller_impl.h
+++ b/content/browser/tracing/tracing_controller_impl.h
@@ -12,8 +12,6 @@
 
 #include "base/callback_forward.h"
 #include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/trace_event/trace_log.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/tracing_controller.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
@@ -38,10 +36,8 @@
 class TracingDelegate;
 class TracingUI;
 
-class TracingControllerImpl
-    : public TracingController,
-      public mojo::DataPipeDrainer::Client,
-      public base::trace_event::TraceLog::AsyncEnabledStateObserver {
+class TracingControllerImpl : public TracingController,
+                              public mojo::DataPipeDrainer::Client {
  public:
   // Create an endpoint for dumping the trace data to a callback.
   CONTENT_EXPORT static scoped_refptr<TraceDataEndpoint> CreateCallbackEndpoint(
@@ -87,10 +83,6 @@
   void OnDataAvailable(const void* data, size_t num_bytes) override;
   void OnDataComplete() override;
 
-  // base::trace_event::TraceLog::AsyncEnabledStateObserver
-  void OnTraceLogEnabled() override;
-  void OnTraceLogDisabled() override;
-
   void OnMetadataAvailable(base::Value metadata);
 
   void CompleteFlush();
@@ -99,7 +91,6 @@
   std::vector<std::unique_ptr<tracing::BaseAgent>> agents_;
   std::unique_ptr<TracingDelegate> delegate_;
   std::unique_ptr<base::trace_event::TraceConfig> trace_config_;
-  StartTracingDoneCallback start_tracing_done_;
   std::unique_ptr<mojo::DataPipeDrainer> drainer_;
   scoped_refptr<TraceDataEndpoint> trace_data_endpoint_;
   std::unique_ptr<base::DictionaryValue> filtered_metadata_;
@@ -107,8 +98,6 @@
   bool is_data_complete_ = false;
   bool is_metadata_available_ = false;
 
-  // NOTE: Weak pointers must be invalidated before all other member variables.
-  base::WeakPtrFactory<TracingControllerImpl> weak_ptr_factory_;
   DISALLOW_COPY_AND_ASSIGN(TracingControllerImpl);
 };
 
diff --git a/content/browser/webauth/authenticator_impl.cc b/content/browser/webauth/authenticator_impl.cc
index eb0d191..bfa9ddeb9 100644
--- a/content/browser/webauth/authenticator_impl.cc
+++ b/content/browser/webauth/authenticator_impl.cc
@@ -659,6 +659,14 @@
         std::move(options->challenge));
   }
 
+  // U2F requests proxied from the cryptotoken extension are limited to USB
+  // devices.
+  const auto transports =
+      OriginIsCryptoTokenExtension(caller_origin_)
+          ? base::flat_set<device::FidoTransportProtocol>(
+                {device::FidoTransportProtocol::kUsbHumanInterfaceDevice})
+          : transports_;
+
   auto authenticator_selection_criteria =
       options->authenticator_selection
           ? mojo::ConvertTo<device::AuthenticatorSelectionCriteria>(
@@ -667,6 +675,7 @@
 
   auto ctap_request = CreateCtapMakeCredentialRequest(
       client_data_json_, options, browser_context()->IsOffTheRecord());
+  // On dual protocol CTAP2/U2F devices, force credential creation over U2F.
   ctap_request.set_is_u2f_only(OriginIsCryptoTokenExtension(caller_origin_));
 
   // Compute the effective attestation conveyance preference and set
@@ -683,7 +692,7 @@
       attestation != ::device::AttestationConveyancePreference::NONE;
 
   request_ = std::make_unique<device::MakeCredentialRequestHandler>(
-      connector_, transports_, std::move(ctap_request),
+      connector_, transports, std::move(ctap_request),
       std::move(authenticator_selection_criteria),
       base::BindOnce(&AuthenticatorImpl::OnRegisterResponse,
                      weak_factory_.GetWeakPtr()));
@@ -730,7 +739,6 @@
   // TODO(kpaulhamus): Fetch and add the Channel ID/Token Binding ID public key
   // used to communicate with the origin.
   if (OriginIsCryptoTokenExtension(caller_origin_)) {
-    // Cryptotoken requests should be proxied without UI.
     request_delegate_->DisableUI();
 
     // As Cryptotoken validates the origin, accept the relying party id as the
@@ -780,6 +788,14 @@
     }
   }
 
+  // U2F requests proxied from the cryptotoken extension are limited to USB
+  // devices.
+  const auto transports =
+      OriginIsCryptoTokenExtension(caller_origin_)
+          ? base::flat_set<device::FidoTransportProtocol>(
+                {device::FidoTransportProtocol::kUsbHumanInterfaceDevice})
+          : transports_;
+
   DCHECK(get_assertion_response_callback_.is_null());
   get_assertion_response_callback_ = std::move(callback);
 
@@ -797,7 +813,7 @@
       CreatePlatformAuthenticatorIfAvailableAndCheckIfCredentialExists(
           ctap_request);
   request_ = std::make_unique<device::GetAssertionRequestHandler>(
-      connector_, transports_, std::move(ctap_request),
+      connector_, transports, std::move(ctap_request),
       base::BindOnce(&AuthenticatorImpl::OnSignResponse,
                      weak_factory_.GetWeakPtr()));
 
diff --git a/content/browser/webauth/authenticator_impl.h b/content/browser/webauth/authenticator_impl.h
index 6fe474b..538ea05 100644
--- a/content/browser/webauth/authenticator_impl.h
+++ b/content/browser/webauth/authenticator_impl.h
@@ -86,6 +86,10 @@
       const {
     return transports_;
   }
+  void set_transports_for_testing(
+      base::flat_set<device::FidoTransportProtocol> transports) {
+    transports_ = transports;
+  }
 
  protected:
   virtual void UpdateRequestDelegate();
@@ -183,7 +187,7 @@
 
   RenderFrameHost* const render_frame_host_;
   service_manager::Connector* connector_ = nullptr;
-  const base::flat_set<device::FidoTransportProtocol> transports_;
+  base::flat_set<device::FidoTransportProtocol> transports_;
 
   std::unique_ptr<device::FidoRequestHandlerBase> request_;
   MakeCredentialCallback make_credential_response_callback_;
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index a6feba0..8924c79 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -418,12 +418,20 @@
     scoped_feature_list_->InitAndDisableFeature(feature);
   }
 
+  void SetUpMockBluetooth() {
+    mock_adapter_ = base::MakeRefCounted<
+        ::testing::NiceMock<device::MockBluetoothAdapter>>();
+    device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
+  }
+
  protected:
   std::unique_ptr<AuthenticatorImpl> authenticator_impl_;
   service_manager::mojom::ConnectorRequest request_;
   std::unique_ptr<service_manager::Connector> connector_;
   std::unique_ptr<device::FakeHidManager> fake_hid_manager_;
   base::Optional<base::test::ScopedFeatureList> scoped_feature_list_;
+  scoped_refptr<::testing::NiceMock<device::MockBluetoothAdapter>>
+      mock_adapter_;
 };
 
 // Verify behavior for various combinations of origins and RP IDs.
@@ -814,6 +822,59 @@
   }
 }
 
+// Test that Cryptotoken requests should only be dispatched to USB
+// authenticators.
+TEST_F(AuthenticatorImplTest, CryptotokenUsbOnly) {
+  TestServiceManagerContext smc;
+  SimulateNavigation(GURL(kTestOrigin1));
+  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
+      base::Time::Now(), base::TimeTicks::Now());
+  url::AddStandardScheme("chrome-extension", url::SCHEME_WITH_HOST);
+  auto authenticator = ConstructAuthenticatorWithTimer(task_runner);
+  authenticator_impl_->set_transports_for_testing(
+      device::GetAllTransportProtocols());
+  SetUpMockBluetooth();
+
+  for (const bool is_cryptotoken_request : {false, true}) {
+    // caBLE and platform discoveries cannot be instantiated through
+    // ScopedVirtualFidoDevice, so we don't test them here.
+    for (const device::FidoTransportProtocol transport :
+         {device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
+          device::FidoTransportProtocol::kBluetoothLowEnergy,
+          device::FidoTransportProtocol::kNearFieldCommunication}) {
+      SCOPED_TRACE(::testing::Message()
+                   << "is_cryptotoken_request=" << is_cryptotoken_request
+                   << ", transport=" << device::ToString(transport));
+
+      OverrideLastCommittedOrigin(
+          main_rfh(),
+          url::Origin::Create(GURL(is_cryptotoken_request ? kCryptotokenOrigin
+                                                          : kTestOrigin1)));
+
+      device::test::ScopedVirtualFidoDevice device;
+      device.SetSupportedProtocol(device::ProtocolVersion::kU2f);
+      device.SetTransport(transport);
+      device.mutable_state()->transport = transport;
+
+      PublicKeyCredentialCreationOptionsPtr options =
+          GetTestPublicKeyCredentialCreationOptions();
+      TestMakeCredentialCallback callback_receiver;
+      authenticator->MakeCredential(std::move(options),
+                                    callback_receiver.callback());
+      base::RunLoop().RunUntilIdle();
+      task_runner->FastForwardBy(base::TimeDelta::FromMinutes(1));
+      callback_receiver.WaitForCallback();
+      EXPECT_EQ(
+          !is_cryptotoken_request ||
+                  transport ==
+                      device::FidoTransportProtocol::kUsbHumanInterfaceDevice
+              ? AuthenticatorStatus::SUCCESS
+              : AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          callback_receiver.status());
+    }
+  }
+}
+
 // Requests originating from cryptotoken should only target U2F devices.
 TEST_F(AuthenticatorImplTest, AttestationPermitted) {
   TestServiceManagerContext smc;
@@ -1043,15 +1104,6 @@
 }
 
 TEST_F(AuthenticatorImplTest, GetAssertionWithEmptyAllowCredentials) {
-  auto mock_adapter =
-      base::MakeRefCounted<::testing::NiceMock<device::MockBluetoothAdapter>>();
-  EXPECT_CALL(*mock_adapter, IsPresent())
-      .WillRepeatedly(::testing::Return(true));
-  device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
-  auto bluetooth_adapter_factory_overrides =
-      device::BluetoothAdapterFactory::Get().InitGlobalValuesForTesting();
-  bluetooth_adapter_factory_overrides->SetLESupported(true);
-
   SimulateNavigation(GURL(kTestOrigin1));
   PublicKeyCredentialRequestOptionsPtr options =
       GetTestPublicKeyCredentialRequestOptions();
@@ -1544,78 +1596,114 @@
   const std::vector<TestCase> kTests = {
       {
           AttestationConveyancePreference::NONE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::SUCCESS, AttestationType::NONE, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::NONE,
+          "",
       },
       {
           AttestationConveyancePreference::NONE,
-          IndividualAttestation::REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::SUCCESS, AttestationType::NONE, "",
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::NONE,
+          "",
       },
       {
           AttestationConveyancePreference::INDIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           AttestationConveyancePreference::INDIRECT,
-          IndividualAttestation::REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           AttestationConveyancePreference::INDIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::U2F,
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::U2F,
           kStandardCommonName,
       },
       {
           AttestationConveyancePreference::INDIRECT,
-          IndividualAttestation::REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::U2F,
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::U2F,
           kStandardCommonName,
       },
       {
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::U2F,
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::U2F,
           kStandardCommonName,
       },
       {
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::U2F,
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::U2F,
           kStandardCommonName,
       },
       {
           AttestationConveyancePreference::ENTERPRISE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           AttestationConveyancePreference::ENTERPRISE,
-          IndividualAttestation::REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           AttestationConveyancePreference::ENTERPRISE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::U2F,
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::U2F,
           kStandardCommonName,
       },
       {
           AttestationConveyancePreference::ENTERPRISE,
-          IndividualAttestation::REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::U2F,
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::U2F,
           kIndividualCommonName,
       },
   };
@@ -1638,32 +1726,42 @@
   const std::vector<TestCase> kTests = {
       {
           AttestationConveyancePreference::ENTERPRISE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::GRANTED,
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::GRANTED,
           AuthenticatorStatus::SUCCESS,
           // If individual attestation was not requested then the attestation
           // certificate will be removed, even if consent is given, because the
           // consent isn't to be tracked.
-          AttestationType::NONE, "",
+          AttestationType::NONE,
+          "",
       },
       {
           AttestationConveyancePreference::ENTERPRISE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::GRANTED,
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::GRANTED,
           AuthenticatorStatus::SUCCESS,
           // If individual attestation was not requested then the attestation
           // certificate will be removed, even if consent is given, because the
           // consent isn't to be tracked.
-          AttestationType::NONE, "",
+          AttestationType::NONE,
+          "",
       },
 
       {
           AttestationConveyancePreference::ENTERPRISE,
-          IndividualAttestation::REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::U2F, kCommonName,
+          IndividualAttestation::REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::U2F,
+          kCommonName,
       },
   };
 
@@ -1694,24 +1792,31 @@
           // attestation is requested, the self-attestation will be removed but,
           // because the transport is kInternal, the AAGUID will be preserved.
           AttestationConveyancePreference::NONE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
           AuthenticatorStatus::SUCCESS,
-          AttestationType::NONE_WITH_NONZERO_AAGUID, "",
+          AttestationType::NONE_WITH_NONZERO_AAGUID,
+          "",
       },
       {
           // If attestation is requested, but denied, we'll still fail the
           // request.
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           // If attestation is requested and granted, the self attestation
           // will be returned.
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::GRANTED,
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::GRANTED,
           AuthenticatorStatus::SUCCESS,
-          AttestationType::SELF_WITH_NONZERO_AAGUID, "",
+          AttestationType::SELF_WITH_NONZERO_AAGUID,
+          "",
       },
   };
 
@@ -1728,22 +1833,31 @@
           // If no attestation is requested, we'll return the self attestation
           // rather than erasing it.
           AttestationConveyancePreference::NONE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::SUCCESS, AttestationType::SELF, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::SELF,
+          "",
       },
       {
           // If attestation is requested, but denied, we'll still fail the
           // request.
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::NOT_ALLOWED_ERROR, AttestationType::ANY, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::NOT_ALLOWED_ERROR,
+          AttestationType::ANY,
+          "",
       },
       {
           // If attestation is requested and granted, the self attestation will
           // be returned.
           AttestationConveyancePreference::DIRECT,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::GRANTED,
-          AuthenticatorStatus::SUCCESS, AttestationType::SELF, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::GRANTED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::SELF,
+          "",
       },
   };
 
@@ -1763,8 +1877,11 @@
           // self-attestation should still be replaced with a "none"
           // attestation.
           AttestationConveyancePreference::NONE,
-          IndividualAttestation::NOT_REQUESTED, AttestationConsent::DENIED,
-          AuthenticatorStatus::SUCCESS, AttestationType::NONE, "",
+          IndividualAttestation::NOT_REQUESTED,
+          AttestationConsent::DENIED,
+          AuthenticatorStatus::SUCCESS,
+          AttestationType::NONE,
+          "",
       },
   };
 
@@ -2101,11 +2218,10 @@
 TEST_F(AuthenticatorImplRequestDelegateTest,
        TestRequestDelegateObservesFidoRequestHandler) {
   EnableFeature(features::kWebAuthBle);
-  auto mock_adapter =
-      base::MakeRefCounted<::testing::NiceMock<device::MockBluetoothAdapter>>();
-  EXPECT_CALL(*mock_adapter, IsPresent())
+  SetUpMockBluetooth();
+
+  EXPECT_CALL(*mock_adapter_, IsPresent())
       .WillRepeatedly(::testing::Return(true));
-  device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
   auto bluetooth_adapter_factory_overrides =
       device::BluetoothAdapterFactory::Get().InitGlobalValuesForTesting();
   bluetooth_adapter_factory_overrides->SetLESupported(true);
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 628baf4..cc3cda7 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -262,7 +262,7 @@
     "java/src/org/chromium/content_public/browser/MotionEventSynthesizer.java",
     "java/src/org/chromium/content_public/browser/NavigationController.java",
     "java/src/org/chromium/content_public/browser/NavigationEntry.java",
-    "java/src/org/chromium/content_public/browser/NavigationHandleProxy.java",
+    "java/src/org/chromium/content_public/browser/NavigationHandle.java",
     "java/src/org/chromium/content_public/browser/NavigationHistory.java",
     "java/src/org/chromium/content_public/browser/RenderCoordinates.java",
     "java/src/org/chromium/content_public/browser/RenderFrameHost.java",
@@ -421,7 +421,7 @@
     "java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java",
     "java/src/org/chromium/content/common/ServiceManagerConnectionImpl.java",
     "java/src/org/chromium/content_public/browser/LoadUrlParams.java",
-    "java/src/org/chromium/content_public/browser/NavigationHandleProxy.java",
+    "java/src/org/chromium/content_public/browser/NavigationHandle.java",
     "java/src/org/chromium/content_public/common/ResourceRequestBody.java",
     "java/src/org/chromium/content_public/common/UseZoomForDSFPolicy.java",
   ]
@@ -553,6 +553,7 @@
   java_files = [
     "junit/src/org/chromium/content/browser/BindingManagerTest.java",
     "junit/src/org/chromium/content/browser/ChildProcessRankingTest.java",
+    "junit/src/org/chromium/content/browser/UiThreadTaskTraitsImplTest.java",
     "junit/src/org/chromium/content/browser/accessibility/BrowserAccessibilityStateTest.java",
     "junit/src/org/chromium/content/browser/SpareChildConnectionTest.java",
     "junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java b/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java
index 835dc95d8..c3146ff 100644
--- a/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java
@@ -6,6 +6,7 @@
 
 import org.chromium.base.task.TaskPriority;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.base.task.TaskTraitsExtensionDescriptor;
 import org.chromium.content_public.browser.BrowserTaskExecutor;
 import org.chromium.content_public.browser.BrowserTaskType;
 
@@ -13,20 +14,49 @@
  * Provides the implementation needed in UiThreadTaskTraits.
  */
 public class UiThreadTaskTraitsImpl {
-    private UiThreadTaskTraitsImpl() {}
+    private static class Descriptor
+            implements TaskTraitsExtensionDescriptor<UiThreadTaskTraitsImpl> {
+        // Corresponds to content::BrowserTaskTraitsExtension.
+        private static final byte EXTENSION_ID = 1;
 
-    // Corresponds to content::BrowserTaskTraitsExtension.
-    public static final byte EXTENSION_ID = 1;
+        // Keep in sync with content::BrowserTaskTraitsExtension::Serialize.
+        private static final byte TASK_TYPE = 1;
+        private static final byte NESTING_INDEX = 2;
 
-    // Keep in sync with content::BrowserTaskTraitsExtension::Serialize.
-    private static final byte TASK_TYPE = 1;
-    private static final byte NESTING_INDEX = 2;
+        @Override
+        public int getId() {
+            return EXTENSION_ID;
+        }
 
-    private static final byte[] sDefaultExtensionData = getDefaultExtensionData();
+        @Override
+        public UiThreadTaskTraitsImpl fromSerializedData(byte[] data) {
+            int taskType = data[TASK_TYPE];
+            return new UiThreadTaskTraitsImpl().setTaskType(taskType);
+        }
 
-    public static final TaskTraits DEFAULT = new TaskTraits(EXTENSION_ID, sDefaultExtensionData);
-    public static final TaskTraits BOOTSTRAP = new TaskTraits(
-            EXTENSION_ID, getExtensionDataForBrowserTaskType(BrowserTaskType.BOOTSTRAP));
+        @Override
+        public byte[] toSerializedData(UiThreadTaskTraitsImpl extension) {
+            byte extensionData[] = new byte[TaskTraits.EXTENSION_STORAGE_SIZE];
+
+            // Note we don't specify the UI thread directly here because it's ID 0 and the array is
+            // initialized to zero.
+
+            // Similarly we don't specify BrowserTaskType.Default its ID is also 0.
+
+            // TODO(crbug.com/876272) Remove this if possible.
+            extensionData[NESTING_INDEX] = 1; // Allow the task to run in a nested RunLoop.
+            extensionData[TASK_TYPE] = (byte) extension.mTaskType;
+            return extensionData;
+        }
+    }
+
+    public static final TaskTraitsExtensionDescriptor<UiThreadTaskTraitsImpl> DESCRIPTOR =
+            new Descriptor();
+
+    public static final TaskTraits DEFAULT =
+            TaskTraits.USER_VISIBLE.withExtension(DESCRIPTOR, new UiThreadTaskTraitsImpl());
+    public static final TaskTraits BOOTSTRAP = TaskTraits.USER_VISIBLE.withExtension(
+            DESCRIPTOR, new UiThreadTaskTraitsImpl().setTaskType(BrowserTaskType.BOOTSTRAP));
     public static final TaskTraits BEST_EFFORT = DEFAULT.taskPriority(TaskPriority.BEST_EFFORT);
     public static final TaskTraits USER_VISIBLE = DEFAULT.taskPriority(TaskPriority.USER_VISIBLE);
     public static final TaskTraits USER_BLOCKING = DEFAULT.taskPriority(TaskPriority.USER_BLOCKING);
@@ -35,22 +65,19 @@
         BrowserTaskExecutor.register();
     }
 
-    private static byte[] getDefaultExtensionData() {
-        byte extensionData[] = new byte[TaskTraits.EXTENSION_STORAGE_SIZE];
+    private @BrowserTaskType int mTaskType;
 
-        // Note we don't specify the UI thread directly here because it's ID 0 and the array is
-        // initialized to zero.
-
-        // Similarly we don't specify BrowserTaskType.Default its ID is also 0.
-
-        // TODO(crbug.com/876272) Remove this if possible.
-        extensionData[NESTING_INDEX] = 1; // Allow the task to run in a nested RunLoop.
-        return extensionData;
+    private UiThreadTaskTraitsImpl() {
+        mTaskType = BrowserTaskType.DEFAULT;
     }
 
-    private static byte[] getExtensionDataForBrowserTaskType(int browserTaskType) {
-        byte extensionData[] = getDefaultExtensionData();
-        extensionData[TASK_TYPE] = (byte) browserTaskType;
-        return extensionData;
+    @BrowserTaskType
+    public int getTaskType() {
+        return mTaskType;
+    }
+
+    private UiThreadTaskTraitsImpl setTaskType(@BrowserTaskType int taskType) {
+        mTaskType = taskType;
+        return this;
     }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
index 79909bd4..a39651f1 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
@@ -9,6 +9,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContentsObserver;
 
 /**
@@ -77,34 +78,23 @@
 
     @Override
     @CalledByNative
-    public void didStartNavigation(
-            String url, boolean isInMainFrame, boolean isSameDocument, long navigationHandleProxy) {
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didStartNavigation(
-                    url, isInMainFrame, isSameDocument, navigationHandleProxy);
-        }
+    public void didStartNavigation(NavigationHandle navigation) {
+        for (mObserversIterator.rewind(); mObserversIterator.hasNext();)
+            mObserversIterator.next().didStartNavigation(navigation);
     }
 
     @Override
     @CalledByNative
-    public void didRedirectNavigation(
-            String url, boolean isInMainFrame, long navigationHandleProxy) {
-        for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didRedirectNavigation(
-                    url, isInMainFrame, navigationHandleProxy);
-        }
+    public void didRedirectNavigation(NavigationHandle navigation) {
+        for (mObserversIterator.rewind(); mObserversIterator.hasNext();)
+            mObserversIterator.next().didRedirectNavigation(navigation);
     }
 
+    @Override
     @CalledByNative
-    private void didFinishNavigation(String url, boolean isInMainFrame, boolean isErrorPage,
-            boolean hasCommitted, boolean isSameDocument, boolean isFragmentNavigation,
-            boolean isRendererInitiated, boolean isDownload, int transition, int errorCode,
-            String errorDescription, int httpStatusCode) {
-        Integer pageTransition = transition == -1 ? null : transition;
+    public void didFinishNavigation(NavigationHandle navigation) {
         for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
-            mObserversIterator.next().didFinishNavigation(url, isInMainFrame, isErrorPage,
-                    hasCommitted, isSameDocument, isFragmentNavigation, isRendererInitiated,
-                    isDownload, pageTransition, errorCode, errorDescription, httpStatusCode);
+            mObserversIterator.next().didFinishNavigation(navigation);
         }
     }
 
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java b/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java
index 2857aa3e..e457409 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java
@@ -61,10 +61,10 @@
     public static void register() {
         // In some tests we will get called multiple times.
         if (sRegistered) return;
+        sRegistered = true;
 
         PostTask.registerTaskExecutor(
-                UiThreadTaskTraitsImpl.EXTENSION_ID, new BrowserTaskExecutor());
-        sRegistered = true;
+                UiThreadTaskTraitsImpl.DESCRIPTOR.getId(), new BrowserTaskExecutor());
     }
 
     private final WeakHashMap<TaskTraits, SingleThreadTaskRunner> mTaskRunners =
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
new file mode 100644
index 0000000..6769ebc
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
@@ -0,0 +1,204 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content_public.browser;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * JNI bridge with content::NavigationHandle
+ */
+@JNINamespace("content")
+public class NavigationHandle {
+    private long mNativeNavigationHandleProxy;
+    private final boolean mIsInMainFrame;
+    private final boolean mIsRendererInitiated;
+    private final boolean mIsSameDocument;
+    private Integer mPageTransition;
+    private String mUrl;
+    private boolean mHasCommitted;
+    private boolean mIsDownload;
+    private boolean mIsErrorPage;
+    private boolean mIsFragmentNavigation;
+    private int mErrorCode;
+    private int mHttpStatusCode;
+
+    @CalledByNative
+    public NavigationHandle(long nativeNavigationHandleProxy, String url, boolean isInMainFrame,
+            boolean isSameDocument, boolean isRendererInitiated) {
+        mNativeNavigationHandleProxy = nativeNavigationHandleProxy;
+        mUrl = url;
+        mIsInMainFrame = isInMainFrame;
+        mIsSameDocument = isSameDocument;
+        mIsRendererInitiated = isRendererInitiated;
+    }
+
+    /**
+     * The navigation received a redirect. Called once per redirect.
+     * @param url The new URL.
+     */
+    @CalledByNative
+    private void didRedirect(String url) {
+        mUrl = url;
+    }
+
+    /**
+     * The navigation finished. Called once per navigation.
+     */
+    @CalledByNative
+    public void didFinish(String url, boolean isErrorPage, boolean hasCommitted,
+            boolean isFragmentNavigation, boolean isDownload, int transition, int errorCode,
+            int httpStatuscode) {
+        mUrl = url;
+        mIsErrorPage = isErrorPage;
+        mHasCommitted = hasCommitted;
+        mIsFragmentNavigation = isFragmentNavigation;
+        mIsDownload = isDownload;
+        mPageTransition = transition == -1 ? null : transition;
+        mErrorCode = errorCode;
+        mHttpStatusCode = httpStatuscode;
+    }
+
+    /**
+     * Release the C++ pointer.
+     */
+    @CalledByNative
+    private void release() {
+        mNativeNavigationHandleProxy = 0;
+    }
+
+    public long nativePtr() {
+        return mNativeNavigationHandleProxy;
+    }
+
+    /**
+     * The URL the frame is navigating to.  This may change during the navigation when encountering
+     * a server redirect.
+     */
+    public String getUrl() {
+        return mUrl;
+    }
+
+    /**
+     * Whether the navigation is taking place in the main frame or in a subframe.
+     */
+    public boolean isInMainFrame() {
+        return mIsInMainFrame;
+    }
+
+    /**
+     * Whether the navigation was initiated by the renderer process. Examples of renderer-initiated
+     * navigations include:
+     *  - <a> link click
+     *  - changing window.location.href
+     *  - redirect via the <meta http-equiv="refresh"> tag
+     *  - using window.history.pushState
+     *
+     * This method returns false for browser-initiated navigations, including:
+     *  - any navigation initiated from the omnibox
+     *  - navigations via suggestions in browser UI
+     *  - navigations via browser UI: Ctrl-R, refresh/forward/back/home buttons
+     *  - using window.history.forward() or window.history.back()
+     *  - any other "explicit" URL navigations, e.g. bookmarks
+     */
+    public boolean isRendererInitiated() {
+        return mIsRendererInitiated;
+    }
+
+    /**
+     * Whether the navigation happened without changing document.
+     * Examples of same document navigations are:
+     * - reference fragment navigations
+     * - pushState/replaceState
+     * - same page history navigation
+     */
+    public boolean isSameDocument() {
+        return mIsSameDocument;
+    }
+
+    public String errorDescription() {
+        // TODO(shaktisahu): Provide appropriate error description (crbug/690784).
+        return "";
+    }
+
+    public int errorCode() {
+        return mErrorCode;
+    }
+
+    /**
+     * Whether the navigation has committed. Navigations that end up being downloads or return
+     * 204/205 response codes do not commit (i.e. the WebContents stays at the existing URL). This
+     * returns true for either successful commits or error pages that replace the previous page
+     * (distinguished by |IsErrorPage|), and false for errors that leave the user on the previous
+     * page.
+     */
+    public boolean hasCommitted() {
+        return mHasCommitted;
+    }
+
+    /**
+     * Return the HTTP status code. This can be used after the response is received in
+     * didFinishNavigation()
+     */
+    public int httpStatusCode() {
+        return mHttpStatusCode;
+    }
+
+    /**
+     * Returns the page transition type.
+     */
+    public Integer pageTransition() {
+        return mPageTransition;
+    }
+
+    /**
+     * Returns true on same-document navigation with fragment change.
+     */
+    public boolean isFragmentNavigation() {
+        return mIsFragmentNavigation;
+    }
+
+    /**
+     * Whether the navigation resulted in an error page.
+     * Note that if an error page reloads, this will return true even though GetNetErrorCode will be
+     * net::OK.
+     */
+    public boolean isErrorPage() {
+        return mIsErrorPage;
+    }
+
+    /**
+     * Returns true if this navigation resulted in a download. Returns false if this navigation did
+     * not result in a download, or if download status is not yet known for this navigation.
+     * Download status is determined for a navigation when processing final (post redirect) HTTP
+     * response headers.
+     */
+    public boolean isDownload() {
+        return mIsDownload;
+    }
+
+    /**
+     * Set request's header. If the header is already present, its value is overwritten. When
+     * modified during a navigation start, the headers will be applied to the initial network
+     * request. When modified during a redirect, the headers will be applied to the redirected
+     * request.
+     */
+    public void setRequestHeader(String headerName, String headerValue) {
+        nativeSetRequestHeader(mNativeNavigationHandleProxy, headerName, headerValue);
+    }
+
+    /**
+     * Remove a request's header. If the header is not present, it has no effect. Must be called
+     * during a redirect.
+     */
+    public void removeRequestHeader(String headerName) {
+        nativeRemoveRequestHeader(mNativeNavigationHandleProxy, headerName);
+    }
+
+    private static native void nativeSetRequestHeader(
+            long nativeNavigationHandleProxy, String headerName, String headerValue);
+    private static native void nativeRemoveRequestHeader(
+            long nativeNavigationHandleProxy, String headerName);
+}
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandleProxy.java b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandleProxy.java
deleted file mode 100644
index 3b9be3c3..0000000
--- a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandleProxy.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content_public.browser;
-
-import org.chromium.base.annotations.JNINamespace;
-
-/**
- * JNI bridge with content::NavigationHandleProxy.
- */
-@JNINamespace("content")
-public class NavigationHandleProxy {
-    public static native void nativeSetRequestHeader(
-            long nativeNavigationHandleProxy, String headerName, String headerValue);
-    public static native void nativeRemoveRequestHeader(
-            long nativeNavigationHandleProxy, String headerName);
-}
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
index 649a54d..21e6d1e 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
@@ -5,7 +5,6 @@
 package org.chromium.content_public.browser;
 
 import android.support.annotation.IntDef;
-import android.support.annotation.Nullable;
 
 import org.chromium.blink.mojom.ViewportFit;
 
@@ -38,54 +37,31 @@
 
     /**
      * Called when the browser process starts a navigation.
-     * @param url The validated URL for the loading page.
-     * @param isInMainFrame Whether the navigation is for the main frame.
-     * @param isSameDocument Whether the main frame navigation did not cause changes to the
-     *                   document (for example scrolling to a named anchor or PopState).
-     * @param navigationHandleProxy Pointer to a NavigationHandleProxy representing the navigation.
-     *                              Its lifetime is bound to this function. Do not store it. It can
-     *                              be used to modify headers.
+     * @param navigationHandle
+     *        NavigationHandle are provided to several WebContentsObserver methods to allow
+     *        observers to track specific navigations. Observers should clear any references to a
+     *        NavigationHandle at didFinishNavigation();
      */
-    public void didStartNavigation(String url, boolean isInMainFrame, boolean isSameDocument,
-            long navigationHandleProxy) {}
+    public void didStartNavigation(NavigationHandle navigationHandle) {}
 
     /**
      * Called when the browser process redirect a navigation.
-     * @param url The validated URL for the loading page.
-     * @param isInMainFrame Whether the navigation is for the main frame.
-     * @param navigationHandleProxy Pointer to a NavigationHandleProxy representing the navigation.
-     *                              Its lifetime is bound to this function. Do not store it. It can
-     *                              be used to modify headers.
+     * @param navigationHandle
+     *        NavigationHandle are provided to several WebContentsObserver methods to allow
+     *        observers to track specific navigations. Observers should clear any references to a
+     *        NavigationHandle at didFinishNavigation();
      */
-    public void didRedirectNavigation(
-            String url, boolean isInMainFrame, long navigationHandleProxy) {}
+    public void didRedirectNavigation(NavigationHandle navigationHandle) {}
 
     /**
      * Called when the current navigation is finished. This happens when a navigation is committed,
      * aborted or replaced by a new one.
-     * @param url The validated URL for the loading page.
-     * @param isInMainFrame Whether the navigation is for the main frame.
-     * @param isErrorPage Whether the navigation shows an error page.
-     * @param hasCommitted Whether the navigation has committed. This returns true for either
-     *                     successful commits or error pages that replace the previous page
-     *                     (distinguished by |isErrorPage|), and false for errors that leave the
-     *                     user on the previous page. When false, |isSameDocument|,
-     *                     |isFragmentNavigation|, |pageTransition| and |httpStatusCode| will have
-     *                     default values.
-     * @param isSameDocument Whether the main frame navigation did not cause changes to the
-     *                   document (for example scrolling to a named anchor or PopState).
-     * @param isFragmentNavigation Whether the navigation was to a different fragment.
-     * @param isRendererInitiated Whether initiated by renderer. Eg clicking on a link.
-     * @param isDownload See NavigationHandle::IsDownload.
-     * @param pageTransition The page transition type associated with this navigation.
-     * @param errorCode The net error code if an error occurred prior to commit, otherwise net::OK.
-     * @param errorDescription The description for the net error code.
-     * @param httpStatusCode The HTTP status code of the navigation.
+     * @param navigationHandle
+     *        NavigationHandle are provided to several WebContentsObserver methods to allow
+     *        observers to track specific navigations. Observers should clear any references to a
+     *        NavigationHandle at the end of this function.
      */
-    public void didFinishNavigation(String url, boolean isInMainFrame, boolean isErrorPage,
-            boolean hasCommitted, boolean isSameDocument, boolean isFragmentNavigation,
-            boolean isRendererInitiated, boolean isDownload, @Nullable Integer pageTransition,
-            int errorCode, String errorDescription, int httpStatusCode) {}
+    public void didFinishNavigation(NavigationHandle navigationHandle) {}
 
     /**
      * Called when the a page starts loading.
diff --git a/content/public/android/junit/src/org/chromium/content/browser/UiThreadTaskTraitsImplTest.java b/content/public/android/junit/src/org/chromium/content/browser/UiThreadTaskTraitsImplTest.java
new file mode 100644
index 0000000..1709097
--- /dev/null
+++ b/content/public/android/junit/src/org/chromium/content/browser/UiThreadTaskTraitsImplTest.java
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.task.TaskTraits;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.content_public.browser.BrowserTaskType;
+
+/**
+ * Tests for {@link UiThreadTaskTraitsImpl}
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class UiThreadTaskTraitsImplTest {
+    @Test
+    @SmallTest
+    public void testContainsExtension() {
+        TaskTraits traits = UiThreadTaskTraitsImpl.BOOTSTRAP;
+        UiThreadTaskTraitsImpl impl = traits.getExtension(UiThreadTaskTraitsImpl.DESCRIPTOR);
+
+        assertNotNull(impl);
+    }
+
+    @Test
+    @SmallTest
+    public void testCanDeserializeProperties() {
+        TaskTraits traits = UiThreadTaskTraitsImpl.BOOTSTRAP;
+        UiThreadTaskTraitsImpl impl = traits.getExtension(UiThreadTaskTraitsImpl.DESCRIPTOR);
+
+        assertEquals(BrowserTaskType.BOOTSTRAP, impl.getTaskType());
+    }
+}
diff --git a/content/public/browser/tracing_controller.h b/content/public/browser/tracing_controller.h
index 51151438..c2e3e98d 100644
--- a/content/public/browser/tracing_controller.h
+++ b/content/public/browser/tracing_controller.h
@@ -75,9 +75,8 @@
   // Tracing begins immediately locally, and asynchronously on child processes
   // as soon as they receive the StartTracing request.
   //
-  // Once tracing is enabled in the current process and trace events can be
-  // emitted (unless excluded from the config), StartTracingDoneCallback will
-  // be called back.
+  // Once all child processes have acked to the StartTracing request,
+  // StartTracingDoneCallback will be called back.
   //
   // |category_filter| is a filter to control what category groups should be
   // traced. A filter can have an optional '-' prefix to exclude category groups
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 4f716c14..47714c8 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -658,6 +658,8 @@
     "//services/device/public/cpp/generic_sensor",
     "//services/device/public/mojom",
     "//services/device/public/mojom:constants",
+    "//services/image_annotation/public/cpp:cpp",
+    "//services/image_annotation/public/mojom:mojom",
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/network:network_service",
     "//services/service_manager/public/cpp",
diff --git a/content/renderer/accessibility/ax_image_annotator.cc b/content/renderer/accessibility/ax_image_annotator.cc
index 8de3080..e8805b0 100644
--- a/content/renderer/accessibility/ax_image_annotator.cc
+++ b/content/renderer/accessibility/ax_image_annotator.cc
@@ -4,19 +4,30 @@
 
 #include "content/renderer/accessibility/ax_image_annotator.h"
 
+#include "base/base64.h"
 #include "base/stl_util.h"
+#include "content/renderer/render_frame_impl.h"
+#include "crypto/sha2.h"
 #include "third_party/blink/public/web/web_ax_object.h"
 #include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_node.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
 
 namespace content {
 
 AXImageAnnotator::AXImageAnnotator(
     RenderAccessibilityImpl* const render_accessibility)
-    : render_accessibility_(render_accessibility) {
+    : render_accessibility_(render_accessibility), weak_factory_(this) {
   DCHECK(render_accessibility_);
+  render_accessibility_->render_frame()->GetRemoteInterfaces()->GetInterface(
+      mojo::MakeRequest(&annotator_ptr_));
 }
 
-AXImageAnnotator::~AXImageAnnotator() {
+AXImageAnnotator::~AXImageAnnotator() {}
+
+void AXImageAnnotator::Destroy() {
   MarkAllImagesDirty();
 }
 
@@ -25,24 +36,57 @@
   DCHECK(!image.IsDetached());
   const auto lookup = image_annotations_.find(image.AxID());
   if (lookup != image_annotations_.end())
-    return lookup->second;
-  return {};
+    return lookup->second.annotation();
+  return std::string();
+}
+
+bool AXImageAnnotator::HasAnnotationInCache(blink::WebAXObject& image) const {
+  DCHECK(!image.IsDetached());
+  if (!HasImageInCache(image))
+    return false;
+  return image_annotations_.at(image.AxID()).HasAnnotation();
+}
+
+bool AXImageAnnotator::HasImageInCache(const blink::WebAXObject& image) const {
+  DCHECK(!image.IsDetached());
+  return base::ContainsKey(image_annotations_, image.AxID());
 }
 
 void AXImageAnnotator::OnImageAdded(blink::WebAXObject& image) {
   DCHECK(!image.IsDetached());
   DCHECK(!base::ContainsKey(image_annotations_, image.AxID()));
+  std::string document_url =
+      render_accessibility_->GetMainDocument().Url().GetString().Utf8();
+  std::string image_src = image.Url().GetString().Utf8();
+  std::string image_id = GenerateImageSourceId(document_url, image_src);
+  if (image_id.empty())
+    return;
+
+  image_annotations_.emplace(image.AxID(), image);
+  ImageInfo& image_info = image_annotations_.at(image.AxID());
   // Fetch image annotation.
-  image_annotations_.emplace(image.AxID(), std::string());
-  render_accessibility_->MarkWebAXObjectDirty(image, false /* subtree */);
+  annotator_ptr_->AnnotateImage(
+      image_id, image_info.GetImageProcessor(),
+      base::BindOnce(&AXImageAnnotator::OnImageAnnotated,
+                     weak_factory_.GetWeakPtr(), image));
 }
 
 void AXImageAnnotator::OnImageUpdated(blink::WebAXObject& image) {
   DCHECK(!image.IsDetached());
   DCHECK(base::ContainsKey(image_annotations_, image.AxID()));
+  std::string document_url =
+      render_accessibility_->GetMainDocument().Url().GetString().Utf8();
+  std::string image_src = image.Url().GetString().Utf8();
+  std::string image_id = GenerateImageSourceId(document_url, image_src);
+  if (image_id.empty())
+    return;
+
+  ImageInfo& image_info = image_annotations_.at(image.AxID());
   // Update annotation.
-  image_annotations_[image.AxID()] = std::string();
-  render_accessibility_->MarkWebAXObjectDirty(image, false /* subtree */);
+  annotator_ptr_->AnnotateImage(
+      image_id, image_info.GetImageProcessor(),
+      base::BindOnce(&AXImageAnnotator::OnImageAnnotated,
+                     weak_factory_.GetWeakPtr(), image));
 }
 
 void AXImageAnnotator::OnImageRemoved(blink::WebAXObject& image) {
@@ -53,7 +97,6 @@
     return;
   }
   image_annotations_.erase(lookup);
-  render_accessibility_->MarkWebAXObjectDirty(image, false /* subtree */);
 }
 
 void AXImageAnnotator::MarkAllImagesDirty() {
@@ -66,4 +109,78 @@
   image_annotations_.clear();
 }
 
+AXImageAnnotator::ImageInfo::ImageInfo(const blink::WebAXObject& image)
+    : image_processor_(
+          base::BindRepeating(&AXImageAnnotator::GetImageData, image)),
+      annotation_(base::nullopt) {}
+
+AXImageAnnotator::ImageInfo::~ImageInfo() = default;
+
+image_annotation::mojom::ImageProcessorPtr
+AXImageAnnotator::ImageInfo::GetImageProcessor() {
+  return image_processor_.GetPtr();
+}
+
+bool AXImageAnnotator::ImageInfo::HasAnnotation() const {
+  return annotation_.has_value();
+}
+
+// static
+std::string AXImageAnnotator::GenerateImageSourceId(
+    const std::string& document_url,
+    const std::string& image_src) {
+  if (document_url.empty() || image_src.empty())
+    return std::string();
+
+  // The |image_src| might be a URL that is relative to the document's URL.
+  const GURL image_url = GURL(document_url).Resolve(image_src);
+  if (!image_url.is_valid())
+    return std::string();
+
+  // If |image_url| appears to be publicly reachable, return the URL as the
+  // image source ID.
+  if (image_url.SchemeIsHTTPOrHTTPS())
+    return image_url.spec();
+
+  // If |image_url| is not publicly reachable, return a hash of |image_url|.
+  // Scheme could be "data", "javascript", "ftp", "file", etc.
+  const std::string& content = image_url.GetContent();
+  if (content.empty())
+    return std::string();
+  std::string source_id;
+  base::Base64Encode(crypto::SHA256HashString(content), &source_id);
+  return source_id;
+}
+
+// static
+SkBitmap AXImageAnnotator::GetImageData(const blink::WebAXObject& image) {
+  if (image.IsDetached())
+    return SkBitmap();
+  blink::WebNode node = image.GetNode();
+  if (node.IsNull() || !node.IsElementNode())
+    return SkBitmap();
+  blink::WebElement element = node.To<blink::WebElement>();
+  return element.ImageContents();
+}
+
+void AXImageAnnotator::OnImageAnnotated(
+    const blink::WebAXObject& image,
+    image_annotation::mojom::AnnotateImageResultPtr result) {
+  if (image.IsDetached())
+    return;
+  if (!base::ContainsKey(image_annotations_, image.AxID()))
+    return;
+  // TODO(nektar): Set the image annotation status on this image to Error.
+  if (result->is_error_code())
+    return;
+  if (!result->is_ocr_text()) {
+    DLOG(WARNING) << "Unrecognized image annotation result.";
+    return;
+  }
+  // TODO(nektar): Add message to explain that the annotation is a best guess,
+  // e.g. "appears to be".
+  image_annotations_.at(image.AxID()).set_annotation(result->get_ocr_text());
+  render_accessibility_->MarkWebAXObjectDirty(image, false /* subtree */);
+}
+
 }  // namespace content
diff --git a/content/renderer/accessibility/ax_image_annotator.h b/content/renderer/accessibility/ax_image_annotator.h
index c3c6c09..5b640e6c 100644
--- a/content/renderer/accessibility/ax_image_annotator.h
+++ b/content/renderer/accessibility/ax_image_annotator.h
@@ -8,9 +8,15 @@
 #include <string>
 #include <unordered_map>
 
+#include "base/bind.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list_types.h"
+#include "base/optional.h"
 #include "content/renderer/accessibility/render_accessibility_impl.h"
+#include "services/image_annotation/public/cpp/image_processor.h"
+#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace blink {
 
@@ -29,23 +35,67 @@
   AXImageAnnotator(RenderAccessibilityImpl* const render_accessibility);
   ~AXImageAnnotator() override;
 
+  void Destroy();
+
   std::string GetImageAnnotation(blink::WebAXObject& image) const;
+  bool HasAnnotationInCache(blink::WebAXObject& image) const;
+  bool HasImageInCache(const blink::WebAXObject& image) const;
 
   void OnImageAdded(blink::WebAXObject& image);
   void OnImageUpdated(blink::WebAXObject& image);
   void OnImageRemoved(blink::WebAXObject& image);
 
  private:
+  // Keeps track of the image data and the automatic annotation for each image.
+  class ImageInfo final {
+   public:
+    ImageInfo(const blink::WebAXObject& image);
+    virtual ~ImageInfo();
+
+    image_annotation::mojom::ImageProcessorPtr GetImageProcessor();
+    bool HasAnnotation() const;
+
+    std::string annotation() const {
+      DCHECK(annotation_.has_value());
+      return annotation_.value_or("");
+    }
+
+    void set_annotation(std::string annotation) { annotation_ = annotation; }
+
+   private:
+    image_annotation::ImageProcessor image_processor_;
+    base::Optional<std::string> annotation_;
+  };
+
+  // Given the URL of the main document and the src attribute of an image,
+  // generates a unique identifier for the image that could be provided to the
+  // image annotation service.
+  static std::string GenerateImageSourceId(const std::string& document_url,
+                                           const std::string& image_src);
+
+  // Retrieves the image data from the renderer.
+  static SkBitmap GetImageData(const blink::WebAXObject& image);
+
   // Removes the automatic image annotations from all images.
   void MarkAllImagesDirty();
 
+  // Gets called when an image gets annotated by the image annotation service.
+  void OnImageAnnotated(const blink::WebAXObject& image,
+                        image_annotation::mojom::AnnotateImageResultPtr result);
+
   // Weak, owns us.
   RenderAccessibilityImpl* const render_accessibility_;
 
-  // Keeps track of all the automatic annotations for each image.
+  // A pointer to the automatic image annotation service.
+  image_annotation::mojom::AnnotatorPtr annotator_ptr_;
+
+  // Keeps track of the image data and the automatic annotations for each image.
   //
   // The key is retrieved using WebAXObject::AxID().
-  std::unordered_map<int, std::string> image_annotations_;
+  std::unordered_map<int, ImageInfo> image_annotations_;
+
+  // This member needs to be last because it should destructed first.
+  base::WeakPtrFactory<AXImageAnnotator> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(AXImageAnnotator);
 };
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index c1f5e20..372534c 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -883,8 +883,12 @@
   if (accessibility_mode_.has_mode(ui::AXMode::kLabelImages) &&
       dst->role == ax::mojom::Role::kImage) {
     DCHECK(image_annotator_);
-    dst->AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
-                            image_annotator_->GetImageAnnotation(src));
+    if (image_annotator_->HasAnnotationInCache(src)) {
+      dst->AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                              image_annotator_->GetImageAnnotation(src));
+    } else if (!image_annotator_->HasImageInCache(src)) {
+      image_annotator_->OnImageAdded(src);
+    }
   }
 
   // The majority of the rest of this code computes attributes needed for
diff --git a/content/renderer/accessibility/blink_ax_tree_source.h b/content/renderer/accessibility/blink_ax_tree_source.h
index 78b2220..7f9c6788 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.h
+++ b/content/renderer/accessibility/blink_ax_tree_source.h
@@ -75,7 +75,7 @@
 
   // The following methods add or remove an image annotator which is used to
   // provide automatic labels for images.
-  void AddImageAnnotator(const AXImageAnnotator* const annotator) {
+  void AddImageAnnotator(AXImageAnnotator* const annotator) {
     image_annotator_ = annotator;
   }
   void RemoveImageAnnotator() { image_annotator_ = nullptr; }
@@ -144,7 +144,7 @@
 
   gfx::Size max_image_data_size_;
 
-  const AXImageAnnotator* image_annotator_ = nullptr;
+  AXImageAnnotator* image_annotator_ = nullptr;
 
   // These are updated when calling |Freeze|.
   bool frozen_ = false;
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index f3b4ee9b..716d7637 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -131,14 +131,7 @@
   const WebDocument& document = GetMainDocument();
   if (!document.IsNull()) {
     ax_context_ = std::make_unique<blink::WebAXContext>(document);
-
-    if (mode.has_mode(ui::AXMode::kLabelImages)) {
-      ax_image_annotator_ = std::make_unique<AXImageAnnotator>(this);
-      tree_source_.AddImageAnnotator(ax_image_annotator_.get());
-    } else {
-      tree_source_.RemoveImageAnnotator();
-      ax_image_annotator_.release();
-    }
+    StartOrStopLabelingImages(ui::AXMode(), mode);
 
     // It's possible that the webview has already loaded a webpage without
     // accessibility being enabled. Initialize the browser's cached
@@ -156,8 +149,9 @@
 }
 
 void RenderAccessibilityImpl::AccessibilityModeChanged() {
+  ui::AXMode old_mode = tree_source_.accessibility_mode();
   ui::AXMode new_mode = render_frame_->accessibility_mode();
-  if (tree_source_.accessibility_mode() == new_mode)
+  if (old_mode == new_mode)
     return;
   tree_source_.SetAccessibilityMode(new_mode);
 
@@ -184,13 +178,7 @@
   serializer_.Reset();
   const WebDocument& document = GetMainDocument();
   if (!document.IsNull()) {
-    if (new_mode.has_mode(ui::AXMode::kLabelImages)) {
-      ax_image_annotator_ = std::make_unique<AXImageAnnotator>(this);
-      tree_source_.AddImageAnnotator(ax_image_annotator_.get());
-    } else {
-      tree_source_.RemoveImageAnnotator();
-      ax_image_annotator_.release();
-    }
+    StartOrStopLabelingImages(old_mode, new_mode);
 
     // If there are any events in flight, |HandleAXEvent| will refuse to process
     // our new event.
@@ -842,6 +830,20 @@
     update->has_tree_data = true;
 }
 
+void RenderAccessibilityImpl::StartOrStopLabelingImages(ui::AXMode old_mode,
+                                                        ui::AXMode new_mode) {
+  if (!old_mode.has_mode(ui::AXMode::kLabelImages) &&
+      new_mode.has_mode(ui::AXMode::kLabelImages)) {
+    ax_image_annotator_ = std::make_unique<AXImageAnnotator>(this);
+    tree_source_.AddImageAnnotator(ax_image_annotator_.get());
+  } else if (old_mode.has_mode(ui::AXMode::kLabelImages) &&
+             !new_mode.has_mode(ui::AXMode::kLabelImages)) {
+    tree_source_.RemoveImageAnnotator();
+    ax_image_annotator_->Destroy();
+    ax_image_annotator_.release();
+  }
+}
+
 void RenderAccessibilityImpl::Scroll(const WebAXObject& target,
                                      ax::mojom::Action scroll_action) {
   WebAXObject offset_container;
diff --git a/content/renderer/accessibility/render_accessibility_impl.h b/content/renderer/accessibility/render_accessibility_impl.h
index 4f5a3151..49dd68b 100644
--- a/content/renderer/accessibility/render_accessibility_impl.h
+++ b/content/renderer/accessibility/render_accessibility_impl.h
@@ -67,9 +67,11 @@
                                         AXContentTreeUpdate* response,
                                         ui::AXMode ax_mode);
 
-  RenderAccessibilityImpl(RenderFrameImpl* render_frame, ui::AXMode mode);
+  RenderAccessibilityImpl(RenderFrameImpl* const render_frame, ui::AXMode mode);
   ~RenderAccessibilityImpl() override;
 
+  RenderFrameImpl* render_frame() { return render_frame_; }
+
   // RenderAccessibility implementation.
   int GenerateAXID() override;
   void SetPluginTreeSource(PluginAXTreeSource* source) override;
@@ -134,6 +136,12 @@
   void OnLoadInlineTextBoxes(const blink::WebAXObject& obj);
   void OnGetImageData(const blink::WebAXObject& obj, const gfx::Size& max_size);
   void AddPluginTreeToUpdate(AXContentTreeUpdate* update);
+
+  // Automatically labels images for accessibility if the accessibility mode for
+  // this feature is turned on, otherwise stops automatic labeling and removes
+  // any automatic annotations that might have been added before.
+  void StartOrStopLabelingImages(ui::AXMode old_mode, ui::AXMode new_mode);
+
   void Scroll(const blink::WebAXObject& target,
               ax::mojom::Action scroll_action);
   void ScrollPlugin(int id_to_make_visible);
@@ -142,7 +150,7 @@
   void RecordImageMetrics(AXContentTreeUpdate* update);
 
   // The RenderFrameImpl that owns us.
-  RenderFrameImpl* render_frame_;
+  RenderFrameImpl* const render_frame_;
 
   // This keeps accessibility enabled as long as it lives.
   std::unique_ptr<blink::WebAXContext> ax_context_;
diff --git a/content/renderer/compositor/layer_tree_view.cc b/content/renderer/compositor/layer_tree_view.cc
index 811c170..13288f8 100644
--- a/content/renderer/compositor/layer_tree_view.cc
+++ b/content/renderer/compositor/layer_tree_view.cc
@@ -571,9 +571,7 @@
   delegate_->WillBeginCompositorFrame();
 }
 
-void LayerTreeView::DidBeginMainFrame() {
-  delegate_->DidBeginMainFrame();
-}
+void LayerTreeView::DidBeginMainFrame() {}
 
 void LayerTreeView::DidUpdateLayers() {
   // Dump property trees and layers if run with:
diff --git a/content/renderer/compositor/layer_tree_view_delegate.h b/content/renderer/compositor/layer_tree_view_delegate.h
index c219245..a5d2d82 100644
--- a/content/renderer/compositor/layer_tree_view_delegate.h
+++ b/content/renderer/compositor/layer_tree_view_delegate.h
@@ -56,10 +56,6 @@
   // Notifies that the compositor has issued a BeginMainFrame.
   virtual void BeginMainFrame(base::TimeTicks frame_time) = 0;
 
-  // Notifies that the layer tree host has completed a call to
-  // RequestMainFrameUpdate in response to a BeginMainFrame.
-  virtual void DidBeginMainFrame() = 0;
-
   // Requests a LayerTreeFrameSink to submit CompositorFrames to.
   virtual void RequestNewLayerTreeFrameSink(
       LayerTreeFrameSinkCallback callback) = 0;
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index c870350..84dbb79 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -1102,12 +1102,6 @@
   GetWebWidget()->BeginFrame(frame_time, record_main_frame_metrics);
 }
 
-void RenderWidget::DidBeginMainFrame() {
-  if (!GetWebWidget())
-    return;
-  GetWebWidget()->DidBeginFrame();
-}
-
 void RenderWidget::RequestNewLayerTreeFrameSink(
     LayerTreeFrameSinkCallback callback) {
   // For widgets that are never visible, we don't start the compositor, so we
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index bd4a3b94..6205ae9 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -333,7 +333,6 @@
   void SendScrollEndEventFromImplSide(
       cc::ElementId scroll_latched_element_id) override;
   void BeginMainFrame(base::TimeTicks frame_time) override;
-  void DidBeginMainFrame() override;
   void RequestNewLayerTreeFrameSink(
       LayerTreeFrameSinkCallback callback) override;
   void DidCommitAndDrawCompositorFrame() override;
diff --git a/content/renderer/v8_value_converter_impl_unittest.cc b/content/renderer/v8_value_converter_impl_unittest.cc
index e60f671..2d3d67c 100644
--- a/content/renderer/v8_value_converter_impl_unittest.cc
+++ b/content/renderer/v8_value_converter_impl_unittest.cc
@@ -249,7 +249,7 @@
 };
 
 TEST_F(V8ValueConverterImplTest, BasicRoundTrip) {
-  std::unique_ptr<base::Value> original_root = base::test::ParseJson(
+  std::unique_ptr<base::Value> original_root = base::test::ParseJsonDeprecated(
       "{ \n"
       "  \"null\": null, \n"
       "  \"true\": true, \n"
@@ -389,7 +389,7 @@
 
 TEST_F(V8ValueConverterImplTest, KeysWithDots) {
   std::unique_ptr<base::Value> original =
-      base::test::ParseJson("{ \"foo.bar\": \"baz\" }");
+      base::test::ParseJsonDeprecated("{ \"foo.bar\": \"baz\" }");
 
   v8::HandleScope handle_scope(isolate_);
   v8::Local<v8::Context> context =
@@ -480,7 +480,7 @@
   // because the setters/getters are defined on the array instance, not
   // on the Array's prototype.
   converted.reset(static_cast<base::ListValue*>(
-      base::test::ParseJson("[ \"foo\", \"bar\" ]").release()));
+      base::test::ParseJsonDeprecated("[ \"foo\", \"bar\" ]").release()));
   v8::Local<v8::Array> copy =
       converter.ToV8Value(converted.get(), context).As<v8::Array>();
   ASSERT_FALSE(copy.IsEmpty());
@@ -547,7 +547,7 @@
 
 TEST_F(V8ValueConverterImplTest, ObjectPrototypeSetter) {
   std::unique_ptr<base::Value> original =
-      base::test::ParseJson("{ \"foo\": \"good value\" }");
+      base::test::ParseJsonDeprecated("{ \"foo\": \"good value\" }");
 
   v8::HandleScope handle_scope(isolate_);
   v8::Local<v8::Context> context =
@@ -615,7 +615,7 @@
 
 TEST_F(V8ValueConverterImplTest, ArrayPrototypeSetter) {
   std::unique_ptr<base::Value> original =
-      base::test::ParseJson("[100, 200, 300]");
+      base::test::ParseJsonDeprecated("[100, 200, 300]");
 
   v8::HandleScope handle_scope(isolate_);
   v8::Local<v8::Context> context =
@@ -776,7 +776,7 @@
   V8ValueConverterImpl converter;
   std::unique_ptr<base::Value> actual(converter.FromV8Value(object, context));
 
-  std::unique_ptr<base::Value> expected = base::test::ParseJson(
+  std::unique_ptr<base::Value> expected = base::test::ParseJsonDeprecated(
       "{ \n"
       "  \"1\": \"foo\", \n"
       "  \"2\": \"bar\", \n"
@@ -848,16 +848,18 @@
 
   std::unique_ptr<base::Value> actual_object(
       converter.FromV8Value(object, context));
-  EXPECT_EQ(*base::test::ParseJson("{ \"bar\": null }"), *actual_object);
+  EXPECT_EQ(*base::test::ParseJsonDeprecated("{ \"bar\": null }"),
+            *actual_object);
 
   // Everything is null because JSON stringification preserves array length.
   std::unique_ptr<base::Value> actual_array(
       converter.FromV8Value(array, context));
-  EXPECT_EQ(*base::test::ParseJson("[ null, null, null ]"), *actual_array);
+  EXPECT_EQ(*base::test::ParseJsonDeprecated("[ null, null, null ]"),
+            *actual_array);
 
   std::unique_ptr<base::Value> actual_sparse_array(
       converter.FromV8Value(sparse_array, context));
-  EXPECT_EQ(*base::test::ParseJson("[ null, null, null ]"),
+  EXPECT_EQ(*base::test::ParseJsonDeprecated("[ null, null, null ]"),
             *actual_sparse_array);
 }
 
@@ -885,7 +887,7 @@
 
   // The expected base::Value result.
   std::unique_ptr<base::Value> expected =
-      base::test::ParseJson("[{},{},[],[]]");
+      base::test::ParseJsonDeprecated("[{},{},[],[]]");
   ASSERT_TRUE(expected.get());
 
   // The actual result.
@@ -1212,7 +1214,7 @@
       converter.FromV8Value(object, context));
   ASSERT_TRUE(object_value);
   std::unique_ptr<base::Value> reference_object_value(
-      base::test::ParseJson("{}"));
+      base::test::ParseJsonDeprecated("{}"));
   EXPECT_EQ(*reference_object_value, *object_value);
 
   v8::Local<v8::Array> array(v8::Array::New(isolate_));
@@ -1220,7 +1222,7 @@
       converter.FromV8Value(array, context));
   ASSERT_TRUE(array_value);
   std::unique_ptr<base::Value> reference_array_value(
-      base::test::ParseJson("[]"));
+      base::test::ParseJsonDeprecated("[]"));
   EXPECT_EQ(*reference_array_value, *array_value);
 
   const char kExampleData[] = {1, 2, 3, 4, 5};
@@ -1249,7 +1251,7 @@
       converter.FromV8Value(number, context));
   ASSERT_TRUE(number_value);
   std::unique_ptr<base::Value> reference_number_value(
-      base::test::ParseJson("0"));
+      base::test::ParseJsonDeprecated("0"));
   EXPECT_EQ(*reference_number_value, *number_value);
 
   v8::Local<v8::Primitive> undefined(v8::Undefined(isolate_));
diff --git a/content/test/data/gpu/power_video_bear_1280x720_mp4.html b/content/test/data/gpu/power_video_bear_1280x720_mp4.html
new file mode 100644
index 0000000..fdaeb22
--- /dev/null
+++ b/content/test/data/gpu/power_video_bear_1280x720_mp4.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<title>MP4 Video Power Test</title>
+<script src="power_video_test.js"></script>
+</head>
+<body onload="main()">
+<button id="fullscreen" onclick="goFullscreen()">Fullscreen</button>
+<div id="container">
+<video id="video" width="1280" height="720" >
+<source src="/media/test/data/bear-1280x720.mp4" type="video/mp4">
+</video>
+</div>
+</body>
+</html>
diff --git a/content/test/data/gpu/power_video_test.js b/content/test/data/gpu/power_video_test.js
new file mode 100644
index 0000000..283f551
--- /dev/null
+++ b/content/test/data/gpu/power_video_test.js
@@ -0,0 +1,71 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var video = null;
+var box = null;
+
+function main() {
+  video = document.getElementById("video");
+  // This is necessary for us to insert a colored box on top of the video
+  // and position it to the corner correctly so video becomes underlay.
+  video.parentNode.style.position = "relative";
+  video.loop = true;
+  video.muted = true;
+  video.addEventListener('timeupdate', waitForVideoToPlay);
+  video.play();
+}
+
+function waitForVideoToPlay() {
+  if (video.currentTime > 0) {
+    video.removeEventListener('timeupdate', waitForVideoToPlay);
+    domAutomationController.send("SUCCESS");
+  }
+}
+
+function goFullscreen() {
+  if (video.requestFullscreen) {
+    video.requestFullscreen();
+  } else if (video.webkitRequestFullscreen) {
+    video.webkitRequestFullscreen();
+  }
+}
+
+function goUnderlay() {
+  if (!box) {
+    // Draw a solid color box in the top left corner of the video, so the
+    // video becomes an underlay.
+    box = document.createElement("div");
+    //redbox.style.border = "thick solid rgb(0,0,255)";
+    box.style.backgroundColor = "red";
+    box.style.width = "100px";
+    box.style.height = "50px";
+    box.style.position = "absolute";
+    box.style.zIndex = "1000";
+    var vid_rect = video.getBoundingClientRect();
+    var parent_rect = video.parentNode.getBoundingClientRect();
+    var top = vid_rect.top - parent_rect.top + 10;
+    var left = vid_rect.left - parent_rect.left + 10;
+    box.style.top = top.toString() + "px";
+    box.style.left = left.toString() + "px";
+    box.style.visibility = "visible";
+    video.parentNode.appendChild(box);
+  }
+  // Change box color between red/blue every second. This is to emulate UI
+  // or ads change (once per second) on top of a video.
+  setTimeout(setBoxColorToBlue, 1000);
+}
+
+function setBoxColorToBlue() {
+  if (!box)
+    return;
+  box.style.backgroundColor = "blue";
+  setTimeout(setBoxColorToRed, 1000);
+}
+
+function setBoxColorToRed() {
+  if (!box)
+    return;
+  box.style.backgroundColor = "red";
+  setTimeout(setBoxColorToBlue, 1000);
+}
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index 7c908f6..4f88735 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -136,8 +136,8 @@
         cls.tab = cls.browser.tabs[0]
         return
       except Exception:
-        logging.warning('Browser start failed (attempt %d of %d)',
-                        x, _START_BROWSER_RETRIES)
+        logging.exception('Browser start failed (attempt %d of %d). Backtrace:',
+                          x, _START_BROWSER_RETRIES)
         # If we are on the last try and there is an exception take a screenshot
         # to try and capture more about the browser failure and raise
         if x == _START_BROWSER_RETRIES:
diff --git a/content/test/gpu/gpu_tests/power_measurement_integration_test.py b/content/test/gpu/gpu_tests/power_measurement_integration_test.py
index 4a9b484b..1f7a82a 100644
--- a/content/test/gpu/gpu_tests/power_measurement_integration_test.py
+++ b/content/test/gpu/gpu_tests/power_measurement_integration_test.py
@@ -25,6 +25,7 @@
 
 from gpu_tests import gpu_integration_test
 from gpu_tests import ipg_utils
+from gpu_tests import path_util
 from gpu_tests.gpu_test_expectations import GpuTestExpectations
 
 import logging
@@ -42,6 +43,40 @@
 # Measures power in resolution of [x] milli-seconds.
 _POWER_MEASUREMENT_RESOLUTION = 100
 
+_GPU_RELATIVE_PATH = "content/test/data/gpu/"
+
+_DATA_PATHS = [os.path.join(
+                   path_util.GetChromiumSrcDir(), _GPU_RELATIVE_PATH),
+               os.path.join(
+                   path_util.GetChromiumSrcDir(), 'media', 'test', 'data')]
+
+_BASIC_TEST_HARNESS_SCRIPT = r"""
+  var domAutomationController = {};
+
+  domAutomationController._proceed = false;
+
+  domAutomationController._readyForActions = false;
+  domAutomationController._succeeded = false;
+  domAutomationController._finished = false;
+
+  domAutomationController.send = function(msg) {
+    domAutomationController._proceed = true;
+    let lmsg = msg.toLowerCase();
+    if (lmsg == "ready") {
+      domAutomationController._readyForActions = true;
+    } else {
+      domAutomationController._finished = true;
+      if (lmsg == "success") {
+        domAutomationController._succeeded = true;
+      } else {
+        domAutomationController._succeeded = false;
+      }
+    }
+  }
+
+  window.domAutomationController = domAutomationController;
+"""
+
 _FULLSCREEN_SCRIPT = r"""
   function locateElement(tag) {
     // return the element with largest width.
@@ -196,14 +231,26 @@
       yield ('Basic', '-',
              {'test_func': 'Basic',
               'bypass_ipg': options.bypass_ipg})
+      yield ('Video_720_MP4',
+             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html',
+             {'test_func': 'Video',
+              'bypass_ipg': options.bypass_ipg,
+              'underlay': False,
+              'fullscreen': False})
+      yield ('Video_720_MP4_Underlay',
+             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html',
+             {'test_func': 'Video',
+              'bypass_ipg': options.bypass_ipg,
+              'underlay': True,
+              'fullscreen': False})
 
   @classmethod
   def SetUpProcess(cls):
     super(cls, PowerMeasurementIntegrationTest).SetUpProcess()
-    cls.CustomizeBrowserArgs([
-      '--autoplay-policy=no-user-gesture-required'
-    ])
+    path_util.SetupTelemetryPaths()
+    cls.CustomizeBrowserArgs(cls._AddDefaultArgs([]))
     cls.StartBrowser()
+    cls.SetStaticServerDirs(_DATA_PATHS)
 
   def RunActualGpuTest(self, test_path, *args):
     test_params = args[0]
@@ -215,11 +262,13 @@
   def _CreateExpectations(cls):
     return PowerMeasurementExpectations()
 
-  #########################################
-  # Actual test functions
+  @staticmethod
+  def _AddDefaultArgs(browser_args):
+    # All tests receive the following options.
+    return ['--autoplay-policy=no-user-gesture-required'] + browser_args
 
-  def _RunTest_Basic(self, test_path, params):
-    bypass_ipg = params['bypass_ipg']
+  @staticmethod
+  def _MeasurePowerWithIPG(bypass_ipg):
     total_time = _POWER_MEASUREMENT_DURATION + _POWER_MEASUREMENT_DELAY
     if bypass_ipg:
       logging.info("Bypassing Intel Power Gadget")
@@ -232,6 +281,41 @@
     # chromeperf.appspot.com.
     logging.info("Results: %s", str(results))
 
+  #########################################
+  # Actual test functions
+
+  def _RunTest_Basic(self, test_path, params):
+    bypass_ipg = params['bypass_ipg']
+    PowerMeasurementIntegrationTest._MeasurePowerWithIPG(bypass_ipg)
+
+
+  def _RunTest_Video(self, test_path, params):
+    fullscreen = params['fullscreen']
+    underlay = params['underlay']
+    bypass_ipg = params['bypass_ipg']
+
+    disabled_features = [
+      'D3D11VideoDecoder',
+      'DirectCompositionUseNV12DecodeSwapChain',
+      'DirectCompositionUnderlays']
+    self.RestartBrowserWithArgs(
+      PowerMeasurementIntegrationTest._AddDefaultArgs([
+        '--disable-features=' + ','.join(disabled_features)]))
+
+    url = self.UrlOfStaticFilePath(test_path)
+    self.tab.Navigate(
+      url, script_to_evaluate_on_commit=_BASIC_TEST_HARNESS_SCRIPT)
+    self.tab.action_runner.WaitForJavaScriptCondition(
+      'domAutomationController._finished', timeout=30)
+    if fullscreen:
+      # TODO(zmo): Figure out why the following doesn't work
+      self.tab.action_runner.ClickElement(element_function=(
+        'document.getElementById("fullscreen")'))
+    if underlay:
+      self.tab.action_runner.ExecuteJavaScript('goUnderlay();')
+
+    PowerMeasurementIntegrationTest._MeasurePowerWithIPG(bypass_ipg)
+
 
   def _RunTest_URL(self, test_path, params):
     repeat = params['repeat']
diff --git a/content/test/stub_layer_tree_view_delegate.h b/content/test/stub_layer_tree_view_delegate.h
index eddef3c..a75b0ebe 100644
--- a/content/test/stub_layer_tree_view_delegate.h
+++ b/content/test/stub_layer_tree_view_delegate.h
@@ -26,7 +26,6 @@
   void SendScrollEndEventFromImplSide(
       cc::ElementId scroll_latched_element_id) override {}
   void BeginMainFrame(base::TimeTicks frame_time) override {}
-  void DidBeginMainFrame() override {}
   void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks) override {}
   void RequestNewLayerTreeFrameSink(
diff --git a/device/fido/scoped_virtual_fido_device.cc b/device/fido/scoped_virtual_fido_device.cc
index 4d5f999..9aa0c74 100644
--- a/device/fido/scoped_virtual_fido_device.cc
+++ b/device/fido/scoped_virtual_fido_device.cc
@@ -24,9 +24,10 @@
       public base::SupportsWeakPtr<VirtualFidoDeviceDiscovery> {
  public:
   explicit VirtualFidoDeviceDiscovery(
+      FidoTransportProtocol transport,
       scoped_refptr<VirtualFidoDevice::State> state,
       ProtocolVersion supported_protocol)
-      : FidoDeviceDiscovery(FidoTransportProtocol::kUsbHumanInterfaceDevice),
+      : FidoDeviceDiscovery(transport),
         state_(std::move(state)),
         supported_protocol_(supported_protocol) {}
   ~VirtualFidoDeviceDiscovery() override = default;
@@ -61,6 +62,10 @@
   supported_protocol_ = supported_protocol;
 }
 
+void ScopedVirtualFidoDevice::SetTransport(FidoTransportProtocol transport) {
+  transport_ = transport;
+}
+
 VirtualFidoDevice::State* ScopedVirtualFidoDevice::mutable_state() {
   return state_.get();
 }
@@ -68,10 +73,10 @@
 std::unique_ptr<FidoDiscoveryBase> ScopedVirtualFidoDevice::CreateFidoDiscovery(
     FidoTransportProtocol transport,
     ::service_manager::Connector* connector) {
-  if (transport != FidoTransportProtocol::kUsbHumanInterfaceDevice) {
+  if (transport != transport_) {
     return nullptr;
   }
-  return std::make_unique<VirtualFidoDeviceDiscovery>(state_,
+  return std::make_unique<VirtualFidoDeviceDiscovery>(transport_, state_,
                                                       supported_protocol_);
 }
 
diff --git a/device/fido/scoped_virtual_fido_device.h b/device/fido/scoped_virtual_fido_device.h
index bbe4a5d..24765bec 100644
--- a/device/fido/scoped_virtual_fido_device.h
+++ b/device/fido/scoped_virtual_fido_device.h
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/fido_discovery_factory.h"
+#include "device/fido/fido_transport_protocol.h"
 #include "device/fido/virtual_fido_device.h"
 
 namespace device {
@@ -24,6 +25,14 @@
   ScopedVirtualFidoDevice();
   ~ScopedVirtualFidoDevice() override;
 
+  // Sets the FidoTransportProtocol of the FidoDiscovery to be instantiated by
+  // this ScopedVirtualFidoDevice. The default is
+  // FidoTransportProtocol::kUsbHumanInterfaceDevice.
+  //
+  // The FidoTransportProtocol of the device instantiated by the FidoDiscovery
+  // must be set separately in mutable_state().
+  void SetTransport(FidoTransportProtocol transport);
+
   void SetSupportedProtocol(ProtocolVersion supported_protocol);
   VirtualFidoDevice::State* mutable_state();
 
@@ -34,6 +43,8 @@
 
  private:
   ProtocolVersion supported_protocol_ = ProtocolVersion::kU2f;
+  FidoTransportProtocol transport_ =
+      FidoTransportProtocol::kUsbHumanInterfaceDevice;
   scoped_refptr<VirtualFidoDevice::State> state_;
   DISALLOW_COPY_AND_ASSIGN(ScopedVirtualFidoDevice);
 };
diff --git a/docs/how_to_add_your_feature_flag.md b/docs/how_to_add_your_feature_flag.md
index f9d5eea..e59fa5a 100644
--- a/docs/how_to_add_your_feature_flag.md
+++ b/docs/how_to_add_your_feature_flag.md
@@ -1,6 +1,8 @@
 # Adding a new feature flag in chrome://flags
 
-This document describes how to add your new feature behind a flag.
+This document describes how to add your new feature behind a flag.  See also
+[Configuration](configuration.md), which gives more explanation about flags and
+other options for configuring Chrome.
 
 ## Step 1: Adding a new `base::Feature`
 This step would be different between where you want to use the flag.
diff --git a/extensions/browser/api/declarative/declarative_rule_unittest.cc b/extensions/browser/api/declarative/declarative_rule_unittest.cc
index 04315c53d..87f7195 100644
--- a/extensions/browser/api/declarative/declarative_rule_unittest.cc
+++ b/extensions/browser/api/declarative/declarative_rule_unittest.cc
@@ -12,7 +12,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::test::ParseJson;
+using base::test::ParseJsonDeprecated;
 using url_matcher::URLMatcher;
 using url_matcher::URLMatcherConditionFactory;
 using url_matcher::URLMatcherConditionSet;
@@ -64,8 +64,8 @@
 TEST(DeclarativeConditionTest, ErrorConditionSet) {
   URLMatcher matcher;
   RecordingConditionSet::Values conditions;
-  conditions.push_back(ParseJson("{\"key\": 1}"));
-  conditions.push_back(ParseJson("{\"bad_key\": 2}"));
+  conditions.push_back(ParseJsonDeprecated("{\"key\": 1}"));
+  conditions.push_back(ParseJsonDeprecated("{\"bad_key\": 2}"));
 
   std::string error;
   std::unique_ptr<RecordingConditionSet> result = RecordingConditionSet::Create(
@@ -77,8 +77,8 @@
 TEST(DeclarativeConditionTest, CreateConditionSet) {
   URLMatcher matcher;
   RecordingConditionSet::Values conditions;
-  conditions.push_back(ParseJson("{\"key\": 1}"));
-  conditions.push_back(ParseJson("[\"val1\", 2]"));
+  conditions.push_back(ParseJsonDeprecated("{\"key\": 1}"));
+  conditions.push_back(ParseJsonDeprecated("[\"val1\", 2]"));
 
   // Test insertion
   std::string error;
@@ -89,8 +89,8 @@
   EXPECT_EQ(2u, result->conditions().size());
 
   EXPECT_EQ(matcher.condition_factory(), result->conditions()[0]->factory);
-  EXPECT_TRUE(ParseJson("{\"key\": 1}")->Equals(
-      result->conditions()[0]->value.get()));
+  EXPECT_TRUE(ParseJsonDeprecated("{\"key\": 1}")
+                  ->Equals(result->conditions()[0]->value.get()));
 }
 
 struct FulfillableCondition {
@@ -151,10 +151,10 @@
 TEST(DeclarativeConditionTest, FulfillConditionSet) {
   typedef DeclarativeConditionSet<FulfillableCondition> FulfillableConditionSet;
   FulfillableConditionSet::Values conditions;
-  conditions.push_back(ParseJson("{\"url_id\": 1, \"max\": 3}"));
-  conditions.push_back(ParseJson("{\"url_id\": 2, \"max\": 5}"));
-  conditions.push_back(ParseJson("{\"url_id\": 3, \"max\": 1}"));
-  conditions.push_back(ParseJson("{\"max\": -5}"));  // No url.
+  conditions.push_back(ParseJsonDeprecated("{\"url_id\": 1, \"max\": 3}"));
+  conditions.push_back(ParseJsonDeprecated("{\"url_id\": 2, \"max\": 5}"));
+  conditions.push_back(ParseJsonDeprecated("{\"url_id\": 3, \"max\": 1}"));
+  conditions.push_back(ParseJsonDeprecated("{\"max\": -5}"));  // No url.
 
   // Test insertion
   std::string error;
@@ -254,8 +254,8 @@
 
 TEST(DeclarativeActionTest, ErrorActionSet) {
   SummingActionSet::Values actions;
-  actions.push_back(ParseJson("{\"value\": 1}"));
-  actions.push_back(ParseJson("{\"error\": \"the error\"}"));
+  actions.push_back(ParseJsonDeprecated("{\"value\": 1}"));
+  actions.push_back(ParseJsonDeprecated("{\"error\": \"the error\"}"));
 
   std::string error;
   bool bad = false;
@@ -266,8 +266,8 @@
   EXPECT_FALSE(result);
 
   actions.clear();
-  actions.push_back(ParseJson("{\"value\": 1}"));
-  actions.push_back(ParseJson("{\"bad\": 3}"));
+  actions.push_back(ParseJsonDeprecated("{\"value\": 1}"));
+  actions.push_back(ParseJsonDeprecated("{\"bad\": 3}"));
   result = SummingActionSet::Create(NULL, NULL, actions, &error, &bad);
   EXPECT_EQ("", error);
   EXPECT_TRUE(bad);
@@ -277,9 +277,9 @@
 TEST(DeclarativeActionTest, ApplyActionSet) {
   SummingActionSet::Values actions;
   actions.push_back(
-      ParseJson("{\"value\": 1,"
-                " \"priority\": 5}"));
-  actions.push_back(ParseJson("{\"value\": 2}"));
+      ParseJsonDeprecated("{\"value\": 1,"
+                          " \"priority\": 5}"));
+  actions.push_back(ParseJsonDeprecated("{\"value\": 2}"));
 
   // Test insertion
   std::string error;
@@ -300,21 +300,21 @@
 TEST(DeclarativeRuleTest, Create) {
   typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule;
   Rule::JsonRule json_rule;
-  ASSERT_TRUE(
-      Rule::JsonRule::Populate(*ParseJson("{ \n"
-                                          "  \"id\": \"rule1\", \n"
-                                          "  \"conditions\": [ \n"
-                                          "    {\"url_id\": 1, \"max\": 3}, \n"
-                                          "    {\"url_id\": 2, \"max\": 5}, \n"
-                                          "  ], \n"
-                                          "  \"actions\": [ \n"
-                                          "    { \n"
-                                          "      \"value\": 2 \n"
-                                          "    } \n"
-                                          "  ], \n"
-                                          "  \"priority\": 200 \n"
-                                          "}"),
-                               &json_rule));
+  ASSERT_TRUE(Rule::JsonRule::Populate(
+      *ParseJsonDeprecated("{ \n"
+                           "  \"id\": \"rule1\", \n"
+                           "  \"conditions\": [ \n"
+                           "    {\"url_id\": 1, \"max\": 3}, \n"
+                           "    {\"url_id\": 2, \"max\": 5}, \n"
+                           "  ], \n"
+                           "  \"actions\": [ \n"
+                           "    { \n"
+                           "      \"value\": 2 \n"
+                           "    } \n"
+                           "  ], \n"
+                           "  \"priority\": 200 \n"
+                           "}"),
+      &json_rule));
 
   const char kExtensionId[] = "ext1";
   scoped_refptr<const Extension> extension = ExtensionBuilder()
@@ -376,39 +376,40 @@
                                                  .SetID(kExtensionId)
                                                  .Build();
 
-  ASSERT_TRUE(
-      Rule::JsonRule::Populate(*ParseJson("{ \n"
-                                          "  \"id\": \"rule1\", \n"
-                                          "  \"conditions\": [ \n"
-                                          "    {\"url_id\": 1, \"max\": 3}, \n"
-                                          "    {\"url_id\": 2, \"max\": 5}, \n"
-                                          "  ], \n"
-                                          "  \"actions\": [ \n"
-                                          "    { \n"
-                                          "      \"value\": 2 \n"
-                                          "    } \n"
-                                          "  ], \n"
-                                          "  \"priority\": 200 \n"
-                                          "}"),
-                               &json_rule));
+  ASSERT_TRUE(Rule::JsonRule::Populate(
+      *ParseJsonDeprecated("{ \n"
+                           "  \"id\": \"rule1\", \n"
+                           "  \"conditions\": [ \n"
+                           "    {\"url_id\": 1, \"max\": 3}, \n"
+                           "    {\"url_id\": 2, \"max\": 5}, \n"
+                           "  ], \n"
+                           "  \"actions\": [ \n"
+                           "    { \n"
+                           "      \"value\": 2 \n"
+                           "    } \n"
+                           "  ], \n"
+                           "  \"priority\": 200 \n"
+                           "}"),
+      &json_rule));
   std::unique_ptr<Rule> rule(Rule::Create(
       matcher.condition_factory(), NULL, extension.get(), base::Time(),
       json_rule, base::Bind(AtLeastOneCondition), &error));
   EXPECT_TRUE(rule);
   EXPECT_EQ("", error);
 
-  ASSERT_TRUE(Rule::JsonRule::Populate(*ParseJson("{ \n"
-                                                  "  \"id\": \"rule1\", \n"
-                                                  "  \"conditions\": [ \n"
-                                                  "  ], \n"
-                                                  "  \"actions\": [ \n"
-                                                  "    { \n"
-                                                  "      \"value\": 2 \n"
-                                                  "    } \n"
-                                                  "  ], \n"
-                                                  "  \"priority\": 200 \n"
-                                                  "}"),
-                                       &json_rule));
+  ASSERT_TRUE(
+      Rule::JsonRule::Populate(*ParseJsonDeprecated("{ \n"
+                                                    "  \"id\": \"rule1\", \n"
+                                                    "  \"conditions\": [ \n"
+                                                    "  ], \n"
+                                                    "  \"actions\": [ \n"
+                                                    "    { \n"
+                                                    "      \"value\": 2 \n"
+                                                    "    } \n"
+                                                    "  ], \n"
+                                                    "  \"priority\": 200 \n"
+                                                    "}"),
+                               &json_rule));
   rule = Rule::Create(matcher.condition_factory(), NULL, extension.get(),
                       base::Time(), json_rule, base::Bind(AtLeastOneCondition),
                       &error);
diff --git a/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc b/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc
index 193f15d..0a66a1d 100644
--- a/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc
+++ b/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc
@@ -37,12 +37,11 @@
   // Test wrong condition name passed.
   error.clear();
   result = WebRequestCondition::Create(
-      NULL,
-      matcher.condition_factory(),
-      *base::test::ParseJson(
-           "{ \"invalid\": \"foobar\", \n"
-           "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
-           "}"),
+      NULL, matcher.condition_factory(),
+      *base::test::ParseJsonDeprecated(
+          "{ \"invalid\": \"foobar\", \n"
+          "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+          "}"),
       &error);
   EXPECT_FALSE(error.empty());
   EXPECT_FALSE(result.get());
@@ -50,13 +49,12 @@
   // Test wrong datatype in host_suffix.
   error.clear();
   result = WebRequestCondition::Create(
-      NULL,
-      matcher.condition_factory(),
-      *base::test::ParseJson(
-           "{ \n"
-           "  \"url\": [], \n"
-           "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
-           "}"),
+      NULL, matcher.condition_factory(),
+      *base::test::ParseJsonDeprecated(
+          "{ \n"
+          "  \"url\": [], \n"
+          "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+          "}"),
       &error);
   EXPECT_FALSE(error.empty());
   EXPECT_FALSE(result.get());
@@ -64,14 +62,13 @@
   // Test success (can we support multiple criteria?)
   error.clear();
   result = WebRequestCondition::Create(
-      NULL,
-      matcher.condition_factory(),
-      *base::test::ParseJson(
-           "{ \n"
-           "  \"resourceType\": [\"main_frame\"], \n"
-           "  \"url\": { \"hostSuffix\": \"example.com\" }, \n"
-           "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
-           "}"),
+      NULL, matcher.condition_factory(),
+      *base::test::ParseJsonDeprecated(
+          "{ \n"
+          "  \"resourceType\": [\"main_frame\"], \n"
+          "  \"url\": { \"hostSuffix\": \"example.com\" }, \n"
+          "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+          "}"),
       &error);
   EXPECT_EQ("", error);
   ASSERT_TRUE(result.get());
@@ -133,13 +130,12 @@
   std::unique_ptr<WebRequestCondition> result;
 
   result = WebRequestCondition::Create(
-      NULL,
-      matcher.condition_factory(),
-      *base::test::ParseJson(
-           "{ \n"
-           "  \"firstPartyForCookiesUrl\": { \"hostPrefix\": \"fpfc\"}, \n"
-           "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
-           "}"),
+      NULL, matcher.condition_factory(),
+      *base::test::ParseJsonDeprecated(
+          "{ \n"
+          "  \"firstPartyForCookiesUrl\": { \"hostPrefix\": \"fpfc\"}, \n"
+          "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+          "}"),
       &error);
   EXPECT_EQ("", error);
   ASSERT_TRUE(result.get());
@@ -191,7 +187,7 @@
   std::unique_ptr<WebRequestCondition> condition_empty =
       WebRequestCondition::Create(
           NULL, matcher.condition_factory(),
-          *base::test::ParseJson(
+          *base::test::ParseJsonDeprecated(
               "{ \n"
               "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
               "}"),
@@ -204,7 +200,7 @@
   std::unique_ptr<WebRequestCondition> condition_no_url_true =
       WebRequestCondition::Create(
           NULL, matcher.condition_factory(),
-          *base::test::ParseJson(
+          *base::test::ParseJsonDeprecated(
               "{ \n"
               "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", "
               "\n"
@@ -221,7 +217,7 @@
   std::unique_ptr<WebRequestCondition> condition_no_url_false =
       WebRequestCondition::Create(
           NULL, matcher.condition_factory(),
-          *base::test::ParseJson(
+          *base::test::ParseJsonDeprecated(
               "{ \n"
               "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", "
               "\n"
@@ -260,7 +256,7 @@
   URLMatcher matcher;
 
   WebRequestConditionSet::Values conditions;
-  conditions.push_back(base::test::ParseJson(
+  conditions.push_back(base::test::ParseJsonDeprecated(
       "{ \n"
       "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
       "  \"url\": { \n"
@@ -268,7 +264,7 @@
       "    \"schemes\": [\"http\"], \n"
       "  }, \n"
       "}"));
-  conditions.push_back(base::test::ParseJson(
+  conditions.push_back(base::test::ParseJsonDeprecated(
       "{ \n"
       "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
       "  \"url\": { \n"
@@ -334,7 +330,7 @@
   URLMatcher matcher;
 
   WebRequestConditionSet::Values conditions;
-  conditions.push_back(base::test::ParseJson(
+  conditions.push_back(base::test::ParseJsonDeprecated(
       "{ \n"
       "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
       "  \"url\": { \n"
@@ -403,16 +399,15 @@
   // Test error on incompatible application stages for involved attributes.
   error.clear();
   result = WebRequestCondition::Create(
-      NULL,
-      matcher.condition_factory(),
-      *base::test::ParseJson(
-           "{ \n"
-           "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
-           // Pass a JS array with one empty object to each of the header
-           // filters.
-           "  \"requestHeaders\": [{}], \n"
-           "  \"responseHeaders\": [{}], \n"
-           "}"),
+      NULL, matcher.condition_factory(),
+      *base::test::ParseJsonDeprecated(
+          "{ \n"
+          "  \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+          // Pass a JS array with one empty object to each of the header
+          // filters.
+          "  \"requestHeaders\": [{}], \n"
+          "  \"responseHeaders\": [{}], \n"
+          "}"),
       &error);
   EXPECT_FALSE(error.empty());
   EXPECT_FALSE(result.get());
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 593b8f9c..4ae214d 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -398,6 +398,7 @@
       "api/sockets/sockets_manifest_permission_unittest.cc",
       "component_extension_url_pattern_unittest.cc",
       "csp_validator_unittest.cc",
+      "error_utils_unittest.cc",
       "event_filter_unittest.cc",
       "extension_builder_unittest.cc",
       "extension_icon_set_unittest.cc",
diff --git a/extensions/common/error_utils.cc b/extensions/common/error_utils.cc
index 5fd6d653..211c629 100644
--- a/extensions/common/error_utils.cc
+++ b/extensions/common/error_utils.cc
@@ -4,36 +4,64 @@
 
 #include "extensions/common/error_utils.h"
 
+#include <initializer_list>
+
+#include "base/logging.h"
+#include "base/strings/string_tokenizer.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 
 namespace extensions {
 
+namespace {
+
+std::string FormatErrorMessageInternal(
+    base::StringPiece format,
+    std::initializer_list<base::StringPiece> args) {
+  std::string format_str = format.as_string();
+  base::StringTokenizer tokenizer(format_str, "*");
+  tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS);
+
+  std::vector<base::StringPiece> result_pieces;
+  auto* args_it = args.begin();
+  while (tokenizer.GetNext()) {
+    if (!tokenizer.token_is_delim()) {
+      result_pieces.push_back(tokenizer.token_piece());
+      continue;
+    }
+
+    CHECK_NE(args_it, args.end())
+        << "More placeholders (*) than substitutions.";
+
+    // Substitute the argument.
+    result_pieces.push_back(*args_it);
+    args_it++;
+  }
+
+  // Not all substitutions were consumed.
+  CHECK_EQ(args_it, args.end()) << "Fewer placeholders (*) than substitutions.";
+
+  return base::JoinString(result_pieces, "" /* separator */);
+}
+
+}  // namespace
+
 std::string ErrorUtils::FormatErrorMessage(base::StringPiece format,
                                            base::StringPiece s1) {
-  std::string ret_val = format.as_string();
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
-  return ret_val;
+  return FormatErrorMessageInternal(format, {s1});
 }
 
 std::string ErrorUtils::FormatErrorMessage(base::StringPiece format,
                                            base::StringPiece s1,
                                            base::StringPiece s2) {
-  std::string ret_val = format.as_string();
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
-  return ret_val;
+  return FormatErrorMessageInternal(format, {s1, s2});
 }
 
 std::string ErrorUtils::FormatErrorMessage(base::StringPiece format,
                                            base::StringPiece s1,
                                            base::StringPiece s2,
                                            base::StringPiece s3) {
-  std::string ret_val = format.as_string();
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3);
-  return ret_val;
+  return FormatErrorMessageInternal(format, {s1, s2, s3});
 }
 
 std::string ErrorUtils::FormatErrorMessage(base::StringPiece format,
@@ -41,30 +69,25 @@
                                            base::StringPiece s2,
                                            base::StringPiece s3,
                                            base::StringPiece s4) {
-  std::string ret_val = format.as_string();
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3);
-  base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s4);
-  return ret_val;
+  return FormatErrorMessageInternal(format, {s1, s2, s3, s4});
 }
 
 base::string16 ErrorUtils::FormatErrorMessageUTF16(base::StringPiece format,
                                                    base::StringPiece s1) {
-  return base::UTF8ToUTF16(FormatErrorMessage(format, s1));
+  return base::UTF8ToUTF16(FormatErrorMessageInternal(format, {s1}));
 }
 
 base::string16 ErrorUtils::FormatErrorMessageUTF16(base::StringPiece format,
                                                    base::StringPiece s1,
                                                    base::StringPiece s2) {
-  return base::UTF8ToUTF16(FormatErrorMessage(format, s1, s2));
+  return base::UTF8ToUTF16(FormatErrorMessageInternal(format, {s1, s2}));
 }
 
 base::string16 ErrorUtils::FormatErrorMessageUTF16(base::StringPiece format,
                                                    base::StringPiece s1,
                                                    base::StringPiece s2,
                                                    base::StringPiece s3) {
-  return base::UTF8ToUTF16(FormatErrorMessage(format, s1, s2, s3));
+  return base::UTF8ToUTF16(FormatErrorMessageInternal(format, {s1, s2, s3}));
 }
 
 base::string16 ErrorUtils::FormatErrorMessageUTF16(base::StringPiece format,
@@ -72,7 +95,8 @@
                                                    base::StringPiece s2,
                                                    base::StringPiece s3,
                                                    base::StringPiece s4) {
-  return base::UTF8ToUTF16(FormatErrorMessage(format, s1, s2, s3, s4));
+  return base::UTF8ToUTF16(
+      FormatErrorMessageInternal(format, {s1, s2, s3, s4}));
 }
 
 }  // namespace extensions
diff --git a/extensions/common/error_utils_unittest.cc b/extensions/common/error_utils_unittest.cc
new file mode 100644
index 0000000..6cf8b22
--- /dev/null
+++ b/extensions/common/error_utils_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/error_utils.h"
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+// Tests ErrorUtils::FormatErrorMessage which takes two placeholders.
+TEST(ErrorUtils, FormatErrorMessage_Success) {
+  struct {
+    base::StringPiece format;
+    base::StringPiece s1;
+    base::StringPiece s2;
+    const char* expected;
+  } cases[] = {
+      {"Hello * Bye *", "arg1", "arg2", "Hello arg1 Bye arg2"},
+      // Ensure substitutions respect the size of the StringPiece.
+      {"Hello * Bye *", base::StringPiece("12345", 2), "3", "Hello 12 Bye 3"},
+      // Regression test for crbug.com/928415.
+      {"Hello * Bye *", "*arg1", "*arg2", "Hello *arg1 Bye *arg2"},
+  };
+
+  for (const auto& test_case : cases) {
+    SCOPED_TRACE(test_case.format);
+
+    EXPECT_EQ(test_case.expected,
+              ErrorUtils::FormatErrorMessage(test_case.format, test_case.s1,
+                                             test_case.s2));
+    EXPECT_EQ(base::UTF8ToUTF16(test_case.expected),
+              ErrorUtils::FormatErrorMessageUTF16(test_case.format,
+                                                  test_case.s1, test_case.s2));
+  }
+}
+
+#if defined(GTEST_HAS_DEATH_TEST)
+// Tests that we raise an error if the number of placeholders and substitution
+// arguments are not equal.
+TEST(ErrorUtils, FormatErrorMessage_Death) {
+  struct {
+    base::StringPiece format;
+    base::StringPiece s1;
+    base::StringPiece s2;
+    const char* death_message_regex;
+  } cases[] = {{"Hello * Bye * *", "arg1", "arg2", "More placeholders"},
+               {"Hello * Bye", "arg1", "arg2", "Fewer placeholders"}};
+
+  for (const auto& test_case : cases) {
+    SCOPED_TRACE(test_case.format);
+
+    EXPECT_DEATH(ErrorUtils::FormatErrorMessage(test_case.format, test_case.s1,
+                                                test_case.s2),
+                 test_case.death_message_regex);
+    EXPECT_DEATH(ErrorUtils::FormatErrorMessageUTF16(
+                     test_case.format, test_case.s1, test_case.s2),
+                 test_case.death_message_regex);
+  }
+}
+#endif  // defined(GTEST_HAS_DEATH_TEST)
+
+}  // namespace
+}  // namespace extensions
diff --git a/extensions/common/manifest_handlers/action_handlers_handler_unittest.cc b/extensions/common/manifest_handlers/action_handlers_handler_unittest.cc
index 5196732..2832c7a5 100644
--- a/extensions/common/manifest_handlers/action_handlers_handler_unittest.cc
+++ b/extensions/common/manifest_handlers/action_handlers_handler_unittest.cc
@@ -22,8 +22,7 @@
 class ActionHandlersManifestTest : public ManifestTest {
  protected:
   ManifestData CreateManifest(const std::string& action_handlers) {
-    std::unique_ptr<base::Value> manifest =
-        base::test::ParseJson(R"json({
+    base::Value manifest = base::test::ParseJson(R"json({
                                     "name": "test",
                                     "version": "1",
                                     "app": {
@@ -33,9 +32,9 @@
                                     },
                                     "manifest_version": 2,
                                     "action_handlers": )json" +
-                              action_handlers + "}");
-    EXPECT_TRUE(manifest);
-    return ManifestData(std::move(manifest), "test");
+                                                 action_handlers + "}");
+    return ManifestData(base::Value::ToUniquePtrValue(std::move(manifest)),
+                        "test");
   }
 
   // Returns all action handlers associated with |extension|.
diff --git a/extensions/common/manifest_handlers/file_handler_info.cc b/extensions/common/manifest_handlers/file_handler_info.cc
index 848d17b..da5c5a1 100644
--- a/extensions/common/manifest_handlers/file_handler_info.cc
+++ b/extensions/common/manifest_handlers/file_handler_info.cc
@@ -98,8 +98,7 @@
     if (include_directories->is_bool()) {
       handler.include_directories = include_directories->GetBool();
     } else {
-      *error = ErrorUtils::FormatErrorMessageUTF16(
-          errors::kInvalidFileHandlerIncludeDirectories, handler_id);
+      *error = base::UTF8ToUTF16(errors::kInvalidFileHandlerIncludeDirectories);
       return false;
     }
   }
diff --git a/extensions/common/manifest_handlers/icons_handler_unittest.cc b/extensions/common/manifest_handlers/icons_handler_unittest.cc
index 494bb99..8ea883a 100644
--- a/extensions/common/manifest_handlers/icons_handler_unittest.cc
+++ b/extensions/common/manifest_handlers/icons_handler_unittest.cc
@@ -15,7 +15,7 @@
 
  protected:
   std::unique_ptr<base::Value> CreateManifest(const std::string& extra_icons) {
-    std::unique_ptr<base::Value> manifest = base::test::ParseJson(
+    std::unique_ptr<base::Value> manifest = base::test::ParseJsonDeprecated(
         "{ \n"
         "  \"name\": \"test\", \n"
         "  \"version\": \"0.1\", \n"
diff --git a/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc b/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc
index 1034171..81f9d42 100644
--- a/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc
+++ b/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc
@@ -49,7 +49,7 @@
   std::unique_ptr<base::Value> CreateManifest(AutoApproveValue auto_approve,
                                               bool extension_id_whitelisted,
                                               ClientIdValue client_id) {
-    std::unique_ptr<base::Value> manifest = base::test::ParseJson(
+    std::unique_ptr<base::Value> manifest = base::test::ParseJsonDeprecated(
         "{ \n"
         "  \"name\": \"test\", \n"
         "  \"version\": \"0.1\", \n"
diff --git a/extensions/common/manifest_handlers/permissions_parser.cc b/extensions/common/manifest_handlers/permissions_parser.cc
index 1d9a0aea..0c1d632 100644
--- a/extensions/common/manifest_handlers/permissions_parser.cc
+++ b/extensions/common/manifest_handlers/permissions_parser.cc
@@ -94,8 +94,7 @@
 
   const base::Value* permissions = nullptr;
   if (!extension->manifest()->GetList(key, &permissions)) {
-    *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidPermissions,
-                                                 std::string());
+    *error = base::UTF8ToUTF16(errors::kInvalidPermissions);
     return false;
   }
 
diff --git a/gpu/command_buffer/client/image_decode_accelerator_interface.h b/gpu/command_buffer/client/image_decode_accelerator_interface.h
index 5e1aa72..d56c6b6c 100644
--- a/gpu/command_buffer/client/image_decode_accelerator_interface.h
+++ b/gpu/command_buffer/client/image_decode_accelerator_interface.h
@@ -24,6 +24,9 @@
  public:
   virtual ~ImageDecodeAcceleratorInterface() {}
 
+  virtual bool IsImageSupported(
+      base::span<const uint8_t> encoded_data) const = 0;
+
   virtual SyncToken ScheduleImageDecode(
       base::span<const uint8_t> encoded_data,
       const gfx::Size& output_size,
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index b7c4039..bae4d80 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -1146,6 +1146,11 @@
     FlushPaintCachePurgedEntries();
 }
 
+bool RasterImplementation::CanDecodeWithHardwareAcceleration(
+    base::span<const uint8_t> encoded_data) {
+  return image_decode_accelerator_->IsImageSupported(encoded_data);
+}
+
 SyncToken RasterImplementation::ScheduleImageDecode(
     base::span<const uint8_t> encoded_data,
     const gfx::Size& output_size,
diff --git a/gpu/command_buffer/client/raster_implementation.h b/gpu/command_buffer/client/raster_implementation.h
index c4e01fd..22a403a 100644
--- a/gpu/command_buffer/client/raster_implementation.h
+++ b/gpu/command_buffer/client/raster_implementation.h
@@ -134,6 +134,8 @@
                       const gfx::Vector2dF& post_translate,
                       GLfloat post_scale,
                       bool requires_clear) override;
+  bool CanDecodeWithHardwareAcceleration(
+      base::span<const uint8_t> encoded_data) override;
   SyncToken ScheduleImageDecode(base::span<const uint8_t> encoded_data,
                                 const gfx::Size& output_size,
                                 uint32_t transfer_cache_entry_id,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index 4fd888e1..293d0bb 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -153,6 +153,12 @@
   NOTREACHED();
 }
 
+bool RasterImplementationGLES::CanDecodeWithHardwareAcceleration(
+    base::span<const uint8_t> encoded_data) {
+  NOTREACHED();
+  return false;
+}
+
 SyncToken RasterImplementationGLES::ScheduleImageDecode(
     base::span<const uint8_t> encoded_data,
     const gfx::Size& output_size,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.h b/gpu/command_buffer/client/raster_implementation_gles.h
index 2de8b7f..9d8e39f 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.h
+++ b/gpu/command_buffer/client/raster_implementation_gles.h
@@ -78,6 +78,8 @@
   void EndRasterCHROMIUM() override;
 
   // Image decode acceleration.
+  bool CanDecodeWithHardwareAcceleration(
+      base::span<const uint8_t> encoded_data) override;
   SyncToken ScheduleImageDecode(base::span<const uint8_t> encoded_data,
                                 const gfx::Size& output_size,
                                 uint32_t transfer_cache_entry_id,
diff --git a/gpu/command_buffer/client/raster_interface.h b/gpu/command_buffer/client/raster_interface.h
index 7373ad4..e88f36dc 100644
--- a/gpu/command_buffer/client/raster_interface.h
+++ b/gpu/command_buffer/client/raster_interface.h
@@ -66,6 +66,12 @@
                               GLfloat post_scale,
                               bool requires_clear) = 0;
 
+  // Determines if an encoded image can be decoded using hardware decode
+  // acceleration. If this method returns true, then the client can be confident
+  // that a call to ScheduleImageDecode() will succeed.
+  virtual bool CanDecodeWithHardwareAcceleration(
+      base::span<const uint8_t> encoded_data) = 0;
+
   // Schedules a hardware-accelerated image decode and a sync token that's
   // released when the image decode is complete. If the decode could not be
   // scheduled, an empty sync token is returned.
diff --git a/gpu/dawn_end2end_tests_main.cc b/gpu/dawn_end2end_tests_main.cc
index 1188ccb..d6a85ab 100644
--- a/gpu/dawn_end2end_tests_main.cc
+++ b/gpu/dawn_end2end_tests_main.cc
@@ -19,10 +19,15 @@
 
 }  // namespace
 
+// Definition located in third_party/dawn/src/tests/DawnTest.h
+// Forward declared here to avoid pulling in the Dawn headers.
+void InitDawnEnd2EndTestEnvironment(int argc, char** argv);
+
 int main(int argc, char** argv) {
   base::CommandLine::Init(argc, argv);
   testing::InitGoogleMock(&argc, argv);
   base::TestSuite test_suite(argc, argv);
+  InitDawnEnd2EndTestEnvironment(argc, argv);
   int rt = base::LaunchUnitTestsWithOptions(
       argc, argv,
       1,     // Run tests serially.
diff --git a/gpu/ipc/client/BUILD.gn b/gpu/ipc/client/BUILD.gn
index acab7a514..5a9ee4b7 100644
--- a/gpu/ipc/client/BUILD.gn
+++ b/gpu/ipc/client/BUILD.gn
@@ -37,6 +37,7 @@
     "//gpu/command_buffer/common:common_sources",
     "//gpu/config:config_sources",
     "//gpu/ipc/common:ipc_common_sources",
+    "//media/filters:jpeg_parser",
     "//mojo/public/cpp/system",
     "//ui/gfx:color_space",
     "//ui/gfx/geometry",
diff --git a/gpu/ipc/client/DEPS b/gpu/ipc/client/DEPS
index 1caa6b1..3333b39 100644
--- a/gpu/ipc/client/DEPS
+++ b/gpu/ipc/client/DEPS
@@ -10,6 +10,9 @@
   "gpu_in_process_context_tests.cc": [
     "+components/viz/test/test_gpu_memory_buffer_manager.h",
   ],
+  "image_decode_accelerator_proxy.cc": [
+    "+media/filters/jpeg_parser.h",
+  ],
   "raster_in_process_context_tests.cc": [
     "+cc/paint/color_space_transfer_cache_entry.h",
     "+components/viz/common/resources/resource_format.h",
diff --git a/gpu/ipc/client/image_decode_accelerator_proxy.cc b/gpu/ipc/client/image_decode_accelerator_proxy.cc
index 4691684..d992ee9 100644
--- a/gpu/ipc/client/image_decode_accelerator_proxy.cc
+++ b/gpu/ipc/client/image_decode_accelerator_proxy.cc
@@ -4,24 +4,160 @@
 
 #include "gpu/ipc/client/image_decode_accelerator_proxy.h"
 
+#include <string.h>
+
+#include <algorithm>
 #include <utility>
 #include <vector>
 
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
 #include "gpu/command_buffer/common/constants.h"
+#include "gpu/config/gpu_info.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
 #include "gpu/ipc/common/command_buffer_id.h"
 #include "gpu/ipc/common/gpu_messages.h"
+#include "media/filters/jpeg_parser.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace gpu {
 
+namespace {
+
+bool IsJpegImage(base::span<const uint8_t> encoded_data) {
+  if (encoded_data.size() < 3u)
+    return false;
+  return memcmp("\xFF\xD8\xFF", encoded_data.data(), 3u) == 0;
+}
+
+ImageDecodeAcceleratorType GetImageType(
+    base::span<const uint8_t> encoded_data) {
+  static_assert(static_cast<int>(ImageDecodeAcceleratorType::kMaxValue) == 1,
+                "GetImageType() must be adapted to support all image types in "
+                "ImageDecodeAcceleratorType");
+
+  // Currently, only JPEG images are supported.
+  if (IsJpegImage(encoded_data))
+    return ImageDecodeAcceleratorType::kJpeg;
+
+  return ImageDecodeAcceleratorType::kUnknown;
+}
+
+bool GetJpegSubsampling(const media::JpegParseResult& parse_result,
+                        ImageDecodeAcceleratorSubsampling* subsampling) {
+  static_assert(
+      static_cast<int>(ImageDecodeAcceleratorSubsampling::kMaxValue) == 1,
+      "GetJpegSubsampling() must be adapted to support all "
+      "subsampling factors in ImageDecodeAcceleratorSubsampling");
+
+  // Currently, only 3 components are supported (this excludes, for example,
+  // grayscale and CMYK JPEGs).
+  if (parse_result.frame_header.num_components != 3u)
+    return false;
+
+  const uint8_t comp0_h =
+      parse_result.frame_header.components[0].horizontal_sampling_factor;
+  const uint8_t comp0_v =
+      parse_result.frame_header.components[0].vertical_sampling_factor;
+  const uint8_t comp1_h =
+      parse_result.frame_header.components[1].horizontal_sampling_factor;
+  const uint8_t comp1_v =
+      parse_result.frame_header.components[1].vertical_sampling_factor;
+  const uint8_t comp2_h =
+      parse_result.frame_header.components[2].horizontal_sampling_factor;
+  const uint8_t comp2_v =
+      parse_result.frame_header.components[2].vertical_sampling_factor;
+
+  if (comp0_h == 2u && (comp1_h == 1u && comp1_v == 1u) &&
+      (comp2_h == 1u && comp2_v == 1u)) {
+    if (comp0_v == 2u) {
+      *subsampling = ImageDecodeAcceleratorSubsampling::k420;
+      return true;
+    } else if (comp0_v == 1u) {
+      *subsampling = ImageDecodeAcceleratorSubsampling::k422;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool IsSupportedJpegImage(
+    base::span<const uint8_t> encoded_data,
+    const ImageDecodeAcceleratorSupportedProfile& supported_profile) {
+  DCHECK(IsJpegImage(encoded_data));
+  DCHECK_EQ(ImageDecodeAcceleratorType::kJpeg, supported_profile.image_type);
+
+  // First, parse the JPEG file. This fails for progressive JPEGs (which we
+  // don't support anyway).
+  media::JpegParseResult parse_result;
+  if (!media::ParseJpegPicture(encoded_data.data(), encoded_data.size(),
+                               &parse_result)) {
+    return false;
+  }
+
+  // Now, check the chroma subsampling format.
+  ImageDecodeAcceleratorSubsampling subsampling;
+  if (!GetJpegSubsampling(parse_result, &subsampling))
+    return false;
+  if (std::find(supported_profile.subsamplings.cbegin(),
+                supported_profile.subsamplings.cend(),
+                subsampling) == supported_profile.subsamplings.cend()) {
+    return false;
+  }
+
+  // Now, check the dimensions.
+  const int encoded_width =
+      base::strict_cast<int>(parse_result.frame_header.coded_width);
+  const int encoded_height =
+      base::strict_cast<int>(parse_result.frame_header.coded_height);
+  if (encoded_width < supported_profile.min_encoded_dimensions.width() ||
+      encoded_height < supported_profile.min_encoded_dimensions.height() ||
+      encoded_width > supported_profile.max_encoded_dimensions.width() ||
+      encoded_height > supported_profile.max_encoded_dimensions.height()) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
 ImageDecodeAcceleratorProxy::ImageDecodeAcceleratorProxy(GpuChannelHost* host,
                                                          int32_t route_id)
     : host_(host), route_id_(route_id) {}
 
 ImageDecodeAcceleratorProxy::~ImageDecodeAcceleratorProxy() {}
 
+bool ImageDecodeAcceleratorProxy::IsImageSupported(
+    base::span<const uint8_t> encoded_data) const {
+  DCHECK(host_);
+
+  const ImageDecodeAcceleratorType image_type = GetImageType(encoded_data);
+  if (image_type == ImageDecodeAcceleratorType::kUnknown)
+    return false;
+
+  // Find the image decode accelerator supported profile according to the type
+  // of the image.
+  const std::vector<ImageDecodeAcceleratorSupportedProfile>& profiles =
+      host_->gpu_info().image_decode_accelerator_supported_profiles;
+  auto profile_it = std::find_if(
+      profiles.cbegin(), profiles.cend(),
+      [image_type](const ImageDecodeAcceleratorSupportedProfile& profile) {
+        return profile.image_type == image_type;
+      });
+  if (profile_it == profiles.cend())
+    return false;
+
+  // Validate the image according to that profile.
+  if (image_type == ImageDecodeAcceleratorType::kJpeg)
+    return IsSupportedJpegImage(encoded_data, *profile_it);
+
+  NOTREACHED();
+  return false;
+}
+
 SyncToken ImageDecodeAcceleratorProxy::ScheduleImageDecode(
     base::span<const uint8_t> encoded_data,
     const gfx::Size& output_size,
@@ -35,6 +171,7 @@
   DCHECK(host_);
   DCHECK_EQ(host_->channel_id(),
             ChannelIdFromCommandBufferId(raster_decoder_command_buffer_id));
+  DCHECK(IsImageSupported(encoded_data));
 
   GpuChannelMsg_ScheduleImageDecode_Params params;
   params.encoded_data =
diff --git a/gpu/ipc/client/image_decode_accelerator_proxy.h b/gpu/ipc/client/image_decode_accelerator_proxy.h
index 5ad7bec..b40a3fe 100644
--- a/gpu/ipc/client/image_decode_accelerator_proxy.h
+++ b/gpu/ipc/client/image_decode_accelerator_proxy.h
@@ -50,6 +50,12 @@
   ImageDecodeAcceleratorProxy(GpuChannelHost* host, int32_t route_id);
   ~ImageDecodeAcceleratorProxy() override;
 
+  // Determines if an encoded image is supported by the hardware accelerator.
+  // The ScheduleImageDecode() method should only be called for images for which
+  // IsImageSupported() returns true. Otherwise, the client faces a GPU channel
+  // teardown if the decode fails.
+  bool IsImageSupported(base::span<const uint8_t> encoded_data) const override;
+
   // Schedules a hardware-accelerated image decode on the GPU process. The image
   // in |encoded_data| is decoded and scaled to |output_size|. Upon completion
   // and after the sync token corresponding to
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm
index ff4f843..c62ba9d 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.mm
@@ -122,6 +122,7 @@
   [super tableView:tableView didSelectRowAtIndexPath:indexPath];
   TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
   [self.serviceDelegate didSelectItem:item];
+  [tableView deselectRowAtIndexPath:indexPath animated:YES];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm b/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
index a3169a4..ba0c0bd 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
@@ -1581,8 +1581,8 @@
                             UIAccessibilityTraitNotEnabled))];
 }
 
-// Test that user can type text in search field and that it filters out the
-// passwords and blacklisted items.
+// Test that when user types text in search field, passwords and blacklisted
+// items are filtered out and "save passwords" switch is removed.
 - (void)testSearchPasswords {
   SaveExamplePasswordForms();
   SaveExampleBlacklistedForms();
@@ -1598,12 +1598,14 @@
   [GetInteractionForPasswordEntry(@"exclude2.com")
       assertWithMatcher:grey_notNil()];
 
-  [[EarlGrey
-      selectElementWithMatcher:grey_accessibilityID(kPasswordsTableViewId)]
-      performAction:grey_scrollToContentEdge(kGREYContentEdgeTop)];
   [[EarlGrey selectElementWithMatcher:SearchTextField()]
       performAction:grey_typeText(@"2")];
 
+  // Check that the "Save Passwords" switch is hidden.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::SettingsSwitchCell(
+                                          @"savePasswordsItem_switch", YES)]
+      assertWithMatcher:grey_nil()];
+
   [GetInteractionForPasswordEntry(@"example11.com, user1")
       assertWithMatcher:grey_nil()];
   [GetInteractionForPasswordEntry(@"example12.com, user2")
diff --git a/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm b/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm
index 2356a19e..8c53971 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_table_view_controller.mm
@@ -159,6 +159,8 @@
   // The observable boolean that binds to the password manager setting state.
   // Saved passwords are only on if the password manager is enabled.
   PrefBackedBoolean* passwordManagerEnabled_;
+  // The header for save passwords switch section.
+  TableViewLinkHeaderFooterItem* manageAccountLinkItem_;
   // The item related to the switch for the password manager setting.
   SettingsSwitchItem* savePasswordsItem_;
   // The item related to the button for exporting passwords.
@@ -207,6 +209,9 @@
 // The scrim view that covers the table view when search bar is focused with
 // empty search term. Tapping on the scrim view will dismiss the search bar.
 @property(nonatomic, strong) UIControl* scrimView;
+// Example headers for calculating headers' heights.
+@property(nonatomic, strong)
+    NSMutableDictionary<Class, UITableViewHeaderFooterView*>* exampleHeaders;
 
 @end
 
@@ -228,6 +233,7 @@
     _passwordExporter = [[PasswordExporter alloc]
         initWithReauthenticationModule:reauthenticationModule_
                               delegate:self];
+    self.exampleHeaders = [[NSMutableDictionary alloc] init];
     self.title = l10n_util::GetNSString(IDS_IOS_PASSWORDS);
     self.shouldHideDoneButton = YES;
     self.searchTerm = @"";
@@ -341,9 +347,8 @@
   savePasswordsItem_ = [self savePasswordsItem];
   [model addItem:savePasswordsItem_
       toSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch];
-  TableViewLinkHeaderFooterItem* manageAccountLinkItem =
-      [self manageAccountLinkItem];
-  [model setHeader:manageAccountLinkItem
+  manageAccountLinkItem_ = [self manageAccountLinkItem];
+  [model setHeader:manageAccountLinkItem_
       forSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch];
 
   // Saved passwords.
@@ -500,15 +505,79 @@
   [self reloadData];
 }
 
+#pragma mark - UITableViewDelegate
+
+// Uses a group of example headers to calculate the heights. Returning
+// UITableViewAutomaticDimension here will cause UITableView to cache the
+// heights and reuse them when sections are inserted or removed, which will
+// break the UI. For example:
+//   1. UITableView is inited with 4 headers;
+//   2. "tableView:heightForHeaderInSection" is called and
+//      UITableViewAutomaticDimension is returned;
+//   3. UITableView calculates headers' heights and get [10, 20, 10, 20];
+//   4. UITableView caches these heights;
+//   5. The first header is removed from UITableView;
+//   6. "tableView:heightForHeaderInSection" is called and
+//      UITableViewAutomaticDimension is returned;
+//   7. UITableView decides to use cached results as [10, 20, 10], while
+//      expected heights are [20, 10, 20].
+- (CGFloat)tableView:(UITableView*)tableView
+    heightForHeaderInSection:(NSInteger)section {
+  if ([self.tableViewModel headerForSection:section]) {
+    TableViewHeaderFooterItem* item =
+        [self.tableViewModel headerForSection:section];
+    Class headerClass = item.cellClass;
+    if (!self.exampleHeaders[headerClass]) {
+      UITableViewHeaderFooterView* view =
+          [[headerClass alloc] initWithReuseIdentifier:@""];
+      [item configureHeaderFooterView:view withStyler:self.styler];
+      [self.exampleHeaders setObject:view forKey:headerClass];
+    }
+    UITableViewHeaderFooterView* view = self.exampleHeaders[headerClass];
+    CGSize size =
+        [view systemLayoutSizeFittingSize:self.tableView.safeAreaLayoutGuide
+                                              .layoutFrame.size
+            withHorizontalFittingPriority:UILayoutPriorityRequired
+                  verticalFittingPriority:1];
+    return size.height;
+  }
+  return [super tableView:tableView heightForHeaderInSection:section];
+}
+
 #pragma mark - UISearchControllerDelegate
 
 - (void)willPresentSearchController:(UISearchController*)searchController {
   [self showScrim];
+  // Remove save passwords switch section.
+  [self
+      performBatchTableViewUpdates:^{
+        [self clearSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch
+                        withRowAnimation:UITableViewRowAnimationTop];
+      }
+                        completion:nil];
 }
 
 - (void)willDismissSearchController:(UISearchController*)searchController {
   [self hideScrim];
   [self searchForTerm:@""];
+  // Recover save passwords switch section.
+  TableViewModel* model = self.tableViewModel;
+  [self.tableView
+      performBatchUpdates:^{
+        [model insertSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch
+                                   atIndex:0];
+        [model setHeader:manageAccountLinkItem_
+            forSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch];
+        [self.tableView insertSections:[NSIndexSet indexSetWithIndex:0]
+                      withRowAnimation:UITableViewRowAnimationTop];
+        [model addItem:savePasswordsItem_
+            toSectionWithIdentifier:SectionIdentifierSavePasswordsSwitch];
+        [self.tableView
+            insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:0
+                                                         inSection:0] ]
+                  withRowAnimation:UITableViewRowAnimationTop];
+      }
+               completion:nil];
 }
 
 #pragma mark - UISearchBarDelegate
@@ -708,13 +777,13 @@
 
 // Removes the given section if it exists and if isEmpty is true.
 - (void)clearSectionWithIdentifier:(NSInteger)sectionIdentifier
-                           ifEmpty:(bool)isEmpty {
+                  withRowAnimation:(UITableViewRowAnimation)animation {
   TableViewModel* model = self.tableViewModel;
-  if (isEmpty && [model hasSectionForSectionIdentifier:sectionIdentifier]) {
+  if ([model hasSectionForSectionIdentifier:sectionIdentifier]) {
     NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier];
     [model removeSectionWithIdentifier:sectionIdentifier];
     [[self tableView] deleteSections:[NSIndexSet indexSetWithIndex:section]
-                    withRowAnimation:UITableViewRowAnimationAutomatic];
+                    withRowAnimation:animation];
   }
 }
 
@@ -795,11 +864,15 @@
         // Delete in reverse order of section indexes (bottom up of section
         // displayed), so that indexes in model matches those in the view.  if
         // we don't we'll cause a crash.
-        [strongSelf
-            clearSectionWithIdentifier:SectionIdentifierBlacklist
-                               ifEmpty:strongSelf->blacklistedForms_.empty()];
-        [strongSelf clearSectionWithIdentifier:SectionIdentifierSavedPasswords
-                                       ifEmpty:strongSelf->savedForms_.empty()];
+        if (strongSelf->blacklistedForms_.empty()) {
+          [self clearSectionWithIdentifier:SectionIdentifierBlacklist
+                          withRowAnimation:UITableViewRowAnimationAutomatic];
+        }
+        if (strongSelf->savedForms_.empty()) {
+          [strongSelf
+              clearSectionWithIdentifier:SectionIdentifierSavedPasswords
+                        withRowAnimation:UITableViewRowAnimationAutomatic];
+        }
       }
       completion:^(BOOL finished) {
         PasswordsTableViewController* strongSelf = weakSelf;
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm
index 6ea5ce0..007e5d7 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm
@@ -91,12 +91,12 @@
     // beyond the margins.
     heightConstraint.priority = UILayoutPriorityDefaultHigh + 1;
     NSLayoutConstraint* topAnchorConstraint = [containerView.topAnchor
-        constraintGreaterThanOrEqualToAnchor:self.contentView.topAnchor
-                                    constant:kTableViewVerticalSpacing];
+        constraintEqualToAnchor:self.contentView.topAnchor
+                       constant:kTableViewVerticalSpacing];
     topAnchorConstraint.priority = UILayoutPriorityDefaultHigh;
     NSLayoutConstraint* bottomAnchorConstraint = [containerView.bottomAnchor
-        constraintLessThanOrEqualToAnchor:self.contentView.bottomAnchor
-                                 constant:-kTableViewVerticalSpacing];
+        constraintEqualToAnchor:self.contentView.bottomAnchor
+                       constant:-kTableViewVerticalSpacing];
     bottomAnchorConstraint.priority = UILayoutPriorityDefaultHigh;
     NSLayoutConstraint* leadingAnchorConstraint = [containerView.leadingAnchor
         constraintEqualToAnchor:self.contentView.leadingAnchor
diff --git a/ios/web/public/test/web_test_with_web_state.mm b/ios/web/public/test/web_test_with_web_state.mm
index 78ea327..bf5b24c 100644
--- a/ios/web/public/test/web_test_with_web_state.mm
+++ b/ios/web/public/test/web_test_with_web_state.mm
@@ -128,32 +128,6 @@
 }
 
 void WebTestWithWebState::LoadHtml(NSString* html, const GURL& url) {
-  // Sets MIME type to "text/html" once navigation is committed.
-  class MimeTypeUpdater : public WebStateObserver {
-   public:
-    MimeTypeUpdater() = default;
-
-    // WebStateObserver overrides:
-    void DidFinishNavigation(WebState* web_state,
-                             NavigationContext* context) override {
-      // loadHTML:forURL: does not notify web view delegate about received
-      // response, so web controller does not get a chance to properly update
-      // MIME type and it should be set manually after navigation is committed
-      // but before WebState signal load completion and clients will start
-      // checking if MIME type is in fact HTML.
-      static_cast<WebStateImpl*>(web_state)->SetContentsMimeType("text/html");
-    }
-    void WebStateDestroyed(WebState* web_state) override { NOTREACHED(); }
-
-   private:
-    DISALLOW_COPY_AND_ASSIGN(MimeTypeUpdater);
-  };
-
-  MimeTypeUpdater mime_type_updater;
-  ScopedObserver<WebState, WebStateObserver> scoped_observer(
-      &mime_type_updater);
-  scoped_observer.Add(web_state());
-
   // Initiate asynchronous HTML load.
   CRWWebController* web_controller = GetWebController(web_state());
   ASSERT_EQ(PAGE_LOADED, web_controller.loadPhase);
diff --git a/ios/web/web_state/navigation_context_impl.h b/ios/web/web_state/navigation_context_impl.h
index b5c760b..ca98a33 100644
--- a/ios/web/web_state/navigation_context_impl.h
+++ b/ios/web/web_state/navigation_context_impl.h
@@ -88,6 +88,10 @@
   bool IsPlaceholderNavigation() const;
   void SetPlaceholderNavigation(bool flag);
 
+  // MIMEType of the navigation.
+  void SetMimeType(NSString* mime_type);
+  NSString* GetMimeType() const;
+
  private:
   NavigationContextImpl(WebState* web_state,
                         const GURL& url,
@@ -104,7 +108,7 @@
   bool has_committed_ = false;
   bool is_download_ = false;
   bool is_post_ = false;
-  NSError* error_;
+  NSError* error_ = nil;
   scoped_refptr<net::HttpResponseHeaders> response_headers_;
   bool is_renderer_initiated_ = false;
   int navigation_item_unique_id_ = -1;
@@ -113,6 +117,7 @@
   bool is_loading_html_string_ = false;
   bool is_native_content_presented_ = false;
   bool is_placeholder_navigation_ = false;
+  NSString* mime_type_ = nil;
 
   DISALLOW_COPY_AND_ASSIGN(NavigationContextImpl);
 };
diff --git a/ios/web/web_state/navigation_context_impl.mm b/ios/web/web_state/navigation_context_impl.mm
index fbc28ef..9981c6726 100644
--- a/ios/web/web_state/navigation_context_impl.mm
+++ b/ios/web/web_state/navigation_context_impl.mm
@@ -179,6 +179,14 @@
   is_placeholder_navigation_ = flag;
 }
 
+void NavigationContextImpl::SetMimeType(NSString* mime_type) {
+  mime_type_ = mime_type;
+}
+
+NSString* NavigationContextImpl::GetMimeType() const {
+  return mime_type_;
+}
+
 NavigationContextImpl::NavigationContextImpl(WebState* web_state,
                                              const GURL& url,
                                              bool has_user_gesture,
diff --git a/ios/web/web_state/navigation_context_impl_unittest.mm b/ios/web/web_state/navigation_context_impl_unittest.mm
index ad4be8f..6f177658 100644
--- a/ios/web/web_state/navigation_context_impl_unittest.mm
+++ b/ios/web/web_state/navigation_context_impl_unittest.mm
@@ -7,6 +7,7 @@
 #import "ios/web/public/test/fakes/test_web_state.h"
 #include "net/http/http_response_headers.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#import "testing/gtest_mac.h"
 #include "testing/platform_test.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -93,6 +94,7 @@
   ASSERT_FALSE(context->IsRendererInitiated());
   ASSERT_NE(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetUrl
   GURL new_url("https://new.test");
@@ -107,6 +109,7 @@
   EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetSameDocument
   context->SetIsSameDocument(true);
@@ -120,6 +123,7 @@
   EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetHasCommitted
   context->SetHasCommitted(true);
@@ -133,6 +137,7 @@
   EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetIsDownload
   context->SetIsDownload(true);
@@ -146,6 +151,7 @@
   EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetPost
   context->SetIsPost(true);
@@ -158,6 +164,7 @@
   EXPECT_FALSE(context->IsRendererInitiated());
   EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetErrorPage
   NSError* error = [[NSError alloc] initWithDomain:@"" code:0 userInfo:nil];
@@ -171,6 +178,7 @@
   EXPECT_FALSE(context->IsRendererInitiated());
   EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetResponseHeaders
   context->SetResponseHeaders(response_headers_);
@@ -183,6 +191,7 @@
   EXPECT_FALSE(context->IsRendererInitiated());
   EXPECT_EQ(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeOther, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
 
   // SetWKNavigationType
   context->SetWKNavigationType(WKNavigationTypeBackForward);
@@ -195,6 +204,19 @@
   EXPECT_FALSE(context->IsRendererInitiated());
   EXPECT_EQ(response_headers_.get(), context->GetResponseHeaders());
   EXPECT_EQ(WKNavigationTypeBackForward, context->GetWKNavigationType());
+  EXPECT_FALSE(context->GetMimeType());
+
+  context->SetMimeType(@"test/mime");
+  EXPECT_EQ(new_url, context->GetUrl());
+  EXPECT_TRUE(context->IsSameDocument());
+  EXPECT_TRUE(context->HasCommitted());
+  EXPECT_TRUE(context->IsDownload());
+  ASSERT_TRUE(context->IsPost());
+  EXPECT_EQ(error, context->GetError());
+  EXPECT_FALSE(context->IsRendererInitiated());
+  EXPECT_EQ(response_headers_.get(), context->GetResponseHeaders());
+  EXPECT_EQ(WKNavigationTypeBackForward, context->GetWKNavigationType());
+  EXPECT_NSEQ(@"test/mime", context->GetMimeType());
 }
 
 }  // namespace web
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 7d0d593..2d5c77f 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -1550,6 +1550,7 @@
   // Note: it is possible that the URL in item already match |url|. But item can
   // also contain a placeholder URL intended to be replaced.
   item->SetURL(URL);
+  navigationContext->SetMimeType(MIMEType);
   if (item->GetUserAgentType() == web::UserAgentType::NONE &&
       web::wk_navigation_util::URLNeedsUserAgentType(URL)) {
     item->SetUserAgentType(web::UserAgentType::MOBILE);
@@ -1712,10 +1713,6 @@
   if ([_pendingNavigationInfo referrer]) {
     _currentReferrerString = [[_pendingNavigationInfo referrer] copy];
   }
-  if ([_pendingNavigationInfo MIMEType]) {
-    self.webStateImpl->SetContentsMimeType(
-        base::SysNSStringToUTF8([_pendingNavigationInfo MIMEType]));
-  }
   [self updateCurrentBackForwardListItemHolder];
 
   _pendingNavigationInfo = nil;
@@ -4263,6 +4260,7 @@
                         placeholderNavigation:NO];
   }
   context->SetLoadingHtmlString(true);
+  context->SetMimeType(@"text/html");
   [_navigationStates setContext:std::move(context) forNavigation:navigation];
 }
 
@@ -4729,6 +4727,12 @@
     _webStateImpl->OnHttpResponseHeadersReceived(headers.get(), responseURL);
   }
 
+  if (WKResponse.forMainFrame) {
+    web::NavigationContextImpl* context =
+        [self contextForPendingMainFrameNavigationWithURL:responseURL];
+    context->SetMimeType(WKResponse.response.MIMEType);
+  }
+
   // The page will not be changed until this navigation is committed, so the
   // retrieved state will be pending until |didCommitNavigation| callback.
   [self updatePendingNavigationInfoFromNavigationResponse:WKResponse];
@@ -5031,27 +5035,12 @@
       context->SetHasCommitted(true);
     }
     context->SetResponseHeaders(_webStateImpl->GetHttpResponseHeaders());
+    self.webStateImpl->SetContentsMimeType(
+        base::SysNSStringToUTF8(context->GetMimeType()));
   }
 
   [self commitPendingNavigationInfo];
 
-  // TODO(crbug.com/925304): Pending item (which is stores
-  // currentBackForwardListItemHolder) should be owned by NavigationContext.
-  // Pending item should never be null.
-  if (self.currentNavItem) {
-    if ([self currentBackForwardListItemHolder]
-        -> navigation_type() == WKNavigationTypeBackForward) {
-      // A fast back/forward won't call decidePolicyForNavigationResponse, so
-      // the MIME type needs to be updated explicitly.
-      NSString* storedMIMEType =
-          [self currentBackForwardListItemHolder] -> mime_type();
-      if (storedMIMEType) {
-        self.webStateImpl->SetContentsMimeType(
-            base::SysNSStringToUTF8(storedMIMEType));
-      }
-    }
-  }
-
   [self removeAllWebFrames];
 
   // This point should closely approximate the document object change, so reset
@@ -5207,6 +5196,10 @@
   context->SetIsPost((holder && [holder->http_method() isEqual:@"POST"]) ||
                      item->HasPostData());
 
+  if (holder) {
+    context->SetMimeType(holder->mime_type());
+  }
+
   [_navigationStates setContext:std::move(context) forNavigation:navigation];
   [_navigationStates setState:web::WKNavigationState::REQUESTED
                 forNavigation:navigation];
@@ -6053,6 +6046,7 @@
       // |didCommitNavigation:| may not be called for fast navigation, so update
       // the navigation type now as it is already known.
       navigationContext->SetWKNavigationType(WKNavigationTypeBackForward);
+      navigationContext->SetMimeType(holder->mime_type());
       holder->set_navigation_type(WKNavigationTypeBackForward);
       navigation =
           [_webView goToBackForwardListItem:holder->back_forward_list_item()];
diff --git a/ios/web/web_state/web_state_observer_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
index 6d6265d..2f25fb1 100644
--- a/ios/web/web_state/web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -277,7 +277,8 @@
   EXPECT_FALSE((*context)->GetError());
   EXPECT_FALSE((*context)->IsRendererInitiated());
   EXPECT_FALSE((*context)->GetResponseHeaders());
-  EXPECT_FALSE(web_state->ContentIsHTML());
+  EXPECT_TRUE(web_state->ContentIsHTML());
+  EXPECT_EQ(mime_type, web_state->GetContentsMimeType());
   EXPECT_FALSE(web_state->IsLoading());
   NavigationManager* navigation_manager = web_state->GetNavigationManager();
   NavigationItem* item = navigation_manager->GetLastCommittedItem();
diff --git a/media/capture/BUILD.gn b/media/capture/BUILD.gn
index 2ff140b..c60fa8a 100644
--- a/media/capture/BUILD.gn
+++ b/media/capture/BUILD.gn
@@ -256,10 +256,14 @@
       "video/chromeos/camera_hal_dispatcher_impl.h",
       "video/chromeos/camera_metadata_utils.cc",
       "video/chromeos/camera_metadata_utils.h",
+      "video/chromeos/cros_image_capture_impl.cc",
+      "video/chromeos/cros_image_capture_impl.h",
       "video/chromeos/display_rotation_observer.cc",
       "video/chromeos/display_rotation_observer.h",
       "video/chromeos/pixel_format_utils.cc",
       "video/chromeos/pixel_format_utils.h",
+      "video/chromeos/reprocess_manager.cc",
+      "video/chromeos/reprocess_manager.h",
       "video/chromeos/request_builder.cc",
       "video/chromeos/request_builder.h",
       "video/chromeos/request_manager.cc",
diff --git a/media/capture/video/chromeos/camera_device_delegate.cc b/media/capture/video/chromeos/camera_device_delegate.cc
index ceac453b..d0c61e8 100644
--- a/media/capture/video/chromeos/camera_device_delegate.cc
+++ b/media/capture/video/chromeos/camera_device_delegate.cc
@@ -22,6 +22,7 @@
 #include "media/capture/video/chromeos/camera_device_context.h"
 #include "media/capture/video/chromeos/camera_hal_delegate.h"
 #include "media/capture/video/chromeos/camera_metadata_utils.h"
+#include "media/capture/video/chromeos/reprocess_manager.h"
 #include "media/capture/video/chromeos/request_manager.h"
 
 namespace media {
@@ -148,11 +149,13 @@
 CameraDeviceDelegate::CameraDeviceDelegate(
     VideoCaptureDeviceDescriptor device_descriptor,
     scoped_refptr<CameraHalDelegate> camera_hal_delegate,
-    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner)
+    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+    ReprocessManager* reprocess_manager)
     : device_descriptor_(device_descriptor),
       camera_id_(std::stoi(device_descriptor.device_id)),
       camera_hal_delegate_(std::move(camera_hal_delegate)),
       ipc_task_runner_(std::move(ipc_task_runner)),
+      reprocess_manager_(reprocess_manager),
       weak_ptr_factory_(this) {}
 
 CameraDeviceDelegate::~CameraDeviceDelegate() = default;
diff --git a/media/capture/video/chromeos/camera_device_delegate.h b/media/capture/video/chromeos/camera_device_delegate.h
index a94abbc..05fe7f59 100644
--- a/media/capture/video/chromeos/camera_device_delegate.h
+++ b/media/capture/video/chromeos/camera_device_delegate.h
@@ -20,6 +20,7 @@
 class Camera3AController;
 class CameraDeviceContext;
 class CameraHalDelegate;
+class ReprocessManager;
 class RequestManager;
 
 enum class StreamType : uint64_t {
@@ -66,7 +67,8 @@
   CameraDeviceDelegate(
       VideoCaptureDeviceDescriptor device_descriptor,
       scoped_refptr<CameraHalDelegate> camera_hal_delegate,
-      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner);
+      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+      ReprocessManager* reprocess_manager);
 
   ~CameraDeviceDelegate();
 
@@ -182,6 +184,8 @@
 
   VideoCaptureDevice::SetPhotoOptionsCallback set_photo_option_callback_;
 
+  ReprocessManager* reprocess_manager_;  // weak
+
   base::WeakPtrFactory<CameraDeviceDelegate> weak_ptr_factory_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(CameraDeviceDelegate);
diff --git a/media/capture/video/chromeos/camera_device_delegate_unittest.cc b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
index 41e6c60..427de1a 100644
--- a/media/capture/video/chromeos/camera_device_delegate_unittest.cc
+++ b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
@@ -19,6 +19,7 @@
 #include "media/capture/video/chromeos/camera_hal_delegate.h"
 #include "media/capture/video/chromeos/mock_camera_module.h"
 #include "media/capture/video/chromeos/mock_video_capture_client.h"
+#include "media/capture/video/chromeos/reprocess_manager.h"
 #include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
 #include "media/capture/video/mock_gpu_memory_buffer_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -120,11 +121,13 @@
     hal_delegate_thread_.Start();
     camera_hal_delegate_ =
         new CameraHalDelegate(hal_delegate_thread_.task_runner());
+    reprocess_manager_ = std::make_unique<ReprocessManager>();
     camera_hal_delegate_->SetCameraModule(
         mock_camera_module_.GetInterfacePtrInfo());
   }
 
   void TearDown() override {
+    reprocess_manager_.reset();
     camera_hal_delegate_->Reset();
     hal_delegate_thread_.Stop();
   }
@@ -134,8 +137,8 @@
     ASSERT_FALSE(camera_device_delegate_);
     device_delegate_thread_.Start();
     camera_device_delegate_ = std::make_unique<CameraDeviceDelegate>(
-        descriptor, camera_hal_delegate_,
-        device_delegate_thread_.task_runner());
+        descriptor, camera_hal_delegate_, device_delegate_thread_.task_runner(),
+        reprocess_manager_.get());
     num_streams_ = 0;
   }
 
@@ -433,6 +436,8 @@
   mojo::Binding<cros::mojom::Camera3DeviceOps> mock_camera_device_binding_;
   cros::mojom::Camera3CallbackOpsPtr callback_ops_;
 
+  std::unique_ptr<ReprocessManager> reprocess_manager_;
+
   base::Thread device_delegate_thread_;
 
   std::unique_ptr<CameraDeviceContext> device_context_;
diff --git a/media/capture/video/chromeos/camera_hal_delegate.cc b/media/capture/video/chromeos/camera_hal_delegate.cc
index 0568264..68bdbfed 100644
--- a/media/capture/video/chromeos/camera_hal_delegate.cc
+++ b/media/capture/video/chromeos/camera_hal_delegate.cc
@@ -20,6 +20,7 @@
 #include "media/capture/video/chromeos/camera_buffer_factory.h"
 #include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
 #include "media/capture/video/chromeos/camera_metadata_utils.h"
+#include "media/capture/video/chromeos/reprocess_manager.h"
 #include "media/capture/video/chromeos/video_capture_device_chromeos_halv3.h"
 
 namespace media {
@@ -124,7 +125,8 @@
 
 std::unique_ptr<VideoCaptureDevice> CameraHalDelegate::CreateDevice(
     scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_screen_observer,
-    const VideoCaptureDeviceDescriptor& device_descriptor) {
+    const VideoCaptureDeviceDescriptor& device_descriptor,
+    ReprocessManager* reprocess_manager) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   std::unique_ptr<VideoCaptureDevice> capture_device;
   if (!UpdateBuiltInCameraInfo()) {
@@ -136,7 +138,8 @@
     return capture_device;
   }
   capture_device.reset(new VideoCaptureDeviceChromeOSHalv3(
-      std::move(task_runner_for_screen_observer), device_descriptor, this));
+      std::move(task_runner_for_screen_observer), device_descriptor, this,
+      reprocess_manager));
   return capture_device;
 }
 
diff --git a/media/capture/video/chromeos/camera_hal_delegate.h b/media/capture/video/chromeos/camera_hal_delegate.h
index 6d60146..bad13be 100644
--- a/media/capture/video/chromeos/camera_hal_delegate.h
+++ b/media/capture/video/chromeos/camera_hal_delegate.h
@@ -24,6 +24,7 @@
 namespace media {
 
 class CameraBufferFactory;
+class ReprocessManager;
 
 // CameraHalDelegate is the component which does Mojo IPCs to the camera HAL
 // process on Chrome OS to access the module-level camera functionalities such
@@ -56,7 +57,8 @@
   std::unique_ptr<VideoCaptureDevice> CreateDevice(
       scoped_refptr<base::SingleThreadTaskRunner>
           task_runner_for_screen_observer,
-      const VideoCaptureDeviceDescriptor& device_descriptor);
+      const VideoCaptureDeviceDescriptor& device_descriptor,
+      ReprocessManager* reprocess_manager);
   void GetSupportedFormats(
       const VideoCaptureDeviceDescriptor& device_descriptor,
       VideoCaptureFormats* supported_formats);
diff --git a/media/capture/video/chromeos/cros_image_capture_impl.cc b/media/capture/video/chromeos/cros_image_capture_impl.cc
new file mode 100644
index 0000000..6574fef2
--- /dev/null
+++ b/media/capture/video/chromeos/cros_image_capture_impl.cc
@@ -0,0 +1,45 @@
+// 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/capture/video/chromeos/cros_image_capture_impl.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/task/post_task.h"
+
+namespace media {
+
+CrosImageCaptureImpl::CrosImageCaptureImpl(ReprocessManager* reprocess_manager)
+    : reprocess_manager_(reprocess_manager) {}
+
+CrosImageCaptureImpl::~CrosImageCaptureImpl() = default;
+
+void CrosImageCaptureImpl::BindRequest(
+    cros::mojom::CrosImageCaptureRequest request) {
+  bindings_.AddBinding(this, std::move(request));
+}
+
+void CrosImageCaptureImpl::GetSupportedEffects(
+    GetSupportedEffectsCallback callback) {
+  reprocess_manager_->GetSupportedEffects(
+      base::BindOnce(&CrosImageCaptureImpl::OnGetSupportedEffects,
+                     base::Unretained(this), std::move(callback)));
+}
+
+void CrosImageCaptureImpl::SetReprocessOption(
+    cros::mojom::Effect effect,
+    SetReprocessOptionCallback callback) {
+  reprocess_manager_->SetReprocessOption(effect, std::move(callback));
+}
+
+void CrosImageCaptureImpl::OnGetSupportedEffects(
+    GetSupportedEffectsCallback callback,
+    base::flat_set<cros::mojom::Effect> supported_effects) {
+  std::vector<cros::mojom::Effect> effects(supported_effects.begin(),
+                                           supported_effects.end());
+  std::move(callback).Run(std::move(effects));
+}
+
+}  // namespace media
diff --git a/media/capture/video/chromeos/cros_image_capture_impl.h b/media/capture/video/chromeos/cros_image_capture_impl.h
new file mode 100644
index 0000000..cc9d80c
--- /dev/null
+++ b/media/capture/video/chromeos/cros_image_capture_impl.h
@@ -0,0 +1,42 @@
+// 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_CAPTURE_VIDEO_CHROMEOS_CROS_IMAGE_CAPTURE_IMPL_H_
+#define MEDIA_CAPTURE_VIDEO_CHROMEOS_CROS_IMAGE_CAPTURE_IMPL_H_
+
+#include "base/containers/flat_set.h"
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#include "media/capture/video/chromeos/reprocess_manager.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+
+namespace media {
+
+class CrosImageCaptureImpl : public cros::mojom::CrosImageCapture {
+ public:
+  explicit CrosImageCaptureImpl(ReprocessManager* reprocess_manager);
+  ~CrosImageCaptureImpl() override;
+
+  void BindRequest(cros::mojom::CrosImageCaptureRequest request);
+
+  // cros::mojom::CrosImageCapture implementations.
+
+  void GetSupportedEffects(GetSupportedEffectsCallback callback) override;
+  void SetReprocessOption(cros::mojom::Effect effect,
+                          SetReprocessOptionCallback callback) override;
+
+ private:
+  void OnGetSupportedEffects(
+      GetSupportedEffectsCallback callback,
+      base::flat_set<cros::mojom::Effect> supported_effects);
+
+  ReprocessManager* reprocess_manager_;  // weak
+
+  mojo::BindingSet<cros::mojom::CrosImageCapture> bindings_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrosImageCaptureImpl);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_CROS_IMAGE_CAPTURE_IMPL_H_
diff --git a/media/capture/video/chromeos/mojo/BUILD.gn b/media/capture/video/chromeos/mojo/BUILD.gn
index a0372ce..ccd75a9 100644
--- a/media/capture/video/chromeos/mojo/BUILD.gn
+++ b/media/capture/video/chromeos/mojo/BUILD.gn
@@ -11,9 +11,11 @@
     "camera_metadata.mojom",
     "camera_metadata_tags.mojom",
     "cros_camera_service.mojom",
+    "cros_image_capture.mojom",
   ]
 
   deps = [
+    "//media/capture/mojom:image_capture",
     "//media/mojo/interfaces",
   ]
 }
diff --git a/media/capture/video/chromeos/mojo/cros_image_capture.mojom b/media/capture/video/chromeos/mojo/cros_image_capture.mojom
new file mode 100644
index 0000000..88b32ef
--- /dev/null
+++ b/media/capture/video/chromeos/mojo/cros_image_capture.mojom
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module cros.mojom;
+
+import "media/capture/mojom/image_capture.mojom";
+
+// Effect that recognized by Chrome OS.
+enum Effect {
+  PORTRAIT_MODE = 0,
+  NO_EFFECT
+};
+
+// Interface for Chrome OS specific Image Capture API which supports reprocess
+// mechanism.
+interface CrosImageCapture {
+  // Gets supported effects for current active device.
+  GetSupportedEffects() => (array<Effect> supported_effects);
+
+  // Sets reprocess option to bind with the coming take photo request. When this
+  // method is called, the reprocess option will be queued. All reprocess
+  // options in the queue will be consumed when TakePhoto() method in Image
+  // Capture API is triggered and all the queued reprocess options will be bound
+  // to that take photo request.
+  SetReprocessOption(Effect effect)
+      => (int32 status, media.mojom.Blob blob);
+};
\ No newline at end of file
diff --git a/media/capture/video/chromeos/reprocess_manager.cc b/media/capture/video/chromeos/reprocess_manager.cc
new file mode 100644
index 0000000..8872c7c3
--- /dev/null
+++ b/media/capture/video/chromeos/reprocess_manager.cc
@@ -0,0 +1,173 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "media/capture/video/chromeos/camera_metadata_utils.h"
+#include "media/capture/video/chromeos/reprocess_manager.h"
+
+namespace media {
+
+namespace {
+
+void OnStillCaptureDone(media::mojom::ImageCapture::TakePhotoCallback callback,
+                        int status,
+                        mojom::BlobPtr blob) {
+  std::move(callback).Run(std::move(blob));
+}
+
+}  // namespace
+
+ReprocessTask::ReprocessTask() = default;
+
+ReprocessTask::ReprocessTask(ReprocessTask&& other)
+    : effect(other.effect),
+      callback(std::move(other.callback)),
+      extra_metadata(std::move(other.extra_metadata)) {}
+
+ReprocessTask::~ReprocessTask() = default;
+
+// static
+int ReprocessManager::GetReprocessReturnCode(
+    cros::mojom::Effect effect,
+    cros::mojom::CameraMetadataPtr* metadata) {
+  if (effect == cros::mojom::Effect::PORTRAIT_MODE) {
+    auto* portrait_mode_segmentation_result = GetMetadataEntry(
+        *metadata, static_cast<cros::mojom::CameraMetadataTag>(
+                       kPortraitModeSegmentationResultVendorKey));
+    CHECK(portrait_mode_segmentation_result);
+    return static_cast<int>((*portrait_mode_segmentation_result)->data[0]);
+  }
+  return kReprocessSuccess;
+}
+
+ReprocessManager::ReprocessManager()
+    : sequenced_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
+          {base::TaskPriority::USER_VISIBLE})),
+      impl(new ReprocessManager::ReprocessManagerImpl) {}
+
+ReprocessManager::~ReprocessManager() {
+  sequenced_task_runner_->DeleteSoon(FROM_HERE, std::move(impl));
+};
+
+void ReprocessManager::SetReprocessOption(
+    cros::mojom::Effect effect,
+    cros::mojom::CrosImageCapture::SetReprocessOptionCallback
+        reprocess_result_callback) {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &ReprocessManager::ReprocessManagerImpl::SetReprocessOption,
+          base::Unretained(impl.get()), effect,
+          std::move(reprocess_result_callback)));
+}
+
+void ReprocessManager::ConsumeReprocessOptions(
+    media::mojom::ImageCapture::TakePhotoCallback take_photo_callback,
+    base::OnceCallback<void(ReprocessTaskQueue)> consumption_callback) {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &ReprocessManager::ReprocessManagerImpl::ConsumeReprocessOptions,
+          base::Unretained(impl.get()), std::move(take_photo_callback),
+          std::move(consumption_callback)));
+}
+
+void ReprocessManager::FlushReprocessOptions() {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &ReprocessManager::ReprocessManagerImpl::FlushReprocessOptions,
+          base::Unretained(impl.get())));
+}
+
+void ReprocessManager::GetSupportedEffects(
+    GetSupportedEffectsCallback callback) {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &ReprocessManager::ReprocessManagerImpl::GetSupportedEffects,
+          base::Unretained(impl.get()), std::move(callback)));
+}
+
+void ReprocessManager::UpdateSupportedEffects(
+    const cros::mojom::CameraMetadataPtr& metadata) {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &ReprocessManager::ReprocessManagerImpl::UpdateSupportedEffects,
+          base::Unretained(impl.get()), base::ConstRef(metadata)));
+}
+
+ReprocessManager::ReprocessManagerImpl::ReprocessManagerImpl() {}
+
+ReprocessManager::ReprocessManagerImpl::~ReprocessManagerImpl() = default;
+
+void ReprocessManager::ReprocessManagerImpl::SetReprocessOption(
+    cros::mojom::Effect effect,
+    cros::mojom::CrosImageCapture::SetReprocessOptionCallback
+        reprocess_result_callback) {
+  ReprocessTask task;
+  task.effect = effect;
+  task.callback = std::move(reprocess_result_callback);
+
+  if (effect == cros::mojom::Effect::PORTRAIT_MODE) {
+    std::vector<uint8_t> portrait_mode_value(sizeof(int32_t));
+    *reinterpret_cast<int32_t*>(portrait_mode_value.data()) = 1;
+    cros::mojom::CameraMetadataEntryPtr e =
+        cros::mojom::CameraMetadataEntry::New();
+    e->tag =
+        static_cast<cros::mojom::CameraMetadataTag>(kPortraitModeVendorKey);
+    e->type = cros::mojom::EntryType::TYPE_INT32;
+    e->count = 1;
+    e->data = std::move(portrait_mode_value);
+    task.extra_metadata.push_back(std::move(e));
+  }
+
+  reprocess_task_queue_.push(std::move(task));
+}
+
+void ReprocessManager::ReprocessManagerImpl::ConsumeReprocessOptions(
+    media::mojom::ImageCapture::TakePhotoCallback take_photo_callback,
+    base::OnceCallback<void(ReprocessTaskQueue)> consumption_callback) {
+  ReprocessTaskQueue result_task_queue;
+
+  ReprocessTask still_capture_task;
+  still_capture_task.effect = cros::mojom::Effect::NO_EFFECT;
+  still_capture_task.callback =
+      base::BindOnce(&OnStillCaptureDone, std::move(take_photo_callback));
+  result_task_queue.push(std::move(still_capture_task));
+
+  auto& task_queue = reprocess_task_queue_;
+  while (!task_queue.empty()) {
+    result_task_queue.push(std::move(task_queue.front()));
+    task_queue.pop();
+  }
+  std::move(consumption_callback).Run(std::move(result_task_queue));
+}
+
+void ReprocessManager::ReprocessManagerImpl::FlushReprocessOptions() {
+  auto empty_queue = ReprocessTaskQueue();
+  reprocess_task_queue_.swap(empty_queue);
+}
+
+void ReprocessManager::ReprocessManagerImpl::GetSupportedEffects(
+    GetSupportedEffectsCallback callback) {
+  std::move(callback).Run(
+      base::flat_set<cros::mojom::Effect>(supported_effects_));
+}
+
+void ReprocessManager::ReprocessManagerImpl::UpdateSupportedEffects(
+    const cros::mojom::CameraMetadataPtr& metadata) {
+  const cros::mojom::CameraMetadataEntryPtr* portrait_mode =
+      media::GetMetadataEntry(
+          metadata,
+          static_cast<cros::mojom::CameraMetadataTag>(kPortraitModeVendorKey));
+  supported_effects_.clear();
+  if (portrait_mode) {
+    supported_effects_.insert(cros::mojom::Effect::PORTRAIT_MODE);
+  }
+}
+
+}  // namespace media
diff --git a/media/capture/video/chromeos/reprocess_manager.h b/media/capture/video/chromeos/reprocess_manager.h
new file mode 100644
index 0000000..715081925
--- /dev/null
+++ b/media/capture/video/chromeos/reprocess_manager.h
@@ -0,0 +1,114 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CAPTURE_VIDEO_CHROMEOS_REPROCESS_MANAGER_H_
+#define MEDIA_CAPTURE_VIDEO_CHROMEOS_REPROCESS_MANAGER_H_
+
+#include <queue>
+#include <set>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task/post_task.h"
+#include "media/capture/capture_export.h"
+#include "media/capture/mojom/image_capture.mojom.h"
+#include "media/capture/video/chromeos/mojo/camera3.mojom.h"
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace media {
+
+struct ReprocessTask {
+ public:
+  ReprocessTask();
+  ReprocessTask(ReprocessTask&& other);
+  ~ReprocessTask();
+  cros::mojom::Effect effect;
+  cros::mojom::CrosImageCapture::SetReprocessOptionCallback callback;
+  std::vector<cros::mojom::CameraMetadataEntryPtr> extra_metadata;
+};
+
+using ReprocessTaskQueue = base::queue<ReprocessTask>;
+
+constexpr uint32_t kPortraitModeVendorKey = 0x80000000;
+constexpr uint32_t kPortraitModeSegmentationResultVendorKey = 0x80000001;
+constexpr int32_t kReprocessSuccess = 0;
+
+// ReprocessManager is used to communicate between the reprocess requester and
+// the consumer. When reprocess is requested, the reprocess information will be
+// wrapped as a ReprocessTask and stored in the queue. When consumption, all
+// ReprocessTask in the queue will be dumped and a default NO_EFFECT task will
+// be added on the top of the result queue. Note that all calls will be
+// sequentialize to a single sequence.
+class CAPTURE_EXPORT ReprocessManager {
+ public:
+  using GetSupportedEffectsCallback =
+      base::OnceCallback<void(base::flat_set<cros::mojom::Effect>)>;
+
+  class ReprocessManagerImpl {
+   public:
+    ReprocessManagerImpl();
+    ~ReprocessManagerImpl();
+
+    void SetReprocessOption(
+        cros::mojom::Effect effect,
+        cros::mojom::CrosImageCapture::SetReprocessOptionCallback
+            reprocess_result_callback);
+
+    void ConsumeReprocessOptions(
+        media::mojom::ImageCapture::TakePhotoCallback take_photo_callback,
+        base::OnceCallback<void(ReprocessTaskQueue)> consumption_callback);
+
+    void FlushReprocessOptions();
+
+    void GetSupportedEffects(GetSupportedEffectsCallback callback);
+
+    void UpdateSupportedEffects(const cros::mojom::CameraMetadataPtr& metadata);
+
+   private:
+    base::queue<ReprocessTask> reprocess_task_queue_;
+    base::flat_set<cros::mojom::Effect> supported_effects_;
+
+    DISALLOW_COPY_AND_ASSIGN(ReprocessManagerImpl);
+  };
+
+  static int GetReprocessReturnCode(cros::mojom::Effect effect,
+                                    cros::mojom::CameraMetadataPtr* metadata);
+  ReprocessManager();
+  ~ReprocessManager();
+
+  // Sets the reprocess option for given effect. Each reprocess
+  // option has a corressponding callback.
+  void SetReprocessOption(
+      cros::mojom::Effect effect,
+      cros::mojom::CrosImageCapture::SetReprocessOptionCallback
+          reprocess_result_callback);
+
+  // Consumes all ReprocessTasks in the queue. A default NO_EFFECT task will be
+  // added on the top of the result queue.
+  void ConsumeReprocessOptions(
+      media::mojom::ImageCapture::TakePhotoCallback take_photo_callback,
+      base::OnceCallback<void(ReprocessTaskQueue)> consumption_callback);
+
+  // Clears all remaining ReprocessTasks in the queue.
+  void FlushReprocessOptions();
+
+  // Gets supported effects for current active device.
+  void GetSupportedEffects(GetSupportedEffectsCallback callback);
+
+  // Updates supported effects for given active device. This method should be
+  // triggered whenever the camera characteristics is updated.
+  void UpdateSupportedEffects(const cros::mojom::CameraMetadataPtr& metadata);
+
+ private:
+  scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
+  std::unique_ptr<ReprocessManagerImpl> impl;
+
+  DISALLOW_COPY_AND_ASSIGN(ReprocessManager);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_REPROCESS_MANAGER_H_
diff --git a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc
index 8ca3677..9b67f893 100644
--- a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc
+++ b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc
@@ -19,6 +19,7 @@
 #include "media/capture/video/chromeos/camera_device_context.h"
 #include "media/capture/video/chromeos/camera_device_delegate.h"
 #include "media/capture/video/chromeos/camera_hal_delegate.h"
+#include "media/capture/video/chromeos/reprocess_manager.h"
 #include "ui/display/display.h"
 #include "ui/display/display_observer.h"
 #include "ui/display/screen.h"
@@ -28,7 +29,8 @@
 VideoCaptureDeviceChromeOSHalv3::VideoCaptureDeviceChromeOSHalv3(
     scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_screen_observer,
     const VideoCaptureDeviceDescriptor& device_descriptor,
-    scoped_refptr<CameraHalDelegate> camera_hal_delegate)
+    scoped_refptr<CameraHalDelegate> camera_hal_delegate,
+    ReprocessManager* reprocess_manager)
     : device_descriptor_(device_descriptor),
       camera_hal_delegate_(std::move(camera_hal_delegate)),
       capture_task_runner_(base::ThreadTaskRunnerHandle::Get()),
@@ -44,6 +46,7 @@
       rotates_with_device_(lens_facing_ !=
                            VideoFacingMode::MEDIA_VIDEO_FACING_NONE),
       rotation_(0),
+      reprocess_manager_(reprocess_manager),
       weak_ptr_factory_(this) {
   chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(
       this);
@@ -76,7 +79,7 @@
   device_context_ = std::make_unique<CameraDeviceContext>(std::move(client));
   camera_device_delegate_ = std::make_unique<CameraDeviceDelegate>(
       device_descriptor_, camera_hal_delegate_,
-      camera_device_ipc_thread_.task_runner());
+      camera_device_ipc_thread_.task_runner(), reprocess_manager_);
   OpenDevice();
 }
 
diff --git a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h
index b08e093..492755d8 100644
--- a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h
+++ b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h
@@ -28,6 +28,7 @@
 class CameraHalDelegate;
 class CameraDeviceContext;
 class CameraDeviceDelegate;
+class ReprocessManager;
 
 // Implementation of VideoCaptureDevice for ChromeOS with CrOS camera HALv3.
 class CAPTURE_EXPORT VideoCaptureDeviceChromeOSHalv3 final
@@ -39,7 +40,8 @@
       scoped_refptr<base::SingleThreadTaskRunner>
           task_runner_for_screen_observer,
       const VideoCaptureDeviceDescriptor& device_descriptor,
-      scoped_refptr<CameraHalDelegate> camera_hal_delegate);
+      scoped_refptr<CameraHalDelegate> camera_hal_delegate,
+      ReprocessManager* reprocess_manager);
 
   ~VideoCaptureDeviceChromeOSHalv3() final;
 
@@ -99,6 +101,8 @@
   const bool rotates_with_device_;
   int rotation_;
 
+  ReprocessManager* reprocess_manager_;  // weak
+
   base::WeakPtrFactory<VideoCaptureDeviceChromeOSHalv3> weak_ptr_factory_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureDeviceChromeOSHalv3);
diff --git a/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc b/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
index 6fb9ccac..6d3ad46 100644
--- a/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
+++ b/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
@@ -4,8 +4,12 @@
 
 #include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
 
+#include <utility>
+
 #include "base/memory/ptr_util.h"
 #include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
+#include "media/capture/video/chromeos/cros_image_capture_impl.h"
+#include "media/capture/video/chromeos/reprocess_manager.h"
 
 namespace media {
 
@@ -19,6 +23,8 @@
     scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_screen_observer)
     : task_runner_for_screen_observer_(task_runner_for_screen_observer),
       camera_hal_ipc_thread_("CameraHalIpcThread"),
+      reprocess_manager_(new ReprocessManager),
+      cros_image_capture_(new CrosImageCaptureImpl(reprocess_manager_.get())),
       initialized_(Init()) {}
 
 VideoCaptureDeviceFactoryChromeOS::~VideoCaptureDeviceFactoryChromeOS() {
@@ -34,7 +40,8 @@
     return std::unique_ptr<VideoCaptureDevice>();
   }
   return camera_hal_delegate_->CreateDevice(task_runner_for_screen_observer_,
-                                            device_descriptor);
+                                            device_descriptor,
+                                            reprocess_manager_.get());
 }
 
 void VideoCaptureDeviceFactoryChromeOS::GetSupportedFormats(
@@ -83,4 +90,9 @@
   return true;
 }
 
+void VideoCaptureDeviceFactoryChromeOS::BindCrosImageCaptureRequest(
+    cros::mojom::CrosImageCaptureRequest request) {
+  cros_image_capture_->BindRequest(std::move(request));
+}
+
 }  // namespace media
diff --git a/media/capture/video/chromeos/video_capture_device_factory_chromeos.h b/media/capture/video/chromeos/video_capture_device_factory_chromeos.h
index bc452c3..56c74b52 100644
--- a/media/capture/video/chromeos/video_capture_device_factory_chromeos.h
+++ b/media/capture/video/chromeos/video_capture_device_factory_chromeos.h
@@ -10,10 +10,14 @@
 #include "base/macros.h"
 #include "base/single_thread_task_runner.h"
 #include "media/capture/video/chromeos/camera_hal_delegate.h"
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
 #include "media/capture/video/video_capture_device_factory.h"
 
 namespace media {
 
+class CrosImageCaptureImpl;
+class ReprocessManager;
+
 class CAPTURE_EXPORT VideoCaptureDeviceFactoryChromeOS final
     : public VideoCaptureDeviceFactory {
  public:
@@ -35,6 +39,9 @@
   static gpu::GpuMemoryBufferManager* GetBufferManager();
   static void SetGpuBufferManager(gpu::GpuMemoryBufferManager* buffer_manager);
 
+  void BindCrosImageCaptureRequest(
+      cros::mojom::CrosImageCaptureRequest request);
+
  private:
   // Initializes the factory. The factory is functional only after this call
   // succeeds.
@@ -53,6 +60,10 @@
   // |camera_hal_ipc_thread_|.
   scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
 
+  std::unique_ptr<ReprocessManager> reprocess_manager_;
+
+  std::unique_ptr<CrosImageCaptureImpl> cros_image_capture_;
+
   bool initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(VideoCaptureDeviceFactoryChromeOS);
diff --git a/media/capture/video/video_capture_system.h b/media/capture/video/video_capture_system.h
index 016b8e2..c4cdaaf 100644
--- a/media/capture/video/video_capture_system.h
+++ b/media/capture/video/video_capture_system.h
@@ -8,6 +8,10 @@
 #include "media/capture/video/video_capture_device_factory.h"
 #include "media/capture/video/video_capture_device_info.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace media {
 
 // GetDeviceInfosAsync() should be called at least once before calling
@@ -29,6 +33,12 @@
   // wrong.
   virtual std::unique_ptr<VideoCaptureDevice> CreateDevice(
       const std::string& device_id) = 0;
+
+#if defined(OS_CHROMEOS)
+  // Pass the mojo request to bind with DeviceFactory for Chrome OS.
+  virtual void BindCrosImageCaptureRequest(
+      cros::mojom::CrosImageCaptureRequest request) = 0;
+#endif  // defined(OS_CHROMEOS)
 };
 
 }  // namespace media
diff --git a/media/capture/video/video_capture_system_impl.cc b/media/capture/video/video_capture_system_impl.cc
index ef82af1..966c3ee 100644
--- a/media/capture/video/video_capture_system_impl.cc
+++ b/media/capture/video/video_capture_system_impl.cc
@@ -4,11 +4,18 @@
 
 #include "media/capture/video/video_capture_system_impl.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "build/build_config.h"
 #include "media/base/bind_to_current_loop.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/public/cros_features.h"
+#include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace {
 
 // Compares two VideoCaptureFormat by checking smallest frame_size area, then
@@ -156,4 +163,16 @@
   ProcessDeviceInfoRequest();
 }
 
+#if defined(OS_CHROMEOS)
+void VideoCaptureSystemImpl::BindCrosImageCaptureRequest(
+    cros::mojom::CrosImageCaptureRequest request) {
+  CHECK(factory_);
+
+  if (media::ShouldUseCrosCameraService()) {
+    static_cast<VideoCaptureDeviceFactoryChromeOS*>(factory_.get())
+        ->BindCrosImageCaptureRequest(std::move(request));
+  }
+}
+#endif  // defined(OS_CHROMEOS)
+
 }  // namespace media
diff --git a/media/capture/video/video_capture_system_impl.h b/media/capture/video/video_capture_system_impl.h
index 581979a..0cbd5c8 100644
--- a/media/capture/video/video_capture_system_impl.h
+++ b/media/capture/video/video_capture_system_impl.h
@@ -7,6 +7,10 @@
 
 #include "media/capture/video/video_capture_system.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace media {
 
 // Layer on top of VideoCaptureDeviceFactory that translates device descriptors
@@ -22,6 +26,11 @@
   std::unique_ptr<VideoCaptureDevice> CreateDevice(
       const std::string& device_id) override;
 
+#if defined(OS_CHROMEOS)
+  void BindCrosImageCaptureRequest(
+      cros::mojom::CrosImageCaptureRequest request) override;
+#endif  // defined(OS_CHROMEOS)
+
  private:
   using DeviceEnumQueue = std::list<DeviceInfoCallback>;
 
diff --git a/media/filters/BUILD.gn b/media/filters/BUILD.gn
index f551991..81c1e4fc 100644
--- a/media/filters/BUILD.gn
+++ b/media/filters/BUILD.gn
@@ -47,8 +47,6 @@
     "frame_processor.h",
     "gpu_video_decoder.cc",
     "gpu_video_decoder.h",
-    "jpeg_parser.cc",
-    "jpeg_parser.h",
     "memory_data_source.cc",
     "memory_data_source.h",
     "offloading_video_decoder.cc",
@@ -96,6 +94,10 @@
     "//media:subcomponent_config",
   ]
 
+  public_deps = [
+    ":jpeg_parser",
+  ]
+
   deps = [
     "//cc/base",  # For MathUtil.
     "//gpu/command_buffer/common",
@@ -229,6 +231,34 @@
   }
 }
 
+# This component allows other targets to use the JPEG parser as a standalone,
+# general-purpose utility without having to pull all of //media as a dependency
+# (which could potentially result in cycles).
+component("jpeg_parser") {
+  output_name = "media_filters_jpeg_parser"
+  sources = [
+    "jpeg_parser.cc",
+    "jpeg_parser.h",
+  ]
+  defines = [ "IS_JPEG_PARSER_IMPL" ]
+  deps = [
+    "//base",
+  ]
+}
+
+source_set("jpeg_parser_unit_tests") {
+  testonly = true
+  sources = [
+    "jpeg_parser_unittest.cc",
+  ]
+  deps = [
+    ":jpeg_parser",
+    "//base",
+    "//media:test_support",
+    "//testing/gtest",
+  ]
+}
+
 source_set("perftests") {
   testonly = true
   sources = []
@@ -288,7 +318,6 @@
     "frame_buffer_pool_unittest.cc",
     "frame_processor_unittest.cc",
     "ivf_parser_unittest.cc",
-    "jpeg_parser_unittest.cc",
     "memory_data_source_unittest.cc",
     "offloading_video_decoder_unittest.cc",
     "pipeline_controller_unittest.cc",
@@ -304,6 +333,7 @@
   ]
 
   deps = [
+    ":jpeg_parser_unit_tests",
     "//base/test:test_support",
     "//media:test_support",
     "//testing/gmock",
diff --git a/media/filters/jpeg_parser.h b/media/filters/jpeg_parser.h
index c616123..39df6b1 100644
--- a/media/filters/jpeg_parser.h
+++ b/media/filters/jpeg_parser.h
@@ -7,7 +7,8 @@
 
 #include <stddef.h>
 #include <stdint.h>
-#include "media/base/media_export.h"
+
+#include "base/component_export.h"
 
 namespace media {
 
@@ -80,12 +81,12 @@
 };
 
 // K.3.3.1 "Specification of typical tables for DC difference coding"
-MEDIA_EXPORT extern const JpegHuffmanTable
-    kDefaultDcTable[kJpegMaxHuffmanTableNumBaseline];
+COMPONENT_EXPORT(JPEG_PARSER)
+extern const JpegHuffmanTable kDefaultDcTable[kJpegMaxHuffmanTableNumBaseline];
 
 // K.3.3.2 "Specification of typical tables for AC coefficient coding"
-MEDIA_EXPORT extern const JpegHuffmanTable
-    kDefaultAcTable[kJpegMaxHuffmanTableNumBaseline];
+COMPONENT_EXPORT(JPEG_PARSER)
+extern const JpegHuffmanTable kDefaultAcTable[kJpegMaxHuffmanTableNumBaseline];
 
 // Parsing result of JPEG DQT marker.
 struct JpegQuantizationTable {
@@ -93,11 +94,12 @@
   uint8_t value[kDctSize];  // baseline only supports 8 bits quantization table
 };
 
-MEDIA_EXPORT extern const uint8_t kZigZag8x8[64];
+COMPONENT_EXPORT(JPEG_PARSER) extern const uint8_t kZigZag8x8[64];
 
 // Table K.1 Luminance quantization table
 // Table K.2 Chrominance quantization table
-MEDIA_EXPORT extern const JpegQuantizationTable kDefaultQuantTable[2];
+COMPONENT_EXPORT(JPEG_PARSER)
+extern const JpegQuantizationTable kDefaultQuantTable[2];
 
 // Parsing result of a JPEG component.
 struct JpegComponent {
@@ -144,16 +146,18 @@
 // Parses JPEG picture in |buffer| with |length|.  Returns true iff header is
 // valid and JPEG baseline sequential process is present. If parsed
 // successfully, |result| is the parsed result.
-MEDIA_EXPORT bool ParseJpegPicture(const uint8_t* buffer,
-                                   size_t length,
-                                   JpegParseResult* result);
+COMPONENT_EXPORT(JPEG_PARSER)
+bool ParseJpegPicture(const uint8_t* buffer,
+                      size_t length,
+                      JpegParseResult* result);
 
 // Parses the first image of JPEG stream in |buffer| with |length|.  Returns
 // true iff header is valid and JPEG baseline sequential process is present.
 // If parsed successfully, |result| is the parsed result.
-MEDIA_EXPORT bool ParseJpegStream(const uint8_t* buffer,
-                                  size_t length,
-                                  JpegParseResult* result);
+COMPONENT_EXPORT(JPEG_PARSER)
+bool ParseJpegStream(const uint8_t* buffer,
+                     size_t length,
+                     JpegParseResult* result);
 
 }  // namespace media
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index ecf7245..db0ddad1 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1481,6 +1481,7 @@
       "third_party/quic/platform/api/quic_clock.h",
       "third_party/quic/platform/api/quic_containers.h",
       "third_party/quic/platform/api/quic_endian.h",
+      "third_party/quic/platform/api/quic_error_code_wrappers.h",
       "third_party/quic/platform/api/quic_estimate_memory_usage.h",
       "third_party/quic/platform/api/quic_export.h",
       "third_party/quic/platform/api/quic_exported_stats.h",
@@ -1497,6 +1498,7 @@
       "third_party/quic/platform/api/quic_ip_address.h",
       "third_party/quic/platform/api/quic_ip_address_family.h",
       "third_party/quic/platform/api/quic_logging.h",
+      "third_party/quic/platform/api/quic_macros.h",
       "third_party/quic/platform/api/quic_map_util.h",
       "third_party/quic/platform/api/quic_mem_slice.h",
       "third_party/quic/platform/api/quic_mem_slice_span.h",
@@ -1526,6 +1528,7 @@
       "third_party/quic/platform/impl/quic_client_stats_impl.h",
       "third_party/quic/platform/impl/quic_containers_impl.h",
       "third_party/quic/platform/impl/quic_endian_impl.h",
+      "third_party/quic/platform/impl/quic_error_code_wrappers_impl.h",
       "third_party/quic/platform/impl/quic_estimate_memory_usage_impl.h",
       "third_party/quic/platform/impl/quic_export_impl.h",
       "third_party/quic/platform/impl/quic_fallthrough_impl.h",
@@ -1540,6 +1543,7 @@
       "third_party/quic/platform/impl/quic_ip_address_impl.cc",
       "third_party/quic/platform/impl/quic_ip_address_impl.h",
       "third_party/quic/platform/impl/quic_logging_impl.h",
+      "third_party/quic/platform/impl/quic_macros_impl.h",
       "third_party/quic/platform/impl/quic_map_util_impl.h",
       "third_party/quic/platform/impl/quic_mem_slice_impl.cc",
       "third_party/quic/platform/impl/quic_mem_slice_impl.h",
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index c5af703..9295d8cdf 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -688,6 +688,7 @@
     CertVerifier* /* cert_verifier */)
     : ParentPool(0,
                  0,
+                 base::TimeDelta(),
                  NULL,
                  host_resolver,
                  NULL,
@@ -14262,8 +14263,9 @@
   // each round of multi-round authentication.
   HttpNetworkSessionPeer session_peer(session.get());
   TransportClientSocketPool* transport_pool = new TransportClientSocketPool(
-      50,  // Max sockets for pool
-      1,   // Max sockets per group
+      50,                                // Max sockets for pool
+      1,                                 // Max sockets per group
+      base::TimeDelta::FromSeconds(10),  // unused_idle_socket_timeout
       session_deps_.socket_factory.get(), session_deps_.host_resolver.get(),
       nullptr /* proxy_delegate */, session_deps_.cert_verifier.get(),
       session_deps_.channel_id_service.get(),
diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_unittest.cc
index 0ade6427..457b4a9 100644
--- a/net/http/http_proxy_client_socket_pool_unittest.cc
+++ b/net/http/http_proxy_client_socket_pool_unittest.cc
@@ -49,6 +49,8 @@
 
 const int kMaxSockets = 32;
 const int kMaxSocketsPerGroup = 6;
+constexpr base::TimeDelta kUnusedIdleSocketTimeout =
+    base::TimeDelta::FromSeconds(10);
 const char * const kAuthHeaders[] = {
   "proxy-authorization", "Basic Zm9vOmJhcg=="
 };
@@ -73,6 +75,7 @@
       : pool_(std::make_unique<TransportClientSocketPool>(
             kMaxSockets,
             kMaxSocketsPerGroup,
+            kUnusedIdleSocketTimeout,
             &socket_factory_,
             session_deps_.host_resolver.get(),
             nullptr /* proxy_delegate */,
@@ -95,8 +98,8 @@
 
   void InitPoolWithProxyDelegate(ProxyDelegate* proxy_delegate) {
     pool_ = std::make_unique<TransportClientSocketPool>(
-        kMaxSockets, kMaxSocketsPerGroup, &socket_factory_,
-        session_deps_.host_resolver.get(), proxy_delegate,
+        kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+        &socket_factory_, session_deps_.host_resolver.get(), proxy_delegate,
         session_deps_.cert_verifier.get(),
         session_deps_.channel_id_service.get(),
         session_deps_.transport_security_state.get(),
diff --git a/net/http/http_proxy_connect_job_unittest.cc b/net/http/http_proxy_connect_job_unittest.cc
index ca9b002..e55fa43a 100644
--- a/net/http/http_proxy_connect_job_unittest.cc
+++ b/net/http/http_proxy_connect_job_unittest.cc
@@ -56,6 +56,7 @@
       : transport_socket_pool_(
             32 /* max_sockets */,
             6 /* max_sockets_pre_group */,
+            base::TimeDelta::FromSeconds(10) /* unused_idle_socket_timeout */,
             &socket_factory_,
             session_deps_.host_resolver.get(),
             nullptr /* proxy_delegate */,
@@ -70,22 +71,24 @@
             nullptr /* socket_performance_watcher_factory */,
             nullptr /* network_quality_estimator */,
             nullptr /* net_log */),
-        ssl_socket_pool_(32 /* max_sockets */,
-                         6 /* max_sockets_pre_group */,
-                         &socket_factory_,
-                         session_deps_.host_resolver.get(),
-                         nullptr /* proxy_delegate */,
-                         session_deps_.cert_verifier.get(),
-                         session_deps_.channel_id_service.get(),
-                         session_deps_.transport_security_state.get(),
-                         session_deps_.cert_transparency_verifier.get(),
-                         session_deps_.ct_policy_enforcer.get(),
-                         nullptr /* ssl_client_session_cache */,
-                         nullptr /* ssl_client_session_cache_privacy_mode */,
-                         session_deps_.ssl_config_service.get(),
-                         nullptr /* socket_performance_watcher_factory */,
-                         nullptr /* network_quality_estimator */,
-                         nullptr /* net_log */),
+        ssl_socket_pool_(
+            32 /* max_sockets */,
+            6 /* max_sockets_pre_group */,
+            base::TimeDelta::FromSeconds(10) /* unused_idle_socket_timeout */,
+            &socket_factory_,
+            session_deps_.host_resolver.get(),
+            nullptr /* proxy_delegate */,
+            session_deps_.cert_verifier.get(),
+            session_deps_.channel_id_service.get(),
+            session_deps_.transport_security_state.get(),
+            session_deps_.cert_transparency_verifier.get(),
+            session_deps_.ct_policy_enforcer.get(),
+            nullptr /* ssl_client_session_cache */,
+            nullptr /* ssl_client_session_cache_privacy_mode */,
+            session_deps_.ssl_config_service.get(),
+            nullptr /* socket_performance_watcher_factory */,
+            nullptr /* network_quality_estimator */,
+            nullptr /* net_log */),
         field_trial_list_(nullptr) {
     session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
   }
diff --git a/net/http/http_stream_factory_unittest.cc b/net/http/http_stream_factory_unittest.cc
index 388333e..216f10b 100644
--- a/net/http/http_stream_factory_unittest.cc
+++ b/net/http/http_stream_factory_unittest.cc
@@ -470,6 +470,7 @@
     CTPolicyEnforcer*)
     : ParentPool(0,
                  0,
+                 base::TimeDelta(),
                  nullptr /* socket_factory */,
                  host_resolver,
                  nullptr /* proxy_delegate */,
diff --git a/net/network_error_logging/network_error_logging_service_unittest.cc b/net/network_error_logging/network_error_logging_service_unittest.cc
index 0d57ed8..d660b6d 100644
--- a/net/network_error_logging/network_error_logging_service_unittest.cc
+++ b/net/network_error_logging/network_error_logging_service_unittest.cc
@@ -838,7 +838,8 @@
       kServerIP_, kHeaderWrongTypes);
 
   base::Value actual = service()->StatusAsValue();
-  std::unique_ptr<base::Value> expected = base::test::ParseJson(R"json(
+  std::unique_ptr<base::Value> expected =
+      base::test::ParseJsonDeprecated(R"json(
       {
         "originPolicies": [
           {
diff --git a/net/nqe/network_quality_estimator.cc b/net/nqe/network_quality_estimator.cc
index 6ff54cb..fa5338af 100644
--- a/net/nqe/network_quality_estimator.cc
+++ b/net/nqe/network_quality_estimator.cc
@@ -1063,17 +1063,8 @@
             nqe::internal::InvalidRTT() &&
         *http_rtt >= params_->ConnectionThreshold(type).http_rtt();
 
-    const bool estimated_throughput_is_lower_than_threshold =
-        *downstream_throughput_kbps != nqe::internal::INVALID_RTT_THROUGHPUT &&
-        params_->ConnectionThreshold(type).downstream_throughput_kbps() !=
-            nqe::internal::INVALID_RTT_THROUGHPUT &&
-        *downstream_throughput_kbps <=
-            params_->ConnectionThreshold(type).downstream_throughput_kbps();
-
-    if (estimated_http_rtt_is_higher_than_threshold ||
-        estimated_throughput_is_lower_than_threshold) {
+    if (estimated_http_rtt_is_higher_than_threshold)
       return type;
-    }
   }
   // Return the fastest connection type.
   return static_cast<EffectiveConnectionType>(EFFECTIVE_CONNECTION_TYPE_LAST -
diff --git a/net/nqe/network_quality_estimator_params.cc b/net/nqe/network_quality_estimator_params.cc
index c7fdb0a6..ca44e02 100644
--- a/net/nqe/network_quality_estimator_params.cc
+++ b/net/nqe/network_quality_estimator_params.cc
@@ -381,14 +381,11 @@
                 .http_rtt()
                 .InMilliseconds())));
 
-    connection_thresholds[i].set_transport_rtt(
-        default_effective_connection_type_thresholds[i].transport_rtt());
-
-    connection_thresholds[i].set_downstream_throughput_kbps(
-        GetValueForVariationParam(
-            params, connection_type_name + ".ThresholdMedianKbps",
-            default_effective_connection_type_thresholds[i]
-                .downstream_throughput_kbps()));
+    DCHECK_EQ(nqe::internal::InvalidRTT(),
+              default_effective_connection_type_thresholds[i].transport_rtt());
+    DCHECK_EQ(nqe::internal::INVALID_RTT_THROUGHPUT,
+              default_effective_connection_type_thresholds[i]
+                  .downstream_throughput_kbps());
     DCHECK(i == 0 ||
            connection_thresholds[i].IsFaster(connection_thresholds[i - 1]));
   }
diff --git a/net/nqe/network_quality_estimator_unittest.cc b/net/nqe/network_quality_estimator_unittest.cc
index 634485b..e7d1f05 100644
--- a/net/nqe/network_quality_estimator_unittest.cc
+++ b/net/nqe/network_quality_estimator_unittest.cc
@@ -1173,11 +1173,6 @@
   variation_params["2G.ThresholdMedianHttpRTTMsec"] = "1000";
   variation_params["3G.ThresholdMedianHttpRTTMsec"] = "500";
 
-  variation_params["Offline.ThresholdMedianKbps"] = "10";
-  variation_params["Slow2G.ThresholdMedianKbps"] = "100";
-  variation_params["2G.ThresholdMedianKbps"] = "300";
-  variation_params["3G.ThresholdMedianKbps"] = "500";
-
   TestNetworkQualityEstimator estimator(variation_params);
 
   // Simulate the connection type as Wi-Fi so that GetEffectiveConnectionType
@@ -1190,20 +1185,6 @@
     int32_t downlink_throughput_kbps;
     EffectiveConnectionType expected_ect;
   } tests[] = {
-      // Set RTT to a very low value to observe the effect of throughput.
-      // Throughput is the bottleneck.
-      {1, 5, EFFECTIVE_CONNECTION_TYPE_OFFLINE},
-      {1, 10, EFFECTIVE_CONNECTION_TYPE_OFFLINE},
-      {1, 50, EFFECTIVE_CONNECTION_TYPE_SLOW_2G},
-      {1, 100, EFFECTIVE_CONNECTION_TYPE_SLOW_2G},
-      {1, 150, EFFECTIVE_CONNECTION_TYPE_2G},
-      {1, 300, EFFECTIVE_CONNECTION_TYPE_2G},
-      {1, 400, EFFECTIVE_CONNECTION_TYPE_3G},
-      {1, 500, EFFECTIVE_CONNECTION_TYPE_3G},
-      {1, 700, EFFECTIVE_CONNECTION_TYPE_4G},
-      {1, 1000, EFFECTIVE_CONNECTION_TYPE_4G},
-      {1, 1500, EFFECTIVE_CONNECTION_TYPE_4G},
-      {1, 2500, EFFECTIVE_CONNECTION_TYPE_4G},
       // Set both RTT and throughput. RTT is the bottleneck.
       {3000, 25000, EFFECTIVE_CONNECTION_TYPE_SLOW_2G},
       {700, 25000, EFFECTIVE_CONNECTION_TYPE_3G},
diff --git a/net/reporting/reporting_cache_unittest.cc b/net/reporting/reporting_cache_unittest.cc
index a6d1f6a3..b76fb24 100644
--- a/net/reporting/reporting_cache_unittest.cc
+++ b/net/reporting/reporting_cache_unittest.cc
@@ -286,7 +286,8 @@
   cache()->RemoveReports({report2}, ReportingReport::Outcome::UNKNOWN);
 
   base::Value actual = cache()->GetReportsAsValue();
-  std::unique_ptr<base::Value> expected = base::test::ParseJson(R"json(
+  std::unique_ptr<base::Value> expected =
+      base::test::ParseJsonDeprecated(R"json(
       [
         {
           "url": "https://origin1/path",
@@ -429,7 +430,8 @@
   cache()->IncrementEndpointDeliveries(kOrigin2_, kEndpoint2_, 1, false);
 
   base::Value actual = cache()->GetClientsAsValue();
-  std::unique_ptr<base::Value> expected = base::test::ParseJson(R"json(
+  std::unique_ptr<base::Value> expected =
+      base::test::ParseJsonDeprecated(R"json(
       [
         {
           "origin": "https://origin1",
diff --git a/net/socket/client_socket_pool.cc b/net/socket/client_socket_pool.cc
index 4c9f13d..f68929d 100644
--- a/net/socket/client_socket_pool.cc
+++ b/net/socket/client_socket_pool.cc
@@ -8,12 +8,6 @@
 
 namespace {
 
-// The maximum duration, in seconds, to keep unused idle persistent sockets
-// alive.
-// TODO(ziadh): Change this timeout after getting histogram data on how long it
-// should be.
-int64_t g_unused_idle_socket_timeout_s = 10;
-
 // The maximum duration, in seconds, to keep used idle persistent sockets alive.
 int64_t g_used_idle_socket_timeout_s = 300;  // 5 minutes
 
@@ -22,17 +16,6 @@
 namespace net {
 
 // static
-base::TimeDelta ClientSocketPool::unused_idle_socket_timeout() {
-  return base::TimeDelta::FromSeconds(g_unused_idle_socket_timeout_s);
-}
-
-// static
-void ClientSocketPool::set_unused_idle_socket_timeout(base::TimeDelta timeout) {
-  DCHECK_GT(timeout.InSeconds(), 0);
-  g_unused_idle_socket_timeout_s = timeout.InSeconds();
-}
-
-// static
 base::TimeDelta ClientSocketPool::used_idle_socket_timeout() {
   return base::TimeDelta::FromSeconds(g_used_idle_socket_timeout_s);
 }
diff --git a/net/socket/client_socket_pool.h b/net/socket/client_socket_pool.h
index 069f1711..7894baf 100644
--- a/net/socket/client_socket_pool.h
+++ b/net/socket/client_socket_pool.h
@@ -188,9 +188,6 @@
   // Returns the maximum amount of time to wait before retrying a connect.
   static const int kMaxConnectRetryIntervalMs = 250;
 
-  static base::TimeDelta unused_idle_socket_timeout();
-  static void set_unused_idle_socket_timeout(base::TimeDelta timeout);
-
   static base::TimeDelta used_idle_socket_timeout();
   static void set_used_idle_socket_timeout(base::TimeDelta timeout);
 
diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc
index 157503a..164b7f8 100644
--- a/net/socket/client_socket_pool_base_unittest.cc
+++ b/net/socket/client_socket_pool_base_unittest.cc
@@ -63,6 +63,8 @@
 
 const int kDefaultMaxSockets = 4;
 const int kDefaultMaxSocketsPerGroup = 2;
+constexpr base::TimeDelta kUnusedIdleSocketTimeout =
+    base::TimeDelta::FromSeconds(10);
 
 // Make sure |handle| sets load times correctly when it has been assigned a
 // reused socket.
@@ -739,11 +741,9 @@
   }
 
   void CreatePool(int max_sockets, int max_sockets_per_group) {
-    CreatePoolWithIdleTimeouts(
-        max_sockets,
-        max_sockets_per_group,
-        ClientSocketPool::unused_idle_socket_timeout(),
-        ClientSocketPool::used_idle_socket_timeout());
+    CreatePoolWithIdleTimeouts(max_sockets, max_sockets_per_group,
+                               kUnusedIdleSocketTimeout,
+                               ClientSocketPool::used_idle_socket_timeout());
   }
 
   void CreatePoolWithIdleTimeouts(
diff --git a/net/socket/client_socket_pool_manager.cc b/net/socket/client_socket_pool_manager.cc
index bf468c1..c16de189 100644
--- a/net/socket/client_socket_pool_manager.cc
+++ b/net/socket/client_socket_pool_manager.cc
@@ -62,6 +62,8 @@
   kDefaultMaxSocketsPerProxyServer   // WEBSOCKET_SOCKET_POOL
 };
 
+base::TimeDelta g_unused_idle_socket_timeout = base::TimeDelta::FromSeconds(10);
+
 static_assert(base::size(g_max_sockets_per_proxy_server) ==
                   HttpNetworkSession::NUM_SOCKET_POOL_TYPES,
               "max sockets per proxy server length mismatch");
@@ -356,6 +358,19 @@
   g_max_sockets_per_proxy_server[pool_type] = socket_count;
 }
 
+// static
+base::TimeDelta ClientSocketPoolManager::unused_idle_socket_timeout(
+    HttpNetworkSession::SocketPoolType pool_type) {
+  return g_unused_idle_socket_timeout;
+}
+
+// static
+void ClientSocketPoolManager::set_unused_idle_socket_timeout(
+    HttpNetworkSession::SocketPoolType pool_type,
+    base::TimeDelta timeout) {
+  g_unused_idle_socket_timeout = timeout;
+}
+
 int InitSocketHandleForHttpRequest(
     ClientSocketPoolManager::SocketGroupType group_type,
     const HostPortPair& endpoint,
diff --git a/net/socket/client_socket_pool_manager.h b/net/socket/client_socket_pool_manager.h
index a1f86f77..91d6b58 100644
--- a/net/socket/client_socket_pool_manager.h
+++ b/net/socket/client_socket_pool_manager.h
@@ -75,6 +75,12 @@
       HttpNetworkSession::SocketPoolType pool_type,
       int socket_count);
 
+  static base::TimeDelta unused_idle_socket_timeout(
+      HttpNetworkSession::SocketPoolType pool_type);
+  static void set_unused_idle_socket_timeout(
+      HttpNetworkSession::SocketPoolType pool_type,
+      base::TimeDelta timeout);
+
   virtual void FlushSocketPoolsWithError(int error) = 0;
   virtual void CloseIdleSockets() = 0;
   // Returns the socket pool for direct HTTP and SSL connections.
diff --git a/net/socket/client_socket_pool_manager_impl.cc b/net/socket/client_socket_pool_manager_impl.cc
index 288dfd8..dcd7affb 100644
--- a/net/socket/client_socket_pool_manager_impl.cc
+++ b/net/socket/client_socket_pool_manager_impl.cc
@@ -76,6 +76,7 @@
               ? std::make_unique<WebSocketTransportClientSocketPool>(
                     max_sockets_per_pool(pool_type),
                     max_sockets_per_group(pool_type),
+                    unused_idle_socket_timeout(pool_type),
                     socket_factory_,
                     host_resolver,
                     proxy_delegate,
@@ -178,7 +179,6 @@
           http_proxy,
           CreateTransportSocketPool(
               http_proxy, true /* use_socket_performance_watcher_factory */)));
-
   return ret.first->second.get();
 }
 
@@ -196,12 +196,13 @@
   int sockets_per_proxy_server = max_sockets_per_proxy_server(pool_type_);
   int sockets_per_group = std::min(sockets_per_proxy_server,
                                    max_sockets_per_group(pool_type_));
-
   std::pair<TransportSocketPoolMap::iterator, bool> ret =
       ssl_socket_pools_for_proxies_.insert(std::make_pair(
           proxy_server,
           std::make_unique<TransportClientSocketPool>(
-              sockets_per_proxy_server, sockets_per_group, socket_factory_,
+
+              sockets_per_proxy_server, sockets_per_group,
+              unused_idle_socket_timeout(pool_type_), socket_factory_,
               host_resolver_, proxy_delegate_, cert_verifier_,
               channel_id_service_, transport_security_state_,
               cert_transparency_verifier_, ct_policy_enforcer_,
@@ -259,8 +260,9 @@
         std::min(sockets_per_proxy_server, max_sockets_per_group(pool_type_));
   }
   return std::make_unique<TransportClientSocketPool>(
-      sockets_per_proxy_server, sockets_per_group, socket_factory_,
-      host_resolver_, proxy_delegate_, cert_verifier_, channel_id_service_,
+      sockets_per_proxy_server, sockets_per_group,
+      unused_idle_socket_timeout(pool_type_), socket_factory_, host_resolver_,
+      proxy_delegate_, cert_verifier_, channel_id_service_,
       transport_security_state_, cert_transparency_verifier_,
       ct_policy_enforcer_, ssl_client_session_cache_,
       ssl_client_session_cache_privacy_mode_, ssl_config_service_,
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc
index 8379d114..e3c21f1 100644
--- a/net/socket/socket_test_util.cc
+++ b/net/socket/socket_test_util.cc
@@ -2148,6 +2148,7 @@
     : TransportClientSocketPool(
           max_sockets,
           max_sockets_per_group,
+          base::TimeDelta::FromSeconds(10) /* unused_idle_socket_timeout */,
           socket_factory,
           nullptr /* host_resolver */,
           nullptr /* proxy_delegate */,
diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc
index 31508ed..99cf66c 100644
--- a/net/socket/ssl_client_socket_pool_unittest.cc
+++ b/net/socket/ssl_client_socket_pool_unittest.cc
@@ -55,6 +55,8 @@
 
 const int kMaxSockets = 32;
 const int kMaxSocketsPerGroup = 6;
+constexpr base::TimeDelta kUnusedIdleSocketTimeout =
+    base::TimeDelta::FromSeconds(10);
 const char kGroupName[] = "a";
 
 class SSLClientSocketPoolTest : public TestWithScopedTaskEnvironment {
@@ -92,6 +94,7 @@
         http_proxy_socket_pool_(
             kMaxSockets,
             kMaxSocketsPerGroup,
+            kUnusedIdleSocketTimeout,
             &socket_factory_,
             &host_resolver_,
             nullptr /* proxy_delegate */,
@@ -111,10 +114,10 @@
 
   void CreatePool(bool http_proxy_pool) {
     pool_.reset(new TransportClientSocketPool(
-        kMaxSockets, kMaxSocketsPerGroup, &socket_factory_, &host_resolver_,
-        NULL /* proxy_delegate */, cert_verifier_.get(),
-        NULL /* channel_id_service */, transport_security_state_.get(),
-        &ct_verifier_, &ct_policy_enforcer_,
+        kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+        &socket_factory_, &host_resolver_, NULL /* proxy_delegate */,
+        cert_verifier_.get(), NULL /* channel_id_service */,
+        transport_security_state_.get(), &ct_verifier_, &ct_policy_enforcer_,
         nullptr /* ssl_client_session_cache */,
         nullptr /* ssl_client_session_cache_privacy_mode */,
         nullptr /* ssl_config_service */,
diff --git a/net/socket/ssl_connect_job_unittest.cc b/net/socket/ssl_connect_job_unittest.cc
index 3ac937a..fb8740d 100644
--- a/net/socket/ssl_connect_job_unittest.cc
+++ b/net/socket/ssl_connect_job_unittest.cc
@@ -51,6 +51,8 @@
 
 const int kMaxSockets = 32;
 const int kMaxSocketsPerGroup = 6;
+constexpr base::TimeDelta kUnusedIdleSocketTimeout =
+    base::TimeDelta::FromSeconds(10);
 const char kGroupName[] = "a";
 
 // Just check that all connect times are set to base::TimeTicks::Now(), for
@@ -125,6 +127,7 @@
         http_proxy_socket_pool_(
             kMaxSockets,
             kMaxSocketsPerGroup,
+            kUnusedIdleSocketTimeout,
             &socket_factory_,
             &host_resolver_,
             nullptr /* proxy_delegate */,
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc
index ef760d3..9ac3e121 100644
--- a/net/socket/transport_client_socket_pool.cc
+++ b/net/socket/transport_client_socket_pool.cc
@@ -165,6 +165,7 @@
 TransportClientSocketPool::TransportClientSocketPool(
     int max_sockets,
     int max_sockets_per_group,
+    base::TimeDelta unused_idle_socket_timeout,
     ClientSocketFactory* client_socket_factory,
     HostResolver* host_resolver,
     ProxyDelegate* proxy_delegate,
@@ -183,7 +184,7 @@
     : base_(this,
             max_sockets,
             max_sockets_per_group,
-            ClientSocketPool::unused_idle_socket_timeout(),
+            unused_idle_socket_timeout,
             ClientSocketPool::used_idle_socket_timeout(),
             new TransportConnectJobFactory(
                 client_socket_factory,
diff --git a/net/socket/transport_client_socket_pool.h b/net/socket/transport_client_socket_pool.h
index 29031d7..a70a0f2 100644
--- a/net/socket/transport_client_socket_pool.h
+++ b/net/socket/transport_client_socket_pool.h
@@ -105,6 +105,7 @@
   TransportClientSocketPool(
       int max_sockets,
       int max_sockets_per_group,
+      base::TimeDelta unused_idle_socket_timeout,
       ClientSocketFactory* client_socket_factory,
       HostResolver* host_resolver,
       ProxyDelegate* proxy_delegate,
diff --git a/net/socket/transport_client_socket_pool_unittest.cc b/net/socket/transport_client_socket_pool_unittest.cc
index 806013d4..cc2cda85 100644
--- a/net/socket/transport_client_socket_pool_unittest.cc
+++ b/net/socket/transport_client_socket_pool_unittest.cc
@@ -53,6 +53,8 @@
 
 const int kMaxSockets = 32;
 const int kMaxSocketsPerGroup = 6;
+constexpr base::TimeDelta kUnusedIdleSocketTimeout =
+    base::TimeDelta::FromSeconds(10);
 const RequestPriority kDefaultPriority = LOW;
 
 class SOCKS5MockData {
@@ -96,6 +98,7 @@
         client_socket_factory_(&net_log_),
         pool_(kMaxSockets,
               kMaxSocketsPerGroup,
+              kUnusedIdleSocketTimeout,
               &client_socket_factory_,
               host_resolver_.get(),
               nullptr /* proxy_delegate */,
@@ -113,6 +116,7 @@
         pool_for_real_sockets_(
             kMaxSockets,
             kMaxSocketsPerGroup,
+            kUnusedIdleSocketTimeout,
             ClientSocketFactory::GetDefaultFactory(),
             host_resolver_.get(),
             nullptr /* proxy_delegate */,
@@ -447,9 +451,10 @@
 
 TEST_F(TransportClientSocketPoolTest, RequestIgnoringLimitsIsNotReprioritized) {
   TransportClientSocketPool pool(
-      kMaxSockets, 1, &client_socket_factory_, host_resolver_.get(),
-      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
-      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
+      kMaxSockets, 1, kUnusedIdleSocketTimeout, &client_socket_factory_,
+      host_resolver_.get(), nullptr /* proxy_delegate */,
+      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
+      nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -1160,9 +1165,10 @@
   for (IoMode socket_io_mode : {SYNCHRONOUS, ASYNC}) {
     MockTaggingClientSocketFactory socket_factory;
     TransportClientSocketPool pool(
-        kMaxSockets, kMaxSocketsPerGroup, &socket_factory, host_resolver_.get(),
-        nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
-        nullptr /* channel_id_server */, nullptr /* transport_security_state */,
+        kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+        &socket_factory, host_resolver_.get(), nullptr /* proxy_delegate */,
+        nullptr /* cert_verifier */, nullptr /* channel_id_server */,
+        nullptr /* transport_security_state */,
         nullptr /* cert_transparency_verifier */,
         nullptr /* ct_policy_enforcer */,
         nullptr /* ssl_client_session_cache */,
@@ -1211,7 +1217,7 @@
   ASSERT_TRUE(test_server.Start());
 
   TransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
       ClientSocketFactory::GetDefaultFactory(), host_resolver_.get(),
       nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
       nullptr /* channel_id_server */, nullptr /* transport_security_state */,
@@ -1337,9 +1343,10 @@
   host_resolver_->set_synchronous_mode(true);
   MockTaggingClientSocketFactory socket_factory;
   TransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &socket_factory, host_resolver_.get(),
-      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
-      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &socket_factory, host_resolver_.get(), nullptr /* proxy_delegate */,
+      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
+      nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
diff --git a/net/socket/websocket_transport_client_socket_pool.cc b/net/socket/websocket_transport_client_socket_pool.cc
index 113c2d1f..737e976 100644
--- a/net/socket/websocket_transport_client_socket_pool.cc
+++ b/net/socket/websocket_transport_client_socket_pool.cc
@@ -29,6 +29,7 @@
 WebSocketTransportClientSocketPool::WebSocketTransportClientSocketPool(
     int max_sockets,
     int max_sockets_per_group,
+    base::TimeDelta unused_idle_socket_timeout,
     ClientSocketFactory* client_socket_factory,
     HostResolver* host_resolver,
     ProxyDelegate* proxy_delegate,
@@ -46,6 +47,7 @@
     : TransportClientSocketPool(
           max_sockets,
           max_sockets_per_group,
+          unused_idle_socket_timeout,
           client_socket_factory,
           host_resolver,
           proxy_delegate,
diff --git a/net/socket/websocket_transport_client_socket_pool.h b/net/socket/websocket_transport_client_socket_pool.h
index 56d9c50..0d33467 100644
--- a/net/socket/websocket_transport_client_socket_pool.h
+++ b/net/socket/websocket_transport_client_socket_pool.h
@@ -45,6 +45,7 @@
   WebSocketTransportClientSocketPool(
       int max_sockets,
       int max_sockets_per_group,
+      base::TimeDelta unused_idle_socket_timeout,
       ClientSocketFactory* client_socket_factory,
       HostResolver* host_resolver,
       ProxyDelegate* proxy_delegate,
diff --git a/net/socket/websocket_transport_client_socket_pool_unittest.cc b/net/socket/websocket_transport_client_socket_pool_unittest.cc
index 8a2624d..6d6525a6 100644
--- a/net/socket/websocket_transport_client_socket_pool_unittest.cc
+++ b/net/socket/websocket_transport_client_socket_pool_unittest.cc
@@ -44,6 +44,8 @@
 
 const int kMaxSockets = 32;
 const int kMaxSocketsPerGroup = 6;
+constexpr base::TimeDelta kUnusedIdleSocketTimeout =
+    base::TimeDelta::FromSeconds(10);
 const RequestPriority kDefaultPriority = LOW;
 
 // RunLoop doesn't support this natively but it is easy to emulate.
@@ -69,6 +71,7 @@
         client_socket_factory_(&net_log_),
         pool_(kMaxSockets,
               kMaxSocketsPerGroup,
+              kUnusedIdleSocketTimeout,
               &client_socket_factory_,
               host_resolver_.get(),
               nullptr /* proxy_delegate */,
@@ -539,10 +542,10 @@
 TEST_F(WebSocketTransportClientSocketPoolTest,
        IPv6FallbackSocketIPv4FinishesFirst) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -585,10 +588,10 @@
 TEST_F(WebSocketTransportClientSocketPoolTest,
        IPv6FallbackSocketIPv6FinishesFirst) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -630,10 +633,10 @@
 TEST_F(WebSocketTransportClientSocketPoolTest,
        IPv6NoIPv4AddressesToFallbackTo) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -667,10 +670,10 @@
 
 TEST_F(WebSocketTransportClientSocketPoolTest, IPv4HasNoFallback) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -705,10 +708,10 @@
 // proceeed immediately.
 TEST_F(WebSocketTransportClientSocketPoolTest, IPv6InstantFail) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -747,10 +750,10 @@
 // connections proceed immediately.
 TEST_F(WebSocketTransportClientSocketPoolTest, IPv6RapidFail) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -797,10 +800,10 @@
 // type do not race).
 TEST_F(WebSocketTransportClientSocketPoolTest, FirstSuccessWins) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -841,10 +844,10 @@
 // We should not report failure until all connections have failed.
 TEST_F(WebSocketTransportClientSocketPoolTest, LastFailureWins) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
@@ -889,10 +892,10 @@
 // want to run it.
 TEST_F(WebSocketTransportClientSocketPoolTest, DISABLED_OverallTimeoutApplies) {
   WebSocketTransportClientSocketPool pool(
-      kMaxSockets, kMaxSocketsPerGroup, &client_socket_factory_,
-      host_resolver_.get(), nullptr /* proxy_delegate */,
-      nullptr /* cert_verifier */, nullptr /* channel_id_server */,
-      nullptr /* transport_security_state */,
+      kMaxSockets, kMaxSocketsPerGroup, kUnusedIdleSocketTimeout,
+      &client_socket_factory_, host_resolver_.get(),
+      nullptr /* proxy_delegate */, nullptr /* cert_verifier */,
+      nullptr /* channel_id_server */, nullptr /* transport_security_state */,
       nullptr /* cert_transparency_verifier */,
       nullptr /* ct_policy_enforcer */, nullptr /* ssl_client_session_cache */,
       nullptr /* ssl_client_session_cache_privacy_mode */,
diff --git a/net/third_party/quic/core/quic_config.cc b/net/third_party/quic/core/quic_config.cc
index 23fe58e..510e0c9 100644
--- a/net/third_party/quic/core/quic_config.cc
+++ b/net/third_party/quic/core/quic_config.cc
@@ -14,6 +14,7 @@
 #include "net/third_party/quic/platform/api/quic_flag_utils.h"
 #include "net/third_party/quic/platform/api/quic_flags.h"
 #include "net/third_party/quic/platform/api/quic_logging.h"
+#include "net/third_party/quic/platform/api/quic_macros.h"
 #include "net/third_party/quic/platform/api/quic_ptr_util.h"
 #include "net/third_party/quic/platform/api/quic_string.h"
 #include "net/third_party/quic/platform/api/quic_string_piece.h"
@@ -492,7 +493,7 @@
 }
 
 // TODO(ianswett) Use this for silent close on mobile, or delete.
-void QuicConfig::SetSilentClose(bool silent_close) {
+QUIC_UNUSED void QuicConfig::SetSilentClose(bool silent_close) {
   silent_close_.set(silent_close ? 1 : 0, silent_close ? 1 : 0);
 }
 
diff --git a/net/third_party/quic/core/quic_connection.cc b/net/third_party/quic/core/quic_connection.cc
index 258d168..3889542f 100644
--- a/net/third_party/quic/core/quic_connection.cc
+++ b/net/third_party/quic/core/quic_connection.cc
@@ -17,7 +17,6 @@
 #include "base/format_macros.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
-#include "net/base/net_errors.h"
 #include "net/third_party/quic/core/crypto/crypto_protocol.h"
 #include "net/third_party/quic/core/crypto/quic_decrypter.h"
 #include "net/third_party/quic/core/crypto/quic_encrypter.h"
@@ -30,6 +29,7 @@
 #include "net/third_party/quic/core/quic_utils.h"
 #include "net/third_party/quic/platform/api/quic_bug_tracker.h"
 #include "net/third_party/quic/platform/api/quic_client_stats.h"
+#include "net/third_party/quic/platform/api/quic_error_code_wrappers.h"
 #include "net/third_party/quic/platform/api/quic_exported_stats.h"
 #include "net/third_party/quic/platform/api/quic_flag_utils.h"
 #include "net/third_party/quic/platform/api/quic_flags.h"
@@ -63,10 +63,6 @@
 // One eighth RTT delay when doing ack decimation.
 const float kShortAckDecimationDelay = 0.125;
 
-// Error code used in WriteResult to indicate that the packet writer rejected
-// the message as being too big.
-const int kMessageTooBigErrorCode = net::ERR_MSG_TOO_BIG;
-
 // The minimum release time into future in ms.
 const int kMinReleaseTimeIntoFutureMs = 1;
 
@@ -2386,8 +2382,7 @@
 
 bool QuicConnection::IsMsgTooBig(const WriteResult& result) {
   return (result.status == WRITE_STATUS_MSG_TOO_BIG) ||
-         (IsWriteError(result.status) &&
-          result.error_code == kMessageTooBigErrorCode);
+         (IsWriteError(result.status) && result.error_code == QUIC_EMSGSIZE);
 }
 
 bool QuicConnection::ShouldDiscardPacket(const SerializedPacket& packet) {
@@ -2422,7 +2417,7 @@
       "Write failed with error: ", error_code, " (", strerror(error_code), ")");
   QUIC_LOG_FIRST_N(ERROR, 2) << ENDPOINT << error_details;
   switch (error_code) {
-    case kMessageTooBigErrorCode:
+    case QUIC_EMSGSIZE:
       CloseConnection(
           QUIC_PACKET_WRITE_ERROR, error_details,
           ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET_WITH_NO_ACK);
diff --git a/net/third_party/quic/platform/api/quic_error_code_wrappers.h b/net/third_party/quic/platform/api/quic_error_code_wrappers.h
new file mode 100644
index 0000000..2c03a15
--- /dev/null
+++ b/net/third_party/quic/platform/api/quic_error_code_wrappers.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_ERROR_CODE_WRAPPERS_H_
+#define NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_ERROR_CODE_WRAPPERS_H_
+
+#include "net/third_party/quic/platform/impl/quic_error_code_wrappers_impl.h"
+
+// TODO(vasilvv): ensure WRITE_STATUS_MSG_TOO_BIG works everywhere and remove
+// this.
+#define QUIC_EMSGSIZE QUIC_EMSGSIZE_IMPL
+
+#endif  // NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_ERROR_CODE_WRAPPERS_H_
diff --git a/net/third_party/quic/platform/api/quic_macros.h b/net/third_party/quic/platform/api/quic_macros.h
new file mode 100644
index 0000000..d786df5
--- /dev/null
+++ b/net/third_party/quic/platform/api/quic_macros.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_MACROS_H_
+#define NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_MACROS_H_
+
+#include "net/third_party/quic/platform/impl/quic_macros_impl.h"
+
+#define QUIC_WARN_UNUSED_RESULT QUIC_MUST_USE_RESULT_IMPL
+#define QUIC_UNUSED QUIC_UNUSED_IMPL
+
+#endif  // NET_THIRD_PARTY_QUIC_PLATFORM_API_QUIC_MACROS_H_
diff --git a/net/third_party/quic/platform/impl/quic_error_code_wrappers_impl.h b/net/third_party/quic/platform/impl/quic_error_code_wrappers_impl.h
new file mode 100644
index 0000000..387f6b9
--- /dev/null
+++ b/net/third_party/quic/platform/impl/quic_error_code_wrappers_impl.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_ERROR_CODE_WRAPPERS_IMPL_H_
+#define NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_ERROR_CODE_WRAPPERS_IMPL_H_
+
+#include "net/base/net_errors.h"
+
+#define QUIC_EMSGSIZE_IMPL net::ERR_MSG_TOO_BIG
+
+#endif  // NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_ERROR_CODE_WRAPPERS_IMPL_H_
diff --git a/net/third_party/quic/platform/impl/quic_macros_impl.h b/net/third_party/quic/platform/impl/quic_macros_impl.h
new file mode 100644
index 0000000..8d4d48a
--- /dev/null
+++ b/net/third_party/quic/platform/impl/quic_macros_impl.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_MACROS_IMPL_H_
+#define NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_MACROS_IMPL_H_
+
+#include "base/compiler_specific.h"
+
+#define QUIC_MUST_USE_RESULT_IMPL WARN_UNUSED_RESULT
+#define QUIC_UNUSED_IMPL ALLOW_UNUSED_TYPE
+
+#endif  // NET_THIRD_PARTY_QUIC_PLATFORM_IMPL_QUIC_MACROS_IMPL_H_
diff --git a/net/third_party/quic/test_tools/packet_dropping_test_writer.h b/net/third_party/quic/test_tools/packet_dropping_test_writer.h
index 0954858..5f7e6a19 100644
--- a/net/third_party/quic/test_tools/packet_dropping_test_writer.h
+++ b/net/third_party/quic/test_tools/packet_dropping_test_writer.h
@@ -16,6 +16,7 @@
 #include "net/third_party/quic/core/quic_alarm.h"
 #include "net/third_party/quic/core/quic_packet_writer_wrapper.h"
 #include "net/third_party/quic/platform/api/quic_clock.h"
+#include "net/third_party/quic/platform/api/quic_macros.h"
 #include "net/third_party/quic/test_tools/quic_test_client.h"
 #include "net/third_party/quic/test_tools/quic_test_utils.h"
 
@@ -121,7 +122,7 @@
   }
 
   // Useful for reproducing very flaky issues.
-  void set_seed(uint64_t seed) { simple_random_.set_seed(seed); }
+  QUIC_UNUSED void set_seed(uint64_t seed) { simple_random_.set_seed(seed); }
 
  private:
   // Writes out the next packet to the contained writer and returns the time
diff --git a/net/third_party/quic/tools/quic_client_base.h b/net/third_party/quic/tools/quic_client_base.h
index 17c86208..7b89b7c 100644
--- a/net/third_party/quic/tools/quic_client_base.h
+++ b/net/third_party/quic/tools/quic_client_base.h
@@ -10,12 +10,12 @@
 
 #include <string>
 
-#include "base/macros.h"
 #include "net/third_party/quic/core/crypto/crypto_handshake.h"
 #include "net/third_party/quic/core/http/quic_client_push_promise_index.h"
 #include "net/third_party/quic/core/http/quic_spdy_client_session.h"
 #include "net/third_party/quic/core/http/quic_spdy_client_stream.h"
 #include "net/third_party/quic/core/quic_config.h"
+#include "net/third_party/quic/platform/api/quic_macros.h"
 #include "net/third_party/quic/platform/api/quic_socket_address.h"
 #include "net/third_party/quic/platform/api/quic_string_piece.h"
 
@@ -102,7 +102,7 @@
 
   // Wait for events until the handshake is confirmed.
   // Returns true if the crypto handshake succeeds, false otherwise.
-  bool WaitForCryptoHandshakeConfirmed() WARN_UNUSED_RESULT;
+  bool WaitForCryptoHandshakeConfirmed() QUIC_WARN_UNUSED_RESULT;
 
   // Wait up to 50ms, and handle any events which occur.
   // Returns true if there are any outstanding requests.
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 6733c55..334b054b 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -84,6 +84,7 @@
 if (!is_ios) {
   test("services_perftests") {
     deps = [
+      "//services/device:perftests",
       "//services/test:run_all_unittests",
       "//services/viz/public/cpp/compositing:perftests",
     ]
diff --git a/services/device/BUILD.gn b/services/device/BUILD.gn
index 1e28c33..9bb21b45 100644
--- a/services/device/BUILD.gn
+++ b/services/device/BUILD.gn
@@ -66,6 +66,24 @@
 
 is_linux_without_udev = is_linux && !use_udev
 
+source_set("perftests") {
+  testonly = true
+
+  sources = [
+    "geolocation/position_cache_impl_perftest.cc",
+  ]
+
+  deps = [
+    ":test_support",
+    "//base",
+    "//base/test:test_support",
+    "//services/device/geolocation",
+    "//services/device/geolocation:test_support",
+    "//testing/gtest",
+    "//testing/perf",
+  ]
+}
+
 source_set("tests") {
   testonly = true
 
@@ -92,6 +110,7 @@
     "geolocation/geolocation_service_unittest.cc",
     "geolocation/location_arbitrator_unittest.cc",
     "geolocation/network_location_provider_unittest.cc",
+    "geolocation/position_cache_impl_unittest.cc",
     "geolocation/public_ip_address_geolocator_unittest.cc",
     "geolocation/public_ip_address_location_notifier_unittest.cc",
     "geolocation/wifi_data_provider_chromeos_unittest.cc",
diff --git a/services/device/geolocation/BUILD.gn b/services/device/geolocation/BUILD.gn
index 1f52214..ea8b7c7 100644
--- a/services/device/geolocation/BUILD.gn
+++ b/services/device/geolocation/BUILD.gn
@@ -37,6 +37,9 @@
     "network_location_provider.h",
     "network_location_request.cc",
     "network_location_request.h",
+    "position_cache.h",
+    "position_cache_impl.cc",
+    "position_cache_impl.h",
     "public_ip_address_geolocation_provider.cc",
     "public_ip_address_geolocation_provider.h",
     "public_ip_address_geolocator.cc",
@@ -156,6 +159,10 @@
   sources = [
     "fake_location_provider.cc",
     "fake_location_provider.h",
+    "fake_position_cache.cc",
+    "fake_position_cache.h",
+    "position_cache_test_util.cc",
+    "position_cache_test_util.h",
   ]
   public_deps = [
     ":geolocation",
diff --git a/services/device/geolocation/fake_position_cache.cc b/services/device/geolocation/fake_position_cache.cc
new file mode 100644
index 0000000..7447bd0
--- /dev/null
+++ b/services/device/geolocation/fake_position_cache.cc
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/device/geolocation/fake_position_cache.h"
+
+#include <algorithm>
+
+#include "services/device/geolocation/wifi_data.h"
+#include "services/device/public/cpp/geolocation/geoposition.h"
+
+namespace device {
+namespace {
+
+template <typename Set>
+bool SetsEqual(const Set& lhs, const Set& rhs) {
+  // Since sets order elements via an operator, std::equal doesn't work. It
+  // would require elements to be equal-comparable. Check if symmetric
+  // difference is empty instead.
+  std::vector<typename Set::value_type> symmetric_difference;
+  std::set_symmetric_difference(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(),
+                                std::back_inserter(symmetric_difference),
+                                typename Set::value_compare());
+  return symmetric_difference.empty();
+}
+
+}  // namespace
+
+FakePositionCache::FakePositionCache() = default;
+FakePositionCache::~FakePositionCache() = default;
+
+void FakePositionCache::CachePosition(const WifiData& wifi_data,
+                                      const mojom::Geoposition& position) {
+  data.push_back(std::make_pair(wifi_data, position));
+}
+
+const mojom::Geoposition* FakePositionCache::FindPosition(
+    const WifiData& wifi_data) const {
+  auto it = std::find_if(
+      data.begin(), data.end(), [&wifi_data](const auto& candidate_pair) {
+        return SetsEqual(wifi_data.access_point_data,
+                         candidate_pair.first.access_point_data);
+      });
+  return it == data.end() ? nullptr : &(it->second);
+}
+
+const mojom::Geoposition& FakePositionCache::GetLastUsedNetworkPosition()
+    const {
+  return last_used_position;
+}
+
+void FakePositionCache::SetLastUsedNetworkPosition(
+    const mojom::Geoposition& position) {
+  last_used_position = position;
+}
+
+}  // namespace device
diff --git a/services/device/geolocation/fake_position_cache.h b/services/device/geolocation/fake_position_cache.h
new file mode 100644
index 0000000..ec7c951e
--- /dev/null
+++ b/services/device/geolocation/fake_position_cache.h
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_DEVICE_GEOLOCATION_FAKE_POSITION_CACHE_H_
+#define SERVICES_DEVICE_GEOLOCATION_FAKE_POSITION_CACHE_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "services/device/geolocation/position_cache.h"
+#include "services/device/public/mojom/geoposition.mojom.h"
+
+namespace device {
+
+class FakePositionCache : public PositionCache {
+ public:
+  FakePositionCache();
+  ~FakePositionCache() override;
+
+  void CachePosition(const WifiData& wifi_data,
+                     const mojom::Geoposition& position) override;
+  const mojom::Geoposition* FindPosition(
+      const WifiData& wifi_data) const override;
+  const mojom::Geoposition& GetLastUsedNetworkPosition() const override;
+  void SetLastUsedNetworkPosition(const mojom::Geoposition& position) override;
+
+ private:
+  std::vector<std::pair<WifiData, mojom::Geoposition>> data;
+  mojom::Geoposition last_used_position;
+  DISALLOW_COPY_AND_ASSIGN(FakePositionCache);
+};
+
+}  // namespace device
+
+#endif  // SERVICES_DEVICE_GEOLOCATION_FAKE_POSITION_CACHE_H_
diff --git a/services/device/geolocation/geolocation_provider_impl.cc b/services/device/geolocation/geolocation_provider_impl.cc
index 49b700a4..058933ca 100644
--- a/services/device/geolocation/geolocation_provider_impl.cc
+++ b/services/device/geolocation/geolocation_provider_impl.cc
@@ -16,7 +16,10 @@
 #include "base/memory/singleton.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "net/base/network_change_notifier.h"
 #include "services/device/geolocation/location_arbitrator.h"
+#include "services/device/geolocation/position_cache_impl.h"
 #include "services/device/public/cpp/geolocation/geoposition.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -234,9 +237,13 @@
         std::move(g_url_loader_factory_info.Get()));
   }
 
+  DCHECK(net::NetworkChangeNotifier::HasNetworkChangeNotifier())
+      << "PositionCacheImpl needs a global NetworkChangeNotifier";
   arbitrator_ = std::make_unique<LocationArbitrator>(
       g_custom_location_provider_callback.Get(), std::move(url_loader_factory),
-      g_api_key.Get());
+      g_api_key.Get(),
+      std::make_unique<PositionCacheImpl>(
+          base::DefaultTickClock::GetInstance()));
   arbitrator_->SetUpdateCallback(callback);
 }
 
diff --git a/services/device/geolocation/geolocation_provider_impl.h b/services/device/geolocation/geolocation_provider_impl.h
index 79cf0af..6e75055 100644
--- a/services/device/geolocation/geolocation_provider_impl.h
+++ b/services/device/geolocation/geolocation_provider_impl.h
@@ -7,6 +7,7 @@
 
 #include <list>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/callback_forward.h"
diff --git a/services/device/geolocation/geolocation_service_unittest.cc b/services/device/geolocation/geolocation_service_unittest.cc
index 3b514cb..3d69c7a6 100644
--- a/services/device/geolocation/geolocation_service_unittest.cc
+++ b/services/device/geolocation/geolocation_service_unittest.cc
@@ -11,6 +11,7 @@
 #include "chromeos/network/geolocation_handler.h"
 #endif
 #include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "net/base/network_change_notifier.h"
 #include "services/device/device_service_test_base.h"
 #include "services/device/geolocation/geolocation_provider_impl.h"
 #include "services/device/geolocation/network_location_request.h"
@@ -42,7 +43,7 @@
     chromeos::DBusThreadManager::Initialize();
     chromeos::NetworkHandler::Initialize();
 #endif
-
+    network_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock());
     // We need to initialize the above *before* the base fixture instantiates
     // the device service.
     DeviceServiceTestBase::SetUp();
@@ -75,6 +76,7 @@
     connector()->BindInterface(mojom::kServiceName, &geolocation_config_);
   }
 
+  std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_;
   mojom::GeolocationControlPtr geolocation_control_;
   mojom::GeolocationContextPtr geolocation_context_;
   mojom::GeolocationPtr geolocation_;
diff --git a/services/device/geolocation/location_arbitrator.cc b/services/device/geolocation/location_arbitrator.cc
index 21ea659..24391a0 100644
--- a/services/device/geolocation/location_arbitrator.cc
+++ b/services/device/geolocation/location_arbitrator.cc
@@ -27,12 +27,14 @@
 LocationArbitrator::LocationArbitrator(
     const CustomLocationProviderCallback& custom_location_provider_getter,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const std::string& api_key)
+    const std::string& api_key,
+    std::unique_ptr<PositionCache> position_cache)
     : custom_location_provider_getter_(custom_location_provider_getter),
       url_loader_factory_(url_loader_factory),
       api_key_(api_key),
       position_provider_(nullptr),
       is_permission_granted_(false),
+      position_cache_(std::move(position_cache)),
       is_running_(false) {}
 
 LocationArbitrator::~LocationArbitrator() {
@@ -50,15 +52,6 @@
     provider->OnPermissionGranted();
 }
 
-void LocationArbitrator::SetLastNetworkPosition(
-    const mojom::Geoposition& position) {
-  last_network_position_ = position;
-}
-
-const mojom::Geoposition& LocationArbitrator::GetLastNetworkPosition() {
-  return last_network_position_;
-}
-
 void LocationArbitrator::StartProvider(bool enable_high_accuracy) {
   is_running_ = true;
   enable_high_accuracy_ = enable_high_accuracy;
@@ -157,7 +150,7 @@
   return nullptr;
 #else
   return std::make_unique<NetworkLocationProvider>(
-      std::move(url_loader_factory), api_key, this);
+      std::move(url_loader_factory), api_key, position_cache_.get());
 #endif
 }
 
diff --git a/services/device/geolocation/location_arbitrator.h b/services/device/geolocation/location_arbitrator.h
index 5d89450..eae4ee0 100644
--- a/services/device/geolocation/location_arbitrator.h
+++ b/services/device/geolocation/location_arbitrator.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/callback_forward.h"
@@ -16,6 +17,7 @@
 #include "base/time/time.h"
 #include "services/device/geolocation/geolocation_provider_impl.h"
 #include "services/device/geolocation/network_location_provider.h"
+#include "services/device/geolocation/position_cache.h"
 #include "services/device/public/cpp/geolocation/location_provider.h"
 #include "services/device/public/mojom/geoposition.mojom.h"
 #include "url/gurl.h"
@@ -29,8 +31,7 @@
 // This class is responsible for handling updates from multiple underlying
 // providers and resolving them to a single 'best' location fix at any given
 // moment.
-class LocationArbitrator : public LocationProvider,
-                           public NetworkLocationProvider::LastPositionCache {
+class LocationArbitrator : public LocationProvider {
  public:
   // The TimeDelta newer a location provider has to be that it's worth
   // switching to this location provider on the basis of it being fresher
@@ -42,7 +43,8 @@
   LocationArbitrator(
       const CustomLocationProviderCallback& custom_location_provider_getter,
       const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::string& api_key);
+      const std::string& api_key,
+      std::unique_ptr<PositionCache> position_cache);
   ~LocationArbitrator() override;
 
   static GURL DefaultNetworkProviderURL();
@@ -56,10 +58,6 @@
   const mojom::Geoposition& GetPosition() override;
   void OnPermissionGranted() override;
 
-  // NetworkLocationProvider::LastPositionCache implementation.
-  void SetLastNetworkPosition(const mojom::Geoposition& position) override;
-  const mojom::Geoposition& GetLastNetworkPosition() override;
-
  protected:
   // These functions are useful for injection of dependencies in derived
   // testing classes.
@@ -109,10 +107,7 @@
   // The current best estimate of our position.
   mojom::Geoposition position_;
 
-  // The most recent position estimate returned by the network location
-  // provider. This must be preserved by LocationArbitrator so it is not lost
-  // when the provider is destroyed in StopProvider.
-  mojom::Geoposition last_network_position_;
+  std::unique_ptr<PositionCache> position_cache_;
 
   // Tracks whether providers should be running.
   bool is_running_;
diff --git a/services/device/geolocation/location_arbitrator_unittest.cc b/services/device/geolocation/location_arbitrator_unittest.cc
index 08c61f5..2cbf5089 100644
--- a/services/device/geolocation/location_arbitrator_unittest.cc
+++ b/services/device/geolocation/location_arbitrator_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "services/device/geolocation/fake_location_provider.h"
+#include "services/device/geolocation/fake_position_cache.h"
 #include "services/device/public/cpp/geolocation/geoposition.h"
 #include "services/device/public/cpp/geolocation/location_provider.h"
 #include "services/device/public/mojom/geoposition.mojom.h"
@@ -87,7 +88,8 @@
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
       : LocationArbitrator(provider_getter,
                            std::move(url_loader_factory),
-                           std::string() /* api_key */),
+                           std::string() /* api_key */,
+                           std::make_unique<FakePositionCache>()),
         cell_(nullptr),
         gps_(nullptr) {
     SetUpdateCallback(callback);
diff --git a/services/device/geolocation/network_location_provider.cc b/services/device/geolocation/network_location_provider.cc
index c2f1ccd..99412f31 100644
--- a/services/device/geolocation/network_location_provider.cc
+++ b/services/device/geolocation/network_location_provider.cc
@@ -13,6 +13,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/device/geolocation/position_cache.h"
 #include "services/device/public/cpp/geolocation/geoposition.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -28,88 +29,17 @@
 const int kLastPositionMaxAgeSeconds = 10 * 60;  // 10 minutes
 }  // namespace
 
-// static
-const size_t NetworkLocationProvider::PositionCache::kMaximumSize = 10;
-
-NetworkLocationProvider::PositionCache::PositionCache() = default;
-
-NetworkLocationProvider::PositionCache::~PositionCache() = default;
-
-bool NetworkLocationProvider::PositionCache::CachePosition(
-    const WifiData& wifi_data,
-    const mojom::Geoposition& position) {
-  // Check that we can generate a valid key for the wifi data.
-  base::string16 key;
-  if (!MakeKey(wifi_data, &key)) {
-    return false;
-  }
-  // If the cache is full, remove the oldest entry.
-  if (cache_.size() == kMaximumSize) {
-    DCHECK(cache_age_list_.size() == kMaximumSize);
-    CacheAgeList::iterator oldest_entry = cache_age_list_.begin();
-    DCHECK(oldest_entry != cache_age_list_.end());
-    cache_.erase(*oldest_entry);
-    cache_age_list_.erase(oldest_entry);
-  }
-  DCHECK_LT(cache_.size(), kMaximumSize);
-  // Insert the position into the cache.
-  std::pair<CacheMap::iterator, bool> result =
-      cache_.insert(std::make_pair(key, position));
-  if (!result.second) {
-    NOTREACHED();  // We never try to add the same key twice.
-    CHECK_EQ(cache_.size(), cache_age_list_.size());
-    return false;
-  }
-  cache_age_list_.push_back(result.first);
-  DCHECK_EQ(cache_.size(), cache_age_list_.size());
-  return true;
-}
-
-// Searches for a cached position response for the current WiFi data. Returns
-// the cached position if available, nullptr otherwise.
-const mojom::Geoposition* NetworkLocationProvider::PositionCache::FindPosition(
-    const WifiData& wifi_data) {
-  base::string16 key;
-  if (!MakeKey(wifi_data, &key)) {
-    return nullptr;
-  }
-  CacheMap::const_iterator iter = cache_.find(key);
-  return iter == cache_.end() ? nullptr : &iter->second;
-}
-
-// Makes the key for the map of cached positions, using the available data.
-// Returns true if a good key was generated, false otherwise.
-//
-// static
-bool NetworkLocationProvider::PositionCache::MakeKey(const WifiData& wifi_data,
-                                                     base::string16* key) {
-  // Currently we use only WiFi data and base the key only on the MAC addresses.
-  DCHECK(key);
-  key->clear();
-  const size_t kCharsPerMacAddress = 6 * 3 + 1;  // e.g. "11:22:33:44:55:66|"
-  key->reserve(wifi_data.access_point_data.size() * kCharsPerMacAddress);
-  const base::string16 separator(base::ASCIIToUTF16("|"));
-  for (const auto& access_point_data : wifi_data.access_point_data) {
-    *key += separator;
-    *key += access_point_data.mac_address;
-    *key += separator;
-  }
-  // If the key is the empty string, return false, as we don't want to cache a
-  // position for such data.
-  return !key->empty();
-}
-
 // NetworkLocationProvider
 NetworkLocationProvider::NetworkLocationProvider(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     const std::string& api_key,
-    LastPositionCache* last_position_cache)
+    PositionCache* position_cache)
     : wifi_data_provider_manager_(nullptr),
       wifi_data_update_callback_(
           base::Bind(&NetworkLocationProvider::OnWifiDataUpdate,
                      base::Unretained(this))),
       is_wifi_data_complete_(false),
-      last_position_delegate_(last_position_cache),
+      position_cache_(position_cache),
       is_permission_granted_(false),
       is_new_data_available_(false),
       request_(new NetworkLocationRequest(
@@ -117,9 +47,8 @@
           api_key,
           base::Bind(&NetworkLocationProvider::OnLocationResponse,
                      base::Unretained(this)))),
-      position_cache_(new PositionCache),
       weak_factory_(this) {
-  DCHECK(last_position_delegate_);
+  DCHECK(position_cache_);
 }
 
 NetworkLocationProvider::~NetworkLocationProvider() {
@@ -174,14 +103,13 @@
     const WifiData& wifi_data) {
   DCHECK(thread_checker_.CalledOnValidThread());
   // Record the position and update our cache.
-  last_position_delegate_->SetLastNetworkPosition(position);
+  position_cache_->SetLastUsedNetworkPosition(position);
   if (ValidateGeoposition(position))
     position_cache_->CachePosition(wifi_data, position);
 
   // Let listeners know that we now have a position available.
   if (!location_provider_update_callback_.is_null()) {
-    location_provider_update_callback_.Run(
-        this, last_position_delegate_->GetLastNetworkPosition());
+    location_provider_update_callback_.Run(this, position);
   }
 }
 
@@ -214,7 +142,7 @@
 }
 
 const mojom::Geoposition& NetworkLocationProvider::GetPosition() {
-  return last_position_delegate_->GetLastNetworkPosition();
+  return position_cache_->GetLastUsedNetworkPosition();
 }
 
 void NetworkLocationProvider::RequestPosition() {
@@ -230,7 +158,7 @@
   // there is no pending network request), report the last network position
   // estimate as if it were a fresh estimate.
   const mojom::Geoposition& last_position =
-      last_position_delegate_->GetLastNetworkPosition();
+      position_cache_->GetLastUsedNetworkPosition();
   if (!is_new_data_available_ && !request_->is_request_pending() &&
       ValidateGeoposition(last_position)) {
     base::Time now = base::Time::Now();
@@ -261,7 +189,7 @@
     is_new_data_available_ = false;
 
     // Record the position.
-    last_position_delegate_->SetLastNetworkPosition(position);
+    position_cache_->SetLastUsedNetworkPosition(position);
 
     // Let listeners know that we now have a position available.
     if (!location_provider_update_callback_.is_null())
diff --git a/services/device/geolocation/network_location_provider.h b/services/device/geolocation/network_location_provider.h
index 75878bb..5229664 100644
--- a/services/device/geolocation/network_location_provider.h
+++ b/services/device/geolocation/network_location_provider.h
@@ -10,6 +10,7 @@
 #include <list>
 #include <map>
 #include <memory>
+#include <string>
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
@@ -23,60 +24,13 @@
 #include "services/device/public/mojom/geoposition.mojom.h"
 
 namespace device {
-
+class PositionCache;
 class NetworkLocationProvider : public LocationProvider {
  public:
-  // To ensure the last-used position estimate can be preserved when the network
-  // location provider is torn down, a delegate manages the state of the cached
-  // position estimate outside of this provider.
-  class LastPositionCache {
-   public:
-    virtual ~LastPositionCache() = default;
-    virtual void SetLastNetworkPosition(
-        const mojom::Geoposition& new_position) = 0;
-    virtual const mojom::Geoposition& GetLastNetworkPosition() = 0;
-  };
-
-  // Cache of recently resolved locations, keyed by the set of unique WiFi APs
-  // used in the network query. Public for tests.
-  class PositionCache {
-   public:
-    // The maximum size of the cache of positions.
-    static const size_t kMaximumSize;
-
-    PositionCache();
-    ~PositionCache();
-
-    // Caches the current position response for the current set of cell ID and
-    // WiFi data. In the case of the cache exceeding kMaximumSize this will
-    // evict old entries in FIFO orderer of being added.
-    // Returns true on success, false otherwise.
-    bool CachePosition(const WifiData& wifi_data,
-                       const mojom::Geoposition& position);
-
-    // Searches for a cached position response for the current set of data.
-    // Returns NULL if the position is not in the cache, or the cached
-    // position if available. Ownership remains with the cache.
-    const mojom::Geoposition* FindPosition(const WifiData& wifi_data);
-
-   private:
-    // Makes the key for the map of cached positions, using a set of
-    // data. Returns true if a good key was generated, false otherwise.
-    static bool MakeKey(const WifiData& wifi_data, base::string16* key);
-
-    // The cache of positions. This is stored as a map keyed on a string that
-    // represents a set of data, and a list to provide
-    // least-recently-added eviction.
-    typedef std::map<base::string16, mojom::Geoposition> CacheMap;
-    CacheMap cache_;
-    typedef std::list<CacheMap::iterator> CacheAgeList;
-    CacheAgeList cache_age_list_;  // Oldest first.
-  };
-
   NetworkLocationProvider(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       const std::string& api_key,
-      LastPositionCache* last_position_cache);
+      PositionCache* position_cache);
   ~NetworkLocationProvider() override;
 
   // LocationProvider implementation
@@ -113,9 +67,7 @@
   // The timestamp for the latest wifi data update.
   base::Time wifi_timestamp_;
 
-  // A delegate to manage the current best network position estimate. Must not
-  // be nullptr.
-  LastPositionCache* const last_position_delegate_;
+  PositionCache* const position_cache_;
 
   LocationProvider::LocationProviderUpdateCallback
       location_provider_update_callback_;
@@ -128,9 +80,6 @@
   // The network location request object.
   const std::unique_ptr<NetworkLocationRequest> request_;
 
-  // The cache of positions.
-  const std::unique_ptr<PositionCache> position_cache_;
-
   base::ThreadChecker thread_checker_;
 
   base::WeakPtrFactory<NetworkLocationProvider> weak_factory_;
diff --git a/services/device/geolocation/network_location_provider_unittest.cc b/services/device/geolocation/network_location_provider_unittest.cc
index 24dae46b..abf6e87 100644
--- a/services/device/geolocation/network_location_provider_unittest.cc
+++ b/services/device/geolocation/network_location_provider_unittest.cc
@@ -8,6 +8,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/bind.h"
@@ -22,6 +23,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "net/base/net_errors.h"
+#include "services/device/geolocation/fake_position_cache.h"
 #include "services/device/geolocation/location_arbitrator.h"
 #include "services/device/geolocation/wifi_data_provider.h"
 #include "services/device/public/cpp/geolocation/geoposition.h"
@@ -107,21 +109,6 @@
   DISALLOW_COPY_AND_ASSIGN(MockWifiDataProvider);
 };
 
-// An implementation of LastPositionCache.
-class TestLastPositionCache
-    : public NetworkLocationProvider::LastPositionCache {
- public:
-  void SetLastNetworkPosition(const mojom::Geoposition& position) override {
-    last_network_position_ = position;
-  }
-  const mojom::Geoposition& GetLastNetworkPosition() override {
-    return last_network_position_;
-  }
-
- private:
-  mojom::Geoposition last_network_position_;
-};
-
 MockWifiDataProvider* MockWifiDataProvider::instance_ = nullptr;
 
 // Main test fixture
@@ -131,11 +118,12 @@
     WifiDataProviderManager::ResetFactoryForTesting();
   }
 
-  LocationProvider* CreateProvider(bool set_permission_granted,
-                                   const std::string& api_key = std::string()) {
-    LocationProvider* provider = new NetworkLocationProvider(
+  std::unique_ptr<LocationProvider> CreateProvider(
+      bool set_permission_granted,
+      const std::string& api_key = std::string()) {
+    auto provider = std::make_unique<NetworkLocationProvider>(
         test_url_loader_factory_.GetSafeWeakWrapper(), api_key,
-        last_position_cache_.get());
+        &position_cache_);
     if (set_permission_granted)
       provider->OnPermissionGranted();
     return provider;
@@ -143,8 +131,7 @@
 
  protected:
   GeolocationNetworkProviderTest()
-      : wifi_data_provider_(MockWifiDataProvider::CreateInstance()),
-        last_position_cache_(std::make_unique<TestLastPositionCache>()) {
+      : wifi_data_provider_(MockWifiDataProvider::CreateInstance()) {
     // TODO(joth): Really these should be in SetUp, not here, but they take no
     // effect on Mac OS Release builds if done there. I kid not. Figure out why.
     WifiDataProviderManager::SetFactoryForTesting(
@@ -287,8 +274,7 @@
   const base::MessageLoop main_message_loop_;
   network::TestURLLoaderFactory test_url_loader_factory_;
   const scoped_refptr<MockWifiDataProvider> wifi_data_provider_;
-  std::unique_ptr<NetworkLocationProvider::LastPositionCache>
-      last_position_cache_;
+  FakePositionCache position_cache_;
 };
 
 // Tests that fixture members were SetUp correctly.
@@ -524,32 +510,6 @@
   CheckRequestIsValid(kScanCount, 0);
 }
 
-// Tests that the provider's position cache correctly caches each item, and
-// begins evicting the oldest entries in order once it reaches its maximum size.
-TEST_F(GeolocationNetworkProviderTest, NetworkPositionCache) {
-  NetworkLocationProvider::PositionCache cache;
-
-  const int kCacheSize = NetworkLocationProvider::PositionCache::kMaximumSize;
-  for (int i = 1; i < kCacheSize * 2 + 1; ++i) {
-    mojom::Geoposition pos = CreateReferencePosition(i);
-    bool ret = cache.CachePosition(CreateReferenceWifiScanData(i), pos);
-    EXPECT_TRUE(ret) << i;
-    const mojom::Geoposition* item =
-        cache.FindPosition(CreateReferenceWifiScanData(i));
-    ASSERT_TRUE(item) << i;
-    EXPECT_EQ(pos.latitude, item->latitude) << i;
-    EXPECT_EQ(pos.longitude, item->longitude) << i;
-    if (i <= kCacheSize) {
-      // Nothing should have spilled yet; check oldest item is still there.
-      EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(1)));
-    } else {
-      const int evicted = i - kCacheSize;
-      EXPECT_FALSE(cache.FindPosition(CreateReferenceWifiScanData(evicted)));
-      EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(evicted + 1)));
-    }
-  }
-}
-
 // Tests that the provider's last position cache delegate is correctly used to
 // cache the most recent network position estimate, and that this estimate is
 // not lost when the provider is torn down and recreated.
@@ -562,7 +522,7 @@
   EXPECT_FALSE(ValidateGeoposition(position));
 
   // Check that the cached value is also invalid.
-  position = last_position_cache_->GetLastNetworkPosition();
+  position = position_cache_.GetLastUsedNetworkPosition();
   EXPECT_FALSE(ValidateGeoposition(position));
 
   // Now wifi data arrives -- SetData will notify listeners.
@@ -601,7 +561,7 @@
   provider = nullptr;
 
   // The cache preserves the last estimate while the provider is inactive.
-  position = last_position_cache_->GetLastNetworkPosition();
+  position = position_cache_.GetLastUsedNetworkPosition();
   EXPECT_EQ(51.0, position.latitude);
   EXPECT_EQ(-0.1, position.longitude);
   EXPECT_EQ(1200.4, position.accuracy);
@@ -609,7 +569,7 @@
   EXPECT_TRUE(ValidateGeoposition(position));
 
   // Restart the provider.
-  provider.reset(CreateProvider(true));
+  provider = CreateProvider(true);
   provider->StartProvider(false);
 
   // Check that the most recent position estimate is retained.
@@ -632,7 +592,7 @@
   // timestamp set to the current time.
   mojom::Geoposition last_position = CreateReferencePosition(0);
   EXPECT_TRUE(ValidateGeoposition(last_position));
-  last_position_cache_->SetLastNetworkPosition(last_position);
+  position_cache_.SetLastUsedNetworkPosition(last_position);
 
   // Simulate no initial wifi data.
   wifi_data_provider_->set_got_data(false);
@@ -676,7 +636,7 @@
   last_position.timestamp =
       base::Time::Now() - base::TimeDelta::FromMinutes(20);
   EXPECT_TRUE(ValidateGeoposition(last_position));
-  last_position_cache_->SetLastNetworkPosition(last_position);
+  position_cache_.SetLastUsedNetworkPosition(last_position);
 
   // Simulate no initial wifi data.
   wifi_data_provider_->set_got_data(false);
@@ -712,7 +672,7 @@
   // of the cached position is set to the current time.
   mojom::Geoposition last_position = CreateReferencePosition(0);
   EXPECT_TRUE(ValidateGeoposition(last_position));
-  last_position_cache_->SetLastNetworkPosition(last_position);
+  position_cache_.SetLastUsedNetworkPosition(last_position);
 
   // Simulate a completed wifi scan.
   const int kFirstScanAps = 6;
diff --git a/services/device/geolocation/position_cache.h b/services/device/geolocation/position_cache.h
new file mode 100644
index 0000000..eae8d57
--- /dev/null
+++ b/services/device/geolocation/position_cache.h
@@ -0,0 +1,45 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_H_
+#define SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_H_
+
+namespace device {
+namespace mojom {
+class Geoposition;
+}  // namespace mojom
+
+struct WifiData;
+
+// Cache of recently resolved locations, keyed by the set of unique WiFi APs
+// used in the network query.
+class PositionCache {
+ public:
+  virtual ~PositionCache() = default;
+
+  // Caches the current position response for the current set of cell ID and
+  // WiFi data. In the case of the cache exceeding an implementation-defined
+  // maximum size this will evict old entries in FIFO orderer of being added.
+  virtual void CachePosition(const WifiData& wifi_data,
+                             const mojom::Geoposition& position) = 0;
+
+  // Searches for a cached position response for the current set of data.
+  // Returns nullptr if the position is not in the cache, or the cached
+  // position if available. Ownership remains with the cache. Do not store
+  // the pointer, treat it as an iterator into the cache's internals.
+  virtual const mojom::Geoposition* FindPosition(
+      const WifiData& wifi_data) const = 0;
+
+  // Returns most recently used position, or an invalid Geoposition if
+  // SetLastUsedNetworkPosition wasn't called yet.
+  virtual const mojom::Geoposition& GetLastUsedNetworkPosition() const = 0;
+
+  // Stores the most recently used position.
+  virtual void SetLastUsedNetworkPosition(
+      const mojom::Geoposition& position) = 0;
+};
+
+}  // namespace device
+
+#endif  // SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_H_
diff --git a/services/device/geolocation/position_cache_impl.cc b/services/device/geolocation/position_cache_impl.cc
new file mode 100644
index 0000000..11cf0798
--- /dev/null
+++ b/services/device/geolocation/position_cache_impl.cc
@@ -0,0 +1,108 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/device/geolocation/position_cache_impl.h"
+
+#include <algorithm>
+
+#include "base/strings/utf_string_conversions.h"
+#include "services/device/geolocation/wifi_data.h"
+#include "services/device/public/mojom/geoposition.mojom.h"
+
+namespace device {
+
+// static
+const size_t PositionCacheImpl::kMaximumSize = 10;
+// static
+const base::TimeDelta PositionCacheImpl::kMaximumLifetime =
+    base::TimeDelta::FromDays(1);
+
+PositionCacheImpl::CacheEntry::CacheEntry(
+    const Hash& hash,
+    const mojom::Geoposition& position,
+    std::unique_ptr<base::OneShotTimer> eviction_timer)
+    : hash_(hash),
+      position_(position),
+      eviction_timer_(std::move(eviction_timer)) {}
+PositionCacheImpl::CacheEntry::~CacheEntry() = default;
+PositionCacheImpl::CacheEntry::CacheEntry(CacheEntry&&) = default;
+PositionCacheImpl::CacheEntry& PositionCacheImpl::CacheEntry::operator=(
+    CacheEntry&&) = default;
+
+// static
+PositionCacheImpl::Hash PositionCacheImpl::MakeKey(const WifiData& wifi_data) {
+  // Currently we use only WiFi data and base the key only on the MAC addresses.
+  base::string16 key;
+  const size_t kCharsPerMacAddress = 6 * 3 + 1;  // e.g. "11:22:33:44:55:66|"
+  key.reserve(wifi_data.access_point_data.size() * kCharsPerMacAddress);
+  const base::string16 separator(base::ASCIIToUTF16("|"));
+  for (const auto& access_point_data : wifi_data.access_point_data) {
+    key += separator;
+    key += access_point_data.mac_address;
+    key += separator;
+  }
+  return key;
+}
+
+PositionCacheImpl::PositionCacheImpl(const base::TickClock* clock)
+    : clock_(clock) {
+  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+}
+
+PositionCacheImpl::~PositionCacheImpl() {
+  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+}
+
+void PositionCacheImpl::CachePosition(const WifiData& wifi_data,
+                                      const mojom::Geoposition& position) {
+  const Hash key = MakeKey(wifi_data);
+
+  // If the cache is full, remove the oldest entry.
+  if (data_.size() == kMaximumSize)
+    data_.erase(data_.begin());
+
+  DCHECK_LT(data_.size(), kMaximumSize);
+
+  auto eviction_timer = std::make_unique<base::OneShotTimer>(clock_);
+  // Ensure that the entry we're adding will be evicted after kMaximumLifetime.
+  // base::Unretained safe because the timer is indirectly owned by |this|.
+  eviction_timer->Start(FROM_HERE, kMaximumLifetime,
+                        base::BindOnce(&PositionCacheImpl::EvictEntry,
+                                       base::Unretained(this), key));
+
+  data_.emplace_back(key, position, std::move(eviction_timer));
+}
+
+const mojom::Geoposition* PositionCacheImpl::FindPosition(
+    const WifiData& wifi_data) const {
+  const Hash key = MakeKey(wifi_data);
+  auto it = std::find(data_.begin(), data_.end(), key);
+  return it == data_.end() ? nullptr : (it->position());
+}
+
+const mojom::Geoposition& PositionCacheImpl::GetLastUsedNetworkPosition()
+    const {
+  return last_used_position_;
+}
+
+void PositionCacheImpl::SetLastUsedNetworkPosition(
+    const mojom::Geoposition& position) {
+  last_used_position_ = position;
+}
+
+void PositionCacheImpl::OnNetworkChanged(
+    net::NetworkChangeNotifier::ConnectionType) {
+  // OnNetworkChanged is called " when a change occurs to the host
+  // computer's hardware or software that affects the route network packets
+  // take to any network server.". This means that whatever position we had
+  // stored for a wired connection (empty WifiData) could have become stale.
+  EvictEntry(MakeKey(WifiData()));
+  last_used_position_ = {};
+}
+
+void PositionCacheImpl::EvictEntry(const Hash& hash) {
+  data_.erase(std::remove(data_.begin(), data_.end(), hash), data_.end());
+}
+
+}  // namespace device
diff --git a/services/device/geolocation/position_cache_impl.h b/services/device/geolocation/position_cache_impl.h
new file mode 100644
index 0000000..1ee5a1fe
--- /dev/null
+++ b/services/device/geolocation/position_cache_impl.h
@@ -0,0 +1,89 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_IMPL_H_
+#define SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_IMPL_H_
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/network_change_notifier.h"
+#include "services/device/geolocation/position_cache.h"
+#include "services/device/public/mojom/geoposition.mojom.h"
+
+namespace base {
+class TickClock;
+}  // namespace base
+
+namespace device {
+
+class PositionCacheImpl
+    : public PositionCache,
+      public net::NetworkChangeNotifier::NetworkChangeObserver {
+ public:
+  // The maximum size of the cache of positions.
+  static const size_t kMaximumSize;
+  // The maximum time an entry can reside in cache before forced eviction.
+  // This is to ensure the user's location cannot be tracked arbitrarily far
+  // back in history.
+  static const base::TimeDelta kMaximumLifetime;
+
+  // |clock| is used to measure time left until kMaximumLifetime.
+  explicit PositionCacheImpl(const base::TickClock* clock);
+  ~PositionCacheImpl() override;
+
+  void CachePosition(const WifiData& wifi_data,
+                     const mojom::Geoposition& position) override;
+
+  const mojom::Geoposition* FindPosition(
+      const WifiData& wifi_data) const override;
+
+  const mojom::Geoposition& GetLastUsedNetworkPosition() const override;
+  void SetLastUsedNetworkPosition(const mojom::Geoposition& position) override;
+
+  // net::NetworkChangeNotifier::NetworkChangeObserver
+  void OnNetworkChanged(
+      net::NetworkChangeNotifier::ConnectionType type) override;
+
+ private:
+  // In order to avoid O(N) comparisons while searching for the right WifiData,
+  // we hash the contents of those objects and use the hashes as cache keys.
+  using Hash = base::string16;
+
+  class CacheEntry {
+   public:
+    CacheEntry(const Hash& hash,
+               const mojom::Geoposition& position,
+               std::unique_ptr<base::OneShotTimer> eviction_timer);
+    ~CacheEntry();
+    CacheEntry(CacheEntry&&);
+    CacheEntry& operator=(CacheEntry&&);
+
+    inline bool operator==(const Hash& hash) const { return hash_ == hash; }
+    const mojom::Geoposition* position() const { return &position_; }
+
+   private:
+    Hash hash_;
+    mojom::Geoposition position_;
+    std::unique_ptr<base::OneShotTimer> eviction_timer_;
+    DISALLOW_COPY_AND_ASSIGN(CacheEntry);
+  };
+
+  static Hash MakeKey(const WifiData& wifi_data);
+  void EvictEntry(const Hash& hash);
+
+  const base::TickClock* clock_;
+  std::vector<CacheEntry> data_;
+  mojom::Geoposition last_used_position_;
+  DISALLOW_COPY_AND_ASSIGN(PositionCacheImpl);
+};
+
+}  // namespace device
+
+#endif  // SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_IMPL_H_
diff --git a/services/device/geolocation/position_cache_impl_perftest.cc b/services/device/geolocation/position_cache_impl_perftest.cc
new file mode 100644
index 0000000..f0f74826
--- /dev/null
+++ b/services/device/geolocation/position_cache_impl_perftest.cc
@@ -0,0 +1,62 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/device/geolocation/position_cache_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/time/time.h"
+#include "services/device/geolocation/position_cache_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace device {
+
+class PositionCacheImplPerfTest : public ::testing::Test {
+ public:
+  PositionCacheImplPerfTest()
+      : task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
+        cache_(task_environment_.GetMockTickClock()) {}
+
+  void SetUp() override {
+    data_.reserve(kBatchSize);
+    for (size_t i = 0; i < kBatchSize; ++i)
+      data_.push_back(std::make_pair(testing::CreateDefaultUniqueWifiData(),
+                                     testing::CreateGeoposition(i % 90)));
+  }
+
+ protected:
+  static constexpr size_t kBatchSize = 5000;
+  std::vector<std::pair<WifiData, mojom::Geoposition>> data_;
+  base::test::ScopedTaskEnvironment task_environment_;
+  PositionCacheImpl cache_;
+};
+
+TEST_F(PositionCacheImplPerfTest, Adding) {
+  base::Time start = base::Time::Now();
+  for (const auto& pair : data_)
+    cache_.CachePosition(pair.first, pair.second);
+  base::Time end = base::Time::Now();
+  perf_test::PrintResult("adding_to_cache", "", "",
+                         base::TimeDelta(end - start).InMillisecondsF(),
+                         "ms per batch", true);
+}
+
+TEST_F(PositionCacheImplPerfTest, Finding) {
+  for (const auto& pair : data_)
+    cache_.CachePosition(pair.first, pair.second);
+  base::Time start = base::Time::Now();
+  for (const auto& pair : data_)
+    cache_.FindPosition(pair.first);
+  base::Time end = base::Time::Now();
+  perf_test::PrintResult("finding_in_cache", "", "",
+                         base::TimeDelta(end - start).InMillisecondsF(),
+                         "ms per batch", true);
+}
+}  // namespace device
diff --git a/services/device/geolocation/position_cache_impl_unittest.cc b/services/device/geolocation/position_cache_impl_unittest.cc
new file mode 100644
index 0000000..1d7a605
--- /dev/null
+++ b/services/device/geolocation/position_cache_impl_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/device/geolocation/position_cache_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_task_environment.h"
+#include "net/base/network_change_notifier.h"
+#include "services/device/geolocation/position_cache_test_util.h"
+#include "services/device/public/cpp/geolocation/geoposition.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+class PositionCacheImplTest : public ::testing::Test {
+ public:
+  PositionCacheImplTest()
+      : task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
+        network_change_notifier_(net::NetworkChangeNotifier::CreateMock()),
+        cache_(task_environment_.GetMockTickClock()) {}
+
+ protected:
+  base::test::ScopedTaskEnvironment task_environment_;
+  std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_;
+  PositionCacheImpl cache_;
+};
+
+TEST_F(PositionCacheImplTest, EmptyCacheReturnsNoLocations) {
+  WifiData empty_wifi_data;
+  EXPECT_EQ(nullptr, cache_.FindPosition(empty_wifi_data));
+}
+
+TEST_F(PositionCacheImplTest, CanAddEmptyWifiData) {
+  WifiData empty_wifi_data;
+  mojom::Geoposition geoposition = testing::CreateGeoposition(1);
+  cache_.CachePosition(empty_wifi_data, geoposition);
+
+  const mojom::Geoposition* found_position =
+      cache_.FindPosition(empty_wifi_data);
+  ASSERT_NE(nullptr, found_position);
+  EXPECT_TRUE(geoposition.Equals(*found_position));
+}
+
+TEST_F(PositionCacheImplTest, FirstAddedWifiDataReturned) {
+  WifiData wifi_data = testing::CreateDefaultUniqueWifiData();
+  mojom::Geoposition geoposition = testing::CreateGeoposition(1);
+  cache_.CachePosition(wifi_data, geoposition);
+
+  const mojom::Geoposition* found_position = cache_.FindPosition(wifi_data);
+  ASSERT_NE(nullptr, found_position);
+  EXPECT_TRUE(geoposition.Equals(*found_position));
+}
+
+TEST_F(PositionCacheImplTest, LastAddedWifiDataReturned) {
+  cache_.CachePosition(testing::CreateDefaultUniqueWifiData(),
+                       testing::CreateGeoposition(1));
+  cache_.CachePosition(testing::CreateDefaultUniqueWifiData(),
+                       testing::CreateGeoposition(2));
+
+  WifiData final_wifi_data = testing::CreateDefaultUniqueWifiData();
+  mojom::Geoposition final_geoposition = testing::CreateGeoposition(5);
+  cache_.CachePosition(final_wifi_data, final_geoposition);
+
+  const mojom::Geoposition* found_position =
+      cache_.FindPosition(final_wifi_data);
+  ASSERT_NE(nullptr, found_position);
+  EXPECT_TRUE(final_geoposition.Equals(*found_position));
+}
+
+TEST_F(PositionCacheImplTest, MaxPositionsFound) {
+  std::vector<std::pair<WifiData, mojom::Geoposition>> test_data;
+  for (size_t i = 0; i < PositionCacheImpl::kMaximumSize; ++i) {
+    test_data.push_back(std::make_pair(testing::CreateDefaultUniqueWifiData(),
+                                       testing::CreateGeoposition(i)));
+  }
+
+  // Populate the cache
+  for (const auto& test_data_pair : test_data) {
+    cache_.CachePosition(test_data_pair.first, test_data_pair.second);
+  }
+  // Make sure all elements are cached.
+  for (const auto& test_data_pair : test_data) {
+    const mojom::Geoposition* found_position =
+        cache_.FindPosition(test_data_pair.first);
+    ASSERT_NE(nullptr, found_position);
+    EXPECT_TRUE(test_data_pair.second.Equals(*found_position));
+  }
+}
+
+TEST_F(PositionCacheImplTest, Eviction) {
+  WifiData initial_wifi_data = testing::CreateDefaultUniqueWifiData();
+  mojom::Geoposition initial_geoposition = testing::CreateGeoposition(1);
+  cache_.CachePosition(initial_wifi_data, initial_geoposition);
+
+  // Add as many entries as the cache's size limit, which should evict
+  // |initial_wifi_data|.
+  for (size_t i = 0; i < PositionCacheImpl::kMaximumSize; ++i) {
+    cache_.CachePosition(testing::CreateDefaultUniqueWifiData(),
+                         testing::CreateGeoposition(i));
+  }
+
+  // |initial_wifi_data| can no longer be found in cache_.
+  ASSERT_EQ(nullptr, cache_.FindPosition(initial_wifi_data));
+}
+
+TEST_F(PositionCacheImplTest, LastUsedPositionRemembered) {
+  // Initially, invalid.
+  EXPECT_FALSE(ValidateGeoposition(cache_.GetLastUsedNetworkPosition()));
+  // Remembered after setting.
+  mojom::Geoposition position = testing::CreateGeoposition(4);
+  cache_.SetLastUsedNetworkPosition(position);
+  EXPECT_TRUE(position.Equals(cache_.GetLastUsedNetworkPosition()));
+}
+
+TEST_F(PositionCacheImplTest, EntryEvictedAfterMaxLifetimeReached) {
+  WifiData initial_wifi_data = testing::CreateDefaultUniqueWifiData();
+  mojom::Geoposition initial_geoposition = testing::CreateGeoposition(1);
+  cache_.CachePosition(initial_wifi_data, initial_geoposition);
+
+  // Initially, the position is there.
+  const mojom::Geoposition* found_position =
+      cache_.FindPosition(initial_wifi_data);
+  ASSERT_NE(nullptr, found_position);
+  EXPECT_TRUE(initial_geoposition.Equals(*found_position));
+
+  task_environment_.FastForwardBy(PositionCacheImpl::kMaximumLifetime);
+
+  // Position was evicted.
+  EXPECT_EQ(nullptr, cache_.FindPosition(initial_wifi_data));
+}
+
+TEST_F(PositionCacheImplTest, OnlyOldEntriesEvicted) {
+  WifiData older_wifi_data = testing::CreateDefaultUniqueWifiData();
+  mojom::Geoposition older_geoposition = testing::CreateGeoposition(1);
+  cache_.CachePosition(older_wifi_data, older_geoposition);
+
+  // Some time passes, but less than kMaximumLifetime
+  task_environment_.FastForwardBy(PositionCacheImpl::kMaximumLifetime * 0.5);
+
+  // Old position is still there.
+  const mojom::Geoposition* found_position =
+      cache_.FindPosition(older_wifi_data);
+  ASSERT_NE(nullptr, found_position);
+  EXPECT_TRUE(older_geoposition.Equals(*found_position));
+
+  // New position is added.
+  WifiData newer_wifi_data = testing::CreateDefaultUniqueWifiData();
+  mojom::Geoposition newer_geoposition = testing::CreateGeoposition(2);
+  cache_.CachePosition(newer_wifi_data, newer_geoposition);
+
+  // Enough time passes to evict the older entry, but not enough to evict the
+  // newer one.
+  task_environment_.FastForwardBy(PositionCacheImpl::kMaximumLifetime * 0.75);
+
+  EXPECT_EQ(nullptr, cache_.FindPosition(older_wifi_data));
+
+  const mojom::Geoposition* found_newer_position =
+      cache_.FindPosition(newer_wifi_data);
+  ASSERT_NE(nullptr, found_newer_position);
+  EXPECT_TRUE(newer_geoposition.Equals(*found_newer_position));
+}
+
+TEST_F(PositionCacheImplTest, NetworkChangeClearsEmptyWifiDataPosition) {
+  // Cache a position for non-empty WifiData.
+  WifiData initial_wifi_data = testing::CreateDefaultUniqueWifiData();
+  mojom::Geoposition initial_geoposition = testing::CreateGeoposition(1);
+  cache_.CachePosition(initial_wifi_data, initial_geoposition);
+
+  // Cache a position for empty WifiData (wired network).
+  WifiData empty_wifi_data;
+  mojom::Geoposition empty_data_geoposition = testing::CreateGeoposition(2);
+  cache_.CachePosition(empty_wifi_data, empty_data_geoposition);
+
+  cache_.SetLastUsedNetworkPosition(initial_geoposition);
+
+  // When network changes...
+  net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+      net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+  task_environment_.RunUntilIdle();
+
+  // ... last network position is cleared
+  EXPECT_FALSE(ValidateGeoposition(cache_.GetLastUsedNetworkPosition()));
+
+  // And the position for empty WifiData is cleared, since we're probably on
+  // a different wired network now.
+  EXPECT_EQ(nullptr, cache_.FindPosition(empty_wifi_data));
+
+  // But the position for non-empty WifiData remains, since our access point
+  // scans remain valid even if we moved to a different access point.
+  const mojom::Geoposition* found_position =
+      cache_.FindPosition(initial_wifi_data);
+  ASSERT_NE(nullptr, found_position);
+  EXPECT_TRUE(initial_geoposition.Equals(*found_position));
+}
+}  // namespace device
diff --git a/services/device/geolocation/position_cache_test_util.cc b/services/device/geolocation/position_cache_test_util.cc
new file mode 100644
index 0000000..d980e789
--- /dev/null
+++ b/services/device/geolocation/position_cache_test_util.cc
@@ -0,0 +1,40 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/device/geolocation/position_cache_test_util.h"
+
+#include <cmath>
+
+#include "base/guid.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace device {
+namespace testing {
+
+WifiData CreateUniqueWifiData(int number_of_access_points) {
+  WifiData wifi_data;
+  for (int i = 0; i < number_of_access_points; ++i) {
+    AccessPointData single_access_point;
+    single_access_point.channel = 2;
+    single_access_point.mac_address = base::ASCIIToUTF16(base::GenerateGUID());
+    single_access_point.radio_signal_strength = 4;
+    single_access_point.signal_to_noise = 5;
+    single_access_point.ssid = base::ASCIIToUTF16(base::GenerateGUID());
+    wifi_data.access_point_data.insert(single_access_point);
+  }
+  return wifi_data;
+}
+
+mojom::Geoposition CreateGeoposition(int offset) {
+  DCHECK_LT(std::abs(offset), 90) << "latitudes larger than 90 degrees are not "
+                                     "possible on spherical planets";
+  mojom::Geoposition position;
+  position.latitude = offset;
+  position.longitude = offset;
+  return position;
+}
+
+}  // namespace testing
+}  // namespace device
diff --git a/services/device/geolocation/position_cache_test_util.h b/services/device/geolocation/position_cache_test_util.h
new file mode 100644
index 0000000..44337d1b
--- /dev/null
+++ b/services/device/geolocation/position_cache_test_util.h
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_TEST_UTIL_H_
+#define SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_TEST_UTIL_H_
+
+#include "services/device/geolocation/wifi_data.h"
+#include "services/device/public/mojom/geoposition.mojom.h"
+
+namespace device {
+namespace testing {
+
+WifiData CreateUniqueWifiData(int number_of_access_points);
+
+inline WifiData CreateDefaultUniqueWifiData() {
+  return CreateUniqueWifiData(10);
+}
+
+mojom::Geoposition CreateGeoposition(int offset);
+
+}  // namespace testing
+}  // namespace device
+
+#endif  // SERVICES_DEVICE_GEOLOCATION_POSITION_CACHE_TEST_UTIL_H_
diff --git a/services/identity/public/cpp/accounts_cookie_mutator.h b/services/identity/public/cpp/accounts_cookie_mutator.h
index 1c60863..6bfdd6b 100644
--- a/services/identity/public/cpp/accounts_cookie_mutator.h
+++ b/services/identity/public/cpp/accounts_cookie_mutator.h
@@ -21,17 +21,25 @@
   AccountsCookieMutator() = default;
   virtual ~AccountsCookieMutator() = default;
 
+  typedef base::OnceCallback<void(const std::string& account_id,
+                                  const GoogleServiceAuthError& error)>
+      AddAccountToCookieCompletedCallback;
+
   // Adds an account identified by |account_id| to the cookie responsible for
   // tracking the list of logged-in Google sessions across the web.
-  virtual void AddAccountToCookie(const std::string& account_id,
-                                  gaia::GaiaSource source) = 0;
+  virtual void AddAccountToCookie(
+      const std::string& account_id,
+      gaia::GaiaSource source,
+      AddAccountToCookieCompletedCallback completion_callback) = 0;
 
   // Adds an account identified by |account_id| and with |access_token| to the
   // cookie responsible for tracking the list of logged-in Google sessions
   // across the web.
-  virtual void AddAccountToCookieWithToken(const std::string& account_id,
-                                           const std::string& access_token,
-                                           gaia::GaiaSource source) = 0;
+  virtual void AddAccountToCookieWithToken(
+      const std::string& account_id,
+      const std::string& access_token,
+      gaia::GaiaSource source,
+      AddAccountToCookieCompletedCallback completion_callback) = 0;
 
   // Updates the state of the Gaia cookie to contain |account_ids|, including
   // removal of any accounts that are currently present in the cookie but not
diff --git a/services/identity/public/cpp/accounts_cookie_mutator_impl.cc b/services/identity/public/cpp/accounts_cookie_mutator_impl.cc
index 1f6dc5f..874a5a97 100644
--- a/services/identity/public/cpp/accounts_cookie_mutator_impl.cc
+++ b/services/identity/public/cpp/accounts_cookie_mutator_impl.cc
@@ -20,16 +20,19 @@
 
 void AccountsCookieMutatorImpl::AddAccountToCookie(
     const std::string& account_id,
-    gaia::GaiaSource source) {
-  gaia_cookie_manager_service_->AddAccountToCookie(account_id, source);
+    gaia::GaiaSource source,
+    AddAccountToCookieCompletedCallback completion_callback) {
+  gaia_cookie_manager_service_->AddAccountToCookie(
+      account_id, source, std::move(completion_callback));
 }
 
 void AccountsCookieMutatorImpl::AddAccountToCookieWithToken(
     const std::string& account_id,
     const std::string& access_token,
-    gaia::GaiaSource source) {
+    gaia::GaiaSource source,
+    AddAccountToCookieCompletedCallback completion_callback) {
   gaia_cookie_manager_service_->AddAccountToCookieWithToken(
-      account_id, access_token, source);
+      account_id, access_token, source, std::move(completion_callback));
 }
 
 void AccountsCookieMutatorImpl::SetAccountsInCookie(
diff --git a/services/identity/public/cpp/accounts_cookie_mutator_impl.h b/services/identity/public/cpp/accounts_cookie_mutator_impl.h
index aa7edc7..72b56be1 100644
--- a/services/identity/public/cpp/accounts_cookie_mutator_impl.h
+++ b/services/identity/public/cpp/accounts_cookie_mutator_impl.h
@@ -27,12 +27,16 @@
       GaiaCookieManagerService* gaia_cookie_manager_service);
   ~AccountsCookieMutatorImpl() override;
 
-  void AddAccountToCookie(const std::string& account_id,
-                          gaia::GaiaSource source) override;
+  void AddAccountToCookie(
+      const std::string& account_id,
+      gaia::GaiaSource source,
+      AddAccountToCookieCompletedCallback completion_callback) override;
 
-  void AddAccountToCookieWithToken(const std::string& account_id,
-                                   const std::string& access_token,
-                                   gaia::GaiaSource source) override;
+  void AddAccountToCookieWithToken(
+      const std::string& account_id,
+      const std::string& access_token,
+      gaia::GaiaSource source,
+      AddAccountToCookieCompletedCallback completion_callback) override;
 
   void SetAccountsInCookie(
       const std::vector<std::string>& account_ids,
diff --git a/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc b/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc
index c7938a11..24c739a6 100644
--- a/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc
+++ b/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/gtest_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "components/signin/core/browser/list_accounts_test_utils.h"
@@ -142,18 +143,23 @@
 // results in an error due to such account not being available.
 TEST_F(AccountsCookieMutatorTest, AddAccountToCookie_NonExistingAccount) {
   base::RunLoop run_loop;
-  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
-      run_loop.QuitClosure());
+  std::string account_id_from_add_account_to_cookie_completed_callback;
+  GoogleServiceAuthError error_from_add_account_to_cookie_completed_callback;
+  auto completion_callback = base::BindLambdaForTesting(
+      [&](const std::string& account_id, const GoogleServiceAuthError& error) {
+        account_id_from_add_account_to_cookie_completed_callback = account_id;
+        error_from_add_account_to_cookie_completed_callback = error;
+        run_loop.Quit();
+      });
+
   accounts_cookie_mutator()->AddAccountToCookie(kTestUnavailableAccountId,
-                                                gaia::GaiaSource::kChrome);
+                                                gaia::GaiaSource::kChrome,
+                                                std::move(completion_callback));
   run_loop.Run();
 
-  EXPECT_EQ(identity_manager_observer()
-                ->AccountFromAddAccountToCookieCompletedCallback(),
+  EXPECT_EQ(account_id_from_add_account_to_cookie_completed_callback,
             kTestUnavailableAccountId);
-  EXPECT_EQ(identity_manager_observer()
-                ->ErrorFromAddAccountToCookieCompletedCallback()
-                .state(),
+  EXPECT_EQ(error_from_add_account_to_cookie_completed_callback.state(),
             GoogleServiceAuthError::USER_NOT_SIGNED_UP);
 }
 
@@ -165,21 +171,26 @@
 
   std::string account_id = AddAcountWithRefreshToken(kTestAccountEmail);
   base::RunLoop run_loop;
-  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
-      run_loop.QuitClosure());
-  accounts_cookie_mutator()->AddAccountToCookie(account_id,
-                                                gaia::GaiaSource::kChrome);
+  std::string account_id_from_add_account_to_cookie_completed_callback;
+  GoogleServiceAuthError error_from_add_account_to_cookie_completed_callback;
+  auto completion_callback = base::BindLambdaForTesting(
+      [&](const std::string& account_id, const GoogleServiceAuthError& error) {
+        account_id_from_add_account_to_cookie_completed_callback = account_id;
+        error_from_add_account_to_cookie_completed_callback = error;
+        run_loop.Quit();
+      });
+
+  accounts_cookie_mutator()->AddAccountToCookie(
+      account_id, gaia::GaiaSource::kChrome, std::move(completion_callback));
+
   identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
       account_id, kTestAccessToken,
       base::Time::Now() + base::TimeDelta::FromHours(1));
   run_loop.Run();
 
-  EXPECT_EQ(identity_manager_observer()
-                ->AccountFromAddAccountToCookieCompletedCallback(),
+  EXPECT_EQ(account_id_from_add_account_to_cookie_completed_callback,
             account_id);
-  EXPECT_EQ(identity_manager_observer()
-                ->ErrorFromAddAccountToCookieCompletedCallback()
-                .state(),
+  EXPECT_EQ(error_from_add_account_to_cookie_completed_callback.state(),
             GoogleServiceAuthError::NONE);
 }
 
@@ -191,18 +202,23 @@
       AccountsCookiesMutatorAction::kAddAccountToCookie);
 
   base::RunLoop run_loop;
-  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
-      run_loop.QuitClosure());
+  std::string account_id_from_add_account_to_cookie_completed_callback;
+  GoogleServiceAuthError error_from_add_account_to_cookie_completed_callback;
+  auto completion_callback = base::BindLambdaForTesting(
+      [&](const std::string& account_id, const GoogleServiceAuthError& error) {
+        account_id_from_add_account_to_cookie_completed_callback = account_id;
+        error_from_add_account_to_cookie_completed_callback = error;
+        run_loop.Quit();
+      });
+
   accounts_cookie_mutator()->AddAccountToCookieWithToken(
-      kTestUnavailableAccountId, kTestAccessToken, gaia::GaiaSource::kChrome);
+      kTestUnavailableAccountId, kTestAccessToken, gaia::GaiaSource::kChrome,
+      std::move(completion_callback));
   run_loop.Run();
 
-  EXPECT_EQ(identity_manager_observer()
-                ->AccountFromAddAccountToCookieCompletedCallback(),
+  EXPECT_EQ(account_id_from_add_account_to_cookie_completed_callback,
             kTestUnavailableAccountId);
-  EXPECT_EQ(identity_manager_observer()
-                ->ErrorFromAddAccountToCookieCompletedCallback()
-                .state(),
+  EXPECT_EQ(error_from_add_account_to_cookie_completed_callback.state(),
             GoogleServiceAuthError::NONE);
 }
 
@@ -215,19 +231,24 @@
 
   std::string account_id = AddAcountWithRefreshToken(kTestAccountEmail);
   base::RunLoop run_loop;
-  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
-      run_loop.QuitClosure());
+  std::string account_id_from_add_account_to_cookie_completed_callback;
+  GoogleServiceAuthError error_from_add_account_to_cookie_completed_callback;
+  auto completion_callback = base::BindLambdaForTesting(
+      [&](const std::string& account_id, const GoogleServiceAuthError& error) {
+        account_id_from_add_account_to_cookie_completed_callback = account_id;
+        error_from_add_account_to_cookie_completed_callback = error;
+        run_loop.Quit();
+      });
+
   accounts_cookie_mutator()->AddAccountToCookieWithToken(
-      account_id, kTestAccessToken, gaia::GaiaSource::kChrome);
+      account_id, kTestAccessToken, gaia::GaiaSource::kChrome,
+      std::move(completion_callback));
 
   run_loop.Run();
 
-  EXPECT_EQ(identity_manager_observer()
-                ->AccountFromAddAccountToCookieCompletedCallback(),
+  EXPECT_EQ(account_id_from_add_account_to_cookie_completed_callback,
             account_id);
-  EXPECT_EQ(identity_manager_observer()
-                ->ErrorFromAddAccountToCookieCompletedCallback()
-                .state(),
+  EXPECT_EQ(error_from_add_account_to_cookie_completed_callback.state(),
             GoogleServiceAuthError::NONE);
 }
 
diff --git a/services/identity/public/cpp/identity_manager.cc b/services/identity/public/cpp/identity_manager.cc
index 4bf6e5ad..d960705 100644
--- a/services/identity/public/cpp/identity_manager.cc
+++ b/services/identity/public/cpp/identity_manager.cc
@@ -463,14 +463,6 @@
   }
 }
 
-void IdentityManager::OnAddAccountToCookieCompleted(
-    const std::string& account_id,
-    const GoogleServiceAuthError& error) {
-  for (auto& observer : observer_list_) {
-    observer.OnAddAccountToCookieCompleted(account_id, error);
-  }
-}
-
 void IdentityManager::OnGaiaCookieDeletedByUserAction() {
   for (auto& observer : observer_list_) {
     observer.OnAccountsCookieDeletedByUserAction();
diff --git a/services/identity/public/cpp/identity_manager.h b/services/identity/public/cpp/identity_manager.h
index 2fbd28d..7d58b9f 100644
--- a/services/identity/public/cpp/identity_manager.h
+++ b/services/identity/public/cpp/identity_manager.h
@@ -138,13 +138,6 @@
         const AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
         const GoogleServiceAuthError& error) {}
 
-    // Called whenever an attempt to add |account_id| to the list of Gaia
-    // accounts in the cookie jar has finished. If |error| is equal to
-    // GoogleServiceAuthError::AuthErrorNone() then the addittion succeeded.
-    virtual void OnAddAccountToCookieCompleted(
-        const std::string& account_id,
-        const GoogleServiceAuthError& error) {}
-
     // Called when the Gaia cookie has been deleted explicitly by a user
     // action, e.g. from the settings or by an extension.
     virtual void OnAccountsCookieDeletedByUserAction() {}
@@ -525,9 +518,6 @@
       const std::vector<gaia::ListedAccount>& signed_in_accounts,
       const std::vector<gaia::ListedAccount>& signed_out_accounts,
       const GoogleServiceAuthError& error) override;
-  void OnAddAccountToCookieCompleted(
-      const std::string& account_id,
-      const GoogleServiceAuthError& error) override;
   void OnGaiaCookieDeletedByUserAction() override;
 
   // OAuth2TokenService::DiagnosticsObserver:
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index 9bda7898..aa48533d 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -1934,32 +1934,48 @@
 
 TEST_F(IdentityManagerTest, CallbackSentOnSuccessfulAdditionOfAccountToCookie) {
   const char kTestAccountId[] = "account_id";
-  gaia_cookie_manager_service()->AddAccountToCookie(kTestAccountId,
-                                                    gaia::GaiaSource::kChrome);
+
+  std::string account_from_add_account_to_cookie_completed_callback;
+  GoogleServiceAuthError error_from_add_account_to_cookie_completed_callback;
+  auto completion_callback = base::BindLambdaForTesting(
+      [&](const std::string& account_id, const GoogleServiceAuthError& error) {
+        account_from_add_account_to_cookie_completed_callback = account_id;
+        error_from_add_account_to_cookie_completed_callback = error;
+      });
+
+  gaia_cookie_manager_service()->AddAccountToCookie(
+      kTestAccountId, gaia::GaiaSource::kChrome,
+      std::move(completion_callback));
   SimulateAdditionOfAccountToCookieSuccess(gaia_cookie_manager_service(),
                                            "token");
-  EXPECT_EQ(identity_manager_observer()
-                ->AccountFromAddAccountToCookieCompletedCallback(),
+  EXPECT_EQ(account_from_add_account_to_cookie_completed_callback,
             kTestAccountId);
-  EXPECT_EQ(identity_manager_observer()
-                ->ErrorFromAddAccountToCookieCompletedCallback(),
+  EXPECT_EQ(error_from_add_account_to_cookie_completed_callback,
             GoogleServiceAuthError::AuthErrorNone());
 }
 
 TEST_F(IdentityManagerTest, CallbackSentOnFailureAdditionOfAccountToCookie) {
   const char kTestAccountId[] = "account_id";
-  gaia_cookie_manager_service()->AddAccountToCookie(kTestAccountId,
-                                                    gaia::GaiaSource::kChrome);
+
+  std::string account_from_add_account_to_cookie_completed_callback;
+  GoogleServiceAuthError error_from_add_account_to_cookie_completed_callback;
+  auto completion_callback = base::BindLambdaForTesting(
+      [&](const std::string& account_id, const GoogleServiceAuthError& error) {
+        account_from_add_account_to_cookie_completed_callback = account_id;
+        error_from_add_account_to_cookie_completed_callback = error;
+      });
+
+  gaia_cookie_manager_service()->AddAccountToCookie(
+      kTestAccountId, gaia::GaiaSource::kChrome,
+      std::move(completion_callback));
 
   GoogleServiceAuthError error(GoogleServiceAuthError::SERVICE_ERROR);
   SimulateAdditionOfAccountToCookieSuccessFailure(gaia_cookie_manager_service(),
                                                   error);
-  EXPECT_EQ(identity_manager_observer()
-                ->AccountFromAddAccountToCookieCompletedCallback(),
+
+  EXPECT_EQ(account_from_add_account_to_cookie_completed_callback,
             kTestAccountId);
-  EXPECT_EQ(identity_manager_observer()
-                ->ErrorFromAddAccountToCookieCompletedCallback(),
-            error);
+  EXPECT_EQ(error_from_add_account_to_cookie_completed_callback, error);
 }
 
 TEST_F(IdentityManagerTest,
diff --git a/services/identity/public/cpp/test_identity_manager_observer.cc b/services/identity/public/cpp/test_identity_manager_observer.cc
index 2f57d1e..57d63a29 100644
--- a/services/identity/public/cpp/test_identity_manager_observer.cc
+++ b/services/identity/public/cpp/test_identity_manager_observer.cc
@@ -105,23 +105,6 @@
   return error_from_cookie_change_callback_;
 }
 
-void TestIdentityManagerObserver::SetOnAddAccountToCookieCompletedCallback(
-    base::OnceClosure callback) {
-  on_add_account_to_cookie_completed_callback_ = std::move(callback);
-}
-
-const std::string&
-TestIdentityManagerObserver::AccountFromAddAccountToCookieCompletedCallback()
-    const {
-  return account_from_add_account_to_cookie_completed_callback_;
-}
-
-const GoogleServiceAuthError&
-TestIdentityManagerObserver::ErrorFromAddAccountToCookieCompletedCallback()
-    const {
-  return error_from_add_account_to_cookie_completed_callback_;
-}
-
 void TestIdentityManagerObserver::SetOnCookieDeletedByUserCallback(
     base::OnceClosure callback) {
   on_cookie_deleted_by_user_callback_ = std::move(callback);
@@ -215,15 +198,6 @@
     std::move(on_accounts_in_cookie_updated_callback_).Run();
 }
 
-void TestIdentityManagerObserver::OnAddAccountToCookieCompleted(
-    const std::string& account_id,
-    const GoogleServiceAuthError& error) {
-  account_from_add_account_to_cookie_completed_callback_ = account_id;
-  error_from_add_account_to_cookie_completed_callback_ = error;
-  if (on_add_account_to_cookie_completed_callback_)
-    std::move(on_add_account_to_cookie_completed_callback_).Run();
-}
-
 void TestIdentityManagerObserver::OnAccountsCookieDeletedByUserAction() {
   std::move(on_cookie_deleted_by_user_callback_).Run();
 }
diff --git a/services/identity/public/cpp/test_identity_manager_observer.h b/services/identity/public/cpp/test_identity_manager_observer.h
index 28a4b9d..91cc2e6 100644
--- a/services/identity/public/cpp/test_identity_manager_observer.h
+++ b/services/identity/public/cpp/test_identity_manager_observer.h
@@ -52,11 +52,6 @@
   const GoogleServiceAuthError& ErrorFromAccountsInCookieUpdatedCallback()
       const;
 
-  void SetOnAddAccountToCookieCompletedCallback(base::OnceClosure callback);
-  const std::string& AccountFromAddAccountToCookieCompletedCallback() const;
-  const GoogleServiceAuthError& ErrorFromAddAccountToCookieCompletedCallback()
-      const;
-
   void SetOnCookieDeletedByUserCallback(base::OnceClosure callback);
 
   const AccountInfo& AccountFromAccountUpdatedCallback();
@@ -87,9 +82,6 @@
   void OnAccountsInCookieUpdated(
       const AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
       const GoogleServiceAuthError& error) override;
-  void OnAddAccountToCookieCompleted(
-      const std::string& account_id,
-      const GoogleServiceAuthError& error) override;
   void OnAccountsCookieDeletedByUserAction() override;
 
   void OnExtendedAccountInfoUpdated(const AccountInfo& info) override;
@@ -126,10 +118,6 @@
   AccountsInCookieJarInfo accounts_info_from_cookie_change_callback_;
   GoogleServiceAuthError error_from_cookie_change_callback_;
 
-  base::OnceClosure on_add_account_to_cookie_completed_callback_;
-  std::string account_from_add_account_to_cookie_completed_callback_;
-  GoogleServiceAuthError error_from_add_account_to_cookie_completed_callback_;
-
   base::OnceClosure on_cookie_deleted_by_user_callback_;
 
   AccountInfo account_from_account_updated_callback_;
diff --git a/services/tracing/BUILD.gn b/services/tracing/BUILD.gn
index 3ca7786..fe6160c0 100644
--- a/services/tracing/BUILD.gn
+++ b/services/tracing/BUILD.gn
@@ -64,6 +64,8 @@
 
   sources = [
     "agent_registry_unittest.cc",
+    "coordinator_test_util.cc",
+    "coordinator_test_util.h",
     "coordinator_unittest.cc",
     "public/cpp/trace_event_agent_unittest.cc",
     "recorder_unittest.cc",
@@ -99,6 +101,7 @@
     sources += [
       "perfetto/json_trace_exporter_unittest.cc",
       "perfetto/perfetto_integration_unittest.cc",
+      "perfetto/perfetto_tracing_coordinator_unittest.cc",
       "perfetto/test_utils.cc",
       "perfetto/test_utils.h",
     ]
diff --git a/services/tracing/agent_registry.cc b/services/tracing/agent_registry.cc
index 759aacc7..f1e4764 100644
--- a/services/tracing/agent_registry.cc
+++ b/services/tracing/agent_registry.cc
@@ -16,6 +16,8 @@
 
 namespace tracing {
 
+const int32_t kAgentResponseTimeoutInSeconds = 10;
+
 AgentRegistry::AgentEntry::AgentEntry(size_t id,
                                       AgentRegistry* agent_registry,
                                       mojom::AgentPtr agent,
@@ -40,10 +42,17 @@
     base::OnceClosure closure) {
   DCHECK_EQ(0u, closures_.count(closure_name));
   closures_[closure_name] = std::move(closure);
+
+  // Adding a disconnect closure means we're waiting for a response from the
+  // agent. If the client becomes unresponsive, we disconnect it.
+  timer_.Start(FROM_HERE,
+               base::TimeDelta::FromSeconds(kAgentResponseTimeoutInSeconds),
+               this, &AgentRegistry::AgentEntry::OnConnectionError);
 }
 
 bool AgentRegistry::AgentEntry::RemoveDisconnectClosure(
     const void* closure_name) {
+  timer_.Stop();
   return closures_.erase(closure_name) > 0;
 }
 
diff --git a/services/tracing/agent_registry.h b/services/tracing/agent_registry.h
index 523bc52..cb701f3c 100644
--- a/services/tracing/agent_registry.h
+++ b/services/tracing/agent_registry.h
@@ -12,6 +12,7 @@
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "services/service_manager/public/cpp/identity.h"
 #include "services/tracing/public/mojom/tracing.mojom.h"
@@ -53,6 +54,7 @@
     const mojom::TraceDataType type_;
     const base::ProcessId pid_;
     std::map<const void*, base::OnceClosure> closures_;
+    base::RepeatingTimer timer_;
 
     DISALLOW_COPY_AND_ASSIGN(AgentEntry);
   };
@@ -84,7 +86,7 @@
 
  private:
   friend class AgentRegistryTest;  // For testing.
-  friend class CoordinatorTest;    // For testing.
+  friend class CoordinatorTestUtil;  // For testing.
 
   // mojom::AgentRegistry
   void RegisterAgent(mojom::AgentPtr agent,
diff --git a/services/tracing/coordinator.cc b/services/tracing/coordinator.cc
index 821f0de4..b426897 100644
--- a/services/tracing/coordinator.cc
+++ b/services/tracing/coordinator.cc
@@ -37,11 +37,18 @@
 const char kMetadataTraceLabel[] = "metadata";
 
 const char kRequestBufferUsageClosureName[] = "RequestBufferUsageClosure";
+const char kStartTracingClosureName[] = "StartTracingClosure";
+const int32_t kBeginTracingTimeoutInSeconds = 10;
 
 }  // namespace
 
 namespace tracing {
 
+// static
+const void* Coordinator::GetStartTracingClosureName() {
+  return &kStartTracingClosureName;
+}
+
 class Coordinator::TraceStreamer : public base::SupportsWeakPtr<TraceStreamer> {
  public:
   // Constructed on |main_task_runner_|.
@@ -269,9 +276,9 @@
 
 Coordinator::Coordinator(AgentRegistry* agent_registry,
                          const base::RepeatingClosure& on_disconnect_callback)
-    : on_disconnect_callback_(std::move(on_disconnect_callback)),
+    : task_runner_(base::SequencedTaskRunnerHandle::Get()),
+      on_disconnect_callback_(std::move(on_disconnect_callback)),
       binding_(this),
-      task_runner_(base::SequencedTaskRunnerHandle::Get()),
       // USER_VISIBLE because the task posted from StopAndFlushInternal() is
       // required to stop tracing from the UI.
       // TODO(fdoray): Once we have support for dynamic priorities
@@ -294,10 +301,14 @@
 }
 
 void Coordinator::Reset() {
+  start_tracing_callback_timer_.Stop();
+
   if (!stop_and_flush_callback_.is_null()) {
     base::ResetAndReturn(&stop_and_flush_callback_)
         .Run(base::Value(base::Value::Type::DICTIONARY));
   }
+  if (!start_tracing_callback_.is_null())
+    base::ResetAndReturn(&start_tracing_callback_).Run(false);
   if (!request_buffer_usage_callback_.is_null())
     base::ResetAndReturn(&request_buffer_usage_callback_).Run(false, 0, 0);
 
@@ -326,8 +337,11 @@
       &Coordinator::OnClientConnectionError, base::Unretained(this)));
 }
 
-void Coordinator::StartTracing(const std::string& config) {
-  if ((is_tracing_ && config == config_)) {
+void Coordinator::StartTracing(const std::string& config,
+                               StartTracingCallback callback) {
+  bool is_initializing = !start_tracing_callback_.is_null();
+  if (is_initializing || (is_tracing_ && config == config_)) {
+    std::move(callback).Run(config == config_);
     return;
   }
 
@@ -338,13 +352,113 @@
       base::BindRepeating(&Coordinator::SendStartTracingToAgent,
                           weak_ptr_factory_.GetWeakPtr()),
       false /* call_on_new_agents_only */);
+
+  SetStartTracingCallback(std::move(callback));
+}
+
+void Coordinator::SetStartTracingCallback(StartTracingCallback callback) {
+  start_tracing_callback_ = std::move(callback);
+  CallStartTracingCallbackIfNeeded();
+
+  if (!start_tracing_callback_)
+    return;
+
+  // We can't know for sure whether all processses we request
+  // to connect to the tracing service will connect back, or
+  // if all the connected services will ACK our BeginTracing
+  // request eventually, so we'll add a timeout for that case.
+  start_tracing_callback_timer_.Start(
+      FROM_HERE, base::TimeDelta::FromSeconds(kBeginTracingTimeoutInSeconds),
+      this, &Coordinator::OnBeginTracingTimeout);
+}
+
+void Coordinator::OnBeginTracingTimeout() {
+  if (start_tracing_callback_)
+    std::move(start_tracing_callback_).Run(true);
+}
+
+void Coordinator::CallStartTracingCallbackIfNeeded() {
+  // We still have processes we've asked to begin tracing and
+  // need ACKs from.
+  if (agent_registry_->HasDisconnectClosure(&kStartTracingClosureName))
+    return;
+
+  // We're still waiting for the list of PIDs of the currently
+  // running services.
+  if (pending_currently_running_pids_)
+    return;
+
+  // We're still waiting for processes to connect to the
+  // service.
+  for (auto& pid : pending_connected_pids_) {
+    if (parsed_config_.process_filter_config().IsEnabled(pid))
+      return;
+  }
+
+  if (start_tracing_callback_)
+    std::move(start_tracing_callback_).Run(true);
+
+  start_tracing_callback_timer_.Stop();
+}
+
+void Coordinator::AddExpectedPID(base::ProcessId pid) {
+  if (!pid)
+    return;
+
+  bool pid_is_connected = false;
+  agent_registry_->ForAllAgents(
+      [&pid_is_connected, pid](AgentRegistry::AgentEntry* agent_entry) {
+        if (pid == agent_entry->pid())
+          pid_is_connected = true;
+      });
+
+  if (!pid_is_connected)
+    pending_connected_pids_.insert(pid);
+}
+
+void Coordinator::RemoveExpectedPID(base::ProcessId pid) {
+  pending_connected_pids_.erase(pid);
+  CallStartTracingCallbackIfNeeded();
+}
+
+void Coordinator::FinishedReceivingRunningPIDs() {
+  pending_currently_running_pids_ = false;
+  CallStartTracingCallbackIfNeeded();
+}
+
+void Coordinator::ClearConnectedPIDs() {
+  if (!pending_connected_pids_.empty()) {
+    pending_connected_pids_.clear();
+    CallStartTracingCallbackIfNeeded();
+  }
 }
 
 void Coordinator::SendStartTracingToAgent(
     AgentRegistry::AgentEntry* agent_entry) {
+  if (agent_entry->HasDisconnectClosure(&kStartTracingClosureName))
+    return;
   if (!parsed_config_.process_filter_config().IsEnabled(agent_entry->pid()))
     return;
-  agent_entry->agent()->StartTracing(config_, TRACE_TIME_TICKS_NOW());
+  agent_entry->AddDisconnectClosure(
+      &kStartTracingClosureName,
+      base::BindOnce(&Coordinator::OnTracingStarted,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     base::Unretained(agent_entry), false));
+  RemoveExpectedPID(agent_entry->pid());
+
+  agent_entry->agent()->StartTracing(
+      config_, TRACE_TIME_TICKS_NOW(),
+      base::BindRepeating(&Coordinator::OnTracingStarted,
+                          weak_ptr_factory_.GetWeakPtr(),
+                          base::Unretained(agent_entry)));
+}
+
+void Coordinator::OnTracingStarted(AgentRegistry::AgentEntry* agent_entry,
+                                   bool success) {
+  bool removed =
+      agent_entry->RemoveDisconnectClosure(&kStartTracingClosureName);
+  DCHECK(removed);
+  CallStartTracingCallbackIfNeeded();
 }
 
 void Coordinator::StopAndFlush(mojo::ScopedDataPipeProducerHandle stream,
@@ -363,6 +477,12 @@
   DCHECK(!trace_streamer_);
   DCHECK(stream.is_valid());
   is_tracing_ = false;
+  // If we get a Stop call and this is non-empty, it means we either
+  // hit a timeout waiting for processes to connect, or the trace
+  // client sent a stop without waiting for the BeginTracing callback
+  // to happen. In either case, we don't want/need to wait for additional
+  // processes to connect anymore.
+  ClearConnectedPIDs();
 
   trace_streamer_.reset(new Coordinator::TraceStreamer(
       std::move(stream), agent_label, task_runner_,
@@ -372,6 +492,18 @@
 }
 
 void Coordinator::StopAndFlushInternal() {
+  if (start_tracing_callback_) {
+    // We received a |StopAndFlush| command before receiving |StartTracing| acks
+    // from all agents. Let's retry after a delay.
+    task_runner_->PostDelayedTask(
+        FROM_HERE,
+        base::BindRepeating(&Coordinator::StopAndFlushInternal,
+                            weak_ptr_factory_.GetWeakPtr()),
+        base::TimeDelta::FromMilliseconds(
+            mojom::kStopTracingRetryTimeMilliseconds));
+    return;
+  }
+
   size_t num_initialized_agents =
       agent_registry_->SetAgentInitializationCallback(
           base::BindRepeating(&Coordinator::SendStopTracingToAgent,
diff --git a/services/tracing/coordinator.h b/services/tracing/coordinator.h
index 842dc8c..50b5381 100644
--- a/services/tracing/coordinator.h
+++ b/services/tracing/coordinator.h
@@ -14,6 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
+#include "base/timer/timer.h"
 #include "base/trace_event/trace_config.h"
 #include "base/values.h"
 #include "mojo/public/cpp/system/data_pipe.h"
@@ -50,21 +51,36 @@
 
   bool IsConnected();
 
+  void AddExpectedPID(base::ProcessId pid);
+  void RemoveExpectedPID(base::ProcessId pid);
+  void FinishedReceivingRunningPIDs();
+
  protected:
   ~Coordinator() override;
 
+  static const void* GetStartTracingClosureName();
+
   virtual void OnClientConnectionError();
+  void CallStartTracingCallbackIfNeeded();
+  void OnBeginTracingTimeout();
+  void SetStartTracingCallback(StartTracingCallback callback);
+  void ClearConnectedPIDs();
+
+  base::trace_event::TraceConfig parsed_config_;
+  StartTracingCallback start_tracing_callback_;
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
  private:
   friend std::default_delete<Coordinator>;
-  friend class CoordinatorTest;  // For testing.
+  friend class CoordinatorTestUtil;  // For testing.
 
   class TraceStreamer;
 
   void Reset();
 
   // mojom::Coordinator
-  void StartTracing(const std::string& config) override;
+  void StartTracing(const std::string& config,
+                    StartTracingCallback callback) override;
   void StopAndFlush(mojo::ScopedDataPipeProducerHandle stream,
                     StopAndFlushCallback callback) override;
   void StopAndFlushAgent(mojo::ScopedDataPipeProducerHandle stream,
@@ -75,6 +91,7 @@
 
   // Internal methods for collecting events from agents.
   void SendStartTracingToAgent(AgentRegistry::AgentEntry* agent_entry);
+  void OnTracingStarted(AgentRegistry::AgentEntry* agent_entry, bool success);
   void StopAndFlushInternal();
   void SendStopTracingToAgent(AgentRegistry::AgentEntry* agent_entry);
   void SendStopTracingWithNoOpRecorderToAgent(
@@ -89,14 +106,15 @@
 
   base::RepeatingClosure on_disconnect_callback_;
   mojo::Binding<mojom::Coordinator> binding_;
-  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
   const scoped_refptr<base::SequencedTaskRunner> backend_task_runner_;
   AgentRegistry* agent_registry_;
   std::string config_;
-  base::trace_event::TraceConfig parsed_config_;
   bool is_tracing_ = false;
 
   std::unique_ptr<TraceStreamer> trace_streamer_;
+  std::set<base::ProcessId> pending_connected_pids_;
+  bool pending_currently_running_pids_ = true;
+  base::OneShotTimer start_tracing_callback_timer_;
   StopAndFlushCallback stop_and_flush_callback_;
 
   // For computing trace buffer usage.
diff --git a/services/tracing/coordinator_test_util.cc b/services/tracing/coordinator_test_util.cc
new file mode 100644
index 0000000..41bdee5
--- /dev/null
+++ b/services/tracing/coordinator_test_util.cc
@@ -0,0 +1,134 @@
+// 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 "services/tracing/coordinator_test_util.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "services/tracing/agent_registry.h"
+#include "services/tracing/coordinator.h"
+#include "services/tracing/public/mojom/tracing.mojom.h"
+#include "services/tracing/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace tracing {
+
+CoordinatorTestUtil::CoordinatorTestUtil() {}
+CoordinatorTestUtil::~CoordinatorTestUtil() {}
+
+// testing::Test
+void CoordinatorTestUtil::SetUp() {
+  agent_registry_ = std::make_unique<AgentRegistry>();
+  output_ = "";
+  tracing_begin_callback_received_ = false;
+}
+
+// testing::Test
+void CoordinatorTestUtil::TearDown() {
+  agents_.clear();
+  coordinator_.reset();
+  agent_registry_.reset();
+}
+
+// mojo::DataPipeDrainer::Client
+void CoordinatorTestUtil::OnDataAvailable(const void* data, size_t num_bytes) {
+  output_.append(static_cast<const char*>(data), num_bytes);
+}
+
+// mojo::DataPipeDrainer::Client
+void CoordinatorTestUtil::OnDataComplete() {
+  std::move(wait_for_data_closure_).Run();
+}
+
+MockAgent* CoordinatorTestUtil::AddArrayAgent(base::ProcessId pid) {
+  auto agent = std::make_unique<MockAgent>();
+  agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "traceEvents",
+                                 mojom::TraceDataType::ARRAY, pid);
+  agents_.push_back(std::move(agent));
+  return agents_.back().get();
+}
+
+MockAgent* CoordinatorTestUtil::AddArrayAgent() {
+  return AddArrayAgent(base::kNullProcessId);
+}
+
+MockAgent* CoordinatorTestUtil::AddObjectAgent() {
+  auto agent = std::make_unique<MockAgent>();
+  agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "systemTraceEvents",
+                                 mojom::TraceDataType::OBJECT,
+                                 base::kNullProcessId);
+  agents_.push_back(std::move(agent));
+  return agents_.back().get();
+}
+
+MockAgent* CoordinatorTestUtil::AddStringAgent() {
+  auto agent = std::make_unique<MockAgent>();
+  agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "power",
+                                 mojom::TraceDataType::STRING,
+                                 base::kNullProcessId);
+  agents_.push_back(std::move(agent));
+  return agents_.back().get();
+}
+
+void CoordinatorTestUtil::StartTracing(std::string config,
+                                       bool expected_response) {
+  wait_for_data_closure_ = tracing_loop_.QuitClosure();
+
+  coordinator_->StartTracing(
+      config, base::BindRepeating(
+                  [](bool expected, bool* tracing_begin_callback_received,
+                     bool actual) {
+                    EXPECT_EQ(expected, actual);
+                    *tracing_begin_callback_received = true;
+                  },
+                  expected_response, &tracing_begin_callback_received_));
+}
+
+std::string CoordinatorTestUtil::StopAndFlush() {
+  mojo::DataPipe data_pipe;
+  auto dummy_callback = [](base::Value metadata) {};
+  coordinator_->StopAndFlush(std::move(data_pipe.producer_handle),
+                             base::BindRepeating(dummy_callback));
+  drainer_.reset(
+      new mojo::DataPipeDrainer(this, std::move(data_pipe.consumer_handle)));
+  if (!wait_for_data_closure_.is_null())
+    tracing_loop_.Run();
+
+  return output_;
+}
+
+void CoordinatorTestUtil::IsTracing(bool expected_response) {
+  coordinator_->IsTracing(base::BindRepeating(
+      [](bool expected, bool actual) { EXPECT_EQ(expected, actual); },
+      expected_response));
+}
+
+void CoordinatorTestUtil::RequestBufferUsage(float expected_usage,
+                                             uint32_t expected_count) {
+  coordinator_->RequestBufferUsage(base::BindRepeating(
+      [](float expected_usage, uint32_t expected_count, bool success,
+         float usage, uint32_t count) {
+        EXPECT_TRUE(success);
+        EXPECT_EQ(expected_usage, usage);
+        EXPECT_EQ(expected_count, count);
+      },
+      expected_usage, expected_count));
+}
+
+void CoordinatorTestUtil::CheckDisconnectClosures(size_t num_agents) {
+  // Verify that all disconnect closures are cleared up. This means that, for
+  // each agent, either the tracing service is notified that the agent is
+  // disconnected or the agent has answered to all requests.
+  size_t count = 0;
+  agent_registry_->ForAllAgents([&count](AgentRegistry::AgentEntry* entry) {
+    count++;
+    EXPECT_EQ(0u, entry->num_disconnect_closures_for_testing());
+  });
+  EXPECT_EQ(num_agents, count);
+}
+
+}  // namespace tracing
diff --git a/services/tracing/coordinator_test_util.h b/services/tracing/coordinator_test_util.h
new file mode 100644
index 0000000..21c08625
--- /dev/null
+++ b/services/tracing/coordinator_test_util.h
@@ -0,0 +1,74 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_TRACING_COORDINATOR_TEST_UTIL_H_
+#define SERVICES_TRACING_COORDINATOR_TEST_UTIL_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/data_pipe_drainer.h"
+
+namespace tracing {
+
+class AgentRegistry;
+class Coordinator;
+class MockAgent;
+
+class CoordinatorTestUtil : public mojo::DataPipeDrainer::Client {
+ public:
+  CoordinatorTestUtil();
+  ~CoordinatorTestUtil() override;
+
+  void SetUp();
+  void TearDown();
+
+  // mojo::DataPipeDrainer::Client
+  void OnDataAvailable(const void* data, size_t num_bytes) override;
+
+  // mojo::DataPipeDrainer::Client
+  void OnDataComplete() override;
+
+  MockAgent* AddArrayAgent(base::ProcessId pid);
+  MockAgent* AddArrayAgent();
+  MockAgent* AddObjectAgent();
+  MockAgent* AddStringAgent();
+
+  void StartTracing(std::string config, bool expected_response);
+  std::string StopAndFlush();
+
+  void IsTracing(bool expected_response);
+
+  void RequestBufferUsage(float expected_usage, uint32_t expected_count);
+
+  void CheckDisconnectClosures(size_t num_agents);
+
+  AgentRegistry* agent_registry() { return agent_registry_.get(); }
+  bool tracing_begin_callback_received() const {
+    return tracing_begin_callback_received_;
+  }
+
+ protected:
+  std::unique_ptr<Coordinator> coordinator_;
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  std::unique_ptr<AgentRegistry> agent_registry_;
+  std::vector<std::unique_ptr<MockAgent>> agents_;
+  std::unique_ptr<mojo::DataPipeDrainer> drainer_;
+
+  base::RunLoop tracing_loop_;
+  base::RepeatingClosure wait_for_data_closure_;
+  std::string output_;
+  bool tracing_begin_callback_received_ = false;
+};
+
+}  // namespace tracing
+
+#endif  // SERVICES_TRACING_COORDINATOR_TEST_UTIL_H_
diff --git a/services/tracing/coordinator_unittest.cc b/services/tracing/coordinator_unittest.cc
index 4063dd7c..beb9bca 100644
--- a/services/tracing/coordinator_unittest.cc
+++ b/services/tracing/coordinator_unittest.cc
@@ -4,133 +4,30 @@
 
 #include "services/tracing/coordinator.h"
 
-#include <algorithm>
 #include <memory>
-#include <set>
-#include <string>
-#include <utility>
-#include <vector>
 
-#include "base/bind.h"
 #include "base/run_loop.h"
-#include "base/strings/string_split.h"
-#include "base/test/scoped_task_environment.h"
-#include "mojo/public/cpp/system/data_pipe.h"
-#include "mojo/public/cpp/system/data_pipe_drainer.h"
-#include "services/service_manager/public/cpp/service_context_ref.h"
-#include "services/tracing/public/mojom/tracing.mojom.h"
+#include "services/tracing/coordinator_test_util.h"
 #include "services/tracing/test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace tracing {
 
-class CoordinatorTest : public testing::Test,
-                        public mojo::DataPipeDrainer::Client {
+class CoordinatorTest : public testing::Test, public CoordinatorTestUtil {
  public:
-  // testing::Test
   void SetUp() override {
-    agent_registry_ = std::make_unique<AgentRegistry>();
-    coordinator_ = std::make_unique<Coordinator>(agent_registry_.get(),
+    CoordinatorTestUtil::SetUp();
+    coordinator_ = std::make_unique<Coordinator>(agent_registry(),
                                                  base::RepeatingClosure());
-    output_ = "";
+    coordinator_->FinishedReceivingRunningPIDs();
   }
-
-  // testing::Test
-  void TearDown() override {
-    agents_.clear();
-    coordinator_.reset();
-    agent_registry_.reset();
-  }
-
-  // mojo::DataPipeDrainer::Client
-  void OnDataAvailable(const void* data, size_t num_bytes) override {
-    output_.append(static_cast<const char*>(data), num_bytes);
-  }
-
-  // mojo::DataPipeDrainer::Client
-  void OnDataComplete() override { std::move(quit_closure_).Run(); }
-
-  MockAgent* AddArrayAgent(base::ProcessId pid) {
-    auto agent = std::make_unique<MockAgent>();
-    agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "traceEvents",
-                                   mojom::TraceDataType::ARRAY, pid);
-    agents_.push_back(std::move(agent));
-    return agents_.back().get();
-  }
-
-  MockAgent* AddArrayAgent() { return AddArrayAgent(base::kNullProcessId); }
-
-  MockAgent* AddObjectAgent() {
-    auto agent = std::make_unique<MockAgent>();
-    agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "systemTraceEvents",
-                                   mojom::TraceDataType::OBJECT,
-                                   base::kNullProcessId);
-    agents_.push_back(std::move(agent));
-    return agents_.back().get();
-  }
-
-  MockAgent* AddStringAgent() {
-    auto agent = std::make_unique<MockAgent>();
-    agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "power",
-                                   mojom::TraceDataType::STRING,
-                                   base::kNullProcessId);
-    agents_.push_back(std::move(agent));
-    return agents_.back().get();
-  }
-
-  void StartTracing(std::string config) { coordinator_->StartTracing(config); }
-
-  void StopAndFlush() {
-    mojo::DataPipe data_pipe;
-    auto dummy_callback = [](base::Value metadata) {};
-    coordinator_->StopAndFlush(std::move(data_pipe.producer_handle),
-                               base::BindRepeating(dummy_callback));
-    drainer_.reset(
-        new mojo::DataPipeDrainer(this, std::move(data_pipe.consumer_handle)));
-  }
-
-  void IsTracing(bool expected_response) {
-    coordinator_->IsTracing(base::BindRepeating(
-        [](bool expected, bool actual) { EXPECT_EQ(expected, actual); },
-        expected_response));
-  }
-
-  void RequestBufferUsage(float expected_usage, uint32_t expected_count) {
-    coordinator_->RequestBufferUsage(base::BindRepeating(
-        [](float expected_usage, uint32_t expected_count, bool success,
-           float usage, uint32_t count) {
-          EXPECT_TRUE(success);
-          EXPECT_EQ(expected_usage, usage);
-          EXPECT_EQ(expected_count, count);
-        },
-        expected_usage, expected_count));
-  }
-
-  void CheckDisconnectClosures(size_t num_agents) {
-    // Verify that all disconnect closures are cleared up. This means that, for
-    // each agent, either the tracing service is notified that the agent is
-    // disconnected or the agent has answered to all requests.
-    size_t count = 0;
-    agent_registry_->ForAllAgents([&count](AgentRegistry::AgentEntry* entry) {
-      count++;
-      EXPECT_EQ(0u, entry->num_disconnect_closures_for_testing());
-    });
-    EXPECT_EQ(num_agents, count);
-  }
-
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
-  std::unique_ptr<AgentRegistry> agent_registry_;
-  std::unique_ptr<Coordinator> coordinator_;
-  std::vector<std::unique_ptr<MockAgent>> agents_;
-  std::unique_ptr<mojo::DataPipeDrainer> drainer_;
-  base::RepeatingClosure quit_closure_;
-  std::string output_;
+  void TearDown() override { CoordinatorTestUtil::TearDown(); }
 };
 
 TEST_F(CoordinatorTest, StartTracingSimple) {
   base::RunLoop run_loop;
   auto* agent = AddArrayAgent();
-  StartTracing("*");
+  StartTracing("*", true);
   run_loop.RunUntilIdle();
 
   // The agent should have received exactly one call from the coordinator.
@@ -141,7 +38,7 @@
 TEST_F(CoordinatorTest, StartTracingTwoAgents) {
   base::RunLoop run_loop;
   auto* agent1 = AddArrayAgent();
-  StartTracing("*");
+  StartTracing("*", true);
   auto* agent2 = AddStringAgent();
   run_loop.RunUntilIdle();
 
@@ -156,13 +53,13 @@
   base::RunLoop run_loop1;
   auto* agent1 = AddArrayAgent(static_cast<base::ProcessId>(1));
   auto* agent2 = AddArrayAgent(static_cast<base::ProcessId>(2));
-  StartTracing("{\"included_process_ids\":[2,4]}");
+  StartTracing("{\"included_process_ids\":[2,4]}", true);
   run_loop1.RunUntilIdle();
 
   base::RunLoop run_loop2;
   auto* agent3 = AddArrayAgent(static_cast<base::ProcessId>(3));
   auto* agent4 = AddArrayAgent(static_cast<base::ProcessId>(4));
-  StartTracing("{\"included_process_ids\":[4,6]}");
+  StartTracing("{\"included_process_ids\":[4,6]}", true);
   run_loop2.RunUntilIdle();
 
   base::RunLoop run_loop3;
@@ -171,27 +68,38 @@
   run_loop3.RunUntilIdle();
 
   // StartTracing should only be received by agents 2, 4, and 6.
-  // Agent 4 should receive StartTracing twice, as it's
-  // included in both configs.
   EXPECT_EQ(0u, agent1->call_stat().size());
   EXPECT_EQ(1u, agent2->call_stat().size());
   EXPECT_EQ("StartTracing", agent2->call_stat()[0]);
   EXPECT_EQ(0u, agent3->call_stat().size());
-  EXPECT_EQ(2u, agent4->call_stat().size());
+  EXPECT_EQ(1u, agent4->call_stat().size());
   EXPECT_EQ("StartTracing", agent4->call_stat()[0]);
-  EXPECT_EQ("StartTracing", agent4->call_stat()[1]);
   EXPECT_EQ(0u, agent5->call_stat().size());
   EXPECT_EQ(1u, agent6->call_stat().size());
   EXPECT_EQ("StartTracing", agent6->call_stat()[0]);
 }
 
+TEST_F(CoordinatorTest, StartTracingWithDifferentConfigs) {
+  base::RunLoop run_loop;
+  auto* agent = AddArrayAgent();
+  StartTracing("config 1", true);
+  // The 2nd |StartTracing| should return false.
+  StartTracing("config 2", false);
+  run_loop.RunUntilIdle();
+
+  // The agent should have received exactly one call from the coordinator
+  // because the 2nd |StartTracing| was aborted.
+  EXPECT_EQ(1u, agent->call_stat().size());
+  EXPECT_EQ("StartTracing", agent->call_stat()[0]);
+}
+
 TEST_F(CoordinatorTest, StartTracingWithSameConfigs) {
   base::RunLoop run_loop;
   auto* agent = AddArrayAgent();
-  StartTracing("config");
-  // The 2nd |StartTracing| should succeed when we are not trying to change
+  StartTracing("config", true);
+  // The 2nd |StartTracing| should return true when we are not trying to change
   // the config.
-  StartTracing("config");
+  StartTracing("config", true);
   run_loop.RunUntilIdle();
 
   // The agent should have received exactly one call from the coordinator
@@ -201,20 +109,15 @@
 }
 
 TEST_F(CoordinatorTest, StopAndFlushObjectAgent) {
-  base::RunLoop run_loop;
-  quit_closure_ = run_loop.QuitClosure();
-
   auto* agent = AddObjectAgent();
   agent->data_.push_back("\"content\":{\"a\":1}");
   agent->data_.push_back("\"name\":\"etw\"");
 
-  StartTracing("config");
-  StopAndFlush();
-  if (!quit_closure_.is_null())
-    run_loop.Run();
+  StartTracing("config", true);
+  std::string output = StopAndFlush();
 
   EXPECT_EQ("{\"systemTraceEvents\":{\"content\":{\"a\":1},\"name\":\"etw\"}}",
-            output_);
+            output);
 
   // Each agent should have received exactly two calls.
   EXPECT_EQ(2u, agent->call_stat().size());
@@ -223,9 +126,6 @@
 }
 
 TEST_F(CoordinatorTest, StopAndFlushTwoArrayAgents) {
-  base::RunLoop run_loop;
-  quit_closure_ = run_loop.QuitClosure();
-
   auto* agent1 = AddArrayAgent();
   agent1->data_.push_back("e1");
   agent1->data_.push_back("e2");
@@ -234,22 +134,20 @@
   agent2->data_.push_back("e3");
   agent2->data_.push_back("e4");
 
-  StartTracing("config");
-  StopAndFlush();
-  if (!quit_closure_.is_null())
-    run_loop.Run();
+  StartTracing("config", true);
+  std::string output = StopAndFlush();
 
-  // |output_| should be of the form {"traceEvents":[ei,ej,ek,el]}, where
+  // |output| should be of the form {"traceEvents":[ei,ej,ek,el]}, where
   // ei,ej,ek,el is a permutation of e1,e2,e3,e4 such that e1 is before e2 and
   // e3 is before e4 since e1 and 2 come from the same agent and their order
   // should be preserved and, similarly, the order of e3 and e4 should be
   // preserved, too.
-  EXPECT_TRUE(output_ == "{\"traceEvents\":[e1,e2,e3,e4]}" ||
-              output_ == "{\"traceEvents\":[e1,e3,e2,e4]}" ||
-              output_ == "{\"traceEvents\":[e1,e3,e4,e2]}" ||
-              output_ == "{\"traceEvents\":[e3,e1,e2,e4]}" ||
-              output_ == "{\"traceEvents\":[e3,e1,e4,e2]}" ||
-              output_ == "{\"traceEvents\":[e3,e4,e1,e2]}");
+  EXPECT_TRUE(output == "{\"traceEvents\":[e1,e2,e3,e4]}" ||
+              output == "{\"traceEvents\":[e1,e3,e2,e4]}" ||
+              output == "{\"traceEvents\":[e1,e3,e4,e2]}" ||
+              output == "{\"traceEvents\":[e3,e1,e2,e4]}" ||
+              output == "{\"traceEvents\":[e3,e1,e4,e2]}" ||
+              output == "{\"traceEvents\":[e3,e4,e1,e2]}");
 
   // Each agent should have received exactly two calls.
   EXPECT_EQ(2u, agent1->call_stat().size());
@@ -262,9 +160,6 @@
 }
 
 TEST_F(CoordinatorTest, StopAndFlushDifferentTypeAgents) {
-  base::RunLoop run_loop;
-  quit_closure_ = run_loop.QuitClosure();
-
   auto* agent1 = AddArrayAgent();
   agent1->data_.push_back("e1");
   agent1->data_.push_back("e2");
@@ -273,13 +168,11 @@
   agent2->data_.push_back("e3");
   agent2->data_.push_back("e4");
 
-  StartTracing("config");
-  StopAndFlush();
-  if (!quit_closure_.is_null())
-    run_loop.Run();
+  StartTracing("config", true);
+  std::string output = StopAndFlush();
 
-  EXPECT_TRUE(output_ == "{\"traceEvents\":[e1,e2],\"power\":\"e3e4\"}" ||
-              output_ == "{\"power\":\"e3e4\",\"traceEvents\":[e1,e2]}");
+  EXPECT_TRUE(output == "{\"traceEvents\":[e1,e2],\"power\":\"e3e4\"}" ||
+              output == "{\"power\":\"e3e4\",\"traceEvents\":[e1,e2]}");
 
   // Each agent should have received exactly two calls.
   EXPECT_EQ(2u, agent1->call_stat().size());
@@ -292,21 +185,16 @@
 }
 
 TEST_F(CoordinatorTest, StopAndFlushWithMetadata) {
-  base::RunLoop run_loop;
-  quit_closure_ = run_loop.QuitClosure();
-
   auto* agent = AddArrayAgent();
   agent->data_.push_back("event");
   agent->metadata_.SetString("key", "value");
 
-  StartTracing("config");
-  StopAndFlush();
-  if (!quit_closure_.is_null())
-    run_loop.Run();
+  StartTracing("config", true);
+  std::string output = StopAndFlush();
 
   // Metadata is written at after trace data.
   EXPECT_EQ("{\"traceEvents\":[event],\"metadata\":{\"key\":\"value\"}}",
-            output_);
+            output);
   EXPECT_EQ(2u, agent->call_stat().size());
   EXPECT_EQ("StartTracing", agent->call_stat()[0]);
   EXPECT_EQ("StopAndFlush", agent->call_stat()[1]);
@@ -315,7 +203,7 @@
 TEST_F(CoordinatorTest, IsTracing) {
   base::RunLoop run_loop;
   AddArrayAgent();
-  StartTracing("config");
+  StartTracing("config", true);
   IsTracing(true);
   run_loop.RunUntilIdle();
 }
@@ -361,18 +249,15 @@
 }
 
 TEST_F(CoordinatorTest, LateAgents) {
-  base::RunLoop run_loop1;
-  quit_closure_ = run_loop1.QuitClosure();
   auto* agent1 = AddArrayAgent();
-  StartTracing("config");
-  StopAndFlush();
-  if (!quit_closure_.is_null())
-    run_loop1.Run();
 
-  base::RunLoop run_loop2;
+  StartTracing("config", true);
+  StopAndFlush();
+
+  base::RunLoop run_loop;
   auto* agent2 = AddArrayAgent();
   agent2->data_.push_back("discarded data");
-  run_loop2.RunUntilIdle();
+  run_loop.RunUntilIdle();
 
   EXPECT_EQ(2u, agent1->call_stat().size());
   EXPECT_EQ("StartTracing", agent1->call_stat()[0]);
@@ -384,4 +269,31 @@
   EXPECT_EQ("StopAndFlush", agent2->call_stat()[0]);
 }
 
+TEST_F(CoordinatorTest, WaitForSpecificPIDs) {
+  coordinator_->AddExpectedPID(42);
+  coordinator_->AddExpectedPID(4242);
+
+  auto* agent1 = AddArrayAgent(42);
+  StartTracing("config", true);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(tracing_begin_callback_received());
+
+  auto* agent2 = AddArrayAgent(4242);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(tracing_begin_callback_received());
+
+  StopAndFlush();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(2u, agent1->call_stat().size());
+  EXPECT_EQ("StartTracing", agent1->call_stat()[0]);
+  EXPECT_EQ("StopAndFlush", agent1->call_stat()[1]);
+
+  EXPECT_EQ(2u, agent2->call_stat().size());
+  EXPECT_EQ("StartTracing", agent2->call_stat()[0]);
+  EXPECT_EQ("StopAndFlush", agent2->call_stat()[1]);
+}
+
 }  // namespace tracing
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator.cc b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
index 3212cbe..d0ddfc7 100644
--- a/services/tracing/perfetto/perfetto_tracing_coordinator.cc
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/trace_event/trace_log.h"
@@ -13,6 +14,7 @@
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "services/tracing/perfetto/json_trace_exporter.h"
 #include "services/tracing/perfetto/perfetto_service.h"
+#include "services/tracing/public/mojom/constants.mojom.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/consumer.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
@@ -21,15 +23,6 @@
 
 namespace tracing {
 
-namespace {
-
-bool IsArgumentFilterEnabled(const std::string& config) {
-  base::trace_event::TraceConfig chrome_trace_config_obj(config);
-  return chrome_trace_config_obj.IsArgumentFilterEnabled();
-}
-
-}  // namespace
-
 // A TracingSession acts as a perfetto consumer and is used to wrap all the
 // associated state of an on-going tracing session, for easy setup and cleanup.
 //
@@ -40,21 +33,26 @@
  public:
   TracingSession(const std::string& config,
                  base::OnceClosure tracing_over_callback)
-      : json_trace_exporter_(std::make_unique<JSONTraceExporter>(
-            IsArgumentFilterEnabled(config)
-                ? base::trace_event::TraceLog::GetInstance()
-                      ->GetArgumentFilterPredicate()
-                : JSONTraceExporter::ArgumentFilterPredicate(),
-            base::BindRepeating(&TracingSession::OnJSONTraceEventCallback,
-                                base::Unretained(this)))),
-        tracing_over_callback_(std::move(tracing_over_callback)) {
+      : tracing_over_callback_(std::move(tracing_over_callback)) {
+    base::trace_event::TraceConfig chrome_trace_config_obj(config);
+    json_trace_exporter_.reset(new JSONTraceExporter(
+        chrome_trace_config_obj.IsArgumentFilterEnabled()
+            ? base::trace_event::TraceLog::GetInstance()
+                  ->GetArgumentFilterPredicate()
+            : JSONTraceExporter::ArgumentFilterPredicate(),
+        base::BindRepeating(&TracingSession::OnJSONTraceEventCallback,
+                            base::Unretained(this))));
     perfetto::TracingService* service =
         PerfettoService::GetInstance()->GetService();
     consumer_endpoint_ = service->ConnectConsumer(this, /*uid=*/0);
 
     // Start tracing.
     perfetto::TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(4096 * 100);
+    size_t size_limit = chrome_trace_config_obj.GetTraceBufferSizeInKb();
+    if (size_limit == 0) {
+      size_limit = 400 * 1024;
+    }
+    trace_config.add_buffers()->set_size_kb(size_limit);
 
     // Perfetto uses clock_gettime for its internal snapshotting, which gets
     // blocked by the sandboxed and isn't needed for Chrome regardless.
@@ -200,6 +198,11 @@
       binding_(this),
       weak_factory_(this) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
+
+  agent_registry->SetAgentInitializationCallback(
+      base::BindRepeating(&PerfettoTracingCoordinator::OnNewAgentConnected,
+                          weak_factory_.GetWeakPtr()),
+      false /* call_on_new_agents_only */);
 }
 
 PerfettoTracingCoordinator::~PerfettoTracingCoordinator() {
@@ -222,11 +225,44 @@
                      base::Unretained(this)));
 }
 
-void PerfettoTracingCoordinator::StartTracing(const std::string& config) {
+void PerfettoTracingCoordinator::StartTracing(const std::string& config,
+                                              StartTracingCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  parsed_config_ = base::trace_event::TraceConfig(config);
   tracing_session_ = std::make_unique<TracingSession>(
       config, base::BindOnce(&PerfettoTracingCoordinator::OnTracingOverCallback,
                              weak_factory_.GetWeakPtr()));
+
+  SetStartTracingCallback(std::move(callback));
+}
+
+void PerfettoTracingCoordinator::OnNewAgentConnected(
+    AgentRegistry::AgentEntry* agent_entry) {
+  // TODO(oysteine): While we're still using the Agent
+  // system as a fallback when using Perfetto, rather than
+  // the browser directly using a Consumer interface, we have to
+  // attempt to linearize with newly connected agents so we only
+  // call the BeginTracing callback when we can be fairly sure
+  // that all current agents have registered with Perfetto and
+  // started tracing if requested. We do this linearization
+  // by calling RequestBufferStatus but just throwing away the
+  // result.
+  agent_entry->AddDisconnectClosure(GetStartTracingClosureName(),
+                                    base::DoNothing());
+
+  agent_entry->agent()->RequestBufferStatus(base::BindRepeating(
+      [](base::WeakPtr<PerfettoTracingCoordinator> coordinator,
+         AgentRegistry::AgentEntry* agent_entry, uint32_t capacity,
+         uint32_t count) {
+        bool removed =
+            agent_entry->RemoveDisconnectClosure(GetStartTracingClosureName());
+        DCHECK(removed);
+
+        if (coordinator) {
+          coordinator->RemoveExpectedPID(agent_entry->pid());
+        }
+      },
+      weak_factory_.GetWeakPtr(), base::Unretained(agent_entry)));
 }
 
 void PerfettoTracingCoordinator::OnTracingOverCallback() {
@@ -238,8 +274,25 @@
 void PerfettoTracingCoordinator::StopAndFlush(
     mojo::ScopedDataPipeProducerHandle stream,
     StopAndFlushCallback callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(tracing_session_);
+  StopAndFlushAgent(std::move(stream), "", std::move(callback));
+}
+
+void PerfettoTracingCoordinator::StopAndFlushInternal(
+    mojo::ScopedDataPipeProducerHandle stream,
+    StopAndFlushCallback callback) {
+  if (start_tracing_callback_) {
+    // We received a |StopAndFlush| command before receiving |StartTracing| acks
+    // from all agents. Let's retry after a delay.
+    task_runner_->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&PerfettoTracingCoordinator::StopAndFlushInternal,
+                       weak_factory_.GetWeakPtr(), std::move(stream),
+                       std::move(callback)),
+        base::TimeDelta::FromMilliseconds(
+            mojom::kStopTracingRetryTimeMilliseconds));
+    return;
+  }
+
   tracing_session_->StopAndFlush(std::move(stream), std::move(callback));
 }
 
@@ -247,7 +300,11 @@
     mojo::ScopedDataPipeProducerHandle stream,
     const std::string& agent_label,
     StopAndFlushCallback callback) {
-  NOTREACHED();
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(tracing_session_);
+
+  ClearConnectedPIDs();
+  StopAndFlushInternal(std::move(stream), std::move(callback));
 }
 
 void PerfettoTracingCoordinator::IsTracing(IsTracingCallback callback) {
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator.h b/services/tracing/perfetto/perfetto_tracing_coordinator.h
index 6db0495..554346b 100644
--- a/services/tracing/perfetto/perfetto_tracing_coordinator.h
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator.h
@@ -37,7 +37,8 @@
 
   // mojom::Coordinator implementation.
   // Called by the tracing controller.
-  void StartTracing(const std::string& config) override;
+  void StartTracing(const std::string& config,
+                    StartTracingCallback callback) override;
   void StopAndFlush(mojo::ScopedDataPipeProducerHandle stream,
                     StopAndFlushCallback callback) override;
   void StopAndFlushAgent(mojo::ScopedDataPipeProducerHandle stream,
@@ -49,6 +50,9 @@
  private:
   void OnTracingOverCallback();
   void OnClientConnectionError() override;
+  void OnNewAgentConnected(AgentRegistry::AgentEntry* agent_entry);
+  void StopAndFlushInternal(mojo::ScopedDataPipeProducerHandle stream,
+                            StopAndFlushCallback callback);
 
   mojo::Binding<mojom::Coordinator> binding_;
 
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator_unittest.cc b/services/tracing/perfetto/perfetto_tracing_coordinator_unittest.cc
new file mode 100644
index 0000000..ac471fd
--- /dev/null
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/tracing/perfetto/perfetto_tracing_coordinator.h"
+
+#include "base/json/json_reader.h"
+#include "services/tracing/coordinator_test_util.h"
+#include "services/tracing/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace tracing {
+
+class PerfettoCoordinatorTest : public testing::Test,
+                                public CoordinatorTestUtil {
+ public:
+  void SetUp() override {
+    CoordinatorTestUtil::SetUp();
+    coordinator_ = std::make_unique<PerfettoTracingCoordinator>(
+        agent_registry(), base::RepeatingClosure());
+    coordinator_->FinishedReceivingRunningPIDs();
+  }
+  void TearDown() override { CoordinatorTestUtil::TearDown(); }
+};
+
+TEST_F(PerfettoCoordinatorTest, TraceBufferSizeInBytes) {
+  auto* agent = AddArrayAgent();
+  agent->data_.push_back("e1");
+
+  StartTracing("{\"trace_buffer_size_in_kb\":4}", true);
+  std::string output = StopAndFlush();
+
+  auto json_value = base::JSONReader::Read(output);
+  ASSERT_TRUE(json_value);
+
+  const base::DictionaryValue* dict = nullptr;
+  json_value->GetAsDictionary(&dict);
+  ASSERT_TRUE(dict->GetDictionary("metadata", &dict));
+  ASSERT_TRUE(dict->GetDictionary("perfetto_trace_stats", &dict));
+  const base::ListValue* list = nullptr;
+  ASSERT_TRUE(dict->GetList("buffer_stats", &list));
+  EXPECT_EQ(1u, list->GetSize());
+  ASSERT_TRUE(list->GetDictionary(0, &dict));
+  int buffer_size = 0;
+  ASSERT_TRUE(dict->GetInteger("buffer_size", &buffer_size));
+  EXPECT_EQ(4096, buffer_size);
+}
+
+}  // namespace tracing
diff --git a/services/tracing/public/cpp/base_agent.cc b/services/tracing/public/cpp/base_agent.cc
index 0e14ac9..d31c1a9b2 100644
--- a/services/tracing/public/cpp/base_agent.cc
+++ b/services/tracing/public/cpp/base_agent.cc
@@ -39,7 +39,10 @@
 }
 
 void BaseAgent::StartTracing(const std::string& config,
-                             base::TimeTicks coordinator_time) {}
+                             base::TimeTicks coordinator_time,
+                             Agent::StartTracingCallback callback) {
+  std::move(callback).Run(true /* success */);
+}
 
 void BaseAgent::StopAndFlush(tracing::mojom::RecorderPtr recorder) {}
 
diff --git a/services/tracing/public/cpp/base_agent.h b/services/tracing/public/cpp/base_agent.h
index dc1325d..8be2cf54 100644
--- a/services/tracing/public/cpp/base_agent.h
+++ b/services/tracing/public/cpp/base_agent.h
@@ -35,7 +35,8 @@
 
   // tracing::mojom::Agent:
   void StartTracing(const std::string& config,
-                    base::TimeTicks coordinator_time) override;
+                    base::TimeTicks coordinator_time,
+                    Agent::StartTracingCallback callback) override;
   void StopAndFlush(tracing::mojom::RecorderPtr recorder) override;
   void RequestBufferStatus(
       Agent::RequestBufferStatusCallback callback) override;
diff --git a/services/tracing/public/cpp/trace_event_agent.cc b/services/tracing/public/cpp/trace_event_agent.cc
index 88e25d8..94cc70a 100644
--- a/services/tracing/public/cpp/trace_event_agent.cc
+++ b/services/tracing/public/cpp/trace_event_agent.cc
@@ -72,7 +72,8 @@
 }
 
 void TraceEventAgent::StartTracing(const std::string& config,
-                                   base::TimeTicks coordinator_time) {
+                                   base::TimeTicks coordinator_time,
+                                   StartTracingCallback callback) {
   DCHECK(!recorder_);
 #if defined(__native_client__)
   // NaCl and system times are offset by a bit, so subtract some time from
@@ -87,6 +88,7 @@
     enabled_tracing_modes_ |= base::trace_event::TraceLog::FILTERING_MODE;
   base::trace_event::TraceLog::GetInstance()->SetEnabled(
       trace_config, enabled_tracing_modes_);
+  std::move(callback).Run(true);
 }
 
 void TraceEventAgent::StopAndFlush(mojom::RecorderPtr recorder) {
diff --git a/services/tracing/public/cpp/trace_event_agent.h b/services/tracing/public/cpp/trace_event_agent.h
index 1a7c8915..70063d2 100644
--- a/services/tracing/public/cpp/trace_event_agent.h
+++ b/services/tracing/public/cpp/trace_event_agent.h
@@ -49,7 +49,8 @@
 
   // mojom::Agent
   void StartTracing(const std::string& config,
-                    base::TimeTicks coordinator_time) override;
+                    base::TimeTicks coordinator_time,
+                    StartTracingCallback callback) override;
   void StopAndFlush(mojom::RecorderPtr recorder) override;
 
   void RequestBufferStatus(RequestBufferStatusCallback callback) override;
diff --git a/services/tracing/public/cpp/trace_event_agent_unittest.cc b/services/tracing/public/cpp/trace_event_agent_unittest.cc
index 443dd11..9749aac3 100644
--- a/services/tracing/public/cpp/trace_event_agent_unittest.cc
+++ b/services/tracing/public/cpp/trace_event_agent_unittest.cc
@@ -90,7 +90,8 @@
   void StartTracing(const std::string& categories) {
     TraceEventAgent::GetInstance()->StartTracing(
         base::trace_event::TraceConfig(categories, "").ToString(),
-        base::TimeTicks::Now());
+        base::TimeTicks::Now(),
+        base::BindRepeating([](bool success) { EXPECT_TRUE(success); }));
   }
 
   void StopAndFlush(base::Closure quit_closure) {
diff --git a/services/tracing/public/mojom/constants.mojom b/services/tracing/public/mojom/constants.mojom
index d258406d..f62a072 100644
--- a/services/tracing/public/mojom/constants.mojom
+++ b/services/tracing/public/mojom/constants.mojom
@@ -4,6 +4,8 @@
 
 module tracing.mojom;
 
+const uint32 kStopTracingRetryTimeMilliseconds = 100;
+
 const string kServiceName = "tracing";
 
 // The label of agents that provide trace data of the format explained in
diff --git a/services/tracing/public/mojom/tracing.mojom b/services/tracing/public/mojom/tracing.mojom
index 8c081774..3c282c4 100644
--- a/services/tracing/public/mojom/tracing.mojom
+++ b/services/tracing/public/mojom/tracing.mojom
@@ -39,7 +39,8 @@
 // close the recorder connection to signal the tracing service that no more data
 // will be sent.
 interface Agent {
-  StartTracing(string config, mojo_base.mojom.TimeTicks coordinator_time);
+  StartTracing(string config, mojo_base.mojom.TimeTicks coordinator_time)
+      => (bool success);
   StopAndFlush(Recorder recorder);
   RequestBufferStatus() => (uint32 capacity, uint32 count);
 };
@@ -60,7 +61,7 @@
   // The return value is false if tracing is already enabled with a different
   // config. Otherwise, true is returned as soon as the service receives acks
   // from all existing agents and agents that connect during |StartTracing|.
-  StartTracing(string config);
+  StartTracing(string config) => (bool success);
   StopAndFlush(handle<data_pipe_producer> stream)
       => (mojo_base.mojom.DictionaryValue metadata);
   // Same as |StopAndFlush| but only write data from a certain |agent_label| to
diff --git a/services/tracing/test_util.cc b/services/tracing/test_util.cc
index e514302..1bce1291 100644
--- a/services/tracing/test_util.cc
+++ b/services/tracing/test_util.cc
@@ -21,8 +21,10 @@
 }
 
 void MockAgent::StartTracing(const std::string& config,
-                             base::TimeTicks coordinator_time) {
+                             base::TimeTicks coordinator_time,
+                             StartTracingCallback cb) {
   call_stat_.push_back("StartTracing");
+  std::move(cb).Run(true);
 }
 
 void MockAgent::StopAndFlush(mojom::RecorderPtr recorder) {
diff --git a/services/tracing/test_util.h b/services/tracing/test_util.h
index 948f30c..821a7eb0 100644
--- a/services/tracing/test_util.h
+++ b/services/tracing/test_util.h
@@ -37,7 +37,8 @@
  private:
   // mojom::Agent
   void StartTracing(const std::string& config,
-                    base::TimeTicks coordinator_time) override;
+                    base::TimeTicks coordinator_time,
+                    StartTracingCallback cb) override;
   void StopAndFlush(mojom::RecorderPtr recorder) override;
   void RequestBufferStatus(RequestBufferStatusCallback cb) override;
 
diff --git a/services/tracing/tracing_service.cc b/services/tracing/tracing_service.cc
index 1a08557..d277c63 100644
--- a/services/tracing/tracing_service.cc
+++ b/services/tracing/tracing_service.cc
@@ -4,6 +4,7 @@
 
 #include "services/tracing/tracing_service.h"
 
+#include <map>
 #include <utility>
 #include <vector>
 
@@ -25,10 +26,12 @@
 class ServiceListener : public service_manager::mojom::ServiceManagerListener {
  public:
   ServiceListener(service_manager::Connector* connector,
-                  AgentRegistry* agent_registry)
+                  AgentRegistry* agent_registry,
+                  Coordinator* coordinator)
       : binding_(this),
         connector_(connector),
-        agent_registry_(agent_registry) {
+        agent_registry_(agent_registry),
+        coordinator_(coordinator) {
     service_manager::mojom::ServiceManagerPtr service_manager;
     connector_->BindInterface(service_manager::mojom::kServiceName,
                               &service_manager);
@@ -58,31 +61,75 @@
     traced_process->ConnectToTracingService(std::move(new_connection_request));
   }
 
+  size_t CountServicesWithPID(uint32_t pid) {
+    return std::count_if(service_pid_map_.begin(), service_pid_map_.end(),
+                         [pid](decltype(service_pid_map_)::value_type p) {
+                           return p.second == pid;
+                         });
+  }
+
+  void ServiceAddedWithPID(const service_manager::Identity& identity,
+                           uint32_t pid) {
+    service_pid_map_[identity] = pid;
+    // First service with this PID added; expect a connection from it.
+    if (CountServicesWithPID(pid) == 1) {
+      coordinator_->AddExpectedPID(pid);
+      ConnectProcessToTracingService(identity);
+    }
+  }
+
+  void ServiceRemoved(const service_manager::Identity& identity) {
+    auto entry = service_pid_map_.find(identity);
+    if (entry != service_pid_map_.end()) {
+      uint32_t pid = entry->second;
+      service_pid_map_.erase(entry);
+      // Last entry with this PID removed; stop expecting it
+      // to connect to the tracing service.
+      if (CountServicesWithPID(pid) == 0) {
+        coordinator_->RemoveExpectedPID(pid);
+      }
+    }
+  }
+
   // service_manager::mojom::ServiceManagerListener implementation.
   void OnInit(std::vector<service_manager::mojom::RunningServiceInfoPtr>
                   running_services) override {
     for (auto& service : running_services) {
-      ConnectProcessToTracingService(service->identity);
+      if (service->pid) {
+        ServiceAddedWithPID(service->identity, service->pid);
+      }
     }
+
+    coordinator_->FinishedReceivingRunningPIDs();
+  }
+
+  void OnServicePIDReceived(const service_manager::Identity& identity,
+                            uint32_t pid) override {
+    ServiceAddedWithPID(identity, pid);
+  }
+
+  void OnServiceFailedToStart(
+      const service_manager::Identity& identity) override {
+    ServiceRemoved(identity);
+  }
+
+  void OnServiceStopped(const service_manager::Identity& identity) override {
+    ServiceRemoved(identity);
   }
 
   void OnServiceStarted(const service_manager::Identity& identity,
                         uint32_t pid) override {
-    ConnectProcessToTracingService(identity);
   }
 
   void OnServiceCreated(
       service_manager::mojom::RunningServiceInfoPtr service) override {}
-  void OnServicePIDReceived(const service_manager::Identity& identity,
-                            uint32_t pid) override {}
-  void OnServiceFailedToStart(
-      const service_manager::Identity& identity) override {}
-  void OnServiceStopped(const service_manager::Identity& identity) override {}
 
  private:
   mojo::Binding<service_manager::mojom::ServiceManagerListener> binding_;
   service_manager::Connector* connector_;
   AgentRegistry* agent_registry_;
+  Coordinator* coordinator_;
+  std::map<service_manager::Identity, uint32_t> service_pid_map_;
 };
 
 TracingService::TracingService(service_manager::mojom::ServiceRequest request)
@@ -105,7 +152,7 @@
     registry_.AddInterface(
         base::BindRepeating(&PerfettoTracingCoordinator::BindCoordinatorRequest,
                             base::Unretained(perfetto_coordinator.get())));
-    perfetto_tracing_coordinator_ = std::move(perfetto_coordinator);
+    tracing_coordinator_ = std::move(perfetto_coordinator);
   } else {
     auto tracing_coordinator = std::make_unique<Coordinator>(
         tracing_agent_registry_.get(),
@@ -118,7 +165,8 @@
   }
 
   service_listener_ = std::make_unique<ServiceListener>(
-      service_binding_.GetConnector(), tracing_agent_registry_.get());
+      service_binding_.GetConnector(), tracing_agent_registry_.get(),
+      tracing_coordinator_.get());
 }
 
 void TracingService::OnBindInterface(
diff --git a/services/tracing/tracing_service.h b/services/tracing/tracing_service.h
index 298c504..0ff7bbfa3 100644
--- a/services/tracing/tracing_service.h
+++ b/services/tracing/tracing_service.h
@@ -22,7 +22,6 @@
 namespace tracing {
 
 class ServiceListener;
-class PerfettoTracingCoordinator;
 
 class TracingService : public service_manager::Service {
  public:
@@ -47,7 +46,6 @@
       registry_;
   std::unique_ptr<tracing::AgentRegistry> tracing_agent_registry_;
   std::unique_ptr<Coordinator> tracing_coordinator_;
-  std::unique_ptr<PerfettoTracingCoordinator> perfetto_tracing_coordinator_;
 
   std::unique_ptr<ServiceListener> service_listener_;
 
diff --git a/services/video_capture/BUILD.gn b/services/video_capture/BUILD.gn
index a8bdf6f..60a4948 100644
--- a/services/video_capture/BUILD.gn
+++ b/services/video_capture/BUILD.gn
@@ -66,6 +66,10 @@
     "//services/video_capture/public/uma",
     "//services/ws/public/cpp/gpu:gpu",
   ]
+
+  if (is_chromeos) {
+    public_deps += [ "//media/capture/video/chromeos/mojo:cros_camera" ]
+  }
 }
 
 source_set("tests") {
diff --git a/services/video_capture/device_factory.h b/services/video_capture/device_factory.h
index 07af7dd4..4fe8376 100644
--- a/services/video_capture/device_factory.h
+++ b/services/video_capture/device_factory.h
@@ -8,12 +8,20 @@
 #include "services/service_manager/public/cpp/service_context_ref.h"
 #include "services/video_capture/public/mojom/device_factory.mojom.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace video_capture {
 
 class DeviceFactory : public mojom::DeviceFactory {
  public:
   virtual void SetServiceRef(
       std::unique_ptr<service_manager::ServiceContextRef> service_ref) = 0;
+#if defined(OS_CHROMEOS)
+  virtual void BindCrosImageCaptureRequest(
+      cros::mojom::CrosImageCaptureRequest request) = 0;
+#endif  // defined(OS_CHROMEOS)
 };
 
 }  // namespace video_capture
diff --git a/services/video_capture/device_factory_media_to_mojo_adapter.cc b/services/video_capture/device_factory_media_to_mojo_adapter.cc
index ba80b1c..d8a6fdc 100644
--- a/services/video_capture/device_factory_media_to_mojo_adapter.cc
+++ b/services/video_capture/device_factory_media_to_mojo_adapter.cc
@@ -5,6 +5,7 @@
 #include "services/video_capture/device_factory_media_to_mojo_adapter.h"
 
 #include <sstream>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/logging.h"
@@ -194,4 +195,13 @@
   active_devices_by_id_.erase(device_id);
 }
 
+#if defined(OS_CHROMEOS)
+void DeviceFactoryMediaToMojoAdapter::BindCrosImageCaptureRequest(
+    cros::mojom::CrosImageCaptureRequest request) {
+  CHECK(capture_system_);
+
+  capture_system_->BindCrosImageCaptureRequest(std::move(request));
+}
+#endif  // defined(OS_CHROMEOS)
+
 }  // namespace video_capture
diff --git a/services/video_capture/device_factory_media_to_mojo_adapter.h b/services/video_capture/device_factory_media_to_mojo_adapter.h
index d2d0fa3..0122958 100644
--- a/services/video_capture/device_factory_media_to_mojo_adapter.h
+++ b/services/video_capture/device_factory_media_to_mojo_adapter.h
@@ -13,6 +13,10 @@
 #include "services/service_manager/public/cpp/service_context_ref.h"
 #include "services/video_capture/device_factory.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace video_capture {
 
 class DeviceMediaToMojoAdapter;
@@ -48,6 +52,11 @@
       mojom::DevicesChangedObserverPtr observer,
       bool raise_event_if_virtual_devices_already_present) override;
 
+#if defined(OS_CHROMEOS)
+  void BindCrosImageCaptureRequest(
+      cros::mojom::CrosImageCaptureRequest request) override;
+#endif  // defined(OS_CHROMEOS)
+
  private:
   struct ActiveDeviceEntry {
     ActiveDeviceEntry();
diff --git a/services/video_capture/device_factory_provider_impl.cc b/services/video_capture/device_factory_provider_impl.cc
index 14ee298..c73f826 100644
--- a/services/video_capture/device_factory_provider_impl.cc
+++ b/services/video_capture/device_factory_provider_impl.cc
@@ -4,6 +4,8 @@
 
 #include "services/video_capture/device_factory_provider_impl.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/task/post_task.h"
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
@@ -176,4 +178,13 @@
     device_factory_->SetServiceRef(nullptr);
 }
 
+#if defined(OS_CHROMEOS)
+void DeviceFactoryProviderImpl::BindCrosImageCaptureRequest(
+    cros::mojom::CrosImageCaptureRequest request) {
+  CHECK(device_factory_);
+
+  device_factory_->BindCrosImageCaptureRequest(std::move(request));
+}
+#endif  // defined(OS_CHROMEOS)
+
 }  // namespace video_capture
diff --git a/services/video_capture/device_factory_provider_impl.h b/services/video_capture/device_factory_provider_impl.h
index e42a6188..5000a6a0 100644
--- a/services/video_capture/device_factory_provider_impl.h
+++ b/services/video_capture/device_factory_provider_impl.h
@@ -18,6 +18,10 @@
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
 #include "services/video_capture/public/mojom/video_source_provider.mojom.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace video_capture {
 
 class VirtualDeviceEnabledDeviceFactory;
@@ -40,6 +44,11 @@
       mojom::VideoSourceProviderRequest request) override;
   void ShutdownServiceAsap() override;
 
+#if defined(OS_CHROMEOS)
+  void BindCrosImageCaptureRequest(
+      cros::mojom::CrosImageCaptureRequest request);
+#endif  // defined(OS_CHROMEOS)
+
  private:
   class GpuDependenciesContext;
 
diff --git a/services/video_capture/public/cpp/BUILD.gn b/services/video_capture/public/cpp/BUILD.gn
index 5388362..07fdfdaa 100644
--- a/services/video_capture/public/cpp/BUILD.gn
+++ b/services/video_capture/public/cpp/BUILD.gn
@@ -34,6 +34,9 @@
     "//services/video_capture/public/mojom",
     "//services/video_capture/public/mojom:constants",
   ]
+  if (is_chromeos) {
+    deps += [ "//media/capture/video/chromeos/mojo:cros_camera" ]
+  }
 }
 
 source_set("mocks") {
diff --git a/services/video_capture/public/cpp/manifest.cc b/services/video_capture/public/cpp/manifest.cc
index 37a1937..89e38fc 100644
--- a/services/video_capture/public/cpp/manifest.cc
+++ b/services/video_capture/public/cpp/manifest.cc
@@ -5,6 +5,9 @@
 #include "services/video_capture/public/cpp/manifest.h"
 
 #include "base/no_destructor.h"
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
 #include "services/service_manager/public/cpp/manifest_builder.h"
 #include "services/video_capture/public/mojom/constants.mojom.h"
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
@@ -14,22 +17,26 @@
 
 const service_manager::Manifest& GetManifest() {
   static base::NoDestructor<service_manager::Manifest> manifest{
-      service_manager::ManifestBuilder()
-          .WithServiceName(mojom::kServiceName)
-          .WithDisplayName("Video Capture")
-          .WithOptions(service_manager::ManifestOptionsBuilder()
-                           .WithSandboxType("none")
-                           .WithInstanceSharingPolicy(
-                               service_manager::Manifest::
-                                   InstanceSharingPolicy::kSharedAcrossGroups)
-                           .Build())
-          .ExposeCapability("capture", service_manager::Manifest::InterfaceList<
-                                           mojom::DeviceFactoryProvider>())
-          .ExposeCapability(
-              "tests",
-              service_manager::Manifest::InterfaceList<
-                  mojom::DeviceFactoryProvider, mojom::TestingControls>())
-          .Build()};
+    service_manager::ManifestBuilder()
+        .WithServiceName(mojom::kServiceName)
+        .WithDisplayName("Video Capture")
+        .WithOptions(service_manager::ManifestOptionsBuilder()
+                         .WithSandboxType("none")
+                         .WithInstanceSharingPolicy(
+                             service_manager::Manifest::InstanceSharingPolicy::
+                                 kSharedAcrossGroups)
+                         .Build())
+        .ExposeCapability("capture", service_manager::Manifest::InterfaceList<
+#if defined(OS_CHROMEOS)
+                                         cros::mojom::CrosImageCapture,
+#endif  // defined(OS_CHROMEOS)
+                                         mojom::DeviceFactoryProvider>())
+        .ExposeCapability(
+            "tests",
+            service_manager::Manifest::InterfaceList<
+                mojom::DeviceFactoryProvider, mojom::TestingControls>())
+        .Build()
+  };
   return *manifest;
 }
 
diff --git a/services/video_capture/service_impl.cc b/services/video_capture/service_impl.cc
index 46eff00..5bf1432 100644
--- a/services/video_capture/service_impl.cc
+++ b/services/video_capture/service_impl.cc
@@ -4,6 +4,8 @@
 
 #include "services/video_capture/service_impl.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
@@ -12,6 +14,10 @@
 #include "services/video_capture/public/uma/video_capture_service_event.h"
 #include "services/video_capture/testing_controls_impl.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace video_capture {
 
 namespace {
@@ -92,6 +98,11 @@
       base::BindRepeating(&ServiceImpl::OnTestingControlsRequest,
                           base::Unretained(this)));
 
+#if defined(OS_CHROMEOS)
+  registry_.AddInterface<cros::mojom::CrosImageCapture>(base::BindRepeating(
+      &ServiceImpl::OnCrosImageCaptureRequest, base::Unretained(this)));
+#endif  // defined(OS_CHROMEOS)
+
   // Unretained |this| is safe because |factory_provider_bindings_| is owned by
   // |this|.
   factory_provider_bindings_.set_connection_error_handler(base::BindRepeating(
@@ -145,6 +156,14 @@
       std::move(request));
 }
 
+#if defined(OS_CHROMEOS)
+void ServiceImpl::OnCrosImageCaptureRequest(
+    cros::mojom::CrosImageCaptureRequest request) {
+  LazyInitializeDeviceFactoryProvider();
+  device_factory_provider_->BindCrosImageCaptureRequest(std::move(request));
+}
+#endif  // defined(OS_CHROMEOS)
+
 void ServiceImpl::LazyInitializeDeviceFactoryProvider() {
   if (device_factory_provider_)
     return;
diff --git a/services/video_capture/service_impl.h b/services/video_capture/service_impl.h
index b9f234f..65b2a12a 100644
--- a/services/video_capture/service_impl.h
+++ b/services/video_capture/service_impl.h
@@ -24,6 +24,10 @@
 #include "base/win/scoped_com_initializer.h"
 #endif
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace base {
 class SingleThreadTaskRunner;
 }
@@ -68,6 +72,9 @@
   void OnDeviceFactoryProviderRequest(
       mojom::DeviceFactoryProviderRequest request);
   void OnTestingControlsRequest(mojom::TestingControlsRequest request);
+#if defined(OS_CHROMEOS)
+  void OnCrosImageCaptureRequest(cros::mojom::CrosImageCaptureRequest request);
+#endif  // defined(OS_CHROMEOS)
   void MaybeRequestQuitDelayed();
   void MaybeRequestQuit();
   void LazyInitializeDeviceFactoryProvider();
diff --git a/services/video_capture/virtual_device_enabled_device_factory.cc b/services/video_capture/virtual_device_enabled_device_factory.cc
index 3448093..131d981 100644
--- a/services/video_capture/virtual_device_enabled_device_factory.cc
+++ b/services/video_capture/virtual_device_enabled_device_factory.cc
@@ -253,4 +253,13 @@
   devices_changed_observers_.erase(iter);
 }
 
+#if defined(OS_CHROMEOS)
+void VirtualDeviceEnabledDeviceFactory::BindCrosImageCaptureRequest(
+    cros::mojom::CrosImageCaptureRequest request) {
+  CHECK(device_factory_);
+
+  device_factory_->BindCrosImageCaptureRequest(std::move(request));
+}
+#endif  // defined(OS_CHROMEOS)
+
 }  // namespace video_capture
diff --git a/services/video_capture/virtual_device_enabled_device_factory.h b/services/video_capture/virtual_device_enabled_device_factory.h
index ec508ea..ac6a644 100644
--- a/services/video_capture/virtual_device_enabled_device_factory.h
+++ b/services/video_capture/virtual_device_enabled_device_factory.h
@@ -6,6 +6,7 @@
 #define SERVICES_VIDEO_CAPTURE_VIRTUAL_DEVICE_ENABLED_DEVICE_FACTORY_H_
 
 #include <map>
+#include <utility>
 
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/service_manager/public/cpp/service_context_ref.h"
@@ -13,6 +14,10 @@
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/virtual_device.mojom.h"
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/mojo/cros_image_capture.mojom.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace video_capture {
 
 // Decorator that adds support for virtual devices to a given DeviceFactory.
@@ -41,6 +46,11 @@
       mojom::DevicesChangedObserverPtr observer,
       bool raise_event_if_virtual_devices_already_present) override;
 
+#if defined(OS_CHROMEOS)
+  void BindCrosImageCaptureRequest(
+      cros::mojom::CrosImageCaptureRequest request) override;
+#endif  // defined(OS_CHROMEOS)
+
  private:
   class VirtualDeviceEntry;
 
diff --git a/third_party/blink/public/web/web_widget.h b/third_party/blink/public/web/web_widget.h
index a7da4679..42de1cd7 100644
--- a/third_party/blink/public/web/web_widget.h
+++ b/third_party/blink/public/web/web_widget.h
@@ -101,9 +101,6 @@
   virtual void BeginFrame(base::TimeTicks last_frame_time,
                           bool record_main_frame_metrics) {}
 
-  // Called after UpdateAllLifecyclePhases has run in response to a BeginFrame.
-  virtual void DidBeginFrame() {}
-
   // Called when main frame metrics are desired. The local frame's UKM
   // aggregator must be informed that collection is starting for the
   // frame.
diff --git a/third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h b/third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h
index 6080546a..144b9e42 100644
--- a/third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h
+++ b/third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h
@@ -6,8 +6,10 @@
 #define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_ACTIVE_SCRIPT_WRAPPABLE_H_
 
 #include "base/macros.h"
+#include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/bindings/active_script_wrappable_base.h"
+#include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
 
@@ -48,13 +50,31 @@
   bool IsContextDestroyed() const final {
     const auto* execution_context =
         static_cast<const T*>(this)->GetExecutionContext();
-    return !execution_context || execution_context->IsContextDestroyed();
+    if (!execution_context)
+      return true;
+
+    if (execution_context->IsContextDestroyed())
+      return true;
+
+    if (const auto* doc = DynamicTo<Document>(execution_context)) {
+      // Not all Document objects have an ExecutionContext that is actually
+      // destroyed. In such cases we defer to the ContextDocument if possible.
+      // If no such Document exists we consider the ExecutionContext as
+      // destroyed. This is needed to ensure that an ActiveScriptWrappable that
+      // always returns true in HasPendingActivity does not result in a memory
+      // leak.
+      const Document* context_doc = doc->ContextDocument();
+      if (!context_doc)
+        return true;
+      return context_doc->IsContextDestroyed();
+    }
+
+    return false;
   }
 
   bool DispatchHasPendingActivity() const final {
     return static_cast<const T*>(this)->HasPendingActivity();
   }
-
   ScriptWrappable* ToScriptWrappable() final { return static_cast<T*>(this); }
 
  private:
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_threaded_test.cc b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_threaded_test.cc
index 1fd3bfde..387060f 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_threaded_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_threaded_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
 
+#include "base/synchronization/waitable_event.h"
 #include "build/build_config.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/unpacked_serialized_script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
@@ -68,10 +69,10 @@
 
   // Wait for a subsequent task on the worker to finish, to ensure that the
   // references held by the task are dropped.
-  WaitableEvent done;
+  base::WaitableEvent done;
   worker_thread.GetWorkerBackingThread().BackingThread().PostTask(
-      FROM_HERE,
-      CrossThreadBind(&WaitableEvent::Signal, CrossThreadUnretained(&done)));
+      FROM_HERE, CrossThreadBind(&base::WaitableEvent::Signal,
+                                 CrossThreadUnretained(&done)));
   done.Wait();
 
   // Now destroy the value on the main thread.
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h b/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h
index 6b817eea..65f3e3f 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h
@@ -177,6 +177,19 @@
   V8SetReturnValueFast(callbackInfo, notShared.View(), wrappable);
 }
 
+template <typename CallbackInfo, typename T>
+inline void V8SetReturnValue(const CallbackInfo& callback_info,
+                             MaybeShared<T> maybe_shared) {
+  V8SetReturnValue(callback_info, maybe_shared.View());
+}
+
+template <typename CallbackInfo, typename T>
+inline void V8SetReturnValueFast(const CallbackInfo& callback_info,
+                                 MaybeShared<T> maybe_shared,
+                                 const ScriptWrappable* wrappable) {
+  V8SetReturnValueFast(callback_info, maybe_shared.View(), wrappable);
+}
+
 // Specialized overload, used by interface indexed property handlers in their
 // descriptor callbacks, which need an actual V8 Object with the properties of
 // a property descriptor.
diff --git a/third_party/blink/renderer/bindings/scripts/v8_methods.py b/third_party/blink/renderer/bindings/scripts/v8_methods.py
index 6d067908..846035a 100644
--- a/third_party/blink/renderer/bindings/scripts/v8_methods.py
+++ b/third_party/blink/renderer/bindings/scripts/v8_methods.py
@@ -188,7 +188,8 @@
         'arguments': argument_contexts,
         'camel_case_name': NameStyleConverter(name).to_upper_camel_case(),
         'cpp_type': (v8_types.cpp_template_type('base::Optional', idl_type.cpp_type)
-                     if idl_type.is_explicit_nullable else idl_type.cpp_type),
+                     if idl_type.is_explicit_nullable
+                     else v8_types.cpp_type(idl_type, extended_attributes=extended_attributes)),
         'cpp_value': this_cpp_value,
         'cpp_type_initializer': idl_type.cpp_type_initializer,
         'high_entropy': v8_utilities.high_entropy(method),  # [HighEntropy]
diff --git a/third_party/blink/renderer/core/css/cssom/cross_thread_style_value_test.cc b/third_party/blink/renderer/core/css/cssom/cross_thread_style_value_test.cc
index d276588..95bd0b92 100644
--- a/third_party/blink/renderer/core/css/cssom/cross_thread_style_value_test.cc
+++ b/third_party/blink/renderer/core/css/cssom/cross_thread_style_value_test.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_keyword_value.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_unit_value.h"
@@ -15,21 +16,20 @@
 #include "third_party/blink/renderer/core/css/cssom/css_unit_value.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 
 namespace blink {
 
 class CrossThreadStyleValueTest : public testing::Test {
  public:
-  void ShutDown(WaitableEvent* waitable_event) {
+  void ShutDown(base::WaitableEvent* waitable_event) {
     DCHECK(!IsMainThread());
     thread_->ShutdownOnThread();
     waitable_event->Signal();
   }
 
   void ShutDownThread() {
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     thread_->PostTask(FROM_HERE,
                       CrossThreadBind(&CrossThreadStyleValueTest::ShutDown,
                                       CrossThreadUnretained(this),
@@ -38,7 +38,7 @@
   }
 
   void CheckUnsupportedValue(
-      WaitableEvent* waitable_event,
+      base::WaitableEvent* waitable_event,
       std::unique_ptr<CrossThreadUnsupportedValue> value) {
     DCHECK(!IsMainThread());
     thread_->InitializeOnThread();
@@ -47,7 +47,7 @@
     waitable_event->Signal();
   }
 
-  void CheckKeywordValue(WaitableEvent* waitable_event,
+  void CheckKeywordValue(base::WaitableEvent* waitable_event,
                          std::unique_ptr<CrossThreadKeywordValue> value) {
     DCHECK(!IsMainThread());
     thread_->InitializeOnThread();
@@ -56,7 +56,7 @@
     waitable_event->Signal();
   }
 
-  void CheckUnitValue(WaitableEvent* waitable_event,
+  void CheckUnitValue(base::WaitableEvent* waitable_event,
                       std::unique_ptr<CrossThreadUnitValue> value) {
     DCHECK(!IsMainThread());
     thread_->InitializeOnThread();
@@ -80,7 +80,7 @@
   // Use a WebThreadSupportingGC to emulate worklet thread.
   thread_ = WebThreadSupportingGC::Create(
       ThreadCreationParams(WebThreadType::kTestThread));
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   thread_->PostTask(
       FROM_HERE,
       CrossThreadBind(&CrossThreadStyleValueTest::CheckUnsupportedValue,
@@ -111,7 +111,7 @@
   // Use a WebThreadSupportingGC to emulate worklet thread.
   thread_ = WebThreadSupportingGC::Create(
       ThreadCreationParams(WebThreadType::kTestThread));
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   thread_->PostTask(
       FROM_HERE, CrossThreadBind(&CrossThreadStyleValueTest::CheckKeywordValue,
                                  CrossThreadUnretained(this),
@@ -142,7 +142,7 @@
   // Use a WebThreadSupportingGC to emulate worklet thread.
   thread_ = WebThreadSupportingGC::Create(
       ThreadCreationParams(WebThreadType::kTestThread));
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   thread_->PostTask(FROM_HERE,
                     CrossThreadBind(&CrossThreadStyleValueTest::CheckUnitValue,
                                     CrossThreadUnretained(this),
diff --git a/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc b/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc
index f44753b..2277f039 100644
--- a/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc
+++ b/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
 #include "third_party/blink/renderer/core/css/css_test_helpers.h"
@@ -16,7 +17,6 @@
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 
 namespace blink {
@@ -29,14 +29,14 @@
 
   Node* PageNode() { return GetDocument().documentElement(); }
 
-  void ShutDown(WaitableEvent* waitable_event) {
+  void ShutDown(base::WaitableEvent* waitable_event) {
     DCHECK(!IsMainThread());
     thread_->ShutdownOnThread();
     waitable_event->Signal();
   }
 
   void ShutDownThread() {
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     thread_->PostTask(
         FROM_HERE, CrossThreadBind(&PaintWorkletStylePropertyMapTest::ShutDown,
                                    CrossThreadUnretained(this),
@@ -95,7 +95,7 @@
     exception_state.ClearException();
   }
 
-  void CheckStyleMap(WaitableEvent* waitable_event,
+  void CheckStyleMap(base::WaitableEvent* waitable_event,
                      scoped_refptr<PaintWorkletInput> input) {
     DCHECK(!IsMainThread());
     thread_->InitializeOnThread();
@@ -243,7 +243,7 @@
 
   thread_ = WebThreadSupportingGC::Create(
       ThreadCreationParams(WebThreadType::kTestThread));
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   thread_->PostTask(
       FROM_HERE, CrossThreadBind(
                      &PaintWorkletStylePropertyMapTest::CheckStyleMap,
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 71b8964..a08682a9 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -671,9 +671,9 @@
     return;
   }
 
-  TreeScopeStyleSheetCollection* collection = StyleSheetCollectionFor(scope);
-  DCHECK(collection);
-  collection->MarkSheetListDirty();
+  TreeScopeStyleSheetCollection& collection =
+      EnsureStyleSheetCollectionFor(scope);
+  collection.MarkSheetListDirty();
   dirty_tree_scopes_.insert(&scope);
   GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
 }
diff --git a/third_party/blink/renderer/core/css/threaded/multi_threaded_test_util.h b/third_party/blink/renderer/core/css/threaded/multi_threaded_test_util.h
index b0e022ff..a65d8167 100644
--- a/third_party/blink/renderer/core/css/threaded/multi_threaded_test_util.h
+++ b/third_party/blink/renderer/core/css/threaded/multi_threaded_test_util.h
@@ -10,12 +10,12 @@
 #include <memory>
 
 #include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
@@ -55,12 +55,12 @@
   template <typename FunctionType, typename... Ps>
   void RunOnThreads(FunctionType function, Ps&&... parameters) {
     Vector<std::unique_ptr<WebThreadSupportingGC>> threads;
-    Vector<std::unique_ptr<WaitableEvent>> waits;
+    Vector<std::unique_ptr<base::WaitableEvent>> waits;
 
     for (int i = 0; i < num_threads_; ++i) {
       threads.push_back(WebThreadSupportingGC::Create(
           ThreadCreationParams(WebThreadType::kTestThread)));
-      waits.push_back(std::make_unique<WaitableEvent>());
+      waits.push_back(std::make_unique<base::WaitableEvent>());
     }
 
     for (int i = 0; i < num_threads_; ++i) {
@@ -82,7 +82,7 @@
       PostCrossThreadTask(
           *task_runner, FROM_HERE,
           CrossThreadBind(
-              [](WebThreadSupportingGC* thread, WaitableEvent* w) {
+              [](WebThreadSupportingGC* thread, base::WaitableEvent* w) {
                 thread->ShutdownOnThread();
                 w->Signal();
               },
diff --git a/third_party/blink/renderer/core/dom/shadow_root.cc b/third_party/blink/renderer/core/dom/shadow_root.cc
index 4b7d59d..54d246a 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.cc
+++ b/third_party/blink/renderer/core/dom/shadow_root.cc
@@ -167,6 +167,9 @@
   if (!insertion_point.isConnected())
     return kInsertionDone;
 
+  if (HasAdoptedStyleSheets())
+    GetDocument().GetStyleEngine().SetNeedsActiveStyleUpdate(*this);
+
   GetDocument().GetSlotAssignmentEngine().Connected(*this);
 
   // FIXME: When parsing <video controls>, InsertedInto() is called many times
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 54ad0cd3..e9544783c 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -10689,11 +10689,8 @@
 
   void ExecuteScriptOnMainFrame(const WebScriptSource& script) {
     MainFrame()->ExecuteScript(script);
-    MainFrame()->View()->MainFrameWidget()->BeginFrame(base::TimeTicks::Now(),
-                                                       false);
     MainFrame()->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
         WebWidget::LifecycleUpdateReason::kTest);
-    MainFrame()->View()->MainFrameWidget()->DidBeginFrame();
     RunPendingTasks();
   }
 
@@ -10783,11 +10780,8 @@
 
   void ExecuteScriptOnMainFrame(const WebScriptSource& script) {
     MainFrame()->ExecuteScript(script);
-    MainFrame()->View()->MainFrameWidget()->BeginFrame(base::TimeTicks::Now(),
-                                                       false);
     MainFrame()->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
         WebWidget::LifecycleUpdateReason::kTest);
-    MainFrame()->View()->MainFrameWidget()->DidBeginFrame();
     RunPendingTasks();
   }
 
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 4321830..66748699 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1521,13 +1521,6 @@
   PageWidgetDelegate::Animate(*AsView().page, last_frame_time);
 }
 
-void WebViewImpl::DidBeginFrame() {
-  DCHECK(MainFrameImpl()->GetFrame());
-  DocumentLifecycle::AllowThrottlingScope throttling_scope(
-      MainFrameImpl()->GetFrame()->GetDocument()->Lifecycle());
-  PageWidgetDelegate::DidBeginFrame(*MainFrameImpl()->GetFrame());
-}
-
 void WebViewImpl::BeginRafAlignedInput() {
   raf_aligned_input_start_time_ = CurrentTimeTicks();
 }
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 0a44b85..46a12c4 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -432,7 +432,6 @@
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) override;
   void BeginFrame(base::TimeTicks last_frame_time,
                   bool record_main_frame_metrics) override;
-  void DidBeginFrame() override;
   void BeginRafAlignedInput() override;
   void EndRafAlignedInput() override;
   void RecordStartOfFrameMetrics() override;
@@ -485,7 +484,6 @@
   friend class WebView;  // So WebView::Create can call our constructor
   friend class WebViewFrameWidget;
   friend class WTF::RefCounted<WebViewImpl>;
-  friend class SimCompositor;
 
   WebViewImpl(WebViewClient*,
               bool is_hidden,
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 2612a2d..f5ce434 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -1037,29 +1037,6 @@
   return frame_->GetDocument()->Lifecycle();
 }
 
-void LocalFrameView::RunPostLifecycleSteps() {
-  RunIntersectionObserverSteps();
-  UpdateThrottlingStatusForSubtree();
-}
-
-void LocalFrameView::RunIntersectionObserverSteps() {
-#if DCHECK_IS_ON()
-  bool was_dirty = NeedsLayout();
-#endif
-  if (ShouldThrottleRendering() || Lifecycle().LifecyclePostponed() ||
-      !frame_->GetDocument()->IsActive()) {
-    return;
-  }
-  TRACE_EVENT0("blink,benchmark",
-               "LocalFrameView::UpdateViewportIntersectionsForSubtree");
-  SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(),
-                           LocalFrameUkmAggregator::kIntersectionObservation);
-  UpdateViewportIntersectionsForSubtree();
-#if DCHECK_IS_ON()
-  DCHECK(was_dirty || !NeedsLayout());
-#endif
-}
-
 LayoutSVGRoot* LocalFrameView::EmbeddedReplacedContent() const {
   auto* layout_view = this->GetLayoutView();
   if (!layout_view)
@@ -2210,6 +2187,17 @@
   // Run the lifecycle updates.
   UpdateLifecyclePhasesInternal(target_state);
 
+  // Update intersection observations if needed.
+  if (target_state == DocumentLifecycle::kPaintClean) {
+    TRACE_EVENT0("blink,benchmark",
+                 "LocalFrameView::UpdateViewportIntersectionsForSubtree");
+    SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(),
+                             LocalFrameUkmAggregator::kIntersectionObservation);
+    UpdateViewportIntersectionsForSubtree();
+  }
+
+  UpdateThrottlingStatusForSubtree();
+
   ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
     for (auto& observer : frame_view.lifecycle_observers_)
       observer->DidFinishLifecycleUpdate();
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index b4db161..4eac4650 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -370,12 +370,6 @@
   // desired state.
   bool UpdateLifecycleToLayoutClean();
 
-  // This for doing work that needs to run synchronously at the end of lifecyle
-  // updates, but needs to happen outside of the lifecycle code. It's OK to
-  // schedule another animation frame here, but the layout tree should not be
-  // invalidated.
-  void RunPostLifecycleSteps();
-
   void ScheduleVisualUpdateForPaintInvalidationIfNeeded();
 
   bool InvalidateViewportConstrainedObjects();
@@ -817,8 +811,6 @@
 
   DocumentLifecycle& Lifecycle() const;
 
-  void RunIntersectionObserverSteps();
-
   // Methods to do point conversion via layoutObjects, in order to take
   // transforms into account.
   IntRect ConvertToContainingEmbeddedContentView(const IntRect&) const;
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 1b9051f..8fab72df6 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -300,11 +300,6 @@
     GetPage()->GetValidationMessageClient().LayoutOverlay();
 }
 
-void WebFrameWidgetImpl::DidBeginFrame() {
-  DCHECK(LocalRootImpl()->GetFrame());
-  PageWidgetDelegate::DidBeginFrame(*LocalRootImpl()->GetFrame());
-}
-
 void WebFrameWidgetImpl::BeginRafAlignedInput() {
   raf_aligned_input_start_time_ = CurrentTimeTicks();
 }
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index 8c6b4ea..0ae71d3 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -85,7 +85,6 @@
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) final;
   void BeginFrame(base::TimeTicks last_frame_time,
                   bool record_main_frame_metrics) override;
-  void DidBeginFrame() override;
   void BeginRafAlignedInput() override;
   void EndRafAlignedInput() override;
   void RecordStartOfFrameMetrics() override;
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 2d35cf4..a688b04b 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -61,10 +61,6 @@
   web_view_->BeginFrame(last_frame_time, record_main_frame_metrics);
 }
 
-void WebViewFrameWidget::DidBeginFrame() {
-  web_view_->DidBeginFrame();
-}
-
 void WebViewFrameWidget::BeginRafAlignedInput() {
   web_view_->BeginRafAlignedInput();
 }
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.h b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
index 5ea0099..f14a81cd 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.h
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
@@ -50,7 +50,6 @@
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) final;
   void BeginFrame(base::TimeTicks last_frame_time,
                   bool record_main_frame_metrics) override;
-  void DidBeginFrame() override;
   void BeginRafAlignedInput() override;
   void EndRafAlignedInput() override;
   void RecordStartOfFrameMetrics() override;
diff --git a/third_party/blink/renderer/core/layout/api/line_layout_text.h b/third_party/blink/renderer/core/layout/api/line_layout_text.h
index eb666a9..6f982e75 100644
--- a/third_party/blink/renderer/core/layout/api/line_layout_text.h
+++ b/third_party/blink/renderer/core/layout/api/line_layout_text.h
@@ -33,7 +33,7 @@
 
   InlineTextBox* LastTextBox() const { return ToText()->LastTextBox(); }
 
-  InlineTextBox* CreateInlineTextBox(int start, unsigned short length) {
+  InlineTextBox* CreateInlineTextBox(int start, uint16_t length) {
     return ToText()->CreateInlineTextBox(start, length);
   }
 
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index 00b0c5d..2138b179f 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -439,7 +439,7 @@
   if (!box)
     return IntRect();
 
-  unsigned short truncation = box->Truncation();
+  uint16_t truncation = box->Truncation();
   if (truncation == kCNoTruncation)
     return IntRect();
 
@@ -1909,12 +1909,11 @@
   valid_ng_items_ = false;
 }
 
-InlineTextBox* LayoutText::CreateTextBox(int start, unsigned short length) {
+InlineTextBox* LayoutText::CreateTextBox(int start, uint16_t length) {
   return new InlineTextBox(LineLayoutItem(this), start, length);
 }
 
-InlineTextBox* LayoutText::CreateInlineTextBox(int start,
-                                               unsigned short length) {
+InlineTextBox* LayoutText::CreateInlineTextBox(int start, uint16_t length) {
   InlineTextBox* text_box = CreateTextBox(start, length);
   MutableTextBoxes().AppendLineBox(text_box);
   return text_box;
diff --git a/third_party/blink/renderer/core/layout/layout_text.h b/third_party/blink/renderer/core/layout/layout_text.h
index 8a927685..0cc567b 100644
--- a/third_party/blink/renderer/core/layout/layout_text.h
+++ b/third_party/blink/renderer/core/layout/layout_text.h
@@ -105,7 +105,7 @@
   // Returns first letter part of |LayoutTextFragment|.
   virtual LayoutText* GetFirstLetterPart() const { return nullptr; }
 
-  InlineTextBox* CreateInlineTextBox(int start, unsigned short length);
+  InlineTextBox* CreateInlineTextBox(int start, uint16_t length);
   void DirtyOrDeleteLineBoxesIfNeeded(bool full_layout);
   void DirtyLineBoxes();
 
@@ -340,9 +340,8 @@
       const LayoutRect& container_rect,
       TouchAction container_whitelisted_touch_action) const override;
 
-  virtual InlineTextBox* CreateTextBox(
-      int start,
-      unsigned short length);  // Subclassed by SVG.
+  virtual InlineTextBox* CreateTextBox(int start,
+                                       uint16_t length);  // Subclassed by SVG.
 
   void InvalidateDisplayItemClients(PaintInvalidationReason) const override;
 
diff --git a/third_party/blink/renderer/core/layout/line/inline_iterator.h b/third_party/blink/renderer/core/layout/line/inline_iterator.h
index 95732c89..6a481c2 100644
--- a/third_party/blink/renderer/core/layout/line/inline_iterator.h
+++ b/third_party/blink/renderer/core/layout/line/inline_iterator.h
@@ -760,7 +760,7 @@
   while (end > start || add_empty_run) {
     add_empty_run = false;
     const int kLimit =
-        USHRT_MAX;  // InlineTextBox stores text length as unsigned short.
+        USHRT_MAX;  // InlineTextBox stores text length as uint16_t.
     unsigned limited_end = end;
     if (end - start > kLimit)
       limited_end = start + kLimit;
diff --git a/third_party/blink/renderer/core/layout/line/inline_text_box.cc b/third_party/blink/renderer/core/layout/line/inline_text_box.cc
index 2b0157f7..678d192 100644
--- a/third_party/blink/renderer/core/layout/line/inline_text_box.cc
+++ b/third_party/blink/renderer/core/layout/line/inline_text_box.cc
@@ -47,7 +47,7 @@
 
 struct SameSizeAsInlineTextBox : public InlineBox {
   unsigned variables[1];
-  unsigned short variables2[2];
+  uint16_t variables2[2];
   void* pointers[2];
 };
 
diff --git a/third_party/blink/renderer/core/layout/line/inline_text_box.h b/third_party/blink/renderer/core/layout/line/inline_text_box.h
index 6a8cd02c..15ed072 100644
--- a/third_party/blink/renderer/core/layout/line/inline_text_box.h
+++ b/third_party/blink/renderer/core/layout/line/inline_text_box.h
@@ -39,7 +39,7 @@
 
 class CORE_EXPORT InlineTextBox : public InlineBox {
  public:
-  InlineTextBox(LineLayoutItem item, int start, unsigned short length)
+  InlineTextBox(LineLayoutItem item, int start, uint16_t length)
       : InlineBox(item),
         prev_text_box_(nullptr),
         next_text_box_(nullptr),
@@ -71,7 +71,7 @@
 
   void OffsetRun(int delta);
 
-  unsigned short Truncation() const { return truncation_; }
+  uint16_t Truncation() const { return truncation_; }
 
   void MarkDirty() final;
 
@@ -224,12 +224,12 @@
   InlineTextBox* next_text_box_;
 
   int start_;
-  unsigned short len_;
+  uint16_t len_;
 
   // Where to truncate when text overflow is applied.  We use special constants
   // to denote no truncation (the whole run paints) and full truncation (nothing
   // paints at all).
-  unsigned short truncation_;
+  uint16_t truncation_;
 
  private:
   TextRun::ExpansionBehavior GetExpansionBehavior() const {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
index 26613a1a..ad531312 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -730,16 +730,15 @@
     unsigned end_offset) {
   DCHECK(item_result.item);
   const NGInlineItem& item = *item_result.item;
-  const ShapeResult* source_result = item.TextShapeResult();
-  DCHECK(source_result);
 
   // Check given offsets require to truncate |item_result.shape_result|.
   const unsigned start_offset = item_result.start_offset;
-  DCHECK(item_result.shape_result);
-  DCHECK_GE(start_offset, item_result.shape_result->StartIndex());
-  DCHECK_LE(end_offset, item_result.shape_result->EndIndex());
-  DCHECK(start_offset > item_result.shape_result->StartIndex() ||
-         end_offset < item_result.shape_result->EndIndex());
+  const ShapeResultView* source_result = item_result.shape_result.get();
+  DCHECK(source_result);
+  DCHECK_GE(start_offset, source_result->StartIndex());
+  DCHECK_LE(end_offset, source_result->EndIndex());
+  DCHECK(start_offset > source_result->StartIndex() ||
+         end_offset < source_result->EndIndex());
 
   if (!NeedsAccurateEndPosition(*line_info_, item)) {
     return ShapeResultView::Create(source_result, start_offset, end_offset);
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc
index c008a95..bf141df8a 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.cc
@@ -89,8 +89,7 @@
   }
 }
 
-InlineTextBox* LayoutSVGInlineText::CreateTextBox(int start,
-                                                  unsigned short length) {
+InlineTextBox* LayoutSVGInlineText::CreateTextBox(int start, uint16_t length) {
   InlineTextBox* box =
       new SVGInlineTextBox(LineLayoutItem(this), start, length);
   box->SetHasVirtualLogicalHeight();
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h b/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h
index 3eaa0a5e..c31e7449 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h
@@ -76,7 +76,7 @@
       int caret_offset,
       LayoutUnit* extra_width_to_end_of_line = nullptr) const override;
   LayoutRect LinesBoundingBox() const override;
-  InlineTextBox* CreateTextBox(int start, unsigned short length) override;
+  InlineTextBox* CreateTextBox(int start, uint16_t length) override;
 
   LayoutRect VisualRectInDocument() const final;
   FloatRect VisualRectInLocalSVGCoordinates() const final;
diff --git a/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc b/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc
index 44562a16..a01d167 100644
--- a/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc
+++ b/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.cc
@@ -41,7 +41,7 @@
 
 SVGInlineTextBox::SVGInlineTextBox(LineLayoutItem item,
                                    int start,
-                                   unsigned short length)
+                                   uint16_t length)
     : InlineTextBox(item, start, length), starts_new_text_chunk_(false) {}
 
 void SVGInlineTextBox::DirtyLineBoxes() {
diff --git a/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.h b/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.h
index efaa8c5..320ea2c6 100644
--- a/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.h
+++ b/third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.h
@@ -31,7 +31,7 @@
 
 class SVGInlineTextBox final : public InlineTextBox {
  public:
-  SVGInlineTextBox(LineLayoutItem, int start, unsigned short length);
+  SVGInlineTextBox(LineLayoutItem, int start, uint16_t length);
 
   bool IsSVGInlineTextBox() const override { return true; }
 
diff --git a/third_party/blink/renderer/core/loader/threadable_loader_test.cc b/third_party/blink/renderer/core/loader/threadable_loader_test.cc
index b46ae03..5cb0456 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/threadable_loader_test.cc
@@ -33,7 +33,6 @@
 #include "third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
diff --git a/third_party/blink/renderer/core/page/page_widget_delegate.cc b/third_party/blink/renderer/core/page/page_widget_delegate.cc
index 4d98a13c..50d836f5 100644
--- a/third_party/blink/renderer/core/page/page_widget_delegate.cc
+++ b/third_party/blink/renderer/core/page/page_widget_delegate.cc
@@ -74,11 +74,6 @@
   }
 }
 
-void PageWidgetDelegate::DidBeginFrame(LocalFrame& root) {
-  if (LocalFrameView* frame_view = root.View())
-    frame_view->RunPostLifecycleSteps();
-}
-
 void PageWidgetDelegate::PaintContent(cc::PaintCanvas* canvas,
                                       const WebRect& rect,
                                       LocalFrame& root) {
diff --git a/third_party/blink/renderer/core/page/page_widget_delegate.h b/third_party/blink/renderer/core/page/page_widget_delegate.h
index 2f2bb41..926e97e 100644
--- a/third_party/blink/renderer/core/page/page_widget_delegate.h
+++ b/third_party/blink/renderer/core/page/page_widget_delegate.h
@@ -85,9 +85,6 @@
                               WebWidget::LifecycleUpdate requested_update,
                               WebWidget::LifecycleUpdateReason reason);
 
-  // See comment of WebWidget::DidBeginFrame
-  static void DidBeginFrame(LocalFrame& root);
-
   // See documents of methods with the same names in FrameView class.
   static void PaintContent(cc::PaintCanvas*, const WebRect&, LocalFrame& root);
   // See FIXME in the function body about nullptr |root|.
diff --git a/third_party/blink/renderer/core/script/module_import_meta.h b/third_party/blink/renderer/core/script/module_import_meta.h
index d6af2ba4..24b26bd 100644
--- a/third_party/blink/renderer/core/script/module_import_meta.h
+++ b/third_party/blink/renderer/core/script/module_import_meta.h
@@ -13,6 +13,8 @@
 // Represents import.meta data structure, which is the return value of
 // https://html.spec.whatwg.org/C/#hostgetimportmetaproperties
 class CORE_EXPORT ModuleImportMeta final {
+  STACK_ALLOCATED();
+
  public:
   explicit ModuleImportMeta(const String& url) : url_(url) {}
 
diff --git a/third_party/blink/renderer/core/script/parsed_specifier.h b/third_party/blink/renderer/core/script/parsed_specifier.h
index 93c762e9..c95e30f8 100644
--- a/third_party/blink/renderer/core/script/parsed_specifier.h
+++ b/third_party/blink/renderer/core/script/parsed_specifier.h
@@ -29,6 +29,8 @@
 // instead of passing String with occasionally converting to KURL.
 // This avoid duplicated URL parsing.
 class ParsedSpecifier final {
+  STACK_ALLOCATED();
+
  public:
   // Parse |specifier|, which may be a non-bare or bare specifier.
   // This implements
diff --git a/third_party/blink/renderer/core/testing/page_test_base.cc b/third_party/blink/renderer/core/testing/page_test_base.cc
index 8c8e8b25..5698d8f 100644
--- a/third_party/blink/renderer/core/testing/page_test_base.cc
+++ b/third_party/blink/renderer/core/testing/page_test_base.cc
@@ -117,7 +117,6 @@
 void PageTestBase::UpdateAllLifecyclePhasesForTest() {
   GetDocument().View()->UpdateAllLifecyclePhases(
       DocumentLifecycle::LifecycleUpdateReason::kTest);
-  GetDocument().View()->RunPostLifecycleSteps();
 }
 
 StyleEngine& PageTestBase::GetStyleEngine() {
diff --git a/third_party/blink/renderer/core/testing/sim/sim_compositor.cc b/third_party/blink/renderer/core/testing/sim/sim_compositor.cc
index ec460ab..e06c443 100644
--- a/third_party/blink/renderer/core/testing/sim/sim_compositor.cc
+++ b/third_party/blink/renderer/core/testing/sim/sim_compositor.cc
@@ -60,9 +60,14 @@
 
   last_frame_time_ += base::TimeDelta::FromSecondsD(time_delta_in_seconds);
 
+  SimCanvas::Commands commands;
+  paint_commands_ = &commands;
+
   layer_tree_view_->layer_tree_host()->Composite(last_frame_time_,
                                                  /*raster=*/false);
-  return PaintFrame();
+
+  paint_commands_ = nullptr;
+  return commands;
 }
 
 SimCanvas::Commands SimCompositor::PaintFrame() {
@@ -99,6 +104,7 @@
   web_view_->MainFrameWidget()->BeginFrame(last_frame_time_, false);
   web_view_->MainFrameWidget()->UpdateAllLifecyclePhases(
       WebWidget::LifecycleUpdateReason::kTest);
+  *paint_commands_ = PaintFrame();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/testing/sim/sim_compositor.h b/third_party/blink/renderer/core/testing/sim/sim_compositor.h
index 259e28e..466733e 100644
--- a/third_party/blink/renderer/core/testing/sim/sim_compositor.h
+++ b/third_party/blink/renderer/core/testing/sim/sim_compositor.h
@@ -98,7 +98,6 @@
   void RequestNewLayerTreeFrameSink(
       LayerTreeFrameSinkCallback callback) override;
   void BeginMainFrame(base::TimeTicks frame_time) override;
-  void DidBeginMainFrame() override { web_view_->DidBeginFrame(); }
 
   WebViewImpl* web_view_ = nullptr;
   content::LayerTreeView* layer_tree_view_ = nullptr;
@@ -107,6 +106,10 @@
 
   base::TimeTicks last_frame_time_;
 
+  // During BeginFrame(), painting is done, and the result is stored here to
+  // be returned from BeginFrame().
+  SimCanvas::Commands* paint_commands_;
+
   std::unique_ptr<cc::ScopedDeferMainFrameUpdate>
       scoped_defer_main_frame_update_;
 };
diff --git a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h
index 382addc..975463ec 100644
--- a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h
+++ b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h
@@ -53,6 +53,13 @@
   // transfer that.
   bool Transfer(v8::Isolate*, WTF::ArrayBufferContents& result);
 
+  // Share the ArrayBuffer, even if it is non-shared. Such sharing is necessary
+  // for e.g. WebAudio which uses a separate thread for processing the
+  // ArrayBuffer while at the same time exposing a NonShared Float32Array.
+  bool ShareNonSharedForInternalUse(WTF::ArrayBufferContents& result) {
+    return Buffer()->ShareNonSharedForInternalUse(result);
+  }
+
   v8::Local<v8::Object> Wrap(v8::Isolate*,
                              v8::Local<v8::Object> creation_context) override;
 };
diff --git a/third_party/blink/renderer/core/workers/worker_thread.cc b/third_party/blink/renderer/core/workers/worker_thread.cc
index b7d47b3..184e25a1 100644
--- a/third_party/blink/renderer/core/workers/worker_thread.cc
+++ b/third_party/blink/renderer/core/workers/worker_thread.cc
@@ -54,7 +54,6 @@
 #include "third_party/blink/renderer/platform/scheduler/public/worker_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/worker/worker_thread.h"
 #include "third_party/blink/renderer/platform/scheduler/worker/worker_thread_scheduler.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
diff --git a/third_party/blink/renderer/core/workers/worker_thread_test.cc b/third_party/blink/renderer/core/workers/worker_thread_test.cc
index 0df86fe..8939e3b 100644
--- a/third_party/blink/renderer/core/workers/worker_thread_test.cc
+++ b/third_party/blink/renderer/core/workers/worker_thread_test.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/synchronization/waitable_event.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_cache_options.h"
@@ -19,7 +20,6 @@
 #include "third_party/blink/renderer/core/workers/worker_thread_test_helper.h"
 #include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 using testing::_;
 using testing::AtMost;
@@ -32,7 +32,7 @@
 
 // Used as a debugger task. Waits for a signal from the main thread.
 void WaitForSignalTask(WorkerThread* worker_thread,
-                       WaitableEvent* waitable_event) {
+                       base::WaitableEvent* waitable_event) {
   EXPECT_TRUE(worker_thread->IsCurrentThread());
 
   worker_thread->DebuggerTaskStarted();
@@ -46,7 +46,7 @@
 }
 
 void TerminateParentOfNestedWorker(WorkerThread* parent_thread,
-                                   WaitableEvent* waitable_event) {
+                                   base::WaitableEvent* waitable_event) {
   EXPECT_TRUE(IsMainThread());
   parent_thread->Terminate();
   waitable_event->Signal();
@@ -99,7 +99,7 @@
   nested_worker_helper->worker_thread->WaitForInit();
 
   // Ask the main threat to terminate this parent thread.
-  WaitableEvent child_waitable;
+  base::WaitableEvent child_waitable;
   PostCrossThreadTask(
       *parent_thread->GetParentExecutionContextTaskRunners()->Get(
           TaskType::kInternalTest),
@@ -120,7 +120,7 @@
 
 void VerifyParentAndChildAreTerminated(WorkerThread* parent_thread,
                                        NestedWorkerHelper* nested_worker_helper,
-                                       WaitableEvent* waitable_event) {
+                                       base::WaitableEvent* waitable_event) {
   EXPECT_TRUE(parent_thread->IsCurrentThread());
   EXPECT_EQ(ExitCode::kGracefullyTerminated,
             parent_thread->GetExitCodeForTesting());
@@ -386,7 +386,7 @@
 
   // Used to wait for worker thread termination in a debugger task on the
   // worker thread.
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   PostCrossThreadTask(
       *worker_thread_->GetTaskRunner(TaskType::kInternalInspector), FROM_HERE,
       CrossThreadBind(&WaitForSignalTask,
@@ -433,7 +433,7 @@
 
   // Used to wait for worker thread termination in a debugger task on the
   // worker thread.
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   PostCrossThreadTask(
       *worker_thread_->GetTaskRunner(TaskType::kInternalInspector), FROM_HERE,
       CrossThreadBind(&WaitForSignalTask,
@@ -484,7 +484,7 @@
                       CrossThreadUnretained(&nested_worker_helper)));
   test::EnterRunLoop();
 
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   worker_thread_->GetWorkerBackingThread().BackingThread().PostTask(
       FROM_HERE, CrossThreadBind(&VerifyParentAndChildAreTerminated,
                                  CrossThreadUnretained(worker_thread_.get()),
diff --git a/third_party/blink/renderer/core/workers/worker_thread_test_helper.h b/third_party/blink/renderer/core/workers/worker_thread_test_helper.h
index 821ae55..743dd50 100644
--- a/third_party/blink/renderer/core/workers/worker_thread_test_helper.h
+++ b/third_party/blink/renderer/core/workers/worker_thread_test_helper.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/synchronization/waitable_event.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/mojom/net/ip_address_space.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
@@ -29,7 +30,6 @@
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
@@ -115,9 +115,9 @@
   }
 
   void WaitForInit() {
-    WaitableEvent completion_event;
+    base::WaitableEvent completion_event;
     GetWorkerBackingThread().BackingThread().PostTask(
-        FROM_HERE, CrossThreadBind(&WaitableEvent::Signal,
+        FROM_HERE, CrossThreadBind(&base::WaitableEvent::Signal,
                                    CrossThreadUnretained(&completion_event)));
     completion_event.Wait();
   }
@@ -160,7 +160,7 @@
   void WaitUntilScriptEvaluation() { script_evaluation_event_.Wait(); }
 
  private:
-  WaitableEvent script_evaluation_event_;
+  base::WaitableEvent script_evaluation_event_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/workers/worklet_thread_holder.h b/third_party/blink/renderer/core/workers/worklet_thread_holder.h
index 2a18f37c..121bb33 100644
--- a/third_party/blink/renderer/core/workers/worklet_thread_holder.h
+++ b/third_party/blink/renderer/core/workers/worklet_thread_holder.h
@@ -5,10 +5,10 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_WORKLET_THREAD_HOLDER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_WORKLET_THREAD_HOLDER_H_
 
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/workers/worker_backing_thread.h"
 #include "third_party/blink/renderer/core/workers/worker_backing_thread_startup_data.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 
 namespace blink {
@@ -70,7 +70,7 @@
 
   void ShutdownAndWait() {
     DCHECK(IsMainThread());
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     thread_->BackingThread().PostTask(
         FROM_HERE,
         CrossThreadBind(&WorkletThreadHolder::ShutdownOnWorkletThread,
@@ -79,7 +79,7 @@
     waitable_event.Wait();
   }
 
-  void ShutdownOnWorkletThread(WaitableEvent* waitable_event) {
+  void ShutdownOnWorkletThread(base::WaitableEvent* waitable_event) {
     thread_->ShutdownOnBackingThread();
     waitable_event->Signal();
   }
diff --git a/third_party/blink/renderer/core/xml/document_xpath_evaluator.cc b/third_party/blink/renderer/core/xml/document_xpath_evaluator.cc
index a2538cc..d884cf77 100644
--- a/third_party/blink/renderer/core/xml/document_xpath_evaluator.cc
+++ b/third_party/blink/renderer/core/xml/document_xpath_evaluator.cc
@@ -72,7 +72,7 @@
                                               const String& expression,
                                               Node* context_node,
                                               XPathNSResolver* resolver,
-                                              unsigned short type,
+                                              uint16_t type,
                                               const ScriptValue&,
                                               ExceptionState& exception_state) {
   DocumentXPathEvaluator& suplement = From(document);
diff --git a/third_party/blink/renderer/core/xml/document_xpath_evaluator.h b/third_party/blink/renderer/core/xml/document_xpath_evaluator.h
index b4d892540..dd3895d 100644
--- a/third_party/blink/renderer/core/xml/document_xpath_evaluator.h
+++ b/third_party/blink/renderer/core/xml/document_xpath_evaluator.h
@@ -55,7 +55,7 @@
                                const String& expression,
                                Node* context_node,
                                XPathNSResolver*,
-                               unsigned short type,
+                               uint16_t type,
                                const ScriptValue&,
                                ExceptionState&);
 
diff --git a/third_party/blink/renderer/core/xml/xpath_evaluator.cc b/third_party/blink/renderer/core/xml/xpath_evaluator.cc
index f0dfb4b..3c1cede 100644
--- a/third_party/blink/renderer/core/xml/xpath_evaluator.cc
+++ b/third_party/blink/renderer/core/xml/xpath_evaluator.cc
@@ -53,7 +53,7 @@
 XPathResult* XPathEvaluator::evaluate(const String& expression,
                                       Node* context_node,
                                       XPathNSResolver* resolver,
-                                      unsigned short type,
+                                      uint16_t type,
                                       const ScriptValue&,
                                       ExceptionState& exception_state) {
   if (!IsValidContextNode(context_node)) {
diff --git a/third_party/blink/renderer/core/xml/xpath_evaluator.h b/third_party/blink/renderer/core/xml/xpath_evaluator.h
index ecf02abc..cd932471 100644
--- a/third_party/blink/renderer/core/xml/xpath_evaluator.h
+++ b/third_party/blink/renderer/core/xml/xpath_evaluator.h
@@ -56,7 +56,7 @@
   XPathResult* evaluate(const String& expression,
                         Node* context_node,
                         XPathNSResolver*,
-                        unsigned short type,
+                        uint16_t type,
                         const ScriptValue&,
                         ExceptionState&);
 };
diff --git a/third_party/blink/renderer/core/xml/xpath_expression.cc b/third_party/blink/renderer/core/xml/xpath_expression.cc
index a32a06c..7002fbb 100644
--- a/third_party/blink/renderer/core/xml/xpath_expression.cc
+++ b/third_party/blink/renderer/core/xml/xpath_expression.cc
@@ -61,7 +61,7 @@
 }
 
 XPathResult* XPathExpression::evaluate(Node* context_node,
-                                       unsigned short type,
+                                       uint16_t type,
                                        const ScriptValue&,
                                        ExceptionState& exception_state) {
   if (!IsValidContextNode(context_node)) {
diff --git a/third_party/blink/renderer/core/xml/xpath_expression.h b/third_party/blink/renderer/core/xml/xpath_expression.h
index 0539ca6..5e6655f 100644
--- a/third_party/blink/renderer/core/xml/xpath_expression.h
+++ b/third_party/blink/renderer/core/xml/xpath_expression.h
@@ -57,7 +57,7 @@
   XPathExpression();
 
   XPathResult* evaluate(Node* context_node,
-                        unsigned short type,
+                        uint16_t type,
                         const ScriptValue&,
                         ExceptionState&);
 
diff --git a/third_party/blink/renderer/core/xml/xpath_result.cc b/third_party/blink/renderer/core/xml/xpath_result.cc
index 58785a13..3cc8ce4 100644
--- a/third_party/blink/renderer/core/xml/xpath_result.cc
+++ b/third_party/blink/renderer/core/xml/xpath_result.cc
@@ -65,8 +65,7 @@
   ScriptWrappable::Trace(visitor);
 }
 
-void XPathResult::ConvertTo(unsigned short type,
-                            ExceptionState& exception_state) {
+void XPathResult::ConvertTo(uint16_t type, ExceptionState& exception_state) {
   switch (type) {
     case kAnyType:
       break;
@@ -118,7 +117,7 @@
   }
 }
 
-unsigned short XPathResult::resultType() const {
+uint16_t XPathResult::resultType() const {
   return result_type_;
 }
 
diff --git a/third_party/blink/renderer/core/xml/xpath_result.h b/third_party/blink/renderer/core/xml/xpath_result.h
index 7fa3fc2..80b25fcf 100644
--- a/third_party/blink/renderer/core/xml/xpath_result.h
+++ b/third_party/blink/renderer/core/xml/xpath_result.h
@@ -66,9 +66,9 @@
 
   XPathResult(xpath::EvaluationContext&, const xpath::Value&);
 
-  void ConvertTo(unsigned short type, ExceptionState&);
+  void ConvertTo(uint16_t type, ExceptionState&);
 
-  unsigned short resultType() const;
+  uint16_t resultType() const;
 
   double numberValue(ExceptionState&) const;
   String stringValue(ExceptionState&) const;
@@ -91,7 +91,7 @@
   unsigned node_set_position_;
   // FIXME: why duplicate the node set stored in value_?
   Member<xpath::NodeSet> node_set_;
-  unsigned short result_type_;
+  uint16_t result_type_;
   Member<Document> document_;
   uint64_t dom_tree_version_;
 };
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
index 34c72788..248edb75 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.h"
 
+#include "base/synchronization/waitable_event.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/public/platform/web_url_request.h"
@@ -25,7 +26,6 @@
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_position.h"
 
 #include <memory>
@@ -73,15 +73,15 @@
     reporting_proxy_ = std::make_unique<WorkerReportingProxy>();
   }
 
-  using TestCalback = void (AnimationWorkletGlobalScopeTest::*)(WorkerThread*,
-                                                                WaitableEvent*);
+  using TestCalback = void (
+      AnimationWorkletGlobalScopeTest::*)(WorkerThread*, base::WaitableEvent*);
   // Create a new animation worklet and run the callback task on it. Terminate
   // the worklet once the task completion is signaled.
   void RunTestOnWorkletThread(TestCalback callback) {
     std::unique_ptr<WorkerThread> worklet =
         CreateAnimationAndPaintWorkletThread(&GetDocument(),
                                              reporting_proxy_.get());
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     PostCrossThreadTask(
         *worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(callback, CrossThreadUnretained(this),
@@ -95,7 +95,7 @@
 
   void RunScriptOnWorklet(String source_code,
                           WorkerThread* thread,
-                          WaitableEvent* waitable_event) {
+                          base::WaitableEvent* waitable_event) {
     ASSERT_TRUE(thread->IsCurrentThread());
     auto* global_scope = To<AnimationWorkletGlobalScope>(thread->GlobalScope());
     ScriptState* script_state =
@@ -109,7 +109,7 @@
   }
 
   void RunBasicParsingTestOnWorklet(WorkerThread* thread,
-                                    WaitableEvent* waitable_event) {
+                                    base::WaitableEvent* waitable_event) {
     ASSERT_TRUE(thread->IsCurrentThread());
     auto* global_scope = To<AnimationWorkletGlobalScope>(thread->GlobalScope());
     ScriptState* script_state =
@@ -153,8 +153,9 @@
     waitable_event->Signal();
   }
 
-  void RunConstructAndAnimateTestOnWorklet(WorkerThread* thread,
-                                           WaitableEvent* waitable_event) {
+  void RunConstructAndAnimateTestOnWorklet(
+      WorkerThread* thread,
+      base::WaitableEvent* waitable_event) {
     ASSERT_TRUE(thread->IsCurrentThread());
     auto* global_scope = To<AnimationWorkletGlobalScope>(thread->GlobalScope());
     ScriptState* script_state =
@@ -227,7 +228,7 @@
   }
 
   void RunStateExistenceTestOnWorklet(WorkerThread* thread,
-                                      WaitableEvent* waitable_event) {
+                                      base::WaitableEvent* waitable_event) {
     ASSERT_TRUE(thread->IsCurrentThread());
     auto* global_scope = To<AnimationWorkletGlobalScope>(thread->GlobalScope());
     ScriptState* script_state =
@@ -274,7 +275,7 @@
   }
 
   void RunAnimateOutputTestOnWorklet(WorkerThread* thread,
-                                     WaitableEvent* waitable_event) {
+                                     base::WaitableEvent* waitable_event) {
     AnimationWorkletGlobalScope* global_scope =
         static_cast<AnimationWorkletGlobalScope*>(thread->GlobalScope());
     ASSERT_TRUE(global_scope);
@@ -317,8 +318,9 @@
   // This test verifies that an animator instance is not created if
   // MutatorInputState does not have an animation in
   // added_and_updated_animations.
-  void RunAnimatorInstanceCreationTestOnWorklet(WorkerThread* thread,
-                                                WaitableEvent* waitable_event) {
+  void RunAnimatorInstanceCreationTestOnWorklet(
+      WorkerThread* thread,
+      base::WaitableEvent* waitable_event) {
     AnimationWorkletGlobalScope* global_scope =
         static_cast<AnimationWorkletGlobalScope*>(thread->GlobalScope());
     ASSERT_TRUE(global_scope);
@@ -371,8 +373,9 @@
 
   // This test verifies that an animator instance is created and removed
   // properly.
-  void RunAnimatorInstanceUpdateTestOnWorklet(WorkerThread* thread,
-                                              WaitableEvent* waitable_event) {
+  void RunAnimatorInstanceUpdateTestOnWorklet(
+      WorkerThread* thread,
+      base::WaitableEvent* waitable_event) {
     AnimationWorkletGlobalScope* global_scope =
         static_cast<AnimationWorkletGlobalScope*>(thread->GlobalScope());
     ASSERT_TRUE(global_scope);
@@ -488,7 +491,7 @@
   // creation.
   EXPECT_FALSE(proxy_client->did_add_global_scope());
 
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   String source_code =
       R"JS(
         registerAnimator('test', class {
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc
index ea58bea..85f36abc 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client_test.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/synchronization/waitable_event.h"
 #include "base/test/test_simple_task_runner.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -54,7 +55,7 @@
 
   void AddGlobalScopeForTesting(WorkerThread* thread,
                                 AnimationWorkletProxyClient* proxy_client,
-                                WaitableEvent* waitable_event) {
+                                base::WaitableEvent* waitable_event) {
     proxy_client->AddGlobalScopeForTesting(
         To<WorkletGlobalScope>(thread->GlobalScope()));
     waitable_event->Signal();
@@ -119,7 +120,7 @@
 
   // Register global scopes with proxy client. This step must be performed on
   // the worker threads.
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   PostCrossThreadTask(
       *first_worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
       CrossThreadBind(
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
index 7ba26c6..5c5740c 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.h"
 
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
 #include "third_party/blink/renderer/core/inspector/worker_devtools_params.h"
 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
@@ -76,15 +77,15 @@
     return thread;
   }
 
-  using TestCalback = void (PaintWorkletGlobalScopeTest::*)(WorkerThread*,
-                                                            WaitableEvent*);
+  using TestCalback = void (
+      PaintWorkletGlobalScopeTest::*)(WorkerThread*, base::WaitableEvent*);
 
   // Create a new paint worklet and run the callback task on it. Terminate the
   // worklet once the task completion is signaled.
   void RunTestOnWorkletThread(TestCalback callback) {
     std::unique_ptr<WorkerThread> worklet =
         CreateAnimationAndPaintWorkletThread(nullptr);
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     PostCrossThreadTask(
         *worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(callback, CrossThreadUnretained(this),
@@ -98,7 +99,7 @@
 
   void RunScriptOnWorklet(String source_code,
                           WorkerThread* thread,
-                          WaitableEvent* waitable_event) {
+                          base::WaitableEvent* waitable_event) {
     ASSERT_TRUE(thread->IsCurrentThread());
     auto* global_scope = To<PaintWorkletGlobalScope>(thread->GlobalScope());
     ScriptState* script_state =
@@ -112,7 +113,7 @@
   }
 
   void RunBasicParsingTestOnWorklet(WorkerThread* thread,
-                                    WaitableEvent* waitable_event) {
+                                    base::WaitableEvent* waitable_event) {
     ASSERT_TRUE(thread->IsCurrentThread());
     auto* global_scope = To<PaintWorkletGlobalScope>(thread->GlobalScope());
     ScriptState* script_state =
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture.cc b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
index 523a49f..71ce2f0 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/modules/mediastream/media_track_capabilities.h"
 #include "third_party/blink/renderer/modules/mediastream/media_track_constraints.h"
 #include "third_party/blink/renderer/platform/mojo/mojo_helper.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/modules/media_controls/resources/legacyMediaControls.css b/third_party/blink/renderer/modules/media_controls/resources/legacyMediaControls.css
index 48efe33..f8a6e31 100644
--- a/third_party/blink/renderer/modules/media_controls/resources/legacyMediaControls.css
+++ b/third_party/blink/renderer/modules/media_controls/resources/legacyMediaControls.css
@@ -638,7 +638,6 @@
     display: inline;
 
     background-color: rgba(0, 0, 0, 0.8);
-    padding: 2px 2px;
 }
 
 video::-webkit-media-text-track-region {
diff --git a/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css b/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css
index 30b9a61..3b25f7ae 100644
--- a/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css
+++ b/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css
@@ -1168,7 +1168,6 @@
     display: inline;
 
     background-color: rgba(0, 0, 0, 0.8);
-    padding: 2px 2px;
 }
 
 video::-webkit-media-text-track-region {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer.cc b/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
index ae02dde..a63be55 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
@@ -346,4 +346,24 @@
   }
 }
 
+std::unique_ptr<SharedAudioBuffer> AudioBuffer::CreateSharedAudioBuffer() {
+  return std::unique_ptr<SharedAudioBuffer>(new SharedAudioBuffer(this));
+}
+
+SharedAudioBuffer::SharedAudioBuffer(AudioBuffer* buffer)
+    : sample_rate_(buffer->sampleRate()), length_(buffer->length()) {
+  channels_.resize(buffer->numberOfChannels());
+  for (unsigned int i = 0; i < buffer->numberOfChannels(); ++i) {
+    buffer->getChannelData(i).View()->buffer()->ShareNonSharedForInternalUse(
+        channels_[i]);
+  }
+}
+
+void SharedAudioBuffer::Zero() {
+  for (unsigned i = 0; i < channels_.size(); ++i) {
+    float* data = static_cast<float*>(channels_[i].Data());
+    memset(data, 0, length() * sizeof(*data));
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer.h b/third_party/blink/renderer/modules/webaudio/audio_buffer.h
index ae3389c..50eddb79 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer.h
@@ -41,6 +41,7 @@
 class AudioBus;
 class AudioBufferOptions;
 class ExceptionState;
+class SharedAudioBuffer;
 
 class MODULES_EXPORT AudioBuffer final : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
@@ -116,6 +117,8 @@
     ScriptWrappable::Trace(visitor);
   }
 
+  std::unique_ptr<SharedAudioBuffer> CreateSharedAudioBuffer();
+
  private:
   static DOMFloat32Array* CreateFloat32ArrayOrNull(
       uint32_t length,
@@ -129,6 +132,28 @@
   HeapVector<Member<DOMFloat32Array>> channels_;
 };
 
+// Shared data that audio threads can hold onto.
+class SharedAudioBuffer final {
+ public:
+  explicit SharedAudioBuffer(AudioBuffer*);
+
+  unsigned numberOfChannels() const { return channels_.size(); }
+  uint32_t length() const { return length_; }
+  double duration() const {
+    return length() / static_cast<double>(sampleRate());
+  }
+  float sampleRate() const { return sample_rate_; }
+
+  const Vector<WTF::ArrayBufferContents>& channels() { return channels_; }
+
+  void Zero();
+
+ private:
+  float sample_rate_;
+  uint32_t length_;
+  Vector<WTF::ArrayBufferContents> channels_;
+};
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_BUFFER_H_
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
index 2f8dbf0..656d0c6 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
@@ -61,7 +61,6 @@
     : AudioScheduledSourceHandler(kNodeTypeAudioBufferSource,
                                   node,
                                   sample_rate),
-      buffer_(nullptr),
       playback_rate_(&playback_rate),
       detune_(&detune),
       is_looping_(false),
@@ -115,7 +114,7 @@
     // is updated to the new number of channels because of use of tryLocks() in
     // the context's updating system.  In this case, if the the buffer has just
     // been changed and we're not quite ready yet, then just output silence.
-    if (NumberOfChannels() != Buffer()->numberOfChannels()) {
+    if (NumberOfChannels() != shared_buffer_->numberOfChannels()) {
       output_bus->Zero();
       return;
     }
@@ -223,8 +222,8 @@
   // Offset the pointers to the correct offset frame.
   unsigned write_index = destination_frame_offset;
 
-  uint32_t buffer_length = Buffer()->length();
-  double buffer_sample_rate = Buffer()->sampleRate();
+  uint32_t buffer_length = shared_buffer_->length();
+  double buffer_sample_rate = shared_buffer_->sampleRate();
 
   // Avoid converting from time to sample-frames twice by computing
   // the grain end time first before computing the sample frame.
@@ -248,8 +247,8 @@
   if (Loop() && (loop_start_ || loop_end_) && loop_start_ >= 0 &&
       loop_end_ > 0 && loop_start_ < loop_end_) {
     // Convert from seconds to sample-frames.
-    double loop_start_frame = loop_start_ * Buffer()->sampleRate();
-    double loop_end_frame = loop_end_ * Buffer()->sampleRate();
+    double loop_start_frame = loop_start_ * shared_buffer_->sampleRate();
+    double loop_end_frame = loop_end_ * shared_buffer_->sampleRate();
 
     virtual_end_frame = std::min(loop_end_frame, virtual_end_frame);
     virtual_delta_frames = virtual_end_frame - loop_start_frame;
@@ -260,7 +259,7 @@
   // needs to be done.
   if (Loop() && virtual_read_index_ >= virtual_end_frame) {
     virtual_read_index_ =
-        (loop_start_ < 0) ? 0 : (loop_start_ * Buffer()->sampleRate());
+        (loop_start_ < 0) ? 0 : (loop_start_ * shared_buffer_->sampleRate());
     virtual_read_index_ =
         std::min(virtual_read_index_, static_cast<double>(buffer_length - 1));
   }
@@ -439,35 +438,39 @@
       return;
     }
 
+    shared_buffer_ = buffer->CreateSharedAudioBuffer();
+
     Output(0).SetNumberOfChannels(number_of_channels);
 
     source_channels_ = std::make_unique<const float* []>(number_of_channels);
     destination_channels_ = std::make_unique<float* []>(number_of_channels);
 
-    for (unsigned i = 0; i < number_of_channels; ++i)
-      source_channels_[i] = buffer->getChannelData(i).View()->Data();
+    for (unsigned i = 0; i < number_of_channels; ++i) {
+      source_channels_[i] =
+          static_cast<float*>(shared_buffer_->channels()[i].Data());
+    }
 
     // If this is a grain (as set by a previous call to start()), validate the
     // grain parameters now since it wasn't validated when start was called
     // (because there was no buffer then).
     if (is_grain_)
-      ClampGrainParameters(buffer);
+      ClampGrainParameters(shared_buffer_.get());
   }
 
   virtual_read_index_ = 0;
-  buffer_ = buffer;
 }
 
 unsigned AudioBufferSourceHandler::NumberOfChannels() {
   return Output(0).NumberOfChannels();
 }
 
-void AudioBufferSourceHandler::ClampGrainParameters(const AudioBuffer* buffer) {
+void AudioBufferSourceHandler::ClampGrainParameters(
+    const SharedAudioBuffer* buffer) {
   DCHECK(buffer);
 
   // We have a buffer so we can clip the offset and duration to lie within the
   // buffer.
-  double buffer_duration = buffer->duration();
+  double buffer_duration = shared_buffer_->duration();
 
   grain_offset_ = clampTo(grain_offset_, 0.0, buffer_duration);
 
@@ -495,8 +498,8 @@
   // degrade the quality. When aligned to the sample-frame the playback will be
   // identical to the PCM data stored in the buffer. Since playbackRate == 1 is
   // very common, it's worth considering quality.
-  virtual_read_index_ =
-      audio_utilities::TimeToSampleFrame(grain_offset_, buffer->sampleRate());
+  virtual_read_index_ = audio_utilities::TimeToSampleFrame(
+      grain_offset_, shared_buffer_->sampleRate());
 }
 
 void AudioBufferSourceHandler::Start(double when,
@@ -507,8 +510,8 @@
 void AudioBufferSourceHandler::Start(double when,
                                      double grain_offset,
                                      ExceptionState& exception_state) {
-  StartSource(when, grain_offset, Buffer() ? Buffer()->duration() : 0, false,
-              exception_state);
+  StartSource(when, grain_offset, Buffer() ? shared_buffer_->duration() : 0,
+              false, exception_state);
 }
 
 void AudioBufferSourceHandler::Start(double when,
@@ -583,8 +586,8 @@
   double sample_rate_factor = 1.0;
   if (Buffer()) {
     // Use doubles to compute this to full accuracy.
-    sample_rate_factor =
-        Buffer()->sampleRate() / static_cast<double>(Context()->sampleRate());
+    sample_rate_factor = shared_buffer_->sampleRate() /
+                         static_cast<double>(Context()->sampleRate());
   }
 
   // Use finalValue() to incorporate changes of AudioParamTimeline and
@@ -621,7 +624,7 @@
 }
 
 bool AudioBufferSourceHandler::PropagatesSilence() const {
-  return !IsPlayingOrScheduled() || HasFinished() || !buffer_;
+  return !IsPlayingOrScheduled() || HasFinished() || !shared_buffer_.get();
 }
 
 void AudioBufferSourceHandler::HandleStoppableSourceNode() {
@@ -718,6 +721,7 @@
 void AudioBufferSourceNode::Trace(blink::Visitor* visitor) {
   visitor->Trace(playback_rate_);
   visitor->Trace(detune_);
+  visitor->Trace(buffer_);
   AudioScheduledSourceNode::Trace(visitor);
 }
 
@@ -727,12 +731,14 @@
 }
 
 AudioBuffer* AudioBufferSourceNode::buffer() const {
-  return GetAudioBufferSourceHandler().Buffer();
+  return buffer_.Get();
 }
 
 void AudioBufferSourceNode::setBuffer(AudioBuffer* new_buffer,
                                       ExceptionState& exception_state) {
   GetAudioBufferSourceHandler().SetBuffer(new_buffer, exception_state);
+  if (!exception_state.HadException())
+    buffer_ = new_buffer;
 }
 
 AudioParam* AudioBufferSourceNode::playbackRate() const {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h
index 4786354..1aef3bb 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h
@@ -61,7 +61,7 @@
   // setBuffer() is called on the main thread. This is the buffer we use for
   // playback.
   void SetBuffer(AudioBuffer*, ExceptionState&);
-  AudioBuffer* Buffer() { return buffer_.Get(); }
+  SharedAudioBuffer* Buffer() { return shared_buffer_.get(); }
 
   // numberOfChannels() returns the number of output channels.  This value
   // equals the number of channels from the buffer.  If a new buffer is set with
@@ -134,13 +134,11 @@
                                                  uint32_t frames_to_process);
 
   // Clamps grain parameters to the duration of the given AudioBuffer.
-  void ClampGrainParameters(const AudioBuffer*);
+  void ClampGrainParameters(const SharedAudioBuffer*);
 
-  // m_buffer holds the sample data which this node outputs.
-  // This Persistent doesn't make a reference cycle including
-  // AudioBufferSourceNode.
-  // It is cross-thread, as it will be accessed by the audio and main threads.
-  CrossThreadPersistent<AudioBuffer> buffer_;
+  // Sample data for the outputs of this node. The shared buffer can safely be
+  // accessed from the audio thread.
+  std::unique_ptr<SharedAudioBuffer> shared_buffer_;
 
   // Pointers for the buffer and destination.
   std::unique_ptr<const float* []> source_channels_;
@@ -229,6 +227,7 @@
  private:
   Member<AudioParam> playback_rate_;
   Member<AudioParam> detune_;
+  Member<AudioBuffer> buffer_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
index 5beab54f..d75f8f7 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/synchronization/waitable_event.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/public/platform/web_url_request.h"
@@ -85,7 +86,7 @@
   }
 
   void RunBasicTest(WorkerThread* thread) {
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     PostCrossThreadTask(
         *thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(
@@ -96,7 +97,7 @@
   }
 
   void RunSimpleProcessTest(WorkerThread* thread) {
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     PostCrossThreadTask(
         *thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(
@@ -107,7 +108,7 @@
   }
 
   void RunParsingTest(WorkerThread* thread) {
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     PostCrossThreadTask(
         *thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(
@@ -118,7 +119,7 @@
   }
 
   void RunParsingParameterDescriptorTest(WorkerThread* thread) {
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     PostCrossThreadTask(
         *thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(&AudioWorkletGlobalScopeTest::
@@ -153,7 +154,7 @@
   // if the class definition is correctly registered, then instantiate an
   // AudioWorkletProcessor instance from the definition.
   void RunBasicTestOnWorkletThread(WorkerThread* thread,
-                                   WaitableEvent* wait_event) {
+                                   base::WaitableEvent* wait_event) {
     EXPECT_TRUE(thread->IsCurrentThread());
 
     auto* global_scope = To<AudioWorkletGlobalScope>(thread->GlobalScope());
@@ -201,7 +202,7 @@
 
   // Test if various class definition patterns are parsed correctly.
   void RunParsingTestOnWorkletThread(WorkerThread* thread,
-                                     WaitableEvent* wait_event) {
+                                     base::WaitableEvent* wait_event) {
     EXPECT_TRUE(thread->IsCurrentThread());
 
     auto* global_scope = To<AudioWorkletGlobalScope>(thread->GlobalScope());
@@ -256,7 +257,7 @@
   // Test if the invocation of process() method in AudioWorkletProcessor and
   // AudioWorkletGlobalScope is performed correctly.
   void RunSimpleProcessTestOnWorkletThread(WorkerThread* thread,
-                                           WaitableEvent* wait_event) {
+                                           base::WaitableEvent* wait_event) {
     EXPECT_TRUE(thread->IsCurrentThread());
 
     auto* global_scope = To<AudioWorkletGlobalScope>(thread->GlobalScope());
@@ -322,7 +323,7 @@
 
   void RunParsingParameterDescriptorTestOnWorkletThread(
       WorkerThread* thread,
-      WaitableEvent* wait_event) {
+      base::WaitableEvent* wait_event) {
     EXPECT_TRUE(thread->IsCurrentThread());
 
     auto* global_scope = To<AudioWorkletGlobalScope>(thread->GlobalScope());
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_thread.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_thread.cc
index 2778e171..80f8318c 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_thread.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_thread.cc
@@ -14,7 +14,6 @@
 #include "third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc
index 52503a9..55f4322b 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/webaudio/audio_worklet_thread.h"
 
 #include <memory>
+#include "base/synchronization/waitable_event.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_url_request.h"
@@ -29,7 +30,6 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_position.h"
 
@@ -70,7 +70,7 @@
 
   // Attempts to run some simple script for |thread|.
   void CheckWorkletCanExecuteScript(WorkerThread* thread) {
-    WaitableEvent wait_event;
+    base::WaitableEvent wait_event;
     thread->GetWorkerBackingThread().BackingThread().PostTask(
         FROM_HERE,
         CrossThreadBind(&AudioWorkletThreadTest::ExecuteScriptInWorklet,
@@ -81,7 +81,8 @@
   }
 
  private:
-  void ExecuteScriptInWorklet(WorkerThread* thread, WaitableEvent* wait_event) {
+  void ExecuteScriptInWorklet(WorkerThread* thread,
+                              base::WaitableEvent* wait_event) {
     ScriptState* script_state =
         thread->GlobalScope()->ScriptController()->GetScriptState();
     EXPECT_TRUE(script_state);
diff --git a/third_party/blink/renderer/modules/webaudio/convolver_node.cc b/third_party/blink/renderer/modules/webaudio/convolver_node.cc
index 26c4cc6..9131343 100644
--- a/third_party/blink/renderer/modules/webaudio/convolver_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/convolver_node.cc
@@ -96,7 +96,7 @@
 
   if (!buffer) {
     reverb_.reset();
-    buffer_ = buffer;
+    shared_buffer_ = nullptr;
     return;
   }
 
@@ -132,9 +132,10 @@
   // reference to it is kept for later use in that class.
   scoped_refptr<AudioBus> buffer_bus =
       AudioBus::Create(number_of_channels, buffer_length, false);
-  for (unsigned i = 0; i < number_of_channels; ++i)
+  for (unsigned i = 0; i < number_of_channels; ++i) {
     buffer_bus->SetChannelMemory(i, buffer->getChannelData(i).View()->Data(),
                                  buffer_length);
+  }
 
   buffer_bus->SetSampleRate(buffer->sampleRate());
 
@@ -151,21 +152,16 @@
     // Synchronize with process().
     MutexLocker locker(process_lock_);
     reverb_ = std::move(reverb);
-    buffer_ = buffer;
+    shared_buffer_ = buffer->CreateSharedAudioBuffer();
     if (buffer) {
       // This will propagate the channel count to any nodes connected further
       // downstream in the graph.
       Output(0).SetNumberOfChannels(ComputeNumberOfOutputChannels(
-          Input(0).NumberOfChannels(), buffer_->numberOfChannels()));
+          Input(0).NumberOfChannels(), shared_buffer_->numberOfChannels()));
     }
   }
 }
 
-AudioBuffer* ConvolverHandler::Buffer() {
-  DCHECK(IsMainThread());
-  return buffer_.Get();
-}
-
 bool ConvolverHandler::RequiresTailProcessing() const {
   // Always return true even if the tail time and latency might both be zero.
   return true;
@@ -237,9 +233,9 @@
   if (input != &this->Input(0))
     return;
 
-  if (buffer_) {
+  if (shared_buffer_) {
     unsigned number_of_output_channels = ComputeNumberOfOutputChannels(
-        input->NumberOfChannels(), buffer_->numberOfChannels());
+        input->NumberOfChannels(), shared_buffer_->numberOfChannels());
 
     if (IsInitialized() &&
         number_of_output_channels != Output(0).NumberOfChannels()) {
@@ -294,12 +290,14 @@
 }
 
 AudioBuffer* ConvolverNode::buffer() const {
-  return GetConvolverHandler().Buffer();
+  return buffer_;
 }
 
 void ConvolverNode::setBuffer(AudioBuffer* new_buffer,
                               ExceptionState& exception_state) {
   GetConvolverHandler().SetBuffer(new_buffer, exception_state);
+  if (!exception_state.HadException())
+    buffer_ = new_buffer;
 }
 
 bool ConvolverNode::normalize() const {
@@ -310,4 +308,9 @@
   GetConvolverHandler().SetNormalize(normalize);
 }
 
+void ConvolverNode::Trace(Visitor* visitor) {
+  visitor->Trace(buffer_);
+  AudioNode::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/convolver_node.h b/third_party/blink/renderer/modules/webaudio/convolver_node.h
index d615533..22cf96b 100644
--- a/third_party/blink/renderer/modules/webaudio/convolver_node.h
+++ b/third_party/blink/renderer/modules/webaudio/convolver_node.h
@@ -40,6 +40,7 @@
 class ConvolverOptions;
 class ExceptionState;
 class Reverb;
+class SharedAudioBuffer;
 
 class MODULES_EXPORT ConvolverHandler final : public AudioHandler {
  public:
@@ -54,7 +55,6 @@
 
   // Impulse responses
   void SetBuffer(AudioBuffer*, ExceptionState&);
-  AudioBuffer* Buffer();
 
   bool Normalize() const { return normalize_; }
   void SetNormalize(bool normalize) { normalize_ = normalize; }
@@ -74,10 +74,7 @@
                                          unsigned response_channels) const;
 
   std::unique_ptr<Reverb> reverb_;
-  // This Persistent doesn't make a reference cycle including the owner
-  // ConvolverNode.
-  // It is cross-thread, as it will be accessed by the audio and main threads.
-  CrossThreadPersistent<AudioBuffer> buffer_;
+  std::unique_ptr<SharedAudioBuffer> shared_buffer_;
 
   // This synchronizes dynamic changes to the convolution impulse response with
   // process().
@@ -105,9 +102,13 @@
   bool normalize() const;
   void setNormalize(bool);
 
+  void Trace(Visitor*) override;
+
  private:
   ConvolverHandler& GetConvolverHandler() const;
 
+  Member<AudioBuffer> buffer_;
+
   FRIEND_TEST_ALL_PREFIXES(ConvolverNodeTest, ReverbLifetime);
 };
 
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc b/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
index df4d7ae..6b8c8760 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
@@ -216,6 +216,8 @@
   // Start rendering and return the promise.
   is_rendering_started_ = true;
   SetContextState(kRunning);
+  static_cast<OfflineAudioDestinationNode*>(destination())
+      ->SetDestinationBuffer(render_target);
   DestinationHandler().InitializeOfflineRenderThread(render_target);
   DestinationHandler().StartRendering();
 
@@ -359,8 +361,9 @@
 
   // Avoid firing the event if the document has already gone away.
   if (GetExecutionContext()) {
-    AudioBuffer* rendered_buffer = DestinationHandler().RenderTarget();
-
+    AudioBuffer* rendered_buffer =
+        static_cast<OfflineAudioDestinationNode*>(destination())
+            ->DestinationBuffer();
     DCHECK(rendered_buffer);
     if (!rendered_buffer)
       return;
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc
index d2ff67e..cb15f3e 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc
@@ -47,7 +47,6 @@
     uint32_t frames_to_process,
     float sample_rate)
     : AudioDestinationHandler(node),
-      render_target_(nullptr),
       frames_processed_(0),
       frames_to_process_(frames_to_process),
       is_rendering_started_(false),
@@ -109,7 +108,7 @@
 
 void OfflineAudioDestinationHandler::StartRendering() {
   DCHECK(IsMainThread());
-  DCHECK(render_target_);
+  DCHECK(shared_render_target_);
   DCHECK(render_thread_task_runner_);
 
   // Rendering was not started. Starting now.
@@ -147,7 +146,7 @@
     AudioBuffer* render_target) {
   DCHECK(IsMainThread());
 
-  render_target_ = render_target;
+  shared_render_target_ = render_target->CreateSharedAudioBuffer();
   render_bus_ = AudioBus::Create(render_target->numberOfChannels(),
                                  audio_utilities::kRenderQuantumFrames);
   DCHECK(render_bus_);
@@ -167,8 +166,8 @@
   if (!is_audio_context_initialized)
     return;
 
-  bool channels_match =
-      render_bus_->NumberOfChannels() == render_target_->numberOfChannels();
+  bool channels_match = render_bus_->NumberOfChannels() ==
+                        shared_render_target_->numberOfChannels();
   DCHECK(channels_match);
   if (!channels_match)
     return;
@@ -203,10 +202,12 @@
       return;
     }
 
-    number_of_channels = render_target_->numberOfChannels();
+    number_of_channels = shared_render_target_->numberOfChannels();
     destinations.ReserveInitialCapacity(number_of_channels);
-    for (unsigned i = 0; i < number_of_channels; ++i)
-      destinations.push_back(render_target_->getChannelData(i).View()->Data());
+    for (unsigned i = 0; i < number_of_channels; ++i) {
+      destinations.push_back(
+          static_cast<float*>(shared_render_target_->channels()[i].Data()));
+    }
     ProcessHeap::CrossThreadPersistentMutex().unlock();
   }
 
@@ -417,4 +418,9 @@
       *context, number_of_channels, frames_to_process, sample_rate);
 }
 
+void OfflineAudioDestinationNode::Trace(Visitor* visitor) {
+  visitor->Trace(destination_buffer_);
+  AudioDestinationNode::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.h b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.h
index dc22545..e4d2d8b 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.h
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.h
@@ -79,7 +79,6 @@
   // allows creation of the AudioBuffer when startRendering is called
   // instead of when the OfflineAudioContext is created.
   void InitializeOfflineRenderThread(AudioBuffer* render_target);
-  AudioBuffer* RenderTarget() const { return render_target_.Get(); }
 
   unsigned NumberOfChannels() const { return number_of_channels_; }
 
@@ -125,10 +124,8 @@
   // from AudioWorkletThread will be used until the rendering is finished.
   void PrepareTaskRunnerForRendering();
 
-  // This AudioHandler renders into this AudioBuffer.
-  // This Persistent doesn't make a reference cycle including the owner
-  // OfflineAudioDestinationNode. It is accessed by both audio and main thread.
-  CrossThreadPersistent<AudioBuffer> render_target_;
+  // This AudioHandler renders into this SharedAudioBuffer.
+  std::unique_ptr<SharedAudioBuffer> shared_render_target_;
   // Temporary AudioBus for each render quantum.
   scoped_refptr<AudioBus> render_bus_;
 
@@ -164,6 +161,17 @@
                               unsigned number_of_channels,
                               uint32_t frames_to_process,
                               float sample_rate);
+
+  AudioBuffer* DestinationBuffer() const { return destination_buffer_; }
+
+  void SetDestinationBuffer(AudioBuffer* buffer) {
+    destination_buffer_ = buffer;
+  }
+
+  void Trace(Visitor* visitor) override;
+
+ private:
+  Member<AudioBuffer> destination_buffer_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/oscillator_node.cc b/third_party/blink/renderer/modules/webaudio/oscillator_node.cc
index 76d7e06..c2b628f 100644
--- a/third_party/blink/renderer/modules/webaudio/oscillator_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/oscillator_node.cc
@@ -125,7 +125,7 @@
   }
 }
 
-bool OscillatorHandler::SetType(unsigned type) {
+bool OscillatorHandler::SetType(uint8_t type) {
   PeriodicWave* periodic_wave = nullptr;
 
   switch (type) {
diff --git a/third_party/blink/renderer/modules/webaudio/oscillator_node.h b/third_party/blink/renderer/modules/webaudio/oscillator_node.h
index a3840ec..40563bbe 100644
--- a/third_party/blink/renderer/modules/webaudio/oscillator_node.h
+++ b/third_party/blink/renderer/modules/webaudio/oscillator_node.h
@@ -46,7 +46,13 @@
  public:
   // The waveform type.
   // These must be defined as in the .idl file.
-  enum { SINE = 0, SQUARE = 1, SAWTOOTH = 2, TRIANGLE = 3, CUSTOM = 4 };
+  enum : uint8_t {
+    SINE = 0,
+    SQUARE = 1,
+    SAWTOOTH = 2,
+    TRIANGLE = 3,
+    CUSTOM = 4
+  };
 
   static scoped_refptr<OscillatorHandler> Create(AudioNode&,
                                                  float sample_rate,
@@ -71,7 +77,7 @@
                     PeriodicWave* wave_table,
                     AudioParamHandler& frequency,
                     AudioParamHandler& detune);
-  bool SetType(unsigned);  // Returns true on success.
+  bool SetType(uint8_t);  // Returns true on success.
 
   // Returns true if there are sample-accurate timeline parameter changes.
   bool CalculateSampleAccuratePhaseIncrements(uint32_t frames_to_process);
@@ -79,7 +85,7 @@
   bool PropagatesSilence() const override;
 
   // One of the waveform types defined in the enum.
-  unsigned short type_;
+  uint8_t type_;
 
   // Frequency value in Hertz.
   scoped_refptr<AudioParamHandler> frequency_;
diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc
index b733e5a..3f3b28b 100644
--- a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc
@@ -39,7 +39,6 @@
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 namespace blink {
 
@@ -48,11 +47,11 @@
     float sample_rate,
     uint32_t buffer_size,
     uint32_t number_of_input_channels,
-    uint32_t number_of_output_channels)
+    uint32_t number_of_output_channels,
+    const HeapVector<Member<AudioBuffer>>& input_buffers,
+    const HeapVector<Member<AudioBuffer>>& output_buffers)
     : AudioHandler(kNodeTypeScriptProcessor, node, sample_rate),
       double_buffer_index_(0),
-      input_buffers_(MakeGarbageCollected<HeapVector<Member<AudioBuffer>>>()),
-      output_buffers_(MakeGarbageCollected<HeapVector<Member<AudioBuffer>>>()),
       buffer_size_(buffer_size),
       buffer_read_write_index_(0),
       number_of_input_channels_(number_of_input_channels),
@@ -61,11 +60,7 @@
           AudioBus::Create(number_of_input_channels,
                            audio_utilities::kRenderQuantumFrames,
                            false)) {
-  // Regardless of the allowed buffer sizes, we still need to process at the
-  // granularity of the AudioNode.
-  if (buffer_size_ < audio_utilities::kRenderQuantumFrames)
-    buffer_size_ = audio_utilities::kRenderQuantumFrames;
-
+  DCHECK_GE(buffer_size_, audio_utilities::kRenderQuantumFrames);
   DCHECK_LE(number_of_input_channels, BaseAudioContext::MaxNumberOfChannels());
 
   AddInput();
@@ -79,6 +74,15 @@
         TaskType::kMediaElementEvent);
   }
 
+  for (uint32_t i = 0; i < 2; ++i) {
+    shared_input_buffers_.push_back(
+        input_buffers[i] ? input_buffers[i]->CreateSharedAudioBuffer()
+                         : nullptr);
+    shared_output_buffers_.push_back(
+        output_buffers[i] ? output_buffers[i]->CreateSharedAudioBuffer()
+                          : nullptr);
+  }
+
   Initialize();
 }
 
@@ -87,10 +91,12 @@
     float sample_rate,
     uint32_t buffer_size,
     uint32_t number_of_input_channels,
-    uint32_t number_of_output_channels) {
+    uint32_t number_of_output_channels,
+    const HeapVector<Member<AudioBuffer>>& input_buffers,
+    const HeapVector<Member<AudioBuffer>>& output_buffers) {
   return base::AdoptRef(new ScriptProcessorHandler(
       node, sample_rate, buffer_size, number_of_input_channels,
-      number_of_output_channels));
+      number_of_output_channels, input_buffers, output_buffers));
 }
 
 ScriptProcessorHandler::~ScriptProcessorHandler() {
@@ -100,28 +106,6 @@
 void ScriptProcessorHandler::Initialize() {
   if (IsInitialized())
     return;
-
-  float sample_rate = Context()->sampleRate();
-
-  // Create double buffers on both the input and output sides.
-  // These AudioBuffers will be directly accessed in the main thread by
-  // JavaScript.
-  for (uint32_t i = 0; i < 2; ++i) {
-    AudioBuffer* input_buffer =
-        number_of_input_channels_
-            ? AudioBuffer::Create(number_of_input_channels_, BufferSize(),
-                                  sample_rate)
-            : nullptr;
-    AudioBuffer* output_buffer =
-        number_of_output_channels_
-            ? AudioBuffer::Create(number_of_output_channels_, BufferSize(),
-                                  sample_rate)
-            : nullptr;
-
-    input_buffers_->push_back(input_buffer);
-    output_buffers_->push_back(output_buffer);
-  }
-
   AudioHandler::Initialize();
 }
 
@@ -142,25 +126,29 @@
   // sides.
   uint32_t double_buffer_index = this->DoubleBufferIndex();
   bool is_double_buffer_index_good =
-      double_buffer_index < 2 && double_buffer_index < input_buffers_->size() &&
-      double_buffer_index < output_buffers_->size();
+      double_buffer_index < 2 &&
+      double_buffer_index < shared_input_buffers_.size() &&
+      double_buffer_index < shared_output_buffers_.size();
   DCHECK(is_double_buffer_index_good);
   if (!is_double_buffer_index_good)
     return;
 
-  AudioBuffer* input_buffer = input_buffers_->at(double_buffer_index).Get();
-  AudioBuffer* output_buffer = output_buffers_->at(double_buffer_index).Get();
+  SharedAudioBuffer* shared_input_buffer =
+      shared_input_buffers_.at(double_buffer_index).get();
+  SharedAudioBuffer* shared_output_buffer =
+      shared_output_buffers_.at(double_buffer_index).get();
 
   // Check the consistency of input and output buffers.
   uint32_t number_of_input_channels = internal_input_bus_->NumberOfChannels();
   bool buffers_are_good =
-      output_buffer && BufferSize() == output_buffer->length() &&
+      shared_output_buffer && BufferSize() == shared_output_buffer->length() &&
       buffer_read_write_index_ + frames_to_process <= BufferSize();
 
   // If the number of input channels is zero, it's ok to have inputBuffer = 0.
-  if (internal_input_bus_->NumberOfChannels())
-    buffers_are_good = buffers_are_good && input_buffer &&
-                       BufferSize() == input_buffer->length();
+  if (internal_input_bus_->NumberOfChannels()) {
+    buffers_are_good = buffers_are_good && shared_input_buffer &&
+                       BufferSize() == shared_input_buffer->length();
+  }
 
   DCHECK(buffers_are_good);
   if (!buffers_are_good)
@@ -187,7 +175,7 @@
   for (uint32_t i = 0; i < number_of_input_channels; ++i)
     internal_input_bus_->SetChannelMemory(
         i,
-        input_buffer->getChannelData(i).View()->Data() +
+        static_cast<float*>(shared_input_buffer->channels()[i].Data()) +
             buffer_read_write_index_,
         frames_to_process);
 
@@ -197,7 +185,7 @@
   // Copy from the output buffer to the output.
   for (uint32_t i = 0; i < number_of_output_channels; ++i) {
     memcpy(output_bus->Channel(i)->MutableData(),
-           output_buffer->getChannelData(i).View()->Data() +
+           static_cast<float*>(shared_output_buffer->channels()[i].Data()) +
                buffer_read_write_index_,
            sizeof(float) * frames_to_process);
   }
@@ -219,7 +207,7 @@
     if (!try_locker.Locked()) {
       // We're late in handling the previous request. The main thread must be
       // very busy.  The best we can do is clear out the buffer ourself here.
-      output_buffer->Zero();
+      shared_output_buffer->Zero();
     } else {
       // With the realtime context, execute the script code asynchronously
       // and do not wait.
@@ -233,8 +221,8 @@
       } else {
         // If this node is in the offline audio context, use the
         // waitable event to synchronize to the offline rendering thread.
-        std::unique_ptr<WaitableEvent> waitable_event =
-            std::make_unique<WaitableEvent>();
+        std::unique_ptr<base::WaitableEvent> waitable_event =
+            std::make_unique<base::WaitableEvent>();
 
         PostCrossThreadTask(
             *task_runner_, FROM_HERE,
@@ -263,12 +251,6 @@
   if (double_buffer_index > 1)
     return;
 
-  AudioBuffer* input_buffer = input_buffers_->at(double_buffer_index).Get();
-  AudioBuffer* output_buffer = output_buffers_->at(double_buffer_index).Get();
-  DCHECK(output_buffer);
-  if (!output_buffer)
-    return;
-
   // Avoid firing the event if the document has already gone away.
   if (GetNode()) {
     // This synchronizes with process().
@@ -280,16 +262,14 @@
     // double-buffering.
     double playback_time = (Context()->CurrentSampleFrame() + buffer_size_) /
                            static_cast<double>(Context()->sampleRate());
-
-    // Call the JavaScript event handler which will do the audio processing.
-    GetNode()->DispatchEvent(*AudioProcessingEvent::Create(
-        input_buffer, output_buffer, playback_time));
+    static_cast<ScriptProcessorNode*>(GetNode())->DispatchEvent(
+        playback_time, double_buffer_index);
   }
 }
 
 void ScriptProcessorHandler::FireProcessEventForOfflineAudioContext(
     uint32_t double_buffer_index,
-    WaitableEvent* waitable_event) {
+    base::WaitableEvent* waitable_event) {
   DCHECK(IsMainThread());
 
   if (!Context() || !Context()->GetExecutionContext())
@@ -301,21 +281,13 @@
     return;
   }
 
-  AudioBuffer* input_buffer = input_buffers_->at(double_buffer_index).Get();
-  AudioBuffer* output_buffer = output_buffers_->at(double_buffer_index).Get();
-  DCHECK(output_buffer);
-  if (!output_buffer) {
-    waitable_event->Signal();
-    return;
-  }
-
   if (GetNode()) {
     // We do not need a process lock here because the offline render thread
     // is locked by the waitable event.
     double playback_time = (Context()->CurrentSampleFrame() + buffer_size_) /
                            static_cast<double>(Context()->sampleRate());
-    GetNode()->DispatchEvent(*AudioProcessingEvent::Create(
-        input_buffer, output_buffer, playback_time));
+    static_cast<ScriptProcessorNode*>(GetNode())->DispatchEvent(
+        playback_time, double_buffer_index);
   }
 
   waitable_event->Signal();
@@ -369,9 +341,31 @@
                                          uint32_t number_of_input_channels,
                                          uint32_t number_of_output_channels)
     : AudioNode(context) {
-  SetHandler(ScriptProcessorHandler::Create(*this, sample_rate, buffer_size,
-                                            number_of_input_channels,
-                                            number_of_output_channels));
+  // Regardless of the allowed buffer sizes, we still need to process at the
+  // granularity of the AudioNode.
+  if (buffer_size < audio_utilities::kRenderQuantumFrames)
+    buffer_size = audio_utilities::kRenderQuantumFrames;
+
+  // Create double buffers on both the input and output sides.
+  // These AudioBuffers will be directly accessed in the main thread by
+  // JavaScript.
+  for (uint32_t i = 0; i < 2; ++i) {
+    AudioBuffer* input_buffer =
+        number_of_input_channels ? AudioBuffer::Create(number_of_input_channels,
+                                                       buffer_size, sample_rate)
+                                 : nullptr;
+    AudioBuffer* output_buffer =
+        number_of_output_channels
+            ? AudioBuffer::Create(number_of_output_channels, buffer_size,
+                                  sample_rate)
+            : nullptr;
+
+    input_buffers_.push_back(input_buffer);
+    output_buffers_.push_back(output_buffer);
+  }
+  SetHandler(ScriptProcessorHandler::Create(
+      *this, sample_rate, buffer_size, number_of_input_channels,
+      number_of_output_channels, input_buffers_, output_buffers_));
 }
 
 static uint32_t ChooseBufferSize(uint32_t callback_buffer_size) {
@@ -511,6 +505,15 @@
   return static_cast<ScriptProcessorHandler&>(Handler()).BufferSize();
 }
 
+void ScriptProcessorNode::DispatchEvent(double playback_time,
+                                        uint32_t double_buffer_index) {
+  AudioBuffer* input_buffer = input_buffers_.at(double_buffer_index).Get();
+  AudioBuffer* output_buffer = output_buffers_.at(double_buffer_index).Get();
+  DCHECK(output_buffer);
+  AudioNode::DispatchEvent(*AudioProcessingEvent::Create(
+      input_buffer, output_buffer, playback_time));
+}
+
 bool ScriptProcessorNode::HasPendingActivity() const {
   // To prevent the node from leaking after the context is closed.
   if (context()->IsContextClosed())
@@ -524,4 +527,10 @@
   return false;
 }
 
+void ScriptProcessorNode::Trace(Visitor* visitor) {
+  visitor->Trace(input_buffers_);
+  visitor->Trace(output_buffers_);
+  AudioNode::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node.h b/third_party/blink/renderer/modules/webaudio/script_processor_node.h
index 6dc2433..99f1e3f 100644
--- a/third_party/blink/renderer/modules/webaudio/script_processor_node.h
+++ b/third_party/blink/renderer/modules/webaudio/script_processor_node.h
@@ -28,6 +28,7 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
@@ -37,8 +38,9 @@
 
 namespace blink {
 
-class BaseAudioContext;
 class AudioBuffer;
+class BaseAudioContext;
+class SharedAudioBuffer;
 class WaitableEvent;
 
 // ScriptProcessorNode is an AudioNode which allows for arbitrary synthesis or
@@ -56,7 +58,9 @@
       float sample_rate,
       uint32_t buffer_size,
       uint32_t number_of_input_channels,
-      uint32_t number_of_output_channels);
+      uint32_t number_of_output_channels,
+      const HeapVector<Member<AudioBuffer>>& input_buffers,
+      const HeapVector<Member<AudioBuffer>>& output_buffers);
   ~ScriptProcessorHandler() override;
 
   // AudioHandler
@@ -77,23 +81,23 @@
                          float sample_rate,
                          uint32_t buffer_size,
                          uint32_t number_of_input_channels,
-                         uint32_t number_of_output_channels);
+                         uint32_t number_of_output_channels,
+                         const HeapVector<Member<AudioBuffer>>& input_buffers,
+                         const HeapVector<Member<AudioBuffer>>& output_buffers);
   double TailTime() const override;
   double LatencyTime() const override;
   bool RequiresTailProcessing() const final;
 
   void FireProcessEvent(uint32_t);
-  void FireProcessEventForOfflineAudioContext(uint32_t, WaitableEvent*);
+  void FireProcessEventForOfflineAudioContext(uint32_t, base::WaitableEvent*);
 
   // Double buffering
   uint32_t DoubleBufferIndex() const { return double_buffer_index_; }
   void SwapBuffers() { double_buffer_index_ = 1 - double_buffer_index_; }
   uint32_t double_buffer_index_;
 
-  // These Persistent don't make reference cycles including the owner
-  // ScriptProcessorNode.
-  CrossThreadPersistent<HeapVector<Member<AudioBuffer>>> input_buffers_;
-  CrossThreadPersistent<HeapVector<Member<AudioBuffer>>> output_buffers_;
+  WTF::Vector<std::unique_ptr<SharedAudioBuffer>> shared_input_buffers_;
+  WTF::Vector<std::unique_ptr<SharedAudioBuffer>> shared_output_buffers_;
 
   uint32_t buffer_size_;
   uint32_t buffer_read_write_index_;
@@ -148,10 +152,16 @@
   DEFINE_ATTRIBUTE_EVENT_LISTENER(audioprocess, kAudioprocess)
   uint32_t bufferSize() const;
 
+  void DispatchEvent(double playback_time, uint32_t double_buffer_index);
+
   // ScriptWrappable
   bool HasPendingActivity() const final;
 
-  void Trace(blink::Visitor* visitor) override { AudioNode::Trace(visitor); }
+  void Trace(blink::Visitor* visitor) override;
+
+ private:
+  HeapVector<Member<AudioBuffer>> input_buffers_;
+  HeapVector<Member<AudioBuffer>> output_buffers_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc b/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc
index 01073040..d5cf3a6 100644
--- a/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/script_processor_node_test.cc
@@ -18,14 +18,14 @@
       context->createScriptProcessor(ASSERT_NO_EXCEPTION);
   ScriptProcessorHandler& handler =
       static_cast<ScriptProcessorHandler&>(node->Handler());
-  EXPECT_EQ(2u, handler.input_buffers_->size());
-  EXPECT_EQ(2u, handler.output_buffers_->size());
+  EXPECT_EQ(2u, handler.shared_input_buffers_.size());
+  EXPECT_EQ(2u, handler.shared_input_buffers_.size());
   BaseAudioContext::GraphAutoLocker locker(context);
   handler.Dispose();
   // Buffers should live after dispose() because an audio thread is using
   // them.
-  EXPECT_EQ(2u, handler.input_buffers_->size());
-  EXPECT_EQ(2u, handler.output_buffers_->size());
+  EXPECT_EQ(2u, handler.shared_input_buffers_.size());
+  EXPECT_EQ(2u, handler.shared_input_buffers_.size());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webdatabase/database.cc b/third_party/blink/renderer/modules/webdatabase/database.cc
index 4448256..45e85eb 100644
--- a/third_party/blink/renderer/modules/webdatabase/database.cc
+++ b/third_party/blink/renderer/modules/webdatabase/database.cc
@@ -27,6 +27,7 @@
 
 #include <memory>
 
+#include "base/synchronization/waitable_event.h"
 #include "base/thread_annotations.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
@@ -52,7 +53,6 @@
 #include "third_party/blink/renderer/modules/webdatabase/storage_log.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 // Registering "opened" databases with the DatabaseTracker
 // =======================================================
@@ -279,7 +279,7 @@
                                     DatabaseError& error,
                                     String& error_message,
                                     V8DatabaseCallback* creation_callback) {
-  WaitableEvent event;
+  base::WaitableEvent event;
   if (!GetDatabaseContext()->DatabaseThreadAvailable())
     return false;
 
@@ -901,7 +901,7 @@
   // take strict turns in dealing with them. However, if the code changes,
   // this may not be true anymore.
   Vector<String> result;
-  WaitableEvent event;
+  base::WaitableEvent event;
   if (!GetDatabaseContext()->DatabaseThreadAvailable())
     return result;
 
diff --git a/third_party/blink/renderer/modules/webdatabase/database_task.cc b/third_party/blink/renderer/modules/webdatabase/database_task.cc
index 957f0d1..6b917f3 100644
--- a/third_party/blink/renderer/modules/webdatabase/database_task.cc
+++ b/third_party/blink/renderer/modules/webdatabase/database_task.cc
@@ -28,6 +28,7 @@
 
 #include "third_party/blink/renderer/modules/webdatabase/database_task.h"
 
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/modules/webdatabase/database.h"
 #include "third_party/blink/renderer/modules/webdatabase/database_context.h"
 #include "third_party/blink/renderer/modules/webdatabase/database_thread.h"
@@ -35,7 +36,8 @@
 
 namespace blink {
 
-DatabaseTask::DatabaseTask(Database* database, WaitableEvent* complete_event)
+DatabaseTask::DatabaseTask(Database* database,
+                           base::WaitableEvent* complete_event)
     : database_(database),
       complete_event_(complete_event)
 #if DCHECK_IS_ON()
@@ -85,12 +87,13 @@
 // Opens the database file and verifies the version matches the expected
 // version.
 
-Database::DatabaseOpenTask::DatabaseOpenTask(Database* database,
-                                             bool set_version_in_new_database,
-                                             WaitableEvent* complete_event,
-                                             DatabaseError& error,
-                                             String& error_message,
-                                             bool& success)
+Database::DatabaseOpenTask::DatabaseOpenTask(
+    Database* database,
+    bool set_version_in_new_database,
+    base::WaitableEvent* complete_event,
+    DatabaseError& error,
+    String& error_message,
+    bool& success)
     : DatabaseTask(database, complete_event),
       set_version_in_new_database_(set_version_in_new_database),
       error_(error),
@@ -117,8 +120,9 @@
 // *** DatabaseCloseTask ***
 // Closes the database.
 
-Database::DatabaseCloseTask::DatabaseCloseTask(Database* database,
-                                               WaitableEvent* complete_event)
+Database::DatabaseCloseTask::DatabaseCloseTask(
+    Database* database,
+    base::WaitableEvent* complete_event)
     : DatabaseTask(database, complete_event) {}
 
 void Database::DatabaseCloseTask::DoPerformTask() {
@@ -168,7 +172,7 @@
 
 Database::DatabaseTableNamesTask::DatabaseTableNamesTask(
     Database* database,
-    WaitableEvent* complete_event,
+    base::WaitableEvent* complete_event,
     Vector<String>& names)
     : DatabaseTask(database, complete_event), table_names_(names) {
   DCHECK(complete_event);  // A task with output parameters is supposed to be
diff --git a/third_party/blink/renderer/modules/webdatabase/database_task.h b/third_party/blink/renderer/modules/webdatabase/database_task.h
index 992d23e9..6339445 100644
--- a/third_party/blink/renderer/modules/webdatabase/database_task.h
+++ b/third_party/blink/renderer/modules/webdatabase/database_task.h
@@ -33,12 +33,12 @@
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/modules/webdatabase/database.h"
 #include "third_party/blink/renderer/modules/webdatabase/database_basic_types.h"
 #include "third_party/blink/renderer/modules/webdatabase/database_error.h"
 #include "third_party/blink/renderer/modules/webdatabase/sql_transaction_backend.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/threading.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
@@ -56,14 +56,14 @@
   Database* GetDatabase() const { return database_.Get(); }
 
  protected:
-  DatabaseTask(Database*, WaitableEvent* complete_event);
+  DatabaseTask(Database*, base::WaitableEvent* complete_event);
 
  private:
   virtual void DoPerformTask() = 0;
   virtual void TaskCancelled() {}
 
   CrossThreadPersistent<Database> database_;
-  WaitableEvent* complete_event_;
+  base::WaitableEvent* complete_event_;
 
 #if DCHECK_IS_ON()
   virtual const char* DebugTaskName() const = 0;
@@ -78,7 +78,7 @@
   static std::unique_ptr<DatabaseOpenTask> Create(
       Database* db,
       bool set_version_in_new_database,
-      WaitableEvent* complete_event,
+      base::WaitableEvent* complete_event,
       DatabaseError& error,
       String& error_message,
       bool& success) {
@@ -90,7 +90,7 @@
  private:
   DatabaseOpenTask(Database*,
                    bool set_version_in_new_database,
-                   WaitableEvent*,
+                   base::WaitableEvent*,
                    DatabaseError&,
                    String& error_message,
                    bool& success);
@@ -110,12 +110,12 @@
  public:
   static std::unique_ptr<DatabaseCloseTask> Create(
       Database* db,
-      WaitableEvent* synchronizer) {
+      base::WaitableEvent* synchronizer) {
     return base::WrapUnique(new DatabaseCloseTask(db, synchronizer));
   }
 
  private:
-  DatabaseCloseTask(Database*, WaitableEvent*);
+  DatabaseCloseTask(Database*, base::WaitableEvent*);
 
   void DoPerformTask() override;
 #if DCHECK_IS_ON()
@@ -149,14 +149,18 @@
 
 class Database::DatabaseTableNamesTask final : public DatabaseTask {
  public:
-  static std::unique_ptr<DatabaseTableNamesTask>
-  Create(Database* db, WaitableEvent* synchronizer, Vector<String>& names) {
+  static std::unique_ptr<DatabaseTableNamesTask> Create(
+      Database* db,
+      base::WaitableEvent* synchronizer,
+      Vector<String>& names) {
     return base::WrapUnique(
         new DatabaseTableNamesTask(db, synchronizer, names));
   }
 
  private:
-  DatabaseTableNamesTask(Database*, WaitableEvent*, Vector<String>& names);
+  DatabaseTableNamesTask(Database*,
+                         base::WaitableEvent*,
+                         Vector<String>& names);
 
   void DoPerformTask() override;
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/renderer/modules/webdatabase/database_thread.cc b/third_party/blink/renderer/modules/webdatabase/database_thread.cc
index 38671f3..c7411f5a 100644
--- a/third_party/blink/renderer/modules/webdatabase/database_thread.cc
+++ b/third_party/blink/renderer/modules/webdatabase/database_thread.cc
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/modules/webdatabase/database_thread.h"
 
 #include <memory>
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/modules/webdatabase/database.h"
 #include "third_party/blink/renderer/modules/webdatabase/database_task.h"
@@ -36,7 +37,6 @@
 #include "third_party/blink/renderer/modules/webdatabase/sql_transaction_coordinator.h"
 #include "third_party/blink/renderer/modules/webdatabase/storage_log.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 
 namespace blink {
@@ -74,7 +74,7 @@
 
 void DatabaseThread::Terminate() {
   DCHECK(IsMainThread());
-  WaitableEvent sync;
+  base::WaitableEvent sync;
   {
     MutexLocker lock(termination_requested_mutex_);
     DCHECK(!termination_requested_);
diff --git a/third_party/blink/renderer/modules/webdatabase/database_thread.h b/third_party/blink/renderer/modules/webdatabase/database_thread.h
index 168cc4ab..fc146e7 100644
--- a/third_party/blink/renderer/modules/webdatabase/database_thread.h
+++ b/third_party/blink/renderer/modules/webdatabase/database_thread.h
@@ -29,6 +29,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBDATABASE_DATABASE_THREAD_H_
 
 #include <memory>
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
@@ -40,7 +41,6 @@
 class DatabaseTask;
 class SQLTransactionClient;
 class SQLTransactionCoordinator;
-class WaitableEvent;
 
 class DatabaseThread : public GarbageCollectedFinalized<DatabaseThread> {
  public:
@@ -86,7 +86,7 @@
 
   std::unique_ptr<SQLTransactionClient> transaction_client_;
   CrossThreadPersistent<SQLTransactionCoordinator> transaction_coordinator_;
-  WaitableEvent* cleanup_sync_;
+  base::WaitableEvent* cleanup_sync_;
 
   Mutex termination_requested_mutex_;
   bool termination_requested_;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index b45de597..52f2ef8a 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -104,7 +104,6 @@
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
@@ -622,7 +621,7 @@
 
 static void CreateContextProviderOnMainThread(
     ContextProviderCreationInfo* creation_info,
-    WaitableEvent* waitable_event) {
+    base::WaitableEvent* waitable_event) {
   DCHECK(IsMainThread());
   // Ask for gpu compositing mode when making the context. The context will be
   // lost if the mode changes.
@@ -641,7 +640,7 @@
     Platform::GraphicsInfo* gl_info,
     bool* using_gpu_compositing,
     const KURL& url) {
-  WaitableEvent waitable_event;
+  base::WaitableEvent waitable_event;
   ContextProviderCreationInfo creation_info;
   creation_info.context_attributes = context_attributes;
   creation_info.gl_info = gl_info;
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
index 1118ca1..4f648b5d 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
@@ -275,7 +275,8 @@
 
 bool WebSocketChannelImpl::Connect(const KURL& url, const String& protocol) {
   network::mojom::blink::WebSocketPtr socket_ptr;
-  auto socket_request = mojo::MakeRequest(&socket_ptr);
+  auto socket_request = mojo::MakeRequest(
+      &socket_ptr, execution_context_->GetTaskRunner(TaskType::kWebSocket));
   service_manager::InterfaceProvider* interface_provider =
       execution_context_->GetInterfaceProvider();
   if (interface_provider)
diff --git a/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread.cc b/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread.cc
index 5b692654..58d7cc7 100644
--- a/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread.cc
+++ b/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread.h"
 
 #include "base/memory/ptr_util.h"
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
 #include "third_party/blink/renderer/core/workers/worker_backing_thread.h"
 #include "third_party/blink/renderer/core/workers/worklet_thread_holder.h"
@@ -64,14 +65,14 @@
               ->GetThread();
 }
 
-static void CollectAllGarbageOnThread(WaitableEvent* done_event) {
+static void CollectAllGarbageOnThread(base::WaitableEvent* done_event) {
   blink::ThreadState::Current()->CollectAllGarbage();
   done_event->Signal();
 }
 
 void AnimationAndPaintWorkletThread::CollectAllGarbage() {
   DCHECK(IsMainThread());
-  WaitableEvent done_event;
+  base::WaitableEvent done_event;
   auto* holder =
       WorkletThreadHolder<AnimationAndPaintWorkletThread>::GetInstance();
   if (!holder)
diff --git a/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc b/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc
index c9f0ba8f..e3224671 100644
--- a/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc
+++ b/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc
@@ -27,7 +27,6 @@
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/web_thread_supporting_gc.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_position.h"
 
@@ -55,8 +54,8 @@
 
   // Attempts to run some simple script for |thread|.
   void CheckWorkletCanExecuteScript(WorkerThread* thread) {
-    std::unique_ptr<WaitableEvent> wait_event =
-        std::make_unique<WaitableEvent>();
+    std::unique_ptr<base::WaitableEvent> wait_event =
+        std::make_unique<base::WaitableEvent>();
     thread->GetWorkerBackingThread().BackingThread().PostTask(
         FROM_HERE,
         CrossThreadBind(
@@ -69,7 +68,8 @@
   std::unique_ptr<WorkerReportingProxy> reporting_proxy_;
 
  private:
-  void ExecuteScriptInWorklet(WorkerThread* thread, WaitableEvent* wait_event) {
+  void ExecuteScriptInWorklet(WorkerThread* thread,
+                              base::WaitableEvent* wait_event) {
     ScriptState* script_state =
         thread->GlobalScope()->ScriptController()->GetScriptState();
     EXPECT_TRUE(script_state);
diff --git a/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc b/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc
index faac2ca..050cbb4 100644
--- a/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc
+++ b/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc
@@ -29,10 +29,10 @@
 #include "third_party/blink/renderer/platform/audio/hrtf_database_loader.h"
 
 #include "base/location.h"
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 namespace blink {
 
@@ -117,7 +117,7 @@
 
 // This cleanup task is needed just to make sure that the loader thread finishes
 // the load task and thus the loader thread doesn't touch m_thread any more.
-void HRTFDatabaseLoader::CleanupTask(WaitableEvent* sync) {
+void HRTFDatabaseLoader::CleanupTask(base::WaitableEvent* sync) {
   sync->Signal();
 }
 
@@ -125,7 +125,7 @@
   if (!thread_)
     return;
 
-  WaitableEvent sync;
+  base::WaitableEvent sync;
   // TODO(alexclarke): Should this be posted as a loading task?
   PostCrossThreadTask(*thread_->GetTaskRunner(), FROM_HERE,
                       CrossThreadBind(&HRTFDatabaseLoader::CleanupTask,
diff --git a/third_party/blink/renderer/platform/audio/hrtf_database_loader.h b/third_party/blink/renderer/platform/audio/hrtf_database_loader.h
index d1ac9dc..f7908dda 100644
--- a/third_party/blink/renderer/platform/audio/hrtf_database_loader.h
+++ b/third_party/blink/renderer/platform/audio/hrtf_database_loader.h
@@ -30,6 +30,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_HRTF_DATABASE_LOADER_H_
 
 #include <memory>
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/renderer/platform/audio/hrtf_database.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
@@ -81,7 +82,7 @@
 
   // Called in asynchronous loading thread.
   void LoadTask();
-  void CleanupTask(WaitableEvent*);
+  void CleanupTask(base::WaitableEvent*);
 
   // Holding a m_lock is required when accessing m_hrtfDatabase since we access
   // it from multiple threads.
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
index 02253633..06930f5 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
@@ -760,7 +760,7 @@
   HarfBuzzShaper shaper(string);
   scoped_refptr<ShapeResult> result = shaper.Shape(&font, data.direction);
 
-  auto& run = TestInfo(result)->RunInfoForTesting(data.run_index);
+  const auto& run = TestInfo(result)->RunInfoForTesting(data.run_index);
   auto glyphs = run.FindGlyphDataRange(data.start_offset, data.end_offset);
   unsigned start_glyph = std::distance(run.glyph_data_.begin(), glyphs.begin);
   EXPECT_EQ(data.start_glyph, start_glyph);
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
index 90861cb..20f5f6ed 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
@@ -438,6 +438,10 @@
   float LineLeftBounds() const;
   float LineRightBounds() const;
 
+  // Common signatures with ShapeResultView, to templatize algorithms.
+  const Vector<scoped_refptr<RunInfo>>& RunsOrParts() const { return runs_; }
+  unsigned StartIndexOffsetForRun() const { return 0; }
+
   float width_;
   FloatRect glyph_bounding_box_;
   Vector<scoped_refptr<RunInfo>> runs_;
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
index 30ab296..4be97d3 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
@@ -169,41 +169,20 @@
   // iterator pattern; i.e., |begin| is lower or equal to |end| in the address
   // space regardless of LTR/RTL. |begin| is inclusive, |end| is exclusive.
   struct GlyphDataRange {
-    HarfBuzzRunGlyphData* begin;
-    HarfBuzzRunGlyphData* end;
+    GlyphDataRange FindGlyphDataRange(bool is_rtl,
+                                      unsigned start_character_index,
+                                      unsigned end_character_index) const;
+
+    const HarfBuzzRunGlyphData* begin;
+    const HarfBuzzRunGlyphData* end;
   };
 
   // Find the range of HarfBuzzRunGlyphData for the specified character index
   // range. This function uses binary search twice, hence O(2 log n).
   GlyphDataRange FindGlyphDataRange(unsigned start_character_index,
-                                    unsigned end_character_index) {
-    const auto comparer = [](const HarfBuzzRunGlyphData& glyph_data,
-                             unsigned index) {
-      return glyph_data.character_index < index;
-    };
-    if (!Rtl()) {
-      HarfBuzzRunGlyphData* start_glyph =
-          std::lower_bound(glyph_data_.begin(), glyph_data_.end(),
-                           start_character_index, comparer);
-      if (UNLIKELY(start_glyph == glyph_data_.end()))
-        return {nullptr, nullptr};
-      HarfBuzzRunGlyphData* end_glyph = std::lower_bound(
-          start_glyph, glyph_data_.end(), end_character_index, comparer);
-      return {start_glyph, end_glyph};
-    }
-
-    // RTL needs to use reverse iterators because there maybe multiple glyphs
-    // for a character, and we want to find the first one in the logical order.
-    auto start_glyph =
-        std::lower_bound(glyph_data_.rbegin(), glyph_data_.rend(),
-                         start_character_index, comparer);
-    if (UNLIKELY(start_glyph == glyph_data_.rend()))
-      return {nullptr, nullptr};
-    auto end_glyph = std::lower_bound(start_glyph, glyph_data_.rend(),
-                                      end_character_index, comparer);
-    // Convert reverse iterators to pointers. Then increment to make |begin|
-    // inclusive and |end| exclusive.
-    return {&*end_glyph + 1, &*start_glyph + 1};
+                                    unsigned end_character_index) const {
+    return GetGlyphDataRange().FindGlyphDataRange(Rtl(), start_character_index,
+                                                  end_character_index);
   }
 
   // Creates a new RunInfo instance representing a subset of the current run.
@@ -264,6 +243,13 @@
     }
   }
 
+  // Common signatures with RunInfoPart, to templatize algorithms.
+  const RunInfo* GetRunInfo() const { return this; }
+  const GlyphDataRange GetGlyphDataRange() const {
+    return {glyph_data_.begin(), glyph_data_.end()};
+  }
+  unsigned OffsetToRunStartIndex() const { return 0; }
+
   scoped_refptr<SimpleFontData> font_data_;
   hb_direction_t direction_;
   // For upright-in-vertical we need to tell the ShapeResultBloberizer to rotate
@@ -281,6 +267,42 @@
   float width_;
 };
 
+// Find the range of HarfBuzzRunGlyphData for the specified character index
+// range. This function uses binary search twice, hence O(2 log n).
+inline ShapeResult::RunInfo::GlyphDataRange
+ShapeResult::RunInfo::GlyphDataRange::FindGlyphDataRange(
+    bool is_rtl,
+    unsigned start_character_index,
+    unsigned end_character_index) const {
+  const auto comparer = [](const HarfBuzzRunGlyphData& glyph_data,
+                           unsigned index) {
+    return glyph_data.character_index < index;
+  };
+  if (!is_rtl) {
+    const HarfBuzzRunGlyphData* start_glyph =
+        std::lower_bound(begin, end, start_character_index, comparer);
+    if (UNLIKELY(start_glyph == end))
+      return {nullptr, nullptr};
+    const HarfBuzzRunGlyphData* end_glyph =
+        std::lower_bound(start_glyph, end, end_character_index, comparer);
+    return {start_glyph, end_glyph};
+  }
+
+  // RTL needs to use reverse iterators because there maybe multiple glyphs
+  // for a character, and we want to find the first one in the logical order.
+  const auto rbegin = std::reverse_iterator<const HarfBuzzRunGlyphData*>(end);
+  const auto rend = std::reverse_iterator<const HarfBuzzRunGlyphData*>(begin);
+  const auto start_glyph =
+      std::lower_bound(rbegin, rend, start_character_index, comparer);
+  if (UNLIKELY(start_glyph == rend))
+    return {nullptr, nullptr};
+  const auto end_glyph =
+      std::lower_bound(start_glyph, rend, end_character_index, comparer);
+  // Convert reverse iterators to pointers. Then increment to make |begin|
+  // inclusive and |end| exclusive.
+  return {&*end_glyph + 1, &*start_glyph + 1};
+}
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_SHAPE_RESULT_INLINE_HEADERS_H_
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.cc
index fad0549..88adc3a 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.cc
@@ -16,7 +16,7 @@
   USING_FAST_MALLOC(RunInfoPart);
 
  public:
-  RunInfoPart(scoped_refptr<ShapeResult::RunInfo> run,
+  RunInfoPart(scoped_refptr<const ShapeResult::RunInfo> run,
               ShapeResult::RunInfo::GlyphDataRange range,
               unsigned start_index,
               unsigned offset,
@@ -54,7 +54,20 @@
     return run_->GlyphToCharacterIndex(i);
   }
 
-  scoped_refptr<ShapeResult::RunInfo> run_;
+  // Common signatures with RunInfo, to templatize algorithms.
+  const ShapeResult::RunInfo* GetRunInfo() const { return run_.get(); }
+  const ShapeResult::RunInfo::GlyphDataRange& GetGlyphDataRange() const {
+    return range_;
+  }
+  ShapeResult::RunInfo::GlyphDataRange FindGlyphDataRange(
+      unsigned start_character_index,
+      unsigned end_character_index) const {
+    return GetGlyphDataRange().FindGlyphDataRange(Rtl(), start_character_index,
+                                                  end_character_index);
+  }
+  unsigned OffsetToRunStartIndex() const { return offset_; }
+
+  scoped_refptr<const ShapeResult::RunInfo> run_;
   ShapeResult::RunInfo::GlyphDataRange range_;
 
   // Start index for partial run, adjusted to ensure that runs are continuous.
@@ -87,7 +100,8 @@
   return 0;
 }
 
-ShapeResultView::ShapeResultView(const ShapeResult* other)
+template <class ShapeResultType>
+ShapeResultView::ShapeResultView(const ShapeResultType* other)
     : primary_font_(other->primary_font_),
       start_index_(0),
       num_characters_(0),
@@ -128,28 +142,34 @@
   return base::AdoptRef(new_result);
 }
 
-void ShapeResultView::CreateViewsForResult(const ShapeResult* other,
+template <class ShapeResultType>
+void ShapeResultView::CreateViewsForResult(const ShapeResultType* other,
                                            unsigned start_index,
                                            unsigned end_index) {
   bool first_result = num_characters_ == 0;
-  for (const auto& run : other->runs_) {
-    if (!run)
+  for (const auto& run : other->RunsOrParts()) {
+    if (!run->GetRunInfo())
       continue;
-    unsigned part_start = run->start_index_;
+    // Compute start/end of the run, or of the part if ShapeResultView.
+    unsigned part_start = run->start_index_ + other->StartIndexOffsetForRun();
     unsigned run_end = part_start + run->num_characters_;
     if (start_index < run_end && end_index > part_start) {
       ShapeResult::RunInfo::GlyphDataRange range;
 
+      // Adjust start/end to the character index of |RunInfo|. The start index
+      // of |RunInfo| could be different from |part_start| for ShapeResultView.
+      DCHECK_GE(part_start, run->OffsetToRunStartIndex());
+      unsigned run_start = part_start - run->OffsetToRunStartIndex();
       unsigned adjusted_start =
-          start_index > part_start ? start_index - part_start : 0;
-      unsigned adjusted_end = std::min(end_index, run_end) - part_start;
+          start_index > run_start ? start_index - run_start : 0;
+      unsigned adjusted_end = std::min(end_index, run_end) - run_start;
       DCHECK(adjusted_end > adjusted_start);
       unsigned part_characters = adjusted_end - adjusted_start;
       float part_width;
 
       // Avoid O(log n) find operation if the entire run is in range.
       if (part_start >= start_index && run_end <= end_index) {
-        range = {run->glyph_data_.begin(), run->glyph_data_.end()};
+        range = run->GetGlyphDataRange();
         part_width = run->width_;
       } else {
         range = run->FindGlyphDataRange(adjusted_start, adjusted_end);
@@ -170,8 +190,8 @@
       }
 
       parts_.push_back(std::make_unique<RunInfoPart>(
-          run, range, part_start_index, part_offset, part_characters,
-          part_width));
+          run->GetRunInfo(), range, part_start_index, part_offset,
+          part_characters, part_width));
 
       num_characters_ += part_characters;
       num_glyphs_ += range.end - range.begin;
@@ -185,7 +205,16 @@
 
 scoped_refptr<ShapeResultView> ShapeResultView::Create(const Segment* segments,
                                                        size_t segment_count) {
-  ShapeResultView* out = new ShapeResultView(segments[0].result);
+  DCHECK_GT(segment_count, 0u);
+#if DCHECK_IS_ON()
+  for (unsigned i = 0; i < segment_count; ++i) {
+    DCHECK((segments[i].result || segments[i].view) &&
+           (!segments[i].result || !segments[i].view));
+  }
+#endif
+  ShapeResultView* out = segments[0].result
+                             ? new ShapeResultView(segments[0].result)
+                             : new ShapeResultView(segments[0].view);
   out->AddSegments(segments, segment_count);
   return base::AdoptRef(out);
 }
@@ -199,6 +228,14 @@
 }
 
 scoped_refptr<ShapeResultView> ShapeResultView::Create(
+    const ShapeResultView* result,
+    unsigned start_index,
+    unsigned end_index) {
+  Segment segment = {result, start_index, end_index};
+  return Create(&segment, 1);
+}
+
+scoped_refptr<ShapeResultView> ShapeResultView::Create(
     const ShapeResult* result) {
   // This specialization is an optimization to allow the bounding box to be
   // re-used.
@@ -223,18 +260,28 @@
   // Compute start index offset for the overall run. This is added to the start
   // index of each glyph to ensure consistency with ShapeResult::SubRange
   if (!Rtl()) {  // Left-to-right
-    char_index_offset_ =
-        std::max(segments[0].result->StartIndex(), segments[0].start_index);
+    char_index_offset_ = segments[0].result ? segments[0].result->StartIndex()
+                                            : segments[0].view->StartIndex();
+    char_index_offset_ = std::max(char_index_offset_, segments[0].start_index);
   } else {  // Right to left
     char_index_offset_ = 0;
   }
 
   for (unsigned i = 0; i < segment_count; i++) {
     const Segment& segment = segments[Rtl() ? last_segment_index - i : i];
-    DCHECK_EQ(segment.result->Direction(), Direction());
-    CreateViewsForResult(segment.result, segment.start_index,
-                         segment.end_index);
-    has_vertical_offsets_ |= segment.result->has_vertical_offsets_;
+    if (segment.result) {
+      DCHECK_EQ(segment.result->Direction(), Direction());
+      CreateViewsForResult(segment.result, segment.start_index,
+                           segment.end_index);
+      has_vertical_offsets_ |= segment.result->has_vertical_offsets_;
+    } else if (segment.view) {
+      DCHECK_EQ(segment.view->Direction(), Direction());
+      CreateViewsForResult(segment.view, segment.start_index,
+                           segment.end_index);
+      has_vertical_offsets_ |= segment.view->has_vertical_offsets_;
+    } else {
+      NOTREACHED();
+    }
   }
 
   float origin = 0;
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h
index e6cbd2b..2fea4fe 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h
@@ -71,7 +71,21 @@
   // Create a new ShapeResultView from a pre-defined list of segments.
   // The segments list is assumed to be in logical order.
   struct Segment {
+    Segment() = default;
+    Segment(const ShapeResult* result, unsigned start_index, unsigned end_index)
+        : result(result),
+          view(nullptr),
+          start_index(start_index),
+          end_index(end_index) {}
+    Segment(const ShapeResultView* view,
+            unsigned start_index,
+            unsigned end_index)
+        : result(nullptr),
+          view(view),
+          start_index(start_index),
+          end_index(end_index) {}
     const ShapeResult* result;
+    const ShapeResultView* view;
     unsigned start_index;
     unsigned end_index;
   };
@@ -82,6 +96,9 @@
   static scoped_refptr<ShapeResultView> Create(const ShapeResult*,
                                                unsigned start_index,
                                                unsigned end_index);
+  static scoped_refptr<ShapeResultView> Create(const ShapeResultView*,
+                                               unsigned start_index,
+                                               unsigned end_index);
 
   ~ShapeResultView();
 
@@ -125,17 +142,25 @@
   void GetRunFontData(Vector<ShapeResult::RunFontData>*) const;
 
  private:
-  ShapeResultView(const ShapeResult*);
+  template <class ShapeResultType>
+  ShapeResultView(const ShapeResultType*);
   unsigned ComputeStartIndex() const;
 
   struct RunInfoPart;
-  void CreateViewsForResult(const ShapeResult*,
+  template <class ShapeResultType>
+  void CreateViewsForResult(const ShapeResultType*,
                             unsigned start_index,
                             unsigned end_index);
   void AddSegments(const Segment*, size_t);
   template <bool is_horizontal_run>
   void ComputeBoundsForPart(const RunInfoPart&, float origin);
 
+  // Common signatures with ShapeResult, to templatize algorithms.
+  const Vector<std::unique_ptr<RunInfoPart>, 4>& RunsOrParts() const {
+    return parts_;
+  }
+  unsigned StartIndexOffsetForRun() const { return char_index_offset_; }
+
   scoped_refptr<const SimpleFontData> primary_font_;
 
   mutable unsigned start_index_;  // Cached and updated by ComputeStartIndex.
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_view_test.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result_view_test.cc
index 8a96202..bed2c25 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_view_test.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_view_test.cc
@@ -289,4 +289,23 @@
                                   reference_glyphs.size()));
 }
 
+TEST_F(ShapeResultViewTest, TrimEndOfView) {
+  String string = To16Bit("12345678901234567890", 20);
+  TextDirection direction = TextDirection::kLtr;
+  HarfBuzzShaper shaper(string);
+  scoped_refptr<const ShapeResult> result = shaper.Shape(&font, direction);
+
+  // Create a view from 5 to 20.
+  scoped_refptr<const ShapeResultView> view1 =
+      ShapeResultView::Create(result.get(), 5, 20);
+  EXPECT_EQ(view1->NumCharacters(), 15u);
+  EXPECT_EQ(view1->NumGlyphs(), 15u);
+
+  // Trim the last character from the view.
+  scoped_refptr<const ShapeResultView> view2 =
+      ShapeResultView::Create(view1.get(), 5, 19);
+  EXPECT_EQ(view2->NumCharacters(), 14u);
+  EXPECT_EQ(view2->NumGlyphs(), 14u);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc
index 50f2bac..02b1d00 100644
--- a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc
+++ b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc
@@ -6,6 +6,7 @@
 
 #include "base/barrier_closure.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/timer/elapsed_timer.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
@@ -15,7 +16,6 @@
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 namespace blink {
 
@@ -108,9 +108,9 @@
   if (mutator_input_map_.IsEmpty())
     return;
 
-  WaitableEvent event;
+  base::WaitableEvent event;
   WTF::CrossThreadClosure on_done = CrossThreadBind(
-      &WaitableEvent::Signal, WTF::CrossThreadUnretained(&event));
+      &base::WaitableEvent::Signal, WTF::CrossThreadUnretained(&event));
   RequestMutations(std::move(on_done));
   event.Wait();
 
diff --git a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc
index 87b8dcd7..7426c6d 100644
--- a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc
+++ b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc
@@ -17,7 +17,6 @@
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 #include <memory>
 
@@ -88,7 +87,7 @@
 
   MOCK_METHOD0(NotifyAnimationsReadyRef, void());
 
-  void SignalWhenComplete(WaitableEvent* done_event) {
+  void SignalWhenComplete(base::WaitableEvent* done_event) {
     done_event_ = done_event;
   }
 
@@ -96,7 +95,7 @@
                void(cc::MutatorOutputState* output_state));
 
  private:
-  WaitableEvent* done_event_;  // not owned.
+  base::WaitableEvent* done_event_;  // not owned.
 };
 
 class AnimationWorkletMutatorDispatcherImplTest : public ::testing::Test {
@@ -352,7 +351,7 @@
   // notification from the client.
   void CallMutateAndWaitForClientCompletion(
       MutateAsyncCallback mutate_callback) {
-    WaitableEvent done_event;
+    base::WaitableEvent done_event;
     client_->SignalWhenComplete(&done_event);
     PostCrossThreadTask(*Thread::CompositorThread()->GetTaskRunner(), FROM_HERE,
                         std::move(mutate_callback));
@@ -364,16 +363,16 @@
   // such as when there are no inputs.
   void CallMutateAndWaitForCallbackCompletion(
       MutateAsyncCallback mutate_callback) {
-    WaitableEvent done_event;
-    PostCrossThreadTask(
-        *Thread::CompositorThread()->GetTaskRunner(), FROM_HERE,
-        CrossThreadBind(
-            [](MutateAsyncCallback mutate_callback, WaitableEvent* done_event) {
-              mutate_callback.Run();
-              done_event->Signal();
-            },
-            WTF::Passed(std::move(mutate_callback)),
-            WTF::CrossThreadUnretained(&done_event)));
+    base::WaitableEvent done_event;
+    PostCrossThreadTask(*Thread::CompositorThread()->GetTaskRunner(), FROM_HERE,
+                        CrossThreadBind(
+                            [](MutateAsyncCallback mutate_callback,
+                               base::WaitableEvent* done_event) {
+                              mutate_callback.Run();
+                              done_event->Signal();
+                            },
+                            WTF::Passed(std::move(mutate_callback)),
+                            WTF::CrossThreadUnretained(&done_event)));
     done_event.Wait();
   }
 };
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
index 0e5a797..8724a3f 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
@@ -58,7 +58,6 @@
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/skia/include/core/SkSurface.h"
 #include "third_party/skia/include/gpu/gl/GrGLTypes.h"
 
@@ -971,8 +970,8 @@
   bridge->SetLoggerForTesting(std::move(mock_logger));
 
   // Test entering hibernation
-  std::unique_ptr<WaitableEvent> hibernation_started_event =
-      std::make_unique<WaitableEvent>();
+  std::unique_ptr<base::WaitableEvent> hibernation_started_event =
+      std::make_unique<base::WaitableEvent>();
   EXPECT_CALL(
       *mock_logger_ptr,
       ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled));
diff --git a/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc b/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc
index 4ef2065b..2d8c47f 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc
@@ -13,7 +13,6 @@
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 namespace blink {
 
@@ -52,7 +51,7 @@
     bool only_if_gpu_compositing,
     bool* gpu_compositing_disabled,
     std::unique_ptr<WebGraphicsContext3DProviderWrapper>* wrapper,
-    WaitableEvent* waitable_event) {
+    base::WaitableEvent* waitable_event) {
   DCHECK(IsMainThread());
 
   Platform::ContextAttributes context_attributes;
@@ -122,7 +121,7 @@
     // This synchronous round-trip to the main thread is the reason why
     // SharedGpuContext encasulates the context provider: so we only have to do
     // this once per thread.
-    WaitableEvent waitable_event;
+    base::WaitableEvent waitable_event;
     scoped_refptr<base::SingleThreadTaskRunner> task_runner =
         Thread::MainThread()->GetTaskRunner();
     PostCrossThreadTask(
diff --git a/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.cc b/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.cc
index 5091b0b..77bbd09b 100644
--- a/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.cc
+++ b/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.cc
@@ -115,7 +115,7 @@
       client_(nullptr),
       result_(kShouldWait),
       is_handle_attached_(true),
-      detached_(std::make_unique<WaitableEvent>()) {}
+      detached_(std::make_unique<base::WaitableEvent>()) {}
 
 const ReplayingWebDataConsumerHandle::Command&
 ReplayingWebDataConsumerHandle::Context::Top() {
diff --git a/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.h b/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.h
index 1a6f643..59a471c 100644
--- a/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.h
+++ b/third_party/blink/renderer/platform/loader/testing/replaying_web_data_consumer_handle.h
@@ -7,8 +7,8 @@
 
 #include <memory>
 #include "base/memory/scoped_refptr.h"
+#include "base/synchronization/waitable_event.h"
 #include "third_party/blink/public/platform/web_data_consumer_handle.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
 #include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
@@ -74,7 +74,7 @@
     void DetachHandle();
     Result BeginRead(const void** buffer, Flags, size_t* available);
     Result EndRead(size_t read_size);
-    WaitableEvent* Detached() { return detached_.get(); }
+    base::WaitableEvent* Detached() { return detached_.get(); }
 
    private:
     Context();
@@ -92,7 +92,7 @@
     Result result_;
     bool is_handle_attached_;
     Mutex mutex_;
-    std::unique_ptr<WaitableEvent> detached_;
+    std::unique_ptr<base::WaitableEvent> detached_;
   };
 
   Context* GetContext() { return context_.get(); }
diff --git a/third_party/blink/renderer/platform/scheduler/common/worker_pool_unittest.cc b/third_party/blink/renderer/platform/scheduler/common/worker_pool_unittest.cc
index e3fe93f..4e3d9398 100644
--- a/third_party/blink/renderer/platform/scheduler/common/worker_pool_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/worker_pool_unittest.cc
@@ -9,13 +9,12 @@
 #include "base/test/scoped_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 
 namespace blink {
 
 namespace {
 
-void PingPongTask(WaitableEvent* done_event) {
+void PingPongTask(base::WaitableEvent* done_event) {
   done_event->Signal();
 }
 
@@ -23,7 +22,8 @@
 
 TEST(BackgroundSchedulerTest, RunOnBackgroundThread) {
   base::test::ScopedTaskEnvironment scoped_task_environment;
-  std::unique_ptr<WaitableEvent> done_event = std::make_unique<WaitableEvent>();
+  std::unique_ptr<base::WaitableEvent> done_event =
+      std::make_unique<base::WaitableEvent>();
   worker_pool::PostTask(
       FROM_HERE,
       CrossThreadBind(&PingPongTask, CrossThreadUnretained(done_event.get())));
diff --git a/third_party/blink/renderer/platform/scheduler/test/fuzzer/simple_thread_impl.h b/third_party/blink/renderer/platform/scheduler/test/fuzzer/simple_thread_impl.h
index d19ec4c..0ef9d7e 100644
--- a/third_party/blink/renderer/platform/scheduler/test/fuzzer/simple_thread_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/test/fuzzer/simple_thread_impl.h
@@ -46,7 +46,7 @@
 
   // Used by the Run function to only terminate when |this| is destructed, and
   // this is used so that |thread_data_| will live as long as |this|.
-  WaitableEvent thread_can_shutdown_;
+  base::WaitableEvent thread_can_shutdown_;
 
   ThreadCallback callback_;
 };
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_thread.h b/third_party/blink/renderer/platform/scheduler/worker/worker_thread.h
index 811bef1..ae2150e 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_thread.h
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_thread.h
@@ -9,6 +9,7 @@
 #include "base/message_loop/message_loop_current.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/atomic_flag.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "third_party/blink/public/platform/web_private_ptr.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
diff --git a/third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.cc b/third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.cc
index 8b5ae307..9fc93066 100644
--- a/third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.cc
+++ b/third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.cc
@@ -8,7 +8,6 @@
 #include "base/task/sequence_manager/test/sequence_manager_for_test.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h"
-#include "third_party/blink/renderer/platform/waitable_event.h"
 #include "third_party/blink/renderer/platform/wtf/time.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/wtf/Allocator.md b/third_party/blink/renderer/platform/wtf/Allocator.md
index 4523800..b8c7ab1 100644
--- a/third_party/blink/renderer/platform/wtf/Allocator.md
+++ b/third_party/blink/renderer/platform/wtf/Allocator.md
@@ -19,7 +19,7 @@
 * HeapVector<T>, HeapHashSet<T>, HeapHashMap<T, U> etc
 
 The implementation is in platform/heap/.
-See [BlinkGCDesign.md](../platform/heap/BlinkGCDesign.md) to learn the design.
+See [BlinkGCDesign.md](../heap/BlinkGCDesign.md) to learn the design.
 
 ### PartitionAlloc
 
@@ -74,7 +74,7 @@
 * Use Oilpan if you want a GC to manage the lifetime of the object.
 You need to make the object inherit from GarbageCollected<T> or
 GarbageCollectedFinalized<T>. See
-[BlinkGCAPIReference.md](../platform/heap/BlinkGCAPIReference.md) to learn
+[BlinkGCAPIReference.md](../heap/BlinkGCAPIReference.md) to learn
 programming with Oilpan.
 
 ```c++
@@ -88,8 +88,9 @@
 ```
 
 * Use PartitionAlloc if you don't need a GC to manage the lifetime of
-the object (i.e., if RefPtr or OwnPtr is enough to manage the lifetime
-of the object). You need to add a USING_FAST_MALLOC macro to the object.
+the object (i.e., if scoped_refptr or unique_ptr is enough to manage the
+lifetime of the object). You need to add a USING_FAST_MALLOC macro to the
+object.
 
 ```c++
 class X {
@@ -159,7 +160,7 @@
 }
 ```
 
-Note that these macros are inherited. See a comment in wtf/Allocator.h
+Note that these macros are inherited. See a comment in wtf/allocator.h
 for more details about the relationship between the macros and Oilpan.
 
 If you have any question, ask oilpan-reviews@chromium.org.
diff --git a/third_party/blink/renderer/platform/wtf/README.md b/third_party/blink/renderer/platform/wtf/README.md
index 6ea347a..4d9e25a 100644
--- a/third_party/blink/renderer/platform/wtf/README.md
+++ b/third_party/blink/renderer/platform/wtf/README.md
@@ -47,29 +47,29 @@
 
 * **Reference counting**
 
-  [RefCounted], [RefPtr]
+  [RefCounted]
 
 * **Memory**
 
-  [Allocator.h] (memory placement macros)
+  [allocator.h] (memory placement macros)
 
 * **Functors, binding**
 
-  [Functional.h]
+  [functional.h]
 
 * **Threading**
 
-  [Threading.h], [ThreadingPrimitives.h]
+  [threading.h], [threading_primitives.h]
 
 * **Compile-time switch macros**
 
-  [Compiler.h] (e.g. `COMPILER(GCC)`),
-  [CPU.h] (e.g. `WTF_CPU_ARM_NEON`),
+  [compiler.h] (e.g. `OBJC_CLASS`),
+  [cpu.h] (e.g. `WTF_CPU_ARM_NEON`),
 
 * **Miscellaneous**
 
-  [StdLibExtras.h] (`DEFINE_STATIC_LOCAL` etc.),
-  [Time.h]
+  [std_lib_extras.h] (`DEFINE_STATIC_LOCAL` etc.),
+  [time.h]
 
 ## History
 
@@ -87,7 +87,7 @@
 but it moved to Source/WTF/wtf in 2011-2012, then to Source/wtf in 2013.
 
 Blink forked WebKit in 2013. In 2017, the directory finally [moved to the
-current location][4] Source/platform/wtf.
+current location][4] platform/wtf.
 
 [the directory listing]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/
 [base]: https://cs.chromium.org/chromium/src/base/
@@ -100,15 +100,15 @@
 [StringBuilder]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/text/string_builder.h
 [CString]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/text/cstring.h
 [RefCounted]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/ref_counted.h
-[Allocator.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/allocator.h
-[Functional.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/functional.h
-[Threading.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/threading.h
-[ThreadingPrimitives.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/threading_primitives.h
-[Compiler.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/compiler.h
-[CPU.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/cpu.h
+[allocator.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/allocator.h
+[functional.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/functional.h
+[threading.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/threading.h
+[threading_primitives.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/threading_primitives.h
+[compiler.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/compiler.h
+[cpu.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/cpu.h
 [build_config.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/build_config.h
-[StdLibExtras.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/std_lib_extras.h
-[Time.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/time.h
+[std_lib_extras.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/std_lib_extras.h
+[time.h]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/wtf/time.h
 [1]: https://chromium.googlesource.com/chromium/src/+/e372c152fc6e57743ebc508fe17f6eb131b4ff8d
 [2]: https://chromium.googlesource.com/chromium/src/+/547a6ca360a56fbee3d5ea4a71ba18f91622455c
 [3]: https://chromium.googlesource.com/chromium/src/+/478890427ee03fd88e6f0f58ee8220512044bed9/third_party/WebKit/WebCore/kwq/KWQAssertions.h
diff --git a/third_party/blink/renderer/platform/wtf/ScopedLogger.md b/third_party/blink/renderer/platform/wtf/ScopedLogger.md
index 3593e16..868ea26 100644
--- a/third_party/blink/renderer/platform/wtf/ScopedLogger.md
+++ b/third_party/blink/renderer/platform/wtf/ScopedLogger.md
@@ -82,12 +82,11 @@
 ## Requirements
 
 The ScopedLogger class and associated macros are defined in
-[Assertions.h](Assertions.h), which most Blink source files already include
-indirectly.  ScopedLogger can't be used outside of Blink code yet.
+[scoped_logger.h](scoped_logger.h).  ScopedLogger can't be used outside of
+Blink code yet.
 
 The ScopedLogger macros work in debug builds by default.  They are compiled out
-of release builds, unless your `GYP_DEFINES` or GN args file includes one of the
-following:
+of release builds, unless your GN args file includes one of the following:
 
 * `dcheck_always_on`: enables assertions and ScopedLogger
 
diff --git a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.cc b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.cc
index 3a92200..7e58be3 100644
--- a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.cc
+++ b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.cc
@@ -82,6 +82,19 @@
   return true;
 }
 
+bool ArrayBuffer::ShareNonSharedForInternalUse(ArrayBufferContents& result) {
+  DCHECK(!IsShared());
+  scoped_refptr<ArrayBuffer> keep_alive(this);
+
+  if (!contents_.Data()) {
+    result.Neuter();
+    return false;
+  }
+
+  contents_.ShareNonSharedForInternalUse(result);
+  return true;
+}
+
 void ArrayBuffer::AddView(ArrayBufferView* view) {
   view->buffer_ = this;
   view->prev_view_ = nullptr;
diff --git a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.h b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.h
index fc67bba6..bd02fd17 100644
--- a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.h
+++ b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.h
@@ -81,6 +81,8 @@
 
   bool Transfer(ArrayBufferContents&);
   bool ShareContentsWith(ArrayBufferContents&);
+  // Documentation see DOMArrayBuffer.
+  bool ShareNonSharedForInternalUse(ArrayBufferContents&);
   bool IsNeutered() const { return is_neutered_; }
   bool IsShared() const { return contents_.IsShared(); }
 
diff --git a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.cc b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.cc
index 5a8dbf1..00312421 100644
--- a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.cc
+++ b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.cc
@@ -100,6 +100,14 @@
   other.holder_ = holder_;
 }
 
+void ArrayBufferContents::ShareNonSharedForInternalUse(
+    ArrayBufferContents& other) {
+  DCHECK(!IsShared());
+  DCHECK(!other.holder_->Data());
+  DCHECK(holder_->Data());
+  other.holder_ = holder_;
+}
+
 void ArrayBufferContents::CopyTo(ArrayBufferContents& other) {
   DCHECK(!holder_->IsShared() && !other.holder_->IsShared());
   other.holder_->CopyMemoryFrom(*holder_);
diff --git a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.h b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.h
index 3cc1abe8..98bda66 100644
--- a/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.h
+++ b/third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.h
@@ -131,6 +131,7 @@
 
   void Transfer(ArrayBufferContents& other);
   void ShareWith(ArrayBufferContents& other);
+  void ShareNonSharedForInternalUse(ArrayBufferContents& other);
   void CopyTo(ArrayBufferContents& other);
 
   static void* AllocateMemoryOrNull(size_t, InitializationPolicy);
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index c8223f7..2a9c47c 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -57205,6 +57205,30 @@
      {}
     ]
    ],
+   "css/css-pseudo/first-letter-background-image-dynamic.html": [
+    [
+     "/css/css-pseudo/first-letter-background-image-dynamic.html",
+     [
+      [
+       "/css/css-pseudo/first-letter-background-image-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-pseudo/first-letter-background-image.html": [
+    [
+     "/css/css-pseudo/first-letter-background-image.html",
+     [
+      [
+       "/css/css-pseudo/first-letter-background-image-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-pseudo/first-letter-block-to-inline.html": [
     [
      "/css/css-pseudo/first-letter-block-to-inline.html",
@@ -60025,6 +60049,42 @@
      {}
     ]
    ],
+   "css/css-tables/subpixel-collapsed-borders-001.html": [
+    [
+     "/css/css-tables/subpixel-collapsed-borders-001.html",
+     [
+      [
+       "/css/css-tables/subpixel-collapsed-borders-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-tables/subpixel-collapsed-borders-002.html": [
+    [
+     "/css/css-tables/subpixel-collapsed-borders-002.html",
+     [
+      [
+       "/css/css-tables/subpixel-collapsed-borders-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-tables/subpixel-collapsed-borders-003.html": [
+    [
+     "/css/css-tables/subpixel-collapsed-borders-003.html",
+     [
+      [
+       "/css/css-tables/subpixel-collapsed-borders-003-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-tables/subpixel-table-cell-height-001.html": [
     [
      "/css/css-tables/subpixel-table-cell-height-001.html",
@@ -106225,6 +106285,18 @@
      {}
     ]
    ],
+   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html": [
+    [
+     "/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html",
+     [
+      [
+       "/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html": [
     [
      "/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html",
@@ -107441,6 +107513,78 @@
      {}
     ]
    ],
+   "mathml/presentation-markup/fractions/frac-color-001.html": [
+    [
+     "/mathml/presentation-markup/fractions/frac-color-001.html",
+     [
+      [
+       "/mathml/presentation-markup/fractions/frac-color-001-notref.html",
+       "!="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-linethickness-001.html": [
+    [
+     "/mathml/presentation-markup/fractions/frac-linethickness-001.html",
+     [
+      [
+       "/mathml/presentation-markup/fractions/frac-linethickness-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-linethickness-002.html": [
+    [
+     "/mathml/presentation-markup/fractions/frac-linethickness-002.html",
+     [
+      [
+       "/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-linethickness-003.html": [
+    [
+     "/mathml/presentation-markup/fractions/frac-linethickness-003.html",
+     [
+      [
+       "/mathml/presentation-markup/fractions/frac-linethickness-003-notref.html",
+       "!="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-mrow-001.html": [
+    [
+     "/mathml/presentation-markup/fractions/frac-mrow-001.html",
+     [
+      [
+       "/mathml/presentation-markup/fractions/frac-mrow-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-numalign-denomalign-001.html": [
+    [
+     "/mathml/presentation-markup/fractions/frac-numalign-denomalign-001.html",
+     [
+      [
+       "/mathml/presentation-markup/fractions/frac-numalign-denomalign-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "mathml/presentation-markup/spaces/space-2.html": [
     [
      "/mathml/presentation-markup/spaces/space-2.html",
@@ -141468,6 +141612,11 @@
      {}
     ]
    ],
+   "css/css-pseudo/first-letter-background-image-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-pseudo/first-letter-block-to-inline-ref.html": [
     [
      {}
@@ -142818,6 +142967,16 @@
      {}
     ]
    ],
+   "css/css-tables/subpixel-collapsed-borders-003-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-tables/subpixel-collapsed-borders-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-tables/subpixel-table-cell-height-001-ref.html": [
     [
      {}
@@ -170648,6 +170807,11 @@
      {}
     ]
    ],
+   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html": [
     [
      {}
@@ -175748,6 +175912,36 @@
      {}
     ]
    ],
+   "mathml/presentation-markup/fractions/frac-color-001-notref.html": [
+    [
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-linethickness-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-linethickness-002-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-linethickness-003-notref.html": [
+    [
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-mrow-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "mathml/presentation-markup/fractions/frac-numalign-denomalign-001-ref.html": [
+    [
+     {}
+    ]
+   ],
    "mathml/presentation-markup/spaces/space-2-ref.html": [
     [
      {}
@@ -214188,6 +214382,12 @@
      {}
     ]
    ],
+   "css/css-scroll-anchoring/device-pixel-adjustment.html": [
+    [
+     "/css/css-scroll-anchoring/device-pixel-adjustment.html",
+     {}
+    ]
+   ],
    "css/css-scroll-anchoring/exclude-fixed-position.html": [
     [
      "/css/css-scroll-anchoring/exclude-fixed-position.html",
@@ -242456,12 +242656,6 @@
      {}
     ]
    ],
-   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html": [
-    [
-     "/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html",
-     {}
-    ]
-   ],
    "html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html": [
     [
      "/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html",
@@ -356549,6 +356743,18 @@
    "a10612ac9731a7ecdf07416df23e802c131b7570",
    "reftest"
   ],
+  "css/css-pseudo/first-letter-background-image-dynamic.html": [
+   "6f8a9378c5c2f041ae8be4bc0a4e22be8869a107",
+   "reftest"
+  ],
+  "css/css-pseudo/first-letter-background-image-ref.html": [
+   "c235ba6eef6328f1b726acfe0076020bcd0b6f33",
+   "support"
+  ],
+  "css/css-pseudo/first-letter-background-image.html": [
+   "aa9341df1bd7d97afd12f3896a645f008bdc8c50",
+   "reftest"
+  ],
   "css/css-pseudo/first-letter-block-to-inline-ref.html": [
    "5c777f1a92e373042ffbfeb5604cc665013663f7",
    "support"
@@ -357077,6 +357283,10 @@
    "654f34a051dc39ea3506a73a1e96602d0c664c61",
    "testharness"
   ],
+  "css/css-scroll-anchoring/device-pixel-adjustment.html": [
+   "4a135939fde657ab6819b167cfa72972373771b1",
+   "testharness"
+  ],
   "css/css-scroll-anchoring/exclude-fixed-position.html": [
    "d48d3f7cedf19ce502a5087d945675b62e62fdf9",
    "testharness"
@@ -357238,11 +357448,11 @@
    "testharness"
   ],
   "css/css-scroll-snap/parsing/scroll-snap-align-invalid.html": [
-   "c31aa3c17513b3bed1888c1d9becd5874ca470b6",
+   "9a1eeb77bbe481ec2ca842c86af45f6aa471e636",
    "testharness"
   ],
   "css/css-scroll-snap/parsing/scroll-snap-align-valid.html": [
-   "163f786b7d45932dbede2fd2bc0a3b75e72a2f83",
+   "0201448825e67c055f78b8be8bd08531d9068e1d",
    "testharness"
   ],
   "css/css-scroll-snap/parsing/scroll-snap-stop-invalid.html": [
@@ -359709,6 +359919,26 @@
    "a24556aa0dedfab35ff7792f03b804f44c0f4160",
    "reftest"
   ],
+  "css/css-tables/subpixel-collapsed-borders-001.html": [
+   "a71f5cc698b74a518411e57974577b4f4c4dccda",
+   "reftest"
+  ],
+  "css/css-tables/subpixel-collapsed-borders-002.html": [
+   "8f8292cd22becd3ddba61f5205828596a8995e44",
+   "reftest"
+  ],
+  "css/css-tables/subpixel-collapsed-borders-003-ref.html": [
+   "78e0be389393e235781549cd90ea961385ec5c68",
+   "support"
+  ],
+  "css/css-tables/subpixel-collapsed-borders-003.html": [
+   "358562cbd7ff49f43c234db502513faabc8390d1",
+   "reftest"
+  ],
+  "css/css-tables/subpixel-collapsed-borders-ref.html": [
+   "0a2e7a8d95b95dc21ad1c103fdfba65b8f5b1b86",
+   "support"
+  ],
   "css/css-tables/subpixel-table-cell-height-001-ref.html": [
    "3b6297fc4ab06643b2910194479b571ca3a462f2",
    "support"
@@ -394626,7 +394856,7 @@
    "support"
   ],
   "domparsing/DOMParser-parseFromString-html.html": [
-   "ec424423a1d28988d2b949b51e9467f0cf702290",
+   "ad65cc58618462591568dda622f35b68a64c1d63",
    "testharness"
   ],
   "domparsing/DOMParser-parseFromString-xml-doctype.html": [
@@ -412845,12 +413075,16 @@
    "76019c9b41e4fd4c56f4252d399f088b7b3e4470",
    "reftest"
   ],
+  "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue-ref.html": [
+   "837c4fd7833d569f2949bf1c2a677836ac851597",
+   "support"
+  ],
   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html": [
-   "427189f6fc78074ae13c58e94ae3d02bc0e513b2",
-   "testharness"
+   "c2d300999eeaa207365b5c0b42f9b033e6b96446",
+   "reftest"
   ],
   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html": [
-   "8354041eb2a0aa57b283e12d9c0390f16327ac80",
+   "c4c14bc2a394ff19c14bfa76c4b59ad5d6ddb618",
    "support"
   ],
   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html": [
@@ -412858,7 +413092,7 @@
    "reftest"
   ],
   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html": [
-   "39461350b089b9041a71a6ca9aad7f1cfc078d68",
+   "c3ee804c485b762d8b69614136558bbf2ed833a8",
    "support"
   ],
   "html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html": [
@@ -422078,11 +422312,11 @@
    "manual"
   ],
   "input-events/input-events-exec-command-expected.txt": [
-   "89da9dc6ef95591372c59492930e8d26bff84672",
+   "1e219ecd5bead581a7eca478bcefcdd72dc8c9be",
    "support"
   ],
   "input-events/input-events-exec-command.html": [
-   "9ba423f4bade15ee47a099b611306b0e941af459",
+   "8f8493651e5af4b747d952ac786334ace8ed61c4",
    "testharness"
   ],
   "input-events/input-events-get-target-ranges-manual.html": [
@@ -423269,6 +423503,54 @@
    "848eb5b144cef3740b794475f699229243c1287e",
    "testharness"
   ],
+  "mathml/presentation-markup/fractions/frac-color-001-notref.html": [
+   "498d6277a3258c70882e7b2ec92e82e974bf9ab0",
+   "support"
+  ],
+  "mathml/presentation-markup/fractions/frac-color-001.html": [
+   "bc61dbf51ffe487122859f189223b2ef43f88bb1",
+   "reftest"
+  ],
+  "mathml/presentation-markup/fractions/frac-linethickness-001-ref.html": [
+   "c703edb764fe883e05e47e26464149aa64af1070",
+   "support"
+  ],
+  "mathml/presentation-markup/fractions/frac-linethickness-001.html": [
+   "94111174c6aa3ecf1fff4982bfc9ecbf4a10c2ee",
+   "reftest"
+  ],
+  "mathml/presentation-markup/fractions/frac-linethickness-002-ref.html": [
+   "69663938c1951a34a1e1134d549b610ea00c15e8",
+   "support"
+  ],
+  "mathml/presentation-markup/fractions/frac-linethickness-002.html": [
+   "5bb0d6bfb9d6113a04b9d3160ccc9db80aceaaa6",
+   "reftest"
+  ],
+  "mathml/presentation-markup/fractions/frac-linethickness-003-notref.html": [
+   "934d66633397313175e98c4bda871bb0a95e5db0",
+   "support"
+  ],
+  "mathml/presentation-markup/fractions/frac-linethickness-003.html": [
+   "e535e703b52c75800155b5b25995730b2a236287",
+   "reftest"
+  ],
+  "mathml/presentation-markup/fractions/frac-mrow-001-ref.html": [
+   "e42cb96fe8383959bef2d778cfb29d0638f1de03",
+   "support"
+  ],
+  "mathml/presentation-markup/fractions/frac-mrow-001.html": [
+   "e5c6f52529443e881b8235a67f3e8cc7aa10fb51",
+   "reftest"
+  ],
+  "mathml/presentation-markup/fractions/frac-numalign-denomalign-001-ref.html": [
+   "2fa978624dc206c834f70d4cf620b565f8f3ac12",
+   "support"
+  ],
+  "mathml/presentation-markup/fractions/frac-numalign-denomalign-001.html": [
+   "61f49e2ff5381c9684e5b36d03a51daf7f8f3e74",
+   "reftest"
+  ],
   "mathml/presentation-markup/fractions/frac-parameters-1.html": [
    "543017e19006377cac0c1a434bd43400467fac3b",
    "testharness"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image-dynamic.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image-dynamic.html
new file mode 100644
index 0000000..6f8a937
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image-dynamic.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>CSS Test: ::first-letter correctly applies background-image dynamically</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-letter-styling">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1528451">
+<link rel="match" href="first-letter-background-image-ref.html">
+<style>
+  div::first-letter {
+    color: lime;
+  }
+  div.image::first-letter {
+    /* Lime background */
+    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==');
+  }
+</style>
+<div>
+  A letter
+</div>
+<script>
+  let div = document.querySelector("div");
+  getComputedStyle(div).color;
+  div.classList.add('image');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image-ref.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image-ref.html
new file mode 100644
index 0000000..c235ba6e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image-ref.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<style>
+  div::first-letter {
+    color: lime;
+    background-color: lime;
+  }
+</style>
+<div>
+  A letter
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image.html
new file mode 100644
index 0000000..aa9341df
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/first-letter-background-image.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>CSS Test: ::first-letter correctly applies background-image</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-letter-styling">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1528451">
+<link rel="match" href="first-letter-background-image-ref.html">
+<style>
+  div::first-letter {
+    color: lime;
+    /* Lime background */
+    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==');
+  }
+</style>
+<div>
+  A letter
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-anchoring/device-pixel-adjustment.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-anchoring/device-pixel-adjustment.html
new file mode 100644
index 0000000..4a13593
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-anchoring/device-pixel-adjustment.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body {
+    height: 200vh;
+}
+#anchor {
+    width: 100px;
+    height: 100px;
+    background-color: blue;
+}
+
+</style>
+<div id="expander"></div>
+<div id="anchor"></div>
+<script>
+
+// This tests that scroll anchor adjustments can happen by quantities smaller
+// than a device pixel.
+//
+// Unfortunately, we can't test this by simply reading 'scrollTop', because
+// 'scrollTop' may be rounded to the nearest CSS pixel. So, to test that
+// subpixel adjustments can in fact happen, we repeatedly trigger a scroll
+// adjustment in a way that would produce a different final .scrollTop value,
+// depending on whether or not we rounded each adjustment as we apply it.
+
+test(() => {
+    let scroller = document.scrollingElement;
+    let expander = document.querySelector("#expander");
+    let anchor = document.querySelector("#anchor");
+    const initialTop = 10;
+
+    // Scroll 10px to activate scroll anchoring
+    scroller.scrollTop = initialTop;
+
+    // Helper to insert a div with specified height before the anchor node
+    function addChild(height) {
+        let child = document.createElement("div");
+        child.style.height = `${height}px`;
+        anchor.before(child);
+    }
+
+    // Calculate what fraction of a CSS pixel corresponds to one device pixel
+    let devicePixel = 1.0 / window.devicePixelRatio;
+    assert_true(devicePixel <= 1.0, "there should be more device pixels than CSS pixels");
+
+    // The 0.5 is an arbitrary scale when creating the subpixel delta
+    let delta = 0.5 * devicePixel;
+
+    // To help us check for for premature rounding of adjustments, we'll
+    // trigger "count" subpixel adjustments of size "delta", where "count" is
+    // the first positive integer such that:
+    //   round(count * delta) != count * round(delta)
+    // As round(X) and count are integers, this happens when:
+    //   count * delta = count * round(delta) +/- 1
+    // Solving for count:
+    //   count = 1 / abs(delta - round(delta))
+    // Note that we don't need to worry about the denominator being zero, as:
+    //   0 < devicePixel <= 1
+    // And so halving devicePixel should never yield a whole number.
+    let count = 1 / Math.abs(delta - Math.round(delta));
+
+    for (let i = 0; i < count; i++) {
+        addChild(delta);
+        // Trigger an anchor adjustment by forcing a layout flush
+        scroller.scrollTop;
+    }
+
+    let destination = Math.round(initialTop + delta * count);
+    assert_equals(scroller.scrollTop, destination,
+        `adjusting by ${delta}px, ${count} times, should be the same as adjusting by ${delta * count}px, once.`);
+}, "Test that scroll anchor adjustments can happen by a sub device-pixel amount.");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-invalid.html
index c31aa3c1..9a1eeb77 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-invalid.html
@@ -13,6 +13,8 @@
 <script>
 test_invalid_value("scroll-snap-align", "auto");
 
+test_invalid_value("scroll-snap-align", "start invalid");
+
 test_invalid_value("scroll-snap-align", "start end center");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-valid.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-valid.html
index 163f786..0201448 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/parsing/scroll-snap-align-valid.html
@@ -18,6 +18,8 @@
 
 test_valid_value("scroll-snap-align", "start none");
 test_valid_value("scroll-snap-align", "center end");
+test_valid_value("scroll-snap-align", "start start", "start");
+
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable.html b/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable.html
index e3083d2..80189fe 100644
--- a/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable.html
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/CSSStyleSheet-constructable.html
@@ -14,7 +14,7 @@
     <span class="yellow"></span>
   </div>
 </section>
-<section id="secondSection"></section>
+<section id="shadowHost"></section>
 <section id="thirdSection"></section>
 
 <script>
@@ -27,7 +27,8 @@
 
 const firstDiv = document.querySelector('#firstSection > div');
 const secondDiv = firstDiv.cloneNode(true);
-const shadowRoot = document.querySelector('#secondSection').attachShadow({mode: 'open'});
+const shadowHost = document.querySelector('#shadowHost');
+const shadowRoot = shadowHost.attachShadow({mode: 'open'});
 shadowRoot.appendChild(secondDiv);
 
 const greenSpan = firstDiv.children[0];
@@ -210,6 +211,24 @@
 }, 'Constructed style sheets can be applied on shadow root');
 
 promise_test(() => {
+  return Promise.all(createAllSheetsPromise()).then(values => {
+    const greenStyleSheet = values[0];
+    const redStyleSheet = values[1];
+    shadowRoot.adoptedStyleSheets = [greenStyleSheet];
+    assert_equals(getComputedStyle(greenShadowSpan).color, "rgb(0, 128, 0)", "Style applies connected");
+    assert_equals(getComputedStyle(redShadowSpan).color, "rgb(0, 0, 0)", "Style applies when connected");
+    let hostParent = shadowHost.parentNode;
+    hostParent.removeChild(shadowHost);
+    assert_equals(getComputedStyle(greenShadowSpan).color, "", "Style doesn't apply when detached");
+    assert_equals(getComputedStyle(redShadowSpan).color, "", "Style doesn't apply when detached");
+    shadowRoot.adoptedStyleSheets = [redStyleSheet, greenStyleSheet];
+    hostParent.appendChild(shadowHost);
+    assert_equals(getComputedStyle(greenShadowSpan).color, "rgb(0, 128, 0)", "Style applies after reattach");
+    assert_equals(getComputedStyle(redShadowSpan).color, "rgb(255, 0, 0)", "Style applies after reattach");
+  });
+}, 'Re-attaching shadow host with adopted stylesheets work');
+
+promise_test(() => {
   const plainSheet = new CSSStyleSheet();
   const redStyleSheetPromise = plainSheet.replace(redStyleTexts[0]);
   return redStyleSheetPromise.then(function(redStyleSheet) {
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue-ref.html
new file mode 100644
index 0000000..837c4fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Reference for track rendering with empty cue</title>
+<script src="/common/reftest-wait.js"></script>
+<style>
+html { overflow:hidden }
+body { margin:0 }
+.container {
+  display: inline-block;
+  position: relative;
+}
+</style>
+<div class="container">
+  <video width="320" height="180">
+    <source src="/media/white.webm" type="video/webm">
+    <source src="/media/white.mp4" type="video/mp4">
+    <script>
+      var video = document.querySelector("video");
+      video.addEventListener('playing', () => {
+        video.pause();
+        takeScreenshot();
+      }, { once: true});
+      video.play();
+    </script>
+  </video>
+</div>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html
index 427189f..c2d3009 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html
@@ -1,16 +1,26 @@
 <!DOCTYPE html>
-<title>Empty cues</title>
-<script src="/common/media.js"></script>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-async_test(function(t) {
-    var video = document.createElement("video");
-    video.src = getVideoURI("/media/test");
-    video.addTextTrack("captions", "regular captions track", "en");
-    video.textTracks[0].addCue(new VTTCue(0, 4, ""));
+<html class="reftest-wait">
+<title>Track rendering with empty cue</title>
+<link rel="match" href="track-cue-rendering-empty-cue-ref.html">
+<style>
+html { overflow:hidden }
+body { margin:0 }
+</style>
+<script src="/common/reftest-wait.js"></script>
+<video width="320" height="180">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+  <script>
+    var video = document.querySelector("video");
+    var track = video.addTextTrack("captions", "regular captions track", "en");
+    track.addCue(new VTTCue(0, 4, ""));
+    track.mode = "showing";
 
-    video.onplaying = t.step_func_done();
+    video.addEventListener('playing', () => {
+      video.pause();
+      takeScreenshot();
+    }, { once: true});
     video.play();
-});
-</script>
\ No newline at end of file
+  </script>
+</video>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html
index 8354041e..c4c14bc2 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html
@@ -20,7 +20,6 @@
   background: green;
   color: green;
   font-size: 120px;
-  padding: 2px;
 }
 </style>
 <div class="container">
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html
index 39461350..c3ee804 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html
@@ -22,7 +22,6 @@
   background: green;
   color: green;
   font-size: 50px;
-  padding: 2px;
 }
 </style>
 <div class="container">
diff --git a/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command-expected.txt b/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command-expected.txt
index 89da9dc..1e219ec 100644
--- a/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command-expected.txt
@@ -1,49 +1,317 @@
 This is a testharness.js-based test.
-PASS Calling execCommand("insertText", false, a)
-PASS Calling execCommand("insertText", false, bc)
+Found 313 tests; 239 PASS, 74 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Calling execCommand("insertText", false, a) (calling execCommand)
+PASS Calling execCommand("insertText", false, a) (shouldn't fire beforeinput)
+PASS Calling execCommand("insertText", false, a) (inputType value)
+PASS Calling execCommand("insertText", false, a) (data value)
+PASS Calling execCommand("insertText", false, a) (dataTransfer value)
+PASS Calling execCommand("insertText", false, bc) (calling execCommand)
+PASS Calling execCommand("insertText", false, bc) (shouldn't fire beforeinput)
+PASS Calling execCommand("insertText", false, bc) (inputType value)
+PASS Calling execCommand("insertText", false, bc) (data value)
+PASS Calling execCommand("insertText", false, bc) (dataTransfer value)
 PASS execCommand("insertText") should insert "abc" into the editor
-PASS Calling execCommand("insertOrderedList", false, null)
+PASS Calling execCommand("insertOrderedList", false, null) (calling execCommand)
+PASS Calling execCommand("insertOrderedList", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("insertOrderedList", false, null) (inputType value)
+PASS Calling execCommand("insertOrderedList", false, null) (data value)
+PASS Calling execCommand("insertOrderedList", false, null) (dataTransfer value)
 PASS execCommand("insertOrderedList") should make <ol> and wrap the text with it
-PASS Calling execCommand("insertUnorderedList", false, null)
+PASS Calling execCommand("insertUnorderedList", false, null) (calling execCommand)
+PASS Calling execCommand("insertUnorderedList", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("insertUnorderedList", false, null) (inputType value)
+PASS Calling execCommand("insertUnorderedList", false, null) (data value)
+PASS Calling execCommand("insertUnorderedList", false, null) (dataTransfer value)
 PASS execCommand("insertUnorderedList") should make <ul> and wrap the text with it
-PASS Calling execCommand("insertLineBreak", false, null)
-PASS Calling execCommand("insertParagraph", false, null)
-FAIL Calling execCommand("insertHorizontalRule", false, null) assert_equals: Calling execCommand("insertHorizontalRule", false, null) should produce inputType: insertHorizontalRule expected "insertHorizontalRule" but got ""
-PASS Calling execCommand("bold", false, null)
+PASS Calling execCommand("insertLineBreak", false, null) (calling execCommand)
+PASS Calling execCommand("insertLineBreak", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("insertLineBreak", false, null) (inputType value)
+PASS Calling execCommand("insertLineBreak", false, null) (data value)
+PASS Calling execCommand("insertLineBreak", false, null) (dataTransfer value)
+PASS Calling execCommand("insertParagraph", false, null) (calling execCommand)
+PASS Calling execCommand("insertParagraph", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("insertParagraph", false, null) (inputType value)
+PASS Calling execCommand("insertParagraph", false, null) (data value)
+PASS Calling execCommand("insertParagraph", false, null) (dataTransfer value)
+PASS Calling execCommand("insertHorizontalRule", false, null) (calling execCommand)
+PASS Calling execCommand("insertHorizontalRule", false, null) (shouldn't fire beforeinput)
+FAIL Calling execCommand("insertHorizontalRule", false, null) (inputType value) assert_equals: expected "insertHorizontalRule" but got ""
+PASS Calling execCommand("insertHorizontalRule", false, null) (data value)
+PASS Calling execCommand("insertHorizontalRule", false, null) (dataTransfer value)
+PASS Calling execCommand("bold", false, null) (calling execCommand)
+PASS Calling execCommand("bold", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("bold", false, null) (inputType value)
+PASS Calling execCommand("bold", false, null) (data value)
+PASS Calling execCommand("bold", false, null) (dataTransfer value)
 PASS execCommand("bold") should wrap selected text with <b> element
-PASS Calling execCommand("italic", false, null)
+PASS Calling execCommand("italic", false, null) (calling execCommand)
+PASS Calling execCommand("italic", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("italic", false, null) (inputType value)
+PASS Calling execCommand("italic", false, null) (data value)
+PASS Calling execCommand("italic", false, null) (dataTransfer value)
 PASS execCommand("italic") should wrap selected text with <i> element
-PASS Calling execCommand("underline", false, null)
+PASS Calling execCommand("underline", false, null) (calling execCommand)
+PASS Calling execCommand("underline", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("underline", false, null) (inputType value)
+PASS Calling execCommand("underline", false, null) (data value)
+PASS Calling execCommand("underline", false, null) (dataTransfer value)
 PASS execCommand("underline") should wrap selected text with <u> element
-PASS Calling execCommand("strikeThrough", false, null)
+PASS Calling execCommand("strikeThrough", false, null) (calling execCommand)
+PASS Calling execCommand("strikeThrough", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("strikeThrough", false, null) (inputType value)
+PASS Calling execCommand("strikeThrough", false, null) (data value)
+PASS Calling execCommand("strikeThrough", false, null) (dataTransfer value)
 PASS execCommand("strikeThrough") should wrap selected text with <strike> element
-PASS Calling execCommand("superscript", false, null)
+PASS Calling execCommand("superscript", false, null) (calling execCommand)
+PASS Calling execCommand("superscript", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("superscript", false, null) (inputType value)
+PASS Calling execCommand("superscript", false, null) (data value)
+PASS Calling execCommand("superscript", false, null) (dataTransfer value)
 PASS execCommand("superscript") should wrap selected text with <sup> element
-PASS Calling execCommand("subscript", false, null)
+PASS Calling execCommand("subscript", false, null) (calling execCommand)
+PASS Calling execCommand("subscript", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("subscript", false, null) (inputType value)
+PASS Calling execCommand("subscript", false, null) (data value)
+PASS Calling execCommand("subscript", false, null) (dataTransfer value)
 PASS execCommand("subscript") should wrap selected text with <sub> element
-FAIL Calling execCommand("backColor", false, #000000) assert_equals: Calling execCommand("backColor", false, #000000) should produce inputType: formatBackColor expected "formatBackColor" but got ""
-FAIL Calling execCommand("foreColor", false, #FFFFFF) assert_equals: Calling execCommand("foreColor", false, #FFFFFF) should produce inputType: formatFontColor expected "formatFontColor" but got ""
-FAIL Calling execCommand("hiliteColor", false, #FFFF00) assert_equals: Calling execCommand("hiliteColor", false, #FFFF00) should produce inputType: formatBackColor expected "formatBackColor" but got ""
-FAIL Calling execCommand("fontName", false, monospace) assert_equals: Calling execCommand("fontName", false, monospace) should produce inputType: formatFontName expected "formatFontName" but got ""
-PASS Calling execCommand("justifyCenter", false, null)
+PASS Calling execCommand("backColor", false, #FF0000) (calling execCommand)
+PASS Calling execCommand("backColor", false, #FF0000) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, #FF0000) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, #FF0000) (data value) assert_equals: expected (string) "rgb(255, 0, 0)" but got (object) null
+PASS Calling execCommand("backColor", false, #FF0000) (dataTransfer value)
+PASS Calling execCommand("backColor", false, #00FF00FF) (calling execCommand)
+PASS Calling execCommand("backColor", false, #00FF00FF) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, #00FF00FF) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, #00FF00FF) (data value) assert_equals: expected (string) "rgb(0, 255, 0)" but got (object) null
+PASS Calling execCommand("backColor", false, #00FF00FF) (dataTransfer value)
+PASS Calling execCommand("backColor", false, #0000FF88) (calling execCommand)
+PASS Calling execCommand("backColor", false, #0000FF88) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, #0000FF88) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, #0000FF88) (data value) assert_equals: expected (string) "rgba(0, 0, 255, 0.533)" but got (object) null
+PASS Calling execCommand("backColor", false, #0000FF88) (dataTransfer value)
+PASS Calling execCommand("backColor", false, orange) (calling execCommand)
+PASS Calling execCommand("backColor", false, orange) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, orange) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, orange) (data value) assert_equals: expected (string) "rgb(255, 165, 0)" but got (object) null
+PASS Calling execCommand("backColor", false, orange) (dataTransfer value)
+PASS Calling execCommand("backColor", false, Inherit) (calling execCommand)
+PASS Calling execCommand("backColor", false, Inherit) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, Inherit) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, Inherit) (data value) assert_equals: expected (string) "inherit" but got (object) null
+PASS Calling execCommand("backColor", false, Inherit) (dataTransfer value)
+PASS Calling execCommand("backColor", false, Initial) (calling execCommand)
+PASS Calling execCommand("backColor", false, Initial) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, Initial) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, Initial) (data value) assert_equals: expected (string) "initial" but got (object) null
+PASS Calling execCommand("backColor", false, Initial) (dataTransfer value)
+PASS Calling execCommand("backColor", false, Reset) (calling execCommand)
+PASS Calling execCommand("backColor", false, Reset) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, Reset) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, Reset) (data value) assert_equals: expected (string) "reset" but got (object) null
+PASS Calling execCommand("backColor", false, Reset) (dataTransfer value)
+PASS Calling execCommand("backColor", false, transparent) (calling execCommand)
+PASS Calling execCommand("backColor", false, transparent) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, transparent) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, transparent) (data value) assert_equals: expected (string) "rgba(0, 0, 0, 0)" but got (object) null
+PASS Calling execCommand("backColor", false, transparent) (dataTransfer value)
+PASS Calling execCommand("backColor", false, CurrentColor) (calling execCommand)
+PASS Calling execCommand("backColor", false, CurrentColor) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, CurrentColor) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, CurrentColor) (data value) assert_equals: expected (string) "currentcolor" but got (object) null
+PASS Calling execCommand("backColor", false, CurrentColor) (dataTransfer value)
+PASS Calling execCommand("backColor", false, Invalid-Value) (calling execCommand)
+PASS Calling execCommand("backColor", false, Invalid-Value) (shouldn't fire beforeinput)
+FAIL Calling execCommand("backColor", false, Invalid-Value) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("backColor", false, Invalid-Value) (data value) assert_equals: expected (string) "Invalid-Value" but got (object) null
+PASS Calling execCommand("backColor", false, Invalid-Value) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, #FF0000) (calling execCommand)
+PASS Calling execCommand("foreColor", false, #FF0000) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, #FF0000) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, #FF0000) (data value) assert_equals: expected (string) "rgb(255, 0, 0)" but got (object) null
+PASS Calling execCommand("foreColor", false, #FF0000) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, #00FF00FF) (calling execCommand)
+PASS Calling execCommand("foreColor", false, #00FF00FF) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, #00FF00FF) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, #00FF00FF) (data value) assert_equals: expected (string) "rgb(0, 255, 0)" but got (object) null
+PASS Calling execCommand("foreColor", false, #00FF00FF) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, #0000FF88) (calling execCommand)
+PASS Calling execCommand("foreColor", false, #0000FF88) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, #0000FF88) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, #0000FF88) (data value) assert_equals: expected (string) "rgba(0, 0, 255, 0.533)" but got (object) null
+PASS Calling execCommand("foreColor", false, #0000FF88) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, orange) (calling execCommand)
+PASS Calling execCommand("foreColor", false, orange) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, orange) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, orange) (data value) assert_equals: expected (string) "rgb(255, 165, 0)" but got (object) null
+PASS Calling execCommand("foreColor", false, orange) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, Inherit) (calling execCommand)
+PASS Calling execCommand("foreColor", false, Inherit) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, Inherit) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, Inherit) (data value) assert_equals: expected (string) "inherit" but got (object) null
+PASS Calling execCommand("foreColor", false, Inherit) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, Initial) (calling execCommand)
+PASS Calling execCommand("foreColor", false, Initial) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, Initial) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, Initial) (data value) assert_equals: expected (string) "initial" but got (object) null
+PASS Calling execCommand("foreColor", false, Initial) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, Reset) (calling execCommand)
+PASS Calling execCommand("foreColor", false, Reset) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, Reset) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, Reset) (data value) assert_equals: expected (string) "reset" but got (object) null
+PASS Calling execCommand("foreColor", false, Reset) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, transparent) (calling execCommand)
+PASS Calling execCommand("foreColor", false, transparent) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, transparent) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, transparent) (data value) assert_equals: expected (string) "rgba(0, 0, 0, 0)" but got (object) null
+PASS Calling execCommand("foreColor", false, transparent) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, CurrentColor) (calling execCommand)
+PASS Calling execCommand("foreColor", false, CurrentColor) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, CurrentColor) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, CurrentColor) (data value) assert_equals: expected (string) "currentcolor" but got (object) null
+PASS Calling execCommand("foreColor", false, CurrentColor) (dataTransfer value)
+PASS Calling execCommand("foreColor", false, Invalid-Value) (calling execCommand)
+PASS Calling execCommand("foreColor", false, Invalid-Value) (shouldn't fire beforeinput)
+FAIL Calling execCommand("foreColor", false, Invalid-Value) (inputType value) assert_equals: expected "formatFontColor" but got ""
+FAIL Calling execCommand("foreColor", false, Invalid-Value) (data value) assert_equals: expected (string) "Invalid-Value" but got (object) null
+PASS Calling execCommand("foreColor", false, Invalid-Value) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, #FF0000) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, #FF0000) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, #FF0000) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, #FF0000) (data value) assert_equals: expected (string) "rgb(255, 0, 0)" but got (object) null
+PASS Calling execCommand("hiliteColor", false, #FF0000) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, #00FF00FF) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, #00FF00FF) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, #00FF00FF) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, #00FF00FF) (data value) assert_equals: expected (string) "rgb(0, 255, 0)" but got (object) null
+PASS Calling execCommand("hiliteColor", false, #00FF00FF) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, #0000FF88) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, #0000FF88) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, #0000FF88) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, #0000FF88) (data value) assert_equals: expected (string) "rgba(0, 0, 255, 0.533)" but got (object) null
+PASS Calling execCommand("hiliteColor", false, #0000FF88) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, orange) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, orange) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, orange) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, orange) (data value) assert_equals: expected (string) "rgb(255, 165, 0)" but got (object) null
+PASS Calling execCommand("hiliteColor", false, orange) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, Inherit) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, Inherit) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, Inherit) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, Inherit) (data value) assert_equals: expected (string) "inherit" but got (object) null
+PASS Calling execCommand("hiliteColor", false, Inherit) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, Initial) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, Initial) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, Initial) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, Initial) (data value) assert_equals: expected (string) "initial" but got (object) null
+PASS Calling execCommand("hiliteColor", false, Initial) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, Reset) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, Reset) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, Reset) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, Reset) (data value) assert_equals: expected (string) "reset" but got (object) null
+PASS Calling execCommand("hiliteColor", false, Reset) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, transparent) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, transparent) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, transparent) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, transparent) (data value) assert_equals: expected (string) "rgba(0, 0, 0, 0)" but got (object) null
+PASS Calling execCommand("hiliteColor", false, transparent) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, CurrentColor) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, CurrentColor) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, CurrentColor) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, CurrentColor) (data value) assert_equals: expected (string) "currentcolor" but got (object) null
+PASS Calling execCommand("hiliteColor", false, CurrentColor) (dataTransfer value)
+PASS Calling execCommand("hiliteColor", false, Invalid-Value) (calling execCommand)
+PASS Calling execCommand("hiliteColor", false, Invalid-Value) (shouldn't fire beforeinput)
+FAIL Calling execCommand("hiliteColor", false, Invalid-Value) (inputType value) assert_equals: expected "formatBackColor" but got ""
+FAIL Calling execCommand("hiliteColor", false, Invalid-Value) (data value) assert_equals: expected (string) "Invalid-Value" but got (object) null
+PASS Calling execCommand("hiliteColor", false, Invalid-Value) (dataTransfer value)
+PASS Calling execCommand("fontName", false, monospace) (calling execCommand)
+PASS Calling execCommand("fontName", false, monospace) (shouldn't fire beforeinput)
+FAIL Calling execCommand("fontName", false, monospace) (inputType value) assert_equals: expected "formatFontName" but got ""
+FAIL Calling execCommand("fontName", false, monospace) (data value) assert_equals: expected (string) "monospace" but got (object) null
+PASS Calling execCommand("fontName", false, monospace) (dataTransfer value)
+PASS Calling execCommand("fontName", false,  monospace ) (calling execCommand)
+PASS Calling execCommand("fontName", false,  monospace ) (shouldn't fire beforeinput)
+FAIL Calling execCommand("fontName", false,  monospace ) (inputType value) assert_equals: expected "formatFontName" but got ""
+FAIL Calling execCommand("fontName", false,  monospace ) (data value) assert_equals: expected (string) " monospace " but got (object) null
+PASS Calling execCommand("fontName", false,  monospace ) (dataTransfer value)
+PASS Calling execCommand("fontName", false,   monospace  ) (calling execCommand)
+PASS Calling execCommand("fontName", false,   monospace  ) (shouldn't fire beforeinput)
+FAIL Calling execCommand("fontName", false,   monospace  ) (inputType value) assert_equals: expected "formatFontName" but got ""
+FAIL Calling execCommand("fontName", false,   monospace  ) (data value) assert_equals: expected (string) "  monospace  " but got (object) null
+PASS Calling execCommand("fontName", false,   monospace  ) (dataTransfer value)
+PASS Calling execCommand("justifyCenter", false, null) (calling execCommand)
+PASS Calling execCommand("justifyCenter", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("justifyCenter", false, null) (inputType value)
+PASS Calling execCommand("justifyCenter", false, null) (data value)
+PASS Calling execCommand("justifyCenter", false, null) (dataTransfer value)
 PASS execCommand("justifyCenter") should wrap the text with <div> element whose text-align is center
-PASS Calling execCommand("justifyFull", false, null)
+PASS Calling execCommand("justifyFull", false, null) (calling execCommand)
+PASS Calling execCommand("justifyFull", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("justifyFull", false, null) (inputType value)
+PASS Calling execCommand("justifyFull", false, null) (data value)
+PASS Calling execCommand("justifyFull", false, null) (dataTransfer value)
 PASS execCommand("justifyFull") should wrap the text with <div> element whose text-align is justify
-PASS Calling execCommand("justifyRight", false, null)
+PASS Calling execCommand("justifyRight", false, null) (calling execCommand)
+PASS Calling execCommand("justifyRight", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("justifyRight", false, null) (inputType value)
+PASS Calling execCommand("justifyRight", false, null) (data value)
+PASS Calling execCommand("justifyRight", false, null) (dataTransfer value)
 PASS execCommand("justifyRight") should wrap the text with <div> element whose text-align is right
-PASS Calling execCommand("justifyLeft", false, null)
+PASS Calling execCommand("justifyLeft", false, null) (calling execCommand)
+PASS Calling execCommand("justifyLeft", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("justifyLeft", false, null) (inputType value)
+PASS Calling execCommand("justifyLeft", false, null) (data value)
+PASS Calling execCommand("justifyLeft", false, null) (dataTransfer value)
 PASS execCommand("justifyLeft") should wrap the text with <div> element whose text-align is left
-PASS Calling execCommand("removeFormat", false, null)
+PASS Calling execCommand("removeFormat", false, null) (calling execCommand)
+PASS Calling execCommand("removeFormat", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("removeFormat", false, null) (inputType value)
+PASS Calling execCommand("removeFormat", false, null) (data value)
+PASS Calling execCommand("removeFormat", false, null) (dataTransfer value)
 PASS execCommand("removeFormat") should remove the style of current block
-PASS Calling execCommand("indent", false, null)
-PASS Calling execCommand("outdent", false, null)
+PASS Calling execCommand("indent", false, null) (calling execCommand)
+PASS Calling execCommand("indent", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("indent", false, null) (inputType value)
+PASS Calling execCommand("indent", false, null) (data value)
+PASS Calling execCommand("indent", false, null) (dataTransfer value)
+PASS Calling execCommand("outdent", false, null) (calling execCommand)
+PASS Calling execCommand("outdent", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("outdent", false, null) (inputType value)
+PASS Calling execCommand("outdent", false, null) (data value)
+PASS Calling execCommand("outdent", false, null) (dataTransfer value)
 PASS Set of execCommand("indent") and execCommand("outdent") should keep same DOM tree
-PASS Calling execCommand("copy", false, null)
-PASS Calling execCommand("cut", false, null)
-PASS Calling execCommand("paste", false, null)
-FAIL Calling execCommand("createLink", false, https://example.com/) assert_equals: Calling execCommand("createLink", false, https://example.com/) should produce inputType: insertLink expected "insertLink" but got ""
-PASS execCommand("createLink") should create a link
-PASS Calling execCommand("unlink", false, null)
+PASS Calling execCommand("copy", false, null) (calling execCommand)
+PASS Calling execCommand("copy", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("copy", false, null) (inputType value)
+PASS Calling execCommand("cut", false, null) (calling execCommand)
+PASS Calling execCommand("cut", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("cut", false, null) (inputType value)
+PASS Calling execCommand("cut", false, null) (data value)
+PASS Calling execCommand("cut", false, null) (dataTransfer value)
+PASS Calling execCommand("paste", false, null) (calling execCommand)
+PASS Calling execCommand("paste", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("paste", false, null) (inputType value)
+PASS Calling execCommand("paste", false, null) (data value)
+FAIL Calling execCommand("paste", false, null) (dataTransfer value, text/plain) assert_true: calling dataTransfer.getData(text/plain) caused exception: TypeError: Cannot read property 'getData' of null expected true got false
+FAIL Calling execCommand("paste", false, null) (dataTransfer.clearData(text/plain)) assert_true: calling dataTransfer.clearData(text/plain) caused exception: TypeError: Cannot read property 'clearData' of null expected true got false
+FAIL Calling execCommand("paste", false, null) (dataTransfer.setData(text/plain)) assert_true: calling dataTransfer.setData(text/plain) caused exception: TypeError: Cannot read property 'setData' of null expected true got false
+PASS Calling execCommand("createLink", false, https://example.com/) (calling execCommand)
+PASS Calling execCommand("createLink", false, https://example.com/) (shouldn't fire beforeinput)
+FAIL Calling execCommand("createLink", false, https://example.com/) (inputType value) assert_equals: expected "insertLink" but got ""
+FAIL Calling execCommand("createLink", false, https://example.com/) (data value) assert_equals: expected (string) "https://example.com/" but got (object) null
+PASS Calling execCommand("createLink", false, https://example.com/) (dataTransfer value)
+PASS execCommand("createLink") should create a link with absolute URL
+PASS Calling execCommand("unlink", false, null) (calling execCommand)
+PASS Calling execCommand("unlink", false, null) (shouldn't fire beforeinput)
+PASS Calling execCommand("unlink", false, null) (inputType value)
+PASS Calling execCommand("unlink", false, null) (data value)
+PASS Calling execCommand("unlink", false, null) (dataTransfer value)
 PASS execCommand("createLink") should remove the link
+PASS Calling execCommand("createLink", false, foo.html) (calling execCommand)
+PASS Calling execCommand("createLink", false, foo.html) (shouldn't fire beforeinput)
+FAIL Calling execCommand("createLink", false, foo.html) (inputType value) assert_equals: expected "insertLink" but got ""
+FAIL Calling execCommand("createLink", false, foo.html) (data value) assert_equals: expected (string) "foo.html" but got (object) null
+PASS Calling execCommand("createLink", false, foo.html) (dataTransfer value)
+PASS execCommand("createLink") should create a link with relative URL
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command.html b/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command.html
index 9ba423f..8f84936 100644
--- a/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command.html
+++ b/third_party/blink/web_tests/external/wpt/input-events/input-events-exec-command.html
@@ -7,141 +7,224 @@
 <script>
 (function() {
     let lastBeforeInputType = '';
+    let lastBeforeInputData = '';
+    let lastBeforeInputDataTransfer = undefined;
     let lastInputType = '';
+    let lastInputData = '';
+    let lastInputDataTransfer = undefined;
     const txt = document.getElementById('txt');
     txt.addEventListener('beforeinput', function(event) {
         assert_true(event instanceof InputEvent);
         assert_false(event.isComposing);
         lastBeforeInputType = event.inputType;
+        lastBeforeInputData = event.data;
+        lastBeforeInputDataTransfer = event.dataTransfer;
     });
     txt.addEventListener('input', function(event) {
         assert_true(event instanceof InputEvent);
         assert_false(event.isComposing);
         lastInputType = event.inputType;
+        lastInputData = event.data;
+        lastInputDataTransfer = event.dataTransfer;
     });
 
     const NO_INPUT_EVENT_FIRED = 'NO_INPUT_EVENT_FIRED';
-    function testExecCommandInputType(command, args, inputType) {
+    function testExecCommandInputType(command, args, inputType, data, dataTransfer) {
         const description = `Calling execCommand("${command}", false, ${args})`;
+        lastBeforeInputType = NO_INPUT_EVENT_FIRED;
+        lastBeforeInputData = NO_INPUT_EVENT_FIRED;
+        lastBeforeInputDataTransfer = NO_INPUT_EVENT_FIRED;
+        lastInputType = NO_INPUT_EVENT_FIRED;
+        lastInputData = NO_INPUT_EVENT_FIRED;
+        lastInputDataTransfer = NO_INPUT_EVENT_FIRED;
         test(function() {
-            lastBeforeInputType = NO_INPUT_EVENT_FIRED;
-            lastInputType = NO_INPUT_EVENT_FIRED;
             try {
                 document.execCommand(command, false, args);
             } catch (e) {
-                assert(false, `execCommand shouldn't cause any exception: ${e}`);
+                assert_true(false, `execCommand shouldn't cause any exception: ${e}`);
             }
-            assert_equals(lastBeforeInputType, NO_INPUT_EVENT_FIRED,
-                          `${description} shouldn't fire beforeinput`);
-            assert_equals(lastInputType, inputType,
-                          `${description} should produce inputType: ${inputType}`);
-        }, description);
+        }, description + " (calling execCommand)");
+        test(function() {
+            assert_equals(lastBeforeInputType, NO_INPUT_EVENT_FIRED);
+            assert_equals(lastBeforeInputData, NO_INPUT_EVENT_FIRED);
+            assert_equals(lastBeforeInputDataTransfer, NO_INPUT_EVENT_FIRED);
+        }, description + " (shouldn't fire beforeinput)");
+        test(function() {
+            assert_equals(lastInputType, inputType);
+        }, description + " (inputType value)");
+        if (lastInputType === NO_INPUT_EVENT_FIRED) {
+            return;
+        }
+        test(function() {
+            assert_equals(lastInputData, data);
+        }, description + " (data value)");
+        if (dataTransfer === null) {
+            test(function() {
+                assert_equals(lastInputDataTransfer, dataTransfer,
+                              `${description} should produce dataTransfer: null`);
+            }, description + " (dataTransfer value)");
+        } else {
+            for (let item of dataTransfer) {
+                test(function() {
+                    try {
+                        assert_equals(lastInputDataTransfer.getData(item.type), item.data,
+                                      `${description} should produce dataTransfer.getData(${item.type}): ${item.data}`);
+                    } catch (e) {
+                        assert_true(false, `calling dataTransfer.getData(${item.type}) caused exception: ${e}`);
+                    }
+                }, `${description} (dataTransfer value, ${item.type})`);
+                test(function() {
+                    try {
+                        lastInputDataTransfer.clearData(item.type);
+                    } catch (e) {
+                        assert_true(false, `calling dataTransfer.clearData(${item.type}) caused exception: ${e}`);
+                    }
+                    assert_equals(lastInputDataTransfer.getData(item.type), item.data,
+                                  `${description} dataTransfer.clearData(${item.type}) should do nothing`);
+                }, `${description} (dataTransfer.clearData(${item.type}))`);
+                test(function() {
+                    try {
+                        lastInputDataTransfer.setData(item.type, "foo");
+                    } catch (e) {
+                        assert_true(false, `calling dataTransfer.setData(${item.type}) caused exception: ${e}`);
+                    }
+                    assert_equals(lastInputDataTransfer.getData(item.type), item.data,
+                                  `${description} dataTransfer.setData(${item.type}, "foo") should do nothing`);
+                }, `${description} (dataTransfer.setData(${item.type}))`);
+            }
+        }
     }
 
     txt.focus();
     // InsertText
-    testExecCommandInputType('insertText', 'a', 'insertText');
-    testExecCommandInputType('insertText', 'bc', 'insertText');
+    testExecCommandInputType('insertText', 'a', 'insertText', 'a', null);
+    testExecCommandInputType('insertText', 'bc', 'insertText', 'bc', null);
     test(function() {
         assert_equals(txt.innerHTML, 'abc');
     }, "execCommand(\"insertText\") should insert \"abc\" into the editor");
-    testExecCommandInputType('insertOrderedList', null, 'insertOrderedList');
+    testExecCommandInputType('insertOrderedList', null, 'insertOrderedList', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<ol><li>abc</li></ol>');
     }, "execCommand(\"insertOrderedList\") should make <ol> and wrap the text with it");
-    testExecCommandInputType('insertUnorderedList', null, 'insertUnorderedList');
+    testExecCommandInputType('insertUnorderedList', null, 'insertUnorderedList', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<ul><li>abc</li></ul>');
     }, "execCommand(\"insertUnorderedList\") should make <ul> and wrap the text with it");
-    testExecCommandInputType('insertLineBreak', null, 'insertLineBreak');
-    testExecCommandInputType('insertParagraph', null, 'insertParagraph');
+    testExecCommandInputType('insertLineBreak', null, 'insertLineBreak', null, null);
+    testExecCommandInputType('insertParagraph', null, 'insertParagraph', null, null);
     txt.innerHTML = '';
-    testExecCommandInputType('insertHorizontalRule', null, 'insertHorizontalRule');
+    testExecCommandInputType('insertHorizontalRule', null, 'insertHorizontalRule', null, null);
 
     // Styling
     txt.innerHTML = 'abc';
     var selection = window.getSelection();
     selection.collapse(txt, 0);
     selection.extend(txt, 1);
-    testExecCommandInputType('bold', null, 'formatBold');
+    testExecCommandInputType('bold', null, 'formatBold', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<b>abc</b>');
     }, "execCommand(\"bold\") should wrap selected text with <b> element");
-    testExecCommandInputType('italic', null, 'formatItalic');
+    testExecCommandInputType('italic', null, 'formatItalic', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<b><i>abc</i></b>');
     }, "execCommand(\"italic\") should wrap selected text with <i> element");
-    testExecCommandInputType('underline', null, 'formatUnderline');
+    testExecCommandInputType('underline', null, 'formatUnderline', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<b><i><u>abc</u></i></b>');
     }, "execCommand(\"underline\") should wrap selected text with <u> element");
-    testExecCommandInputType('strikeThrough', null, 'formatStrikeThrough');
+    testExecCommandInputType('strikeThrough', null, 'formatStrikeThrough', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<b><i><u><strike>abc</strike></u></i></b>');
     }, "execCommand(\"strikeThrough\") should wrap selected text with <strike> element");
-    testExecCommandInputType('superscript', null, 'formatSuperscript');
+    testExecCommandInputType('superscript', null, 'formatSuperscript', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<b><i><u><strike><sup>abc</sup></strike></u></i></b>');
     }, "execCommand(\"superscript\") should wrap selected text with <sup> element");
-    testExecCommandInputType('subscript', null, 'formatSubscript');
+    testExecCommandInputType('subscript', null, 'formatSubscript', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<b><i><u><strike><sub>abc</sub></strike></u></i></b>');
     }, "execCommand(\"subscript\") should wrap selected text with <sub> element");
     txt.innerHTML = 'abc';
     selection.collapse(txt, 0);
     selection.extend(txt, 1);
-    testExecCommandInputType('backColor', '#000000', 'formatBackColor');
-    testExecCommandInputType('foreColor', '#FFFFFF', 'formatFontColor');
-    testExecCommandInputType('hiliteColor', '#FFFF00', 'formatBackColor');
-    testExecCommandInputType('fontName', 'monospace', 'formatFontName');
+    for (let test of [{command: 'backColor', inputType: 'formatBackColor'},
+                      {command: 'foreColor', inputType: 'formatFontColor'},
+                      {command: 'hiliteColor', inputType: 'formatBackColor'}]) {
+      testExecCommandInputType(test.command, '#FF0000', test.inputType, 'rgb(255, 0, 0)', null);
+      testExecCommandInputType(test.command, '#00FF00FF', test.inputType, 'rgb(0, 255, 0)', null);
+      testExecCommandInputType(test.command, '#0000FF88', test.inputType, 'rgba(0, 0, 255, 0.533)', null);
+      testExecCommandInputType(test.command, 'orange', test.inputType, 'rgb(255, 165, 0)', null);
+      testExecCommandInputType(test.command, 'Inherit', test.inputType, 'inherit', null);
+      testExecCommandInputType(test.command, 'Initial', test.inputType, 'initial', null);
+      testExecCommandInputType(test.command, 'Reset', test.inputType, 'reset', null);
+      testExecCommandInputType(test.command, 'transparent', test.inputType, 'rgba(0, 0, 0, 0)', null);
+      testExecCommandInputType(test.command, 'CurrentColor', test.inputType, 'currentcolor', null);
+      testExecCommandInputType(test.command, 'Invalid-Value', test.inputType, 'Invalid-Value', null);
+    }
+
+    testExecCommandInputType('fontName', 'monospace', 'formatFontName', 'monospace', null);
+    testExecCommandInputType('fontName', ' monospace ', 'formatFontName', ' monospace ', null);
+    testExecCommandInputType('fontName', '  monospace  ', 'formatFontName', '  monospace  ', null);
 
     // Formating
     txt.innerHTML = 'abc';
-    testExecCommandInputType('justifyCenter', null, 'formatJustifyCenter');
+    testExecCommandInputType('justifyCenter', null, 'formatJustifyCenter', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<div style="text-align: center;">abc</div>');
     }, "execCommand(\"justifyCenter\") should wrap the text with <div> element whose text-align is center");
-    testExecCommandInputType('justifyFull', null, 'formatJustifyFull');
+    testExecCommandInputType('justifyFull', null, 'formatJustifyFull', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<div style="text-align: justify;">abc</div>');
     }, "execCommand(\"justifyFull\") should wrap the text with <div> element whose text-align is justify");
-    testExecCommandInputType('justifyRight', null, 'formatJustifyRight');
+    testExecCommandInputType('justifyRight', null, 'formatJustifyRight', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<div style="text-align: right;">abc</div>');
     }, "execCommand(\"justifyRight\") should wrap the text with <div> element whose text-align is right");
-    testExecCommandInputType('justifyLeft', null, 'formatJustifyLeft');
+    testExecCommandInputType('justifyLeft', null, 'formatJustifyLeft', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<div style="text-align: left;">abc</div>');
     }, "execCommand(\"justifyLeft\") should wrap the text with <div> element whose text-align is left");
     selection.collapse(txt, 0);
     selection.extend(txt, 1);
-    testExecCommandInputType('removeFormat', null, 'formatRemove');
+    testExecCommandInputType('removeFormat', null, 'formatRemove', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<div style="">abc</div>');
     }, "execCommand(\"removeFormat\") should remove the style of current block");
-    testExecCommandInputType('indent', null, 'formatIndent');
-    testExecCommandInputType('outdent', null, 'formatOutdent');
+    testExecCommandInputType('indent', null, 'formatIndent', null, null);
+    testExecCommandInputType('outdent', null, 'formatOutdent', null, null);
     test(function() {
         assert_equals(txt.innerHTML, '<div style="">abc</div>');
     }, "Set of execCommand(\"indent\") and execCommand(\"outdent\") should keep same DOM tree");
 
     // Copy shouldn't fire 'input'.
-    testExecCommandInputType('copy', null, NO_INPUT_EVENT_FIRED);
+    txt.innerHTML = 'ab<b>c</b>def';
+    selection.collapse(txt.firstChild, 1);
+    selection.extend(txt.firstChild.nextSibling.nextSibling, 1);
+    testExecCommandInputType('copy', null, NO_INPUT_EVENT_FIRED, NO_INPUT_EVENT_FIRED, NO_INPUT_EVENT_FIRED);
     // Cut/Paste should fire 'input'.
-    testExecCommandInputType('cut', null, 'deleteByCut');
-    testExecCommandInputType('paste', null, 'insertFromPaste');
+    testExecCommandInputType('cut', null, 'deleteByCut', null, null);
+    // XXX How can we test 'text/html' case? The detail of copied HTML fragment depends on browser.
+    testExecCommandInputType('paste', null, 'insertFromPaste', null,  [{type: 'text/plain', data: 'bcd'}]);
 
     // Link and Unlink
     txt.innerHTML = 'abc';
     selection.collapse(txt.firstChild, 1);
     selection.extend(txt.firstChild, 2);
-    testExecCommandInputType('createLink', 'https://example.com/', 'insertLink');
+    testExecCommandInputType('createLink', 'https://example.com/', 'insertLink', 'https://example.com/', null);
     test(function() {
         assert_equals(txt.innerHTML, 'a<a href="https://example.com/">b</a>c');
-    }, "execCommand(\"createLink\") should create a link");
-    testExecCommandInputType('unlink', null, '');
+    }, "execCommand(\"createLink\") should create a link with absolute URL");
+    testExecCommandInputType('unlink', null, '', null, null);
     test(function() {
         assert_equals(txt.innerHTML, 'abc');
     }, "execCommand(\"createLink\") should remove the link");
+
+    txt.innerHTML = 'abc';
+    selection.collapse(txt.firstChild, 1);
+    selection.extend(txt.firstChild, 2);
+    testExecCommandInputType('createLink', 'foo.html', 'insertLink', 'foo.html', null);
+    test(function() {
+        assert_equals(txt.innerHTML, 'a<a href="foo.html">b</a>c');
+    }, "execCommand(\"createLink\") should create a link with relative URL");
 })();
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-color-001-notref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-color-001-notref.html
new file mode 100644
index 0000000..498d627
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-color-001-notref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Fraction bar color</title>
+  </head>
+  <body style="font-size: 20pt;">
+    <p>This test passes if you see a fraction with a blue fraction bar.</p>
+    <math>
+      <mfrac>
+        <mspace width="200px" height="20px" style="background: black"></mspace>
+        <mspace width="200px" height="20px" style="background: black"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-color-001.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-color-001.html
new file mode 100644
index 0000000..bc61dbf5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-color-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Fraction bar color</title>
+    <link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+    <meta name="assert" content="The CSS color property has an effect on the fraction bar.">
+    <link rel="mismatch" href="frac-color-001-notref.html">
+  </head>
+  <body style="font-size: 20pt;">
+    <p>This test passes if you see a fraction with a blue fraction bar.</p>
+    <math>
+      <mfrac style="color: blue;">
+        <mspace width="200px" height="20px" style="background: black"></mspace>
+        <mspace width="200px" height="20px" style="background: black"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-001-ref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-001-ref.html
new file mode 100644
index 0000000..c703edb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-001-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>fractions linethickness</title>
+    <style type="text/css">
+      @font-face {
+        font-family: TestFont;
+        src: url("/fonts/math/fraction-rulethickness10000.woff");
+      }
+      math {
+        /* FractionRuleThickness = 10000 * 1 / 1000 = 10px; */
+        font-family: "TestFont";
+        font-size: 1px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>This test passes if you see fractions with line thickness equal to the height of their blue numerator and cyan denominator.</p>
+    <math>
+      <mfrac linethickness="5px">
+        <mspace width="20px" height="5px" style="background: blue"></mspace>
+        <mspace width="20px" height="5px" style="background: cyan"></mspace>
+      </mfrac>
+      <mfrac linethickness="10px">
+        <mspace width="20px" height="10px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+      <mfrac linethickness="20px">
+        <mspace width="20px" height="20px" style="background: blue"></mspace>
+        <mspace width="20px" height="20px" style="background: cyan"></mspace>
+      </mfrac>
+      <mfrac linethickness="50px">
+        <mspace width="20px" height="50px" style="background: blue"></mspace>
+        <mspace width="20px" height="50px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-001.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-001.html
new file mode 100644
index 0000000..9411117
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-001.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>fractions linethickness</title>
+    <link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+    <meta name="assert" content="Verifies 'thin', 'medium', 'thick' and unitless values for the linethickness attribute of the mfrac element">
+    <link rel="match" href="frac-linethickness-001-ref.html">
+    <style type="text/css">
+      @font-face {
+        font-family: TestFont;
+        src: url("/fonts/math/fraction-rulethickness10000.woff");
+      }
+      math {
+        /* FractionRuleThickness = 10000 * 1 / 1000 = 10px; */
+        font-family: "TestFont";
+        font-size: 1px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>This test passes if you see fractions with line thickness equal to the height of their blue numerator and cyan denominator.</p>
+    <math>
+      <mfrac linethickness="thin">
+        <mspace width="20px" height="5px" style="background: blue"></mspace>
+        <mspace width="20px" height="5px" style="background: cyan"></mspace>
+      </mfrac>
+      <mfrac linethickness="medium">
+        <mspace width="20px" height="10px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+      <mfrac linethickness="thick">
+        <mspace width="20px" height="20px" style="background: blue"></mspace>
+        <mspace width="20px" height="20px" style="background: cyan"></mspace>
+      </mfrac>
+      <mfrac linethickness="5">
+        <mspace width="20px" height="50px" style="background: blue"></mspace>
+        <mspace width="20px" height="50px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html
new file mode 100644
index 0000000..6966393
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>fractions linethickness</title>
+    <style type="text/css">
+      @font-face {
+        font-family: TestFont;
+        src: url("/fonts/math/fraction-rulethickness10000.woff");
+      }
+      math {
+        /* FractionRuleThickness = 10000 * 1 / 1000 = 10px; */
+        font-family: "TestFont";
+        font-size: 1px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>This test passes if you see fraction with a cyan denominator and
+      a blue numerator as tall as its black bar.</p>
+    <math>
+      <mfrac linethickness="0px">
+        <mspace width="20px" height="0px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+    <math>
+      <mfrac linethickness="50px">
+        <mspace width="20px" height="50px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+    <math style="font-size: 180px">
+      <mfrac linethickness="0.3888888888888889em">
+        <mspace width="20px" height="70px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002.html
new file mode 100644
index 0000000..5bb0d6b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>fractions linethickness</title>
+    <link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+    <meta name="assert" content="Verifies fraction with negative, percent and named space linethickness values.">
+    <link rel="match" href="frac-linethickness-002-ref.html">
+    <style type="text/css">
+      @font-face {
+        font-family: TestFont;
+        src: url("/fonts/math/fraction-rulethickness10000.woff");
+      }
+      math {
+        /* FractionRuleThickness = 10000 * 1 / 1000 = 10px; */
+        font-family: "TestFont";
+        font-size: 1px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>This test passes if you see fraction with a cyan denominator and
+      a blue numerator as tall as its black bar.</p>
+    <math>
+      <mfrac linethickness="-1.23em">
+        <mspace width="20px" height="0px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+    <math>
+      <mfrac linethickness="500%">
+        <mspace width="20px" height="50px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+    <math style="font-size: 180px">
+      <mfrac linethickness="veryverythickmathspace">
+        <mspace width="20px" height="70px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-003-notref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-003-notref.html
new file mode 100644
index 0000000..934d666
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-003-notref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>fractions linethickness</title>
+  </head>
+  <body>
+    <p>This test passes if you see a fraction without fraction bar.</p>
+    <math>
+      <mfrac>
+        <mspace width="20px" height="5px" style="background: blue"></mspace>
+        <mspace width="20px" height="5px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-003.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-003.html
new file mode 100644
index 0000000..e535e70
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-003.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>fractions linethickness</title>
+    <link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+    <meta name="assert" content="Verifies fraction with 0px bar.">
+    <link rel="mismatch" href="frac-linethickness-003-notref.html">
+  </head>
+  <body>
+    <p>This test passes if you see a fraction without fraction bar.</p>
+    <math>
+      <mfrac linethickness="0px">
+        <mspace width="20px" height="5px" style="background: blue"></mspace>
+        <mspace width="20px" height="5px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-mrow-001-ref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-mrow-001-ref.html
new file mode 100644
index 0000000..e42cb96
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-mrow-001-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Fraction mrow</title>
+  </head>
+  <body style="font-size: 20pt;">
+    <p>This test passes if you see a fraction with a blue square as numerator and a cyan square as denominator.</p>
+    <math>
+      <mfrac>
+        <mspace width="60px" height="60px" style="background: blue"></mspace>
+        <mspace width="60px" height="60px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-mrow-001.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-mrow-001.html
new file mode 100644
index 0000000..e5c6f52
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-mrow-001.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Fraction mrow</title>
+    <link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+    <meta name="assert" content="This test that <mrow> elements can be used as numerator and denominator of fractions.">
+    <link rel="match" href="frac-mrow-001-ref.html">
+  </head>
+  <body style="font-size: 20pt;">
+    <p>This test passes if you see a fraction with a blue square as numerator and a cyan square as denominator.</p>
+    <math>
+      <mfrac>
+        <mrow>
+          <mspace width="30px" height="60px" style="background: blue"></mspace>
+          <mspace width="30px" height="60px" style="background: blue "></mspace>
+        </mrow>
+        <mrow>
+          <mspace width="30px" height="60px" style="background: cyan"></mspace>
+          <mspace width="30px" height="60px" style="background: cyan "></mspace>
+        </mrow>
+      </mfrac>
+    </math>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-numalign-denomalign-001-ref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-numalign-denomalign-001-ref.html
new file mode 100644
index 0000000..2fa9786
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-numalign-denomalign-001-ref.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Fraction numalign denomalign</title>
+  </head>
+  <body>
+    <p>This test passes if you see 3 fractions with a numerator respectively
+      aligned left/center/right with respect to the denominator ;
+      followed by 3 fractions with a denominator respectively aligned
+      left/center/right with respect to the numerator.</p>
+    <p>
+      <math>
+        <mfrac>
+          <mrow>
+            <mspace width="10px" height="20px" style="background: blue;"></mspace>
+            <mspace width="20px" height="20px"></mspace>
+          </mrow>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac>
+          <mrow>
+            <mspace width="10px" height="20px"></mspace>
+            <mspace width="10px" height="20px" style="background: blue;"></mspace>
+            <mspace width="10px" height="20px"></mspace>
+          </mrow>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac>
+          <mrow>
+            <mspace width="20px" height="20px"></mspace>
+            <mspace width="10px" height="20px" style="background: blue;"></mspace>
+          </mrow>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+        </mfrac>
+      </math>
+    </p>
+    <p>
+      <math>
+        <mfrac>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+          <mrow>
+            <mspace width="10px" height="20px" style="background: blue;"></mspace>
+            <mspace width="20px" height="20px"></mspace>
+          </mrow>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+          <mrow>
+            <mspace width="10px" height="20px"></mspace>
+            <mspace width="10px" height="20px" style="background: blue;"></mspace>
+            <mspace width="10px" height="20px"></mspace>
+          </mrow>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+          <mrow>
+            <mspace width="20px" height="20px"></mspace>
+            <mspace width="10px" height="20px" style="background: blue;"></mspace>
+          </mrow>
+        </mfrac>
+      </math>
+    </p>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-numalign-denomalign-001.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-numalign-denomalign-001.html
new file mode 100644
index 0000000..61f49e2f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-numalign-denomalign-001.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Fraction numalign denomalign</title>
+    <link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+    <meta name="assert" content="This fraction alignment with the numalign/denomalign attributes.">
+    <link rel="match" href="frac-numalign-denomalign-001-ref.html">
+  </head>
+  <body>
+    <p>This test passes if you see 3 fractions with a numerator respectively
+      aligned left/center/right with respect to the denominator ;
+      followed by 3 fractions with a denominator respectively aligned
+      left/center/right with respect to the numerator.</p>
+    <p>
+      <math>
+        <mfrac numalign="left">
+          <mspace width="10px" height="20px" style="background: blue;"></mspace>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac numalign="center">
+          <mspace width="10px" height="20px" style="background: blue;"></mspace>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac numalign="right">
+          <mspace width="10px" height="20px" style="background: blue;"></mspace>
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+        </mfrac>
+      </math>
+    </p>
+    <p>
+      <math>
+        <mfrac denomalign="left">
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+          <mspace width="10px" height="20px" style="background: blue;"></mspace>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac denomalign="center">
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+          <mspace width="10px" height="20px" style="background: blue;"></mspace>
+        </mfrac>
+      </math>
+      <math>
+        <mfrac denomalign="right">
+          <mspace width="30px" height="20px" style="background: cyan;"></mspace>
+          <mspace width="10px" height="20px" style="background: blue;"></mspace>
+        </mfrac>
+      </math>
+    </p>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-expected.html b/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-expected.html
index 901df80ac..e0f988d 100644
--- a/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-expected.html
+++ b/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-expected.html
@@ -30,7 +30,6 @@
     background: green;
     color: green;
     font-size: 50px;
-    padding: 2px;
 }
 </style>
 <div class="container">
diff --git a/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-rtl-expected.html b/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-rtl-expected.html
index 0654db4e..c71606a 100644
--- a/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-rtl-expected.html
+++ b/third_party/blink/web_tests/media/track/track-cue-rendering-position-auto-rtl-expected.html
@@ -32,7 +32,6 @@
     background: green;
     color: green;
     font-size: 50px;
-    padding: 2px;
 }
 </style>
 <div class="container">
diff --git a/third_party/blink/web_tests/media/track/track-cue-rendering-with-padding.html b/third_party/blink/web_tests/media/track/track-cue-rendering-with-padding.html
index 9ccce45..70474ba 100644
--- a/third_party/blink/web_tests/media/track/track-cue-rendering-with-padding.html
+++ b/third_party/blink/web_tests/media/track/track-cue-rendering-with-padding.html
@@ -5,7 +5,7 @@
 <script src="../../resources/testharnessreport.js"></script>
 <style>
 video::-webkit-media-text-track-display {
-  padding: 15px;
+  padding: 10px;
 }
 </style>
 <video>
diff --git a/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png b/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png
index 1c767b4..744f01f 100644
--- a/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-vertical-expected.png
index c53e8c5a..0fb28191 100644
--- a/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/linux/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/video-surface-layer/media/track/track-cue-rendering-horizontal-expected.png b/third_party/blink/web_tests/platform/linux/virtual/video-surface-layer/media/track/track-cue-rendering-horizontal-expected.png
new file mode 100644
index 0000000..744f01f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/video-surface-layer/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/linux/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
index c53e8c5a..0fb28191 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png
index 07055e21..9c2e12ea 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
index 07055e21..9c2e12ea 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/media/track/track-cue-rendering-vertical-expected.png
index 3a2ab97..08193f25 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.11/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
index 3a2ab97..08193f25 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/media/track/track-cue-rendering-horizontal-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/media/track/track-cue-rendering-horizontal-expected.png
index 17b7f9d6..38d844e 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.12/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/video-surface-layer/media/track/track-cue-rendering-horizontal-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/video-surface-layer/media/track/track-cue-rendering-horizontal-expected.png
index 17b7f9d6..38d844e 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/video-surface-layer/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/video-surface-layer/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png b/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png
index 26a8ba6..112cac81 100644
--- a/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-vertical-expected.png
index 015250c..86a37f3 100644
--- a/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/mac/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-horizontal-expected.png b/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-horizontal-expected.png
index 021412e..5a483e8 100644
--- a/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-horizontal-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-horizontal-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-vertical-expected.png
index 457955f..2196206d 100644
--- a/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/win/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/win7/media/track/track-cue-rendering-vertical-expected.png
index 12d70d3..950738b 100644
--- a/third_party/blink/web_tests/platform/win7/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/win7/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png b/third_party/blink/web_tests/platform/win7/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
index 12d70d3..950738b 100644
--- a/third_party/blink/web_tests/platform/win7/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
+++ b/third_party/blink/web_tests/platform/win7/virtual/video-surface-layer/media/track/track-cue-rendering-vertical-expected.png
Binary files differ
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 67421e85..136a8c8 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -6519,6 +6519,28 @@
   <int value="10" label="FollowBookmark"/>
   <int value="11" label="PageSelectorNavigateFirst"/>
   <int value="12" label="PageSelectorNavigate"/>
+  <int value="13" label="SaveFirst"/>
+  <int value="14" label="Save"/>
+  <int value="15" label="SaveWithAnnotationFirst"/>
+  <int value="16" label="SaveWithAnnotation"/>
+  <int value="17" label="PrintFirst"/>
+  <int value="18" label="Print"/>
+  <int value="19" label="EnterAnnotationModeFirst"/>
+  <int value="20" label="EnterAnnotationMode"/>
+  <int value="21" label="ExitAnnotationModeFirst"/>
+  <int value="22" label="ExitAnnotationMode"/>
+  <int value="23" label="AnnotateStrokeToolPenFirst"/>
+  <int value="24" label="AnnotateStrokeToolPen"/>
+  <int value="25" label="AnnotateStrokeToolEraserFirst"/>
+  <int value="26" label="AnnotateStrokeToolEraser"/>
+  <int value="27" label="AnnotateStrokeToolHighlighterFirst"/>
+  <int value="28" label="AnnotateStrokeToolHighlighter"/>
+  <int value="29" label="AnnotateStrokeDeviceTouchFirst"/>
+  <int value="30" label="AnnotateStrokeDeviceTouch"/>
+  <int value="31" label="AnnotateStrokeDeviceMouseFirst"/>
+  <int value="32" label="AnnotateStrokeDeviceMouse"/>
+  <int value="33" label="AnnotateStrokeDevicePenFirst"/>
+  <int value="34" label="AnnotateStrokeDevicePen"/>
 </enum>
 
 <enum name="ChromePDFViewerAnnotationType">
@@ -31472,6 +31494,7 @@
       label="kAutofillRationalizeRepeatedServerPredictions:disabled"/>
   <int value="-894214299" label="fill-on-account-select:enabled"/>
   <int value="-894185031" label="HighDynamicRange:disabled"/>
+  <int value="-892428689" label="ManualPasswordGenerationAndroid:enabled"/>
   <int value="-891856063" label="MidiManagerAndroid:enabled"/>
   <int value="-886912558" label="ChromeHomePromo:enabled"/>
   <int value="-885601782" label="enable-contextual-search"/>
@@ -32739,6 +32762,7 @@
   <int value="1298927156" label="TouchpadOverscrollHistoryNavigation:enabled"/>
   <int value="1298981651" label="disable-new-task-manager"/>
   <int value="1300282719" label="OfflinePagesBackgroundLoading:enabled"/>
+  <int value="1300753556" label="ManualPasswordGenerationAndroid:disabled"/>
   <int value="1302421166" label="NativeNotifications:disabled"/>
   <int value="1304636193" label="ArcEnableUnifiedAudioFocus:enabled"/>
   <int value="1307003774" label="AutofillEnableCompanyName:disabled"/>
diff --git a/tools/perf/scripts_smoke_unittest.py b/tools/perf/scripts_smoke_unittest.py
index f776538c..a78ea68 100644
--- a/tools/perf/scripts_smoke_unittest.py
+++ b/tools/perf/scripts_smoke_unittest.py
@@ -150,7 +150,9 @@
       shutil.rmtree(tempdir)
 
 
-  @decorators.Disabled('win')  # ".exe" is auto-added which breaks Windows.
+  # Windows: ".exe" is auto-added which breaks Windows.
+  # ChromeOS: crbug.com/754913.
+  @decorators.Disabled('win', 'chromeos')
   def testRunPerformanceTestsGtest_end2end(self):
     tempdir = tempfile.mkdtemp()
     benchmark = 'dummy_gtest'
diff --git a/tools/ubsan/blacklist.txt b/tools/ubsan/blacklist.txt
index 68e25a21..13dce2bd 100644
--- a/tools/ubsan/blacklist.txt
+++ b/tools/ubsan/blacklist.txt
@@ -12,9 +12,6 @@
 
 #############################################################################
 # V8 UBsan supressions
-# UBSan bug, fixed in LLVM r350779. Drop this suppression when that
-# revision has rolled into Chromium's bundled Clang.
-fun:*v8*internal*NewArray*
 
 # Bug v8:8735: PropertyCallbackInfo<void> vs PropertyCallbackInfo<T>.
 fun:*v8*internal*PropertyCallbackArguments*CallAccessorSetter*
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css
index b06bd5b..4fac7e0e 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -1197,6 +1197,18 @@
   background-color: rgb(221, 242, 253);
 }
 
+.thumbnail-grid .thumbnail-item[selected] .shield {
+ background-color: rgba(51, 103, 214, 0.5);
+
+}
+.thumbnail-grid .thumbnail-item[lead]:not([selected]) .shield {
+  background-color: rgba(51, 103, 214, 0.2);
+}
+
+body.check-select .thumbnail-grid:focus .thumbnail-item[lead] {
+  outline: 1px solid rgba(51, 103, 214, 0.5);
+}
+
 .thumbnail-grid .checkmark {
   background-position: center;
   background-repeat: no-repeat;
@@ -1274,7 +1286,8 @@
   background-color: rgba(51, 103, 214, 0.5);
 }
 
-.thumbnail-grid .thumbnail-item[selected] .shield {
+.thumbnail-grid .thumbnail-item[selected] .shield,
+.thumbnail-grid .thumbnail-item[lead] .shield {
   display: block;
 }
 
@@ -1288,11 +1301,13 @@
   background-color: rgb(255, 255, 255);
 }
 
-.thumbnail-grid .thumbnail-item[selected].directory .thumbnail-bottom {
+.thumbnail-grid .thumbnail-item[selected].directory .thumbnail-bottom,
+.thumbnail-grid .thumbnail-item[lead].directory .thumbnail-bottom {
   background-color: rgb(232, 232, 232);
 }
 
-.thumbnail-grid:focus .thumbnail-item[selected].directory .thumbnail-bottom {
+.thumbnail-grid:focus .thumbnail-item[selected].directory .thumbnail-bottom,
+.thumbnail-grid:focus .thumbnail-item[lead].directory .thumbnail-bottom {
   background-color: rgb(232, 246, 253);
 }
 
@@ -1360,16 +1375,28 @@
 #list-container list > li[selected],
 #list-container list > li:active,
 #list-container list > li.accepts,
+#list-container list:focus > li[lead],
 #default-tasks-list > li[selected] {
   background-color: rgb(232, 232, 232);
 }
 
 #list-container list:focus > li[selected],
 #list-container list:focus > li.accepts[selected],
+#list-container list:focus > li[lead],
 #default-tasks-list:focus > li[selected] {
   background-color: rgb(216, 223, 240);
 }
 
+body.check-select #list-container list > li[lead],
+#list-container list > li[lead]:not([selected]) {
+  background-color: rgba(232, 234, 237, 0.5);
+}
+
+body.check-select #list-container list:focus > li[lead],
+#list-container list > li[lead]:not([selected]):focus {
+  background-color: rgb(232, 234, 237);
+}
+
 #list-container.nohover grid > .accepts {
   background-color: transparent;
 }
diff --git a/ui/file_manager/file_manager/foreground/elements/files_tooltip_unittest.html b/ui/file_manager/file_manager/foreground/elements/files_tooltip_unittest.html
deleted file mode 100644
index 4548367..0000000
--- a/ui/file_manager/file_manager/foreground/elements/files_tooltip_unittest.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE html>
-<!-- Copyright 2015 The Chromium Authors. All rights reserved.
-  -- Use of this source code is governed by a BSD-style license that can be
-  -- found in the LICENSE file.
-  -->
-
-<script src="../../../../webui/resources/js/assert.js"></script>
-<script src="../../../../webui/resources/js/util.js"></script>
-<script src="../../../base/js/test_error_reporting.js"></script>
-<script src="files_tooltip_unittest.js"></script>
diff --git a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
index e0c18d6..e9b135b7 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
@@ -299,6 +299,18 @@
   ]
 }
 
+js_unittest("file_table_list_unittest") {
+  deps = [
+    ":file_table_list",
+    ":file_table",
+    "../../../common/js:util",
+    "//ui/file_manager/base/js:test_error_reporting",
+    "//ui/webui/resources/js:webui_resource_test",
+    "//ui/file_manager/file_manager/background/js:mock_volume_manager",
+    "//ui/file_manager/file_manager/foreground/js/metadata:mock_metadata",
+  ]
+}
+
 js_library("file_tap_handler") {
   deps = [
     "../../../common/js:util",
@@ -464,6 +476,7 @@
     ":file_table_unittest",
     ":file_tap_handler_unittest",
     ":list_container_unittest",
+    ":file_table_list_unittest",
     ":multi_menu_unittest",
   ]
 }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_list_selection_model.js b/ui/file_manager/file_manager/foreground/js/ui/file_list_selection_model.js
index 8c57553..fac679f 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_list_selection_model.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_list_selection_model.js
@@ -7,6 +7,14 @@
   constructor(opt_length) {
     super(opt_length);
 
+    /**
+     * Overwrite ListSelectionModel to allow lead item to be independent of the
+     * current selected item(s).
+     * @private {boolean}
+     * @override
+     */
+    this.independentLeadItem_ = true;
+
     /** @private {boolean} */
     this.isCheckSelectMode_ = false;
 
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
index 7d66b67..1994f9f2 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
@@ -496,8 +496,29 @@
   if (e.key == ' ') {
     if (leadIndex != -1) {
       const selected = sm.getIndexSelected(leadIndex);
-      if (e.ctrlKey || !selected) {
-        sm.setIndexSelected(leadIndex, !selected || !sm.multiple);
+      if (e.ctrlKey) {
+        sm.beginChange();
+
+        // Force selecting if it's the first item selected, otherwise flip the
+        // "selected" status.
+        if (selected && sm.selectedIndexes.length === 1 &&
+            !sm.getCheckSelectMode()) {
+          // It needs to go back/forth to trigger the 'change' event.
+          sm.setIndexSelected(leadIndex, false);
+          sm.setIndexSelected(leadIndex, true);
+        } else {
+          // Toggle the current one and make it anchor index.
+          sm.setIndexSelected(leadIndex, !selected);
+        }
+
+        // Force check-select, FileListSelectionModel.onChangeEvent_ resets it
+        // if needed.
+        sm.setCheckSelectMode(true);
+        sm.endChange();
+
+        // Prevents space to opening quickview.
+        e.stopPropagation();
+        e.preventDefault();
         return;
       }
     }
@@ -547,6 +568,9 @@
       } else {
         sm.selectRange(anchorIndex, newIndex);
       }
+    } else if (e.ctrlKey) {
+      // While Ctrl is being held, only leadIndex and anchorIndex are moved.
+      sm.anchorIndex = newIndex;
     } else {
       // 1) When pressing direction key results in a single selection, the
       //    check-select mode should be terminated.
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js
new file mode 100644
index 0000000..51deaeab
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js
@@ -0,0 +1,216 @@
+// 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.
+
+'use strict';
+
+/** @type {!MockVolumeManager} */
+let volumeManager;
+
+/** @type {!DirectoryModel} */
+let directoryModel;
+
+/** @type {!MetadataModel} */
+let metadataModel;
+
+/** @type {!importer.HistoryLoader} */
+let historyLoader;
+
+/** @type {!HTMLElement} */
+let element;
+
+// Set up test components.
+function setUp() {
+  // Mock LoadTimeData strings.
+  window.loadTimeData.getString = id => id;
+  window.loadTimeData.data = {};
+
+  // Setup mock components.
+  volumeManager = new MockVolumeManager();
+  metadataModel = new MockMetadataModel({});
+  historyLoader = /** @type {!importer.HistoryLoader} */ ({
+    getHistory: () => {
+      return Promise.resolve();
+    },
+  });
+
+  // Create DOM element parent of the file list under test.
+  element = setupBody();
+}
+
+/**
+ * Returns the element used to parent the file list. The element is
+ * attached to the body, and styled for visual display.
+ *
+ * @return {!HTMLElement}
+ */
+function setupBody() {
+  const style = `
+      <style>
+        list {
+          display: block;
+          height: 200px;
+          width: 800px;
+        }
+       </style>
+      `;
+  document.body.innerHTML = style;
+
+  const element = document.createElement('div');
+  document.body.appendChild(element);
+  return /** @type {!HTMLElement} */ (element);
+}
+
+/** @param {string} keyName */
+function key(keyName) {
+  return {
+    bubbles: true,
+    composed: true,
+    key: keyName,
+  };
+}
+
+/** @param {string} keyName */
+function ctrlAndKey(keyName) {
+  return {
+    ctrlKey: true,
+    shiftKey: false,
+    altKey: false,
+    bubbles: true,
+    composed: true,
+    key: keyName,
+  };
+}
+
+/**
+ * Tests that the keyboard can be used to navigate the FileTableList.
+ */
+function testMultipleSelectionWithKeyboard() {
+  // Render the FileTable on |element|.
+  const fullPage = true;
+  FileTable.decorate(
+      element, metadataModel, volumeManager, historyLoader, fullPage);
+
+  // Overwrite the selectionModel of the FileTable class (since events
+  // would be handled by cr.ui.ListSelectionModel otherwise).
+  const sm = new FileListSelectionModel();
+  const table = /** @type {FileTable} */ (element);
+  table.selectionModel = sm;
+
+  // Add FileTableList file entries, then draw and focus the table list.
+  const entries = [
+      new FakeEntry('entry1-label', VolumeManagerCommon.RootType.CROSTINI),
+      new FakeEntry('entry2-label', VolumeManagerCommon.RootType.CROSTINI),
+      new FakeEntry('entry3-label', VolumeManagerCommon.RootType.CROSTINI),
+  ];
+  const dataModel = new FileListModel(metadataModel);
+  dataModel.splice(0, 0, ...entries);
+  const tableList = /** @type {FileTableList} */ (element.list);
+  tableList.dataModel = dataModel;
+  tableList.redraw();
+  tableList.focus();
+
+  // Grab all the elements in the file list.
+  const listItem0 = tableList.items[0];
+  const listItem1 = tableList.items[1];
+  const listItem2 = tableList.items[2];
+
+  // Assert file table list |item| selection state.
+  function assertItemIsSelected(item, selected = true) {
+    if (selected) {
+      assertTrue(item.hasAttribute('selected'));
+      assertEquals('true', item.getAttribute('aria-selected'));
+    } else {
+      assertFalse(item.hasAttribute('selected'));
+      assertEquals(null, item.getAttribute('aria-selected'));
+    }
+  }
+
+  // Assert file table list |item| focus/lead state.
+  function assertItemIsTheLead(item, lead = true) {
+    if (lead) {
+      assertEquals('lead', item.getAttribute('lead'));
+      assertEquals(item.id, tableList.getAttribute('aria-activedescendant'));
+    } else {
+      assertFalse(item.hasAttribute('lead'));
+      assertFalse(item.id === tableList.getAttribute('aria-activedescendant'));
+    }
+  }
+ 
+  // FileTableList always allows multiple selection.
+  assertEquals('true', tableList.getAttribute('aria-multiselectable'));
+
+  // Home key selects the first item (listItem0).
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('Home')));
+  // Only 1 item selected.
+  assertEquals(1, sm.selectedIndexes.length);
+  // listItem0 should be selected and focused.
+  assertEquals(0, sm.selectedIndexes[0]);
+  assertItemIsTheLead(listItem0);
+  assertItemIsSelected(listItem0);
+  // Only one item is selected: multiple selection should be inactive.
+  assertFalse(sm.getCheckSelectMode());
+
+  // ArrowDown moves and selects next item.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('ArrowDown')));
+  // Only listItem1 should be selected.
+  assertEquals(1, sm.selectedIndexes.length);
+  assertEquals(1, sm.selectedIndexes[0]);
+  // listItem1 should be focused.
+  assertItemIsTheLead(listItem1);
+  assertItemIsSelected(listItem1);
+  // Only one item is selected: multiple selection should be inactive.
+  assertFalse(sm.getCheckSelectMode());
+
+  // Ctrl+ArrowDown only moves the focus.
+  tableList.dispatchEvent(
+      new KeyboardEvent('keydown', ctrlAndKey('ArrowDown')));
+  // listItem1 should be not focused but still selected.
+  assertEquals(1, sm.selectedIndexes.length);
+  assertEquals(1, sm.selectedIndexes[0]);
+  assertItemIsTheLead(listItem1, false);
+  assertItemIsSelected(listItem1);
+  // listItem2 should be focused but not selected.
+  assertItemIsTheLead(listItem2);
+  assertItemIsSelected(listItem2, false);
+ 
+  // Only one item is selected: multiple selection should be inactive.
+  assertFalse(sm.getCheckSelectMode());
+  
+  // Ctrl+Space selects the focused item.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', ctrlAndKey(' ')));
+  // Multiple selection mode should now be activated.
+  assertTrue(sm.getCheckSelectMode());
+  // Both listItem1 and listItem2 should be selected.
+  assertEquals(2, sm.selectedIndexes.length);
+  assertEquals(1, sm.selectedIndexes[0]);
+  assertEquals(2, sm.selectedIndexes[1]);
+  // listItem1 should not be focused.
+  assertItemIsTheLead(listItem1, false);
+  assertItemIsSelected(listItem1);
+  // listItem1 should be focused and selected.
+  assertItemIsTheLead(listItem2);
+  assertItemIsSelected(listItem2);
+
+  // Hit Esc to cancel the whole selection.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('Escape')));
+  // The item with the focus should not change.
+  assertItemIsTheLead(listItem2);
+  // But there should be no selected items anymore.
+  assertFalse(sm.getCheckSelectMode());
+  assertEquals(0, sm.selectedIndexes.length);
+  for (let i = 0; i < tableList.items.length; i++) {
+    if (i !== 2) {
+      // Item 2 should have focus.
+      assertFalse(
+          tableList.items[i].hasAttribute('lead'),
+          'item ' + i + ' should not have focus');
+    }
+    assertEquals(
+        'false', tableList.items[i].getAttribute('aria-selected'),
+        'item ' + i + ' should have aria-selected=false');
+    assertFalse(
+        tableList.items[i].hasAttribute('selected'),
+        'item ' + i + ' should not have selected attr');
+  }
+}
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index 6f9b0c3f..78256bdf 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -253,6 +253,7 @@
   ]
   deps += [
     ":gfx_export",
+    ":resize_image_dimensions",
     "//base",
     "//base:base_static",
     "//base:i18n",
@@ -405,6 +406,14 @@
   defines = [ "COLOR_SPACE_IMPLEMENTATION" ]
 }
 
+# Depend on this to use image/resize_image_dimensions.h without pulling in
+# all of gfx.
+source_set("resize_image_dimensions") {
+  sources = [
+    "image/resize_image_dimensions.h",
+  ]
+}
+
 # Depend on this to use native_widget_types.h without pulling in all of gfx.
 source_set("native_widget_types") {
   public = [
diff --git a/ui/gfx/image/image_util.cc b/ui/gfx/image/image_util.cc
index b856788..80bca3ea 100644
--- a/ui/gfx/image/image_util.cc
+++ b/ui/gfx/image/image_util.cc
@@ -15,6 +15,7 @@
 #include "ui/gfx/codec/jpeg_codec.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/resize_image_dimensions.h"
 
 namespace {
 
diff --git a/ui/gfx/image/image_util.h b/ui/gfx/image/image_util.h
index 5b48487b..e8593f4d 100644
--- a/ui/gfx/image/image_util.h
+++ b/ui/gfx/image/image_util.h
@@ -18,11 +18,6 @@
 
 namespace gfx {
 
-// Dimensions to use when downsizing an image for search-by-image.
-const int kSearchByImageMaxImageArea = 90000;
-const int kSearchByImageMaxImageWidth = 600;
-const int kSearchByImageMaxImageHeight = 400;
-
 // Creates an image from the given JPEG-encoded input. If there was an error
 // creating the image, returns an IsEmpty() Image.
 GFX_EXPORT Image ImageFrom1xJPEGEncodedData(const unsigned char* input,
diff --git a/ui/gfx/image/image_util_ios.mm b/ui/gfx/image/image_util_ios.mm
index adac57a..cc66b24 100644
--- a/ui/gfx/image/image_util_ios.mm
+++ b/ui/gfx/image/image_util_ios.mm
@@ -7,6 +7,7 @@
 #include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_util.h"
+#include "ui/gfx/image/resize_image_dimensions.h"
 
 #include "base/logging.h"
 
diff --git a/ui/gfx/image/resize_image_dimensions.h b/ui/gfx/image/resize_image_dimensions.h
new file mode 100644
index 0000000..65f7925
--- /dev/null
+++ b/ui/gfx/image/resize_image_dimensions.h
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_IMAGE_RESIZE_IMAGE_DIMENSIONS_H_
+#define UI_GFX_IMAGE_RESIZE_IMAGE_DIMENSIONS_H_
+
+namespace gfx {
+
+// Dimensions to use when downsizing an image for search-by-image.
+const int kSearchByImageMaxImageArea = 90000;
+const int kSearchByImageMaxImageWidth = 600;
+const int kSearchByImageMaxImageHeight = 400;
+
+}  // namespace gfx
+
+#endif  // UI_GFX_IMAGE_RESIZE_IMAGE_DIMENSIONS_H_
diff --git a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
index 086b8bc..8d400af 100644
--- a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
+++ b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
@@ -170,7 +170,7 @@
   listeners: {
     'keydown': 'onKeyDown_',
     'mouseover': 'onMouseover_',
-    'tap': 'onTap_',
+    'click': 'onClick_',
   },
 
   /** override */
@@ -224,7 +224,7 @@
    * @param {!Event} e
    * @private
    */
-  onTap_: function(e) {
+  onClick_: function(e) {
     if (e.target == this) {
       this.close();
       e.stopPropagation();