diff --git a/DEPS b/DEPS
index c06f3ac..f47f043d 100644
--- a/DEPS
+++ b/DEPS
@@ -138,11 +138,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'eca66b32fdb91344b2a22d98c405794c3fded66a',
+  'skia_revision': 'fec9b902a626c4e9fa5aa13c03c5b1261666f45c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'ddbb5499b8722f494df8daa5b5e21e3f5e2b6f64',
+  'v8_revision': 'd6158eea40c4b8863977f863a26aeb8f423b17b8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -150,11 +150,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '98f2167125a8d204ed120d483d79a7af577806ca',
+  'angle_revision': '4a75741655d330e257b2f20d7404cce1e51ef72e',
   # 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': 'bb575d48d5f9f96cef08a5d23b0d51ce3c57ae1a',
+  'swiftshader_revision': '75841d73c2de727355d48d8d9af9f6cd7e3f7738',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -201,7 +201,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '58f6c4682e28f328945fa14a616d6b4e1cdde6ad',
+  'catapult_revision': '1c7a411305c552836f51768b3ad613d8d500b17a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -257,11 +257,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': '699e167d78eee66232f140d4dcf5e9453de2dc74',
+  'spv_tools_revision': '2c0111e6eba779cf30e8c7f5a733ea0762895ba0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_headers_revision': '903d447d96eb3973bdf95e9d16581b704784a0e3',
+  'spv_headers_revision': '8b911bd2ba37677037b38c9bd286c7c05701bcda',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -807,7 +807,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '6e80eaeb04000f78322820e286e91aa474e8d0f8',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '062420146dfd839935afb7147f6a52128b4f8f5a',
       'condition': 'checkout_linux',
   },
 
@@ -1350,7 +1350,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6f0b34abee8dba611c253738d955c59f703c147a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '114e8bb7a68f239b5611f71414ca99058ae02464',
+    Var('webrtc_git') + '/src.git' + '@' + '102b7289a9f2db72f74884a7788605187a2ddfee',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 5415b00..1126a666 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -467,7 +467,6 @@
   '^ui/events/',
   '^ui/gfx/',
   '^ui/message_center/',
-  '^ui/ozone/',
   '^ui/snapshot/',
   '^ui/views_content_client/',
   '^ui/wm/',
@@ -923,6 +922,7 @@
         r'.*_ios\.(cc|h)$',
         r'^net[\\/].*\.(cc|h)$',
         r'.*[\\/]tools[\\/].*\.(cc|h)$',
+        r'^fuchsia/engine/web_engine_debug_integration_test\.cc$',
       ),
     ),
     (
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index dccaa63..ecc438d5 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -612,6 +612,8 @@
     "browser/net/input_stream_reader.h",
     "browser/network_service/android_stream_reader_url_loader.cc",
     "browser/network_service/android_stream_reader_url_loader.h",
+    "browser/network_service/aw_proxying_restricted_cookie_manager.cc",
+    "browser/network_service/aw_proxying_restricted_cookie_manager.h",
     "browser/network_service/aw_proxying_url_loader_factory.cc",
     "browser/network_service/aw_proxying_url_loader_factory.h",
     "browser/network_service/aw_url_loader_throttle.cc",
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index 1b08fcfe..7369a5e 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -29,6 +29,7 @@
 #include "android_webview/browser/cookie_manager.h"
 #include "android_webview/browser/net/aw_proxy_config_monitor.h"
 #include "android_webview/browser/net/aw_url_request_context_getter.h"
+#include "android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h"
 #include "android_webview/browser/network_service/aw_proxying_url_loader_factory.h"
 #include "android_webview/browser/network_service/aw_url_loader_throttle.h"
 #include "android_webview/browser/network_service/net_helpers.h"
@@ -1043,6 +1044,23 @@
   }
 }
 
+void AwContentBrowserClient::WillCreateRestrictedCookieManager(
+    const url::Origin& origin,
+    bool is_service_worker,
+    int process_id,
+    int routing_id,
+    network::mojom::RestrictedCookieManagerRequest* request) {
+  network::mojom::RestrictedCookieManagerRequest orig_request =
+      std::move(*request);
+
+  network::mojom::RestrictedCookieManagerPtrInfo target_rcm_info;
+  *request = mojo::MakeRequest(&target_rcm_info);
+
+  AwProxyingRestrictedCookieManager::CreateAndBind(
+      std::move(target_rcm_info), is_service_worker, process_id, routing_id,
+      std::move(orig_request));
+}
+
 std::string AwContentBrowserClient::GetProduct() const {
   return android_webview::GetProduct();
 }
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index 2c06509..64116c9b 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -238,6 +238,12 @@
       network::mojom::AuthenticationHandlerPtr* authentication_handler,
       network::mojom::TrustedHeaderClientPtr* header_client,
       uint32_t* options) override;
+  void WillCreateRestrictedCookieManager(
+      const url::Origin& origin,
+      bool is_service_worker,
+      int process_id,
+      int routing_id,
+      network::mojom::RestrictedCookieManagerRequest* request) override;
   std::string GetProduct() const override;
   std::string GetUserAgent() const override;
   ContentBrowserClient::WideColorGamutHeuristic GetWideColorGamutHeuristic()
diff --git a/android_webview/browser/aw_download_manager_delegate.cc b/android_webview/browser/aw_download_manager_delegate.cc
index 63c263a0..0c3daa2c 100644
--- a/android_webview/browser/aw_download_manager_delegate.cc
+++ b/android_webview/browser/aw_download_manager_delegate.cc
@@ -13,6 +13,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
+#include "services/network/public/cpp/features.h"
 
 namespace android_webview {
 
@@ -71,6 +72,9 @@
     const std::string& request_origin,
     int64_t content_length,
     content::WebContents* web_contents) {
+  if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
+    return false;
+
   if (!web_contents)
     return false;
 
diff --git a/android_webview/browser/net/aw_cookie_change_dispatcher_wrapper.cc b/android_webview/browser/net/aw_cookie_change_dispatcher_wrapper.cc
index de3ea504..5202e93 100644
--- a/android_webview/browser/net/aw_cookie_change_dispatcher_wrapper.cc
+++ b/android_webview/browser/net/aw_cookie_change_dispatcher_wrapper.cc
@@ -41,7 +41,13 @@
  public:
   SubscriptionWrapper() : weak_factory_(this) {}
 
+  enum Mode {
+    kByCookie,
+    kByUrl,
+  };
+
   std::unique_ptr<net::CookieChangeSubscription> Subscribe(
+      Mode mode,
       const GURL& url,
       const std::string& name,
       net::CookieChangeCallback callback) {
@@ -49,7 +55,7 @@
     DCHECK(callback_list_.empty());
 
     nested_subscription_ =
-        NestedSubscription::Create(url, name, weak_factory_.GetWeakPtr());
+        NestedSubscription::Create(mode, url, name, weak_factory_.GetWeakPtr());
     return std::make_unique<AwCookieChangeSubscription>(
         callback_list_.Add(std::move(callback)));
   }
@@ -62,13 +68,14 @@
       : public base::RefCountedDeleteOnSequence<NestedSubscription> {
    public:
     static scoped_refptr<NestedSubscription> Create(
+        Mode mode,
         const GURL& url,
         const std::string& name,
         base::WeakPtr<SubscriptionWrapper> subscription_wrapper) {
       auto subscription = base::WrapRefCounted(
           new NestedSubscription(std::move(subscription_wrapper)));
       PostTaskToCookieStoreTaskRunner(base::BindOnce(
-          &NestedSubscription::Subscribe, subscription, url, name));
+          &NestedSubscription::Subscribe, subscription, mode, url, name));
       return subscription;
     }
 
@@ -85,11 +92,20 @@
 
     ~NestedSubscription() {}
 
-    void Subscribe(const GURL& url, const std::string& name) {
-      subscription_ =
-          GetCookieStore()->GetChangeDispatcher().AddCallbackForCookie(
-              url, name,
-              base::BindRepeating(&NestedSubscription::OnChanged, this));
+    void Subscribe(Mode mode, const GURL& url, const std::string& name) {
+      switch (mode) {
+        case kByCookie:
+          subscription_ =
+              GetCookieStore()->GetChangeDispatcher().AddCallbackForCookie(
+                  url, name,
+                  base::BindRepeating(&NestedSubscription::OnChanged, this));
+          break;
+        case kByUrl:
+          subscription_ =
+              GetCookieStore()->GetChangeDispatcher().AddCallbackForUrl(
+                  url,
+                  base::BindRepeating(&NestedSubscription::OnChanged, this));
+      }
     }
 
     void OnChanged(const net::CanonicalCookie& cookie,
@@ -149,16 +165,21 @@
   // subscription when the AwCookieStoreWrapper is destroyed a bit ugly.
   // TODO(mmenke):  Still worth adding a DCHECK?
   SubscriptionWrapper* subscription = new SubscriptionWrapper();
-  return subscription->Subscribe(url, name, std::move(callback));
+  return subscription->Subscribe(SubscriptionWrapper::kByCookie, url, name,
+                                 std::move(callback));
 }
 
 std::unique_ptr<net::CookieChangeSubscription>
 AwCookieChangeDispatcherWrapper::AddCallbackForUrl(
     const GURL& url,
     net::CookieChangeCallback callback) {
-  // Implement when needed by Android Webview consumers.
-  NOTIMPLEMENTED();
-  return nullptr;
+#if DCHECK_IS_ON()
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
+#endif  // DCHECK_IS_ON()
+
+  SubscriptionWrapper* subscription = new SubscriptionWrapper();
+  return subscription->Subscribe(SubscriptionWrapper::kByUrl, url,
+                                 /* name=, ignored */ "", std::move(callback));
 }
 
 std::unique_ptr<net::CookieChangeSubscription>
diff --git a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
new file mode 100644
index 0000000..2bc32c5c
--- /dev/null
+++ b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
@@ -0,0 +1,159 @@
+// 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 "android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h"
+
+#include "android_webview/browser/aw_cookie_access_policy.h"
+#include "base/memory/ptr_util.h"
+#include "base/task/post_task.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace android_webview {
+
+class AwProxyingRestrictedCookieManagerListener
+    : public network::mojom::CookieChangeListener {
+ public:
+  AwProxyingRestrictedCookieManagerListener(
+      const GURL& url,
+      const GURL& site_for_cookies,
+      base::WeakPtr<AwProxyingRestrictedCookieManager>
+          aw_restricted_cookie_manager,
+      network::mojom::CookieChangeListenerPtr client_listener)
+      : url_(url),
+        site_for_cookies_(site_for_cookies),
+        aw_restricted_cookie_manager_(aw_restricted_cookie_manager),
+        client_listener_(std::move(client_listener)) {}
+
+  void OnCookieChange(const net::CanonicalCookie& cookie,
+                      network::mojom::CookieChangeCause cause) override {
+    if (aw_restricted_cookie_manager_ &&
+        aw_restricted_cookie_manager_->AllowCookies(url_, site_for_cookies_))
+      client_listener_->OnCookieChange(cookie, cause);
+  }
+
+ private:
+  const GURL url_;
+  const GURL site_for_cookies_;
+  base::WeakPtr<AwProxyingRestrictedCookieManager>
+      aw_restricted_cookie_manager_;
+  network::mojom::CookieChangeListenerPtr client_listener_;
+};
+
+// static
+void AwProxyingRestrictedCookieManager::CreateAndBind(
+    network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm,
+    bool is_service_worker,
+    int process_id,
+    int frame_id,
+    network::mojom::RestrictedCookieManagerRequest request) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::IO},
+      base::BindOnce(
+          &AwProxyingRestrictedCookieManager::CreateAndBindOnIoThread,
+          std::move(underlying_rcm), is_service_worker, process_id, frame_id,
+          std::move(request)));
+}
+
+AwProxyingRestrictedCookieManager::~AwProxyingRestrictedCookieManager() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+}
+
+void AwProxyingRestrictedCookieManager::GetAllForUrl(
+    const GURL& url,
+    const GURL& site_for_cookies,
+    network::mojom::CookieManagerGetOptionsPtr options,
+    GetAllForUrlCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+  if (AllowCookies(url, site_for_cookies)) {
+    underlying_restricted_cookie_manager_->GetAllForUrl(
+        url, site_for_cookies, std::move(options), std::move(callback));
+  } else {
+    std::move(callback).Run(std::vector<net::CanonicalCookie>());
+  }
+}
+
+void AwProxyingRestrictedCookieManager::SetCanonicalCookie(
+    const net::CanonicalCookie& cookie,
+    const GURL& url,
+    const GURL& site_for_cookies,
+    SetCanonicalCookieCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+  if (AllowCookies(url, site_for_cookies)) {
+    underlying_restricted_cookie_manager_->SetCanonicalCookie(
+        cookie, url, site_for_cookies, std::move(callback));
+  } else {
+    std::move(callback).Run(false);
+  }
+}
+
+void AwProxyingRestrictedCookieManager::AddChangeListener(
+    const GURL& url,
+    const GURL& site_for_cookies,
+    network::mojom::CookieChangeListenerPtr listener,
+    AddChangeListenerCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+  network::mojom::CookieChangeListenerPtr proxy_listener_ptr;
+  auto proxy_listener =
+      std::make_unique<AwProxyingRestrictedCookieManagerListener>(
+          url, site_for_cookies, weak_factory_.GetWeakPtr(),
+          std::move(listener));
+
+  mojo::MakeStrongBinding(std::move(proxy_listener),
+                          mojo::MakeRequest(&proxy_listener_ptr));
+
+  underlying_restricted_cookie_manager_->AddChangeListener(
+      url, site_for_cookies, std::move(proxy_listener_ptr),
+      std::move(callback));
+}
+
+AwProxyingRestrictedCookieManager::AwProxyingRestrictedCookieManager(
+    network::mojom::RestrictedCookieManagerPtr
+        underlying_restricted_cookie_manager,
+    bool is_service_worker,
+    int process_id,
+    int frame_id)
+    : underlying_restricted_cookie_manager_(
+          std::move(underlying_restricted_cookie_manager)),
+      is_service_worker_(is_service_worker),
+      process_id_(process_id),
+      frame_id_(frame_id),
+      weak_factory_(this) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+}
+
+// static
+void AwProxyingRestrictedCookieManager::CreateAndBindOnIoThread(
+    network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm,
+    bool is_service_worker,
+    int process_id,
+    int frame_id,
+    network::mojom::RestrictedCookieManagerRequest request) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  auto wrapper = base::WrapUnique(new AwProxyingRestrictedCookieManager(
+      network::mojom::RestrictedCookieManagerPtr(std::move(underlying_rcm)),
+      is_service_worker, process_id, frame_id));
+  mojo::MakeStrongBinding(std::move(wrapper), std::move(request));
+}
+
+bool AwProxyingRestrictedCookieManager::AllowCookies(
+    const GURL& url,
+    const GURL& site_for_cookies) const {
+  if (is_service_worker_) {
+    // Service worker cookies are always first-party, so only need to check
+    // the global toggle.
+    return AwCookieAccessPolicy::GetInstance()->GetShouldAcceptCookies();
+  } else {
+    return AwCookieAccessPolicy::GetInstance()->AllowCookies(
+        url, site_for_cookies, process_id_, frame_id_);
+  }
+}
+
+}  // namespace android_webview
diff --git a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h
new file mode 100644
index 0000000..65b89a7
--- /dev/null
+++ b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ANDROID_WEBVIEW_BROWSER_NETWORK_SERVICE_AW_PROXYING_RESTRICTED_COOKIE_MANAGER_H_
+#define ANDROID_WEBVIEW_BROWSER_NETWORK_SERVICE_AW_PROXYING_RESTRICTED_COOKIE_MANAGER_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
+#include "url/gurl.h"
+
+namespace android_webview {
+
+// A RestrictedCookieManager which conditionally proxies to an underlying
+// RestrictedCookieManager, first consulting WebView's cookie settings.
+class AwProxyingRestrictedCookieManager
+    : public network::mojom::RestrictedCookieManager {
+ public:
+  // Creates a AwProxyingRestrictedCookieManager that lives on IO thread,
+  // binding it to handle communications from |request|. The requests will be
+  // delegated to |underlying_rcm|. The resulting object will be owned by the
+  // pipe corresponding to |request| and will in turn own |underlying_rcm|.
+  //
+  // Expects to be called on the UI thread.
+  static void CreateAndBind(
+      network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm,
+      bool is_service_worker,
+      int process_id,
+      int frame_id,
+      network::mojom::RestrictedCookieManagerRequest request);
+
+  ~AwProxyingRestrictedCookieManager() override;
+
+  // network::mojom::RestrictedCookieManager interface:
+  void GetAllForUrl(const GURL& url,
+                    const GURL& site_for_cookies,
+                    network::mojom::CookieManagerGetOptionsPtr options,
+                    GetAllForUrlCallback callback) override;
+  void SetCanonicalCookie(const net::CanonicalCookie& cookie,
+                          const GURL& url,
+                          const GURL& site_for_cookies,
+                          SetCanonicalCookieCallback callback) override;
+  void AddChangeListener(const GURL& url,
+                         const GURL& site_for_cookies,
+                         network::mojom::CookieChangeListenerPtr listener,
+                         AddChangeListenerCallback callback) override;
+
+  bool AllowCookies(const GURL& url, const GURL& site_for_cookies) const;
+
+ private:
+  AwProxyingRestrictedCookieManager(network::mojom::RestrictedCookieManagerPtr
+                                        underlying_restricted_cookie_manager,
+                                    bool is_service_worker,
+                                    int process_id,
+                                    int frame_id);
+
+  static void CreateAndBindOnIoThread(
+      network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm,
+      bool is_service_worker,
+      int process_id,
+      int frame_id,
+      network::mojom::RestrictedCookieManagerRequest request);
+
+  network::mojom::RestrictedCookieManagerPtr
+      underlying_restricted_cookie_manager_;
+  bool is_service_worker_;
+  int process_id_;
+  int frame_id_;
+
+  base::WeakPtrFactory<AwProxyingRestrictedCookieManager> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(AwProxyingRestrictedCookieManager);
+};
+
+}  // namespace android_webview
+
+#endif  // ANDROID_WEBVIEW_BROWSER_NETWORK_SERVICE_AW_PROXYING_RESTRICTED_COOKIE_MANAGER_H_
diff --git a/android_webview/java/src/org/chromium/android_webview/services/AwVariationsSeedFetcher.java b/android_webview/java/src/org/chromium/android_webview/services/AwVariationsSeedFetcher.java
index f535fba..53e9df3 100644
--- a/android_webview/java/src/org/chromium/android_webview/services/AwVariationsSeedFetcher.java
+++ b/android_webview/java/src/org/chromium/android_webview/services/AwVariationsSeedFetcher.java
@@ -76,12 +76,15 @@
 
     private static JobScheduler getScheduler() {
         if (sMockJobScheduler != null) return sMockJobScheduler;
+
+        // This may be null due to vendor framework bugs. https://crbug.com/968636
         return (JobScheduler) ContextUtils.getApplicationContext().getSystemService(
                 Context.JOB_SCHEDULER_SERVICE);
     }
 
     public static void scheduleIfNeeded() {
         JobScheduler scheduler = getScheduler();
+        if (scheduler == null) return;
 
         // Check if it's already scheduled.
         if (getPendingJob(scheduler, JOB_ID) != null) {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
index a21570c4..cfdb79ad 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
@@ -23,6 +23,7 @@
 import org.chromium.android_webview.test.util.CookieUtils.TestCallback;
 import org.chromium.android_webview.test.util.JSUtils;
 import org.chromium.base.Callback;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
@@ -97,7 +98,8 @@
             throws Throwable {
         mCookieManager.setAcceptCookie(acceptCookieValue);
 
-        TestWebServer webServer = TestWebServer.start();
+        // Using SSL server here since CookieStore API requires a secure schema.
+        TestWebServer webServer = TestWebServer.startSsl();
         try {
             String path = "/cookie_test.html";
             String responseStr =
@@ -106,7 +108,7 @@
             mActivityTestRule.loadUrlSync(
                     mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
             final String jsCookieName = "js-test" + cookieSuffix;
-            setCookieWithJavaScript(jsCookieName, "value");
+            setCookieWithDocumentCookieAPI(jsCookieName, "value");
             if (acceptCookieValue) {
                 waitForCookie(url);
                 assertHasCookies(url);
@@ -115,6 +117,16 @@
                 assertNoCookies(url);
             }
 
+            final String cookieStoreCookieName = "cookiestore-test" + cookieSuffix;
+            setCookieWithCookieStoreAPI(cookieStoreCookieName, "value");
+            if (acceptCookieValue) {
+                waitForCookie(url);
+                assertHasCookies(url);
+                validateCookies(url, jsCookieName, cookieStoreCookieName);
+            } else {
+                assertNoCookies(url);
+            }
+
             final List<Pair<String, String>> responseHeaders =
                     new ArrayList<Pair<String, String>>();
             final String headerCookieName = "header-test" + cookieSuffix;
@@ -126,7 +138,7 @@
             if (acceptCookieValue) {
                 waitForCookie(url);
                 assertHasCookies(url);
-                validateCookies(url, jsCookieName, headerCookieName);
+                validateCookies(url, jsCookieName, cookieStoreCookieName, headerCookieName);
             } else {
                 assertNoCookies(url);
             }
@@ -138,6 +150,8 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
+    @CommandLineFlags.Add({"enable-blink-features=CookieStore"})
+    // TODO(https://crbug.com/968649) Remove switch when CookieStore launched.
     public void testAcceptCookie_falseWontSetCookies() throws Throwable {
         testAcceptCookieHelper(false, "-disabled");
     }
@@ -145,6 +159,8 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
+    @CommandLineFlags.Add({"enable-blink-features=CookieStore"})
+    // TODO(https://crbug.com/968649) Remove switch when CookieStore launched.
     public void testAcceptCookie_trueWillSetCookies() throws Throwable {
         testAcceptCookieHelper(true, "-enabled");
     }
@@ -209,7 +225,7 @@
         }
     }
 
-    private void setCookieWithJavaScript(final String name, final String value)
+    private void setCookieWithDocumentCookieAPI(final String name, final String value)
             throws Throwable {
         JSUtils.executeJavaScriptAndWaitForResult(InstrumentationRegistry.getInstrumentation(),
                 mAwContents, mContentsClient.getOnEvaluateJavaScriptResultHelper(),
@@ -219,6 +235,16 @@
                         + "; expires=' + expirationDate.toUTCString();");
     }
 
+    private void setCookieWithCookieStoreAPI(final String name, final String value)
+            throws Throwable {
+        JavaScriptUtils.runJavascriptWithAsyncResult(mAwContents.getWebContents(),
+                "async function doSet() {"
+                        + makeCookieStoreSetFragment("'" + name + "'", "'" + value + "'",
+                                "window.domAutomationController.send(true);")
+                        + "}\n"
+                        + "doSet()");
+    }
+
     private String getCookieWithJavaScript(final String name) throws Throwable {
         return JSUtils.executeJavaScriptAndWaitForResult(
                 InstrumentationRegistry.getInstrumentation(), mAwContents,
@@ -556,6 +582,51 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
+    @CommandLineFlags.Add({"enable-blink-features=CookieStore"})
+    // TODO(https://crbug.com/968649) Remove switch when CookieStore launched.
+    public void testCookieStoreListener() throws Throwable {
+        TestWebServer webServer = TestWebServer.startSsl();
+        try {
+            allowFirstPartyCookies();
+
+            String url = makeCookieScriptUrl(webServer, "/cookie_1.html", "test1", "value1");
+            mActivityTestRule.loadUrlSync(
+                    mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+
+            // Add a listener...
+            JSUtils.executeJavaScriptAndWaitForResult(InstrumentationRegistry.getInstrumentation(),
+                    mAwContents, mContentsClient.getOnEvaluateJavaScriptResultHelper(),
+                    "window.events = [];"
+                            + "cookieStore.addEventListener('change', (event) => {"
+                            + "  for (let d of event.deleted)"
+                            + "    window.events.push({'del': d.name});"
+                            + "  for (let c of event.changed)"
+                            + "    window.events.push({'change': c.name});"
+                            + "})");
+
+            // Clearing all cookies with cookies disabled shouldn't report anything.
+            blockAllCookies();
+            clearCookies();
+
+            // Re-enable cookies, set one.
+            allowFirstPartyCookies();
+            setCookieWithDocumentCookieAPI("test2", "value2");
+
+            // Look up the result. Should see the second set, but not the
+            // delete, based on whether cookie access was permitted or not
+            // at the time.
+            String reported = JSUtils.executeJavaScriptAndWaitForResult(
+                    InstrumentationRegistry.getInstrumentation(), mAwContents,
+                    mContentsClient.getOnEvaluateJavaScriptResultHelper(), "window.events");
+            Assert.assertEquals("[{\"change\":\"test2\"}]", reported);
+        } finally {
+            webServer.shutdown();
+        }
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "Privacy"})
     public void testThirdPartyCookie_redirectFromThirdPartyToFirst() throws Throwable {
         TestWebServer webServer = TestWebServer.start();
         try {
@@ -750,8 +821,11 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
+    @CommandLineFlags.Add({"enable-blink-features=CookieStore"})
+    // TODO(https://crbug.com/968649) Remove switch when CookieStore launched.
     public void testThirdPartyJavascriptCookie() throws Throwable {
-        TestWebServer webServer = TestWebServer.start();
+        // Using SSL server here since CookieStore API requires a secure schema.
+        TestWebServer webServer = TestWebServer.startSsl();
         try {
             // This test again uses 127.0.0.1/localhost trick to simulate a third party.
             ThirdPartyCookiesTestHelper thirdParty =
@@ -775,8 +849,11 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
+    @CommandLineFlags.Add({"enable-blink-features=CookieStore"})
+    // TODO(https://crbug.com/968649) Remove switch when CookieStore launched.
     public void testThirdPartyCookiesArePerWebview() throws Throwable {
-        TestWebServer webServer = TestWebServer.start();
+        // Using SSL server here since CookieStore API requires a secure schema.
+        TestWebServer webServer = TestWebServer.startSsl();
         try {
             allowFirstPartyCookies();
             mCookieManager.removeAllCookies();
@@ -905,6 +982,7 @@
         void assertThirdPartyIFrameCookieResult(String suffix, boolean expectedResult)
                 throws Throwable {
             String key = "test" + suffix;
+            String cookieStoreKey = "cookieStoreTest" + suffix;
             String value = "value" + suffix;
             String iframePath = "/iframe_" + suffix + ".html";
             String pagePath = "/content_" + suffix + ".html";
@@ -925,6 +1003,17 @@
                 assertNoCookies(cookieUrl);
             }
 
+            // Try to set via CookieStore API as well.
+            JavaScriptUtils.runJavascriptWithAsyncResult(
+                    mAwContents.getWebContents(), "callIframe('" + cookieStoreKey + "')");
+
+            if (expectedResult) {
+                assertHasCookies(cookieUrl);
+                validateCookies(cookieUrl, key, cookieStoreKey);
+            } else {
+                assertNoCookies(cookieUrl);
+            }
+
             // Clear the cookies.
             clearCookies();
             Assert.assertFalse(mCookieManager.hasCookies());
@@ -932,15 +1021,25 @@
     }
 
     /**
-     * Creates a response on the TestWebServer which attempts to set a cookie when fetched.
+     * Creates a response on the TestWebServer which load a given URL in an iframe,
+     * and provides helpers for forwarding JavaScript calls to that iframe via postMessage.
      * @param  webServer  the webServer on which to create the response
      * @param  path the path component of the url (e.g "/my_thing_with_iframe.html")
      * @param  url the url which which should appear as the src of the iframe.
      * @return  the url which gets the response
      */
     private String makeIframeUrl(TestWebServer webServer, String path, String url) {
-        String responseStr = "<html><head><title>Content!</title></head>"
-                + "<body><iframe src=" + url + "></iframe></body></html>";
+        String responseStr = "<html><head><title>Content!</title>"
+                + "<script>"
+                + "window.onmessage = function(ev) { "
+                + "  window.domAutomationController.send(ev.data); "
+                + "}\n"
+                + "function callIframe(data) { "
+                + "  document.getElementById('if').contentWindow.postMessage("
+                + "      data, '*'); "
+                + "}"
+                + "</script>"
+                + "</head><body><iframe id=if src=" + url + "></iframe></body></html>";
         return webServer.setResponse(path, responseStr, null);
     }
 
@@ -955,11 +1054,33 @@
     private String makeCookieScriptUrl(TestWebServer webServer, String path, String key,
             String value) {
         String response = "<html><head></head><body>"
-                + "<script>document.cookie = \"" + key + "=" + value + "\";</script></body></html>";
+                + "<script>document.cookie = \"" + key + "=" + value + "\";"
+                + "window.onmessage = async function(ev) {"
+                + makeCookieStoreSetFragment(
+                        "ev.data", "'" + value + "'", "ev.source.postMessage(true, '*');")
+                + "}"
+                + "</script></body></html>";
         return webServer.setResponse(path, response, null);
     }
 
     /**
+     * Returns code fragment to be embedded into an async function to set a cookie with CookieStore
+     * API
+     * @param name name of cookie to set
+     * @param value value to set the cookie to
+     * @param finallyAction code to run once set finishes, regardless of success or failure
+     */
+    private String makeCookieStoreSetFragment(String name, String value, String finallyAction) {
+        return "try {"
+                + "  await window.cookieStore.set("
+                + "      " + name + ", " + value + ", "
+                + "      { expires: Date.now() + 3600*1000,"
+                + "        sameSite: 'unrestricted' });"
+                + "} finally {"
+                + "  " + finallyAction + "}\n";
+    }
+
+    /**
      * Makes a url look as if it comes from a different host.
      * @param  url the url to fake.
      * @return  the resulting url after faking.
diff --git a/android_webview/tools/apk_merger.py b/android_webview/tools/apk_merger.py
index 5b95d3d..7be38cdb 100755
--- a/android_webview/tools/apk_merger.py
+++ b/android_webview/tools/apk_merger.py
@@ -3,19 +3,31 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-""" Merges a 64-bit and a 32-bit APK into a single APK
+""" Merges a 32-bit APK into a 32/64-bit APK.
 
-This script is used to merge two APKs which have only 32-bit or 64-bit
-binaries respectively into a APK that has both 32-bit and 64-bit binaries
-for 64-bit Android platform.
+This script is used to make the 32-bit parts of a pure 32-bit APK identical to
+those of an APK built on a 64-bit configuration (whether adding 32-bit parts to
+a pure 64-bit build, or replacing 32-bit parts in a multi-architecture build to
+ensurce consistency with pure 32-bit builds).
 
-You normally don't need this script because GN 64-bit build generates
-such APK for you.
+In an ideal world, the libraries and assets in a pure 32-bit APK would be
+identical to the 32-bit equivalents in a 64-bit-generated APK (with secondary
+ABI). However, this isn't reality. For example, subtle differences due to paths
+yield different native libraries (a benign difference). Accidental differences
+in build configuration yield legitimate differences (impacting functionality and
+binary size). This script overwrites parts of the 64-bit APK with pieces from
+the 32-bit APK, so that the two versions are identical from a 32-bit
+perspective.
+
+Longer term, the 64-bit build configuration should be updated to generate pure
+32-bit APKs. While slightly counter-intuitive, this would ensure that 32-bit
+pieces are identical across the different versions without having to merge
+anything.
 
 To use this script, you need to
 1. Build 32-bit APK as usual.
 2. Build 64-bit APK with GN variable build_apk_secondary_abi=false OR true.
-3. Use this script to merge 2 APKs.
+3. Use this script to merge the 2 APKs.
 
 """
 
@@ -25,7 +37,6 @@
 import logging
 import os
 import pprint
-import re
 import shutil
 import sys
 import tempfile
@@ -102,27 +113,19 @@
   return copy_files
 
 
-def CheckFilesExpected(actual_files, expected_files, component_build):
+def CheckFilesExpected(actual_files, expected_files):
   """ Check that the lists of actual and expected files are the same. """
   actual_file_names = collections.defaultdict(int)
   for f in actual_files:
-    actual_file_names[os.path.basename(f)] += 1
+    actual_file_names[f] += 1
   actual_file_set = set(actual_file_names.iterkeys())
-  expected_file_set = set(expected_files.iterkeys())
+  expected_file_set = set(expected_files)
 
   unexpected_file_set = actual_file_set.difference(expected_file_set)
-  if component_build:
-    unexpected_file_set = set(
-        f for f in unexpected_file_set if not f.endswith('.so'))
   missing_file_set = expected_file_set.difference(actual_file_set)
   duplicate_file_set = set(
       f for f, n in actual_file_names.iteritems() if n > 1)
 
-  # TODO(crbug.com/839191): Remove this once we're plumbing the lib correctly.
-  missing_file_set = set(
-      f for f in missing_file_set if not os.path.basename(f) ==
-      'libarcore_sdk_c.so')
-
   errors = []
   if unexpected_file_set:
     errors.append(
@@ -137,81 +140,38 @@
         "Files don't match expectations:\n%s" % '\n'.join(errors))
 
 
-def AddDiffFiles(diff_files, tmp_dir_32, out_zip, expected_files,
-                 component_build, uncompress_shared_libraries):
+def AddDiffFiles(diff_files, tmp_dir_32, out_zip, uncompress_shared_libraries):
   """ Insert files only present in 32-bit APK into 64-bit APK (tmp_apk). """
   for diff_file in diff_files:
-    if component_build and diff_file.endswith('.so'):
-      compress = not uncompress_shared_libraries
-    else:
-      compress = expected_files[os.path.basename(diff_file)]
+    compress = not uncompress_shared_libraries and diff_file.endswith('.so')
     build_utils.AddToZipHermetic(out_zip,
                                  diff_file,
                                  os.path.join(tmp_dir_32, diff_file),
                                  compress=compress)
 
 
-def GetTargetAbiPath(apk_path, shared_library):
-  with zipfile.ZipFile(apk_path) as z:
-    matches = [p for p in z.namelist() if p.endswith(shared_library)]
-  if len(matches) != 1:
-    raise ApkMergeFailure('Found multiple/no libs for %s: %s' % (
-        shared_library, matches))
-  return matches[0]
-
-
-def GetSecondaryAbi(apk_zipfile, shared_library):
-  ret = ''
-  for name in apk_zipfile.namelist():
-    if os.path.basename(name) == shared_library:
-      abi = re.search('(^lib/)(.+)(/' + shared_library + '$)', name).group(2)
-      # Intentionally not to add 64bit abi because they are not used.
-      if abi == 'armeabi-v7a' or abi == 'armeabi':
-        ret = 'arm64-v8a'
-      elif abi == 'mips':
-        ret = 'mips64'
-      elif abi == 'x86':
-        ret = 'x86_64'
-      else:
-        raise ApkMergeFailure('Unsupported abi ' + abi)
-  if ret == '':
-    raise ApkMergeFailure('Failed to find secondary abi')
-  return ret
-
 def MergeApk(args, tmp_apk, tmp_dir_32, tmp_dir_64):
-  # Expected files to copy from 32- to 64-bit APK together with whether to
-  # compress within the .apk.
-  expected_files = {'snapshot_blob_32.bin': False}
-  if args.shared_library:
-    expected_files[args.shared_library] = not args.uncompress_shared_libraries
-  if args.has_unwind_cfi:
-    expected_files['unwind_cfi_32'] = False
+  # expected_files is the set of 32-bit related files that we expect to differ
+  # between a 32- and 64-bit build. Hence, they will be skipped when seeding the
+  # generated APK with the original 64-bit version, and explicitly copied in
+  # from the 32-bit version.
+  expected_files = []
 
-  # TODO(crbug.com/839191): we should pass this in via script arguments.
-  if not args.loadable_module_32:
-    args.loadable_module_32.append('libarcore_sdk_c.so')
-
-  for f in args.loadable_module_32:
-    expected_files[f] = not args.uncompress_shared_libraries
-
-  for f in args.loadable_module_64:
-    expected_files[f] = not args.uncompress_shared_libraries
-
-  # need to unpack APKs to compare their contents
   assets_path = 'base/assets' if args.bundle else 'assets'
-  exclude_files_64 = ['%s/snapshot_blob_32.bin' % assets_path,
-                      GetTargetAbiPath(args.apk_32bit, args.shared_library)]
-  if 'libcrashpad_handler.so' in expected_files:
-    exclude_files_64.append(
-        GetTargetAbiPath(args.apk_32bit, 'libcrashpad_handler.so'))
-  if 'libcrashpad_handler_trampoline.so' in expected_files:
-    exclude_files_64.append(
-        GetTargetAbiPath(args.apk_32bit, 'libcrashpad_handler_trampoline.so'))
-  if args.has_unwind_cfi:
-    exclude_files_64.append('%s/unwind_cfi_32' % assets_path)
-  UnpackApk(args.apk_64bit, tmp_dir_64, exclude_files_64)
-  UnpackApk(args.apk_32bit, tmp_dir_32)
+  expected_files.append('%s/snapshot_blob_32.bin' % assets_path)
 
+  if args.has_unwind_cfi:
+    expected_files.append('%s/unwind_cfi_32' % assets_path)
+
+  # All native libraries are assumed to differ, and will be merged.
+  with zipfile.ZipFile(args.apk_32bit) as z:
+    expected_files.extend([p for p in z.namelist() if p.endswith('.so')])
+
+  UnpackApk(args.apk_32bit, tmp_dir_32)
+  UnpackApk(args.apk_64bit, tmp_dir_64, expected_files)
+
+  # These are files that we know will be different, and we will hence ignore in
+  # the file comparison.
   ignores = ['META-INF', 'AndroidManifest.xml']
   if args.ignore_classes_dex:
     ignores += ['classes.dex', 'classes2.dex', 'classes3.dex']
@@ -232,26 +192,26 @@
 
   # Check that diff_files match exactly those files we want to insert into
   # the 64-bit APK.
-  CheckFilesExpected(diff_files, expected_files, args.component_build)
+  CheckFilesExpected(diff_files, expected_files)
 
   with zipfile.ZipFile(tmp_apk, 'w') as out_zip:
-    exclude_patterns = ['META-INF/*'] + exclude_files_64
+    exclude_patterns = ['META-INF/*'] + expected_files
 
-    # If there are libraries for which we don't want the 32 bit versions, we
-    # should remove them here.
-    if args.loadable_module_32:
-      exclude_patterns.extend(['*' + f for f in args.loadable_module_32 if
-                               f not in args.loadable_module_64])
-
+    # Build the initial merged APK from the 64-bit APK, excluding all files we
+    # will pull from the 32-bit APK.
     path_transform = (
         lambda p: None if build_utils.MatchesGlob(p, exclude_patterns) else p)
     build_utils.MergeZips(
         out_zip, [args.apk_64bit], path_transform=path_transform)
-    AddDiffFiles(diff_files, tmp_dir_32, out_zip, expected_files,
-                 args.component_build, args.uncompress_shared_libraries)
+
+    # Add the files from the 32-bit APK.
+    AddDiffFiles(diff_files, tmp_dir_32, out_zip,
+                 args.uncompress_shared_libraries)
 
 
 def main():
+  # TODO(cjgrant): Remove obsolete arguments once the build scripts stop
+  # specifying them.
   parser = argparse.ArgumentParser(
       description='Merge a 32-bit APK into a 64-bit APK')
   # Using type=os.path.abspath converts file paths to absolute paths so that
@@ -263,15 +223,13 @@
   parser.add_argument('--keystore_path', required=True, type=os.path.abspath)
   parser.add_argument('--key_name', required=True)
   parser.add_argument('--key_password', required=True)
-  group = parser.add_mutually_exclusive_group(required=True)
-  group.add_argument('--component-build', action='store_true')
-  group.add_argument('--shared_library')
+  parser.add_argument('--component-build', action='store_true')
+  parser.add_argument('--shared_library')
   parser.add_argument('--page-align-shared-libraries', action='store_true',
                       help='Obsolete, but remains for backwards compatibility')
   parser.add_argument('--uncompress-shared-libraries', action='store_true')
   parser.add_argument('--bundle', action='store_true')
   parser.add_argument('--debug', action='store_true')
-  # This option shall only used in debug build, see http://crbug.com/631494.
   parser.add_argument('--ignore-classes-dex', action='store_true')
   parser.add_argument('--has-unwind-cfi', action='store_true',
                       help='Specifies if the 32-bit apk has unwind_cfi file')
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 09daa9b..d33d422 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1404,7 +1404,6 @@
     "//ui/native_theme",
     "//ui/ozone",
     "//ui/platform_window",
-    "//ui/platform_window/mojo",
     "//ui/platform_window/stub",
     "//ui/snapshot",
     "//ui/views/window/vector_icons",
diff --git a/ash/host/ash_window_tree_host_platform.cc b/ash/host/ash_window_tree_host_platform.cc
index 775aa644..bbc1888 100644
--- a/ash/host/ash_window_tree_host_platform.cc
+++ b/ash/host/ash_window_tree_host_platform.cc
@@ -24,7 +24,6 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/transform.h"
-#include "ui/platform_window/mojo/ime_type_converters.h"
 #include "ui/platform_window/platform_ime_controller.h"
 #include "ui/platform_window/platform_window.h"
 #include "ui/platform_window/platform_window_init_properties.h"
diff --git a/ash/login/login_screen_controller.cc b/ash/login/login_screen_controller.cc
index 9a101d1..48eb189 100644
--- a/ash/login/login_screen_controller.cc
+++ b/ash/login/login_screen_controller.cc
@@ -301,6 +301,20 @@
   return &login_data_dispatcher_;
 }
 
+void LoginScreenController::ShowGuestButtonInOobe(bool show) {
+  Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())
+      ->shelf_widget()
+      ->login_shelf_view()
+      ->ShowGuestButtonInOobe(show);
+}
+
+void LoginScreenController::ShowParentAccessButton(bool show) {
+  Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())
+      ->shelf_widget()
+      ->login_shelf_view()
+      ->ShowParentAccessButton(show);
+}
+
 void LoginScreenController::ShowParentAccessWidget(
     const AccountId& child_account_id,
     base::RepeatingCallback<void(bool success)> callback) {
@@ -314,7 +328,7 @@
 
 void LoginScreenController::ShowLockScreen(ShowLockScreenCallback on_shown) {
   OnShow();
-  ash::LockScreen::Show(ash::LockScreen::ScreenType::kLock);
+  LockScreen::Show(LockScreen::ScreenType::kLock);
   std::move(on_shown).Run(true);
 }
 
@@ -330,22 +344,11 @@
   }
 
   OnShow();
-  // TODO(jdufault): rename ash::LockScreen to ash::LoginScreen.
-  ash::LockScreen::Show(ash::LockScreen::ScreenType::kLogin);
+  // TODO(jdufault): rename LockScreen to LoginScreen.
+  LockScreen::Show(LockScreen::ScreenType::kLogin);
   std::move(on_shown).Run(true);
 }
 
-void LoginScreenController::ShowErrorMessage(int32_t login_attempts,
-                                             const std::string& error_text,
-                                             const std::string& help_link_text,
-                                             int32_t help_topic_id) {
-  NOTIMPLEMENTED();
-}
-
-void LoginScreenController::ClearErrors() {
-  NOTIMPLEMENTED();
-}
-
 void LoginScreenController::IsReadyForPassword(
     IsReadyForPasswordCallback callback) {
   std::move(callback).Run(LockScreen::HasInstance() && !IsAuthenticating());
@@ -366,24 +369,6 @@
       ->SetAllowLoginAsGuest(allow_guest);
 }
 
-void LoginScreenController::SetShowGuestButtonInOobe(bool show) {
-  Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())
-      ->shelf_widget()
-      ->login_shelf_view()
-      ->SetShowGuestButtonInOobe(show);
-}
-
-void LoginScreenController::SetShowParentAccessButton(bool show) {
-  Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())
-      ->shelf_widget()
-      ->login_shelf_view()
-      ->SetShowParentAccessButton(show);
-}
-
-void LoginScreenController::SetShowParentAccessDialog(bool show) {
-  login_data_dispatcher_.SetShowParentAccessDialog(show);
-}
-
 void LoginScreenController::FocusLoginShelf(bool reverse) {
   Shelf* shelf = Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow());
   // Tell the focus direction to the status area or the shelf so they can focus
diff --git a/ash/login/login_screen_controller.h b/ash/login/login_screen_controller.h
index ce29018..da03b8c 100644
--- a/ash/login/login_screen_controller.h
+++ b/ash/login/login_screen_controller.h
@@ -110,6 +110,8 @@
 
   // LoginScreen:
   LoginScreenModel* GetModel() override;
+  void ShowGuestButtonInOobe(bool show) override;
+  void ShowParentAccessButton(bool show) override;
   void ShowParentAccessWidget(
       const AccountId& child_account_id,
       base::RepeatingCallback<void(bool success)> callback) override;
@@ -118,19 +120,11 @@
   void SetClient(mojom::LoginScreenClientPtr client) override;
   void ShowLockScreen(ShowLockScreenCallback on_shown) override;
   void ShowLoginScreen(ShowLoginScreenCallback on_shown) override;
-  void ShowErrorMessage(int32_t login_attempts,
-                        const std::string& error_text,
-                        const std::string& help_link_text,
-                        int32_t help_topic_id) override;
-  void ClearErrors() override;
   void IsReadyForPassword(IsReadyForPasswordCallback callback) override;
   void ShowKioskAppError(const std::string& message) override;
   void SetAddUserButtonEnabled(bool enable) override;
   void SetShutdownButtonEnabled(bool enable) override;
   void SetAllowLoginAsGuest(bool allow_guest) override;
-  void SetShowGuestButtonInOobe(bool show) override;
-  void SetShowParentAccessButton(bool show) override;
-  void SetShowParentAccessDialog(bool show) override;
   void FocusLoginShelf(bool reverse) override;
 
   // KioskAppMenu:
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index f2bccf8..f40661a 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -545,6 +545,18 @@
   }
 }
 
+void LockContentsView::ShowParentAccessDialog(bool show) {
+  if (!primary_big_view_)
+    return;
+
+  if (show)
+    primary_big_view_->ShowParentAccessView();
+  else
+    primary_big_view_->HideParentAccessView();
+
+  Layout();
+}
+
 void LockContentsView::Layout() {
   View::Layout();
   LayoutTopHeader();
@@ -1074,19 +1086,6 @@
     GetWidget()->GetFocusManager()->ClearFocus();
 }
 
-void LockContentsView::OnSetShowParentAccessDialog(bool show) {
-  if (!primary_big_view_)
-    return;
-
-  if (show) {
-    primary_big_view_->ShowParentAccessView();
-  } else {
-    primary_big_view_->HideParentAccessView();
-  }
-
-  Layout();
-}
-
 void LockContentsView::OnFocusLeavingLockScreenApps(bool reverse) {
   if (!reverse || lock_screen_apps_active_)
     FocusNextWidget(reverse);
@@ -1735,7 +1734,7 @@
 }
 
 void LockContentsView::OnParentAccessValidationFinished(bool access_granted) {
-  OnSetShowParentAccessDialog(false);
+  ShowParentAccessDialog(false);
 }
 
 keyboard::KeyboardController* LockContentsView::GetKeyboardControllerForView()
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index c47b282..ccfcac3c 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -124,6 +124,7 @@
 
   void FocusNextUser();
   void FocusPreviousUser();
+  void ShowParentAccessDialog(bool show);
 
   // views::View:
   void Layout() override;
@@ -172,7 +173,6 @@
       bool show_full_management_disclosure) override;
   void OnDetachableBasePairingStatusChanged(
       DetachableBasePairingStatus pairing_status) override;
-  void OnSetShowParentAccessDialog(bool show) override;
   void OnFocusLeavingLockScreenApps(bool reverse) override;
   void OnOobeDialogStateChanged(OobeDialogState state) override;
 
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index 7c21325..e7a22a5 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -1946,7 +1946,7 @@
                   .textfield()
                   ->HasFocus());
 
-  DataDispatcher()->SetShowParentAccessDialog(true);
+  contents->ShowParentAccessDialog(true);
 
   EXPECT_TRUE(primary_view->auth_user());
   EXPECT_TRUE(primary_view->parent_access());
@@ -1957,7 +1957,7 @@
       ParentAccessView::TestApi(primary_view->parent_access())
           .access_code_view()));
 
-  DataDispatcher()->SetShowParentAccessDialog(false);
+  contents->ShowParentAccessDialog(false);
 
   EXPECT_TRUE(primary_view->auth_user());
   EXPECT_FALSE(primary_view->parent_access());
diff --git a/ash/login/ui/lock_debug_view.cc b/ash/login/ui/lock_debug_view.cc
index 98819c2..1c2b09e 100644
--- a/ash/login/ui/lock_debug_view.cc
+++ b/ash/login/ui/lock_debug_view.cc
@@ -1036,7 +1036,7 @@
   // Toggle parent access view.
   if (sender->GetID() == ButtonId::kGlobalToggleParentAccess) {
     is_parent_access_shown_ = !is_parent_access_shown_;
-    lock_->OnSetShowParentAccessDialog(is_parent_access_shown_);
+    lock_->ShowParentAccessDialog(is_parent_access_shown_);
   }
 }
 
diff --git a/ash/login/ui/lock_screen.cc b/ash/login/ui/lock_screen.cc
index 7eedb40..f6db4af 100644
--- a/ash/login/ui/lock_screen.cc
+++ b/ash/login/ui/lock_screen.cc
@@ -145,6 +145,10 @@
   contents_view_->FocusPreviousUser();
 }
 
+void LockScreen::ShowParentAccessDialog() {
+  contents_view_->ShowParentAccessDialog(true);
+}
+
 void LockScreen::OnLockScreenNoteStateChanged(mojom::TrayActionState state) {
   Shell::Get()
       ->login_screen_controller()
diff --git a/ash/login/ui/lock_screen.h b/ash/login/ui/lock_screen.h
index 89df403..05761e2 100644
--- a/ash/login/ui/lock_screen.h
+++ b/ash/login/ui/lock_screen.h
@@ -62,6 +62,7 @@
 
   void FocusNextUser();
   void FocusPreviousUser();
+  void ShowParentAccessDialog();
 
   // TrayActionObserver:
   void OnLockScreenNoteStateChanged(mojom::TrayActionState state) override;
diff --git a/ash/login/ui/login_data_dispatcher.cc b/ash/login/ui/login_data_dispatcher.cc
index 4ca7fbb..644a02a 100644
--- a/ash/login/ui/login_data_dispatcher.cc
+++ b/ash/login/ui/login_data_dispatcher.cc
@@ -79,8 +79,6 @@
 void LoginDataDispatcher::Observer::OnDetachableBasePairingStatusChanged(
     DetachableBasePairingStatus pairing_status) {}
 
-void LoginDataDispatcher::Observer::OnSetShowParentAccessDialog(bool show) {}
-
 void LoginDataDispatcher::Observer::OnFocusLeavingLockScreenApps(bool reverse) {
 }
 
@@ -228,11 +226,6 @@
     observer.OnDetachableBasePairingStatusChanged(pairing_status);
 }
 
-void LoginDataDispatcher::SetShowParentAccessDialog(bool show) {
-  for (auto& observer : observers_)
-    observer.OnSetShowParentAccessDialog(show);
-}
-
 void LoginDataDispatcher::HandleFocusLeavingLockScreenApps(bool reverse) {
   for (auto& observer : observers_)
     observer.OnFocusLeavingLockScreenApps(reverse);
diff --git a/ash/login/ui/login_data_dispatcher.h b/ash/login/ui/login_data_dispatcher.h
index fe66da28..64f8dad 100644
--- a/ash/login/ui/login_data_dispatcher.h
+++ b/ash/login/ui/login_data_dispatcher.h
@@ -127,9 +127,6 @@
     virtual void OnDetachableBasePairingStatusChanged(
         DetachableBasePairingStatus pairing_status);
 
-    // Called when parent access code input dialog visibility should change.
-    virtual void OnSetShowParentAccessDialog(bool show);
-
     // Called when focus is leaving a lock screen app window due to tabbing.
     // |reverse| - whether the tab order is reversed.
     virtual void OnFocusLeavingLockScreenApps(bool reverse);
@@ -187,7 +184,6 @@
       bool show_full_management_disclosure) override;
   void SetDetachableBasePairingStatus(
       DetachableBasePairingStatus pairing_status);
-  void SetShowParentAccessDialog(bool show);
   void HandleFocusLeavingLockScreenApps(bool reverse) override;
   void NotifyOobeDialogState(OobeDialogState state) override;
 
diff --git a/ash/mojo_interface_factory.cc b/ash/mojo_interface_factory.cc
index a0f351fb..cec4b408 100644
--- a/ash/mojo_interface_factory.cc
+++ b/ash/mojo_interface_factory.cc
@@ -34,7 +34,6 @@
 #include "ash/system/night_light/night_light_controller.h"
 #include "ash/tray_action/tray_action.h"
 #include "ash/voice_interaction/voice_interaction_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/lazy_instance.h"
@@ -147,11 +146,6 @@
   Shell::Get()->shutdown_controller()->BindRequest(std::move(request));
 }
 
-void BindTabletModeRequestOnMainThread(
-    mojom::TabletModeControllerRequest request) {
-  Shell::Get()->tablet_mode_controller()->BindRequest(std::move(request));
-}
-
 void BindTrayActionRequestOnMainThread(mojom::TrayActionRequest request) {
   Shell::Get()->tray_action()->BindRequest(std::move(request));
 }
@@ -233,9 +227,6 @@
       base::BindRepeating(&BindShutdownControllerRequestOnMainThread),
       main_thread_task_runner);
   registry->AddInterface(
-      base::BindRepeating(&BindTabletModeRequestOnMainThread),
-      main_thread_task_runner);
-  registry->AddInterface(
       base::BindRepeating(&BindTrayActionRequestOnMainThread),
       main_thread_task_runner);
   registry->AddInterface(
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index ee82c1e..7ae5f27b 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -151,6 +151,7 @@
     "system_tray_focus_observer.h",
     "tablet_mode.cc",
     "tablet_mode.h",
+    "tablet_mode_toggle_observer.h",
     "touch_uma.cc",
     "touch_uma.h",
     "wallpaper_controller.cc",
diff --git a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
index 8be9d51..b19c9c6e 100644
--- a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
+++ b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
@@ -403,7 +403,7 @@
     }
   } else if (sender == close_button_) {
     frame_->Close();
-    if (TabletMode::IsEnabled())
+    if (TabletMode::Get()->IsEnabled())
       RecordAction(UserMetricsAction("Tablet_WindowCloseFromCaptionButton"));
     else
       RecordAction(UserMetricsAction("CloseButton_Clk"));
diff --git a/ash/public/cpp/login_screen.h b/ash/public/cpp/login_screen.h
index 920979d..36ba948 100644
--- a/ash/public/cpp/login_screen.h
+++ b/ash/public/cpp/login_screen.h
@@ -26,6 +26,12 @@
 
   virtual LoginScreenModel* GetModel() = 0;
 
+  // Shows or hides the guest button on the login shelf during OOBE.
+  virtual void ShowGuestButtonInOobe(bool show) = 0;
+
+  // Shows or hides the parent access button on the login shelf.
+  virtual void ShowParentAccessButton(bool show) = 0;
+
   // Shows a standalone Parent Access dialog. If |child_account_id| is valid, it
   // validates the parent access code for that child only, when it is  empty it
   // validates the code for any child signed in the device. |callback| is
diff --git a/ash/public/cpp/manifest.cc b/ash/public/cpp/manifest.cc
index 704e1e8..6a1d01e6 100644
--- a/ash/public/cpp/manifest.cc
+++ b/ash/public/cpp/manifest.cc
@@ -21,7 +21,6 @@
 #include "ash/public/interfaces/night_light_controller.mojom.h"
 #include "ash/public/interfaces/shelf_integration_test_api.mojom.h"
 #include "ash/public/interfaces/shutdown.mojom.h"
-#include "ash/public/interfaces/tablet_mode.mojom.h"
 #include "ash/public/interfaces/tray_action.mojom.h"
 #include "ash/public/interfaces/voice_interaction_controller.mojom.h"
 #include "ash/public/interfaces/vpn_list.mojom.h"
@@ -70,8 +69,8 @@
                   mojom::KeyboardController, mojom::LocaleUpdateController,
                   mojom::LoginScreen, mojom::MediaController,
                   mojom::NightLightController, mojom::ShutdownController,
-                  mojom::TabletModeController, mojom::TrayAction,
-                  mojom::VoiceInteractionController, mojom::VpnList>())
+                  mojom::TrayAction, mojom::VoiceInteractionController,
+                  mojom::VpnList>())
           .ExposeCapability("test", service_manager::Manifest::InterfaceList<
                                         mojom::ShelfIntegrationTestApi>())
           .RequireCapability("*", "accessibility")
diff --git a/ash/public/cpp/tablet_mode.cc b/ash/public/cpp/tablet_mode.cc
index 91efb38..a0e5be2 100644
--- a/ash/public/cpp/tablet_mode.cc
+++ b/ash/public/cpp/tablet_mode.cc
@@ -4,29 +4,26 @@
 
 #include "ash/public/cpp/tablet_mode.h"
 
-#include "base/callback.h"
-#include "base/no_destructor.h"
+#include "base/logging.h"
 
 namespace ash {
 
 namespace {
-
-TabletMode::TabletModeCallback* GetCallback() {
-  static base::NoDestructor<TabletMode::TabletModeCallback> callback;
-  return callback.get();
+TabletMode* g_instance = nullptr;
 }
 
-}  // namespace
-
-// static
-void TabletMode::SetCallback(TabletModeCallback callback) {
-  DCHECK(GetCallback()->is_null() || callback.is_null());
-  *GetCallback() = std::move(callback);
+TabletMode* TabletMode::Get() {
+  return g_instance;
 }
 
-// static
-bool TabletMode::IsEnabled() {
-  return GetCallback()->Run();
+TabletMode::TabletMode() {
+  DCHECK_EQ(nullptr, g_instance);
+  g_instance = this;
+}
+
+TabletMode::~TabletMode() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
 }
 
 }  // namespace ash
diff --git a/ash/public/cpp/tablet_mode.h b/ash/public/cpp/tablet_mode.h
index ac59f37..d330e2e 100644
--- a/ash/public/cpp/tablet_mode.h
+++ b/ash/public/cpp/tablet_mode.h
@@ -6,25 +6,29 @@
 #define ASH_PUBLIC_CPP_TABLET_MODE_H_
 
 #include "ash/public/cpp/ash_public_export.h"
-#include "base/callback_forward.h"
-#include "base/macros.h"
 
 namespace ash {
 
-// A utility to allow code in //ash/public/cpp to access the tablet mode state,
-// regardless of what process it's running in.
-class TabletMode {
- public:
-  using TabletModeCallback = base::RepeatingCallback<bool(void)>;
+class TabletModeToggleObserver;
 
-  // Sets the callback to be run by IsEnabled().
-  static void ASH_PUBLIC_EXPORT SetCallback(TabletModeCallback callback);
+// An interface implemented by Ash that allows Chrome to be informed of changes
+// to tablet mode state.
+class ASH_PUBLIC_EXPORT TabletMode {
+ public:
+  // Returns the singleton instance.
+  static TabletMode* Get();
+
+  virtual void SetTabletModeToggleObserver(
+      TabletModeToggleObserver* observer) = 0;
 
   // Returns whether the system is in tablet mode.
-  static bool IsEnabled();
+  virtual bool IsEnabled() const = 0;
 
- private:
-  DISALLOW_IMPLICIT_CONSTRUCTORS(TabletMode);
+  virtual void SetEnabledForTest(bool enabled) = 0;
+
+ protected:
+  TabletMode();
+  virtual ~TabletMode();
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/tablet_mode_toggle_observer.h b/ash/public/cpp/tablet_mode_toggle_observer.h
new file mode 100644
index 0000000..4bd0d20
--- /dev/null
+++ b/ash/public/cpp/tablet_mode_toggle_observer.h
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_TABLET_MODE_TOGGLE_OBSERVER_H_
+#define ASH_PUBLIC_CPP_TABLET_MODE_TOGGLE_OBSERVER_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+
+namespace ash {
+
+// A simplified observer which allows Ash to inform Chrome when tablet mode has
+// been enabled or disabled.
+class ASH_PUBLIC_EXPORT TabletModeToggleObserver {
+ public:
+  // Fired after the tablet mode has been toggled.
+  virtual void OnTabletModeToggled(bool enabled) = 0;
+
+ protected:
+  virtual ~TabletModeToggleObserver() = default;
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_TABLET_MODE_TOGGLE_OBSERVER_H_
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index 4b68ddc..41cb9a7 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -37,7 +37,6 @@
     "night_light_controller.mojom",
     "shelf_integration_test_api.mojom",
     "shutdown.mojom",
-    "tablet_mode.mojom",
     "tray_action.mojom",
     "update.mojom",
     "voice_interaction_controller.mojom",
diff --git a/ash/public/interfaces/login_screen.mojom b/ash/public/interfaces/login_screen.mojom
index a194c40..67e5d53 100644
--- a/ash/public/interfaces/login_screen.mojom
+++ b/ash/public/interfaces/login_screen.mojom
@@ -24,21 +24,6 @@
   // successfully displayed.
   ShowLoginScreen() => (bool did_show);
 
-  // Requests to show error message in the ash lock screen.
-  // TODO(xiaoyinh): login_attempts is probably not needed from chrome,
-  // remove it when we start to count the login attempts in ash lock screen.
-  // |login_attempts|: The number of the login authentication attempts.
-  // |error_text|:     The error text to be shown in lock screen.
-  // |help_link_text|: The help link to be shown in lock screen.
-  // |help_topic_id|:  The id of the help app topic regarding this error.
-  ShowErrorMessage(int32 login_attempts,
-                   string error_text,
-                   string help_link_text,
-                   int32 help_topic_id);
-
-  // Requests to close any displayed error messages in ash lock screen.
-  ClearErrors();
-
   // Check if the login/lock screen is ready for a password.
   IsReadyForPassword() => (bool is_ready);
 
@@ -55,15 +40,6 @@
   // true the button may still not be visible.
   SetAllowLoginAsGuest(bool allow_guest);
 
-  // Sets if the guest button on the login shelf can be shown during OOBE.
-  SetShowGuestButtonInOobe(bool show);
-
-  // Sets whether parent access button can be shown on the login shelf.
-  SetShowParentAccessButton(bool show);
-
-  // Sets whether parent access code input dialog is shown on the lock screen.
-  SetShowParentAccessDialog(bool show);
-
   // Transitions focus to the shelf area. If |reverse|, focuses the status area.
   FocusLoginShelf(bool reverse);
 };
diff --git a/ash/public/interfaces/tablet_mode.mojom b/ash/public/interfaces/tablet_mode.mojom
deleted file mode 100644
index 0e98802..0000000
--- a/ash/public/interfaces/tablet_mode.mojom
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module ash.mojom;
-
-// Controls tablet mode state in a client app (e.g. chrome).
-interface TabletModeClient {
-  // Fired after the tablet mode has been toggled.
-  OnTabletModeToggled(bool enabled);
-};
-
-// Controls tablet mode state in ash.
-interface TabletModeController {
-  // Sets a client (e.g. chrome). Triggers OnTabletModeToggled() to provide
-  // the initial state.
-  SetClient(TabletModeClient client);
-
-  // Enables or disables tablet mode. For testing only.
-  SetTabletModeEnabledForTesting(bool enabled) => (bool enabled);
-};
diff --git a/ash/shelf/login_shelf_view.cc b/ash/shelf/login_shelf_view.cc
index d62ae0f..6b14e1d 100644
--- a/ash/shelf/login_shelf_view.cc
+++ b/ash/shelf/login_shelf_view.cc
@@ -496,7 +496,7 @@
       StartAddUser();
       break;
     case kParentAccess:
-      Shell::Get()->login_screen_controller()->SetShowParentAccessDialog(true);
+      LockScreen::Get()->ShowParentAccessDialog();
       break;
     default:
       NOTREACHED();
@@ -549,12 +549,12 @@
   UpdateUi();
 }
 
-void LoginShelfView::SetShowParentAccessButton(bool show) {
+void LoginShelfView::ShowParentAccessButton(bool show) {
   show_parent_access_ = show;
   UpdateUi();
 }
 
-void LoginShelfView::SetShowGuestButtonInOobe(bool show) {
+void LoginShelfView::ShowGuestButtonInOobe(bool show) {
   allow_guest_in_oobe_ = show;
   UpdateUi();
 }
diff --git a/ash/shelf/login_shelf_view.h b/ash/shelf/login_shelf_view.h
index 56215e63..398426e 100644
--- a/ash/shelf/login_shelf_view.h
+++ b/ash/shelf/login_shelf_view.h
@@ -90,11 +90,11 @@
   void SetAllowLoginAsGuest(bool allow_guest);
 
   // Sets whether parent access button can be shown on the login shelf.
-  void SetShowParentAccessButton(bool show);
+  void ShowParentAccessButton(bool show);
 
   // Sets if the guest button on the login shelf can be shown during gaia
   // signin screen.
-  void SetShowGuestButtonInOobe(bool show);
+  void ShowGuestButtonInOobe(bool show);
 
   // Sets whether users can be added from the login screen.
   void SetAddUserButtonEnabled(bool enable_add_user);
diff --git a/ash/shelf/login_shelf_view_unittest.cc b/ash/shelf/login_shelf_view_unittest.cc
index cfec9bf..e23ebd26 100644
--- a/ash/shelf/login_shelf_view_unittest.cc
+++ b/ash/shelf/login_shelf_view_unittest.cc
@@ -525,7 +525,7 @@
 
 TEST_F(LoginShelfViewTest, ParentAccessButtonVisibility) {
   // Parent access button should only be visible on lock screen.
-  Shell::Get()->login_screen_controller()->SetShowParentAccessButton(true);
+  Shell::Get()->login_screen_controller()->ShowParentAccessButton(true);
 
   NotifySessionStateChanged(SessionState::LOGIN_PRIMARY);
   EXPECT_TRUE(ShowsShelfButtons({LoginShelfView::kShutdown,
@@ -556,12 +556,12 @@
   EXPECT_TRUE(
       ShowsShelfButtons({LoginShelfView::kShutdown, LoginShelfView::kSignOut}));
 
-  Shell::Get()->login_screen_controller()->SetShowParentAccessButton(true);
+  Shell::Get()->login_screen_controller()->ShowParentAccessButton(true);
   EXPECT_TRUE(
       ShowsShelfButtons({LoginShelfView::kShutdown, LoginShelfView::kSignOut,
                          LoginShelfView::kParentAccess}));
 
-  Shell::Get()->login_screen_controller()->SetShowParentAccessButton(false);
+  Shell::Get()->login_screen_controller()->ShowParentAccessButton(false);
   EXPECT_TRUE(
       ShowsShelfButtons({LoginShelfView::kShutdown, LoginShelfView::kSignOut}));
 }
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 1db757b..69ce386d 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -699,6 +699,16 @@
   is_home_launcher_shown_ = shown;
   is_home_launcher_target_position_shown_ = false;
   MaybeUpdateShelfBackground(AnimationChangeType::IMMEDIATE);
+
+  // Cancel ongoing drag when self is hidden on home screen to prevent
+  // visibility issues.
+  if (shown && drag_status_ != kDragNone &&
+      !Shell::Get()
+           ->home_screen_controller()
+           ->delegate()
+           ->ShouldShowShelfOnHomeScreen()) {
+    CancelDrag();
+  }
 }
 
 void ShelfLayoutManager::OnWindowActivated(ActivationReason reason,
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index b545db8..e87e1da 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -111,7 +111,8 @@
     bluez_dbus_manager_initialized_ = true;
   }
 
-  chromeos::PowerManagerClient::InitializeFake();
+  if (!chromeos::PowerManagerClient::Get())
+    chromeos::PowerManagerClient::InitializeFake();
 
   if (!chromeos::PowerPolicyController::IsInitialized()) {
     chromeos::PowerPolicyController::Initialize(
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index 8ccb240..e7fe0a7 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/fps_counter.h"
 #include "ash/public/cpp/tablet_mode.h"
+#include "ash/public/cpp/tablet_mode_toggle_observer.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
@@ -109,7 +110,7 @@
          kNoisyMagnitudeDeviation;
 }
 
-bool IsEnabled() {
+bool ShouldInitTabletModeController() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       switches::kAshEnableTabletMode);
 }
@@ -171,10 +172,7 @@
 TabletModeController::TabletModeController()
     : event_blocker_(new InternalInputDevicesEventBlocker),
       tablet_mode_usage_interval_start_time_(base::Time::Now()),
-      tick_clock_(base::DefaultTickClock::GetInstance()),
-      binding_(this),
-      scoped_session_observer_(this),
-      weak_factory_(this) {
+      tick_clock_(base::DefaultTickClock::GetInstance()) {
   Shell::Get()->AddShellObserver(this);
   base::RecordAction(base::UserMetricsAction("Touchview_Initially_Disabled"));
 
@@ -182,7 +180,7 @@
   // unavailable. This will require refactoring
   // IsTabletModeWindowManagerEnabled to check for the existence of the
   // controller.
-  if (IsEnabled()) {
+  if (ShouldInitTabletModeController()) {
     Shell::Get()->window_tree_host_manager()->AddObserver(this);
     AccelerometerReader::GetInstance()->AddObserver(this);
     ui::InputDeviceManager::GetInstance()->AddObserver(this);
@@ -199,10 +197,6 @@
   power_manager_client->AddObserver(this);
   power_manager_client->GetSwitchStates(base::BindOnce(
       &TabletModeController::OnGetSwitchStates, weak_factory_.GetWeakPtr()));
-
-  TabletMode::SetCallback(base::BindRepeating(
-      &TabletModeController::IsTabletModeWindowManagerEnabled,
-      base::Unretained(this)));
 }
 
 TabletModeController::~TabletModeController() {
@@ -222,7 +216,7 @@
   Shell::Get()->RemoveShellObserver(this);
   Shell::Get()->kiosk_next_shell_controller()->RemoveObserver(this);
 
-  if (IsEnabled()) {
+  if (ShouldInitTabletModeController()) {
     Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
     AccelerometerReader::GetInstance()->RemoveObserver(this);
     ui::InputDeviceManager::GetInstance()->RemoveObserver(this);
@@ -231,8 +225,6 @@
 
   for (auto& observer : tablet_mode_observers_)
     observer.OnTabletControllerDestroyed();
-
-  TabletMode::SetCallback(TabletMode::TabletModeCallback());
 }
 
 // TODO(jcliang): Hide or remove EnableTabletModeWindowManager
@@ -273,8 +265,8 @@
     }
 
     state_ = State::kInTabletMode;
-    if (client_)  // Null at startup and in tests.
-      client_->OnTabletModeToggled(true);
+    if (toggle_observer_)  // Null at startup and in tests.
+      toggle_observer_->OnTabletModeToggled(true);
     VLOG(1) << "Enter tablet mode.";
   } else {
     state_ = State::kExitingTabletMode;
@@ -292,8 +284,8 @@
       observer.OnTabletModeEnded();
 
     state_ = State::kInClamshellMode;
-    if (client_)  // Null at startup and in tests.
-      client_->OnTabletModeToggled(false);
+    if (toggle_observer_)  // Null at startup and in tests.
+      toggle_observer_->OnTabletModeToggled(false);
     VLOG(1) << "Exit tablet mode.";
   }
 
@@ -309,12 +301,6 @@
     tablet_mode_window_manager_->AddWindow(window);
 }
 
-void TabletModeController::BindRequest(
-    mojom::TabletModeControllerRequest request) {
-  DCHECK(!binding_.is_bound()) << "Only one client allowed.";
-  binding_.Bind(std::move(request));
-}
-
 void TabletModeController::AddObserver(TabletModeObserver* observer) {
   tablet_mode_observers_.AddObserver(observer);
 }
@@ -337,10 +323,6 @@
   return event_blocker_->should_be_blocked();
 }
 
-void TabletModeController::FlushForTesting() {
-  binding_.FlushForTesting();
-}
-
 bool TabletModeController::TriggerRecordLidAngleTimerForTesting() {
   if (!record_lid_angle_timer_.IsRunning())
     return false;
@@ -380,6 +362,28 @@
       window->layer()->GetCompositor(), entering_tablet_mode);
 }
 
+void TabletModeController::SetTabletModeToggleObserver(
+    TabletModeToggleObserver* observer) {
+  DCHECK(observer);
+  DCHECK(!toggle_observer_);
+  toggle_observer_ = observer;
+}
+
+bool TabletModeController::IsEnabled() const {
+  return IsTabletModeWindowManagerEnabled();
+}
+
+void TabletModeController::SetEnabledForTest(bool enabled) {
+  // Disable Accelerometer and PowerManagerClient observers to prevent possible
+  // tablet mode overrides. It won't be possible to physically switch to/from
+  // tablet mode after calling this function. This is needed for tests that
+  // run on DUTs and require switching to/back tablet mode in runtime, like some
+  // ARC++ Tast tests.
+  AccelerometerReader::GetInstance()->RemoveObserver(this);
+  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
+  EnableTabletModeWindowManager(enabled);
+}
+
 void TabletModeController::OnShellInitialized() {
   force_ui_mode_ = GetTabletMode();
   if (force_ui_mode_ == UiMode::kTabletMode)
@@ -519,7 +523,7 @@
   // Stop listening to any incoming input device changes during suspend as the
   // input devices may be removed during suspend and cause the device enter/exit
   // tablet mode unexpectedly.
-  if (IsEnabled()) {
+  if (ShouldInitTabletModeController()) {
     ui::InputDeviceManager::GetInstance()->RemoveObserver(this);
     bluetooth_devices_observer_.reset();
   }
@@ -530,7 +534,7 @@
   tablet_mode_usage_interval_start_time_ = base::Time::Now();
 
   // Start listening to the input device changes again.
-  if (IsEnabled()) {
+  if (ShouldInitTabletModeController()) {
     bluetooth_devices_observer_ =
         std::make_unique<BluetoothDevicesObserver>(base::BindRepeating(
             &TabletModeController::OnBluetoothAdapterOrDeviceChanged,
@@ -748,26 +752,6 @@
   return TABLET_MODE_INTERVAL_INACTIVE;
 }
 
-void TabletModeController::SetClient(mojom::TabletModeClientPtr client) {
-  client_ = std::move(client);
-  client_->OnTabletModeToggled(IsTabletModeWindowManagerEnabled());
-}
-
-// Used for testing. Called via Mojo.
-void TabletModeController::SetTabletModeEnabledForTesting(
-    bool enabled,
-    SetTabletModeEnabledForTestingCallback callback) {
-  // Disable Accelerometer and PowerManagerClient observers to prevent possible
-  // tablet mode overrides. It won't be possible to physically switch to/from
-  // tablet mode after calling this function. This is needed for tests that
-  // run on DUTs and require switching to/back tablet mode in runtime, like some
-  // ARC++ Tast tests.
-  AccelerometerReader::GetInstance()->RemoveObserver(this);
-  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
-  EnableTabletModeWindowManager(enabled);
-  std::move(callback).Run(IsTabletModeWindowManagerEnabled());
-}
-
 bool TabletModeController::AllowUiModeChange() const {
   return force_ui_mode_ == UiMode::kNone && !kiosk_next_enabled_;
 }
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.h b/ash/wm/tablet_mode/tablet_mode_controller.h
index ac41bc8..4893d59 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller.h
@@ -13,7 +13,7 @@
 #include "ash/bluetooth_devices_observer.h"
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/kiosk_next/kiosk_next_shell_observer.h"
-#include "ash/public/interfaces/tablet_mode.mojom.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/session/session_observer.h"
 #include "ash/shell_observer.h"
 #include "base/compiler_specific.h"
@@ -24,8 +24,6 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chromeos/dbus/power/power_manager_client.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "ui/aura/window_observer.h"
 #include "ui/aura/window_occlusion_tracker.h"
 #include "ui/compositor/layer_animation_observer.h"
@@ -65,7 +63,7 @@
 class ASH_EXPORT TabletModeController
     : public AccelerometerReader::Observer,
       public chromeos::PowerManagerClient::Observer,
-      public mojom::TabletModeController,
+      public TabletMode,
       public ShellObserver,
       public WindowTreeHostManager::Observer,
       public SessionObserver,
@@ -107,9 +105,6 @@
   // If the tablet mode is not enabled no action will be performed.
   void AddWindow(aura::Window* window);
 
-  // Binds the mojom::TabletModeController interface request to this object.
-  void BindRequest(mojom::TabletModeControllerRequest request);
-
   void AddObserver(TabletModeObserver* observer);
   void RemoveObserver(TabletModeObserver* observer);
 
@@ -119,9 +114,6 @@
   // Whether the events from the internal mouse/keyboard are blocked.
   bool AreInternalInputDeviceEventsBlocked() const;
 
-  // Flushes the mojo message pipe to chrome.
-  void FlushForTesting();
-
   // If |record_lid_angle_timer_| is running, invokes its task and returns true.
   // Otherwise, returns false.
   bool TriggerRecordLidAngleTimerForTesting() WARN_UNUSED_RESULT;
@@ -129,6 +121,11 @@
   // Called from a WindowState object when the bounds of |window| changes.
   void MaybeObserveBoundsAnimation(aura::Window* window);
 
+  // TabletMode:
+  void SetTabletModeToggleObserver(TabletModeToggleObserver* observer) override;
+  bool IsEnabled() const override;
+  void SetEnabledForTest(bool enabled) override;
+
   // ShellObserver:
   void OnShellInitialized() override;
 
@@ -238,12 +235,6 @@
   // otherwise returns TABLET_MODE_INTERNAL_INACTIVE.
   TabletModeIntervalType CurrentTabletModeIntervalType();
 
-  // mojom::TabletModeController:
-  void SetClient(mojom::TabletModeClientPtr client) override;
-  void SetTabletModeEnabledForTesting(
-      bool enabled,
-      SetTabletModeEnabledForTestingCallback callback) override;
-
   // Checks whether we want to allow change the current ui mode to tablet mode
   // or clamshell mode. This returns false if the user set a flag for the
   // software to behave in a certain way regardless of configuration.
@@ -351,11 +342,9 @@
   gfx::Vector3dF base_smoothed_;
   gfx::Vector3dF lid_smoothed_;
 
-  // Binding for the TabletModeController interface.
-  mojo::Binding<mojom::TabletModeController> binding_;
-
-  // Client interface (e.g. in chrome).
-  mojom::TabletModeClientPtr client_;
+  // A simplified observer that only gets notified of entering or exiting tablet
+  // mode.
+  TabletModeToggleObserver* toggle_observer_ = nullptr;
 
   // Tracks whether a flag is used to force ui mode.
   UiMode force_ui_mode_ = UiMode::kNone;
@@ -365,7 +354,7 @@
   // Calls RecordLidAngle() periodically.
   base::RepeatingTimer record_lid_angle_timer_;
 
-  ScopedSessionObserver scoped_session_observer_;
+  ScopedSessionObserver scoped_session_observer_{this};
 
   std::unique_ptr<aura::WindowOcclusionTracker::ScopedPause>
       occlusion_tracker_pauser_;
@@ -385,7 +374,7 @@
 
   base::ObserverList<TabletModeObserver>::Unchecked tablet_mode_observers_;
 
-  base::WeakPtrFactory<TabletModeController> weak_factory_;
+  base::WeakPtrFactory<TabletModeController> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TabletModeController);
 };
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index 159052a..e6c8b35 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -665,20 +665,26 @@
 
 // Tests that when a TabletModeController is created that cached tablet mode
 // state will trigger a mode update.
-TEST_F(TabletModeControllerTest, InitializedWhileTabletModeSwitchOn) {
-  base::RunLoop().RunUntilIdle();
-  power_manager_client()->SetTabletMode(
-      chromeos::PowerManagerClient::TabletMode::ON, base::TimeTicks::Now());
+class TabletModeControllerInitedFromPowerManagerClientTest
+    : public TabletModeControllerTest {
+ public:
+  TabletModeControllerInitedFromPowerManagerClientTest() = default;
+  ~TabletModeControllerInitedFromPowerManagerClientTest() override = default;
 
-  // Clear the callback that was set by the original TabletModeController.
-  TabletMode::SetCallback({});
+  void SetUp() override {
+    chromeos::PowerManagerClient::InitializeFake();
+    power_manager_client()->SetTabletMode(
+        chromeos::PowerManagerClient::TabletMode::ON, base::TimeTicks::Now());
+    TabletModeControllerTest::SetUp();
+  }
+};
 
-  TabletModeController controller;
-  controller.OnShellInitialized();
-  EXPECT_FALSE(controller.IsTabletModeWindowManagerEnabled());
+TEST_F(TabletModeControllerInitedFromPowerManagerClientTest,
+       InitializedWhileTabletModeSwitchOn) {
+  EXPECT_FALSE(tablet_mode_controller()->IsTabletModeWindowManagerEnabled());
   // PowerManagerClient callback is a posted task.
   base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(controller.IsTabletModeWindowManagerEnabled());
+  EXPECT_TRUE(tablet_mode_controller()->IsTabletModeWindowManagerEnabled());
 }
 
 TEST_F(TabletModeControllerTest, RestoreAfterExit) {
diff --git a/ash/wm/window_mirror_view.cc b/ash/wm/window_mirror_view.cc
index 7006a4c..fc988f4 100644
--- a/ash/wm/window_mirror_view.cc
+++ b/ash/wm/window_mirror_view.cc
@@ -102,17 +102,14 @@
   target_ = GetWidget()->GetNativeWindow();
   target_->TrackOcclusionState();
 
-  force_occlusion_tracker_visible_.reset();
-  env_observer_.RemoveAll();
-
-  // Wait for window-occlusion tracker to be running before forcing visibility.
-  // This is done to minimize the amount of work during the initial animation
-  // when entering overview. In particular, telling the remote client it is
-  // visible is likely to result in a fair amount of work.
-  if (aura::Env::GetInstance()->GetWindowOcclusionTracker()->IsPaused())
-    env_observer_.Add(aura::Env::GetInstance());
-  else
-    ForceVisibilityAndOcclusion();
+  if (source_) {
+    // Force the occlusion tracker to treat the source as visible.
+    force_occlusion_tracker_visible_ =
+        std::make_unique<aura::WindowOcclusionTracker::ScopedForceVisible>(
+            source_);
+  } else {
+    force_occlusion_tracker_visible_.reset();
+  }
 }
 
 void WindowMirrorView::RemovedFromWidget() {
@@ -163,26 +160,5 @@
   return client_view->ConvertRectToWidget(client_view->GetLocalBounds());
 }
 
-void WindowMirrorView::ForceVisibilityAndOcclusion() {
-  // Force the occlusion tracker to treat the source (or the desk container if
-  // source_ is on an inactive desk) as visible.
-  aura::Window* window_to_force_visible = source_;
-  aura::Window* desk_container =
-      desks_util::GetDeskContainerForContext(source_);
-  if (desk_container && !desks_util::IsActiveDeskContainer(desk_container))
-    window_to_force_visible = desk_container;
-
-  force_occlusion_tracker_visible_ =
-      std::make_unique<aura::WindowOcclusionTracker::ScopedForceVisible>(
-          window_to_force_visible);
-}
-
-void WindowMirrorView::OnWindowOcclusionTrackingResumed() {
-  // Skip if the source_ has already been removed.
-  if (source_)
-    ForceVisibilityAndOcclusion();
-  env_observer_.RemoveAll();
-}
-
 }  // namespace wm
 }  // namespace ash
diff --git a/ash/wm/window_mirror_view.h b/ash/wm/window_mirror_view.h
index d682a42..cdd0062 100644
--- a/ash/wm/window_mirror_view.h
+++ b/ash/wm/window_mirror_view.h
@@ -9,9 +9,6 @@
 
 #include "ash/ash_export.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
-#include "ui/aura/env.h"
-#include "ui/aura/env_observer.h"
 #include "ui/aura/window_observer.h"
 #include "ui/aura/window_occlusion_tracker.h"
 #include "ui/views/view.h"
@@ -29,8 +26,7 @@
 
 // A view that mirrors the client area of a single (source) window.
 class ASH_EXPORT WindowMirrorView : public views::View,
-                                    public aura::WindowObserver,
-                                    public aura::EnvObserver {
+                                    public aura::WindowObserver {
  public:
   WindowMirrorView(aura::Window* source, bool trilinear_filtering_on_init);
   ~WindowMirrorView() override;
@@ -63,11 +59,6 @@
   // coordinate space.
   gfx::Rect GetClientAreaBounds() const;
 
-  void ForceVisibilityAndOcclusion();
-
-  // aura::EnvObserver:
-  void OnWindowOcclusionTrackingResumed() override;
-
   // The original window that is being represented by |this|.
   aura::Window* source_;
 
@@ -85,8 +76,6 @@
   std::unique_ptr<aura::WindowOcclusionTracker::ScopedForceVisible>
       force_occlusion_tracker_visible_;
 
-  ScopedObserver<aura::Env, aura::EnvObserver> env_observer_{this};
-
   DISALLOW_COPY_AND_ASSIGN(WindowMirrorView);
 };
 
diff --git a/ash/wm/workspace/multi_window_resize_controller_unittest.cc b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
index 9edb6f7..5b6fd5c7 100644
--- a/ash/wm/workspace/multi_window_resize_controller_unittest.cc
+++ b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
@@ -320,14 +320,38 @@
   gfx::Rect bounds(resize_widget()->GetWindowBoundsInScreen());
   generator->MoveMouseTo(bounds.x() + 1, bounds.y() + 1);
   generator->PressLeftButton();
+
+  // Test that when drag starts, drag details are created for each window.
+  EXPECT_TRUE(wm::GetWindowState(w1.get())->is_dragged());
+  EXPECT_TRUE(wm::GetWindowState(w2.get())->is_dragged());
+  EXPECT_TRUE(wm::GetWindowState(w3.get())->is_dragged());
+  // Test the window components for each window.
+  EXPECT_EQ(wm::GetWindowState(w1.get())->drag_details()->window_component,
+            HTRIGHT);
+  EXPECT_EQ(wm::GetWindowState(w2.get())->drag_details()->window_component,
+            HTLEFT);
+  EXPECT_EQ(wm::GetWindowState(w3.get())->drag_details()->window_component,
+            HTLEFT);
+
   generator->MoveMouseTo(bounds.x() + 11, bounds.y() + 10);
 
+  // Drag details should exist during dragging.
+  EXPECT_TRUE(wm::GetWindowState(w1.get())->is_dragged());
+  EXPECT_TRUE(wm::GetWindowState(w2.get())->is_dragged());
+  EXPECT_TRUE(wm::GetWindowState(w3.get())->is_dragged());
+
   EXPECT_TRUE(HasTarget(w3.get()));
 
   // Release the mouse. The resizer should still be visible and a subsequent
   // press should not trigger a DCHECK.
   generator->ReleaseLeftButton();
   EXPECT_TRUE(IsShowing());
+
+  // Test that drag details are correctly deleted after dragging.
+  EXPECT_FALSE(wm::GetWindowState(w1.get())->is_dragged());
+  EXPECT_FALSE(wm::GetWindowState(w2.get())->is_dragged());
+  EXPECT_FALSE(wm::GetWindowState(w3.get())->is_dragged());
+
   generator->PressLeftButton();
 }
 
diff --git a/ash/wm/workspace/workspace_window_resizer.cc b/ash/wm/workspace/workspace_window_resizer.cc
index 8d55bda..b3def8d 100644
--- a/ash/wm/workspace/workspace_window_resizer.cc
+++ b/ash/wm/workspace/workspace_window_resizer.cc
@@ -487,6 +487,7 @@
   ::wm::ConvertPointToScreen(GetTarget()->parent(),
                              &last_mouse_location_in_screen);
   window_state()->OnCompleteDrag(last_mouse_location_in_screen);
+  EndDragForAttachedWindows(/*revert_drag=*/false);
 
   if (!did_move_or_resize_)
     return;
@@ -550,6 +551,7 @@
   ::wm::ConvertPointToScreen(GetTarget()->parent(),
                              &last_mouse_location_in_screen);
   window_state()->OnRevertDrag(last_mouse_location_in_screen);
+  EndDragForAttachedWindows(/*revert_drag=*/true);
   window_state()->set_bounds_changed_by_user(initial_bounds_changed_by_user_);
   snap_phantom_window_controller_.reset();
 
@@ -687,6 +689,7 @@
   pre_drag_window_bounds_ = window_state->window()->bounds();
 
   window_state->OnDragStarted(details().window_component);
+  StartDragForAttachedWindows();
 }
 
 void WorkspaceWindowResizer::LayoutAttachedWindows(gfx::Rect* bounds) {
@@ -1195,4 +1198,43 @@
   }
 }
 
+void WorkspaceWindowResizer::StartDragForAttachedWindows() {
+  if (attached_windows_.empty())
+    return;
+
+  // The component of the attached windows is always the opposite component of
+  // the main window.
+  const int main_window_component = details().window_component;
+  DCHECK(main_window_component == HTRIGHT || main_window_component == HTBOTTOM);
+
+  int window_component = HTNOWHERE;
+  if (main_window_component == HTRIGHT)
+    window_component = HTLEFT;
+  else if (main_window_component == HTBOTTOM)
+    window_component = HTTOP;
+  DCHECK(window_component == HTLEFT || window_component == HTTOP);
+
+  for (auto* window : attached_windows_) {
+    wm::WindowState* window_state = wm::GetWindowState(window);
+    window_state->CreateDragDetails(details().initial_location_in_parent,
+                                    window_component,
+                                    ::wm::WINDOW_MOVE_SOURCE_MOUSE);
+    window_state->OnDragStarted(window_component);
+  }
+}
+
+void WorkspaceWindowResizer::EndDragForAttachedWindows(bool revert_drag) {
+  if (attached_windows_.empty())
+    return;
+
+  for (auto* window : attached_windows_) {
+    wm::WindowState* window_state = wm::GetWindowState(window);
+    if (revert_drag)
+      window_state->OnRevertDrag(last_mouse_location_);
+    else
+      window_state->OnCompleteDrag(last_mouse_location_);
+    window_state->DeleteDragDetails();
+  }
+}
+
 }  // namespace ash
diff --git a/ash/wm/workspace/workspace_window_resizer.h b/ash/wm/workspace/workspace_window_resizer.h
index 15afd7d..54b854a 100644
--- a/ash/wm/workspace/workspace_window_resizer.h
+++ b/ash/wm/workspace/workspace_window_resizer.h
@@ -166,6 +166,10 @@
   void SetWindowStateTypeFromGesture(aura::Window* window,
                                      WindowStateType new_state_type);
 
+  // Start/End drag for attached windows if there is any.
+  void StartDragForAttachedWindows();
+  void EndDragForAttachedWindows(bool revert_drag);
+
   wm::WindowState* window_state() { return window_state_; }
 
   const std::vector<aura::Window*> attached_windows_;
diff --git a/base/BUILD.gn b/base/BUILD.gn
index c7f96f21..ba25074 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2961,6 +2961,7 @@
       "files/file_descriptor_watcher_posix_unittest.cc",
       "fuchsia/file_utils_unittest.cc",
       "fuchsia/filtered_service_directory_unittest.cc",
+      "fuchsia/scoped_service_binding_unittest.cc",
       "fuchsia/service_directory_test_base.cc",
       "fuchsia/service_directory_test_base.h",
       "fuchsia/service_directory_unittest.cc",
diff --git a/base/fuchsia/scoped_service_binding.h b/base/fuchsia/scoped_service_binding.h
index efdc30d8..efbd7346 100644
--- a/base/fuchsia/scoped_service_binding.h
+++ b/base/fuchsia/scoped_service_binding.h
@@ -51,6 +51,57 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedServiceBinding);
 };
 
+// Scoped service binding which allows only a single client to be connected
+// at any time. By default a new connection will disconnect an existing client.
+enum class ScopedServiceBindingPolicy { kPreferNew, kPreferExisting };
+
+template <typename Interface,
+          ScopedServiceBindingPolicy Policy =
+              ScopedServiceBindingPolicy::kPreferNew>
+class ScopedSingleClientServiceBinding {
+ public:
+  // |service_directory| and |impl| must outlive the binding.
+  ScopedSingleClientServiceBinding(ServiceDirectory* service_directory,
+                                   Interface* impl)
+      : directory_(service_directory), binding_(impl) {
+    directory_->AddService(BindRepeating(
+        &ScopedSingleClientServiceBinding::BindClient, Unretained(this)));
+  }
+
+  ~ScopedSingleClientServiceBinding() {
+    directory_->RemoveService(Interface::Name_);
+  }
+
+  typename Interface::EventSender_& events() { return binding_.events(); }
+
+  void SetOnLastClientCallback(base::OnceClosure on_last_client_callback) {
+    on_last_client_callback_ = std::move(on_last_client_callback);
+    binding_.set_error_handler(fit::bind_member(
+        this, &ScopedSingleClientServiceBinding::OnBindingEmpty));
+  }
+
+  bool has_clients() const { return binding_.is_bound(); }
+
+ private:
+  void BindClient(fidl::InterfaceRequest<Interface> request) {
+    if (Policy == ScopedServiceBindingPolicy::kPreferExisting &&
+        binding_.is_bound())
+      return;
+    binding_.Bind(std::move(request));
+  }
+
+  void OnBindingEmpty() {
+    binding_.set_error_handler(nullptr);
+    std::move(on_last_client_callback_).Run();
+  }
+
+  ServiceDirectory* const directory_;
+  fidl::Binding<Interface> binding_;
+  base::OnceClosure on_last_client_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedSingleClientServiceBinding);
+};
+
 }  // namespace fuchsia
 }  // namespace base
 
diff --git a/base/fuchsia/scoped_service_binding_unittest.cc b/base/fuchsia/scoped_service_binding_unittest.cc
new file mode 100644
index 0000000..3858ae2b
--- /dev/null
+++ b/base/fuchsia/scoped_service_binding_unittest.cc
@@ -0,0 +1,94 @@
+// 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/fuchsia/scoped_service_binding.h"
+
+#include "base/fuchsia/service_directory_test_base.h"
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace fuchsia {
+
+class ScopedServiceBindingTest : public ServiceDirectoryTestBase {};
+
+// Verifies that ScopedServiceBinding allows connection more than once.
+TEST_F(ScopedServiceBindingTest, ConnectTwice) {
+  auto stub = public_service_directory_client_
+                  ->ConnectToService<testfidl::TestInterface>();
+  auto stub2 = public_service_directory_client_
+                   ->ConnectToService<testfidl::TestInterface>();
+  VerifyTestInterface(&stub, ZX_OK);
+  VerifyTestInterface(&stub2, ZX_OK);
+}
+
+// Verify that if we connect twice to a prefer-new bound service, the existing
+// connection gets closed.
+TEST_F(ScopedServiceBindingTest, SingleClientPreferNew) {
+  // Teardown the default multi-client binding and create a prefer-new one.
+  service_binding_ = nullptr;
+  ScopedSingleClientServiceBinding<testfidl::TestInterface,
+                                   ScopedServiceBindingPolicy::kPreferNew>
+      binding(service_directory_.get(), &test_service_);
+
+  // Connect the first client, and verify that it is functional.
+  auto existing_client = public_service_directory_client_
+                             ->ConnectToService<testfidl::TestInterface>();
+  VerifyTestInterface(&existing_client, ZX_OK);
+
+  // Connect the second client, so the existing one should be disconnected and
+  // the new should be functional.
+  auto new_client = public_service_directory_client_
+                        ->ConnectToService<testfidl::TestInterface>();
+  RunLoop().RunUntilIdle();
+  EXPECT_FALSE(existing_client);
+  VerifyTestInterface(&new_client, ZX_OK);
+}
+
+// Verify that if we connect twice to a prefer-existing bound service, the new
+// connection gets closed.
+TEST_F(ScopedServiceBindingTest, SingleClientPreferExisting) {
+  // Teardown the default multi-client binding and create a prefer-existing one.
+  service_binding_ = nullptr;
+  ScopedSingleClientServiceBinding<testfidl::TestInterface,
+                                   ScopedServiceBindingPolicy::kPreferExisting>
+      binding(service_directory_.get(), &test_service_);
+
+  // Connect the first client, and verify that it is functional.
+  auto existing_client = public_service_directory_client_
+                             ->ConnectToService<testfidl::TestInterface>();
+  VerifyTestInterface(&existing_client, ZX_OK);
+
+  // Connect the second client, then verify that the it gets closed and the
+  // existing one remains functional.
+  auto new_client = public_service_directory_client_
+                        ->ConnectToService<testfidl::TestInterface>();
+  RunLoop().RunUntilIdle();
+  EXPECT_FALSE(new_client);
+  VerifyTestInterface(&existing_client, ZX_OK);
+}
+
+// Verify that the default single-client binding policy is prefer-new.
+TEST_F(ScopedServiceBindingTest, SingleClientDefaultIsPreferNew) {
+  // Teardown the default multi-client binding and create a prefer-new one.
+  service_binding_ = nullptr;
+  ScopedSingleClientServiceBinding<testfidl::TestInterface> binding(
+      service_directory_.get(), &test_service_);
+
+  // Connect the first client, and verify that it is functional.
+  auto existing_client = public_service_directory_client_
+                             ->ConnectToService<testfidl::TestInterface>();
+  VerifyTestInterface(&existing_client, ZX_OK);
+
+  // Connect the second client, so the existing one should be disconnected and
+  // the new should be functional.
+  auto new_client = public_service_directory_client_
+                        ->ConnectToService<testfidl::TestInterface>();
+  RunLoop().RunUntilIdle();
+  EXPECT_FALSE(existing_client);
+  VerifyTestInterface(&new_client, ZX_OK);
+}
+
+}  // namespace fuchsia
+}  // namespace base
diff --git a/base/fuchsia/service_directory_test_base.cc b/base/fuchsia/service_directory_test_base.cc
index 35fc351a..050e1a13 100644
--- a/base/fuchsia/service_directory_test_base.cc
+++ b/base/fuchsia/service_directory_test_base.cc
@@ -7,10 +7,16 @@
 #include <lib/fdio/directory.h>
 #include <utility>
 
+#include "base/bind.h"
+#include "base/test/test_timeouts.h"
+
 namespace base {
 namespace fuchsia {
 
-ServiceDirectoryTestBase::ServiceDirectoryTestBase() {
+ServiceDirectoryTestBase::ServiceDirectoryTestBase()
+    : run_timeout_(TestTimeouts::action_timeout(), BindRepeating([]() {
+                     ADD_FAILURE() << "Run() timed out.";
+                   })) {
   // TODO(https://crbug.com/920920): Remove the ServiceDirectory's implicit
   // "public" sub-directory and update this setup logic.
 
@@ -61,7 +67,7 @@
     fidl::InterfacePtr<testfidl::TestInterface>* stub,
     zx_status_t expected_error) {
   // Call the service and wait for response.
-  base::RunLoop run_loop;
+  RunLoop run_loop;
   zx_status_t actual_error = ZX_OK;
 
   stub->set_error_handler([&run_loop, &actual_error](zx_status_t status) {
diff --git a/base/fuchsia/service_directory_test_base.h b/base/fuchsia/service_directory_test_base.h
index 8099b830..900b008 100644
--- a/base/fuchsia/service_directory_test_base.h
+++ b/base/fuchsia/service_directory_test_base.h
@@ -5,7 +5,7 @@
 #ifndef BASE_FUCHSIA_SERVICE_DIRECTORY_TEST_BASE_H_
 #define BASE_FUCHSIA_SERVICE_DIRECTORY_TEST_BASE_H_
 
-#include <lib/zx/channel.h>
+#include <zircon/types.h>
 #include <memory>
 
 #include "base/fuchsia/scoped_service_binding.h"
@@ -13,6 +13,7 @@
 #include "base/fuchsia/test_interface_impl.h"
 #include "base/fuchsia/testfidl/cpp/fidl.h"
 #include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -27,6 +28,8 @@
                            zx_status_t expected_error);
 
  protected:
+  const RunLoop::ScopedRunTimeoutForTest run_timeout_;
+
   MessageLoopForIO message_loop_;
 
   std::unique_ptr<ServiceDirectory> service_directory_;
diff --git a/base/fuchsia/service_directory_unittest.cc b/base/fuchsia/service_directory_unittest.cc
index 8bf9688..505ad7d 100644
--- a/base/fuchsia/service_directory_unittest.cc
+++ b/base/fuchsia/service_directory_unittest.cc
@@ -4,17 +4,10 @@
 
 #include "base/fuchsia/service_directory.h"
 
-#include <lib/fdio/fdio.h>
-#include <lib/zx/channel.h>
 #include <utility>
 
-#include "base/bind.h"
 #include "base/fuchsia/service_directory_test_base.h"
-#include "base/location.h"
 #include "base/run_loop.h"
-#include "base/task_runner.h"
-#include "base/test/test_timeouts.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -30,19 +23,9 @@
                   ->ConnectToService<testfidl::TestInterface>();
   VerifyTestInterface(&stub, ZX_OK);
 
-  base::RunLoop run_loop;
+  RunLoop run_loop;
   service_binding_->SetOnLastClientCallback(run_loop.QuitClosure());
 
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](base::RunLoop* run_loop) {
-            ADD_FAILURE();
-            run_loop->Quit();
-          },
-          &run_loop),
-      TestTimeouts::action_timeout());
-
   stub.Unbind();
   run_loop.Run();
 }
diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h
index ec40c3d..a50f638 100644
--- a/base/process/process_metrics.h
+++ b/base/process/process_metrics.h
@@ -106,23 +106,6 @@
   BASE_EXPORT TotalsSummary GetTotalsSummary() const;
 #endif
 
-#if defined(OS_MACOSX)
-  struct TaskVMInfo {
-    // Only available on macOS 10.12+.
-    // Anonymous, non-discardable memory, including non-volatile IOKit.
-    // Measured in bytes.
-    uint64_t phys_footprint = 0;
-
-    // Anonymous, non-discardable, non-compressed memory, excluding IOKit.
-    // Measured in bytes.
-    uint64_t internal = 0;
-
-    // Compressed memory measured in bytes.
-    uint64_t compressed = 0;
-  };
-  TaskVMInfo GetTaskVMInfo() const;
-#endif
-
   // Returns the percentage of time spent executing, across all threads of the
   // process, in the interval since the last time the method was called. Since
   // this considers the total execution time across all threads in a process,
diff --git a/base/process/process_metrics_mac.cc b/base/process/process_metrics_mac.cc
index cbb5e93..d05e1228 100644
--- a/base/process/process_metrics_mac.cc
+++ b/base/process/process_metrics_mac.cc
@@ -25,37 +25,6 @@
 
 namespace {
 
-#if !defined(MAC_OS_X_VERSION_10_11) || \
-    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11
-// The |phys_footprint| field was introduced in 10.11.
-struct ChromeTaskVMInfo {
-  mach_vm_size_t virtual_size;
-  integer_t region_count;
-  integer_t page_size;
-  mach_vm_size_t resident_size;
-  mach_vm_size_t resident_size_peak;
-  mach_vm_size_t device;
-  mach_vm_size_t device_peak;
-  mach_vm_size_t internal;
-  mach_vm_size_t internal_peak;
-  mach_vm_size_t external;
-  mach_vm_size_t external_peak;
-  mach_vm_size_t reusable;
-  mach_vm_size_t reusable_peak;
-  mach_vm_size_t purgeable_volatile_pmap;
-  mach_vm_size_t purgeable_volatile_resident;
-  mach_vm_size_t purgeable_volatile_virtual;
-  mach_vm_size_t compressed;
-  mach_vm_size_t compressed_peak;
-  mach_vm_size_t compressed_lifetime;
-  mach_vm_size_t phys_footprint;
-};
-#else
-using ChromeTaskVMInfo = task_vm_info;
-#endif  // MAC_OS_X_VERSION_10_11
-mach_msg_type_number_t ChromeTaskVMInfoCount =
-    sizeof(ChromeTaskVMInfo) / sizeof(natural_t);
-
 bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) {
   if (task == MACH_PORT_NULL)
     return false;
@@ -105,23 +74,6 @@
   return WrapUnique(new ProcessMetrics(process, port_provider));
 }
 
-ProcessMetrics::TaskVMInfo ProcessMetrics::GetTaskVMInfo() const {
-  TaskVMInfo info;
-  ChromeTaskVMInfo task_vm_info;
-  mach_msg_type_number_t count = ChromeTaskVMInfoCount;
-  kern_return_t result =
-      task_info(TaskForPid(process_), TASK_VM_INFO,
-                reinterpret_cast<task_info_t>(&task_vm_info), &count);
-  if (result != KERN_SUCCESS)
-    return info;
-
-  info.internal = task_vm_info.internal;
-  info.compressed = task_vm_info.compressed;
-  if (count == ChromeTaskVMInfoCount)
-    info.phys_footprint = task_vm_info.phys_footprint;
-  return info;
-}
-
 #define TIME_VALUE_TO_TIMEVAL(a, r) do {  \
   (r)->tv_sec = (a)->seconds;             \
   (r)->tv_usec = (a)->microseconds;       \
diff --git a/base/task/OWNERS b/base/task/OWNERS
index 42f0e57..75024e6 100644
--- a/base/task/OWNERS
+++ b/base/task/OWNERS
@@ -3,6 +3,7 @@
 robliao@chromium.org
 alexclarke@chromium.org
 altimin@chromium.org
+carlscab@google.com
 skyosti@lchromium.org
 
 # TEAM: scheduler-dev@chromium.org
diff --git a/base/task/sequence_manager/OWNERS b/base/task/sequence_manager/OWNERS
index 2ef3011..b9ec8df 100644
--- a/base/task/sequence_manager/OWNERS
+++ b/base/task/sequence_manager/OWNERS
@@ -1,5 +1,6 @@
 altimin@chromium.org
 alexclarke@chromium.org
+carlscab@google.com
 skyostil@chromium.org
 
 # TEAM: scheduler-dev@chromium.org
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index ab2e4008..27e1bee 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8911706893625634704
\ No newline at end of file
+8911678666757206080
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index b527f1a..db6eda30 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8911702506200835040
\ No newline at end of file
+8911678778624735728
\ No newline at end of file
diff --git a/build/sanitizers/tsan_suppressions.cc b/build/sanitizers/tsan_suppressions.cc
index f8e3203..60f4cad 100644
--- a/build/sanitizers/tsan_suppressions.cc
+++ b/build/sanitizers/tsan_suppressions.cc
@@ -128,9 +128,6 @@
     "race:sctp_express_handle_sack\n"
     "race:system_base_info\n"
 
-    // http://crbug.com/374135
-    "race:media::AlsaWrapper::PcmWritei\n"
-
     // False positive in libc's tzset_internal, http://crbug.com/379738.
     "race:tzset_internal\n"
 
diff --git a/build/toolchain/mac/BUILD.gn b/build/toolchain/mac/BUILD.gn
index b9c65b0..22316c9 100644
--- a/build/toolchain/mac/BUILD.gn
+++ b/build/toolchain/mac/BUILD.gn
@@ -432,21 +432,10 @@
       # constructed using shell variable $OLDPWD (automatically set when
       # cd is used) as computing the relative path is a bit complex and
       # using pwd would requires a sub-shell to be created.
-      #
-      # A special case exists for copying symbolic links. The pax command,
-      # as noted above, preserves symbolic links but only if they are a child
-      # node of source. But if the source itself is a symbolic link to a
-      # directory, the cd into it will copy it as a logical tree rather than
-      # a symbolic link. Similarly, if source is a symbolic link to a file,
-      # then the copy_command hard link will not produce a symbolic link, it
-      # would hard link the file pointed to by the symbolic link.
       _copydir = "mkdir -p {{output}} && cd {{source}} && " +
                  "pax -rwl . \"\$OLDPWD\"/{{output}}"
-      _copylink = "ln -s \$(readlink {{source}}) {{output}}"
-      command = "rm -rf {{output}} && " +
-                "if [[ -L {{source}} ]]; then $_copylink; " +
-                "elif [[ -d {{source}} ]]; then $_copydir; " +
-                "else $copy_command; fi"
+      command = "rm -rf {{output}} && if [[ -d {{source}} ]]; then " +
+                _copydir + "; else " + copy_command + "; fi"
 
       description = "COPY_BUNDLE_DATA {{source}} {{output}}"
       pool = ":bundle_pool($default_toolchain)"
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 2239c19..9ff8474 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2467,6 +2467,7 @@
     "java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java",
     "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java",
     "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java",
+    "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncPwaDetector.java",
     "java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
     "java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index c785aa3..3e169e6 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -103,6 +103,7 @@
   "java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java",
   "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java",
   "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java",
+  "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncPwaDetector.java",
   "java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java",
   "java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
   "java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantActionsDecoration.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantActionsDecoration.java
index cff21bb..4faa613 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantActionsDecoration.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantActionsDecoration.java
@@ -132,9 +132,13 @@
             canvas.drawRect(mChildRect, mOverlayPaint);
         }
 
+        View beforeLastChild = parent.getChildAt(parent.getChildCount() - 2);
+
         // Draw a fixed size white-to-transparent linear gradient from left to right.
         mGradientDrawable.setBounds(
                 0, mVerticalSpacing, mGradientWidth, parent.getHeight() - mVerticalSpacing);
+        mGradientDrawable.setAlpha(Math.round(getBoundedLinearValue(
+                beforeLastChild.getLeft(), lastChild.getLeft(), lastChild.getRight(), 255, 0)));
         mGradientDrawable.draw(canvas);
 
         canvas.restore();
@@ -148,7 +152,6 @@
         // Draw shadow composed of 4 layers of colors around the last child. We multiply the
         // original alpha of the shadow color by alphaRatio to hide the shadow when there is no
         // child behind the last child.
-        View beforeLastChild = parent.getChildAt(parent.getChildCount() - 2);
         float alphaRatio = getBoundedLinearValue(
                 beforeLastChild.getLeft(), lastChild.getLeft(), lastChild.getRight(), 1, 0);
 
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
index 68b1943..dcee28f 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
@@ -39,6 +39,10 @@
         model.addObserver((source, propertyKey) -> {
             if (AssistantOverlayModel.STATE == propertyKey) {
                 setState(model.get(AssistantOverlayModel.STATE));
+            } else if (AssistantOverlayModel.VISUAL_VIEWPORT == propertyKey) {
+                RectF rect = model.get(AssistantOverlayModel.VISUAL_VIEWPORT);
+                mEventFilter.setVisualViewport(rect);
+                mDrawable.setVisualViewport(rect);
             } else if (AssistantOverlayModel.TOUCHABLE_AREA == propertyKey) {
                 List<RectF> area = model.get(AssistantOverlayModel.TOUCHABLE_AREA);
                 mEventFilter.setTouchableArea(area);
@@ -47,8 +51,6 @@
                 AssistantOverlayDelegate delegate = model.get(AssistantOverlayModel.DELEGATE);
                 mEventFilter.setDelegate(delegate);
                 mDrawable.setDelegate(delegate);
-            } else if (AssistantOverlayModel.WEB_CONTENTS == propertyKey) {
-                mDrawable.setWebContents(model.get(AssistantOverlayModel.WEB_CONTENTS));
             } else if (AssistantOverlayModel.BACKGROUND_COLOR == propertyKey) {
                 mDrawable.setBackgroundColor(model.get(AssistantOverlayModel.BACKGROUND_COLOR));
             } else if (AssistantOverlayModel.HIGHLIGHT_BORDER_COLOR == propertyKey) {
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDrawable.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDrawable.java
index de12d78e..aadec199 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDrawable.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDrawable.java
@@ -29,9 +29,6 @@
 import org.chromium.chrome.browser.compositor.CompositorViewResizer;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener;
-import org.chromium.content_public.browser.GestureListenerManager;
-import org.chromium.content_public.browser.GestureStateListener;
-import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.interpolators.BakedBezierInterpolator;
 
 import java.lang.annotation.Retention;
@@ -49,8 +46,8 @@
  * <p>While scrolling, it keeps track of the current scrolling offset and avoids drawing on top of
  * the top bar which is can be, during animations, just drawn on top of the compositor.
  */
-class AssistantOverlayDrawable extends Drawable
-        implements FullscreenListener, GestureStateListener, CompositorViewResizer.Observer {
+class AssistantOverlayDrawable
+        extends Drawable implements FullscreenListener, CompositorViewResizer.Observer {
     private static final int FADE_DURATION_MS = 250;
 
     /** Default background color and alpha. */
@@ -83,7 +80,18 @@
     /** When in partial mode, don't draw on {@link #mTransparentArea}. */
     private boolean mPartial;
 
-    private List<Box> mTransparentArea = new ArrayList<>();
+    /**
+     * Coordinates of the visual viewport within the page, if known, in CSS pixels relative to the
+     * origin of the page.
+     *
+     * The visual viewport includes the portion of the page that is really visible, excluding any
+     * area not fully visible because of the current zoom value.
+     *
+     * Only relevant in partial mode, when the transparent area is non-empty.
+     */
+    private final RectF mVisualViewport = new RectF();
+
+    private final List<Box> mTransparentArea = new ArrayList<>();
 
     /** Padding added between the element area and the grayed-out area. */
     private final float mPaddingPx;
@@ -94,32 +102,6 @@
     /** A single RectF instance used for drawing, to avoid creating many instances when drawing. */
     private final RectF mDrawRect = new RectF();
 
-    /** True while the browser is scrolling. */
-    private boolean mBrowserScrolling;
-
-    /**
-     * Scrolling offset to use while scrolling right after scrolling.
-     *
-     * <p>This value shifts the transparent area by that many pixels while scrolling.
-     */
-    private int mBrowserScrollOffsetY;
-
-    /**
-     * Offset reported at the beginning of a scroll.
-     *
-     * <p>This is used to interpret the offsets reported by subsequent calls to {@link
-     * #onScrollOffsetOrExtentChanged} or {@link #onScrollEnded}.
-     */
-    private int mInitialBrowserScrollOffsetY;
-
-    /**
-     * Current offset that applies on mTransparentArea.
-     *
-     * <p>This value shifts the transparent area by that many pixels after the end of a scroll and
-     * before the next update, which resets this value.
-     */
-    private int mOffsetY;
-
     /**
      * Current top margin of this view.
      *
@@ -137,7 +119,6 @@
     private int mMarginBottom;
 
     private AssistantOverlayDelegate mDelegate;
-    private GestureListenerManager mGestureListenerManager;
 
     AssistantOverlayDrawable(Context context, ChromeFullscreenManager fullscreenManager,
             CompositorViewResizer viewResizer) {
@@ -203,19 +184,7 @@
         mDelegate = delegate;
     }
 
-    void setWebContents(@Nullable WebContents webContents) {
-        if (mGestureListenerManager != null) {
-            mGestureListenerManager.removeListener(this);
-            mGestureListenerManager = null;
-        }
-        if (webContents != null) {
-            mGestureListenerManager = GestureListenerManager.fromWebContents(webContents);
-            mGestureListenerManager.addListener(this);
-        }
-    }
-
     void destroy() {
-        setWebContents(null);
         mFullscreenManager.removeListener(this);
         mViewResizer.removeObserver(this);
         mDelegate = null;
@@ -242,6 +211,11 @@
         invalidateSelf();
     }
 
+    void setVisualViewport(RectF visualViewport) {
+        mVisualViewport.set(visualViewport);
+        invalidateSelf();
+    }
+
     /** Set or updates the transparent area. */
     void setTransparentArea(List<RectF> transparentArea) {
         // Add or update boxes for each rectangle in the area.
@@ -276,9 +250,6 @@
             }
         }
 
-        mOffsetY = 0;
-        mInitialBrowserScrollOffsetY += mBrowserScrollOffsetY;
-        mBrowserScrollOffsetY = 0;
         invalidateSelf();
     }
 
@@ -310,8 +281,14 @@
 
         canvas.drawPaint(mBackground);
 
+        if (mVisualViewport.isEmpty()) return;
+
+        // Ratio of to use to convert zoomed CSS pixels, to physical pixels. Aspect ratio is
+        // conserved, so width and height are always converted with the same value. Using width
+        // here, since viewport width always corresponds to the overlay width.
+        float cssPixelsToPhysical = ((float) width) / ((float) mVisualViewport.width());
+
         int yTop = (int) mFullscreenManager.getContentOffset();
-        int height = yBottom - yTop;
         for (Box box : mTransparentArea) {
             RectF rect = box.getRectToDraw();
             if (rect.isEmpty() || (!mPartial && box.mAnimationType != AnimationType.FADE_IN)) {
@@ -322,12 +299,13 @@
             int fillAlpha = (int) (mBackgroundAlpha * (1f - box.getVisibility()));
             mBoxFill.setAlpha(fillAlpha);
 
-            mDrawRect.left = rect.left * width - mPaddingPx;
+            mDrawRect.left = (rect.left - mVisualViewport.left) * cssPixelsToPhysical - mPaddingPx;
             mDrawRect.top =
-                    yTop + rect.top * height - mPaddingPx - mBrowserScrollOffsetY - mOffsetY;
-            mDrawRect.right = rect.right * width + mPaddingPx;
+                    yTop + (rect.top - mVisualViewport.top) * cssPixelsToPhysical - mPaddingPx;
+            mDrawRect.right =
+                    (rect.right - mVisualViewport.left) * cssPixelsToPhysical + mPaddingPx;
             mDrawRect.bottom =
-                    yTop + rect.bottom * height + mPaddingPx - mBrowserScrollOffsetY - mOffsetY;
+                    yTop + (rect.bottom - mVisualViewport.top) * cssPixelsToPhysical + mPaddingPx;
             if (mDrawRect.left <= 0 && mDrawRect.right >= width) {
                 // Rounded corners look strange in the case where the rectangle takes exactly the
                 // width of the screen.
@@ -362,48 +340,14 @@
 
     @Override
     public void onUpdateViewportSize() {
+        askForTouchableAreaUpdate();
         invalidateSelf();
     }
 
     @Override
     public void onHeightChanged(int height) {
-        invalidateSelf();
-    }
-
-    /** Called at the beginning of a scroll gesture triggered by the browser. */
-    @Override
-    public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
-        mBrowserScrolling = true;
-        mInitialBrowserScrollOffsetY = scrollOffsetY;
-        mBrowserScrollOffsetY = 0;
-        invalidateSelf();
-    }
-
-    /** Called during a scroll gesture triggered by the browser. */
-    @Override
-    public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scrollExtentY) {
-        if (!mBrowserScrolling) {
-            // onScrollOffsetOrExtentChanged will be called alone, without onScrollStarted during a
-            // Javascript-initiated scroll.
-            askForTouchableAreaUpdate();
-            return;
-        }
-        mBrowserScrollOffsetY = scrollOffsetY - mInitialBrowserScrollOffsetY;
-        invalidateSelf();
         askForTouchableAreaUpdate();
-    }
-
-    /** Called at the end of a scroll gesture triggered by the browser. */
-    @Override
-    public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
-        if (!mBrowserScrolling) {
-            return;
-        }
-        mOffsetY += (scrollOffsetY - mInitialBrowserScrollOffsetY);
-        mBrowserScrollOffsetY = 0;
-        mBrowserScrolling = false;
         invalidateSelf();
-        askForTouchableAreaUpdate();
     }
 
     private void askForTouchableAreaUpdate() {
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayEventFilter.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayEventFilter.java
index b05c468..aff95267 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayEventFilter.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayEventFilter.java
@@ -59,6 +59,16 @@
      */
     private boolean mPartial;
 
+    /**
+     * Coordinates of the visual viewport within the page, if known, in CSS pixels relative to the
+     * origin of the page. This is used to convert pixel coordinates to CSS coordinates.
+     *
+     * The visual viewport includes the portion of the page that is really visible, excluding any
+     * area not fully visible because of the current zoom value.
+     */
+    private final RectF mVisualViewport = new RectF();
+
+    /** Touchable area, expressed in CSS pixels relative to the layout viewport. */
     private List<RectF> mTouchableArea = Collections.emptyList();
 
     /**
@@ -141,6 +151,11 @@
         mTouchableArea = touchableArea;
     }
 
+    /** Sets the visual viewport. */
+    void setVisualViewport(RectF visualViewport) {
+        mVisualViewport.set(visualViewport);
+    }
+
     @Override
     protected boolean onInterceptTouchEventInternal(MotionEvent event, boolean isKeyboardShowing) {
         // All events should be sent to onTouchEvent().
@@ -297,8 +312,7 @@
     private boolean shouldLetEventThrough(MotionEvent event) {
         int yTop = (int) mFullscreenManager.getContentOffset();
         int height = mCompositorView.getHeight() - getBottomBarHeight() - yTop;
-        return isInTouchableArea(((float) event.getX()) / mCompositorView.getWidth(),
-                (((float) event.getY() - yTop) / height));
+        return isInTouchableArea(event.getX(), event.getY() - yTop);
     }
 
     /** Considers whether to let the client know about unexpected taps. */
@@ -317,17 +331,18 @@
         }
     }
 
-    private void askForTouchableAreaUpdate() {
-        if (mDelegate != null) {
-            mDelegate.updateTouchableArea();
-        }
-    }
-
     private boolean isInTouchableArea(float x, float y) {
+        if (mVisualViewport.isEmpty() || mTouchableArea.isEmpty()) return false;
+
+        // Ratio of to use to convert physical pixels to zoomed CSS pixels. Aspect ratio is
+        // conserved, so width and height are always converted with the same value. Using width
+        // here, since viewport width always corresponds to the overlay width.
+        float physicalPixelsToCss =
+                ((float) mVisualViewport.width()) / ((float) mCompositorView.getWidth());
+        float absoluteXCss = (x * physicalPixelsToCss) + mVisualViewport.left;
+        float absoluteYCss = (y * physicalPixelsToCss) + mVisualViewport.top;
         for (RectF rect : mTouchableArea) {
-            if (rect.contains(x, y, x, y)) {
-                return true;
-            }
+            if (rect.contains(absoluteXCss, absoluteYCss)) return true;
         }
         return false;
     }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java
index 55ed5b1..e212489 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java
@@ -10,7 +10,6 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
@@ -23,15 +22,13 @@
 public class AssistantOverlayModel extends PropertyModel {
     public static final WritableIntPropertyKey STATE = new WritableIntPropertyKey();
 
-    // Skipping equality as a way of fixing offset issues. See b/129048184.
-    // TODO(b/129050125): Handle offsets properly and remove.
     public static final WritableObjectPropertyKey<List<RectF>> TOUCHABLE_AREA =
-            new WritableObjectPropertyKey<>(/* skipEquality= */ true);
-
-    public static final WritableObjectPropertyKey<AssistantOverlayDelegate> DELEGATE =
             new WritableObjectPropertyKey<>();
 
-    public static final WritableObjectPropertyKey<WebContents> WEB_CONTENTS =
+    public static final WritableObjectPropertyKey<RectF> VISUAL_VIEWPORT =
+            new WritableObjectPropertyKey<>();
+
+    public static final WritableObjectPropertyKey<AssistantOverlayDelegate> DELEGATE =
             new WritableObjectPropertyKey<>();
 
     public static final WritableObjectPropertyKey<Integer> BACKGROUND_COLOR =
@@ -41,7 +38,7 @@
             new WritableObjectPropertyKey<>();
 
     public AssistantOverlayModel() {
-        super(STATE, TOUCHABLE_AREA, DELEGATE, WEB_CONTENTS, BACKGROUND_COLOR,
+        super(STATE, TOUCHABLE_AREA, VISUAL_VIEWPORT, DELEGATE, BACKGROUND_COLOR,
                 HIGHLIGHT_BORDER_COLOR);
     }
 
@@ -51,6 +48,11 @@
     }
 
     @CalledByNative
+    private void setVisualViewport(float left, float top, float right, float bottom) {
+        set(VISUAL_VIEWPORT, new RectF(left, top, right, bottom));
+    }
+
+    @CalledByNative
     private void setTouchableArea(float[] coords) {
         List<RectF> boxes = new ArrayList<>();
         for (int i = 0; i < coords.length; i += 4) {
@@ -66,11 +68,6 @@
     }
 
     @CalledByNative
-    private void setWebContents(WebContents webContents) {
-        set(WEB_CONTENTS, webContents);
-    }
-
-    @CalledByNative
     private boolean setBackgroundColor(String colorString) {
         return setColor(BACKGROUND_COLOR, colorString);
     }
diff --git a/chrome/android/java/res/drawable/data_reduction_illustration.xml b/chrome/android/java/res/drawable/data_reduction_illustration.xml
index ec8e23f6..f5a18fc 100644
--- a/chrome/android/java/res/drawable/data_reduction_illustration.xml
+++ b/chrome/android/java/res/drawable/data_reduction_illustration.xml
@@ -9,104 +9,88 @@
     xmlns:tools="http://schemas.android.com/tools"
     tools:targetApi="21"
     tools:ignore="VectorRaster"
-    android:width="232dp"
+    android:width="224dp"
     android:height="107dp"
-    android:viewportWidth="232"
+    android:viewportWidth="224"
     android:viewportHeight="107">
-  <path
+  <path android:fillColor="#F1F3F4"
+      android:fillType="nonZero"
       android:pathData="M156.91,3.733l18.155,31.59l-90.126,-1.508l12.968,-31.662z"
-      android:strokeWidth="1"
-      android:fillColor="#F1F3F4"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#E1741F"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M227.729,52.338L225.928,52.338C224.703,52.338 223.694,51.333 223.694,50.113L223.694,44.369C223.694,43.149 224.703,42.144 225.928,42.144L227.729,42.144C228.954,42.144 229.962,43.149 229.962,44.369L229.962,50.113C230.034,51.333 229.026,52.338 227.729,52.338Z"
-      android:strokeWidth="1"
-      android:fillColor="#E1741F"
+      android:pathData="M220.729,52.338L218.928,52.338C217.703,52.338 216.694,51.333 216.694,50.113L216.694,44.369C216.694,43.149 217.703,42.144 218.928,42.144L220.729,42.144C221.954,42.144 222.962,43.149 222.962,44.369L222.962,50.113C223.034,51.333 222.026,52.338 220.729,52.338Z"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M115.125,1.149h2.089v39.056h-2.089z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#F9BB2D"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M49.638,81.128L205.612,81.128C217.283,81.128 226.72,71.723 226.72,60.092L226.72,37.836C226.72,35.467 224.775,33.528 222.398,33.528L32.419,33.528C30.186,33.528 28.313,35.323 28.313,37.621L28.313,59.949C28.313,71.651 37.895,81.128 49.638,81.128Z"
-      android:strokeWidth="1"
-      android:fillColor="#F9BB2D"
+      android:pathData="M48.842,81.128L198.992,81.128C210.228,81.128 219.313,71.723 219.313,60.092L219.313,37.836C219.313,35.467 217.44,33.528 215.152,33.528L32.266,33.528C30.116,33.528 28.313,35.323 28.313,37.621L28.313,59.949C28.313,71.651 37.537,81.128 48.842,81.128Z"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#494C4F"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M74.764,106.115a25.924,22.906 104.033,1 0,12.573 -50.301a25.924,22.906 104.033,1 0,-12.573 50.301z"
-      android:strokeWidth="1"
-      android:fillColor="#3C4043"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M78.334,91.832a11.203,9.94 104.033,1 0,5.433 -21.737a11.203,9.94 104.033,1 0,-5.433 21.737z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#494C4F"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M175.629,106.111a25.924,22.906 104.033,1 0,12.573 -50.301a25.924,22.906 104.033,1 0,-12.573 50.301z"
-      android:strokeWidth="1"
-      android:fillColor="#3C4043"
+      android:pathData="M161.629,106.111a25.924,22.906 104.033,1 0,12.573 -50.301a25.924,22.906 104.033,1 0,-12.573 50.301z"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M179.199,91.828a11.203,9.94 104.033,1 0,5.433 -21.737a11.203,9.94 104.033,1 0,-5.433 21.737z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
+      android:pathData="M165.199,91.828a11.203,9.94 104.033,1 0,5.433 -21.737a11.203,9.94 104.033,1 0,-5.433 21.737z"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#BDC1C6"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
-      android:pathData="M227.801,77.682L223.118,77.682C221.101,77.682 219.444,76.031 219.444,74.021L219.444,71.149C219.444,69.138 221.101,67.487 223.118,67.487L227.801,67.487C229.818,67.487 231.475,69.138 231.475,71.149L231.475,74.021C231.475,76.031 229.818,77.682 227.801,77.682Z"
-      android:strokeWidth="1"
-      android:fillColor="#BDC1C6"
+      android:pathData="M219.801,77.682L215.118,77.682C213.101,77.682 211.444,76.031 211.444,74.021L211.444,71.149C211.444,69.138 213.101,67.487 215.118,67.487L219.801,67.487C221.818,67.487 223.475,69.138 223.475,71.149L223.475,74.021C223.475,76.031 221.818,77.682 219.801,77.682Z"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#BDC1C6"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M38.975,77.682L26.728,77.682C24.567,77.682 22.766,75.887 22.766,73.733L22.766,71.364C22.766,69.21 24.567,67.415 26.728,67.415L38.903,67.415C41.065,67.415 42.866,69.21 42.866,71.364L42.866,73.733C42.938,75.959 41.137,77.682 38.975,77.682Z"
-      android:strokeWidth="1"
-      android:fillColor="#BDC1C6"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M79.752,12.851L14.697,12.851C12.752,12.851 11.239,11.272 11.239,9.405L11.239,9.405C11.239,7.467 12.824,5.959 14.697,5.959L79.752,5.959C81.697,5.959 83.21,7.538 83.21,9.405L83.21,9.405C83.21,11.272 81.697,12.851 79.752,12.851Z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M12.608,30.872L3.962,30.872C2.017,30.872 0.504,29.292 0.504,27.426L0.504,27.426C0.504,25.487 2.089,23.979 3.962,23.979L12.608,23.979C14.553,23.979 16.066,25.559 16.066,27.426L16.066,27.426C16.138,29.292 14.553,30.872 12.608,30.872Z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M37.967,48.821L12.103,48.821C10.158,48.821 8.645,47.241 8.645,45.374L8.645,45.374C8.645,43.436 10.23,41.928 12.103,41.928L37.967,41.928C39.912,41.928 41.425,43.508 41.425,45.374L41.425,45.374C41.497,47.313 39.912,48.821 37.967,48.821Z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M98.267,48.821L52.664,48.821C50.718,48.821 49.206,47.241 49.206,45.374L49.206,45.374C49.206,43.436 50.791,41.928 52.664,41.928L98.267,41.928C100.212,41.928 101.725,43.508 101.725,45.374L101.725,45.374C101.725,47.313 100.212,48.821 98.267,48.821Z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#5B5D5F"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M47.765,35.897C47.188,35.897 46.9,35.395 47.188,34.964L72.115,3.949C74.133,1.436 77.158,-0 80.328,-0L157.198,-0C158.207,-0 159,0.862 159,1.867L159,2.154C159,3.159 158.207,4.021 157.198,4.021L105.039,4.021C102.374,4.021 99.996,5.528 98.771,7.897L84.939,34.892C84.651,35.467 83.93,35.897 83.138,35.897L47.765,35.897L47.765,35.897Z"
-      android:strokeWidth="1"
-      android:fillColor="#3C4043"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
+  <path android:fillColor="#DADCE0"
       android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
-  <path
       android:pathData="M96.394,30.872L26.584,30.872C24.639,30.872 23.126,29.292 23.126,27.426L23.126,27.426C23.126,25.487 24.711,23.979 26.584,23.979L96.394,23.979C98.339,23.979 99.852,25.559 99.852,27.426L99.852,27.426C99.924,29.292 98.339,30.872 96.394,30.872Z"
-      android:strokeWidth="1"
-      android:fillColor="#DADCE0"
-      android:fillType="nonZero"
-      android:strokeColor="#00000000"/>
+      android:strokeColor="#00000000"
+      android:strokeWidth="1"/>
 </vector>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncPwaDetector.java b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncPwaDetector.java
new file mode 100644
index 0000000..aa7ef9f5
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncPwaDetector.java
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.background_sync;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.browser.webapps.WebappRegistry;
+
+import java.util.Locale;
+
+/**
+ * The {@link BackgroundSyncPwaDetector} singleton is responsible for detecting
+ * installed PWAs (TWA and WebAPK) on Android.
+ * This is used to limit access to Periodic Background Sync APIs.
+ *
+ * Thread model: This class is to be run on the UI thread only.
+ */
+public class BackgroundSyncPwaDetector {
+    /**
+     * Returns true if there's a PWA installed for the given origin, false otherwise.
+     * @param origin The origin for which to look for a PWA.
+     */
+    @CalledByNative
+    public static boolean isPwaInstalled(String origin) {
+        return WebappRegistry.getInstance().hasWebApkForUrl(
+                origin.toLowerCase(Locale.getDefault()));
+    }
+
+    /**
+     * Returns true if there's a TWA installed for the given origin, false otherwise.
+     * @param origin The origin for which to look for a TWA.
+     */
+    @CalledByNative
+    public static boolean isTwaInstalled(String origin) {
+        return WebappRegistry.getInstance().getTrustedWebActivityPermissionStore().isTwaInstalled(
+                origin.toLowerCase(Locale.getDefault()));
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionStore.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionStore.java
index 4091985a..0d9cd36 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionStore.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionStore.java
@@ -106,6 +106,11 @@
         }
     }
 
+    /** Returns true if there's a registered TWA for the origin. */
+    public boolean isTwaInstalled(String origin) {
+        return getStoredOrigins().contains(origin);
+    }
+
     /**
      * Sets the notification state for the origin.
      * Returns whether {@code true} if state was changed, {@code false} if the provided state was
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java
index e2c1a51..44e79c6b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java
@@ -8,6 +8,7 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.IntDef;
@@ -22,6 +23,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.favicon.FaviconHelper.DefaultFaviconHelper;
@@ -505,6 +507,9 @@
                 Drawable drawable = getRoundedFavicon(historyIcon,
                         mActivity.getResources().getDimensionPixelSize(
                                 R.dimen.tile_view_icon_size_modern));
+                drawable.setColorFilter(ApiCompatibilityUtils.getColor(mActivity.getResources(),
+                                                R.color.default_icon_color),
+                        PorterDuff.Mode.SRC_IN);
                 viewHolder.imageView.setImageDrawable(drawable);
                 viewHolder.itemLayout.getLayoutParams().height =
                         mActivity.getResources().getDimensionPixelSize(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java
index d7795d8..ac4c3be 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java
@@ -176,6 +176,25 @@
     }
 
     /**
+     * Returns true if a WebAPK is found whose scope matches the provided URL.
+     * @param url The URL to search a WebAPK for.
+     */
+    public boolean hasWebApkForUrl(String url) {
+        for (HashMap.Entry<String, WebappDataStorage> entry : mStorages.entrySet()) {
+            WebappDataStorage storage = entry.getValue();
+            if (!storage.getId().startsWith(WebApkConstants.WEBAPK_ID_PREFIX)) continue;
+
+            String scope = storage.getScope();
+
+            // Scope shouldn't be empty.
+            assert (!scope.isEmpty());
+
+            if (url.startsWith(scope)) return true;
+        }
+        return false;
+    }
+
+    /**
      * Returns the list of WebAPK IDs with pending updates. Filters out WebAPKs which have been
      * uninstalled.
      * */
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index bb08c7d..4c679bd 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -3755,7 +3755,7 @@
       </message>
 
       <if expr="enable_vr">
-        <part file="../../../app/xr_consent_ui_strings.grdp" />
+        <part file="xr_consent_ui_strings_java.grdp" />
       </if>
 
       <if expr="enable_arcore">
diff --git a/chrome/android/java/strings/xr_consent_ui_strings_java.grdp b/chrome/android/java/strings/xr_consent_ui_strings_java.grdp
new file mode 100644
index 0000000..2f6a0a64
--- /dev/null
+++ b/chrome/android/java/strings/xr_consent_ui_strings_java.grdp
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- XR specific strings (included from generated_resources.grd and android_chrome_strings.grd). -->
+<!-- These strings are used in the consent flow dialog. -->
+<grit-part>
+  <!-- XR consent dialog. -->
+  <message name="IDS_XR_CONSENT_DIALOG_TITLE" desc="Title of the user consent dialog displayed before allowing a website to start a VR presentation">
+    Allow this site to access your VR sensors?
+  </message>
+  <message name="IDS_XR_CONSENT_DIALOG_DESCRIPTION" desc="Body of the user consent dialog displayed before allowing a website to start a VR presentation">
+    Sensor data will only be shared while you're in this VR experience. The site may be able to learn about you using certain info, such as:
+  - Your location
+  - Your physical features, like eye position
+  - Your movements, like how you walk
+
+Make sure you trust this site before you allow access.
+  </message>
+  <message name="IDS_XR_CONSENT_DIALOG_BUTTON_DENY_VR" desc="Text on the button of a user consent dialog which denies a website from starting a VR presentation">
+    Don&#39;t allow
+  </message>
+  <message name="IDS_XR_CONSENT_DIALOG_BUTTON_ALLOW_AND_ENTER_VR" desc="Text on the button of a user consent dialog which allows a website to start a VR presentation">
+    Allow &amp; enter VR
+  </message>
+</grit-part>
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionsTest.java
index 962d43e..a4e691f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/permissiondelegation/TrustedWebActivityPermissionsTest.java
@@ -5,9 +5,12 @@
 package org.chromium.chrome.browser.browserservices.permissiondelegation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -21,6 +24,7 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.ChromeApplication;
 import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.background_sync.BackgroundSyncPwaDetector;
 import org.chromium.chrome.browser.browserservices.Origin;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
@@ -105,6 +109,17 @@
         assertEquals("\"default\"", getNotificationPermission());
     }
 
+    @Test
+    @SmallTest
+    public void detectTwa() throws TimeoutException, InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mPermissionManager.register(mOrigin, mPackage, true));
+        assertTrue(BackgroundSyncPwaDetector.isTwaInstalled(mOrigin.toString()));
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mPermissionManager.unregister(mOrigin); });
+        assertFalse(BackgroundSyncPwaDetector.isTwaInstalled(mOrigin.toString()));
+    }
+
     private String getNotificationPermission() throws TimeoutException, InterruptedException {
         return mCustomTabActivityTestRule.runJavaScriptCodeInCurrentTab("Notification.permission");
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java
index 6c9fb2a..2dd4de1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.webapps;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -671,6 +672,25 @@
         assertEquals(webappId, storage2.getId());
     }
 
+    @Test
+    @Feature({"WebApk"})
+    public void testHasWebApkForUrl() throws Exception {
+        final String startUrl = START_URL;
+        final String testUrl = START_URL + "/index.html";
+
+        assertFalse(WebappRegistry.getInstance().hasWebApkForUrl(testUrl));
+
+        String webappId = "webapp";
+        registerWebapp(webappId, new FetchStorageCallback(createShortcutIntent(startUrl)));
+        assertFalse(WebappRegistry.getInstance().hasWebApkForUrl(testUrl));
+
+        String webApkId = WebApkConstants.WEBAPK_ID_PREFIX + "WebApk";
+        registerWebapp(webApkId,
+                new FetchStorageCallback(
+                        createWebApkIntent(webApkId, startUrl, "org.chromium.webapk")));
+        assertTrue(WebappRegistry.getInstance().hasWebApkForUrl(testUrl));
+    }
+
     private Set<String> addWebappsToRegistry(String... webapps) {
         final Set<String> expected = new HashSet<>(Arrays.asList(webapps));
         mSharedPreferences.edit().putStringSet(WebappRegistry.KEY_WEBAPP_SET, expected).apply();
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 7386ba4..1a0d63f8 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -903,8 +903,8 @@
           <message name="IDS_SAVE_PAGE" desc="The text label of the Save Page As menu item">
             Save page &amp;as...
           </message>
-          <message name="IDS_DISTILL_PAGE" desc="The text label of the 'Distill page' menu item">
-            Distill page
+          <message name="IDS_DISTILL_PAGE" desc="The text label of the 'Toggle distilled page contents' menu item">
+            Toggle distilled page contents
           </message>
           <message name="IDS_MORE_TOOLS_MENU" desc="The text label of the Tools submenu for touch">
             More too&amp;ls
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 616c5bb..beab18d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -628,8 +628,6 @@
     "lifetime/application_lifetime_mac.mm",
     "lifetime/browser_shutdown.cc",
     "lifetime/browser_shutdown.h",
-    "loader/chrome_navigation_data.cc",
-    "loader/chrome_navigation_data.h",
     "lookalikes/lookalike_url_allowlist.cc",
     "lookalikes/lookalike_url_allowlist.h",
     "lookalikes/lookalike_url_controller_client.cc",
@@ -1094,6 +1092,11 @@
     "performance_manager/persistence/site_data/leveldb_site_data_store.h",
     "performance_manager/persistence/site_data/noop_site_data_writer.cc",
     "performance_manager/persistence/site_data/noop_site_data_writer.h",
+    "performance_manager/persistence/site_data/site_data_cache.h",
+    "performance_manager/persistence/site_data/site_data_cache_impl.cc",
+    "performance_manager/persistence/site_data/site_data_cache_impl.h",
+    "performance_manager/persistence/site_data/site_data_cache_inspector.cc",
+    "performance_manager/persistence/site_data/site_data_cache_inspector.h",
     "performance_manager/persistence/site_data/site_data_impl.cc",
     "performance_manager/persistence/site_data/site_data_impl.h",
     "performance_manager/persistence/site_data/site_data_reader.cc",
@@ -2898,6 +2901,8 @@
       "download/download_shelf_controller.h",
       "enterprise_reporting/prefs.cc",
       "enterprise_reporting/prefs.h",
+      "enterprise_reporting/report_scheduler.cc",
+      "enterprise_reporting/report_scheduler.h",
       "enterprise_reporting/report_uploader.cc",
       "enterprise_reporting/report_uploader.h",
       "enterprise_reporting/request_timer.cc",
@@ -5428,8 +5433,6 @@
       "chromeos/policy/fake_device_cloud_policy_manager.h",
       "chromeos/settings/device_settings_test_helper.cc",
       "chromeos/settings/device_settings_test_helper.h",
-      "ui/ash/tablet_mode_client_test_util.cc",
-      "ui/ash/tablet_mode_client_test_util.h",
     ]
     configs += [ "//build/config/linux/dbus" ]
     deps += [ "//chromeos:test_support" ]
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index e4adf9f..906088e 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -120,8 +120,6 @@
                                                     java_web_contents);
   Java_AssistantPaymentRequestModel_setWebContents(
       env, GetPaymentRequestModel(), java_web_contents);
-  Java_AssistantOverlayModel_setWebContents(env, GetOverlayModel(),
-                                            java_web_contents);
   if (ui_delegate->GetState() != AutofillAssistantState::INACTIVE) {
     // The UI was created for an existing Controller.
     OnStatusMessageChanged(ui_delegate->GetStatusMessage());
@@ -137,7 +135,9 @@
 
     std::vector<RectF> area;
     ui_delegate->GetTouchableArea(&area);
-    OnTouchableAreaChanged(area);
+    RectF visual_viewport;
+    ui_delegate->GetVisualViewport(&visual_viewport);
+    OnTouchableAreaChanged(visual_viewport, area);
     OnResizeViewportChanged(ui_delegate->GetResizeViewport());
     OnPeekModeChanged(ui_delegate->GetPeekMode());
     OnFormChanged(ui_delegate->GetForm());
@@ -529,8 +529,13 @@
 }
 
 void UiControllerAndroid::OnTouchableAreaChanged(
+    const RectF& visual_viewport,
     const std::vector<RectF>& areas) {
   JNIEnv* env = AttachCurrentThread();
+  Java_AssistantOverlayModel_setVisualViewport(
+      env, GetOverlayModel(), visual_viewport.left, visual_viewport.top,
+      visual_viewport.right, visual_viewport.bottom);
+
   std::vector<float> flattened;
   for (const auto& rect : areas) {
     flattened.emplace_back(rect.left);
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.h b/chrome/browser/android/autofill_assistant/ui_controller_android.h
index 9de00b01..cabcf97 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.h
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.h
@@ -73,7 +73,8 @@
   void OnInfoBoxChanged(const InfoBox* info_box) override;
   void OnProgressChanged(int progress) override;
   void OnProgressVisibilityChanged(bool visible) override;
-  void OnTouchableAreaChanged(const std::vector<RectF>& areas) override;
+  void OnTouchableAreaChanged(const RectF& visual_viewport,
+                              const std::vector<RectF>& areas) override;
   void OnResizeViewportChanged(bool resize_viewport) override;
   void OnPeekModeChanged(
       ConfigureBottomSheetProto::PeekMode peek_mode) override;
diff --git a/chrome/browser/background_sync/periodic_background_sync_permission_context.cc b/chrome/browser/background_sync/periodic_background_sync_permission_context.cc
index dda667d..2157e27 100644
--- a/chrome/browser/background_sync/periodic_background_sync_permission_context.cc
+++ b/chrome/browser/background_sync/periodic_background_sync_permission_context.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/background_sync/periodic_background_sync_permission_context.h"
 
-#include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/profiles/profile.h"
@@ -13,22 +12,44 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/content_features.h"
 
+#if defined(OS_ANDROID)
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/strings/utf_string_conversions.h"
+#include "jni/BackgroundSyncPwaDetector_jni.h"
+#endif
+
 PeriodicBackgroundSyncPermissionContext::
     PeriodicBackgroundSyncPermissionContext(Profile* profile)
     : PermissionContextBase(profile,
                             CONTENT_SETTINGS_TYPE_PERIODIC_BACKGROUND_SYNC,
                             blink::mojom::FeaturePolicyFeature::kNotFound) {}
 
+PeriodicBackgroundSyncPermissionContext::
+    ~PeriodicBackgroundSyncPermissionContext() = default;
+
 bool PeriodicBackgroundSyncPermissionContext::IsPwaInstalled(
     const GURL& url) const {
 #if defined(OS_ANDROID)
-  // TODO(crbug.com/925297): Add logic to detect PWAs on Android.
-  return false;
+  JNIEnv* env = base::android::AttachCurrentThread();
+  base::android::ScopedJavaLocalRef<jstring> java_url =
+      base::android::ConvertUTF8ToJavaString(env, url.spec());
+  return Java_BackgroundSyncPwaDetector_isPwaInstalled(env, java_url);
 #else
   return extensions::util::GetInstalledPwaForUrl(profile(), url);
 #endif
 }
 
+#if defined(OS_ANDROID)
+bool PeriodicBackgroundSyncPermissionContext::IsTwaInstalled(
+    const GURL& url) const {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  base::android::ScopedJavaLocalRef<jstring> java_url =
+      base::android::ConvertUTF8ToJavaString(env, url.spec());
+  return Java_BackgroundSyncPwaDetector_isTwaInstalled(env, java_url);
+}
+#endif
+
 bool PeriodicBackgroundSyncPermissionContext::IsRestrictedToSecureOrigins()
     const {
   return true;
@@ -44,8 +65,10 @@
   if (!base::FeatureList::IsEnabled(features::kPeriodicBackgroundSync))
     return CONTENT_SETTING_BLOCK;
 
-  // TODO(crbug.com/925297): If there's a TWA installed for the origin, grant
-  // permission.
+#if defined(OS_ANDROID)
+  if (IsTwaInstalled(requesting_origin))
+    return CONTENT_SETTING_ALLOW;
+#endif
 
   if (!IsPwaInstalled(requesting_origin))
     return CONTENT_SETTING_BLOCK;
diff --git a/chrome/browser/background_sync/periodic_background_sync_permission_context.h b/chrome/browser/background_sync/periodic_background_sync_permission_context.h
index 1331e0c8..d2363b22 100644
--- a/chrome/browser/background_sync/periodic_background_sync_permission_context.h
+++ b/chrome/browser/background_sync/periodic_background_sync_permission_context.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_BACKGROUND_SYNC_PERIODIC_BACKGROUND_SYNC_PERMISSION_CONTEXT_H_
 
 #include "base/macros.h"
+#include "build/build_config.h"
 #include "chrome/browser/permissions/permission_context_base.h"
 #include "components/content_settings/core/common/content_settings.h"
 
@@ -20,14 +21,24 @@
 // by disabling the (one-shot) Background Sync permission from content settings
 // UI. The periodic Background Sync content setting can be disabled via Finch,
 // and will prevent usage of the API.
+// The permission decision is made as follows:
+// If the feature is disabled, deny.
+// If we're on Android, and there's a TWA installed for the origin, grant
+// permission.
+// For other platforms, if there's no PWA installed for the origin, deny.
+// If there is a PWA installed, grant/deny permission based on whether the
+// one-shot Background Sync content setting is set to allow/block.
 class PeriodicBackgroundSyncPermissionContext : public PermissionContextBase {
  public:
   explicit PeriodicBackgroundSyncPermissionContext(Profile* profile);
-  ~PeriodicBackgroundSyncPermissionContext() override = default;
+  ~PeriodicBackgroundSyncPermissionContext() override;
 
  protected:
   // Virtual for testing.
   virtual bool IsPwaInstalled(const GURL& url) const;
+#if defined(OS_ANDROID)
+  virtual bool IsTwaInstalled(const GURL& url) const;
+#endif
 
  private:
   // PermissionContextBase implementation.
diff --git a/chrome/browser/background_sync/periodic_background_sync_permission_context_unittest.cc b/chrome/browser/background_sync/periodic_background_sync_permission_context_unittest.cc
index e060c4d..9ff079c 100644
--- a/chrome/browser/background_sync/periodic_background_sync_permission_context_unittest.cc
+++ b/chrome/browser/background_sync/periodic_background_sync_permission_context_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/macros.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/common/web_application_info.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
@@ -30,13 +31,26 @@
 
   void InstallPwa(const GURL& url) { installed_pwas_.insert(url); }
 
+#if defined(OS_ANDROID)
+  void InstallTwa(const GURL& url) { installed_twas_.insert(url); }
+#endif
+
   // PeriodicBackgroundSyncPermissionContext overrides:
   bool IsPwaInstalled(const GURL& url) const override {
     return installed_pwas_.find(url) != installed_pwas_.end();
   }
 
+#if defined(OS_ANDROID)
+  bool IsTwaInstalled(const GURL& url) const override {
+    return installed_twas_.find(url) != installed_twas_.end();
+  }
+#endif
+
  private:
   std::set<GURL> installed_pwas_;
+#if defined(OS_ANDROID)
+  std::set<GURL> installed_twas_;
+#endif
 };
 
 class PeriodicBackgroundSyncPermissionContextTest
@@ -84,6 +98,9 @@
   }
 
   void InstallPwa(const GURL& url) { permission_context_->InstallPwa(url); }
+#if defined(OS_ANDROID)
+  void InstallTwa(const GURL& url) { permission_context_->InstallTwa(url); }
+#endif
 
   void SetUpPwaAndContentSettings(const GURL& url) {
     InstallPwa(url);
@@ -131,7 +148,7 @@
             CONTENT_SETTING_ALLOW);
 }
 
-TEST_F(PeriodicBackgroundSyncPermissionContextTest, DesktopPWA) {
+TEST_F(PeriodicBackgroundSyncPermissionContextTest, DesktopPwa) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kPeriodicBackgroundSync);
   GURL url("https://example.com");
@@ -144,4 +161,18 @@
   EXPECT_EQ(GetPermissionStatus(url), CONTENT_SETTING_BLOCK);
 }
 
+#if defined(OS_ANDROID)
+TEST_F(PeriodicBackgroundSyncPermissionContextTest, Twa) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kPeriodicBackgroundSync);
+  GURL url("https://example.com");
+
+  // No TWA or PWA installed.
+  EXPECT_EQ(GetPermissionStatus(url), CONTENT_SETTING_BLOCK);
+
+  InstallTwa(url);
+  EXPECT_EQ(GetPermissionStatus(url), CONTENT_SETTING_ALLOW);
+}
+#endif
+
 }  // namespace
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index d6113f2..b257db8a 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -1273,9 +1273,9 @@
 #if !defined(OS_ANDROID)
   if (base::FeatureList::IsEnabled(features::kWebUsb)) {
     web_usb_detector_.reset(new WebUsbDetector());
-    BrowserThread::PostAfterStartupTask(
+    base::PostTaskWithTraits(
         FROM_HERE,
-        base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI}),
+        {content::BrowserThread::UI, base::TaskPriority::BEST_EFFORT},
         base::BindOnce(&WebUsbDetector::Initialize,
                        base::Unretained(web_usb_detector_.get())));
   }
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 37e8fff..0a40f3a9 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -434,10 +434,9 @@
 
 void MaybePostSettingsResetPrompt() {
   if (base::FeatureList::IsEnabled(safe_browsing::kSettingsResetPrompt)) {
-    content::BrowserThread::PostAfterStartupTask(
+    base::PostTaskWithTraits(
         FROM_HERE,
-        base::CreateSingleThreadTaskRunnerWithTraits(
-            {content::BrowserThread::UI}),
+        {content::BrowserThread::UI, base::TaskPriority::BEST_EFFORT},
         base::Bind(safe_browsing::MaybeShowSettingsResetPromptWithDelay));
   }
 }
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 99141ce..54ab3740 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -62,7 +62,6 @@
 #include "chrome/browser/font_family_cache.h"
 #include "chrome/browser/language/translate_frame_binder.h"
 #include "chrome/browser/lifetime/browser_shutdown.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/lookalikes/lookalike_url_navigation_throttle.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h"
diff --git a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
index b6c8216..c3fe806 100644
--- a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
+++ b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
@@ -212,10 +212,12 @@
     service_->Shutdown();
     chrome_keyboard_controller_client_test_helper_.reset();
     profile_.reset();
-    tablet_mode_client_.reset(nullptr);
     chromeos::input_method::InputMethodManager::Shutdown();
     ui::IMEBridge::Shutdown();
     ChromeAshTestBase::TearDown();
+    // To match ChromeBrowserMainExtraPartsAsh, shut down the TabletModeClient
+    // after Shell.
+    tablet_mode_client_.reset();
   }
 
  private:
diff --git a/chrome/browser/chromeos/child_accounts/screen_time_controller.cc b/chrome/browser/chromeos/child_accounts/screen_time_controller.cc
index c81f7eb..8b088f1 100644
--- a/chrome/browser/chromeos/child_accounts/screen_time_controller.cc
+++ b/chrome/browser/chromeos/child_accounts/screen_time_controller.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <utility>
 
+#include "ash/public/cpp/login_screen.h"
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/time/clock.h"
@@ -19,7 +20,6 @@
 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/ash/login_screen_client.h"
 #include "chrome/browser/ui/ash/media_client.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
@@ -286,7 +286,7 @@
 
   // Add parent access code button.
   if (base::FeatureList::IsEnabled(features::kParentAccessCode))
-    LoginScreenClient::Get()->login_screen()->SetShowParentAccessButton(true);
+    ash::LoginScreen::Get()->ShowParentAccessButton(true);
 
   // Prevent media from continuing to play after device is locked.
   MediaClient::Get()->SuspendMediaSessions();
@@ -302,7 +302,7 @@
           ->GetAccountId();
   ScreenLocker::default_screen_locker()->EnableAuthForUser(account_id);
   if (base::FeatureList::IsEnabled(features::kParentAccessCode))
-    LoginScreenClient::Get()->login_screen()->SetShowParentAccessButton(false);
+    ash::LoginScreen::Get()->ShowParentAccessButton(false);
 }
 
 void ScreenTimeController::OnPolicyChanged() {
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 5f15e8c..cb2a213 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -10,6 +10,7 @@
 
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/shelf_item.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/interfaces/ash_message_center_controller.mojom.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/shell.h"
@@ -1586,17 +1587,9 @@
   std::unique_ptr<api::autotest_private::SetTabletModeEnabled::Params> params(
       api::autotest_private::SetTabletModeEnabled::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
-  TabletModeClient::Get()->SetTabletModeEnabledForTesting(
-      params->enabled,
-      base::BindOnce(
-          &AutotestPrivateSetTabletModeEnabledFunction::OnSetTabletModeEnabled,
-          this));
-  return RespondLater();
-}
-
-void AutotestPrivateSetTabletModeEnabledFunction::OnSetTabletModeEnabled(
-    bool enabled) {
-  Respond(OneArgument(std::make_unique<base::Value>(enabled)));
+  ash::TabletMode::Get()->SetEnabledForTest(params->enabled);
+  return RespondNow(OneArgument(
+      std::make_unique<base::Value>(ash::TabletMode::Get()->IsEnabled())));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
index 49badeb..f0766dc 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
@@ -620,7 +620,6 @@
                              AUTOTESTPRIVATE_SETTABLETMODEENABLED)
 
  private:
-  void OnSetTabletModeEnabled(bool enabled);
   ~AutotestPrivateSetTabletModeEnabledFunction() override;
   ResponseAction Run() override;
 };
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index 8363cbe..85d16bd 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -47,7 +47,6 @@
 #include "chrome/browser/sync_file_system/sync_file_system_service_factory.h"
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/search/launcher_search/launcher_search_provider.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/views/extensions/extension_dialog.h"
 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
 #include "chrome/common/chrome_features.h"
@@ -2185,7 +2184,7 @@
   }
 
   if (name == "enableTabletMode") {
-    ::test::SetAndWaitForTabletMode(true);
+    ash::ShellTestApi().EnableTabletModeWindowManager(true);
     *output = "tabletModeEnabled";
     return;
   }
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc
index 4d03a0b..836b2fd3 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc
@@ -385,6 +385,7 @@
 
 void EasyUnlockServiceRegular::ShutdownInternal() {
   pref_manager_.reset();
+  notification_controller_.reset();
 
   proximity_auth::ScreenlockBridge::Get()->RemoveObserver(this);
 
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc
index 59bda93a..576bc89 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.cc
@@ -321,6 +321,10 @@
     return;
   service_active_ = false;
 
+  remote_device_cache_.reset();
+  challenge_wrapper_.reset();
+  pref_manager_.reset();
+
   weak_ptr_factory_.InvalidateWeakPtrs();
   proximity_auth::ScreenlockBridge::Get()->RemoveObserver(this);
   user_data_.clear();
diff --git a/chrome/browser/chromeos/login/lock/views_screen_locker.cc b/chrome/browser/chromeos/login/lock/views_screen_locker.cc
index 554a309..6cb3ee6 100644
--- a/chrome/browser/chromeos/login/lock/views_screen_locker.cc
+++ b/chrome/browser/chromeos/login/lock/views_screen_locker.cc
@@ -142,13 +142,11 @@
     int error_msg_id,
     HelpAppLauncher::HelpTopic help_topic_id) {
   // TODO(xiaoyinh): Complete the implementation here.
-  LoginScreenClient::Get()->login_screen()->ShowErrorMessage(
-      0 /* login_attempts */, std::string(), std::string(),
-      static_cast<int>(help_topic_id));
+  NOTIMPLEMENTED();
 }
 
 void ViewsScreenLocker::ClearErrors() {
-  LoginScreenClient::Get()->login_screen()->ClearErrors();
+  NOTIMPLEMENTED();
 }
 
 void ViewsScreenLocker::OnAshLockAnimationFinished() {
diff --git a/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc b/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc
index bd19f8b..07cf5015 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc
+++ b/chrome/browser/chromeos/login/signin/oauth2_login_verifier.cc
@@ -7,6 +7,7 @@
 #include <vector>
 
 #include "base/logging.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "services/identity/public/cpp/accounts_cookie_mutator.h"
 
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 08de5dc1..3c6124a 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -13,6 +13,7 @@
 #include <vector>
 
 #include "ash/public/cpp/ash_switches.h"
+#include "ash/public/cpp/login_screen.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
@@ -1509,7 +1510,7 @@
 }
 
 void WizardController::OnGuestModePolicyUpdated() {
-  LoginScreenClient::Get()->login_screen()->SetShowGuestButtonInOobe(
+  ash::LoginScreen::Get()->ShowGuestButtonInOobe(
       user_manager::UserManager::Get()->IsGuestSessionAllowed());
 }
 
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc
index d804bcc..833e9ca 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc
@@ -83,19 +83,32 @@
 
 bool DeviceOAuth2TokenServiceDelegate::RefreshTokenIsAvailable(
     const std::string& account_id) const {
+  auto accounts = GetAccounts();
+  return std::find(accounts.begin(), accounts.end(), account_id) !=
+         accounts.end();
+}
+
+std::vector<std::string> DeviceOAuth2TokenServiceDelegate::GetAccounts() {
+  return static_cast<const DeviceOAuth2TokenServiceDelegate&>(*this)
+      .GetAccounts();
+}
+
+std::vector<std::string> DeviceOAuth2TokenServiceDelegate::GetAccounts() const {
+  std::vector<std::string> accounts;
   switch (state_) {
     case STATE_NO_TOKEN:
     case STATE_TOKEN_INVALID:
-      return false;
+      return accounts;
     case STATE_LOADING:
     case STATE_VALIDATION_PENDING:
     case STATE_VALIDATION_STARTED:
     case STATE_TOKEN_VALID:
-      return account_id == GetRobotAccountId();
+      accounts.push_back(GetRobotAccountId());
+      return accounts;
   }
 
   NOTREACHED() << "Unhandled state " << state_;
-  return false;
+  return accounts;
 }
 
 std::string DeviceOAuth2TokenServiceDelegate::GetRobotAccountId() const {
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h
index be434be..3636bde 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h
@@ -61,6 +61,8 @@
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       OAuth2AccessTokenConsumer* consumer) override;
 
+  std::vector<std::string> GetAccounts() override;
+
   // gaia::GaiaOAuthClient::Delegate implementation.
   void OnRefreshTokenResponse(const std::string& access_token,
                               int expires_in_seconds) override;
@@ -94,6 +96,8 @@
     STATE_TOKEN_VALID,
   };
 
+  std::vector<std::string> GetAccounts() const;
+
   // Invoked by CrosSettings when the robot account ID becomes available.
   void OnServiceAccountIdentityChanged();
 
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc
index 8416e517..a691da6 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/data_use_measurement/chrome_data_use_measurement.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/previews/previews_service.h"
 #include "chrome/browser/previews/previews_service_factory.h"
@@ -270,17 +269,9 @@
 DataReductionProxyChromeSettings::CreateDataFromNavigationHandle(
     content::NavigationHandle* handle,
     const net::HttpResponseHeaders* headers) {
-  ChromeNavigationData* chrome_navigation_data =
-      static_cast<ChromeNavigationData*>(handle->GetNavigationData());
-  if (chrome_navigation_data) {
-    if (chrome_navigation_data->GetDataReductionProxyData())
-      return chrome_navigation_data->GetDataReductionProxyData()->DeepCopy();
-    return nullptr;
-  }
-
   // Some unit tests don't have data_reduction_proxy_service() set.
   if (!data_reduction_proxy_service())
-    return nullptr;
+    return test_data_ ? std::move(test_data_) : nullptr;
 
   // TODO(721403): Need to fill in:
   //  - request_info_
@@ -342,6 +333,11 @@
   return data;
 }
 
+void DataReductionProxyChromeSettings::SetDataForNextCommitForTesting(
+    std::unique_ptr<data_reduction_proxy::DataReductionProxyData> data) {
+  test_data_ = std::move(data);
+}
+
 // static
 data_reduction_proxy::Client DataReductionProxyChromeSettings::GetClient() {
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h
index 27c3e415..8470118 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h
@@ -99,6 +99,11 @@
   CreateDataFromNavigationHandle(content::NavigationHandle* handle,
                                  const net::HttpResponseHeaders* headers);
 
+  // This data will be used on the next commit if it's HTTP/HTTPS and the page
+  // is not an error page..
+  void SetDataForNextCommitForTesting(
+      std::unique_ptr<data_reduction_proxy::DataReductionProxyData> data);
+
  private:
   // Helper method for migrating the Data Reduction Proxy away from using the
   // proxy pref. Returns the ProxyPrefMigrationResult value indicating the
@@ -109,6 +114,8 @@
   // Null before InitDataReductionProxySettings is called.
   Profile* profile_;
 
+  std::unique_ptr<data_reduction_proxy::DataReductionProxyData> test_data_;
+
   DISALLOW_COPY_AND_ASSIGN(DataReductionProxyChromeSettings);
 };
 
diff --git a/chrome/browser/dom_distiller/tab_utils.cc b/chrome/browser/dom_distiller/tab_utils.cc
index 6c17d5f..f767aa33 100644
--- a/chrome/browser/dom_distiller/tab_utils.cc
+++ b/chrome/browser/dom_distiller/tab_utils.cc
@@ -185,3 +185,22 @@
   StartNavigationToDistillerViewer(destination_web_contents,
                                    source_web_contents->GetLastCommittedURL());
 }
+
+void ReturnToOriginalPage(content::WebContents* distilled_web_contents) {
+  DCHECK(distilled_web_contents);
+  DCHECK(dom_distiller::url_utils::IsDistilledPage(
+      distilled_web_contents->GetLastCommittedURL()));
+
+  GURL distilled_url = distilled_web_contents->GetLastCommittedURL();
+  GURL source_url =
+      dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(distilled_url);
+  DCHECK_NE(source_url, distilled_url)
+      << "Could not retrieve original page for distilled URL: "
+      << distilled_url;
+
+  // TODO(https://crbug.com/925965): Consider saving & retrieving the original
+  // page web contents instead of reloading the page.
+  content::NavigationController::LoadURLParams params(source_url);
+  params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
+  distilled_web_contents->GetController().LoadURLWithParams(params);
+}
diff --git a/chrome/browser/dom_distiller/tab_utils.h b/chrome/browser/dom_distiller/tab_utils.h
index e5364bb7..7200d8a 100644
--- a/chrome/browser/dom_distiller/tab_utils.h
+++ b/chrome/browser/dom_distiller/tab_utils.h
@@ -24,4 +24,9 @@
 void DistillAndView(content::WebContents* source_web_contents,
                     content::WebContents* destination_web_contents);
 
+// Navigates back to the original page from which this page was distilled.
+// |distilled_web_contents| must not be null and must point to an already
+// distilled page. This method does not take ownership of the web contents.
+void ReturnToOriginalPage(content::WebContents* distilled_web_contents);
+
 #endif  // CHROME_BROWSER_DOM_DISTILLER_TAB_UTILS_H_
diff --git a/chrome/browser/dom_distiller/tab_utils_browsertest.cc b/chrome/browser/dom_distiller/tab_utils_browsertest.cc
index fc0db4bc..5fd39092 100644
--- a/chrome/browser/dom_distiller/tab_utils_browsertest.cc
+++ b/chrome/browser/dom_distiller/tab_utils_browsertest.cc
@@ -45,19 +45,44 @@
   return new_web_contents;
 }
 
-// DistilledPageObserver is used to detect if a distilled page has
-// finished loading. This is done by checking how many times the title has
-// been set rather than using "DidFinishLoad" directly due to the content
-// being set by JavaScript.
-class DistilledPageObserver : public content::WebContentsObserver {
+// Helper class that blocks test execution until |observed_contents| enters a
+// certain state. Subclasses specify the precise state by calling
+// |new_url_loaded_runner_|.QuitClosure().Run() when |observed_contents| is
+// ready.
+class NavigationObserver : public content::WebContentsObserver {
  public:
-  explicit DistilledPageObserver(content::WebContents* observed_contents)
-      : title_set_count_(0), loaded_distiller_page_(false) {
+  explicit NavigationObserver(content::WebContents* observed_contents) {
     content::WebContentsObserver::Observe(observed_contents);
   }
 
   void WaitUntilFinishedLoading() { new_url_loaded_runner_.Run(); }
 
+ protected:
+  base::RunLoop new_url_loaded_runner_;
+};
+
+class OriginalPageNavigationObserver : public NavigationObserver {
+ public:
+  using NavigationObserver::NavigationObserver;
+
+  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+                     const GURL& validated_url) override {
+    if (!render_frame_host->GetParent())
+      new_url_loaded_runner_.QuitClosure().Run();
+  }
+};
+
+// DistilledPageObserver is used to detect if a distilled page has
+// finished loading. This is done by checking how many times the title has
+// been set rather than using "DidFinishLoad" directly due to the content
+// being set by JavaScript.
+class DistilledPageObserver : public NavigationObserver {
+ public:
+  explicit DistilledPageObserver(content::WebContents* observed_contents)
+      : NavigationObserver(observed_contents),
+        title_set_count_(0),
+        loaded_distiller_page_(false) {}
+
   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                      const GURL& validated_url) override {
     if (!render_frame_host->GetParent() &&
@@ -76,7 +101,6 @@
   }
 
  private:
-  base::RunLoop new_url_loaded_runner_;
   int title_set_count_;
   bool loaded_distiller_page_;
 };
@@ -164,5 +188,32 @@
   destroyed_watcher.Wait();
 }
 
+IN_PROC_BROWSER_TEST_F(DomDistillerTabUtilsBrowserTest, ToggleOriginalPage) {
+  content::WebContents* source_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // This blocks until the navigation has completely finished.
+  ui_test_utils::NavigateToURL(browser(), article_url());
+
+  // Create and navigate to the distilled page.
+  browser()->tab_strip_model()->AppendWebContents(
+      NewContentsWithSameParamsAs(source_web_contents),
+      /* foreground = */ true);
+  content::WebContents* destination_web_contents =
+      browser()->tab_strip_model()->GetWebContentsAt(1);
+
+  DistillAndView(source_web_contents, destination_web_contents);
+  DistilledPageObserver(destination_web_contents).WaitUntilFinishedLoading();
+  ASSERT_TRUE(url_utils::IsDistilledPage(
+      destination_web_contents->GetLastCommittedURL()));
+
+  // Now return to the original page.
+  ReturnToOriginalPage(destination_web_contents);
+  OriginalPageNavigationObserver(destination_web_contents)
+      .WaitUntilFinishedLoading();
+  EXPECT_EQ(source_web_contents->GetLastCommittedURL(),
+            destination_web_contents->GetLastCommittedURL());
+}
+
 }  // namespace
 }  // namespace dom_distiller
diff --git a/chrome/browser/enterprise_reporting/report_scheduler.cc b/chrome/browser/enterprise_reporting/report_scheduler.cc
new file mode 100644
index 0000000..ca13c06
--- /dev/null
+++ b/chrome/browser/enterprise_reporting/report_scheduler.cc
@@ -0,0 +1,129 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise_reporting/report_scheduler.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/task/post_task.h"
+#include "base/time/time.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/enterprise_reporting/prefs.h"
+#include "chrome/browser/enterprise_reporting/request_timer.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/policy/browser_dm_token_storage.h"
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/prefs/pref_service.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace em = enterprise_management;
+
+namespace enterprise_reporting {
+namespace {
+const int kDefaultUploadIntervalHours =
+    24;  // Default upload interval is 24 hours.
+
+// Reads DM token and client id. Returns true if boths are non empty.
+bool GetDMTokenAndDeviceId(std::string* dm_token, std::string* client_id) {
+  DCHECK(dm_token && client_id);
+  *dm_token = policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
+  *client_id = policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
+
+  if (dm_token->empty() || client_id->empty()) {
+    VLOG(1)
+        << "Enterprise reporting is disabled because device is not enrolled.";
+    return false;
+  }
+  return true;
+}
+
+// Returns true if cloud reporting is enabled.
+bool IsReportingEnabled() {
+  return g_browser_process->local_state()->GetBoolean(
+      prefs::kCloudReportingEnabled);
+}
+
+}  // namespace
+
+ReportScheduler::ReportScheduler(
+    std::unique_ptr<policy::CloudPolicyClient> client,
+    std::unique_ptr<RequestTimer> request_timer)
+    : cloud_policy_client_(std::move(client)),
+      request_timer_(std::move(request_timer)) {
+  RegisterPerfObserver();
+}
+
+ReportScheduler::~ReportScheduler() = default;
+
+void ReportScheduler::RegisterPerfObserver() {
+  pref_change_registrar_.Init(g_browser_process->local_state());
+  pref_change_registrar_.Add(
+      prefs::kCloudReportingEnabled,
+      base::BindRepeating(&ReportScheduler::OnReportEnabledPerfChanged,
+                          base::Unretained(this)));
+  // Trigger first perf check during launch process.
+  OnReportEnabledPerfChanged();
+}
+
+void ReportScheduler::OnReportEnabledPerfChanged() {
+  std::string dm_token;
+  std::string client_id;
+  if (!IsReportingEnabled() || !GetDMTokenAndDeviceId(&dm_token, &client_id)) {
+    if (request_timer_)
+      request_timer_->Stop();
+    return;
+  }
+
+  if (!cloud_policy_client_->is_registered()) {
+    cloud_policy_client_->SetupRegistration(dm_token, client_id,
+                                            std::vector<std::string>());
+  }
+
+  Start();
+}
+
+void ReportScheduler::Start() {
+  base::TimeDelta upload_interval =
+      base::TimeDelta::FromHours(kDefaultUploadIntervalHours);
+  base::TimeDelta first_request_delay =
+      upload_interval -
+      (base::Time::Now() -
+       g_browser_process->local_state()->GetTime(kLastUploadTimestamp));
+  // The first report delay is based on the |lastUploadTimestamp| in the
+  // |local_state|, after that, it's 24 hours for each succeeded upload.
+  request_timer_->Start(
+      FROM_HERE, first_request_delay, upload_interval,
+      base::BindRepeating(&ReportScheduler::GenerateAndUploadReport,
+                          base::Unretained(this)));
+}
+
+void ReportScheduler::GenerateAndUploadReport() {
+  VLOG(1) << "Uploading enterprise report.";
+  // TODO(zmin): Generates a real request.
+  std::unique_ptr<em::ChromeDesktopReportRequest> request =
+      std::make_unique<em::ChromeDesktopReportRequest>();
+  cloud_policy_client_->UploadChromeDesktopReport(
+      std::move(request),
+      base::BindRepeating(&ReportScheduler::OnReportUploaded,
+                          base::Unretained(this)));
+}
+
+void ReportScheduler::OnReportUploaded(bool status) {
+  if (status) {
+    VLOG(1) << "The enterprise report has been uploaded.";
+    g_browser_process->local_state()->SetTime(kLastUploadTimestamp,
+                                              base::Time::Now());
+    if (IsReportingEnabled())
+      request_timer_->Reset();
+    return;
+  }
+  VLOG(1) << "The enterprise report has not been uploaded.";
+  // TODO(zmin): Implement retry logic
+}
+
+}  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise_reporting/report_scheduler.h b/chrome/browser/enterprise_reporting/report_scheduler.h
new file mode 100644
index 0000000..98a8a666
--- /dev/null
+++ b/chrome/browser/enterprise_reporting/report_scheduler.h
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_SCHEDULER_H_
+#define CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_SCHEDULER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "components/prefs/pref_change_registrar.h"
+
+namespace policy {
+class CloudPolicyClient;
+}  // namespace policy
+
+namespace enterprise_reporting {
+
+class RequestTimer;
+
+// Schedules the next report and handles retry in case of error. It also cancels
+// all pending uploads if the report policy is turned off.
+class ReportScheduler {
+ public:
+  ReportScheduler(std::unique_ptr<policy::CloudPolicyClient> client,
+                  std::unique_ptr<RequestTimer> request_timer);
+
+  ~ReportScheduler();
+
+ private:
+  // Observes CloudReportingEnabled policy.
+  void RegisterPerfObserver();
+
+  // Handles kCloudReportingEnabled policy value change, including the first
+  // policy value check during startup.
+  void OnReportEnabledPerfChanged();
+
+  // Schedules the first update request.
+  void Start();
+
+  // Generates a report and uploads it.
+  void GenerateAndUploadReport();
+
+  // Callback once report upload request is finished.
+  void OnReportUploaded(bool status);
+
+  // Policy value watcher
+  PrefChangeRegistrar pref_change_registrar_;
+
+  std::unique_ptr<policy::CloudPolicyClient> cloud_policy_client_;
+
+  std::unique_ptr<RequestTimer> request_timer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportScheduler);
+};
+
+}  // namespace enterprise_reporting
+
+#endif  // CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_SCHEDULER_H_
diff --git a/chrome/browser/enterprise_reporting/report_scheduler_unittest.cc b/chrome/browser/enterprise_reporting/report_scheduler_unittest.cc
new file mode 100644
index 0000000..6e821d30
--- /dev/null
+++ b/chrome/browser/enterprise_reporting/report_scheduler_unittest.cc
@@ -0,0 +1,278 @@
+// 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/enterprise_reporting/report_scheduler.h"
+
+#include <utility>
+
+#include "base/test/scoped_feature_list.h"
+#include "base/test/scoped_task_environment.h"
+#include "chrome/browser/enterprise_reporting/prefs.h"
+#include "chrome/browser/enterprise_reporting/request_timer.h"
+#include "chrome/browser/policy/fake_browser_dm_token_storage.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::WithArgs;
+
+namespace em = enterprise_management;
+
+namespace enterprise_reporting {
+namespace {
+constexpr char kDMToken[] = "dm_token";
+constexpr char kClientId[] = "client_id";
+const int kDefaultUploadInterval = 24;
+}  // namespace
+class FakeRequestTimer : public RequestTimer {
+ public:
+  FakeRequestTimer() = default;
+  ~FakeRequestTimer() override = default;
+  void Start(const base::Location& posted_from,
+             base::TimeDelta first_delay,
+             base::TimeDelta repeat_delay,
+             base::RepeatingClosure user_task) override {
+    first_delay_ = first_delay;
+    repeat_delay_ = repeat_delay;
+    user_task_ = user_task;
+    is_running_ = true;
+  }
+
+  void Stop() override { is_running_ = false; }
+
+  void Reset() override { is_running_ = true; }
+
+  void Fire() {
+    EXPECT_TRUE(is_running_);
+    user_task_.Run();
+    is_running_ = false;
+  }
+
+  bool is_running() { return is_running_; }
+
+  base::TimeDelta first_delay() { return first_delay_; }
+
+  base::TimeDelta repeat_delay() { return repeat_delay_; }
+
+ private:
+  base::TimeDelta first_delay_;
+  base::TimeDelta repeat_delay_;
+  base::RepeatingClosure user_task_;
+  bool is_running_ = false;
+  DISALLOW_COPY_AND_ASSIGN(FakeRequestTimer);
+};
+
+class ReportSchedulerTest : public ::testing::Test {
+ public:
+  ReportSchedulerTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
+            base::test::ScopedTaskEnvironment::NowSource::
+                MAIN_THREAD_MOCK_TIME),
+        local_state_(TestingBrowserProcess::GetGlobal()) {}
+  ~ReportSchedulerTest() override = default;
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kEnterpriseReportingInBrowser);
+    RegisterPrefs(local_state_.Get()->registry());
+    client_ptr_ = std::make_unique<policy::MockCloudPolicyClient>();
+    client_ = client_ptr_.get();
+    timer_ptr_ = std::make_unique<FakeRequestTimer>();
+    timer_ = timer_ptr_.get();
+    Init(true, kDMToken, kClientId);
+  }
+
+  void Init(bool policy_enabled,
+            const std::string& dm_token,
+            const std::string& client_id) {
+    ToggleCloudReport(policy_enabled);
+    storage_.SetDMToken(dm_token);
+    storage_.SetClientId(client_id);
+  }
+
+  void CreateScheduler() {
+    scheduler_ = std::make_unique<ReportScheduler>(std::move(client_ptr_),
+                                                   std::move(timer_ptr_));
+  }
+
+  void SetLastUploadInHour(int gap) {
+    previous_set_last_upload_timestamp_ =
+        base::Time::Now() - base::TimeDelta::FromHours(gap);
+    local_state_.Get()->SetTime(kLastUploadTimestamp,
+                                previous_set_last_upload_timestamp_);
+  }
+
+  void ToggleCloudReport(bool enabled) {
+    local_state_.Get()->SetManagedPref(prefs::kCloudReportingEnabled,
+                                       std::make_unique<base::Value>(enabled));
+  }
+
+  // If lastUploadTimestamp is updated recently, it should be updated as Now().
+  // Otherwise, it should be same as previous set timestamp.
+  void ExpectLastUploadTimestampUpdated(bool is_updated) {
+    auto current_last_upload_timestamp =
+        local_state_.Get()->GetTime(kLastUploadTimestamp);
+    if (is_updated) {
+      EXPECT_EQ(base::Time::Now(), current_last_upload_timestamp);
+    } else {
+      EXPECT_EQ(previous_set_last_upload_timestamp_,
+                current_last_upload_timestamp);
+    }
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  ScopedTestingLocalState local_state_;
+
+  std::unique_ptr<ReportScheduler> scheduler_;
+  FakeRequestTimer* timer_;
+  policy::MockCloudPolicyClient* client_;
+  policy::FakeBrowserDMTokenStorage storage_;
+  base::Time previous_set_last_upload_timestamp_;
+
+ private:
+  std::unique_ptr<policy::MockCloudPolicyClient> client_ptr_;
+  std::unique_ptr<FakeRequestTimer> timer_ptr_;
+  DISALLOW_COPY_AND_ASSIGN(ReportSchedulerTest);
+};
+
+TEST_F(ReportSchedulerTest, NoReportWithoutPolicy) {
+  Init(false, kDMToken, kClientId);
+  CreateScheduler();
+  EXPECT_FALSE(timer_->is_running());
+}
+
+TEST_F(ReportSchedulerTest, NoReportWithoutDMToken) {
+  Init(true, "", kClientId);
+  CreateScheduler();
+  EXPECT_FALSE(timer_->is_running());
+}
+
+TEST_F(ReportSchedulerTest, NoReportWithoutClientId) {
+  Init(true, kDMToken, "");
+  CreateScheduler();
+  EXPECT_FALSE(timer_->is_running());
+}
+
+TEST_F(ReportSchedulerTest, UploadReportSucceeded) {
+  EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
+  EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
+      .WillOnce(WithArgs<1>(policy::ScheduleStatusCallback(true)));
+
+  CreateScheduler();
+  EXPECT_TRUE(timer_->is_running());
+
+  timer_->Fire();
+
+  // timer is paused until the report is finished.
+  EXPECT_FALSE(timer_->is_running());
+
+  // Run pending task.
+  scoped_task_environment_.FastForwardBy(base::TimeDelta());
+
+  // Next report is scheduled.
+  EXPECT_TRUE(timer_->is_running());
+  ExpectLastUploadTimestampUpdated(true);
+
+  ::testing::Mock::VerifyAndClearExpectations(client_);
+}
+
+TEST_F(ReportSchedulerTest, UploadFailed) {
+  EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
+  EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
+      .WillOnce(WithArgs<1>(policy::ScheduleStatusCallback(false)));
+
+  CreateScheduler();
+  EXPECT_TRUE(timer_->is_running());
+
+  timer_->Fire();
+
+  // timer is paused until the report is finished.
+  EXPECT_FALSE(timer_->is_running());
+
+  // Run pending task.
+  scoped_task_environment_.FastForwardBy(base::TimeDelta());
+
+  // Next report is not scheduled.
+  EXPECT_FALSE(timer_->is_running());
+  ExpectLastUploadTimestampUpdated(false);
+
+  ::testing::Mock::VerifyAndClearExpectations(client_);
+}
+
+TEST_F(ReportSchedulerTest, TimerDelayWithLastUploadTimestamp) {
+  int gap = 10;
+  SetLastUploadInHour(gap);
+
+  EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
+
+  CreateScheduler();
+  EXPECT_TRUE(timer_->is_running());
+
+  EXPECT_EQ(base::TimeDelta::FromHours(kDefaultUploadInterval - gap),
+            timer_->first_delay());
+  EXPECT_EQ(base::TimeDelta::FromHours(kDefaultUploadInterval),
+            timer_->repeat_delay());
+
+  ::testing::Mock::VerifyAndClearExpectations(client_);
+}
+
+TEST_F(ReportSchedulerTest, TimerDelayWithoutLastUploadTimestamp) {
+  EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
+
+  CreateScheduler();
+  EXPECT_TRUE(timer_->is_running());
+
+  EXPECT_GT(base::TimeDelta(), timer_->first_delay());
+  EXPECT_EQ(base::TimeDelta::FromHours(kDefaultUploadInterval),
+            timer_->repeat_delay());
+
+  ::testing::Mock::VerifyAndClearExpectations(client_);
+}
+
+TEST_F(ReportSchedulerTest,
+       ReportingIsDisabledWhileNewReportIsScheduledButNotPosted) {
+  EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
+
+  CreateScheduler();
+  EXPECT_TRUE(timer_->is_running());
+
+  ToggleCloudReport(false);
+
+  // Next report is not scheduled.
+  EXPECT_FALSE(timer_->is_running());
+  ExpectLastUploadTimestampUpdated(false);
+
+  ::testing::Mock::VerifyAndClearExpectations(client_);
+}
+
+TEST_F(ReportSchedulerTest, ReportingIsDisabledWhileNewReportIsPosted) {
+  EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
+  EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
+      .WillOnce(WithArgs<1>(policy::ScheduleStatusCallback(true)));
+
+  CreateScheduler();
+  EXPECT_TRUE(timer_->is_running());
+
+  timer_->Fire();
+  ToggleCloudReport(false);
+  EXPECT_FALSE(timer_->is_running());
+
+  // Run pending task.
+  scoped_task_environment_.FastForwardBy(base::TimeDelta());
+
+  ExpectLastUploadTimestampUpdated(true);
+  // Next report is not scheduled.
+  EXPECT_FALSE(timer_->is_running());
+
+  ::testing::Mock::VerifyAndClearExpectations(client_);
+}
+
+}  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise_reporting/request_timer.h b/chrome/browser/enterprise_reporting/request_timer.h
index 48f2a2e..2522d5a 100644
--- a/chrome/browser/enterprise_reporting/request_timer.h
+++ b/chrome/browser/enterprise_reporting/request_timer.h
@@ -17,22 +17,22 @@
 class RequestTimer {
  public:
   RequestTimer();
-  ~RequestTimer();
+  virtual ~RequestTimer();
 
   // Starts the timer. The first task will be ran after |first_delay|. The
   // following task will be ran with |repeat_delay|. If |first_delay| is larger
   // than the |repeat_delay|, the first request will be fired after
   // |repeat_delay| instead. Also, please note that the repeating task is ran
   // once per Reset call.
-  void Start(const base::Location& posted_from,
-             base::TimeDelta first_delay,
-             base::TimeDelta repeat_delay,
-             base::RepeatingClosure user_task);
+  virtual void Start(const base::Location& posted_from,
+                     base::TimeDelta first_delay,
+                     base::TimeDelta repeat_delay,
+                     base::RepeatingClosure user_task);
   // Stops the timer. The running task will not be abandon.
-  void Stop();
+  virtual void Stop();
   // Resets the timer, ran the task again after |repat_delay| that is set in
   // Start(); This is only available after the first task is ran.
-  void Reset();
+  virtual void Reset();
 
   bool IsRepeatTimerRunning() const;
   bool IsFirstTimerRunning() const;
diff --git a/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc b/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc
index 86d1373..cdc589f 100644
--- a/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc
+++ b/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc
@@ -80,14 +80,21 @@
         new DisplayInfoProviderChromeOS(connector_.get()));
 
     tablet_mode_client_ = std::make_unique<TabletModeClient>();
-    ash::mojom::TabletModeControllerPtr controller;
-    ash::Shell::Get()->tablet_mode_controller()->BindRequest(
-        mojo::MakeRequest(&controller));
-    tablet_mode_client_->InitForTesting(std::move(controller));
-    // We must flush the TabletModeClient as we are waiting for the initial
-    // value to be set, as the TabletModeController sends an initial message on
-    // startup when it observes the PowerManagerClient.
-    tablet_mode_client_->FlushForTesting();
+    tablet_mode_client_->Init();
+
+    // Wait for TabletModeController to take its initial state from the power
+    // manager.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(ash::Shell::Get()
+                     ->tablet_mode_controller()
+                     ->IsTabletModeWindowManagerEnabled());
+  }
+
+  void TearDown() override {
+    ChromeAshTestBase::TearDown();
+    // To match ChromeBrowserMainExtraPartsAsh, shut down the TabletModeClient
+    // after Shell.
+    tablet_mode_client_.reset();
   }
 
   void AddCrosDisplayConfigControllerBinding(
@@ -123,7 +130,6 @@
     ash::TabletModeController* controller =
         ash::Shell::Get()->tablet_mode_controller();
     controller->EnableTabletModeWindowManager(enable);
-    tablet_mode_client_->FlushForTesting();
   }
 
   display::DisplayManager* GetDisplayManager() const {
@@ -1571,6 +1577,9 @@
   // Entering tablet mode will cause DisplayConfigurationObserver to set
   // forced mirror mode. https://crbug.com/733092.
   EnableTabletMode(true);
+  // DisplayConfigurationObserver enables mirror mode asynchronously after
+  // tablet mode is enabled.
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(display_manager()->IsInMirrorMode());
   result = GetAllDisplaysInfo();
   ASSERT_EQ(1u, result.size());
diff --git a/chrome/browser/loader/chrome_navigation_data.cc b/chrome/browser/loader/chrome_navigation_data.cc
deleted file mode 100644
index 679838b0..0000000
--- a/chrome/browser/loader/chrome_navigation_data.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/loader/chrome_navigation_data.h"
-
-#include "base/memory/ptr_util.h"
-#include "base/supports_user_data.h"
-#include "net/url_request/url_request.h"
-
-const void* const kChromeNavigationDataUserDataKey =
-    &kChromeNavigationDataUserDataKey;
-
-namespace {
-
-// UserData object that owns ChromeNavigationData. This is used rather than
-// having ChromeNavigationData directly extend base::SupportsUserData::Data to
-// avoid naming conflicts between Data::Clone() and
-// content::NavigationData::Clone().
-class NavigationDataOwner : public base::SupportsUserData::Data {
- public:
-  NavigationDataOwner() = default;
-  ~NavigationDataOwner() override = default;
-
-  ChromeNavigationData* data() { return &data_; }
-
- private:
-  ChromeNavigationData data_;
-
-  DISALLOW_COPY_AND_ASSIGN(NavigationDataOwner);
-};
-
-}  // namespace
-
-ChromeNavigationData::ChromeNavigationData() {}
-
-ChromeNavigationData::~ChromeNavigationData() {}
-
-// static
-ChromeNavigationData* ChromeNavigationData::GetDataAndCreateIfNecessary(
-    net::URLRequest* request) {
-  if (!request)
-    return nullptr;
-  NavigationDataOwner* data_owner_ptr = static_cast<NavigationDataOwner*>(
-      request->GetUserData(kChromeNavigationDataUserDataKey));
-  if (data_owner_ptr)
-    return data_owner_ptr->data();
-  std::unique_ptr<NavigationDataOwner> data_owner =
-      std::make_unique<NavigationDataOwner>();
-  data_owner_ptr = data_owner.get();
-  request->SetUserData(kChromeNavigationDataUserDataKey, std::move(data_owner));
-  return data_owner_ptr->data();
-}
-
-std::unique_ptr<content::NavigationData> ChromeNavigationData::Clone() {
-  std::unique_ptr<ChromeNavigationData> copy(new ChromeNavigationData());
-  if (data_reduction_proxy_data_)
-    copy->SetDataReductionProxyData(data_reduction_proxy_data_->DeepCopy());
-  return std::move(copy);
-}
diff --git a/chrome/browser/loader/chrome_navigation_data.h b/chrome/browser/loader/chrome_navigation_data.h
deleted file mode 100644
index 923e3f3..0000000
--- a/chrome/browser/loader/chrome_navigation_data.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_LOADER_CHROME_NAVIGATION_DATA_H_
-#define CHROME_BROWSER_LOADER_CHROME_NAVIGATION_DATA_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
-#include "content/public/browser/navigation_data.h"
-
-namespace net {
-class URLRequest;
-}
-
-class ChromeNavigationData : public content::NavigationData {
- public:
-  ChromeNavigationData();
-  ~ChromeNavigationData() override;
-
-  // Creates a new ChromeNavigationData that is a deep copy of the original. Any
-  // changes to the original after the clone is created will not be reflected in
-  // the clone.
-  // |data_reduction_proxy_data_| is deep copied.
-  std::unique_ptr<content::NavigationData> Clone() override;
-
-  // Takes ownership of |data_reduction_proxy_data|.
-  void SetDataReductionProxyData(
-      std::unique_ptr<data_reduction_proxy::DataReductionProxyData>
-          data_reduction_proxy_data) {
-    data_reduction_proxy_data_ = std::move(data_reduction_proxy_data);
-  }
-
-  data_reduction_proxy::DataReductionProxyData* GetDataReductionProxyData()
-      const {
-    return data_reduction_proxy_data_.get();
-  }
-
-  static ChromeNavigationData* GetDataAndCreateIfNecessary(
-      net::URLRequest* request);
-
- private:
-  // Manages the lifetime of optional DataReductionProxy information.
-  std::unique_ptr<data_reduction_proxy::DataReductionProxyData>
-      data_reduction_proxy_data_;
-
-  DISALLOW_COPY_AND_ASSIGN(ChromeNavigationData);
-};
-
-#endif  // CHROME_BROWSER_LOADER_CHROME_NAVIGATION_DATA_H_
diff --git a/chrome/browser/loader/chrome_navigation_data_unittest.cc b/chrome/browser/loader/chrome_navigation_data_unittest.cc
deleted file mode 100644
index 5712222..0000000
--- a/chrome/browser/loader/chrome_navigation_data_unittest.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/loader/chrome_navigation_data.h"
-
-#include <memory>
-
-#include "base/memory/ptr_util.h"
-#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
-#include "content/public/browser/navigation_data.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class ChromeNavigationDataTest : public testing::Test {
- public:
-  ChromeNavigationDataTest() {}
-  ~ChromeNavigationDataTest() override {}
-};
-
-TEST_F(ChromeNavigationDataTest, AddingDataReductionProxyData) {
-  std::unique_ptr<ChromeNavigationData> data(new ChromeNavigationData());
-  data_reduction_proxy::DataReductionProxyData* data_reduction_proxy_data =
-      new data_reduction_proxy::DataReductionProxyData();
-  data->SetDataReductionProxyData(base::WrapUnique(data_reduction_proxy_data));
-  EXPECT_EQ(data_reduction_proxy_data, data->GetDataReductionProxyData());
-}
-
-TEST_F(ChromeNavigationDataTest, Clone) {
-  ChromeNavigationData data;
-  EXPECT_FALSE(data.GetDataReductionProxyData());
-  data.SetDataReductionProxyData(
-      std::make_unique<data_reduction_proxy::DataReductionProxyData>());
-
-  std::unique_ptr<content::NavigationData> clone_data = data.Clone();
-  ChromeNavigationData* clone_chrome_data =
-      static_cast<ChromeNavigationData*>(clone_data.get());
-  EXPECT_NE(&data, clone_data.get());
-  EXPECT_NE(&data, clone_chrome_data);
-  EXPECT_NE(data.GetDataReductionProxyData(),
-            clone_chrome_data->GetDataReductionProxyData());
-}
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc
index 307086b..57042173 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc
@@ -12,7 +12,6 @@
 #include "base/time/time.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
 #include "chrome/browser/previews/previews_ui_tab_helper.h"
@@ -25,7 +24,6 @@
 #include "components/data_reduction_proxy/proto/pageload_metrics.pb.h"
 #include "components/previews/content/previews_user_data.h"
 #include "content/public/browser/browser_context.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -131,11 +129,6 @@
     ukm::SourceId source_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // As documented in content/public/browser/navigation_handle.h, this
-  // NavigationData is a clone of the NavigationData instance returned from
-  // ResourceDispatcherHostDelegate::GetNavigationData during commit.
-  // Because ChromeResourceDispatcherHostDelegate always returns a
-  // ChromeNavigationData, it is safe to static_cast here.
   std::unique_ptr<DataReductionProxyData> data;
   auto* settings =
       DataReductionProxyChromeSettingsFactory::GetForBrowserContext(
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc
index 8c5a827..d0edf45 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc
@@ -11,7 +11,8 @@
 #include "base/process/kill.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
 #include "chrome/browser/page_load_metrics/metrics_web_contents_observer.h"
 #include "chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h"
 #include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
@@ -26,6 +27,7 @@
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/previews/content/previews_user_data.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/web_contents_tester.h"
 #include "net/base/ip_endpoint.h"
 #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
@@ -61,13 +63,17 @@
   // DataReductionProxyMetricsObserverBase:
   ObservePolicy OnCommitCalled(content::NavigationHandle* navigation_handle,
                                ukm::SourceId source_id) override {
-    DataReductionProxyData* data =
-        DataForNavigationHandle(web_contents_, navigation_handle);
+    auto data =
+        std::make_unique<data_reduction_proxy::DataReductionProxyData>();
+    data->set_request_url(navigation_handle->GetURL());
     data->set_used_data_reduction_proxy(data_reduction_proxy_used_);
     data->set_was_cached_data_reduction_proxy_response(
         cached_data_reduction_proxy_used_);
     data->set_request_url(GURL(kDefaultTestUrl));
     data->set_lite_page_received(lite_page_used_);
+    DataReductionProxyChromeSettingsFactory::GetForBrowserContext(
+        web_contents_->GetBrowserContext())
+        ->SetDataForNextCommitForTesting(std::move(data));
 
     auto* previews_data = PreviewsDataForNavigationHandle(navigation_handle);
     previews_data->set_black_listed_for_lite_page(black_listed_);
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.cc
index 1d29d9d9..c66f3abf 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h"
 
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/page_load_metrics/metrics_web_contents_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_tracker.h"
@@ -13,20 +12,6 @@
 
 namespace data_reduction_proxy {
 
-DataReductionProxyData* DataForNavigationHandle(
-    content::WebContents* web_contents,
-    content::NavigationHandle* navigation_handle) {
-  auto chrome_navigation_data = std::make_unique<ChromeNavigationData>();
-
-  auto drp_data = std::make_unique<DataReductionProxyData>();
-  DataReductionProxyData* data = drp_data.get();
-  chrome_navigation_data->SetDataReductionProxyData(std::move(drp_data));
-
-  content::WebContentsTester::For(web_contents)
-      ->SetNavigationData(navigation_handle, std::move(chrome_navigation_data));
-  return data;
-}
-
 previews::PreviewsUserData* PreviewsDataForNavigationHandle(
     content::NavigationHandle* navigation_handle) {
   PreviewsUITabHelper* ui_tab_helper =
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h
index 290a0d6..cb7758c0 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h
@@ -19,7 +19,6 @@
 #include "chrome/common/page_load_metrics/test/page_load_metrics_test_util.h"
 #include "components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.h"
-#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
 #include "components/previews/content/previews_user_data.h"
 #include "net/nqe/effective_connection_type.h"
 #include "third_party/blink/public/platform/web_input_event.h"
@@ -29,12 +28,6 @@
 const char kDefaultTestUrl[] = "http://google.com";
 const int kMemoryKb = 1024;
 
-// Attaches a new |DataReductionProxyData| to |navigation_handle|'s navigation
-// data.
-DataReductionProxyData* DataForNavigationHandle(
-    content::WebContents* web_contents,
-    content::NavigationHandle* navigation_handle);
-
 // Attaches a new |PreviewsUserData| to the given |navigation_handle|.
 previews::PreviewsUserData* PreviewsDataForNavigationHandle(
     content::NavigationHandle* navigation_handle);
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_unittest.cc
index c1eb752c..682edef 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_unittest.cc
@@ -10,12 +10,15 @@
 
 #include "base/metrics/field_trial.h"
 #include "base/time/time.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
 #include "chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h"
 #include "chrome/browser/page_load_metrics/observers/histogram_suffixes.h"
 #include "chrome/browser/page_load_metrics/page_load_tracker.h"
 #include "components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
 #include "components/previews/content/previews_user_data.h"
+#include "content/public/browser/web_contents.h"
 
 namespace data_reduction_proxy {
 
@@ -43,13 +46,17 @@
   // DataReductionProxyMetricsObserver:
   ObservePolicy OnCommitCalled(content::NavigationHandle* navigation_handle,
                                ukm::SourceId source_id) override {
-    DataReductionProxyData* data =
-        DataForNavigationHandle(web_contents_, navigation_handle);
+    auto data =
+        std::make_unique<data_reduction_proxy::DataReductionProxyData>();
+    data->set_request_url(navigation_handle->GetURL());
     data->set_used_data_reduction_proxy(data_reduction_proxy_used_);
     data->set_was_cached_data_reduction_proxy_response(
         cached_data_reduction_proxy_used_);
     data->set_request_url(GURL(kDefaultTestUrl));
     data->set_lite_page_received(lite_page_used_);
+    DataReductionProxyChromeSettingsFactory::GetForBrowserContext(
+        web_contents_->GetBrowserContext())
+        ->SetDataForNextCommitForTesting(std::move(data));
 
     auto* previews_data = PreviewsDataForNavigationHandle(navigation_handle);
     previews_data->set_black_listed_for_lite_page(black_listed_);
diff --git a/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer.cc
index 147d208..2a4a2b4 100644
--- a/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer.cc
@@ -10,7 +10,6 @@
 #include "base/time/time.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
 #include "chrome/browser/previews/previews_content_util.h"
diff --git a/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer_unittest.cc
index 59c49b89..cda6175 100644
--- a/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/previews_page_load_metrics_observer_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/optional.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_tracker.h"
diff --git a/chrome/browser/password_manager/password_accessory_controller_impl.cc b/chrome/browser/password_manager/password_accessory_controller_impl.cc
index 16416aa..90777fd 100644
--- a/chrome/browser/password_manager/password_accessory_controller_impl.cc
+++ b/chrome/browser/password_manager/password_accessory_controller_impl.cc
@@ -186,6 +186,14 @@
 void PasswordAccessoryControllerImpl::RefreshSuggestionsForField(
     FocusedFieldType focused_field_type,
     bool is_manual_generation_available) {
+  // Prevent crashing by not acting at all if frame became unfocused at any
+  // point. The next time a focus event happens, this will be called again and
+  // ensure we show correct data.
+  if (web_contents_->GetFocusedFrame() == nullptr)
+    return;
+  url::Origin origin = GetFocusedFrameOrigin();
+  if (origin.opaque())
+    return;  // Don't proceed for invalid origins.
   std::vector<UserInfo> info_to_add;
   std::vector<FooterCommand> footer_commands_to_add;
 
@@ -219,10 +227,10 @@
 
   GetManualFillingController()->RefreshSuggestionsForField(
       focused_field_type,
-      autofill::CreateAccessorySheetData(
-          autofill::AccessoryTabType::PASSWORDS,
-          GetTitle(has_suggestions, GetFocusedFrameOrigin()),
-          std::move(info_to_add), std::move(footer_commands_to_add)));
+      autofill::CreateAccessorySheetData(autofill::AccessoryTabType::PASSWORDS,
+                                         GetTitle(has_suggestions, origin),
+                                         std::move(info_to_add),
+                                         std::move(footer_commands_to_add)));
 
   if (is_password_field) {
     GetManualFillingController()->ShowWhenKeyboardIsVisible(
@@ -243,6 +251,8 @@
     base::OnceCallback<void(const gfx::Image&)> icon_callback) {
   url::Origin origin =
       GetFocusedFrameOrigin();  // Copy origin in case it changes.
+  if (origin.opaque())
+    return;  // Don't proceed for invalid origins.
   // Check whether this request can be immediately answered with a cached icon.
   // It is empty if there wasn't at least one request that found an icon yet.
   FaviconRequestData* icon_request = &icons_request_data_[origin];
@@ -314,6 +324,8 @@
     const base::string16& suggestion,
     bool is_password,
     const url::Origin& origin) const {
+  if (origin.opaque())
+    return false;  // Don't proceed for invalid origins.
   for (const PasswordAccessorySuggestion& element :
        origin_suggestions_.at(origin)) {
     const base::string16& candidate =
@@ -333,6 +345,11 @@
 }
 
 url::Origin PasswordAccessoryControllerImpl::GetFocusedFrameOrigin() const {
+  if (web_contents_->GetFocusedFrame() == nullptr) {
+    LOG(DFATAL) << "Tried to get retrieve origin without focused "
+                   "frame.";
+    return url::Origin();  // Nonce!
+  }
   return web_contents_->GetFocusedFrame()->GetLastCommittedOrigin();
 }
 
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.cc b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.cc
index 8dc9f42..ead87197 100644
--- a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.cc
+++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.cc
@@ -34,8 +34,7 @@
   }
 
   static void NotifyAllFramesInProcessFrozen(ProcessNodeImpl* process_node) {
-    for (auto& observer : process_node->observers())
-      observer.OnAllFramesInProcessFrozen(process_node);
+    process_node->OnAllFramesInProcessFrozen();
   }
 };
 
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h
index 496f1e1..07dbac0 100644
--- a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h
+++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h
@@ -16,7 +16,7 @@
 
 // The FrozenFrameAggregator is responsible for tracking frame frozen states,
 // and aggregating this property to the page and process nodes.
-class FrozenFrameAggregator : public GraphObserverDefaultImpl {
+class FrozenFrameAggregator : public GraphImplObserverDefaultImpl {
  public:
   struct Data;
 
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
index df27098..7f182e9 100644
--- a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
+++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
@@ -20,7 +20,7 @@
 
 using LifecycleState = PageNodeImpl::LifecycleState;
 
-class LenientMockGraphObserver : public GraphObserverDefaultImpl {
+class LenientMockGraphObserver : public GraphImplObserverDefaultImpl {
  public:
   LenientMockGraphObserver() = default;
   ~LenientMockGraphObserver() override = default;
diff --git a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h
index 67dfcdc..f9a9672 100644
--- a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h
+++ b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h
@@ -19,7 +19,7 @@
 // reached an "almost idle" state after initial load, based on CPU and network
 // quiescence, as well as an absolute timeout. This state is then updated on
 // PageNodes in a graph.
-class PageAlmostIdleDecorator : public GraphObserverDefaultImpl {
+class PageAlmostIdleDecorator : public GraphImplObserverDefaultImpl {
  public:
   class Data;
 
diff --git a/chrome/browser/performance_manager/graph/frame_node.cc b/chrome/browser/performance_manager/graph/frame_node.cc
index 1b36b7bc..fda0f77 100644
--- a/chrome/browser/performance_manager/graph/frame_node.cc
+++ b/chrome/browser/performance_manager/graph/frame_node.cc
@@ -12,4 +12,10 @@
 FrameNode::FrameNode() = default;
 FrameNode::~FrameNode() = default;
 
+FrameNodeObserver::FrameNodeObserver() = default;
+FrameNodeObserver::~FrameNodeObserver() = default;
+
+FrameNode::ObserverDefaultImpl::ObserverDefaultImpl() = default;
+FrameNode::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl.cc b/chrome/browser/performance_manager/graph/frame_node_impl.cc
index b8db546..0e0e18b 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/frame_node_impl.cc
@@ -11,9 +11,6 @@
 
 namespace performance_manager {
 
-FrameNodeImplObserver::FrameNodeImplObserver() = default;
-FrameNodeImplObserver::~FrameNodeImplObserver() = default;
-
 FrameNodeImpl::FrameNodeImpl(GraphImpl* graph,
                              ProcessNodeImpl* process_node,
                              PageNodeImpl* page_node,
@@ -104,6 +101,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& observer : observers())
     observer.OnNonPersistentNotificationCreated(this);
+  for (auto* observer : GetObservers())
+    observer->OnNonPersistentNotificationCreated(this);
 }
 
 FrameNodeImpl* FrameNodeImpl::parent_frame_node() const {
@@ -363,7 +362,4 @@
   network_almost_idle.SetAndMaybeNotify(frame_node, false);
 }
 
-FrameNodeImpl::ObserverDefaultImpl::ObserverDefaultImpl() = default;
-FrameNodeImpl::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
-
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl.h b/chrome/browser/performance_manager/graph/frame_node_impl.h
index 8167cee1..0c8c628 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl.h
+++ b/chrome/browser/performance_manager/graph/frame_node_impl.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/performance_manager/graph/node_base.h"
+#include "chrome/browser/performance_manager/observers/graph_observer.h"
 #include "chrome/browser/performance_manager/public/graph/frame_node.h"
 #include "url/gurl.h"
 
@@ -20,37 +21,6 @@
 class PageNodeImpl;
 class ProcessNodeImpl;
 
-// Observer interface for FrameNodeImpl objects. This must be declared first as
-// the type is referenced by members of FrameNodeImpl.
-class FrameNodeImplObserver {
- public:
-  FrameNodeImplObserver();
-  virtual ~FrameNodeImplObserver();
-
-  // Notifications of property changes.
-
-  // Invoked when the |is_current| property changes.
-  virtual void OnIsCurrentChanged(FrameNodeImpl* frame_node) = 0;
-
-  // Invoked when the |network_almost_idle| property changes.
-  virtual void OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) = 0;
-
-  // Invoked when the |lifecycle_state| property changes.
-  virtual void OnLifecycleStateChanged(FrameNodeImpl* frame_node) = 0;
-
-  // Invoked when the |url| property changes.
-  virtual void OnURLChanged(FrameNodeImpl* frame_node) = 0;
-
-  // Events with no property changes.
-
-  // Invoked when a non-persistent notification has been issued by the frame.
-  virtual void OnNonPersistentNotificationCreated(
-      FrameNodeImpl* frame_node) = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(FrameNodeImplObserver);
-};
-
 // Frame nodes form a tree structure, each FrameNode at most has one parent that
 // is a FrameNode. Conceptually, a frame corresponds to a
 // content::RenderFrameHost in the browser, and a content::RenderFrameImpl /
@@ -75,14 +45,12 @@
 // active frame.
 class FrameNodeImpl
     : public PublicNodeImpl<FrameNodeImpl, FrameNode>,
-      public TypedNodeBase<FrameNodeImpl, FrameNodeImplObserver>,
+      public TypedNodeBase<FrameNodeImpl,
+                           GraphImplObserver,
+                           FrameNode,
+                           FrameNodeObserver>,
       public resource_coordinator::mojom::DocumentCoordinationUnit {
  public:
-  // A do-nothing implementation of the observer. Derive from this if you want
-  // to selectively override a few methods and not have to worry about
-  // continuously updating your implementation as new methods are added.
-  class ObserverDefaultImpl;
-
   static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kFrame; }
 
   // Construct a frame node associated with a |process_node|, a |page_node| and
@@ -160,14 +128,19 @@
 
     void Reset(FrameNodeImpl* frame_node, const GURL& url_in);
 
-    ObservedProperty::NotifiesOnlyOnChanges<GURL, &Observer::OnURLChanged> url;
+    ObservedProperty::NotifiesOnlyOnChanges<GURL,
+                                            &GraphImplObserver::OnURLChanged,
+                                            &FrameNodeObserver::OnURLChanged>
+        url;
     bool has_nonempty_beforeunload = false;
 
     // Network is considered almost idle when there are no more than 2 network
     // connections.
-    ObservedProperty::
-        NotifiesOnlyOnChanges<bool, &Observer::OnNetworkAlmostIdleChanged>
-            network_almost_idle{false};
+    ObservedProperty::NotifiesOnlyOnChanges<
+        bool,
+        &GraphImplObserver::OnNetworkAlmostIdleChanged,
+        &FrameNodeObserver::OnNetworkAlmostIdleChanged>
+        network_almost_idle{false};
   };
 
   // Invoked by subframes on joining/leaving the graph.
@@ -208,13 +181,17 @@
   // Does *not* change when a navigation is committed.
   ObservedProperty::NotifiesOnlyOnChanges<
       resource_coordinator::mojom::LifecycleState,
-      &Observer::OnLifecycleStateChanged>
+      &GraphImplObserver::OnLifecycleStateChanged,
+      &FrameNodeObserver::OnLifecycleStateChanged>
       lifecycle_state_{resource_coordinator::mojom::LifecycleState::kRunning};
 
   // This is a one way switch. Once marked an ad-frame, always an ad-frame.
   bool is_ad_frame_ = false;
 
-  ObservedProperty::NotifiesOnlyOnChanges<bool, &Observer::OnIsCurrentChanged>
+  ObservedProperty::NotifiesOnlyOnChanges<
+      bool,
+      &GraphImplObserver::OnIsCurrentChanged,
+      &FrameNodeObserver::OnIsCurrentChanged>
       is_current_{false};
 
   // Intervention policy for this frame. These are communicated from the
@@ -238,23 +215,6 @@
   DISALLOW_COPY_AND_ASSIGN(FrameNodeImpl);
 };
 
-// A do-nothing default implementation of a FrameNodeImpl::Observer.
-class FrameNodeImpl::ObserverDefaultImpl : public FrameNodeImpl::Observer {
- public:
-  ObserverDefaultImpl();
-  ~ObserverDefaultImpl() override;
-
-  // FrameNodeImplObserver implementation:
-  void OnIsCurrentChanged(FrameNodeImpl* frame_node) override {}
-  void OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) override {}
-  void OnLifecycleStateChanged(FrameNodeImpl* frame_node) override {}
-  void OnNonPersistentNotificationCreated(FrameNodeImpl* frame_node) override {}
-  void OnURLChanged(FrameNodeImpl* frame_node) override {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
-};
-
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_FRAME_NODE_IMPL_H_
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
index f77b99e..40a3d6f 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/performance_manager/graph/page_node_impl.h"
 #include "chrome/browser/performance_manager/graph/process_node_impl.h"
 #include "chrome/browser/performance_manager/performance_manager_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
@@ -109,4 +110,97 @@
   EXPECT_TRUE(frame_node->is_ad_frame());
 }
 
+namespace {
+
+class LenientMockObserver : public FrameNodeImpl::Observer {
+ public:
+  LenientMockObserver() {}
+  ~LenientMockObserver() override {}
+
+  MOCK_METHOD1(OnFrameNodeAdded, void(const FrameNode*));
+  MOCK_METHOD1(OnBeforeFrameNodeRemoved, void(const FrameNode*));
+  MOCK_METHOD1(OnIsCurrentChanged, void(const FrameNode*));
+  MOCK_METHOD1(OnNetworkAlmostIdleChanged, void(const FrameNode*));
+  MOCK_METHOD1(OnLifecycleStateChanged, void(const FrameNode*));
+  MOCK_METHOD1(OnNonPersistentNotificationCreated, void(const FrameNode*));
+  MOCK_METHOD1(OnURLChanged, void(const FrameNode*));
+
+  void SetNotifiedFrameNode(const FrameNode* frame_node) {
+    notified_frame_node_ = frame_node;
+  }
+
+  const FrameNode* TakeNotifiedFrameNode() {
+    const FrameNode* node = notified_frame_node_;
+    notified_frame_node_ = nullptr;
+    return node;
+  }
+
+ private:
+  const FrameNode* notified_frame_node_ = nullptr;
+};
+
+using MockObserver = ::testing::StrictMock<LenientMockObserver>;
+
+using testing::_;
+using testing::Invoke;
+
+}  // namespace
+
+TEST_F(FrameNodeImplTest, ObserverWorks) {
+  auto process = CreateNode<ProcessNodeImpl>();
+  auto page = CreateNode<PageNodeImpl>();
+
+  MockObserver obs;
+  graph()->AddFrameNodeObserver(&obs);
+
+  // Create a frame node and expect a matching call to "OnFrameNodeAdded".
+  EXPECT_CALL(obs, OnFrameNodeAdded(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedFrameNode));
+  auto frame_node = CreateNode<FrameNodeImpl>(process.get(), page.get());
+  const FrameNode* raw_frame_node = frame_node.get();
+  EXPECT_EQ(raw_frame_node, obs.TakeNotifiedFrameNode());
+
+  // Invoke "SetIsCurrent" and expect a "OnIsCurrentChanged" callback.
+  EXPECT_CALL(obs, OnIsCurrentChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedFrameNode));
+  frame_node->SetIsCurrent(true);
+  EXPECT_EQ(raw_frame_node, obs.TakeNotifiedFrameNode());
+
+  // Invoke "SetNetworkAlmostIdle" and expect an "OnNetworkAlmostIdleChanged"
+  // callback.
+  EXPECT_CALL(obs, OnNetworkAlmostIdleChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedFrameNode));
+  frame_node->SetNetworkAlmostIdle();
+  EXPECT_EQ(raw_frame_node, obs.TakeNotifiedFrameNode());
+
+  // Invoke "SetLifecycleState" and expect an "OnLifecycleStateChanged"
+  // callback.
+  EXPECT_CALL(obs, OnLifecycleStateChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedFrameNode));
+  frame_node->SetLifecycleState(
+      resource_coordinator::mojom::LifecycleState::kFrozen);
+  EXPECT_EQ(raw_frame_node, obs.TakeNotifiedFrameNode());
+
+  // Invoke "OnNonPersistentNotificationCreated" and expect an
+  // "OnNonPersistentNotificationCreated" callback.
+  EXPECT_CALL(obs, OnNonPersistentNotificationCreated(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedFrameNode));
+  frame_node->OnNonPersistentNotificationCreated();
+  EXPECT_EQ(raw_frame_node, obs.TakeNotifiedFrameNode());
+
+  // Invoke "OnNavigationCommitted" and expect an "OnURLChanged" callback.
+  EXPECT_CALL(obs, OnURLChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedFrameNode));
+  frame_node->OnNavigationCommitted(GURL("https://foo.com/"), true);
+  EXPECT_EQ(raw_frame_node, obs.TakeNotifiedFrameNode());
+
+  // Release the frame node and expect a call to "OnBeforeFrameNodeRemoved".
+  EXPECT_CALL(obs, OnBeforeFrameNodeRemoved(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedFrameNode));
+  frame_node.reset();
+  EXPECT_EQ(raw_frame_node, obs.TakeNotifiedFrameNode());
+
+  graph()->RemoveFrameNodeObserver(&obs);
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/graph.cc b/chrome/browser/performance_manager/graph/graph.cc
index beda5cc..27a6cf9e 100644
--- a/chrome/browser/performance_manager/graph/graph.cc
+++ b/chrome/browser/performance_manager/graph/graph.cc
@@ -9,4 +9,7 @@
 Graph::Graph() = default;
 Graph::~Graph() = default;
 
+GraphObserver::GraphObserver() = default;
+GraphObserver::~GraphObserver() = default;
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/graph_impl.cc b/chrome/browser/performance_manager/graph/graph_impl.cc
index af510f4..1453d8e8 100644
--- a/chrome/browser/performance_manager/graph/graph_impl.cc
+++ b/chrome/browser/performance_manager/graph/graph_impl.cc
@@ -4,12 +4,15 @@
 
 #include "chrome/browser/performance_manager/graph/graph_impl.h"
 
+#include <algorithm>
 #include <utility>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/containers/flat_set.h"
+#include "base/logging.h"
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "chrome/browser/performance_manager/graph/frame_node_impl.h"
 #include "chrome/browser/performance_manager/graph/node_base.h"
 #include "chrome/browser/performance_manager/graph/page_node_impl.h"
@@ -28,6 +31,30 @@
 // A unique type ID for this implementation.
 const uintptr_t kGraphImplType = reinterpret_cast<uintptr_t>(&kGraphImplType);
 
+template <typename NodeObserverType>
+void AddObserverImpl(std::vector<NodeObserverType*>* observers,
+                     NodeObserverType* observer) {
+  DCHECK(observers);
+  DCHECK(observer);
+  auto it = std::find(observers->begin(), observers->end(), observer);
+  DCHECK(it == observers->end());
+  observers->push_back(observer);
+}
+
+template <typename NodeObserverType>
+void RemoveObserverImpl(std::vector<NodeObserverType*>* observers,
+                        NodeObserverType* observer) {
+  DCHECK(observers);
+  DCHECK(observer);
+  // We expect to find the observer in the array.
+  auto it = std::find(observers->begin(), observers->end(), observer);
+  DCHECK(it != observers->end());
+  observers->erase(it);
+  // There should only have been one copy of the observer.
+  it = std::find(observers->begin(), observers->end(), observer);
+  DCHECK(it == observers->end());
+}
+
 }  // namespace
 
 GraphImpl::GraphImpl() {
@@ -37,20 +64,87 @@
 GraphImpl::~GraphImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // Notify graph observers that the graph is being destroyed.
+  for (auto* observer : graph_observers_)
+    observer->OnBeforeGraphDestroyed(this);
+
   // All observers should have been removed before the graph is deleted.
+  // TODO(chrisha): This will disappear, as new observers are allowed to stay
+  // attached at graph death.
   DCHECK(observers_.empty());
   // All process nodes should have been removed already.
   DCHECK(processes_by_pid_.empty());
 
   // Remove the system node from the graph, this should be the only node left.
-  if (system_node_.get()) {
-    RemoveNode(system_node_.get());
-    system_node_.reset();
-  }
+  ReleaseSystemNode();
 
   DCHECK(nodes_.empty());
 }
 
+void GraphImpl::AddGraphObserver(GraphObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  AddObserverImpl(&graph_observers_, observer);
+}
+
+void GraphImpl::AddFrameNodeObserver(FrameNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  AddObserverImpl(&frame_node_observers_, observer);
+}
+
+void GraphImpl::AddPageNodeObserver(PageNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  AddObserverImpl(&page_node_observers_, observer);
+}
+
+void GraphImpl::AddProcessNodeObserver(ProcessNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  AddObserverImpl(&process_node_observers_, observer);
+}
+
+void GraphImpl::AddSystemNodeObserver(SystemNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  AddObserverImpl(&system_node_observers_, observer);
+}
+
+void GraphImpl::RemoveGraphObserver(GraphObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  RemoveObserverImpl(&graph_observers_, observer);
+}
+
+void GraphImpl::RemoveFrameNodeObserver(FrameNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  RemoveObserverImpl(&frame_node_observers_, observer);
+}
+
+void GraphImpl::RemovePageNodeObserver(PageNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  RemoveObserverImpl(&page_node_observers_, observer);
+}
+
+void GraphImpl::RemoveProcessNodeObserver(ProcessNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  RemoveObserverImpl(&process_node_observers_, observer);
+}
+
+void GraphImpl::RemoveSystemNodeObserver(SystemNodeObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  RemoveObserverImpl(&system_node_observers_, observer);
+}
+
+void GraphImpl::RegisterObserver(GraphImplObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  observer->SetGraph(this);
+  AddObserverImpl(&observers_, observer);
+  observer->OnRegistered();
+}
+
+void GraphImpl::UnregisterObserver(GraphImplObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  RemoveObserverImpl(&observers_, observer);
+  observer->OnUnregistered();
+  observer->SetGraph(nullptr);
+}
+
 uintptr_t GraphImpl::GetImplType() const {
   return kGraphImplType;
 }
@@ -65,30 +159,10 @@
   return reinterpret_cast<GraphImpl*>(const_cast<void*>(graph->GetImpl()));
 }
 
-void GraphImpl::RegisterObserver(GraphObserver* observer) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  observer->SetGraph(this);
-  observers_.push_back(observer);
-  observer->OnRegistered();
-}
-
-void GraphImpl::UnregisterObserver(GraphObserver* observer) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  bool removed = false;
-  for (auto it = observers_.begin(); it != observers_.end(); ++it) {
-    if (*it == observer) {
-      observers_.erase(it);
-      removed = true;
-      observer->OnUnregistered();
-      observer->SetGraph(nullptr);
-      break;
-    }
-  }
-  DCHECK(removed);
-}
-
 void GraphImpl::OnNodeAdded(NodeBase* node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // This handles legacy GraphImplObserver implementation.
   for (auto* observer : observers_) {
     if (observer->ShouldObserve(node)) {
       // TODO(chrisha): Remove this logic once all observers have been migrated.
@@ -112,53 +186,74 @@
       observer->OnNodeAdded(node);
     }
   }
+
+  // This handles the strongly typed observer notifications.
+  switch (node->type()) {
+    case NodeTypeEnum::kFrame: {
+      auto* frame_node = FrameNodeImpl::FromNodeBase(node);
+      for (auto* observer : frame_node_observers_)
+        observer->OnFrameNodeAdded(frame_node);
+    } break;
+    case NodeTypeEnum::kPage: {
+      auto* page_node = PageNodeImpl::FromNodeBase(node);
+      for (auto* observer : page_node_observers_)
+        observer->OnPageNodeAdded(page_node);
+    } break;
+    case NodeTypeEnum::kProcess: {
+      auto* process_node = ProcessNodeImpl::FromNodeBase(node);
+      for (auto* observer : process_node_observers_)
+        observer->OnProcessNodeAdded(process_node);
+    } break;
+    case NodeTypeEnum::kSystem: {
+      auto* system_node = SystemNodeImpl::FromNodeBase(node);
+      for (auto* observer : system_node_observers_)
+        observer->OnSystemNodeAdded(system_node);
+    } break;
+    case NodeTypeEnum::kInvalidType: {
+      NOTREACHED();
+    } break;
+  }
 }
 
 void GraphImpl::OnBeforeNodeRemoved(NodeBase* node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // TODO(chrisha): Kill this logic once observer implementations use distinct
-  // interfaces.
+  // This handles the strongly typed observer notifications.
   switch (node->type()) {
     case NodeTypeEnum::kFrame: {
-      OnBeforeNodeRemovedImpl(FrameNodeImpl::FromNodeBase(node));
+      auto* frame_node = FrameNodeImpl::FromNodeBase(node);
+      for (auto* observer : frame_node_observers_)
+        observer->OnBeforeFrameNodeRemoved(frame_node);
     } break;
     case NodeTypeEnum::kPage: {
-      OnBeforeNodeRemovedImpl(PageNodeImpl::FromNodeBase(node));
+      auto* page_node = PageNodeImpl::FromNodeBase(node);
+      for (auto* observer : page_node_observers_)
+        observer->OnBeforePageNodeRemoved(page_node);
     } break;
     case NodeTypeEnum::kProcess: {
-      OnBeforeNodeRemovedImpl(ProcessNodeImpl::FromNodeBase(node));
+      auto* process_node = ProcessNodeImpl::FromNodeBase(node);
+      for (auto* observer : process_node_observers_)
+        observer->OnBeforeProcessNodeRemoved(process_node);
     } break;
     case NodeTypeEnum::kSystem: {
-      OnBeforeNodeRemovedImpl(SystemNodeImpl::FromNodeBase(node));
+      auto* system_node = SystemNodeImpl::FromNodeBase(node);
+      for (auto* observer : system_node_observers_)
+        observer->OnBeforeSystemNodeRemoved(system_node);
     } break;
     case NodeTypeEnum::kInvalidType: {
       NOTREACHED();
     } break;
   }
 
+  // Dispatch to the legacy observers.
+  for (auto& observer : node->observers())
+    observer.OnBeforeNodeRemoved(node);
+
   // Leave the graph only after the OnBeforeNodeRemoved notification so that the
   // node still observes the graph invariant during that callback.
   node->LeaveGraph();
 }
 
-template <typename NodeType>
-void GraphImpl::OnBeforeNodeRemovedImpl(NodeType* node) {
-  // The current observer logic ensures that OnBeforeNodeRemoved is only fired
-  // for nodes that had an observer added via ShouldObserve. This logic will
-  // be disappearing entirely, but emulate it for correctness right now.
-
-  base::flat_set<typename NodeType::Observer*> node_observers;
-  for (auto& observer : node->observers())
-    node_observers.insert(&observer);
-
-  for (auto* observer : observers_) {
-    typename NodeType::Observer* node_observer = observer;
-    if (base::ContainsKey(node_observers, node_observer))
-      observer->OnBeforeNodeRemoved(node);
-  }
-}
-
 int64_t GraphImpl::GetNextNodeSerializationId() {
   return ++current_node_serialization_id_;
 }
@@ -275,14 +370,11 @@
   return ret;
 }
 
-GraphImpl::Observer::Observer() = default;
-GraphImpl::Observer::~Observer() = default;
-
-GraphImpl::ObserverDefaultImpl::ObserverDefaultImpl() = default;
-GraphImpl::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
-
-void GraphImpl::ObserverDefaultImpl::SetGraph(GraphImpl* graph) {
-  graph_ = graph;
+void GraphImpl::ReleaseSystemNode() {
+  if (!system_node_.get())
+    return;
+  RemoveNode(system_node_.get());
+  system_node_.reset();
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/graph_impl.h b/chrome/browser/performance_manager/graph/graph_impl.h
index 48416908..937cb684 100644
--- a/chrome/browser/performance_manager/graph/graph_impl.h
+++ b/chrome/browser/performance_manager/graph/graph_impl.h
@@ -23,9 +23,9 @@
 
 namespace performance_manager {
 
-class NodeBase;
-class GraphObserver;
 class FrameNodeImpl;
+class GraphImplObserver;
+class NodeBase;
 class PageNodeImpl;
 class ProcessNodeImpl;
 class SystemNodeImpl;
@@ -38,12 +38,7 @@
   // Pure virtual observer interface. Derive from this if you want to manually
   // implement the whole interface, and have the compiler enforce that as new
   // methods are added.
-  class Observer;
-
-  // A do-nothing implementation of the observer. Derive from this if you want
-  // to selectively override a few methods and not have to worry about
-  // continuously updating your implementation as new methods are added.
-  class ObserverDefaultImpl;
+  using Observer = GraphObserver;
 
   using NodeSet = std::unordered_set<NodeBase*>;
 
@@ -51,6 +46,16 @@
   ~GraphImpl() override;
 
   // Graph implementation:
+  void AddGraphObserver(GraphObserver* observer) override;
+  void AddFrameNodeObserver(FrameNodeObserver* observer) override;
+  void AddPageNodeObserver(PageNodeObserver* observer) override;
+  void AddProcessNodeObserver(ProcessNodeObserver* observer) override;
+  void AddSystemNodeObserver(SystemNodeObserver* observer) override;
+  void RemoveGraphObserver(GraphObserver* observer) override;
+  void RemoveFrameNodeObserver(FrameNodeObserver* observer) override;
+  void RemovePageNodeObserver(PageNodeObserver* observer) override;
+  void RemoveProcessNodeObserver(ProcessNodeObserver* observer) override;
+  void RemoveSystemNodeObserver(SystemNodeObserver* observer) override;
   uintptr_t GetImplType() const override;
   const void* GetImpl() const override;
 
@@ -64,11 +69,11 @@
   ukm::UkmRecorder* ukm_recorder() const { return ukm_recorder_; }
 
   // Register |observer| on the graph.
-  void RegisterObserver(GraphObserver* observer);
+  void RegisterObserver(GraphImplObserver* observer);
 
   // Unregister |observer| from observing graph changes. Note that this does not
   // unregister |observer| from any nodes it's subscribed to.
-  void UnregisterObserver(GraphObserver* observer);
+  void UnregisterObserver(GraphImplObserver* observer);
 
   SystemNodeImpl* FindOrCreateSystemNode();
   std::vector<ProcessNodeImpl*> GetAllProcessNodes();
@@ -82,7 +87,9 @@
   // Returns true if |node| is in this graph.
   bool NodeInGraph(const NodeBase* node);
 
-  std::vector<GraphObserver*>& observers_for_testing() { return observers_; }
+  std::vector<GraphImplObserver*>& observers_for_testing() {
+    return observers_;
+  }
 
   // Management functions for node owners, any node added to the graph must be
   // removed from the graph before it's deleted.
@@ -95,17 +102,39 @@
   size_t GetNodeAttachedDataCountForTesting(NodeBase* node,
                                             const void* key) const;
 
+  // Allows explicitly invoking SystemNode destruction for testing.
+  void ReleaseSystemNodeForTesting() { ReleaseSystemNode(); }
+
+ protected:
+  friend class NodeBase;
+
+  // Provides access to per-node-class typed observers. Exposed to nodes via
+  // TypedNodeBase.
+  template <typename Observer>
+  const std::vector<Observer*>& GetObservers() const;
+  template <>
+  const std::vector<FrameNodeObserver*>& GetObservers() const {
+    return frame_node_observers_;
+  }
+  template <>
+  const std::vector<PageNodeObserver*>& GetObservers() const {
+    return page_node_observers_;
+  }
+  template <>
+  const std::vector<ProcessNodeObserver*>& GetObservers() const {
+    return process_node_observers_;
+  }
+  template <>
+  const std::vector<SystemNodeObserver*>& GetObservers() const {
+    return system_node_observers_;
+  }
+
  private:
   using ProcessByPidMap = std::unordered_map<base::ProcessId, ProcessNodeImpl*>;
 
   void OnNodeAdded(NodeBase* node);
   void OnBeforeNodeRemoved(NodeBase* node);
 
-  // Templated helper functions for removed nodes.
-  // TODO(chrisha): Kill this off after the observer migration.
-  template <typename NodeType>
-  void OnBeforeNodeRemovedImpl(NodeType* node);
-
   // Returns a new serialization ID.
   friend class NodeBase;
   int64_t GetNextNodeSerializationId();
@@ -118,12 +147,23 @@
   template <typename NodeType>
   std::vector<NodeType*> GetAllNodesOfType();
 
+  void ReleaseSystemNode();
+
   std::unique_ptr<SystemNodeImpl> system_node_;
   NodeSet nodes_;
   ProcessByPidMap processes_by_pid_;
-  std::vector<GraphObserver*> observers_;
+  std::vector<GraphImplObserver*> observers_;
   ukm::UkmRecorder* ukm_recorder_ = nullptr;
 
+  // Typed observers.
+  // TODO(chrisha): We should wrap these containers in something that catches
+  // invalid reentrant usage in DCHECK builds.
+  std::vector<GraphObserver*> graph_observers_;
+  std::vector<FrameNodeObserver*> frame_node_observers_;
+  std::vector<PageNodeObserver*> page_node_observers_;
+  std::vector<ProcessNodeObserver*> process_node_observers_;
+  std::vector<SystemNodeObserver*> system_node_observers_;
+
   // User data storage for the graph.
   friend class NodeAttachedDataMapHelper;
   using NodeAttachedDataKey = std::pair<const NodeBase*, const void*>;
@@ -138,54 +178,6 @@
   DISALLOW_COPY_AND_ASSIGN(GraphImpl);
 };
 
-// Observer interface for GraphImpl objects.
-class GraphImpl::Observer {
- public:
-  Observer();
-  virtual ~Observer();
-
-  // Invoked when an observer is added to or removed from the graph. This is a
-  // convenient place for observers to initialize any necessary state, validate
-  // graph invariants, etc.
-  virtual void OnRegistered() = 0;
-  virtual void OnUnregistered() = 0;
-
-  // Called whenever a node has been added to the graph.
-  virtual void OnNodeAdded(NodeBase* node) = 0;
-
-  // Called when the |node| is about to be removed from the graph.
-  virtual void OnBeforeNodeRemoved(NodeBase* node) = 0;
-
-  // This will be called with a non-null |graph| when the observer is attached
-  // to a graph, and then again with a null |graph| when the observer is
-  // removed.
-  virtual void SetGraph(GraphImpl* graph) = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(Observer);
-};
-
-// A do-nothing default implementation of a GraphImplObserver.
-class GraphImpl::ObserverDefaultImpl : public GraphImpl::Observer {
- public:
-  ObserverDefaultImpl();
-  ~ObserverDefaultImpl() override;
-
-  // GraphImplObserver implementation:
-  void OnRegistered() override {}
-  void OnUnregistered() override {}
-  void OnNodeAdded(NodeBase* node) override {}
-  void OnBeforeNodeRemoved(NodeBase* node) override {}
-  void SetGraph(GraphImpl* graph) override;
-
-  GraphImpl* graph() const { return graph_; }
-
- private:
-  GraphImpl* graph_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
-};
-
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_GRAPH_IMPL_H_
diff --git a/chrome/browser/performance_manager/graph/graph_impl_unittest.cc b/chrome/browser/performance_manager/graph/graph_impl_unittest.cc
index 4a01e059..dc62b1a 100644
--- a/chrome/browser/performance_manager/graph/graph_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/graph_impl_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/performance_manager/graph/graph_impl.h"
 
+#include "base/memory/ptr_util.h"
 #include "base/process/process.h"
 #include "base/time/time.h"
 #include "chrome/browser/performance_manager/graph/frame_node_impl.h"
@@ -11,6 +12,7 @@
 #include "chrome/browser/performance_manager/graph/mock_graphs.h"
 #include "chrome/browser/performance_manager/graph/process_node_impl.h"
 #include "chrome/browser/performance_manager/graph/system_node_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
@@ -129,4 +131,34 @@
             NodeBase::GetSerializationId(system));
 }
 
+namespace {
+
+class LenientMockObserver : public GraphObserver {
+ public:
+  LenientMockObserver() {}
+  ~LenientMockObserver() override {}
+
+  MOCK_METHOD1(OnBeforeGraphDestroyed, void(const Graph*));
+};
+
+using MockObserver = ::testing::StrictMock<LenientMockObserver>;
+
+using testing::_;
+using testing::Invoke;
+
+}  // namespace
+
+TEST(GraphImplTest, ObserverWorks) {
+  std::unique_ptr<GraphImpl> graph = base::WrapUnique(new GraphImpl());
+  const Graph* raw_graph = graph.get();
+
+  MockObserver obs;
+  graph->AddGraphObserver(&obs);
+  graph->RemoveGraphObserver(&obs);
+  graph->AddGraphObserver(&obs);
+
+  EXPECT_CALL(obs, OnBeforeGraphDestroyed(raw_graph));
+  graph.reset();
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/node_base.cc b/chrome/browser/performance_manager/graph/node_base.cc
index 715b53e..bee720f 100644
--- a/chrome/browser/performance_manager/graph/node_base.cc
+++ b/chrome/browser/performance_manager/graph/node_base.cc
@@ -37,22 +37,6 @@
   return node->serialization_id_;
 }
 
-// TODO(chrisha): Remove this!
-void NodeBase::RemoveObserver(GraphObserver* observer) {
-  switch (type()) {
-    case NodeTypeEnum::kFrame:
-      return FrameNodeImpl::FromNodeBase(this)->RemoveObserver(observer);
-    case NodeTypeEnum::kPage:
-      return PageNodeImpl::FromNodeBase(this)->RemoveObserver(observer);
-    case NodeTypeEnum::kProcess:
-      return ProcessNodeImpl::FromNodeBase(this)->RemoveObserver(observer);
-    case NodeTypeEnum::kSystem:
-      return SystemNodeImpl::FromNodeBase(this)->RemoveObserver(observer);
-    case NodeTypeEnum::kInvalidType:
-      NOTREACHED();
-  }
-}
-
 void NodeBase::JoinGraph() {
   DCHECK(graph_->NodeInGraph(this));
 }
diff --git a/chrome/browser/performance_manager/graph/node_base.h b/chrome/browser/performance_manager/graph/node_base.h
index a7af0b0..d8ecf3e5 100644
--- a/chrome/browser/performance_manager/graph/node_base.h
+++ b/chrome/browser/performance_manager/graph/node_base.h
@@ -24,8 +24,8 @@
 
 namespace performance_manager {
 
-// TODO(chrisha): Remove all of these when GraphObserver is killed.
-class GraphObserver;
+// TODO(chrisha): Remove this when GraphImplObserver is killed.
+class GraphImplObserver;
 
 // NodeBase implements shared functionality among different types of graph
 // nodes. A specific type of graph node will derive from this class and can
@@ -35,6 +35,9 @@
 // All methods not documented otherwise are single-threaded.
 class NodeBase {
  public:
+  using ObserverList =
+      typename base::ObserverList<GraphImplObserver>::Unchecked;
+
   // TODO(siggi): Don't store the node type, expose it on a virtual function
   //    instead.
   NodeBase(NodeTypeEnum type, GraphImpl* graph);
@@ -51,14 +54,27 @@
   // provide a stable ID for serialization.
   static int64_t GetSerializationId(NodeBase* node);
 
-  // TODO(chrisha): Remove this after observer migration.
-  // Implementations of these are provided in the *_node_impl.cc translation
-  // units for now.
-  void RemoveObserver(GraphObserver* observer);
+  // TODO(chrisha): Remove these functions once we've moved to typed observers.
+  void AddObserver(GraphImplObserver* observer) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.AddObserver(observer);
+  }
+  void RemoveObserver(GraphImplObserver* observer) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.RemoveObserver(observer);
+  }
+  const ObserverList& observers() const { return observers_; }
 
  protected:
   friend class GraphImpl;
 
+  // Helper function for TypedNodeBase to access the list of typed observers
+  // stored in the graph.
+  template <typename Observer>
+  static const std::vector<Observer*>& GetObservers(const GraphImpl* graph) {
+    return graph->GetObservers<Observer>();
+  }
+
   // Called just before joining |graph_|, a good opportunity to initialize
   // node state.
   virtual void JoinGraph();
@@ -75,6 +91,9 @@
   SEQUENCE_CHECKER(sequence_checker_);
 
  private:
+  // TODO(chrisha): Remove this once we've moved to typed observers.
+  ObserverList observers_;
+
   DISALLOW_COPY_AND_ASSIGN(NodeBase);
 };
 
@@ -95,14 +114,16 @@
   }
 };
 
-template <class NodeImplClass, class NodeImplObserverClass>
+template <class NodeImplClass,
+          class NodeImplObserverClass,
+          class NodeClass,
+          class NodeObserverClass>
 class TypedNodeBase : public NodeBase {
  public:
-  using Observer = NodeImplObserverClass;
-  using ObserverList =
-      typename base::ObserverList<NodeImplObserverClass>::Unchecked;
-  using ObservedProperty =
-      ObservedPropertyImpl<NodeImplClass, NodeImplObserverClass>;
+  using ObservedProperty = ObservedPropertyImpl<NodeImplClass,
+                                                NodeImplObserverClass,
+                                                NodeClass,
+                                                NodeObserverClass>;
 
   explicit TypedNodeBase(GraphImpl* graph)
       : NodeBase(NodeImplClass::Type(), graph) {}
@@ -117,21 +138,15 @@
     return static_cast<NodeImplClass*>(node);
   }
 
-  void AddObserver(Observer* observer) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    observers_.AddObserver(observer);
+  // Convenience accessor to the per-node-class list of observers that is stored
+  // in the graph.
+  const std::vector<NodeObserverClass*>& GetObservers() const {
+    // Mediate through NodeBase, as it's the class that is friended by the
+    // GraphImpl in order to provide access.
+    return NodeBase::GetObservers<NodeObserverClass>(graph());
   }
 
-  void RemoveObserver(Observer* observer) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    observers_.RemoveObserver(observer);
-  }
-
-  const ObserverList& observers() const { return observers_; }
-
  private:
-  ObserverList observers_;
-
   DISALLOW_COPY_AND_ASSIGN(TypedNodeBase);
 };
 
diff --git a/chrome/browser/performance_manager/graph/page_node.cc b/chrome/browser/performance_manager/graph/page_node.cc
index 62039887..b8f9a6ab 100644
--- a/chrome/browser/performance_manager/graph/page_node.cc
+++ b/chrome/browser/performance_manager/graph/page_node.cc
@@ -12,4 +12,10 @@
 PageNode::PageNode() = default;
 PageNode::~PageNode() = default;
 
+PageNodeObserver::PageNodeObserver() = default;
+PageNodeObserver::~PageNodeObserver() = default;
+
+PageNode::ObserverDefaultImpl::ObserverDefaultImpl() = default;
+PageNode::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.cc b/chrome/browser/performance_manager/graph/page_node_impl.cc
index 7da1a30..c007d90 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/page_node_impl.cc
@@ -42,9 +42,6 @@
 
 }  // namespace
 
-PageNodeImplObserver::PageNodeImplObserver() = default;
-PageNodeImplObserver::~PageNodeImplObserver() = default;
-
 PageNodeImpl::PageNodeImpl(GraphImpl* graph,
                            const WebContentsProxy& contents_proxy,
                            bool is_visible)
@@ -113,12 +110,16 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& observer : observers())
     observer.OnFaviconUpdated(this);
+  for (auto* observer : GetObservers())
+    observer->OnFaviconUpdated(this);
 }
 
 void PageNodeImpl::OnTitleUpdated() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& observer : observers())
     observer.OnTitleUpdated(this);
+  for (auto* observer : GetObservers())
+    observer->OnTitleUpdated(this);
 }
 
 void PageNodeImpl::OnMainFrameNavigationCommitted(
@@ -131,6 +132,8 @@
   navigation_id_ = navigation_id;
   for (auto& observer : observers())
     observer.OnMainFrameNavigationCommitted(this);
+  for (auto* observer : GetObservers())
+    observer->OnMainFrameNavigationCommitted(this);
 }
 
 base::flat_set<ProcessNodeImpl*> PageNodeImpl::GetAssociatedProcessNodes()
@@ -441,7 +444,4 @@
     ForFrameAndDescendents(main_frame_node, map_function);
 }
 
-PageNodeImpl::ObserverDefaultImpl::ObserverDefaultImpl() = default;
-PageNodeImpl::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
-
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.h b/chrome/browser/performance_manager/graph/page_node_impl.h
index 374abcc0..df08f37 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl.h
+++ b/chrome/browser/performance_manager/graph/page_node_impl.h
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/performance_manager/graph/node_attached_data.h"
 #include "chrome/browser/performance_manager/graph/node_base.h"
+#include "chrome/browser/performance_manager/observers/graph_observer.h"
 #include "chrome/browser/performance_manager/public/graph/page_node.h"
 #include "chrome/browser/performance_manager/public/web_contents_proxy.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
@@ -24,56 +25,12 @@
 class PageNodeImpl;
 class ProcessNodeImpl;
 
-// Observer interface for PageNodeImpl objects. This must be declared first as
-// the type is referenced by members of PageNodeImpl.
-class PageNodeImplObserver {
- public:
-  PageNodeImplObserver();
-  virtual ~PageNodeImplObserver();
-
-  // Notifications of property changes.
-
-  // Invoked when the |is_visible| property changes.
-  virtual void OnIsVisibleChanged(PageNodeImpl* page_node) = 0;
-
-  // Invoked when the |is_loading| property changes.
-  virtual void OnIsLoadingChanged(PageNodeImpl* page_node) = 0;
-
-  // Invoked when the |ukm_source_id| property changes.
-  virtual void OnUkmSourceIdChanged(PageNodeImpl* page_node) = 0;
-
-  // Invoked when the |lifecycle_state| property changes.
-  virtual void OnLifecycleStateChanged(PageNodeImpl* page_node) = 0;
-
-  // Invoked when the |page_almost_idle| property changes.
-  virtual void OnPageAlmostIdleChanged(PageNodeImpl* page_node) = 0;
-
-  // This is fired when a main frame navigation commits. It indicates that the
-  // |navigation_id| and |main_frame_url| properties have changed.
-  virtual void OnMainFrameNavigationCommitted(PageNodeImpl* page_node) = 0;
-
-  // Events with no property changes.
-
-  // Fired when the tab title associated with a page changes. This property is
-  // not directly reflected on the node.
-  virtual void OnTitleUpdated(PageNodeImpl* page_node) = 0;
-
-  // Fired when the favicon associated with a page is updated. This property is
-  // not directly reflected on the node.
-  virtual void OnFaviconUpdated(PageNodeImpl* page_node) = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(PageNodeImplObserver);
-};
-
 class PageNodeImpl : public PublicNodeImpl<PageNodeImpl, PageNode>,
-                     public TypedNodeBase<PageNodeImpl, PageNodeImplObserver> {
+                     public TypedNodeBase<PageNodeImpl,
+                                          GraphImplObserver,
+                                          PageNode,
+                                          PageNodeObserver> {
  public:
-  // A do-nothing implementation of the observer. Derive from this if you want
-  // to selectively override a few methods and not have to worry about
-  // continuously updating your implementation as new methods are added.
-  class ObserverDefaultImpl;
-
   using LifecycleState = resource_coordinator::mojom::LifecycleState;
 
   static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kPage; }
@@ -172,6 +129,10 @@
     return intervention_policy_frames_reported_;
   }
 
+  void SetLifecycleStateForTesting(LifecycleState lifecycle_state) {
+    SetLifecycleState(lifecycle_state);
+  }
+
   void SetPageAlmostIdleForTesting(bool page_almost_idle) {
     SetPageAlmostIdle(page_almost_idle);
   }
@@ -270,25 +231,37 @@
 
   // Page almost idle state. This is the output that is driven by the
   // PageAlmostIdleDecorator.
-  ObservedProperty::NotifiesOnlyOnChanges<bool,
-                                          &Observer::OnPageAlmostIdleChanged>
+  ObservedProperty::NotifiesOnlyOnChanges<
+      bool,
+      &GraphImplObserver::OnPageAlmostIdleChanged,
+      &PageNodeObserver::OnPageAlmostIdleChanged>
       page_almost_idle_{false};
   // Whether or not the page is visible. Driven by browser instrumentation.
   // Initialized on construction.
-  ObservedProperty::NotifiesOnlyOnChanges<bool, &Observer::OnIsVisibleChanged>
-      is_visible_;
+  ObservedProperty::NotifiesOnlyOnChanges<
+      bool,
+      &GraphImplObserver::OnIsVisibleChanged,
+      &PageNodeObserver::OnIsVisibleChanged>
+      is_visible_{false};
   // The loading state. This is driven by instrumentation in the browser
   // process.
-  ObservedProperty::NotifiesOnlyOnChanges<bool, &Observer::OnIsLoadingChanged>
+  ObservedProperty::NotifiesOnlyOnChanges<
+      bool,
+      &GraphImplObserver::OnIsLoadingChanged,
+      &PageNodeObserver::OnIsLoadingChanged>
       is_loading_{false};
   // The UKM source ID associated with the URL of the main frame of this page.
-  ObservedProperty::NotifiesOnlyOnChanges<ukm::SourceId,
-                                          &Observer::OnUkmSourceIdChanged>
+  ObservedProperty::NotifiesOnlyOnChanges<
+      ukm::SourceId,
+      &GraphImplObserver::OnUkmSourceIdChanged,
+      &PageNodeObserver::OnUkmSourceIdChanged>
       ukm_source_id_{ukm::kInvalidSourceId};
   // The lifecycle state of this page. This is aggregated from the lifecycle
   // state of each frame in the frame tree.
-  ObservedProperty::NotifiesOnlyOnChanges<LifecycleState,
-                                          &Observer::OnLifecycleStateChanged>
+  ObservedProperty::NotifiesOnlyOnChanges<
+      LifecycleState,
+      &GraphImplObserver::OnLifecycleStateChanged,
+      &PageNodeObserver::OnLifecycleStateChanged>
       lifecycle_state_{LifecycleState::kRunning};
 
   // Storage for PageAlmostIdle user data.
@@ -300,26 +273,6 @@
   DISALLOW_COPY_AND_ASSIGN(PageNodeImpl);
 };
 
-// A do-nothing default implementation of a PageNodeImpl::Observer.
-class PageNodeImpl::ObserverDefaultImpl : public PageNodeImpl::Observer {
- public:
-  ObserverDefaultImpl();
-  ~ObserverDefaultImpl() override;
-
-  // PageNodeImpl::Observer implementation:
-  void OnIsVisibleChanged(PageNodeImpl* page_node) override {}
-  void OnIsLoadingChanged(PageNodeImpl* page_node) override {}
-  void OnUkmSourceIdChanged(PageNodeImpl* page_node) override {}
-  void OnLifecycleStateChanged(PageNodeImpl* page_node) override {}
-  void OnPageAlmostIdleChanged(PageNodeImpl* page_node) override {}
-  void OnMainFrameNavigationCommitted(PageNodeImpl* page_node) override {}
-  void OnTitleUpdated(PageNodeImpl* page_node) override {}
-  void OnFaviconUpdated(PageNodeImpl* page_node) override {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
-};
-
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_PAGE_NODE_IMPL_H_
diff --git a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
index 17f5416..41abf3d1 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/performance_manager/graph/page_node_impl.h"
 #include "chrome/browser/performance_manager/graph/process_node_impl.h"
 #include "chrome/browser/performance_manager/performance_manager_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
@@ -391,4 +392,106 @@
       resource_coordinator::mojom::InterventionPolicy::kOptIn, page.get());
 }
 
+namespace {
+
+class LenientMockObserver : public PageNodeImpl::Observer {
+ public:
+  LenientMockObserver() {}
+  ~LenientMockObserver() override {}
+
+  MOCK_METHOD1(OnPageNodeAdded, void(const PageNode*));
+  MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode*));
+  MOCK_METHOD1(OnIsVisibleChanged, void(const PageNode*));
+  MOCK_METHOD1(OnIsLoadingChanged, void(const PageNode*));
+  MOCK_METHOD1(OnUkmSourceIdChanged, void(const PageNode*));
+  MOCK_METHOD1(OnLifecycleStateChanged, void(const PageNode*));
+  MOCK_METHOD1(OnPageAlmostIdleChanged, void(const PageNode*));
+  MOCK_METHOD1(OnMainFrameNavigationCommitted, void(const PageNode*));
+  MOCK_METHOD1(OnTitleUpdated, void(const PageNode*));
+  MOCK_METHOD1(OnFaviconUpdated, void(const PageNode*));
+
+  void SetNotifiedPageNode(const PageNode* page_node) {
+    notified_page_node_ = page_node;
+  }
+
+  const PageNode* TakeNotifiedPageNode() {
+    const PageNode* node = notified_page_node_;
+    notified_page_node_ = nullptr;
+    return node;
+  }
+
+ private:
+  const PageNode* notified_page_node_ = nullptr;
+};
+
+using MockObserver = ::testing::StrictMock<LenientMockObserver>;
+
+using testing::_;
+using testing::Invoke;
+
+}  // namespace
+
+TEST_F(PageNodeImplTest, ObserverWorks) {
+  auto process = CreateNode<ProcessNodeImpl>();
+
+  MockObserver obs;
+  graph()->AddPageNodeObserver(&obs);
+
+  // Create a page node and expect a matching call to "OnPageNodeAdded".
+  EXPECT_CALL(obs, OnPageNodeAdded(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  auto page_node = CreateNode<PageNodeImpl>();
+  const PageNode* raw_page_node = page_node.get();
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnIsVisibleChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->SetIsVisible(true);
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnIsLoadingChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->SetIsLoading(true);
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnUkmSourceIdChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->SetUkmSourceId(static_cast<ukm::SourceId>(0x1234));
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnLifecycleStateChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->SetLifecycleStateForTesting(PageNodeImpl::LifecycleState::kFrozen);
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnPageAlmostIdleChanged(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->SetPageAlmostIdleForTesting(true);
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnMainFrameNavigationCommitted(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->OnMainFrameNavigationCommitted(base::TimeTicks::Now(), 0x1234ull,
+                                            GURL("https://foo.com/"));
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnTitleUpdated(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->OnTitleUpdated();
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  EXPECT_CALL(obs, OnFaviconUpdated(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node->OnFaviconUpdated();
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  // Release the page node and expect a call to "OnBeforePageNodeRemoved".
+  EXPECT_CALL(obs, OnBeforePageNodeRemoved(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedPageNode));
+  page_node.reset();
+  EXPECT_EQ(raw_page_node, obs.TakeNotifiedPageNode());
+
+  graph()->RemovePageNodeObserver(&obs);
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/process_node.cc b/chrome/browser/performance_manager/graph/process_node.cc
index ed3a2f3..df489e6 100644
--- a/chrome/browser/performance_manager/graph/process_node.cc
+++ b/chrome/browser/performance_manager/graph/process_node.cc
@@ -12,4 +12,10 @@
 ProcessNode::ProcessNode() = default;
 ProcessNode::~ProcessNode() = default;
 
+ProcessNodeObserver::ProcessNodeObserver() = default;
+ProcessNodeObserver::~ProcessNodeObserver() = default;
+
+ProcessNode::ObserverDefaultImpl::ObserverDefaultImpl() = default;
+ProcessNode::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/process_node_impl.cc b/chrome/browser/performance_manager/graph/process_node_impl.cc
index f515d6f..c6ce2afa 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/process_node_impl.cc
@@ -11,9 +11,6 @@
 
 namespace performance_manager {
 
-ProcessNodeImplObserver::ProcessNodeImplObserver() = default;
-ProcessNodeImplObserver::~ProcessNodeImplObserver() = default;
-
 ProcessNodeImpl::ProcessNodeImpl(GraphImpl* graph)
     : TypedNodeBase(graph), binding_(this) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
@@ -131,6 +128,13 @@
   cumulative_cpu_usage_ = base::TimeDelta();
 }
 
+void ProcessNodeImpl::OnAllFramesInProcessFrozen() {
+  for (auto& observer : observers())
+    observer.OnAllFramesInProcessFrozen(this);
+  for (auto* observer : GetObservers())
+    observer->OnAllFramesInProcessFrozen(this);
+}
+
 void ProcessNodeImpl::LeaveGraph() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   NodeBase::LeaveGraph();
@@ -144,7 +148,4 @@
   DCHECK(frame_nodes_.empty());
 }
 
-ProcessNodeImpl::ObserverDefaultImpl::ObserverDefaultImpl() = default;
-ProcessNodeImpl::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
-
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/process_node_impl.h b/chrome/browser/performance_manager/graph/process_node_impl.h
index 4fd27bc..a3c2247 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl.h
+++ b/chrome/browser/performance_manager/graph/process_node_impl.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/performance_manager/graph/node_attached_data.h"
 #include "chrome/browser/performance_manager/graph/node_base.h"
 #include "chrome/browser/performance_manager/graph/properties.h"
+#include "chrome/browser/performance_manager/observers/graph_observer.h"
 #include "chrome/browser/performance_manager/public/graph/process_node.h"
 
 namespace performance_manager {
@@ -21,31 +22,6 @@
 class FrameNodeImpl;
 class ProcessNodeImpl;
 
-// Observer interface for ProcessNodeImpl objects. This must be declared first
-// as the type is referenced by members of ProcessNodeImpl.
-class ProcessNodeImplObserver {
- public:
-  ProcessNodeImplObserver();
-  virtual ~ProcessNodeImplObserver();
-
-  // Notifications of property changes.
-
-  // Invoked when a new |expected_task_queueing_duration| sample is available.
-  virtual void OnExpectedTaskQueueingDurationSample(
-      ProcessNodeImpl* process_node) = 0;
-
-  // Invoked when the |main_thread_task_load_is_low| property changes.
-  virtual void OnMainThreadTaskLoadIsLow(ProcessNodeImpl* process_node) = 0;
-
-  // Events with no property changes.
-
-  // Fired when all frames in a process have transitioned to being frozen.
-  virtual void OnAllFramesInProcessFrozen(ProcessNodeImpl* process_node) = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ProcessNodeImplObserver);
-};
-
 // A process node follows the lifetime of a RenderProcessHost.
 // It may reference zero or one processes at a time, but during its lifetime, it
 // may reference more than one process. This can happen if the associated
@@ -58,14 +34,12 @@
 // 4. Back to 2.
 class ProcessNodeImpl
     : public PublicNodeImpl<ProcessNodeImpl, ProcessNode>,
-      public TypedNodeBase<ProcessNodeImpl, ProcessNodeImplObserver>,
+      public TypedNodeBase<ProcessNodeImpl,
+                           GraphImplObserver,
+                           ProcessNode,
+                           ProcessNodeObserver>,
       public resource_coordinator::mojom::ProcessCoordinationUnit {
  public:
-  // A do-nothing implementation of the observer. Derive from this if you want
-  // to selectively override a few methods and not have to worry about
-  // continuously updating your implementation as new methods are added.
-  class ObserverDefaultImpl;
-
   static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kProcess; }
 
   explicit ProcessNodeImpl(GraphImpl* graph);
@@ -126,6 +100,8 @@
   // from the destructor of FrameNodeImpl.
   void RemoveFrame(FrameNodeImpl* frame_node);
 
+  void OnAllFramesInProcessFrozenForTesting() { OnAllFramesInProcessFrozen(); }
+
  protected:
   void SetProcessImpl(base::Process process,
                       base::ProcessId process_id,
@@ -134,6 +110,8 @@
  private:
   friend class FrozenFrameAggregatorAccess;
 
+  void OnAllFramesInProcessFrozen();
+
   void LeaveGraph() override;
 
   mojo::Binding<resource_coordinator::mojom::ProcessCoordinationUnit> binding_;
@@ -148,10 +126,13 @@
 
   ObservedProperty::NotifiesAlways<
       base::TimeDelta,
-      &Observer::OnExpectedTaskQueueingDurationSample>
+      &GraphImplObserver::OnExpectedTaskQueueingDurationSample,
+      &ProcessNodeObserver::OnExpectedTaskQueueingDurationSample>
       expected_task_queueing_duration_;
-  ObservedProperty::NotifiesOnlyOnChanges<bool,
-                                          &Observer::OnMainThreadTaskLoadIsLow>
+  ObservedProperty::NotifiesOnlyOnChanges<
+      bool,
+      &GraphImplObserver::OnMainThreadTaskLoadIsLow,
+      &ProcessNodeObserver::OnMainThreadTaskLoadIsLow>
       main_thread_task_load_is_low_{false};
   double cpu_usage_ = 0;
 
@@ -163,22 +144,6 @@
   DISALLOW_COPY_AND_ASSIGN(ProcessNodeImpl);
 };
 
-// A do-nothing default implementation of a ProcessNodeImpl::Observer.
-class ProcessNodeImpl::ObserverDefaultImpl : public ProcessNodeImpl::Observer {
- public:
-  ObserverDefaultImpl();
-  ~ObserverDefaultImpl() override;
-
-  // ProcessNodeImpl::Observer implementation:
-  void OnExpectedTaskQueueingDurationSample(
-      ProcessNodeImpl* process_node) override {}
-  void OnMainThreadTaskLoadIsLow(ProcessNodeImpl* process_node) override {}
-  void OnAllFramesInProcessFrozen(ProcessNodeImpl* process_node) override {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
-};
-
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_PROCESS_NODE_IMPL_H_
diff --git a/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc
index 33ca033..497e6801 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc
@@ -107,4 +107,74 @@
   }
 }
 
+namespace {
+
+class LenientMockObserver : public ProcessNodeImpl::Observer {
+ public:
+  LenientMockObserver() {}
+  ~LenientMockObserver() override {}
+
+  MOCK_METHOD1(OnProcessNodeAdded, void(const ProcessNode*));
+  MOCK_METHOD1(OnBeforeProcessNodeRemoved, void(const ProcessNode*));
+  MOCK_METHOD1(OnExpectedTaskQueueingDurationSample, void(const ProcessNode*));
+  MOCK_METHOD1(OnMainThreadTaskLoadIsLow, void(const ProcessNode*));
+  MOCK_METHOD1(OnAllFramesInProcessFrozen, void(const ProcessNode*));
+
+  void SetNotifiedProcessNode(const ProcessNode* process_node) {
+    notified_process_node_ = process_node;
+  }
+
+  const ProcessNode* TakeNotifiedProcessNode() {
+    const ProcessNode* node = notified_process_node_;
+    notified_process_node_ = nullptr;
+    return node;
+  }
+
+ private:
+  const ProcessNode* notified_process_node_ = nullptr;
+};
+
+using MockObserver = ::testing::StrictMock<LenientMockObserver>;
+
+using testing::_;
+using testing::Invoke;
+
+}  // namespace
+
+TEST_F(ProcessNodeImplTest, ObserverWorks) {
+  MockObserver obs;
+  graph()->AddProcessNodeObserver(&obs);
+
+  // Create a page node and expect a matching call to "OnProcessNodeAdded".
+  EXPECT_CALL(obs, OnProcessNodeAdded(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
+  auto process_node = CreateNode<ProcessNodeImpl>();
+  const ProcessNode* raw_process_node = process_node.get();
+  EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
+
+  EXPECT_CALL(obs, OnExpectedTaskQueueingDurationSample(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
+  process_node->SetExpectedTaskQueueingDuration(
+      base::TimeDelta::FromSeconds(1));
+  EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
+
+  EXPECT_CALL(obs, OnMainThreadTaskLoadIsLow(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
+  process_node->SetMainThreadTaskLoadIsLow(true);
+  EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
+
+  EXPECT_CALL(obs, OnAllFramesInProcessFrozen(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
+  process_node->OnAllFramesInProcessFrozenForTesting();
+  EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
+
+  // Release the page node and expect a call to "OnBeforeProcessNodeRemoved".
+  EXPECT_CALL(obs, OnBeforeProcessNodeRemoved(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedProcessNode));
+  process_node.reset();
+  EXPECT_EQ(raw_process_node, obs.TakeNotifiedProcessNode());
+
+  graph()->RemoveProcessNodeObserver(&obs);
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/properties.h b/chrome/browser/performance_manager/graph/properties.h
index b2211a55..0a8e7b2 100644
--- a/chrome/browser/performance_manager/graph/properties.h
+++ b/chrome/browser/performance_manager/graph/properties.h
@@ -7,19 +7,26 @@
 
 namespace performance_manager {
 
+// TODO(chrisha): Deprecate the private observer type and have everyone use the
+// public observers!
+
 // Helper classes for setting properties and invoking observer callbacks based
 // on the value change. Note that by contract the NodeType must have a member
 // function "observers()" that returns an iterable collection of
 // ObserverType pointers. This is templated on the observer type to allow
 // easy testing.
-template <typename NodeType, typename ObserverType>
+template <typename NodeImplType,
+          typename ImplObserverType,
+          typename NodeType,
+          typename ObserverType>
 class ObservedPropertyImpl {
  public:
   // Helper class for node properties that represent measurements that are taken
   // periodically, and for which a notification should be sent every time a
   // new sample is recorded, even if identical in value to the last.
   template <typename PropertyType,
-            void (ObserverType::*NotifyFunctionPtr)(NodeType*)>
+            void (ImplObserverType::*ImplNotifyFunctionPtr)(NodeImplType*),
+            void (ObserverType::*NotifyFunctionPtr)(const NodeType*)>
   class NotifiesAlways {
    public:
     NotifiesAlways() {}
@@ -29,10 +36,12 @@
     ~NotifiesAlways() {}
 
     // Sets the property and sends a notification.
-    void SetAndNotify(NodeType* node, PropertyType value) {
+    void SetAndNotify(NodeImplType* node, PropertyType value) {
       value_ = value;
       for (auto& observer : node->observers())
-        ((observer).*(NotifyFunctionPtr))(node);
+        ((observer).*(ImplNotifyFunctionPtr))(node);
+      for (auto* observer : node->GetObservers())
+        ((observer)->*(NotifyFunctionPtr))(node);
     }
 
     const PropertyType& value() const { return value_; }
@@ -46,7 +55,8 @@
   // changes. Calls to SetAndMaybeNotify do not notify if the provided value is
   // the same as the current value.
   template <typename PropertyType,
-            void (ObserverType::*NotifyFunctionPtr)(NodeType*)>
+            void (ImplObserverType::*ImplNotifyFunctionPtr)(NodeImplType*),
+            void (ObserverType::*NotifyFunctionPtr)(const NodeType*)>
   class NotifiesOnlyOnChanges {
    public:
     NotifiesOnlyOnChanges() {}
@@ -57,12 +67,14 @@
 
     // Sets the property and sends a notification if needed. Returns true if a
     // notification was sent, false otherwise.
-    bool SetAndMaybeNotify(NodeType* node, PropertyType value) {
+    bool SetAndMaybeNotify(NodeImplType* node, PropertyType value) {
       if (value_ == value)
         return false;
       value_ = value;
       for (auto& observer : node->observers())
-        ((observer).*(NotifyFunctionPtr))(node);
+        ((observer).*(ImplNotifyFunctionPtr))(node);
+      for (auto* observer : node->GetObservers())
+        ((observer)->*(NotifyFunctionPtr))(node);
       return true;
     }
 
diff --git a/chrome/browser/performance_manager/graph/properties_unittest.cc b/chrome/browser/performance_manager/graph/properties_unittest.cc
index fe9ddc5..b78e13a 100644
--- a/chrome/browser/performance_manager/graph/properties_unittest.cc
+++ b/chrome/browser/performance_manager/graph/properties_unittest.cc
@@ -21,6 +21,8 @@
 
   MOCK_METHOD1(NotifyAlways, void(DummyNode*));
   MOCK_METHOD1(NotifyOnlyOnChanges, void(DummyNode*));
+  MOCK_METHOD1(NotifyAlwaysConst, void(const DummyNode*));
+  MOCK_METHOD1(NotifyOnlyOnChangesConst, void(const DummyNode*));
 };
 
 class DummyNode {
@@ -30,12 +32,15 @@
 
   void AddObserver(DummyObserver* observer) {
     observers_.AddObserver(observer);
+    new_observers_.push_back(observer);
   }
 
   base::ObserverList<DummyObserver>::Unchecked& observers() {
     return observers_;
   }
 
+  const std::vector<DummyObserver*>& GetObservers() { return new_observers_; }
+
   bool observed_always() const { return observed_always_.value(); }
   bool observed_only_on_changes() const {
     return observed_only_on_changes_.value();
@@ -49,15 +54,21 @@
   }
 
  private:
-  using ObservedProperty = ObservedPropertyImpl<DummyNode, DummyObserver>;
+  using ObservedProperty =
+      ObservedPropertyImpl<DummyNode, DummyObserver, DummyNode, DummyObserver>;
 
-  ObservedProperty::NotifiesAlways<bool, &DummyObserver::NotifyAlways>
+  ObservedProperty::NotifiesAlways<bool,
+                                   &DummyObserver::NotifyAlways,
+                                   &DummyObserver::NotifyAlwaysConst>
       observed_always_{false};
-  ObservedProperty::NotifiesOnlyOnChanges<bool,
-                                          &DummyObserver::NotifyOnlyOnChanges>
+  ObservedProperty::NotifiesOnlyOnChanges<
+      bool,
+      &DummyObserver::NotifyOnlyOnChanges,
+      &DummyObserver::NotifyOnlyOnChangesConst>
       observed_only_on_changes_{false};
 
   base::ObserverList<DummyObserver>::Unchecked observers_;
+  std::vector<DummyObserver*> new_observers_;
 };
 
 class GraphPropertiesTest : public ::testing::Test {
@@ -80,16 +91,19 @@
   EXPECT_EQ(false, node_.observed_always());
 
   EXPECT_CALL(observer_, NotifyAlways(&node_));
+  EXPECT_CALL(observer_, NotifyAlwaysConst(&node_));
   node_.SetObservedAlways(false);
   testing::Mock::VerifyAndClear(&observer_);
   EXPECT_EQ(false, node_.observed_always());
 
   EXPECT_CALL(observer_, NotifyAlways(&node_));
+  EXPECT_CALL(observer_, NotifyAlwaysConst(&node_));
   node_.SetObservedAlways(true);
   testing::Mock::VerifyAndClear(&observer_);
   EXPECT_EQ(true, node_.observed_always());
 
   EXPECT_CALL(observer_, NotifyAlways(&node_));
+  EXPECT_CALL(observer_, NotifyAlwaysConst(&node_));
   node_.SetObservedAlways(true);
   testing::Mock::VerifyAndClear(&observer_);
   EXPECT_EQ(true, node_.observed_always());
@@ -104,6 +118,7 @@
   EXPECT_EQ(false, node_.observed_only_on_changes());
 
   EXPECT_CALL(observer_, NotifyOnlyOnChanges(&node_));
+  EXPECT_CALL(observer_, NotifyOnlyOnChangesConst(&node_));
   EXPECT_TRUE(node_.SetObservedOnlyOnChanges(true));
   testing::Mock::VerifyAndClear(&observer_);
   EXPECT_EQ(true, node_.observed_only_on_changes());
diff --git a/chrome/browser/performance_manager/graph/system_node.cc b/chrome/browser/performance_manager/graph/system_node.cc
index 26127fc..809a95f 100644
--- a/chrome/browser/performance_manager/graph/system_node.cc
+++ b/chrome/browser/performance_manager/graph/system_node.cc
@@ -12,4 +12,10 @@
 SystemNode::SystemNode() = default;
 SystemNode::~SystemNode() = default;
 
+SystemNodeObserver::SystemNodeObserver() = default;
+SystemNodeObserver::~SystemNodeObserver() = default;
+
+SystemNode::ObserverDefaultImpl::ObserverDefaultImpl() = default;
+SystemNode::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/system_node_impl.cc b/chrome/browser/performance_manager/graph/system_node_impl.cc
index 95a3a9a..6a36b125 100644
--- a/chrome/browser/performance_manager/graph/system_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/system_node_impl.cc
@@ -17,9 +17,6 @@
 
 namespace performance_manager {
 
-SystemNodeImplObserver::SystemNodeImplObserver() = default;
-SystemNodeImplObserver::~SystemNodeImplObserver() = default;
-
 ProcessResourceMeasurement::ProcessResourceMeasurement() = default;
 ProcessResourceMeasurementBatch::ProcessResourceMeasurementBatch() = default;
 ProcessResourceMeasurementBatch::~ProcessResourceMeasurementBatch() = default;
@@ -156,9 +153,8 @@
 
   for (auto& observer : observers())
     observer.OnProcessCPUUsageReady(this);
+  for (auto* observer : GetObservers())
+    observer->OnProcessCPUUsageReady(this);
 }
 
-SystemNodeImpl::ObserverDefaultImpl::ObserverDefaultImpl() = default;
-SystemNodeImpl::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
-
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/system_node_impl.h b/chrome/browser/performance_manager/graph/system_node_impl.h
index 1f7cf43..8bba25d 100644
--- a/chrome/browser/performance_manager/graph/system_node_impl.h
+++ b/chrome/browser/performance_manager/graph/system_node_impl.h
@@ -13,28 +13,11 @@
 #include "base/process/process_handle.h"
 #include "base/time/time.h"
 #include "chrome/browser/performance_manager/graph/node_base.h"
+#include "chrome/browser/performance_manager/observers/graph_observer.h"
 #include "chrome/browser/performance_manager/public/graph/system_node.h"
 
 namespace performance_manager {
 
-// Observer interface for SystemNodeImpl objects. This must be declared first as
-// the type is referenced by members of SystemNodeImpl.
-class SystemNodeImplObserver {
- public:
-  SystemNodeImplObserver();
-  virtual ~SystemNodeImplObserver();
-
-  // Events with no property changes.
-
-  // Fired when a batch of consistent process CPU measurements is available on
-  // the graph.
-  // TODO(siggi): Deprecate this as the CPU measurement code is reworked.
-  virtual void OnProcessCPUUsageReady(SystemNodeImpl* system_node) = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(SystemNodeImplObserver);
-};
-
 // TODO(siggi): In the end game, this should be private implementation detail
 //     of the performance measurement graph decorator. It's here for now because
 //     there's still a thread hop to get the measurement results into the graph.
@@ -64,15 +47,12 @@
   std::vector<ProcessResourceMeasurement> measurements;
 };
 
-class SystemNodeImpl
-    : public PublicNodeImpl<SystemNodeImpl, SystemNode>,
-      public TypedNodeBase<SystemNodeImpl, SystemNodeImplObserver> {
+class SystemNodeImpl : public PublicNodeImpl<SystemNodeImpl, SystemNode>,
+                       public TypedNodeBase<SystemNodeImpl,
+                                            GraphImplObserver,
+                                            SystemNode,
+                                            SystemNodeObserver> {
  public:
-  // A do-nothing implementation of the observer. Derive from this if you want
-  // to selectively override a few methods and not have to worry about
-  // continuously updating your implementation as new methods are added.
-  class ObserverDefaultImpl;
-
   static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kSystem; }
 
   explicit SystemNodeImpl(GraphImpl* graph);
@@ -97,19 +77,6 @@
   DISALLOW_COPY_AND_ASSIGN(SystemNodeImpl);
 };
 
-// A do-nothing default implementation of a SystemNodeImpl::Observer.
-class SystemNodeImpl::ObserverDefaultImpl : public SystemNodeImpl::Observer {
- public:
-  ObserverDefaultImpl();
-  ~ObserverDefaultImpl() override;
-
-  // SystemNodeImpl::Observer implementation:
-  void OnProcessCPUUsageReady(SystemNodeImpl* system_node) override {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
-};
-
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_GRAPH_SYSTEM_NODE_IMPL_H_
diff --git a/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc
index 900c1b46..958cd1d 100644
--- a/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/performance_manager/graph/system_node_impl.h"
 #include "chrome/browser/performance_manager/observers/graph_observer.h"
 #include "chrome/browser/performance_manager/performance_manager_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
@@ -20,14 +21,9 @@
 namespace {
 
 // Observer used to make sure that signals are dispatched correctly.
-class SystemAndProcessObserver : public GraphObserverDefaultImpl {
+class SystemObserver : public SystemNodeImpl::ObserverDefaultImpl {
  public:
-  // GraphObserver implementation:
-  bool ShouldObserve(const NodeBase* node) override {
-    return node->type() == SystemNodeImpl::Type();
-  }
-
-  void OnProcessCPUUsageReady(SystemNodeImpl* system_node) override {
+  void OnProcessCPUUsageReady(const SystemNode* system_node) override {
     ++system_event_seen_count_;
   }
 
@@ -87,11 +83,9 @@
 }
 
 TEST_F(SystemNodeImplTest, DistributeMeasurementBatch) {
-  SystemAndProcessObserver observer;
+  SystemObserver observer;
   MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
-  mock_graph.system->AddObserver(&observer);
-  mock_graph.process->AddObserver(&observer);
-  mock_graph.other_process->AddObserver(&observer);
+  graph()->AddSystemNodeObserver(&observer);
 
   EXPECT_EQ(0u, observer.system_event_seen_count());
 
@@ -168,7 +162,60 @@
             mock_graph.other_page->cumulative_cpu_usage_estimate());
   EXPECT_EQ(50u, mock_graph.other_page->private_footprint_kb_estimate());
 
-  mock_graph.system->RemoveObserver(&observer);
+  graph()->RemoveSystemNodeObserver(&observer);
+}
+
+namespace {
+
+class LenientMockObserver : public SystemNodeImpl::Observer {
+ public:
+  LenientMockObserver() {}
+  ~LenientMockObserver() override {}
+
+  MOCK_METHOD1(OnSystemNodeAdded, void(const SystemNode*));
+  MOCK_METHOD1(OnBeforeSystemNodeRemoved, void(const SystemNode*));
+  MOCK_METHOD1(OnProcessCPUUsageReady, void(const SystemNode*));
+
+  void SetNotifiedSystemNode(const SystemNode* system_node) {
+    notified_system_node_ = system_node;
+  }
+
+  const SystemNode* TakeNotifiedSystemNode() {
+    const SystemNode* node = notified_system_node_;
+    notified_system_node_ = nullptr;
+    return node;
+  }
+
+ private:
+  const SystemNode* notified_system_node_ = nullptr;
+};
+
+using MockObserver = ::testing::StrictMock<LenientMockObserver>;
+
+using testing::_;
+using testing::Invoke;
+
+}  // namespace
+
+TEST_F(SystemNodeImplTest, ObserverWorks) {
+  MockObserver obs;
+  graph()->AddSystemNodeObserver(&obs);
+
+  // Fetch the system node and expect a matching call to "OnSystemNodeAdded".
+  EXPECT_CALL(obs, OnSystemNodeAdded(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedSystemNode));
+  const SystemNode* system_node = graph()->FindOrCreateSystemNode();
+  EXPECT_EQ(system_node, obs.TakeNotifiedSystemNode());
+
+  // "OnProcessCPUUsageReady" is tested explicitly in the above unittests.
+
+  // Release the system node and expect a call to "OnBeforeSystemNodeRemoved".
+  EXPECT_CALL(obs, OnBeforeSystemNodeRemoved(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedSystemNode));
+  graph()->ReleaseSystemNodeForTesting();
+  EXPECT_EQ(system_node, obs.TakeNotifiedSystemNode());
+
+  graph()->RemoveSystemNodeObserver(&obs);
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/observers/graph_observer.cc b/chrome/browser/performance_manager/observers/graph_observer.cc
index ec0bae9..4619cc03 100644
--- a/chrome/browser/performance_manager/observers/graph_observer.cc
+++ b/chrome/browser/performance_manager/observers/graph_observer.cc
@@ -6,18 +6,18 @@
 
 namespace performance_manager {
 
-GraphObserver::GraphObserver() = default;
+GraphImplObserver::GraphImplObserver() = default;
 
-GraphObserver::~GraphObserver() = default;
+GraphImplObserver::~GraphImplObserver() = default;
 
-GraphObserverDefaultImpl::GraphObserverDefaultImpl() = default;
+GraphImplObserverDefaultImpl::GraphImplObserverDefaultImpl() = default;
 
-GraphObserverDefaultImpl::~GraphObserverDefaultImpl() {
+GraphImplObserverDefaultImpl::~GraphImplObserverDefaultImpl() {
   // This observer should have left the graph before being destroyed.
   DCHECK(!graph_);
 }
 
-void GraphObserverDefaultImpl::SetGraph(GraphImpl* graph) {
+void GraphImplObserverDefaultImpl::SetGraph(GraphImpl* graph) {
   // We can only be going from null to non-null, and vice-versa.
   DCHECK(!graph || !graph_);
   graph_ = graph;
diff --git a/chrome/browser/performance_manager/observers/graph_observer.h b/chrome/browser/performance_manager/observers/graph_observer.h
index 245ccea..464d60fb 100644
--- a/chrome/browser/performance_manager/observers/graph_observer.h
+++ b/chrome/browser/performance_manager/observers/graph_observer.h
@@ -6,15 +6,17 @@
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_OBSERVERS_GRAPH_OBSERVER_H_
 
 #include "base/macros.h"
-#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
-#include "chrome/browser/performance_manager/graph/graph_impl.h"
-#include "chrome/browser/performance_manager/graph/page_node_impl.h"
-#include "chrome/browser/performance_manager/graph/process_node_impl.h"
-#include "chrome/browser/performance_manager/graph/system_node_impl.h"
 #include "services/resource_coordinator/public/mojom/coordination_unit.mojom.h"
 
 namespace performance_manager {
 
+class FrameNodeImpl;
+class GraphImpl;
+class NodeBase;
+class PageNodeImpl;
+class ProcessNodeImpl;
+class SystemNodeImpl;
+
 // An observer API for the graph.
 //
 // Observers are generally instantiated when the graph is empty, and outlive it,
@@ -27,16 +29,12 @@
 //   (3) Before destruction, unregister by calling on
 //       |graph().UnregisterObserver|.
 //
-// NOTE: This interface is deprecated. Please use the individual interfaces
-// that this class is implementing.
-class GraphObserver : public GraphImpl::Observer,
-                      public FrameNodeImpl::Observer,
-                      public PageNodeImpl::Observer,
-                      public ProcessNodeImpl::Observer,
-                      public SystemNodeImpl::Observer {
+// NOTE: This interface is deprecated. Please use the public observer interfaces
+// directly!
+class GraphImplObserver {
  public:
-  GraphObserver();
-  ~GraphObserver() override;
+  GraphImplObserver();
+  virtual ~GraphImplObserver();
 
   // Determines whether or not the observer should be registered with, and
   // invoked for, the |node|.
@@ -44,17 +42,61 @@
   // actual observer implementations.
   virtual bool ShouldObserve(const NodeBase* node) = 0;
 
+  // Invoked when an observer is added to or removed from the graph. This is a
+  // convenient place for observers to initialize any necessary state, validate
+  // graph invariants, etc.
+  virtual void OnRegistered() = 0;
+  virtual void OnUnregistered() = 0;
+
+  // Called whenever a node has been added to the graph.
+  virtual void OnNodeAdded(NodeBase* node) = 0;
+
+  // Called when the |node| is about to be removed from the graph.
+  virtual void OnBeforeNodeRemoved(NodeBase* node) = 0;
+
+  // This will be called with a non-null |graph| when the observer is attached
+  // to a graph, and then again with a null |graph| when the observer is
+  // removed.
+  virtual void SetGraph(GraphImpl* graph) = 0;
+
+  // FrameNodeObserver analogs:
+  virtual void OnIsCurrentChanged(FrameNodeImpl* frame_node) = 0;
+  virtual void OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) = 0;
+  virtual void OnLifecycleStateChanged(FrameNodeImpl* frame_node) = 0;
+  virtual void OnURLChanged(FrameNodeImpl* frame_node) = 0;
+  virtual void OnNonPersistentNotificationCreated(
+      FrameNodeImpl* frame_node) = 0;
+
+  // PageNodeObserver analogs:
+  virtual void OnIsVisibleChanged(PageNodeImpl* page_node) = 0;
+  virtual void OnIsLoadingChanged(PageNodeImpl* page_node) = 0;
+  virtual void OnUkmSourceIdChanged(PageNodeImpl* page_node) = 0;
+  virtual void OnLifecycleStateChanged(PageNodeImpl* page_node) = 0;
+  virtual void OnPageAlmostIdleChanged(PageNodeImpl* page_node) = 0;
+  virtual void OnFaviconUpdated(PageNodeImpl* page_node) = 0;
+  virtual void OnTitleUpdated(PageNodeImpl* page_node) = 0;
+  virtual void OnMainFrameNavigationCommitted(PageNodeImpl* page_node) = 0;
+
+  // ProcessNodeObserver analogs:
+  virtual void OnExpectedTaskQueueingDurationSample(
+      ProcessNodeImpl* process_node) = 0;
+  virtual void OnMainThreadTaskLoadIsLow(ProcessNodeImpl* process_node) = 0;
+  virtual void OnAllFramesInProcessFrozen(ProcessNodeImpl* process_node) = 0;
+
+  // SystemNodeObserver analogs:
+  virtual void OnProcessCPUUsageReady(SystemNodeImpl* system_node) = 0;
+
  private:
-  DISALLOW_COPY_AND_ASSIGN(GraphObserver);
+  DISALLOW_COPY_AND_ASSIGN(GraphImplObserver);
 };
 
 // An empty implementation of the interface.
-class GraphObserverDefaultImpl : public GraphObserver {
+class GraphImplObserverDefaultImpl : public GraphImplObserver {
  public:
-  GraphObserverDefaultImpl();
-  ~GraphObserverDefaultImpl() override;
+  GraphImplObserverDefaultImpl();
+  ~GraphImplObserverDefaultImpl() override;
 
-  // GraphImplObserver implementation:
+  // GraphObserver implementation:
   void OnRegistered() override {}
   void OnUnregistered() override {}
   void OnNodeAdded(NodeBase* node) override {}
@@ -92,7 +134,7 @@
  private:
   GraphImpl* graph_ = nullptr;
 
-  DISALLOW_COPY_AND_ASSIGN(GraphObserverDefaultImpl);
+  DISALLOW_COPY_AND_ASSIGN(GraphImplObserverDefaultImpl);
 };
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/observers/graph_observer_unittest.cc b/chrome/browser/performance_manager/observers/graph_observer_unittest.cc
index cd4db49..173778c 100644
--- a/chrome/browser/performance_manager/observers/graph_observer_unittest.cc
+++ b/chrome/browser/performance_manager/observers/graph_observer_unittest.cc
@@ -20,7 +20,7 @@
 
 class GraphObserverTest : public GraphTestHarness {};
 
-class TestGraphObserver : public GraphObserverDefaultImpl {
+class TestGraphObserver : public GraphImplObserverDefaultImpl {
  public:
   TestGraphObserver() {}
   ~TestGraphObserver() override {}
diff --git a/chrome/browser/performance_manager/observers/isolation_context_metrics.h b/chrome/browser/performance_manager/observers/isolation_context_metrics.h
index 40ec2842..60d6cf2 100644
--- a/chrome/browser/performance_manager/observers/isolation_context_metrics.h
+++ b/chrome/browser/performance_manager/observers/isolation_context_metrics.h
@@ -24,7 +24,7 @@
 // (2) How common it is for pages to be in browsing instances with other pages,
 //     as opposed to in browsing instances on their own. This is for estimating
 //     the impact of extending freezing logic to entire browsing instances.
-class IsolationContextMetrics : public GraphObserverDefaultImpl {
+class IsolationContextMetrics : public GraphImplObserverDefaultImpl {
  public:
   IsolationContextMetrics();
   ~IsolationContextMetrics() override;
diff --git a/chrome/browser/performance_manager/observers/metrics_collector.h b/chrome/browser/performance_manager/observers/metrics_collector.h
index 25eabb70..11a5560f 100644
--- a/chrome/browser/performance_manager/observers/metrics_collector.h
+++ b/chrome/browser/performance_manager/observers/metrics_collector.h
@@ -29,7 +29,7 @@
 extern const int kDefaultFrequencyUkmEQTReported;
 
 // The MetricsCollector is a graph observer that reports UMA/UKM.
-class MetricsCollector : public GraphObserverDefaultImpl {
+class MetricsCollector : public GraphImplObserverDefaultImpl {
  public:
   MetricsCollector();
   ~MetricsCollector() override;
diff --git a/chrome/browser/performance_manager/observers/working_set_trimmer_win.h b/chrome/browser/performance_manager/observers/working_set_trimmer_win.h
index 56f246bb4..092a881 100644
--- a/chrome/browser/performance_manager/observers/working_set_trimmer_win.h
+++ b/chrome/browser/performance_manager/observers/working_set_trimmer_win.h
@@ -26,7 +26,7 @@
 //   to be compressed and/or written to disk preemptively, which makes more
 //   memory available quickly for foreground processes and improves global
 //   browser performance.
-class WorkingSetTrimmer : public GraphObserverDefaultImpl {
+class WorkingSetTrimmer : public GraphImplObserverDefaultImpl {
  public:
   WorkingSetTrimmer();
   ~WorkingSetTrimmer() override;
diff --git a/chrome/browser/performance_manager/performance_manager.cc b/chrome/browser/performance_manager/performance_manager.cc
index fdd3e30..0bed066 100644
--- a/chrome/browser/performance_manager/performance_manager.cc
+++ b/chrome/browser/performance_manager/performance_manager.cc
@@ -130,7 +130,7 @@
 }
 
 void PerformanceManager::RegisterObserver(
-    std::unique_ptr<GraphObserver> observer) {
+    std::unique_ptr<GraphImplObserver> observer) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   graph_.RegisterObserver(observer.get());
   observers_.push_back(std::move(observer));
@@ -263,7 +263,7 @@
     std::unique_ptr<service_manager::Connector> connector) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Register new |GraphObserver| implementations here.
+  // Register new |GraphImplObserver| implementations here.
   RegisterObserver(std::make_unique<MetricsCollector>());
   RegisterObserver(std::make_unique<PageAlmostIdleDecorator>());
   RegisterObserver(std::make_unique<FrozenFrameAggregator>());
diff --git a/chrome/browser/performance_manager/performance_manager.h b/chrome/browser/performance_manager/performance_manager.h
index c79944d..bfd619b0 100644
--- a/chrome/browser/performance_manager/performance_manager.h
+++ b/chrome/browser/performance_manager/performance_manager.h
@@ -110,7 +110,7 @@
   // resource_coordinator migration, so do not use this unless you know what
   // you're doing! Must be called from the performance manager sequence.
   // TODO(chrisha): Kill this dead.
-  void RegisterObserver(std::unique_ptr<GraphObserver> observer);
+  void RegisterObserver(std::unique_ptr<GraphImplObserver> observer);
 
  private:
   using InterfaceRegistry = service_manager::BinderRegistryWithArgs<
@@ -146,7 +146,7 @@
   GraphImpl graph_;
 
   // The registered graph observers.
-  std::vector<std::unique_ptr<GraphObserver>> observers_;
+  std::vector<std::unique_ptr<GraphImplObserver>> observers_;
 
   // Provided to |graph_|.
   // TODO(siggi): This no longer needs to go through mojo.
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache.h b/chrome/browser/performance_manager/persistence/site_data/site_data_cache.h
new file mode 100644
index 0000000..1c158d6
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache.h
@@ -0,0 +1,47 @@
+// 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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_reader.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_writer.h"
+#include "chrome/browser/performance_manager/persistence/site_data/tab_visibility.h"
+#include "url/origin.h"
+
+namespace performance_manager {
+
+// Pure virtual interface for a site data cache.
+class SiteDataCache {
+ public:
+  SiteDataCache() = default;
+  ~SiteDataCache() = default;
+
+  // Returns a SiteDataReader for the given origin.
+  virtual std::unique_ptr<SiteDataReader> GetReaderForOrigin(
+      const url::Origin& origin) = 0;
+
+  // Returns a SiteDataWriter for the given origin.
+  //
+  // |tab_visibility| indicates the current visibility of the tab. The writer
+  // starts in an unloaded state, NotifyTabLoaded() must be called explicitly
+  // afterwards if the site is loaded.
+  virtual std::unique_ptr<SiteDataWriter> GetWriterForOrigin(
+      const url::Origin& origin,
+      performance_manager::TabVisibility tab_visibility) = 0;
+
+  // Indicate if the SiteDataWriter served by this data cache
+  // actually persist information.
+  virtual bool IsRecordingForTesting() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SiteDataCache);
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc
new file mode 100644
index 0000000..e0b0dff
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc
@@ -0,0 +1,145 @@
+// 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/performance_manager/persistence/site_data/site_data_cache_impl.h"
+
+#include <set>
+
+#include "base/feature_list.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_reader.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_writer.h"
+#include "content/public/browser/browser_context.h"
+
+namespace performance_manager {
+
+namespace {
+
+constexpr char kDataStoreDBName[] = "Site Characteristics Database";
+
+}  // namespace
+
+SiteDataCacheImpl::SiteDataCacheImpl(content::BrowserContext* browser_context)
+    : browser_context_(browser_context) {
+  data_store_ = std::make_unique<LevelDBSiteDataStore>(
+      browser_context->GetPath().AppendASCII(kDataStoreDBName));
+
+  // Register the debug interface against the browser context.
+  SiteDataCacheInspector::SetForBrowserContext(this, browser_context);
+}
+
+SiteDataCacheImpl::~SiteDataCacheImpl() {
+  SiteDataCacheInspector::SetForBrowserContext(nullptr, browser_context_);
+}
+
+std::unique_ptr<SiteDataReader> SiteDataCacheImpl::GetReaderForOrigin(
+    const url::Origin& origin) {
+  internal::SiteDataImpl* impl = GetOrCreateFeatureImpl(origin);
+  DCHECK(impl);
+  SiteDataReader* data_reader = new SiteDataReader(impl);
+  return base::WrapUnique(data_reader);
+}
+
+std::unique_ptr<SiteDataWriter> SiteDataCacheImpl::GetWriterForOrigin(
+    const url::Origin& origin,
+    performance_manager::TabVisibility tab_visibility) {
+  internal::SiteDataImpl* impl = GetOrCreateFeatureImpl(origin);
+  DCHECK(impl);
+  SiteDataWriter* data_writer = new SiteDataWriter(impl, tab_visibility);
+  return base::WrapUnique(data_writer);
+}
+
+bool SiteDataCacheImpl::IsRecordingForTesting() {
+  return true;
+}
+
+const char* SiteDataCacheImpl::GetDataCacheName() {
+  return "SiteDataCache";
+}
+
+std::vector<url::Origin> SiteDataCacheImpl::GetAllInMemoryOrigins() {
+  std::vector<url::Origin> ret;
+
+  ret.reserve(origin_data_map_.size());
+  for (const auto& entry : origin_data_map_)
+    ret.push_back(entry.first);
+
+  return ret;
+}
+
+void SiteDataCacheImpl::GetDataStoreSize(DataStoreSizeCallback on_have_data) {
+  data_store_->GetStoreSize(std::move(on_have_data));
+}
+
+bool SiteDataCacheImpl::GetDataForOrigin(const url::Origin& origin,
+                                         bool* is_dirty,
+                                         std::unique_ptr<SiteDataProto>* data) {
+  DCHECK_NE(nullptr, data);
+  const auto it = origin_data_map_.find(origin);
+  if (it == origin_data_map_.end())
+    return false;
+
+  std::unique_ptr<SiteDataProto> ret = std::make_unique<SiteDataProto>();
+  ret->CopyFrom(it->second->FlushStateToProto());
+  *is_dirty = it->second->is_dirty();
+  *data = std::move(ret);
+  return true;
+}
+
+SiteDataCacheImpl* SiteDataCacheImpl::GetDataCache() {
+  return this;
+}
+
+internal::SiteDataImpl* SiteDataCacheImpl::GetOrCreateFeatureImpl(
+    const url::Origin& origin) {
+  // Start by checking if there's already an entry for this origin.
+  auto iter = origin_data_map_.find(origin);
+  if (iter != origin_data_map_.end())
+    return iter->second;
+
+  // If not create a new one and add it to the map.
+  internal::SiteDataImpl* site_data =
+      new internal::SiteDataImpl(origin, this, data_store_.get());
+
+  // internal::SiteDataImpl is a ref-counted object, it's safe to store a raw
+  // pointer to it here as this class will get notified when it's about to be
+  // destroyed and it'll be removed from the map.
+  origin_data_map_.insert(std::make_pair(origin, site_data));
+  return site_data;
+}
+
+void SiteDataCacheImpl::OnSiteDataImplDestroyed(internal::SiteDataImpl* impl) {
+  DCHECK(impl);
+  DCHECK(base::ContainsKey(origin_data_map_, impl->origin()));
+  // Remove the entry for this origin as this is about to get destroyed.
+  auto num_erased = origin_data_map_.erase(impl->origin());
+  DCHECK_EQ(1U, num_erased);
+}
+
+void SiteDataCacheImpl::ClearSiteDataForOrigins(
+    const std::vector<url::Origin>& origins_to_remove) {
+  // It's not necessary to invalidate the pending DB write operations as they
+  // run on a sequenced task and so it's guaranteed that the remove operations
+  // posted here will run after any other pending operation.
+  for (const auto& it : origins_to_remove) {
+    auto map_iter = origin_data_map_.find(it);
+    if (map_iter != origin_data_map_.end())
+      map_iter->second->ClearObservationsAndInvalidateReadOperation();
+  }
+
+  data_store_->RemoveSiteDataFromStore(origins_to_remove);
+}
+
+void SiteDataCacheImpl::ClearAllSiteData() {
+  // It's not necessary to invalidate the pending DB write operations as they
+  // run on a sequenced task and so it's guaranteed that the remove operations
+  // posted here will run after any other pending operation.
+  for (auto& data : origin_data_map_)
+    data.second->ClearObservationsAndInvalidateReadOperation();
+  data_store_->ClearStore();
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h
new file mode 100644
index 0000000..cd361010
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h
@@ -0,0 +1,95 @@
+// 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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace performance_manager {
+
+// Implementation of a SiteDataCache that serves normal reader and writers.
+//
+// This class should never be used for off the record profiles, the
+// NonRecordingSiteDataCache class should be used instead.
+class SiteDataCacheImpl : public SiteDataCache,
+                          public SiteDataCacheInspector,
+                          public internal::SiteDataImpl::OnDestroyDelegate {
+ public:
+  using SiteDataMap = base::flat_map<url::Origin, internal::SiteDataImpl*>;
+
+  explicit SiteDataCacheImpl(content::BrowserContext* browser_context);
+  virtual ~SiteDataCacheImpl();
+
+  // SiteCharacteristicDataCache:
+  std::unique_ptr<SiteDataReader> GetReaderForOrigin(
+      const url::Origin& origin) override;
+  std::unique_ptr<SiteDataWriter> GetWriterForOrigin(
+      const url::Origin& origin,
+      performance_manager::TabVisibility tab_visibility) override;
+  bool IsRecordingForTesting() override;
+
+  const SiteDataMap& origin_data_map_for_testing() const {
+    return origin_data_map_;
+  }
+
+  // NOTE: This should be called before creating any SiteDataImpl object (this
+  // doesn't update the data store used by these objects).
+  void SetDataStoreForTesting(std::unique_ptr<SiteDataStore> data_store) {
+    data_store_ = std::move(data_store);
+  }
+
+  // SiteDataCacheImplInspector:
+  const char* GetDataCacheName() override;
+  std::vector<url::Origin> GetAllInMemoryOrigins() override;
+  void GetDataStoreSize(DataStoreSizeCallback on_have_data) override;
+  bool GetDataForOrigin(const url::Origin& origin,
+                        bool* is_dirty,
+                        std::unique_ptr<SiteDataProto>* data) override;
+  SiteDataCacheImpl* GetDataCache() override;
+
+  // Remove a specific set of entries from the cache and the on-disk store.
+  void ClearSiteDataForOrigins(
+      const std::vector<url::Origin>& origins_to_remove);
+
+  // Clear the data cache and the on-disk store.
+  void ClearAllSiteData();
+
+ private:
+  // Returns a pointer to the SiteDataImpl object associated with |origin|,
+  // create one and add it to |origin_data_map_| if it doesn't exist.
+  internal::SiteDataImpl* GetOrCreateFeatureImpl(const url::Origin& origin);
+
+  // internal::SiteDataImpl::OnDestroyDelegate:
+  void OnSiteDataImplDestroyed(internal::SiteDataImpl* impl) override;
+
+  // Map an origin to a SiteDataImpl pointer.
+  SiteDataMap origin_data_map_;
+
+  std::unique_ptr<SiteDataStore> data_store_;
+
+  // The browser context this data store is associated with.
+  content::BrowserContext* browser_context_;
+
+  DISALLOW_COPY_AND_ASSIGN(SiteDataCacheImpl);
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc
new file mode 100644
index 0000000..abba180
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc
@@ -0,0 +1,269 @@
+// 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/performance_manager/persistence/site_data/site_data_cache_impl.h"
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "chrome/browser/performance_manager/performance_manager_clock.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace performance_manager {
+
+namespace {
+
+const url::Origin kTestOrigin = url::Origin::Create(GURL("http://www.foo.com"));
+const url::Origin kTestOrigin2 =
+    url::Origin::Create(GURL("http://www.bar.com"));
+
+constexpr base::TimeDelta kDelay = base::TimeDelta::FromMinutes(1);
+
+class MockSiteCache : public testing::NoopSiteDataStore {
+ public:
+  MockSiteCache() = default;
+  ~MockSiteCache() = default;
+
+  MOCK_METHOD1(RemoveSiteDataFromStore, void(const std::vector<url::Origin>&));
+  MOCK_METHOD0(ClearStore, void());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSiteCache);
+};
+
+}  // namespace
+
+class SiteDataCacheImplTest : public ::testing::Test {
+ protected:
+  SiteDataCacheImplTest() {
+    PerformanceManagerClock::SetClockForTesting(&test_clock_);
+    data_cache_ = std::make_unique<SiteDataCacheImpl>(&profile_);
+    mock_db_ = new ::testing::StrictMock<MockSiteCache>();
+    data_cache_->SetDataStoreForTesting(base::WrapUnique(mock_db_));
+    test_clock_.SetNowTicks(base::TimeTicks::UnixEpoch());
+    test_clock_.Advance(base::TimeDelta::FromHours(1));
+    WaitForAsyncOperationsToComplete();
+  }
+
+  ~SiteDataCacheImplTest() override {
+    PerformanceManagerClock::ResetClockForTesting();
+  }
+
+  void TearDown() override { WaitForAsyncOperationsToComplete(); }
+
+  void WaitForAsyncOperationsToComplete() {
+    test_browser_thread_bundle_.RunUntilIdle();
+  }
+
+  // Populates |writer_|, |reader_| and |data_| to refer to a tab navigated to
+  // |kTestOrigin| that updated its title in background. Populates |writer2_|,
+  // |reader2_| and |data2_| to refer to a tab navigated to |kTestOrigin2| that
+  // updates its favicon in background.
+  void SetupTwoSitesUsingFeaturesInBackground() {
+    // Load a first origin, and then make use of a feature on it.
+    ASSERT_FALSE(reader_);
+    reader_ = data_cache_->GetReaderForOrigin(kTestOrigin);
+    EXPECT_TRUE(reader_);
+
+    ASSERT_FALSE(writer_);
+    writer_ = data_cache_->GetWriterForOrigin(
+        kTestOrigin, performance_manager::TabVisibility::kBackground);
+    EXPECT_TRUE(writer_);
+
+    ASSERT_FALSE(data_);
+    data_ =
+        data_cache_->origin_data_map_for_testing().find(kTestOrigin)->second;
+    EXPECT_TRUE(data_);
+
+    EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+              reader_->UpdatesTitleInBackground());
+    writer_->NotifySiteLoaded();
+    writer_->NotifyUpdatesTitleInBackground();
+    EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+              reader_->UpdatesTitleInBackground());
+    test_clock_.Advance(kDelay);
+
+    // Load a second origin, make use of a feature on it too.
+    ASSERT_FALSE(reader2_);
+    reader2_ = data_cache_->GetReaderForOrigin(kTestOrigin2);
+    EXPECT_TRUE(reader2_);
+
+    ASSERT_FALSE(writer2_);
+    writer2_ = data_cache_->GetWriterForOrigin(
+        kTestOrigin2, performance_manager::TabVisibility::kBackground);
+    EXPECT_TRUE(writer2_);
+
+    ASSERT_FALSE(data2_);
+    data2_ =
+        data_cache_->origin_data_map_for_testing().find(kTestOrigin2)->second;
+    EXPECT_TRUE(data2_);
+
+    EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+              reader2_->UpdatesFaviconInBackground());
+    writer2_->NotifySiteLoaded();
+    writer2_->NotifyUpdatesFaviconInBackground();
+    EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+              reader2_->UpdatesFaviconInBackground());
+    test_clock_.Advance(kDelay);
+  }
+
+  base::SimpleTestTickClock test_clock_;
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestingProfile profile_;
+
+  // Owned by |data_cache_|.
+  ::testing::StrictMock<MockSiteCache>* mock_db_ = nullptr;
+  std::unique_ptr<SiteDataCacheImpl> data_cache_;
+
+  std::unique_ptr<SiteDataReader> reader_;
+  std::unique_ptr<SiteDataWriter> writer_;
+  internal::SiteDataImpl* data_ = nullptr;
+
+  std::unique_ptr<SiteDataReader> reader2_;
+  std::unique_ptr<SiteDataWriter> writer2_;
+  internal::SiteDataImpl* data2_ = nullptr;
+};
+
+TEST_F(SiteDataCacheImplTest, EndToEnd) {
+  auto reader = data_cache_->GetReaderForOrigin(kTestOrigin);
+  EXPECT_TRUE(reader);
+  auto writer = data_cache_->GetWriterForOrigin(
+      kTestOrigin, performance_manager::TabVisibility::kBackground);
+  EXPECT_TRUE(writer);
+
+  EXPECT_EQ(1U, data_cache_->origin_data_map_for_testing().size());
+
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader->UpdatesTitleInBackground());
+  writer->NotifySiteLoaded();
+  writer->NotifyUpdatesTitleInBackground();
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+            reader->UpdatesTitleInBackground());
+  writer->NotifySiteUnloaded();
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+            reader->UpdatesTitleInBackground());
+
+  auto reader_copy = data_cache_->GetReaderForOrigin(kTestOrigin);
+  EXPECT_EQ(1U, data_cache_->origin_data_map_for_testing().size());
+  auto reader2 = data_cache_->GetReaderForOrigin(kTestOrigin2);
+  EXPECT_EQ(2U, data_cache_->origin_data_map_for_testing().size());
+  reader2.reset();
+
+  WaitForAsyncOperationsToComplete();
+  EXPECT_EQ(1U, data_cache_->origin_data_map_for_testing().size());
+  reader_copy.reset();
+
+  reader.reset();
+  writer.reset();
+  EXPECT_TRUE(data_cache_->origin_data_map_for_testing().empty());
+
+  EXPECT_CALL(*mock_db_, ClearStore());
+  data_cache_->ClearAllSiteData();
+}
+
+TEST_F(SiteDataCacheImplTest, ClearSiteDataForOrigins) {
+  SetupTwoSitesUsingFeaturesInBackground();
+
+  const base::TimeDelta last_loaded_time2_before_urls_deleted =
+      data2_->last_loaded_time_for_testing();
+
+  // Make sure that all data passed to |ClearSiteDataForOrigins| get passed to
+  // the database, even if they're not in the internal map used by the data
+  // cache.
+  const url::Origin kOriginNotInMap =
+      url::Origin::Create(GURL("http://www.url-not-in-map.com"));
+  std::vector<url::Origin> origins_to_remove = {kTestOrigin, kOriginNotInMap};
+  EXPECT_CALL(*mock_db_,
+              RemoveSiteDataFromStore(::testing::WhenSorted(
+                  ::testing::ElementsAre(kTestOrigin, kOriginNotInMap))));
+  data_cache_->ClearSiteDataForOrigins(origins_to_remove);
+  ::testing::Mock::VerifyAndClear(mock_db_);
+
+  // The information for the first site should have been cleared. The last
+  // loaded time should be equal to the current time.
+  EXPECT_EQ(data_->last_loaded_time_for_testing(),
+            test_clock_.NowTicks() - base::TimeTicks::UnixEpoch());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader_->UpdatesTitleInBackground());
+  // The second site shouldn't have been cleared.
+  EXPECT_EQ(data2_->last_loaded_time_for_testing(),
+            last_loaded_time2_before_urls_deleted);
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+            reader2_->UpdatesFaviconInBackground());
+
+  writer_->NotifySiteUnloaded();
+  writer2_->NotifySiteUnloaded();
+}
+
+TEST_F(SiteDataCacheImplTest, ClearAllSiteData) {
+  SetupTwoSitesUsingFeaturesInBackground();
+
+  // Delete all the information stored in the data store.
+  EXPECT_CALL(*mock_db_, ClearStore());
+  data_cache_->ClearAllSiteData();
+  ::testing::Mock::VerifyAndClear(mock_db_);
+
+  // The information for both sites should have been cleared.
+  EXPECT_EQ(data_->last_loaded_time_for_testing(),
+            test_clock_.NowTicks() - base::TimeTicks::UnixEpoch());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader_->UpdatesTitleInBackground());
+  EXPECT_EQ(data2_->last_loaded_time_for_testing(),
+            test_clock_.NowTicks() - base::TimeTicks::UnixEpoch());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader2_->UpdatesFaviconInBackground());
+
+  writer_->NotifySiteUnloaded();
+  writer2_->NotifySiteUnloaded();
+}
+
+TEST_F(SiteDataCacheImplTest, InspectorWorks) {
+  // Make sure the inspector interface was registered at construction.
+  SiteDataCacheInspector* inspector =
+      SiteDataCacheInspector::GetForBrowserContext(&profile_);
+  EXPECT_NE(nullptr, inspector);
+  EXPECT_EQ(data_cache_.get(), inspector);
+
+  EXPECT_STREQ("SiteDataCache", inspector->GetDataCacheName());
+
+  // We expect an empty data store at the outset.
+  EXPECT_EQ(0U, inspector->GetAllInMemoryOrigins().size());
+  std::unique_ptr<SiteDataProto> data;
+  bool is_dirty = false;
+  EXPECT_FALSE(inspector->GetDataForOrigin(kTestOrigin, &is_dirty, &data));
+  EXPECT_FALSE(is_dirty);
+  EXPECT_EQ(nullptr, data.get());
+
+  {
+    // Add an entry, see that it's reflected in the inspector interface.
+    auto writer = data_cache_->GetWriterForOrigin(
+        kTestOrigin, performance_manager::TabVisibility::kBackground);
+
+    EXPECT_EQ(1U, inspector->GetAllInMemoryOrigins().size());
+    EXPECT_TRUE(inspector->GetDataForOrigin(kTestOrigin, &is_dirty, &data));
+    EXPECT_FALSE(is_dirty);
+    ASSERT_NE(nullptr, data.get());
+
+    // Touch the underlying data, see that the dirty bit updates.
+    writer->NotifySiteLoaded();
+    EXPECT_TRUE(inspector->GetDataForOrigin(kTestOrigin, &is_dirty, &data));
+    EXPECT_TRUE(is_dirty);
+  }
+
+  // Make sure the interface is unregistered from the profile on destruction.
+  data_cache_.reset();
+  EXPECT_EQ(nullptr, SiteDataCacheInspector::GetForBrowserContext(&profile_));
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.cc
new file mode 100644
index 0000000..763c6eab
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.cc
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+
+#include "base/macros.h"
+#include "content/public/browser/browser_context.h"
+
+namespace performance_manager {
+
+namespace {
+
+const void* const kSiteDataCacheInspectorUserKey =
+    &kSiteDataCacheInspectorUserKey;
+
+class SiteDataUserData : public base::SupportsUserData::Data {
+ public:
+  explicit SiteDataUserData(SiteDataCacheInspector* inspector)
+      : inspector_(inspector) {}
+
+  SiteDataCacheInspector* inspector() const { return inspector_; }
+
+ private:
+  SiteDataCacheInspector* inspector_;
+};
+
+}  // namespace
+
+// static
+SiteDataCacheInspector* SiteDataCacheInspector::GetForBrowserContext(
+    content::BrowserContext* browser_context) {
+  SiteDataUserData* data = static_cast<SiteDataUserData*>(
+      browser_context->GetUserData(kSiteDataCacheInspectorUserKey));
+
+  if (!data)
+    return nullptr;
+
+  return data->inspector();
+}
+
+// static
+void SiteDataCacheInspector::SetForBrowserContext(
+    SiteDataCacheInspector* inspector,
+    content::BrowserContext* browser_context) {
+  if (inspector) {
+    DCHECK_EQ(nullptr, GetForBrowserContext(browser_context));
+
+    browser_context->SetUserData(kSiteDataCacheInspectorUserKey,
+                                 std::make_unique<SiteDataUserData>(inspector));
+  } else {
+    DCHECK_NE(nullptr, GetForBrowserContext(browser_context));
+    browser_context->RemoveUserData(kSiteDataCacheInspectorUserKey);
+  }
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h
new file mode 100644
index 0000000..f1fb469b
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h
@@ -0,0 +1,79 @@
+// 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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/optional.h"
+#include "base/supports_user_data.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "url/origin.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace performance_manager {
+
+class SiteDataCache;
+
+// An interface that allows LocalSite data cachess to expose diagnostic
+// information for the associated web UI.
+class SiteDataCacheInspector {
+ public:
+  // Retrieves the instance associated with a given browser context, or nullptr
+  // if none is associated with that browser context.
+  static SiteDataCacheInspector* GetForBrowserContext(
+      content::BrowserContext* browser_context);
+
+  // Returns the name of the data cache, which should uniquely identify the kind
+  // of storage it implements.
+  virtual const char* GetDataCacheName() = 0;
+
+  // Retrieves the origins that are current represented by in-memory data
+  // at the present time.
+  virtual std::vector<url::Origin> GetAllInMemoryOrigins() = 0;
+
+  // Retrieves the number of rows and the on-disk size of the store. Invokes
+  // the |on_have_data| callback once the data has been collected, or once it's
+  // determined that the data can't be retrieved.
+  // On callback |num_rows| is the number of rows in the database, or -1 if
+  // the number can't be determined. |on_disk_size_kb| is the on-disk size of
+  // the database, or -1 if the on-disk size can't be determined.
+  using DataStoreSizeCallback =
+      base::OnceCallback<void(base::Optional<int64_t> num_rows,
+                              base::Optional<int64_t> on_disk_size_kb)>;
+  virtual void GetDataStoreSize(DataStoreSizeCallback on_have_data) = 0;
+
+  // Retrieves the in-memory data for a given origin.
+  // On return |data| contains the available data for |origin| if available,
+  // and |is_dirty| is true if the entry needs flushing to disk.
+  // Returns true if an entry exists for |origin|.
+  virtual bool GetDataForOrigin(const url::Origin& origin,
+                                bool* is_dirty,
+                                std::unique_ptr<SiteDataProto>* data) = 0;
+
+  // Retrieves the data cache this inspector is associated with.
+  virtual SiteDataCache* GetDataCache() = 0;
+
+ protected:
+  // Sets the inspector instance associated with a given browser context.
+  // If |inspector| is nullptr the association is cleared.
+  // The caller must ensure that |inspector|'s registration is cleared before
+  // |inspector| or |browser_context| are deleted.
+  // The intent is for this to be called from implementation class' constructors
+  // and destructors.
+  static void SetForBrowserContext(SiteDataCacheInspector* inspector,
+                                   content::BrowserContext* browser_context);
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h b/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h
index 15cd983..65f85c4 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h
@@ -24,6 +24,7 @@
 
 namespace performance_manager {
 
+class SiteDataCacheImpl;
 class SiteDataReaderTest;
 class SiteDataWriterTest;
 
@@ -150,6 +151,7 @@
 
  protected:
   friend class base::RefCounted<SiteDataImpl>;
+  friend class performance_manager::SiteDataCacheImpl;
 
   // Friend all the tests.
   friend class SiteDataImplTest;
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h
index ccf7172..5571b4b 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h
@@ -45,7 +45,9 @@
   const internal::SiteDataImpl* impl_for_testing() const { return impl_.get(); }
 
  private:
+  friend class SiteDataCacheImpl;
   friend class SiteDataReaderTest;
+
   FRIEND_TEST_ALL_PREFIXES(SiteDataReaderTest,
                            DestroyingReaderCancelsPendingCallbacks);
   FRIEND_TEST_ALL_PREFIXES(SiteDataReaderTest,
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_writer.h b/chrome/browser/performance_manager/persistence/site_data/site_data_writer.h
index 7ce97113..65bc2df 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_writer.h
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_writer.h
@@ -43,7 +43,7 @@
 
  protected:
   friend class SiteDataWriterTest;
-  friend class SiteDataStore;
+  friend class SiteDataCacheImpl;
 
   // Protected constructor, these objects are meant to be created by a site data
   // store.
diff --git a/chrome/browser/performance_manager/public/graph/frame_node.h b/chrome/browser/performance_manager/public/graph/frame_node.h
index bdc8f62..d6fa1d5 100644
--- a/chrome/browser/performance_manager/public/graph/frame_node.h
+++ b/chrome/browser/performance_manager/public/graph/frame_node.h
@@ -10,6 +10,7 @@
 namespace performance_manager {
 
 class Graph;
+class FrameNodeObserver;
 
 // Frame nodes form a tree structure, each FrameNode at most has one parent that
 // is a FrameNode. Conceptually, a frame corresponds to a
@@ -35,6 +36,9 @@
 // active frame.
 class FrameNode {
  public:
+  using Observer = FrameNodeObserver;
+  class ObserverDefaultImpl;
+
   FrameNode();
   virtual ~FrameNode();
 
@@ -49,6 +53,67 @@
   DISALLOW_COPY_AND_ASSIGN(FrameNode);
 };
 
+// Pure virtual observer interface. Derive from this if you want to be forced to
+// implement the entire interface.
+class FrameNodeObserver {
+ public:
+  FrameNodeObserver();
+  virtual ~FrameNodeObserver();
+
+  // Node lifetime notifications.
+
+  // Called when a |frame_node| is added to the graph.
+  virtual void OnFrameNodeAdded(const FrameNode* frame_node) = 0;
+
+  // Called before a |frame_node| is removed from the graph.
+  virtual void OnBeforeFrameNodeRemoved(const FrameNode* frame_node) = 0;
+
+  // Notifications of property changes.
+
+  // Invoked when the |is_current| property changes.
+  virtual void OnIsCurrentChanged(const FrameNode* frame_node) = 0;
+
+  // Invoked when the |network_almost_idle| property changes.
+  virtual void OnNetworkAlmostIdleChanged(const FrameNode* frame_node) = 0;
+
+  // Invoked when the |lifecycle_state| property changes.
+  virtual void OnLifecycleStateChanged(const FrameNode* frame_node) = 0;
+
+  // Invoked when the |url| property changes.
+  virtual void OnURLChanged(const FrameNode* frame_node) = 0;
+
+  // Events with no property changes.
+
+  // Invoked when a non-persistent notification has been issued by the frame.
+  virtual void OnNonPersistentNotificationCreated(
+      const FrameNode* frame_node) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FrameNodeObserver);
+};
+
+// Default implementation of observer that provides dummy versions of each
+// function. Derive from this if you only need to implement a few of the
+// functions.
+class FrameNode::ObserverDefaultImpl : public FrameNodeObserver {
+ public:
+  ObserverDefaultImpl();
+  ~ObserverDefaultImpl() override;
+
+  // FrameNodeObserver implementation:
+  void OnFrameNodeAdded(const FrameNode* frame_node) override {}
+  void OnBeforeFrameNodeRemoved(const FrameNode* frame_node) override {}
+  void OnIsCurrentChanged(const FrameNode* frame_node) override {}
+  void OnNetworkAlmostIdleChanged(const FrameNode* frame_node) override {}
+  void OnLifecycleStateChanged(const FrameNode* frame_node) override {}
+  void OnURLChanged(const FrameNode* frame_node) override {}
+  void OnNonPersistentNotificationCreated(
+      const FrameNode* frame_node) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
+};
+
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_GRAPH_FRAME_NODE_H_
diff --git a/chrome/browser/performance_manager/public/graph/graph.h b/chrome/browser/performance_manager/public/graph/graph.h
index bcd66652..5bceb6c0 100644
--- a/chrome/browser/performance_manager/public/graph/graph.h
+++ b/chrome/browser/performance_manager/public/graph/graph.h
@@ -11,14 +11,37 @@
 
 namespace performance_manager {
 
+class GraphObserver;
+class FrameNodeObserver;
+class PageNodeObserver;
+class ProcessNodeObserver;
+class SystemNodeObserver;
+
 // Represents a graph of the nodes representing a single browser. Maintains a
 // set of nodes that can be retrieved in different ways, some indexed. Keeps
 // a list of observers that are notified of node addition and removal.
 class Graph {
  public:
+  using Observer = GraphObserver;
+
   Graph();
   virtual ~Graph();
 
+  // Adds an |observer| on the graph. It is safe for observers to stay
+  // registered on the graph at the time of its death.
+  virtual void AddGraphObserver(GraphObserver* observer) = 0;
+  virtual void AddFrameNodeObserver(FrameNodeObserver* observer) = 0;
+  virtual void AddPageNodeObserver(PageNodeObserver* observer) = 0;
+  virtual void AddProcessNodeObserver(ProcessNodeObserver* observer) = 0;
+  virtual void AddSystemNodeObserver(SystemNodeObserver* observer) = 0;
+
+  // Removes an |observer| from the graph.
+  virtual void RemoveGraphObserver(GraphObserver* observer) = 0;
+  virtual void RemoveFrameNodeObserver(FrameNodeObserver* observer) = 0;
+  virtual void RemovePageNodeObserver(PageNodeObserver* observer) = 0;
+  virtual void RemoveProcessNodeObserver(ProcessNodeObserver* observer) = 0;
+  virtual void RemoveSystemNodeObserver(SystemNodeObserver* observer) = 0;
+
   // The following functions are implementation detail and should not need to be
   // used by external clients. They provide the ability to safely downcast to
   // the underlying implementation.
@@ -29,6 +52,25 @@
   DISALLOW_COPY_AND_ASSIGN(Graph);
 };
 
+// Observer interface for the graph.
+class GraphObserver {
+ public:
+  GraphObserver();
+  virtual ~GraphObserver();
+
+  // Called before the |graph| associated with this observer disappears. This
+  // allows the observer to do any necessary cleanup work. Note that the graph
+  // is in its destructor while this is being called, so the observer should
+  // refrain from uselessly modifying the graph. This is intended to be used to
+  // facilitate lifetime management of observers.
+  // TODO(chrisha): Make this run before the constructor!
+  // crbug.com/966840
+  virtual void OnBeforeGraphDestroyed(const Graph* graph) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GraphObserver);
+};
+
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_GRAPH_GRAPH_H_
diff --git a/chrome/browser/performance_manager/public/graph/page_node.h b/chrome/browser/performance_manager/public/graph/page_node.h
index a1d3a5e..c1999b0 100644
--- a/chrome/browser/performance_manager/public/graph/page_node.h
+++ b/chrome/browser/performance_manager/public/graph/page_node.h
@@ -10,12 +10,16 @@
 namespace performance_manager {
 
 class Graph;
+class PageNodeObserver;
 
 // A PageNode represents the root of a FrameTree, or equivalently a WebContents.
 // These may correspond to normal tabs, WebViews, Portals, Chrome Apps or
 // Extensions.
 class PageNode {
  public:
+  using Observer = PageNodeObserver;
+  class ObserverDefaultImpl;
+
   PageNode();
   virtual ~PageNode();
 
@@ -30,6 +34,80 @@
   DISALLOW_COPY_AND_ASSIGN(PageNode);
 };
 
+// Pure virtual observer interface. Derive from this if you want to be forced to
+// implement the entire interface.
+class PageNodeObserver {
+ public:
+  PageNodeObserver();
+  virtual ~PageNodeObserver();
+
+  // Node lifetime notifications.
+
+  // Called when a |page_node| is added to the graph.
+  virtual void OnPageNodeAdded(const PageNode* page_node) = 0;
+
+  // Called before a |page_node| is removed from the graph.
+  virtual void OnBeforePageNodeRemoved(const PageNode* page_node) = 0;
+
+  // Notifications of property changes.
+
+  // Invoked when the |is_visible| property changes.
+  virtual void OnIsVisibleChanged(const PageNode* page_node) = 0;
+
+  // Invoked when the |is_loading| property changes.
+  virtual void OnIsLoadingChanged(const PageNode* page_node) = 0;
+
+  // Invoked when the |ukm_source_id| property changes.
+  virtual void OnUkmSourceIdChanged(const PageNode* page_node) = 0;
+
+  // Invoked when the |lifecycle_state| property changes.
+  virtual void OnLifecycleStateChanged(const PageNode* page_node) = 0;
+
+  // Invoked when the |page_almost_idle| property changes.
+  virtual void OnPageAlmostIdleChanged(const PageNode* page_node) = 0;
+
+  // This is fired when a main frame navigation commits. It indicates that the
+  // |navigation_id| and |main_frame_url| properties have changed.
+  virtual void OnMainFrameNavigationCommitted(const PageNode* page_node) = 0;
+
+  // Events with no property changes.
+
+  // Fired when the tab title associated with a page changes. This property is
+  // not directly reflected on the node.
+  virtual void OnTitleUpdated(const PageNode* page_node) = 0;
+
+  // Fired when the favicon associated with a page is updated. This property is
+  // not directly reflected on the node.
+  virtual void OnFaviconUpdated(const PageNode* page_node) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PageNodeObserver);
+};
+
+// Default implementation of observer that provides dummy versions of each
+// function. Derive from this if you only need to implement a few of the
+// functions.
+class PageNode::ObserverDefaultImpl : public PageNodeObserver {
+ public:
+  ObserverDefaultImpl();
+  ~ObserverDefaultImpl() override;
+
+  // PageNodeObserver implementation:
+  void OnPageNodeAdded(const PageNode* page_node) override {}
+  void OnBeforePageNodeRemoved(const PageNode* page_node) override {}
+  void OnIsVisibleChanged(const PageNode* page_node) override {}
+  void OnIsLoadingChanged(const PageNode* page_node) override {}
+  void OnUkmSourceIdChanged(const PageNode* page_node) override {}
+  void OnLifecycleStateChanged(const PageNode* page_node) override {}
+  void OnPageAlmostIdleChanged(const PageNode* page_node) override {}
+  void OnMainFrameNavigationCommitted(const PageNode* page_node) override {}
+  void OnTitleUpdated(const PageNode* page_node) override {}
+  void OnFaviconUpdated(const PageNode* page_node) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
+};
+
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_GRAPH_PAGE_NODE_H_
diff --git a/chrome/browser/performance_manager/public/graph/process_node.h b/chrome/browser/performance_manager/public/graph/process_node.h
index 9ab64761..03382aa 100644
--- a/chrome/browser/performance_manager/public/graph/process_node.h
+++ b/chrome/browser/performance_manager/public/graph/process_node.h
@@ -10,6 +10,7 @@
 namespace performance_manager {
 
 class Graph;
+class ProcessNodeObserver;
 
 // A process node follows the lifetime of a RenderProcessHost.
 // It may reference zero or one processes at a time, but during its lifetime, it
@@ -23,6 +24,9 @@
 // 4. Back to 2.
 class ProcessNode {
  public:
+  using Observer = ProcessNodeObserver;
+  class ObserverDefaultImpl;
+
   ProcessNode();
   virtual ~ProcessNode();
 
@@ -37,6 +41,59 @@
   DISALLOW_COPY_AND_ASSIGN(ProcessNode);
 };
 
+// Pure virtual observer interface. Derive from this if you want to be forced to
+// implement the entire interface.
+class ProcessNodeObserver {
+ public:
+  ProcessNodeObserver();
+  virtual ~ProcessNodeObserver();
+
+  // Node lifetime notifications.
+
+  // Called when a |process_node| is added to the graph.
+  virtual void OnProcessNodeAdded(const ProcessNode* process_node) = 0;
+
+  // Called before a |process_node| is removed from the graph.
+  virtual void OnBeforeProcessNodeRemoved(const ProcessNode* process_node) = 0;
+
+  // Notifications of property changes.
+
+  // Invoked when a new |expected_task_queueing_duration| sample is available.
+  virtual void OnExpectedTaskQueueingDurationSample(
+      const ProcessNode* process_node) = 0;
+
+  // Invoked when the |main_thread_task_load_is_low| property changes.
+  virtual void OnMainThreadTaskLoadIsLow(const ProcessNode* process_node) = 0;
+
+  // Events with no property changes.
+
+  // Fired when all frames in a process have transitioned to being frozen.
+  virtual void OnAllFramesInProcessFrozen(const ProcessNode* process_node) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProcessNodeObserver);
+};
+
+// Default implementation of observer that provides dummy versions of each
+// function. Derive from this if you only need to implement a few of the
+// functions.
+class ProcessNode::ObserverDefaultImpl : public ProcessNodeObserver {
+ public:
+  ObserverDefaultImpl();
+  ~ObserverDefaultImpl() override;
+
+  // ProcessNodeObserver implementation:
+  void OnProcessNodeAdded(const ProcessNode* process_node) override {}
+  void OnBeforeProcessNodeRemoved(const ProcessNode* process_node) override {}
+  void OnExpectedTaskQueueingDurationSample(
+      const ProcessNode* process_node) override {}
+  void OnMainThreadTaskLoadIsLow(const ProcessNode* process_node) override {}
+  void OnAllFramesInProcessFrozen(const ProcessNode* process_node) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
+};
+
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_GRAPH_PROCESS_NODE_H_
diff --git a/chrome/browser/performance_manager/public/graph/system_node.h b/chrome/browser/performance_manager/public/graph/system_node.h
index 784ad39f..7ec6875 100644
--- a/chrome/browser/performance_manager/public/graph/system_node.h
+++ b/chrome/browser/performance_manager/public/graph/system_node.h
@@ -10,11 +10,15 @@
 namespace performance_manager {
 
 class Graph;
+class SystemNodeObserver;
 
 // The SystemNode represents system-wide state. There is at most one system node
 // in a graph.
 class SystemNode {
  public:
+  using Observer = SystemNodeObserver;
+  class ObserverDefaultImpl;
+
   SystemNode();
   virtual ~SystemNode();
 
@@ -29,6 +33,49 @@
   DISALLOW_COPY_AND_ASSIGN(SystemNode);
 };
 
+// Pure virtual observer interface. Derive from this if you want to be forced to
+// implement the entire interface.
+class SystemNodeObserver {
+ public:
+  SystemNodeObserver();
+  virtual ~SystemNodeObserver();
+
+  // Node lifetime notifications.
+
+  // Called when the |system_node| is added to the graph.
+  virtual void OnSystemNodeAdded(const SystemNode* system_node) = 0;
+
+  // Called before the |system_node| is removed from the graph.
+  virtual void OnBeforeSystemNodeRemoved(const SystemNode* system_node) = 0;
+
+  // Events with no property changes.
+
+  // Fired when a batch of consistent process CPU measurements is available on
+  // the graph.
+  // TODO(siggi): Deprecate this as the CPU measurement code is reworked.
+  virtual void OnProcessCPUUsageReady(const SystemNode* system_node) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SystemNodeObserver);
+};
+
+// Default implementation of observer that provides dummy versions of each
+// function. Derive from this if you only need to implement a few of the
+// functions.
+class SystemNode::ObserverDefaultImpl : public SystemNodeObserver {
+ public:
+  ObserverDefaultImpl();
+  ~ObserverDefaultImpl() override;
+
+  // SystemNodeObserver implementation:
+  void OnSystemNodeAdded(const SystemNode* system_node) override {}
+  void OnBeforeSystemNodeRemoved(const SystemNode* system_node) override {}
+  void OnProcessCPUUsageReady(const SystemNode* system_node) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
+};
+
 }  // namespace performance_manager
 
 #endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_GRAPH_SYSTEM_NODE_H_
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl.h b/chrome/browser/performance_manager/webui_graph_dump_impl.h
index edd1a86..568d1d56 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl.h
+++ b/chrome/browser/performance_manager/webui_graph_dump_impl.h
@@ -16,7 +16,8 @@
 
 class GraphImpl;
 
-class WebUIGraphDumpImpl : public mojom::WebUIGraphDump, public GraphObserver {
+class WebUIGraphDumpImpl : public mojom::WebUIGraphDump,
+                           public GraphImplObserver {
  public:
   explicit WebUIGraphDumpImpl(GraphImpl* graph);
   ~WebUIGraphDumpImpl() override;
@@ -29,42 +30,45 @@
   void SubscribeToChanges(
       mojom::WebUIGraphChangeStreamPtr change_subscriber) override;
 
-  // GraphObserver implementation.
   void OnRegistered() override {}
   void OnUnregistered() override {}
   bool ShouldObserve(const NodeBase* node) override;
   void OnNodeAdded(NodeBase* node) override;
   void OnBeforeNodeRemoved(NodeBase* node) override;
+  void SetGraph(GraphImpl* graph) override;
+
+  // Frame node functions.
   void OnIsCurrentChanged(FrameNodeImpl* frame_node) override;
   void OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) override;
   void OnLifecycleStateChanged(FrameNodeImpl* frame_node) override;
   void OnURLChanged(FrameNodeImpl* frame_node) override;
   // Event notification.
   void OnNonPersistentNotificationCreated(FrameNodeImpl* frame_node) override {}
+
+  // Page node functions.
   void OnIsVisibleChanged(PageNodeImpl* page_node) override;
   void OnIsLoadingChanged(PageNodeImpl* page_node) override;
   void OnUkmSourceIdChanged(PageNodeImpl* page_node) override;
   void OnLifecycleStateChanged(PageNodeImpl* page_node) override;
   void OnPageAlmostIdleChanged(PageNodeImpl* page_node) override;
-
   // Event notification.
   void OnFaviconUpdated(PageNodeImpl* page_node) override;
   // Event notification.
   void OnTitleUpdated(PageNodeImpl* page_node) override {}
-
   // Event notification that also implies the main_frame_url changed.
   void OnMainFrameNavigationCommitted(PageNodeImpl* page_node) override;
+
+  // Process node functions.
   void OnExpectedTaskQueueingDurationSample(
       ProcessNodeImpl* process_node) override;
   void OnMainThreadTaskLoadIsLow(ProcessNodeImpl* process_node) override;
   // Event notification.
   void OnAllFramesInProcessFrozen(ProcessNodeImpl* process_node) override {}
 
-  // Ignored
+  // System node functions.
+  // Ignored.
   void OnProcessCPUUsageReady(SystemNodeImpl* system_node) override {}
 
-  void SetGraph(GraphImpl* graph) override;
-
  private:
   // The favicon requests happen on the UI thread. This helper class
   // maintains the state required to do that.
diff --git a/chrome/browser/policy/e2e_test/.style.yapf b/chrome/browser/policy/e2e_test/.style.yapf
new file mode 100644
index 0000000..5a64936
--- /dev/null
+++ b/chrome/browser/policy/e2e_test/.style.yapf
@@ -0,0 +1,2 @@
+[style]

+based_on_style = chromium
\ No newline at end of file
diff --git a/chrome/browser/policy/e2e_test/test.py b/chrome/browser/policy/e2e_test/test.py
new file mode 100644
index 0000000..e07c078
--- /dev/null
+++ b/chrome/browser/policy/e2e_test/test.py
@@ -0,0 +1,103 @@
+# 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.
+"""The test runner that runs enterprise end-to-end tests."""
+
+import logging
+import sys
+import traceback
+import warnings
+from absl import app
+from absl import flags
+import chrome_ent_test.infra.controller as controller
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_string(
+    'test', None,
+    'The full class name of the EnterpriseTestCase class (w/ package)')
+flags.mark_flag_as_required('test')
+
+flags.DEFINE_string('test_filter', None,
+                    'The name of the test to run in the test class')
+
+flags.DEFINE_string('host', None,
+                    'The full path to the *.host.textpb file to use')
+flags.mark_flag_as_required('host')
+
+flags.DEFINE_string('cel_ctl', None,
+                    'Which binary to use to deploy the environment')
+flags.mark_flag_as_required('cel_ctl')
+
+flags.DEFINE_bool(
+    'deploy', True, 'Depoly the test environment. '
+    'Set to false to skip the deployment phase and go straight to tests')
+flags.DEFINE_bool(
+    'skip_before_all', False, 'True to skip @before_all methods. '
+    'Like --nodeploy, this is used to skip set up steps. '
+    'Useful when developing new tests.')
+flags.DEFINE_bool('cleanup', False,
+                  'Clean up the host environment after the test')
+flags.DEFINE_string('error_logs_dir', None,
+                    'Where to collect extra logs on test failures')
+flags.DEFINE_multi_string('test_arg', None, 'Flags passed to tests')
+
+
+def ConfigureLogging():
+  # Filter out logs from low level loggers
+  errorOnlyLoggers = [
+      'googleapiclient.discovery_cache', 'google.auth', 'google_auth_httplib2'
+  ]
+  for logger in errorOnlyLoggers:
+    logging.getLogger(logger).setLevel(logging.ERROR)
+  message = 'We recommend that most server applications use service accounts.'
+  warnings.filterwarnings('ignore', '.*%s' % message)
+
+  logging.error("%s: Logging level error is visible." % __file__)
+  logging.warning("%s: Logging level warning is visible." % __file__)
+  logging.info("%s: Logging level info is visible." % __file__)
+  logging.debug("%s: Logging level debug is visible." % __file__)
+
+
+def main(argv):
+  ConfigureLogging()
+
+  c = controller.SingleTestController(
+      FLAGS.test,
+      FLAGS.host,
+      FLAGS.cel_ctl,
+      test_filter=FLAGS.test_filter,
+      skip_before_all=FLAGS.skip_before_all)
+
+  # Parse test specific flags. Note that we need to use a dummy element
+  # as the first element of the list since absl.flags ignores the first element
+  # during parsing.
+  if FLAGS.test_arg is not None:
+    FLAGS([''] + FLAGS.test_arg)
+
+  success = False
+  should_write_logs = (FLAGS.error_logs_dir != None)
+  try:
+    if FLAGS.deploy:
+      c.DeployNewEnvironment()
+
+    success = c.ExecuteTestCase()
+  except KeyboardInterrupt:
+    logging.error('Test aborted.')
+  except:
+    print(traceback.format_exc())
+    logging.error('Test failed.')
+  finally:
+    if not success and should_write_logs:
+      print('Writing Compute logs to "%s"...' % FLAGS.error_logs_dir)
+      c.TryWriteComputeLogsTo(FLAGS.error_logs_dir)
+
+    if FLAGS.cleanup:
+      print('Cleaning up host environment...')
+      c.TryCleanHostEnvironment()
+
+  sys.exit(0 if success else 1)
+
+
+if __name__ == '__main__':
+  app.run(main)
diff --git a/chrome/browser/policy/e2e_test/tests/__init__.py b/chrome/browser/policy/e2e_test/tests/__init__.py
new file mode 100644
index 0000000..98057ef
--- /dev/null
+++ b/chrome/browser/policy/e2e_test/tests/__init__.py
@@ -0,0 +1,5 @@
+# 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.
+
+from force_google_safe_search.force_google_safe_search import *
diff --git a/chrome/browser/policy/e2e_test/tests/force_google_safe_search/__init__.py b/chrome/browser/policy/e2e_test/tests/force_google_safe_search/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/browser/policy/e2e_test/tests/force_google_safe_search/__init__.py
diff --git a/chrome/browser/policy/e2e_test/tests/force_google_safe_search/force_google_safe_search.py b/chrome/browser/policy/e2e_test/tests/force_google_safe_search/force_google_safe_search.py
new file mode 100644
index 0000000..0da01b1
--- /dev/null
+++ b/chrome/browser/policy/e2e_test/tests/force_google_safe_search/force_google_safe_search.py
@@ -0,0 +1,52 @@
+# 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.
+
+import os
+import logging
+from chrome_ent_test.infra.core import environment, before_all, test
+from chrome_ent_test.ent_tests import ChromeEnterpriseTestCase
+from absl import flags
+
+FLAGS = flags.FLAGS
+
+
+@environment(file="../policy_test.asset.textpb")
+class ForceGoogleSafeSearchTest(ChromeEnterpriseTestCase):
+
+  @before_all
+  def setup(self):
+    self.InstallChrome('client2012')
+    self.InstallWebDriver('client2012')
+
+  @test
+  def test_ForceGoogleSafeSearchEnabled(self):
+    # enable policy ForceGoogleSafeSearch
+    self.SetPolicy('win2012-dc', 'ForceGoogleSafeSearch', 1, 'DWORD')
+    self.RunCommand('client2012', 'gpupdate /force')
+    logging.info('ForceGoogleSafeSearch ENABLED')
+    d = os.path.dirname(os.path.abspath(__file__))
+    output = self.RunWebDriverTest(
+        'client2012',
+        os.path.join(d, 'force_google_safe_search_webdriver_test.py'))
+    logging.info('url used: %s', output)
+
+    # assert that safe search is enabled
+    self.assertIn('safe=active', output)
+    self.assertIn('ssui=on', output)
+
+  @test
+  def test_ForceGoogleSafeSearchDisabled(self):
+    # disable policy ForceGoogleSafeSearch
+    self.SetPolicy('win2012-dc', 'ForceGoogleSafeSearch', 0, 'DWORD')
+    self.RunCommand('client2012', 'gpupdate /force')
+    d = os.path.dirname(os.path.abspath(__file__))
+    logging.info('ForceGoogleSafeSearch DISABLED')
+    output = self.RunWebDriverTest(
+        'client2012',
+        os.path.join(d, 'force_google_safe_search_webdriver_test.py'))
+    logging.info('url used: %s', output)
+
+    # assert that safe search is NOT enabled
+    self.assertNotIn('safe=active', output)
+    self.assertNotIn('ssui=on', output)
diff --git a/chrome/browser/policy/e2e_test/tests/force_google_safe_search/force_google_safe_search_webdriver_test.py b/chrome/browser/policy/e2e_test/tests/force_google_safe_search/force_google_safe_search_webdriver_test.py
new file mode 100644
index 0000000..eba78c53
--- /dev/null
+++ b/chrome/browser/policy/e2e_test/tests/force_google_safe_search/force_google_safe_search_webdriver_test.py
@@ -0,0 +1,31 @@
+# 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.
+
+import os
+
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
+
+os.environ["CHROME_LOG_FILE"] = r"c:\temp\chrome_log.txt"
+
+driver = webdriver.Chrome(
+    "C:/ProgramData/chocolatey/lib/chromedriver/tools/chromedriver.exe",
+    service_args=["--verbose", r"--log-path=c:\temp\chromedriver.log"])
+driver.get('http://www.google.com/xhtml')
+
+# wait for page to be loaded
+wait = WebDriverWait(driver, 10)
+wait.until(EC.visibility_of_element_located((By.NAME, 'q')))
+
+search_box = driver.find_element_by_name('q')
+search_box.send_keys('searchTerm')
+search_box.submit()
+
+# wait for the search result page to be loaded
+wait.until(EC.visibility_of_element_located((By.ID, 'search')))
+
+print driver.current_url
+driver.quit()
diff --git a/chrome/browser/policy/e2e_test/tests/policy_test.asset.textpb b/chrome/browser/policy/e2e_test/tests/policy_test.asset.textpb
new file mode 100644
index 0000000..978fc119
--- /dev/null
+++ b/chrome/browser/policy/e2e_test/tests/policy_test.asset.textpb
@@ -0,0 +1,29 @@
+# The test configuration used by most policy tests.

+# It consists of one domain controller and one client.

+network {

+  name: 'primary'

+}

+

+# An ActiveDirectory domain.

+ad_domain {

+  name: 'test1.com'

+  netbios_name: 'example'

+

+  domain_controller {

+    windows_machine: 'win2012-dc'

+  }

+}

+

+# the domain controller.

+windows_machine {

+  name: 'win2012-dc'

+  machine_type: 'win2012r2'

+  network_interface { network: 'primary' }

+}

+

+windows_machine {

+  name: 'client2012'

+  machine_type: 'win2012r2'

+  network_interface { network: 'primary' }

+  container { ad_domain: 'test1.com' }

+}
\ No newline at end of file
diff --git a/chrome/browser/previews/previews_browsertest.cc b/chrome/browser/previews/previews_browsertest.cc
index 17191ca..7a9e09b 100644
--- a/chrome/browser/previews/previews_browsertest.cc
+++ b/chrome/browser/previews/previews_browsertest.cc
@@ -7,12 +7,10 @@
 #include "base/metrics/field_trial_param_associator.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/run_loop.h"
-#include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool/thread_pool.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
-#include "base/test/values_test_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/metrics/subprocess_metrics_provider.h"
@@ -20,7 +18,6 @@
 #include "chrome/browser/previews/previews_service_factory.h"
 #include "chrome/browser/previews/previews_ui_tab_helper.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ssl/cert_verifier_browser_test.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -38,12 +35,9 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/test/browser_test_utils.h"
 #include "net/dns/mock_host_resolver.h"
-#include "net/reporting/reporting_policy.h"
-#include "net/test/embedded_test_server/controllable_http_response.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
-#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/network_quality_tracker.h"
 
 namespace {
@@ -428,147 +422,3 @@
   EXPECT_TRUE(noscript_js_requested());
   EXPECT_FALSE(noscript_css_requested());
 }
-
-namespace {
-
-class PreviewsReportingBrowserTest : public CertVerifierBrowserTest {
- public:
-  PreviewsReportingBrowserTest()
-      : https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {}
-  ~PreviewsReportingBrowserTest() override = default;
-
-  void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        {network::features::kReporting, previews::features::kPreviews,
-         previews::features::kClientLoFi,
-         data_reduction_proxy::features::
-             kDataReductionProxyEnabledWithNetworkService},
-        {network::features::kNetworkErrorLogging});
-    CertVerifierBrowserTest::SetUp();
-    // Make report delivery happen instantly.
-    net::ReportingPolicy policy;
-    policy.delivery_interval = base::TimeDelta::FromSeconds(0);
-    net::ReportingPolicy::UsePolicyForTesting(policy);
-  }
-
-  void SetUpOnMainThread() override {
-    CertVerifierBrowserTest::SetUpOnMainThread();
-
-    g_browser_process->network_quality_tracker()
-        ->ReportEffectiveConnectionTypeForTesting(
-            net::EFFECTIVE_CONNECTION_TYPE_2G);
-
-    host_resolver()->AddRule("*", "127.0.0.1");
-
-    main_frame_response_ =
-        std::make_unique<net::test_server::ControllableHttpResponse>(
-            server(), "/lofi_test");
-    upload_response_ =
-        std::make_unique<net::test_server::ControllableHttpResponse>(server(),
-                                                                     "/upload");
-    mock_cert_verifier()->set_default_result(net::OK);
-    ASSERT_TRUE(server()->Start());
-  }
-
-  void SetUpCommandLine(base::CommandLine* cmd) override {
-    CertVerifierBrowserTest::SetUpCommandLine(cmd);
-    cmd->AppendSwitch("enable-spdy-proxy-auth");
-
-    // Due to race conditions, it's possible that blacklist data is not loaded
-    // at the time of first navigation. That may prevent Preview from
-    // triggering, and causing the test to flake.
-    cmd->AppendSwitch(previews::switches::kIgnorePreviewsBlacklist);
-  }
-
-  net::EmbeddedTestServer* server() { return &https_server_; }
-  int port() const { return https_server_.port(); }
-
-  net::test_server::ControllableHttpResponse* main_frame_response() {
-    return main_frame_response_.get();
-  }
-
-  net::test_server::ControllableHttpResponse* upload_response() {
-    return upload_response_.get();
-  }
-
-  GURL GetReportingEnabledURL() const {
-    return GURL(base::StringPrintf("https://example.com:%d/lofi_test", port()));
-  }
-
-  GURL GetCollectorURL() const {
-    return GURL(base::StringPrintf("https://example.com:%d/upload", port()));
-  }
-
-  std::string GetReportToHeader() const {
-    return "Report-To: {\"endpoints\":[{\"url\":\"" + GetCollectorURL().spec() +
-           "\"}],\"max_age\":86400}\r\n";
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-  net::EmbeddedTestServer https_server_;
-  std::unique_ptr<net::test_server::ControllableHttpResponse>
-      main_frame_response_;
-  std::unique_ptr<net::test_server::ControllableHttpResponse> upload_response_;
-
-  DISALLOW_COPY_AND_ASSIGN(PreviewsReportingBrowserTest);
-};
-
-std::unique_ptr<base::Value> ParseReportUpload(const std::string& payload) {
-  auto parsed_payload = base::test::ParseJsonDeprecated(payload);
-  // Clear out any non-reproducible fields.
-  for (auto& report : parsed_payload->GetList()) {
-    report.RemoveKey("age");
-    auto* user_agent =
-        report.FindKeyOfType("user_agent", base::Value::Type::STRING);
-    if (user_agent != nullptr)
-      *user_agent = base::Value("Mozilla/1.0");
-  }
-  return parsed_payload;
-}
-
-}  // namespace
-
-// Checks that the intervention is reported during a LoFi load.
-IN_PROC_BROWSER_TEST_F(PreviewsReportingBrowserTest,
-                       TestReportingHeadersSentForLoFiPreview) {
-  NavigateParams params(browser(), GetReportingEnabledURL(),
-                        ui::PAGE_TRANSITION_LINK);
-  Navigate(&params);
-
-  main_frame_response()->WaitForRequest();
-  main_frame_response()->Send(
-      "HTTP/1.1 200 OK\r\n"
-      "Content-Type: text/html\r\n"
-      "Empty page \r\n");
-  main_frame_response()->Send(GetReportToHeader());
-  main_frame_response()->Send("\r\n");
-  main_frame_response()->Done();
-
-  upload_response()->WaitForRequest();
-  auto actual = ParseReportUpload(upload_response()->http_request()->content);
-  upload_response()->Send("HTTP/1.1 204 OK\r\n");
-  upload_response()->Send("\r\n");
-  upload_response()->Done();
-
-  // Verify the contents of the report that we received.
-  EXPECT_TRUE(actual != nullptr);
-  auto expected = base::test::ParseJsonDeprecated(base::StringPrintf(
-      R"text(
-        [
-          {
-            "body": {
-              "id": "LitePageServed",
-              "message": "Modified page load behavior on the page because )text"
-      R"text(the page was expected to take a long amount of time to load. )text"
-      R"text(https://www.chromestatus.com/feature/5148050062311424"
-            },
-            "type": "intervention",
-            "url": "https://example.com:%d/lofi_test",
-            "user_agent": "Mozilla/1.0"
-          }
-        ]
-      )text",
-      port()));
-  EXPECT_EQ(*expected, *actual);
-}
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index 10ab94a..209bec6 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -23,6 +23,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/test/simple_test_tick_clock.h"
+#include "base/test/values_test_util.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
@@ -79,6 +80,7 @@
 #include "net/http/http_request_headers.h"
 #include "net/http/http_status_code.h"
 #include "net/nqe/effective_connection_type.h"
+#include "net/reporting/reporting_policy.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -142,6 +144,9 @@
 
     // Previews server will put Chrome into a redirect loop.
     kRedirectLoop = 8,
+
+    // The URL that intervention reports should be sent to.
+    kInterventionReport = 9,
   };
 
   void SetUpCommandLine(base::CommandLine* cmd) override {
@@ -281,8 +286,9 @@
         {previews::features::kPreviews, previews::features::kOptimizationHints,
          previews::features::kResourceLoadingHints,
          data_reduction_proxy::features::
-             kDataReductionProxyEnabledWithNetworkService},
-        {});
+             kDataReductionProxyEnabledWithNetworkService,
+         network::features::kReporting},
+        {network::features::kNetworkErrorLogging});
 
     if (GetParam()) {
       url_loader_feature_list_.InitWithFeatures(
@@ -308,6 +314,10 @@
 
     decider->BlacklistBypassedHost(kBlacklistedHost,
                                    base::TimeDelta::FromHours(1));
+
+    net::ReportingPolicy policy;
+    policy.delivery_interval = base::TimeDelta::FromSeconds(0);
+    net::ReportingPolicy::UsePolicyForTesting(policy);
   }
 
   void InitializeOptimizationHints() {
@@ -503,6 +513,17 @@
         ->GetHttpOriginalContentLength();
   }
 
+  base::Value ParsedInterventionReport() const {
+    base::Value parsed_payload =
+        base::test::ParseJson(intervention_report_content_);
+    // Clear out any non-reproducible fields.
+    for (auto& report : parsed_payload.GetList()) {
+      report.RemoveKey("age");
+      report.RemoveKey("user_agent");
+    }
+    return parsed_payload;
+  }
+
   // Returns a HTTP URL that will respond with the given action and headers when
   // used by the previews server. The response can be delayed a number of
   // milliseconds by passing a value > 0 for |delay_ms| or pass -1 to make the
@@ -582,6 +603,15 @@
     run_loop.Run();
   }
 
+  void WaitForInterventionReport() {
+    if (!intervention_report_content_.empty())
+      return;
+
+    base::RunLoop run_loop;
+    waiting_for_report_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
  private:
   std::unique_ptr<net::test_server::HttpResponse> HandleRedirectRequest(
       const net::test_server::HttpRequest& request) {
@@ -669,6 +699,15 @@
       return response;
     }
 
+    // If this request is for a intervention report, record the content.
+    if (request.GetURL().spec().find("upload_report") != std::string::npos) {
+      intervention_report_content_ = request.content;
+      response->set_code(net::HTTP_NO_CONTENT);
+      if (waiting_for_report_closure_)
+        std::move(waiting_for_report_closure_).Run();
+      return response;
+    }
+
     response->set_content_type("text/html");
 
     std::string original_url_str;
@@ -755,6 +794,12 @@
         response->set_code(net::HTTP_OK);
         response->set_content("porgporgporgporgporg" /* length = 20 */);
         response->AddCustomHeader("chrome-proxy", "ofcl=60");
+        // Use the Host header for the report because CORS.
+        response->AddCustomHeader(
+            "Report-To",
+            base::StringPrintf("{\"endpoints\":[{\"url\":\"https://%s/"
+                               "?upload_report=true\"}],\"max_age\":86400}",
+                               request.headers.find("Host")->second.c_str()));
         break;
       case kRedirectNonPreview:
         response->set_code(net::HTTP_TEMPORARY_REDIRECT);
@@ -835,7 +880,9 @@
   GURL slow_http_url_;
   uint64_t got_page_id_ = 0;
   int subresources_requested_ = 0;
+  std::string intervention_report_content_;
   base::OnceClosure waiting_for_pingback_closure_;
+  base::OnceClosure waiting_for_report_closure_;
 };
 
 // True if testing using the URLLoader Interceptor implementation.
@@ -1427,6 +1474,36 @@
   WaitForPingback();
 }
 
+IN_PROC_BROWSER_TEST_P(
+    PreviewsLitePageServerBrowserTest,
+    DISABLE_ON_WIN_MAC_CHROMESOS(LitePageSendsInterventionReport)) {
+  ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
+  VerifyPreviewLoaded();
+  WaitForInterventionReport();
+
+  base::Value expected = base::test::ParseJson(base::StringPrintf(
+      R"text(
+        [
+          {
+            "body": {
+              "id": "LitePageServed",
+              "message": "Modified page load behavior on the page because )text"
+      R"text(the page was expected to take a long amount of time to load. )text"
+      R"text(https://www.chromestatus.com/feature/5148050062311424"
+            },
+            "type": "intervention",
+            "url": "%s",
+          }
+        ]
+      )text",
+      PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(
+          HttpsLitePageURL(kSuccess))
+          .spec()
+          .c_str()));
+
+  EXPECT_EQ(expected, ParsedInterventionReport());
+}
+
 class TestDataReductionProxyPingbackClient
     : public data_reduction_proxy::DataReductionProxyPingbackClient {
  public:
diff --git a/chrome/browser/previews/previews_ui_tab_helper.cc b/chrome/browser/previews/previews_ui_tab_helper.cc
index 9b1d29fb..d62ec8e5 100644
--- a/chrome/browser/previews/previews_ui_tab_helper.cc
+++ b/chrome/browser/previews/previews_ui_tab_helper.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/page_load_metrics/metrics_web_contents_observer.h"
 #include "chrome/browser/previews/previews_content_util.h"
 #include "chrome/browser/previews/previews_service.h"
diff --git a/chrome/browser/previews/previews_ui_tab_helper_unittest.cc b/chrome/browser/previews/previews_ui_tab_helper_unittest.cc
index 38e53b0..0650b16 100644
--- a/chrome/browser/previews/previews_ui_tab_helper_unittest.cc
+++ b/chrome/browser/previews/previews_ui_tab_helper_unittest.cc
@@ -16,7 +16,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/previews/previews_lite_page_navigation_throttle.h"
 #include "chrome/browser/previews/previews_ui_tab_helper.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
diff --git a/chrome/browser/previews/resource_loading_hints/resource_loading_hints_web_contents_observer.cc b/chrome/browser/previews/resource_loading_hints/resource_loading_hints_web_contents_observer.cc
index a4ef961..d87b08d4 100644
--- a/chrome/browser/previews/resource_loading_hints/resource_loading_hints_web_contents_observer.cc
+++ b/chrome/browser/previews/resource_loading_hints/resource_loading_hints_web_contents_observer.cc
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/metrics/histogram_macros.h"
-#include "chrome/browser/loader/chrome_navigation_data.h"
 #include "chrome/browser/previews/previews_content_util.h"
 #include "chrome/browser/previews/previews_service.h"
 #include "chrome/browser/previews/previews_service_factory.h"
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc b/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc
index f749399..945e65c9 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc
@@ -36,7 +36,7 @@
 }  // namespace
 
 class LocalSiteCharacteristicsWebContentsObserver::GraphObserver
-    : public performance_manager::GraphObserverDefaultImpl {
+    : public performance_manager::GraphImplObserverDefaultImpl {
  public:
   using NodeBase = performance_manager::NodeBase;
   using FrameNodeImpl = performance_manager::FrameNodeImpl;
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
index daf4a72..9f587ea 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
@@ -65,7 +65,7 @@
 // TabLifecycleUnitSource on the UI thread. This is created on the UI thread
 // and ownership passed to the performance manager.
 class TabLifecycleStateObserver
-    : public performance_manager::GraphObserverDefaultImpl {
+    : public performance_manager::GraphImplObserverDefaultImpl {
  public:
   using NodeBase = performance_manager::NodeBase;
   using PageNodeImpl = performance_manager::PageNodeImpl;
diff --git a/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h b/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h
index 4488dce..1fb43b9a 100644
--- a/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h
+++ b/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h
@@ -17,7 +17,7 @@
 // TODO(chrisha): Kill this thing entirely and move all of tab manager into the
 // performance manager.
 class TabManager::ResourceCoordinatorSignalObserver
-    : public performance_manager::GraphObserverDefaultImpl {
+    : public performance_manager::GraphImplObserverDefaultImpl {
  public:
   using NodeBase = performance_manager::NodeBase;
   using PageNodeImpl = performance_manager::PageNodeImpl;
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
index 8e2790a..cd1296b 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
@@ -10,7 +10,6 @@
 
 #oauth-enrollment.saml {
   padding-top: 44px;
-  width: 562px;
 }
 
 #oauth-enroll-step-contents {
@@ -65,7 +64,7 @@
 .oauth-enroll-step-message {
   display: inline-block;
   padding-top: 20px;
-  text-align: left;
+  text-align: start;
   vertical-align: top;
 }
 
@@ -117,14 +116,3 @@
   top: 0;
   z-index: 1;
 }
-
-.oauth-enroll-state-signin #oauth-enroll-navigation,
-.oauth-enroll-state-ad-join #oauth-enroll-navigation,
-.oauth-enroll-state-working #oauth-enroll-navigation {
-  color: white;
-}
-
-#oauth-enrollment.saml #oauth-enroll-navigation {
-  color: rgba(0, 0, 0, .54);
-}
-
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
index 287a098..868ec34 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
@@ -220,11 +220,8 @@
       this.navigation_.addEventListener('close', this.cancel.bind(this));
       this.navigation_.addEventListener('refresh', this.cancel.bind(this));
 
-      this.navigation_.addEventListener(
-          'back', this.onBackButtonClicked_.bind(this, false));
-
       $('oobe-signin-back-button')
-          .addEventListener('tap', this.onBackButtonClicked_.bind(this, true));
+          .addEventListener('tap', this.onBackButtonClicked_.bind(this));
 
 
       $('oauth-enroll-learn-more-link')
@@ -472,13 +469,12 @@
      * Skips the device attribute update,
      * shows the successful enrollment step.
      */
-    onBackButtonClicked_: function(cancelOnClick) {
-      this.navigation_.backVisible = false;
+    onBackButtonClicked_: function() {
       if (this.currentStep_ == STEP_SIGNIN) {
         if (this.lastBackMessageValue_) {
           this.lastBackMessageValue_ = false;
           $('oauth-enroll-auth-view').back();
-        } else if (cancelOnClick) {
+        } else {
           this.cancel();
         }
       }
@@ -502,17 +498,15 @@
      * @type {boolean}
      */
     isAtTheBeginning: function() {
-      return !this.navigation_.backVisible && this.currentStep_ == STEP_SIGNIN;
+      return !this.lastBackMessageValue_ && this.currentStep_ == STEP_SIGNIN;
     },
 
     /**
      * Updates visibility of navigation buttons.
      */
     updateControlsState: function() {
-      this.navigation_.backVisible =
-          this.currentStep_ == STEP_SIGNIN && this.lastBackMessageValue_;
       this.navigation_.refreshVisible =
-          this.isAtTheBeginning() && !this.isManualEnrollment_;
+          this.isAtTheBeginning() && this.isManualEnrollment_ === false;
       this.navigation_.closeVisible =
           (this.currentStep_ == STEP_ERROR && !this.navigation_.refreshVisible)
           || this.currentStep_ == STEP_LICENSE_TYPE;
diff --git a/chrome/browser/resources/local_ntp/BUILD.gn b/chrome/browser/resources/local_ntp/BUILD.gn
index 22531ae..9b5c638 100644
--- a/chrome/browser/resources/local_ntp/BUILD.gn
+++ b/chrome/browser/resources/local_ntp/BUILD.gn
@@ -13,8 +13,8 @@
 js_library("local_ntp") {
   sources = [
     "animations.js",
-    "custom_backgrounds.js",
     "custom_links_edit.js",
+    "customize.js",
     "doodles.js",
     "local_ntp.js",
     "most_visited_single.js",
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.css b/chrome/browser/resources/local_ntp/customize.css
similarity index 100%
rename from chrome/browser/resources/local_ntp/custom_backgrounds.css
rename to chrome/browser/resources/local_ntp/customize.css
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.js b/chrome/browser/resources/local_ntp/customize.js
similarity index 100%
rename from chrome/browser/resources/local_ntp/custom_backgrounds.js
rename to chrome/browser/resources/local_ntp/customize.js
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html
index 186659b8..3f67324 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -6,7 +6,7 @@
 <head>
   <link rel="stylesheet" href="chrome-search://local-ntp/animations.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/local-ntp-common.css"></link>
-  <link rel="stylesheet" href="chrome-search://local-ntp/custom-backgrounds.css"></link>
+  <link rel="stylesheet" href="chrome-search://local-ntp/customize.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/doodles.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/local-ntp.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/theme.css"></link>
@@ -18,8 +18,8 @@
       integrity="$i18n{animationsIntegrity}"></script>
   <script src="chrome-search://local-ntp/config.js"
       integrity="$i18n{configDataIntegrity}"></script>
-  <script src="chrome-search://local-ntp/custom-backgrounds.js"
-      integrity="$i18n{localNtpCustomBgIntegrity}"></script>
+  <script src="chrome-search://local-ntp/customize.js"
+      integrity="$i18n{localNtpCustomizeIntegrity}"></script>
   <script src="chrome-search://local-ntp/doodles.js"
       integrity="$i18n{doodlesIntegrity}"></script>
   <script src="chrome-search://local-ntp/local-ntp.js"
diff --git a/chrome/browser/resources/local_ntp/local_ntp_resources.grd b/chrome/browser/resources/local_ntp/local_ntp_resources.grd
index edb2d731..ade6f64 100644
--- a/chrome/browser/resources/local_ntp/local_ntp_resources.grd
+++ b/chrome/browser/resources/local_ntp/local_ntp_resources.grd
@@ -18,8 +18,8 @@
       <include name="IDR_LOCAL_NTP_ANIMATIONS_CSS" file="animations.css" flattenhtml="true" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_ANIMATIONS_JS" file="animations.js" flattenhtml="true" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_CSS" file="local_ntp.css" flattenhtml="true" type="BINDATA" />
-      <include name="IDR_LOCAL_NTP_CUSTOM_BACKGROUNDS_CSS" file="custom_backgrounds.css" flattenhtml="true" type="BINDATA" />
-      <include name="IDR_LOCAL_NTP_CUSTOM_BACKGROUNDS_JS" file="custom_backgrounds.js" flattenhtml="true" type="BINDATA" />
+      <include name="IDR_LOCAL_NTP_CUSTOMIZE_CSS" file="customize.css" flattenhtml="true" type="BINDATA" />
+      <include name="IDR_LOCAL_NTP_CUSTOMIZE_JS" file="customize.js" flattenhtml="true" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_DOODLES_CSS" file="doodles.css" flattenhtml="true" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_DOODLES_JS" file="doodles.js" flattenhtml="true" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_HTML" file="local_ntp.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
diff --git a/chrome/browser/search/BUILD.gn b/chrome/browser/search/BUILD.gn
index b9a569f..3fb355e 100644
--- a/chrome/browser/search/BUILD.gn
+++ b/chrome/browser/search/BUILD.gn
@@ -2,13 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/compiled_action.gni")
+
 local_ntp_resources = "//chrome/browser/resources/local_ntp"
 
 action("local_ntp_code_generate") {
   script = "tools/generate_integrity_header.py"
   header_path = "$target_gen_dir/local_ntp_js_integrity.h"
   animations_js = local_ntp_resources + "/animations.js"
-  custom_bg_js = local_ntp_resources + "/custom_backgrounds.js"
+  customize_js = local_ntp_resources + "/customize.js"
   doodles_js = local_ntp_resources + "/doodles.js"
   local_ntp_js = local_ntp_resources + "/local_ntp.js"
   utils_js = local_ntp_resources + "/utils.js"
@@ -16,7 +18,7 @@
 
   inputs = [
     animations_js,
-    custom_bg_js,
+    customize_js,
     doodles_js,
     local_ntp_js,
     utils_js,
@@ -30,7 +32,7 @@
   args = [
     "--output_path=" + rebase_path(header_path, root_build_dir),
     rebase_path(animations_js, root_build_dir),
-    rebase_path(custom_bg_js, root_build_dir),
+    rebase_path(customize_js, root_build_dir),
     rebase_path(doodles_js, root_build_dir),
     rebase_path(local_ntp_js, root_build_dir),
     rebase_path(utils_js, root_build_dir),
@@ -38,10 +40,30 @@
   ]
 }
 
+executable("generate_colors_info") {
+  sources = [
+    "chrome_colors/generate_colors_info.cc",
+    "chrome_colors/selected_colors_info.h",
+  ]
+  deps = [
+    "//base",
+    "//skia",
+  ]
+}
+
+compiled_action("generate_chrome_colors_info") {
+  tool = ":generate_colors_info"
+  outputs = [
+    "$target_gen_dir/chrome_colors/",
+  ]
+  args = rebase_path(outputs, root_build_dir)
+}
+
 source_set("generated") {
   sources = get_target_outputs(":local_ntp_code_generate")
 
   public_deps = [
+    ":generate_chrome_colors_info",
     ":local_ntp_code_generate",
   ]
 }
diff --git a/chrome/browser/search/chrome_colors/generate_colors_info.cc b/chrome/browser/search/chrome_colors/generate_colors_info.cc
new file mode 100644
index 0000000..f214cc8
--- /dev/null
+++ b/chrome/browser/search/chrome_colors/generate_colors_info.cc
@@ -0,0 +1,135 @@
+// 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/base64.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/search/chrome_colors/selected_colors_info.h"
+
+// TODO(gayane): Replace with real template.
+// Template for the icon svg.
+// $1 - primary color
+// $2 - secondary color
+const char kIconTemplate[] =
+    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><!DOCTYPE svg "
+    "PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
+    "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg version=\"1.1\" "
+    "xmlns=\"http://www.w3.org/2000/svg\" "
+    "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
+    "preserveAspectRatio=\"xMidYMid meet\" viewBox=\"0 0 640 640\" "
+    "width=\"640\" height=\"640\"><defs><path d=\"M0 0L640 0L640 640L0 640L0 "
+    "0Z\" id=\"a1Y0EmMoCp\"></path><path d=\"M499.23 317.62C499.23 396.82 "
+    "420.79 461.12 324.17 461.12C227.55 461.12 149.11 396.82 149.11 "
+    "317.62C149.11 238.42 227.55 174.12 324.17 174.12C420.79 174.12 499.23 "
+    "238.42 499.23 317.62Z\" id=\"f7eUC88KqB\"></path></defs><g><g><g "
+    "transform=\"matrix(1 0 0 1 0 0)\" "
+    "vector-effect=\"non-scaling-stroke\"><use xlink:href=\"#a1Y0EmMoCp\" "
+    "opacity=\"1\" fill=$1 fill-opacity=\"1\"></use></g><g><use "
+    "xlink:href=\"#f7eUC88KqB\" opacity=\"1\" fill=$2 "
+    "fill-opacity=\"1\"></use></g></g></g></svg>";
+
+// Template for color info line.
+// $1 - color id
+// $2 - red value of primary color
+// $3 - green value of primary color
+// $4 - blue value of primary color
+// $5 - color label
+// $6 - icon data
+const char kColorInfoLineTemplate[] =
+    "   ColorInfo($1, SkColorSetRGB($2, $3, $4), \"$5\", "
+    "\"data:image/svg+xml;base64,$6\")";
+
+// Template for the generated file content.
+// $1 - lines for updated color info.
+const char kFileContentTemplate[] =
+    "// Generated from generate_colors_info.cc. Do not edit!\n"
+    "\n"
+    "#ifndef CHROME_BROWSER_SEARCH_CHROME_COLORS_GENERATED_COLORS_INFO_H_\n"
+    "#define CHROME_BROWSER_SEARCH_CHROME_COLORS_GENERATED_COLORS_INFO_H_\n"
+    "\n"
+    "#include <stdint.h>\n"
+    "\n"
+    "#include \"chrome/common/search/instant_types.h\"\n"
+    "#include \"third_party/skia/include/core/SkColor.h\"\n"
+    "\n"
+    "namespace chrome_colors {\n"
+    "\n"
+    "// List of preselected colors with icon data to show in Chrome Colors"
+    " menu.\n"
+    "constexpr ColorInfo kGeneratedColorsInfo[] = {\n"
+    "$1\n"
+    "};\n"
+    "\n"
+    "} // namespace chrome_colors\n"
+    "\n"
+    "#endif  // CHROME_BROWSER_SEARCH_CHROME_COLORS_GENERATED_COLORS_INFO_H_\n";
+
+// Generated file name.
+const base::FilePath::CharType kColorsInfoFilename[] =
+    FILE_PATH_LITERAL("generated_colors_info.h");
+
+// Returns hex string representation for the |color| in "#FFFFFF" format.
+std::string SkColorToHexString(SkColor color) {
+  return base::StringPrintf("\"#%02X%02X%02X\"", SkColorGetR(color),
+                            SkColorGetG(color), SkColorGetB(color));
+}
+
+// Returns icon data for the given |color| as encoded svg.
+// The returned string can be later directly set in JS with the following
+// format: "data:image/svg+xml;base64<ENCODED_SVG>"
+std::string GenerateIconDataForColor(SkColor color) {
+  std::vector<std::string> subst;
+  subst.push_back(SkColorToHexString(color));
+  // TODO(gayane): Replace white will secondary color calculated using
+  // browser_theme_pack.
+  subst.push_back(SkColorToHexString(SK_ColorWHITE));
+
+  std::string svg_base64;
+  base::Base64Encode(
+      base::ReplaceStringPlaceholders(kIconTemplate, subst, NULL), &svg_base64);
+  return svg_base64;
+}
+
+// Generates color info line in the following format:
+// ColorInfo(ID, SkColorSetRGB(R, G, B), LABEL, ICON_DATA)
+std::string GenerateColorLine(chrome_colors::ColorInfo color_info) {
+  std::vector<std::string> subst;
+  subst.push_back(base::NumberToString(color_info.id));
+  subst.push_back(base::NumberToString(SkColorGetR(color_info.color)));
+  subst.push_back(base::NumberToString(SkColorGetG(color_info.color)));
+  subst.push_back(base::NumberToString(SkColorGetB(color_info.color)));
+  subst.push_back(color_info.label);
+  subst.push_back(GenerateIconDataForColor(color_info.color));
+  return base::ReplaceStringPlaceholders(kColorInfoLineTemplate, subst, NULL);
+}
+
+// Generates 'generated_colors_info.h' that contains selected colors from
+// |chrome_colors::kSelectedColorsInfo| along with generated icon data.
+void GenerateColorsInfoFile(std::string output_dir) {
+  std::vector<std::string> updated_color_info;
+  for (chrome_colors::ColorInfo color_info : chrome_colors::kSelectedColorsInfo)
+    updated_color_info.push_back(GenerateColorLine(color_info));
+
+  std::vector<std::string> subst;
+  subst.push_back(base::JoinString(updated_color_info, ",\n"));
+  std::string output =
+      base::ReplaceStringPlaceholders(kFileContentTemplate, subst, NULL);
+
+  base::FilePath output_path = base::FilePath::FromUTF8Unsafe(output_dir);
+  if (!base::DirectoryExists(output_path))
+    base::CreateDirectory(output_path);
+
+  output_path = output_path.Append(kColorsInfoFilename);
+  if (base::WriteFile(output_path, output.c_str(),
+                      static_cast<uint32_t>(output.size())) <= 0) {
+    LOG(ERROR) << "Failed to write output to " << output_path;
+  }
+}
+
+int main(int argc, char* argv[]) {
+  GenerateColorsInfoFile(argv[1]);
+  return 0;
+}
diff --git a/chrome/browser/search/chrome_colors/selected_colors_info.h b/chrome/browser/search/chrome_colors/selected_colors_info.h
new file mode 100644
index 0000000..638b900
--- /dev/null
+++ b/chrome/browser/search/chrome_colors/selected_colors_info.h
@@ -0,0 +1,34 @@
+// 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_SEARCH_CHROME_COLORS_SELECTED_COLORS_INFO_H_
+#define CHROME_BROWSER_SEARCH_CHROME_COLORS_SELECTED_COLORS_INFO_H_
+
+#include <stdint.h>
+
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace chrome_colors {
+
+struct ColorInfo {
+  ColorInfo(int id, SkColor color, const char* label)
+      : id(id), color(color), label(label) {}
+  constexpr ColorInfo(int id,
+                      SkColor color,
+                      const char* label,
+                      const char* icon_data)
+      : id(id), color(color), label(label), icon_data(icon_data) {}
+  int id;
+  SkColor color;
+  const char* label;
+  const char* icon_data;
+};
+
+// TODO(gayane): Add colors selected by UX.
+// List of preselected colors to show in Chrome Colors menu.
+const ColorInfo kSelectedColorsInfo[] = {};
+
+}  // namespace chrome_colors
+
+#endif  // CHROME_BROWSER_SEARCH_CHROME_COLORS_SELECTED_COLORS_INFO_H_
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index ae2acaf..57bf875 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -117,10 +117,8 @@
     {"animations.css", IDR_LOCAL_NTP_ANIMATIONS_CSS, "text/css"},
     {"animations.js", IDR_LOCAL_NTP_ANIMATIONS_JS, "application/javascript"},
     {"local-ntp-common.css", IDR_LOCAL_NTP_COMMON_CSS, "text/css"},
-    {"custom-backgrounds.css", IDR_LOCAL_NTP_CUSTOM_BACKGROUNDS_CSS,
-     "text/css"},
-    {"custom-backgrounds.js", IDR_LOCAL_NTP_CUSTOM_BACKGROUNDS_JS,
-     "application/javascript"},
+    {"customize.css", IDR_LOCAL_NTP_CUSTOMIZE_CSS, "text/css"},
+    {"customize.js", IDR_LOCAL_NTP_CUSTOMIZE_JS, "application/javascript"},
     {"doodles.css", IDR_LOCAL_NTP_DOODLES_CSS, "text/css"},
     {"doodles.js", IDR_LOCAL_NTP_DOODLES_JS, "application/javascript"},
     {"images/close_3_mask.png", IDR_CLOSE_3_MASK, "image/png"},
@@ -979,8 +977,8 @@
         base::StrCat({kSha256, ANIMATIONS_JS_INTEGRITY});
     replacements["configDataIntegrity"] = base::StrCat(
         {kSha256, search_config_provider_->config_data_integrity()});
-    replacements["localNtpCustomBgIntegrity"] =
-        base::StrCat({kSha256, CUSTOM_BACKGROUNDS_JS_INTEGRITY});
+    replacements["localNtpCustomizeIntegrity"] =
+        base::StrCat({kSha256, CUSTOMIZE_JS_INTEGRITY});
     replacements["doodlesIntegrity"] =
         base::StrCat({kSha256, DOODLES_JS_INTEGRITY});
     replacements["localNtpIntegrity"] =
@@ -1113,9 +1111,8 @@
   std::string script_src_csp = base::StringPrintf(
       "script-src 'strict-dynamic' 'sha256-%s' 'sha256-%s' 'sha256-%s' "
       "'sha256-%s' 'sha256-%s' 'sha256-%s' 'sha256-%s';",
-      ANIMATIONS_JS_INTEGRITY, CUSTOM_BACKGROUNDS_JS_INTEGRITY,
-      DOODLES_JS_INTEGRITY, LOCAL_NTP_JS_INTEGRITY, UTILS_JS_INTEGRITY,
-      VOICE_JS_INTEGRITY,
+      ANIMATIONS_JS_INTEGRITY, CUSTOMIZE_JS_INTEGRITY, DOODLES_JS_INTEGRITY,
+      LOCAL_NTP_JS_INTEGRITY, UTILS_JS_INTEGRITY, VOICE_JS_INTEGRITY,
       search_config_provider_->config_data_integrity().c_str());
 
   return GetContentSecurityPolicyObjectSrc() +
diff --git a/chrome/browser/signin/identity_manager_factory.cc b/chrome/browser/signin/identity_manager_factory.cc
index a6f2dfc..89a9575 100644
--- a/chrome/browser/signin/identity_manager_factory.cc
+++ b/chrome/browser/signin/identity_manager_factory.cc
@@ -16,6 +16,7 @@
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/pref_registry/pref_registry_syncable.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/identity_manager_wrapper.h"
 #include "components/signin/core/browser/signin_manager.h"
 #include "services/identity/public/cpp/accounts_cookie_mutator.h"
@@ -80,20 +81,17 @@
 std::unique_ptr<ConcreteSigninManager> BuildSigninManager(
     Profile* profile,
     AccountTrackerService* account_tracker_service,
-    ProfileOAuth2TokenService* token_service,
-    GaiaCookieManagerService* gaia_cookie_manager_service) {
+    ProfileOAuth2TokenService* token_service) {
   std::unique_ptr<ConcreteSigninManager> signin_manager;
   SigninClient* client =
       ChromeSigninClientFactory::GetInstance()->GetForProfile(profile);
 #if defined(OS_CHROMEOS)
   signin_manager = std::make_unique<ConcreteSigninManager>(
       client, token_service, account_tracker_service,
-      gaia_cookie_manager_service,
       AccountConsistencyModeManager::GetMethodForProfile(profile));
 #else
   signin_manager = std::make_unique<ConcreteSigninManager>(
       client, token_service, account_tracker_service,
-      gaia_cookie_manager_service,
       AccountConsistencyModeManager::GetMethodForProfile(profile));
 #endif
   signin_manager->Initialize(g_browser_process->local_state());
@@ -190,8 +188,7 @@
       token_service.get(), ChromeSigninClientFactory::GetForProfile(profile));
 
   std::unique_ptr<ConcreteSigninManager> signin_manager = BuildSigninManager(
-      profile, account_tracker_service.get(), token_service.get(),
-      gaia_cookie_manager_service.get());
+      profile, account_tracker_service.get(), token_service.get());
 
   std::unique_ptr<identity::PrimaryAccountMutator> primary_account_mutator =
       BuildPrimaryAccountMutator(profile, account_tracker_service.get(),
diff --git a/chrome/browser/spellchecker/spellcheck_service_browsertest.cc b/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
index c73a908..8d5ebe0 100644
--- a/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
+++ b/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
@@ -27,6 +27,7 @@
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/constants.mojom.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/spellcheck/browser/pref_names.h"
@@ -54,6 +55,16 @@
     prefs_ = user_prefs::UserPrefs::Get(GetContext());
   }
 
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    // Sync causes the SpellcheckService to be instantiated (and initialized)
+    // during startup. However, several tests rely on control over when exactly
+    // the SpellcheckService gets created (e.g. by calling
+    // GetEnableSpellcheckState() after InitSpellcheck(), which will wait
+    // forever if the service already existed). So disable sync of the custom
+    // dictionary for these tests.
+    command_line->AppendSwitchASCII(switches::kDisableSyncTypes, "Dictionary");
+  }
+
   void TearDownOnMainThread() override {
     binding_.Close();
     prefs_ = nullptr;
diff --git a/chrome/browser/supervised_user/supervised_user_sync_model_type_controller.cc b/chrome/browser/supervised_user/supervised_user_sync_model_type_controller.cc
index 90b066a..33e4c44 100644
--- a/chrome/browser/supervised_user/supervised_user_sync_model_type_controller.cc
+++ b/chrome/browser/supervised_user/supervised_user_sync_model_type_controller.cc
@@ -17,10 +17,7 @@
     : SyncableServiceBasedModelTypeController(
           type,
           sync_client->GetModelTypeStoreService()->GetStoreFactory(),
-          base::BindOnce(
-              &browser_sync::BrowserSyncClient::GetSyncableServiceForType,
-              base::Unretained(sync_client),
-              type),
+          sync_client->GetSyncableServiceForType(type),
           dump_stack),
       profile_(profile) {
   DCHECK(type == syncer::SUPERVISED_USER_SETTINGS ||
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index 5c5838d..5341b4c 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -152,6 +152,11 @@
   return disabled_types;
 }
 
+base::WeakPtr<syncer::SyncableService> GetWeakPtrOrNull(
+    syncer::SyncableService* service) {
+  return service ? service->AsWeakPtr() : nullptr;
+}
+
 }  // namespace
 
 ChromeSyncClient::ChromeSyncClient(Profile* profile) : profile_(profile) {
@@ -303,9 +308,7 @@
   if (!disabled_types.Has(syncer::APPS)) {
     controllers.push_back(std::make_unique<ExtensionModelTypeController>(
         syncer::APPS, GetModelTypeStoreService()->GetStoreFactory(),
-        base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
-                       base::Unretained(this), syncer::APPS),
-        dump_stack, profile_));
+        GetSyncableServiceForType(syncer::APPS), dump_stack, profile_));
   }
 
   // Extension sync is enabled by default.  Register unless explicitly
@@ -313,9 +316,7 @@
   if (!disabled_types.Has(syncer::EXTENSIONS)) {
     controllers.push_back(std::make_unique<ExtensionModelTypeController>(
         syncer::EXTENSIONS, GetModelTypeStoreService()->GetStoreFactory(),
-        base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
-                       base::Unretained(this), syncer::EXTENSIONS),
-        dump_stack, profile_));
+        GetSyncableServiceForType(syncer::EXTENSIONS), dump_stack, profile_));
   }
 
   // Extension setting sync is enabled by default.  Register unless explicitly
@@ -345,9 +346,7 @@
   if (!disabled_types.Has(syncer::THEMES)) {
     controllers.push_back(std::make_unique<ExtensionModelTypeController>(
         syncer::THEMES, GetModelTypeStoreService()->GetStoreFactory(),
-        base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
-                       base::Unretained(this), syncer::THEMES),
-        dump_stack, profile_));
+        GetSyncableServiceForType(syncer::THEMES), dump_stack, profile_));
   }
 
   // Search Engine sync is enabled by default.  Register unless explicitly
@@ -357,9 +356,7 @@
         std::make_unique<syncer::SyncableServiceBasedModelTypeController>(
             syncer::SEARCH_ENGINES,
             GetModelTypeStoreService()->GetStoreFactory(),
-            base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
-                           base::Unretained(this), syncer::SEARCH_ENGINES),
-            dump_stack));
+            GetSyncableServiceForType(syncer::SEARCH_ENGINES), dump_stack));
   }
 #endif  // !defined(OS_ANDROID)
 
@@ -367,9 +364,7 @@
   controllers.push_back(
       std::make_unique<syncer::SyncableServiceBasedModelTypeController>(
           syncer::APP_LIST, GetModelTypeStoreService()->GetStoreFactory(),
-          base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
-                         base::Unretained(this), syncer::APP_LIST),
-          dump_stack));
+          GetSyncableServiceForType(syncer::APP_LIST), dump_stack));
 #endif  // BUILDFLAG(ENABLE_APP_LIST)
 
 #if defined(OS_LINUX) || defined(OS_WIN)
@@ -378,9 +373,7 @@
     controllers.push_back(
         std::make_unique<syncer::SyncableServiceBasedModelTypeController>(
             syncer::DICTIONARY, GetModelTypeStoreService()->GetStoreFactory(),
-            base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
-                           base::Unretained(this), syncer::DICTIONARY),
-            dump_stack));
+            GetSyncableServiceForType(syncer::DICTIONARY), dump_stack));
   }
 #endif  // defined(OS_LINUX) || defined(OS_WIN)
 
@@ -389,9 +382,8 @@
       !arc::IsArcAppSyncFlowDisabled()) {
     controllers.push_back(std::make_unique<ArcPackageSyncModelTypeController>(
         GetModelTypeStoreService()->GetStoreFactory(),
-        base::BindOnce(&ChromeSyncClient::GetSyncableServiceForType,
-                       base::Unretained(this), syncer::ARC_PACKAGE),
-        dump_stack, sync_service, profile_));
+        GetSyncableServiceForType(syncer::ARC_PACKAGE), dump_stack,
+        sync_service, profile_));
   }
 #endif  // defined(OS_CHROMEOS)
 
@@ -438,21 +430,21 @@
                    profile_web_data_service_.get())
             ->AsWeakPtr();
       }
-      return base::WeakPtr<syncer::SyncableService>();
-    case syncer::AUTOFILL_WALLET_METADATA: {
+      return nullptr;
+    case syncer::AUTOFILL_WALLET_METADATA:
       if (profile_web_data_service_) {
         return autofill::AutofillWalletMetadataSyncableService::
             FromWebDataService(profile_web_data_service_.get())
                 ->AsWeakPtr();
       }
-      return base::WeakPtr<syncer::SyncableService>();
-    }
+      return nullptr;
     case syncer::SEARCH_ENGINES:
-      return TemplateURLServiceFactory::GetForProfile(profile_)->AsWeakPtr();
+      return GetWeakPtrOrNull(
+          TemplateURLServiceFactory::GetForProfile(profile_));
 #if BUILDFLAG(ENABLE_EXTENSIONS)
     case syncer::APPS:
     case syncer::EXTENSIONS:
-      return ExtensionSyncService::Get(profile_)->AsWeakPtr();
+      return GetWeakPtrOrNull(ExtensionSyncService::Get(profile_));
     // TODO(crbug.com/933874): Remove these two from here once the old
     // controllers are deleted.
     case syncer::APP_SETTINGS:
@@ -462,8 +454,8 @@
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 #if BUILDFLAG(ENABLE_APP_LIST)
     case syncer::APP_LIST:
-      return app_list::AppListSyncableServiceFactory::GetForProfile(profile_)->
-          AsWeakPtr();
+      return GetWeakPtrOrNull(
+          app_list::AppListSyncableServiceFactory::GetForProfile(profile_));
 #endif  // BUILDFLAG(ENABLE_APP_LIST)
 #if !defined(OS_ANDROID)
     case syncer::THEMES:
@@ -472,36 +464,35 @@
 #endif  // !defined(OS_ANDROID)
     case syncer::HISTORY_DELETE_DIRECTIVES: {
       history::HistoryService* history = GetHistoryService();
-      return history ? history->GetDeleteDirectivesSyncableService()
-                     : base::WeakPtr<syncer::SyncableService>();
+      return history ? history->GetDeleteDirectivesSyncableService() : nullptr;
     }
 #if BUILDFLAG(ENABLE_SPELLCHECK)
-    case syncer::DICTIONARY:
-      return SpellcheckServiceFactory::GetForContext(profile_)->
-          GetCustomDictionary()->AsWeakPtr();
+    case syncer::DICTIONARY: {
+      SpellcheckService* spellcheck_service =
+          SpellcheckServiceFactory::GetForContext(profile_);
+      return spellcheck_service
+                 ? spellcheck_service->GetCustomDictionary()->AsWeakPtr()
+                 : nullptr;
+    }
 #endif  // BUILDFLAG(ENABLE_SPELLCHECK)
     case syncer::FAVICON_IMAGES:
-    case syncer::FAVICON_TRACKING: {
-      sync_sessions::FaviconCache* favicons =
-          SessionSyncServiceFactory::GetForProfile(profile_)->GetFaviconCache();
-      return favicons ? favicons->AsWeakPtr()
-                      : base::WeakPtr<syncer::SyncableService>();
-    }
+    case syncer::FAVICON_TRACKING:
+      return GetWeakPtrOrNull(SessionSyncServiceFactory::GetForProfile(profile_)
+                                  ->GetFaviconCache());
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
     case syncer::SUPERVISED_USER_SETTINGS:
       return SupervisedUserSettingsServiceFactory::GetForKey(
                  profile_->GetProfileKey())
           ->AsWeakPtr();
-    case syncer::SUPERVISED_USER_WHITELISTS: {
-      SupervisedUserService* supervised_user_service =
-          SupervisedUserServiceFactory::GetForProfile(profile_);
-      return supervised_user_service->GetWhitelistService()->AsWeakPtr();
-    }
+    case syncer::SUPERVISED_USER_WHITELISTS:
+      return SupervisedUserServiceFactory::GetForProfile(profile_)
+          ->GetWhitelistService()
+          ->AsWeakPtr();
 #endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
     case syncer::PASSWORDS: {
       return password_store_.get()
                  ? password_store_->GetPasswordSyncableService()
-                 : base::WeakPtr<syncer::SyncableService>();
+                 : nullptr;
     }
 #if defined(OS_CHROMEOS)
     case syncer::ARC_PACKAGE:
@@ -512,7 +503,7 @@
       // syncer::SyncableService API:
       // Bookmarks
       NOTREACHED();
-      return base::WeakPtr<syncer::SyncableService>();
+      return nullptr;
   }
 }
 
diff --git a/chrome/browser/sync/glue/extension_model_type_controller.cc b/chrome/browser/sync/glue/extension_model_type_controller.cc
index 7cd6f543..6dbc00a 100644
--- a/chrome/browser/sync/glue/extension_model_type_controller.cc
+++ b/chrome/browser/sync/glue/extension_model_type_controller.cc
@@ -14,14 +14,13 @@
 ExtensionModelTypeController::ExtensionModelTypeController(
     syncer::ModelType type,
     syncer::OnceModelTypeStoreFactory store_factory,
-    SyncableServiceProvider syncable_service_provider,
+    base::WeakPtr<syncer::SyncableService> syncable_service,
     const base::RepeatingClosure& dump_stack,
     Profile* profile)
-    : SyncableServiceBasedModelTypeController(
-          type,
-          std::move(store_factory),
-          std::move(syncable_service_provider),
-          dump_stack),
+    : SyncableServiceBasedModelTypeController(type,
+                                              std::move(store_factory),
+                                              syncable_service,
+                                              dump_stack),
       profile_(profile) {
   DCHECK(type == syncer::EXTENSIONS || type == syncer::APPS ||
          type == syncer::THEMES);
diff --git a/chrome/browser/sync/glue/extension_model_type_controller.h b/chrome/browser/sync/glue/extension_model_type_controller.h
index 83c4755..98c7fe4 100644
--- a/chrome/browser/sync/glue/extension_model_type_controller.h
+++ b/chrome/browser/sync/glue/extension_model_type_controller.h
@@ -21,7 +21,7 @@
   ExtensionModelTypeController(
       syncer::ModelType type,
       syncer::OnceModelTypeStoreFactory store_factory,
-      SyncableServiceProvider syncable_service_provider,
+      base::WeakPtr<syncer::SyncableService> syncable_service,
       const base::RepeatingClosure& dump_stack,
       Profile* profile);
   ~ExtensionModelTypeController() override;
diff --git a/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc b/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
index ebcb72b0..6538ed0 100644
--- a/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
@@ -72,11 +72,11 @@
   EXPECT_EQ(
       1U, histogram_tester
               .GetAllSamples(
-                  "Sync.NonReflectionUpdateFreshnessPossiblySkewed.PREFERENCE")
+                  "Sync.NonReflectionUpdateFreshnessPossiblySkewed2.PREFERENCE")
               .size());
   EXPECT_NE(
       0U, histogram_tester
-              .GetAllSamples("Sync.NonReflectionUpdateFreshnessPossiblySkewed")
+              .GetAllSamples("Sync.NonReflectionUpdateFreshnessPossiblySkewed2")
               .size());
 }
 
diff --git a/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.cc b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.cc
index 4685a3aa0..bf2ea04 100644
--- a/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.cc
+++ b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.cc
@@ -12,15 +12,14 @@
 
 ArcPackageSyncModelTypeController::ArcPackageSyncModelTypeController(
     syncer::OnceModelTypeStoreFactory store_factory,
-    SyncableServiceProvider syncable_service_provider,
+    base::WeakPtr<syncer::SyncableService> syncable_service,
     const base::RepeatingClosure& dump_stack,
     syncer::SyncService* sync_service,
     Profile* profile)
-    : syncer::SyncableServiceBasedModelTypeController(
-          syncer::ARC_PACKAGE,
-          std::move(store_factory),
-          std::move(syncable_service_provider),
-          dump_stack),
+    : syncer::SyncableServiceBasedModelTypeController(syncer::ARC_PACKAGE,
+                                                      std::move(store_factory),
+                                                      syncable_service,
+                                                      dump_stack),
       sync_service_(sync_service),
       profile_(profile) {
   arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
diff --git a/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h
index 1f47081..7b0f7f6 100644
--- a/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h
+++ b/chrome/browser/ui/app_list/arc/arc_package_sync_model_type_controller.h
@@ -24,7 +24,7 @@
   // |dump_stack| is called when an unrecoverable error occurs.
   ArcPackageSyncModelTypeController(
       syncer::OnceModelTypeStoreFactory store_factory,
-      SyncableServiceProvider syncable_service_provider,
+      base::WeakPtr<syncer::SyncableService> syncable_service,
       const base::RepeatingClosure& dump_stack,
       syncer::SyncService* sync_service,
       Profile* profile);
diff --git a/chrome/browser/ui/ash/drag_to_overview_interactive_uitest.cc b/chrome/browser/ui/ash/drag_to_overview_interactive_uitest.cc
index 13b4335..d5e992c9 100644
--- a/chrome/browser/ui/ash/drag_to_overview_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/drag_to_overview_interactive_uitest.cc
@@ -9,7 +9,6 @@
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -81,7 +80,7 @@
   }
   void SetUpOnMainThread() override {
     UIPerformanceTest::SetUpOnMainThread();
-    test::SetAndWaitForTabletMode(true);
+    ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
     if (base::SysInfo::IsRunningOnChromeOS()) {
       base::RunLoop run_loop;
diff --git a/chrome/browser/ui/ash/fake_tablet_mode_controller.cc b/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
index e98cf63..d11940c 100644
--- a/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
+++ b/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
@@ -6,24 +6,19 @@
 
 #include <utility>
 
-FakeTabletModeController::FakeTabletModeController() : binding_(this) {}
+FakeTabletModeController::FakeTabletModeController() = default;
 
 FakeTabletModeController::~FakeTabletModeController() = default;
 
-ash::mojom::TabletModeControllerPtr
-FakeTabletModeController::CreateInterfacePtr() {
-  ash::mojom::TabletModeControllerPtr ptr;
-  binding_.Bind(mojo::MakeRequest(&ptr));
-  return ptr;
+void FakeTabletModeController::SetTabletModeToggleObserver(
+    ash::TabletModeToggleObserver* observer) {
+  observer_ = observer;
 }
 
-void FakeTabletModeController::SetClient(
-    ash::mojom::TabletModeClientPtr client) {
-  was_client_set_ = true;
+bool FakeTabletModeController::IsEnabled() const {
+  return enabled_;
 }
 
-void FakeTabletModeController::SetTabletModeEnabledForTesting(
-    bool enabled,
-    SetTabletModeEnabledForTestingCallback callback) {
-  std::move(callback).Run(enabled);
+void FakeTabletModeController::SetEnabledForTest(bool enabled) {
+  enabled_ = enabled;
 }
diff --git a/chrome/browser/ui/ash/fake_tablet_mode_controller.h b/chrome/browser/ui/ash/fake_tablet_mode_controller.h
index 45e9c66..2418ba2 100644
--- a/chrome/browser/ui/ash/fake_tablet_mode_controller.h
+++ b/chrome/browser/ui/ash/fake_tablet_mode_controller.h
@@ -5,32 +5,27 @@
 #ifndef CHROME_BROWSER_UI_ASH_FAKE_TABLET_MODE_CONTROLLER_H_
 #define CHROME_BROWSER_UI_ASH_FAKE_TABLET_MODE_CONTROLLER_H_
 
-#include "ash/public/interfaces/tablet_mode.mojom.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/macros.h"
-#include "mojo/public/cpp/bindings/binding.h"
 
 // Simulates the TabletModeController in ash.
-class FakeTabletModeController : ash::mojom::TabletModeController {
+class FakeTabletModeController : public ash::TabletMode {
  public:
   FakeTabletModeController();
 
   ~FakeTabletModeController() override;
 
-  bool was_client_set() const { return was_client_set_; }
-
-  // Returns a mojo interface pointer bound to this object.
-  ash::mojom::TabletModeControllerPtr CreateInterfacePtr();
+  bool has_observer() const { return !!observer_; }
 
   // ash::mojom::TabletModeController:
-  void SetClient(ash::mojom::TabletModeClientPtr client) override;
-  void SetTabletModeEnabledForTesting(
-      bool enabled,
-      SetTabletModeEnabledForTestingCallback callback) override;
+  void SetTabletModeToggleObserver(
+      ash::TabletModeToggleObserver* observer) override;
+  bool IsEnabled() const override;
+  void SetEnabledForTest(bool enabled) override;
 
  private:
-  mojo::Binding<ash::mojom::TabletModeController> binding_;
-
-  bool was_client_set_ = false;
+  bool enabled_ = false;
+  ash::TabletModeToggleObserver* observer_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(FakeTabletModeController);
 };
diff --git a/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc b/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
index e18fb4c..9c1b3b1 100644
--- a/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
@@ -26,7 +26,6 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_test.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
-#include "chrome/browser/ui/ash/fake_tablet_mode_controller.h"
 #include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
 #include "chrome/browser/ui/ash/launcher/arc_launcher_context_menu.h"
 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
@@ -81,8 +80,6 @@
         std::make_unique<ChromeLauncherController>(&profile_, model_.get());
 
     tablet_mode_client_ = std::make_unique<TabletModeClient>();
-    tablet_mode_client_->InitForTesting(
-        fake_tablet_mode_controller_.CreateInterfacePtr());
 
     // Disable safe icon decoding to ensure ArcAppShortcutRequests returns in
     // the test environment.
@@ -141,6 +138,9 @@
   void TearDown() override {
     launcher_controller_.reset();
     ChromeAshTestBase::TearDown();
+    // To match ChromeBrowserMainExtraPartsAsh, shut down the TabletModeClient
+    // after Shell.
+    tablet_mode_client_.reset();
   }
 
   ArcAppTest& arc_test() { return arc_test_; }
@@ -158,7 +158,6 @@
   std::unique_ptr<ash::ShelfModel> model_;
   std::unique_ptr<ChromeLauncherController> launcher_controller_;
 
-  FakeTabletModeController fake_tablet_mode_controller_;
   std::unique_ptr<TabletModeClient> tablet_mode_client_;
 
   DISALLOW_COPY_AND_ASSIGN(LauncherContextMenuTest);
diff --git a/chrome/browser/ui/ash/overview_animations_interactive_uitest.cc b/chrome/browser/ui/ash/overview_animations_interactive_uitest.cc
index 404bc617..1390f43 100644
--- a/chrome/browser/ui/ash/overview_animations_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/overview_animations_interactive_uitest.cc
@@ -6,7 +6,6 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -35,7 +34,7 @@
     tablet_mode_ = std::get<2>(GetParam());
 
     if (tablet_mode_)
-      test::SetAndWaitForTabletMode(true);
+      ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
     GURL ntp_url("chrome://newtab");
     // The default is blank page.
diff --git a/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc b/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc
index 5246687..faa5b37 100644
--- a/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc
@@ -9,7 +9,6 @@
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -77,7 +76,7 @@
   // UIPerformanceTest:
   void SetUpOnMainThread() override {
     UIPerformanceTest::SetUpOnMainThread();
-    test::SetAndWaitForTabletMode(true);
+    ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
     int additional_browsers = std::get<0>(GetParam()) - 1;
     bool blank_page = std::get<1>(GetParam());
diff --git a/chrome/browser/ui/ash/screen_rotation_interactive_uitest.cc b/chrome/browser/ui/ash/screen_rotation_interactive_uitest.cc
index 5ece549..42520d1 100644
--- a/chrome/browser/ui/ash/screen_rotation_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/screen_rotation_interactive_uitest.cc
@@ -10,7 +10,6 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -35,7 +34,7 @@
   // UIPerformanceTest:
   void SetUpOnMainThread() override {
     UIPerformanceTest::SetUpOnMainThread();
-    test::SetAndWaitForTabletMode(true);
+    ash::ShellTestApi().EnableTabletModeWindowManager(true);
     auto* pref = browser()->profile()->GetPrefs();
     pref->SetBoolean(
         ash::prefs::kDisplayRotationAcceleratorDialogHasBeenAccepted, true);
diff --git a/chrome/browser/ui/ash/split_view_interactive_uitest.cc b/chrome/browser/ui/ash/split_view_interactive_uitest.cc
index d01c2e9..f52e9c40 100644
--- a/chrome/browser/ui/ash/split_view_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/split_view_interactive_uitest.cc
@@ -11,7 +11,6 @@
 #include "base/system/sys_info.h"
 #include "base/task/post_task.h"
 #include "base/test/bind_test_util.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -118,7 +117,7 @@
     run_loop.Run();
   }
 
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
   views::Widget* browser_widget =
       BrowserView::GetBrowserViewForBrowser(browser())->GetWidget();
diff --git a/chrome/browser/ui/ash/tablet_mode_client.cc b/chrome/browser/ui/ash/tablet_mode_client.cc
index 7fb8fd4..8e2dcea 100644
--- a/chrome/browser/ui/ash/tablet_mode_client.cc
+++ b/chrome/browser/ui/ash/tablet_mode_client.cc
@@ -7,7 +7,6 @@
 #include <utility>
 
 #include "ash/public/cpp/tablet_mode.h"
-#include "ash/public/interfaces/constants.mojom.h"
 #include "base/bind.h"
 #include "chrome/browser/chromeos/arc/arc_web_contents_data.h"
 #include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
@@ -21,7 +20,6 @@
 #include "content/public/common/service_manager_connection.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/material_design/material_design_controller.h"
-#include "ui/base/ui_base_features.h"
 
 namespace {
 
@@ -29,33 +27,21 @@
 
 }  // namespace
 
-TabletModeClient::TabletModeClient() : binding_(this) {
+TabletModeClient::TabletModeClient() {
   DCHECK(!g_tablet_mode_client_instance);
   g_tablet_mode_client_instance = this;
-  if (features::IsMultiProcessMash()) {
-    ash::TabletMode::SetCallback(base::BindRepeating(
-        &TabletModeClient::tablet_mode_enabled, base::Unretained(this)));
-  }
 }
 
 TabletModeClient::~TabletModeClient() {
   DCHECK_EQ(this, g_tablet_mode_client_instance);
   g_tablet_mode_client_instance = nullptr;
-  if (features::IsMultiProcessMash())
-    ash::TabletMode::SetCallback({});
+  // The Ash Shell and TabletMode instance should have been destroyed by now.
+  DCHECK(!ash::TabletMode::Get());
 }
 
 void TabletModeClient::Init() {
-  content::ServiceManagerConnection::GetForProcess()
-      ->GetConnector()
-      ->BindInterface(ash::mojom::kServiceName, &tablet_mode_controller_);
-  BindAndSetClient();
-}
-
-void TabletModeClient::InitForTesting(
-    ash::mojom::TabletModeControllerPtr controller) {
-  tablet_mode_controller_ = std::move(controller);
-  BindAndSetClient();
+  ash::TabletMode::Get()->SetTabletModeToggleObserver(this);
+  OnTabletModeToggled(ash::TabletMode::Get()->IsEnabled());
 }
 
 // static
@@ -63,14 +49,6 @@
   return g_tablet_mode_client_instance;
 }
 
-void TabletModeClient::SetTabletModeEnabledForTesting(
-    bool enabled,
-    ash::mojom::TabletModeController::SetTabletModeEnabledForTestingCallback
-        callback) {
-  tablet_mode_controller_->SetTabletModeEnabledForTesting(enabled,
-                                                          std::move(callback));
-}
-
 void TabletModeClient::AddObserver(TabletModeClientObserver* observer) {
   observers_.AddObserver(observer);
 }
@@ -112,16 +90,6 @@
     contents.contents->NotifyPreferencesChanged();
 }
 
-void TabletModeClient::FlushForTesting() {
-  tablet_mode_controller_.FlushForTesting();
-}
-
-void TabletModeClient::BindAndSetClient() {
-  ash::mojom::TabletModeClientPtr client;
-  binding_.Bind(mojo::MakeRequest(&client));
-  tablet_mode_controller_->SetClient(std::move(client));
-}
-
 void TabletModeClient::SetMobileLikeBehaviorEnabled(bool enabled) {
   // Toggling tablet mode on/off should trigger refreshing the WebKit
   // preferences, since in tablet mode, we enable certain mobile-like features
diff --git a/chrome/browser/ui/ash/tablet_mode_client.h b/chrome/browser/ui/ash/tablet_mode_client.h
index 60ec108..309f364a 100644
--- a/chrome/browser/ui/ash/tablet_mode_client.h
+++ b/chrome/browser/ui/ash/tablet_mode_client.h
@@ -7,12 +7,11 @@
 
 #include <memory>
 
-#include "ash/public/interfaces/tablet_mode.mojom.h"
+#include "ash/public/cpp/tablet_mode_toggle_observer.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "chrome/browser/ui/browser_tab_strip_tracker_delegate.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
-#include "mojo/public/cpp/bindings/binding.h"
 
 class BrowserTabStripTracker;
 class TabletModeClientObserver;
@@ -20,7 +19,7 @@
 // Holds tablet mode state in chrome. Observes ash for changes, then
 // synchronously fires all its observers. This allows all tablet mode code in
 // chrome to see a state change at the same time.
-class TabletModeClient : public ash::mojom::TabletModeClient,
+class TabletModeClient : public ash::TabletModeToggleObserver,
                          public BrowserTabStripTrackerDelegate,
                          public TabStripModelObserver {
  public:
@@ -30,28 +29,16 @@
   // Initializes and connects to ash.
   void Init();
 
-  // Tests can provide a mock mojo interface for the ash controller.
-  void InitForTesting(ash::mojom::TabletModeControllerPtr controller);
-
   static TabletModeClient* Get();
 
   bool tablet_mode_enabled() const { return tablet_mode_enabled_; }
 
-  // Enables or disabled tablet mode. For testing only since it disables the
-  // Accelerometer and PowerManager observers from the TabletModeController.
-  // Thus preventing to physically switch to/from tablet mode. |callback| is
-  // called when the operation has completed.
-  void SetTabletModeEnabledForTesting(
-      bool enabled,
-      ash::mojom::TabletModeController::SetTabletModeEnabledForTestingCallback
-          callback);
-
   // Adds the observer and immediately triggers it with the initial state.
   void AddObserver(TabletModeClientObserver* observer);
 
   void RemoveObserver(TabletModeClientObserver* observer);
 
-  // ash::mojom::TabletModeClient:
+  // ash::TabletModeToggleObserver:
   void OnTabletModeToggled(bool enabled) override;
 
   // BrowserTabStripTrackerDelegate:
@@ -63,13 +50,7 @@
       const TabStripModelChange& change,
       const TabStripSelectionChange& selection) override;
 
-  // Flushes the mojo pipe to ash.
-  void FlushForTesting();
-
  private:
-  // Binds this object to its mojo interface and sets it as the ash client.
-  void BindAndSetClient();
-
   // Enables/disables mobile-like behavior for webpages in existing browsers, as
   // well as starts observing new browser pages if |enabled| is true.
   void SetMobileLikeBehaviorEnabled(bool enabled);
@@ -85,12 +66,6 @@
   // a refresh of its WebKit prefs.
   std::unique_ptr<BrowserTabStripTracker> tab_strip_tracker_;
 
-  // Binds to the client interface in ash.
-  mojo::Binding<ash::mojom::TabletModeClient> binding_;
-
-  // Keeps the interface pipe alive to receive mojo return values.
-  ash::mojom::TabletModeControllerPtr tablet_mode_controller_;
-
   base::ObserverList<TabletModeClientObserver,
                      true /* check_empty */>::Unchecked observers_;
 
diff --git a/chrome/browser/ui/ash/tablet_mode_client_test_util.cc b/chrome/browser/ui/ash/tablet_mode_client_test_util.cc
deleted file mode 100644
index f6dca38..0000000
--- a/chrome/browser/ui/ash/tablet_mode_client_test_util.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/public/cpp/test/shell_test_api.h"
-#include "base/run_loop.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
-#include "content/public/common/service_manager_connection.h"
-#include "services/service_manager/public/cpp/connector.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace test {
-
-namespace {
-
-// A helper used to wait for an expected change to the tablet mode state.
-class TestTabletModeClientObserver : public TabletModeClientObserver {
- public:
-  explicit TestTabletModeClientObserver(bool target_state)
-      : target_state_(target_state) {
-    TabletModeClient::Get()->AddObserver(this);
-  }
-
-  ~TestTabletModeClientObserver() override {
-    TabletModeClient::Get()->RemoveObserver(this);
-  }
-
-  void OnTabletModeToggled(bool enabled) override {
-    if (enabled == target_state_)
-      run_loop_.Quit();
-  }
-
-  base::RunLoop* run_loop() { return &run_loop_; }
-
- private:
-  const bool target_state_;
-  base::RunLoop run_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestTabletModeClientObserver);
-};
-
-}  // namespace
-
-// Enables or disables the tablet mode and waits until the change has made its
-// way back into Chrome (from Ash). Should only be called to toggle the current
-// mode.
-void SetAndWaitForTabletMode(bool enabled) {
-  ASSERT_NE(enabled, TabletModeClient::Get()->tablet_mode_enabled());
-
-  ash::ShellTestApi().EnableTabletModeWindowManager(enabled);
-
-  TestTabletModeClientObserver observer(enabled);
-  observer.run_loop()->Run();
-
-  ASSERT_EQ(enabled, TabletModeClient::Get()->tablet_mode_enabled());
-}
-
-}  // namespace test
diff --git a/chrome/browser/ui/ash/tablet_mode_client_test_util.h b/chrome/browser/ui/ash/tablet_mode_client_test_util.h
deleted file mode 100644
index ea25d0c..0000000
--- a/chrome/browser/ui/ash/tablet_mode_client_test_util.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_TEST_UTIL_H_
-#define CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_TEST_UTIL_H_
-
-namespace test {
-
-// Enables or disables the tablet mode and waits to until the change has made
-// its way back into Chrome (from Ash). Should only be called to toggle the
-// current mode.
-void SetAndWaitForTabletMode(bool enabled);
-
-}  // namespace test
-
-#endif  // CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_TEST_UTIL_H_
diff --git a/chrome/browser/ui/ash/tablet_mode_client_unittest.cc b/chrome/browser/ui/ash/tablet_mode_client_unittest.cc
index f698f49..ebf0bb6 100644
--- a/chrome/browser/ui/ash/tablet_mode_client_unittest.cc
+++ b/chrome/browser/ui/ash/tablet_mode_client_unittest.cc
@@ -4,9 +4,7 @@
 
 #include "chrome/browser/ui/ash/tablet_mode_client.h"
 
-#include "ash/public/interfaces/tablet_mode.mojom.h"
 #include "base/macros.h"
-#include "base/test/scoped_task_environment.h"
 #include "chrome/browser/ui/ash/fake_tablet_mode_controller.h"
 #include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -30,33 +28,29 @@
   DISALLOW_COPY_AND_ASSIGN(TestTabletModeClientObserver);
 };
 
-class TabletModeClientTest : public testing::Test {
- public:
-  TabletModeClientTest() = default;
-  ~TabletModeClientTest() override = default;
-
- private:
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
-
-  DISALLOW_COPY_AND_ASSIGN(TabletModeClientTest);
-};
+using TabletModeClientTest = testing::Test;
 
 TEST_F(TabletModeClientTest, Construction) {
+  // In production, TabletModeController is constructed before TabletModeClient
+  // and destroyed before it too. Match that here.
+  auto controller = std::make_unique<FakeTabletModeController>();
   TabletModeClient client;
-  FakeTabletModeController controller;
-  client.InitForTesting(controller.CreateInterfacePtr());
-  client.FlushForTesting();
+  client.Init();
 
   // Singleton was initialized.
   EXPECT_EQ(&client, TabletModeClient::Get());
 
   // Object was set as client.
-  EXPECT_TRUE(controller.was_client_set());
+  EXPECT_TRUE(controller->has_observer());
+
+  controller = nullptr;
 }
 
 TEST_F(TabletModeClientTest, Observers) {
-  TabletModeClient client;
+  auto controller = std::make_unique<FakeTabletModeController>();
   TestTabletModeClientObserver observer;
+  TabletModeClient client;
+  client.Init();
   client.AddObserver(&observer);
 
   // Observer is not notified with state when added.
@@ -72,6 +66,8 @@
   EXPECT_FALSE(observer.last_toggle_);
 
   client.RemoveObserver(&observer);
+
+  controller = nullptr;
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/ash/tablet_mode_transition_interactive_uitest.cc b/chrome/browser/ui/ash/tablet_mode_transition_interactive_uitest.cc
index 72e2e49d..729792f1d 100644
--- a/chrome/browser/ui/ash/tablet_mode_transition_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/tablet_mode_transition_interactive_uitest.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "base/command_line.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -96,7 +96,7 @@
     base::RunLoop run_loop;
     ui::LayerAnimator* animator = browser_window->layer()->GetAnimator();
     TestLayerAnimationObserver waiter(animator, run_loop.QuitClosure());
-    test::SetAndWaitForTabletMode(true);
+    ash::ShellTestApi().EnableTabletModeWindowManager(true);
     run_loop.Run();
   }
 
@@ -104,7 +104,7 @@
     base::RunLoop run_loop;
     ui::LayerAnimator* animator = browser_window->layer()->GetAnimator();
     TestLayerAnimationObserver waiter(animator, run_loop.QuitClosure());
-    test::SetAndWaitForTabletMode(false);
+    ash::ShellTestApi().EnableTabletModeWindowManager(false);
     run_loop.Run();
   }
 }
diff --git a/chrome/browser/ui/ash/test_login_screen.cc b/chrome/browser/ui/ash/test_login_screen.cc
index 54b49b91..366dab6 100644
--- a/chrome/browser/ui/ash/test_login_screen.cc
+++ b/chrome/browser/ui/ash/test_login_screen.cc
@@ -41,13 +41,6 @@
   std::move(callback).Run(true);
 }
 
-void TestLoginScreen::ShowErrorMessage(int32_t login_attempts,
-                                       const std::string& error_text,
-                                       const std::string& help_link_text,
-                                       int32_t help_topic_id) {}
-
-void TestLoginScreen::ClearErrors() {}
-
 void TestLoginScreen::IsReadyForPassword(IsReadyForPasswordCallback callback) {
   std::move(callback).Run(true);
 }
@@ -60,22 +53,20 @@
 
 void TestLoginScreen::SetAllowLoginAsGuest(bool allow_guest) {}
 
-void TestLoginScreen::SetShowGuestButtonInOobe(bool show) {}
-
-void TestLoginScreen::SetShowParentAccessButton(bool show) {}
-
-void TestLoginScreen::SetShowParentAccessDialog(bool show) {}
-
 void TestLoginScreen::FocusLoginShelf(bool reverse) {}
 
-void TestLoginScreen::ShowParentAccessWidget(
-    const AccountId& child_account_id,
-    base::RepeatingCallback<void(bool success)> callback) {}
-
 ash::LoginScreenModel* TestLoginScreen::GetModel() {
   return &test_screen_model_;
 }
 
+void TestLoginScreen::ShowGuestButtonInOobe(bool show) {}
+
+void TestLoginScreen::ShowParentAccessButton(bool show) {}
+
+void TestLoginScreen::ShowParentAccessWidget(
+    const AccountId& child_account_id,
+    base::RepeatingCallback<void(bool success)> callback) {}
+
 void TestLoginScreen::Bind(mojo::ScopedMessagePipeHandle handle) {
   binding_.Bind(ash::mojom::LoginScreenRequest(std::move(handle)));
 }
diff --git a/chrome/browser/ui/ash/test_login_screen.h b/chrome/browser/ui/ash/test_login_screen.h
index be8a34c..4449ae7 100644
--- a/chrome/browser/ui/ash/test_login_screen.h
+++ b/chrome/browser/ui/ash/test_login_screen.h
@@ -31,26 +31,20 @@
   void SetClient(ash::mojom::LoginScreenClientPtr client) override;
   void ShowLockScreen(ShowLockScreenCallback callback) override;
   void ShowLoginScreen(ShowLoginScreenCallback callback) override;
-  void ShowErrorMessage(int32_t login_attempts,
-                        const std::string& error_text,
-                        const std::string& help_link_text,
-                        int32_t help_topic_id) override;
-  void ClearErrors() override;
   void IsReadyForPassword(IsReadyForPasswordCallback callback) override;
   void ShowKioskAppError(const std::string& message) override;
   void SetAddUserButtonEnabled(bool enable) override;
   void SetShutdownButtonEnabled(bool enable) override;
   void SetAllowLoginAsGuest(bool allow_guest) override;
-  void SetShowGuestButtonInOobe(bool show) override;
-  void SetShowParentAccessButton(bool show) override;
-  void SetShowParentAccessDialog(bool show) override;
   void FocusLoginShelf(bool reverse) override;
-  void ShowParentAccessWidget(
-      const AccountId& child_account_id,
-      base::RepeatingCallback<void(bool success)> callback) override;
 
   // ash::LoginScreen:
   ash::LoginScreenModel* GetModel() override;
+  void ShowGuestButtonInOobe(bool show) override;
+  void ShowParentAccessButton(bool show) override;
+  void ShowParentAccessWidget(
+      const AccountId& child_account_id,
+      base::RepeatingCallback<void(bool success)> callback) override;
 
  private:
   void Bind(mojo::ScopedMessagePipeHandle handle);
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index 3a57b04..66dc103 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -713,7 +713,7 @@
       break;
 #endif
     case IDC_DISTILL_PAGE:
-      DistillCurrentPage(browser_);
+      ToggleDistilledView(browser_);
       break;
     case IDC_ROUTE_MEDIA:
       RouteMedia(browser_);
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 85f4b4dc..aa64b3d 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -66,6 +66,7 @@
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/dom_distiller/core/url_utils.h"
 #include "components/favicon/content/content_favicon_driver.h"
 #include "components/feature_engagement/buildflags.h"
 #include "components/google/core/common/google_util.h"
@@ -1215,8 +1216,15 @@
   }
 }
 
-void DistillCurrentPage(Browser* browser) {
-  DistillCurrentPageAndView(browser->tab_strip_model()->GetActiveWebContents());
+void ToggleDistilledView(Browser* browser) {
+  auto* current_web_contents =
+      browser->tab_strip_model()->GetActiveWebContents();
+  if (dom_distiller::url_utils::IsDistilledPage(
+          current_web_contents->GetLastCommittedURL())) {
+    ReturnToOriginalPage(current_web_contents);
+  } else {
+    DistillCurrentPageAndView(current_web_contents);
+  }
 }
 
 bool CanRequestTabletSite(WebContents* current_tab) {
diff --git a/chrome/browser/ui/browser_commands.h b/chrome/browser/ui/browser_commands.h
index 6fb386e..c9a80095 100644
--- a/chrome/browser/ui/browser_commands.h
+++ b/chrome/browser/ui/browser_commands.h
@@ -155,7 +155,7 @@
 void ShowAppMenu(Browser* browser);
 void ShowAvatarMenu(Browser* browser);
 void OpenUpdateChromeDialog(Browser* browser);
-void DistillCurrentPage(Browser* browser);
+void ToggleDistilledView(Browser* browser);
 bool CanRequestTabletSite(content::WebContents* current_tab);
 bool IsRequestingTabletSite(Browser* browser);
 void ToggleRequestTabletSite(Browser* browser);
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc
index c92b6b5..71fd742 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h"
 
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "ash/public/cpp/window_properties.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
@@ -13,7 +14,6 @@
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/apps/platform_apps/app_window_interactive_uitest_base.h"
 #include "chrome/browser/ui/ash/tablet_mode_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chromeos/login/login_state/login_state.h"
 #include "chromeos/login/login_state/scoped_test_public_session_login_state.h"
@@ -101,11 +101,11 @@
   // Verify that since the auto hide title bars in tablet mode feature turned
   // on, immersive mode is enabled once tablet mode is entered, and disabled
   // once tablet mode is exited.
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
   EXPECT_TRUE(IsImmersiveActive());
   ViewBoundsChangeWaiter::VerifyY(client_view, 0);
 
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   EXPECT_FALSE(IsImmersiveActive());
   ViewBoundsChangeWaiter::VerifyY(client_view, kFrameHeight);
 
@@ -113,22 +113,22 @@
   // will remain fullscreened after exiting tablet mode.
   app_window_->OSFullscreen();
   EXPECT_TRUE(IsImmersiveActive());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
   EXPECT_TRUE(IsImmersiveActive());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   EXPECT_TRUE(IsImmersiveActive());
   app_window_->Restore();
 
   // Verify that minimized windows do not have immersive mode enabled.
   app_window_->Minimize();
   EXPECT_FALSE(IsImmersiveActive());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
   EXPECT_FALSE(IsImmersiveActive());
   window()->Restore();
   EXPECT_TRUE(IsImmersiveActive());
   app_window_->Minimize();
   EXPECT_FALSE(IsImmersiveActive());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   EXPECT_FALSE(IsImmersiveActive());
 
   // Verify that activation change should not change the immersive
@@ -153,10 +153,10 @@
 
   app_window_->OSFullscreen();
   EXPECT_EQ(ui::SHOW_STATE_MAXIMIZED, window()->GetRestoredState());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
   EXPECT_TRUE(window()->IsFullscreen());
   EXPECT_EQ(ui::SHOW_STATE_MAXIMIZED, window()->GetRestoredState());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   EXPECT_EQ(ui::SHOW_STATE_MAXIMIZED, window()->GetRestoredState());
 
   CloseAppWindow(app_window_);
@@ -171,9 +171,9 @@
 
   app_window_->ForcedFullscreen();
 
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
   EXPECT_FALSE(IsImmersiveActive());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   EXPECT_FALSE(IsImmersiveActive());
 }
 
@@ -207,7 +207,7 @@
   EXPECT_EQ(ui::SHOW_STATE_MAXIMIZED, window()->GetRestoredState());
   EXPECT_TRUE(window()->IsFullscreen());
   EXPECT_TRUE(IsImmersiveActive());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
   EXPECT_TRUE(window()->IsFullscreen());
   EXPECT_EQ(ui::SHOW_STATE_MAXIMIZED, window()->GetRestoredState());
 
@@ -219,7 +219,7 @@
 
   // Immersive fullscreen should be disabled if window exits fullscreen in
   // clamshell mode.
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   app_window_->OSFullscreen();
   EXPECT_EQ(ui::SHOW_STATE_MAXIMIZED, window()->GetRestoredState());
   EXPECT_TRUE(window()->IsFullscreen());
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index 9dae288a..3f4bf8c 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/frame_header.h"
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller_test_api.h"
 #include "ash/public/cpp/shelf_test_api.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "ash/public/cpp/vector_icons/vector_icons.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/interfaces/constants.mojom.h"
@@ -36,7 +37,6 @@
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
 #include "chrome/browser/ui/ash/multi_user/test_multi_user_window_manager.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -1400,7 +1400,8 @@
   BrowserNonClientFrameViewAsh* frame_view = GetFrameViewAsh(browser_view);
 
   EXPECT_TRUE(frame_view->caption_button_container_->GetVisible());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(true));
   EXPECT_FALSE(frame_view->caption_button_container_->GetVisible());
 
   ToggleOverview();
@@ -1408,7 +1409,8 @@
   ToggleOverview();
   EXPECT_FALSE(frame_view->caption_button_container_->GetVisible());
 
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(false));
   EXPECT_TRUE(frame_view->caption_button_container_->GetVisible());
 }
 
@@ -1430,7 +1432,8 @@
   EXPECT_TRUE(frame_view->caption_button_container_->GetVisible());
 
   // Tablet mode doesn't affect app's caption button's visibility.
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(true));
   EXPECT_TRUE(frame_view->caption_button_container_->GetVisible());
 
   // However, overview mode does.
@@ -1439,7 +1442,8 @@
   ToggleOverview();
   EXPECT_TRUE(frame_view->caption_button_container_->GetVisible());
 
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(false));
   EXPECT_TRUE(frame_view->caption_button_container_->GetVisible());
 }
 
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc b/chrome/browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc
index c4c1e9c..42c984e3 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc
@@ -4,13 +4,13 @@
 
 #include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller_test_api.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "base/macros.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile_io_data.h"
 #include "chrome/browser/ssl/chrome_mock_cert_verifier.h"
 #include "chrome/browser/ui/ash/tablet_mode_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller_test.h"
@@ -219,7 +219,8 @@
   // Verify that after entering tablet mode, immersive mode is enabled, and the
   // the associated window's top inset is 0 (the top of the window is not
   // visible).
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(true));
   EXPECT_TRUE(controller()->IsEnabled());
   EXPECT_EQ(0, aura_window->GetProperty(aura::client::kTopViewInset));
 
@@ -236,12 +237,14 @@
   // mode.
   ToggleFullscreen();
   EXPECT_TRUE(controller()->IsEnabled());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(false));
   EXPECT_TRUE(controller()->IsEnabled());
 
   // Verify that immersive mode remains if the browser was fullscreened when
   // entering tablet mode.
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(true));
   EXPECT_TRUE(controller()->IsEnabled());
 
   // Verify that if the browser is not fullscreened, upon exiting tablet mode,
@@ -249,7 +252,8 @@
   // greater than 0 (the top of the window is visible).
   ToggleFullscreen();
   EXPECT_TRUE(controller()->IsEnabled());
-  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().EnableTabletModeWindowManager(false));
   EXPECT_FALSE(controller()->IsEnabled());
 
   EXPECT_GT(aura_window->GetProperty(aura::client::kTopViewInset), 0);
@@ -273,7 +277,7 @@
   EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
 
   // Verify the size button is hidden in tablet mode.
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
   frame_test_api.EndAnimations();
 
   EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
@@ -282,7 +286,7 @@
 
   // Verify the size button is visible in clamshell mode, and that it does not
   // cover the other two buttons.
-  test::SetAndWaitForTabletMode(false);
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   frame_test_api.EndAnimations();
 
   EXPECT_TRUE(frame_test_api.size_button()->GetVisible());
@@ -299,7 +303,7 @@
 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerAshHostedAppBrowserTest,
                        FrameLayoutStartInTabletMode) {
   // Start in tablet mode
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
   BrowserNonClientFrameViewAsh* frame_view = nullptr;
   {
@@ -320,6 +324,6 @@
 
   // Verify the size button is visible in clamshell mode, and that it does not
   // cover the other two buttons.
-  test::SetAndWaitForTabletMode(false);
+  ash::ShellTestApi().EnableTabletModeWindowManager(false);
   VerifyButtonsInImmersiveMode(frame_view);
 }
diff --git a/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc b/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc
index 21966cd..c73ba58 100644
--- a/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc
+++ b/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
@@ -372,7 +371,7 @@
   ASSERT_NE(nullptr, owning_window);
 
   // Setup tablet mode.
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
   // Open the file dialog on the default path.
   ASSERT_NO_FATAL_FAILURE(OpenDialog(ui::SelectFileDialog::SELECT_OPEN_FILE,
@@ -459,7 +458,7 @@
   ASSERT_NE(nullptr, owning_window);
 
   // Setup tablet mode.
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
   // Enable the virtual keyboard.
   ash::ShellTestApi().EnableVirtualKeyboard();
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index b6a6904c..3ad4e5f 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -75,7 +75,6 @@
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/window_state.h"
 #include "base/test/simple_test_tick_clock.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h"
 #include "content/public/common/service_manager_connection.h"
@@ -1747,7 +1746,7 @@
   // bounds are different from the maximized bound's origin.
   browser()->window()->SetBounds(browser()->window()->GetBounds() +
                                  gfx::Vector2d(100, 50));
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
   DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 1);
   ASSERT_FALSE(TabDragController::IsActive());
@@ -1755,7 +1754,7 @@
 
 IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
                        OffsetForDraggingRightSnappedWindowInTabletMode) {
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
   // Right snap the browser window.
   aura::Window* window = browser()->window()->GetNativeWindow();
@@ -2196,7 +2195,7 @@
   TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
   EXPECT_EQ(2u, browser_list->size());
 
-  test::SetAndWaitForTabletMode(true);
+  ash::ShellTestApi().EnableTabletModeWindowManager(true);
 
   // Move to the first tab of |browser2| and drag it toward to browser(). Note
   // dragging on |browser2| which only has one tab in tablet mode will open
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 59a9d6f..5e523cde 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/login_screen.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/callback.h"
@@ -935,7 +936,7 @@
 }
 
 void GaiaScreenHandler::HandleShowGuestInOobe(bool show) {
-  LoginScreenClient::Get()->login_screen()->SetShowGuestButtonInOobe(show);
+  ash::LoginScreen::Get()->ShowGuestButtonInOobe(show);
 }
 
 void GaiaScreenHandler::OnShowAddUser() {
diff --git a/chrome/browser/ui/webui/chromeos/login/supervision_transition_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/supervision_transition_screen_handler.cc
index c1e7f2e..7b154e0 100644
--- a/chrome/browser/ui/webui/chromeos/login/supervision_transition_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/supervision_transition_screen_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/chromeos/login/supervision_transition_screen_handler.h"
 
+#include "ash/public/cpp/login_screen.h"
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
@@ -101,7 +102,7 @@
   SystemTrayClient::Get()->SetPrimaryTrayEnabled(false);
   LoginScreenClient::Get()->login_screen()->SetShutdownButtonEnabled(false);
   LoginScreenClient::Get()->login_screen()->SetAllowLoginAsGuest(false);
-  LoginScreenClient::Get()->login_screen()->SetShowGuestButtonInOobe(false);
+  ash::LoginScreen::Get()->ShowGuestButtonInOobe(false);
 
   base::DictionaryValue data;
   data.SetBoolean("isRemovingSupervision",
diff --git a/chrome/browser/ui/webui/favicon_source.cc b/chrome/browser/ui/webui/favicon_source.cc
index 1015ae96..c93c9e6 100644
--- a/chrome/browser/ui/webui/favicon_source.cc
+++ b/chrome/browser/ui/webui/favicon_source.cc
@@ -171,7 +171,8 @@
             &FaviconSource::OnFaviconDataAvailable, base::Unretained(this),
             IconRequest(callback, url, parsed.size_in_dip,
                         parsed.device_scale_factor, unsafe_request_origin)),
-        unsafe_request_origin, favicon_service,
+        unsafe_request_origin, favicon::FaviconRequestPlatform::kDesktop,
+        favicon_service,
         LargeIconServiceFactory::GetForBrowserContext(profile_),
         /*icon_url_for_uma=*/
         open_tabs ? open_tabs->GetIconUrlForPageUrl(url) : GURL(),
diff --git a/chrome/renderer/prerender/OWNERS b/chrome/renderer/prerender/OWNERS
index e0db6795..b10effc74 100644
--- a/chrome/renderer/prerender/OWNERS
+++ b/chrome/renderer/prerender/OWNERS
@@ -1,4 +1,5 @@
 mmenke@chromium.org
 pasko@chromium.org
+tbansal@chromium.org
 
 # COMPONENT: Internals>Preload
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 3812d68..72f8e0d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2750,7 +2750,6 @@
     "../browser/internal_auth_unittest.cc",
     "../browser/language/language_model_manager_factory_unittest.cc",
     "../browser/language/url_language_histogram_factory_unittest.cc",
-    "../browser/loader/chrome_navigation_data_unittest.cc",
     "../browser/logging_chrome_unittest.cc",
     "../browser/mac/exception_processor_unittest.mm",
     "../browser/mac/keystone_glue_unittest.mm",
@@ -2869,6 +2868,7 @@
     "../browser/performance_manager/performance_manager_unittest.cc",
     "../browser/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc",
     "../browser/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc",
+    "../browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc",
     "../browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc",
     "../browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc",
     "../browser/performance_manager/persistence/site_data/site_data_writer_unittest.cc",
@@ -3391,6 +3391,7 @@
       "../browser/diagnostics/diagnostics_model_unittest.cc",
       "../browser/download/download_commands_unittest.cc",
       "../browser/download/download_shelf_unittest.cc",
+      "../browser/enterprise_reporting/report_scheduler_unittest.cc",
       "../browser/enterprise_reporting/report_uploader_unittest.cc",
       "../browser/enterprise_reporting/request_timer_unittest.cc",
       "../browser/first_run/first_run_unittest.cc",
diff --git a/chrome/test/chromedriver/chrome/chrome.h b/chrome/test/chromedriver/chrome/chrome.h
index d4d82c2..14d2daa 100644
--- a/chrome/test/chromedriver/chrome/chrome.h
+++ b/chrome/test/chromedriver/chrome/chrome.h
@@ -16,6 +16,11 @@
 
 class Chrome {
  public:
+  enum class WindowType {
+    kWindow,
+    kTab,
+  };
+
   virtual ~Chrome() {}
 
   virtual Status GetAsDesktop(ChromeDesktopImpl** desktop) = 0;
@@ -37,6 +42,11 @@
   // Return the WebView for the given id.
   virtual Status GetWebViewById(const std::string& id, WebView** web_view) = 0;
 
+  // Makes new window or tab.
+  virtual Status NewWindow(const std::string& target_id,
+                           WindowType type,
+                           std::string* window_handle) = 0;
+
   // Gets the size of the specified WebView.
   virtual Status GetWindowSize(const std::string& id,
                                int* width,
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 9fa588f..71d46c42 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -117,6 +117,34 @@
   return Status(kUnknownError, "web view not found");
 }
 
+Status ChromeImpl::NewWindow(const std::string& target_id,
+                             WindowType type,
+                             std::string* window_handle) {
+  Status status = devtools_websocket_client_->ConnectIfNecessary();
+  if (status.IsError())
+    return status;
+
+  Window window;
+  status = GetWindow(target_id, &window);
+  if (status.IsError())
+    return Status(kNoSuchWindow);
+
+  base::DictionaryValue params;
+  params.SetString("url", "about:blank");
+  params.SetBoolean("newWindow", type == WindowType::kWindow);
+  params.SetBoolean("background", true);
+  std::unique_ptr<base::DictionaryValue> result;
+  status = devtools_websocket_client_->SendCommandAndGetResult(
+      "Target.createTarget", params, &result);
+  if (status.IsError())
+    return status;
+
+  if (!result->GetString("targetId", window_handle))
+    return Status(kUnknownError, "no targetId from createTarget");
+
+  return Status(kOk);
+}
+
 Status ChromeImpl::GetWindow(const std::string& target_id, Window* window) {
   Status status = devtools_websocket_client_->ConnectIfNecessary();
   if (status.IsError())
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.h b/chrome/test/chromedriver/chrome/chrome_impl.h
index 0d9871d..3204a85 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_impl.h
@@ -35,6 +35,9 @@
   Status GetWebViewIds(std::list<std::string>* web_view_ids,
                        bool w3c_compliant) override;
   Status GetWebViewById(const std::string& id, WebView** web_view) override;
+  Status NewWindow(const std::string& target_id,
+                   WindowType type,
+                   std::string* window_handle) override;
   Status GetWindowSize(const std::string& id, int* width, int* height) override;
   Status SetWindowSize(const std::string& target_id,
                        int width, int height) override;
diff --git a/chrome/test/chromedriver/chrome/stub_chrome.cc b/chrome/test/chromedriver/chrome/stub_chrome.cc
index 0a46d39..471b9dc 100644
--- a/chrome/test/chromedriver/chrome/stub_chrome.cc
+++ b/chrome/test/chromedriver/chrome/stub_chrome.cc
@@ -36,6 +36,12 @@
   return Status(kOk);
 }
 
+Status StubChrome::NewWindow(const std::string& target_id,
+                             WindowType type,
+                             std::string* window_handle) {
+  return Status(kOk);
+}
+
 Status StubChrome::GetWindowSize(const std::string& id,
                                  int* width,
                                  int* height) {
diff --git a/chrome/test/chromedriver/chrome/stub_chrome.h b/chrome/test/chromedriver/chrome/stub_chrome.h
index 6e57d4d..067a8c0 100644
--- a/chrome/test/chromedriver/chrome/stub_chrome.h
+++ b/chrome/test/chromedriver/chrome/stub_chrome.h
@@ -29,6 +29,9 @@
   Status GetWebViewIds(std::list<std::string>* web_view_ids,
                        bool w3c_compliant) override;
   Status GetWebViewById(const std::string& id, WebView** web_view) override;
+  Status NewWindow(const std::string& target_id,
+                   WindowType type,
+                   std::string* window_handle) override;
   Status GetWindowSize(const std::string& id, int* width, int* height) override;
   Status SetWindowSize(const std::string& id, int width, int height) override;
   Status SetWindowRect(const std::string& target_id,
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc
index 740ef9e..5729872 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.cc
+++ b/chrome/test/chromedriver/chrome/web_view_impl.cc
@@ -996,58 +996,14 @@
   async_args.AppendString("return (" + function + ").apply(null, arguments);");
   async_args.Append(args.CreateDeepCopy());
   async_args.AppendBoolean(is_user_supplied);
-  async_args.AppendInteger(timeout.InMilliseconds());
   std::unique_ptr<base::Value> tmp;
   Status status = CallFunctionWithTimeout(frame, kExecuteAsyncScriptScript,
                                           async_args, timeout, &tmp);
   if (status.IsError())
     return status;
 
-  const char kDocUnloadError[] = "document unloaded while waiting for result";
-  std::string kQueryResult = base::StringPrintf(
-      "function() {"
-      "  var info = document.$chrome_asyncScriptInfo;"
-      "  if (!info)"
-      "    return {status: %d, value: '%s'};"
-      "  var result = info.result;"
-      "  if (!result)"
-      "    return {status: 0};"
-      "  delete info.result;"
-      "  return result;"
-      "}",
-      kJavaScriptError,
-      kDocUnloadError);
-
-  while (true) {
-    base::ListValue no_args;
-    std::unique_ptr<base::Value> query_value;
-    Status status = CallFunction(frame, kQueryResult, no_args, &query_value);
-    if (status.IsError()) {
-      if (status.code() == kNoSuchFrame)
-        return Status(kJavaScriptError, kDocUnloadError);
-      return status;
-    }
-
-    base::DictionaryValue* result_info = NULL;
-    if (!query_value->GetAsDictionary(&result_info))
-      return Status(kUnknownError, "async result info is not a dictionary");
-    int status_code;
-    if (!result_info->GetInteger("status", &status_code))
-      return Status(kUnknownError, "async result info has no int 'status'");
-    if (status_code != kOk) {
-      std::string message;
-      result_info->GetString("value", &message);
-      return Status(static_cast<StatusCode>(status_code), message);
-    }
-
-    base::Value* value = NULL;
-    if (result_info->Get("value", &value)) {
-      result->reset(value->DeepCopy());
-      return Status(kOk);
-    }
-
-    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
-  }
+  *result = std::move(tmp);
+  return status;
 }
 
 Status WebViewImpl::IsNotPendingNavigation(const std::string& frame_id,
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index b3747b0..453f0b0 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -520,6 +520,10 @@
                                {'windowHandle': 'current'})
     return [size['width'], size['height']]
 
+  def NewWindow(self, window_type="window"):
+    return self.ExecuteCommand(Command.NEW_WINDOW,
+                               {'type': window_type})
+
   def GetWindowRect(self):
     rect = self.ExecuteCommand(Command.GET_WINDOW_RECT)
     return [rect['width'], rect['height'], rect['x'], rect['y']]
diff --git a/chrome/test/chromedriver/client/command_executor.py b/chrome/test/chromedriver/client/command_executor.py
index 47339d63..8e3075a 100644
--- a/chrome/test/chromedriver/client/command_executor.py
+++ b/chrome/test/chromedriver/client/command_executor.py
@@ -83,6 +83,8 @@
   GET_WINDOW_RECT = (_Method.GET, '/session/:sessionId/window/rect')
   GET_WINDOW_SIZE = (
       _Method.GET, '/session/:sessionId/window/:windowHandle/size')
+  NEW_WINDOW = (
+      _Method.POST, '/session/:sessionId/window/new')
   GET_WINDOW_POSITION = (
       _Method.GET, '/session/:sessionId/window/:windowHandle/position')
   SET_WINDOW_SIZE = (
diff --git a/chrome/test/chromedriver/js/execute_async_script.js b/chrome/test/chromedriver/js/execute_async_script.js
index b9dc8ab..f30fb4a7 100644
--- a/chrome/test/chromedriver/js/execute_async_script.js
+++ b/chrome/test/chromedriver/js/execute_async_script.js
@@ -13,28 +13,9 @@
   SCRIPT_TIMEOUT: 28,
 };
 
-/**
- * Dictionary key for asynchronous script info.
- * @const
- */
-var ASYNC_INFO_KEY = '$chrome_asyncScriptInfo';
 
 /**
-* Return the information of asynchronous script execution.
-*
-* @return {Object<?>} Information of asynchronous script execution.
-*/
-function getAsyncScriptInfo() {
-  if (!(ASYNC_INFO_KEY in document))
-    document[ASYNC_INFO_KEY] = {'id': 0};
-  return document[ASYNC_INFO_KEY];
-}
-
-/**
-* Execute the given script and save its asynchronous result.
-*
-* If script1 finishes after script2 is executed, then script1's result will be
-* discarded while script2's will be saved.
+* Execute the given script and return a promise containing its result.
 *
 * @param {string} script The asynchronous script to be executed. The script
 *     should be a proper function body. It will be wrapped in a function and
@@ -45,57 +26,49 @@
 *     If not, UnknownError will be used instead of JavaScriptError if an
 *     exception occurs during the script, and an additional error callback will
 *     be supplied to the script.
-* @param {?number} opt_timeoutMillis The timeout, in milliseconds, to use.
-*     If the timeout is exceeded and the callback has not been invoked, a error
-*     result will be saved and future invocation of the callback will be
-*     ignored.
 */
-function executeAsyncScript(script, args, isUserSupplied, opt_timeoutMillis) {
-  var info = getAsyncScriptInfo();
-  info.id++;
-  delete info.result;
-  var id = info.id;
+function executeAsyncScript(script, args, isUserSupplied) {
+  function isThenable(value) {
+    return typeof value === 'object' && typeof value.then === 'function';
+  }
+  let resolveHandle;
+  let rejectHandle;
+  var promise = new Promise((resolve, reject) => {
+    resolveHandle = resolve;
+    rejectHandle = reject;
+  });
 
-  function report(status, value) {
-    if (id != info.id)
-      return;
-    info.id++;
-    // Undefined value is skipped when the object is converted to JSON.
-    // Replace it with null so we don't lose the value.
-    if (value === undefined)
-      value = null;
-    info.result = {status: status, value: value};
-  }
-  function reportValue(value) {
-    report(StatusCode.OK, value);
-  }
-  function reportScriptError(error) {
-    var code = isUserSupplied ? StatusCode.JAVASCRIPT_ERROR :
-                                (error.code || StatusCode.UNKNOWN_ERROR);
-    var message = error.message;
-    if (error.stack) {
-      message += "\nJavaScript stack:\n" + error.stack;
-    }
-    report(code, message);
-  }
-  args.push(reportValue);
+  args.push(resolveHandle);  // Append resolve to end of arguments list.
   if (!isUserSupplied)
-    args.push(reportScriptError);
+    args.push(rejectHandle);
 
+  // This confusing, round-about way accomplishing this script execution is to
+  // follow the W3C execute-async-script spec.
   try {
-    new Function(script).apply(null, args);
-  } catch (error) {
-    reportScriptError(error);
-    return;
+    // The assumption is that each script is an asynchronous script.
+    const scriptResult = new Function(script).apply(null, args);
+
+    // First case is for user-scripts - they are all wrapped in an async
+    // function in order to allow for "await" commands. As a result, all async
+    // scripts from users will return a Promise that is thenable by default,
+    // even if it doesn't return anything.
+    if (isThenable(scriptResult)) {
+      const resolvedPromise = Promise.resolve(scriptResult);
+      resolvedPromise.then((value) => {
+        // Must be thenable if user-supplied.
+        if (!isUserSupplied || isThenable(value))
+          resolveHandle(value);
+      })
+      .catch(rejectHandle);
+    }
+  } catch(error) {
+    rejectHandle(error);
   }
 
-  if (typeof(opt_timeoutMillis) != 'undefined') {
-    window.setTimeout(function() {
-      var code = isUserSupplied ? StatusCode.SCRIPT_TIMEOUT :
-                                  StatusCode.UNKNOWN_ERROR;
-      var errorMsg = 'result was not received in ' + opt_timeoutMillis / 1000 +
-                     ' seconds';
-      report(code, errorMsg);
-    }, opt_timeoutMillis);
-  }
+  return promise.catch((error) => {
+    const code = isUserSupplied ? StatusCode.JAVASCRIPT_ERROR :
+                            (error.code || StatusCode.UNKNOWN_ERROR);
+    error.code = code;
+    throw error;
+  });
 }
diff --git a/chrome/test/chromedriver/js/execute_async_script_test.html b/chrome/test/chromedriver/js/execute_async_script_test.html
index d1ebe26..2b0262b 100644
--- a/chrome/test/chromedriver/js/execute_async_script_test.html
+++ b/chrome/test/chromedriver/js/execute_async_script_test.html
@@ -4,23 +4,28 @@
 <script src='execute_async_script.js'></script>
 <script>
 
-function resetAsyncScriptInfo() {
-  delete document[ASYNC_INFO_KEY];
+async function testScriptThrows() {
+  let promise = executeAsyncScript('f(123);', [], true).then((result) => {
+    assert(false);
+  }).catch((error) => {
+    assertEquals(StatusCode.JAVASCRIPT_ERROR, error.code);
+    return 1;
+  });
+
+  let result = await promise;
+  assertEquals(1, result);
+
+  executeAsyncScript('f(123);', [], false).then((result) => {
+    assert(false);
+  }).catch((error) => {
+    assertEquals(StatusCode.UNKNOWN_ERROR, error.code);
+    return 1;
+  });
+  result = await promise;
+  assertEquals(1, result);
 }
 
-function testScriptThrows() {
-  resetAsyncScriptInfo();
-  var info = getAsyncScriptInfo();
-
-  executeAsyncScript('f(123);', [], true);
-  assertEquals(StatusCode.JAVASCRIPT_ERROR, info.result.status);
-  executeAsyncScript('f(123);', [], false);
-  assertEquals(StatusCode.UNKNOWN_ERROR, info.result.status);
-}
-
-function testUserScriptWithArgs() {
-  resetAsyncScriptInfo();
-
+async function testUserScriptWithArgs() {
   var injectedArgs = null;
   function captureArguments(args) {
     injectedArgs = args;
@@ -30,106 +35,49 @@
   var script =
       'var args = arguments; args[0](args); args[args.length - 1](args[1]);';
   var script_args = [captureArguments, 1];
-  executeAsyncScript(script, script_args, true);
-
+  await executeAsyncScript(script, script_args, true).then((result) => {
+    assertEquals(1, result);
+  });
   assertEquals(3, injectedArgs.length);
   assertEquals(captureArguments, injectedArgs[0]);
   assertEquals(1, injectedArgs[1]);
-
-  var info = getAsyncScriptInfo();
-  assertEquals(0, info.result.status);
-  assertEquals(1, info.result.value);
-  assertEquals(2, info.id);
 }
 
-function testNonUserScript() {
-  resetAsyncScriptInfo();
-  var info = getAsyncScriptInfo();
+async function testNonUserScript() {
+  await executeAsyncScript('arguments[1](arguments[0])', [33],
+                           false).then((result) => {
+    assertEquals(33, result);
+  });
 
-  executeAsyncScript('arguments[1](arguments[0])', [33], false);
-  assertEquals(0, info.result.status);
-  assertEquals(33, info.result.value);
+  await executeAsyncScript('arguments[2](new Error("ERR"))', [33],
+                           false).then((result) => {
+    assert(false);
+  }).catch((error) => {
+    assertEquals(StatusCode.UNKNOWN_ERROR, error.code);
+    assertEquals(0, error.message.indexOf('ERR'));
+  });
 
-  executeAsyncScript('arguments[2](new Error("ERR"))', [33], false);
-  assertEquals(StatusCode.UNKNOWN_ERROR, info.result.status);
-  assertEquals(0, info.result.value.indexOf('ERR'));
-
-  executeAsyncScript('var e = new Error("ERR"); e.code = 111; arguments[1](e)',
-                     [], false);
-  assertEquals(111, info.result.status);
-  assertEquals(0, info.result.value.indexOf('ERR'));
+  await executeAsyncScript(`
+    var e = new Error("ERR");
+    e.code = 111;
+    arguments[1](e)`,
+    [], false).then((result) => { assert(false); }).catch((error) => {
+      assertEquals(111, error.code);
+      assertEquals(0,   error.message.indexOf('ERR'));
+  });
 }
-
-function testNoResultBeforeTimeout() {
-  resetAsyncScriptInfo();
-  var info = getAsyncScriptInfo();
-
+async function testFirstScriptFinishAfterSecondScriptExecute() {
+  let firstCompleted = false;
   executeAsyncScript(
-      'var a = arguments; window.setTimeout(function() {a[0](33)}, 0);',
-      [], true, 0);
-
-  assert(!info.result);
-}
-
-function testZeroTimeout(runner) {
-  resetAsyncScriptInfo();
-  var info = getAsyncScriptInfo();
-
-  executeAsyncScript(
-      'var a = arguments; window.setTimeout(function() {a[0](33)}, 0);',
-      [], true, 0);
-
-  window.setTimeout(function() {
-    assertEquals(0, info.result.status);
-    assertEquals(33, info.result.value);
-    runner.continueTesting();
-  }, 0);
-  runner.waitForAsync();
-}
-
-function testUserScriptTimesOut(runner) {
-  resetAsyncScriptInfo();
-  var info = getAsyncScriptInfo();
-
-  executeAsyncScript('', [], true, 500);
-
-  window.setTimeout(function() {
-    assertEquals(StatusCode.SCRIPT_TIMEOUT, info.result.status);
-    assert(info.result.value.indexOf('0.5') != -1);
-    runner.continueTesting();
-  }, 500);
-
-  runner.waitForAsync();
-}
-
-function testNonUserScriptTimesOut(runner) {
-  resetAsyncScriptInfo();
-  var info = getAsyncScriptInfo();
-
-  executeAsyncScript('', [], false, 500);
-
-  window.setTimeout(function() {
-    assertEquals(StatusCode.UNKNOWN_ERROR, info.result.status);
-    assert(info.result.value.indexOf('0.5') != -1);
-    runner.continueTesting();
-  }, 500);
-
-  runner.waitForAsync();
-}
-
-function testFirstScriptFinishAfterSecondScriptExecute() {
-  resetAsyncScriptInfo();
-
-  executeAsyncScript(
-      'var f = arguments[0]; setTimeout(function(){ f(1); }, 100000);', []);
-  var info = getAsyncScriptInfo();
-  assert(!info.hasOwnProperty('result'));
-  assertEquals(1, info.id);
-
-  executeAsyncScript('var fn = arguments[0]; fn(2);', []);
-  assertEquals(0, info.result.status);
-  assertEquals(2, info.result.value);
-  assertEquals(3, info.id);
+    `var f = arguments[0];
+     setTimeout(function(){ f(1); }, 100000);`, []).then((result) => {
+        firstCompleted = true;
+  });
+  await executeAsyncScript('var fn = arguments[0]; fn(2);',
+                           []).then((result) => {
+    assert(!firstCompleted);
+    assertEquals(2, result);
+  });
 }
 
 </script>
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index 01b96bc8..b9cb1230 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -185,6 +185,9 @@
           kDelete, "session/:sessionId/window",
           WrapToCommand("CloseWindow", base::BindRepeating(&ExecuteClose))),
       CommandMapping(
+          kPost, "session/:sessionId/window/new",
+          WrapToCommand("NewWindow", base::BindRepeating(&ExecuteNewWindow))),
+      CommandMapping(
           kPost, "session/:sessionId/window",
           WrapToCommand("SwitchToWindow",
                         base::BindRepeating(&ExecuteSwitchToWindow))),
diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc
index fdc007d..edff019 100644
--- a/chrome/test/chromedriver/session_commands.cc
+++ b/chrome/test/chromedriver/session_commands.cc
@@ -57,22 +57,6 @@
 const int k2GLatency = 300;
 const int k2GThroughput = 250 * 1024;
 
-const char kWindowHandlePrefix[] = "CDwindow-";
-
-std::string WebViewIdToWindowHandle(const std::string& web_view_id) {
-  return kWindowHandlePrefix + web_view_id;
-}
-
-bool WindowHandleToWebViewId(const std::string& window_handle,
-                             std::string* web_view_id) {
-  if (!base::StartsWith(window_handle, kWindowHandlePrefix,
-                        base::CompareCase::SENSITIVE)) {
-    return false;
-  }
-  *web_view_id = window_handle.substr(sizeof(kWindowHandlePrefix) - 1);
-  return true;
-}
-
 Status EvaluateScriptAndIgnoreResult(Session* session, std::string expression) {
   WebView* web_view = nullptr;
   Status status = session->GetTargetWindow(&web_view);
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 04b6927..36fa18c 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -159,6 +159,8 @@
     'PerfTest.*',
     # HeadlessInvalidCertificateTest is sometimes flaky.
     'HeadlessInvalidCertificateTest.*',
+    # Similar issues with HeadlessChromeDriverTest.
+    'HeadlessChromeDriverTest.*',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2277
     # RemoteBrowserTest requires extra setup. TODO(johnchen@chromium.org):
     # Modify the test so it runs correctly as isolated test.
@@ -213,6 +215,7 @@
         'ChromeDriverTest.testCloseWindowUsingJavascript',
         # Android doesn't support headless mode
         'HeadlessInvalidCertificateTest.*',
+        'HeadlessChromeDriverTest.*',
         # Tests of the desktop Chrome launch process.
         'LaunchDesktopTest.*',
         # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2737
@@ -413,6 +416,23 @@
   def testGetCurrentWindowHandle(self):
     self._driver.GetCurrentWindowHandle()
 
+  def _newWindowDoesNotFocus(self, window_type='window'):
+    current_handles = self._driver.GetWindowHandles()
+    self._driver.Load(self.GetHttpUrlForFile(
+        '/chromedriver/focus_blur_test.html'))
+    new_window = self._driver.NewWindow(window_type=window_type)
+    text = self._driver.FindElement('css selector', '#result').GetText()
+
+    self.assertTrue(new_window['handle'] not in current_handles)
+    self.assertTrue(new_window['handle'] in self._driver.GetWindowHandles())
+    self.assertEquals(text, 'PASS')
+
+  def testNewWindowDoesNotFocus(self):
+    self._newWindowDoesNotFocus(window_type='window')
+
+  def testNewTabDoesNotFocus(self):
+    self._newWindowDoesNotFocus(window_type='tab')
+
   def testCloseWindow(self):
     self._driver.Load(self.GetHttpUrlForFile('/chromedriver/page_test.html'))
     old_handles = self._driver.GetWindowHandles()
@@ -3405,6 +3425,29 @@
     self._driver.FindElement('css selector', '#link')
 
 
+class HeadlessChromeDriverTest(ChromeDriverBaseTestWithWebServer):
+  """End to end tests for ChromeDriver."""
+
+  def setUp(self):
+    self._driver = self.CreateDriver(chrome_switches=['--headless'])
+
+  def _newWindowDoesNotFocus(self, window_type='window'):
+    current_handles = self._driver.GetWindowHandles()
+    self._driver.Load(self.GetHttpUrlForFile(
+        '/chromedriver/focus_blur_test.html'))
+    new_window = self._driver.NewWindow(window_type=window_type)
+    text = self._driver.FindElement('css selector', '#result').GetText()
+
+    self.assertTrue(new_window['handle'] not in current_handles)
+    self.assertTrue(new_window['handle'] in self._driver.GetWindowHandles())
+    self.assertEquals(text, 'PASS')
+
+  def testNewWindowDoesNotFocus(self):
+    self._newWindowDoesNotFocus(window_type='window')
+
+  def testNewTabDoesNotFocus(self):
+    self._newWindowDoesNotFocus(window_type='tab')
+
 class SupportIPv4AndIPv6(ChromeDriverBaseTest):
   def testSupportIPv4AndIPv6(self):
     has_ipv4 = False
diff --git a/chrome/test/chromedriver/util.cc b/chrome/test/chromedriver/util.cc
index 4ff84c60..8ab5a54 100644
--- a/chrome/test/chromedriver/util.cc
+++ b/chrome/test/chromedriver/util.cc
@@ -28,6 +28,8 @@
 #include "chrome/test/chromedriver/session.h"
 #include "third_party/zlib/google/zip.h"
 
+const char kWindowHandlePrefix[] = "CDwindow-";
+
 std::string GenerateId() {
   uint64_t msb = base::RandUint64();
   uint64_t lsb = base::RandUint64();
@@ -547,3 +549,17 @@
   else
     return dict->SetDouble(path, in_value_64);
 }
+
+std::string WebViewIdToWindowHandle(const std::string& web_view_id) {
+  return kWindowHandlePrefix + web_view_id;
+}
+
+bool WindowHandleToWebViewId(const std::string& window_handle,
+                             std::string* web_view_id) {
+  if (!base::StartsWith(window_handle, kWindowHandlePrefix,
+                        base::CompareCase::SENSITIVE)) {
+    return false;
+  }
+  *web_view_id = window_handle.substr(sizeof(kWindowHandlePrefix) - 1);
+  return true;
+}
diff --git a/chrome/test/chromedriver/util.h b/chrome/test/chromedriver/util.h
index f36731c..277b9cf 100644
--- a/chrome/test/chromedriver/util.h
+++ b/chrome/test/chromedriver/util.h
@@ -92,4 +92,11 @@
                 const base::StringPiece path,
                 int64_t in_value_64);
 
+// Provides WindowHandle to WebView method to maintain consistency across
+// ChromeDriver.
+std::string WebViewIdToWindowHandle(const std::string& web_view_id);
+
+bool WindowHandleToWebViewId(const std::string& window_handle,
+                             std::string* web_view_id);
+
 #endif  // CHROME_TEST_CHROMEDRIVER_UTIL_H_
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index 626c0d4..acf17a8f 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -601,13 +601,45 @@
     script = script + "\n";
 
   Status status = web_view->CallUserAsyncFunction(
-      session->GetCurrentFrameId(), "function(){" + script + "}", *args,
+      session->GetCurrentFrameId(), "async function(){" + script + "}", *args,
       session->script_timeout, value);
   if (status.code() == kTimeout)
     return Status(kScriptTimeout);
   return status;
 }
 
+Status ExecuteNewWindow(Session* session,
+                        WebView* web_view,
+                        const base::DictionaryValue& params,
+                        std::unique_ptr<base::Value>* value,
+                        Timeout* timeout) {
+  std::string type = "";
+  // "type" can either be None or a string.
+  auto* type_param = params.FindKey("type");
+  if (!(!type_param || type_param->is_none() ||
+        params.GetString("type", &type)))
+    return Status(kInvalidArgument, "missing or invalid 'type'");
+
+  // By default, creates new tab.
+  Chrome::WindowType window_type = (type == "window")
+                                       ? Chrome::WindowType::kWindow
+                                       : Chrome::WindowType::kTab;
+
+  std::string handle = "";
+  Status status =
+      session->chrome->NewWindow(session->window, window_type, &handle);
+
+  if (status.IsError())
+    return status;
+
+  auto results = std::make_unique<base::DictionaryValue>();
+  results->SetString("handle", WebViewIdToWindowHandle(handle));
+  results->SetString(
+      "type", (window_type == Chrome::WindowType::kWindow) ? "window" : "tab");
+  *value = std::move(results);
+  return Status(kOk);
+}
+
 Status ExecuteSwitchToFrame(Session* session,
                             WebView* web_view,
                             const base::DictionaryValue& params,
diff --git a/chrome/test/chromedriver/window_commands.h b/chrome/test/chromedriver/window_commands.h
index aa863ba..cc43012 100644
--- a/chrome/test/chromedriver/window_commands.h
+++ b/chrome/test/chromedriver/window_commands.h
@@ -56,6 +56,13 @@
                                  std::unique_ptr<base::Value>* value,
                                  Timeout* timeout);
 
+// Creates a new window/tab.
+Status ExecuteNewWindow(Session* session,
+                        WebView* web_view,
+                        const base::DictionaryValue& params,
+                        std::unique_ptr<base::Value>* value,
+                        Timeout* timeout);
+
 // Changes the targeted frame for the given session.
 Status ExecuteSwitchToFrame(Session* session,
                             WebView* web_view,
diff --git a/chrome/test/data/chromedriver/focus_blur_test.html b/chrome/test/data/chromedriver/focus_blur_test.html
new file mode 100644
index 0000000..9f0c348
--- /dev/null
+++ b/chrome/test/data/chromedriver/focus_blur_test.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>No blur test</title>
+<!-- This simple page indicates whether or not this page ever loses focus. -->
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+</head>
+<body>
+  <p id="result">PASS</p>
+</body>
+<script>
+window.onblur = function() {
+  document.getElementById("result").innerHTML = "FAIL";
+};
+</script>
+</html>
diff --git a/chrome/test/data/local_ntp/local_ntp_browsertest.html b/chrome/test/data/local_ntp/local_ntp_browsertest.html
index 837fcf4..3517f42 100644
--- a/chrome/test/data/local_ntp/local_ntp_browsertest.html
+++ b/chrome/test/data/local_ntp/local_ntp_browsertest.html
@@ -7,13 +7,13 @@
   <script>window.localNTPUnitTest = true;</script>
   <link rel="stylesheet" href="chrome-search://local-ntp/animations.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/local-ntp-common.css"></link>
-  <link rel="stylesheet" href="chrome-search://local-ntp/custom-backgrounds.css"></link>
+  <link rel="stylesheet" href="chrome-search://local-ntp/customize.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/doodles.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/local-ntp.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/voice.css"></link>
   <script src="chrome-search://local-ntp/animations.js" charset="utf-8"></script>
   <script src="chrome-search://local-ntp/config.js" charset="utf-8"></script>
-  <script src="chrome-search://local-ntp/custom-backgrounds.js" charset="utf-8"></script>
+  <script src="chrome-search://local-ntp/customize.js" charset="utf-8"></script>
   <script src="chrome-search://local-ntp/doodles.js" charset="utf-8"></script>
   <script src="chrome-search://local-ntp/local-ntp.js" charset="utf-8"></script>
   <script src="chrome-search://local-ntp/utils.js" charset="utf-8"></script>
diff --git a/chromeos/components/multidevice/debug_webui/proximity_auth_webui_handler.cc b/chromeos/components/multidevice/debug_webui/proximity_auth_webui_handler.cc
index 8c42b21..eb1a490 100644
--- a/chromeos/components/multidevice/debug_webui/proximity_auth_webui_handler.cc
+++ b/chromeos/components/multidevice/debug_webui/proximity_auth_webui_handler.cc
@@ -328,9 +328,15 @@
 
 std::unique_ptr<base::Value>
 ProximityAuthWebUIHandler::GetTruncatedLocalDeviceId() {
-  return std::make_unique<base::Value>(
-      device_sync_client_->GetLocalDeviceMetadata()
-          ->GetTruncatedDeviceIdForLogs());
+  base::Optional<multidevice::RemoteDeviceRef> local_device_metadata =
+      device_sync_client_->GetLocalDeviceMetadata();
+
+  std::string device_id =
+      local_device_metadata
+          ? local_device_metadata->GetTruncatedDeviceIdForLogs()
+          : "Missing Device ID";
+
+  return std::make_unique<base::Value>(device_id);
 }
 
 std::unique_ptr<base::ListValue>
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 2aaff6b9..19238d7 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -58,7 +58,7 @@
 // Enables or disables web push for background notifications in
 // Android Messages Integration on Chrome OS.
 const base::Feature kEnableMessagesWebPush{"EnableMessagesWebPush",
-                                           base::FEATURE_DISABLED_BY_DEFAULT};
+                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables the use of Mojo by Chrome-process code to communicate with Power
 // Manager. In order to use mojo, this feature must be turned on and a callsite
@@ -117,7 +117,7 @@
 // Use the messages.google.com domain as part of the "Messages" feature under
 // "Connected Devices" settings.
 const base::Feature kUseMessagesGoogleComDomain{
-    "UseMessagesGoogleComDomain", base::FEATURE_DISABLED_BY_DEFAULT};
+    "UseMessagesGoogleComDomain", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Use the staging URL as part of the "Messages" feature under "Connected
 // Devices" settings.
diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn
index c770bf6b..c0b1614 100644
--- a/components/autofill_assistant/browser/BUILD.gn
+++ b/components/autofill_assistant/browser/BUILD.gn
@@ -161,6 +161,7 @@
   testonly = true
   sources = [
     "actions/autofill_action_unittest.cc",
+    "actions/configure_bottom_sheet_action_unittest.cc",
     "actions/mock_action_delegate.cc",
     "actions/mock_action_delegate.h",
     "actions/prompt_action_unittest.cc",
diff --git a/components/autofill_assistant/browser/actions/action_delegate.h b/components/autofill_assistant/browser/actions/action_delegate.h
index 702b3105..0d985f2 100644
--- a/components/autofill_assistant/browser/actions/action_delegate.h
+++ b/components/autofill_assistant/browser/actions/action_delegate.h
@@ -246,9 +246,19 @@
   // Set whether the viewport should be resized.
   virtual void SetResizeViewport(bool resize_viewport) = 0;
 
+  // Checks whether the viewport should be resized.
+  virtual bool GetResizeViewport() = 0;
+
   // Set the peek mode.
   virtual void SetPeekMode(ConfigureBottomSheetProto::PeekMode peek_mode) = 0;
 
+  // Checks the current peek mode.
+  virtual ConfigureBottomSheetProto::PeekMode GetPeekMode() = 0;
+
+  // Calls the callback once the main document window has been resized.
+  virtual void WaitForWindowHeightChange(
+      base::OnceCallback<void(const ClientStatus&)> callback) = 0;
+
   // Returns the current client settings.
   virtual const ClientSettings& GetSettings() = 0;
 
diff --git a/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.cc b/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.cc
index 447f6c6..d3fd092b 100644
--- a/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.cc
+++ b/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.cc
@@ -4,33 +4,89 @@
 
 #include "components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h"
 
+#include "base/bind.h"
+#include "base/callback.h"
 #include "components/autofill_assistant/browser/actions/action_delegate.h"
+#include "components/autofill_assistant/browser/client_status.h"
 
 namespace autofill_assistant {
 
 ConfigureBottomSheetAction::ConfigureBottomSheetAction(const ActionProto& proto)
-    : Action(proto) {}
+    : Action(proto), weak_ptr_factory_(this) {}
 
 ConfigureBottomSheetAction::~ConfigureBottomSheetAction() {}
 
 void ConfigureBottomSheetAction::InternalProcessAction(
     ActionDelegate* delegate,
     ProcessActionCallback callback) {
-  if (proto_.configure_bottom_sheet().viewport_resizing() ==
-      ConfigureBottomSheetProto::RESIZE) {
+  const ConfigureBottomSheetProto& proto = proto_.configure_bottom_sheet();
+
+  if (proto.resize_timeout_ms() > 0) {
+    // When a window resize is expected, we want to wait for the size change to
+    // be visible to Javascript before moving on to another action. To do that,
+    // this action registers a callback *before* making any change and waits for
+    // a 'resize' event in the Javascript side.
+    bool resize = delegate->GetResizeViewport();
+    bool expect_resize =
+        (!resize &&
+         proto.viewport_resizing() == ConfigureBottomSheetProto::RESIZE) ||
+        (resize &&
+         (proto.viewport_resizing() == ConfigureBottomSheetProto::NO_RESIZE ||
+          (proto.peek_mode() !=
+               ConfigureBottomSheetProto::UNDEFINED_PEEK_MODE &&
+           proto.peek_mode() != delegate->GetPeekMode())));
+    if (expect_resize) {
+      callback_ = std::move(callback);
+
+      timer_.Start(FROM_HERE,
+                   base::TimeDelta::FromMilliseconds(proto.resize_timeout_ms()),
+                   base::BindOnce(&ConfigureBottomSheetAction::OnTimeout,
+                                  weak_ptr_factory_.GetWeakPtr()));
+
+      delegate->WaitForWindowHeightChange(
+          base::BindOnce(&ConfigureBottomSheetAction::OnWindowHeightChange,
+                         weak_ptr_factory_.GetWeakPtr()));
+    }
+  }
+
+  if (proto.viewport_resizing() == ConfigureBottomSheetProto::RESIZE) {
     delegate->SetResizeViewport(true);
-  } else if (proto_.configure_bottom_sheet().viewport_resizing() ==
+  } else if (proto.viewport_resizing() ==
              ConfigureBottomSheetProto::NO_RESIZE) {
     delegate->SetResizeViewport(false);
   }
 
-  if (proto_.configure_bottom_sheet().peek_mode() !=
-      ConfigureBottomSheetProto::UNDEFINED_PEEK_MODE) {
-    delegate->SetPeekMode(proto_.configure_bottom_sheet().peek_mode());
+  if (proto.peek_mode() != ConfigureBottomSheetProto::UNDEFINED_PEEK_MODE) {
+    delegate->SetPeekMode(proto.peek_mode());
   }
 
-  UpdateProcessedAction(ACTION_APPLIED);
-  std::move(callback).Run(std::move(processed_action_proto_));
+  if (callback) {
+    UpdateProcessedAction(OkClientStatus());
+    std::move(callback).Run(std::move(processed_action_proto_));
+  }
+}
+
+void ConfigureBottomSheetAction::OnWindowHeightChange(
+    const ClientStatus& status) {
+  if (!callback_)
+    return;
+
+  timer_.Stop();
+  UpdateProcessedAction(status);
+  std::move(callback_).Run(std::move(processed_action_proto_));
+}
+
+void ConfigureBottomSheetAction::OnTimeout() {
+  if (!callback_)
+    return;
+
+  DVLOG(2)
+      << __func__
+      << " Timed out waiting for window height change. Continuing anyways.";
+  UpdateProcessedAction(OkClientStatus());
+  processed_action_proto_->mutable_status_details()->set_original_status(
+      ProcessedActionStatusProto::TIMED_OUT);
+  std::move(callback_).Run(std::move(processed_action_proto_));
 }
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h b/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h
index ec1247812..8637a3c 100644
--- a/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h
+++ b/components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h
@@ -6,6 +6,8 @@
 #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_CONFIGURE_BOTTOM_SHEET_ACTION_H_
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
 #include "components/autofill_assistant/browser/actions/action.h"
 
 namespace autofill_assistant {
@@ -21,6 +23,12 @@
   void InternalProcessAction(ActionDelegate* delegate,
                              ProcessActionCallback callback) override;
 
+  void OnWindowHeightChange(const ClientStatus& status);
+  void OnTimeout();
+
+  ProcessActionCallback callback_;
+  base::OneShotTimer timer_;
+  base::WeakPtrFactory<ConfigureBottomSheetAction> weak_ptr_factory_;
   DISALLOW_COPY_AND_ASSIGN(ConfigureBottomSheetAction);
 };
 
diff --git a/components/autofill_assistant/browser/actions/configure_bottom_sheet_action_unittest.cc b/components/autofill_assistant/browser/actions/configure_bottom_sheet_action_unittest.cc
new file mode 100644
index 0000000..b96af79
--- /dev/null
+++ b/components/autofill_assistant/browser/actions/configure_bottom_sheet_action_unittest.cc
@@ -0,0 +1,247 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/autofill_assistant/browser/actions/mock_action_delegate.h"
+#include "components/autofill_assistant/browser/mock_run_once_callback.h"
+#include "components/autofill_assistant/browser/mock_web_controller.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+namespace {
+
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::Invoke;
+using ::testing::IsEmpty;
+using ::testing::IsNull;
+using ::testing::Pointee;
+using ::testing::Property;
+using ::testing::SizeIs;
+
+class ConfigureBottomSheetActionTest : public testing::Test {
+ public:
+  ConfigureBottomSheetActionTest()
+      : task_env_(
+            base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {}
+
+  void SetUp() override {
+    ON_CALL(mock_action_delegate_, GetResizeViewport())
+        .WillByDefault(Invoke([this]() { return resize_viewport_; }));
+    ON_CALL(mock_action_delegate_, SetResizeViewport(_))
+        .WillByDefault(
+            Invoke([this](bool value) { resize_viewport_ = value; }));
+    ON_CALL(mock_action_delegate_, GetPeekMode())
+        .WillByDefault(Invoke([this]() { return peek_mode_; }));
+    ON_CALL(mock_action_delegate_, SetPeekMode(_))
+        .WillByDefault(
+            Invoke([this](ConfigureBottomSheetProto::PeekMode peek_mode) {
+              peek_mode_ = peek_mode;
+            }));
+    ON_CALL(mock_action_delegate_, OnWaitForWindowHeightChange(_))
+        .WillByDefault(Invoke(
+            [this](base::OnceCallback<void(const ClientStatus&)>& callback) {
+              on_resize_cb_ = std::move(callback);
+            }));
+  }
+
+ protected:
+  // Runs the action defined in |proto_| and reports the result to |callback_|.
+  //
+  // Once it has run, the result of the action is available in
+  // |processed_action_|. Before the action has run, |processed_action_| status
+  // is UNKNOWN_ACTION_STATUS.
+  void Run() {
+    ActionProto action_proto;
+    *action_proto.mutable_configure_bottom_sheet() = proto_;
+    action_ = std::make_unique<ConfigureBottomSheetAction>(action_proto);
+    action_->ProcessAction(
+        &mock_action_delegate_,
+        base::BindOnce(base::BindLambdaForTesting(
+            [&](std::unique_ptr<ProcessedActionProto> result) {
+              processed_action_ = *result;
+            })));
+  }
+
+  // Runs an action that waits for a resize.
+  void RunWithTimeout() {
+    proto_.set_resize_timeout_ms(100);
+    Run();
+  }
+
+  // Fast forward time enough for an action created by RunWithTimeout() to time
+  // out.
+  void ForceTimeout() {
+    task_env_.FastForwardBy(base::TimeDelta::FromMilliseconds(100));
+    task_env_.FastForwardBy(base::TimeDelta::FromMilliseconds(100));
+  }
+
+  // task_env_ must be first to guarantee other field
+  // creation run in that environment.
+  base::test::ScopedTaskEnvironment task_env_;
+
+  MockActionDelegate mock_action_delegate_;
+  MockWebController mock_web_controller_;
+  ConfigureBottomSheetProto proto_;
+  bool resize_viewport_ = false;
+  base::OnceCallback<void(const ClientStatus&)> on_resize_cb_;
+  ConfigureBottomSheetProto::PeekMode peek_mode_ =
+      ConfigureBottomSheetProto::HANDLE;
+  std::unique_ptr<ConfigureBottomSheetAction> action_;
+  ProcessedActionProto processed_action_;
+};
+
+TEST_F(ConfigureBottomSheetActionTest, NoOp) {
+  Run();
+
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+  EXPECT_FALSE(resize_viewport_);
+  EXPECT_EQ(ConfigureBottomSheetProto::HANDLE, peek_mode_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, ChangePeekMode) {
+  proto_.set_peek_mode(ConfigureBottomSheetProto::HANDLE_HEADER);
+  Run();
+
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+  EXPECT_FALSE(resize_viewport_);
+  EXPECT_EQ(ConfigureBottomSheetProto::HANDLE_HEADER, peek_mode_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, EnableResize) {
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+  Run();
+
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+  EXPECT_TRUE(resize_viewport_);
+  EXPECT_EQ(ConfigureBottomSheetProto::HANDLE, peek_mode_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, EnableResizeWithPeekMode) {
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+  proto_.set_peek_mode(ConfigureBottomSheetProto::HANDLE_HEADER);
+  Run();
+
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+  EXPECT_TRUE(resize_viewport_);
+  EXPECT_EQ(ConfigureBottomSheetProto::HANDLE_HEADER, peek_mode_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, WaitAfterSettingResize) {
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+
+  RunWithTimeout();
+
+  EXPECT_TRUE(resize_viewport_);
+  ASSERT_TRUE(on_resize_cb_);
+
+  std::move(on_resize_cb_).Run(OkClientStatus());
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+}
+
+TEST_F(ConfigureBottomSheetActionTest, WaitFailsAfterSettingResize) {
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+
+  RunWithTimeout();
+
+  ASSERT_TRUE(on_resize_cb_);
+
+  std::move(on_resize_cb_).Run(ClientStatus(OTHER_ACTION_STATUS));
+
+  EXPECT_EQ(OTHER_ACTION_STATUS, processed_action_.status());
+}
+
+TEST_F(ConfigureBottomSheetActionTest, WaitTimesOut) {
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+
+  RunWithTimeout();
+
+  ASSERT_TRUE(on_resize_cb_);
+
+  ForceTimeout();
+
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+  EXPECT_EQ(TIMED_OUT, processed_action_.status_details().original_status());
+}
+
+TEST_F(ConfigureBottomSheetActionTest, TimesOutAfterWindowResized) {
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+
+  RunWithTimeout();
+
+  ASSERT_TRUE(on_resize_cb_);
+
+  std::move(on_resize_cb_).Run(OkClientStatus());
+  ForceTimeout();
+
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+  EXPECT_TRUE(resize_viewport_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, WindowResizedAfterTimeout) {
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+
+  RunWithTimeout();
+
+  ASSERT_TRUE(on_resize_cb_);
+
+  ForceTimeout();
+  std::move(on_resize_cb_).Run(OkClientStatus());
+  EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
+}
+
+TEST_F(ConfigureBottomSheetActionTest, WaitAfterUnsettingResize) {
+  resize_viewport_ = true;
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::NO_RESIZE);
+
+  RunWithTimeout();
+
+  ASSERT_TRUE(on_resize_cb_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, WaitAfterChangingPeekModeInResizeMode) {
+  resize_viewport_ = true;
+  proto_.set_peek_mode(ConfigureBottomSheetProto::HANDLE_HEADER);
+
+  RunWithTimeout();
+
+  EXPECT_EQ(ConfigureBottomSheetProto::HANDLE_HEADER, peek_mode_);
+  ASSERT_TRUE(on_resize_cb_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, DontWaitAfterChangingPeekIfNoResize) {
+  proto_.set_peek_mode(ConfigureBottomSheetProto::HANDLE_HEADER);
+
+  RunWithTimeout();
+
+  ASSERT_FALSE(on_resize_cb_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, DontWaitIfPeekModeNotChanged) {
+  resize_viewport_ = true;
+  proto_.set_peek_mode(ConfigureBottomSheetProto::HANDLE);
+
+  RunWithTimeout();
+
+  ASSERT_FALSE(on_resize_cb_);
+}
+
+TEST_F(ConfigureBottomSheetActionTest, DontWaitIfResizeModeNotChanged) {
+  resize_viewport_ = true;
+  proto_.set_viewport_resizing(ConfigureBottomSheetProto::RESIZE);
+
+  RunWithTimeout();
+
+  ASSERT_FALSE(on_resize_cb_);
+}
+
+}  // namespace
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/actions/mock_action_delegate.h b/components/autofill_assistant/browser/actions/mock_action_delegate.h
index 79167ab..a446f28b 100644
--- a/components/autofill_assistant/browser/actions/mock_action_delegate.h
+++ b/components/autofill_assistant/browser/actions/mock_action_delegate.h
@@ -161,13 +161,23 @@
   MOCK_METHOD1(SetProgressVisible, void(bool visible));
   MOCK_METHOD1(SetChips, void(std::unique_ptr<std::vector<Chip>> chips));
   MOCK_METHOD1(SetResizeViewport, void(bool resize_viewport));
+  MOCK_METHOD0(GetResizeViewport, bool());
   MOCK_METHOD1(SetPeekMode,
                void(ConfigureBottomSheetProto::PeekMode peek_mode));
+  MOCK_METHOD0(GetPeekMode, ConfigureBottomSheetProto::PeekMode());
   MOCK_METHOD2(
       SetForm,
       bool(std::unique_ptr<FormProto> form,
            base::RepeatingCallback<void(const FormProto::Result*)> callback));
 
+  void WaitForWindowHeightChange(
+      base::OnceCallback<void(const ClientStatus&)> callback) {
+    OnWaitForWindowHeightChange(callback);
+  }
+
+  MOCK_METHOD1(OnWaitForWindowHeightChange,
+               void(base::OnceCallback<void(const ClientStatus&)>& callback));
+
   const ClientSettings& GetSettings() override { return client_settings_; }
 
   ClientSettings client_settings_;
diff --git a/components/autofill_assistant/browser/controller.cc b/components/autofill_assistant/browser/controller.cc
index 04146487..45e6217a7 100644
--- a/components/autofill_assistant/browser/controller.cc
+++ b/components/autofill_assistant/browser/controller.cc
@@ -785,7 +785,7 @@
 }
 
 void Controller::UpdateTouchableArea() {
-  touchable_element_area()->UpdatePositions();
+  touchable_element_area()->Update();
 }
 
 void Controller::OnUserInteractionInsideTouchableArea() {
@@ -947,6 +947,11 @@
     touchable_element_area_->GetRectangles(area);
 }
 
+void Controller::GetVisualViewport(RectF* visual_viewport) const {
+  if (touchable_element_area_)
+    touchable_element_area_->GetVisualViewport(visual_viewport);
+}
+
 void Controller::OnFatalError(const std::string& error_message,
                               Metrics::DropOutReason reason) {
   LOG(ERROR) << "Autofill Assistant has encountered an error and is shutting "
@@ -1120,8 +1125,9 @@
          iter->second == "1";
 }
 
-void Controller::OnTouchableAreaChanged(const std::vector<RectF>& areas) {
-  GetUiController()->OnTouchableAreaChanged(areas);
+void Controller::OnTouchableAreaChanged(const RectF& visual_viewport,
+                                        const std::vector<RectF>& areas) {
+  GetUiController()->OnTouchableAreaChanged(visual_viewport, areas);
 }
 
 void Controller::SetPaymentRequestOptions(
diff --git a/components/autofill_assistant/browser/controller.h b/components/autofill_assistant/browser/controller.h
index 7053fada..2455ae14a 100644
--- a/components/autofill_assistant/browser/controller.h
+++ b/components/autofill_assistant/browser/controller.h
@@ -127,6 +127,7 @@
   void SetTermsAndConditions(
       TermsAndConditionsState terms_and_conditions) override;
   void GetTouchableArea(std::vector<RectF>* area) const override;
+  void GetVisualViewport(RectF* visual_viewport) const override;
   void OnFatalError(const std::string& error_message,
                     Metrics::DropOutReason reason) override;
   bool GetResizeViewport() override;
@@ -206,7 +207,8 @@
   void OnWebContentsFocused(
       content::RenderWidgetHost* render_widget_host) override;
 
-  void OnTouchableAreaChanged(const std::vector<RectF>& areas);
+  void OnTouchableAreaChanged(const RectF& visual_viewport,
+                              const std::vector<RectF>& areas);
 
   void SelectChip(std::vector<Chip>* chips, int chip_index);
   void SetOverlayColors(std::unique_ptr<OverlayColors> colors);
diff --git a/components/autofill_assistant/browser/element_area.cc b/components/autofill_assistant/browser/element_area.cc
index bb55e3a..8a31f6b 100644
--- a/components/autofill_assistant/browser/element_area.cc
+++ b/components/autofill_assistant/browser/element_area.cc
@@ -17,15 +17,14 @@
 namespace autofill_assistant {
 
 ElementArea::ElementArea(ScriptExecutorDelegate* delegate)
-    : delegate_(delegate), scheduled_update_(false), weak_ptr_factory_(this) {
+    : delegate_(delegate), weak_ptr_factory_(this) {
   DCHECK(delegate_);
 }
 
 ElementArea::~ElementArea() = default;
 
 void ElementArea::Clear() {
-  rectangles_.clear();
-  ReportUpdate();
+  SetFromProto(ElementAreaProto());
 }
 
 void ElementArea::SetFromProto(const ElementAreaProto& proto) {
@@ -43,32 +42,57 @@
       DVLOG(3) << "  " << position.selector;
     }
   }
-  ReportUpdate();
 
-  if (rectangles_.empty())
+  if (rectangles_.empty()) {
+    timer_.Stop();
+    ReportUpdate();
     return;
+  }
 
-  if (!scheduled_update_) {
-    // Check once and schedule regular updates.
-    scheduled_update_ = true;
-    KeepUpdatingElementPositions();
-  } else {
-    // If regular updates are already scheduled, just force a check of position
-    // right away and keep running the scheduled updates.
-    UpdatePositions();
+  Update();
+  if (!timer_.IsRunning()) {
+    timer_.Start(
+        FROM_HERE, delegate_->GetSettings().element_position_update_interval,
+        base::BindRepeating(
+            &ElementArea::Update,
+            // This ElementArea instance owns |update_element_positions_|
+            base::Unretained(this)));
   }
 }
 
-void ElementArea::UpdatePositions() {
+void ElementArea::Update() {
   if (rectangles_.empty())
     return;
 
+  // If anything is still pending, skip the update.
+  if (visual_viewport_pending_update_)
+    return;
+
+  for (auto& rectangle : rectangles_) {
+    if (rectangle.IsPending())
+      return;
+  }
+
+  // Mark everything as pending at the same time, to avoid reporting partial
+  // results.
+  visual_viewport_pending_update_ = true;
   for (auto& rectangle : rectangles_) {
     for (auto& position : rectangle.positions) {
       // To avoid reporting partial rectangles, all element positions become
       // pending at the same time.
       position.pending_update = true;
     }
+  }
+
+  // Viewport and element positions are always queried, and so reported, at the
+  // same time. This allows supporting both elements whose position is relative
+  // (and move with a scroll) as elements whose position is absolute (and don't
+  // move with a scroll.) Being able to tell the difference would be more
+  // effective and allow refreshing element positions less aggressively.
+  delegate_->GetWebController()->GetVisualViewport(base::BindOnce(
+      &ElementArea::OnGetVisualViewport, weak_ptr_factory_.GetWeakPtr()));
+
+  for (auto& rectangle : rectangles_) {
     for (auto& position : rectangle.positions) {
       delegate_->GetWebController()->GetElementPosition(
           position.selector,
@@ -81,7 +105,7 @@
 void ElementArea::GetRectangles(std::vector<RectF>* area) {
   for (auto& rectangle : rectangles_) {
     area->emplace_back();
-    rectangle.FillRect(&area->back());
+    rectangle.FillRect(&area->back(), visual_viewport_);
   }
 }
 
@@ -102,11 +126,13 @@
   return false;
 }
 
-void ElementArea::Rectangle::FillRect(RectF* rect) const {
+void ElementArea::Rectangle::FillRect(RectF* rect,
+                                      const RectF& visual_viewport) const {
   bool has_first_rect = false;
   for (const auto& position : positions) {
-    if (position.rect.empty())
+    if (position.rect.empty()) {
       continue;
+    }
 
     if (!has_first_rect) {
       *rect = position.rect;
@@ -119,26 +145,12 @@
     rect->right = std::max(rect->right, position.rect.right);
   }
   if (has_first_rect && full_width) {
-    rect->left = 0.0;
-    rect->right = 1.0;
+    rect->left = visual_viewport.left;
+    rect->right = visual_viewport.right;
   }
   return;
 }
 
-void ElementArea::KeepUpdatingElementPositions() {
-  if (rectangles_.empty()) {
-    scheduled_update_ = false;
-    return;
-  }
-
-  UpdatePositions();
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&ElementArea::KeepUpdatingElementPositions,
-                     weak_ptr_factory_.GetWeakPtr()),
-      delegate_->GetSettings().element_position_update_interval);
-}
-
 void ElementArea::OnGetElementPosition(const Selector& selector,
                                        bool found,
                                        const RectF& rect) {
@@ -153,6 +165,7 @@
       }
     }
   }
+
   if (updated) {
     ReportUpdate();
   }
@@ -160,10 +173,35 @@
   // rectangles_. This is fine.
 }
 
+void ElementArea::OnGetVisualViewport(bool success, const RectF& rect) {
+  if (!visual_viewport_pending_update_)
+    return;
+
+  visual_viewport_pending_update_ = false;
+  if (!success)
+    return;
+
+  visual_viewport_ = rect;
+  ReportUpdate();
+}
+
 void ElementArea::ReportUpdate() {
   if (!on_update_)
     return;
 
+  if (rectangles_.empty()) {
+    // Reporting of visual viewport is best effort when reporting empty
+    // rectangles. It might also be empty.
+    on_update_.Run(visual_viewport_, {});
+    return;
+  }
+
+  // If there are rectangles, delay reporting until both the visual viewport
+  // size and the rectangles are available.
+  if (visual_viewport_pending_update_) {
+    return;
+  }
+
   for (const auto& rectangle : rectangles_) {
     if (rectangle.IsPending()) {
       // We don't have everything we need yet
@@ -173,7 +211,8 @@
 
   std::vector<RectF> area;
   GetRectangles(&area);
-  on_update_.Run(area);
+
+  on_update_.Run(visual_viewport_, area);
 }
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/element_area.h b/components/autofill_assistant/browser/element_area.h
index 69c4014..cf13880 100644
--- a/components/autofill_assistant/browser/element_area.h
+++ b/components/autofill_assistant/browser/element_area.h
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
 #include "components/autofill_assistant/browser/client_settings.h"
 #include "components/autofill_assistant/browser/rectf.h"
 #include "components/autofill_assistant/browser/selector.h"
@@ -36,14 +37,14 @@
   // The area is updated asynchronously, so Contains will not work right away.
   void SetFromProto(const ElementAreaProto& proto);
 
-  // Forces an out-of-schedule update of the positions right away.
+  // Forces an out-of-schedule update of the viewport and positions right away.
   //
   // This method is never strictly necessary. It is useful to call it when
   // there's a reason to think the positions might have changed, to speed up
   // updates.
   //
   // Does nothing if the area is empty.
-  void UpdatePositions();
+  void Update();
 
   // Defines a callback that'll be run every time the set of element coordinates
   // changes.
@@ -51,7 +52,8 @@
   // The argument reports the areas that corresponds to currently known
   // elements, which might be empty.
   void SetOnUpdate(
-      base::RepeatingCallback<void(const std::vector<RectF>& rectangles)> cb) {
+      base::RepeatingCallback<void(const RectF& visual_viewport,
+                                   const std::vector<RectF>& rectangles)> cb) {
     on_update_ = cb;
   }
 
@@ -64,6 +66,12 @@
   // Note that the vector is not cleared before rectangles are added.
   void GetRectangles(std::vector<RectF>* area);
 
+  // Gets the coordinates of the visual viewport, in CSS pixels relative to the
+  // layout viewport. Empty if the size of the visual viewport is not known.
+  void GetVisualViewport(RectF* visual_viewport) {
+    *visual_viewport = visual_viewport_;
+  }
+
  private:
   // A rectangle that corresponds to the area of the visual viewport covered by
   // an element. Coordinates are values between 0 and 1, relative to the size of
@@ -97,22 +105,31 @@
     bool IsPending() const;
 
     // Fills the given rectangle from the current state, if possible.
-    void FillRect(RectF* rect) const;
+    void FillRect(RectF* rect, const RectF& visual_viewport) const;
   };
 
-  void KeepUpdatingElementPositions();
   void OnGetElementPosition(const Selector& selector,
                             bool found,
                             const RectF& rect);
+  void OnGetVisualViewport(bool success, const RectF& rect);
   void ReportUpdate();
 
   ScriptExecutorDelegate* const delegate_;
   std::vector<Rectangle> rectangles_;
 
-  // If true, regular updates are currently scheduled.
-  bool scheduled_update_;
+  // If true, update for the visual viewport position is currently scheduled.
+  bool visual_viewport_pending_update_ = false;
 
-  base::RepeatingCallback<void(const std::vector<RectF>& areas)> on_update_;
+  // Visual viewport coordinates, in CSS pixels, relative to the layout
+  // viewport.
+  RectF visual_viewport_;
+
+  // While running, regularly calls Update().
+  base::RepeatingTimer timer_;
+
+  base::RepeatingCallback<void(const RectF& visual_viewport,
+                               const std::vector<RectF>& rectangles)>
+      on_update_;
 
   base::WeakPtrFactory<ElementArea> weak_ptr_factory_;
 
diff --git a/components/autofill_assistant/browser/element_area_unittest.cc b/components/autofill_assistant/browser/element_area_unittest.cc
index 441ea54..e928994 100644
--- a/components/autofill_assistant/browser/element_area_unittest.cc
+++ b/components/autofill_assistant/browser/element_area_unittest.cc
@@ -71,8 +71,14 @@
             base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
         element_area_(&delegate_) {
     delegate_.SetWebController(&mock_web_controller_);
+    delegate_.GetMutableSettings()->element_position_update_interval =
+        base::TimeDelta::FromMilliseconds(100);
+
     ON_CALL(mock_web_controller_, OnGetElementPosition(_, _))
         .WillByDefault(RunOnceCallback<1>(false, RectF()));
+    ON_CALL(mock_web_controller_, OnGetVisualViewport(_))
+        .WillByDefault(RunOnceCallback<0>(true, RectF(0, 0, 200, 400)));
+
     element_area_.SetOnUpdate(base::BindRepeating(&ElementAreaTest::OnUpdate,
                                                   base::Unretained(this)));
   }
@@ -83,7 +89,11 @@
     element_area_.SetFromProto(area);
   }
 
-  void OnUpdate(const std::vector<RectF>& area) { reported_area_ = area; }
+  void OnUpdate(const RectF& visual_viewport, const std::vector<RectF>& area) {
+    on_update_call_count_++;
+    reported_visual_viewport_ = visual_viewport;
+    reported_area_ = area;
+  }
 
   // scoped_task_environment_ must be first to guarantee other field
   // creation run in that environment.
@@ -92,6 +102,8 @@
   MockWebController mock_web_controller_;
   FakeScriptExecutorDelegate delegate_;
   ElementArea element_area_;
+  int on_update_call_count_ = 0;
+  RectF reported_visual_viewport_;
   std::vector<RectF> reported_area_;
 };
 
@@ -101,6 +113,10 @@
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
   EXPECT_THAT(rectangles, IsEmpty());
+
+  RectF viewport;
+  element_area_.GetVisualViewport(&viewport);
+  EXPECT_THAT(viewport, EmptyRectF());
 }
 
 TEST_F(ElementAreaTest, ElementNotFound) {
@@ -112,37 +128,72 @@
   EXPECT_THAT(rectangles, ElementsAre(EmptyRectF()));
 }
 
+TEST_F(ElementAreaTest, GetVisualViewport) {
+  SetElement("#some_element");
+  RectF viewport;
+  element_area_.GetVisualViewport(&viewport);
+  EXPECT_THAT(viewport, MatchingRectF(0, 0, 200, 400));
+}
+
 TEST_F(ElementAreaTest, OneRectangle) {
   EXPECT_CALL(mock_web_controller_,
               OnGetElementPosition(Eq(Selector({"#found"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.25f, 0.25f, 0.75f, 0.75f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(25, 25, 75, 75)));
 
   SetElement("#found");
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles,
-              ElementsAre(MatchingRectF(0.25f, 0.25f, 0.75f, 0.75f)));
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(25, 25, 75, 75)));
 }
 
 TEST_F(ElementAreaTest, CallOnUpdate) {
   EXPECT_CALL(mock_web_controller_,
               OnGetElementPosition(Eq(Selector({"#found"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.25f, 0.25f, 0.75f, 0.75f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(25, 25, 75, 75)));
 
   SetElement("#found");
-  EXPECT_THAT(reported_area_,
-              ElementsAre(MatchingRectF(0.25f, 0.25f, 0.75f, 0.75f)));
+  EXPECT_EQ(on_update_call_count_, 1);
+  EXPECT_THAT(reported_visual_viewport_, MatchingRectF(0, 0, 200, 400));
+  EXPECT_THAT(reported_area_, ElementsAre(MatchingRectF(25, 25, 75, 75)));
+}
+
+TEST_F(ElementAreaTest, DontCallOnUpdateWhenViewportMissing) {
+  // Swallowing calls to OnGetVisualViewport guarantees that the viewport
+  // position will never be known.
+  EXPECT_CALL(mock_web_controller_, OnGetVisualViewport(_))
+      .WillOnce(DoNothing());
+  EXPECT_CALL(mock_web_controller_,
+              OnGetElementPosition(Eq(Selector({"#found"}).MustBeVisible()), _))
+      .WillOnce(RunOnceCallback<1>(true, RectF(25, 25, 75, 75)));
+
+  SetElement("#found");
+  EXPECT_EQ(on_update_call_count_, 0);
+}
+
+TEST_F(ElementAreaTest, CallOnUpdateWhenViewportMissingAndEmptyRect) {
+  EXPECT_CALL(mock_web_controller_, OnGetVisualViewport(_))
+      .WillRepeatedly(RunOnceCallback<0>(false, RectF()));
+
+  SetElement("#found");
+
+  // A newly empty element area should be reported.
+  on_update_call_count_ = 0;
+  element_area_.Clear();
+
+  EXPECT_EQ(on_update_call_count_, 1);
+  EXPECT_THAT(reported_visual_viewport_, EmptyRectF());
+  EXPECT_THAT(reported_area_, IsEmpty());
 }
 
 TEST_F(ElementAreaTest, TwoRectangles) {
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#top_left"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.0f, 0.25f, 0.25f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(0, 0, 25, 25)));
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#bottom_right"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.25f, 0.25f, 1.0f, 1.0f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(25, 25, 100, 100)));
 
   ElementAreaProto area_proto;
   area_proto.add_rectangles()->add_elements()->add_selectors("#top_left");
@@ -151,19 +202,19 @@
 
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.0f, 0.25f, 0.25f),
-                                      MatchingRectF(0.25f, 0.25f, 1.0f, 1.0f)));
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0, 0, 25, 25),
+                                      MatchingRectF(25, 25, 100, 100)));
 }
 
 TEST_F(ElementAreaTest, OneRectangleTwoElements) {
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.3f, 0.2f, 0.4f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(1, 3, 2, 4)));
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.5f, 0.2f, 0.6f, 0.5f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(5, 2, 6, 5)));
 
   ElementAreaProto area_proto;
   auto* rectangle_proto = area_proto.add_rectangles();
@@ -173,14 +224,14 @@
 
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.1f, 0.2f, 0.6f, 0.5f)));
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(1, 2, 6, 5)));
 }
 
 TEST_F(ElementAreaTest, DoNotReportIncompleteRectangles) {
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.3f, 0.2f, 0.4f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(1, 3, 2, 4)));
 
   // Getting the position of #element2 neither succeeds nor fails, simulating an
   // intermediate state which shouldn't be reported to the callback.
@@ -195,30 +246,30 @@
   rectangle_proto->add_elements()->add_selectors("#element2");
   element_area_.SetFromProto(area_proto);
 
-  EXPECT_THAT(reported_area_, ElementsAre(EmptyRectF()));
+  EXPECT_THAT(reported_area_, IsEmpty());
 
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.1f, 0.3f, 0.2f, 0.4f)));
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(1, 3, 2, 4)));
 }
 
 TEST_F(ElementAreaTest, OneRectangleFourElements) {
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.0f, 0.1f, 0.1f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(0, 0, 1, 1)));
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.9f, 0.9f, 1.0f, 1.0f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(9, 9, 100, 100)));
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element3"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.9f, 0.1f, 1.0f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(0, 9, 1, 100)));
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element4"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.9f, 0.0f, 1.0f, 0.1f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(9, 0, 100, 1)));
 
   ElementAreaProto area_proto;
   auto* rectangle_proto = area_proto.add_rectangles();
@@ -230,14 +281,14 @@
 
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.0f, 1.0f, 1.0f)));
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0, 0, 100, 100)));
 }
 
 TEST_F(ElementAreaTest, OneRectangleMissingElementsReported) {
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.1f, 0.2f, 0.2f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(1, 1, 2, 2)));
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
@@ -251,21 +302,22 @@
 
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.1f, 0.1f, 0.2f, 0.2f)));
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(1, 1, 2, 2)));
 
-  EXPECT_THAT(reported_area_,
-              ElementsAre(MatchingRectF(0.1f, 0.1f, 0.2f, 0.2f)));
+  EXPECT_THAT(reported_area_, ElementsAre(MatchingRectF(1, 1, 2, 2)));
 }
 
 TEST_F(ElementAreaTest, FullWidthRectangle) {
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element1"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.1f, 0.3f, 0.2f, 0.4f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(1, 3, 2, 4)));
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element2"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.5f, 0.7f, 0.6f, 0.8f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(5, 7, 6, 8)));
+  EXPECT_CALL(mock_web_controller_, OnGetVisualViewport(_))
+      .WillRepeatedly(RunOnceCallback<0>(true, RectF(100, 0, 200, 400)));
 
   ElementAreaProto area_proto;
   auto* rectangle_proto = area_proto.add_rectangles();
@@ -276,7 +328,10 @@
 
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.3f, 1.0f, 0.8f)));
+
+  // left and right of the box come from the visual viewport, top from the 1st
+  // element, bottom from the 2nd.
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(100, 3, 200, 8)));
 }
 
 TEST_F(ElementAreaTest, ElementMovesAfterUpdate) {
@@ -284,24 +339,25 @@
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.25f, 1.0f, 0.5f)))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.5f, 1.0f, 0.75f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(0, 25, 100, 50)))
+      .WillOnce(RunOnceCallback<1>(true, RectF(0, 50, 100, 75)));
 
   SetElement("#element");
 
-  EXPECT_THAT(reported_area_,
-              ElementsAre(MatchingRectF(0.0f, 0.25f, 1.0f, 0.5f)));
+  std::vector<RectF> original;
+  element_area_.GetRectangles(&original);
+  EXPECT_THAT(original, ElementsAre(MatchingRectF(0, 25, 100, 50)));
+  EXPECT_THAT(reported_area_, ElementsAre(MatchingRectF(0, 25, 100, 50)));
 
-  element_area_.UpdatePositions();
+  element_area_.Update();
 
   // Updated area is available
-  std::vector<RectF> rectangles;
-  element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
+  std::vector<RectF> updated;
+  element_area_.GetRectangles(&updated);
+  EXPECT_THAT(updated, ElementsAre(MatchingRectF(0, 50, 100, 75)));
 
   // Updated area is reported
-  EXPECT_THAT(reported_area_,
-              ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
+  EXPECT_THAT(reported_area_, ElementsAre(MatchingRectF(0, 50, 100, 75)));
 }
 
 TEST_F(ElementAreaTest, ElementMovesWithTime) {
@@ -309,13 +365,12 @@
   EXPECT_CALL(
       mock_web_controller_,
       OnGetElementPosition(Eq(Selector({"#element"}).MustBeVisible()), _))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.25f, 1.0f, 0.5f)))
-      .WillOnce(RunOnceCallback<1>(true, RectF(0.0f, 0.5f, 1.0f, 0.75f)));
+      .WillOnce(RunOnceCallback<1>(true, RectF(0, 25, 100, 50)))
+      .WillRepeatedly(RunOnceCallback<1>(true, RectF(0, 50, 100, 75)));
 
   SetElement("#element");
 
-  EXPECT_THAT(reported_area_,
-              ElementsAre(MatchingRectF(0.0f, 0.25f, 1.0f, 0.5f)));
+  EXPECT_THAT(reported_area_, ElementsAre(MatchingRectF(0, 25, 100, 50)));
 
   scoped_task_environment_.FastForwardBy(
       base::TimeDelta::FromMilliseconds(100));
@@ -323,11 +378,10 @@
   // Updated area is available
   std::vector<RectF> rectangles;
   element_area_.GetRectangles(&rectangles);
-  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
+  EXPECT_THAT(rectangles, ElementsAre(MatchingRectF(0, 50, 100, 75)));
 
   // Updated area is reported
-  EXPECT_THAT(reported_area_,
-              ElementsAre(MatchingRectF(0.0f, 0.5f, 1.0f, 0.75f)));
+  EXPECT_THAT(reported_area_, ElementsAre(MatchingRectF(0, 50, 100, 75)));
 }
 }  // namespace
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/fake_script_executor_delegate.cc b/components/autofill_assistant/browser/fake_script_executor_delegate.cc
index 1559ec5..ee6b0e7 100644
--- a/components/autofill_assistant/browser/fake_script_executor_delegate.cc
+++ b/components/autofill_assistant/browser/fake_script_executor_delegate.cc
@@ -93,10 +93,22 @@
   payment_request_options_ = std::move(options);
 }
 
-void FakeScriptExecutorDelegate::SetResizeViewport(bool resize_viewport) {}
+void FakeScriptExecutorDelegate::SetResizeViewport(bool resize_viewport) {
+  resize_viewport_ = resize_viewport;
+}
+
+bool FakeScriptExecutorDelegate::GetResizeViewport() {
+  return resize_viewport_;
+}
 
 void FakeScriptExecutorDelegate::SetPeekMode(
-    ConfigureBottomSheetProto::PeekMode peek_mode) {}
+    ConfigureBottomSheetProto::PeekMode peek_mode) {
+  peek_mode_ = peek_mode;
+}
+
+ConfigureBottomSheetProto::PeekMode FakeScriptExecutorDelegate::GetPeekMode() {
+  return peek_mode_;
+}
 
 bool FakeScriptExecutorDelegate::HasNavigationError() {
   return navigation_error_;
diff --git a/components/autofill_assistant/browser/fake_script_executor_delegate.h b/components/autofill_assistant/browser/fake_script_executor_delegate.h
index 671d27d..d9d766f 100644
--- a/components/autofill_assistant/browser/fake_script_executor_delegate.h
+++ b/components/autofill_assistant/browser/fake_script_executor_delegate.h
@@ -48,7 +48,9 @@
   void SetPaymentRequestOptions(
       std::unique_ptr<PaymentRequestOptions> options) override;
   void SetResizeViewport(bool resize_viewport) override;
+  bool GetResizeViewport() override;
   void SetPeekMode(ConfigureBottomSheetProto::PeekMode peek_mode) override;
+  ConfigureBottomSheetProto::PeekMode GetPeekMode() override;
   bool SetForm(std::unique_ptr<FormProto> form,
                base::RepeatingCallback<void(const FormProto::Result*)> callback)
       override;
@@ -113,6 +115,9 @@
   bool navigating_to_new_document_ = false;
   bool navigation_error_ = false;
   std::set<ScriptExecutorDelegate::Listener*> listeners_;
+  bool resize_viewport_ = false;
+  ConfigureBottomSheetProto::PeekMode peek_mode_ =
+      ConfigureBottomSheetProto::HANDLE;
 
   DISALLOW_COPY_AND_ASSIGN(FakeScriptExecutorDelegate);
 };
diff --git a/components/autofill_assistant/browser/mock_ui_controller.h b/components/autofill_assistant/browser/mock_ui_controller.h
index a40ea6f..71f9762 100644
--- a/components/autofill_assistant/browser/mock_ui_controller.h
+++ b/components/autofill_assistant/browser/mock_ui_controller.h
@@ -34,7 +34,8 @@
   MOCK_METHOD1(OnInfoBoxChanged, void(const InfoBox* info_box));
   MOCK_METHOD1(OnProgressChanged, void(int progress));
   MOCK_METHOD1(OnProgressVisibilityChanged, void(bool visible));
-  MOCK_METHOD1(OnTouchableAreaChanged, void(const std::vector<RectF>& areas));
+  MOCK_METHOD2(OnTouchableAreaChanged,
+               void(const RectF&, const std::vector<RectF>& areas));
   MOCK_CONST_METHOD0(Terminate, bool());
   MOCK_CONST_METHOD0(GetDropOutReason, Metrics::DropOutReason());
   MOCK_METHOD1(OnResizeViewportChanged, void(bool resize_viewport));
diff --git a/components/autofill_assistant/browser/mock_web_controller.h b/components/autofill_assistant/browser/mock_web_controller.h
index 115dcd3..a725fd8d 100644
--- a/components/autofill_assistant/browser/mock_web_controller.h
+++ b/components/autofill_assistant/browser/mock_web_controller.h
@@ -63,6 +63,13 @@
       void(const Selector& selector,
            base::OnceCallback<void(bool, const std::string&)>& callback));
 
+  void GetVisualViewport(
+      base::OnceCallback<void(bool, const RectF&)> callback) override {
+    OnGetVisualViewport(callback);
+  }
+  MOCK_METHOD1(OnGetVisualViewport,
+               void(base::OnceCallback<void(bool, const RectF&)>& callback));
+
   void GetElementPosition(
       const Selector& selector,
       base::OnceCallback<void(bool, const RectF&)> callback) override {
@@ -82,6 +89,14 @@
   }
 
   MOCK_METHOD0(ClearCookie, void());
+
+  void WaitForWindowHeightChange(
+      base::OnceCallback<void(const ClientStatus&)> callback) {
+    OnWaitForWindowHeightChange(callback);
+  }
+
+  MOCK_METHOD1(OnWaitForWindowHeightChange,
+               void(base::OnceCallback<void(const ClientStatus&)>& callback));
 };
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/script_executor.cc b/components/autofill_assistant/browser/script_executor.cc
index 06cee7c..678f3dd 100644
--- a/components/autofill_assistant/browser/script_executor.cc
+++ b/components/autofill_assistant/browser/script_executor.cc
@@ -456,11 +456,24 @@
   delegate_->SetResizeViewport(resize_viewport);
 }
 
+bool ScriptExecutor::GetResizeViewport() {
+  return delegate_->GetResizeViewport();
+}
+
 void ScriptExecutor::SetPeekMode(
     ConfigureBottomSheetProto::PeekMode peek_mode) {
   delegate_->SetPeekMode(peek_mode);
 }
 
+ConfigureBottomSheetProto::PeekMode ScriptExecutor::GetPeekMode() {
+  return delegate_->GetPeekMode();
+}
+
+void ScriptExecutor::WaitForWindowHeightChange(
+    base::OnceCallback<void(const ClientStatus&)> callback) {
+  delegate_->GetWebController()->WaitForWindowHeightChange(std::move(callback));
+}
+
 const ClientSettings& ScriptExecutor::GetSettings() {
   return delegate_->GetSettings();
 }
diff --git a/components/autofill_assistant/browser/script_executor.h b/components/autofill_assistant/browser/script_executor.h
index 0325853..f3bcedb2 100644
--- a/components/autofill_assistant/browser/script_executor.h
+++ b/components/autofill_assistant/browser/script_executor.h
@@ -179,7 +179,11 @@
   void SetProgress(int progress) override;
   void SetProgressVisible(bool visible) override;
   void SetResizeViewport(bool resize_viewport) override;
+  bool GetResizeViewport() override;
   void SetPeekMode(ConfigureBottomSheetProto::PeekMode peek_mode) override;
+  ConfigureBottomSheetProto::PeekMode GetPeekMode() override;
+  void WaitForWindowHeightChange(
+      base::OnceCallback<void(const ClientStatus&)> callback) override;
   const ClientSettings& GetSettings() override;
   bool SetForm(std::unique_ptr<FormProto> form,
                base::RepeatingCallback<void(const FormProto::Result*)> callback)
diff --git a/components/autofill_assistant/browser/script_executor_delegate.h b/components/autofill_assistant/browser/script_executor_delegate.h
index 7da41dd..70c8c6b 100644
--- a/components/autofill_assistant/browser/script_executor_delegate.h
+++ b/components/autofill_assistant/browser/script_executor_delegate.h
@@ -68,8 +68,10 @@
   virtual void SetProgress(int progress) = 0;
   virtual void SetProgressVisible(bool visible) = 0;
   virtual void SetChips(std::unique_ptr<std::vector<Chip>> chips) = 0;
+  virtual bool GetResizeViewport() = 0;
   virtual void SetResizeViewport(bool resize_viewport) = 0;
   virtual void SetPeekMode(ConfigureBottomSheetProto::PeekMode peek_mode) = 0;
+  virtual ConfigureBottomSheetProto::PeekMode GetPeekMode() = 0;
   virtual bool SetForm(
       std::unique_ptr<FormProto> form,
       base::RepeatingCallback<void(const FormProto::Result*)> callback) = 0;
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index 0d703458..e2cf26c1 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -1231,6 +1231,10 @@
   // resize_viewport is true or was set to true by a previous actions, the
   // viewport will be resized to match the new peek height.
   optional PeekMode peek_mode = 2;
+
+  // Maximum time to wait for the window to resize before continuing with the
+  // script. If 0 or unset, the action doesn't wait.
+  optional int32 resize_timeout_ms = 3;
 }
 
 // Allow scripts to display a form with multiple inputs.
diff --git a/components/autofill_assistant/browser/ui_controller.cc b/components/autofill_assistant/browser/ui_controller.cc
index c91e081e..a2819e78 100644
--- a/components/autofill_assistant/browser/ui_controller.cc
+++ b/components/autofill_assistant/browser/ui_controller.cc
@@ -25,7 +25,8 @@
 void UiController::OnInfoBoxChanged(const InfoBox* info_box) {}
 void UiController::OnProgressChanged(int progress) {}
 void UiController::OnProgressVisibilityChanged(bool visible) {}
-void UiController::OnTouchableAreaChanged(const std::vector<RectF>& areas) {}
+void UiController::OnTouchableAreaChanged(const RectF& visual_viewport,
+                                          const std::vector<RectF>& areas) {}
 void UiController::OnResizeViewportChanged(bool resize_viewport) {}
 void UiController::OnPeekModeChanged(
     ConfigureBottomSheetProto::PeekMode peek_mode) {}
diff --git a/components/autofill_assistant/browser/ui_controller.h b/components/autofill_assistant/browser/ui_controller.h
index 2050eef5..adb0342 100644
--- a/components/autofill_assistant/browser/ui_controller.h
+++ b/components/autofill_assistant/browser/ui_controller.h
@@ -76,10 +76,16 @@
   // Updates the area of the visible viewport that is accessible when the
   // overlay state is OverlayState::PARTIAL.
   //
+  // |visual_viewport| contains the position and size of the visual viewport in
+  // the layout viewport. It might be empty if not known or the touchable area
+  // is empty.
+  //
   // |rectangles| contains one element per configured rectangles, though these
-  // can correspond to empty rectangles. Coordinates are relative to the width
-  // or height of the visible viewport, as a number between 0 and 1.
-  virtual void OnTouchableAreaChanged(const std::vector<RectF>& rectangles);
+  // can correspond to empty rectangles.
+  //
+  // All rectangles are expressed in absolute CSS coordinates.
+  virtual void OnTouchableAreaChanged(const RectF& visual_viewport,
+                                      const std::vector<RectF>& rectangles);
 
   // Called when the viewport resize flag has changed.
   virtual void OnResizeViewportChanged(bool resize_viewport);
diff --git a/components/autofill_assistant/browser/ui_delegate.h b/components/autofill_assistant/browser/ui_delegate.h
index aa613620..288dd0fc 100644
--- a/components/autofill_assistant/browser/ui_delegate.h
+++ b/components/autofill_assistant/browser/ui_delegate.h
@@ -109,12 +109,16 @@
   //
   // At the end of this call, |rectangles| contains one element per configured
   // rectangles, though these can correspond to empty rectangles. Coordinates
-  // are relative to the width or height of the visible viewport, as a number
-  // between 0 and 1.
+  // absolute CSS coordinates.
   //
   // Note that the vector is not cleared before rectangles are added.
   virtual void GetTouchableArea(std::vector<RectF>* rectangles) const = 0;
 
+  // Returns the current size of the visual viewport. May be empty if unknown.
+  //
+  // The rectangle is expressed in absolute CSS coordinates.
+  virtual void GetVisualViewport(RectF* viewport) const = 0;
+
   // Reports a fatal error to Autofill Assistant, which should then stop.
   virtual void OnFatalError(const std::string& error_message,
                             Metrics::DropOutReason reason) = 0;
diff --git a/components/autofill_assistant/browser/web_controller.cc b/components/autofill_assistant/browser/web_controller.cc
index 03aca28..9f9963b 100644
--- a/components/autofill_assistant/browser/web_controller.cc
+++ b/components/autofill_assistant/browser/web_controller.cc
@@ -23,6 +23,7 @@
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill_assistant/browser/client_settings.h"
+#include "components/autofill_assistant/browser/client_status.h"
 #include "components/autofill_assistant/browser/rectf.h"
 #include "components/autofill_assistant/browser/string_conversions_util.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -46,11 +47,19 @@
 const char* const kGetBoundingClientRectAsList =
     R"(function(node) {
       const r = node.getBoundingClientRect();
-      const v = window.visualViewport;
-      return [r.left, r.top, r.right, r.bottom,
-              v.offsetLeft, v.offsetTop, v.width, v.height];
+      return [window.scrollX + r.left,
+              window.scrollY + r.top,
+              window.scrollX + r.right,
+              window.scrollY + r.bottom];
     })";
 
+const char* const kGetVisualViewport =
+    R"({ const v = window.visualViewport;
+         [v.pageLeft,
+          v.pageTop,
+          v.width,
+          v.height] })";
+
 const char* const kScrollIntoViewScript =
     R"(function(node) {
     node.scrollIntoViewIfNeeded();
@@ -189,6 +198,26 @@
       selector.click();
     })";
 
+// Javascript code that returns a promise that will succeed once the main
+// document window has changed height.
+//
+// This ignores width changes, to filter out resizes caused by changes to the
+// screen orientation.
+const char* const kWaitForWindowHeightChange = R"(
+new Promise((fulfill, reject) => {
+  var lastWidth = window.innerWidth;
+  var handler = function(event) {
+    if (window.innerWidth != lastWidth) {
+      lastWidth = window.innerWidth;
+      return
+    }
+    window.removeEventListener('resize', handler)
+    fulfill(true)
+  }
+  window.addEventListener('resize', handler)
+})
+)";
+
 bool ConvertPseudoType(const PseudoType pseudo_type,
                        dom::PseudoType* pseudo_type_output) {
   switch (pseudo_type) {
@@ -1141,6 +1170,24 @@
   std::move(callback).Run(status.ok());
 }
 
+void WebController::WaitForWindowHeightChange(
+    base::OnceCallback<void(const ClientStatus&)> callback) {
+  devtools_client_->GetRuntime()->Evaluate(
+      runtime::EvaluateParams::Builder()
+          .SetExpression(kWaitForWindowHeightChange)
+          .SetAwaitPromise(true)
+          .Build(),
+      base::BindOnce(&WebController::OnWaitForWindowHeightChange,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void WebController::OnWaitForWindowHeightChange(
+    base::OnceCallback<void(const ClientStatus&)> callback,
+    std::unique_ptr<runtime::EvaluateResult> result) {
+  std::move(callback).Run(
+      CheckJavaScriptResult(result.get(), __FILE__, __LINE__));
+}
+
 void WebController::FindElement(const Selector& selector,
                                 bool strict_mode,
                                 FindElementCallback callback) {
@@ -1739,6 +1786,47 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void WebController::GetVisualViewport(
+    base::OnceCallback<void(bool, const RectF&)> callback) {
+  devtools_client_->GetRuntime()->Evaluate(
+      runtime::EvaluateParams::Builder()
+          .SetExpression(std::string(kGetVisualViewport))
+          .SetReturnByValue(true)
+          .Build(),
+      base::BindOnce(&WebController::OnGetVisualViewport,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void WebController::OnGetVisualViewport(
+    base::OnceCallback<void(bool, const RectF&)> callback,
+    std::unique_ptr<runtime::EvaluateResult> result) {
+  ClientStatus status = CheckJavaScriptResult(result.get(), __FILE__, __LINE__);
+  if (!status.ok() || !result->GetResult()->HasValue() ||
+      !result->GetResult()->GetValue()->is_list() ||
+      result->GetResult()->GetValue()->GetList().size() != 4u) {
+    DVLOG(1) << __func__ << " Failed to get visual viewport: " << status;
+    RectF empty;
+    std::move(callback).Run(false, empty);
+    return;
+  }
+  const auto& list = result->GetResult()->GetValue()->GetList();
+  // Value::GetDouble() is safe to call without checking the value type; it'll
+  // return 0.0 if the value has the wrong type.
+
+  float left = static_cast<float>(list[0].GetDouble());
+  float top = static_cast<float>(list[1].GetDouble());
+  float width = static_cast<float>(list[2].GetDouble());
+  float height = static_cast<float>(list[3].GetDouble());
+
+  RectF rect;
+  rect.left = left;
+  rect.top = top;
+  rect.right = left + width;
+  rect.bottom = top + height;
+
+  std::move(callback).Run(true, rect);
+}
+
 void WebController::GetElementPosition(
     const Selector& selector,
     base::OnceCallback<void(bool, const RectF&)> callback) {
@@ -1776,9 +1864,10 @@
     base::OnceCallback<void(bool, const RectF&)> callback,
     std::unique_ptr<runtime::CallFunctionOnResult> result) {
   ClientStatus status = CheckJavaScriptResult(result.get(), __FILE__, __LINE__);
-  if (!status.ok() || !result->GetResult()->GetValue() ||
+  if (!status.ok() || !result->GetResult()->HasValue() ||
       !result->GetResult()->GetValue()->is_list() ||
-      result->GetResult()->GetValue()->GetList().size() != 8u) {
+      result->GetResult()->GetValue()->GetList().size() != 4u) {
+    DVLOG(2) << __func__ << " Failed to get element position: " << status;
     RectF empty;
     std::move(callback).Run(false, empty);
     return;
@@ -1787,22 +1876,11 @@
   // Value::GetDouble() is safe to call without checking the value type; it'll
   // return 0.0 if the value has the wrong type.
 
-  // getBoundingClientRect returns coordinates in the layout viewport. They need
-  // to be transformed into coordinates in the visual viewport, between 0 and 1.
-  float left_layout = static_cast<float>(list[0].GetDouble());
-  float top_layout = static_cast<float>(list[1].GetDouble());
-  float right_layout = static_cast<float>(list[2].GetDouble());
-  float bottom_layout = static_cast<float>(list[3].GetDouble());
-  float visual_left_offset = static_cast<float>(list[4].GetDouble());
-  float visual_top_offset = static_cast<float>(list[5].GetDouble());
-  float visual_w = static_cast<float>(list[6].GetDouble());
-  float visual_h = static_cast<float>(list[7].GetDouble());
-
   RectF rect;
-  rect.left = (left_layout - visual_left_offset) / visual_w;
-  rect.top = (top_layout - visual_top_offset) / visual_h;
-  rect.right = (right_layout - visual_left_offset) / visual_w;
-  rect.bottom = (bottom_layout - visual_top_offset) / visual_h;
+  rect.left = static_cast<float>(list[0].GetDouble());
+  rect.top = static_cast<float>(list[1].GetDouble());
+  rect.right = static_cast<float>(list[2].GetDouble());
+  rect.bottom = static_cast<float>(list[3].GetDouble());
 
   std::move(callback).Run(true, rect);
 }
diff --git a/components/autofill_assistant/browser/web_controller.h b/components/autofill_assistant/browser/web_controller.h
index 760fb4d3..e63a379 100644
--- a/components/autofill_assistant/browser/web_controller.h
+++ b/components/autofill_assistant/browser/web_controller.h
@@ -153,13 +153,18 @@
       base::OnceCallback<void(const ClientStatus&, const std::string&)>
           callback);
 
+  // Gets the visual viewport coordinates and size.
+  //
+  // The rectangle is expressed in absolute CSS coordinates.
+  virtual void GetVisualViewport(
+      base::OnceCallback<void(bool, const RectF&)> callback);
+
   // Gets the position of the element identified by the selector.
   //
   // If unsuccessful, the callback gets (false, 0, 0, 0, 0).
   //
   // If successful, the callback gets (true, left, top, right, bottom), with
-  // coordinates expressed as numbers between 0 and 1, relative to the width or
-  // height of the visible viewport.
+  // coordinates expressed in absolute CSS coordinates.
   virtual void GetElementPosition(
       const Selector& selector,
       base::OnceCallback<void(bool, const RectF&)> callback);
@@ -181,6 +186,10 @@
                             bool strict,
                             base::OnceCallback<void(bool)> callback);
 
+  // Calls the callback once the main document window has been resized.
+  virtual void WaitForWindowHeightChange(
+      base::OnceCallback<void(const ClientStatus&)> callback);
+
  private:
   friend class WebControllerBrowserTest;
 
@@ -285,6 +294,9 @@
   void OnFindElementForCheck(base::OnceCallback<void(bool)> callback,
                              const ClientStatus& status,
                              std::unique_ptr<FindElementResult> result);
+  void OnWaitForWindowHeightChange(
+      base::OnceCallback<void(const ClientStatus&)> callback,
+      std::unique_ptr<runtime::EvaluateResult> result);
 
   // Find the element given by |selector|. If multiple elements match
   // |selector| and if |strict_mode| is false, return the first one that is
@@ -400,6 +412,9 @@
       base::OnceCallback<void(bool, const RectF&)> callback,
       const ClientStatus& status,
       std::unique_ptr<FindElementResult> result);
+  void OnGetVisualViewport(
+      base::OnceCallback<void(bool, const RectF&)> callback,
+      std::unique_ptr<runtime::EvaluateResult> result);
   void OnGetElementPositionResult(
       base::OnceCallback<void(bool, const RectF&)> callback,
       std::unique_ptr<runtime::CallFunctionOnResult> result);
diff --git a/components/autofill_assistant/browser/web_controller_browsertest.cc b/components/autofill_assistant/browser/web_controller_browsertest.cc
index 4253e4e..e123e5aae 100644
--- a/components/autofill_assistant/browser/web_controller_browsertest.cc
+++ b/components/autofill_assistant/browser/web_controller_browsertest.cc
@@ -170,14 +170,14 @@
     ClientStatus result;
     web_controller_->SelectOption(
         selector, option,
-        base::BindOnce(&WebControllerBrowserTest::OnSelectOption,
+        base::BindOnce(&WebControllerBrowserTest::OnClientStatus,
                        base::Unretained(this), run_loop.QuitClosure(),
                        &result));
     run_loop.Run();
     return result;
   }
 
-  void OnSelectOption(base::Closure done_callback,
+  void OnClientStatus(base::Closure done_callback,
                       ClientStatus* result_output,
                       const ClientStatus& status) {
     *result_output = status;
@@ -188,20 +188,13 @@
     base::RunLoop run_loop;
     ClientStatus result;
     web_controller_->HighlightElement(
-        selector, base::BindOnce(&WebControllerBrowserTest::OnHighlightElement,
+        selector, base::BindOnce(&WebControllerBrowserTest::OnClientStatus,
                                  base::Unretained(this), run_loop.QuitClosure(),
                                  &result));
     run_loop.Run();
     return result;
   }
 
-  void OnHighlightElement(base::Closure done_callback,
-                          ClientStatus* result_output,
-                          const ClientStatus& status) {
-    *result_output = status;
-    std::move(done_callback).Run();
-  }
-
   ClientStatus GetOuterHtml(const Selector& selector,
                             std::string* html_output) {
     base::RunLoop run_loop;
@@ -1089,4 +1082,17 @@
   EXPECT_TRUE(SetCookie());
 }
 
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, WaitForHeightChange) {
+  base::RunLoop run_loop;
+  ClientStatus result;
+  web_controller_->WaitForWindowHeightChange(
+      base::BindOnce(&WebControllerBrowserTest::OnClientStatus,
+                     base::Unretained(this), run_loop.QuitClosure(), &result));
+
+  EXPECT_TRUE(
+      content::ExecJs(shell(), "window.dispatchEvent(new Event('resize'))"));
+  run_loop.Run();
+  EXPECT_EQ(ACTION_APPLIED, result.proto_status());
+}
+
 }  // namespace
diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc
index a6e358c781..1578f2e5 100644
--- a/components/bookmarks/browser/bookmark_model.cc
+++ b/components/bookmarks/browser/bookmark_model.cc
@@ -294,7 +294,7 @@
 
   BookmarkNode* mutable_old_parent = AsMutable(old_parent);
   std::unique_ptr<BookmarkNode> owned_node =
-      mutable_old_parent->Remove(AsMutable(node));
+      mutable_old_parent->Remove(old_index);
   BookmarkNode* mutable_new_parent = AsMutable(new_parent);
   mutable_new_parent->Add(std::move(owned_node), index);
 
diff --git a/components/bookmarks/browser/bookmark_model_unittest.cc b/components/bookmarks/browser/bookmark_model_unittest.cc
index 1a09c01c..24de891 100644
--- a/components/bookmarks/browser/bookmark_model_unittest.cc
+++ b/components/bookmarks/browser/bookmark_model_unittest.cc
@@ -1043,10 +1043,10 @@
 
   BookmarkNode* child1 = AsMutable(parent->GetChild(1));
   child1->SetTitle(ASCIIToUTF16("a"));
-  child1->Remove(child1->GetChild(0));
+  child1->Remove(0);
   BookmarkNode* child3 = AsMutable(parent->GetChild(3));
   child3->SetTitle(ASCIIToUTF16("C"));
-  child3->Remove(child3->GetChild(0));
+  child3->Remove(0);
 
   ClearCounts();
 
diff --git a/components/bookmarks/browser/url_index.cc b/components/bookmarks/browser/url_index.cc
index 39f2130..4b87500 100644
--- a/components/bookmarks/browser/url_index.cc
+++ b/components/bookmarks/browser/url_index.cc
@@ -41,7 +41,8 @@
       }
     }
   }
-  return node->parent()->Remove(node);
+  BookmarkNode* parent = node->parent();
+  return parent->Remove(parent->GetIndexOf(node));
 }
 
 void UrlIndex::SetUrl(BookmarkNode* node, const GURL& url) {
diff --git a/components/browser_sync/profile_sync_components_factory_impl.cc b/components/browser_sync/profile_sync_components_factory_impl.cc
index a4cf6436..2937ca96 100644
--- a/components/browser_sync/profile_sync_components_factory_impl.cc
+++ b/components/browser_sync/profile_sync_components_factory_impl.cc
@@ -282,17 +282,13 @@
           std::make_unique<SyncableServiceBasedModelTypeController>(
               syncer::FAVICON_IMAGES,
               sync_client_->GetModelTypeStoreService()->GetStoreFactory(),
-              base::BindOnce(&syncer::SyncClient::GetSyncableServiceForType,
-                             base::Unretained(sync_client_),
-                             syncer::FAVICON_IMAGES),
+              sync_client_->GetSyncableServiceForType(syncer::FAVICON_IMAGES),
               dump_stack));
       controllers.push_back(
           std::make_unique<SyncableServiceBasedModelTypeController>(
               syncer::FAVICON_TRACKING,
               sync_client_->GetModelTypeStoreService()->GetStoreFactory(),
-              base::BindOnce(&syncer::SyncClient::GetSyncableServiceForType,
-                             base::Unretained(sync_client_),
-                             syncer::FAVICON_TRACKING),
+              sync_client_->GetSyncableServiceForType(syncer::FAVICON_TRACKING),
               dump_stack));
     }
   }
@@ -317,8 +313,7 @@
         std::make_unique<SyncableServiceBasedModelTypeController>(
             syncer::PREFERENCES,
             sync_client_->GetModelTypeStoreService()->GetStoreFactory(),
-            base::BindOnce(&syncer::SyncClient::GetSyncableServiceForType,
-                           base::Unretained(sync_client_), syncer::PREFERENCES),
+            sync_client_->GetSyncableServiceForType(syncer::PREFERENCES),
             dump_stack));
   }
 
@@ -327,9 +322,8 @@
         std::make_unique<SyncableServiceBasedModelTypeController>(
             syncer::PRIORITY_PREFERENCES,
             sync_client_->GetModelTypeStoreService()->GetStoreFactory(),
-            base::BindOnce(&syncer::SyncClient::GetSyncableServiceForType,
-                           base::Unretained(sync_client_),
-                           syncer::PRIORITY_PREFERENCES),
+            sync_client_->GetSyncableServiceForType(
+                syncer::PRIORITY_PREFERENCES),
             dump_stack));
   }
 
diff --git a/components/cdm/browser/cdm_message_filter_android.cc b/components/cdm/browser/cdm_message_filter_android.cc
index ffcc775..73979875 100644
--- a/components/cdm/browser/cdm_message_filter_android.cc
+++ b/components/cdm/browser/cdm_message_filter_android.cc
@@ -119,7 +119,8 @@
   return handled;
 }
 
-base::TaskRunner* CdmMessageFilterAndroid::OverrideTaskRunnerForMessage(
+scoped_refptr<base::SequencedTaskRunner>
+CdmMessageFilterAndroid::OverrideTaskRunnerForMessage(
     const IPC::Message& message) {
   // Move the IPC handling to FILE thread as it is not very cheap.
   if (message.type() == ChromeViewHostMsg_QueryKeySystemSupport::ID)
diff --git a/components/cdm/browser/cdm_message_filter_android.h b/components/cdm/browser/cdm_message_filter_android.h
index 3c0e3b7..f5f3a16 100644
--- a/components/cdm/browser/cdm_message_filter_android.h
+++ b/components/cdm/browser/cdm_message_filter_android.h
@@ -31,7 +31,7 @@
 
   // BrowserMessageFilter implementation.
   bool OnMessageReceived(const IPC::Message& message) override;
-  base::TaskRunner* OverrideTaskRunnerForMessage(
+  scoped_refptr<base::SequencedTaskRunner> OverrideTaskRunnerForMessage(
       const IPC::Message& message) override;
 
   // Query the key system information.
diff --git a/components/favicon/core/favicon_request_handler.cc b/components/favicon/core/favicon_request_handler.cc
index b9fe07dc..6d18c49 100644
--- a/components/favicon/core/favicon_request_handler.cc
+++ b/components/favicon/core/favicon_request_handler.cc
@@ -79,8 +79,22 @@
 const bool kFallbackToHost = true;
 
 // Parameter used for local bitmap queries by page url.
-favicon_base::IconTypeSet GetIconTypesForLocalQuery() {
-  return favicon_base::IconTypeSet{favicon_base::IconType::kFavicon};
+favicon_base::IconTypeSet GetIconTypesForLocalQuery(
+    FaviconRequestPlatform platform) {
+  // The value must agree with the one written in the local data for retrieved
+  // server icons so that we can find them on the second lookup. This depends on
+  // whether the caller is a mobile UI or not.
+  switch (platform) {
+    case FaviconRequestPlatform::kMobile:
+      return favicon_base::IconTypeSet{
+          favicon_base::IconType::kFavicon, favicon_base::IconType::kTouchIcon,
+          favicon_base::IconType::kTouchPrecomposedIcon,
+          favicon_base::IconType::kWebManifestIcon};
+    case FaviconRequestPlatform::kDesktop:
+      return favicon_base::IconTypeSet{favicon_base::IconType::kFavicon};
+  }
+  NOTREACHED();
+  return favicon_base::IconTypeSet{};
 }
 
 bool CanOriginQueryGoogleServer(FaviconRequestOrigin origin) {
@@ -92,6 +106,7 @@
     case FaviconRequestOrigin::UNKNOWN:
       return false;
   }
+  NOTREACHED();
   return false;
 }
 
@@ -101,18 +116,16 @@
   return !icon_url.is_empty() ? icon_url : page_url;
 }
 
-}  // namespace
-
-// static
-bool FaviconRequestHandler::CanQueryGoogleServer(
-    LargeIconService* large_icon_service,
-    FaviconRequestOrigin origin,
-    bool can_send_history_data) {
+bool CanQueryGoogleServer(LargeIconService* large_icon_service,
+                          FaviconRequestOrigin origin,
+                          bool can_send_history_data) {
   return large_icon_service && CanOriginQueryGoogleServer(origin) &&
          can_send_history_data &&
          base::FeatureList::IsEnabled(kEnableHistoryFaviconsGoogleServerQuery);
 }
 
+}  // namespace
+
 FaviconRequestHandler::FaviconRequestHandler() {}
 
 FaviconRequestHandler::~FaviconRequestHandler() {}
@@ -122,6 +135,7 @@
     int desired_size_in_pixel,
     favicon_base::FaviconRawBitmapCallback callback,
     FaviconRequestOrigin request_origin,
+    FaviconRequestPlatform request_platform,
     FaviconService* favicon_service,
     LargeIconService* large_icon_service,
     const GURL& icon_url_for_uma,
@@ -137,14 +151,14 @@
 
   // First attempt to find the icon locally.
   favicon_service->GetRawFaviconForPageURL(
-      page_url, GetIconTypesForLocalQuery(), desired_size_in_pixel,
-      kFallbackToHost,
+      page_url, GetIconTypesForLocalQuery(request_platform),
+      desired_size_in_pixel, kFallbackToHost,
       base::BindOnce(&FaviconRequestHandler::OnBitmapLocalDataAvailable,
                      weak_ptr_factory_.GetWeakPtr(), page_url,
                      desired_size_in_pixel,
                      /*response_callback=*/std::move(callback), request_origin,
-                     favicon_service, large_icon_service, icon_url_for_uma,
-                     std::move(synced_favicon_getter),
+                     request_platform, favicon_service, large_icon_service,
+                     icon_url_for_uma, std::move(synced_favicon_getter),
                      CanQueryGoogleServer(large_icon_service, request_origin,
                                           can_send_history_data),
                      tracker),
@@ -187,6 +201,7 @@
     int desired_size_in_pixel,
     favicon_base::FaviconRawBitmapCallback response_callback,
     FaviconRequestOrigin origin,
+    FaviconRequestPlatform platform,
     FaviconService* favicon_service,
     LargeIconService* large_icon_service,
     const GURL& icon_url_for_uma,
@@ -207,8 +222,15 @@
     base::RepeatingCallback<void(const favicon_base::FaviconRawBitmapResult&)>
         repeating_response_callback =
             base::AdaptCallbackForRepeating(std::move(response_callback));
+    // TODO(victorvianna): Set |min_source_size_in_pixel| correctly.
+    std::unique_ptr<FaviconServerFetcherParams> server_parameters =
+        platform == FaviconRequestPlatform::kMobile
+            ? FaviconServerFetcherParams::CreateForMobile(
+                  page_url, /*min_source_size_in_pixel=*/1,
+                  desired_size_in_pixel)
+            : FaviconServerFetcherParams::CreateForDesktop(page_url);
     RequestFromGoogleServer(
-        page_url,
+        page_url, std::move(server_parameters),
         /*empty_response_callback=*/
         base::BindOnce(repeating_response_callback,
                        favicon_base::FaviconRawBitmapResult()),
@@ -216,8 +238,8 @@
         base::BindOnce(
             base::IgnoreResult(&FaviconService::GetRawFaviconForPageURL),
             base::Unretained(favicon_service), page_url,
-            GetIconTypesForLocalQuery(), desired_size_in_pixel, kFallbackToHost,
-            repeating_response_callback, tracker),
+            GetIconTypesForLocalQuery(platform), desired_size_in_pixel,
+            kFallbackToHost, repeating_response_callback, tracker),
         large_icon_service, icon_url_for_uma, origin);
     return;
   }
@@ -262,8 +284,10 @@
     base::RepeatingCallback<void(const favicon_base::FaviconImageResult&)>
         repeating_response_callback =
             base::AdaptCallbackForRepeating(std::move(response_callback));
+    // We use CreateForDesktop because GetFaviconImageForPageURL is only called
+    // by desktop.
     RequestFromGoogleServer(
-        page_url,
+        page_url, FaviconServerFetcherParams::CreateForDesktop(page_url),
         /*empty_response_callback=*/
         base::BindOnce(repeating_response_callback,
                        favicon_base::FaviconImageResult()),
@@ -296,6 +320,7 @@
 
 void FaviconRequestHandler::RequestFromGoogleServer(
     const GURL& page_url,
+    std::unique_ptr<FaviconServerFetcherParams> server_parameters,
     base::OnceClosure empty_response_callback,
     base::OnceClosure local_lookup_callback,
     LargeIconService* large_icon_service,
@@ -345,7 +370,7 @@
       /* default_value= */ false);
   large_icon_service
       ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
-          FaviconServerFetcherParams::CreateForDesktop(page_url),
+          std::move(server_parameters),
           /*may_page_url_be_private=*/true, should_trim_url_path,
           traffic_annotation,
           base::BindOnce(&FaviconRequestHandler::OnGoogleServerDataAvailable,
diff --git a/components/favicon/core/favicon_request_handler.h b/components/favicon/core/favicon_request_handler.h
index 1833ef40..27d5e2cc 100644
--- a/components/favicon/core/favicon_request_handler.h
+++ b/components/favicon/core/favicon_request_handler.h
@@ -16,16 +16,18 @@
 
 namespace favicon {
 
+class FaviconServerFetcherParams;
 class FaviconService;
 class LargeIconService;
 
 // The UI origin of an icon request.
+// TODO(victorvianna): Rename to agree with the naming style of the other enums.
 enum class FaviconRequestOrigin {
   // Unknown origin.
   UNKNOWN,
-  // chrome://history.
+  // History page.
   HISTORY,
-  // chrome://history/syncedTabs.
+  // History synced tabs page (desktop only).
   HISTORY_SYNCED_TABS,
   // Recently closed tabs menu.
   RECENTLY_CLOSED_TABS,
@@ -42,6 +44,12 @@
   kMaxValue = kNotAvailable,
 };
 
+// Platform making the request.
+enum class FaviconRequestPlatform {
+  kMobile,
+  kDesktop,
+};
+
 // Class for handling favicon requests by page url, forwarding them to local
 // storage, sync or Google server accordingly.
 // TODO(victorvianna): Refactor LargeIconService to avoid having to pass both
@@ -69,10 +77,12 @@
   // that history sync is enabled and no custom passphrase is set).
   // If a non-empty |icon_url_for_uma| (optional) is passed, it will be used to
   // record UMA about the grouping of requests to the favicon server.
+  // |request_platform| specifies whether the caller is mobile or desktop code.
   void GetRawFaviconForPageURL(const GURL& page_url,
                                int desired_size_in_pixel,
                                favicon_base::FaviconRawBitmapCallback callback,
                                FaviconRequestOrigin request_origin,
+                               FaviconRequestPlatform request_platform,
                                FaviconService* favicon_service,
                                LargeIconService* large_icon_service,
                                const GURL& icon_url_for_uma,
@@ -88,6 +98,7 @@
   // that history sync is enabled and no custom passphrase is set).
   // If a non-empty |icon_url_for_uma| (optional) is passed, it will be used to
   // record UMA about the grouping of requests to the favicon server.
+  // This method is only called by desktop code.
   void GetFaviconImageForPageURL(const GURL& page_url,
                                  favicon_base::FaviconImageCallback callback,
                                  FaviconRequestOrigin request_origin,
@@ -99,10 +110,6 @@
                                  base::CancelableTaskTracker* tracker);
 
  private:
-  static bool CanQueryGoogleServer(LargeIconService* large_icon_service,
-                                   FaviconRequestOrigin origin,
-                                   bool can_send_history_data);
-
   // Called after the first attempt to retrieve the icon bitmap from local
   // storage. If request succeeded, sends the result. Otherwise attempts to
   // retrieve from sync or the Google favicon server depending whether
@@ -112,6 +119,7 @@
       int desired_size_in_pixel,
       favicon_base::FaviconRawBitmapCallback response_callback,
       FaviconRequestOrigin origin,
+      FaviconRequestPlatform platform,
       FaviconService* favicon_service,
       LargeIconService* large_icon_service,
       const GURL& icon_url_for_uma,
@@ -139,12 +147,14 @@
   // Requests an icon from Google favicon server. Since requests work by
   // populating local storage, a |local_lookup_callback| will be needed in case
   // of success and an |empty_response_callback| in case of failure.
-  void RequestFromGoogleServer(const GURL& page_url,
-                               base::OnceClosure empty_response_callback,
-                               base::OnceClosure local_lookup_callback,
-                               LargeIconService* large_icon_service,
-                               const GURL& icon_url_for_uma,
-                               FaviconRequestOrigin origin);
+  void RequestFromGoogleServer(
+      const GURL& page_url,
+      std::unique_ptr<FaviconServerFetcherParams> server_parameters,
+      base::OnceClosure empty_response_callback,
+      base::OnceClosure local_lookup_callback,
+      LargeIconService* large_icon_service,
+      const GURL& icon_url_for_uma,
+      FaviconRequestOrigin origin);
 
   // Called once the request to the favicon server has finished. If the request
   // succeeded, |local_lookup_callback| is called to effectively retrieve the
diff --git a/components/favicon/core/favicon_request_handler_unittest.cc b/components/favicon/core/favicon_request_handler_unittest.cc
index c83f832..fdbcd404 100644
--- a/components/favicon/core/favicon_request_handler_unittest.cc
+++ b/components/favicon/core/favicon_request_handler_unittest.cc
@@ -29,6 +29,8 @@
 
 const char kDummyPageUrl[] = "https://www.example.com";
 const int kDesiredSizeInPixel = 16;
+// TODO(victorvianna): Add unit tests specific for mobile.
+const FaviconRequestPlatform kDummyPlatform = FaviconRequestPlatform::kDesktop;
 const SkColor kTestColor = SK_ColorRED;
 base::CancelableTaskTracker::TaskId kDummyTaskId = 1;
 
@@ -136,7 +138,7 @@
   favicon_request_handler_.GetRawFaviconForPageURL(
       GURL(kDummyPageUrl), kDesiredSizeInPixel,
       base::BindOnce(&StoreBitmap, &result), FaviconRequestOrigin::UNKNOWN,
-      &mock_favicon_service_, &mock_large_icon_service_,
+      kDummyPlatform, &mock_favicon_service_, &mock_large_icon_service_,
       /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
       /*can_send_history_data=*/true, &tracker_);
   EXPECT_FALSE(result.is_valid());
@@ -161,7 +163,7 @@
   favicon_request_handler_.GetRawFaviconForPageURL(
       GURL(kDummyPageUrl), kDesiredSizeInPixel,
       base::BindOnce(&StoreBitmap, &result), FaviconRequestOrigin::UNKNOWN,
-      &mock_favicon_service_, &mock_large_icon_service_,
+      kDummyPlatform, &mock_favicon_service_, &mock_large_icon_service_,
       /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
       /*can_send_history_data=*/true, &tracker_);
   EXPECT_TRUE(result.is_valid());
@@ -185,7 +187,7 @@
   favicon_request_handler_.GetRawFaviconForPageURL(
       GURL(kDummyPageUrl), kDesiredSizeInPixel,
       base::BindOnce(&StoreBitmap, &result), FaviconRequestOrigin::UNKNOWN,
-      &mock_favicon_service_, &mock_large_icon_service_,
+      kDummyPlatform, &mock_favicon_service_, &mock_large_icon_service_,
       /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
       /*can_send_history_data=*/true, &tracker_);
   EXPECT_TRUE(result.is_valid());
@@ -223,7 +225,7 @@
   favicon_request_handler_.GetRawFaviconForPageURL(
       GURL(kDummyPageUrl), kDesiredSizeInPixel,
       base::BindOnce(&StoreBitmap, &result), FaviconRequestOrigin::HISTORY,
-      &mock_favicon_service_, &mock_large_icon_service_,
+      kDummyPlatform, &mock_favicon_service_, &mock_large_icon_service_,
       /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
       /*can_send_history_data=*/true, &tracker_);
   EXPECT_TRUE(result.is_valid());
@@ -259,7 +261,7 @@
   favicon_request_handler_.GetRawFaviconForPageURL(
       GURL(kDummyPageUrl), kDesiredSizeInPixel,
       base::BindOnce(&StoreBitmap, &result), FaviconRequestOrigin::HISTORY,
-      &mock_favicon_service_, &mock_large_icon_service_,
+      kDummyPlatform, &mock_favicon_service_, &mock_large_icon_service_,
       /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
       /*can_send_history_data=*/true, &tracker_);
   EXPECT_TRUE(result.is_valid());
@@ -284,8 +286,9 @@
   favicon_request_handler_.GetFaviconImageForPageURL(
       GURL(kDummyPageUrl), base::BindOnce(&StoreImage, &result),
       FaviconRequestOrigin::UNKNOWN, &mock_favicon_service_,
-      &mock_large_icon_service_, /*icon_url_for_uma=*/GURL(),
-      synced_favicon_getter_.Get(), /*can_send_history_data=*/true, &tracker_);
+      &mock_large_icon_service_,
+      /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
+      /*can_send_history_data=*/true, &tracker_);
   EXPECT_TRUE(result.image.IsEmpty());
   histogram_tester_.ExpectUniqueSample("Sync.FaviconAvailability.UNKNOWN",
                                        FaviconAvailability::kNotAvailable, 1);
@@ -306,8 +309,9 @@
   favicon_request_handler_.GetFaviconImageForPageURL(
       GURL(kDummyPageUrl), base::BindOnce(&StoreImage, &result),
       FaviconRequestOrigin::UNKNOWN, &mock_favicon_service_,
-      &mock_large_icon_service_, /*icon_url_for_uma=*/GURL(),
-      synced_favicon_getter_.Get(), /*can_send_history_data=*/true, &tracker_);
+      &mock_large_icon_service_,
+      /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
+      /*can_send_history_data=*/true, &tracker_);
   EXPECT_FALSE(result.image.IsEmpty());
   histogram_tester_.ExpectUniqueSample("Sync.FaviconAvailability.UNKNOWN",
                                        FaviconAvailability::kSync, 1);
@@ -327,8 +331,9 @@
   favicon_request_handler_.GetFaviconImageForPageURL(
       GURL(kDummyPageUrl), base::BindOnce(&StoreImage, &result),
       FaviconRequestOrigin::UNKNOWN, &mock_favicon_service_,
-      &mock_large_icon_service_, /*icon_url_for_uma=*/GURL(),
-      synced_favicon_getter_.Get(), /*can_send_history_data=*/true, &tracker_);
+      &mock_large_icon_service_,
+      /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
+      /*can_send_history_data=*/true, &tracker_);
   EXPECT_FALSE(result.image.IsEmpty());
   histogram_tester_.ExpectUniqueSample("Sync.FaviconAvailability.UNKNOWN",
                                        FaviconAvailability::kLocal, 1);
@@ -424,7 +429,7 @@
   favicon_request_handler_.GetRawFaviconForPageURL(
       GURL(kDummyPageUrl), kDesiredSizeInPixel,
       base::BindOnce(&StoreBitmap, &result), FaviconRequestOrigin::HISTORY,
-      &mock_favicon_service_, &mock_large_icon_service_,
+      kDummyPlatform, &mock_favicon_service_, &mock_large_icon_service_,
       /*icon_url_for_uma=*/GURL(), synced_favicon_getter_.Get(),
       /*can_send_history_data=*/false, &tracker_);
 }
diff --git a/components/history/core/browser/sync/history_delete_directives_model_type_controller.cc b/components/history/core/browser/sync/history_delete_directives_model_type_controller.cc
index 38b3467..8955d0f 100644
--- a/components/history/core/browser/sync/history_delete_directives_model_type_controller.cc
+++ b/components/history/core/browser/sync/history_delete_directives_model_type_controller.cc
@@ -23,9 +23,8 @@
     : SyncableServiceBasedModelTypeController(
           syncer::HISTORY_DELETE_DIRECTIVES,
           model_type_store_service->GetStoreFactory(),
-          base::BindOnce(&syncer::SyncClient::GetSyncableServiceForType,
-                         base::Unretained(sync_client),
-                         syncer::HISTORY_DELETE_DIRECTIVES),
+          sync_client->GetSyncableServiceForType(
+              syncer::HISTORY_DELETE_DIRECTIVES),
           dump_stack),
       sync_service_(sync_service) {}
 
diff --git a/components/signin/core/browser/gaia_cookie_manager_service.cc b/components/signin/core/browser/gaia_cookie_manager_service.cc
index 50df29d..af75bff 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service.cc
+++ b/components/signin/core/browser/gaia_cookie_manager_service.cc
@@ -653,14 +653,6 @@
   }
 }
 
-void GaiaCookieManagerService::AddObserver(Observer* observer) {
-  observer_list_.AddObserver(observer);
-}
-
-void GaiaCookieManagerService::RemoveObserver(Observer* observer) {
-  observer_list_.RemoveObserver(observer);
-}
-
 void GaiaCookieManagerService::CancelAll() {
   VLOG(1) << "GaiaCookieManagerService::CancelAll";
   gaia_auth_fetcher_.reset();
@@ -694,8 +686,8 @@
 
   if (cause == network::mojom::CookieChangeCause::EXPLICIT) {
     DCHECK(net::CookieChangeCauseIsDeletion(net::CookieChangeCause::EXPLICIT));
-    for (auto& observer : observer_list_) {
-      observer.OnGaiaCookieDeletedByUserAction();
+    if (gaia_cookie_deleted_by_user_action_callback_) {
+      gaia_cookie_deleted_by_user_action_callback_.Run();
     }
   }
 
@@ -743,6 +735,18 @@
   requests_.front().RunSetAccountsInCookieCompletedCallback(result);
 }
 
+void GaiaCookieManagerService::SetGaiaAccountsInCookieUpdatedCallback(
+    GaiaAccountsInCookieUpdatedCallback callback) {
+  DCHECK(!gaia_accounts_updated_in_cookie_callback_);
+  gaia_accounts_updated_in_cookie_callback_ = std::move(callback);
+}
+
+void GaiaCookieManagerService::SetGaiaCookieDeletedByUserActionCallback(
+    GaiaCookieDeletedByUserActionCallback callback) {
+  DCHECK(!gaia_cookie_deleted_by_user_action_callback_);
+  gaia_cookie_deleted_by_user_action_callback_ = std::move(callback);
+}
+
 void GaiaCookieManagerService::OnUbertokenFetchComplete(
     GoogleServiceAuthError error,
     const std::string& uber_token) {
@@ -851,8 +855,8 @@
   // services, in response to OnGaiaAccountsInCookieUpdated, may try in return
   // to call ListAccounts, which would immediately return false if the
   // ListAccounts request is still sitting in queue.
-  for (auto& observer : observer_list_) {
-    observer.OnGaiaAccountsInCookieUpdated(
+  if (gaia_accounts_updated_in_cookie_callback_) {
+    gaia_accounts_updated_in_cookie_callback_.Run(
         listed_accounts_, signed_out_accounts_,
         GoogleServiceAuthError(GoogleServiceAuthError::NONE));
   }
@@ -880,10 +884,12 @@
   }
 
   RecordListAccountsFailure(error.state());
-  for (auto& observer : observer_list_) {
-    observer.OnGaiaAccountsInCookieUpdated(listed_accounts_,
-                                           signed_out_accounts_, error);
+
+  if (gaia_accounts_updated_in_cookie_callback_) {
+    gaia_accounts_updated_in_cookie_callback_.Run(listed_accounts_,
+                                                  signed_out_accounts_, error);
   }
+
   HandleNextRequest();
 }
 
diff --git a/components/signin/core/browser/gaia_cookie_manager_service.h b/components/signin/core/browser/gaia_cookie_manager_service.h
index 13aeee1..626d1888 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service.h
+++ b/components/signin/core/browser/gaia_cookie_manager_service.h
@@ -86,6 +86,12 @@
                                   const GoogleServiceAuthError&)>
       AddAccountToCookieCompletedCallback;
 
+  typedef base::RepeatingCallback<void(const std::vector<gaia::ListedAccount>&,
+                                       const std::vector<gaia::ListedAccount>&,
+                                       const GoogleServiceAuthError&)>
+      GaiaAccountsInCookieUpdatedCallback;
+  typedef base::RepeatingCallback<void()> GaiaCookieDeletedByUserActionCallback;
+
   // Contains the information and parameters for any request.
   class GaiaCookieRequest {
    public:
@@ -143,28 +149,6 @@
     DISALLOW_COPY_AND_ASSIGN(GaiaCookieRequest);
   };
 
-  class Observer {
-   public:
-    // 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
-    // to ListAccounts(). The GCMS will delay calling ListAccounts if other
-    // requests are in queue that would modify the APISID cookie.
-    // If the ListAccounts call fails and the GCMS cannot recover, the reason
-    // is passed in |error|.
-    virtual void OnGaiaAccountsInCookieUpdated(
-        const std::vector<gaia::ListedAccount>& accounts,
-        const std::vector<gaia::ListedAccount>& signed_out_accounts,
-        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 OnGaiaCookieDeletedByUserAction() {}
-
-   protected:
-    virtual ~Observer() {}
-  };
-
   // Class to retrieve the external connection check results from gaia.
   // Declared publicly for unit tests.
   class ExternalCcResultFetcher : public GaiaAuthConsumer {
@@ -269,10 +253,6 @@
   // service. Virtual for testing.
   virtual void ForceOnCookieChangeProcessing();
 
-  // Add or remove observers of this helper.
-  void AddObserver(Observer* observer);
-  void RemoveObserver(Observer* observer);
-
   // Cancel all login requests.
   void CancelAll();
 
@@ -294,6 +274,25 @@
     list_accounts_stale_ = stale;
   }
 
+  // If set, this callback will be invoked 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 to
+  // ListAccounts(). The GCMS will delay calling ListAccounts if other
+  // requests are in queue that would modify the APISID cookie.
+  // If the ListAccounts call fails and the GCMS cannot recover, the reason
+  // is passed in |error|.
+  // This method can only be called once.
+  void SetGaiaAccountsInCookieUpdatedCallback(
+      GaiaAccountsInCookieUpdatedCallback callback);
+
+  // If set, this callback will be invoked whenever the Gaia cookie has
+  // been deleted explicitly by a user action, e.g. from the settings or by an
+  // extension.
+  // This method can only be called once.
+  void SetGaiaCookieDeletedByUserActionCallback(
+      GaiaCookieDeletedByUserActionCallback callback);
+
   // Returns a non-NULL pointer to its instance of net::BackoffEntry
   const net::BackoffEntry* GetBackoffEntry() { return &fetcher_backoff_; }
 
@@ -360,6 +359,11 @@
   OAuth2TokenService* token_service_;
   SigninClient* signin_client_;
 
+  GaiaAccountsInCookieUpdatedCallback gaia_accounts_updated_in_cookie_callback_;
+  GaiaCookieDeletedByUserActionCallback
+      gaia_cookie_deleted_by_user_action_callback_;
+  base::RepeatingCallback<scoped_refptr<network::SharedURLLoaderFactory>()>
+      shared_url_loader_factory_getter_;
   std::unique_ptr<GaiaAuthFetcher> gaia_auth_fetcher_;
   std::unique_ptr<signin::UbertokenFetcherImpl> uber_token_fetcher_;
   ExternalCcResultFetcher external_cc_result_fetcher_;
@@ -385,10 +389,6 @@
   // executed at a time.
   base::circular_deque<GaiaCookieRequest> requests_;
 
-  // List of observers to notify when merge session completes.
-  // Makes sure list is empty on destruction.
-  base::ObserverList<Observer, true>::Unchecked observer_list_;
-
   // True once the ExternalCCResultFetcher has completed once.
   bool external_cc_result_fetched_;
 
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 80c2108c..5a27c8f 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
+++ b/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
@@ -38,21 +38,19 @@
 using MockAddAccountToCookieCompletedCallback = base::MockCallback<
     GaiaCookieManagerService::AddAccountToCookieCompletedCallback>;
 
-class MockObserver : public GaiaCookieManagerService::Observer {
+class MockObserver {
  public:
-  explicit MockObserver(GaiaCookieManagerService* helper) : helper_(helper) {
-    helper_->AddObserver(this);
+  explicit MockObserver(GaiaCookieManagerService* helper) {
+    helper->SetGaiaAccountsInCookieUpdatedCallback(base::BindRepeating(
+        &MockObserver::OnGaiaAccountsInCookieUpdated, base::Unretained(this)));
   }
 
-  ~MockObserver() override { helper_->RemoveObserver(this); }
-
   MOCK_METHOD3(OnGaiaAccountsInCookieUpdated,
                void(const std::vector<gaia::ListedAccount>&,
                     const std::vector<gaia::ListedAccount>&,
                     const GoogleServiceAuthError&));
 
  private:
-  GaiaCookieManagerService* helper_;
 
   DISALLOW_COPY_AND_ASSIGN(MockObserver);
 };
diff --git a/components/signin/core/browser/identity_manager_wrapper.cc b/components/signin/core/browser/identity_manager_wrapper.cc
index 2025ebc..fab2d43 100644
--- a/components/signin/core/browser/identity_manager_wrapper.cc
+++ b/components/signin/core/browser/identity_manager_wrapper.cc
@@ -5,6 +5,7 @@
 #include "components/signin/core/browser/identity_manager_wrapper.h"
 
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "services/identity/public/cpp/accounts_cookie_mutator.h"
 #include "services/identity/public/cpp/accounts_mutator.h"
 #include "services/identity/public/cpp/diagnostics_provider.h"
diff --git a/components/signin/core/browser/signin_manager.cc b/components/signin/core/browser/signin_manager.cc
index 9de36b3..8f8a1100 100644
--- a/components/signin/core/browser/signin_manager.cc
+++ b/components/signin/core/browser/signin_manager.cc
@@ -27,12 +27,10 @@
     SigninClient* client,
     ProfileOAuth2TokenService* token_service,
     AccountTrackerService* account_tracker_service,
-    GaiaCookieManagerService* cookie_manager_service,
     signin::AccountConsistencyMethod account_consistency)
     : SigninManagerBase(client,
                         token_service,
                         account_tracker_service,
-                        cookie_manager_service,
                         account_consistency),
       weak_pointer_factory_(this) {}
 
@@ -122,34 +120,6 @@
   return identity::IsUsernameAllowedByPattern(username, pattern);
 }
 
-void SigninManager::SignIn(const std::string& username) {
-  AccountInfo info =
-      account_tracker_service()->FindAccountInfoByEmail(username);
-  DCHECK(!info.gaia.empty());
-  DCHECK(!info.email.empty());
-
-  bool reauth_in_progress = IsAuthenticated();
-
-  signin_client()->GetPrefs()->SetInt64(
-      prefs::kSignedInTime,
-      base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
-
-  SetAuthenticatedAccountInfo(info.gaia, info.email);
-
-  if (!reauth_in_progress)
-    FireGoogleSigninSucceeded();
-
-  signin_metrics::LogSigninProfile(signin_client()->IsFirstRun(),
-                                   signin_client()->GetInstallDate());
-}
-
-void SigninManager::FireGoogleSigninSucceeded() {
-  const AccountInfo account_info = GetAuthenticatedAccountInfo();
-  if (observer_ != nullptr) {
-    observer_->GoogleSigninSucceeded(account_info);
-  }
-}
-
 void SigninManager::OnRefreshTokensLoaded() {
   token_service()->RemoveObserver(this);
 
diff --git a/components/signin/core/browser/signin_manager.h b/components/signin/core/browser/signin_manager.h
index 0b31e088..dfa3992a 100644
--- a/components/signin/core/browser/signin_manager.h
+++ b/components/signin/core/browser/signin_manager.h
@@ -56,7 +56,6 @@
   SigninManager(SigninClient* client,
                 ProfileOAuth2TokenService* token_service,
                 AccountTrackerService* account_tracker_service,
-                GaiaCookieManagerService* cookie_manager_service,
                 signin::AccountConsistencyMethod account_consistency);
   ~SigninManager() override;
 
@@ -71,10 +70,6 @@
   void FinalizeInitBeforeLoadingRefreshTokens(
       PrefService* local_state) override;
 
-  // Signs a user in. SigninManager assumes that |username| can be used to look
-  // up the corresponding account_id and gaia_id for this email.
-  void SignIn(const std::string& username);
-
  private:
   friend class identity::IdentityManager;
   FRIEND_TEST_ALL_PREFIXES(SigninManagerTest, Prohibited);
@@ -83,9 +78,6 @@
   // Returns true if a signin to Chrome is allowed (by policy or pref).
   bool IsSigninAllowed() const;
 
-  // Send all observers |GoogleSigninSucceeded| notifications.
-  void FireGoogleSigninSucceeded();
-
   // OAuth2TokenService::Observer:
   void OnRefreshTokensLoaded() override;
 
diff --git a/components/signin/core/browser/signin_manager_base.cc b/components/signin/core/browser/signin_manager_base.cc
index 37ee807..be56c5d 100644
--- a/components/signin/core/browser/signin_manager_base.cc
+++ b/components/signin/core/browser/signin_manager_base.cc
@@ -17,7 +17,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_info.h"
 #include "components/signin/core/browser/account_tracker_service.h"
-#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_client.h"
 #include "components/signin/core/browser/signin_pref_names.h"
@@ -30,7 +29,6 @@
     SigninClient* client,
     ProfileOAuth2TokenService* token_service,
     AccountTrackerService* account_tracker_service,
-    GaiaCookieManagerService* cookie_manager_service,
     signin::AccountConsistencyMethod account_consistency)
     : client_(client),
       token_service_(token_service),
@@ -251,6 +249,27 @@
 }
 
 #if !defined(OS_CHROMEOS)
+void SigninManagerBase::SignIn(const std::string& username) {
+  AccountInfo info =
+      account_tracker_service()->FindAccountInfoByEmail(username);
+  DCHECK(!info.gaia.empty());
+  DCHECK(!info.email.empty());
+
+  bool reauth_in_progress = IsAuthenticated();
+
+  signin_client()->GetPrefs()->SetInt64(
+      prefs::kSignedInTime,
+      base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
+
+  SetAuthenticatedAccountInfo(info.gaia, info.email);
+
+  if (!reauth_in_progress && observer_ != nullptr)
+    observer_->GoogleSigninSucceeded(GetAuthenticatedAccountInfo());
+
+  signin_metrics::LogSigninProfile(signin_client()->IsFirstRun(),
+                                   signin_client()->GetInstallDate());
+}
+
 void SigninManagerBase::SignOut(
     signin_metrics::ProfileSignout signout_source_metric,
     signin_metrics::SignoutDelete signout_delete_metric) {
diff --git a/components/signin/core/browser/signin_manager_base.h b/components/signin/core/browser/signin_manager_base.h
index 9297ca09..6ae447f6 100644
--- a/components/signin/core/browser/signin_manager_base.h
+++ b/components/signin/core/browser/signin_manager_base.h
@@ -39,7 +39,6 @@
 #include "google_apis/gaia/google_service_auth_error.h"
 
 class AccountTrackerService;
-class GaiaCookieManagerService;
 class PrefRegistrySimple;
 class PrefService;
 class ProfileOAuth2TokenService;
@@ -85,7 +84,6 @@
   SigninManagerBase(SigninClient* client,
                     ProfileOAuth2TokenService* token_service,
                     AccountTrackerService* account_tracker_service,
-                    GaiaCookieManagerService* cookie_manager_service,
                     signin::AccountConsistencyMethod account_consistency);
 #if !defined(OS_CHROMEOS)
  public:
@@ -147,6 +145,15 @@
   void SetObserver(Observer* observer);
   void ClearObserver();
 
+  // Signin API surfaces. Not used on ChromeOS.
+  // TODO(https://crbug.com/814787): Go through this API to set the primary
+  // account on ChromeOS.
+#if !defined(OS_CHROMEOS)
+  // Signs a user in. SigninManager assumes that |username| can be used to look
+  // up the corresponding account_id and gaia_id for this email.
+  void SignIn(const std::string& username);
+#endif
+
   // Signout API surfaces (not supported on ChromeOS, where signout is not
   // permitted).
 #if !defined(OS_CHROMEOS)
diff --git a/components/signin/core/browser/signin_manager_unittest.cc b/components/signin/core/browser/signin_manager_unittest.cc
index f6f77f7..c3ca8c9 100644
--- a/components/signin/core/browser/signin_manager_unittest.cc
+++ b/components/signin/core/browser/signin_manager_unittest.cc
@@ -23,7 +23,6 @@
 #include "components/signin/core/browser/account_fetcher_service.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/device_id_helper.h"
-#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_pref_names.h"
 #include "components/signin/core/browser/test_signin_client.h"
@@ -63,7 +62,6 @@
       : test_signin_client_(&user_prefs_),
         token_service_(&user_prefs_,
                        std::make_unique<FakeOAuth2TokenServiceDelegate>()),
-        cookie_manager_service_(&token_service_, &test_signin_client_),
         account_consistency_(signin::AccountConsistencyMethod::kDisabled) {
     AccountFetcherService::RegisterPrefs(user_prefs_.registry());
     AccountTrackerService::RegisterPrefs(user_prefs_.registry());
@@ -82,7 +80,6 @@
     }
     token_service_.Shutdown();
     test_signin_client_.Shutdown();
-    cookie_manager_service_.Shutdown();
     account_tracker_.Shutdown();
     account_fetcher_.Shutdown();
   }
@@ -107,7 +104,7 @@
     DCHECK(!manager_);
     manager_ = std::make_unique<SigninManager>(
         &test_signin_client_, &token_service_, &account_tracker_,
-        &cookie_manager_service_, account_consistency_);
+        account_consistency_);
     manager_->Initialize(&local_state_);
     manager_->SetObserver(&test_observer_);
   }
@@ -137,7 +134,6 @@
   TestSigninClient test_signin_client_;
   ProfileOAuth2TokenService token_service_;
   AccountTrackerService account_tracker_;
-  GaiaCookieManagerService cookie_manager_service_;
   AccountFetcherService account_fetcher_;
   std::unique_ptr<SigninManager> manager_;
   TestSigninManagerObserver test_observer_;
diff --git a/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc b/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc
index 68fe1d4..c480da6 100644
--- a/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc
+++ b/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/memory/weak_ptr.h"
 #include "components/sync/model_impl/client_tag_based_model_type_processor.h"
 #include "components/sync/model_impl/proxy_model_type_controller_delegate.h"
 #include "components/sync/model_impl/syncable_service_based_bridge.h"
@@ -16,49 +17,64 @@
 namespace {
 
 // Helper object that allows constructing and destructing the
-// SyncableServiceBasedBridge lazily and on the model thread.
-class LazyBridgeBuilder {
+// SyncableServiceBasedBridge on the model thread.
+class BridgeBuilder {
  public:
-  LazyBridgeBuilder(
+  BridgeBuilder(
       ModelType type,
       OnceModelTypeStoreFactory store_factory,
       NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
           syncable_service_provider,
       const base::RepeatingClosure& dump_stack,
       scoped_refptr<base::SequencedTaskRunner> task_runner)
-      : type_(type),
-        store_factory_(std::move(store_factory)),
-        syncable_service_provider_(std::move(syncable_service_provider)),
-        dump_stack_(dump_stack),
-        bridge_(nullptr, base::OnTaskRunnerDeleter(std::move(task_runner))) {
-    DCHECK(store_factory_);
-    DCHECK(syncable_service_provider_);
+      : task_runner_(task_runner),
+        bridge_(nullptr, base::OnTaskRunnerDeleter(task_runner)),
+        weak_ptr_factory_(this) {
+    DCHECK(store_factory);
+    DCHECK(syncable_service_provider);
+
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&BridgeBuilder::BuildBridgeDelegate,
+                       weak_ptr_factory_.GetWeakPtr(), type,
+                       std::move(store_factory),
+                       std::move(syncable_service_provider), dump_stack));
   }
 
-  base::WeakPtr<ModelTypeControllerDelegate> BuildOrGetBridgeDelegate() {
-    if (!bridge_) {
-      base::WeakPtr<SyncableService> syncable_service =
-          std::move(syncable_service_provider_).Run();
-      DCHECK(syncable_service);
-      // std::make_unique() avoided here due to custom deleter.
-      bridge_.reset(new SyncableServiceBasedBridge(
-          type_, std::move(store_factory_),
-          std::make_unique<ClientTagBasedModelTypeProcessor>(type_,
-                                                             dump_stack_),
-          syncable_service.get()));
-    }
+  base::WeakPtr<ModelTypeControllerDelegate> GetBridgeDelegate() {
+    DCHECK(task_runner_->RunsTasksInCurrentSequence());
+    DCHECK(bridge_);
     return bridge_->change_processor()->GetControllerDelegate();
   }
 
  private:
-  const ModelType type_;
-  OnceModelTypeStoreFactory store_factory_;
-  NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
-      syncable_service_provider_;
-  const base::RepeatingClosure dump_stack_;
+  void BuildBridgeDelegate(
+      ModelType type,
+      OnceModelTypeStoreFactory store_factory,
+      NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
+          syncable_service_provider,
+      const base::RepeatingClosure& dump_stack) {
+    DCHECK(task_runner_->RunsTasksInCurrentSequence());
+    DCHECK(!bridge_);
+
+    base::WeakPtr<SyncableService> syncable_service =
+        std::move(syncable_service_provider).Run();
+    // |syncable_service| can be null in tests.
+    if (syncable_service) {
+      // std::make_unique() avoided here due to custom deleter.
+      bridge_.reset(new SyncableServiceBasedBridge(
+          type, std::move(store_factory),
+          std::make_unique<ClientTagBasedModelTypeProcessor>(type, dump_stack),
+          syncable_service.get()));
+    }
+  }
+
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
   std::unique_ptr<ModelTypeSyncBridge, base::OnTaskRunnerDeleter> bridge_;
 
-  DISALLOW_COPY_AND_ASSIGN(LazyBridgeBuilder);
+  base::WeakPtrFactory<BridgeBuilder> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(BridgeBuilder);
 };
 
 }  // namespace
@@ -74,8 +90,8 @@
           type,
           std::make_unique<ProxyModelTypeControllerDelegate>(
               task_runner,
-              base::BindRepeating(&LazyBridgeBuilder::BuildOrGetBridgeDelegate,
-                                  std::make_unique<LazyBridgeBuilder>(
+              base::BindRepeating(&BridgeBuilder::GetBridgeDelegate,
+                                  std::make_unique<BridgeBuilder>(
                                       type,
                                       std::move(store_factory),
                                       std::move(syncable_service_provider),
diff --git a/components/sync/driver/sync_session_durations_metrics_recorder.h b/components/sync/driver/sync_session_durations_metrics_recorder.h
index f4860817..b69ed49 100644
--- a/components/sync/driver/sync_session_durations_metrics_recorder.h
+++ b/components/sync/driver/sync_session_durations_metrics_recorder.h
@@ -20,8 +20,7 @@
 // Tracks the active browsing time that the user spends signed in and/or syncing
 // as fraction of their total browsing time.
 class SyncSessionDurationsMetricsRecorder
-    : public GaiaCookieManagerService::Observer,
-      public syncer::SyncServiceObserver,
+    : public syncer::SyncServiceObserver,
       public identity::IdentityManager::Observer {
  public:
   // Callers must ensure that the parameters outlive this object.
diff --git a/components/sync/driver/syncable_service_based_model_type_controller.cc b/components/sync/driver/syncable_service_based_model_type_controller.cc
index d90bbe3..4e90a70 100644
--- a/components/sync/driver/syncable_service_based_model_type_controller.cc
+++ b/components/sync/driver/syncable_service_based_model_type_controller.cc
@@ -17,63 +17,56 @@
 // reference to SyncableService in a lazy way, which is convenient for tests.
 class ControllerDelegate : public ModelTypeControllerDelegate {
  public:
-  using SyncableServiceProvider =
-      SyncableServiceBasedModelTypeController::SyncableServiceProvider;
-
   ControllerDelegate(ModelType type,
                      OnceModelTypeStoreFactory store_factory,
-                     SyncableServiceProvider syncable_service_provider,
+                     base::WeakPtr<SyncableService> syncable_service,
                      const base::RepeatingClosure& dump_stack)
       : type_(type),
         store_factory_(std::move(store_factory)),
-        syncable_service_provider_(std::move(syncable_service_provider)),
         dump_stack_(dump_stack) {
     DCHECK(store_factory_);
-    DCHECK(syncable_service_provider_);
-  }
 
-  ~ControllerDelegate() override {}
-
-  void OnSyncStarting(const DataTypeActivationRequest& request,
-                      StartCallback callback) override {
-    BuildOrGetBridgeDelegate()->OnSyncStarting(request, std::move(callback));
-  }
-
-  void OnSyncStopping(SyncStopMetadataFate metadata_fate) override {
-    BuildOrGetBridgeDelegate()->OnSyncStopping(metadata_fate);
-  }
-
-  void GetAllNodesForDebugging(AllNodesCallback callback) override {
-    BuildOrGetBridgeDelegate()->GetAllNodesForDebugging(std::move(callback));
-  }
-
-  void GetStatusCountersForDebugging(StatusCountersCallback callback) override {
-    BuildOrGetBridgeDelegate()->GetStatusCountersForDebugging(
-        std::move(callback));
-  }
-
-  void RecordMemoryUsageAndCountsHistograms() override {
-    BuildOrGetBridgeDelegate()->RecordMemoryUsageAndCountsHistograms();
-  }
-
- private:
-  ModelTypeControllerDelegate* BuildOrGetBridgeDelegate() {
-    if (!bridge_) {
-      base::WeakPtr<SyncableService> syncable_service =
-          std::move(syncable_service_provider_).Run();
-      DCHECK(syncable_service);
+    // The |syncable_service| can be null in tests.
+    if (syncable_service) {
       bridge_ = std::make_unique<SyncableServiceBasedBridge>(
           type_, std::move(store_factory_),
           std::make_unique<ClientTagBasedModelTypeProcessor>(type_,
                                                              dump_stack_),
           syncable_service.get());
     }
+  }
+
+  ~ControllerDelegate() override {}
+
+  void OnSyncStarting(const DataTypeActivationRequest& request,
+                      StartCallback callback) override {
+    GetBridgeDelegate()->OnSyncStarting(request, std::move(callback));
+  }
+
+  void OnSyncStopping(SyncStopMetadataFate metadata_fate) override {
+    GetBridgeDelegate()->OnSyncStopping(metadata_fate);
+  }
+
+  void GetAllNodesForDebugging(AllNodesCallback callback) override {
+    GetBridgeDelegate()->GetAllNodesForDebugging(std::move(callback));
+  }
+
+  void GetStatusCountersForDebugging(StatusCountersCallback callback) override {
+    GetBridgeDelegate()->GetStatusCountersForDebugging(std::move(callback));
+  }
+
+  void RecordMemoryUsageAndCountsHistograms() override {
+    GetBridgeDelegate()->RecordMemoryUsageAndCountsHistograms();
+  }
+
+ private:
+  ModelTypeControllerDelegate* GetBridgeDelegate() {
+    DCHECK(bridge_);
     return bridge_->change_processor()->GetControllerDelegate().get();
   }
 
   const ModelType type_;
   OnceModelTypeStoreFactory store_factory_;
-  SyncableServiceProvider syncable_service_provider_;
   const base::RepeatingClosure dump_stack_;
   std::unique_ptr<ModelTypeSyncBridge> bridge_;
 
@@ -86,14 +79,14 @@
     SyncableServiceBasedModelTypeController(
         ModelType type,
         OnceModelTypeStoreFactory store_factory,
-        SyncableServiceProvider syncable_service_provider,
+        base::WeakPtr<SyncableService> syncable_service,
         const base::RepeatingClosure& dump_stack)
-    : ModelTypeController(type,
-                          std::make_unique<ControllerDelegate>(
-                              type,
-                              std::move(store_factory),
-                              std::move(syncable_service_provider),
-                              dump_stack)) {}
+    : ModelTypeController(
+          type,
+          std::make_unique<ControllerDelegate>(type,
+                                               std::move(store_factory),
+                                               syncable_service,
+                                               dump_stack)) {}
 
 SyncableServiceBasedModelTypeController::
     ~SyncableServiceBasedModelTypeController() {}
diff --git a/components/sync/driver/syncable_service_based_model_type_controller.h b/components/sync/driver/syncable_service_based_model_type_controller.h
index 6daf3ff..f66eab2 100644
--- a/components/sync/driver/syncable_service_based_model_type_controller.h
+++ b/components/sync/driver/syncable_service_based_model_type_controller.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "components/sync/base/model_type.h"
@@ -22,13 +21,11 @@
 // a non-blocking datatype (USS), for datatypes living in the UI thread.
 class SyncableServiceBasedModelTypeController : public ModelTypeController {
  public:
-  using SyncableServiceProvider =
-      base::OnceCallback<base::WeakPtr<syncer::SyncableService>()>;
-
+  // |syncable_service| may be null in tests.
   SyncableServiceBasedModelTypeController(
       ModelType type,
       OnceModelTypeStoreFactory store_factory,
-      SyncableServiceProvider syncable_service_provider,
+      base::WeakPtr<SyncableService> syncable_service,
       const base::RepeatingClosure& dump_stack);
   ~SyncableServiceBasedModelTypeController() override;
 
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 2d4da9cd..8541725 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
@@ -44,13 +44,19 @@
                                           base::Time remote_modification_time) {
   const base::TimeDelta latency = base::Time::Now() - remote_modification_time;
 
-  UMA_HISTOGRAM_LONG_TIMES("Sync.NonReflectionUpdateFreshnessPossiblySkewed",
-                           latency);
+  UMA_HISTOGRAM_CUSTOM_TIMES("Sync.NonReflectionUpdateFreshnessPossiblySkewed2",
+                             latency,
+                             /*min=*/base::TimeDelta::FromMilliseconds(100),
+                             /*max=*/base::TimeDelta::FromDays(7),
+                             /*bucket_count=*/50);
 
-  base::UmaHistogramLongTimes(
-      std::string("Sync.NonReflectionUpdateFreshnessPossiblySkewed.") +
+  base::UmaHistogramCustomTimes(
+      std::string("Sync.NonReflectionUpdateFreshnessPossiblySkewed2.") +
           ModelTypeToHistogramSuffix(type),
-      latency);
+      latency,
+      /*min=*/base::TimeDelta::FromMilliseconds(100),
+      /*max=*/base::TimeDelta::FromDays(7),
+      /*bucket_count=*/50);
 }
 
 }  // namespace
diff --git a/components/sync/model_impl/syncable_service_based_bridge.cc b/components/sync/model_impl/syncable_service_based_bridge.cc
index 313451e..aadefe8e 100644
--- a/components/sync/model_impl/syncable_service_based_bridge.cc
+++ b/components/sync/model_impl/syncable_service_based_bridge.cc
@@ -283,11 +283,13 @@
     : ModelTypeSyncBridge(std::move(change_processor)),
       type_(type),
       syncable_service_(syncable_service),
-      store_factory_(std::move(store_factory)),
       syncable_service_started_(false),
       weak_ptr_factory_(this) {
-  DCHECK(store_factory_);
   DCHECK(syncable_service_);
+
+  std::move(store_factory)
+      .Run(type_, base::BindOnce(&SyncableServiceBasedBridge::OnStoreCreated,
+                                 weak_ptr_factory_.GetWeakPtr()));
 }
 
 SyncableServiceBasedBridge::~SyncableServiceBasedBridge() {
@@ -305,25 +307,6 @@
   return ModelTypeStore::WriteBatch::CreateMetadataChangeList();
 }
 
-void SyncableServiceBasedBridge::OnSyncStarting(
-    const DataTypeActivationRequest& request) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!syncable_service_started_);
-
-  if (!store_factory_) {
-    // Sync was have been started earlier, and |store_| is guaranteed to be
-    // initialized because stopping of the datatype cannot be completed before
-    // ModelReadyToSync().
-    DCHECK(store_);
-    ReportErrorIfSet(MaybeStartSyncableService());
-    return;
-  }
-
-  syncable_service_->WaitUntilReadyToSync(
-      base::BindOnce(&SyncableServiceBasedBridge::OnSyncableServiceReady,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
 base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData(
     std::unique_ptr<MetadataChangeList> metadata_change_list,
     EntityChangeList entity_change_list) {
@@ -337,10 +320,9 @@
                                std::move(entity_change_list));
 
   // We ignore the output of previous call of StoreAndConvertRemoteChanges() at
-  // this point and let MaybeStartSyncableService() read from
-  // |in_memory_store_|, which has been updated above as part of
-  // StoreAndConvertRemoteChanges().
-  return MaybeStartSyncableService();
+  // this point and let StartSyncableService() read from |in_memory_store_|,
+  // which has been updated above as part of StoreAndConvertRemoteChanges().
+  return StartSyncableService();
 }
 
 base::Optional<ModelError> SyncableServiceBasedBridge::ApplySyncChanges(
@@ -382,6 +364,7 @@
 
 std::string SyncableServiceBasedBridge::GetClientTag(
     const EntityData& entity_data) {
+  // Not supported as per SupportsGetClientTag().
   NOTREACHED();
   return std::string();
 }
@@ -425,11 +408,15 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(store_);
 
-  if (delete_metadata_change_list) {
-    in_memory_store_.clear();
-    store_->DeleteAllDataAndMetadata(base::DoNothing());
+  // If Sync is being stopped only temporarily (i.e. we want to keep tracking
+  // metadata), then there's nothing to do here.
+  if (!delete_metadata_change_list) {
+    return;
   }
 
+  in_memory_store_.clear();
+  store_->DeleteAllDataAndMetadata(base::DoNothing());
+
   if (syncable_service_started_) {
     syncable_service_->StopSyncing(type_);
     syncable_service_started_ = false;
@@ -453,15 +440,6 @@
       other);
 }
 
-void SyncableServiceBasedBridge::OnSyncableServiceReady() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  std::move(store_factory_)
-      .Run(type_, base::BindOnce(&SyncableServiceBasedBridge::OnStoreCreated,
-                                 weak_ptr_factory_.GetWeakPtr()));
-  DCHECK(!store_factory_);
-}
-
 void SyncableServiceBasedBridge::OnStoreCreated(
     const base::Optional<ModelError>& error,
     std::unique_ptr<ModelTypeStore> store) {
@@ -515,6 +493,16 @@
     return;
   }
 
+  syncable_service_->WaitUntilReadyToSync(base::BindOnce(
+      &SyncableServiceBasedBridge::OnSyncableServiceReady,
+      weak_ptr_factory_.GetWeakPtr(), std::move(metadata_batch)));
+}
+
+void SyncableServiceBasedBridge::OnSyncableServiceReady(
+    std::unique_ptr<MetadataBatch> metadata_batch) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!syncable_service_started_);
+
   // Guard against inconsistent state, and recover from it by starting from
   // scratch, which will cause the eventual refetching of all entities from the
   // server.
@@ -529,19 +517,19 @@
 
   change_processor()->ModelReadyToSync(std::move(metadata_batch));
 
-  ReportErrorIfSet(MaybeStartSyncableService());
+  // If sync was previously enabled according to the loaded metadata, then
+  // immediately start the SyncableService to track as many local changes as
+  // possible (regardless of whether sync actually starts or not). Otherwise,
+  // the SyncableService will be started from MergeSyncData().
+  if (change_processor()->IsTrackingMetadata()) {
+    ReportErrorIfSet(StartSyncableService());
+  }
 }
 
-base::Optional<ModelError>
-SyncableServiceBasedBridge::MaybeStartSyncableService() {
-  DCHECK(!syncable_service_started_);
+base::Optional<ModelError> SyncableServiceBasedBridge::StartSyncableService() {
   DCHECK(store_);
-
-  // If sync wasn't enabled according to the loaded metadata, let's wait until
-  // MergeSyncData() is called before starting the SyncableService.
-  if (!change_processor()->IsTrackingMetadata()) {
-    return base::nullopt;
-  }
+  DCHECK(!syncable_service_started_);
+  DCHECK(change_processor()->IsTrackingMetadata());
 
   const base::TimeTicks start_time = base::TimeTicks::Now();
 
diff --git a/components/sync/model_impl/syncable_service_based_bridge.h b/components/sync/model_impl/syncable_service_based_bridge.h
index 3a1a64a..514d92c 100644
--- a/components/sync/model_impl/syncable_service_based_bridge.h
+++ b/components/sync/model_impl/syncable_service_based_bridge.h
@@ -47,7 +47,6 @@
   ~SyncableServiceBasedBridge() override;
 
   // ModelTypeSyncBridge implementation.
-  void OnSyncStarting(const DataTypeActivationRequest& request) override;
   std::unique_ptr<MetadataChangeList> CreateMetadataChangeList() override;
   base::Optional<ModelError> MergeSyncData(
       std::unique_ptr<MetadataChangeList> metadata_change_list,
@@ -76,14 +75,14 @@
                                        ModelTypeChangeProcessor* other);
 
  private:
-  void OnSyncableServiceReady();
   void OnStoreCreated(const base::Optional<ModelError>& error,
                       std::unique_ptr<ModelTypeStore> store);
   void OnReadAllDataForInit(std::unique_ptr<InMemoryStore> in_memory_store,
                             const base::Optional<ModelError>& error);
   void OnReadAllMetadataForInit(const base::Optional<ModelError>& error,
                                 std::unique_ptr<MetadataBatch> metadata_batch);
-  base::Optional<ModelError> MaybeStartSyncableService() WARN_UNUSED_RESULT;
+  void OnSyncableServiceReady(std::unique_ptr<MetadataBatch> metadata_batch);
+  base::Optional<ModelError> StartSyncableService() WARN_UNUSED_RESULT;
   SyncChangeList StoreAndConvertRemoteChanges(
       std::unique_ptr<MetadataChangeList> metadata_change_list,
       EntityChangeList input_entity_change_list);
@@ -101,7 +100,6 @@
 
   const ModelType type_;
   SyncableService* const syncable_service_;
-  OnceModelTypeStoreFactory store_factory_;
 
   std::unique_ptr<ModelTypeStore> store_;
   bool syncable_service_started_;
diff --git a/components/sync/model_impl/syncable_service_based_bridge_unittest.cc b/components/sync/model_impl/syncable_service_based_bridge_unittest.cc
index 3fa2e26..155e582 100644
--- a/components/sync/model_impl/syncable_service_based_bridge_unittest.cc
+++ b/components/sync/model_impl/syncable_service_based_bridge_unittest.cc
@@ -31,15 +31,12 @@
 namespace {
 
 using testing::_;
-using testing::DoAll;
 using testing::ElementsAre;
 using testing::Invoke;
 using testing::IsEmpty;
-using testing::IsNull;
 using testing::NotNull;
 using testing::Pair;
 using testing::Return;
-using testing::SaveArg;
 
 const ModelType kModelType = PREFERENCES;
 
@@ -227,22 +224,21 @@
   // Bridge initialization alone, without sync itself starting, should not
   // issue calls to the syncable service.
   InitializeBridge();
-  EXPECT_FALSE(syncable_service_ready_cb);
+
+  EXPECT_CALL(syncable_service_, WaitUntilReadyToSync(_));
+  // Required to initialize the store.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(syncable_service_ready_cb);
 
   // Sync itself starting should wait until the syncable service becomes ready,
   // before issuing any other call (e.g. MergeDataAndStartSyncing()).
-  EXPECT_CALL(syncable_service_, WaitUntilReadyToSync(_));
   real_processor_->OnSyncStarting(GetTestActivationRequest(),
                                   base::DoNothing());
-  ASSERT_TRUE(syncable_service_ready_cb);
 
   // When the SyncableService gets ready, the bridge should propagate this
   // information to the processor.
   EXPECT_CALL(mock_processor_, ModelReadyToSync(_));
   std::move(syncable_service_ready_cb).Run();
-
-  // Required to initialize the store.
-  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(SyncableServiceBasedBridgeTest,
@@ -252,7 +248,7 @@
   worker_->UpdateFromServer();
 
   EXPECT_CALL(syncable_service_, StopSyncing(kModelType));
-  real_processor_->OnSyncStopping(KEEP_METADATA);
+  real_processor_->OnSyncStopping(CLEAR_METADATA);
 
   EXPECT_CALL(syncable_service_, StopSyncing(_)).Times(0);
   ShutdownBridge();
@@ -304,18 +300,26 @@
 }
 
 TEST_F(SyncableServiceBasedBridgeTest,
-       ShouldStartSyncingWithPreviousDirectoryDataWithoutRestart) {
+       ShouldKeepSyncingWhenSyncStoppedTemporarily) {
   InitializeBridge();
   StartSyncing();
   worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
+
+  // Stopping Sync temporarily (KEEP_METADATA) should *not* result in the
+  // SyncableService being stopped.
+  EXPECT_CALL(syncable_service_, StopSyncing(_)).Times(0);
   real_processor_->OnSyncStopping(KEEP_METADATA);
   EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash, _)));
 
-  EXPECT_CALL(syncable_service_,
-              MergeDataAndStartSyncing(
-                  kModelType, ElementsAre(SyncDataRemoteMatches("name1")),
-                  NotNull(), NotNull()));
+  // Since the SyncableService wasn't stopped, it shouldn't get restarted either
+  // when Sync starts up again.
+  EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing(_, _, _, _)).Times(0);
   StartSyncing();
+
+  // Finally, shutting down the bridge (during browser shutdown) should also
+  // stop the SyncableService.
+  EXPECT_CALL(syncable_service_, StopSyncing(kModelType));
+  ShutdownBridge();
 }
 
 TEST_F(SyncableServiceBasedBridgeTest,
diff --git a/components/sync_preferences/pref_model_associator.cc b/components/sync_preferences/pref_model_associator.cc
index d3e8110..66dec66 100644
--- a/components/sync_preferences/pref_model_associator.cc
+++ b/components/sync_preferences/pref_model_associator.cc
@@ -171,7 +171,7 @@
 
 void PrefModelAssociator::WaitUntilReadyToSync(base::OnceClosure done) {
   // Prefs are loaded very early during profile initialization.
-  DCHECK_NE(pref_service_->GetAllPrefStoresInitializationStatus(),
+  DCHECK_NE(pref_service_->GetInitializationStatus(),
             PrefService::INITIALIZATION_STATUS_WAITING);
   std::move(done).Run();
 }
diff --git a/components/sync_sessions/favicon_cache.cc b/components/sync_sessions/favicon_cache.cc
index 004e626d..4a36980 100644
--- a/components/sync_sessions/favicon_cache.cc
+++ b/components/sync_sessions/favicon_cache.cc
@@ -238,7 +238,8 @@
 FaviconCache::~FaviconCache() {}
 
 void FaviconCache::WaitUntilReadyToSync(base::OnceClosure done) {
-  if (history_service_->backend_loaded()) {
+  // |history_service_| can be null in tests. In that case, no point in waiting.
+  if (!history_service_ || history_service_->backend_loaded()) {
     std::move(done).Run();
   } else {
     // Wait until HistoryService's backend loads, reported via
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
index 062442b..625cc1f 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-9dd756a65b2bb331409e1ab968b13641a6486bfa
\ No newline at end of file
+20ef25e0c4454df34f07331a5ef397bf987efd0f
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
index 58de354b..90dbf3f 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.close_incognito_tabs_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-fa59896eefa99b5a3ffd60986c9f50e607d727f1
\ No newline at end of file
+80e942fa06f0518c6ca00909cdb88093f87fbb1e
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-25.png.sha1
index 63f38a0..ec8e36e 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-023f43d2fd3843654911a570a8493d10ced14118
\ No newline at end of file
+136f44df7c66102775da397441f0fbe797336d4a
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-26.png.sha1
index 17630ee2..ad2972a 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_browser_ui.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-f699f6cf9f3727a39598a3d2cb890110e30d4273
\ No newline at end of file
+d05218e88bf24a6b4479563c0c4b22f26e8f94a5
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
index 75e2653..bc5f351a 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-fa9a5e738eeb4fa451153811027602fcefd64498
\ No newline at end of file
+048310a22b411d78ed175077d6eda9b41d398e19
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
index 09c5cb9..d678c24 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.forward_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-e6fbc61b2a194b313e285c3f28c5e2c5d7fba366
\ No newline at end of file
+ed7b6698e44c9250b8e18d2ca6324e7ad3d1e609
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-25.png.sha1
index d0536e7..8c9085e 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-d06ba3194aae828b30f09436a8f1d099e4c03551
\ No newline at end of file
+5e8f35695f82f98297cc4351eb43d68f9f92ff38
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-26.png.sha1
index ca29df6..3338514 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.new_incognito_tab_hover_browser_ui.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-d361f9d0784f3a77f7047ff9055bf5916f0174eb
\ No newline at end of file
+b93542ff3f1b6eb511eb53841c4196a0529b66ba
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-25.png.sha1
index 9dc2b93..568346d 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-115da8f258062830b8b861cf0ffa8c8d101b71ba
\ No newline at end of file
+06260f4b7b4fe02102c9a687d90d08f25364e3a9
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-26.png.sha1
index 65ac77f..7fb7a23a 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.overflow_menu_visible_browser_ui.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-0dfae90e612afa2537605da7fb73e09fbea6149c
\ No newline at end of file
+b4aa286e7056050761ea964c7107e2607af8b50e
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-25.png.sha1
index e6a9898..ad85b463b 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-5e5b493ef51a12b2a790a3d365ca0731b713aeff
\ No newline at end of file
+f189360f70397d81fd50b3f7bd6ef43316a42014
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-26.png.sha1
index c7ab849..1c1af10 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_browser_ui.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-752f6aac5ec25c566dfb4c7e0cbd70a6279c1a59
\ No newline at end of file
+1745cab5afd4a1c2bdbeded28e229ce17bf480e3
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
index fa95eba..6a3ae04c 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-7da4a86a2148a744b2d2bec26a2dfa5aec490a5f
\ No newline at end of file
+02cd4997198ed6c3a6da558c89a9ddd8862010dc
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
index f8ca74a..6ee7744 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.reload_button_hover_incognito_browser_ui.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-b94d691b62528b513f4d10256f0c1edaebc0be85
\ No newline at end of file
+c45ec31b360af756412cfcd8880606bbb7ee469f
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-25.png.sha1
index 6ed90228..daf7d5e7 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-69c9176c3446e13cbef1da62a0662d149c9b325a
\ No newline at end of file
+d1cbcc0fadbc33ee721228f28dff50ea1d4c3d05
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-26.png.sha1
index 52c2a865..9e02b0b 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.repositioned_overflow_menu.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-76a094c08e36daf87c632f1cef8ed6e66e63e07a
\ No newline at end of file
+89766057cc26687587d89e576cbd166384252d53
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-25.png.sha1
index ca20a2e..5da5f52 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-0f9b558ecaac382f144cae2647579a597152b8a3
\ No newline at end of file
+9450f2e125b5e04ee4463bcbc2dff8b8a102d9e6
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-26.png.sha1
index fb7c2f1..db587a6 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_back_enabled.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-e5fa46dc5e243ac8f164a8b12a077c0109a25c3e
\ No newline at end of file
+6e6f14ad097fa9095ac194ac5be19097429ccf79
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-25.png.sha1
index 1c202ad..277ab0b 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-67d5565b078f625bfcc6fa4d951529bd638922b2
\ No newline at end of file
+a771e024aec046967ea08ec97f1968b7ff5e5230
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-26.png.sha1
index 65ac77f..7fb7a23a 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_both_disabled.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-0dfae90e612afa2537605da7fb73e09fbea6149c
\ No newline at end of file
+b4aa286e7056050761ea964c7107e2607af8b50e
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-25.png.sha1
index 59a7316..f149bf53 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-a22df8a3582ec5f241ab5b2864cfad7385f4c3f6
\ No newline at end of file
+c69ce2e9225ac8e2bef4c4d97435f57a32712a29
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-26.png.sha1
index 0e284c0..b1f6897 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNavigationTest.navigation_buttons_forward_enabled.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-ed308dcd89ed718b2f4c607f5c8776fa9e3b9c0c
\ No newline at end of file
+497547d4e9aabe2ee4c2c0d0eac3a46a23ce3ebe
\ No newline at end of file
diff --git a/content/browser/android/java/gin_java_bridge_message_filter.cc b/content/browser/android/java/gin_java_bridge_message_filter.cc
index f4ce393d..c3802dc 100644
--- a/content/browser/android/java/gin_java_bridge_message_filter.cc
+++ b/content/browser/android/java/gin_java_bridge_message_filter.cc
@@ -59,7 +59,8 @@
   return handled;
 }
 
-base::TaskRunner* GinJavaBridgeMessageFilter::OverrideTaskRunnerForMessage(
+scoped_refptr<base::SequencedTaskRunner>
+GinJavaBridgeMessageFilter::OverrideTaskRunnerForMessage(
     const IPC::Message& message) {
   // As the filter is only invoked for the messages of the particular class,
   // we can return the task runner unconditionally.
diff --git a/content/browser/android/java/gin_java_bridge_message_filter.h b/content/browser/android/java/gin_java_bridge_message_filter.h
index 1847f24..b4232aab 100644
--- a/content/browser/android/java/gin_java_bridge_message_filter.h
+++ b/content/browser/android/java/gin_java_bridge_message_filter.h
@@ -37,7 +37,7 @@
   // BrowserMessageFilter
   void OnDestruct() const override;
   bool OnMessageReceived(const IPC::Message& message) override;
-  base::TaskRunner* OverrideTaskRunnerForMessage(
+  scoped_refptr<base::SequencedTaskRunner> OverrideTaskRunnerForMessage(
       const IPC::Message& message) override;
 
   // RenderProcessHostObserver
diff --git a/content/browser/android/java/java_bridge_thread.cc b/content/browser/android/java/java_bridge_thread.cc
index d0d0381..8919e96 100644
--- a/content/browser/android/java/java_bridge_thread.cc
+++ b/content/browser/android/java/java_bridge_thread.cc
@@ -40,8 +40,8 @@
 }
 
 // static
-base::TaskRunner* JavaBridgeThread::GetTaskRunner() {
-  return g_background_thread.Get().message_loop()->task_runner().get();
+scoped_refptr<base::SingleThreadTaskRunner> JavaBridgeThread::GetTaskRunner() {
+  return g_background_thread.Get().message_loop()->task_runner();
 }
 
 }  // namespace content
diff --git a/content/browser/android/java/java_bridge_thread.h b/content/browser/android/java/java_bridge_thread.h
index 662f031..05334270 100644
--- a/content/browser/android/java/java_bridge_thread.h
+++ b/content/browser/android/java/java_bridge_thread.h
@@ -6,10 +6,7 @@
 #define CONTENT_BROWSER_ANDROID_JAVA_GIN_JAVA_JAVA_BRIDGE_THREAD_H_
 
 #include "base/android/java_handler_thread.h"
-
-namespace base {
-class TaskRunner;
-}
+#include "base/single_thread_task_runner.h"
 
 namespace content {
 
@@ -21,7 +18,9 @@
   ~JavaBridgeThread() override;
 
   static bool CurrentlyOn();
-  static base::TaskRunner* GetTaskRunner();
+  // TODO(altimin): Make it const scoped_refptr& after we support this
+  // which is blocked by revoming MessageLoop::SetTaskRunner.
+  static scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner();
 };
 
 }  // namespace content
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.cc b/content/browser/blob_storage/chrome_blob_storage_context.cc
index 94588aa..859cf37 100644
--- a/content/browser/blob_storage/chrome_blob_storage_context.cc
+++ b/content/browser/blob_storage/chrome_blob_storage_context.cc
@@ -155,6 +155,11 @@
                      context_->mutable_memory_controller()->GetWeakPtr()));
 }
 
+storage::BlobStorageContext* ChromeBlobStorageContext::context() const {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  return context_.get();
+}
+
 std::unique_ptr<BlobHandle> ChromeBlobStorageContext::CreateMemoryBackedBlob(
     const char* data,
     size_t length,
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.h b/content/browser/blob_storage/chrome_blob_storage_context.h
index afd21be63..8353f2a 100644
--- a/content/browser/blob_storage/chrome_blob_storage_context.h
+++ b/content/browser/blob_storage/chrome_blob_storage_context.h
@@ -57,7 +57,7 @@
   void InitializeOnIOThread(base::FilePath blob_storage_dir,
                             scoped_refptr<base::TaskRunner> file_task_runner);
 
-  storage::BlobStorageContext* context() const { return context_.get(); }
+  storage::BlobStorageContext* context() const;
 
   // Returns a NULL scoped_ptr on failure.
   std::unique_ptr<BlobHandle> CreateMemoryBackedBlob(
diff --git a/content/browser/frame_host/navigation_handle_impl.cc b/content/browser/frame_host/navigation_handle_impl.cc
index 6bdd0a2..b6a3781 100644
--- a/content/browser/frame_host/navigation_handle_impl.cc
+++ b/content/browser/frame_host/navigation_handle_impl.cc
@@ -403,10 +403,6 @@
   return navigation_request_->common_params().base_url_for_data_url;
 }
 
-NavigationData* NavigationHandleImpl::GetNavigationData() {
-  return navigation_data_.get();
-}
-
 void NavigationHandleImpl::RegisterSubresourceOverride(
     mojom::TransferrableURLLoaderPtr transferrable_loader) {
   if (!transferrable_loader)
diff --git a/content/browser/frame_host/navigation_handle_impl.h b/content/browser/frame_host/navigation_handle_impl.h
index 01c0439..6337d2c 100644
--- a/content/browser/frame_host/navigation_handle_impl.h
+++ b/content/browser/frame_host/navigation_handle_impl.h
@@ -26,7 +26,6 @@
 #include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/global_request_id.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/navigation_throttle.h"
 #include "content/public/browser/navigation_type.h"
 #include "content/public/browser/restore_type.h"
@@ -122,7 +121,6 @@
   // deferring NavigationThrottle do the resuming.
   void CallResumeForTesting();
 
-  NavigationData* GetNavigationData() override;
   void RegisterSubresourceOverride(
       mojom::TransferrableURLLoaderPtr transferrable_loader) override;
 
@@ -187,13 +185,6 @@
   // |state_| and inform the delegate.
   void ReadyToCommitNavigation(bool is_error);
 
-  // Called during commit. Takes ownership of the embedder's NavigationData
-  // instance. This NavigationData may have been cloned prior to being added
-  // here.
-  void set_navigation_data(std::unique_ptr<NavigationData> navigation_data) {
-    navigation_data_ = std::move(navigation_data);
-  }
-
   NavigationUIData* navigation_ui_data() const {
     return navigation_request_->navigation_ui_data();
   }
@@ -339,9 +330,6 @@
   // to service the navigation request.
   std::unique_ptr<AppCacheNavigationHandle> appcache_handle_;
 
-  // Embedder data from the IO thread tied to this navigation.
-  std::unique_ptr<NavigationData> navigation_data_;
-
   // The unique id to identify this to navigation with.
   int64_t navigation_id_;
 
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index cb48aa1..767a99e 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -54,7 +54,6 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/global_request_id.h"
 #include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/navigation_ui_data.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/site_isolation_policy.h"
@@ -1214,7 +1213,6 @@
 void NavigationRequest::OnResponseStarted(
     const scoped_refptr<network::ResourceResponse>& response,
     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-    std::unique_ptr<NavigationData> navigation_data,
     const GlobalRequestID& request_id,
     bool is_download,
     NavigationDownloadPolicy download_policy,
@@ -1336,9 +1334,6 @@
     }
   }
 
-  if (navigation_data)
-    navigation_handle_->set_navigation_data(std::move(navigation_data));
-
   // This must be set before DetermineCommittedPreviews is called.
   navigation_handle_->set_proxy_server(response->head.proxy_server);
 
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index 05bf0830..6137a0b 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -46,7 +46,6 @@
 class FrameTreeNode;
 class NavigationHandleImpl;
 class NavigationURLLoader;
-class NavigationData;
 class NavigationUIData;
 class NavigatorDelegate;
 class PrefetchedSignedExchangeCache;
@@ -452,7 +451,6 @@
   void OnResponseStarted(
       const scoped_refptr<network::ResourceResponse>& response,
       network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-      std::unique_ptr<NavigationData> navigation_data,
       const GlobalRequestID& request_id,
       bool is_download,
       NavigationDownloadPolicy download_policy,
diff --git a/content/browser/frame_host/navigator_impl_unittest.cc b/content/browser/frame_host/navigator_impl_unittest.cc
index bd18c53..25e97c4 100644
--- a/content/browser/frame_host/navigator_impl_unittest.cc
+++ b/content/browser/frame_host/navigator_impl_unittest.cc
@@ -19,7 +19,6 @@
 #include "content/common/frame.mojom.h"
 #include "content/common/frame_messages.h"
 #include "content/common/navigation_params.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/common/url_utils.h"
@@ -366,8 +365,7 @@
   const char kNoContentHeaders[] = "HTTP/1.1 204 No Content\0\0";
   response->head.headers = new net::HttpResponseHeaders(
       std::string(kNoContentHeaders, base::size(kNoContentHeaders)));
-  GetLoaderForNavigationRequest(main_request)
-      ->CallOnResponseStarted(response, nullptr);
+  GetLoaderForNavigationRequest(main_request)->CallOnResponseStarted(response);
 
   // There should be no pending nor speculative RenderFrameHost; the navigation
   // was aborted.
@@ -392,8 +390,7 @@
   const char kResetContentHeaders[] = "HTTP/1.1 205 Reset Content\0\0";
   response->head.headers = new net::HttpResponseHeaders(
       std::string(kResetContentHeaders, base::size(kResetContentHeaders)));
-  GetLoaderForNavigationRequest(main_request)
-      ->CallOnResponseStarted(response, nullptr);
+  GetLoaderForNavigationRequest(main_request)->CallOnResponseStarted(response);
 
   // There should be no pending nor speculative RenderFrameHost; the navigation
   // was aborted.
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 387831a4..e027a78 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -466,6 +466,9 @@
     int frame_id,
     StoragePartition* storage_partition,
     network::mojom::RestrictedCookieManagerRequest request) {
+  GetContentClient()->browser()->WillCreateRestrictedCookieManager(
+      frame_host->GetLastCommittedOrigin(),
+      /* is_service_worker = */ false, process_id, frame_id, &request);
   storage_partition->GetNetworkContext()->GetRestrictedCookieManager(
       std::move(request), frame_host->GetLastCommittedOrigin(),
       /* is_service_worker = */ false, process_id, frame_id);
diff --git a/content/browser/loader/mojo_async_resource_handler_unittest.cc b/content/browser/loader/mojo_async_resource_handler_unittest.cc
index a6acea3..43a6de88 100644
--- a/content/browser/loader/mojo_async_resource_handler_unittest.cc
+++ b/content/browser/loader/mojo_async_resource_handler_unittest.cc
@@ -25,7 +25,6 @@
 #include "content/browser/loader/resource_dispatcher_host_impl.h"
 #include "content/browser/loader/resource_request_info_impl.h"
 #include "content/public/browser/appcache_service.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/resource_context.h"
 #include "content/public/browser/resource_dispatcher_host_delegate.h"
 #include "content/public/browser/resource_throttle.h"
@@ -143,11 +142,6 @@
     ADD_FAILURE() << "RequestComplete should not be called.";
   }
 
-  NavigationData* GetNavigationData(net::URLRequest* request) const override {
-    ADD_FAILURE() << "GetNavigationData should not be called.";
-    return nullptr;
-  }
-
  private:
   DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcherHostDelegate);
 };
diff --git a/content/browser/loader/navigation_url_loader_delegate.h b/content/browser/loader/navigation_url_loader_delegate.h
index 270ab931..63634013 100644
--- a/content/browser/loader/navigation_url_loader_delegate.h
+++ b/content/browser/loader/navigation_url_loader_delegate.h
@@ -25,7 +25,6 @@
 
 namespace content {
 
-class NavigationData;
 struct GlobalRequestID;
 struct SubresourceLoaderParams;
 
@@ -54,7 +53,6 @@
   virtual void OnResponseStarted(
       const scoped_refptr<network::ResourceResponse>& response,
       network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-      std::unique_ptr<NavigationData> navigation_data,
       const GlobalRequestID& request_id,
       bool is_download,
       NavigationDownloadPolicy download_policy,
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index b938ff82..23b9bd71 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -59,7 +59,6 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/download_utils.h"
 #include "content/public/browser/global_request_id.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/navigation_ui_data.h"
 #include "content/public/browser/plugin_service.h"
 #include "content/public/browser/resource_dispatcher_host_delegate.h"
@@ -1068,8 +1067,6 @@
 
     bool is_download;
 
-    std::unique_ptr<NavigationData> cloned_navigation_data;
-
     bool must_download =
         download_utils::MustDownload(url_, head.headers.get(), head.mime_type);
     bool known_mime_type = blink::IsSupportedMimeType(head.mime_type);
@@ -1096,7 +1093,7 @@
     if (base::FeatureList::IsEnabled(network::features::kNetworkService) ||
         !default_loader_used_) {
       CallOnReceivedResponse(head, std::move(url_loader_client_endpoints),
-                             std::move(cloned_navigation_data), is_download);
+                             is_download);
       return;
     }
 
@@ -1111,21 +1108,12 @@
       ResourceRequestInfoImpl* info =
           ResourceRequestInfoImpl::ForRequest(url_request);
       is_download = !head.intercepted_by_plugin && info->IsDownload();
-      if (rdh->delegate()) {
-        NavigationData* navigation_data =
-            rdh->delegate()->GetNavigationData(url_request);
-
-        // Clone the embedder's NavigationData before moving it to the UI
-        // thread.
-        if (navigation_data)
-          cloned_navigation_data = navigation_data->Clone();
-      }
     } else {
       is_download = false;
     }
 
     CallOnReceivedResponse(head, std::move(url_loader_client_endpoints),
-                           std::move(cloned_navigation_data), is_download);
+                           is_download);
   }
 
 #if BUILDFLAG(ENABLE_PLUGINS)
@@ -1159,14 +1147,13 @@
     bool is_download = !has_plugin && is_download_if_not_handled_by_plugin;
 
     CallOnReceivedResponse(head, std::move(url_loader_client_endpoints),
-                           nullptr, is_download);
+                           is_download);
   }
 #endif
 
   void CallOnReceivedResponse(
       const network::ResourceResponseHead& head,
       network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-      std::unique_ptr<NavigationData> cloned_navigation_data,
       bool is_download) {
     scoped_refptr<network::ResourceResponse> response(
         new network::ResourceResponse());
@@ -1183,8 +1170,8 @@
         base::BindOnce(&NavigationURLLoaderImpl::OnReceiveResponse, owner_,
                        response->DeepCopy(),
                        std::move(url_loader_client_endpoints),
-                       std::move(cloned_navigation_data), global_request_id_,
-                       is_download, ui_to_io_time_, base::Time::Now()));
+                       global_request_id_, is_download, ui_to_io_time_,
+                       base::Time::Now()));
   }
 
   void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
@@ -1679,7 +1666,6 @@
 void NavigationURLLoaderImpl::OnReceiveResponse(
     scoped_refptr<network::ResourceResponse> response,
     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-    std::unique_ptr<NavigationData> navigation_data,
     const GlobalRequestID& global_request_id,
     bool is_download,
     base::TimeDelta total_ui_to_io_time,
@@ -1702,8 +1688,8 @@
   // NavigationResourceHandler::OnResponseStarted() does.
   delegate_->OnResponseStarted(
       std::move(response), std::move(url_loader_client_endpoints),
-      std::move(navigation_data), global_request_id, is_download,
-      download_policy_, request_controller_->TakeSubresourceLoaderParams());
+      global_request_id, is_download, download_policy_,
+      request_controller_->TakeSubresourceLoaderParams());
 }
 
 void NavigationURLLoaderImpl::OnReceiveRedirect(
diff --git a/content/browser/loader/navigation_url_loader_impl.h b/content/browser/loader/navigation_url_loader_impl.h
index 2d77856..63b715e 100644
--- a/content/browser/loader/navigation_url_loader_impl.h
+++ b/content/browser/loader/navigation_url_loader_impl.h
@@ -23,7 +23,6 @@
 
 namespace content {
 
-class NavigationData;
 class NavigationLoaderInterceptor;
 class PrefetchedSignedExchangeCache;
 class ResourceContext;
@@ -58,7 +57,6 @@
   void OnReceiveResponse(
       scoped_refptr<network::ResourceResponse> response,
       network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-      std::unique_ptr<NavigationData> navigation_data,
       const GlobalRequestID& global_request_id,
       bool is_download,
       base::TimeDelta total_ui_to_io_time,
diff --git a/content/browser/scheduler/OWNERS b/content/browser/scheduler/OWNERS
index 970514c..da9ba63 100644
--- a/content/browser/scheduler/OWNERS
+++ b/content/browser/scheduler/OWNERS
@@ -1,4 +1,5 @@
 altimin@chromium.org
 alexclarke@chromium.org
+carlscab@chromium.org
 eseckler@chromium.org
 skyostil@chromium.org
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index 419ca23c..1faf563 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -105,6 +105,9 @@
   if (interface_name == network::mojom::RestrictedCookieManager::Name_) {
     network::mojom::RestrictedCookieManagerRequest request(
         std::move(interface_pipe));
+    GetContentClient()->browser()->WillCreateRestrictedCookieManager(
+        origin, true /* is_service_worker */, process_id, MSG_ROUTING_NONE,
+        &request);
     process->GetStoragePartition()
         ->GetNetworkContext()
         ->GetRestrictedCookieManager(std::move(request), origin,
diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc
index 830ef3b1..ba02d67b 100644
--- a/content/browser/storage_partition_impl_map.cc
+++ b/content/browser/storage_partition_impl_map.cc
@@ -61,6 +61,33 @@
 
 namespace {
 
+// Wrapper to call ChromeBlobStorageContext::context() on the IO thread.
+class BlobProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+  explicit BlobProtocolHandler(ChromeBlobStorageContext* blob_storage_context)
+      : blob_storage_context_(blob_storage_context) {}
+
+  ~BlobProtocolHandler() override {}
+
+  net::URLRequestJob* MaybeCreateJob(
+      net::URLRequest* request,
+      net::NetworkDelegate* network_delegate) const override {
+    if (!blob_protocol_handler_) {
+      // Construction is deferred because 'this' is constructed on
+      // the main thread but we want blob_protocol_handler_ constructed
+      // on the IO thread.
+      blob_protocol_handler_.reset(
+          new storage::BlobProtocolHandler(blob_storage_context_->context()));
+    }
+    return blob_protocol_handler_->MaybeCreateJob(request, network_delegate);
+  }
+
+ private:
+  const scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;
+  mutable std::unique_ptr<storage::BlobProtocolHandler> blob_protocol_handler_;
+  DISALLOW_COPY_AND_ASSIGN(BlobProtocolHandler);
+};
+
 // These constants are used to create the directory structure under the profile
 // where renderers with a non-default storage partition keep their persistent
 // state. This will contain a set of directories that partially mirror the
@@ -360,28 +387,27 @@
   StoragePartitionImpl* partition = partition_ptr.get();
   partitions_[partition_config] = std::move(partition_ptr);
 
-  ChromeBlobStorageContext* blob_storage_context =
-      ChromeBlobStorageContext::GetFor(browser_context_);
-  ProtocolHandlerMap protocol_handlers;
-  protocol_handlers[url::kBlobScheme] =
-      std::make_unique<storage::BlobProtocolHandler>(
-          blob_storage_context->context());
-  protocol_handlers[url::kFileSystemScheme] = CreateFileSystemProtocolHandler(
-      partition_domain, partition->GetFileSystemContext());
-  for (const auto& scheme : URLDataManagerBackend::GetWebUISchemes()) {
-    protocol_handlers[scheme] = URLDataManagerBackend::CreateProtocolHandler(
-        browser_context_->GetResourceContext(), blob_storage_context);
-  }
-
-  URLRequestInterceptorScopedVector request_interceptors;
-
-  auto devtools_interceptor =
-      DevToolsURLRequestInterceptor::MaybeCreate(browser_context_);
-  if (devtools_interceptor)
-    request_interceptors.push_back(std::move(devtools_interceptor));
-  request_interceptors.push_back(std::make_unique<AppCacheInterceptor>());
-
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    ChromeBlobStorageContext* blob_storage_context =
+        ChromeBlobStorageContext::GetFor(browser_context_);
+    ProtocolHandlerMap protocol_handlers;
+    protocol_handlers[url::kBlobScheme] =
+        std::make_unique<BlobProtocolHandler>(blob_storage_context);
+    protocol_handlers[url::kFileSystemScheme] = CreateFileSystemProtocolHandler(
+        partition_domain, partition->GetFileSystemContext());
+    for (const auto& scheme : URLDataManagerBackend::GetWebUISchemes()) {
+      protocol_handlers[scheme] = URLDataManagerBackend::CreateProtocolHandler(
+          browser_context_->GetResourceContext(), blob_storage_context);
+    }
+
+    URLRequestInterceptorScopedVector request_interceptors;
+
+    auto devtools_interceptor =
+        DevToolsURLRequestInterceptor::MaybeCreate(browser_context_);
+    if (devtools_interceptor)
+      request_interceptors.push_back(std::move(devtools_interceptor));
+    request_interceptors.push_back(std::make_unique<AppCacheInterceptor>());
+
     // These calls must happen after StoragePartitionImpl::Create().
     if (partition_domain.empty()) {
       partition->SetURLRequestContext(browser_context_->CreateRequestContext(
@@ -392,22 +418,14 @@
               partition->GetPath(), in_memory, &protocol_handlers,
               std::move(request_interceptors)));
     }
-  }
 
-  // A separate media cache isn't used with the network service.
-  if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    // A separate media cache isn't used with the network service.
     partition->SetMediaURLRequestContext(
         partition_domain.empty()
             ? browser_context_->CreateMediaRequestContext()
             : browser_context_->CreateMediaRequestContextForStoragePartition(
                   partition->GetPath(), in_memory));
-  }
 
-  // Arm the serviceworker cookie change observation API.
-  partition->GetCookieStoreContext()->ListenToCookieChanges(
-      partition->GetNetworkContext(), /*success_callback=*/base::DoNothing());
-
-  if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
     // This needs to happen after SetURLRequestContext() since we need this
     // code path only for non-NetworkService cases where NetworkContext needs to
     // be initialized using |url_request_context_|, which is initialized by
@@ -417,6 +435,10 @@
     partition->url_loader_factory_getter()->HandleFactoryRequests();
   }
 
+  // Arm the serviceworker cookie change observation API.
+  partition->GetCookieStoreContext()->ListenToCookieChanges(
+      partition->GetNetworkContext(), /*success_callback=*/base::DoNothing());
+
   PostCreateInitialization(partition, in_memory);
 
   return partition;
diff --git a/content/common/render_widget_host_ns_view.mojom b/content/common/render_widget_host_ns_view.mojom
index 5659d670..3b0787c 100644
--- a/content/common/render_widget_host_ns_view.mojom
+++ b/content/common/render_widget_host_ns_view.mojom
@@ -13,7 +13,6 @@
 import "ui/gfx/geometry/mojo/geometry.mojom";
 import "ui/gfx/mojo/ca_layer_params.mojom";
 import "ui/gfx/range/mojo/range.mojom";
-import "ui/platform_window/mojo/text_input_state.mojom";
 
 // The interface through which code in the browser process, in
 // RenderWidgetHostViewMac, sends messages to the app shim process, targeting
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index ffddd10a..86c4431 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -196,7 +196,6 @@
     "native_web_keyboard_event.h",
     "navigation_controller.cc",
     "navigation_controller.h",
-    "navigation_data.h",
     "navigation_details.cc",
     "navigation_details.h",
     "navigation_entry.h",
diff --git a/content/public/browser/browser_message_filter.cc b/content/public/browser/browser_message_filter.cc
index b9906035..a52d3c1 100644
--- a/content/public/browser/browser_message_filter.cc
+++ b/content/public/browser/browser_message_filter.cc
@@ -63,7 +63,7 @@
     filter_->OverrideThreadForMessage(message, &thread);
 
     if (thread == BrowserThread::IO) {
-      scoped_refptr<base::TaskRunner> runner =
+      scoped_refptr<base::SequencedTaskRunner> runner =
           filter_->OverrideTaskRunnerForMessage(message);
       if (runner.get()) {
         runner->PostTask(
@@ -160,7 +160,8 @@
   return false;
 }
 
-base::TaskRunner* BrowserMessageFilter::OverrideTaskRunnerForMessage(
+scoped_refptr<base::SequencedTaskRunner>
+BrowserMessageFilter::OverrideTaskRunnerForMessage(
     const IPC::Message& message) {
   return nullptr;
 }
diff --git a/content/public/browser/browser_message_filter.h b/content/public/browser/browser_message_filter.h
index 505473cf..b4ee929 100644
--- a/content/public/browser/browser_message_filter.h
+++ b/content/public/browser/browser_message_filter.h
@@ -11,6 +11,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/process/process.h"
+#include "base/sequenced_task_runner.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
@@ -20,10 +21,6 @@
 #include "base/synchronization/lock.h"
 #endif
 
-namespace base {
-class TaskRunner;
-}
-
 namespace IPC {
 class MessageFilter;
 }
@@ -75,7 +72,7 @@
   // return a non-null task runner which will target tasks accordingly.
   // Note: To target the UI thread, please use OverrideThreadForMessage
   // since that has extra checks to avoid deadlocks.
-  virtual base::TaskRunner* OverrideTaskRunnerForMessage(
+  virtual scoped_refptr<base::SequencedTaskRunner> OverrideTaskRunnerForMessage(
       const IPC::Message& message);
 
   // Override this to receive messages.
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index f4c61acb..53e6771 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -759,6 +759,13 @@
     network::mojom::TrustedHeaderClientPtr* header_client,
     uint32_t* options) {}
 
+void ContentBrowserClient::WillCreateRestrictedCookieManager(
+    const url::Origin& origin,
+    bool is_service_worker,
+    int process_id,
+    int frame_id,
+    network::mojom::RestrictedCookieManagerRequest* request) {}
+
 std::vector<std::unique_ptr<URLLoaderRequestInterceptor>>
 ContentBrowserClient::WillCreateURLLoaderRequestInterceptors(
     content::NavigationUIData* navigation_ui_data,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 6ab15079..2b4c2c1 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -1252,6 +1252,29 @@
       network::mojom::TrustedHeaderClientPtr* header_client,
       uint32_t* options);
 
+  // Allows the embedder to intercept the mojo object vended to renderer
+  // processes for limited, origin-locked (to |origin|), access to
+  // script-accessible cookies.  |*request| is always valid upon entry and MUST
+  // be valid upon return. The embedder may swap out the value of |*request| for
+  // its own.
+  //
+  // Currently this only affects the (hidden by default, experimental)
+  // CookieStore API, but may affect all JavaScript cookie operations in the
+  // future.
+  //
+  // If |is_service_worker| is false, then |process_id| and |routing_id|
+  // describe the frame the result is to be used from. If it's true, operations
+  // are not bound to a particular frame, but are in context of a service worker
+  // appropriate for |origin|.
+  //
+  // This is called on the UI thread.
+  virtual void WillCreateRestrictedCookieManager(
+      const url::Origin& origin,
+      bool is_service_worker,
+      int process_id,
+      int routing_id,
+      network::mojom::RestrictedCookieManagerRequest* request);
+
   // Allows the embedder to returns a list of request interceptors that can
   // intercept a navigation request.
   //
diff --git a/content/public/browser/navigation_data.h b/content/public/browser/navigation_data.h
deleted file mode 100644
index d753809..0000000
--- a/content/public/browser/navigation_data.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_PUBLIC_BROWSER_NAVIGATION_DATA_H_
-#define CONTENT_PUBLIC_BROWSER_NAVIGATION_DATA_H_
-
-#include <memory>
-
-namespace content {
-
-// Copyable interface for embedders to pass opaque data to content/. It is
-// expected to be created on the IO thread, and content/ will transfer it to the
-// UI thread as a clone.
-class NavigationData {
- public:
-  virtual ~NavigationData() {}
-
-  // Creates a new NavigationData that is a deep copy of the original
-  virtual std::unique_ptr<NavigationData> Clone() = 0;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_PUBLIC_BROWSER_NAVIGATION_DATA_H_
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index af69b3d2..77af5b4 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -29,7 +29,6 @@
 
 namespace content {
 struct GlobalRequestID;
-class NavigationData;
 class NavigationThrottle;
 class NavigationUIData;
 class RenderFrameHost;
@@ -320,6 +319,9 @@
   //   navigation entry.
   virtual int GetNavigationEntryOffset() = 0;
 
+  virtual void RegisterSubresourceOverride(
+      mojom::TransferrableURLLoaderPtr transferrable_loader) = 0;
+
   // Testing methods ----------------------------------------------------------
   //
   // The following methods should be used exclusively for writing unit tests.
@@ -336,14 +338,6 @@
 
   // Returns whether this navigation is currently deferred.
   virtual bool IsDeferredForTesting() = 0;
-
-  // The NavigationData that the embedder returned from
-  // ResourceDispatcherHostDelegate::GetNavigationData during commit. This will
-  // be a clone of the NavigationData.
-  virtual NavigationData* GetNavigationData() = 0;
-
-  virtual void RegisterSubresourceOverride(
-      mojom::TransferrableURLLoaderPtr transferrable_loader) = 0;
 };
 
 }  // namespace content
diff --git a/content/public/browser/resource_dispatcher_host_delegate.cc b/content/public/browser/resource_dispatcher_host_delegate.cc
index 95c5d64..7ef14a6 100644
--- a/content/public/browser/resource_dispatcher_host_delegate.cc
+++ b/content/public/browser/resource_dispatcher_host_delegate.cc
@@ -4,7 +4,6 @@
 
 #include "content/public/browser/resource_dispatcher_host_delegate.h"
 
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/resource_request_info.h"
 
 namespace content {
@@ -45,9 +44,4 @@
 void ResourceDispatcherHostDelegate::RequestComplete(
     net::URLRequest* url_request) {}
 
-NavigationData* ResourceDispatcherHostDelegate::GetNavigationData(
-    net::URLRequest* request) const {
-  return nullptr;
-}
-
 }  // namespace content
diff --git a/content/public/browser/resource_dispatcher_host_delegate.h b/content/public/browser/resource_dispatcher_host_delegate.h
index aed1343..c414b4db 100644
--- a/content/public/browser/resource_dispatcher_host_delegate.h
+++ b/content/public/browser/resource_dispatcher_host_delegate.h
@@ -25,7 +25,6 @@
 namespace content {
 
 class AppCacheService;
-class NavigationData;
 class ResourceContext;
 class ResourceThrottle;
 
@@ -73,10 +72,6 @@
   // Deprecated.
   // TODO(maksims): Remove this once all the callers are modified.
   virtual void RequestComplete(net::URLRequest* url_request);
-
-  // Asks the embedder for NavigationData related to this request. It is only
-  // called for navigation requests.
-  virtual NavigationData* GetNavigationData(net::URLRequest* request) const;
 };
 
 }  // namespace content
diff --git a/content/public/test/mock_navigation_handle.h b/content/public/test/mock_navigation_handle.h
index 569bb0b..e5809ef 100644
--- a/content/public/test/mock_navigation_handle.h
+++ b/content/public/test/mock_navigation_handle.h
@@ -91,7 +91,6 @@
   MOCK_METHOD1(RegisterThrottleForTesting,
                void(std::unique_ptr<NavigationThrottle>));
   MOCK_METHOD0(IsDeferredForTesting, bool());
-  NavigationData* GetNavigationData() override { return nullptr; }
   MOCK_METHOD1(RegisterSubresourceOverride,
                void(mojom::TransferrableURLLoaderPtr));
   MOCK_METHOD0(IsSameProcess, bool());
diff --git a/content/public/test/web_contents_tester.h b/content/public/test/web_contents_tester.h
index 292a667..411e9188 100644
--- a/content/public/test/web_contents_tester.h
+++ b/content/public/test/web_contents_tester.h
@@ -29,7 +29,6 @@
 namespace content {
 
 class BrowserContext;
-class NavigationData;
 class NavigationHandle;
 class RenderFrameHost;
 
@@ -112,11 +111,6 @@
                                const GURL& url,
                                ui::PageTransition transition) = 0;
 
-  // Sets NavgationData on |navigation_handle|.
-  virtual void SetNavigationData(
-      NavigationHandle* navigation_handle,
-      std::unique_ptr<NavigationData> navigation_data) = 0;
-
   // Sets HttpResponseData on |navigation_handle|.
   virtual void SetHttpResponseHeaders(
       NavigationHandle* navigation_handle,
diff --git a/content/shell/browser/web_test/web_test_message_filter.cc b/content/shell/browser/web_test/web_test_message_filter.cc
index ff3f157..b2e1510 100644
--- a/content/shell/browser/web_test/web_test_message_filter.cc
+++ b/content/shell/browser/web_test/web_test_message_filter.cc
@@ -59,7 +59,8 @@
   BrowserThread::DeleteOnUIThread::Destruct(this);
 }
 
-base::TaskRunner* WebTestMessageFilter::OverrideTaskRunnerForMessage(
+scoped_refptr<base::SequencedTaskRunner>
+WebTestMessageFilter::OverrideTaskRunnerForMessage(
     const IPC::Message& message) {
   switch (message.type()) {
     case WebTestHostMsg_ClearAllDatabases::ID:
@@ -73,8 +74,7 @@
     case WebTestHostMsg_InitiateCaptureDump::ID:
     case WebTestHostMsg_InspectSecondaryWindow::ID:
     case WebTestHostMsg_DeleteAllCookies::ID:
-      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI})
-          .get();
+      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
   }
   return nullptr;
 }
diff --git a/content/shell/browser/web_test/web_test_message_filter.h b/content/shell/browser/web_test/web_test_message_filter.h
index daa984c..a1f7f1b 100644
--- a/content/shell/browser/web_test/web_test_message_filter.h
+++ b/content/shell/browser/web_test/web_test_message_filter.h
@@ -55,7 +55,7 @@
 
   // BrowserMessageFilter implementation.
   void OnDestruct() const override;
-  base::TaskRunner* OverrideTaskRunnerForMessage(
+  scoped_refptr<base::SequencedTaskRunner> OverrideTaskRunnerForMessage(
       const IPC::Message& message) override;
   bool OnMessageReceived(const IPC::Message& message) override;
 
diff --git a/content/test/content_test_launcher.cc b/content/test/content_test_launcher.cc
index e8a53f3e..37aec6f6 100644
--- a/content/test/content_test_launcher.cc
+++ b/content/test/content_test_launcher.cc
@@ -24,6 +24,10 @@
 #include "ui/base/buildflags.h"
 #include "ui/base/ui_base_switches.h"
 
+#if defined(OS_WIN)
+#include "base/win/win_util.h"
+#endif  // OS_WIN
+
 namespace content {
 
 class ContentBrowserTestSuite : public ContentTestSuiteBase {
@@ -86,6 +90,11 @@
   if (parallel_jobs > 1U) {
     parallel_jobs /= 2U;
   }
+#if defined(OS_WIN)
+  // Load and pin user32.dll to avoid having to load it once tests start while
+  // on the main thread loop where blocking calls are disallowed.
+  base::win::PinUser32();
+#endif  // OS_WIN
   content::ContentTestLauncherDelegate launcher_delegate;
   return LaunchTests(&launcher_delegate, parallel_jobs, argc, argv);
 }
diff --git a/content/test/test_navigation_url_loader.cc b/content/test/test_navigation_url_loader.cc
index 555f76a..6026fa4 100644
--- a/content/test/test_navigation_url_loader.cc
+++ b/content/test/test_navigation_url_loader.cc
@@ -9,7 +9,6 @@
 #include "content/browser/loader/navigation_url_loader_delegate.h"
 #include "content/browser/navigation_subresource_loader_params.h"
 #include "content/public/browser/global_request_id.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/ssl_status.h"
@@ -68,8 +67,7 @@
 }
 
 void TestNavigationURLLoader::CallOnResponseStarted(
-    const scoped_refptr<network::ResourceResponse>& response,
-    std::unique_ptr<NavigationData> navigation_data) {
+    const scoped_refptr<network::ResourceResponse>& response) {
   // Start the request_ids at 1000 to avoid collisions with request ids from
   // network resources (it should be rare to compare these in unit tests).
   static int request_id = 1000;
@@ -94,8 +92,8 @@
           url_loader_ptr.PassInterface(), std::move(url_loader_client_request));
 
   delegate_->OnResponseStarted(response, std::move(url_loader_client_endpoints),
-                               std::move(navigation_data), global_id, false,
-                               NavigationDownloadPolicy(), base::nullopt);
+                               global_id, false, NavigationDownloadPolicy(),
+                               base::nullopt);
 }
 
 TestNavigationURLLoader::~TestNavigationURLLoader() {}
diff --git a/content/test/test_navigation_url_loader.h b/content/test/test_navigation_url_loader.h
index f7cd812..1d8ce23 100644
--- a/content/test/test_navigation_url_loader.h
+++ b/content/test/test_navigation_url_loader.h
@@ -22,7 +22,6 @@
 
 namespace content {
 
-class NavigationData;
 class NavigationURLLoaderDelegate;
 
 // Test implementation of NavigationURLLoader to simulate the network stack
@@ -52,8 +51,7 @@
       const net::RedirectInfo& redirect_info,
       const scoped_refptr<network::ResourceResponse>& response);
   void CallOnResponseStarted(
-      const scoped_refptr<network::ResourceResponse>& response,
-      std::unique_ptr<NavigationData> navigation_data);
+      const scoped_refptr<network::ResourceResponse>& response);
 
   int redirect_count() { return redirect_count_; }
 
diff --git a/content/test/test_navigation_url_loader_delegate.cc b/content/test/test_navigation_url_loader_delegate.cc
index 05a7a5d3..1cd221ecd 100644
--- a/content/test/test_navigation_url_loader_delegate.cc
+++ b/content/test/test_navigation_url_loader_delegate.cc
@@ -8,7 +8,6 @@
 #include "content/browser/navigation_subresource_loader_params.h"
 #include "content/common/navigation_params.h"
 #include "content/public/browser/global_request_id.h"
-#include "content/public/browser/navigation_data.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -59,7 +58,6 @@
 void TestNavigationURLLoaderDelegate::OnResponseStarted(
     const scoped_refptr<network::ResourceResponse>& response,
     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-    std::unique_ptr<NavigationData> navigation_data,
     const GlobalRequestID& request_id,
     bool is_download,
     NavigationDownloadPolicy download_policy,
diff --git a/content/test/test_navigation_url_loader_delegate.h b/content/test/test_navigation_url_loader_delegate.h
index 5abfe62b..f658b5e 100644
--- a/content/test/test_navigation_url_loader_delegate.h
+++ b/content/test/test_navigation_url_loader_delegate.h
@@ -63,7 +63,6 @@
   void OnResponseStarted(
       const scoped_refptr<network::ResourceResponse>& response,
       network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
-      std::unique_ptr<NavigationData> navigation_data,
       const GlobalRequestID& request_id,
       bool is_download,
       NavigationDownloadPolicy download_policy,
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index dcd35c4..c72007e 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -428,7 +428,7 @@
   response->head.ssl_info = ssl_info;
   // TODO(carlosk): Ideally, it should be possible someday to
   // fully commit the navigation at this call to CallOnResponseStarted.
-  url_loader->CallOnResponseStarted(response, nullptr);
+  url_loader->CallOnResponseStarted(response);
 }
 
 void TestRenderFrameHost::SimulateCommitProcessed(
diff --git a/content/test/test_web_contents.cc b/content/test/test_web_contents.cc
index 4d42b2a..020ea03e 100644
--- a/content/test/test_web_contents.cc
+++ b/content/test/test_web_contents.cc
@@ -22,7 +22,6 @@
 #include "content/common/frame_messages.h"
 #include "content/common/render_message_filter.mojom.h"
 #include "content/common/view_messages.h"
-#include "content/public/browser/navigation_data.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/notification_types.h"
@@ -371,13 +370,6 @@
             history_length);
 }
 
-void TestWebContents::SetNavigationData(
-    NavigationHandle* navigation_handle,
-    std::unique_ptr<NavigationData> navigation_data) {
-  static_cast<NavigationHandleImpl*>(navigation_handle)
-      ->set_navigation_data(std::move(navigation_data));
-}
-
 void TestWebContents::SetHttpResponseHeaders(
     NavigationHandle* navigation_handle,
     scoped_refptr<net::HttpResponseHeaders> response_headers) {
diff --git a/content/test/test_web_contents.h b/content/test/test_web_contents.h
index 1640763..dbc195a8 100644
--- a/content/test/test_web_contents.h
+++ b/content/test/test_web_contents.h
@@ -36,7 +36,6 @@
 
 namespace content {
 
-class NavigationData;
 class NavigationHandle;
 class RenderViewHost;
 class TestRenderViewHost;
@@ -92,9 +91,6 @@
                                          bool was_within_same_document,
                                          int item_sequence_number,
                                          int document_sequence_number);
-  void SetNavigationData(
-      NavigationHandle* navigation_handle,
-      std::unique_ptr<NavigationData> navigation_data) override;
   void SetHttpResponseHeaders(
       NavigationHandle* navigation_handle,
       scoped_refptr<net::HttpResponseHeaders> response_headers) override;
diff --git a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
index e05f19f..57d19cbd 100644
--- a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
+++ b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
@@ -1994,6 +1994,29 @@
 void AutomationInternalCustomBindings::MaybeSendFocusAndBlur(
     AutomationAXTreeWrapper* tree,
     const ExtensionMsg_AccessibilityEventBundleParams& event_bundle) {
+  // Only send focus or blur if we got one of these events from the originating
+  // renderer. While sending events purely based upon whether the targeted
+  // focus/blur node changed may work, we end up firing too many events since
+  // intermediate states trigger more events than likely necessary. Also, the
+  // |event_from| field is only properly associated with focus/blur when the
+  // event type is also focus/blur.
+  base::Optional<ax::mojom::EventFrom> event_from;
+  for (const auto& event : event_bundle.events) {
+    if (event.event_type == ax::mojom::Event::kBlur ||
+        event.event_type == ax::mojom::Event::kFocus)
+      event_from = event.event_from;
+  }
+
+  if (!event_from) {
+    // There was no explicit focus/blur; return early.
+    // Make an exception for the desktop tree, where we can reliably infer
+    // focus/blur even without an explicit event.
+    if (!tree->IsDesktopTree())
+      return;
+
+    event_from = ax::mojom::EventFrom::kNone;
+  }
+
   // Get the root-most tree.
   AutomationAXTreeWrapper* root_tree = tree;
   while (
@@ -2014,18 +2037,11 @@
   if (new_wrapper == old_wrapper && new_node == old_node)
     return;
 
-  ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
-  for (const auto& event : event_bundle.events) {
-    if (event.event_type == ax::mojom::Event::kFocus ||
-        event.event_type == ax::mojom::Event::kBlur)
-      event_from = event.event_from;
-  }
-
   // Blur previous focus.
   if (old_node) {
     ui::AXEvent blur_event;
     blur_event.id = old_node->id();
-    blur_event.event_from = event_from;
+    blur_event.event_from = *event_from;
     SendAutomationEvent(old_wrapper->tree_id(), event_bundle.mouse_location,
                         blur_event, api::automation::EVENT_TYPE_BLUR);
 
@@ -2037,7 +2053,7 @@
   if (new_node) {
     ui::AXEvent focus_event;
     focus_event.id = new_node->id();
-    focus_event.event_from = event_from;
+    focus_event.event_from = *event_from;
     SendAutomationEvent(new_wrapper->tree_id(), event_bundle.mouse_location,
                         focus_event, api::automation::EVENT_TYPE_FOCUS);
     focus_id_ = new_node->id();
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index 5dd737dcc..ff295ca 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -115,6 +115,8 @@
     "browser/web_engine_content_browser_client.h",
     "browser/web_engine_devtools_manager_delegate.cc",
     "browser/web_engine_devtools_manager_delegate.h",
+    "browser/web_engine_devtools_socket_factory.cc",
+    "browser/web_engine_devtools_socket_factory.h",
     "browser/web_engine_net_log.cc",
     "browser/web_engine_net_log.h",
     "browser/web_engine_screen.cc",
@@ -231,6 +233,31 @@
   ]
 }
 
+test("web_engine_integration_tests") {
+  sandbox_policy = "integration_tests_sandbox_policy"
+  sources = [
+    "test_debug_listener.cc",
+    "test_debug_listener.h",
+    "web_engine_debug_integration_test.cc",
+  ]
+  data = [
+    "test/data",
+  ]
+  deps = [
+    "//base",
+    "//base/test:run_all_unittests",
+    "//fuchsia/base",
+    "//fuchsia/base:test_support",
+    "//net",
+    "//net:test_support",
+    "//third_party/fuchsia-sdk/sdk:web",
+  ]
+  package_deps = [ [
+        ":web_engine",
+        "chromium",
+      ] ]
+}
+
 if (is_official_build) {
   symbol_archive("symbol_archive") {
     deps = [
diff --git a/fuchsia/engine/browser/context_impl.cc b/fuchsia/engine/browser/context_impl.cc
index 83be083f..2c0ae51 100644
--- a/fuchsia/engine/browser/context_impl.cc
+++ b/fuchsia/engine/browser/context_impl.cc
@@ -9,14 +9,57 @@
 #include <utility>
 
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/strings/string_tokenizer.h"
 #include "content/public/browser/web_contents.h"
 #include "fuchsia/engine/browser/frame_impl.h"
+#include "fuchsia/engine/browser/web_engine_browser_context.h"
+#include "fuchsia/engine/common.h"
 
 ContextImpl::ContextImpl(content::BrowserContext* browser_context)
-    : browser_context_(browser_context) {}
+    : browser_context_(browser_context) {
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          kRemoteDebuggerHandles)) {
+    std::string handle_ids_str =
+        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+            kRemoteDebuggerHandles);
+
+    // Extract individual handle IDs from the comma-separated list.
+    base::StringTokenizer tokenizer(handle_ids_str, ",");
+    while (tokenizer.GetNext()) {
+      uint32_t handle_id = 0;
+      if (!base::StringToUint(tokenizer.token(), &handle_id))
+        continue;
+      fidl::InterfacePtr<fuchsia::web::DevToolsPerContextListener> listener;
+      listener.Bind(zx::channel(zx_take_startup_handle(handle_id)));
+      devtools_listeners_.AddInterfacePtr(std::move(listener));
+    }
+  }
+}
 
 ContextImpl::~ContextImpl() = default;
 
+void ContextImpl::DestroyFrame(FrameImpl* frame) {
+  DCHECK(frames_.find(frame) != frames_.end());
+  frames_.erase(frames_.find(frame));
+}
+
+bool ContextImpl::IsJavaScriptInjectionAllowed() {
+  return allow_javascript_injection_;
+}
+
+void ContextImpl::OnDevToolsPortReady() {
+  if (devtools_port_ == 0 || devtools_listeners_notified_)
+    return;
+  for (auto& listener : devtools_listeners_.ptrs()) {
+    listener->get()->OnHttpPortOpen(devtools_port_);
+  }
+  devtools_listeners_notified_ = true;
+}
+
+void ContextImpl::OnDevToolsPortOpened(uint16_t port) {
+  devtools_port_ = port;
+}
+
 void ContextImpl::CreateFrame(
     fidl::InterfaceRequest<fuchsia::web::Frame> frame) {
   content::WebContents::CreateParams create_params(browser_context_, nullptr);
@@ -27,15 +70,6 @@
                                              std::move(frame)));
 }
 
-void ContextImpl::DestroyFrame(FrameImpl* frame) {
-  DCHECK(frames_.find(frame) != frames_.end());
-  frames_.erase(frames_.find(frame));
-}
-
-bool ContextImpl::IsJavaScriptInjectionAllowed() {
-  return allow_javascript_injection_;
-}
-
 FrameImpl* ContextImpl::GetFrameImplForTest(fuchsia::web::FramePtr* frame_ptr) {
   DCHECK(frame_ptr);
 
diff --git a/fuchsia/engine/browser/context_impl.h b/fuchsia/engine/browser/context_impl.h
index 1475b60..96ef6a9 100644
--- a/fuchsia/engine/browser/context_impl.h
+++ b/fuchsia/engine/browser/context_impl.h
@@ -6,6 +6,7 @@
 #define FUCHSIA_ENGINE_BROWSER_CONTEXT_IMPL_H_
 
 #include <fuchsia/web/cpp/fidl.h>
+#include <lib/fidl/cpp/interface_ptr_set.h>
 #include <memory>
 #include <set>
 
@@ -40,6 +41,14 @@
   // Returns |true| if JS injection was enabled for this Context.
   bool IsJavaScriptInjectionAllowed();
 
+  // Called by Frames to signal a document has been loaded and signal to the
+  // |devtools_listeners_| that they can now successfully connect ChromeDriver
+  // on |devtools_port_|.
+  void OnDevToolsPortReady();
+
+  // Signals the DevTools debugging port has been opened.
+  void OnDevToolsPortOpened(uint16_t port);
+
   // fuchsia::web::Context implementation.
   void CreateFrame(fidl::InterfaceRequest<fuchsia::web::Frame> frame) override;
 
@@ -48,7 +57,7 @@
   FrameImpl* GetFrameImplForTest(fuchsia::web::FramePtr* frame_ptr);
 
  private:
-  content::BrowserContext* browser_context_;
+  content::BrowserContext* const browser_context_;
 
   // TODO(crbug.com/893236): Make this false by default, and allow it to be
   // initialized at Context creation time.
@@ -58,6 +67,11 @@
   // destruction when this ContextImpl is destroyed.
   std::set<std::unique_ptr<FrameImpl>, base::UniquePtrComparator> frames_;
 
+  fidl::InterfacePtrSet<fuchsia::web::DevToolsPerContextListener>
+      devtools_listeners_;
+  bool devtools_listeners_notified_ = false;
+  uint16_t devtools_port_ = 0;
+
   DISALLOW_COPY_AND_ASSIGN(ContextImpl);
 };
 
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index 10f2152..0ffbd22 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -694,8 +694,9 @@
 
 void FrameImpl::DidFinishLoad(content::RenderFrameHost* render_frame_host,
                               const GURL& validated_url) {
-  // The document and its statically-declared subresources are loaded.
+  context_->OnDevToolsPortReady();
 
+  // The document and its statically-declared subresources are loaded.
   is_main_document_loaded_ = true;
   OnNavigationEntryChanged();
 }
diff --git a/fuchsia/engine/browser/web_engine_browser_main_parts.cc b/fuchsia/engine/browser/web_engine_browser_main_parts.cc
index 1539ab9f..e93f67b 100644
--- a/fuchsia/engine/browser/web_engine_browser_main_parts.cc
+++ b/fuchsia/engine/browser/web_engine_browser_main_parts.cc
@@ -10,9 +10,11 @@
 #include "base/files/file_util.h"
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/logging.h"
+#include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/render_frame_host.h"
 #include "fuchsia/engine/browser/context_impl.h"
 #include "fuchsia/engine/browser/web_engine_browser_context.h"
+#include "fuchsia/engine/browser/web_engine_devtools_socket_factory.h"
 #include "fuchsia/engine/browser/web_engine_screen.h"
 #include "fuchsia/engine/common.h"
 #include "ui/aura/screen_ozone.h"
@@ -49,6 +51,16 @@
   context_binding_ = std::make_unique<fidl::Binding<fuchsia::web::Context>>(
       context_service_.get(), std::move(request_));
 
+  // Start the remote debugging server.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          kRemoteDebuggerHandles)) {
+    content::DevToolsAgentHost::StartRemoteDebuggingServer(
+        std::make_unique<WebEngineDevToolsSocketFactory>(
+            base::BindRepeating(&ContextImpl::OnDevToolsPortOpened,
+                                base::Unretained(context_service_.get()))),
+        browser_context_->GetPath(), base::FilePath());
+  }
+
   // Quit the browser main loop when the Context connection is dropped.
   context_binding_->set_error_handler([this](zx_status_t status) {
     ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status)
@@ -74,6 +86,11 @@
   DCHECK(!context_service_);
   DCHECK(!context_binding_->is_bound());
 
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          kRemoteDebuggerHandles)) {
+    content::DevToolsAgentHost::StopRemoteDebuggingServer();
+  }
+
   // These resources must be freed while a MessageLoop is still available, so
   // that they may post cleanup tasks during teardown.
   // NOTE: Please destroy objects in the reverse order of their creation.
diff --git a/fuchsia/engine/browser/web_engine_devtools_socket_factory.cc b/fuchsia/engine/browser/web_engine_devtools_socket_factory.cc
new file mode 100644
index 0000000..a80d661
--- /dev/null
+++ b/fuchsia/engine/browser/web_engine_devtools_socket_factory.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 "fuchsia/engine/browser/web_engine_devtools_socket_factory.h"
+#include "fuchsia/engine/browser/web_engine_browser_context.h"
+#include "net/base/net_errors.h"
+#include "net/socket/tcp_server_socket.h"
+
+namespace {
+
+const int kTcpListenBackLog = 5;
+
+}  //  namespace
+
+WebEngineDevToolsSocketFactory::WebEngineDevToolsSocketFactory(
+    OnPortOpenedCallback callback)
+    : callback_(std::move(callback)) {}
+
+WebEngineDevToolsSocketFactory::~WebEngineDevToolsSocketFactory() = default;
+
+std::unique_ptr<net::ServerSocket>
+WebEngineDevToolsSocketFactory::CreateForHttpServer() {
+  std::unique_ptr<net::ServerSocket> socket(
+      new net::TCPServerSocket(nullptr, net::NetLogSource()));
+  if (socket->Listen(net::IPEndPoint(net::IPAddress::IPv4Localhost(), 0),
+                     kTcpListenBackLog) == net::OK) {
+    net::IPEndPoint end_point;
+    socket->GetLocalAddress(&end_point);
+    callback_.Run(end_point.port());
+    return socket;
+  }
+  int error = socket->Listen(
+      net::IPEndPoint(net::IPAddress::IPv6Localhost(), 0), kTcpListenBackLog);
+  if (error == net::OK) {
+    net::IPEndPoint end_point;
+    socket->GetLocalAddress(&end_point);
+    callback_.Run(end_point.port());
+    return socket;
+  }
+  LOG(WARNING) << "Failed to start the HTTP debugger service. "
+               << net::ErrorToString(error);
+  return nullptr;
+}
+
+std::unique_ptr<net::ServerSocket>
+WebEngineDevToolsSocketFactory::CreateForTethering(std::string* out_name) {
+  return nullptr;
+}
diff --git a/fuchsia/engine/browser/web_engine_devtools_socket_factory.h b/fuchsia/engine/browser/web_engine_devtools_socket_factory.h
new file mode 100644
index 0000000..e35d933
--- /dev/null
+++ b/fuchsia/engine/browser/web_engine_devtools_socket_factory.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 FUCHSIA_ENGINE_BROWSER_WEB_ENGINE_DEVTOOLS_SOCKET_FACTORY_H_
+#define FUCHSIA_ENGINE_BROWSER_WEB_ENGINE_DEVTOOLS_SOCKET_FACTORY_H_
+
+#include "base/callback.h"
+#include "content/public/browser/devtools_socket_factory.h"
+
+class WebEngineDevToolsSocketFactory : public content::DevToolsSocketFactory {
+ public:
+  using OnPortOpenedCallback = base::RepeatingCallback<void(uint16_t)>;
+
+  explicit WebEngineDevToolsSocketFactory(OnPortOpenedCallback callback);
+  ~WebEngineDevToolsSocketFactory() override;
+
+  // content::DevToolsSocketFactory implementation.
+  std::unique_ptr<net::ServerSocket> CreateForHttpServer() override;
+  std::unique_ptr<net::ServerSocket> CreateForTethering(
+      std::string* out_name) override;
+
+ private:
+  OnPortOpenedCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebEngineDevToolsSocketFactory);
+};
+
+#endif  // FUCHSIA_ENGINE_BROWSER_WEB_ENGINE_DEVTOOLS_SOCKET_FACTORY_H_
diff --git a/fuchsia/engine/common.cc b/fuchsia/engine/common.cc
index 2c43db2b..61927a9 100644
--- a/fuchsia/engine/common.cc
+++ b/fuchsia/engine/common.cc
@@ -5,3 +5,4 @@
 #include "fuchsia/engine/common.h"
 
 constexpr char kIncognitoSwitch[] = "incognito";
+constexpr char kRemoteDebuggerHandles[] = "remote-debugger-handles";
diff --git a/fuchsia/engine/common.h b/fuchsia/engine/common.h
index 350067c..b6f2672 100644
--- a/fuchsia/engine/common.h
+++ b/fuchsia/engine/common.h
@@ -9,12 +9,16 @@
 
 #include "fuchsia/engine/web_engine_export.h"
 
-// Switch passed to content process when running in incognito mode, i.e. when
+// This file contains constants and functions shared between Context and
+// ContextProvider processes.
+
+// Switch passed to Context process when running in incognito mode, i.e. when
 // there is no kWebContextDataPath.
 WEB_ENGINE_EXPORT extern const char kIncognitoSwitch[];
 
-// This file contains constants and functions shared between Context and
-// ContextProvider processes.
+// Switch passed to Context process when enabling remote debugging. It takes
+// a comma-separated list of remote debugger handle IDs as an argument.
+WEB_ENGINE_EXPORT extern const char kRemoteDebuggerHandles[];
 
 // Handle ID for the Context interface request passed from ContextProvider to
 // Context process.
diff --git a/fuchsia/engine/context_provider_impl.cc b/fuchsia/engine/context_provider_impl.cc
index de2c27e..53e7fb2ff 100644
--- a/fuchsia/engine/context_provider_impl.cc
+++ b/fuchsia/engine/context_provider_impl.cc
@@ -22,6 +22,8 @@
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 #include "fuchsia/engine/common.h"
 #include "services/service_manager/sandbox/fuchsia/sandbox_policy_fuchsia.h"
 
@@ -110,12 +112,27 @@
   }
   launch_options.job_handle = job.get();
 
-  const base::CommandLine* launch_command =
-      base::CommandLine::ForCurrentProcess();
+  base::CommandLine launch_command(*base::CommandLine::ForCurrentProcess());
+  if (devtools_listeners_.size() != 0) {
+    std::vector<std::string> handles_ids;
+    for (auto& devtools_listener : devtools_listeners_.ptrs()) {
+      fidl::InterfaceHandle<fuchsia::web::DevToolsPerContextListener>
+          client_listener;
+      devtools_listener.get()->get()->OnContextDevToolsAvailable(
+          client_listener.NewRequest());
+      handles_ids.push_back(
+          base::NumberToString(base::LaunchOptions::AddHandleToTransfer(
+              &launch_options.handles_to_transfer,
+              client_listener.TakeChannel().release())));
+    }
+    launch_command.AppendSwitchNative(kRemoteDebuggerHandles,
+                                      base::JoinString(handles_ids, ","));
+  }
+
   if (launch_for_test_)
-    launch_for_test_.Run(*launch_command, launch_options);
+    launch_for_test_.Run(launch_command, launch_options);
   else
-    base::LaunchProcess(*launch_command, launch_options);
+    base::LaunchProcess(launch_command, launch_options);
 
   // |context_handle| was transferred (not copied) to the Context process.
   ignore_result(context_handle.release());
@@ -125,3 +142,10 @@
     LaunchCallbackForTest launch) {
   launch_for_test_ = std::move(launch);
 }
+
+void ContextProviderImpl::EnableDevTools(
+    fidl::InterfaceHandle<fuchsia::web::DevToolsListener> listener,
+    EnableDevToolsCallback callback) {
+  devtools_listeners_.AddInterfacePtr(listener.Bind());
+  callback();
+}
diff --git a/fuchsia/engine/context_provider_impl.h b/fuchsia/engine/context_provider_impl.h
index e25a982a..b08fdaa 100644
--- a/fuchsia/engine/context_provider_impl.h
+++ b/fuchsia/engine/context_provider_impl.h
@@ -7,6 +7,7 @@
 
 #include <fuchsia/web/cpp/fidl.h>
 #include <lib/fidl/cpp/binding_set.h>
+#include <lib/fidl/cpp/interface_ptr_set.h>
 #include <memory>
 
 #include "base/callback.h"
@@ -20,7 +21,8 @@
 }  // namespace base
 
 class WEB_ENGINE_EXPORT ContextProviderImpl
-    : public fuchsia::web::ContextProvider {
+    : public fuchsia::web::ContextProvider,
+      public fuchsia::web::Debug {
  public:
   using LaunchCallbackForTest = base::RepeatingCallback<base::Process(
       const base::CommandLine& command,
@@ -39,10 +41,18 @@
   void SetLaunchCallbackForTest(LaunchCallbackForTest launch);
 
  private:
+  // fuchsia::web::Debug implementation.
+  void EnableDevTools(
+      fidl::InterfaceHandle<fuchsia::web::DevToolsListener> listener,
+      EnableDevToolsCallback callback) override;
+
   // Set by tests to use to launch Context child processes, e.g. to allow a
   // fake Context process to be launched.
   LaunchCallbackForTest launch_for_test_;
 
+  // The DevToolsListeners registered via the Debug interface.
+  fidl::InterfacePtrSet<fuchsia::web::DevToolsListener> devtools_listeners_;
+
   DISALLOW_COPY_AND_ASSIGN(ContextProviderImpl);
 };
 
diff --git a/fuchsia/engine/context_provider_main.cc b/fuchsia/engine/context_provider_main.cc
index 0d25951..d4b4675 100644
--- a/fuchsia/engine/context_provider_main.cc
+++ b/fuchsia/engine/context_provider_main.cc
@@ -21,9 +21,8 @@
   base::fuchsia::ScopedServiceBinding<fuchsia::web::ContextProvider> binding(
       directory, &context_provider);
 
-  fuchsia::web::ContextProviderPtr fuchsia_context_provider;
-  fidl::Binding<fuchsia::web::ContextProvider> fuchsia_binding(
-      &context_provider, fuchsia_context_provider.NewRequest());
+  base::fuchsia::ScopedServiceBinding<fuchsia::web::Debug> debug_binding(
+      directory->debug(), &context_provider);
 
   base::RunLoop run_loop;
   cr_fuchsia::LifecycleImpl lifecycle(directory, run_loop.QuitClosure());
diff --git a/fuchsia/engine/integration_tests_sandbox_policy b/fuchsia/engine/integration_tests_sandbox_policy
new file mode 100644
index 0000000..f987ae2
--- /dev/null
+++ b/fuchsia/engine/integration_tests_sandbox_policy
@@ -0,0 +1,14 @@
+{
+  "features": [
+      "isolated-persistent-storage",
+      "shell",
+      "system-temp" ],
+  "services": [
+      "fuchsia.logger.LogSink",
+      "fuchsia.net.SocketProvider",
+      "fuchsia.netstack.Netstack",
+      "fuchsia.process.Launcher",
+      "fuchsia.sysmem.Allocator",
+      "fuchsia.web.ContextProvider"
+  ]
+}
diff --git a/fuchsia/engine/test_debug_listener.cc b/fuchsia/engine/test_debug_listener.cc
new file mode 100644
index 0000000..9f3c67031
--- /dev/null
+++ b/fuchsia/engine/test_debug_listener.cc
@@ -0,0 +1,61 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/engine/test_debug_listener.h"
+
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TestDebugListener::TestDebugListener() {}
+TestDebugListener::~TestDebugListener() = default;
+
+void TestDebugListener::DestroyListener(TestPerContextListener* listener) {
+  EXPECT_EQ(per_context_listeners_.erase(listener), 1u);
+}
+
+void TestDebugListener::AddPort(uint16_t port) {
+  EXPECT_EQ(debug_ports_.find(port), debug_ports_.end());
+  debug_ports_.insert(port);
+  if (run_ack_)
+    std::move(run_ack_).Run();
+}
+
+void TestDebugListener::RemovePort(uint16_t port) {
+  EXPECT_EQ(debug_ports_.erase(port), 1u);
+  if (run_ack_)
+    std::move(run_ack_).Run();
+}
+
+void TestDebugListener::RunUntilNumberOfPortsIs(size_t size) {
+  while (debug_ports_.size() != size) {
+    base::RunLoop run_loop;
+    run_ack_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+}
+
+TestDebugListener::TestPerContextListener::TestPerContextListener(
+    TestDebugListener* test_debug_listener,
+    fidl::InterfaceRequest<fuchsia::web::DevToolsPerContextListener> listener)
+    : test_debug_listener_(test_debug_listener),
+      binding_(this, std::move(listener)) {
+  binding_.set_error_handler([this](zx_status_t) {
+    if (port_ != 0)
+      test_debug_listener_->RemovePort(port_);
+    test_debug_listener_->DestroyListener(this);
+  });
+}
+
+TestDebugListener::TestPerContextListener::~TestPerContextListener() = default;
+
+void TestDebugListener::TestPerContextListener::OnHttpPortOpen(uint16_t port) {
+  port_ = port;
+  test_debug_listener_->AddPort(port);
+}
+
+void TestDebugListener::OnContextDevToolsAvailable(
+    fidl::InterfaceRequest<fuchsia::web::DevToolsPerContextListener> listener) {
+  per_context_listeners_.insert(
+      std::make_unique<TestPerContextListener>(this, std::move(listener)));
+}
diff --git a/fuchsia/engine/test_debug_listener.h b/fuchsia/engine/test_debug_listener.h
new file mode 100644
index 0000000..fba93a7
--- /dev/null
+++ b/fuchsia/engine/test_debug_listener.h
@@ -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.
+
+#ifndef FUCHSIA_ENGINE_TEST_DEBUG_LISTENER_H_
+#define FUCHSIA_ENGINE_TEST_DEBUG_LISTENER_H_
+
+#include <fuchsia/web/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/callback.h"
+#include "base/containers/flat_set.h"
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/macros.h"
+
+// Listens to debug events and enables test code to block until a desired
+// number of DevTools ports are open.
+class TestDebugListener : public fuchsia::web::DevToolsListener {
+ public:
+  TestDebugListener();
+  ~TestDebugListener() final;
+
+  // Spins a RunLoop until there are exactly |size| DevTools ports open.
+  void RunUntilNumberOfPortsIs(size_t size);
+
+  base::flat_set<uint16_t>& debug_ports() { return debug_ports_; }
+
+ private:
+  class TestPerContextListener
+      : public fuchsia::web::DevToolsPerContextListener {
+   public:
+    TestPerContextListener(
+        TestDebugListener* test_debug_listener,
+        fidl::InterfaceRequest<fuchsia::web::DevToolsPerContextListener>
+            listener);
+    ~TestPerContextListener() final;
+
+   private:
+    // fuchsia::web::DevToolsPerContextListener implementation.
+    void OnHttpPortOpen(uint16_t port) final;
+
+    uint16_t port_ = 0;
+    TestDebugListener* test_debug_listener_;
+    fidl::Binding<fuchsia::web::DevToolsPerContextListener> binding_;
+
+    DISALLOW_COPY_AND_ASSIGN(TestPerContextListener);
+  };
+
+  // fuchsia::web::DevToolsListener implementation.
+  void OnContextDevToolsAvailable(
+      fidl::InterfaceRequest<fuchsia::web::DevToolsPerContextListener> listener)
+      final;
+
+  void DestroyListener(TestPerContextListener* listener);
+  void AddPort(uint16_t port);
+  void RemovePort(uint16_t port);
+
+  base::flat_set<uint16_t> debug_ports_;
+  base::flat_set<std::unique_ptr<TestPerContextListener>,
+                 base::UniquePtrComparator>
+      per_context_listeners_;
+  base::OnceClosure run_ack_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestDebugListener);
+};
+
+#endif  // FUCHSIA_ENGINE_TEST_DEBUG_LISTENER_H_
diff --git a/fuchsia/engine/web_engine_debug_integration_test.cc b/fuchsia/engine/web_engine_debug_integration_test.cc
new file mode 100644
index 0000000..e8d8766
--- /dev/null
+++ b/fuchsia/engine/web_engine_debug_integration_test.cc
@@ -0,0 +1,283 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/web/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/binding_set.h>
+
+#include "base/files/file_enumerator.h"
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/service_directory_client.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "fuchsia/base/fit_adapter.h"
+#include "fuchsia/base/frame_test_util.h"
+#include "fuchsia/base/result_receiver.h"
+#include "fuchsia/base/test_navigation_listener.h"
+#include "fuchsia/engine/test_debug_listener.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTestServerRoot[] = FILE_PATH_LITERAL("fuchsia/engine/test/data");
+
+}  // namespace
+
+class WebEngineDebugIntegrationTest : public testing::Test,
+                                      public net::URLFetcherDelegate {
+ public:
+  WebEngineDebugIntegrationTest()
+      : dev_tools_listener_binding_(&dev_tools_listener_) {}
+
+  ~WebEngineDebugIntegrationTest() override = default;
+
+  void SetUp() override {
+    web_context_provider_ =
+        base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
+            ->ConnectToService<fuchsia::web::ContextProvider>();
+    web_context_provider_.set_error_handler(
+        [](zx_status_t status) { ADD_FAILURE(); });
+
+    WaitForWebEngine();
+
+    // Connect to the Debug API.
+    base::FileEnumerator file_enum(base::FilePath("/hub/c/chromium.cmx"), false,
+                                   base::FileEnumerator::DIRECTORIES);
+    base::FilePath chromium_path = file_enum.Next();
+    ASSERT_FALSE(chromium_path.empty());
+
+    // There should only be one instance of WebEngine in the realm.
+    ASSERT_TRUE(file_enum.Next().empty());
+
+    debug_dir_ = std::make_unique<base::fuchsia::ServiceDirectoryClient>(
+        base::fuchsia::OpenDirectory(chromium_path.Append("out/debug")));
+    debug_ = debug_dir_->ConnectToServiceSync<fuchsia::web::Debug>();
+
+    // Attach the DevToolsListener. EnableDevTools has an acknowledgement
+    // callback so the listener will have been added after this call returns.
+    debug_->EnableDevTools(dev_tools_listener_binding_.NewBinding());
+
+    request_context_getter_ =
+        base::MakeRefCounted<net::TestURLRequestContextGetter>(
+            message_loop_.task_runner());
+
+    test_server_.ServeFilesFromSourceDirectory(kTestServerRoot);
+    ASSERT_TRUE(test_server_.Start());
+  }
+
+ protected:
+  void WaitForWebEngine() {
+    // Create a throwaway web context to ensure the WebEngine process is
+    // initialized and a Debug instance can be created. This is necessary
+    // because the Debug service is not available on the debug directory until
+    // after the WebEngine is fully initialized.
+    fuchsia::web::CreateContextParams create_params;
+    auto directory = base::fuchsia::OpenDirectory(
+        base::FilePath(base::fuchsia::kServiceDirectoryPath));
+    ASSERT_TRUE(directory.is_valid());
+    create_params.set_service_directory(std::move(directory));
+
+    fuchsia::web::ContextPtr web_context;
+    web_context_provider_->Create(std::move(create_params),
+                                  web_context.NewRequest());
+    web_context.set_error_handler([](zx_status_t status) { ADD_FAILURE(); });
+
+    fuchsia::web::FramePtr web_frame;
+    web_context->CreateFrame(web_frame.NewRequest());
+    web_frame.set_error_handler([](zx_status_t status) { ADD_FAILURE(); });
+
+    fuchsia::web::NavigationControllerPtr nav_controller;
+    web_frame->GetNavigationController(nav_controller.NewRequest());
+    nav_controller.set_error_handler([](zx_status_t status) { ADD_FAILURE(); });
+
+    base::RunLoop run_loop;
+    cr_fuchsia::ResultReceiver<fuchsia::web::NavigationState> result(
+        run_loop.QuitClosure());
+    nav_controller->GetVisibleEntry(
+        cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
+    run_loop.Run();
+
+    // Sanity check, the NavigationState should be empty at this point.
+    ASSERT_TRUE(result->IsEmpty());
+  }
+
+  base::Value GetDevToolsListFromPort(uint16_t port) {
+    std::string url = base::StringPrintf("http://127.0.0.1:%d/json/list", port);
+    std::unique_ptr<net::URLFetcher> fetcher = net::URLFetcher::Create(
+        GURL(url), net::URLFetcher::GET, this, TRAFFIC_ANNOTATION_FOR_TESTS);
+    fetcher->SetRequestContext(request_context_getter_.get());
+    fetcher->Start();
+
+    base::RunLoop run_loop;
+    on_url_fetch_complete_ack_ = run_loop.QuitClosure();
+    run_loop.Run();
+
+    if (fetcher->GetStatus().status() != net::URLRequestStatus::SUCCESS)
+      return base::Value();
+
+    if (fetcher->GetResponseCode() != net::HTTP_OK)
+      return base::Value();
+
+    std::string result;
+    if (!fetcher->GetResponseAsString(&result))
+      return base::Value();
+
+    return base::JSONReader::Read(result).value_or(base::Value());
+  }
+
+  // fuchsia::web::URLFetcherDelegate implementation.
+  void OnURLFetchComplete(const net::URLFetcher* source) override {
+    if (on_url_fetch_complete_ack_)
+      std::move(on_url_fetch_complete_ack_).Run();
+  }
+
+  base::MessageLoopForIO message_loop_;
+
+  TestDebugListener dev_tools_listener_;
+  fidl::Binding<fuchsia::web::DevToolsListener> dev_tools_listener_binding_;
+  std::unique_ptr<base::fuchsia::ServiceDirectoryClient> debug_dir_;
+  fuchsia::web::ContextProviderPtr web_context_provider_;
+  fuchsia::web::DebugSyncPtr debug_;
+
+  scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+  base::OnceClosure on_url_fetch_complete_ack_;
+
+  net::EmbeddedTestServer test_server_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebEngineDebugIntegrationTest);
+};
+
+// Helper struct to intiialize all data necessary for a Context to create a
+// Frame and navigate it to a specific URL.
+struct TestContextAndFrame {
+  explicit TestContextAndFrame(fuchsia::web::ContextProvider* context_provider,
+                               std::string url) {
+    // Create a Context, a Frame and navigate it to |url|.
+    auto directory = base::fuchsia::OpenDirectory(
+        base::FilePath(base::fuchsia::kServiceDirectoryPath));
+    if (!directory.is_valid())
+      return;
+
+    fuchsia::web::CreateContextParams create_params;
+    create_params.set_service_directory(std::move(directory));
+    context_provider->Create(std::move(create_params), context.NewRequest());
+    context->CreateFrame(frame.NewRequest());
+    frame->GetNavigationController(controller.NewRequest());
+    if (!cr_fuchsia::LoadUrlAndExpectResponse(
+            controller.get(), fuchsia::web::LoadUrlParams(), url)) {
+      ADD_FAILURE();
+      context.Unbind();
+      frame.Unbind();
+      controller.Unbind();
+      return;
+    }
+  }
+  ~TestContextAndFrame() = default;
+
+  fuchsia::web::ContextPtr context;
+  fuchsia::web::FramePtr frame;
+  fuchsia::web::NavigationControllerPtr controller;
+
+  DISALLOW_COPY_AND_ASSIGN(TestContextAndFrame);
+};
+
+// Test the Debug service is properly started and accessible.
+TEST_F(WebEngineDebugIntegrationTest, DebugService) {
+  std::string url = test_server_.GetURL("/title1.html").spec();
+  TestContextAndFrame frame_data(web_context_provider_.get(), url);
+  ASSERT_TRUE(frame_data.context);
+
+  // Test the debug information is correct.
+  dev_tools_listener_.RunUntilNumberOfPortsIs(1u);
+
+  base::Value devtools_list =
+      GetDevToolsListFromPort(*dev_tools_listener_.debug_ports().begin());
+  ASSERT_TRUE(devtools_list.is_list());
+  EXPECT_EQ(devtools_list.GetList().size(), 1u);
+
+  base::Value* devtools_url = devtools_list.GetList()[0].FindPath("url");
+  ASSERT_TRUE(devtools_url->is_string());
+  EXPECT_EQ(devtools_url->GetString(), url);
+
+  base::Value* devtools_title = devtools_list.GetList()[0].FindPath("title");
+  ASSERT_TRUE(devtools_title->is_string());
+  EXPECT_EQ(devtools_title->GetString(), "title 1");
+
+  // Unbind the context and wait for the listener to no longer have any active
+  // DevTools port.
+  frame_data.context.Unbind();
+  dev_tools_listener_.RunUntilNumberOfPortsIs(0);
+}
+
+TEST_F(WebEngineDebugIntegrationTest, MultipleDebugClients) {
+  std::string url1 = test_server_.GetURL("/title1.html").spec();
+  TestContextAndFrame frame_data1(web_context_provider_.get(), url1);
+  ASSERT_TRUE(frame_data1.context);
+
+  // Test the debug information is correct.
+  dev_tools_listener_.RunUntilNumberOfPortsIs(1u);
+  uint16_t port1 = *dev_tools_listener_.debug_ports().begin();
+
+  base::Value devtools_list1 = GetDevToolsListFromPort(port1);
+  ASSERT_TRUE(devtools_list1.is_list());
+  EXPECT_EQ(devtools_list1.GetList().size(), 1u);
+
+  base::Value* devtools_url1 = devtools_list1.GetList()[0].FindPath("url");
+  ASSERT_TRUE(devtools_url1->is_string());
+  EXPECT_EQ(devtools_url1->GetString(), url1);
+
+  base::Value* devtools_title1 = devtools_list1.GetList()[0].FindPath("title");
+  ASSERT_TRUE(devtools_title1->is_string());
+  EXPECT_EQ(devtools_title1->GetString(), "title 1");
+
+  // Connect a second Debug interface.
+  fuchsia::web::DebugSyncPtr debug2;
+  debug2 = debug_dir_->ConnectToServiceSync<fuchsia::web::Debug>();
+  TestDebugListener dev_tools_listener2;
+  fidl::Binding<fuchsia::web::DevToolsListener> dev_tools_listener_binding2(
+      &dev_tools_listener2);
+  debug2->EnableDevTools(dev_tools_listener_binding2.NewBinding());
+
+  // Create a second Context, a second Frame and navigate it to title2.html.
+  std::string url2 = test_server_.GetURL("/title2.html").spec();
+  TestContextAndFrame frame_data2(web_context_provider_.get(), url2);
+  ASSERT_TRUE(frame_data2.context);
+
+  // Ensure each DevTools listener has the right information.
+  dev_tools_listener_.RunUntilNumberOfPortsIs(2u);
+  dev_tools_listener2.RunUntilNumberOfPortsIs(1u);
+
+  uint16_t port2 = *dev_tools_listener2.debug_ports().begin();
+  ASSERT_NE(port1, port2);
+  ASSERT_NE(dev_tools_listener_.debug_ports().find(port2),
+            dev_tools_listener_.debug_ports().end());
+
+  base::Value devtools_list2 = GetDevToolsListFromPort(port2);
+  ASSERT_TRUE(devtools_list2.is_list());
+  EXPECT_EQ(devtools_list2.GetList().size(), 1u);
+
+  base::Value* devtools_url2 = devtools_list2.GetList()[0].FindPath("url");
+  ASSERT_TRUE(devtools_url2->is_string());
+  EXPECT_EQ(devtools_url2->GetString(), url2);
+
+  base::Value* devtools_title2 = devtools_list2.GetList()[0].FindPath("title");
+  ASSERT_TRUE(devtools_title2->is_string());
+  EXPECT_EQ(devtools_title2->GetString(), "title 2");
+
+  // Unbind the first Context, each listener should still have one open port.
+  frame_data1.context.Unbind();
+  dev_tools_listener_.RunUntilNumberOfPortsIs(1u);
+  dev_tools_listener2.RunUntilNumberOfPortsIs(1u);
+
+  // Unbind the second Context, no listener should have any open port.
+  frame_data2.context.Unbind();
+  dev_tools_listener_.RunUntilNumberOfPortsIs(0);
+  dev_tools_listener2.RunUntilNumberOfPortsIs(0);
+}
\ No newline at end of file
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 1a76fc8..59eac568 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -1810,6 +1810,14 @@
       mixins: "win-ci"
     }
     builders {
+      name: "win-pixel-builder-rel"
+      mixins: "win-ci"
+    }
+    builders {
+      name: "win-pixel-tester-rel"
+      mixins: "win-ci"
+    }
+    builders {
       name: "win32-arm64-rel"
       dimensions: "os:Windows-10"
       dimensions: "cpu:x86"
diff --git a/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
index d91cbe2..dbdbdf7 100644
--- a/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
+++ b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
@@ -27,7 +27,6 @@
 #import "ios/chrome/browser/tabs/legacy_tab_helper.h"
 #import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
-#import "ios/chrome/browser/tabs/tab_model_observer.h"
 #import "ios/chrome/browser/u2f/u2f_tab_helper.h"
 #import "ios/chrome/browser/ui/main/test/stub_browser_interface_provider.h"
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
diff --git a/ios/chrome/browser/crash_report/crash_report_helper.mm b/ios/chrome/browser/crash_report/crash_report_helper.mm
index ff10d64..7688f518 100644
--- a/ios/chrome/browser/crash_report/crash_report_helper.mm
+++ b/ios/chrome/browser/crash_report/crash_report_helper.mm
@@ -77,7 +77,7 @@
 
 @end
 
-// TabModelObserver that some tabs stats to be sent to the crash server.
+// WebStateList Observer that some tabs stats to be sent to the crash server.
 @interface CrashReporterTabStateObserver
     : NSObject <CRWWebStateObserver, WebStateListObserving> {
  @private
diff --git a/ios/chrome/browser/signin/identity_manager_factory.cc b/ios/chrome/browser/signin/identity_manager_factory.cc
index 0d7e6e2..475cd00 100644
--- a/ios/chrome/browser/signin/identity_manager_factory.cc
+++ b/ios/chrome/browser/signin/identity_manager_factory.cc
@@ -12,6 +12,7 @@
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/signin/core/browser/account_consistency_method.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/identity_manager_wrapper.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_manager.h"
@@ -62,11 +63,10 @@
 std::unique_ptr<SigninManager> BuildSigninManager(
     ios::ChromeBrowserState* chrome_browser_state,
     AccountTrackerService* account_tracker_service,
-    ProfileOAuth2TokenService* token_service,
-    GaiaCookieManagerService* gaia_cookie_manager_service) {
+    ProfileOAuth2TokenService* token_service) {
   std::unique_ptr<SigninManager> service = std::make_unique<SigninManager>(
       SigninClientFactory::GetForBrowserState(chrome_browser_state),
-      token_service, account_tracker_service, gaia_cookie_manager_service,
+      token_service, account_tracker_service,
       signin::AccountConsistencyMethod::kMirror);
   service->Initialize(GetApplicationContext()->GetLocalState());
   return service;
@@ -140,8 +140,7 @@
       SigninClientFactory::GetForBrowserState(browser_state));
 
   std::unique_ptr<SigninManager> signin_manager = BuildSigninManager(
-      browser_state, account_tracker_service.get(), token_service.get(),
-      gaia_cookie_manager_service.get());
+      browser_state, account_tracker_service.get(), token_service.get());
 
   auto primary_account_mutator =
       std::make_unique<identity::PrimaryAccountMutatorImpl>(
diff --git a/ios/chrome/browser/tabs/BUILD.gn b/ios/chrome/browser/tabs/BUILD.gn
index 2032415..872a462 100644
--- a/ios/chrome/browser/tabs/BUILD.gn
+++ b/ios/chrome/browser/tabs/BUILD.gn
@@ -10,7 +10,6 @@
     "tab_model.h",
     "tab_model_list.h",
     "tab_model_list_observer.h",
-    "tab_model_observer.h",
     "tab_model_synced_window_delegate.h",
     "tab_model_synced_window_delegate_getter.h",
     "tab_private.h",
@@ -45,8 +44,6 @@
     "tab_model_closing_web_state_observer.h",
     "tab_model_closing_web_state_observer.mm",
     "tab_model_list.mm",
-    "tab_model_observers.h",
-    "tab_model_observers.mm",
     "tab_model_selected_tab_observer.h",
     "tab_model_selected_tab_observer.mm",
     "tab_model_synced_window_delegate.mm",
diff --git a/ios/chrome/browser/tabs/tab_model.h b/ios/chrome/browser/tabs/tab_model.h
index a82734e..f337aedd 100644
--- a/ios/chrome/browser/tabs/tab_model.h
+++ b/ios/chrome/browser/tabs/tab_model.h
@@ -19,7 +19,6 @@
 @class SessionServiceIOS;
 @class SessionWindowIOS;
 @class Tab;
-@protocol TabModelObserver;
 class TabModelSyncedWindowDelegate;
 class TabUsageRecorder;
 class WebStateList;
@@ -141,17 +140,6 @@
 // when the app is shutting down.
 - (void)haltAllTabs;
 
-// Notifies observers that the given |tab| was changed.
-- (void)notifyTabChanged:(Tab*)tab;
-
-// Adds |observer| to the list of observers. |observer| is not retained. Does
-// nothing if |observer| is already in the list. Any added observers must be
-// explicitly removed before the TabModel is destroyed.
-- (void)addObserver:(id<TabModelObserver>)observer;
-
-// Removes |observer| from the list of observers.
-- (void)removeObserver:(id<TabModelObserver>)observer;
-
 // Resets all session counters.
 - (void)resetSessionMetrics;
 
diff --git a/ios/chrome/browser/tabs/tab_model.mm b/ios/chrome/browser/tabs/tab_model.mm
index 336f703..a813b48 100644
--- a/ios/chrome/browser/tabs/tab_model.mm
+++ b/ios/chrome/browser/tabs/tab_model.mm
@@ -42,7 +42,6 @@
 #import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/tabs/tab_model_closing_web_state_observer.h"
 #import "ios/chrome/browser/tabs/tab_model_list.h"
-#import "ios/chrome/browser/tabs/tab_model_observers.h"
 #import "ios/chrome/browser/tabs/tab_model_selected_tab_observer.h"
 #import "ios/chrome/browser/tabs/tab_model_synced_window_delegate.h"
 #import "ios/chrome/browser/tabs/tab_model_web_state_list_delegate.h"
@@ -240,8 +239,6 @@
   std::unique_ptr<TabUsageRecorder> _tabUsageRecorder;
   // Saves session's state.
   SessionServiceIOS* _sessionService;
-  // List of TabModelObservers.
-  TabModelObservers* _observers;
 
   // Used to ensure thread-safety of the certificate policy management code.
   base::CancelableTaskTracker _clearPoliciesTaskTracker;
@@ -266,9 +263,6 @@
 - (void)dealloc {
   // browserStateDestroyed should always have been called before destruction.
   DCHECK(!_browserState);
-
-  // Make sure the observers do clean after themselves.
-  DCHECK([_observers empty]);
 }
 
 #pragma mark - Public methods
@@ -312,8 +306,6 @@
 - (instancetype)initWithSessionService:(SessionServiceIOS*)service
                           browserState:(ios::ChromeBrowserState*)browserState {
   if ((self = [super init])) {
-    _observers = [TabModelObservers observers];
-
     _webStateListDelegate = std::make_unique<TabModelWebStateListDelegate>();
     _webStateList = std::make_unique<WebStateList>(_webStateListDelegate.get());
 
@@ -508,18 +500,6 @@
   }
 }
 
-- (void)notifyTabChanged:(Tab*)tab {
-  [_observers tabModel:self didChangeTab:tab];
-}
-
-- (void)addObserver:(id<TabModelObserver>)observer {
-  [_observers addObserver:observer];
-}
-
-- (void)removeObserver:(id<TabModelObserver>)observer {
-  [_observers removeObserver:observer];
-}
-
 - (void)resetSessionMetrics {
   if (_webStateListMetricsObserver)
     _webStateListMetricsObserver->ResetSessionMetrics();
@@ -613,9 +593,6 @@
   // It is only ok to pass a nil |window| during the initial restore.
   DCHECK(window || initialRestore);
 
-  // The initial restore can only happen before observers are registered.
-  DCHECK(!initialRestore || [_observers empty]);
-
   // Setting the sesion progress to |YES|, so BVC can check it to work around
   // crbug.com/763964.
   _restoringSession = YES;
@@ -740,8 +717,6 @@
 
 - (void)webState:(web::WebState*)webState
     didFinishNavigation:(web::NavigationContext*)navigation {
-  Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
-  [self notifyTabChanged:tab];
 
   if (!navigation->IsSameDocument() && navigation->HasCommitted() &&
       !self.offTheRecord) {
@@ -752,7 +727,6 @@
 
 - (void)webState:(web::WebState*)webState
     didStartNavigation:(web::NavigationContext*)navigation {
-  Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
 
   // In order to avoid false positive in the crash loop detection, disable the
   // counter as soon as an URL is loaded. This requires an user action and is a
@@ -769,8 +743,6 @@
     _tabUsageRecorder->RecordPageLoadStart(webState);
   }
 
-  [self notifyTabChanged:tab];
-
   DCHECK(webState->GetNavigationManager());
   web::NavigationItem* navigationItem =
       webState->GetNavigationManager()->GetPendingItem();
@@ -800,9 +772,6 @@
 }
 
 - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
-  Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
-  [self notifyTabChanged:tab];
-
   RecordInterfaceOrientationMetric();
   RecordMainFrameNavigationMetric(webState);
 
@@ -811,25 +780,6 @@
                     loadSuccess:success];
 }
 
-- (void)webState:(web::WebState*)webState
-    didChangeLoadingProgress:(double)progress {
-  // TODO(crbug.com/546406): It is probably possible to do something smarter,
-  // but the fact that this is not always sent will have to be taken into
-  // account.
-  Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
-  [self notifyTabChanged:tab];
-}
-
-- (void)webStateDidChangeTitle:(web::WebState*)webState {
-  Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
-  [self notifyTabChanged:tab];
-}
-
-- (void)webStateDidChangeVisibleSecurityState:(web::WebState*)webState {
-  Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
-  [self notifyTabChanged:tab];
-}
-
 - (void)webStateDestroyed:(web::WebState*)webState {
   // The TabModel is removed from WebState's observer when the WebState is
   // detached from WebStateList which happens before WebState destructor,
@@ -837,11 +787,6 @@
   NOTREACHED();
 }
 
-- (void)webStateDidStopLoading:(web::WebState*)webState {
-  Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
-  [self notifyTabChanged:tab];
-}
-
 #pragma mark - WebStateListObserving
 
 - (void)webStateList:(WebStateList*)webStateList
diff --git a/ios/chrome/browser/tabs/tab_model_observer.h b/ios/chrome/browser/tabs/tab_model_observer.h
deleted file mode 100644
index 9d99ceca..0000000
--- a/ios/chrome/browser/tabs/tab_model_observer.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_TABS_TAB_MODEL_OBSERVER_H_
-#define IOS_CHROME_BROWSER_TABS_TAB_MODEL_OBSERVER_H_
-
-#import <Foundation/Foundation.h>
-
-@class Tab;
-@class TabModel;
-
-// Observers implement these methods to be alerted to changes in the model.
-// These methods are all optional.
-@protocol TabModelObserver<NSObject>
-@optional
-
-// Some properties about the given tab changed, such as the URL or title.
-- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_TABS_TAB_MODEL_OBSERVER_H_
diff --git a/ios/chrome/browser/tabs/tab_model_observers.h b/ios/chrome/browser/tabs/tab_model_observers.h
deleted file mode 100644
index ab382a9c..0000000
--- a/ios/chrome/browser/tabs/tab_model_observers.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_TABS_TAB_MODEL_OBSERVERS_H_
-#define IOS_CHROME_BROWSER_TABS_TAB_MODEL_OBSERVERS_H_
-
-#import "base/ios/crb_protocol_observers.h"
-#import "ios/chrome/browser/tabs/tab_model_observer.h"
-
-@interface TabModelObservers : CRBProtocolObservers<TabModelObserver>
-
-+ (instancetype)observers;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_TABS_TAB_MODEL_OBSERVERS_H_
diff --git a/ios/chrome/browser/tabs/tab_model_observers.mm b/ios/chrome/browser/tabs/tab_model_observers.mm
deleted file mode 100644
index fd86c71..0000000
--- a/ios/chrome/browser/tabs/tab_model_observers.mm
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/tabs/tab_model_observers.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation TabModelObservers
-
-+ (instancetype)observers {
-  return [self observersWithProtocol:@protocol(TabModelObserver)];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/activity_services/BUILD.gn b/ios/chrome/browser/ui/activity_services/BUILD.gn
index 2e60c5b..cd31a20 100644
--- a/ios/chrome/browser/ui/activity_services/BUILD.gn
+++ b/ios/chrome/browser/ui/activity_services/BUILD.gn
@@ -146,3 +146,28 @@
   ]
   libs = [ "XCTest.framework" ]
 }
+
+source_set("eg2_tests") {
+  defines = [ "CHROME_EARL_GREY_2" ]
+  configs += [
+    "//build/config/compiler:enable_arc",
+    "//build/config/ios:xctest_config",
+  ]
+  testonly = true
+
+  sources = [
+    "activity_service_controller_egtest.mm",
+  ]
+
+  deps = [
+    "//components/strings",
+    "//ios/chrome/app/strings:ios_strings_grit",
+    "//ios/chrome/test/earl_grey:eg_test_support+eg2",
+    "//ios/testing/earl_grey:eg_test_support+eg2",
+    "//ios/third_party/earl_grey2:test_lib",
+    "//ios/web/public/test/http_server",
+    "//ui/base",
+  ]
+
+  libs = [ "UIKit.framework" ]
+}
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 6130515..dbb026e7 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -77,7 +77,6 @@
 #import "ios/chrome/browser/tabs/legacy_tab_helper.h"
 #import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
-#import "ios/chrome/browser/tabs/tab_model_observer.h"
 #import "ios/chrome/browser/tabs/tab_private.h"
 #import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
 #import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
@@ -393,7 +392,6 @@
                                      SideSwipeControllerDelegate,
                                      SigninPresenter,
                                      SnapshotGeneratorDelegate,
-                                     TabModelObserver,
                                      TabStripPresentation,
                                      ToolbarHeightProviderForFullscreen,
                                      WebStateListObserving,
@@ -1106,7 +1104,7 @@
 }
 
 - (web::WebState*)currentWebState {
-  return self.tabModel.currentTab.webState;
+  return self.tabModel.webStateList->GetActiveWebState();
 }
 
 - (BOOL)usesSafeInsetsForViewportAdjustments {
@@ -1464,7 +1462,6 @@
   // SideSwipeController is a tab model observer, so it needs to stop observing
   // before self.tabModel is released.
   _sideSwipeController = nil;
-  [self.tabModel removeObserver:self];
   self.tabModel.webStateList->RemoveObserver(_webStateListObserver.get());
   _webStateListObserver.reset();
   _allWebStateObservationForwarder = nullptr;
@@ -1933,7 +1930,6 @@
       ->GetForBrowserState(_browserState)
       ->SetWebStateList(self.tabModel.webStateList);
 
-  [self.tabModel addObserver:self];
   _webStateObserverBridge = std::make_unique<web::WebStateObserverBridge>(self);
   _allWebStateObservationForwarder =
       std::make_unique<AllWebStateObservationForwarder>(
@@ -3519,6 +3515,12 @@
 
 #pragma mark - CRWWebStateObserver methods.
 
+- (void)webState:(web::WebState*)webState
+    didStartNavigation:(web::NavigationContext*)navigation {
+  if (webState == self.currentWebState)
+    [self updateToolbar];
+}
+
 // TODO(crbug.com/918934): This call to closeFindInPage incorrectly triggers for
 // all navigations, not just navigations in the active WebState.
 - (void)webState:(web::WebState*)webState
@@ -3664,8 +3666,7 @@
     NOTREACHED();
   }
   // If a native controller is vended before its tab is added to the tab model,
-  // use the temporary key and add it under the new tab's tabId in the
-  // TabModelObserver callback.  This happens:
+  // use the temporary key. This happens:
   // - when there is no current tab (occurs when vending the NTP controller for
   //   the first tab that is opened),
   // - when the current tab's url doesn't match |url| (occurs when a native
@@ -4490,15 +4491,6 @@
                       willOpenInBackground:!activating];
 }
 
-#pragma mark - TabModelObserver methods
-
-- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
-  DCHECK(tab && ([self.tabModel indexOfTab:tab] != NSNotFound));
-  if (tab == self.tabModel.currentTab) {
-    [self updateToolbar];
-  }
-}
-
 #pragma mark - WebStateListObserver helpers (new tab animations)
 
 - (void)initiateNewTabAnimationForWebState:(web::WebState*)webState
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index 5b6ea9b..b4ea30e8 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -31,7 +31,6 @@
 #import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/tabs/tab_helper_util.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
-#import "ios/chrome/browser/tabs/tab_model_observer.h"
 #import "ios/chrome/browser/ui/activity_services/share_protocol.h"
 #import "ios/chrome/browser/ui/activity_services/share_to_data.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
@@ -88,8 +87,7 @@
 @class ToolbarButtonUpdater;
 
 // Private methods in BrowserViewController to test.
-@interface BrowserViewController (Testing) <CRWNativeContentProvider,
-                                            TabModelObserver>
+@interface BrowserViewController (Testing) <CRWNativeContentProvider>
 - (void)pageLoadStarted:(NSNotification*)notification;
 - (void)pageLoadComplete:(NSNotification*)notification;
 - (void)webStateSelected:(web::WebState*)webState
@@ -216,8 +214,6 @@
         initWithRepresentedObject:[OCMockObject niceMockForClass:[Tab class]]];
     [[[tabModel stub] andReturn:currentTab] currentTab];
     [[[tabModel stub] andReturn:currentTab] tabAtIndex:0];
-    [[tabModel stub] addObserver:[OCMArg any]];
-    [[tabModel stub] removeObserver:[OCMArg any]];
     [[tabModel stub] saveSessionImmediately:NO];
     [[tabModel stub] setCurrentTab:[OCMArg any]];
     [[tabModel stub] closeAllTabs];
@@ -230,6 +226,7 @@
     [currentTab setWebState:webStateImpl_];
     tabModel_.webStateList->InsertWebState(0, std::move(webState), 0,
                                            WebStateOpener());
+    tabModel_.webStateList->ActivateWebStateAt(0);
 
     // Load TemplateURLService.
     TemplateURLService* template_url_service =
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm
index e09e5bf..ffa2987 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm
+++ b/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm
@@ -140,14 +140,6 @@
                                  WebStateList::CLOSE_NO_FLAGS);
 }
 
-- (void)addObserver:(id<TabModelObserver>)observer {
-  // Do nothing.
-}
-
-- (void)removeObserver:(id<TabModelObserver>)observer {
-  // Do nothing.
-}
-
 - (WebStateList*)webStateList {
   return _webStateList.get();
 }
diff --git a/ios/chrome/browser/web/forms_egtest.mm b/ios/chrome/browser/web/forms_egtest.mm
index f34cb6a0..7b33e06 100644
--- a/ios/chrome/browser/web/forms_egtest.mm
+++ b/ios/chrome/browser/web/forms_egtest.mm
@@ -242,6 +242,11 @@
 // Tests that a POST followed by navigating to a new page and then tapping back
 // to the form result page resends data.
 - (void)testRepostFormAfterTappingBack {
+  // TODO(crbug.com/968296): Test is failing on iPad for slim nav.
+  if (IsIPadIdiom() && web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
+  }
+
   [self setUpFormTestSimpleHttpServer];
   const GURL destinationURL = GetDestinationUrl();
 
diff --git a/ios/chrome/browser/web_state_list/web_state_list_favicon_driver_observer.h b/ios/chrome/browser/web_state_list/web_state_list_favicon_driver_observer.h
index 255197f..b4031d0 100644
--- a/ios/chrome/browser/web_state_list/web_state_list_favicon_driver_observer.h
+++ b/ios/chrome/browser/web_state_list/web_state_list_favicon_driver_observer.h
@@ -68,7 +68,7 @@
 
   // Maps FaviconDriver to the WebState they are attached to. Used
   // to find the WebState that should be passed when forwarding the
-  // notification to TabModelObservers.
+  // notification to WebStateFaviconDriverObservers.
   std::map<favicon::FaviconDriver*, web::WebState*> driver_to_web_state_map_;
 
   ScopedObserver<WebStateList, WebStateListObserver> web_state_list_observer_;
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn
index fa31f92..e249ed8 100644
--- a/ios/chrome/test/earl_grey2/BUILD.gn
+++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -39,6 +39,7 @@
   xcode_test_application_name = "ios_chrome_eg2tests"
 
   deps = [
+    "//ios/chrome/browser/ui/activity_services:eg2_tests",
     "//ios/chrome/browser/ui/download:eg2_tests",
   ]
 }
diff --git a/ios/web_view/internal/signin/web_view_identity_manager_factory.mm b/ios/web_view/internal/signin/web_view_identity_manager_factory.mm
index b814bb9..5f3b7f2 100644
--- a/ios/web_view/internal/signin/web_view_identity_manager_factory.mm
+++ b/ios/web_view/internal/signin/web_view_identity_manager_factory.mm
@@ -12,6 +12,7 @@
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_consistency_method.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/identity_manager_wrapper.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_manager.h"
@@ -73,8 +74,7 @@
 std::unique_ptr<SigninManager> BuildSigninManager(
     WebViewBrowserState* browser_state,
     AccountTrackerService* account_tracker_service,
-    ProfileOAuth2TokenService* token_service,
-    GaiaCookieManagerService* gaia_cookie_manager_service) {
+    ProfileOAuth2TokenService* token_service) {
   // Clearing the sign in state on start up greatly simplifies the management of
   // ChromeWebView's signin state.
   PrefService* pref_service = browser_state->GetPrefs();
@@ -84,7 +84,7 @@
 
   std::unique_ptr<SigninManager> service = std::make_unique<SigninManager>(
       WebViewSigninClientFactory::GetForBrowserState(browser_state),
-      token_service, account_tracker_service, gaia_cookie_manager_service,
+      token_service, account_tracker_service,
       signin::AccountConsistencyMethod::kDisabled);
   service->Initialize(ApplicationContext::GetInstance()->GetLocalState());
   return service;
@@ -142,8 +142,7 @@
       WebViewSigninClientFactory::GetForBrowserState(browser_state));
 
   std::unique_ptr<SigninManager> signin_manager = BuildSigninManager(
-      browser_state, account_tracker_service.get(), token_service.get(),
-      gaia_cookie_manager_service.get());
+      browser_state, account_tracker_service.get(), token_service.get());
 
   auto primary_account_mutator =
       std::make_unique<identity::PrimaryAccountMutatorImpl>(
diff --git a/mojo/public/cpp/bindings/receiver_set.h b/mojo/public/cpp/bindings/receiver_set.h
index 5019554..5244f6a 100644
--- a/mojo/public/cpp/bindings/receiver_set.h
+++ b/mojo/public/cpp/bindings/receiver_set.h
@@ -338,14 +338,6 @@
 template <typename Interface, typename ContextType = void>
 using ReceiverSet = ReceiverSetBase<Receiver<Interface>, ContextType>;
 
-// Helper for a set of Receivers where each bound Receiver is tied to an owned
-// implementation. The |Add()| method takes a std::unique_ptr<Interface> for
-// each bound implementation.
-template <typename Interface, typename ContextType = void>
-using OwnedReceiverSet =
-    ReceiverSetBase<Receiver<Interface, UniquePtrImplRefTraits<Interface>>,
-                    ContextType>;
-
 }  // namespace mojo
 
 #endif  // MOJO_PUBLIC_CPP_BINDINGS_RECEIVER_SET_H_
diff --git a/mojo/public/cpp/bindings/tests/new_endpoint_types_unittest.cc b/mojo/public/cpp/bindings/tests/new_endpoint_types_unittest.cc
index c254fd9..d421da9c 100644
--- a/mojo/public/cpp/bindings/tests/new_endpoint_types_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/new_endpoint_types_unittest.cc
@@ -18,8 +18,8 @@
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/unique_receiver_set.h"
 #include "mojo/public/interfaces/bindings/tests/new_endpoint_types.test-mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -71,7 +71,7 @@
 
  private:
   mojo::Receiver<mojom::WidgetFactory> receiver_;
-  mojo::OwnedReceiverSet<mojom::Widget> widgets_;
+  mojo::UniqueReceiverSet<mojom::Widget> widgets_;
 
   DISALLOW_COPY_AND_ASSIGN(FactoryImpl);
 };
diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md
index 60ef3f7a..07bc7d3 100644
--- a/mojo/public/cpp/system/README.md
+++ b/mojo/public/cpp/system/README.md
@@ -145,10 +145,10 @@
 
 ``` cpp
 mojo::ScopedSharedBufferMapping mapping = buffer->Map(64);
-static_cast<int*>(mapping.get()) = 42;
+static_cast<int*>(mapping.get())[0] = 42;
 
 mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4);
-static_cast<int*>(mapping.get()) = 43;
+static_cast<int*>(mapping.get())[0] = 43;
 ```
 
 When `mapping` and `another_mapping` are destroyed, they automatically unmap
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 9614b3d8..38cdde8 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -309,9 +309,7 @@
 // Server request for client certificate did not contain any types we support.
 NET_ERROR(CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, -151)
 
-// Server requested one type of cert, then requested a different type while the
-// first was still being generated.
-NET_ERROR(ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH, -152)
+// Error -152 was removed (ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH)
 
 // An SSL peer sent us a fatal decrypt_error alert. This typically occurs when
 // a peer could not correctly verify a signature (in CertificateVerify or
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index 1b17b2c..52f18829 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -2678,9 +2678,10 @@
 
   // TODO(crbug.com/878582): Respect the type of cache lookup that should be
   // performed.
-  if (out_tasks->front() == TaskType::SECURE_CACHE_LOOKUP ||
-      out_tasks->front() == TaskType::INSECURE_CACHE_LOOKUP ||
-      out_tasks->front() == TaskType::CACHE_LOOKUP) {
+  if (!out_tasks->empty() &&
+      (out_tasks->front() == TaskType::SECURE_CACHE_LOOKUP ||
+       out_tasks->front() == TaskType::INSECURE_CACHE_LOOKUP ||
+       out_tasks->front() == TaskType::CACHE_LOOKUP)) {
     out_tasks->pop_front();
     if (cache_usage == ResolveHostParameters::CacheUsage::ALLOWED ||
         cache_usage == ResolveHostParameters::CacheUsage::STALE_ALLOWED) {
diff --git a/net/docs/proxy.md b/net/docs/proxy.md
index 9524db0d..8b99c8e2 100644
--- a/net/docs/proxy.md
+++ b/net/docs/proxy.md
@@ -1,45 +1,47 @@
 # Proxy support in Chrome
 
-This document establishes basic proxy terminology, as well as describing
-behaviors specific to Chrome.
+This document establishes basic proxy terminology and describes Chrome-specific
+proxy behaviors.
 
-## Proxy Server
+[TOC]
 
-A proxy server is an intermediary used for network requests. It can be
-identified by the 3-tuple (scheme, host, port) where:
+## Proxy server identifiers
 
-* scheme - protocol used to communicate with the proxy (ex: SOCKSv5, HTTPS).
-* host - IP or hostname of the proxy server (ex: 192.168.0.1)
-* port - TCP/UDP port number (ex: 443)
+A proxy server is an intermediary used for network requests. A proxy server can
+be described by its address, along with the proxy scheme that should be used to
+communicate with it.
 
-There are a variety of proxy server schemes supported by Chrome. When using an
-explicit proxy in the browser, multiple layers of the network request are
-impacted.
+This can be written as a string using either the "PAC format" or the "URI
+format".
 
-Difference between proxy server schemes include:
+The PAC format is how one names a proxy server in [Proxy
+auto-config](https://en.wikipedia.org/wiki/Proxy_auto-config) scripts. For
+example:
+* `PROXY foo:2138`
+* `SOCKS5 foo:1080`
+* `DIRECT`
 
-* Is communication to the proxy done over a secure channel?
-* Is name resolution (ex: DNS) done client side, or proxy side?
-* What authentication schemes to the proxy server are supported?
-* What network traffic can be sent through the proxy?
+The "URI format" instead encodes the information as a URL. For example:
+* `foo:2138`
+* `http://foo:2138`
+* `socks5://foo:1080`
+* `direct://`
 
-Identifiers for proxy servers are often written as strings, using either the
-PAC format (ex: `PROXY foo`) or Chrome's URI format (ex: `http://foo`).
+The port number is optional in both formats. When omitted, a per-scheme default
+is used.
 
-When a proxy server's scheme is not stated, it's assumed to be HTTP in most
-contexts.
+See the [Proxy server schemes](#Proxy-server-schemes) section for details on
+what schemes Chrome supports, and how to write them in the PAC and URI formats.
 
-This can lead to some confusion, particularly when discussing system proxy
-settings. Major platform UIs have converged on the term "Secure proxy" to mean
-the host:port for an (insecure) HTTP proxy to use for proxying https:// URLs.
+Most UI surfaces in Chrome (including command lines and policy) expect URI
+formatted proxy server identifiers. However outside of Chrome, proxy servers
+are generally identified less precisely by just an address -- the proxy
+scheme is assumed based on context.
 
-So when someone refers to their "HTTPS proxy" be aware of this ambiguity. The
-intended meaning could be either "an HTTP proxy for https:// URLs", or "a proxy
-using the HTTPS scheme".
-
-In this document when we say "an HTTPS proxy", we always mean "a proxy
-that the browser speaks HTTPS to", and not "an (HTTP) proxy used to proxy
-https:// URLs".
+In Windows' proxy settings there are host and port fields for the
+"HTTP", "Secure", "FTP", and "SOCKS" proxy. With the exception of "SOCKS",
+those are all identifiers for insecure HTTP proxy servers (proxy scheme is
+assumed as HTTP).
 
 ## Proxy resolution
 
@@ -49,31 +51,42 @@
 to send the request to. This can be either a proxy server, or the target host.
 
 This is called proxy resolution. The input to proxy resolution is a URL, and
-the output is an ordered list of proxy server options.
+the output is an ordered list of [proxy server
+identifiers](#Proxy-server-identifiers).
 
 What proxies to use can be described using either:
 
-* Manual proxy settings - proxy resolution is defined using a declarative set
-  of rules. These rules are expressed as a mapping from URL scheme to proxy
-  server(s), and a list of proxy bypass rules for when to go DIRECT instead of
-  using the mapped proxy.
+* [Manual proxy settings](#Manual-proxy-settings) - proxy resolution is defined
+  using a declarative set of rules. These rules are expressed as a mapping from
+  URL scheme to proxy server identifier(s), and a list of proxy bypass rules for
+  when to go DIRECT instead of using the mapped proxy.
 
 * PAC script - proxy resolution is defined using a JavaScript program, that is
-  invoked whenever fetching a URL to get the list of proxy servers to use.
+  invoked whenever fetching a URL to get the list of proxy server identifiers
+  to use.
 
 * Auto-detect - the WPAD protocol is used to probe the network (using DHCP/DNS)
   and possibly discover the URL of a PAC script.
 
 ## Proxy server schemes
 
-Chrome supports the following proxy server schemes:
+When using an explicit proxy in the browser, multiple layers of the network
+request are impacted, depending on the scheme that is used. Some implications
+of the proxy scheme are:
 
-* DIRECT
-* HTTP
-* HTTPS
-* SOCKSv4
-* SOCKSv5
-* QUIC
+* Is communication to the proxy done over a secure channel?
+* Is name resolution (ex: DNS) done client side, or proxy side?
+* What authentication schemes to the proxy server are supported?
+* What network traffic can be sent through the proxy?
+
+Chrome supports these proxy server schemes:
+
+* [DIRECT](#DIRECT-proxy-scheme)
+* [HTTP](#HTTP-proxy-scheme)
+* [HTTPS](#HTTPS-proxy-scheme)
+* [SOCKSv4](#SOCKSv4-proxy-scheme)
+* [SOCKSv5](#SOCKSv5-proxy-scheme)
+* [QUIC](#QUIC-proxy-scheme)
 
 ### DIRECT proxy scheme
 
@@ -107,7 +120,7 @@
 clear.
 
 HTTP proxies in Chrome support the same HTTP authentiation schemes as for
-target servers: Basic, Digest, Negotiate/NTLM.
+target servers: Basic, Digest, Negotiate, NTLM.
 
 ### HTTPS proxy scheme
 
@@ -115,12 +128,21 @@
 * Example identifier (PAC): `HTTPS proxy:8080`
 * Example identifier (URI): `https://proxy:8080`
 
-This works exactly like an HTTP proxy, except the communication to the proxy
-server is protected by TLS. Hence `http://` requests, and hostnames for
-`https://` requests are not sent in the clear as with HTTP proxies.
+This works like an [HTTP proxy](#HTTP-proxy-scheme), except the
+communication to the proxy server is protected by TLS, and may negotiate
+HTTP/2.
 
-In addition to HTTP authentication methods, one can also use client
-certificates to authenticate to HTTPS proxies.
+Because the connection to the proxy server is secure, https:// requests
+sent through the proxy are not sent in the clear as with an HTTP proxy.
+Similarly, since CONNECT requests are sent over a protected channel, the
+hostnames for proxied https:// URLs is also not revealed.
+
+In addition to the usual HTTP authentication methods, HTTPS proxies also
+support client certificates.
+
+HTTPS proxies using HTTP/2 can offer better performance in Chrome than a
+regular HTTP proxy due to higher connection limits (HTTP/1.1 proxies in Chrome
+are limited to 32 simultaneous connections across all domains).
 
 ### SOCKSv4 proxy scheme
 
@@ -183,18 +205,18 @@
 The simplest way to configure proxy resolution is by providing a static list of
 rules comprised of:
 
-1. A mapping of URL schemes to proxy servers
-2. A list of proxy bypass rules
+1. A mapping of URL schemes to [proxy server identifiers](#Proxy-server-identifiers).
+2. A list of [proxy bypass rules](#Proxy-bypass-rules)
 
 We refer to this mode of configuration as "manual proxy settings".
 
 Manual proxy settings can succinctly describe setups like:
 
-* Use HTTPS proxy `foo:8080` for all requests
-* Use HTTP proxy `foo:8080` for all requests except those to a `google.com`
+* Use proxy `http://foo:8080` for all requests
+* Use proxy `http://foo:8080` for all requests except those to a `google.com`
   subdomain.
-* Use HTTP proxy `foo:8080` for all `https://` requests, and the SOCKSv5 proxy
-  `mysocks:90` for everything else
+* Use proxy `http://foo:8080` for all `https://` requests, and proxy
+  `socsk5://mysocks:90` for everything else
 
 Although manual proxy settings are a ubiquituous way to configure proxies
 across platforms, there is no standard representation or feature set.
@@ -205,14 +227,14 @@
 suffix matches.
 
 When defining manual proxy settings in Chrome, we specify three (possibly
-empty) lists of proxy servers:
+empty) lists of [proxy server identifiers](#Proxy-server-identifiers).
 
-  * proxies for HTTP - A list of proxy servers to use for `http://` requests,
-    if non-empty.
-  * proxies for HTTPS - A list of proxy servers to use for `https://` requests,
-    if non-empty.
-  * other proxies - A list of proxy servers to use for everything else
-    (whatever isn't matched by the other two lists)
+  * proxies for HTTP - A list of proxy server identifiers to use for `http://`
+    requests, if non-empty.
+  * proxies for HTTPS - A list of proxy server identifiers to use for
+    `https://` requests, if non-empty.
+  * other proxies - A list of proxy server identifiers to use for everything
+    else (whatever isn't matched by the other two lists)
 
 There are a lot of ways to end up with manual proxy settings in Chrome
 (discussed in other sections).
@@ -220,8 +242,8 @@
 The following examples will use the command line method. Launching Chrome with
 `--proxy-server=XXX` (and optionally `--proxy-bypass-list=YYY`)
 
-Example: To use the HTTP proxy `foo:8080` for all requests we can launch
-Chrome with `--proxy-server="http://foo:8080"`. This translates into:
+Example: To use proxy `http://foo:8080` for all requests we can launch
+Chrome with `--proxy-server="http://foo:8080"`. This translates to:
 
   * proxies for HTTP - *empty*
   * proxies for HTTPS - *empty*
@@ -238,8 +260,8 @@
   * other proxies - `http://foo:8080`, `direct://`
 
 If instead we wanted to proxy only `http://` URLs through the
-HTTPS proxy `foo:443`, and have everything else use the SOCKSv5 proxy
-`mysocks:1080` we could launch Chrome with
+HTTPS proxy `https://foo:443`, and have everything else use the SOCKSv5 proxy
+`socks5://mysocks:1080` we could launch Chrome with
 `--proxy-server="http=https://foo:443;socks=socks5://mysocks:1080"`. This now
 expands to:
 
@@ -247,18 +269,20 @@
   * proxies for HTTPS - *empty*
   * other proxies - `socks5://mysocks:1080`
 
-The command line above uses WinInet's proxy map format, with two modifications:
+The command line above uses WinInet's proxy map format, with some additional
+features:
 
-* Proxy servers can be optionally prefixed with a scheme (i.e. Chrome's "URI
-  format" for proxy server identifiers)
-* The `socks=` mapping is understood as "other proxies". The subsequent proxy
-  list can include proxies of any scheme, however if the scheme is unspecified
-  it is understood to be `socks4://`.
+* Instead of naming proxy servers by just a hostname:port, you can use Chrome's
+  URI format for proxy server identifiers. In other words, you can prefix the
+  proxy scheme so it doesn't default to HTTP.
+* The `socks=` mapping is understood more broadly as "other proxies". The
+  subsequent proxy list can include proxies of any scheme, however if the
+  scheme is omitted it will be understood as SOCKSv4 rather than HTTP.
 
-## Mapping WebSockets URLs to a proxy
+### Mapping WebSockets URLs to a proxy
 
-Manual proxy settings don't have mappings for `ws://` or `wss://` URLs - you
-can't specify a separate proxy to use for those schemes.
+[Manual proxy settings](#Manual-proxy-settings) don't have mappings for `ws://`
+or `wss://` URLs.
 
 Selecting a proxy for these URL schemes is a bit different from other URL
 schemes. The algorithm that Chrome uses is:
@@ -272,19 +296,22 @@
 
 It is possible to route `ws://` and `wss://` separately using a PAC script.
 
-## Proxy credentials in manual proxy settings
+### Proxy credentials in manual proxy settings
 
-Most platforms' manual proxy settings allow specifying a cleartext
-username/password for proxy sign in. Chrome does not implement this, and will
-not use any credentials embedded in the proxy settings.
+Most platforms' [manual proxy settings](#Manual-proxy-settings) allow
+specifying a cleartext username/password for proxy sign in. Chrome does not
+implement this, and will not use any credentials embedded in the proxy
+settings.
 
 Proxy authentication will instead go through the ordinary flow to find
 credentials.
 
 ## Proxy bypass rules
 
-In addition to specifying three lists of proxy servers, Chrome's manual proxy
-settings also lets you specify a list of "proxy bypass rules".
+In addition to specifying three lists of [proxy server
+identifiers](#proxy-server-identifiers), Chrome's [manual proxy
+settings](#Manual-proxy-settings) lets you specify a list of "proxy bypass
+rules".
 
 This ruleset determines whether a given URL should skip use of a proxy all
 together, even when a proxy is otherwise defined for it.
@@ -369,8 +396,8 @@
 Matches any URL whose hostname is an IPv4 literal, and falls between the given
 address range.
 
-Only applies to URLs that are IP literals - see "Meaning of IP address range
-bypass rules".
+Note this [only applies to URLs that are IP
+literals](#Meaning-of-IP-address-range-bypass-rules).
 
 Examples:
 
@@ -385,8 +412,8 @@
 Matches any URL that is an IPv6 literal that falls between the given range.
 Note that IPv6 literals must *not* be bracketed.
 
-Only applies to URLs that are IP literals - see "Meaning of IP address range
-bypass rules".
+Note this [only applies to URLs that are IP
+literals](#Meaning-of-IP-address-range-bypass-rules).
 
 Examples:
 
@@ -408,8 +435,8 @@
 
 The rule name comes from WinInet, and can easily be confused with the concept
 of localhost. However the two concepts are completely orthogonal. In practice
-one wouldn't add rules to bypass localhost, as it is already done implicitly
-(see "Implicit bypass rules").
+one wouldn't add rules to bypass localhost, as it is [already done
+implicitly](#Implicit-bypass-rules).
 
 ### Bypass rule: Subtract implicit rules
 
@@ -417,10 +444,9 @@
 <-loopback>
 ```
 
-*Subtracts* the implicit proxy bypass rules (localhost and link local
-addresses). See the "Implicit bypass rules" section for details on when/why to
-use this, and the security caveats to doing so. Generally this is used for test
-setups.
+*Subtracts* the [implicit proxy bypass rules](#Implicit-bypass-rules)
+(localhost and link local addresses). This is generally only needed for test
+setupe. Beware of the security implications to proxying localhost.
 
 Whereas regular bypass rules instruct the browser about URLs that should *not*
 use the proxy, this rule has the opposite effect and tells the browser to
@@ -432,8 +458,8 @@
 
 ### Meaning of IP address range bypass rules
 
-The IP address range bypass rules in manual proxy settings applies ONLY TO URL
-LITERALS. This is not what one would intuitively expect!
+The IP address range bypass rules in manual proxy settings applies only to URL
+literals. This is not what one would intuitively expect.
 
 Example:
 
@@ -497,7 +523,7 @@
 * In M72 Chrome generalized the implicit proxy bypass rules to manually
   configured proxies
 
-## Overriding the implicit bypass rules
+### Overriding the implicit bypass rules
 
 If you want traffic to `localhost` to be sent through a proxy despite the
 security concerns, it can be done by adding the special proxy bypass rule
@@ -516,8 +542,9 @@
 
 ## Evaluating proxy lists (proxy fallback)
 
-Proxy resolution results in a _list_ of proxy servers to use for a given
-request, not just a single proxy server.
+Proxy resolution results in a _list_ of [proxy server
+identifiers](#Proxy-server-identifiers) to use for a
+given request, not just a single proxy server identifier.
 
 For instance, consider this PAC script:
 
@@ -532,12 +559,13 @@
 ```
 
 What proxy will Chrome use for connections to `www.example.com`, given that
-we have a choice of 3 separate proxies, each of different type?
+we have a choice of three separate proxy server identifiers to choose from
+{`http://proxy1:80`, `https://proxy2:443`, `socks5://proxy3:1080`}?
 
-Initially, Chrome will try the proxies in order. This means first attempting the
-request through the HTTP WebProxy `proxy1`. If that "fails", the request is
-next attempted through the HTTPS proxy `proxy2`. Lastly if that fails, the
-request is attempted through the SOCKSv5 proxy `proxy3`.
+Initially, Chrome will try the proxies in order. This means first attempting
+the request through `http://proxy1:80`. If that "fails", the request is
+next attempted through `https://proxy2:443`. Lastly if that fails, the
+request is attempted through `socks5://proxy3:1080`.
 
 This process is referred to as _proxy fallback_. What constitutes a
 "failure" is described later.
@@ -546,26 +574,24 @@
 is influenced by the past responsiveness of proxy servers.
 
 Let's say we request `http://www.example.com/`. Per the PAC script this
-resolves to:
+resolves to a list of three proxy server identifiers:
 
-```
-"PROXY proxy1; HTTPS proxy2; SOCKS5 proxy3"
-```
+{`http://proxy1:80`, `https://proxy2:443`, `socks5://proxy3:1080`}
 
 Chrome will first attempt to issue the request through these proxies in the
-left-to-right order (`proxy1`, `proxy2`, `proxy3`).
+left-to-right order.
 
-Let's say that the attempt through `proxy1` fails, but then the attempt through
-`proxy2` succeeds. Chrome will mark `proxy1` as _bad_ for the next 5 minutes.
-Being marked as _bad_ means that `proxy1` is de-prioritized with respect to
-other proxies options (including DIRECT) that are not marked as bad.
+Let's say that the attempt through `http://proxy1:80` fails, but then the
+attempt through `https://proxy2:443` succeeds. Chrome will mark
+`http://proxy1:80` as _bad_ for the next 5 minutes. Being marked as _bad_
+means that `http://proxy1:80` is de-prioritized with respect to
+other proxy server identifiers (including `direct://`) that are not marked as
+bad.
 
 That means the next time `http://www.example.com/` is requested, the effective
 order for proxies to attempt will be:
 
-```
-HTTPS proxy2; SOCKS5 proxy3; "PROXY proxy1"
-```
+{`https://proxy2:443`, `socks5://proxy3:1080`, `http://proxy1:80`}
 
 Conceptually, _bad_ proxies are moved to the end of the list, rather than being
 removed from consideration all together.
@@ -615,7 +641,7 @@
 will not give feedback that the bad proxies were cleared, however capturing a
 new NetLog dump can confirm it was cleared.
 
-## Arguments passed to `FindProxyForURL(url, host)` in PAC scripts
+## Arguments passed to FindProxyForURL() in PAC scripts
 
 PAC scripts in Chrome are expected to define a JavaScript function
 `FindProxyForURL`.
@@ -668,7 +694,7 @@
 capability](https://bugs.chromium.org/p/chromium/issues/detail?id=882536) in
 favor of a consistent policy.
 
-## Resolving client's IP address within a PAC script using `myIpAddress()`
+## Resolving client's IP address within a PAC script using myIpAddress()
 
 PAC scripts can invoke `myIpAddress()` to obtain the client's IP address. This
 function returns a single IP literal, or `"127.0.0.1"` on failure.
@@ -705,10 +731,10 @@
 *Historical note*: Prior to M72, Chrome's implementation of `myIpAddress()` was
 effectively just `getaddrinfo(gethostname)`. This is now step 2 of the heuristic.
 
-### What about `var pacUseMultihomedDNS`?
+### What about pacUseMultihomedDNS?
 
-In Firefox, if you define a global named `pacUseMultihomedDNS` in your PAC
-script, it causes `myIpAddress()` to report the IP address of the interface
+In Firefox, if you define a global variable named `pacUseMultihomedDNS` in your
+PAC script, it causes `myIpAddress()` to report the IP address of the interface
 that would (likely) have been used had we connected to it DIRECT.
 
 In particular, it will do a DNS resolution of the target host (the hostname of
@@ -720,7 +746,7 @@
 side-effects. Chrome has no APIs or settings to change `myIpAddress()`'s
 algorithm.
 
-## Resolving client's IP address within a PAC script using `myIpAddressEx()`
+## Resolving client's IP address within a PAC script using myIpAddressEx()
 
 Chrome supports the [Microsoft PAC
 extension](https://docs.microsoft.com/en-us/windows/desktop/winhttp/myipaddressex)
diff --git a/remoting/protocol/BUILD.gn b/remoting/protocol/BUILD.gn
index f4348df..993159e 100644
--- a/remoting/protocol/BUILD.gn
+++ b/remoting/protocol/BUILD.gn
@@ -266,12 +266,15 @@
 
     deps += [
       "//remoting/proto/remoting/v1:network_traversal_grpc_library",
-      "//third_party/webrtc/api:create_peerconnection_factory",
+      "//third_party/webrtc/api:callfactory_api",
       "//third_party/webrtc/api/audio_codecs:audio_codecs_api",
       "//third_party/webrtc/api/audio_codecs/opus:audio_decoder_opus",
       "//third_party/webrtc/api/audio_codecs/opus:audio_encoder_opus",
+      "//third_party/webrtc/api/rtc_event_log:rtc_event_log_factory",
       "//third_party/webrtc/api/video_codecs:builtin_video_decoder_factory",
+      "//third_party/webrtc/media:rtc_audio_video",
       "//third_party/webrtc_overrides:init_webrtc",
+      "//third_party/webrtc_overrides:task_queue_factory",
     ]
   }
 
diff --git a/remoting/protocol/DEPS b/remoting/protocol/DEPS
index 1fa327ef..d05d3a9 100644
--- a/remoting/protocol/DEPS
+++ b/remoting/protocol/DEPS
@@ -15,6 +15,7 @@
   "+third_party/libjingle_xmpp/xmpp",
   "+third_party/protobuf/src",
   "+third_party/webrtc",
+  "+third_party/webrtc_overrides",
   "+ui/events/keycodes/dom",
   "+services/network/test",
   "+services/network/public/cpp",
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
index 16ad0bce..cbea8e8 100644
--- a/remoting/protocol/webrtc_transport.cc
+++ b/remoting/protocol/webrtc_transport.cc
@@ -33,9 +33,14 @@
 #include "third_party/webrtc/api/audio_codecs/audio_encoder_factory_template.h"
 #include "third_party/webrtc/api/audio_codecs/opus/audio_decoder_opus.h"
 #include "third_party/webrtc/api/audio_codecs/opus/audio_encoder_opus.h"
-#include "third_party/webrtc/api/create_peerconnection_factory.h"
+#include "third_party/webrtc/api/call/call_factory_interface.h"
+#include "third_party/webrtc/api/peer_connection_interface.h"
+#include "third_party/webrtc/api/rtc_event_log/rtc_event_log_factory.h"
 #include "third_party/webrtc/api/stats/rtcstats_objects.h"
 #include "third_party/webrtc/api/video_codecs/builtin_video_decoder_factory.h"
+#include "third_party/webrtc/media/engine/webrtc_media_engine.h"
+#include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
+#include "third_party/webrtc_overrides/task_queue_factory.h"
 
 using jingle_xmpp::QName;
 using jingle_xmpp::XmlElement;
@@ -246,16 +251,28 @@
       : transport_(transport) {
     audio_module_ = new rtc::RefCountedObject<WebrtcAudioModule>();
 
-    peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
-        worker_thread,  // network_thread
-        worker_thread,
-        rtc::Thread::Current(),  // signaling_thread
-        audio_module_,
-        webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus>(),
-        webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus>(),
-        std::move(encoder_factory), webrtc::CreateBuiltinVideoDecoderFactory(),
-        nullptr,   // audio_mixer
-        nullptr);  // audio_processing
+    webrtc::PeerConnectionFactoryDependencies pcf_deps;
+    pcf_deps.network_thread = worker_thread;
+    pcf_deps.worker_thread = worker_thread;
+    pcf_deps.signaling_thread = rtc::Thread::Current();
+    pcf_deps.task_queue_factory = CreateWebRtcTaskQueueFactory();
+    pcf_deps.call_factory = webrtc::CreateCallFactory();
+    pcf_deps.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(
+        pcf_deps.task_queue_factory.get());
+    cricket::MediaEngineDependencies media_deps;
+    media_deps.task_queue_factory = pcf_deps.task_queue_factory.get();
+    media_deps.adm = audio_module_;
+    media_deps.audio_encoder_factory =
+        webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus>();
+    media_deps.audio_decoder_factory =
+        webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus>();
+    media_deps.video_encoder_factory = std::move(encoder_factory);
+    media_deps.video_decoder_factory =
+        webrtc::CreateBuiltinVideoDecoderFactory();
+    media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create();
+    pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
+    peer_connection_factory_ =
+        webrtc::CreateModularPeerConnectionFactory(std::move(pcf_deps));
 
     webrtc::PeerConnectionInterface::RTCConfiguration rtc_config;
     rtc_config.enable_dtls_srtp = true;
diff --git a/services/identity/public/cpp/access_token_fetcher_unittest.cc b/services/identity/public/cpp/access_token_fetcher_unittest.cc
index e5b287b..4fe4632 100644
--- a/services/identity/public/cpp/access_token_fetcher_unittest.cc
+++ b/services/identity/public/cpp/access_token_fetcher_unittest.cc
@@ -8,9 +8,9 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_task_environment.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
@@ -116,7 +116,7 @@
       std::move(on_access_token_request_callback_).Run();
   }
 
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   TestingPrefServiceSyncable pref_service_;
   TestSigninClient signin_client_;
   FakeProfileOAuth2TokenService token_service_;
diff --git a/services/identity/public/cpp/accounts_mutator_unittest.cc b/services/identity/public/cpp/accounts_mutator_unittest.cc
index dc23d0b..9874096 100644
--- a/services/identity/public/cpp/accounts_mutator_unittest.cc
+++ b/services/identity/public/cpp/accounts_mutator_unittest.cc
@@ -5,9 +5,9 @@
 #include "services/identity/public/cpp/accounts_mutator_impl.h"
 
 #include "base/bind.h"
-#include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_task_environment.h"
 #include "components/signin/core/browser/device_id_helper.h"
 #include "components/signin/core/browser/signin_metrics.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -111,7 +111,7 @@
   }
 
  private:
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   sync_preferences::TestingPrefServiceSyncable prefs_;
   network::TestURLLoaderFactory test_url_loader_factory_;
   identity::IdentityTestEnvironment identity_test_env_;
diff --git a/services/identity/public/cpp/identity_manager.cc b/services/identity/public/cpp/identity_manager.cc
index 7bdf6ab..12bd2ad 100644
--- a/services/identity/public/cpp/identity_manager.cc
+++ b/services/identity/public/cpp/identity_manager.cc
@@ -8,6 +8,7 @@
 
 #include "build/build_config.h"
 #include "components/signin/core/browser/account_fetcher_service.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/ubertoken_fetcher_impl.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "services/identity/public/cpp/accounts_cookie_mutator.h"
@@ -65,7 +66,15 @@
   token_service_->AddDiagnosticsObserver(this);
   token_service_->AddObserver(this);
   account_tracker_service_->AddObserver(this);
-  gaia_cookie_manager_service_->AddObserver(this);
+
+  // IdentityManager owns gaia_cookie_manager_service_ and will outlive it, so
+  // base::Unretained is safe.
+  gaia_cookie_manager_service_->SetGaiaAccountsInCookieUpdatedCallback(
+      base::BindRepeating(&IdentityManager::OnGaiaAccountsInCookieUpdated,
+                          base::Unretained(this)));
+  gaia_cookie_manager_service_->SetGaiaCookieDeletedByUserActionCallback(
+      base::BindRepeating(&IdentityManager::OnGaiaCookieDeletedByUserAction,
+                          base::Unretained(this)));
 
   // Seed the primary account with any state that |signin_manager_| loaded from
   // prefs.
@@ -86,7 +95,6 @@
   token_service_->RemoveObserver(this);
   token_service_->RemoveDiagnosticsObserver(this);
   account_tracker_service_->RemoveObserver(this);
-  gaia_cookie_manager_service_->RemoveObserver(this);
 }
 
 // TODO(862619) change return type to base::Optional<CoreAccountInfo>
diff --git a/services/identity/public/cpp/identity_manager.h b/services/identity/public/cpp/identity_manager.h
index 30d6ab7..a651290 100644
--- a/services/identity/public/cpp/identity_manager.h
+++ b/services/identity/public/cpp/identity_manager.h
@@ -13,7 +13,6 @@
 #include "components/signin/core/browser/account_fetcher_service.h"
 #include "components/signin/core/browser/account_info.h"
 #include "components/signin/core/browser/account_tracker_service.h"
-#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_manager_base.h"
 #include "components/signin/core/browser/signin_metrics.h"
@@ -40,6 +39,8 @@
 class PrefRegistrySimple;
 class SigninManagerAndroid;
 
+class GaiaCookieManagerService;
+
 namespace identity {
 
 class AccountsMutator;
@@ -56,7 +57,6 @@
 class IdentityManager : public SigninManagerBase::Observer,
                         public OAuth2TokenService::DiagnosticsObserver,
                         public OAuth2TokenService::Observer,
-                        public GaiaCookieManagerService::Observer,
                         public AccountTrackerService::Observer {
  public:
   class Observer {
@@ -600,12 +600,12 @@
   void OnAuthErrorChanged(const std::string& account_id,
                           const GoogleServiceAuthError& auth_error) override;
 
-  // GaiaCookieManagerService::Observer:
+  // GaiaCookieManagerService callbacks:
   void OnGaiaAccountsInCookieUpdated(
       const std::vector<gaia::ListedAccount>& signed_in_accounts,
       const std::vector<gaia::ListedAccount>& signed_out_accounts,
-      const GoogleServiceAuthError& error) override;
-  void OnGaiaCookieDeletedByUserAction() override;
+      const GoogleServiceAuthError& error);
+  void OnGaiaCookieDeletedByUserAction();
 
   // OAuth2TokenService::DiagnosticsObserver:
   void OnAccessTokenRequested(
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index bd196ec7..5b20265d 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -11,15 +11,16 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/containers/flat_set.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
 #include "components/image_fetcher/core/fake_image_decoder.h"
 #include "components/signin/core/browser/account_consistency_method.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/list_accounts_test_utils.h"
 #include "components/signin/core/browser/set_accounts_in_cookie_result.h"
 #include "components/signin/core/browser/signin_manager.h"
@@ -297,11 +298,11 @@
         << "AccountConsistency is not used by SigninManagerBase";
     auto signin_manager = std::make_unique<SigninManagerBase>(
         &signin_client_, token_service.get(), account_tracker_service.get(),
-        gaia_cookie_manager_service.get(), account_consistency);
+        account_consistency);
 #else
     auto signin_manager = std::make_unique<SigninManager>(
         &signin_client_, token_service.get(), account_tracker_service.get(),
-        gaia_cookie_manager_service.get(), account_consistency);
+        account_consistency);
 #endif
 
     // Passing this switch ensures that the new SigninManager starts with a
@@ -368,7 +369,7 @@
   }
 
  private:
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   sync_preferences::TestingPrefServiceSyncable pref_service_;
   network::TestURLLoaderFactory test_url_loader_factory_;
   TestSigninClient signin_client_;
diff --git a/services/identity/public/cpp/identity_test_environment.cc b/services/identity/public/cpp/identity_test_environment.cc
index 104a313..8b2d0a6 100644
--- a/services/identity/public/cpp/identity_test_environment.cc
+++ b/services/identity/public/cpp/identity_test_environment.cc
@@ -202,18 +202,18 @@
   std::unique_ptr<SigninManagerBase> signin_manager =
       std::make_unique<SigninManagerBase>(signin_client, token_service.get(),
                                           account_tracker_service.get(),
-                                          nullptr, account_consistency);
+                                          account_consistency);
 #else
   std::unique_ptr<SigninManagerBase> signin_manager =
       std::make_unique<SigninManager>(signin_client, token_service.get(),
-                                      account_tracker_service.get(), nullptr,
+                                      account_tracker_service.get(),
                                       account_consistency);
 #endif
   signin_manager->Initialize(pref_service);
 
-  std::unique_ptr<GaiaCookieManagerService> gaia_cookie_manager_service;
-    gaia_cookie_manager_service = std::make_unique<GaiaCookieManagerService>(
-        token_service.get(), signin_client);
+  std::unique_ptr<GaiaCookieManagerService> gaia_cookie_manager_service =
+      std::make_unique<GaiaCookieManagerService>(token_service.get(),
+                                                 signin_client);
 
   std::unique_ptr<PrimaryAccountMutator> primary_account_mutator;
   std::unique_ptr<AccountsMutator> accounts_mutator;
diff --git a/services/identity/public/cpp/identity_test_utils.cc b/services/identity/public/cpp/identity_test_utils.cc
index 4d2215d..d380841b 100644
--- a/services/identity/public/cpp/identity_test_utils.cc
+++ b/services/identity/public/cpp/identity_test_utils.cc
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string_split.h"
 #include "components/signin/core/browser/account_tracker_service.h"
+#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/list_accounts_test_utils.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "google_apis/gaia/gaia_auth_util.h"
diff --git a/services/identity/public/cpp/primary_account_access_token_fetcher_unittest.cc b/services/identity/public/cpp/primary_account_access_token_fetcher_unittest.cc
index a79464d..ab3e9e5 100644
--- a/services/identity/public/cpp/primary_account_access_token_fetcher_unittest.cc
+++ b/services/identity/public/cpp/primary_account_access_token_fetcher_unittest.cc
@@ -8,9 +8,9 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_task_environment.h"
 #include "services/identity/public/cpp/identity_manager.h"
 #include "services/identity/public/cpp/identity_test_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -79,7 +79,7 @@
   }
 
  private:
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   IdentityTestEnvironment identity_test_env_;
   AccessTokenInfo access_token_info_;
 };
diff --git a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_mac.cc b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_mac.cc
index d668ed60..a2041824 100644
--- a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_mac.cc
+++ b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_mac.cc
@@ -22,6 +22,37 @@
 
 namespace {
 
+#if !defined(MAC_OS_X_VERSION_10_11) || \
+    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11
+// The |phys_footprint| field was introduced in 10.11.
+struct ChromeTaskVMInfo {
+  mach_vm_size_t virtual_size;
+  integer_t region_count;
+  integer_t page_size;
+  mach_vm_size_t resident_size;
+  mach_vm_size_t resident_size_peak;
+  mach_vm_size_t device;
+  mach_vm_size_t device_peak;
+  mach_vm_size_t internal;
+  mach_vm_size_t internal_peak;
+  mach_vm_size_t external;
+  mach_vm_size_t external_peak;
+  mach_vm_size_t reusable;
+  mach_vm_size_t reusable_peak;
+  mach_vm_size_t purgeable_volatile_pmap;
+  mach_vm_size_t purgeable_volatile_resident;
+  mach_vm_size_t purgeable_volatile_virtual;
+  mach_vm_size_t compressed;
+  mach_vm_size_t compressed_peak;
+  mach_vm_size_t compressed_lifetime;
+  mach_vm_size_t phys_footprint;
+};
+#else
+using ChromeTaskVMInfo = task_vm_info;
+#endif  // MAC_OS_X_VERSION_10_11
+mach_msg_type_number_t ChromeTaskVMInfoCount =
+    sizeof(ChromeTaskVMInfo) / sizeof(natural_t);
+
 using VMRegion = mojom::VmRegion;
 
 bool IsAddressInSharedRegion(uint64_t address) {
@@ -209,14 +240,22 @@
 // static
 bool OSMetrics::FillOSMemoryDump(base::ProcessId pid,
                                  mojom::RawOSMemDump* dump) {
-  // Creating process metrics for child processes in mac or windows requires
-  // additional information like ProcessHandle or port provider.
-  DCHECK_EQ(base::kNullProcessId, pid);
-  auto process_metrics = base::ProcessMetrics::CreateCurrentProcessMetrics();
-  base::ProcessMetrics::TaskVMInfo info = process_metrics->GetTaskVMInfo();
-  dump->platform_private_footprint->phys_footprint_bytes = info.phys_footprint;
-  dump->platform_private_footprint->internal_bytes = info.internal;
-  dump->platform_private_footprint->compressed_bytes = info.compressed;
+  ChromeTaskVMInfo task_vm_info;
+  mach_msg_type_number_t count = ChromeTaskVMInfoCount;
+  kern_return_t result =
+      task_info(mach_task_self(), TASK_VM_INFO,
+                reinterpret_cast<task_info_t>(&task_vm_info), &count);
+  if (result != KERN_SUCCESS)
+    return false;
+
+  dump->platform_private_footprint->internal_bytes = task_vm_info.internal;
+  dump->platform_private_footprint->compressed_bytes = task_vm_info.compressed;
+
+  if (count == ChromeTaskVMInfoCount) {
+    dump->platform_private_footprint->phys_footprint_bytes =
+        task_vm_info.phys_footprint;
+  }
+
   return true;
 }
 
diff --git a/services/ws/public/mojom/BUILD.gn b/services/ws/public/mojom/BUILD.gn
index 8d53c49..f19658b 100644
--- a/services/ws/public/mojom/BUILD.gn
+++ b/services/ws/public/mojom/BUILD.gn
@@ -22,7 +22,6 @@
     "//mojo/public/mojom/base",
     "//ui/gfx/geometry/mojo",
     "//ui/gfx/mojo",
-    "//ui/platform_window/mojo:interfaces",
   ]
 
   if (is_chromeos) {
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 2a14f13..d2cffce 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -1132,10 +1132,9 @@
       {
         "args": [
           "-v",
-          "--browser=exact",
+          "--browser=android-chrome",
           "--upload-results",
-          "--browser-executable=../../out/Release/bin/monochrome_64_32_bundle",
-          "--device=android",
+          "--run-ref-build",
           "--test-shard-map-filename=android-pixel2-perf_map.json"
         ],
         "isolate_name": "performance_test_suite",
diff --git a/testing/test.gni b/testing/test.gni
index 563586d..9fb6865 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -274,17 +274,18 @@
       ]
     }
 
+    fuchsia_package(_pkg_target) {
+      testonly = true
+      forward_variables_from(invoker, [ "sandbox_policy" ])
+      binary = ":$_exec_target"
+      package_name_override = _output_name
+    }
+
     executable(_exec_target) {
       forward_variables_from(invoker, "*")
       testonly = true
       output_name = _exec_target
     }
-
-    fuchsia_package(_pkg_target) {
-      testonly = true
-      binary = ":$_exec_target"
-      package_name_override = _output_name
-    }
   } else if (is_ios) {
     import("//build/config/ios/ios_sdk.gni")
     import("//build/config/ios/rules.gni")
diff --git a/third_party/blink/PRESUBMIT.py b/third_party/blink/PRESUBMIT.py
index e6b80501..0095457d 100644
--- a/third_party/blink/PRESUBMIT.py
+++ b/third_party/blink/PRESUBMIT.py
@@ -25,9 +25,10 @@
 
 
 _EXCLUDED_PATHS = (
-    # These directories are created and updated via a script.
-    r'^third_party[\\\/]blink[\\\/]tools[\\\/]blinkpy[\\\/]third_party[\\\/]wpt[\\\/]wpt[\\\/].*',
-    r'^third_party[\\\/]blink[\\\/]web_tests[\\\/]external[\\\/]wpt[\\\/]tools[\\\/].*',
+    # These are third-party dependencies that we don't directly control.
+    r'^third_party[\\/]blink[\\/]tools[\\/]blinkpy[\\/]third_party[\\/]wpt[\\/]wpt[\\/].*',
+    r'^third_party[\\/]blink[\\/]web_tests[\\/]external[\\/]wpt[\\/]tools[\\/].*',
+    r'^third_party[\\/]blink[\\/]web_tests[\\/]external[\\/]wpt[\\/]resources[\\/]webidl2[\\/].*',
 )
 
 
diff --git a/third_party/blink/public/platform/scheduler/OWNERS b/third_party/blink/public/platform/scheduler/OWNERS
index 8423b49..00ad1396 100644
--- a/third_party/blink/public/platform/scheduler/OWNERS
+++ b/third_party/blink/public/platform/scheduler/OWNERS
@@ -1,5 +1,6 @@
 altimin@chromium.org
 alexclarke@chromium.org
+carlscab@google.com
 rmcilroy@chromium.org
 skyostil@chromium.org
 
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 5595c3bb8..7a4a457 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1847,7 +1847,7 @@
     "frame/performance_monitor_test.cc",
     "frame/root_frame_viewport_test.cc",
     "frame/rotation_viewport_anchor_test.cc",
-    "frame/use_counter_test.cc",
+    "frame/use_counter_helper_test.cc",
     "frame/visual_viewport_test.cc",
     "fullscreen/scoped_allow_fullscreen_test.cc",
     "geometry/dom_matrix_test.cc",
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index a56192af..525a24b 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -1132,9 +1132,10 @@
   Element* element = state.GetElement();
   DCHECK(element);
 
-  // The animating element may be this element, or its pseudo element. It is
-  // null when calculating the style for a potential pseudo element that has
-  // yet to be created.
+  // The animating element may be this element, the pseudo element we are
+  // resolving style for, or null if we are resolving style for a pseudo
+  // element which is not represented by a PseudoElement like scrollbar pseudo
+  // elements.
   DCHECK(animating_element == element || !animating_element ||
          animating_element->ParentOrShadowHostElement() == element);
 
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_budget_test.cc b/third_party/blink/renderer/core/display_lock/display_lock_budget_test.cc
index 7295877..b372b973 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_budget_test.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_budget_test.cc
@@ -18,22 +18,15 @@
 
 namespace blink {
 
-class DisplayLockBudgetTest : public RenderingTest {
+class DisplayLockBudgetTest : public RenderingTest,
+                              private ScopedDisplayLockingForTest {
  public:
+  DisplayLockBudgetTest() : ScopedDisplayLockingForTest(true) {}
   void SetUp() override {
     RenderingTest::SetUp();
-    features_backup_.emplace();
-    RuntimeEnabledFeatures::SetDisplayLockingEnabled(true);
     test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
   }
 
-  void TearDown() override {
-    if (features_backup_) {
-      features_backup_->Restore();
-      features_backup_.reset();
-    }
-  }
-
   double GetBudgetMs(const YieldingDisplayLockBudget& budget) const {
     return budget.GetCurrentBudgetMs();
   }
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc b/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
index 373a8b74..eb369f613 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
@@ -17,7 +17,7 @@
 #include "third_party/blink/renderer/core/html/html_template_element.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 
 namespace blink {
@@ -84,19 +84,16 @@
 };
 }  // namespace
 
-class DisplayLockContextTest : public testing::Test {
+class DisplayLockContextTest : public testing::Test,
+                               private ScopedDisplayLockingForTest {
  public:
+  DisplayLockContextTest() : ScopedDisplayLockingForTest(true) {}
+
   void SetUp() override {
-    features_backup_.emplace();
-    RuntimeEnabledFeatures::SetDisplayLockingEnabled(true);
     web_view_helper_.Initialize();
   }
 
   void TearDown() override {
-    if (features_backup_) {
-      features_backup_->Restore();
-      features_backup_.reset();
-    }
     web_view_helper_.Reset();
   }
 
@@ -163,7 +160,6 @@
   const int FAKE_FIND_ID = 1;
 
  private:
-  base::Optional<RuntimeEnabledFeatures::Backup> features_backup_;
   frame_test_helpers::WebViewHelper web_view_helper_;
 };
 
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
index 69e1164..e34792f 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
@@ -10,23 +10,16 @@
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 
 namespace blink {
 
-class DisplayLockUtilitiesTest : public RenderingTest {
+class DisplayLockUtilitiesTest : public RenderingTest,
+                                 private ScopedDisplayLockingForTest {
  public:
   DisplayLockUtilitiesTest()
-      : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {}
-
-  void SetUp() override {
-    RenderingTest::SetUp();
-    RuntimeEnabledFeatures::SetDisplayLockingEnabled(true);
-  }
-
-  void TearDown() override {
-    RenderingTest::TearDown();
-    RuntimeEnabledFeatures::SetDisplayLockingEnabled(false);
-  }
+      : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()),
+        ScopedDisplayLockingForTest(true) {}
 };
 
 TEST_F(DisplayLockUtilitiesTest, ActivatableLockedInclusiveAncestors) {
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 8208796..4d8b1abe 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -66,7 +66,7 @@
 #include "third_party/blink/renderer/core/execution_context/security_context.h"
 #include "third_party/blink/renderer/core/frame/dom_timer_coordinator.h"
 #include "third_party/blink/renderer/core/frame/hosts_using_features.h"
-#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/frame/use_counter_helper.h"
 #include "third_party/blink/renderer/core/html/custom/v0_custom_element.h"
 #include "third_party/blink/renderer/core/html/parser/parser_synchronization_policy.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 65e52777a..e7a208e 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -267,8 +267,14 @@
     return false;
   // The only block-container display types that potentially don't establish a
   // new formatting context, are 'block' and 'list-item'.
-  if (display != EDisplay::kBlock && display != EDisplay::kListItem)
-    return true;
+  if (display != EDisplay::kBlock && display != EDisplay::kListItem) {
+    // DETAILS and SUMMARY elements partially or completely ignore the display
+    // type, though, and may end up disregarding the display type and just
+    // create block containers. And those don't necessarily create a formatting
+    // context.
+    if (!IsHTMLDetailsElement(node) && !IsHTMLSummaryElement(node))
+      return true;
+  }
   if (!style.IsOverflowVisible())
     return node.GetDocument().ViewportDefiningElement() != &node;
   if (style.HasOutOfFlowPosition() || style.IsFloating() ||
diff --git a/third_party/blink/renderer/core/frame/BUILD.gn b/third_party/blink/renderer/core/frame/BUILD.gn
index 03647d3..807a17d 100644
--- a/third_party/blink/renderer/core/frame/BUILD.gn
+++ b/third_party/blink/renderer/core/frame/BUILD.gn
@@ -171,8 +171,9 @@
     "smart_clip.cc",
     "smart_clip.h",
     "test_report_body.h",
-    "use_counter.cc",
     "use_counter.h",
+    "use_counter_helper.cc",
+    "use_counter_helper.h",
     "user_activation.cc",
     "user_activation.h",
     "viewport_data.cc",
diff --git a/third_party/blink/renderer/core/frame/ad_tracker_test.cc b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
index 5d16464..bc115e8 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker_test.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 
 namespace blink {
@@ -494,11 +495,11 @@
   EXPECT_TRUE(local_subframe->IsAdSubframe());
 }
 
-class AdTrackerDisabledSimTest : public SimTest {
+class AdTrackerDisabledSimTest : public SimTest,
+                                 private ScopedAdTaggingForTest {
  protected:
+  AdTrackerDisabledSimTest() : ScopedAdTaggingForTest(false) {}
   void SetUp() override {
-    RuntimeEnabledFeatures::SetAdTaggingEnabled(false);
-
     SimTest::SetUp();
     main_resource_ = std::make_unique<SimRequest>(
         "https://example.com/test.html", "text/html");
diff --git a/third_party/blink/renderer/core/frame/frame_test.cc b/third_party/blink/renderer/core/frame/frame_test.cc
index 58ebe05..0a126c3 100644
--- a/third_party/blink/renderer/core/frame/frame_test.cc
+++ b/third_party/blink/renderer/core/frame/frame_test.cc
@@ -10,6 +10,7 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/testing/document_interface_broker_test_helpers.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 
 namespace blink {
@@ -163,7 +164,7 @@
 }
 
 TEST_F(FrameTest, UserActivationInterfaceTest) {
-  RuntimeEnabledFeatures::SetUserActivationV2Enabled(true);
+  ScopedUserActivationV2ForTest scoped_feature(true);
 
   // Initially both sticky and transient bits are false.
   EXPECT_FALSE(GetDocument().GetFrame()->HasBeenActivated());
@@ -187,7 +188,7 @@
 }
 
 TEST_F(FrameTest, UserActivationHistograms) {
-  RuntimeEnabledFeatures::SetUserActivationV2Enabled(true);
+  ScopedUserActivationV2ForTest scoped_feature(true);
   base::HistogramTester histograms;
 
   LocalFrame::HasTransientUserActivation(GetDocument().GetFrame());
diff --git a/third_party/blink/renderer/core/frame/use_counter.h b/third_party/blink/renderer/core/frame/use_counter.h
index 4a948b3..9f1e86c52 100644
--- a/third_party/blink/renderer/core/frame/use_counter.h
+++ b/third_party/blink/renderer/core/frame/use_counter.h
@@ -26,151 +26,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_USE_COUNTER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_USE_COUNTER_H_
 
-#include <bitset>
-#include "base/macros.h"
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/css/css_property_names.h"
-#include "third_party/blink/renderer/core/css/parser/css_parser_mode.h"
-#include "third_party/blink/renderer/core/frame/web_feature.h"
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
-#include "third_party/blink/renderer/platform/wtf/forward.h"
-
-namespace blink {
-
-class DocumentLoader;
-class Element;
-class EnumerationHistogram;
-class LocalFrame;
-class StyleSheetContents;
-
-// Utility class for muting UseCounter, for instance ignoring attributes
-// constructed in user-agent shadow DOM. Once constructed, all UseCounting
-// is muted, until the object is destroyed again. It is the callees
-// responsibility to make sure this happens.
-class UseCounterMuteScope {
-  STACK_ALLOCATED();
-
- public:
-  UseCounterMuteScope(const Element& element);
-  ~UseCounterMuteScope();
-
- private:
-  Member<DocumentLoader> loader_;
-};
-
-// This class provides an implementation of UseCounter - see the class comment
-// of blink::UseCounter for the feature.
-// Changes on UseCounterHelper are observable by UseCounterHelper::Observer.
-class CORE_EXPORT UseCounterHelper final {
-  DISALLOW_NEW();
-
- public:
-  // The context determines whether a feature is reported to UMA histograms. For
-  // example, when the context is set to kDisabledContext, no features will be
-  // reported to UMA, but features may still be marked as seen to avoid multiple
-  // console warnings for deprecation.
-  enum Context {
-    kDefaultContext,
-    // Counters for extensions.
-    kExtensionContext,
-    // Context for file:// URLs.
-    kFileContext,
-    // Context when counters should be disabled (eg, internal pages such as
-    // about, devtools, etc).
-    kDisabledContext
-  };
-
-  enum CommitState { kPreCommit, kCommited };
-
-  // CSS properties for animation are separately counted. This enum is used to
-  // distinguish them.
-  enum class CSSPropertyType { kDefault, kAnimation };
-
-  explicit UseCounterHelper(Context = kDefaultContext,
-                            CommitState = kPreCommit);
-
-  // An interface to observe UseCounterHelper changes. Note that this is never
-  // notified when the counter is disabled by |m_muteCount| or when |m_context|
-  // is kDisabledContext.
-  class Observer : public GarbageCollected<Observer> {
-   public:
-    // Notified when a feature is counted for the first time. This should return
-    // true if it no longer needs to observe changes so that the counter can
-    // remove a reference to the observer and stop notifications.
-    virtual bool OnCountFeature(WebFeature) = 0;
-
-    virtual void Trace(blink::Visitor* visitor) {}
-  };
-
-  // Repeated calls are ignored.
-  void Count(CSSPropertyID, CSSPropertyType, const LocalFrame*);
-  // Repeated calls are ignored.
-  void Count(WebFeature, const LocalFrame*);
-
-  bool IsCounted(CSSPropertyID unresolved_property, CSSPropertyType) const;
-
-  // Retains a reference to the observer to notify of UseCounterHelper changes.
-  void AddObserver(Observer*);
-
-  // Invoked when a new document is loaded into the main frame of the page.
-  void DidCommitLoad(const LocalFrame*);
-
-  // When muted, all calls to "count" functions are ignoed.  May be nested.
-  void MuteForInspector();
-  void UnmuteForInspector();
-
-  void RecordMeasurement(WebFeature, const LocalFrame&);
-  void ReportAndTraceMeasurementByFeatureId(int, const LocalFrame&);
-  void ReportAndTraceMeasurementByCSSSampleId(int,
-                                              const LocalFrame*,
-                                              bool /*is_animated*/);
-
-  // Return whether the feature has been seen since the last page load
-  // (except when muted).  Does include features seen in documents which have
-  // reporting disabled.
-  bool HasRecordedMeasurement(WebFeature) const;
-
-  void ClearMeasurementForTesting(WebFeature);
-
-  void Trace(blink::Visitor*);
-
- private:
-  // Notifies that a feature is newly counted to |m_observers|. This shouldn't
-  // be called when the counter is disabled by |m_muteCount| or when |m_context|
-  // if kDisabledContext.
-  void NotifyFeatureCounted(WebFeature);
-
-  EnumerationHistogram& FeaturesHistogram() const;
-  EnumerationHistogram& CssHistogram() const;
-  EnumerationHistogram& AnimatedCSSHistogram() const;
-
-  static int MapCSSPropertyIdToCSSSampleIdForHistogram(CSSPropertyID);
-
-  // If non-zero, ignore all 'count' calls completely.
-  unsigned mute_count_;
-
-  // The scope represented by this UseCounterHelper instance, which must be
-  // fixed for the duration of a page but can change when a new page is loaded.
-  Context context_;
-  // CommitState tracks whether navigation has commited. Prior to commit,
-  // UseCounters are logged locally and delivered to the browser only once the
-  // document has been commited (eg. to ensure never logging a feature that has
-  // no corresponding PageVisits).
-  CommitState commit_state_;
-
-  // Track what features/properties have been recorded.
-  std::bitset<static_cast<size_t>(WebFeature::kNumberOfFeatures)>
-      features_recorded_;
-  std::bitset<numCSSPropertyIDs> css_recorded_;
-  std::bitset<numCSSPropertyIDs> animated_css_recorded_;
-
-  HeapHashSet<Member<Observer>> observers_;
-
-  DISALLOW_COPY_AND_ASSIGN(UseCounterHelper);
-};
-
-}  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_USE_COUNTER_H_
diff --git a/third_party/blink/renderer/core/frame/use_counter.cc b/third_party/blink/renderer/core/frame/use_counter_helper.cc
similarity index 99%
rename from third_party/blink/renderer/core/frame/use_counter.cc
rename to third_party/blink/renderer/core/frame/use_counter_helper.cc
index 5b723084..ca80548 100644
--- a/third_party/blink/renderer/core/frame/use_counter.cc
+++ b/third_party/blink/renderer/core/frame/use_counter_helper.cc
@@ -23,7 +23,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/frame/use_counter_helper.h"
 
 #include "third_party/blink/public/mojom/use_counter/css_property_id.mojom-blink.h"
 #include "third_party/blink/renderer/core/css/css_style_sheet.h"
diff --git a/third_party/blink/renderer/core/frame/use_counter_helper.h b/third_party/blink/renderer/core/frame/use_counter_helper.h
new file mode 100644
index 0000000..8b0145a
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/use_counter_helper.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2012 Google, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_USE_COUNTER_HELPER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_USE_COUNTER_HELPER_H_
+
+#include <bitset>
+#include "base/macros.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/css/css_property_names.h"
+#include "third_party/blink/renderer/core/css/parser/css_parser_mode.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+class DocumentLoader;
+class Element;
+class EnumerationHistogram;
+class LocalFrame;
+
+// Utility class for muting UseCounter, for instance ignoring attributes
+// constructed in user-agent shadow DOM. Once constructed, all UseCounting
+// is muted, until the object is destroyed again. It is the callees
+// responsibility to make sure this happens.
+class UseCounterMuteScope {
+  STACK_ALLOCATED();
+
+ public:
+  UseCounterMuteScope(const Element& element);
+  ~UseCounterMuteScope();
+
+ private:
+  Member<DocumentLoader> loader_;
+};
+
+// This class provides an implementation of UseCounter - see the class comment
+// of blink::UseCounter for the feature.
+// Changes on UseCounterHelper are observable by UseCounterHelper::Observer.
+class CORE_EXPORT UseCounterHelper final {
+  DISALLOW_NEW();
+
+ public:
+  // The context determines whether a feature is reported to UMA histograms. For
+  // example, when the context is set to kDisabledContext, no features will be
+  // reported to UMA, but features may still be marked as seen to avoid multiple
+  // console warnings for deprecation.
+  enum Context {
+    kDefaultContext,
+    // Counters for extensions.
+    kExtensionContext,
+    // Context for file:// URLs.
+    kFileContext,
+    // Context when counters should be disabled (eg, internal pages such as
+    // about, devtools, etc).
+    kDisabledContext
+  };
+
+  enum CommitState { kPreCommit, kCommited };
+
+  // CSS properties for animation are separately counted. This enum is used to
+  // distinguish them.
+  enum class CSSPropertyType { kDefault, kAnimation };
+
+  explicit UseCounterHelper(Context = kDefaultContext,
+                            CommitState = kPreCommit);
+
+  // An interface to observe UseCounterHelper changes. Note that this is never
+  // notified when the counter is disabled by |m_muteCount| or when |m_context|
+  // is kDisabledContext.
+  class Observer : public GarbageCollected<Observer> {
+   public:
+    // Notified when a feature is counted for the first time. This should return
+    // true if it no longer needs to observe changes so that the counter can
+    // remove a reference to the observer and stop notifications.
+    virtual bool OnCountFeature(WebFeature) = 0;
+
+    virtual void Trace(blink::Visitor* visitor) {}
+  };
+
+  // Repeated calls are ignored.
+  void Count(CSSPropertyID, CSSPropertyType, const LocalFrame*);
+  // Repeated calls are ignored.
+  void Count(WebFeature, const LocalFrame*);
+
+  bool IsCounted(CSSPropertyID unresolved_property, CSSPropertyType) const;
+
+  // Retains a reference to the observer to notify of UseCounterHelper changes.
+  void AddObserver(Observer*);
+
+  // Invoked when a new document is loaded into the main frame of the page.
+  void DidCommitLoad(const LocalFrame*);
+
+  // When muted, all calls to "count" functions are ignoed.  May be nested.
+  void MuteForInspector();
+  void UnmuteForInspector();
+
+  void RecordMeasurement(WebFeature, const LocalFrame&);
+  void ReportAndTraceMeasurementByFeatureId(int, const LocalFrame&);
+  void ReportAndTraceMeasurementByCSSSampleId(int,
+                                              const LocalFrame*,
+                                              bool /*is_animated*/);
+
+  // Return whether the feature has been seen since the last page load
+  // (except when muted).  Does include features seen in documents which have
+  // reporting disabled.
+  bool HasRecordedMeasurement(WebFeature) const;
+
+  void ClearMeasurementForTesting(WebFeature);
+
+  void Trace(blink::Visitor*);
+
+ private:
+  // Notifies that a feature is newly counted to |m_observers|. This shouldn't
+  // be called when the counter is disabled by |m_muteCount| or when |m_context|
+  // if kDisabledContext.
+  void NotifyFeatureCounted(WebFeature);
+
+  EnumerationHistogram& FeaturesHistogram() const;
+  EnumerationHistogram& CssHistogram() const;
+  EnumerationHistogram& AnimatedCSSHistogram() const;
+
+  static int MapCSSPropertyIdToCSSSampleIdForHistogram(CSSPropertyID);
+
+  // If non-zero, ignore all 'count' calls completely.
+  unsigned mute_count_;
+
+  // The scope represented by this UseCounterHelper instance, which must be
+  // fixed for the duration of a page but can change when a new page is loaded.
+  Context context_;
+  // CommitState tracks whether navigation has commited. Prior to commit,
+  // UseCounters are logged locally and delivered to the browser only once the
+  // document has been commited (eg. to ensure never logging a feature that has
+  // no corresponding PageVisits).
+  CommitState commit_state_;
+
+  // Track what features/properties have been recorded.
+  std::bitset<static_cast<size_t>(WebFeature::kNumberOfFeatures)>
+      features_recorded_;
+  std::bitset<numCSSPropertyIDs> css_recorded_;
+  std::bitset<numCSSPropertyIDs> animated_css_recorded_;
+
+  HeapHashSet<Member<Observer>> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(UseCounterHelper);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_USE_COUNTER_HELPER_H_
diff --git a/third_party/blink/renderer/core/frame/use_counter_test.cc b/third_party/blink/renderer/core/frame/use_counter_helper_test.cc
similarity index 99%
rename from third_party/blink/renderer/core/frame/use_counter_test.cc
rename to third_party/blink/renderer/core/frame/use_counter_helper_test.cc
index ca4a76a..6c51028a 100644
--- a/third_party/blink/renderer/core/frame/use_counter_test.cc
+++ b/third_party/blink/renderer/core/frame/use_counter_helper_test.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/core/frame/use_counter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/use_counter/css_property_id.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/frame/deprecation.h"
+#include "third_party/blink/renderer/core/frame/use_counter.h"
 #include "third_party/blink/renderer/core/html/html_html_element.h"
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/page/page.h"
diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index ebde4d2..b490f79a 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -2294,12 +2294,10 @@
     if (capture_target) {
       LayoutObject* layout_object = capture_target->GetLayoutObject();
 
-      // TODO(eirage): Is kIgnoreTransforms correct here?
       LayoutPoint local_point =
           layout_object ? layout_object
                               ->AbsoluteToLocalPoint(
-                                  PhysicalOffsetToBeNoop(document_point),
-                                  kIgnoreTransforms)
+                                  PhysicalOffsetToBeNoop(document_point))
                               .ToLayoutPoint()
                         : document_point;
 
diff --git a/third_party/blink/renderer/core/input/event_handler_test.cc b/third_party/blink/renderer/core/input/event_handler_test.cc
index 9cb23518..2b94c53 100644
--- a/third_party/blink/renderer/core/input/event_handler_test.cc
+++ b/third_party/blink/renderer/core/input/event_handler_test.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/public/platform/web_keyboard_event.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/range.h"
+#include "third_party/blink/renderer/core/editing/dom_selection.h"
 #include "third_party/blink/renderer/core/editing/editing_behavior.h"
 #include "third_party/blink/renderer/core/editing/editor.h"
 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
@@ -2170,4 +2171,62 @@
   ASSERT_EQ(visual_viewport.GetScrollOffset().Height(), 300);
 }
 
+TEST_F(EventHandlerSimTest, SelecteTransformedTextWhenCapturing) {
+  WebView().MainFrameWidget()->Resize(WebSize(800, 600));
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+      <div id='target' style = "width:250px; transform: rotate(180deg)">
+      Some text to select
+      </div>
+  )HTML");
+  Compositor().BeginFrame();
+
+  WebMouseEvent mouse_down_event(WebInputEvent::kMouseDown,
+                                 WebFloatPoint(100, 20), WebFloatPoint(0, 0),
+                                 WebPointerProperties::Button::kLeft, 1,
+                                 WebInputEvent::Modifiers::kLeftButtonDown,
+                                 WebInputEvent::GetStaticTimeStampForTests());
+  mouse_down_event.SetFrameScale(1);
+  GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
+      mouse_down_event);
+
+  ASSERT_TRUE(GetDocument()
+                  .GetFrame()
+                  ->GetEventHandler()
+                  .GetSelectionController()
+                  .MouseDownMayStartSelect());
+
+  Element* target = GetDocument().getElementById("target");
+  GetDocument().GetFrame()->GetEventHandler().SetPointerCapture(
+      PointerEventFactory::kMouseId, target);
+
+  WebMouseEvent mouse_move_event(WebInputEvent::kMouseMove,
+                                 WebFloatPoint(258, 20), WebFloatPoint(0, 0),
+                                 WebPointerProperties::Button::kLeft, 1,
+                                 WebInputEvent::Modifiers::kLeftButtonDown,
+                                 WebInputEvent::GetStaticTimeStampForTests());
+  mouse_move_event.SetFrameScale(1);
+  GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
+      mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
+
+  WebMouseEvent mouse_up_event(
+      WebMouseEvent::kMouseUp, WebFloatPoint(258, 20), WebFloatPoint(0, 0),
+      WebPointerProperties::Button::kLeft, 1, WebInputEvent::kNoModifiers,
+      WebInputEvent::GetStaticTimeStampForTests());
+  mouse_up_event.SetFrameScale(1);
+  GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
+      mouse_up_event);
+
+  ASSERT_FALSE(GetDocument()
+                   .GetFrame()
+                   ->GetEventHandler()
+                   .GetSelectionController()
+                   .MouseDownMayStartSelect());
+
+  ASSERT_TRUE(GetDocument().GetSelection());
+  EXPECT_EQ("Some text to select", GetDocument().GetSelection()->toString());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/jank_tracker_test.cc b/third_party/blink/renderer/core/layout/jank_tracker_test.cc
index 2df6908..ed95372 100644
--- a/third_party/blink/renderer/core/layout/jank_tracker_test.cc
+++ b/third_party/blink/renderer/core/layout/jank_tracker_test.cc
@@ -331,4 +331,55 @@
   EXPECT_FLOAT_EQ(0.15, jank_tracker.WeightedScore());
 }
 
+TEST_F(JankTrackerTest, StableCompositingChanges) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      body { margin: 0; }
+      #outer {
+        margin-left: 50px;
+        margin-top: 50px;
+        width: 200px;
+        height: 200px;
+        background: #dde;
+      }
+      .tr {
+        will-change: transform;
+      }
+      .pl {
+        position: relative;
+        z-index: 0;
+        left: 0;
+        top: 0;
+      }
+      #inner {
+        display: inline-block;
+        width: 100px;
+        height: 100px;
+        background: #666;
+        margin-left: 50px;
+        margin-top: 50px;
+      }
+    </style>
+    <div id=outer><div id=inner></div></div>
+  )HTML");
+
+  Element* element = GetDocument().getElementById("outer");
+  size_t state = 0;
+  auto advance = [this, element, &state]() -> bool {
+    //
+    // Test each of the following transitions:
+    // - add/remove a PaintLayer
+    // - add/remove a cc::Layer when there is already a PaintLayer
+    // - add/remove a cc::Layer and a PaintLayer together
+
+    static const char* states[] = {"", "pl", "pl tr", "pl", "", "tr", ""};
+    element->setAttribute(html_names::kClassAttr, AtomicString(states[state]));
+    UpdateAllLifecyclePhases();
+    return ++state < sizeof states / sizeof *states;
+  };
+  while (advance()) {
+  }
+  EXPECT_FLOAT_EQ(0, GetJankTracker().Score());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index 54b0485..0839573 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -1115,13 +1115,13 @@
 
 void LayoutBlock::AddPercentHeightDescendant(LayoutBox* descendant) {
   // A replaced object is incapable of properly acting as a containing block for
-  // its children (this is an issue with VIDEO elements, for instance, which
-  // inserts some percentage height flexbox children). Assert that the
-  // descendant hasn't escaped from within a replaced object. Registering the
-  // percentage height descendant further up in the tree is only going to cause
-  // trouble, especially if the replaced object is out-of-flow positioned (and
-  // we failed to notice).
-  DCHECK(!descendant->Container()->IsLayoutReplaced());
+  // its children. This is an issue with VIDEO elements, for instance, which
+  // insert some percentage height flexbox children. It is also very easily
+  // achievable with a foreignObject inside an SVG. Detect this situation and
+  // bail. The assumption is that there is no situation where we require quirky
+  // percentage height behavior inside replaced content.
+  if (UNLIKELY(descendant->Container()->IsLayoutReplaced()))
+    return;
 
   if (descendant->PercentHeightContainer()) {
     if (descendant->PercentHeightContainer() == this) {
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 9cbf663..5b1349b 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -3793,13 +3793,7 @@
           &cb, &skipped_auto_height_containing_block);
 
   DCHECK(cb);
-
-  // If the container of the descendant is a replaced element (a VIDEO, for
-  // instance), |cb| (which uses ContainingBlock()) may actually not be in the
-  // containing block chain for the descendant.
-  const LayoutObject* container = Container();
-  if (!container->IsLayoutReplaced())
-    cb->AddPercentHeightDescendant(const_cast<LayoutBox*>(this));
+  cb->AddPercentHeightDescendant(const_cast<LayoutBox*>(this));
 
   if (available_height == -1)
     return available_height;
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.h b/third_party/blink/renderer/core/loader/base_fetch_context.h
index de03346..c8117e9 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.h
@@ -51,6 +51,8 @@
     return *fetcher_properties_;
   }
 
+  virtual void CountUsage(mojom::WebFeature) const = 0;
+  virtual void CountDeprecation(mojom::WebFeature) const = 0;
   virtual KURL GetSiteForCookies() const = 0;
 
   // Returns the origin of the top frame in the document.
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index e375881..f1460c1 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -48,6 +48,7 @@
 #include "third_party/blink/renderer/core/frame/dactyloscoper.h"
 #include "third_party/blink/renderer/core/frame/frame_types.h"
 #include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/frame/use_counter_helper.h"
 #include "third_party/blink/renderer/core/html/parser/parser_synchronization_policy.h"
 #include "third_party/blink/renderer/core/loader/document_load_timing.h"
 #include "third_party/blink/renderer/core/loader/frame_loader_types.h"
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
index 768b7ab..ea33c4f 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -94,6 +94,7 @@
 #include "third_party/blink/renderer/platform/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
 #include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.h"
+#include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
@@ -240,9 +241,9 @@
                                               properties),
       frame.GetTaskRunner(TaskType::kNetworking),
       MakeGarbageCollected<LoaderFactoryForFrame>(frame_or_imported_document));
-  auto* console_logger =
+  init.use_counter = MakeGarbageCollected<DetachableUseCounter>(&document);
+  init.console_logger =
       MakeGarbageCollected<DetachableConsoleLogger>(&document);
-  init.console_logger = console_logger;
   // Frame loading should normally start with |kTight| throttling, as the
   // frame will be in layout-blocking state until the <body> tag is inserted
   init.initial_throttling_policy =
@@ -276,9 +277,8 @@
                                               properties),
       document->GetTaskRunner(blink::TaskType::kNetworking),
       MakeGarbageCollected<LoaderFactoryForFrame>(frame_or_imported_document));
-  auto* console_logger =
-      MakeGarbageCollected<DetachableConsoleLogger>(document);
-  init.console_logger = console_logger;
+  init.use_counter = MakeGarbageCollected<DetachableUseCounter>(document);
+  init.console_logger = MakeGarbageCollected<DetachableConsoleLogger>(document);
   init.frame_scheduler = frame.GetFrameScheduler();
   auto* fetcher = MakeGarbageCollected<ResourceFetcher>(init);
   fetcher->SetResourceLoadObserver(
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h
index 756bbef..42d028b 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h
@@ -33,6 +33,7 @@
 
 #include "base/optional.h"
 #include "base/single_thread_task_runner.h"
+#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom-blink.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/loader/base_fetch_context.h"
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
index 8412d3f..4f41fac 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
@@ -381,8 +381,9 @@
 #endif
 }
 
-static void ForceRecomputeVisualRectsIncludingNonCompositingDescendants(
-    LayoutObject& layout_object) {
+void PaintLayerCompositor::
+    ForceRecomputeVisualRectsIncludingNonCompositingDescendants(
+        LayoutObject& layout_object) {
   // We clear the previous visual rect as it's wrong (paint invalidation
   // container changed, ...). Forcing a full invalidation will make us recompute
   // it. Also we are not changing the previous position from our paint
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h
index 7264ea9..bbb7d42 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h
@@ -167,6 +167,9 @@
     compositing_inputs_root_.Update(layer);
   }
 
+  void ForceRecomputeVisualRectsIncludingNonCompositingDescendants(
+      LayoutObject&);
+
  private:
 #if DCHECK_IS_ON()
   void AssertNoUnresolvedDirtyBits();
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index c08e7746..ae57e30e 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -214,8 +214,10 @@
 
   // Child layers will be deleted by their corresponding layout objects, so
   // we don't need to delete them ourselves.
-
-  ClearCompositedLayerMapping(true);
+  {
+    DisableCompositingQueryAsserts disabler;
+    ClearCompositedLayerMapping(true);
+  }
 
   if (scrollable_area_)
     scrollable_area_->Dispose();
@@ -2793,7 +2795,7 @@
 }
 
 void PaintLayer::EnsureCompositedLayerMapping() {
-  if (rare_data_ && rare_data_->composited_layer_mapping)
+  if (HasCompositedLayerMapping())
     return;
 
   EnsureRareData().composited_layer_mapping =
@@ -2803,7 +2805,25 @@
 }
 
 void PaintLayer::ClearCompositedLayerMapping(bool layer_being_destroyed) {
-  if (!layer_being_destroyed) {
+  if (!HasCompositedLayerMapping())
+    return;
+
+  if (layer_being_destroyed) {
+    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+      // The visual rects will be in a different coordinate space after losing
+      // their compositing container. Clear them before prepaint to avoid
+      // spurious layout shift reports from JankTracker.
+      // If the PaintLayer were not being destroyed, this would happen during
+      // the compositing update (PaintLayerCompositor::UpdateIfNeeded).
+      // TODO: JankTracker's reliance on having visual rects cleared before
+      // prepaint in the case of compositing changes is not ideal, and will not
+      // work with CompositeAfterPaint. Some transform tree changes may still
+      // produce incorrect behavior from JankTracker (see discussion on review
+      // thread of http://crrev.com/c/1636403).
+      Compositor()->ForceRecomputeVisualRectsIncludingNonCompositingDescendants(
+          layout_object_);
+    }
+  } else {
     // We need to make sure our decendants get a geometry update. In principle,
     // we could call setNeedsGraphicsLayerUpdate on our children, but that would
     // require walking the z-order lists to find them. Instead, we
@@ -2813,9 +2833,8 @@
       compositing_parent->GetCompositedLayerMapping()
           ->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
   }
-
-  if (rare_data_)
-    rare_data_->composited_layer_mapping.reset();
+  DCHECK(rare_data_);
+  rare_data_->composited_layer_mapping.reset();
 }
 
 void PaintLayer::SetGroupedMapping(CompositedLayerMapping* grouped_mapping,
diff --git a/third_party/blink/renderer/core/script/classic_pending_script.cc b/third_party/blink/renderer/core/script/classic_pending_script.cc
index 3bd0f996..cbd7834 100644
--- a/third_party/blink/renderer/core/script/classic_pending_script.cc
+++ b/third_party/blink/renderer/core/script/classic_pending_script.cc
@@ -22,6 +22,7 @@
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/loader/allowed_by_nosniff.h"
 #include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h"
+#include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h"
 #include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
 #include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_client.h"
@@ -342,7 +343,7 @@
   auto* fetcher = GetElement()->GetDocument().ContextDocument()->Fetcher();
   // If the MIME check fails, which is considered as load failure.
   if (!AllowedByNosniff::MimeTypeAsScript(
-          fetcher->Context(), &fetcher->GetConsoleLogger(),
+          fetcher->GetUseCounter(), &fetcher->GetConsoleLogger(),
           resource->GetResponse(), AllowedByNosniff::MimeTypeCheck::kLax)) {
     return nullptr;
   }
diff --git a/third_party/blink/renderer/core/workers/worker_classic_script_loader.cc b/third_party/blink/renderer/core/workers/worker_classic_script_loader.cc
index eeeaa47..c51ae114 100644
--- a/third_party/blink/renderer/core/workers/worker_classic_script_loader.cc
+++ b/third_party/blink/renderer/core/workers/worker_classic_script_loader.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/renderer/core/loader/resource/script_resource.h"
 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
+#include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
@@ -177,7 +178,7 @@
     return;
   }
   if (!AllowedByNosniff::MimeTypeAsScript(
-          fetch_client_settings_object_fetcher_->Context(),
+          fetch_client_settings_object_fetcher_->GetUseCounter(),
           &fetch_client_settings_object_fetcher_->GetConsoleLogger(), response,
           fetch_client_settings_object_fetcher_->GetProperties()
               .GetFetchClientSettingsObject()
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
index 477614b..e27efa1 100644
--- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
+++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h"
 #include "third_party/blink/renderer/core/workers/worker_thread.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
+#include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
 #include "third_party/blink/renderer/platform/loader/fetch/null_resource_fetcher_properties.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
@@ -306,8 +307,8 @@
         GetTaskRunner(TaskType::kNetworking),
         MakeGarbageCollected<LoaderFactoryForWorker>(
             *this, web_worker_fetch_context_));
-    auto* console_logger = MakeGarbageCollected<DetachableConsoleLogger>(this);
-    init.console_logger = console_logger;
+    init.use_counter = MakeGarbageCollected<DetachableUseCounter>(this);
+    init.console_logger = MakeGarbageCollected<DetachableConsoleLogger>(this);
     fetcher = MakeGarbageCollected<ResourceFetcher>(init);
     fetcher->SetResourceLoadObserver(
         MakeGarbageCollected<ResourceLoadObserverForWorker>(
diff --git a/third_party/blink/renderer/devtools/front_end/ui/ui_strings.grdp b/third_party/blink/renderer/devtools/front_end/ui/ui_strings.grdp
index cd35e68..263028b 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/ui_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/ui/ui_strings.grdp
@@ -45,6 +45,9 @@
   <message name="IDS_DEVTOOLS_2c0924faed417c521b57d553c678ec21" desc="">
     Step into
   </message>
+  <message name="IDS_DEVTOOLS_2ce2fc341b0bd9219a3634ff43a90bde" desc="">
+    <ph name="ITEM_LABEL">$1s</ph>, <ph name="ITEM_SHORTCUT">$2s</ph>
+  </message>
   <message name="IDS_DEVTOOLS_2dfef1e9ae6e4da36eb0bbeea94742b9" desc="">
     <ph name="MEGABYTES">$1.1f</ph> MB
   </message>
@@ -219,6 +222,12 @@
   <message name="IDS_DEVTOOLS_b228e7bd736e688236ab3aa37996bf8f" desc="">
     Decrement by <ph name="__">$1f</ph>
   </message>
+  <message name="IDS_DEVTOOLS_b4b10705557aadcdad1f3182cadeaa76" desc="">
+    <ph name="ITEM_LABEL">$1s</ph>, checked
+  </message>
+  <message name="IDS_DEVTOOLS_b508f4cb8252d1d90689d1a61344c8fc" desc="">
+    <ph name="ITEM_LABEL">$1s</ph>, checked, <ph name="ITEM_SHORTCUT">$2s</ph>
+  </message>
   <message name="IDS_DEVTOOLS_b79165bf6163186253ed04568e1aee11" desc="">
     Pause/ Continue
   </message>
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.cc b/third_party/blink/renderer/modules/webaudio/audio_context.cc
index 89a0de1..f323213 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.cc
@@ -529,7 +529,7 @@
 }
 
 bool AudioContext::HandlePreRenderTasks(const AudioIOPosition* output_position,
-                                        const AudioIOCallbackMetric* metric) {
+                                        const AudioCallbackMetric* metric) {
   DCHECK(IsAudioThread());
 
   // At the beginning of every render quantum, try to update the internal
@@ -677,7 +677,7 @@
 double AudioContext::RenderCapacity() {
   DCHECK(IsMainThread());
   GraphAutoLocker locker(this);
-  return callback_metric_.render_duration / callback_metric_.callback_interval;
+  return callback_metric_.render_capacity;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.h b/third_party/blink/renderer/modules/webaudio/audio_context.h
index e8cbb7a..1402683 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.h
@@ -73,7 +73,7 @@
   void set_was_audible_for_testing(bool value) { was_audible_ = value; }
 
   bool HandlePreRenderTasks(const AudioIOPosition* output_position,
-                            const AudioIOCallbackMetric* metric) final;
+                            const AudioCallbackMetric* metric) final;
 
   // Called at the end of each render quantum.
   void HandlePostRenderTasks() final;
@@ -152,7 +152,7 @@
   Member<ScriptPromiseResolver> close_resolver_;
 
   AudioIOPosition output_position_;
-  AudioIOCallbackMetric callback_metric_;
+  AudioCallbackMetric callback_metric_;
 
   // Whether a user gesture is required to start this AudioContext.
   bool user_gesture_required_ = false;
diff --git a/third_party/blink/renderer/modules/webaudio/base_audio_context.h b/third_party/blink/renderer/modules/webaudio/base_audio_context.h
index 8351bd86..d3ff40e 100644
--- a/third_party/blink/renderer/modules/webaudio/base_audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/base_audio_context.h
@@ -43,6 +43,7 @@
 #include "third_party/blink/renderer/modules/webaudio/deferred_task_handler.h"
 #include "third_party/blink/renderer/modules/webaudio/iir_filter_node.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
+#include "third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h"
 #include "third_party/blink/renderer/platform/audio/audio_io_callback.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
@@ -249,7 +250,7 @@
   //   - The return value indicates whether the context needs to be suspended or
   //   not after rendering.
   virtual bool HandlePreRenderTasks(const AudioIOPosition* output_position,
-                                    const AudioIOCallbackMetric* metric) = 0;
+                                    const AudioCallbackMetric* metric) = 0;
 
   // Called at the end of each render quantum.
   virtual void HandlePostRenderTasks() = 0;
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 4e74b80c..3830b5d 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
@@ -389,11 +389,14 @@
 
 bool OfflineAudioContext::HandlePreRenderTasks(
     const AudioIOPosition* output_position,
-    const AudioIOCallbackMetric* metric) {
-  DCHECK(IsAudioThread());
+    const AudioCallbackMetric* metric) {
+  // TODO(hongchan, rtoy): passing |nullptr| as an argument is not a good
+  // pattern. Consider rewriting this method/interface.
   DCHECK_EQ(output_position, nullptr);
   DCHECK_EQ(metric, nullptr);
 
+  DCHECK(IsAudioThread());
+
   // OfflineGraphAutoLocker here locks the audio graph for this scope. Note
   // that this locker does not use tryLock() inside because the timing of
   // suspension MUST NOT be delayed.
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_context.h b/third_party/blink/renderer/modules/webaudio/offline_audio_context.h
index bc9462e..4c1cbd8 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_context.h
@@ -76,7 +76,7 @@
   void FireCompletionEvent();
 
   bool HandlePreRenderTasks(const AudioIOPosition* output_position,
-                            const AudioIOCallbackMetric* metric) final;
+                            const AudioCallbackMetric* metric) final;
   void HandlePostRenderTasks() final;
 
   // Resolve a suspend scheduled at the specified frame. With this specified
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc
index 376f84f..aac30e2 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc
@@ -165,7 +165,7 @@
     AudioBus* destination_bus,
     uint32_t number_of_frames,
     const AudioIOPosition& output_position,
-    const AudioIOCallbackMetric& metric) {
+    const AudioCallbackMetric& metric) {
   TRACE_EVENT0("webaudio", "RealtimeAudioDestinationHandler::Render");
 
   // Denormals can seriously hurt performance of audio processing. This will
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h
index e495ec6d..05a865d1 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h
+++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h
@@ -29,6 +29,7 @@
 #include <memory>
 #include "third_party/blink/public/platform/web_audio_latency_hint.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_destination_node.h"
+#include "third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h"
 #include "third_party/blink/renderer/platform/audio/audio_destination.h"
 #include "third_party/blink/renderer/platform/audio/audio_io_callback.h"
 
@@ -71,7 +72,7 @@
   void Render(AudioBus* destination_bus,
               uint32_t number_of_frames,
               const AudioIOPosition& output_position,
-              const AudioIOCallbackMetric& metric) final;
+              const AudioCallbackMetric& metric) final;
 
   // Returns a hadrware callback buffer size from audio infra.
   uint32_t GetCallbackBufferSize() const;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_vertex_input_descriptor.idl b/third_party/blink/renderer/modules/webgpu/gpu_vertex_input_descriptor.idl
index ab127f7..0f9591f 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_vertex_input_descriptor.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_vertex_input_descriptor.idl
@@ -5,7 +5,7 @@
 // https://github.com/gpuweb/gpuweb/blob/master/design/sketch.webidl
 
 dictionary GPUVertexInputDescriptor {
-    required GPUIndexFormat indexFormat;
+    GPUIndexFormat indexFormat = "uint32";
     // TODO(crbug.com/951629): Make this a sequence of nullables.
     object vertexBuffers; // We validate this is an array of nullable GPUVertexBufferDescriptor
 };
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 070fd58..64ae02a 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -291,6 +291,8 @@
     "audio/audio_array.h",
     "audio/audio_bus.cc",
     "audio/audio_bus.h",
+    "audio/audio_callback_metric_reporter.cc",
+    "audio/audio_callback_metric_reporter.h",
     "audio/audio_channel.cc",
     "audio/audio_channel.h",
     "audio/audio_delay_dsp_kernel.cc",
diff --git a/third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.cc b/third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.cc
new file mode 100644
index 0000000..708ee0a
--- /dev/null
+++ b/third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.cc
@@ -0,0 +1,73 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h"
+
+#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
+
+namespace blink {
+
+void AudioCallbackMetricReporter::Initialize(
+    int callback_buffer_size, float sample_rate) {
+  DCHECK_GT(callback_buffer_size, 0);
+  DCHECK_GT(sample_rate, 0);
+
+  metric_.expected_callback_interval =
+      callback_buffer_size / static_cast<double>(sample_rate);
+
+  // Prime the mean interval with the expected one.
+  metric_.mean_callback_interval = metric_.expected_callback_interval;
+
+  // Calculates |alpha_| based on the specified time constant. Instead of
+  // the sample rate, we use "callbacks per second".
+  alpha_ = audio_utilities::DiscreteTimeConstantForSampleRate(
+      time_constant_,
+      1.0 / metric_.expected_callback_interval);
+}
+
+void AudioCallbackMetricReporter::BeginTrace() {
+  callback_start_time_ = base::TimeTicks::Now();
+
+  // If this is the first callback, the previous timestamps are not valid.
+  if (metric_.number_of_callbacks == 0) {
+    previous_callback_start_time_ =
+        callback_start_time_ -
+        base::TimeDelta::FromSecondsD(metric_.expected_callback_interval);
+
+    // Let's assume that the previous render duration is zero.
+    previous_render_end_time_ = previous_callback_start_time_;
+  }
+
+  UpdateMetric();
+}
+
+void AudioCallbackMetricReporter::EndTrace() {
+  previous_render_end_time_ = base::TimeTicks::Now();
+  previous_callback_start_time_ = callback_start_time_;
+}
+
+void AudioCallbackMetricReporter::UpdateMetric() {
+  metric_.number_of_callbacks++;
+
+  // Calculate the callback interval between callback(n-1) and callback(n) and
+  // the render duration of previous render quantum.
+  callback_interval_ =
+      (callback_start_time_ - previous_callback_start_time_).InSecondsF();
+  render_duration_ =
+      (previous_render_end_time_ - previous_callback_start_time_)
+          .InSecondsF();
+
+  // Calculates the instantaneous render capacity.
+  metric_.render_capacity = render_duration_ / callback_interval_;
+
+  // The algorithm for exponentially-weighted mean and variance:
+  // http://people.ds.cam.ac.uk/fanf2/hermes/doc/antiforgery/stats.pdf (p. 8)
+  double diff = callback_interval_ - metric_.mean_callback_interval;
+  double increment = alpha_ * diff;
+  metric_.mean_callback_interval += increment;
+  metric_.variance_callback_interval =
+      (1 - alpha_) * (metric_.variance_callback_interval + diff * increment);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h b/third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h
new file mode 100644
index 0000000..6c239c3
--- /dev/null
+++ b/third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h
@@ -0,0 +1,85 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_CALLBACK_METRIC_REPORTER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_CALLBACK_METRIC_REPORTER_H_
+
+#include "base/time/time.h"
+
+namespace blink {
+
+// A data storage for callback/render related metric. WebAudio DevTool consumes
+// this data eventually.
+struct AudioCallbackMetric {
+  // The total number of callback so far.
+  int64_t number_of_callbacks = 0;
+
+  // A value represents the current capacity of WebAudio renderer.
+  // = (time spent on actual graph rendering) / (platform callback interval)`
+  double render_capacity = 0.0;
+
+  // Expected time (in sec) between callbacks, derived from the platform
+  // callback buffer size and the context sample rate.
+  double expected_callback_interval = 0.0;
+
+  // A running mean of callback interval. This value should be close to
+  // |expected_callback_interval| on a system with accurate callback timer.
+  double mean_callback_interval = 0.0;
+
+  // A running variance of callback interval. This value should be
+  // close to zero.
+  double variance_callback_interval = 0.0;
+};
+
+class AudioCallbackMetricReporter {
+ public:
+  AudioCallbackMetricReporter() {}
+
+  // Must be called after WebAudioDevice is created, because we need the
+  // platform parameters like callback buffer size and hardware sample rate.
+  void Initialize(int callback_buffer_size, float sample_rate);
+
+  // Gets called when the callback function starts. Calculates the metric for
+  // the previous callback right after obtaining the timestamp of current
+  // callback.
+  void BeginTrace();
+
+  // Gets called just before the callback function ends. Stores data for the
+  // current callback.
+  void EndTrace();
+
+  const AudioCallbackMetric& GetMetric() const { return metric_; }
+
+ private:
+  void UpdateMetric();
+
+  // The start time of current callback function.
+  base::TimeTicks callback_start_time_;
+
+  // The end time of previous callback function. The callback interval can be
+  // measured by |callback_start_time_| - |previous_callback_start_time_|.
+  base::TimeTicks previous_callback_start_time_;
+
+  // The end time of previous render quantum. The previous render duration can
+  // be calculated by
+  // |previous_callback_start_time_| - |previous_render_end_time|.
+  base::TimeTicks previous_render_end_time_;
+
+  double callback_interval_ = 0.0;
+  double render_duration_ = 0.0;
+
+  // Time constant for smoothing the mean and variance metrics. Roughly, five
+  // times this value will be the memory of the metrics.
+  double time_constant_ = 1.0;
+
+  // Filter coefficient for weighted mean and variance. Derived from
+  // |time_constant_|.
+  double alpha_ = 0.0;
+
+  AudioCallbackMetric metric_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_CALLBACK_METRIC_REPORTER_H_
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.cc b/third_party/blink/renderer/platform/audio/audio_destination.cc
index 8fad2c4d..9b5cc1e 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.cc
+++ b/third_party/blink/renderer/platform/audio/audio_destination.cc
@@ -87,6 +87,9 @@
 
   callback_buffer_size_ = web_audio_device_->FramesPerBuffer();
 
+  metric_reporter_.Initialize(
+      callback_buffer_size_, web_audio_device_->SampleRate());
+
   // Primes the FIFO for the given callback buffer size. This is to prevent
   // first FIFO pulls from causing "underflow" errors.
   const unsigned priming_render_quanta =
@@ -213,16 +216,14 @@
                "frames_to_render", frames_to_render, "timestamp (s)",
                delay_timestamp);
 
+  metric_reporter_.BeginTrace();
+
   frames_elapsed_ -= std::min(frames_elapsed_, prior_frames_skipped);
   output_position_.position =
       frames_elapsed_ / static_cast<double>(web_audio_device_->SampleRate()) -
       delay;
   output_position_.timestamp = delay_timestamp;
-
   base::TimeTicks callback_request = base::TimeTicks::Now();
-  metric_.callback_interval =
-      (callback_request - previous_callback_request_).InSecondsF();
-  metric_.render_duration = previous_render_duration_.InSecondsF();
 
   for (size_t pushed_frames = 0; pushed_frames < frames_to_render;
        pushed_frames += audio_utilities::kRenderQuantumFrames) {
@@ -246,18 +247,15 @@
     } else {
       // Process WebAudio graph and push the rendered output to FIFO.
       callback_.Render(render_bus_.get(), audio_utilities::kRenderQuantumFrames,
-                       output_position_, metric_);
+                       output_position_, metric_reporter_.GetMetric());
     }
 
     fifo_->Push(render_bus_.get());
   }
 
-  // Update the IO callback metric with information from the current iteration.
-  // They are will be picked up in the next render request.
-  previous_callback_request_ = callback_request;
-  previous_render_duration_ = base::TimeTicks::Now() - callback_request;
-
   frames_elapsed_ += frames_requested;
+
+  metric_reporter_.EndTrace();
 }
 
 void AudioDestination::Start() {
@@ -366,6 +364,6 @@
 void AudioDestination::ProvideResamplerInput(int resampler_frame_delay,
                                              AudioBus* dest) {
   callback_.Render(dest, audio_utilities::kRenderQuantumFrames,
-                   output_position_, metric_);
+                   output_position_, metric_reporter_.GetMetric());
 }
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.h b/third_party/blink/renderer/platform/audio/audio_destination.h
index a19445e..f3045cc 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.h
+++ b/third_party/blink/renderer/platform/audio/audio_destination.h
@@ -152,12 +152,6 @@
   // engine. (i.e. DestinationNode)
   AudioIOCallback& callback_;
 
-  // When the last callback function from the device is called.
-  base::TimeTicks previous_callback_request_;
-
-  // The time duration spent on rendering previous render quanta per callback.
-  base::TimeDelta previous_render_duration_;
-
   // Accessed by rendering thread.
   size_t frames_elapsed_;
 
@@ -171,7 +165,8 @@
 
   // Required for RequestRender and also in the resampling callback (if used).
   AudioIOPosition output_position_;
-  AudioIOCallbackMetric metric_;
+
+  AudioCallbackMetricReporter metric_reporter_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioDestination);
 };
diff --git a/third_party/blink/renderer/platform/audio/audio_destination_test.cc b/third_party/blink/renderer/platform/audio/audio_destination_test.cc
index ffab6ad..d1a43dc2 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination_test.cc
+++ b/third_party/blink/renderer/platform/audio/audio_destination_test.cc
@@ -9,6 +9,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/web_audio_device.h"
 #include "third_party/blink/public/platform/web_audio_latency_hint.h"
+#include "third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h"
 #include "third_party/blink/renderer/platform/audio/audio_io_callback.h"
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
@@ -53,10 +54,10 @@
 
 class AudioCallback : public blink::AudioIOCallback {
  public:
-  void Render(AudioBus* destination_bus,
+  void Render(AudioBus*,
               uint32_t frames_to_process,
-              const AudioIOPosition& output_position,
-              const AudioIOCallbackMetric& metric) override {
+              const AudioIOPosition&,
+              const AudioCallbackMetric&) override {
     frames_processed_ += frames_to_process;
   }
 
diff --git a/third_party/blink/renderer/platform/audio/audio_io_callback.h b/third_party/blink/renderer/platform/audio/audio_io_callback.h
index cb9e87d..2f5f7238 100644
--- a/third_party/blink/renderer/platform/audio/audio_io_callback.h
+++ b/third_party/blink/renderer/platform/audio/audio_io_callback.h
@@ -29,24 +29,12 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_IO_CALLBACK_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_IO_CALLBACK_H_
 
-#include "base/time/time.h"
+#include "third_party/blink/renderer/platform/audio/audio_callback_metric_reporter.h"
 
 namespace blink {
 
 class AudioBus;
 
-// For the calculation of "render capacity". The render capacity can be
-// calculated by dividing |render_duration| by |callback_interval|.
-struct AudioIOCallbackMetric {
-  // The time interval in seconds between the onset of previous callback
-  // function and the current one.
-  double callback_interval;
-
-  // The time duration spent on rendering render quanta (i.e. batch pulling of
-  // audio graph) per a device callback request.
-  double render_duration;
-};
-
 struct AudioIOPosition {
   // Audio stream position in seconds.
   double position;
@@ -63,7 +51,7 @@
   virtual void Render(AudioBus* destination_bus,
                       uint32_t frames_to_process,
                       const AudioIOPosition& output_position,
-                      const AudioIOCallbackMetric& metric) = 0;
+                      const AudioCallbackMetric& metric) = 0;
 
   virtual ~AudioIOCallback() = default;
 };
diff --git a/third_party/blink/renderer/platform/heap/heap_page.cc b/third_party/blink/renderer/platform/heap/heap_page.cc
index 5734032..2ccddf6e 100644
--- a/third_party/blink/renderer/platform/heap/heap_page.cc
+++ b/third_party/blink/renderer/platform/heap/heap_page.cc
@@ -273,15 +273,13 @@
   return result;
 }
 
-void BaseArena::SweepUnsweptPage() {
-  while (BasePage* page = unswept_pages_.Pop()) {
-    SetCurrentlyProccesedPage(page);
-    if (page->Sweep()) {
-      page->RemoveFromHeap();
-    } else {
-      swept_pages_.Push(page);
-      page->MarkAsSwept();
-    }
+void BaseArena::SweepUnsweptPage(BasePage* page) {
+  SetCurrentlyProccesedPage(page);
+  if (page->Sweep()) {
+    page->RemoveFromHeap();
+  } else {
+    swept_pages_.Push(page);
+    page->MarkAsSwept();
   }
   SetCurrentlyProccesedPage(nullptr);
 }
@@ -307,8 +305,8 @@
     }
   }
   int page_count = 1;
-  while (!SweepingCompleted()) {
-    SweepUnsweptPage();
+  while (BasePage* page = unswept_pages_.Pop()) {
+    SweepUnsweptPage(page);
     if (page_count % kDeadlineCheckInterval == 0) {
       if (deadline <= CurrentTimeTicks()) {
         // Deadline has come.
@@ -332,8 +330,8 @@
   // Some phases, e.g. verification, require iterability of a page.
   MakeIterable();
 
-  while (!SweepingCompleted()) {
-    SweepUnsweptPage();
+  while (BasePage* page = unswept_pages_.Pop()) {
+    SweepUnsweptPage(page);
   }
 }
 
diff --git a/third_party/blink/renderer/platform/heap/heap_page.h b/third_party/blink/renderer/platform/heap/heap_page.h
index 59681b8..d78a7c3 100644
--- a/third_party/blink/renderer/platform/heap/heap_page.h
+++ b/third_party/blink/renderer/platform/heap/heap_page.h
@@ -762,7 +762,7 @@
   void PoisonArena();
 #endif
   Address LazySweep(size_t, size_t gc_info_index);
-  void SweepUnsweptPage();
+  void SweepUnsweptPage(BasePage*);
   // Returns true if we have swept all pages within the deadline. Returns false
   // otherwise.
   bool LazySweepWithDeadline(TimeTicks deadline);
diff --git a/third_party/blink/renderer/platform/loader/BUILD.gn b/third_party/blink/renderer/platform/loader/BUILD.gn
index 29698d2..dedf6288 100644
--- a/third_party/blink/renderer/platform/loader/BUILD.gn
+++ b/third_party/blink/renderer/platform/loader/BUILD.gn
@@ -34,6 +34,7 @@
     "fetch/cross_origin_attribute_value.h",
     "fetch/data_pipe_bytes_consumer.cc",
     "fetch/data_pipe_bytes_consumer.h",
+    "fetch/detachable_use_counter.h",
     "fetch/fetch_client_settings_object.h",
     "fetch/fetch_client_settings_object_snapshot.cc",
     "fetch/fetch_client_settings_object_snapshot.h",
diff --git a/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc b/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc
index 4a554a39..cb493b92 100644
--- a/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc
+++ b/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc
@@ -4,8 +4,8 @@
 
 #include "third_party/blink/renderer/platform/loader/allowed_by_nosniff.h"
 
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/loader/fetch/console_logger.h"
-#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
 #include "third_party/blink/renderer/platform/network/http_names.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
@@ -124,7 +124,7 @@
 
 }  // namespace
 
-bool AllowedByNosniff::MimeTypeAsScript(FetchContext& context,
+bool AllowedByNosniff::MimeTypeAsScript(UseCounter& use_counter,
                                         ConsoleLogger* console_logger,
                                         const ResourceResponse& response,
                                         MimeTypeCheck mime_type_check_mode) {
@@ -171,15 +171,15 @@
   // These record usages for two MIME types (without subtypes), per same/cross
   // origin.
   if (mime_type.StartsWithIgnoringASCIICase("application/")) {
-    context.CountUsage(kApplicationFeatures[same_origin]);
+    use_counter.CountUse(kApplicationFeatures[same_origin]);
   } else if (mime_type.StartsWithIgnoringASCIICase("text/")) {
-    context.CountUsage(kTextFeatures[same_origin]);
+    use_counter.CountUse(kTextFeatures[same_origin]);
   }
 
   // The code above has made a decision and handed down the result in accept
   // and counter.
   if (counter != kWebFeatureNone) {
-    context.CountUsage(counter);
+    use_counter.CountUse(counter);
   }
   if (!allow) {
     console_logger->AddConsoleMessage(
diff --git a/third_party/blink/renderer/platform/loader/allowed_by_nosniff.h b/third_party/blink/renderer/platform/loader/allowed_by_nosniff.h
index bf12ac4..34cffd5 100644
--- a/third_party/blink/renderer/platform/loader/allowed_by_nosniff.h
+++ b/third_party/blink/renderer/platform/loader/allowed_by_nosniff.h
@@ -10,14 +10,14 @@
 namespace blink {
 
 class ConsoleLogger;
-class FetchContext;
+class UseCounter;
 class ResourceResponse;
 
 class PLATFORM_EXPORT AllowedByNosniff final {
  public:
   enum class MimeTypeCheck { kStrict, kLax };
 
-  static bool MimeTypeAsScript(FetchContext&,
+  static bool MimeTypeAsScript(UseCounter&,
                                ConsoleLogger*,
                                const ResourceResponse&,
                                MimeTypeCheck mime_type_check_mode);
diff --git a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
index a26edc1..809c1005f 100644
--- a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
+++ b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
@@ -6,10 +6,10 @@
 
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/loader/fetch/console_logger.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
-#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h"
 #include "third_party/blink/renderer/platform/loader/testing/test_loader_factory.h"
 #include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
 
@@ -21,13 +21,17 @@
 using WebFeature = mojom::WebFeature;
 using ::testing::_;
 
-class CountUsageMockFetchContext : public MockFetchContext {
+class MockUseCounter : public GarbageCollectedFinalized<MockUseCounter>,
+                       public UseCounter {
+  USING_GARBAGE_COLLECTED_MIXIN(MockUseCounter);
+
  public:
-  static CountUsageMockFetchContext* Create() {
-    return MakeGarbageCollected<
-        ::testing::StrictMock<CountUsageMockFetchContext>>();
+  static MockUseCounter* Create() {
+    return MakeGarbageCollected<testing::StrictMock<MockUseCounter>>();
   }
-  MOCK_CONST_METHOD1(CountUsage, void(mojom::WebFeature));
+
+  MOCK_METHOD1(CountUse, void(mojom::WebFeature));
+  MOCK_METHOD1(CountDeprecation, void(mojom::WebFeature));
 };
 
 class MockConsoleLogger : public GarbageCollectedFinalized<MockConsoleLogger>,
@@ -45,9 +49,6 @@
 
 class AllowedByNosniffTest : public testing::Test {
  public:
-  static scoped_refptr<base::SingleThreadTaskRunner> CreateTaskRunner() {
-    return base::MakeRefCounted<scheduler::FakeTaskRunner>();
-  }
 };
 
 TEST_F(AllowedByNosniffTest, AllowedOrNot) {
@@ -102,33 +103,27 @@
                  << (testcase.strict_allowed ? "true" : "false"));
 
     const KURL url("https://bla.com/");
-    auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>(
-        SecurityOrigin::Create(url));
-    auto* context = CountUsageMockFetchContext::Create();
-    // Bind |properties| to |context| through a ResourceFetcher.
-    MakeGarbageCollected<ResourceFetcher>(ResourceFetcherInit(
-        properties->MakeDetachable(), context, CreateTaskRunner(),
-        MakeGarbageCollected<TestLoaderFactory>()));
+    Persistent<MockUseCounter> use_counter = MockUseCounter::Create();
     Persistent<MockConsoleLogger> logger =
         MakeGarbageCollected<MockConsoleLogger>();
     ResourceResponse response(url);
     response.SetHttpHeaderField("Content-Type", testcase.mimetype);
 
-    EXPECT_CALL(*context, CountUsage(_)).Times(::testing::AnyNumber());
+    EXPECT_CALL(*use_counter, CountUse(_)).Times(::testing::AnyNumber());
     if (!testcase.allowed)
       EXPECT_CALL(*logger, AddConsoleMessage(_, _, _));
     EXPECT_EQ(testcase.allowed,
-              AllowedByNosniff::MimeTypeAsScript(*context, logger, response,
+              AllowedByNosniff::MimeTypeAsScript(*use_counter, logger, response,
                                                  MimeTypeCheck::kLax));
-    ::testing::Mock::VerifyAndClear(context);
+    ::testing::Mock::VerifyAndClear(use_counter);
 
-    EXPECT_CALL(*context, CountUsage(_)).Times(::testing::AnyNumber());
+    EXPECT_CALL(*use_counter, CountUse(_)).Times(::testing::AnyNumber());
     if (!testcase.strict_allowed)
       EXPECT_CALL(*logger, AddConsoleMessage(_, _, _));
     EXPECT_EQ(testcase.strict_allowed,
-              AllowedByNosniff::MimeTypeAsScript(*context, logger, response,
+              AllowedByNosniff::MimeTypeAsScript(*use_counter, logger, response,
                                                  MimeTypeCheck::kStrict));
-    ::testing::Mock::VerifyAndClear(context);
+    ::testing::Mock::VerifyAndClear(use_counter);
   }
 }
 
@@ -174,25 +169,19 @@
                  << testcase.origin << "\n  mime type: " << testcase.mimetype
                  << "\n response type: " << testcase.response_type
                  << "\n  webfeature: " << testcase.expected);
-    auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>(
-        SecurityOrigin::Create(KURL(testcase.origin)));
-    auto* context = CountUsageMockFetchContext::Create();
-    // Bind |properties| to |context| through a ResourceFetcher.
-    MakeGarbageCollected<ResourceFetcher>(ResourceFetcherInit(
-        properties->MakeDetachable(), context, CreateTaskRunner(),
-        MakeGarbageCollected<TestLoaderFactory>()));
+    Persistent<MockUseCounter> use_counter = MockUseCounter::Create();
     Persistent<MockConsoleLogger> logger =
         MakeGarbageCollected<MockConsoleLogger>();
     ResourceResponse response(KURL(testcase.url));
     response.SetType(testcase.response_type);
     response.SetHttpHeaderField("Content-Type", testcase.mimetype);
 
-    EXPECT_CALL(*context, CountUsage(testcase.expected));
-    EXPECT_CALL(*context, CountUsage(::testing::Ne(testcase.expected)))
+    EXPECT_CALL(*use_counter, CountUse(testcase.expected));
+    EXPECT_CALL(*use_counter, CountUse(::testing::Ne(testcase.expected)))
         .Times(::testing::AnyNumber());
-    AllowedByNosniff::MimeTypeAsScript(*context, logger, response,
+    AllowedByNosniff::MimeTypeAsScript(*use_counter, logger, response,
                                        MimeTypeCheck::kLax);
-    ::testing::Mock::VerifyAndClear(context);
+    ::testing::Mock::VerifyAndClear(use_counter);
   }
 }
 
@@ -222,12 +211,7 @@
   };
 
   for (auto& testcase : data) {
-    auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
-    auto* context = CountUsageMockFetchContext::Create();
-    // Bind |properties| to |context| through a ResourceFetcher.
-    MakeGarbageCollected<ResourceFetcher>(ResourceFetcherInit(
-        properties->MakeDetachable(), context, CreateTaskRunner(),
-        MakeGarbageCollected<TestLoaderFactory>()));
+    auto* use_counter = MockUseCounter::Create();
     Persistent<MockConsoleLogger> logger =
         MakeGarbageCollected<MockConsoleLogger>();
     EXPECT_CALL(*logger, AddConsoleMessage(_, _, _))
@@ -238,7 +222,7 @@
     response.SetHttpHeaderField("Content-Type", "invalid");
     response.SetHttpHeaderField("X-Content-Type-Options", "nosniff");
     EXPECT_EQ(testcase.allowed,
-              AllowedByNosniff::MimeTypeAsScript(*context, logger, response,
+              AllowedByNosniff::MimeTypeAsScript(*use_counter, logger, response,
                                                  MimeTypeCheck::kLax));
   }
 }
diff --git a/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h b/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h
new file mode 100644
index 0000000..353f792
--- /dev/null
+++ b/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_DETACHABLE_USE_COUNTER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_DETACHABLE_USE_COUNTER_H_
+
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+
+namespace blink {
+
+class DetachableUseCounter final
+    : public GarbageCollected<DetachableUseCounter>,
+      public UseCounter {
+  USING_GARBAGE_COLLECTED_MIXIN(DetachableUseCounter);
+
+ public:
+  // |use_counter| can be null, and in that case |this| is already detached.
+  explicit DetachableUseCounter(UseCounter* use_counter)
+      : use_counter_(use_counter) {}
+  ~DetachableUseCounter() override = default;
+
+  // UseCounter
+  void CountUse(mojom::WebFeature feature) override {
+    if (use_counter_) {
+      use_counter_->CountUse(feature);
+    }
+  }
+  void CountDeprecation(mojom::WebFeature feature) override {
+    if (use_counter_) {
+      use_counter_->CountDeprecation(feature);
+    }
+  }
+  void Trace(Visitor* visitor) override { visitor->Trace(use_counter_); }
+
+  void Detach() { use_counter_ = nullptr; }
+
+ private:
+  Member<UseCounter> use_counter_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_DETACHABLE_USE_COUNTER_H_
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
index 9bbb1a4..7324736 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
@@ -34,22 +34,6 @@
 
 namespace blink {
 
-namespace {
-
-class NullFetchContext final : public FetchContext {
- public:
-  NullFetchContext() = default;
-
-  void CountUsage(mojom::WebFeature) const override {}
-  void CountDeprecation(mojom::WebFeature) const override {}
-};
-
-}  // namespace
-
-FetchContext& FetchContext::NullInstance() {
-  return *MakeGarbageCollected<NullFetchContext>();
-}
-
 void FetchContext::AddAdditionalRequestHeaders(ResourceRequest&) {}
 
 mojom::FetchCacheMode FetchContext::ResourceRequestCachePolicy(
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
index 01892c8..73248b2e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
@@ -38,16 +38,11 @@
 #include "base/single_thread_task_runner.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
 #include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-shared.h"
-#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
-#include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/resource_request_blocked_reason.h"
-#include "third_party/blink/public/platform/web_url_loader.h"
-#include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
-#include "third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
 #include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
@@ -74,7 +69,9 @@
  public:
   FetchContext() = default;
 
-  static FetchContext& NullInstance();
+  static FetchContext& NullInstance() {
+    return *MakeGarbageCollected<FetchContext>();
+  }
 
   virtual ~FetchContext() = default;
 
@@ -128,9 +125,6 @@
     return ResourceRequestBlockedReason::kOther;
   }
 
-  virtual void CountUsage(mojom::WebFeature) const = 0;
-  virtual void CountDeprecation(mojom::WebFeature) const = 0;
-
   // Populates the ResourceRequest using the given values and information
   // stored in the FetchContext implementation. Used by ResourceFetcher to
   // prepare a ResourceRequest instance at the start of resource loading.
@@ -144,8 +138,7 @@
   // with "keepalive" specified).
   // Returns a "detached" fetch context which cannot be null.
   virtual FetchContext* Detach() {
-    auto* context = &NullInstance();
-    return context;
+    return MakeGarbageCollected<FetchContext>();
   }
 
   // Determine if the request is on behalf of an advertisement. If so, return
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index dbb45cc..86261252 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -47,6 +47,7 @@
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
 #include "third_party/blink/renderer/platform/loader/cors/cors.h"
 #include "third_party/blink/renderer/platform/loader/fetch/console_logger.h"
+#include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
@@ -132,8 +133,8 @@
       return ResourceLoadPriority::kHigh;
     case ResourceType::kManifest:
     case ResourceType::kMock:
-      // Also late-body scripts discovered by the preload scanner (set
-      // explicitly in loadPriority)
+      // Also late-body scripts and stylesheets discovered by the
+      // preload scanner (set explicitly in loadPriority)
       return ResourceLoadPriority::kMedium;
     case ResourceType::kImage:
     case ResourceType::kTextTrack:
@@ -438,6 +439,18 @@
   if (type == ResourceType::kImage && !is_link_preload)
     image_fetched_ = true;
 
+  // Check for late-in-document resources discovered by the preload scanner.
+  // kInDocument means it was found in the document by the preload scanner.
+  // image_fetched_ is used as the divider between "early" and "late" where
+  // anything after the first image is considered "late" in the document.
+  // This is used for lowering the priority of late-body scripts/stylesheets.
+  bool late_document_from_preload_scanner = false;
+  if (speculative_preload_type ==
+          FetchParameters::SpeculativePreloadType::kInDocument &&
+      image_fetched_) {
+    late_document_from_preload_scanner = true;
+  }
+
   // A preloaded font should not take precedence over critical CSS or
   // parser-blocking scripts.
   if (type == ResourceType::kFont && is_link_preload)
@@ -453,13 +466,17 @@
     // Preload late in document: Medium
     if (FetchParameters::kLazyLoad == defer_option) {
       priority = ResourceLoadPriority::kLow;
-    } else if (speculative_preload_type ==
-                   FetchParameters::SpeculativePreloadType::kInDocument &&
-               image_fetched_) {
-      // Speculative preload is used as a signal for scripts at the bottom of
-      // the document.
+    } else if (late_document_from_preload_scanner) {
       priority = ResourceLoadPriority::kMedium;
     }
+  } else if (type == ResourceType::kCSSStyleSheet &&
+             late_document_from_preload_scanner) {
+    // Lower the priority of late-body stylesheets discovered by the preload
+    // scanner. They do not block render and this gives them the same behavior
+    // as late-body scripts. If the main parser reaches the stylesheet before
+    // it is loaded, a non-speculative fetch will be made and the priority will
+    // be boosted (just like with scripts).
+    priority = ResourceLoadPriority::kMedium;
   } else if (FetchParameters::kLazyLoad == defer_option) {
     priority = ResourceLoadPriority::kVeryLow;
   } else if (resource_request.GetRequestContext() ==
@@ -512,6 +529,9 @@
     : properties_(*init.properties),
       context_(init.context),
       task_runner_(init.task_runner),
+      use_counter_(init.use_counter
+                       ? init.use_counter.Get()
+                       : MakeGarbageCollected<DetachableUseCounter>(nullptr)),
       console_logger_(init.console_logger
                           ? init.console_logger.Get()
                           : MakeGarbageCollected<DetachableConsoleLogger>()),
@@ -1635,6 +1655,7 @@
   }
 
   resource_load_observer_ = nullptr;
+  use_counter_->Detach();
   console_logger_->Detach();
   loader_factory_ = nullptr;
 
@@ -1831,7 +1852,7 @@
     RemovePreload(resource);
   if (network_utils::IsCertificateTransparencyRequiredError(
           error.ErrorCode())) {
-    Context().CountUsage(
+    use_counter_->CountUse(
         mojom::WebFeature::kCertificateTransparencyRequiredErrorOnResourceLoad);
   }
   resource->FinishAsError(error, task_runner_.get());
@@ -2095,6 +2116,7 @@
   visitor->Trace(context_);
   visitor->Trace(properties_);
   visitor->Trace(resource_load_observer_);
+  visitor->Trace(use_counter_);
   visitor->Trace(console_logger_);
   visitor->Trace(loader_factory_);
   visitor->Trace(scheduler_);
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
index 42abc92..d8cec98 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
@@ -51,6 +51,7 @@
 enum class ResourceType : uint8_t;
 class CodeCacheLoader;
 class DetachableConsoleLogger;
+class DetachableUseCounter;
 class DetachableResourceFetcherProperties;
 class FetchContext;
 class FrameScheduler;
@@ -170,6 +171,7 @@
 
   FetchContext& Context() const;
   void ClearContext();
+  DetachableUseCounter& GetUseCounter() { return *use_counter_; }
   DetachableConsoleLogger& GetConsoleLogger() { return *console_logger_; }
 
   int BlockingRequestCount() const;
@@ -375,6 +377,7 @@
   Member<ResourceLoadObserver> resource_load_observer_;
   Member<FetchContext> context_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  const Member<DetachableUseCounter> use_counter_;
   const Member<DetachableConsoleLogger> console_logger_;
   Member<LoaderFactory> loader_factory_;
   const Member<ResourceLoadScheduler> scheduler_;
@@ -462,6 +465,7 @@
   const Member<FetchContext> context;
   const scoped_refptr<base::SingleThreadTaskRunner> task_runner;
   const Member<ResourceFetcher::LoaderFactory> loader_factory;
+  Member<DetachableUseCounter> use_counter;
   Member<DetachableConsoleLogger> console_logger;
   ResourceLoadScheduler::ThrottlingPolicy initial_throttling_policy =
       ResourceLoadScheduler::ThrottlingPolicy::kNormal;
diff --git a/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h b/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h
index bcef719..7f63406 100644
--- a/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h
@@ -31,9 +31,6 @@
 
   uint64_t GetTransferSize() const { return transfer_size_; }
 
-  void CountUsage(mojom::WebFeature) const override {}
-  void CountDeprecation(mojom::WebFeature) const override {}
-
   bool AllowImage(bool images_enabled, const KURL&) const override {
     return true;
   }
diff --git a/third_party/blink/renderer/platform/scheduler/OWNERS b/third_party/blink/renderer/platform/scheduler/OWNERS
index 8423b49..00ad1396 100644
--- a/third_party/blink/renderer/platform/scheduler/OWNERS
+++ b/third_party/blink/renderer/platform/scheduler/OWNERS
@@ -1,5 +1,6 @@
 altimin@chromium.org
 alexclarke@chromium.org
+carlscab@google.com
 rmcilroy@chromium.org
 skyostil@chromium.org
 
diff --git a/third_party/blink/web_tests/ASANExpectations b/third_party/blink/web_tests/ASANExpectations
index 7973887..9762aef 100644
--- a/third_party/blink/web_tests/ASANExpectations
+++ b/third_party/blink/web_tests/ASANExpectations
@@ -67,16 +67,16 @@
 crbug.com/803276 inspector-protocol/memory/sampling-native-snapshot.js [ Skip ]
 
 # CORS test crash on ASAN
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-cookies-redirect.any.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-cookies-redirect.any.worker.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-preflight-star.any.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-preflight-star.any.worker.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-preflight.any.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-preflight.any.worker.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-redirect-preflight.any.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/cors/cors-redirect-preflight.any.worker.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Crash ]
-crbug.com/838057 [ Linux ] virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video.https.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-cookies-redirect.any.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-cookies-redirect.any.worker.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-preflight-star.any.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-preflight-star.any.worker.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-preflight.any.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-preflight.any.worker.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-redirect-preflight.any.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/cors/cors-redirect-preflight.any.worker.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Crash ]
+crbug.com/838057 [ Linux ] virtual/outofblink-cors/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video.https.html [ Crash ]
 
 # Disabled by sheriff due to test crash
 crbug.com/896068 [ Linux ] webaudio/AudioBuffer/huge-buffer.html [ Crash ]
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-unsafe-webgpu b/third_party/blink/web_tests/FlagExpectations/enable-unsafe-webgpu
index 55a55dd0..791bf45 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-unsafe-webgpu
+++ b/third_party/blink/web_tests/FlagExpectations/enable-unsafe-webgpu
@@ -1,3 +1,4 @@
 # WebGPU tests are only run with --enable-unsafe-webgpu
 Bug(none) webgpu/canvas_context.html [ Pass ]
+Bug(none) webgpu/buffer_mapping.html [ Pass ]
 Bug(none) webgpu/fence.html [ Pass ]
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index 36271445..8618df11 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -158,25 +158,25 @@
 crbug.com/856601 [ Linux ] external/wpt/webstorage/idlharness.window.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/idlharness.https.window.html [ Pass Timeout ]
-crbug.com/856601 [ Linux ] virtual/blink-cors/external/wpt/fetch/cors-rfc1918/idlharness.tentative.https.any.serviceworker.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/cors-rfc1918/idlharness.tentative.https.any.serviceworker.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
-crbug.com/856601 [ Linux ] virtual/blink-cors/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
+crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/service-worker-servicification/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] external/wpt/fetch/api/idl.any.sharedworker.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/fetch/cors-rfc1918/idlharness.tentative.https.any.serviceworker.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/media-capabilities/idlharness.any.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/orientation-event/idlharness.window.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/pointerlock/interfaces.window.html [ Pass Timeout ]
-crbug.com/856601 [ Linux ] virtual/blink-cors/external/wpt/xhr/idlharness.any.sharedworker.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/xhr/idlharness.any.sharedworker.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/bluetooth/idl/idlharness.tentative.https.window.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] external/wpt/css/css-pseudo/idlharness.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] external/wpt/hr-time/idlharness.any.worker.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] external/wpt/media-source/idlharness.any.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] external/wpt/page-visibility/idlharness.window.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/new-remote-playback-pipeline/external/wpt/remote-playback/idlharness.window.html [ Timeout Pass ]
-crbug.com/856601 [ Linux ] virtual/blink-cors/external/wpt/fetch/api/idl.any.html [ Timeout Pass ]
-crbug.com/856601 [ Linux ] virtual/blink-cors/external/wpt/fetch/cors-rfc1918/idlharness.tentative.any.html [ Timeout Pass ]
-crbug.com/856601 [ Linux ] virtual/blink-cors/external/wpt/xhr/idlharness.any.html [ Timeout Pass ]
+crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/api/idl.any.html [ Timeout Pass ]
+crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/cors-rfc1918/idlharness.tentative.any.html [ Timeout Pass ]
+crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/xhr/idlharness.any.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/service-worker-servicification/external/wpt/service-workers/service-worker/interfaces-window.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/idlharness.https.window.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] external/wpt/css/filter-effects/interfaces.any.html [ Timeout Pass ]
@@ -213,7 +213,7 @@
 crbug.com/914900 [ Linux ] external/wpt/fetch/api/idl.any.worker.html [ Pass Timeout ]
 crbug.com/914900 [ Linux ] external/wpt/secure-contexts/idlharness.any.worker.html [ Pass Timeout ]
 crbug.com/914900 [ Linux ] http/tests/devtools/network/preview-searchable.js [ Pass Timeout ]
-crbug.com/914900 [ Linux ] virtual/blink-cors/external/wpt/xhr/idlharness.any.worker.html [ Pass Timeout ]
+crbug.com/914900 [ Linux ] virtual/outofblink-cors/external/wpt/xhr/idlharness.any.worker.html [ Pass Timeout ]
 
 # Sheriff 2019-01-28
 crbug.com/925600 [ Linux ] external/wpt/webrtc-quic/RTCQuicStream.https.html [ Pass Timeout ]
@@ -244,4 +244,4 @@
 # Sheriff 2019-05-21
 crbug.com/856601 [ Linux ] external/wpt/notifications/idlharness.https.window.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/fetch/cors-rfc1918/idlharness.tentative.any.serviceworker.html [ Pass Timeout ]
-crbug.com/856601 [ Linux ] external/wpt/notifications/idlharness.https.any.sharedworker.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] external/wpt/notifications/idlharness.https.any.sharedworker.html [ Pass Timeout ]
\ No newline at end of file
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 10c83ef..a7cee3ca 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -2162,11 +2162,6 @@
 external/wpt/webstorage/storage_session-manual.html [ WontFix ]
 external/wpt/xhr/send-authentication-existing-session-manual.htm [ WontFix ]
 external/wpt/xhr/send-authentication-prompt-2-manual.htm [ WontFix ]
-virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html [ WontFix ]
-virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-event-is-history-forward-navigation-manual.https.html [ WontFix ]
-virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html [ WontFix ]
-virtual/blink-cors/external/wpt/xhr/send-authentication-existing-session-manual.htm [ WontFix ]
-virtual/blink-cors/external/wpt/xhr/send-authentication-prompt-2-manual.htm [ WontFix ]
 virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaStream-MediaElement-preload-none-manual.https.html [ WontFix ]
 virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaStreamTrack-end-manual.https.html [ WontFix ]
 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html [ WontFix ]
@@ -2236,7 +2231,6 @@
 # https://foo.example.com/path?token=secret needs to be replaced with just
 # https://foo.example.com).
 crbug.com/669083 http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture.html [ WontFix ]
-crbug.com/669083 virtual/blink-cors/http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture.html [ WontFix ]
 # --------------------------- code cache isolation -----------------------
 # Code cache isolation tests only make sense if either
 # 1) site isolation is disabled or
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index b6f6746..47f8e8d 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -151,7 +151,6 @@
 
 crbug.com/522646 http/tests/media/encrypted-media/encrypted-media-encrypted-event-different-origin.html [ Slow ]
 crbug.com/411164 [ Win ] http/tests/security/powerfulFeatureRestrictions/serviceworker-on-insecure-origin.html [ Slow ]
-crbug.com/411164 [ Win ] virtual/blink-cors/http/tests/security/powerfulFeatureRestrictions/serviceworker-on-insecure-origin.html [ Slow ]
 crbug.com/510337 http/tests/devtools/console/console-format.js [ Slow ]
 crbug.com/357427 http/tests/workers/terminate-during-sync-operation-file.html [ Slow ]
 crbug.com/357427 http/tests/workers/terminate-during-sync-operation-filesystem.html [ Slow ]
@@ -173,7 +172,6 @@
 crbug.com/372424 http/tests/serviceworker/registration-stress.html [ Slow ]
 crbug.com/448670 http/tests/serviceworker/register-different-script-many-times.html [ Slow ]
 crbug.com/516319 [ Win ] http/tests/fetch/ [ Slow ]
-crbug.com/516319 [ Win ] virtual/blink-cors/http/tests/fetch/ [ Slow ]
 crbug.com/516319 [ Win ] virtual/streaming-preload/http/tests/fetch/ [ Slow ]
 
 # Most crypto/subtle tests are slow some or most of the time.
@@ -258,8 +256,6 @@
 # These tests take 90 seconds on MSAN due to a large amount of JS execution.
 crbug.com/853977 [ Linux ] http/tests/fetch/chromium/call-extra-crash-tee.html [ Slow ]
 crbug.com/853977 [ Linux ] http/tests/fetch/chromium/release-handle-crash.html [ Slow ]
-crbug.com/853977 [ Linux ] virtual/blink-cors/http/tests/fetch/chromium/call-extra-crash-tee.html [ Slow ]
-crbug.com/853977 [ Linux ] virtual/blink-cors/http/tests/fetch/chromium/release-handle-crash.html [ Slow ]
 crbug.com/853977 [ Linux ] virtual/streaming-preload/http/tests/fetch/chromium/call-extra-crash-tee.html [ Slow ]
 crbug.com/853977 [ Linux ] virtual/streaming-preload/http/tests/fetch/chromium/release-handle-crash.html [ Slow ]
 crbug.com/853977 [ Linux ] virtual/streams-native/http/tests/fetch/chromium/call-extra-crash-tee.html [ Slow ]
@@ -634,34 +630,3 @@
 crbug.com/962831 [ Release ] http/tests/devtools/network/preview-searchable.js [ Slow ]
 
 crbug.com/967526 http/tests/devtools/console/console-uncaught-promise.js [ Slow ]
-
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/serviceworker/body-mixin-base-https-other-https.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/serviceworker/body-mixin.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/serviceworker/stream-reader-base-https-other-https.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/serviceworker/stream-reader.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/serviceworker/thorough/ [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/serviceworker-proxied/thorough/ [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/window/body-mixin-base-https-other-https.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/window/body-mixin.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/window/stream-reader-base-https-other-https.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/window/stream-reader.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/window/thorough/ [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/workers/body-mixin-base-https-other-https.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/workers/body-mixin.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/workers/stream-reader-base-https-other-https.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/workers/stream-reader.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/fetch/workers/thorough/ [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/navigation/navigation-interrupted-by-fragment.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/navigation/new-window-redirect-history.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/navigation/slowmetaredirect-basic.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/navigation/slowtimerredirect-basic.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/security/contentSecurityPolicy/redirect-with-delay.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/security/cross-frame-mouse-source-capabilities.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/security/frameNavigation/xss-ALLOWED-same-origin-top-navigation-without-user-gesture.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/security/video-poster-cross-origin-crash2.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-aborted.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-overridesexpires.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-simple.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-twice.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Slow ]
-crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-twice.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 3dd4c25..799a583 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -13,6 +13,7 @@
 
 # WebGPU tests are only run with --enable-unsafe-webgpu
 Bug(none) webgpu/canvas_context.html [ Skip ]
+Bug(none) webgpu/buffer_mapping.html [ Skip ]
 Bug(none) webgpu/fence.html [ Skip ]
 
 crbug.com/807686 crbug.com/24182 jquery/manipulation.html [ Timeout Pass ]
@@ -63,7 +64,6 @@
 crbug.com/645641 external/wpt/html/syntax/parsing/html5lib_tests19.html [ Crash Failure ]
 crbug.com/665058 http/tests/local/drag-over-remote-content.html [ Crash ]
 crbug.com/771003 http/tests/security/mixedContent/insecure-iframe-in-main-frame.html [ Failure ]
-crbug.com/771003 virtual/blink-cors/http/tests/security/mixedContent/insecure-iframe-in-main-frame.html [ Failure ]
 
 # Tests temporarily disabled with Site Isolation - known differences in product
 # behavior (either accepted for the long-term, or for the short-term):
@@ -1745,10 +1745,8 @@
 crbug.com/410974 virtual/threaded/fast/scroll-behavior/scroll-customization/touch-scroll-customization.html [ Pass Failure ]
 
 crbug.com/390452 http/tests/security/isolatedWorld/media-query-wrapper-leaks.html [ Failure Pass Timeout ]
-crbug.com/390452 virtual/blink-cors/http/tests/security/isolatedWorld/media-query-wrapper-leaks.html [ Failure Pass Timeout ]
 crbug.com/390452 virtual/isolated_world_csp/http/tests/security/isolatedWorld/media-query-wrapper-leaks.html [ Failure Pass Timeout ]
 crbug.com/518987 http/tests/xmlhttprequest/navigation-abort-detaches-frame.html [ Pass Timeout ]
-crbug.com/518987 virtual/blink-cors/http/tests/xmlhttprequest/navigation-abort-detaches-frame.html [ Pass Timeout ]
 
 # These performance-sensitive user-timing tests are flaky in debug on all platforms, and flaky on all configurations of windows.
 # See: crbug.com/567965, crbug.com/518992, and crbug.com/518993
@@ -2173,10 +2171,6 @@
 crbug.com/528062 [ Win ] http/tests/security/contentSecurityPolicy/cached-frame-csp.html [ Failure ]
 crbug.com/528062 [ Win ] http/tests/security/xssAuditor/cached-frame.html [ Failure ]
 crbug.com/528062 [ Win ] http/tests/security/xssAuditor/chunked-big-script.html [ Failure ]
-crbug.com/528062 [ Win ] virtual/blink-cors/http/tests/security/XFrameOptions/x-frame-options-cached.html [ Failure ]
-crbug.com/528062 [ Win ] virtual/blink-cors/http/tests/security/contentSecurityPolicy/cached-frame-csp.html [ Failure ]
-crbug.com/528062 [ Win ] virtual/blink-cors/http/tests/security/xssAuditor/cached-frame.html [ Failure ]
-crbug.com/528062 [ Win ] virtual/blink-cors/http/tests/security/xssAuditor/chunked-big-script.html [ Failure ]
 
 # When drawing subpixel smoothed glyphs, CoreGraphics will fake bold the glyphs.
 # In this configuration, the pixel smoothed glyphs will be created from subpixel smoothed glyphs.
@@ -2443,7 +2437,6 @@
 crbug.com/501659 fast/xsl/xslt-missing-namespace-in-xslt.xml [ Failure ]
 
 crbug.com/501659 http/tests/security/xss-DENIED-xml-external-entity.xhtml [ Failure ]
-crbug.com/501659 virtual/blink-cors/http/tests/security/xss-DENIED-xml-external-entity.xhtml [ Failure ]
 crbug.com/501659 fast/css/stylesheet-candidate-nodes-crash.xhtml [ Failure ]
 
 crbug.com/591500 [ Win10 ] printing/webgl-repeated-printing.html [ Failure ]
@@ -2552,7 +2545,6 @@
 
 # Skip the non-virtualized CORS-RFC1918 tests:
 crbug.com/763830 http/tests/security/cors-rfc1918/ [ Skip ]
-crbug.com/763830 virtual/blink-cors/http/tests/security/cors-rfc1918/ [ Skip ]
 
 crbug.com/831729 external/wpt/event-timing/crossiframe.html [ Timeout ]
 crbug.com/831729 external/wpt/event-timing/observer-manual.html [ Skip ]
@@ -2594,7 +2586,6 @@
 
 # Remove from virtual tests when FreezeUserAgent is turned on by default.
 crbug.com/955620 http/tests/navigation/frozen-useragent.html [ Skip ]
-crbug.com/955620 virtual/blink-cors/http/tests/navigation/frozen-useragent.html [ Skip ]
 crbug.com/955620 virtual/stable/http/tests/navigation/frozen-useragent.html [ Skip ]
 
 crbug.com/863896 http/tests/permissions/test-query.html [ Timeout ]
@@ -2668,7 +2659,6 @@
 
 # Failure messages are unstable so we cannot create baselines.
 crbug.com/832071 external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
-crbug.com/832071 virtual/blink-cors/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 crbug.com/832071 virtual/navigation-mojo-response/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 crbug.com/832071 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Timeout ]
 
@@ -2804,6 +2794,9 @@
 crbug.com/965409 external/wpt/css/css-font-loading/fontface-descriptor-updates.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Linux ] external/wpt/infrastructure/testdriver/actions/actionsWithKeyPressed.html [ Timeout ]
+crbug.com/626703 [ Mac ] external/wpt/infrastructure/testdriver/actions/actionsWithKeyPressed.html [ Timeout ]
+crbug.com/626703 [ Win ] external/wpt/infrastructure/testdriver/actions/actionsWithKeyPressed.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/css/css-backgrounds/border-radius-dynamic-from-no-radius.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-backgrounds/border-radius-dynamic-from-no-radius.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-backgrounds/border-radius-dynamic-from-no-radius.html [ Failure ]
@@ -2861,9 +2854,8 @@
 crbug.com/626703 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_ref_only.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/html/rendering/widgets/button-layout/propagate-text-decoration.html [ Failure ]
 crbug.com/626703 external/wpt/infrastructure/reftest/reftest_fuzzy_ini_short.html [ Failure ]
-crbug.com/626703 external/wpt/xhr/abort-after-stop.any.worker.html [ Timeout ]
-crbug.com/626703 virtual/blink-cors/external/wpt/xhr/abort-after-stop.any.worker.html [ Timeout ]
 crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/abort-after-stop.any.worker.html [ Timeout ]
+crbug.com/626703 external/wpt/xhr/abort-after-stop.any.worker.html [ Timeout ]
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/bidi/vertical_rl.html [ Failure ]
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue/vertical_ruby-position.html [ Failure ]
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_vertical_text-combine-upright.html [ Failure ]
@@ -2933,8 +2925,6 @@
 crbug.com/626703 external/wpt/css/css-scroll-snap/scroll-target-margin-003.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html [ Failure ]
 crbug.com/626703 external/wpt/xhr/event-readystatechange-loaded.any.worker.html [ Timeout Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/xhr/event-readystatechange-loaded.any.worker.html [ Timeout Failure ]
-crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/event-readystatechange-loaded.any.worker.html [ Timeout Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-text/line-breaking/line-breaking-017.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-scroll-snap/scroll-target-padding-003.html [ Failure ]
@@ -2943,6 +2933,7 @@
 crbug.com/626703 external/wpt/css/css-lists/li-list-item-counter.html [ Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-snap-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html [ Failure ]
+crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/event-readystatechange-loaded.any.html [ Timeout Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-margin-003.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-scroll-snap/scroll-target-align-003.html [ Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-align-002.html [ Failure ]
@@ -2956,8 +2947,6 @@
 crbug.com/626703 external/wpt/css/css-text/text-transform/text-transform-multiple-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-lists/li-value-counter-reset-001.html [ Failure ]
 crbug.com/626703 external/wpt/xhr/event-readystatechange-loaded.any.html [ Timeout Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/xhr/event-readystatechange-loaded.any.html [ Timeout Failure ]
-crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/event-readystatechange-loaded.any.html [ Timeout Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-margin-002.html [ Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-padding-002.html [ Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-snap-003.html [ Failure ]
@@ -2967,6 +2956,7 @@
 crbug.com/626703 external/wpt/css/css-scroll-snap/scroll-target-padding-001.html [ Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-scroll-snap/scroll-target-padding-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-scroll-snap/scroll-target-snap-003.html [ Failure ]
+crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/event-readystatechange-loaded.any.worker.html [ Timeout Failure ]
 crbug.com/626703 external/wpt/css/css-scroll-snap/scroll-target-snap-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-scroll-snap/scroll-target-margin-002.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html [ Failure ]
@@ -3108,12 +3098,11 @@
 crbug.com/626703 external/wpt/svg/painting/marker-006.svg [ Failure ]
 crbug.com/626703 external/wpt/svg/painting/marker-005.svg [ Failure ]
 crbug.com/906369 external/wpt/css/css-text/text-transform/text-transform-capitalize-033.html [ Failure ]
+crbug.com/626703 virtual/streams-native/external/wpt/fetch/content-type/response.window.html [ Timeout ]
 crbug.com/626703 [ Mac10.10 ] external/wpt/mimesniff/mime-types/parsing.any.worker.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.11 ] external/wpt/mimesniff/mime-types/parsing.any.worker.html [ Failure Timeout ]
 crbug.com/626703 [ Win ] external/wpt/html/semantics/embedded-content/media-elements/ready-states/autoplay-hidden.optional.html [ Failure Timeout ]
 crbug.com/626703 external/wpt/fetch/content-type/response.window.html [ Timeout ]
-crbug.com/626703 virtual/blink-cors/external/wpt/fetch/content-type/response.window.html [ Timeout ]
-crbug.com/626703 virtual/streams-native/external/wpt/fetch/content-type/response.window.html [ Timeout ]
 crbug.com/626703 virtual/streaming-preload/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-manual-module.html [ Timeout ]
 crbug.com/626703 virtual/streaming-preload/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-manual-classic.html [ Timeout ]
 crbug.com/626703 external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-manual-classic.html [ Timeout ]
@@ -3190,11 +3179,6 @@
 crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/internal-stylesheet.html [ Timeout Failure ]
 crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/presentation-attribute.html [ Timeout Failure ]
 crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/processing-instruction.html [ Timeout Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/referrer-policy/css-integration/svg/external-stylesheet.html [ Timeout Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/referrer-policy/css-integration/svg/inline-style.html [ Timeout Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/referrer-policy/css-integration/svg/internal-stylesheet.html [ Timeout Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/referrer-policy/css-integration/svg/presentation-attribute.html [ Timeout Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/referrer-policy/css-integration/svg/processing-instruction.html [ Timeout Failure ]
 crbug.com/626703 virtual/omt-worker-fetch/external/wpt/referrer-policy/css-integration/svg/external-stylesheet.html [ Timeout Failure ]
 crbug.com/626703 virtual/omt-worker-fetch/external/wpt/referrer-policy/css-integration/svg/inline-style.html [ Timeout Failure ]
 crbug.com/626703 virtual/omt-worker-fetch/external/wpt/referrer-policy/css-integration/svg/internal-stylesheet.html [ Timeout Failure ]
@@ -3237,42 +3221,6 @@
 crbug.com/906959 external/wpt/referrer-policy/unset-referrer-policy/http-rp/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
 crbug.com/906959 external/wpt/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
 crbug.com/906959 external/wpt/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer/http-rp/same-origin/http-http/shared-worker/no-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer/meta-referrer/same-origin/http-http/shared-worker/no-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer-when-downgrade/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer-when-downgrade/http-rp/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin/http-rp/same-origin/http-http/shared-worker/no-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin/meta-referrer/same-origin/http-http/shared-worker/no-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/http-rp/same-origin/http-http/shared-worker/no-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/meta-referrer/same-origin/http-http/shared-worker/no-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/same-origin/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/same-origin/http-rp/same-origin/http-http/shared-worker/no-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/same-origin/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/same-origin/meta-referrer/same-origin/http-http/shared-worker/no-redirect/same-origin-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin/http-rp/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin/meta-referrer/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/same-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-http/shared-worker/no-redirect/same-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/same-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-http/shared-worker/no-redirect/same-insecure.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unsafe-url/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unsafe-url/http-rp/same-origin/http-http/shared-worker/no-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unsafe-url/meta-referrer/same-origin/http-http/shared-worker/no-redirect/generic.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unset-referrer-policy/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unset-referrer-policy/http-rp/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
-crbug.com/906959 virtual/blink-cors/external/wpt/referrer-policy/unset-referrer-policy/meta-referrer/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
 crbug.com/906959 virtual/omt-worker-fetch/external/wpt/referrer-policy/no-referrer-when-downgrade/http-rp/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
 crbug.com/906959 virtual/omt-worker-fetch/external/wpt/referrer-policy/no-referrer-when-downgrade/http-rp/same-origin/http-http/shared-worker/no-redirect/insecure-protocol.http.html [ Failure ]
 crbug.com/906959 virtual/omt-worker-fetch/external/wpt/referrer-policy/no-referrer-when-downgrade/meta-referrer/same-origin/http-http/shared-worker/keep-origin-redirect/insecure-protocol.http.html [ Failure ]
@@ -3434,7 +3382,6 @@
 crbug.com/626703 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/navigation.sub.html?encoding=utf8 [ Timeout ]
 crbug.com/626703 external/wpt/css/css-transforms/transform-box/view-box-mutation.html [ Failure ]
 crbug.com/626703 external/wpt/fetch/security/redirect-to-url-with-credentials.https.html [ Timeout ]
-crbug.com/626703 virtual/blink-cors/external/wpt/fetch/security/redirect-to-url-with-credentials.https.html [ Timeout ]
 crbug.com/626703 virtual/streams-native/external/wpt/fetch/security/redirect-to-url-with-credentials.https.html [ Timeout ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-024.html [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-ellipse-048.html [ Failure ]
@@ -3522,7 +3469,6 @@
 crbug.com/626703 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-010.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-009.html [ Failure ]
 crbug.com/626703 external/wpt/fetch/http-cache/basic-auth-cache-test.html [ Timeout ]
-crbug.com/626703 virtual/blink-cors/external/wpt/fetch/http-cache/basic-auth-cache-test.html [ Timeout ]
 crbug.com/626703 virtual/streams-native/external/wpt/fetch/http-cache/basic-auth-cache-test.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-fonts/font-feature-settings-descriptor-01.html [ Failure ]
 crbug.com/626703 [ Win10 ] external/wpt/fetch/api/redirect/redirect-count.any.worker.html [ Timeout ]
@@ -3776,8 +3722,7 @@
 crbug.com/626703 external/wpt/requestidlecallback/callback-xhr-sync.html [ Timeout ]
 crbug.com/626703 external/wpt/screen-orientation/onchange-event-subframe.html [ Timeout ]
 crbug.com/648295 external/wpt/service-workers/service-worker/update-bytecheck.https.html [ Timeout ]
-crbug.com/648295 virtual/blink-cors/external/wpt/service-workers/service-worker/update-bytecheck.https.html [ Timeout ]
-crbug.com/648295 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/update-bytecheck.https.html [ Timeout ]
+crbug.com/626703 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/update-bytecheck.https.html [ Timeout ]
 crbug.com/626703 external/wpt/svg/linking/reftests/href-filter-element.html [ Failure ]
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/2_cues_overlapping_completely_move_up.html [ Failure ]
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/2_cues_overlapping_partially_move_down.html [ Failure ]
@@ -3991,10 +3936,8 @@
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/too_many_cues_wrapped.html [ Failure ]
 crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/event-readystatechange-loaded.htm [ Failure Timeout ]
 crbug.com/626703 external/wpt/xhr/preserve-ua-header-on-redirect.htm [ Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/xhr/preserve-ua-header-on-redirect.htm [ Failure ]
 crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/preserve-ua-header-on-redirect.htm [ Failure ]
 crbug.com/626703 external/wpt/xhr/setrequestheader-header-allowed.htm [ Failure ]
-crbug.com/626703 virtual/blink-cors/external/wpt/xhr/setrequestheader-header-allowed.htm [ Failure ]
 crbug.com/626703 virtual/omt-worker-fetch/external/wpt/xhr/setrequestheader-header-allowed.htm [ Failure ]
 crbug.com/626703 [ Win10 ] external/wpt/preload/delaying-onload-link-preload-after-discovery.html [ Timeout ]
 crbug.com/626703 [ Win ] external/wpt/css/css-writing-modes/box-offsets-rel-pos-vlr-005.xht [ Failure ]
@@ -4070,8 +4013,6 @@
 # Can't add baselines because a generated unique ID is part of the failure message.
 crbug.com/888470 external/wpt/referrer-policy/css-integration/child-css/internal-import-stylesheet.html [ Failure ]
 crbug.com/888470 external/wpt/referrer-policy/css-integration/child-css/processing-instruction.html [ Failure ]
-crbug.com/888470 virtual/blink-cors/external/wpt/referrer-policy/css-integration/child-css/internal-import-stylesheet.html [ Failure ]
-crbug.com/888470 virtual/blink-cors/external/wpt/referrer-policy/css-integration/child-css/processing-instruction.html [ Failure ]
 crbug.com/888470 virtual/omt-worker-fetch/external/wpt/referrer-policy/css-integration/child-css/internal-import-stylesheet.html [ Failure ]
 crbug.com/888470 virtual/omt-worker-fetch/external/wpt/referrer-policy/css-integration/child-css/processing-instruction.html [ Failure ]
 
@@ -4105,7 +4046,6 @@
 
 # Unclear if XHR events should still be fired after its frame is discarded.
 crbug.com/881180 external/wpt/xhr/open-url-multi-window-4.htm [ Timeout ]
-crbug.com/881180 virtual/blink-cors/external/wpt/xhr/open-url-multi-window-4.htm [ Timeout ]
 crbug.com/881180 virtual/omt-worker-fetch/external/wpt/xhr/open-url-multi-window-4.htm [ Timeout ]
 
 crbug.com/655458 external/wpt/workers/Worker_terminate_event_queue.htm [ Pass Timeout ]
@@ -4201,12 +4141,11 @@
 
 # This test requires a special browser flag and seems not suitable for a wpt test, see bug.
 crbug.com/691944 external/wpt/service-workers/service-worker/update-after-oneday.https.html [ Skip ]
-crbug.com/691944 virtual/blink-cors/external/wpt/service-workers/service-worker/update-after-oneday.https.html [ Skip ]
 crbug.com/691944 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/update-after-oneday.https.html [ Skip ]
 
 # These tests (erroneously) see a platform-specific User-Agent header
 crbug.com/595993 external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
-crbug.com/595993 virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
+crbug.com/595993 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
 crbug.com/595993 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
 
 crbug.com/619427 [ Mac ] fast/overflow/overflow-height-float-not-removed-crash3.html [ Pass Failure ]
@@ -4233,7 +4172,6 @@
 
 # Added 2016-12-12
 crbug.com/610835 http/tests/security/XFrameOptions/x-frame-options-deny-multiple-clients.html [ Failure Pass ]
-crbug.com/610835 virtual/blink-cors/http/tests/security/XFrameOptions/x-frame-options-deny-multiple-clients.html [ Failure Pass ]
 
 #crbug.com/765738 [ Linux Win Mac ] http/tests/wasm/wasm_remote_postMessage_test.https.html [ Pass Timeout ]
 crbug.com/892212 http/tests/wasm/wasm_remote_postMessage_test.https.html [ Pass Failure Timeout ]
@@ -4392,8 +4330,6 @@
 # Sheriff failures 2017-05-11
 crbug.com/724027 http/tests/security/contentSecurityPolicy/directive-parsing-03.html [ Skip ]
 crbug.com/724027 http/tests/security/contentSecurityPolicy/source-list-parsing-04.html [ Skip ]
-crbug.com/724027 virtual/blink-cors/http/tests/security/contentSecurityPolicy/directive-parsing-03.html [ Skip ]
-crbug.com/724027 virtual/blink-cors/http/tests/security/contentSecurityPolicy/source-list-parsing-04.html [ Skip ]
 
 # Sheriff failures 2017-05-16
 crbug.com/722212 fast/events/pointerevents/mouse-pointer-event-properties.html [ Failure Timeout Pass ]
@@ -4475,7 +4411,6 @@
 
 # Sheriff failures 2017-07-03
 crbug.com/708994 http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
-crbug.com/708994 virtual/blink-cors/http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
 
 crbug.com/745887 [ Mac ] fast/frames/sandboxed-iframe-plugins.html [ Failure Pass ]
 crbug.com/745887 [ Win ] fast/frames/sandboxed-iframe-plugins.html [ Failure Pass ]
@@ -4637,7 +4572,6 @@
 
 # Sheriff failures 2017-09-21
 crbug.com/767469 http/tests/navigation/start-load-during-provisional-loader-detach.html [ Pass Failure ]
-crbug.com/767469 virtual/blink-cors/http/tests/navigation/start-load-during-provisional-loader-detach.html [ Pass Failure ]
 crbug.com/767469 virtual/stable/http/tests/navigation/start-load-during-provisional-loader-detach.html [ Pass Failure ]
 
 # Sheriff failures 2017-10-02
@@ -5236,7 +5170,6 @@
 crbug.com/881207 fast/js/regress/splice-to-remove.html [ Timeout Pass ]
 
 crbug.com/882689 http/tests/security/cookies/third-party-cookie-blocking-worker.html [ Pass Failure ]
-crbug.com/882689 virtual/blink-cors/http/tests/security/cookies/third-party-cookie-blocking-worker.html [ Pass Failure ]
 
 # The following tests need LazyFrameLoading.
 crbug.com/869492 external/wpt/feature-policy/experimental-features/lazyload/lazyload-enabled-tentative.sub.html [ Failure ]
@@ -5550,8 +5483,6 @@
 
 # These started failing when network service was enabled by default.
 crbug.com/933880 external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
-crbug.com/933880 virtual/blink-cors/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
-crbug.com/933880 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
 crbug.com/933880 http/tests/inspector-protocol/network/interception-take-stream.js [ Failure ]
 crbug.com/933880 http/tests/inspector-protocol/network/xhr-interception-auth-fail.js [ Failure ]
 # This passes in content_shell but not in chrome with network service disabled,
@@ -5838,6 +5769,3 @@
 crbug.com/966345 external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks.html [ Failure ]
 crbug.com/966345 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_white-space_normal_wrapped.html [ Failure ]
 crbug.com/966345 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_white-space_pre-line_wrapped.html [ Failure ]
-
-crbug.com/905971 virtual/blink-cors/http/tests/security/img-redirect-to-crossorigin-credentials.html [ Failure ]
-crbug.com/905971 virtual/blink-cors/http/tests/security/script-crossorigin-redirect-credentials.html [ Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index a1e6ca1..3498184 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -988,50 +988,5 @@
     "prefix": "cookies-without-samesite-must-be-secure",
     "base": "external/wpt/cookies/samesite-none-secure",
     "args": ["--enable-features=SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "external/wpt/fetch",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "external/wpt/http",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "external/wpt/referrer-policy",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "external/wpt/service-workers",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "external/wpt/xhr",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "http/tests/fetch",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "http/tests/navigation",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "http/tests/security",
-    "args": ["--disable-features=OutOfBlinkCors"]
-  },
-  {
-    "prefix": "blink-cors",
-    "base": "http/tests/xmlhttprequest",
-    "args": ["--disable-features=OutOfBlinkCors"]
   }
 ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
index a1c6d3f..d83c35b6 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
@@ -6535,12 +6535,6 @@
      {}
     ]
    ],
-   "pointerevents/pointerevent_touch-action-table-test_touch-manual.html": [
-    [
-     "pointerevents/pointerevent_touch-action-table-test_touch-manual.html",
-     {}
-    ]
-   ],
    "pointerevents/pointerlock/pointerevent_movementxy-manual.html": [
     [
      "pointerevents/pointerlock/pointerevent_movementxy-manual.html",
@@ -70877,6 +70871,18 @@
      {}
     ]
    ],
+   "css/css-text/white-space/trailing-space-align-start.tentative.html": [
+    [
+     "css/css-text/white-space/trailing-space-align-start.tentative.html",
+     [
+      [
+       "/css/css-text/white-space/reference/trailing-space-align-start-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-text/white-space/white-space-empty-text-sibling.html": [
     [
      "css/css-text/white-space/white-space-empty-text-sibling.html",
@@ -153475,6 +153481,11 @@
      {}
     ]
    ],
+   "css/css-text/white-space/reference/trailing-space-align-start-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-text/white-space/reference/white-space-break-spaces-005-ref.html": [
     [
      {}
@@ -183425,6 +183436,11 @@
      {}
     ]
    ],
+   "infrastructure/metadata/infrastructure/testdriver/actions/actionsWithKeyPressed.html.ini": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/metadata/infrastructure/testdriver/actions/elementPosition.html.ini": [
     [
      {}
@@ -193510,6 +193526,16 @@
      {}
     ]
    ],
+   "service-workers/service-worker/registration-schedule-job.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "service-workers/service-worker/registration-updateviacache.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/service-worker/resources/404.py": [
     [
      {}
@@ -240764,6 +240790,12 @@
      {}
     ]
    ],
+   "css/css-text/text-align/text-align-last-empty-inline.html": [
+    [
+     "css/css-text/text-align/text-align-last-empty-inline.html",
+     {}
+    ]
+   ],
    "css/css-text/text-indent/percentage-value-intrinsic-size.html": [
     [
      "css/css-text/text-indent/percentage-value-intrinsic-size.html",
@@ -273164,6 +273196,12 @@
      {}
     ]
    ],
+   "html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html": [
+    [
+     "html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html",
+     {}
+    ]
+   ],
    "html/semantics/interactive-elements/the-details-element/toggleEvent.html": [
     [
      "html/semantics/interactive-elements/the-details-element/toggleEvent.html",
@@ -273260,6 +273298,12 @@
      {}
     ]
    ],
+   "html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html": [
+    [
+     "html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html",
+     {}
+    ]
+   ],
    "html/semantics/interfaces.html": [
     [
      "html/semantics/interfaces.html",
@@ -279284,6 +279328,14 @@
      {}
     ]
    ],
+   "infrastructure/testdriver/actions/actionsWithKeyPressed.html": [
+    [
+     "infrastructure/testdriver/actions/actionsWithKeyPressed.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "infrastructure/testdriver/actions/elementPosition.html": [
     [
      "infrastructure/testdriver/actions/elementPosition.html",
@@ -295945,6 +295997,15 @@
      }
     ]
    ],
+   "pointerevents/pointerevent_touch-action-table-none-test_touch.html": [
+    [
+     "pointerevents/pointerevent_touch-action-table-none-test_touch.html",
+     {
+      "testdriver": true,
+      "timeout": "long"
+     }
+    ]
+   ],
    "pointerevents/pointerevent_touch-action-verification.html": [
     [
      "pointerevents/pointerevent_touch-action-verification.html",
@@ -308948,6 +309009,12 @@
      }
     ]
    ],
+   "service-workers/service-worker/registration-schedule-job.https.html": [
+    [
+     "service-workers/service-worker/registration-schedule-job.https.html",
+     {}
+    ]
+   ],
    "service-workers/service-worker/registration-scope.https.html": [
     [
      "service-workers/service-worker/registration-scope.https.html",
@@ -376483,31 +376550,31 @@
    "reftest"
   ],
   "css/css-color-adjust/inheritance-expected.txt": [
-   "b0c6f2fbd74b6740458bd98ca4a6b5c2910a5544",
+   "9fdd1886b54c37355ae8ce4c4de083dedabe048a",
    "support"
   ],
   "css/css-color-adjust/inheritance.html": [
-   "c48d2ade7bb0339377480cf506dad7312303deb1",
+   "f7f6529349bf1ba0436e2d1165a81552de95ffaa",
    "testharness"
   ],
   "css/css-color-adjust/parsing/color-scheme-computed.tentative-expected.txt": [
-   "35ae840413c8030e4c79eefe68b68ef6fbeeb200",
+   "0aa58bb90e191bef42d9077a4bd8ea8949527c15",
    "support"
   ],
   "css/css-color-adjust/parsing/color-scheme-computed.tentative.html": [
-   "74d1bb919dc299c20fa8d1a11fe116e28fa59bfd",
+   "80b9803981f7123d22c637ee9056c04ba3108818",
    "testharness"
   ],
   "css/css-color-adjust/parsing/color-scheme-invalid.html": [
-   "0def5b3fc1090ab288c8ab9d601824e54e620f08",
+   "48fa4d1d1f3a1f6d172f650e2452bf29921314c7",
    "testharness"
   ],
   "css/css-color-adjust/parsing/color-scheme-valid-expected.txt": [
-   "4839a167d34345d86ee0e1a0bedcec6b343dad2c",
+   "4fd64bb6a2ad2406946265291b397411e67698d9",
    "support"
   ],
   "css/css-color-adjust/parsing/color-scheme-valid.html": [
-   "433610061f29411dcc9b7379ce4123fbb5110766",
+   "2ba01da394ebc63c9b6b87ea53714d1567032f9b",
    "testharness"
   ],
   "css/css-color/LICENSE": [
@@ -407718,6 +407785,10 @@
    "0b10eb768825a44a2252c55a53326d6641d3fff9",
    "visual"
   ],
+  "css/css-text/text-align/text-align-last-empty-inline.html": [
+   "07dcb3ba896c1f393896a397775cc3ff83fb5ef3",
+   "testharness"
+  ],
   "css/css-text/text-align/text-align-start-001.html": [
    "40c6abaef4847027a628e53acfc88b707bad8c38",
    "reftest"
@@ -409086,6 +409157,10 @@
    "9f579f29e0c897c744e1b06d5d2d60e85612f823",
    "support"
   ],
+  "css/css-text/white-space/reference/trailing-space-align-start-ref.html": [
+   "b65a8fe75479c0119a8ecc156b9a5d5f318d66d0",
+   "support"
+  ],
   "css/css-text/white-space/reference/white-space-break-spaces-005-ref.html": [
    "dece5f7394470d5bbc393c4318fa412ea25f9b4e",
    "support"
@@ -409314,6 +409389,10 @@
    "af3187ae8d3d955dcb23a02de7ecc6b0b4db9479",
    "reftest"
   ],
+  "css/css-text/white-space/trailing-space-align-start.tentative.html": [
+   "7908de12adff86e96df50a30a9ae57e2db9cd7f5",
+   "reftest"
+  ],
   "css/css-text/white-space/trailing-space-before-br-001.html": [
    "2ecd4d3767acf5b2d54782eff26ae5bd3c97a87f",
    "testharness"
@@ -462350,6 +462429,10 @@
    "5ed14c53afc9371275917d460f5b9638ab9a091e",
    "testharness"
   ],
+  "html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html": [
+   "80812cccb548ef5bec9aa4c9d240faaab8de7747",
+   "testharness"
+  ],
   "html/semantics/interactive-elements/the-details-element/toggleEvent-expected.txt": [
    "5c808aa0a050a4ad866e65445b3fbd7c6807903d",
    "support"
@@ -462438,6 +462521,10 @@
    "4a3693bd2dafe710b82054bfadd8bcaa97b16db5",
    "testharness"
   ],
+  "html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html": [
+   "57cc45478e03ce1cdbb755281b2f434b38582563",
+   "testharness"
+  ],
   "html/semantics/interfaces-expected.txt": [
    "886e7b2c154d563563708f78582f57808814c643",
    "support"
@@ -468150,6 +468237,10 @@
    "cbae6b15410e13433c4a9fadd8c2a8cc5fbc4fdc",
    "support"
   ],
+  "infrastructure/metadata/infrastructure/testdriver/actions/actionsWithKeyPressed.html.ini": [
+   "38f84319c91d0b617ed0a72716bfa8756fc2c317",
+   "support"
+  ],
   "infrastructure/metadata/infrastructure/testdriver/actions/elementPosition.html.ini": [
    "9ae71a6e73e22a855c69d3269936d71c17d6e9e5",
    "support"
@@ -468350,6 +468441,10 @@
    "ea7973a62e0ee9cdc874879fd844b2309e944e61",
    "testharness"
   ],
+  "infrastructure/testdriver/actions/actionsWithKeyPressed.html": [
+   "74e939f5fde4773aade6ce4f7bbee573e39ae8ec",
+   "testharness"
+  ],
   "infrastructure/testdriver/actions/elementPosition.html": [
    "145852e7b51bd0cdc9e7b4ef5ebddcbf1c0235c5",
    "testharness"
@@ -482614,9 +482709,9 @@
    "252ed2fed0cfec792dfc2fccacea27234216d8c4",
    "testharness"
   ],
-  "pointerevents/pointerevent_touch-action-table-test_touch-manual.html": [
-   "07a78f572985658c04b6ce7709e01b936c73f0fd",
-   "manual"
+  "pointerevents/pointerevent_touch-action-table-none-test_touch.html": [
+   "6f5e16a8c5315935d89f2d2240e52339e63d9715",
+   "testharness"
   ],
   "pointerevents/pointerevent_touch-action-verification.html": [
    "f42d9f6bd6703b962058b79faae413fbed0757cc",
@@ -495291,7 +495386,7 @@
    "testharness"
   ],
   "service-workers/service-worker/fetch-event-async-respond-with.https.html": [
-   "7842a829c9b82c0ebff94a3902284ad191b5be71",
+   "ae64fcb9a5445843afb75679d0dafa45a3366677",
    "testharness"
   ],
   "service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html": [
@@ -495830,6 +495925,14 @@
    "aa9d38cedc989c61c7267acd4150b889744855e2",
    "testharness"
   ],
+  "service-workers/service-worker/registration-schedule-job.https-expected.txt": [
+   "8deae645bc1036a908886e0eccf2c1b6175509e1",
+   "support"
+  ],
+  "service-workers/service-worker/registration-schedule-job.https.html": [
+   "18589e0aa0b32e130934a65b1b065d5c7f79e36e",
+   "testharness"
+  ],
   "service-workers/service-worker/registration-scope.https.html": [
    "f4a77d72b9120098bba40fbbc1a3174fe15b2f5e",
    "testharness"
@@ -495850,8 +495953,12 @@
    "f7b52d5ddced18613ca225386bb57f28b9bacd62",
    "testharness"
   ],
+  "service-workers/service-worker/registration-updateviacache.https-expected.txt": [
+   "002474415b16b6a2fadc92c78ba872760e0e90ae",
+   "support"
+  ],
   "service-workers/service-worker/registration-updateviacache.https.html": [
-   "dbcc6eab1f82a6b33ed7d889cba51be5b984b610",
+   "423908db0e428d65b48b8b8d16162a5ebcf87268",
    "testharness"
   ],
   "service-workers/service-worker/rejections.https.html": [
@@ -496187,7 +496294,7 @@
    "support"
   ],
   "service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js": [
-   "3409d0a0397bdc552da166961b435fb2363e3468",
+   "dc3f1a1e98563ad1b5924c8315c0d64d17ffbbfd",
    "support"
   ],
   "service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html": [
@@ -501615,7 +501722,7 @@
    "support"
   ],
   "tools/manifest/update.py": [
-   "65c428317654de386adbf18e33fa54d14afb1ddb",
+   "166a7c9caddbab5dfa40712be8840de7b3efb65f",
    "support"
   ],
   "tools/manifest/utils.py": [
@@ -501959,7 +502066,7 @@
    "support"
   ],
   "tools/serve/serve.py": [
-   "b86ad309142b5a7b617d046265019a2e7cae6439",
+   "575f8b7a3accfcc9789c27c9255e3b289cf1c984",
    "support"
   ],
   "tools/serve/test_functional.py": [
@@ -505811,7 +505918,7 @@
    "support"
   ],
   "tools/wpt/testfiles.py": [
-   "1707048754641137418ca7bf85d063a9ea52713c",
+   "b1c81877cd620f99c76e139329614e905c6271fe",
    "support"
   ],
   "tools/wpt/tox.ini": [
@@ -506339,7 +506446,7 @@
    "support"
   ],
   "tools/wptserve/wptserve/server.py": [
-   "d57a36b133844b1146c4ab44ae411ea94222cc6d",
+   "e58b4acd7195d798bd829ce0d10e8d4d1986e253",
    "support"
   ],
   "tools/wptserve/wptserve/sslutils/__init__.py": [
@@ -512235,7 +512342,7 @@
    "testharness"
   ],
   "webrtc/RTCRtpReceiver-getSynchronizationSources.https-expected.txt": [
-   "1a941ec8a33bdb772931c17cfa639f74cfcf7e0d",
+   "c07866218f3e7a3364cfb6eb43b40529561f8d6c",
    "support"
   ],
   "webrtc/RTCRtpReceiver-getSynchronizationSources.https.html": [
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html
new file mode 100644
index 0000000..80812cc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=969619">
+<details open="open" style="display:table;"><rt></rt></details>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(()=> { }, "No crash");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html
new file mode 100644
index 0000000..57cc454
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-summary-element/display-table-with-rt-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=969619">
+<summary style="display:table;"><rt></rt></summary>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(()=> { }, "No crash");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/actions/actionsWithKeyPressed.html.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/actions/actionsWithKeyPressed.html.ini
new file mode 100644
index 0000000..38f8431
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/actions/actionsWithKeyPressed.html.ini
@@ -0,0 +1,9 @@
+[actionsWithKeyPressed.html]
+  expected:
+    if product == "safari": ERROR
+
+
+  [TestDriver actions: actions with key pressed]
+    expected:
+      if product == "firefox": FAIL
+
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/actions/actionsWithKeyPressed.html b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/actions/actionsWithKeyPressed.html
new file mode 100644
index 0000000..74e939f5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/actions/actionsWithKeyPressed.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver actions: actions with key pressed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<style>
+div#test1, div#test2 {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100px;
+  height: 100px;
+  background-color: blue;
+}
+
+div#test2 {
+  position: fixed;
+  top: 100px;
+  left: 0;
+  width: 100px;
+  height: 100px;
+  background-color: green;
+}
+</style>
+
+<div id="test1">
+</div>
+
+<div id="test2">
+</div>
+
+<script>
+let keys = [];
+
+async_test(t => {
+  let test1 = document.getElementById("test1");
+  let test2 = document.getElementById("test2");
+  document.getElementById("test1").addEventListener("click",
+    e => {keys.push(e.getModifierState("Control"))});
+  document.getElementById("test2").addEventListener("click",
+    e => {keys.push(e.getModifierState("Control"))});
+
+  let actions = new test_driver.Actions()
+    .keyDown("\uE009")
+    .addTick()
+    .pointerMove(0, 0, {origin: test1})
+    .pointerDown()
+    .pointerUp()
+    .pointerMove(0, 0, {origin: test2})
+    .pointerDown()
+    .pointerUp()
+    .addTick()
+    .keyUp("\uE009")
+    .addTick()
+    .pointerMove(0, 0, {origin: test1})
+    .pointerDown()
+    .pointerUp();
+
+  actions.send()
+    .then(t.step_func_done(() => assert_array_equals(keys, [true, true, false])))
+    .catch(e => t.step_func(() => assert_unreached("Actions sequence failed " + e)));
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-table-test_touch-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-table-none-test_touch.html
similarity index 71%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-table-test_touch-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-table-none-test_touch.html
index 07a78f5..6f5e16a8c 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-table-test_touch-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-table-none-test_touch.html
@@ -8,6 +8,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script src="pointerevent_support.js"></script>
         <style>
             #target0 {
@@ -38,7 +41,8 @@
     </head>
     <body onload="run()">
         <h2>Pointer Events touch-action attribute support</h2>
-        <h4 id="desc">Test Description: Try to scroll element DOWN starting your touch over the 1st Row. Wait for description update.</h4>
+        <h4 id="desc">Test Description: Try to scroll element DOWN then RIGHT starting your touch over the 1st Row. <br>
+        And try to scroll element DOWN then RIGHT starting your touch inside of the Cell 3, then tap complete button.</h4>
         <p>Note: this test is for touch only</p>
         <div id="target0">
             <table id="testtable">
@@ -56,14 +60,13 @@
             var xScrollIsReceived = false;
             var yScrollIsReceived = false;
             var xScr0, yScr0, xScr1, yScr1;
-            var scrollReturnInterval = 1000;
             var isFirstPart = true;
-            setup({ explicit_timeout: true });
             add_completion_callback(showPointerTypes);
 
             function run() {
                 var target0 = document.getElementById("target0");
                 var btnComplete = document.getElementById("btnComplete");
+                var actions_promise;
 
                 //TA 15.19
                 var test_touchaction_cell = async_test("touch-action attribute test on the cell");
@@ -74,11 +77,15 @@
 
                 on_event(btnComplete, 'click', function(event) {
                     test_touchaction_cell.step(function() {
-                        assert_equals(target0.scrollLeft, 0, "table scroll x offset should be 0 in the end of the test");
-                        assert_equals(target0.scrollTop, 0, "table scroll y offset should be 0 in the end of the test");
+                        assert_equals(target0.scrollLeft, xScr1, "table scroll x offset should be 0 in the end of the test");
+                        assert_equals(target0.scrollTop, yScr1, "table scroll y offset should be 0 in the end of the test");
                         assert_true(xScrollIsReceived && yScrollIsReceived, "target0 x and y scroll offsets should be greater than 0 after first two interactions (outside red border) respectively");
                     });
-                    test_touchaction_cell.done();
+
+                    // Make sure the test finishes after all the input actions are completed.
+                    actions_promise.then( () => {
+                        test_touchaction_cell.done();
+                    });
                     updateDescriptionComplete();
                 });
 
@@ -99,43 +106,29 @@
                             test_touchaction_row.step(function () {
                                 yScrollIsReceived = true;
                             });
-                            updateDescriptionSecondStepTable(target0, scrollReturnInterval);
                         }
 
                         if(xScrollIsReceived && yScrollIsReceived) {
                             test_touchaction_row.done();
-                            updateDescriptionThirdStepTable(target0, scrollReturnInterval, function() {
-                                setTimeout(function() {
-                                    isFirstPart = false;
-                                }, scrollReturnInterval); // avoid immediate triggering while scroll is still being performed
-                            });
                         }
                     }
                     else {
                         test_touchaction_cell.step(failOnScroll, "scroll received while shouldn't");
                     }
                 });
-            }
 
-            function updateDescriptionSecondStepTable(target, scrollReturnInterval, element) {
-                window.setTimeout(function() {
-                    objectScroller(target, 'up', 0);
-                }
-                , scrollReturnInterval);
-                document.getElementById('desc').innerHTML = "Test Description: Try to scroll element RIGHT staring your touch over the Row 1";
+                // Inject touch inputs.
+                actions_promise = touchScrollInTarget(row1, 'down').then(function() {
+                    return touchScrollInTarget(row1, 'right');
+                }).then(function() {
+                    isFirstPart = false;
+                    return touchScrollInTarget(cell3, 'down');
+                }).then(function() {
+                    return touchScrollInTarget(cell3, 'right');
+                }).then(function() {
+                    return clickInTarget("touch", btnComplete);
+                });
             }
-
-            function updateDescriptionThirdStepTable(target, scrollReturnInterval, callback = null) {
-                window.setTimeout(function() {
-                    objectScroller(target, 'left', 0);
-                    if (callback) {
-                        callback();
-                    }
-                }
-                , scrollReturnInterval);
-                document.getElementById('desc').innerHTML = "Test Description: Try to scroll element DOWN then RIGHT starting your touch inside of the Cell 3";
-            }
-
         </script>
         <h1>touch-action: none</h1>
         <div id="complete-notice">
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-schedule-job.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-schedule-job.https-expected.txt
new file mode 100644
index 0000000..8deae64
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-schedule-job.https-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+FAIL different scriptURL and updateViaCache assert_equals: expected "none" but got "imports"
+FAIL different type assert_equals: expected (string) "classic" but got (undefined) undefined
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-schedule-job.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-schedule-job.https.html
new file mode 100644
index 0000000..18589e0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-schedule-job.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Service Worker: Schedule Job algorithm</title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Tests for https://w3c.github.io/ServiceWorker/#schedule-job-algorithm
+// Non-equivalent register jobs should not be coalesced.
+const scope = 'resources/';
+const script1 = 'resources/empty.js';
+const script2 = 'resources/empty.js?change';
+
+async function cleanup() {
+  const registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+}
+
+function absolute_url(url) {
+  return new URL(url, self.location).toString();
+}
+
+// Test scriptURL and updateViaCache.
+promise_test(async t => {
+  await cleanup();
+  t.add_cleanup(cleanup);
+
+  // Check defaults.
+  const registration = await
+      navigator.serviceWorker.register(script1, {scope});
+  assert_equals(registration.updateViaCache, 'imports');
+
+  // Schedule several register jobs.
+  navigator.serviceWorker.register(script1, {scope});
+  navigator.serviceWorker.register(script2, {scope});
+  await navigator.serviceWorker.register(script2,
+                                         {scope, updateViaCache: 'none'});
+
+  // None of the changes should have been coalesced.
+  assert_equals(registration.installing.scriptURL, absolute_url(script2));
+  assert_equals(registration.updateViaCache, 'none');
+}, 'different scriptURL and updateViaCache');
+
+// Test |type| in another test case because most browsers don't support it.
+promise_test(async t => {
+  const script1 = 'resources/empty.js';
+  const script2 = 'resources/empty.js?change';
+
+  await cleanup();
+  t.add_cleanup(cleanup);
+
+  // Check defaults.
+  const registration = await
+      navigator.serviceWorker.register(script1, {scope});
+  assert_equals(registration.installing.type, 'classic');
+
+  // Schedule several register jobs.
+  navigator.serviceWorker.register(script1, {scope});
+  navigator.serviceWorker.register(script2, {scope});
+  await navigator.serviceWorker.register(script2, {scope, type: 'module'});
+
+  // None of the changes should have been coalesced.
+  assert_equals(registration.installing.scriptURL, absolute_url(script2));
+  assert_equals(registration.installing.type, 'module');
+}, 'different type');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-updateviacache.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-updateviacache.https-expected.txt
new file mode 100644
index 0000000..0024744
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-updateviacache.https-expected.txt
@@ -0,0 +1,28 @@
+This is a testharness.js-based test.
+PASS register-with-updateViaCache-undefined
+PASS register-with-updateViaCache-imports
+PASS register-with-updateViaCache-all
+PASS register-with-updateViaCache-none
+PASS register-with-updateViaCache-undefined-then-undefined
+PASS register-with-updateViaCache-undefined-then-imports
+PASS register-with-updateViaCache-undefined-then-all
+PASS register-with-updateViaCache-undefined-then-none
+PASS register-with-updateViaCache-imports-then-undefined
+PASS register-with-updateViaCache-imports-then-imports
+PASS register-with-updateViaCache-imports-then-all
+PASS register-with-updateViaCache-imports-then-none
+PASS register-with-updateViaCache-all-then-undefined
+PASS register-with-updateViaCache-all-then-imports
+PASS register-with-updateViaCache-all-then-all
+PASS register-with-updateViaCache-all-then-none
+PASS register-with-updateViaCache-none-then-undefined
+PASS register-with-updateViaCache-none-then-imports
+PASS register-with-updateViaCache-none-then-all
+PASS register-with-updateViaCache-none-then-none
+PASS access-updateViaCache-after-unregister-undefined
+PASS access-updateViaCache-after-unregister-imports
+PASS access-updateViaCache-after-unregister-all
+PASS access-updateViaCache-after-unregister-none
+FAIL updateViaCache is not updated if register() rejects assert_equals: after update attempt expected "imports" but got "none"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-updateviacache.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-updateviacache.https.html
index dbcc6eab..423908db 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-updateviacache.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-updateviacache.https.html
@@ -184,4 +184,21 @@
     }, testName);
   }
 
+  promise_test(async t => {
+    await cleanup();
+    t.add_cleanup(cleanup);
+
+    const registration = await navigator.serviceWorker.register(
+        'resources/empty.js',
+        {scope: SCOPE});
+    assert_equals(registration.updateViaCache, 'imports',
+                  'before update attempt');
+
+    const fail = navigator.serviceWorker.register(
+        'resources/malformed-worker.py?parse-error',
+        {scope: SCOPE, updateViaCache: 'none'});
+    await promise_rejects(t, new TypeError(), fail);
+    assert_equals(registration.updateViaCache, 'imports',
+                  'after update attempt');
+  }, 'updateViaCache is not updated if register() rejects');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/tools/manifest/update.py b/third_party/blink/web_tests/external/wpt/tools/manifest/update.py
index 65c42831..166a7c9 100755
--- a/third_party/blink/web_tests/external/wpt/tools/manifest/update.py
+++ b/third_party/blink/web_tests/external/wpt/tools/manifest/update.py
@@ -23,7 +23,7 @@
 
 def update(tests_root,  # type: str
            manifest,  # type: Manifest
-           manifest_path,  # type: Optional[str]
+           manifest_path=None,  # type: Optional[str]
            working_copy=True,  # type: bool
            cache_root=None,  # type: Optional[str]
            rebuild=False  # type: bool
diff --git a/third_party/blink/web_tests/external/wpt/tools/serve/serve.py b/third_party/blink/web_tests/external/wpt/tools/serve/serve.py
index b86ad30..575f8b7a 100644
--- a/third_party/blink/web_tests/external/wpt/tools/serve/serve.py
+++ b/third_party/blink/web_tests/external/wpt/tools/serve/serve.py
@@ -32,6 +32,12 @@
 from mod_pywebsocket import standalone as pywebsocket
 
 
+EDIT_HOSTS_HELP = ("Please ensure all the necessary WPT subdomains "
+                   "are mapped to a loopback device in /etc/hosts. "
+                   "See https://github.com/web-platform-tests/wpt#running-the-tests "
+                   "for instructions.")
+
+
 def replace_end(s, old, new):
     """
     Given a string `s` that ends with `old`, replace that occurrence of `old`
@@ -444,18 +450,19 @@
     wrapper.start(start_http_server, host, port, paths, build_routes(aliases),
                   bind_address, config)
 
+    url = "http://{}:{}/".format(host, port)
     connected = False
     for i in range(10):
         try:
-            urllib.request.urlopen("http://%s:%d/" % (host, port))
+            urllib.request.urlopen(url)
             connected = True
             break
         except urllib.error.URLError:
             time.sleep(1)
 
     if not connected:
-        logger.critical("Failed to connect to test server on http://%s:%s. "
-                        "You may need to edit /etc/hosts or similar, see README.md." % (host, port))
+        logger.critical("Failed to connect to test server "
+                        "on {}. {}".format(url, EDIT_HOSTS_HELP))
         sys.exit(1)
 
     for domain in config.domains_set:
@@ -465,8 +472,7 @@
         try:
             urllib.request.urlopen("http://%s:%d/" % (domain, port))
         except Exception:
-            logger.critical("Failed probing domain %s. "
-                            "You may need to edit /etc/hosts or similar, see README.md." % domain)
+            logger.critical("Failed probing domain {}. {}".format(domain, EDIT_HOSTS_HELP))
             sys.exit(1)
 
     wrapper.wait()
diff --git a/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py b/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py
index 1707048..b1c81877 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wpt/testfiles.py
@@ -5,6 +5,7 @@
 import subprocess
 import sys
 
+import six
 from collections import OrderedDict
 from six import iteritems
 
@@ -375,11 +376,13 @@
 
 
 def get_revish(**kwargs):
-    # type: (**Any) -> str
-    revish = kwargs["revish"]
+    # type: (**Any) -> bytes
+    revish = kwargs.get("revish")
     if revish is None:
         revish = "%s..HEAD" % branch_point()
-    assert isinstance(revish, str)
+    if isinstance(revish, six.text_type):
+        revish = revish.encode("utf8")
+    assert isinstance(revish, six.binary_type)
     return revish
 
 
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py b/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py
index d57a36b..e58b4ac 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py
@@ -29,7 +29,8 @@
 from .utils import HTTPException
 from .constants import h2_headers
 
-"""HTTP server designed for testing purposes.
+"""
+HTTP server designed for testing purposes.
 
 The server is designed to provide flexibility in the way that
 requests are handled, and to provide control both of exactly
@@ -63,6 +64,12 @@
 """
 
 
+EDIT_HOSTS_HELP = ("Please ensure all the necessary WPT subdomains "
+                  "are mapped to a loopback device in /etc/hosts. "
+                  "See https://github.com/web-platform-tests/wpt#running-the-tests "
+                  "for instructions.")
+
+
 class RequestRewriter(object):
     def __init__(self, rules):
         """Object for rewriting the request path.
@@ -669,8 +676,7 @@
 
             _host, self.port = self.httpd.socket.getsockname()
         except Exception:
-            self.logger.error("Failed to start HTTP server. "
-                              "You may need to edit /etc/hosts or similar, see README.md.")
+            self.logger.error("Failed to start HTTP server. {}".format(EDIT_HOSTS_HELP))
             raise
 
     def start(self, block=False):
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-table-test_touch-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-table-test_touch-manual-automation.js
deleted file mode 100644
index 2ae10e0..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-table-test_touch-manual-automation.js
+++ /dev/null
@@ -1,15 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-   return touchScrollInTarget('#row1', 'down').then(function() {
-    return touchScrollInTarget('#row1', 'right');
-  }).then(function() {
-    return delayPromise(4*scrollReturnInterval);
-  }).then(function() {
-    return touchScrollInTarget('#cell3', 'down');
-  }).then(function() {
-    return touchScrollInTarget('#cell3', 'right');
-  }).then(function() {
-    return touchTapInTarget('#btnComplete');
-  });
-}
diff --git a/third_party/blink/web_tests/fast/replaced/percentage-height-foreign-object-crash.html b/third_party/blink/web_tests/fast/replaced/percentage-height-foreign-object-crash.html
new file mode 100644
index 0000000..c9720d0
--- /dev/null
+++ b/third_party/blink/web_tests/fast/replaced/percentage-height-foreign-object-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<svg>
+  <foreignObject style="height:100%;">
+    <object style="max-height:100%;"></object>
+  </foreignObject>
+</svg>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+  test(()=> { }, "did not crash");
+</script>
diff --git a/third_party/blink/web_tests/http/tests/devtools/network/resource-priority-expected.txt b/third_party/blink/web_tests/http/tests/devtools/network/resource-priority-expected.txt
index 71ad775..043283f 100644
--- a/third_party/blink/web_tests/http/tests/devtools/network/resource-priority-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/network/resource-priority-expected.txt
@@ -12,6 +12,12 @@
 Request: empty-script.js?precededByImage priority: Medium
 sendScriptRequestPrecededByPreloadedImage
 Request: abe.png?preloaded priority: Low
+Request: style.css?precededByPreloadedImage priority: VeryHigh
+sendStyleRequestPrecededByImage
+Request: abe.png?precedingStyle priority: Low
+Request: style.css?precededByImage priority: Medium
+sendStyleRequestPrecededByPreloadedImage
+Request: abe.png?preloaded priority: Low
 Request: empty-script.js?precededByPreloadedImage priority: High
 sendXHRSync
 Request: empty.html?xhr-sync priority: VeryHigh
diff --git a/third_party/blink/web_tests/http/tests/devtools/network/resource-priority.js b/third_party/blink/web_tests/http/tests/devtools/network/resource-priority.js
index 8fa9b71..be47d6fa 100644
--- a/third_party/blink/web_tests/http/tests/devtools/network/resource-priority.js
+++ b/third_party/blink/web_tests/http/tests/devtools/network/resource-priority.js
@@ -70,6 +70,24 @@
           var iframe = document.createElement("iframe");
           document.body.appendChild(iframe);
           iframe.srcdoc = '<html><body><link href="resources/abe.png?preloaded" rel=preload as=image>'
+              + '<link rel="stylesheet" type="text/css" href="http://localhost:8000/devtools/network/resources/style.css?precededByPreloadedImage">'
+              + '<img src="resources/abe.png?preloaded"></body></html>';
+      }
+
+      function sendStyleRequestPrecededByImage()
+      {
+          var iframe = document.createElement("iframe");
+          document.body.appendChild(iframe);
+          iframe.srcdoc = '<html><body><img src="resources/abe.png?precedingStyle">'
+              + '<link rel="stylesheet" type="text/css" href="http://localhost:8000/devtools/network/resources/style.css?precededByImage">'
+              + '</body></html>';
+      }
+
+      function sendStyleRequestPrecededByPreloadedImage()
+      {
+          var iframe = document.createElement("iframe");
+          document.body.appendChild(iframe);
+          iframe.srcdoc = '<html><body><link href="resources/abe.png?preloaded" rel=preload as=image>'
               + '<script src="http://localhost:8000/devtools/network/resources/empty-script.js?precededByPreloadedImage"></s'
               + 'cript><img src="resources/abe.png?preloaded"></body></html>';
       }
@@ -97,6 +115,8 @@
     {'fn': 'sendModuleScriptRequest', 'requests': 2},
     {'fn': 'sendScriptRequestPrecededByImage', 'requests': 2},
     {'fn': 'sendScriptRequestPrecededByPreloadedImage', 'requests': 2},
+    {'fn': 'sendStyleRequestPrecededByImage', 'requests': 2},
+    {'fn': 'sendStyleRequestPrecededByPreloadedImage', 'requests': 2},
     {'fn': 'sendXHRSync', 'requests': 1},
     {'fn': 'sendXHRAsync', 'requests': 1},
     {'fn': 'sendImageRequest', 'requests': 1},
diff --git a/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/html/css3-modsel-18-expected.png b/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/html/css3-modsel-18-expected.png
deleted file mode 100644
index 0a689e2..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/html/css3-modsel-18-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/xhtml/css3-modsel-18-expected.png b/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/xhtml/css3-modsel-18-expected.png
deleted file mode 100644
index 0a689e2..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/xhtml/css3-modsel-18-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/xml/css3-modsel-18-expected.png b/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/xml/css3-modsel-18-expected.png
deleted file mode 100644
index a8a805f..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/css3/selectors3/xml/css3-modsel-18-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-001-expected.png b/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-001-expected.png
deleted file mode 100644
index cabbc51..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-001-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-002-expected.png b/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-002-expected.png
deleted file mode 100644
index cabbc51..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-002-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-003-expected.png b/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-003-expected.png
deleted file mode 100644
index cabbc51..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-003-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-004-expected.png b/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-004-expected.png
deleted file mode 100644
index cabbc51..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-004-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-005-expected.png b/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-005-expected.png
deleted file mode 100644
index cabbc51..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/editing/pasteboard/paste-line-endings-005-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/editing/selection/4402375-expected.png b/third_party/blink/web_tests/platform/fuchsia/editing/selection/4402375-expected.png
deleted file mode 100644
index 05255e8..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/editing/selection/4402375-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/editing/style/block-styles-007-expected.png b/third_party/blink/web_tests/platform/fuchsia/editing/style/block-styles-007-expected.png
deleted file mode 100644
index 5dc9b6a..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/editing/style/block-styles-007-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/backgrounds/background-clip-text-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/backgrounds/background-clip-text-expected.png
deleted file mode 100644
index 080a043..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/backgrounds/background-clip-text-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/block/float/centered-float-avoidance-complexity-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/block/float/centered-float-avoidance-complexity-expected.png
deleted file mode 100644
index d464e27..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/block/float/centered-float-avoidance-complexity-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/block/float/float-in-float-hit-testing-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/block/float/float-in-float-hit-testing-expected.png
deleted file mode 100644
index dc6a133e..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/block/float/float-in-float-hit-testing-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/block/float/float-in-float-painting-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/block/float/float-in-float-painting-expected.png
deleted file mode 100644
index e47f0b83..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/block/float/float-in-float-painting-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css/clip-zooming-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css/clip-zooming-expected.png
deleted file mode 100644
index 59ee8ff..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css/clip-zooming-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css/text-overflow-ellipsis-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css/text-overflow-ellipsis-expected.png
deleted file mode 100644
index a3d2066..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css/text-overflow-ellipsis-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css/text-overflow-ellipsis-strict-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css/text-overflow-ellipsis-strict-expected.png
deleted file mode 100644
index a3d2066..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css/text-overflow-ellipsis-strict-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/inline-block/overflow-clip-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/inline-block/overflow-clip-expected.png
deleted file mode 100644
index f1aa6dc9..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/inline-block/overflow-clip-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-expected.png
deleted file mode 100644
index 96aeebc..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-repeat-x-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-repeat-x-expected.png
deleted file mode 100644
index 166d29d..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-repeat-x-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-repeat-y-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-repeat-y-expected.png
deleted file mode 100644
index aa020ce..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/inline/inline-box-background-repeat-y-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/lists/003-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/lists/003-expected.png
deleted file mode 100644
index 409e070..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/lists/003-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/lists/scrolled-marker-paint-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/lists/scrolled-marker-paint-expected.png
deleted file mode 100644
index b177f17..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/lists/scrolled-marker-paint-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/selectors/018-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/selectors/018-expected.png
deleted file mode 100644
index 2c1c277..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/selectors/018-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/table/border-collapsing/004-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/table/border-collapsing/004-expected.png
deleted file mode 100644
index 678d2c4..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/table/border-collapsing/004-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/text/international/001-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/text/international/001-expected.png
deleted file mode 100644
index 07cc1d3..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/text/international/001-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/text/international/003-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/text/international/003-expected.png
deleted file mode 100644
index a04ecdee..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/text/international/003-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/text/international/bidi-LDB-2-formatting-characters-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/text/international/bidi-LDB-2-formatting-characters-expected.png
deleted file mode 100644
index 772c849..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/text/international/bidi-LDB-2-formatting-characters-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/text/stroking-decorations-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/text/stroking-decorations-expected.png
deleted file mode 100644
index b265fcb3..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/fast/text/stroking-decorations-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/selection/selected-replaced-expected.png b/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/selection/selected-replaced-expected.png
deleted file mode 100644
index 11009a3..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/selection/selected-replaced-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/shadow-multiple-expected.png b/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/shadow-multiple-expected.png
deleted file mode 100644
index 127675da..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/shadow-multiple-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-4-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-4-expected.png
deleted file mode 100644
index 5c6ee300..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-4-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/core/bloomberg-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/core/bloomberg-expected.png
deleted file mode 100644
index 953c918..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/core/bloomberg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug1010-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug1010-expected.png
deleted file mode 100644
index 839c2a1..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug1010-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug1055-2-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug1055-2-expected.png
deleted file mode 100644
index 8ae4e04f..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug1055-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug2479-5-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug2479-5-expected.png
deleted file mode 100644
index 95eae29..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla_expected_failures/bugs/bug2479-5-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/transforms/transformed-caret-expected.png b/third_party/blink/web_tests/platform/fuchsia/transforms/transformed-caret-expected.png
deleted file mode 100644
index 6f79b58..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/transforms/transformed-caret-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/resources/testdriver-vendor.js b/third_party/blink/web_tests/resources/testdriver-vendor.js
index 95b584b..2dc45ac 100644
--- a/third_party/blink/web_tests/resources/testdriver-vendor.js
+++ b/third_party/blink/web_tests/resources/testdriver-vendor.js
@@ -29,7 +29,6 @@
     }
 
     var centerPoint = getInViewCenterPoint(rectangles[0]);
-
     if ("elementsFromPoint" in document) {
       return document.elementsFromPoint(centerPoint[0], centerPoint[1]);
     } else if ("msElementsFromPoint" in document) {
@@ -42,7 +41,7 @@
 
   function inView(element) {
     var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
-    return pointerInteractablePaintTree.indexOf(element) !== -1;
+    return pointerInteractablePaintTree.indexOf(element) !== -1 || element.contains(pointerInteractablePaintTree[0]);
   }
 
   window.test_driver_internal.click = function(element, coords) {
diff --git a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/fetch/README.txt b/third_party/blink/web_tests/virtual/blink-cors/external/wpt/fetch/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/fetch/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/http/README.txt b/third_party/blink/web_tests/virtual/blink-cors/external/wpt/http/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/http/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/referrer-policy/README.txt b/third_party/blink/web_tests/virtual/blink-cors/external/wpt/referrer-policy/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/referrer-policy/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/service-workers/README.txt b/third_party/blink/web_tests/virtual/blink-cors/external/wpt/service-workers/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/service-workers/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt b/third_party/blink/web_tests/virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
deleted file mode 100644
index 44c6a05..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-This is a testharness.js-based test.
-PASS initialize global state
-FAIL event.request has the expected headers for same-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin GET. lengths differ, expected 1 got 3"
-FAIL event.request has the expected headers for same-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin POST. lengths differ, expected 2 got 5"
-FAIL event.request has the expected headers for cross-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin GET. lengths differ, expected 1 got 3"
-FAIL event.request has the expected headers for cross-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin POST. lengths differ, expected 2 got 5"
-PASS FetchEvent#request.body contains XHR request data (string)
-PASS FetchEvent#request.body contains XHR request data (blob)
-PASS FetchEvent#request.method is set to XHR method
-PASS XHR using OPTIONS method
-PASS XHR with form data
-PASS XHR with mode/credentials set
-PASS XHR to data URL
-PASS restore global state
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/xhr/README.txt b/third_party/blink/web_tests/virtual/blink-cors/external/wpt/xhr/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/external/wpt/xhr/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/http/tests/fetch/README.txt b/third_party/blink/web_tests/virtual/blink-cors/http/tests/fetch/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/http/tests/fetch/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/http/tests/navigation/README.txt b/third_party/blink/web_tests/virtual/blink-cors/http/tests/navigation/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/http/tests/navigation/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/http/tests/security/README.txt b/third_party/blink/web_tests/virtual/blink-cors/http/tests/security/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/http/tests/security/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture-expected.txt b/third_party/blink/web_tests/virtual/blink-cors/http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture-expected.txt
deleted file mode 100644
index 96c842b..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-CONSOLE ERROR: line 8: Unsafe JavaScript attempt to initiate navigation for frame with origin 'http://127.0.0.1:8000' from frame with URL 'http://localhost:8000/security/frameNavigation/resources/iframe-that-performs-top-navigation-without-user-gesture.html'. The frame attempting navigation is targeting its top-level window, but is neither same-origin with its target nor has it received a user gesture. See https://www.chromestatus.com/features/5851021045661696.
-
-
-
---------
-Frame: '<!--framePath //<!--frame0-->-->'
---------
-The navigation should fail. This text should be visible.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/README.txt b/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/README.txt
deleted file mode 100644
index 19936c1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing Blink CORS implementation.
diff --git a/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/cross-origin-unsupported-url-expected.txt b/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/cross-origin-unsupported-url-expected.txt
deleted file mode 100644
index a18432e1..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/cross-origin-unsupported-url-expected.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-CONSOLE WARNING: line 8: Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-This is a testharness.js-based test.
-PASS sync test for url=mailto:foo@bar.com, contentType=undefined
-PASS sync test for url=mailto:foo@bar.com, contentType=application/json
-PASS async test for url=mailto:foo@bar.com, contentType=undefined
-PASS async test for url=mailto:foo@bar.com, contentType=application/json
-PASS sync test for url=localhost:8080/, contentType=undefined
-PASS sync test for url=localhost:8080/, contentType=application/json
-PASS async test for url=localhost:8080/, contentType=undefined
-PASS async test for url=localhost:8080/, contentType=application/json
-PASS sync test for url=tel:1234, contentType=undefined
-PASS sync test for url=tel:1234, contentType=application/json
-PASS async test for url=tel:1234, contentType=undefined
-PASS async test for url=tel:1234, contentType=application/json
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/workers/cross-origin-unsupported-url-expected.txt b/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/workers/cross-origin-unsupported-url-expected.txt
deleted file mode 100644
index 086a854..0000000
--- a/third_party/blink/web_tests/virtual/blink-cors/http/tests/xmlhttprequest/workers/cross-origin-unsupported-url-expected.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 13: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'mailto:foo@bar.com' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'localhost:8080/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-CONSOLE ERROR: line 29: Access to XMLHttpRequest at 'tel:1234' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
-This is a testharness.js-based test.
-PASS sync test for url=mailto:foo@bar.com, contentType=undefined
-PASS sync test for url=mailto:foo@bar.com, contentType=application/json
-PASS async test for url=mailto:foo@bar.com, contentType=undefined
-PASS async test for url=mailto:foo@bar.com, contentType=application/json
-PASS sync test for url=localhost:8080/, contentType=undefined
-PASS sync test for url=localhost:8080/, contentType=application/json
-PASS async test for url=localhost:8080/, contentType=undefined
-PASS async test for url=localhost:8080/, contentType=application/json
-PASS sync test for url=tel:1234, contentType=undefined
-PASS sync test for url=tel:1234, contentType=application/json
-PASS async test for url=tel:1234, contentType=undefined
-PASS async test for url=tel:1234, contentType=application/json
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/webgpu/buffer_mapping.html b/third_party/blink/web_tests/webgpu/buffer_mapping.html
new file mode 100644
index 0000000..54b91745c
--- /dev/null
+++ b/third_party/blink/web_tests/webgpu/buffer_mapping.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script>
+promise_test(async t => {
+
+  const adapter = await navigator.gpu.requestAdapter();
+  const device = await adapter.requestDevice();
+  const queue = device.getQueue();
+
+  // Check expected length and zero-initialization.
+  function checkMapWriteResult(arrayBuffer, expectedSize) {
+    assert_equals(arrayBuffer.byteLength, expectedSize);
+    const view = new Uint8Array(arrayBuffer);
+    for (let i = 0; i < view.length; ++i) {
+      assert_equals(view[i], 0);
+    }
+  }
+
+  // Copy and MapRead data to check buffer contents
+  async function expectContents(buffer, expected) {
+    const size = expected.byteLength;
+
+    const readback = device.createBuffer({
+      size,
+      usage: GPUBufferUsage.TRANSFER_DST | GPUBufferUsage.MAP_READ,
+    });
+
+    const commandEncoder = device.createCommandEncoder({});
+    commandEncoder.copyBufferToBuffer(buffer, 0, readback, 0, size);
+    queue.submit([commandEncoder.finish()]);
+
+    const actual = new Uint8Array(await readback.mapReadAsync());
+    assert_equals(actual.byteLength, size);
+
+    // Cast the expected contents as a byte array.
+    const expectedView = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength);
+
+    for (let i = 0; i < size; ++i) {
+      assert_equals(actual[i], expectedView[i]);
+    }
+  }
+
+  {
+    // Test simple MapWrite.
+    const buffer = device.createBuffer({
+      size: 12,
+      usage: GPUBufferUsage.TRANSFER_SRC | GPUBufferUsage.MAP_WRITE,
+    });
+
+    const arrayBuffer = await buffer.mapWriteAsync();
+    checkMapWriteResult(arrayBuffer, 12);
+
+    const view = new Uint32Array(arrayBuffer);
+    view[1] = 7;
+    buffer.unmap();
+
+    // Array buffer should be detached.
+    assert_equals(arrayBuffer.byteLength, 0);
+    assert_equals(view.byteLength, 0);
+
+    await expectContents(buffer, new Uint32Array([0, 7, 0]));
+  }
+
+  {
+    // Test large MapWrite
+    const size = 512 * 1024;
+
+    const buffer = device.createBuffer({
+      size,
+      usage: GPUBufferUsage.TRANSFER_SRC | GPUBufferUsage.MAP_WRITE,
+    });
+
+    const arrayBuffer = await buffer.mapWriteAsync();
+    checkMapWriteResult(arrayBuffer, size);
+
+    const view = new Uint32Array(arrayBuffer);
+    assert_equals(view.byteLength, size);
+    for (let i = 0; i < view.length; ++i) {
+      view[i] = i;
+    }
+    const expected = view.slice();
+    buffer.unmap();
+
+    // Array buffer should be detached.
+    assert_equals(arrayBuffer.byteLength, 0);
+    assert_equals(view.byteLength, 0);
+
+    await expectContents(buffer, expected);
+  }
+
+  {
+    // Test simple MapRead
+    const buffer = device.createBuffer({
+      size: 12,
+      usage: GPUBufferUsage.TRANSFER_DST | GPUBufferUsage.MAP_READ,
+    });
+
+    buffer.setSubData(8, new Uint32Array([3]));
+
+    // TODO(enga): This should check the other values are zero, but we don't have
+    // lazy zero-initialization yet.
+    const actual = new Uint32Array(await buffer.mapReadAsync());
+    assert_equals(actual[2], 3);
+  }
+
+  {
+    // Test large MapRead
+    const size = 512 * 1024;
+
+    const buffer = device.createBuffer({
+      size,
+      usage: GPUBufferUsage.TRANSFER_DST | GPUBufferUsage.MAP_READ,
+    });
+
+    const data = new Uint32Array(new ArrayBuffer(size));
+    for (let i = 0; i < data.length; ++i) {
+      data[i] = i;
+    }
+    buffer.setSubData(0, data);
+
+    const actual = new Uint32Array(await buffer.mapReadAsync());
+    assert_equals(data.length, actual.length);
+    for (let i = 0; i < data.length; ++i) {
+      assert_equals(data[i], actual[i]);
+    }
+
+  }
+
+}, "Test WebGPU buffer mapping");
+</script>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 0ae1738..c34a9e0 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -128232,6 +128232,12 @@
 </histogram>
 
 <histogram name="Sync.NonReflectionUpdateFreshnessPossiblySkewed" units="ms">
+  <obsolete>
+    Deprecated as of 05/2019 because it recorded freshness only up to 1hr and
+    most samples were thus out of bounds. Replaced by
+    Sync.NonReflectionUpdateFreshnessPossiblySkewed2 which records freshness up
+    to 7 days.
+  </obsolete>
   <owner>mastiz@chromium.org</owner>
   <owner>melandory@chromium.org</owner>
   <summary>
@@ -128243,6 +128249,18 @@
   </summary>
 </histogram>
 
+<histogram name="Sync.NonReflectionUpdateFreshnessPossiblySkewed2" units="ms">
+  <owner>mastiz@chromium.org</owner>
+  <owner>melandory@chromium.org</owner>
+  <summary>
+    Freshness of the sync data per received sync entity update, excluding
+    reflections. The time represents the clock difference from the model being
+    modified (usually on another device) until the change is processing by this
+    instance of the browser. The time is capped at 1 week. Beware of potential
+    clock skew due to two clients being involved.
+  </summary>
+</histogram>
+
 <histogram name="Sync.PageRevisitBookmarksDuration" units="ms"
     expires_after="2018-02-21">
   <obsolete>
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 5a7c3d1..9ca9b42 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -413,17 +413,13 @@
       {
         'isolate': 'performance_test_suite',
         'extra_args': [
-          # TODO(crbug.com/612455): Enable ref builds once can pass both
-          # --browser=exact (used by this bot to have it run Monochrome6432)
-          # and --browser=reference together.
-          #'--run-ref-build',
+          '--run-ref-build',
           '--test-shard-map-filename=android-pixel2-perf_map.json',
         ],
         'num_shards': 35
       }
     ],
     'platform': 'android-chrome',
-    'browser': 'bin/monochrome_64_32_bundle',
     'dimension': {
       'pool': 'chrome.tests.perf',
       'os': 'Android',
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index f201984..e296dc2 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -126,7 +126,6 @@
     "//ui/gfx/geometry",
     "//ui/gl",
     "//ui/platform_window",
-    "//ui/platform_window/mojo",
     "//ui/platform_window/stub",
   ]
 
diff --git a/ui/base/ime/win/tsf_text_store.cc b/ui/base/ime/win/tsf_text_store.cc
index faf3fb3..ba41c24c 100644
--- a/ui/base/ime/win/tsf_text_store.cc
+++ b/ui/base/ime/win/tsf_text_store.cc
@@ -856,6 +856,7 @@
     if (SUCCEEDED(
             context_composition->EnumCompositions(&enum_composition_view))) {
       Microsoft::WRL::ComPtr<ITfCompositionView> composition_view;
+      bool has_composition = false;
       if (enum_composition_view->Next(1, &composition_view, nullptr) == S_OK) {
         Microsoft::WRL::ComPtr<ITfRange> range;
         if (SUCCEEDED(composition_view->GetRange(&range))) {
@@ -864,14 +865,21 @@
             LONG start = 0;
             LONG length = 0;
             if (SUCCEEDED(range_acp->GetExtent(&start, &length))) {
-              composition_start_ = start;
-              has_composition_range_ = true;
-              composition_range_.set_start(start);
-              composition_range_.set_end(start + length);
+              // We should only consider it as a valid composition if the
+              // composition range is not collapsed (length > 0).
+              if (length > 0) {
+                has_composition = true;
+                composition_start_ = start;
+                has_composition_range_ = true;
+                composition_range_.set_start(start);
+                composition_range_.set_end(start + length);
+              }
             }
           }
         }
-      } else {
+      }
+
+      if (!has_composition) {
         composition_start_ = selection_.start();
         if (has_composition_range_) {
           has_composition_range_ = false;
diff --git a/ui/base/models/tree_node_model.h b/ui/base/models/tree_node_model.h
index fac7dfbc..98e352c 100644
--- a/ui/base/models/tree_node_model.h
+++ b/ui/base/models/tree_node_model.h
@@ -108,14 +108,6 @@
     return ptr;
   }
 
-  // Removes the given node. Prefer to remove by index if you know it to avoid
-  // the search for the node to remove.
-  std::unique_ptr<NodeType> Remove(NodeType* node) {
-    int i = GetIndexOf(node);
-    DCHECK_NE(-1, i);
-    return Remove(i);
-  }
-
   // Removes all the children from this node.
   void DeleteAll() { children_.clear(); }
 
diff --git a/ui/base/models/tree_node_model_unittest.cc b/ui/base/models/tree_node_model_unittest.cc
index f5df948..cec60d4 100644
--- a/ui/base/models/tree_node_model_unittest.cc
+++ b/ui/base/models/tree_node_model_unittest.cc
@@ -272,11 +272,11 @@
   EXPECT_EQ(2, root.child_count());
   EXPECT_EQ(child1->parent(), child2->parent());
 
-  std::unique_ptr<TestNode> c2 = root.Remove(child2);
+  std::unique_ptr<TestNode> c2 = root.Remove(1);
   EXPECT_EQ(1, root.child_count());
   EXPECT_EQ(NULL, child2->parent());
 
-  std::unique_ptr<TestNode> c1 = root.Remove(child1);
+  std::unique_ptr<TestNode> c1 = root.Remove(0);
   EXPECT_EQ(0, root.child_count());
 }
 
diff --git a/ui/ozone/demo/software_renderer.cc b/ui/ozone/demo/software_renderer.cc
index 44e6330..92b799f7 100644
--- a/ui/ozone/demo/software_renderer.cc
+++ b/ui/ozone/demo/software_renderer.cc
@@ -67,8 +67,8 @@
 
   if (vsync_provider_) {
     vsync_provider_->GetVSyncParameters(
-        base::Bind(&SoftwareRenderer::UpdateVSyncParameters,
-                   weak_ptr_factory_.GetWeakPtr()));
+        base::BindOnce(&SoftwareRenderer::UpdateVSyncParameters,
+                       weak_ptr_factory_.GetWeakPtr()));
   }
 
   timer_.Start(FROM_HERE, vsync_period_, this, &SoftwareRenderer::RenderFrame);
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.cc b/ui/ozone/demo/surfaceless_gl_renderer.cc
index 342179a..b7b24c7d 100644
--- a/ui/ozone/demo/surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/surfaceless_gl_renderer.cc
@@ -266,9 +266,9 @@
 
   back_buffer_ ^= 1;
   gl_surface_->SwapBuffersAsync(
-      base::Bind(&SurfacelessGlRenderer::PostRenderFrameTask,
-                 weak_ptr_factory_.GetWeakPtr()),
-      base::Bind([](const gfx::PresentationFeedback&) {}));
+      base::BindOnce(&SurfacelessGlRenderer::PostRenderFrameTask,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::DoNothing());
 }
 
 void SurfacelessGlRenderer::PostRenderFrameTask(
diff --git a/ui/ozone/platform/drm/gpu/proxy_helpers.h b/ui/ozone/platform/drm/gpu/proxy_helpers.h
index d25933b..b037713 100644
--- a/ui/ozone/platform/drm/gpu/proxy_helpers.h
+++ b/ui/ozone/platform/drm/gpu/proxy_helpers.h
@@ -19,14 +19,6 @@
 template <typename... Args>
 void PostAsyncTask(
     const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
-    const base::Callback<void(Args...)>& callback,
-    Args... args) {
-  task_runner->PostTask(FROM_HERE, base::BindOnce(callback, args...));
-}
-
-template <typename... Args>
-void PostAsyncTaskOnce(
-    const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
     base::OnceCallback<void(Args...)> callback,
     Args... args) {
   auto closure = base::BindOnce(std::move(callback), std::move(args)...);
@@ -59,7 +51,7 @@
 template <typename... Args>
 base::OnceCallback<void(Args...)> CreateSafeOnceCallback(
     base::OnceCallback<void(Args...)> callback) {
-  return base::BindOnce(&internal::PostAsyncTaskOnce<Args...>,
+  return base::BindOnce(&internal::PostAsyncTask<Args...>,
                         base::ThreadTaskRunnerHandle::Get(),
                         std::move(callback));
 }
diff --git a/ui/ozone/platform/drm/host/drm_device_connector.cc b/ui/ozone/platform/drm/host/drm_device_connector.cc
index d207dbe1..3ab9667 100644
--- a/ui/ozone/platform/drm/host/drm_device_connector.cc
+++ b/ui/ozone/platform/drm/host/drm_device_connector.cc
@@ -52,7 +52,7 @@
     int host_id,
     scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
     scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-    const base::RepeatingCallback<void(IPC::Message*)>& send_callback) {
+    base::RepeatingCallback<void(IPC::Message*)> send_callback) {
   NOTREACHED();
 }
 
diff --git a/ui/ozone/platform/drm/host/drm_device_connector.h b/ui/ozone/platform/drm/host/drm_device_connector.h
index b915d3b..851208b4 100644
--- a/ui/ozone/platform/drm/host/drm_device_connector.h
+++ b/ui/ozone/platform/drm/host/drm_device_connector.h
@@ -35,8 +35,7 @@
       int host_id,
       scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
       scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-      const base::RepeatingCallback<void(IPC::Message*)>& send_callback)
-      override;
+      base::RepeatingCallback<void(IPC::Message*)> send_callback) override;
   void OnChannelDestroyed(int host_id) override;
   void OnMessageReceived(const IPC::Message& message) override;
   void OnGpuServiceLaunched(
diff --git a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
index 4e353fb4..408a5cd 100644
--- a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
+++ b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
@@ -31,7 +31,7 @@
 class CursorIPC : public DrmCursorProxy {
  public:
   CursorIPC(scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-            const base::Callback<void(IPC::Message*)>& send_callback);
+            base::RepeatingCallback<void(IPC::Message*)> send_callback);
   ~CursorIPC() override;
 
   // DrmCursorProxy implementation.
@@ -47,14 +47,14 @@
   void Send(IPC::Message* message);
 
   scoped_refptr<base::SingleThreadTaskRunner> send_runner_;
-  base::Callback<void(IPC::Message*)> send_callback_;
+  base::RepeatingCallback<void(IPC::Message*)> send_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(CursorIPC);
 };
 
 CursorIPC::CursorIPC(scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-                     const base::Callback<void(IPC::Message*)>& send_callback)
-    : send_runner_(send_runner), send_callback_(send_callback) {}
+                     base::RepeatingCallback<void(IPC::Message*)> send_callback)
+    : send_runner_(send_runner), send_callback_(std::move(send_callback)) {}
 
 CursorIPC::~CursorIPC() {}
 
@@ -132,7 +132,7 @@
     int host_id,
     scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
     scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-    const base::Callback<void(IPC::Message*)>& send_callback) {
+    base::RepeatingCallback<void(IPC::Message*)> send_callback) {
   // If there was a task runner set during construction, prefer using that.
   if (!ui_runner_) {
     ui_runner_ = std::move(ui_runner);
@@ -143,7 +143,7 @@
                "host_id", host_id);
   host_id_ = host_id;
   send_runner_ = std::move(send_runner);
-  send_callback_ = send_callback;
+  send_callback_ = std::move(send_callback);
 
   for (GpuThreadObserver& observer : gpu_thread_observers_)
     observer.OnGpuProcessLaunched();
@@ -167,7 +167,6 @@
     for (GpuThreadObserver& observer : gpu_thread_observers_)
       observer.OnGpuThreadRetired();
   }
-
 }
 
 void DrmGpuPlatformSupportHost::OnMessageReceived(const IPC::Message& message) {
diff --git a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h
index 6d9746e..caef027 100644
--- a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h
+++ b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h
@@ -39,7 +39,7 @@
       int host_id,
       scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
       scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-      const base::Callback<void(IPC::Message*)>& send_callback) override;
+      base::RepeatingCallback<void(IPC::Message*)> send_callback) override;
   void OnChannelDestroyed(int host_id) override;
   void OnGpuServiceLaunched(
       scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
@@ -124,7 +124,7 @@
 
   scoped_refptr<base::SingleThreadTaskRunner> ui_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> send_runner_;
-  base::Callback<void(IPC::Message*)> send_callback_;
+  base::RepeatingCallback<void(IPC::Message*)> send_callback_;
 
   DrmDisplayHostManager* display_manager_;  // Not owned.
   DrmOverlayManagerHost* overlay_manager_;  // Not owned.
@@ -139,4 +139,4 @@
 
 }  // namespace ui
 
-#endif  // UI_OZONE_GPU_DRM_GPU_PLATFORM_SUPPORT_HOST_H_
+#endif  // UI_OZONE_PLATFORM_DRM_HOST_DRM_GPU_PLATFORM_SUPPORT_HOST_H_
diff --git a/ui/ozone/platform/drm/ozone_platform_gbm.cc b/ui/ozone/platform/drm/ozone_platform_gbm.cc
index 5431d13c..c35ea84 100644
--- a/ui/ozone/platform/drm/ozone_platform_gbm.cc
+++ b/ui/ozone/platform/drm/ozone_platform_gbm.cc
@@ -112,13 +112,13 @@
       return;
 
     registry->AddInterface<ozone::mojom::DeviceCursor>(
-        base::Bind(&OzonePlatformGbm::CreateDeviceCursorBinding,
-                   weak_factory_.GetWeakPtr()),
+        base::BindRepeating(&OzonePlatformGbm::CreateDeviceCursorBinding,
+                            weak_factory_.GetWeakPtr()),
         base::ThreadTaskRunnerHandle::Get());
 
     registry->AddInterface<ozone::mojom::DrmDevice>(
-        base::Bind(&OzonePlatformGbm::CreateDrmDeviceBinding,
-                   weak_factory_.GetWeakPtr()),
+        base::BindRepeating(&OzonePlatformGbm::CreateDrmDeviceBinding,
+                            weak_factory_.GetWeakPtr()),
         base::ThreadTaskRunnerHandle::Get());
   }
 
diff --git a/ui/ozone/platform/scenic/scenic_gpu_host.cc b/ui/ozone/platform/scenic/scenic_gpu_host.cc
index 7863ef19..6f8ecf5 100644
--- a/ui/ozone/platform/scenic/scenic_gpu_host.cc
+++ b/ui/ozone/platform/scenic/scenic_gpu_host.cc
@@ -72,7 +72,7 @@
     int host_id,
     scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
     scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-    const base::RepeatingCallback<void(IPC::Message*)>& send_callback) {
+    base::RepeatingCallback<void(IPC::Message*)> send_callback) {
   NOTREACHED();
 }
 
diff --git a/ui/ozone/platform/scenic/scenic_gpu_host.h b/ui/ozone/platform/scenic/scenic_gpu_host.h
index 26bc402..8fb2bef63 100644
--- a/ui/ozone/platform/scenic/scenic_gpu_host.h
+++ b/ui/ozone/platform/scenic/scenic_gpu_host.h
@@ -45,8 +45,7 @@
       int host_id,
       scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
       scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-      const base::RepeatingCallback<void(IPC::Message*)>& send_callback)
-      override;
+      base::RepeatingCallback<void(IPC::Message*)> send_callback) override;
   void OnChannelDestroyed(int host_id) override;
   void OnMessageReceived(const IPC::Message& message) override;
   void OnGpuServiceLaunched(
diff --git a/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.cc b/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.cc
index 8d8a8db..de99b68 100644
--- a/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.cc
+++ b/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.cc
@@ -41,7 +41,7 @@
     int host_id,
     scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
     scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-    const base::RepeatingCallback<void(IPC::Message*)>& send_callback) {}
+    base::RepeatingCallback<void(IPC::Message*)> send_callback) {}
 
 void WaylandBufferManagerConnector::OnChannelDestroyed(int host_id) {
   buffer_manager_->OnChannelDestroyed();
diff --git a/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.h b/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.h
index 29c91d7..e4fc4fc 100644
--- a/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.h
+++ b/ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.h
@@ -27,8 +27,7 @@
       int host_id,
       scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
       scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-      const base::RepeatingCallback<void(IPC::Message*)>& send_callback)
-      override;
+      base::RepeatingCallback<void(IPC::Message*)> send_callback) override;
   void OnChannelDestroyed(int host_id) override;
   void OnMessageReceived(const IPC::Message& message) override;
   void OnGpuServiceLaunched(
diff --git a/ui/ozone/public/gpu_platform_support_host.cc b/ui/ozone/public/gpu_platform_support_host.cc
index 0881946..e52608e 100644
--- a/ui/ozone/public/gpu_platform_support_host.cc
+++ b/ui/ozone/public/gpu_platform_support_host.cc
@@ -19,7 +19,7 @@
       int host_id,
       scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
       scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-      const base::Callback<void(IPC::Message*)>& send_callback) override {}
+      base::RepeatingCallback<void(IPC::Message*)> send_callback) override {}
 
   void OnChannelDestroyed(int host_id) override {}
   void OnMessageReceived(const IPC::Message&) override {}
diff --git a/ui/ozone/public/gpu_platform_support_host.h b/ui/ozone/public/gpu_platform_support_host.h
index 2f81d97..9694d730 100644
--- a/ui/ozone/public/gpu_platform_support_host.h
+++ b/ui/ozone/public/gpu_platform_support_host.h
@@ -42,7 +42,7 @@
       int host_id,
       scoped_refptr<base::SingleThreadTaskRunner> ui_runner,
       scoped_refptr<base::SingleThreadTaskRunner> send_runner,
-      const base::Callback<void(IPC::Message*)>& sender) = 0;
+      base::RepeatingCallback<void(IPC::Message*)> sender) = 0;
 
   // Called when the GPU process is destroyed.
   // This is called from browser UI thread.
diff --git a/ui/ozone/public/ozone_gpu_test_helper.cc b/ui/ozone/public/ozone_gpu_test_helper.cc
index ca78153..575f17d 100644
--- a/ui/ozone/public/ozone_gpu_test_helper.cc
+++ b/ui/ozone/public/ozone_gpu_test_helper.cc
@@ -88,7 +88,7 @@
     ui::OzonePlatform::GetInstance()
         ->GetGpuPlatformSupportHost()
         ->OnGpuProcessLaunched(kGpuProcessHostId, ui_task_runner_,
-                               gpu_io_task_runner_, sender);
+                               gpu_io_task_runner_, std::move(sender));
   }
 
  private:
diff --git a/ui/ozone/public/surface_factory_ozone.h b/ui/ozone/public/surface_factory_ozone.h
index ba04a36..7512ad7 100644
--- a/ui/ozone/public/surface_factory_ozone.h
+++ b/ui/ozone/public/surface_factory_ozone.h
@@ -149,7 +149,7 @@
   // be used instead of the NativePixmap that would have been produced by the
   // standard, implementation-specific NativePixmapHandle import mechanism.
   using GetProtectedNativePixmapCallback =
-      base::Callback<scoped_refptr<gfx::NativePixmap>(
+      base::RepeatingCallback<scoped_refptr<gfx::NativePixmap>(
           const gfx::NativePixmapHandle& handle)>;
   // Called by an external service to set the GetProtectedNativePixmapCallback,
   // to be used by the implementation when importing NativePixmapHandles.
diff --git a/ui/platform_window/mojo/BUILD.gn b/ui/platform_window/mojo/BUILD.gn
deleted file mode 100644
index b42cf4f..0000000
--- a/ui/platform_window/mojo/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/jumbo.gni")
-import("//mojo/public/tools/bindings/mojom.gni")
-
-# This target does NOT depend on skia. One can depend on this target to avoid
-# picking up a dependency on skia.
-jumbo_component("mojo") {
-  output_name = "mojo_ime_lib"
-
-  public_deps = [
-    "//ui/base/ime",
-  ]
-  deps = [
-    ":interfaces",
-    "//base",
-    "//ui/platform_window",
-  ]
-
-  defines = [ "MOJO_IME_IMPLEMENTATION" ]
-
-  sources = [
-    "ime_type_converters.cc",
-    "ime_type_converters.h",
-    "mojo_ime_export.h",
-  ]
-}
-
-mojom("interfaces") {
-  sources = [
-    "text_input_state.mojom",
-  ]
-
-  public_deps = [
-    "//ui/base/ime/mojo",
-  ]
-}
diff --git a/ui/platform_window/mojo/DEPS b/ui/platform_window/mojo/DEPS
deleted file mode 100644
index 1d56d19d..0000000
--- a/ui/platform_window/mojo/DEPS
+++ /dev/null
@@ -1,5 +0,0 @@
-include_rules = [
-  "+ui/base/ime/text_input_flags.h",
-  "+ui/base/ime/text_input_type.h",
-  "+ui/platform_window/text_input_state.h",
-]
diff --git a/ui/platform_window/mojo/OWNERS b/ui/platform_window/mojo/OWNERS
deleted file mode 100644
index 5d54957..0000000
--- a/ui/platform_window/mojo/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-per-file *_type_converter*.*=set noparent
-per-file *_type_converter*.*=file://ipc/SECURITY_OWNERS
-
-per-file *.mojom=set noparent
-per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/ui/platform_window/mojo/ime_type_converters.cc b/ui/platform_window/mojo/ime_type_converters.cc
deleted file mode 100644
index 9435045..0000000
--- a/ui/platform_window/mojo/ime_type_converters.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/platform_window/mojo/ime_type_converters.h"
-
-#include <stdint.h>
-
-#include "base/macros.h"
-
-namespace mojo {
-
-#define TEXT_INPUT_TYPE_ASSERT(NAME)                                    \
-  static_assert(static_cast<int32_t>(ui::mojom::TextInputType::NAME) == \
-                    static_cast<int32_t>(ui::TEXT_INPUT_TYPE_##NAME),   \
-                "TEXT_INPUT_TYPE must match")
-TEXT_INPUT_TYPE_ASSERT(NONE);
-TEXT_INPUT_TYPE_ASSERT(TEXT);
-TEXT_INPUT_TYPE_ASSERT(PASSWORD);
-TEXT_INPUT_TYPE_ASSERT(SEARCH);
-TEXT_INPUT_TYPE_ASSERT(EMAIL);
-TEXT_INPUT_TYPE_ASSERT(NUMBER);
-TEXT_INPUT_TYPE_ASSERT(TELEPHONE);
-TEXT_INPUT_TYPE_ASSERT(URL);
-TEXT_INPUT_TYPE_ASSERT(DATE);
-TEXT_INPUT_TYPE_ASSERT(DATE_TIME);
-TEXT_INPUT_TYPE_ASSERT(DATE_TIME_LOCAL);
-TEXT_INPUT_TYPE_ASSERT(MONTH);
-TEXT_INPUT_TYPE_ASSERT(TIME);
-TEXT_INPUT_TYPE_ASSERT(WEEK);
-TEXT_INPUT_TYPE_ASSERT(TEXT_AREA);
-TEXT_INPUT_TYPE_ASSERT(CONTENT_EDITABLE);
-TEXT_INPUT_TYPE_ASSERT(DATE_TIME_FIELD);
-TEXT_INPUT_TYPE_ASSERT(MAX);
-
-#define TEXT_INPUT_FLAG_ASSERT(NAME)                                    \
-  static_assert(static_cast<int32_t>(ui::mojom::TextInputFlag::NAME) == \
-                    static_cast<int32_t>(ui::TEXT_INPUT_FLAG_##NAME),   \
-                "TEXT_INPUT_FLAG must match")
-TEXT_INPUT_FLAG_ASSERT(NONE);
-TEXT_INPUT_FLAG_ASSERT(AUTOCOMPLETE_ON);
-TEXT_INPUT_FLAG_ASSERT(AUTOCOMPLETE_OFF);
-TEXT_INPUT_FLAG_ASSERT(AUTOCORRECT_ON);
-TEXT_INPUT_FLAG_ASSERT(AUTOCORRECT_OFF);
-TEXT_INPUT_FLAG_ASSERT(SPELLCHECK_ON);
-TEXT_INPUT_FLAG_ASSERT(SPELLCHECK_OFF);
-TEXT_INPUT_FLAG_ASSERT(AUTOCAPITALIZE_NONE);
-TEXT_INPUT_FLAG_ASSERT(AUTOCAPITALIZE_CHARACTERS);
-TEXT_INPUT_FLAG_ASSERT(AUTOCAPITALIZE_WORDS);
-TEXT_INPUT_FLAG_ASSERT(AUTOCAPITALIZE_SENTENCES);
-
-// static
-ui::TextInputState
-TypeConverter<ui::TextInputState, ui::mojom::TextInputStatePtr>::Convert(
-    const ui::mojom::TextInputStatePtr& input) {
-  return ui::TextInputState(
-      ConvertTo<ui::TextInputType>(input->type), input->flags,
-      input->text.has_value() ? input->text.value() : std::string(),
-      input->selection_start, input->selection_end, input->composition_start,
-      input->composition_end, input->can_compose_inline);
-}
-
-}  // namespace mojo
diff --git a/ui/platform_window/mojo/ime_type_converters.h b/ui/platform_window/mojo/ime_type_converters.h
deleted file mode 100644
index a55125d..0000000
--- a/ui/platform_window/mojo/ime_type_converters.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_PLATFORM_WINDOW_MOJO_IME_TYPE_CONVERTERS_H_
-#define UI_PLATFORM_WINDOW_MOJO_IME_TYPE_CONVERTERS_H_
-
-#include "ui/base/ime/ime_text_span.h"
-#include "ui/platform_window/mojo/mojo_ime_export.h"
-#include "ui/platform_window/mojo/text_input_state.mojom.h"
-#include "ui/platform_window/text_input_state.h"
-
-namespace mojo {
-
-template <>
-struct MOJO_IME_EXPORT
-    TypeConverter<ui::TextInputState, ui::mojom::TextInputStatePtr> {
-  static ui::TextInputState Convert(const ui::mojom::TextInputStatePtr& input);
-};
-
-}  // namespace mojo
-
-#endif  // UI_PLATFORM_WINDOW_MOJO_IME_TYPE_CONVERTERS_H_
diff --git a/ui/platform_window/mojo/mojo_ime_export.h b/ui/platform_window/mojo/mojo_ime_export.h
deleted file mode 100644
index e616f25..0000000
--- a/ui/platform_window/mojo/mojo_ime_export.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_PLATFORM_WINDOW_MOJO_MOJO_IME_EXPORT_H_
-#define UI_PLATFORM_WINDOW_MOJO_MOJO_IME_EXPORT_H_
-
-#if defined(COMPONENT_BUILD)
-
-#if defined(WIN32)
-
-#if defined(MOJO_IME_IMPLEMENTATION)
-#define MOJO_IME_EXPORT __declspec(dllexport)
-#else
-#define MOJO_IME_EXPORT __declspec(dllimport)
-#endif
-
-#else  // !defined(WIN32)
-
-#if defined(MOJO_IME_IMPLEMENTATION)
-#define MOJO_IME_EXPORT __attribute__((visibility("default")))
-#else
-#define MOJO_IME_EXPORT
-#endif
-
-#endif  // defined(WIN32)
-
-#else  // !defined(COMPONENT_BUILD)
-#define MOJO_IME_EXPORT
-#endif
-
-#endif  // UI_PLATFORM_WINDOW_MOJO_MOJO_IME_EXPORT_H_
diff --git a/ui/platform_window/mojo/text_input_state.mojom b/ui/platform_window/mojo/text_input_state.mojom
deleted file mode 100644
index 21767a5..0000000
--- a/ui/platform_window/mojo/text_input_state.mojom
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module ui.mojom;
-
-import "ui/base/ime/mojo/ime_types.mojom";
-
-// Text input info which is based on blink::WebTextInputInfo.
-struct TextInputState {
-  // The type of input field.
-  TextInputType type;
-
-  // The flags of the input field (autocorrect, autocomplete, etc.).
-  int32 flags;
-
-  // The value of the input field.
-  string? text;
-
-  // The cursor position of the current selection start, or the caret position
-  // if nothing is selected.
-  int32 selection_start;
-
-  // The cursor position of the current selection end, or the caret position
-  // if nothing is selected.
-  int32 selection_end;
-
-  // The start position of the current composition, or -1 if there is none.
-  int32 composition_start;
-
-  // The end position of the current composition, or -1 if there is none.
-  int32 composition_end;
-
-  // Whether or not inline composition can be performed for the current input.
-  bool can_compose_inline;
-};
diff --git a/ui/surface/transport_dib.h b/ui/surface/transport_dib.h
index 759c23b..ff29b86 100644
--- a/ui/surface/transport_dib.h
+++ b/ui/surface/transport_dib.h
@@ -14,10 +14,6 @@
 #include "build/build_config.h"
 #include "ui/surface/surface_export.h"
 
-#if defined(OS_WIN)
-#include <windows.h>
-#endif
-
 class SkCanvas;
 
 // -----------------------------------------------------------------------------