diff --git a/DEPS b/DEPS
index a46da3c..96b0a5f 100644
--- a/DEPS
+++ b/DEPS
@@ -145,11 +145,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': '80e7bd03d4736fb2d7591fb034f3bcc2d79dbf8b',
+  'skia_revision': 'c711a8649301281c2faaa932fb80ea4d51fcf08b',
   # 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': '69c9ca09df3a8bb7d8eac52325df0a2008b00cab',
+  'v8_revision': 'b0db4f562fd85fd0f7a981e3d407b44cf41cae18',
   # 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.
@@ -157,7 +157,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '868f5fa02353df97ae486d45af5afb08f495e489',
+  'angle_revision': '369f9e5df60b349e3d7ad8693fc90ede86e609f5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -165,7 +165,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '6d82d95f2dd0b0aed10d205e29b3244af467211b',
+  'pdfium_revision': '25661d1e1dc69283fad42f5ac39502fd08270432',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -276,11 +276,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.
-  'shaderc_revision': '3101bd58464af111c74f4d98197fae1d29db928a',
+  'shaderc_revision': 'd289a55e46ff1c931ca0d90785218370f5ce68e0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '56f3a7b90d37c6e2a3ce4ab230416656a2f562a9',
+  'dawn_revision': '574b951188bbfa23a144f05519f5fcc048b88fe2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -809,7 +809,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5dd6b1e0a5eff1b2fb269bac969beac5f461483f',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '3bd52c2334a004f6197204aa0a37acda2f0fb988',
       'condition': 'checkout_linux',
   },
 
@@ -834,7 +834,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '447b45d42307021be3dc11d9e557b59e42f30ef8',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '59bb8cce842ce937f07064f64f18a6f9192110de',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1207,7 +1207,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'bf0e385cd6fb456a816419c50de2c3622be2c2ae',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'f50c2ff2f70cce05fbff61d26f3d3133be590866',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1416,7 +1416,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7e33a7e91b899e98146b9be23ad049a73935bcf0',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f0390f88f270e9eb2ced17a343902404abc3c0ad',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index eec47087..4b975f9 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -35,6 +35,7 @@
     "apk/java/src/com/android/webview/chromium/WebViewApkApplication.java",
     "java/src/org/chromium/android_webview/AndroidProtocolHandler.java",
     "java/src/org/chromium/android_webview/AwAutofillClient.java",
+    "java/src/org/chromium/android_webview/AwBrowserContext.java",
     "java/src/org/chromium/android_webview/AwBrowserProcess.java",
     "java/src/org/chromium/android_webview/AwContents.java",
     "java/src/org/chromium/android_webview/AwContentsBackgroundThreadClient.java",
diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc
index 7f9382e8..916f5c86 100644
--- a/android_webview/browser/aw_browser_context.cc
+++ b/android_webview/browser/aw_browser_context.cc
@@ -8,7 +8,6 @@
 #include <string>
 #include <utility>
 
-#include "android_webview/browser/aw_browser_policy_connector.h"
 #include "android_webview/browser/aw_browser_process.h"
 #include "android_webview/browser/aw_content_browser_client.h"
 #include "android_webview/browser/aw_download_manager_delegate.h"
@@ -22,6 +21,7 @@
 #include "android_webview/browser/cookie_manager.h"
 #include "android_webview/browser/network_service/net_helpers.h"
 #include "android_webview/browser/safe_browsing/aw_safe_browsing_whitelist_manager.h"
+#include "android_webview/native_jni/AwBrowserContext_jni.h"
 #include "base/base_paths_posix.h"
 #include "base/bind.h"
 #include "base/feature_list.h"
@@ -87,6 +87,17 @@
 
   BrowserContext::Initialize(this, context_storage_path_);
 
+  CreateUserPrefService();
+
+  visitedlink_master_.reset(
+      new visitedlink::VisitedLinkMaster(this, this, false));
+  visitedlink_master_->Init();
+
+  form_database_service_.reset(
+      new AwFormDatabaseService(context_storage_path_));
+
+  EnsureResourceContextInitialized(this);
+
   // This constructor is entered during the creation of ContentBrowserClient,
   // before browser threads are created. Therefore any checks to enforce
   // threading (such as BrowserThread::CurrentlyOn()) will fail here.
@@ -96,6 +107,8 @@
   DCHECK_EQ(this, g_browser_context);
   BrowserContext::NotifyWillBeDestroyed(this);
   SimpleKeyMap::GetInstance()->Dissociate(this);
+  ShutdownStoragePartitions();
+
   g_browser_context = NULL;
 }
 
@@ -181,8 +194,6 @@
 }
 
 void AwBrowserContext::CreateUserPrefService() {
-  browser_policy_connector_ = std::make_unique<AwBrowserPolicyConnector>();
-
   auto pref_registry = base::MakeRefCounted<user_prefs::PrefRegistrySyncable>();
 
   RegisterPrefs(pref_registry.get());
@@ -200,11 +211,13 @@
       /*validation_delegate=*/nullptr));
 
   policy::URLBlacklistManager::RegisterProfilePrefs(pref_registry.get());
+  AwBrowserPolicyConnector* browser_policy_connector =
+      AwBrowserProcess::GetInstance()->browser_policy_connector();
   pref_service_factory.set_managed_prefs(
       base::MakeRefCounted<policy::ConfigurationPolicyPrefStore>(
-          browser_policy_connector_.get(),
-          browser_policy_connector_->GetPolicyService(),
-          browser_policy_connector_->GetHandlerList(),
+          browser_policy_connector,
+          browser_policy_connector->GetPolicyService(),
+          browser_policy_connector->GetHandlerList(),
           policy::POLICY_LEVEL_MANDATORY));
 
   user_pref_service_ = pref_service_factory.Create(pref_registry);
@@ -235,27 +248,6 @@
   return supported_schemes;
 }
 
-void AwBrowserContext::PreMainMessageLoopRun() {
-  CreateUserPrefService();
-
-  scoped_refptr<base::SequencedTaskRunner> db_task_runner =
-      base::CreateSequencedTaskRunner(
-          {base::ThreadPool(), base::MayBlock(),
-           base::TaskPriority::BEST_EFFORT,
-           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
-  visitedlink_master_.reset(
-      new visitedlink::VisitedLinkMaster(this, this, false));
-  visitedlink_master_->Init();
-
-  form_database_service_.reset(
-      new AwFormDatabaseService(context_storage_path_));
-
-  EnsureResourceContextInitialized(this);
-
-  content::WebUIControllerFactory::RegisterFactory(
-      AwWebUIControllerFactory::GetInstance());
-}
-
 void AwBrowserContext::AddVisitedURLs(const std::vector<GURL>& urls) {
   DCHECK(visitedlink_master_);
   visitedlink_master_->AddURLs(urls);
@@ -443,4 +435,18 @@
   return context_params;
 }
 
+base::android::ScopedJavaLocalRef<jobject> JNI_AwBrowserContext_GetDefaultJava(
+    JNIEnv* env) {
+  return g_browser_context->GetJavaBrowserContext();
+}
+
+base::android::ScopedJavaLocalRef<jobject>
+AwBrowserContext::GetJavaBrowserContext() {
+  if (!obj_) {
+    JNIEnv* env = base::android::AttachCurrentThread();
+    obj_ = Java_AwBrowserContext_create(env, reinterpret_cast<intptr_t>(this));
+  }
+  return base::android::ScopedJavaLocalRef<jobject>(obj_);
+}
+
 }  // namespace android_webview
diff --git a/android_webview/browser/aw_browser_context.h b/android_webview/browser/aw_browser_context.h
index 6094df6..0d3d38a2 100644
--- a/android_webview/browser/aw_browser_context.h
+++ b/android_webview/browser/aw_browser_context.h
@@ -37,10 +37,6 @@
 class InProgressDownloadManager;
 }
 
-namespace policy {
-class BrowserPolicyConnectorBase;
-}
-
 namespace visitedlink {
 class VisitedLinkMaster;
 }
@@ -74,9 +70,6 @@
   // Get the list of authentication schemes to support.
   static std::vector<std::string> GetAuthSchemes();
 
-  // Maps to BrowserMainParts::PreMainMessageLoopRun.
-  void PreMainMessageLoopRun();
-
   // These methods map to Add methods in visitedlink::VisitedLinkMaster.
   void AddVisitedURLs(const std::vector<GURL>& urls);
 
@@ -115,6 +108,8 @@
       bool in_memory,
       const base::FilePath& relative_partition_path);
 
+  base::android::ScopedJavaLocalRef<jobject> GetJavaBrowserContext();
+
  private:
   void CreateUserPrefService();
   void MigrateLocalStatePrefs();
@@ -131,12 +126,13 @@
   std::unique_ptr<content::ResourceContext> resource_context_;
 
   std::unique_ptr<PrefService> user_pref_service_;
-  std::unique_ptr<policy::BrowserPolicyConnectorBase> browser_policy_connector_;
   std::unique_ptr<AwSSLHostStateDelegate> ssl_host_state_delegate_;
   std::unique_ptr<content::PermissionControllerDelegate> permission_manager_;
 
   SimpleFactoryKey simple_factory_key_;
 
+  base::android::ScopedJavaGlobalRef<jobject> obj_;
+
   DISALLOW_COPY_AND_ASSIGN(AwBrowserContext);
 };
 
diff --git a/android_webview/browser/aw_browser_context_unittest.cc b/android_webview/browser/aw_browser_context_unittest.cc
index 47d3734..ee9f0338 100644
--- a/android_webview/browser/aw_browser_context_unittest.cc
+++ b/android_webview/browser/aw_browser_context_unittest.cc
@@ -3,9 +3,12 @@
 // found in the LICENSE file.
 
 #include "android_webview/browser/aw_browser_context.h"
+#include "android_webview/browser/aw_browser_process.h"
+#include "android_webview/browser/aw_feature_list_creator.h"
 #include "base/test/scoped_feature_list.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_content_client_initializer.h"
 #include "mojo/core/embedder/embedder.h"
 #include "services/network/public/cpp/features.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -17,11 +20,21 @@
   void SetUp() override {
     mojo::core::Init();
     feature_list_.InitAndEnableFeature(network::features::kNetworkService);
+    test_content_client_initializer_ =
+        new content::TestContentClientInitializer();
+
+    AwFeatureListCreator* aw_feature_list_creator = new AwFeatureListCreator();
+    aw_feature_list_creator->CreateLocalState();
+    browser_process_ = new AwBrowserProcess(aw_feature_list_creator);
   }
 
+  void TearDown() override { delete test_content_client_initializer_; }
+
   base::test::ScopedFeatureList feature_list_;
   // Create the TestBrowserThreads.
   content::TestBrowserThreadBundle thread_bundle_;
+  content::TestContentClientInitializer* test_content_client_initializer_;
+  AwBrowserProcess* browser_process_;
 };
 
 // Tests that constraints on trust for Symantec-issued certificates are not
diff --git a/android_webview/browser/aw_browser_main_parts.cc b/android_webview/browser/aw_browser_main_parts.cc
index eef4a67..d3875fb 100644
--- a/android_webview/browser/aw_browser_main_parts.cc
+++ b/android_webview/browser/aw_browser_main_parts.cc
@@ -13,6 +13,7 @@
 #include "android_webview/browser/aw_browser_terminator.h"
 #include "android_webview/browser/aw_content_browser_client.h"
 #include "android_webview/browser/aw_metrics_service_client.h"
+#include "android_webview/browser/aw_web_ui_controller_factory.h"
 #include "android_webview/browser/memory_metrics_logger.h"
 #include "android_webview/browser/net/aw_network_change_notifier_factory.h"
 #include "android_webview/common/aw_descriptors.h"
@@ -121,8 +122,9 @@
 
 void AwBrowserMainParts::PreMainMessageLoopRun() {
   AwBrowserProcess::GetInstance()->PreMainMessageLoopRun();
-  AwBrowserContext* context = browser_client_->InitBrowserContext();
-  context->PreMainMessageLoopRun();
+  browser_client_->InitBrowserContext();
+  content::WebUIControllerFactory::RegisterFactory(
+      AwWebUIControllerFactory::GetInstance());
   content::RenderFrameHost::AllowInjectingJavaScript();
   metrics_logger_ = std::make_unique<MemoryMetricsLogger>();
 }
diff --git a/android_webview/browser/aw_browser_process.cc b/android_webview/browser/aw_browser_process.cc
index 2bfeaae..223c796 100644
--- a/android_webview/browser/aw_browser_process.cc
+++ b/android_webview/browser/aw_browser_process.cc
@@ -76,14 +76,27 @@
   DCHECK(local_state_);
 }
 
+AwBrowserPolicyConnector* AwBrowserProcess::browser_policy_connector() {
+  if (!browser_policy_connector_)
+    CreateBrowserPolicyConnector();
+  return browser_policy_connector_.get();
+}
+
+void AwBrowserProcess::CreateBrowserPolicyConnector() {
+  DCHECK(!browser_policy_connector_);
+
+  browser_policy_connector_ =
+      aw_feature_list_creator_->TakeBrowserPolicyConnector();
+  DCHECK(browser_policy_connector_);
+}
+
 void AwBrowserProcess::InitSafeBrowsing() {
   CreateSafeBrowsingUIManager();
   CreateSafeBrowsingWhitelistManager();
 }
 
 void AwBrowserProcess::CreateSafeBrowsingUIManager() {
-  safe_browsing_ui_manager_ =
-      new AwSafeBrowsingUIManager(AwBrowserProcess::GetAwURLRequestContext());
+  safe_browsing_ui_manager_ = new AwSafeBrowsingUIManager();
 }
 
 void AwBrowserProcess::CreateSafeBrowsingWhitelistManager() {
diff --git a/android_webview/browser/aw_browser_process.h b/android_webview/browser/aw_browser_process.h
index 89eee6a4..d852dd17 100644
--- a/android_webview/browser/aw_browser_process.h
+++ b/android_webview/browser/aw_browser_process.h
@@ -40,7 +40,9 @@
   static AwBrowserProcess* GetInstance();
 
   PrefService* local_state();
+  AwBrowserPolicyConnector* browser_policy_connector();
 
+  void CreateBrowserPolicyConnector();
   void CreateLocalState();
   void InitSafeBrowsing();
 
@@ -83,6 +85,8 @@
 
   std::unique_ptr<PrefService> local_state_;
 
+  std::unique_ptr<AwBrowserPolicyConnector> browser_policy_connector_;
+
   // Accessed on both UI and IO threads.
   scoped_refptr<AwSafeBrowsingUIManager> safe_browsing_ui_manager_;
 
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index a63cb61..ebc4a7d 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -481,15 +481,6 @@
   return rb.GetImageNamed(IDR_DEFAULT_FAVICON).AsImageSkia();
 }
 
-bool AwContentBrowserClient::AllowAppCacheOnIO(
-    const GURL& manifest_url,
-    const GURL& first_party,
-    content::ResourceContext* context) {
-  // WebView doesn't have a per-site policy for locally stored data,
-  // instead AppCache can be disabled for individual WebViews.
-  return true;
-}
-
 bool AwContentBrowserClient::AllowAppCache(const GURL& manifest_url,
                                            const GURL& first_party,
                                            content::BrowserContext* context) {
@@ -785,47 +776,6 @@
 }
 
 std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-AwContentBrowserClient::CreateURLLoaderThrottlesOnIO(
-    const network::ResourceRequest& request,
-    content::ResourceContext* resource_context,
-    const base::RepeatingCallback<content::WebContents*()>& wc_getter,
-    content::NavigationUIData* navigation_ui_data,
-    int frame_tree_node_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(!base::FeatureList::IsEnabled(::features::kNavigationLoaderOnUI));
-
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> result;
-
-  if (base::FeatureList::IsEnabled(network::features::kNetworkService) ||
-      base::FeatureList::IsEnabled(safe_browsing::kCheckByURLLoaderThrottle)) {
-    result.push_back(safe_browsing::BrowserURLLoaderThrottle::Create(
-        base::BindOnce(
-            [](AwContentBrowserClient* client, content::ResourceContext*) {
-              return client->GetSafeBrowsingUrlCheckerDelegate();
-            },
-            base::Unretained(this)),
-        wc_getter, frame_tree_node_id, resource_context));
-  }
-
-  if (request.resource_type ==
-      static_cast<int>(content::ResourceType::kMainFrame)) {
-    const bool is_load_url =
-        request.transition_type & ui::PAGE_TRANSITION_FROM_API;
-    const bool is_go_back_forward =
-        request.transition_type & ui::PAGE_TRANSITION_FORWARD_BACK;
-    const bool is_reload = ui::PageTransitionCoreTypeIs(
-        static_cast<ui::PageTransition>(request.transition_type),
-        ui::PAGE_TRANSITION_RELOAD);
-    if (is_load_url || is_go_back_forward || is_reload) {
-      result.push_back(std::make_unique<AwURLLoaderThrottle>(
-          static_cast<AwResourceContext*>(resource_context)));
-    }
-  }
-
-  return result;
-}
-
-std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
 AwContentBrowserClient::CreateURLLoaderThrottles(
     const network::ResourceRequest& request,
     content::BrowserContext* browser_context,
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index 744a9a9..485f5d4 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -75,9 +75,6 @@
   std::string GetApplicationLocale() override;
   std::string GetAcceptLangs(content::BrowserContext* context) override;
   gfx::ImageSkia GetDefaultFavicon() override;
-  bool AllowAppCacheOnIO(const GURL& manifest_url,
-                         const GURL& first_party,
-                         content::ResourceContext* context) override;
   bool AllowAppCache(const GURL& manifest_url,
                      const GURL& first_party,
                      content::BrowserContext* context) override;
@@ -168,13 +165,6 @@
       service_manager::BinderRegistry* registry,
       content::RenderFrameHost* render_frame_host) override;
   std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-  CreateURLLoaderThrottlesOnIO(
-      const network::ResourceRequest& request,
-      content::ResourceContext* resource_context,
-      const base::RepeatingCallback<content::WebContents*()>& wc_getter,
-      content::NavigationUIData* navigation_ui_data,
-      int frame_tree_node_id) override;
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
   CreateURLLoaderThrottles(
       const network::ResourceRequest& request,
       content::BrowserContext* browser_context,
diff --git a/android_webview/browser/aw_contents.cc b/android_webview/browser/aw_contents.cc
index f1d6dcd..9f2e47ce 100644
--- a/android_webview/browser/aw_contents.cc
+++ b/android_webview/browser/aw_contents.cc
@@ -372,6 +372,15 @@
   return web_contents_->GetJavaWebContents();
 }
 
+base::android::ScopedJavaLocalRef<jobject> AwContents::GetBrowserContext(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj) {
+  if (!web_contents_)
+    return base::android::ScopedJavaLocalRef<jobject>();
+  return AwBrowserContext::FromWebContents(web_contents_.get())
+      ->GetJavaBrowserContext();
+}
+
 void AwContents::SetCompositorFrameConsumer(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj,
@@ -399,12 +408,11 @@
   delete this;
 }
 
-static jlong JNI_AwContents_Init(JNIEnv* env,
-                                 const JavaParamRef<jobject>& browser_context) {
-  // TODO(joth): Use |browser_context| to get the native BrowserContext, rather
-  // than hard-code the default instance lookup here.
+static jlong JNI_AwContents_Init(JNIEnv* env, jlong browser_context_pointer) {
+  AwBrowserContext* browser_context =
+      reinterpret_cast<AwBrowserContext*>(browser_context_pointer);
   std::unique_ptr<WebContents> web_contents(content::WebContents::Create(
-      content::WebContents::CreateParams(AwBrowserContext::GetDefault())));
+      content::WebContents::CreateParams(browser_context)));
   // Return an 'uninitialized' instance; most work is deferred until the
   // subsequent SetJavaPeers() call.
   return reinterpret_cast<intptr_t>(new AwContents(std::move(web_contents)));
diff --git a/android_webview/browser/aw_contents.h b/android_webview/browser/aw_contents.h
index cc49f4b7..9a7fc0fe8 100644
--- a/android_webview/browser/aw_contents.h
+++ b/android_webview/browser/aw_contents.h
@@ -98,6 +98,9 @@
   base::android::ScopedJavaLocalRef<jobject> GetWebContents(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
+  base::android::ScopedJavaLocalRef<jobject> GetBrowserContext(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
   void SetCompositorFrameConsumer(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
diff --git a/android_webview/browser/aw_feature_list_creator.cc b/android_webview/browser/aw_feature_list_creator.cc
index cfbe9435..0c95a3a 100644
--- a/android_webview/browser/aw_feature_list_creator.cc
+++ b/android_webview/browser/aw_feature_list_creator.cc
@@ -150,8 +150,13 @@
       &ignored_safe_seed_manager);
 }
 
-void AwFeatureListCreator::CreateFeatureListAndFieldTrials() {
+void AwFeatureListCreator::CreateLocalState() {
+  browser_policy_connector_ = std::make_unique<AwBrowserPolicyConnector>();
   local_state_ = CreatePrefService();
+}
+
+void AwFeatureListCreator::CreateFeatureListAndFieldTrials() {
+  CreateLocalState();
   AwMetricsServiceClient::GetInstance()->Initialize(local_state_.get());
   SetUpFieldTrials();
 }
diff --git a/android_webview/browser/aw_feature_list_creator.h b/android_webview/browser/aw_feature_list_creator.h
index 0742ceaf..04ae897e 100644
--- a/android_webview/browser/aw_feature_list_creator.h
+++ b/android_webview/browser/aw_feature_list_creator.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "android_webview/browser/aw_browser_policy_connector.h"
 #include "android_webview/browser/aw_field_trials.h"
 #include "android_webview/browser/aw_variations_service_client.h"
 #include "base/metrics/field_trial.h"
@@ -29,12 +30,20 @@
   // field trials.
   void CreateFeatureListAndFieldTrials();
 
+  void CreateLocalState();
+
   // Passes ownership of the |local_state_| to the caller.
   std::unique_ptr<PrefService> TakePrefService() {
     DCHECK(local_state_);
     return std::move(local_state_);
   }
 
+  // Passes ownership of the |browser_policy_connector_| to the caller.
+  std::unique_ptr<AwBrowserPolicyConnector> TakeBrowserPolicyConnector() {
+    DCHECK(browser_policy_connector_);
+    return std::move(browser_policy_connector_);
+  }
+
  private:
   // Sets up the field trials and related initialization.
   void SetUpFieldTrials();
@@ -58,6 +67,8 @@
 
   std::unique_ptr<AwVariationsServiceClient> client_;
 
+  std::unique_ptr<AwBrowserPolicyConnector> browser_policy_connector_;
+
   DISALLOW_COPY_AND_ASSIGN(AwFeatureListCreator);
 };
 
diff --git a/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.cc b/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.cc
index fd9fe9f..4bb7504d 100644
--- a/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.cc
+++ b/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.cc
@@ -5,7 +5,6 @@
 #include "android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.h"
 
 #include "android_webview/browser/aw_content_browser_client.h"
-#include "android_webview/browser/net/aw_url_request_context_getter.h"
 #include "android_webview/browser/safe_browsing/aw_safe_browsing_blocking_page.h"
 #include "android_webview/common/aw_paths.h"
 #include "base/bind.h"
@@ -15,7 +14,6 @@
 #include "base/task/post_task.h"
 #include "components/safe_browsing/base_ui_manager.h"
 #include "components/safe_browsing/browser/safe_browsing_network_context.h"
-#include "components/safe_browsing/browser/safe_browsing_url_request_context_getter.h"
 #include "components/safe_browsing/common/safebrowsing_constants.h"
 #include "components/safe_browsing/db/v4_protocol_manager_util.h"
 #include "components/safe_browsing/ping_manager.h"
@@ -50,8 +48,7 @@
 
 }  // namespace
 
-AwSafeBrowsingUIManager::AwSafeBrowsingUIManager(
-    AwURLRequestContextGetter* browser_url_request_context_getter) {
+AwSafeBrowsingUIManager::AwSafeBrowsingUIManager() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   // TODO(timvolodine): verify this is what we want regarding the directory.
@@ -60,15 +57,9 @@
                                        &user_data_dir);
   DCHECK(result);
 
-  if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-    url_request_context_getter_ =
-        new safe_browsing::SafeBrowsingURLRequestContextGetter(
-            browser_url_request_context_getter, user_data_dir);
-  }
-
   network_context_ =
       std::make_unique<safe_browsing::SafeBrowsingNetworkContext>(
-          url_request_context_getter_, user_data_dir,
+          user_data_dir,
           base::BindRepeating(CreateDefaultNetworkContextParams));
 }
 
diff --git a/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.h b/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.h
index 7ca75bd..0d3897e 100644
--- a/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.h
+++ b/android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.h
@@ -20,11 +20,9 @@
 namespace safe_browsing {
 class PingManager;
 class SafeBrowsingNetworkContext;
-class SafeBrowsingURLRequestContextGetter;
 }  // namespace safe_browsing
 
 namespace android_webview {
-class AwURLRequestContextGetter;
 
 class AwSafeBrowsingUIManager : public safe_browsing::BaseUIManager {
  public:
@@ -40,8 +38,7 @@
   };
 
   // Construction needs to happen on the UI thread.
-  AwSafeBrowsingUIManager(
-      AwURLRequestContextGetter* browser_url_request_context_getter);
+  AwSafeBrowsingUIManager();
 
   // Gets the correct ErrorUiType for the web contents
   int GetErrorUiType(const UnsafeResource& resource) const;
@@ -72,16 +69,8 @@
   // Provides phishing and malware statistics. Accessed on IO thread.
   std::unique_ptr<safe_browsing::PingManager> ping_manager_;
 
-  // The SafeBrowsingURLRequestContextGetter used to access
-  // |url_request_context_|. Accessed on UI thread.
-  // This is only valid if the network service is disabled.
-  scoped_refptr<safe_browsing::SafeBrowsingURLRequestContextGetter>
-      url_request_context_getter_;
-
-  // If the network service is disabled, this is a wrapper around
-  // |url_request_context_getter_|. Otherwise it's what owns the
-  // URLRequestContext inside the network service. This is used by
-  // SimpleURLLoader for safe browsing requests.
+  // This is what owns the URLRequestContext inside the network service. This is
+  // used by SimpleURLLoader for Safe Browsing requests.
   std::unique_ptr<safe_browsing::SafeBrowsingNetworkContext> network_context_;
 
   // A SharedURLLoaderFactory and its interfaceptr used on the IO thread.
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 375c31d..a5bca535 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -70,6 +70,7 @@
     // TODO(gsennton): store aw-objects instead of adapters here
     // Initialization guarded by mLock.
     private AwBrowserContext mBrowserContext;
+    private AwTracingController mTracingController;
     private SharedStatics mSharedStatics;
     private GeolocationPermissionsAdapter mGeolocationPermissions;
     private CookieManagerAdapter mCookieManager;
@@ -205,7 +206,7 @@
                 mGeolocationPermissions = new GeolocationPermissionsAdapter(
                         mFactory, awBrowserContext.getGeolocationPermissions());
                 mWebStorage = new WebStorageAdapter(mFactory, AwQuotaManagerBridge.getInstance());
-                mAwTracingController = awBrowserContext.getTracingController();
+                mAwTracingController = getTracingController();
                 mServiceWorkerController = awBrowserContext.getServiceWorkerController();
                 mAwProxyController = new AwProxyController();
             }
@@ -342,6 +343,13 @@
         }
     }
 
+    public AwTracingController getTracingController() {
+        if (mTracingController == null) {
+            mTracingController = new AwTracingController();
+        }
+        return mTracingController;
+    }
+
     // Only on UI thread.
     AwBrowserContext getBrowserContextOnUiThread() {
         assert mStarted;
@@ -352,8 +360,7 @@
         }
 
         if (mBrowserContext == null) {
-            mBrowserContext = new AwBrowserContext(
-                mFactory.getWebViewPrefs(), ContextUtils.getApplicationContext());
+            mBrowserContext = AwBrowserContext.getDefault();
         }
         return mBrowserContext;
     }
diff --git a/android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java b/android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java
index 8f991b6eb..10a0090 100644
--- a/android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java
+++ b/android_webview/java/src/org/chromium/android_webview/AndroidProtocolHandler.java
@@ -203,23 +203,50 @@
             String path = uri.getPath();
             // The content URL type can be queried directly.
             if (uri.getScheme().equals(CONTENT_SCHEME)) {
-                return ContextUtils.getApplicationContext().getContentResolver().getType(uri);
+                String mimeType =
+                        ContextUtils.getApplicationContext().getContentResolver().getType(uri);
+                if (mimeType == null) {
+                    AwHistogramRecorder.recordMimeType(
+                            AwHistogramRecorder.MimeType.NULL_FROM_CONTENT_PROVIDER);
+                } else {
+                    AwHistogramRecorder.recordMimeType(
+                            AwHistogramRecorder.MimeType.NONNULL_FROM_CONTENT_PROVIDER);
+                }
+                return mimeType;
                 // Asset files may have a known extension.
             } else if (uri.getScheme().equals(FILE_SCHEME)
                     && path.startsWith(nativeGetAndroidAssetPath())) {
                 String mimeType = URLConnection.guessContentTypeFromName(path);
-                if (mimeType != null) {
+                if (mimeType == null) {
+                    AwHistogramRecorder.recordMimeType(
+                            AwHistogramRecorder.MimeType.CANNOT_GUESS_FROM_ANDROID_ASSET_PATH);
+                    // Do not return yet, try guessing from the stream.
+                } else {
+                    AwHistogramRecorder.recordMimeType(
+                            AwHistogramRecorder.MimeType.GUESSED_FROM_ANDROID_ASSET_PATH);
                     return mimeType;
                 }
             }
         } catch (Exception ex) {
             Log.e(TAG, "Unable to get mime type" + url);
+            AwHistogramRecorder.recordMimeType(
+                    AwHistogramRecorder.MimeType.CANNOT_GUESS_DUE_TO_GENERIC_EXCEPTION);
             return null;
         }
         // Fall back to sniffing the type from the stream.
         try {
-            return URLConnection.guessContentTypeFromStream(stream);
+            String mimeType = URLConnection.guessContentTypeFromStream(stream);
+            if (mimeType == null) {
+                AwHistogramRecorder.recordMimeType(
+                        AwHistogramRecorder.MimeType.CANNOT_GUESS_FROM_ANDROID_ASSET_INPUT_STREAM);
+            } else {
+                AwHistogramRecorder.recordMimeType(
+                        AwHistogramRecorder.MimeType.GUESSED_FROM_ANDROID_ASSET_INPUT_STREAM);
+            }
+            return mimeType;
         } catch (IOException e) {
+            AwHistogramRecorder.recordMimeType(
+                    AwHistogramRecorder.MimeType.CANNOT_GUESS_DUE_TO_IO_EXCEPTION);
             return null;
         }
     }
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
index 1189c29..50fe5e5 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
@@ -7,30 +7,38 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 
+import org.chromium.base.ContextUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.memory.MemoryPressureMonitor;
 import org.chromium.content_public.browser.ContentViewStatics;
 
 /**
  * Java side of the Browser Context: contains all the java side objects needed to host one
- * browing session (i.e. profile).
+ * browsing session (i.e. profile).
  *
  * Note that historically WebView was running in single process mode, and limitations on renderer
  * process only being able to use a single browser context, currently there can only be one
  * AwBrowserContext instance, so at this point the class mostly exists for conceptual clarity.
  */
+@JNINamespace("android_webview")
 public class AwBrowserContext {
+    private static final String CHROMIUM_PREFS_NAME = "WebViewChromiumPrefs";
+
     private static final String TAG = "AwBrowserContext";
     private final SharedPreferences mSharedPreferences;
 
     private AwGeolocationPermissions mGeolocationPermissions;
     private AwFormDatabase mFormDatabase;
     private AwServiceWorkerController mServiceWorkerController;
-    private AwTracingController mTracingController;
-    private Context mApplicationContext;
 
-    public AwBrowserContext(SharedPreferences sharedPreferences, Context applicationContext) {
+    /** Pointer to the Native-side AwBrowserContext. */
+    private long mNativeAwBrowserContext;
+
+    public AwBrowserContext(SharedPreferences sharedPreferences, long nativeAwBrowserContext) {
+        mNativeAwBrowserContext = nativeAwBrowserContext;
         mSharedPreferences = sharedPreferences;
-        mApplicationContext = applicationContext;
 
         PlatformServiceBridge.getInstance().setSafeBrowsingHandler();
 
@@ -49,6 +57,11 @@
         });
     }
 
+    @VisibleForTesting
+    public void setNativePointer(long nativeAwBrowserContext) {
+        mNativeAwBrowserContext = nativeAwBrowserContext;
+    }
+
     public AwGeolocationPermissions getGeolocationPermissions() {
         if (mGeolocationPermissions == null) {
             mGeolocationPermissions = new AwGeolocationPermissions(mSharedPreferences);
@@ -65,18 +78,12 @@
 
     public AwServiceWorkerController getServiceWorkerController() {
         if (mServiceWorkerController == null) {
-            mServiceWorkerController = new AwServiceWorkerController(mApplicationContext, this);
+            mServiceWorkerController =
+                    new AwServiceWorkerController(ContextUtils.getApplicationContext(), this);
         }
         return mServiceWorkerController;
     }
 
-    public AwTracingController getTracingController() {
-        if (mTracingController == null) {
-            mTracingController = new AwTracingController();
-        }
-        return mTracingController;
-    }
-
     /**
      * @see android.webkit.WebView#pauseTimers()
      */
@@ -90,4 +97,27 @@
     public void resumeTimers() {
         ContentViewStatics.setWebKitSharedTimersSuspended(false);
     }
+
+    public long getNativePointer() {
+        return mNativeAwBrowserContext;
+    }
+
+    private static AwBrowserContext sInstance;
+    public static AwBrowserContext getDefault() {
+        if (sInstance == null) {
+            sInstance = nativeGetDefaultJava();
+        }
+        return sInstance;
+    }
+
+    @CalledByNative
+    public static AwBrowserContext create(long nativeAwBrowserContext) {
+        SharedPreferences sharedPreferences =
+                ContextUtils.getApplicationContext().getSharedPreferences(
+                        CHROMIUM_PREFS_NAME, Context.MODE_PRIVATE);
+
+        return new AwBrowserContext(sharedPreferences, nativeAwBrowserContext);
+    }
+
+    private static native AwBrowserContext nativeGetDefaultJava();
 }
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index cf3b99b..f9f99886 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -369,7 +369,7 @@
     }
 
     private long mNativeAwContents;
-    private final AwBrowserContext mBrowserContext;
+    private AwBrowserContext mBrowserContext;
     private ViewGroup mContainerView;
     private AwFunctor mDrawFunctor;
     private final Context mContext;
@@ -658,6 +658,16 @@
                 mContentsClient.getCallbackHelper().postOnLoadResource(url);
             }
 
+            if (awWebResourceResponse != null) {
+                String mimeType = awWebResourceResponse.getMimeType();
+                if (mimeType == null) {
+                    AwHistogramRecorder.recordMimeType(
+                            AwHistogramRecorder.MimeType.NULL_FROM_SHOULD_INTERCEPT_REQUEST);
+                } else {
+                    AwHistogramRecorder.recordMimeType(
+                            AwHistogramRecorder.MimeType.NONNULL_FROM_SHOULD_INTERCEPT_REQUEST);
+                }
+            }
             if (awWebResourceResponse != null && awWebResourceResponse.getData() == null) {
                 // In this case the intercepted URLRequest job will simulate an empty response
                 // which doesn't trigger the onReceivedError callback. For WebViewClassic
@@ -877,6 +887,7 @@
             InternalAccessDelegate internalAccessAdapter,
             NativeDrawFunctorFactory nativeDrawFunctorFactory, AwContentsClient contentsClient,
             AwSettings settings, DependencyFactory dependencyFactory) {
+        assert browserContext != null;
         try (ScopedSysTraceEvent e1 = ScopedSysTraceEvent.scoped("AwContents.constructor")) {
             mRendererPriority = RendererPriority.HIGH;
             mSettings = settings;
@@ -934,7 +945,7 @@
             setOverScrollMode(mContainerView.getOverScrollMode());
             setScrollBarStyle(mInternalAccessAdapter.super_getScrollBarStyle());
 
-            setNewAwContents(nativeInit(mBrowserContext));
+            setNewAwContents(nativeInit(mBrowserContext.getNativePointer()));
 
             onContainerViewChanged();
         }
@@ -1242,11 +1253,9 @@
 
         mNativeAwContents = newAwContentsPtr;
         updateNativeAwGLFunctor();
-        // TODO(joth): when the native and java counterparts of AwBrowserContext are hooked up to
-        // each other, we should update |mBrowserContext| according to the newly received native
-        // WebContent's browser context.
 
         mWebContents = nativeGetWebContents(mNativeAwContents);
+        mBrowserContext = nativeGetBrowserContext(mNativeAwContents);
 
         mWindowAndroid = getWindowAndroid(mContext);
         mViewAndroidDelegate =
@@ -3917,7 +3926,7 @@
     //  Native methods
     //--------------------------------------------------------------------------------------------
 
-    private static native long nativeInit(AwBrowserContext browserContext);
+    private static native long nativeInit(long browserContextPointer);
     private static native void nativeDestroy(long nativeAwContents);
     private static native boolean nativeHasRequiredHardwareExtensions();
     private static native void nativeSetAwDrawSWFunctionTable(long functionTablePointer);
@@ -3935,6 +3944,7 @@
             InterceptNavigationDelegate navigationInterceptionDelegate,
             AutofillProvider autofillProvider);
     private native WebContents nativeGetWebContents(long nativeAwContents);
+    private native AwBrowserContext nativeGetBrowserContext(long nativeAwContents);
     private native void nativeSetCompositorFrameConsumer(
             long nativeAwContents, long nativeCompositorFrameConsumer);
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwHistogramRecorder.java b/android_webview/java/src/org/chromium/android_webview/AwHistogramRecorder.java
index 499bb221..2fed1a4 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwHistogramRecorder.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwHistogramRecorder.java
@@ -12,7 +12,11 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Collect information about callbacks in Android WebView.
+ * Collect information about Android WebView usage. Adding metrics to this class can be helpful if
+ * you need to log the same metric from different call sites in different Java classes.
+ *
+ * <p>If you only need to log at a single call site, prefer calling {@link RecordHistogram} methods
+ * directly.
  */
 public class AwHistogramRecorder {
     @Retention(RetentionPolicy.SOURCE)
@@ -38,11 +42,37 @@
         int NUM_ENTRIES = 9;
     }
 
-    // not meant to be instantiated
-    private AwHistogramRecorder() {}
-
     public static void recordCallbackInvocation(@WebViewCallbackType int result) {
         RecordHistogram.recordEnumeratedHistogram(
                 "Android.WebView.Callback.Counts", result, WebViewCallbackType.NUM_ENTRIES);
     }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({MimeType.NULL_FROM_CONTENT_PROVIDER, MimeType.NONNULL_FROM_CONTENT_PROVIDER,
+            MimeType.CANNOT_GUESS_FROM_ANDROID_ASSET_PATH, MimeType.GUESSED_FROM_ANDROID_ASSET_PATH,
+            MimeType.CANNOT_GUESS_FROM_ANDROID_ASSET_INPUT_STREAM,
+            MimeType.GUESSED_FROM_ANDROID_ASSET_INPUT_STREAM})
+    public @interface MimeType {
+        // These values are persisted to logs. Entries should not be renumbered and numeric values
+        // should never be reused. Update NUM_ENTRIES when adding more entries.
+        int NULL_FROM_CONTENT_PROVIDER = 0;
+        int NONNULL_FROM_CONTENT_PROVIDER = 1;
+        int CANNOT_GUESS_FROM_ANDROID_ASSET_PATH = 2;
+        int GUESSED_FROM_ANDROID_ASSET_PATH = 3;
+        int CANNOT_GUESS_DUE_TO_GENERIC_EXCEPTION = 4;
+        int CANNOT_GUESS_FROM_ANDROID_ASSET_INPUT_STREAM = 5;
+        int GUESSED_FROM_ANDROID_ASSET_INPUT_STREAM = 6;
+        int CANNOT_GUESS_DUE_TO_IO_EXCEPTION = 7;
+        int NULL_FROM_SHOULD_INTERCEPT_REQUEST = 8;
+        int NONNULL_FROM_SHOULD_INTERCEPT_REQUEST = 9;
+        int NUM_ENTRIES = 10;
+    }
+
+    public static void recordMimeType(@MimeType int type) {
+        RecordHistogram.recordEnumeratedHistogram(
+                "Android.WebView.Mimetype.AppProvided", type, MimeType.NUM_ENTRIES);
+    }
+
+    // not meant to be instantiated
+    private AwHistogramRecorder() {}
 }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
index d187b2b..4011006 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
@@ -99,9 +99,9 @@
         return getActivity();
     }
 
-    public AwBrowserContext createAwBrowserContextOnUiThread(
-            InMemorySharedPreferences prefs, Context appContext) {
-        return new AwBrowserContext(prefs, appContext);
+    public AwBrowserContext createAwBrowserContextOnUiThread(InMemorySharedPreferences prefs) {
+        // Native pointer is initialized later in startBrowserProcess if needed.
+        return new AwBrowserContext(prefs, 0);
     }
 
     public TestDependencyFactory createTestDependencyFactory() {
@@ -133,17 +133,19 @@
         }
         launchActivity(); // The Activity must be launched in order to load native code
         final InMemorySharedPreferences prefs = new InMemorySharedPreferences();
-        final Context appContext = InstrumentationRegistry.getInstrumentation()
-                                           .getTargetContext()
-                                           .getApplicationContext();
         TestThreadUtils.runOnUiThreadBlockingNoException(
-                () -> mBrowserContext = createAwBrowserContextOnUiThread(prefs, appContext));
+                () -> mBrowserContext = createAwBrowserContextOnUiThread(prefs));
     }
 
     public void startBrowserProcess() throws Exception {
         // The Activity must be launched in order for proper webview statics to be setup.
         launchActivity();
         TestThreadUtils.runOnUiThreadBlocking(() -> AwBrowserProcess.start());
+        if (mBrowserContext != null) {
+            TestThreadUtils.runOnUiThreadBlocking(
+                    () -> mBrowserContext.setNativePointer(
+                            AwBrowserContext.getDefault().getNativePointer()));
+        }
     }
 
     public static void enableJavaScriptOnUiThread(final AwContents awContents) {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerStartupTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerStartupTest.java
index 591f74e..9dbbe4c 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerStartupTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerStartupTest.java
@@ -68,10 +68,8 @@
     }
 
     private void startChromiumWithClient(TestAwContentsClient contentsClient) throws Exception {
-        // The activity must be launched in order for proper webview statics to be setup.
-        mActivityTestRule.launchActivity();
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> AwBrowserProcess.start());
-
+        mActivityTestRule.createAwBrowserContext();
+        mActivityTestRule.startBrowserProcess();
         mContentsClient = contentsClient;
         final AwTestContainerView testContainerView =
                 mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java
index a39c51a..cc505a8 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java
@@ -78,9 +78,8 @@
          * sites are malicious
          */
         @Override
-        public AwBrowserContext createAwBrowserContextOnUiThread(
-                InMemorySharedPreferences prefs, Context appContext) {
-            return new MockAwBrowserContext(prefs, appContext);
+        public AwBrowserContext createAwBrowserContextOnUiThread(InMemorySharedPreferences prefs) {
+            return new MockAwBrowserContext(prefs);
         }
     };
 
@@ -201,9 +200,8 @@
      * A fake AwBrowserContext which loads the MockSafeBrowsingApiHandler instead of the real one.
      */
     private static class MockAwBrowserContext extends AwBrowserContext {
-        public MockAwBrowserContext(
-                SharedPreferences sharedPreferences, Context applicationContext) {
-            super(sharedPreferences, applicationContext);
+        public MockAwBrowserContext(SharedPreferences sharedPreferences) {
+            super(sharedPreferences, 0);
             SafeBrowsingApiBridge.setSafeBrowsingHandlerType(MockSafeBrowsingApiHandler.class);
         }
     }
diff --git a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java
index 5b67248b..67a44ac9 100644
--- a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java
+++ b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java
@@ -197,7 +197,8 @@
         SharedPreferences sharedPreferences =
                 getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
         if (mBrowserContext == null) {
-            mBrowserContext = new AwBrowserContext(sharedPreferences, getApplicationContext());
+            mBrowserContext = new AwBrowserContext(
+                    sharedPreferences, AwBrowserContext.getDefault().getNativePointer());
         }
         final AwSettings awSettings =
                 new AwSettings(this /* context */, false /* isAccessFromFileURLsGrantedByDefault */,
diff --git a/ash/public/cpp/tablet_mode_observer.h b/ash/public/cpp/tablet_mode_observer.h
index 99a60cf0..f75a98e 100644
--- a/ash/public/cpp/tablet_mode_observer.h
+++ b/ash/public/cpp/tablet_mode_observer.h
@@ -10,7 +10,6 @@
 namespace ash {
 
 // Used to observe tablet mode changes inside ash. Exported for tests.
-// NOTE: Code in chrome should use TabletModeClientObserver.
 class ASH_PUBLIC_EXPORT TabletModeObserver {
  public:
   // Called when the tablet mode is about to start.
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc
index 93f99339..6eb1a1d 100644
--- a/ash/wm/desks/desks_bar_view.cc
+++ b/ash/wm/desks/desks_bar_view.cc
@@ -22,6 +22,7 @@
 #include "ui/aura/window.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/events/event_observer.h"
+#include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/views/event_monitor.h"
 #include "ui/views/widget/widget.h"
@@ -33,6 +34,9 @@
 
 constexpr int kBarHeight = 104;
 
+// TODO(minch): Migrate this to retrieve the color from AshColorProvider.
+constexpr SkColor kBarColor = SkColorSetA(gfx::kGoogleGrey900, 0xBC);  // 74%
+
 base::string16 GetMiniViewTitle(int mini_view_index) {
   DCHECK_GE(mini_view_index, 0);
   DCHECK_LT(mini_view_index, 4);
@@ -117,7 +121,7 @@
 
   background_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
   background_view_->layer()->SetFillsBoundsOpaquely(false);
-  background_view_->layer()->SetColor(SkColorSetARGB(60, 0, 0, 0));
+  background_view_->layer()->SetColor(kBarColor);
 
   AddChildView(background_view_);
   AddChildView(new_desk_button_);
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 1e1e3cb..556ecc0 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -832,7 +832,7 @@
     return;
 
   // When swiping away overview mode via shelf, windows will get minimized, but
-  // we do not want to create minimized widgets in their place.
+  // we do not want show a mirrored view in this case.
   if (overview_session_->enter_exit_overview_type() ==
       OverviewSession::EnterExitOverviewType::kSwipeFromShelf) {
     return;
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index e24e7ea..e3aa4fe 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -661,8 +661,8 @@
   }
 
   const gfx::Point location = gfx::ToRoundedPoint(location_in_screen);
-    window->layer()->SetOpacity(DragWindowController::GetDragWindowOpacity(
-        root_window_, window, location));
+  window->layer()->SetOpacity(DragWindowController::GetDragWindowOpacity(
+      root_window_, window, location));
   phantoms_for_dragging_->Update(location);
 }
 
diff --git a/ash/wm/overview/scoped_overview_transform_window.cc b/ash/wm/overview/scoped_overview_transform_window.cc
index eddcfe2..2faf5cb 100644
--- a/ash/wm/overview/scoped_overview_transform_window.cc
+++ b/ash/wm/overview/scoped_overview_transform_window.cc
@@ -383,20 +383,19 @@
 
 void ScopedOverviewTransformWindow::UpdateRoundedCorners(bool show,
                                                          bool update_clip) {
-  // Minimized windows have their corners rounded in CaptionContainerView.
-  if (IsMinimized())
-    return;
-
+  // Hide the corners if minimized, CaptionContainerView will handle showing the
+  // rounded corners on the UI.
+  const bool show_corners = show && !IsMinimized();
   // Add the mask which gives the overview item rounded corners, and add the
   // shadow around the window.
   ui::Layer* layer = window_->layer();
   const float scale = layer->transform().Scale2d().x();
-  const gfx::RoundedCornersF radii(show ? kOverviewWindowRoundingDp / scale
-                                        : 0.0f);
+  const gfx::RoundedCornersF radii(
+      show_corners ? kOverviewWindowRoundingDp / scale : 0.0f);
   layer->SetRoundedCornerRadius(radii);
   layer->SetIsFastRoundedCorner(true);
 
-  if (!update_clip || layer->GetAnimator()->is_animating())
+  if (!update_clip || layer->GetAnimator()->is_animating() || IsMinimized())
     return;
 
   const int top_inset = GetTopInset();
diff --git a/base/BUILD.gn b/base/BUILD.gn
index f757bba..b459468 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -17,6 +17,7 @@
 # a bit easier to see which files apply in which cases rather than having a
 # huge sequence of random-looking conditionals.
 
+import("//base/allocator/allocator.gni")
 import("//build/buildflag_header.gni")
 import("//build/config/allocator.gni")
 import("//build/config/arm.gni")
diff --git a/base/allocator/BUILD.gn b/base/allocator/BUILD.gn
index fb234fbf..ac26524 100644
--- a/base/allocator/BUILD.gn
+++ b/base/allocator/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//base/allocator/allocator.gni")
 import("//build/buildflag_header.gni")
 import("//build/config/allocator.gni")
 import("//build/config/compiler/compiler.gni")
@@ -294,6 +295,11 @@
     "USE_ALLOCATOR_SHIM=$use_allocator_shim",
     "USE_NEW_TCMALLOC=$use_new_tcmalloc",
   ]
+  if (use_allocator == "tcmalloc") {
+    flags += [ "USE_TCMALLOC=1" ]
+  } else {
+    flags += [ "USE_TCMALLOC=0" ]
+  }
 }
 
 # Used to shim malloc symbols on Android. see //base/allocator/README.md.
diff --git a/base/allocator/allocator.gni b/base/allocator/allocator.gni
new file mode 100644
index 0000000..9c82dd29
--- /dev/null
+++ b/base/allocator/allocator.gni
@@ -0,0 +1,45 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/sanitizers/sanitizers.gni")
+
+# Temporarily disable tcmalloc on arm64 linux to get rid of compilation errors.
+if (is_android || is_mac || is_ios || is_asan || is_lsan || is_tsan ||
+    is_msan || is_win || is_fuchsia || (is_linux && target_cpu == "arm64")) {
+  _default_allocator = "none"
+} else {
+  _default_allocator = "tcmalloc"
+}
+
+# The debug CRT on Windows has some debug features that are incompatible with
+# the shim. NaCl in particular does seem to link some binaries statically
+# against the debug CRT with "is_nacl=false".
+if ((is_linux || is_android || is_mac ||
+     (is_win && !is_component_build && !is_debug)) && !is_asan && !is_hwasan &&
+    !is_lsan && !is_tsan && !is_msan) {
+  _default_use_allocator_shim = true
+} else {
+  _default_use_allocator_shim = false
+}
+
+declare_args() {
+  # Memory allocator to use. Set to "none" to use default allocator.
+  use_allocator = _default_allocator
+
+  # Causes all the allocations to be routed via allocator_shim.cc.
+  use_allocator_shim = _default_use_allocator_shim
+}
+
+assert(use_allocator == "none" || use_allocator == "tcmalloc")
+
+assert(!is_win || use_allocator == "none", "Tcmalloc doesn't work on Windows.")
+assert(!is_mac || use_allocator == "none", "Tcmalloc doesn't work on macOS.")
+
+assert(!use_allocator_shim || is_linux || is_android || is_win || is_mac,
+       "use_allocator_shim works only on Android, Linux, macOS, and Windows.")
+
+if (is_win && use_allocator_shim) {
+  assert(!is_component_build,
+         "The allocator shim doesn't work for the component build on Windows.")
+}
diff --git a/base/allocator/allocator_check.cc b/base/allocator/allocator_check.cc
index 5fb86467..5ea77ef2 100644
--- a/base/allocator/allocator_check.cc
+++ b/base/allocator/allocator_check.cc
@@ -27,7 +27,7 @@
   // Set by allocator_shim_override_ucrt_symbols_win.h when the
   // shimmed _set_new_mode() is called.
   return g_is_win_shim_layer_initialized;
-#elif defined(OS_LINUX) && defined(USE_TCMALLOC) && \
+#elif defined(OS_LINUX) && BUILDFLAG(USE_TCMALLOC) && \
     !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
 // From third_party/tcmalloc/chromium/src/gperftools/tcmalloc.h.
 // TODO(primiano): replace with an include once base can depend on allocator.
diff --git a/base/allocator/allocator_extension.cc b/base/allocator/allocator_extension.cc
index 232c02a..c05b3d1e 100644
--- a/base/allocator/allocator_extension.cc
+++ b/base/allocator/allocator_extension.cc
@@ -6,7 +6,7 @@
 #include "base/allocator/buildflags.h"
 #include "base/logging.h"
 
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
 #if BUILDFLAG(USE_NEW_TCMALLOC)
 #include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
 #include "third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h"
@@ -22,33 +22,33 @@
 namespace allocator {
 
 void ReleaseFreeMemory() {
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   ::MallocExtension::instance()->ReleaseFreeMemory();
 #endif
 }
 
 bool GetNumericProperty(const char* name, size_t* value) {
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   return ::MallocExtension::instance()->GetNumericProperty(name, value);
 #endif
   return false;
 }
 
 bool SetNumericProperty(const char* name, size_t value) {
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   return ::MallocExtension::instance()->SetNumericProperty(name, value);
 #endif
   return false;
 }
 
 void GetHeapSample(std::string* writer) {
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   ::MallocExtension::instance()->GetHeapSample(writer);
 #endif
 }
 
 bool IsHeapProfilerRunning() {
-#if defined(USE_TCMALLOC) && defined(ENABLE_PROFILING)
+#if BUILDFLAG(USE_TCMALLOC) && defined(ENABLE_PROFILING)
   return ::IsHeapProfilerRunning();
 #endif
   return false;
@@ -56,7 +56,7 @@
 
 void SetHooks(AllocHookFunc alloc_hook, FreeHookFunc free_hook) {
 // TODO(sque): Use allocator shim layer instead.
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   // Make sure no hooks get overwritten.
   auto prev_alloc_hook = MallocHook::SetNewHook(alloc_hook);
   if (alloc_hook)
@@ -69,7 +69,7 @@
 }
 
 int GetCallStack(void** stack, int max_stack_size) {
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   return MallocHook::GetCallerStackTrace(stack, max_stack_size, 0);
 #endif
   return 0;
diff --git a/base/allocator/allocator_shim.cc b/base/allocator/allocator_shim.cc
index e6838ebd..ef42d5ad 100644
--- a/base/allocator/allocator_shim.cc
+++ b/base/allocator/allocator_shim.cc
@@ -9,6 +9,7 @@
 #include <atomic>
 #include <new>
 
+#include "base/allocator/buildflags.h"
 #include "base/atomicops.h"
 #include "base/bits.h"
 #include "base/logging.h"
@@ -351,7 +352,7 @@
 // In the case of tcmalloc we also want to plumb into the glibc hooks
 // to avoid that allocations made in glibc itself (e.g., strdup()) get
 // accidentally performed on the glibc heap instead of the tcmalloc one.
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
 #include "base/allocator/allocator_shim_override_glibc_weak_symbols.h"
 #endif
 
diff --git a/base/allocator/tcmalloc_unittest.cc b/base/allocator/tcmalloc_unittest.cc
index 1cdf965..bd3ab17 100644
--- a/base/allocator/tcmalloc_unittest.cc
+++ b/base/allocator/tcmalloc_unittest.cc
@@ -5,6 +5,7 @@
 #include <stddef.h>
 #include <stdio.h>
 
+#include "base/allocator/buildflags.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/process/process_metrics.h"
@@ -12,7 +13,7 @@
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
 namespace {
 
 using std::min;
diff --git a/base/debug/profiler.cc b/base/debug/profiler.cc
index ba82e4a..3c651a3 100644
--- a/base/debug/profiler.cc
+++ b/base/debug/profiler.cc
@@ -19,7 +19,7 @@
 #endif  // defined(OS_WIN)
 
 // TODO(peria): Enable profiling on Windows.
-#if BUILDFLAG(ENABLE_PROFILING) && !defined(NO_TCMALLOC) && !defined(OS_WIN)
+#if BUILDFLAG(ENABLE_PROFILING) && BUILDFLAG(USE_TCMALLOC) && !defined(OS_WIN)
 
 #if BUILDFLAG(USE_NEW_TCMALLOC)
 #include "third_party/tcmalloc/chromium/src/gperftools/profiler.h"
@@ -33,7 +33,7 @@
 namespace debug {
 
 // TODO(peria): Enable profiling on Windows.
-#if BUILDFLAG(ENABLE_PROFILING) && !defined(NO_TCMALLOC) && !defined(OS_WIN)
+#if BUILDFLAG(ENABLE_PROFILING) && BUILDFLAG(USE_TCMALLOC) && !defined(OS_WIN)
 
 static int profile_count = 0;
 
diff --git a/base/process/memory_linux.cc b/base/process/memory_linux.cc
index 2e3d730..0b67672 100644
--- a/base/process/memory_linux.cc
+++ b/base/process/memory_linux.cc
@@ -18,7 +18,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
 
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
 #if BUILDFLAG(USE_NEW_TCMALLOC)
 #include "third_party/tcmalloc/chromium/src/config.h"
 #include "third_party/tcmalloc/chromium/src/gperftools/tcmalloc.h"
diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc
index 5763432..044bd8d 100644
--- a/base/process/process_metrics_posix.cc
+++ b/base/process/process_metrics_posix.cc
@@ -10,6 +10,7 @@
 #include <sys/time.h>
 #include <unistd.h>
 
+#include "base/allocator/buildflags.h"
 #include "base/logging.h"
 #include "build/build_config.h"
 
@@ -111,7 +112,7 @@
   return stats.size_in_use;
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
   struct mallinfo minfo = mallinfo();
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   return minfo.uordblks;
 #else
   return minfo.hblkhd + minfo.arena;
diff --git a/base/run_loop.cc b/base/run_loop.cc
index f350a429..7c854cc 100644
--- a/base/run_loop.cc
+++ b/base/run_loop.cc
@@ -124,8 +124,11 @@
 }
 
 RunLoop::~RunLoop() {
-  // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235.
-  // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // ~RunLoop() must happen-after the RunLoop is done running but it doesn't
+  // have to be on |sequence_checker_| (it usually is but sometimes it can be a
+  // member of a RefCountedThreadSafe object and be destroyed on another thread
+  // after being quit).
+  DCHECK(!running_);
 }
 
 void RunLoop::Run() {
diff --git a/base/security_unittest.cc b/base/security_unittest.cc
index 9efdcde6..5f90959 100644
--- a/base/security_unittest.cc
+++ b/base/security_unittest.cc
@@ -47,9 +47,9 @@
 }
 
 // TCmalloc, currently supported only by Linux/CrOS, supports malloc limits.
-// - NO_TCMALLOC (should be defined if compiled with use_allocator!="tcmalloc")
+// - USE_TCMALLOC (should be set if compiled with use_allocator=="tcmalloc")
 // - ADDRESS_SANITIZER it has its own memory allocator
-#if defined(OS_LINUX) && !defined(NO_TCMALLOC) && !defined(ADDRESS_SANITIZER)
+#if defined(OS_LINUX) && BUILDFLAG(USE_TCMALLOC) && !defined(ADDRESS_SANITIZER)
 #define MALLOC_OVERFLOW_TEST(function) function
 #else
 #define MALLOC_OVERFLOW_TEST(function) DISABLED_##function
diff --git a/base/trace_event/malloc_dump_provider.cc b/base/trace_event/malloc_dump_provider.cc
index 0077d8b..e89597c 100644
--- a/base/trace_event/malloc_dump_provider.cc
+++ b/base/trace_event/malloc_dump_provider.cc
@@ -87,7 +87,7 @@
   size_t resident_size = 0;
   size_t allocated_objects_size = 0;
   size_t allocated_objects_count = 0;
-#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_TCMALLOC)
   bool res =
       allocator::GetNumericProperty("generic.heap_size", &total_virtual_size);
   DCHECK(res);
diff --git a/build/build_config.h b/build/build_config.h
index 4d1ba77f..07227e5 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -86,12 +86,6 @@
 #define OS_POSIX 1
 #endif
 
-// Use tcmalloc
-#if (defined(OS_WIN) || defined(OS_LINUX) || defined(OS_ANDROID)) && \
-    !defined(NO_TCMALLOC)
-#define USE_TCMALLOC 1
-#endif
-
 // Compiler detection.
 #if defined(__GNUC__)
 #define COMPILER_GCC 1
diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn
index 3eb67d36..8b0501f 100644
--- a/build/config/BUILD.gn
+++ b/build/config/BUILD.gn
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/allocator.gni")
 import("//build/config/c++/c++.gni")
 import("//build/config/chrome_build.gni")
 import("//build/config/chromecast_build.gni")
@@ -21,7 +20,6 @@
 import("//build/config/sanitizers/sanitizers.gni")
 import("//build/config/ui.gni")
 import("//build/toolchain/goma.gni")
-
 if (is_android) {
   import("//build/config/android/abi.gni")
 }
@@ -96,9 +94,6 @@
   if (use_x11) {
     defines += [ "USE_X11=1" ]
   }
-  if (use_allocator != "tcmalloc") {
-    defines += [ "NO_TCMALLOC" ]
-  }
   if (is_asan || is_hwasan || is_lsan || is_tsan || is_msan) {
     defines += [ "MEMORY_TOOL_REPLACES_ALLOCATOR" ]
   }
diff --git a/build/config/allocator.gni b/build/config/allocator.gni
index 611b43e7..32c2652 100644
--- a/build/config/allocator.gni
+++ b/build/config/allocator.gni
@@ -2,57 +2,13 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/sanitizers/sanitizers.gni")
-
-# Temporarily disable tcmalloc on arm64 linux to get rid of compilation errors.
-if (is_android || is_mac || is_ios || is_asan || is_lsan || is_tsan ||
-    is_msan || is_win || is_fuchsia || (is_linux && target_cpu == "arm64")) {
-  _default_allocator = "none"
-} else {
-  _default_allocator = "tcmalloc"
-}
-
-# The debug CRT on Windows has some debug features that are incompatible with
-# the shim. NaCl in particular does seem to link some binaries statically
-# against the debug CRT with "is_nacl=false".
-if ((is_linux || is_android || is_mac ||
-     (is_win && !is_component_build && !is_debug)) && !is_asan && !is_hwasan &&
-    !is_lsan && !is_tsan && !is_msan) {
-  _default_use_allocator_shim = true
-} else {
-  _default_use_allocator_shim = false
-}
-
 declare_args() {
-  # Memory allocator to use. Set to "none" to use default allocator.
-  use_allocator = _default_allocator
-
-  # Causes all the allocations to be routed via allocator_shim.cc.
-  use_allocator_shim = _default_use_allocator_shim
-
   # Partition alloc is included by default except iOS.
+  # TODO(thakis): Move this elsewhere, probably
+  # base/allocator/partition_allocator/buildflags.gni
   use_partition_alloc = !is_ios
 
   # Use the new tcmalloc. It's relevant only when use_allocator == "tcmalloc".
+  # TODO(https://crbug.com/989976): Remove this.
   use_new_tcmalloc = true
 }
-
-if (is_nacl) {
-  # Turn off the build flag for NaCL builds to minimize confusion, as NaCL
-  # doesn't support the heap shim.
-  use_allocator_shim = false
-}
-
-assert(use_allocator == "none" || use_allocator == "tcmalloc")
-
-assert(!is_win || use_allocator == "none", "Tcmalloc doesn't work on Windows.")
-assert(!is_mac || use_allocator == "none", "Tcmalloc doesn't work on macOS.")
-
-assert(
-    !use_allocator_shim || is_linux || is_android || is_win || is_mac,
-    "use_allocator_shim is supported only on Linux, Android, Windows and macOS targets")
-
-if (is_win && use_allocator_shim) {
-  assert(!is_component_build,
-         "The allocator shim doesn't work for the component build on Windows.")
-}
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 1c685a7..ae7bfa2 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8906307421479005200
\ No newline at end of file
+8906280394466493984
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index df25e79c..6d24487 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8906307426525263280
\ No newline at end of file
+8906280210551708592
\ No newline at end of file
diff --git a/build/toolchain/nacl_toolchain.gni b/build/toolchain/nacl_toolchain.gni
index 11404e1e..532a409 100644
--- a/build/toolchain/nacl_toolchain.gni
+++ b/build/toolchain/nacl_toolchain.gni
@@ -50,6 +50,7 @@
 
       # We do not support tcmalloc in the NaCl toolchains.
       use_allocator = "none"
+      use_allocator_shim = false
 
       # We do not support clang code coverage in the NaCl toolchains.
       use_clang_coverage = false
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index 4fcd8024..592b50e 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -664,9 +664,10 @@
   inputs_.corner_radii = corner_radii;
   SetSubtreePropertyChanged();
   SetNeedsCommit();
-  PropertyTrees* property_trees = layer_tree_host_->property_trees();
+  PropertyTrees* property_trees =
+      layer_tree_host_ ? layer_tree_host_->property_trees() : nullptr;
   EffectNode* node = nullptr;
-  if (effect_tree_index() != EffectTree::kInvalidNodeId &&
+  if (property_trees && effect_tree_index() != EffectTree::kInvalidNodeId &&
       (node = property_trees->effect_tree.Node(effect_tree_index()))) {
     node->rounded_corner_bounds =
         gfx::RRectF(EffectiveClipRect(), corner_radii);
diff --git a/chrome/VERSION b/chrome/VERSION
index 9bff7c1..67f2da8c 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=78
 MINOR=0
-BUILD=3872
+BUILD=3873
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
index 8dd6e6b..f0ac0eb3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
@@ -82,6 +82,8 @@
     // Handles overscroll history navigation.
     private NavigationHandler mNavigationHandler;
 
+    private NavigationHandler.ActionDelegate mActionDelegate;
+
     public static SwipeRefreshHandler from(Tab tab) {
         SwipeRefreshHandler handler = get(tab);
         if (handler == null) {
@@ -172,6 +174,7 @@
         if (mNavigationHandler != null) {
             mNavigationHandler.destroy();
             mNavigationHandler = null;
+            mActionDelegate = null;
         }
         setEnabled(false);
     }
@@ -219,7 +222,7 @@
             return mSwipeRefreshLayout.start();
         } else if (type == OverscrollAction.HISTORY_NAVIGATION) {
             if (mNavigationHandler != null) {
-                boolean navigable = navigateForward ? mTab.canGoForward() : mTab.canGoBack();
+                boolean navigable = mActionDelegate.canNavigate(navigateForward);
                 boolean showGlow = navigateForward && !mTab.canGoForward();
                 mNavigationHandler.onDown(); // Simulates the initial onDown event.
                 if (navigable) {
@@ -237,8 +240,8 @@
     private void updateNavigationHandler() {
         if (mNavigationDelegate.isNavigationEnabled(mContainerView)) {
             if (mNavigationHandler == null) {
-                mNavigationHandler = new NavigationHandler(mContainerView,
-                        mNavigationDelegate.createActionDelegate(),
+                mActionDelegate = mNavigationDelegate.createActionDelegate();
+                mNavigationHandler = new NavigationHandler(mContainerView, mActionDelegate,
                         NavigationGlowFactory.forRenderedPage(
                                 mContainerView, mTab.getWebContents()));
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
index ff36281..09c04972 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
@@ -303,6 +303,7 @@
     }
 
     /** Associates a notification ID with the tracked download for future usage. */
+    // TODO(shaktisahu): Find an alternative way after moving to offline content provider.
     public void onNotificationShown(ContentId id, int notificationId) {
         mNotificationIds.put(id, notificationId);
     }
@@ -386,6 +387,10 @@
             return false;
         }
 
+        if (DownloadUtils.shouldAutoOpenDownload(offlineItem.mimeType, true)) {
+            return false;
+        }
+
         return true;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/TabbedActionDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/TabbedActionDelegate.java
index ad5002a..db2de23 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/TabbedActionDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/TabbedActionDelegate.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.gesturenav;
 
+import android.os.Handler;
+
 import org.chromium.chrome.browser.tab.Tab;
 
 /**
@@ -13,6 +15,7 @@
  */
 public class TabbedActionDelegate implements NavigationHandler.ActionDelegate {
     private final Tab mTab;
+    private final Handler mHandler = new Handler();
 
     public TabbedActionDelegate(Tab tab) {
         mTab = tab;
@@ -28,7 +31,10 @@
         if (forward) {
             mTab.goForward();
         } else {
-            mTab.getActivity().onBackPressed();
+            // Perform back action at the next UI thread execution. The back action can
+            // potentially close the tab we're running on, which causes use-after-destroy
+            // exception if the closing operation is performed synchronously.
+            mHandler.post(() -> mTab.getActivity().onBackPressed());
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
index e84645d4..1768b42 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
@@ -66,7 +66,7 @@
 
     @Override
     public String getWebApkPackageName() {
-        return getWebappInfo().webApkPackageName();
+        return getWebApkInfo().webApkPackageName();
     }
 
     @Override
@@ -78,7 +78,7 @@
     @Override
     public void onResumeWithNative() {
         super.onResumeWithNative();
-        AppHooks.get().setDisplayModeForActivity(getWebappInfo().displayMode(), this);
+        AppHooks.get().setDisplayModeForActivity(getWebApkInfo().displayMode(), this);
     }
 
     @Override
@@ -92,7 +92,7 @@
     protected void onDeferredStartupWithStorage(WebappDataStorage storage) {
         super.onDeferredStartupWithStorage(storage);
 
-        WebApkInfo info = (WebApkInfo) getWebappInfo();
+        WebApkInfo info = getWebApkInfo();
         WebApkUma.recordShellApkVersion(info.shellApkVersion(), info.distributor());
 
         mUpdateManager = new WebApkUpdateManager(storage);
@@ -100,6 +100,29 @@
     }
 
     @Override
+    protected void onDeferredStartupWithNullStorage(
+            WebappDisclosureSnackbarController disclosureSnackbarController) {
+        // WebappDataStorage objects are cleared if a user clears Chrome's data. Recreate them
+        // for WebAPKs since we need to store metadata for updates and disclosure notifications.
+        WebappRegistry.getInstance().register(
+                getWebApkInfo().id(), new WebappRegistry.FetchWebappDataStorageCallback() {
+                    @Override
+                    public void onWebappDataStorageRetrieved(WebappDataStorage storage) {
+                        if (isActivityFinishingOrDestroyed()) return;
+
+                        onDeferredStartupWithStorage(storage);
+                        // Set force == true to indicate that we need to show a privacy
+                        // disclosure for the newly installed unbound WebAPKs which
+                        // have no storage yet. We can't simply default to a showing if the
+                        // storage has a default value as we don't want to show this disclosure
+                        // for pre-existing unbound WebAPKs.
+                        disclosureSnackbarController.maybeShowDisclosure(
+                                WebApkActivity.this, storage, true /* force */);
+                    }
+                });
+    }
+
+    @Override
     protected void onUpdatedLastUsedTime(
             WebappDataStorage storage, boolean previouslyLaunched, long previousUsageTimestamp) {
         if (previouslyLaunched) {
@@ -109,7 +132,7 @@
 
     @Override
     public void onPauseWithNative() {
-        WebApkInfo info = (WebApkInfo) getWebappInfo();
+        WebApkInfo info = getWebApkInfo();
         long sessionDuration = SystemClock.elapsedRealtime() - mStartTime;
         WebApkUma.recordWebApkSessionDuration(info.distributor(), sessionDuration);
         WebApkUkmRecorder.recordWebApkSessionDuration(
@@ -130,11 +153,19 @@
         super.onDestroyInternal();
     }
 
+    /**
+     * Returns structure containing data about the WebApk currently displayed.
+     * The return value should not be cached.
+     */
+    public WebApkInfo getWebApkInfo() {
+        return (WebApkInfo) getWebappInfo();
+    }
+
     @Override
     protected boolean handleBackPressed() {
         if (super.handleBackPressed()) return true;
 
-        if (getWebappInfo().isSplashProvidedByWebApk() && isSplashShowing()) {
+        if (getWebApkInfo().isSplashProvidedByWebApk() && isSplashShowing()) {
             // When the WebAPK provides the splash screen, the splash screen activity is stacked
             // underneath the WebAPK. The splash screen finishes itself in
             // {@link Activity#onResume()}. When finishing the WebApkActivity, there is sometimes a
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index d777d04d..029e6ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -482,7 +482,7 @@
         if (storage != null) {
             onDeferredStartupWithStorage(storage);
         } else {
-            onDeferredStartupWithNullStorage();
+            onDeferredStartupWithNullStorage(mDisclosureSnackbarController);
         }
     }
 
@@ -497,27 +497,9 @@
         updateStorage(storage);
     }
 
-    protected void onDeferredStartupWithNullStorage() {
-        if (!mWebappInfo.isForWebApk()) return;
-
-        // WebappDataStorage objects are cleared if a user clears Chrome's data. Recreate them
-        // for WebAPKs since we need to store metadata for updates and disclosure notifications.
-        WebappRegistry.getInstance().register(
-                mWebappInfo.id(), new WebappRegistry.FetchWebappDataStorageCallback() {
-                    @Override
-                    public void onWebappDataStorageRetrieved(WebappDataStorage storage) {
-                        if (isActivityFinishingOrDestroyed()) return;
-
-                        onDeferredStartupWithStorage(storage);
-                        // Set force == true to indicate that we need to show a privacy
-                        // disclosure for the newly installed unbound WebAPKs which
-                        // have no storage yet. We can't simply default to a showing if the
-                        // storage has a default value as we don't want to show this disclosure
-                        // for pre-existing unbound WebAPKs.
-                        mDisclosureSnackbarController.maybeShowDisclosure(
-                                WebappActivity.this, storage, true /* force */);
-                    }
-                });
+    protected void onDeferredStartupWithNullStorage(
+            WebappDisclosureSnackbarController disclosureSnackbarController) {
+        // Overridden in WebApkActivity
     }
 
     @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkReorderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkReorderTest.java
index 390301a..248ae7f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkReorderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkReorderTest.java
@@ -74,6 +74,7 @@
         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
         addBookmark(TEST_TITLE_A, TEST_URL_A);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         BookmarkRow test =
@@ -115,6 +116,7 @@
         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
         addFolder(TEST_TITLE_A);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         View searchButton = mManager.getToolbarForTests().findViewById(R.id.search_menu_id);
@@ -131,11 +133,8 @@
         TestThreadUtils.runOnUiThreadBlocking(searchButton::performClick);
 
         // Callback occurs when Item "test" is selected.
-        CriteriaHelper.pollUiThread(()
-                                            -> mBookmarkActivity.getManagerForTesting()
-                                                       .getToolbarForTests()
-                                                       .isSearching(),
-                "Expected to enter search mode");
+        CriteriaHelper.pollUiThread(
+                () -> mManager.getToolbarForTests().isSearching(), "Expected to enter search mode");
 
         toggleSelectionAndEndAnimation(testId, test);
 
@@ -188,6 +187,7 @@
         expected.add(aId);
         expected.add(googleId);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         // Callback occurs upon changes inside of the bookmark model.
@@ -252,6 +252,7 @@
         expected.add(aId);
         expected.add(testId);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         // Callback occurs upon changes inside of the bookmark model.
@@ -314,6 +315,7 @@
         expected.add(testId);
         expected.add(aId);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         // Callback occurs upon changes inside of the bookmark model.
@@ -357,6 +359,7 @@
     public void testPromoDraggability() throws Exception {
         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         ViewHolder promo = mItemsContainer.findViewHolderForAdapterPosition(0);
@@ -376,6 +379,7 @@
     @MediumTest
     public void testPartnerFolderDraggability() throws Exception {
         BookmarkId testId = addFolderWithPartner(TEST_FOLDER_TITLE);
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         ViewHolder partner = mItemsContainer.findViewHolderForAdapterPosition(2);
@@ -397,6 +401,7 @@
         BookmarkId aId = addBookmark("a", "http://a.com");
         addFolder(TEST_FOLDER_TITLE);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         ViewHolder test = mItemsContainer.findViewHolderForAdapterPosition(1);
@@ -419,6 +424,7 @@
     public void testCannotSelectPromo() throws Exception {
         addFolder(TEST_FOLDER_TITLE);
 
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         View promo = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
@@ -433,6 +439,7 @@
     @MediumTest
     public void testCannotSelectPartner() throws Exception {
         addFolderWithPartner(TEST_FOLDER_TITLE);
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         View partner = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
@@ -448,6 +455,7 @@
     public void testMoveUpMenuItem() throws Exception {
         addBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
         addFolder(TEST_FOLDER_TITLE);
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         View google = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
@@ -473,6 +481,7 @@
     public void testMoveDownMenuItem() throws Exception {
         addBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
         addFolder(TEST_FOLDER_TITLE);
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
@@ -498,6 +507,7 @@
     public void testMoveDownGoneForBottomElement() throws Exception {
         addBookmarkWithPartner(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
         addFolderWithPartner(TEST_FOLDER_TITLE);
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         View google = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
@@ -513,6 +523,7 @@
     public void testMoveUpGoneForTopElement() throws Exception {
         addBookmark(TEST_PAGE_TITLE_GOOGLE, TEST_URL_A);
         addFolder(TEST_FOLDER_TITLE);
+        BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         openBookmarkManager();
 
         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
@@ -533,11 +544,8 @@
         TestThreadUtils.runOnUiThreadBlocking(searchButton::performClick);
 
         // Callback occurs when Item "test" is selected.
-        CriteriaHelper.pollUiThread(()
-                                            -> mBookmarkActivity.getManagerForTesting()
-                                                       .getToolbarForTests()
-                                                       .isSearching(),
-                "Expected to enter search mode");
+        CriteriaHelper.pollUiThread(
+                () -> mManager.getToolbarForTests().isSearching(), "Expected to enter search mode");
 
         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
@@ -554,7 +562,6 @@
         super.openBookmarkManager();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mManager.getDragStateDelegate().setA11yStateForTesting(false);
-            BookmarkPromoHeader.forcePromoStateForTests(BookmarkPromoHeader.PromoState.PROMO_SYNC);
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/tooltip/FeedTooltipTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/tooltip/FeedTooltipTest.java
index 08a5e74a..ac1b879 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/tooltip/FeedTooltipTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/tooltip/FeedTooltipTest.java
@@ -14,6 +14,7 @@
 
 import static org.hamcrest.Matchers.instanceOf;
 
+import android.os.Build;
 import android.support.test.espresso.contrib.RecyclerViewActions;
 import android.support.test.espresso.matcher.RootMatchers;
 import android.support.test.filters.MediumTest;
@@ -30,6 +31,7 @@
 
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
@@ -110,6 +112,8 @@
 
     @Test
     @MediumTest
+    @DisableIf.
+    Build(sdk_is_greater_than = Build.VERSION_CODES.LOLLIPOP, message = "crbug.com/990135")
     @Feature({"FeedNewTabPage"})
     public void testShowTooltip() throws Exception {
         int callCount = mTestObserver.firstCardShownCallback.getCallCount();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcherTest.java
index e43fbbc0..e59c771 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcherTest.java
@@ -292,7 +292,7 @@
         // If the CONTENT_SETTINGS_NUM_TYPES value changes *and* a new value has been exposed on
         // Android, then please update this code block to include a test for your new type.
         // Otherwise, just update count in the assert.
-        Assert.assertEquals(52, ContentSettingsType.CONTENT_SETTINGS_NUM_TYPES);
+        Assert.assertEquals(53, ContentSettingsType.CONTENT_SETTINGS_NUM_TYPES);
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(ContentSettingsType.CONTENT_SETTINGS_TYPE_COOKIES,
                         googleOrigin, ContentSettingValues.DEFAULT, preferenceSource));
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 821d1752..3943910 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -571,7 +571,6 @@
     "//chrome/services/file_util/public/cpp:manifest",
     "//components/services/quarantine",
     "//components/services/quarantine/public/cpp:manifest",
-    "//components/spellcheck/common:interfaces",
     "//components/startup_metric_utils/common:interfaces",
     "//device/vr/buildflags",
     "//extensions/buildflags",
@@ -637,7 +636,6 @@
     "//base",
     "//chrome/common:mojo_bindings",
     "//components/safe_browsing/common:interfaces",
-    "//components/spellcheck/common:interfaces",
     "//services/service_manager/public/cpp",
   ]
 }
diff --git a/chrome/app/DEPS b/chrome/app/DEPS
index 7ae6470..ef53833 100644
--- a/chrome/app/DEPS
+++ b/chrome/app/DEPS
@@ -98,7 +98,6 @@
     "+components/services/pdf_compositor",
     "+components/services/quarantine",
     "+components/services/unzip/public",
-    "+components/spellcheck/common",
     "+components/startup_metric_utils/common",
     "+device/vr/buildflags",
     "+extensions/buildflags",
@@ -106,8 +105,5 @@
     "+services/content/simple_browser",
     "+services/proxy_resolver",
   ],
-  "chrome_renderer_manifest\.cc": [
-    "+components/spellcheck/common",
-  ],
 }
 
diff --git a/chrome/app/builtin_service_manifests.cc b/chrome/app/builtin_service_manifests.cc
index fbc31424..a710e6f 100644
--- a/chrome/app/builtin_service_manifests.cc
+++ b/chrome/app/builtin_service_manifests.cc
@@ -10,7 +10,6 @@
 #include "chrome/common/constants.mojom.h"
 #include "chrome/services/file_util/public/cpp/manifest.h"
 #include "components/services/quarantine/public/cpp/manifest.h"
-#include "components/spellcheck/common/spellcheck.mojom.h"
 #include "components/startup_metric_utils/common/startup_metric.mojom.h"
 #include "device/vr/buildflags/buildflags.h"
 #include "extensions/buildflags/buildflags.h"
@@ -26,10 +25,6 @@
 #include "chromeos/services/secure_channel/public/cpp/manifest.h"
 #endif
 
-#if defined(OS_MACOSX)
-#include "components/spellcheck/common/spellcheck_panel.mojom.h"
-#endif
-
 #if defined(OS_WIN)
 #include "base/feature_list.h"
 #include "chrome/services/wifi_util_win/public/cpp/manifest.h"
@@ -84,10 +79,6 @@
                 .Build())
         .ExposeCapability("renderer",
                           service_manager::Manifest::InterfaceList<
-#if defined(OS_MACOSX)
-                              spellcheck::mojom::SpellCheckPanelHost,
-#endif
-                              spellcheck::mojom::SpellCheckHost,
                               startup_metric_utils::mojom::StartupMetricHost>())
         .RequireCapability(chrome::mojom::kRendererServiceName, "browser")
         .Build()
diff --git a/chrome/app/chrome_renderer_manifest.cc b/chrome/app/chrome_renderer_manifest.cc
index 2bdac3d9..df82fedd 100644
--- a/chrome/app/chrome_renderer_manifest.cc
+++ b/chrome/app/chrome_renderer_manifest.cc
@@ -9,7 +9,6 @@
 #include "chrome/common/constants.mojom.h"
 #include "chrome/common/media/webrtc_logging.mojom.h"
 #include "components/safe_browsing/common/safe_browsing.mojom.h"
-#include "components/spellcheck/common/spellcheck.mojom.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
 
 const service_manager::Manifest& GetChromeRendererManifest() {
@@ -19,8 +18,7 @@
           .ExposeCapability("browser",
                             service_manager::Manifest::InterfaceList<
                                 chrome::mojom::WebRtcLoggingAgent,
-                                safe_browsing::mojom::PhishingModelSetter,
-                                spellcheck::mojom::SpellChecker>())
+                                safe_browsing::mojom::PhishingModelSetter>())
           .RequireCapability(chrome::mojom::kServiceName, "renderer")
           .Build()};
   return *manifest;
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 86fe1c3..eb6cd6b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5637,10 +5637,10 @@
 
   if (has_spellcheck_panel) {
     sources += [
-      "spellchecker/test/spellcheck_content_browser_client.cc",
-      "spellchecker/test/spellcheck_content_browser_client.h",
       "spellchecker/test/spellcheck_mock_panel_host.cc",
       "spellchecker/test/spellcheck_mock_panel_host.h",
+      "spellchecker/test/spellcheck_panel_browsertest_helper.cc",
+      "spellchecker/test/spellcheck_panel_browsertest_helper.h",
     ]
   }
 }
diff --git a/chrome/browser/apps/app_service/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon_factory.cc
index 2f9473a..3a362e5 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory.cc
@@ -114,6 +114,52 @@
   std::move(callback).Run(apps::mojom::IconValue::New());
 }
 
+void RunCallbackWithCompressedDataFromExtension(
+    const extensions::Extension* extension,
+    extensions::ExtensionResource ext_resource,
+    int size_hint_in_dip,
+    int default_icon_resource,
+    bool is_placeholder_icon,
+    apps::IconEffects icon_effects,
+    apps::mojom::Publisher::LoadIconCallback callback) {
+  // Load some component extensions' icons from statically compiled
+  // resources (built into the Chrome binary), and other extensions'
+  // icons (whether component extensions or otherwise) from files on
+  // disk.
+  //
+  // For the kUncompressed case, RunCallbackWithUncompressedImage
+  // calls extensions::ImageLoader::LoadImageAsync, which already handles
+  // that distinction. We can't use LoadImageAsync here, because the
+  // caller has asked for compressed icons (i.e. PNG-formatted data), not
+  // uncompressed (i.e. a gfx::ImageSkia).
+  if (extension && extension->location() == extensions::Manifest::COMPONENT) {
+    int resource_id = 0;
+    const extensions::ComponentExtensionResourceManager* manager =
+        extensions::ExtensionsBrowserClient::Get()
+            ->GetComponentExtensionResourceManager();
+    if (manager &&
+        manager->IsComponentExtensionResource(
+            extension->path(), ext_resource.relative_path(), &resource_id)) {
+      base::StringPiece data =
+          ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
+              resource_id);
+      RunCallbackWithCompressedData(
+          size_hint_in_dip, default_icon_resource, is_placeholder_icon,
+          icon_effects, std::move(callback),
+          std::vector<uint8_t>(data.begin(), data.end()));
+      return;
+    }
+  }
+
+  // Try and load data from the resource file.
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
+      base::BindOnce(&CompressedDataFromResource, std::move(ext_resource)),
+      base::BindOnce(&RunCallbackWithCompressedData, size_hint_in_dip,
+                     default_icon_resource, is_placeholder_icon, icon_effects,
+                     std::move(callback)));
+}
+
 // Like RunCallbackWithCompressedData, but calls "fallback(callback)" if the
 // data is empty.
 void RunCallbackWithCompressedDataWithFallback(
@@ -260,43 +306,10 @@
       }
 
       case apps::mojom::IconCompression::kCompressed: {
-        // Load some component extensions' icons from statically compiled
-        // resources (built into the Chrome binary), and other extensions'
-        // icons (whether component extensions or otherwise) from files on
-        // disk.
-        //
-        // For the kUncompressed case above, RunCallbackWithUncompressedImage
-        // calls extensions::ImageLoader::LoadImageAsync, which already handles
-        // that distinction. We can't use LoadImageAsync here, because the
-        // caller has asked for compressed icons (i.e. PNG-formatted data), not
-        // uncompressed (i.e. a gfx::ImageSkia).
-        if (extension->location() == extensions::Manifest::COMPONENT) {
-          int resource_id = 0;
-          const extensions::ComponentExtensionResourceManager* manager =
-              extensions::ExtensionsBrowserClient::Get()
-                  ->GetComponentExtensionResourceManager();
-          if (manager && manager->IsComponentExtensionResource(
-                             extension->path(), ext_resource.relative_path(),
-                             &resource_id)) {
-            base::StringPiece data =
-                ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
-                    resource_id);
-            RunCallbackWithCompressedData(
-                size_hint_in_dip, default_icon_resource, is_placeholder_icon,
-                icon_effects, std::move(callback),
-                std::vector<uint8_t>(data.begin(), data.end()));
-            return;
-          }
-        }
-
-        // Try and load data from the resource file.
-        base::PostTaskWithTraitsAndReplyWithResult(
-            FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
-            base::BindOnce(&CompressedDataFromResource,
-                           std::move(ext_resource)),
-            base::BindOnce(&RunCallbackWithCompressedData, size_hint_in_dip,
-                           default_icon_resource, is_placeholder_icon,
-                           icon_effects, std::move(callback)));
+        RunCallbackWithCompressedDataFromExtension(
+            extension, std::move(ext_resource), size_hint_in_dip,
+            default_icon_resource, is_placeholder_icon, icon_effects,
+            std::move(callback));
         return;
       }
     }
diff --git a/chrome/browser/apps/app_shim/app_shim_host_manager_mac.mm b/chrome/browser/apps/app_shim/app_shim_host_manager_mac.mm
index 5b0cab8a..f622a98 100644
--- a/chrome/browser/apps/app_shim/app_shim_host_manager_mac.mm
+++ b/chrome/browser/apps/app_shim/app_shim_host_manager_mac.mm
@@ -79,12 +79,6 @@
       std::make_unique<apps::MachBootstrapAcceptor>(name_fragment, this);
   mach_acceptor_->Start();
 
-  // TODO(rsesek): Delete this after a little while, to ensure everything
-  // is cleaned up.
-  base::FilePath mojo_channel_mac_signal_file =
-      user_data_dir.Append(app_mode::kMojoChannelMacSignalFile);
-  base::DeleteFile(mojo_channel_mac_signal_file, false);
-
   // Create a symlink containing the current version string. This allows the
   // shim to load the same framework version as the currently running Chrome
   // process.
diff --git a/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc b/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc
index 91f82d2..17b536d9 100644
--- a/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc
+++ b/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h"
 #include "chrome/browser/apps/app_shim/app_shim_host_mac.h"
 #include "chrome/browser/apps/app_shim/app_shim_host_manager_mac.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/launch_util.h"
@@ -32,7 +33,7 @@
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/ui/extensions/app_launch_params.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
 #include "chrome/browser/ui/user_manager.h"
@@ -320,9 +321,10 @@
   extensions::RecordAppLaunchType(
       extension_misc::APP_LAUNCH_CMD_LINE_APP, extension->GetType());
   if (extension->is_hosted_app()) {
-    OpenApplication(CreateAppLaunchParamsUserContainer(
-        profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
-        extensions::AppLaunchSource::kSourceCommandLine));
+    apps::LaunchService::Get(profile)->OpenApplication(
+        CreateAppLaunchParamsUserContainer(
+            profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+            apps::mojom::AppLaunchSource::kSourceCommandLine));
     return;
   }
   if (files.empty()) {
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
index d138fb6..358cc7b 100644
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
+++ b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
@@ -28,7 +28,6 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/web_contents.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/constants.h"
 #include "third_party/blink/public/mojom/referrer.mojom.h"
 #include "url/origin.h"
@@ -140,15 +139,8 @@
       close_reason == IntentPickerCloseReason::OPEN_APP;
   switch (app_type) {
     case apps::mojom::AppType::kWeb:
-      if (should_launch_app) {
-        const extensions::Extension* extension =
-            extensions::ExtensionRegistry::Get(
-                web_contents->GetBrowserContext())
-                ->GetExtensionById(launch_name,
-                                   extensions::ExtensionRegistry::ENABLED);
-        DCHECK(extension);
-        ReparentWebContentsIntoAppBrowser(web_contents, extension);
-      }
+      if (should_launch_app)
+        ReparentWebContentsIntoAppBrowser(web_contents, launch_name);
       break;
     case apps::mojom::AppType::kUnknown:
       // We reach here if the picker was closed without an app being chosen,
diff --git a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_apitest.cc b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_apitest.cc
index b9b4d193..6e9d1a2 100644
--- a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_apitest.cc
+++ b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_apitest.cc
@@ -18,14 +18,14 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/media_galleries/media_file_system_registry.h"
 #include "chrome/browser/media_galleries/media_galleries_preferences.h"
 #include "chrome/browser/media_galleries/media_galleries_test_util.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_paths.h"
 #include "components/nacl/common/buildflags.h"
 #include "components/storage_monitor/storage_info.h"
@@ -47,7 +47,6 @@
 
 #if BUILDFLAG(ENABLE_NACL)
 #include "base/command_line.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "ppapi/shared_impl/ppapi_switches.h"
 #endif
 
@@ -271,11 +270,11 @@
 
   extensions::ResultCatcher catcher;
   AppLaunchParams params(browser()->profile(), extension->id(),
-                         extensions::LaunchContainer::kLaunchContainerNone,
+                         apps::mojom::LaunchContainer::kLaunchContainerNone,
                          WindowOpenDisposition::NEW_WINDOW,
-                         extensions::AppLaunchSource::kSourceTest);
+                         apps::mojom::AppLaunchSource::kSourceTest);
   params.command_line = *base::CommandLine::ForCurrentProcess();
-  OpenApplication(params);
+  apps::LaunchService::Get(browser()->profile())->OpenApplication(params);
 
   bool result = true;
   if (!catcher.GetNextResult()) {
diff --git a/chrome/browser/apps/platform_apps/app_browsertest.cc b/chrome/browser/apps/platform_apps/app_browsertest.cc
index 0aa59273..6709938 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest.cc
@@ -20,6 +20,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/devtools/devtools_window.h"
@@ -30,7 +31,6 @@
 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
@@ -238,12 +238,12 @@
     }
 
     AppLaunchParams params(browser()->profile(), extension->id(),
-                           extensions::LaunchContainer::kLaunchContainerNone,
+                           apps::mojom::LaunchContainer::kLaunchContainerNone,
                            WindowOpenDisposition::NEW_WINDOW,
-                           extensions::AppLaunchSource::kSourceTest);
+                           apps::mojom::AppLaunchSource::kSourceTest);
     params.command_line = command_line;
     params.current_directory = test_data_dir_;
-    OpenApplication(params);
+    apps::LaunchService::Get(browser()->profile())->OpenApplication(params);
 
     if (!catcher.GetNextResult()) {
       message_ = catcher.message();
@@ -874,10 +874,12 @@
     content::WindowedNotificationObserver app_loaded_observer(
         content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
         content::NotificationService::AllSources());
-    OpenApplication(AppLaunchParams(browser()->profile(), extension->id(),
-                                    LaunchContainer::kLaunchContainerNone,
-                                    WindowOpenDisposition::NEW_WINDOW,
-                                    extensions::AppLaunchSource::kSourceTest));
+    apps::LaunchService::Get(browser()->profile())
+        ->OpenApplication(
+            AppLaunchParams(browser()->profile(), extension->id(),
+                            LaunchContainer::kLaunchContainerNone,
+                            WindowOpenDisposition::NEW_WINDOW,
+                            apps::mojom::AppLaunchSource::kSourceTest));
     app_loaded_observer.Wait();
     window = GetFirstAppWindow();
     ASSERT_TRUE(window);
@@ -1021,10 +1023,12 @@
   ASSERT_TRUE(should_install.seen());
 
   ExtensionTestMessageListener launched_listener("Launched", false);
-  OpenApplication(AppLaunchParams(browser()->profile(), extension->id(),
-                                  LaunchContainer::kLaunchContainerNone,
-                                  WindowOpenDisposition::NEW_WINDOW,
-                                  extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(
+          AppLaunchParams(browser()->profile(), extension->id(),
+                          LaunchContainer::kLaunchContainerNone,
+                          WindowOpenDisposition::NEW_WINDOW,
+                          apps::mojom::AppLaunchSource::kSourceTest));
 
   ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
 }
@@ -1044,10 +1048,12 @@
   ASSERT_TRUE(extension);
 
   ExtensionTestMessageListener launched_listener("Launched", false);
-  OpenApplication(AppLaunchParams(browser()->profile(), extension->id(),
-                                  LaunchContainer::kLaunchContainerNone,
-                                  WindowOpenDisposition::NEW_WINDOW,
-                                  extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(
+          AppLaunchParams(browser()->profile(), extension->id(),
+                          LaunchContainer::kLaunchContainerNone,
+                          WindowOpenDisposition::NEW_WINDOW,
+                          apps::mojom::AppLaunchSource::kSourceTest));
 
   ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
   ASSERT_FALSE(should_not_install.seen());
@@ -1083,10 +1089,12 @@
   ASSERT_TRUE(should_install.seen());
 
   ExtensionTestMessageListener launched_listener("Launched", false);
-  OpenApplication(AppLaunchParams(browser()->profile(), extension->id(),
-                                  LaunchContainer::kLaunchContainerNone,
-                                  WindowOpenDisposition::NEW_WINDOW,
-                                  extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(
+          AppLaunchParams(browser()->profile(), extension->id(),
+                          LaunchContainer::kLaunchContainerNone,
+                          WindowOpenDisposition::NEW_WINDOW,
+                          apps::mojom::AppLaunchSource::kSourceTest));
 
   ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
 }
@@ -1109,10 +1117,12 @@
 
   {
     ExtensionTestMessageListener launched_listener("Launched", false);
-    OpenApplication(AppLaunchParams(browser()->profile(), extension->id(),
-                                    LaunchContainer::kLaunchContainerNone,
-                                    WindowOpenDisposition::NEW_WINDOW,
-                                    extensions::AppLaunchSource::kSourceTest));
+    apps::LaunchService::Get(browser()->profile())
+        ->OpenApplication(
+            AppLaunchParams(browser()->profile(), extension->id(),
+                            LaunchContainer::kLaunchContainerNone,
+                            WindowOpenDisposition::NEW_WINDOW,
+                            apps::mojom::AppLaunchSource::kSourceTest));
     ASSERT_TRUE(launched_listener.WaitUntilSatisfied());
   }
 
@@ -1231,10 +1241,11 @@
   ASSERT_TRUE(registry != NULL);
   registry->AddObserver(this);
 
-  OpenApplication(CreateAppLaunchParamsUserContainer(
-      incognito_profile, file_manager,
-      WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(incognito_profile)
+      ->OpenApplication(CreateAppLaunchParamsUserContainer(
+          incognito_profile, file_manager,
+          WindowOpenDisposition::NEW_FOREGROUND_TAB,
+          apps::mojom::AppLaunchSource::kSourceTest));
 
   while (!base::Contains(opener_app_ids_, file_manager->id())) {
     content::RunAllPendingInMessageLoop();
diff --git a/chrome/browser/apps/platform_apps/app_browsertest_util.cc b/chrome/browser/apps/platform_apps/app_browsertest_util.cc
index ad94228..17097b62 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest_util.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest_util.cc
@@ -9,12 +9,12 @@
 
 #include "base/command_line.h"
 #include "base/strings/stringprintf.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/api/tabs/tabs_api.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
 #include "chrome/browser/ui/apps/chrome_app_delegate.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
 #include "content/public/test/browser_test_utils.h"
@@ -154,17 +154,20 @@
 }
 
 void PlatformAppBrowserTest::LaunchPlatformApp(const Extension* extension) {
-  OpenApplication(AppLaunchParams(browser()->profile(), extension->id(),
-                                  LaunchContainer::kLaunchContainerNone,
-                                  WindowOpenDisposition::NEW_WINDOW,
-                                  extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(
+          AppLaunchParams(browser()->profile(), extension->id(),
+                          LaunchContainer::kLaunchContainerNone,
+                          WindowOpenDisposition::NEW_WINDOW,
+                          apps::mojom::AppLaunchSource::kSourceTest));
 }
 
 void PlatformAppBrowserTest::LaunchHostedApp(const Extension* extension) {
-  OpenApplication(CreateAppLaunchParamsUserContainer(
-      browser()->profile(), extension,
-      WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      extensions::AppLaunchSource::kSourceCommandLine));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(CreateAppLaunchParamsUserContainer(
+          browser()->profile(), extension,
+          WindowOpenDisposition::NEW_FOREGROUND_TAB,
+          apps::mojom::AppLaunchSource::kSourceCommandLine));
 }
 
 WebContents* PlatformAppBrowserTest::GetFirstAppWindowWebContents() {
diff --git a/chrome/browser/apps/platform_apps/app_window_browsertest.cc b/chrome/browser/apps/platform_apps/app_window_browsertest.cc
index 6a4f36d..b878244 100644
--- a/chrome/browser/apps/platform_apps/app_window_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_window_browsertest.cc
@@ -4,11 +4,11 @@
 
 #include "base/run_loop.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
 #include "content/public/test/test_utils.h"
@@ -238,11 +238,12 @@
       test_data_dir_.AppendASCII("platform_apps").AppendASCII("window_api"));
   EXPECT_TRUE(extension);
 
-  OpenApplication(
-      AppLaunchParams(browser()->profile(), extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerNone,
-                      WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(
+          AppLaunchParams(browser()->profile(), extension->id(),
+                          apps::mojom::LaunchContainer::kLaunchContainerNone,
+                          WindowOpenDisposition::NEW_WINDOW,
+                          apps::mojom::AppLaunchSource::kSourceTest));
 
   ExtensionTestMessageListener geometry_listener("ListenGeometryChange", true);
 
diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/browser/background/background_mode_manager.cc
index e68ffc373..3aed251 100644
--- a/chrome/browser/background/background_mode_manager.cc
+++ b/chrome/browser/background/background_mode_manager.cc
@@ -26,6 +26,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/background/background_application_list_model.h"
 #include "chrome/browser/background/background_mode_optimizer.h"
 #include "chrome/browser/browser_process.h"
@@ -45,7 +46,6 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/user_manager.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_switches.h"
@@ -340,9 +340,10 @@
 void BackgroundModeManager::LaunchBackgroundApplication(
     Profile* profile,
     const Extension* extension) {
-  OpenApplication(CreateAppLaunchParamsUserContainer(
-      profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      extensions::AppLaunchSource::kSourceBackground));
+  apps::LaunchService::Get(profile)->OpenApplication(
+      CreateAppLaunchParamsUserContainer(
+          profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+          apps::mojom::AppLaunchSource::kSourceBackground));
 }
 
 // static
diff --git a/chrome/browser/browsing_data/browsing_data_appcache_helper.cc b/chrome/browser/browsing_data/browsing_data_appcache_helper.cc
index c36a096..189a46a 100644
--- a/chrome/browser/browsing_data/browsing_data_appcache_helper.cc
+++ b/chrome/browser/browsing_data/browsing_data_appcache_helper.cc
@@ -18,7 +18,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/storage_usage_info.h"
-#include "content/public/common/content_features.h"
 #include "net/base/completion_once_callback.h"
 #include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
 
@@ -57,12 +56,7 @@
     result.emplace_back(origin, total_size, last_modified);
   }
 
-  if (base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)) {
-    std::move(callback).Run(std::move(result));
-  } else {
-    base::PostTask(FROM_HERE, {BrowserThread::UI},
-                   base::BindOnce(std::move(callback), std::move(result)));
-  }
+  std::move(callback).Run(std::move(result));
 }
 
 }  // namespace
@@ -75,35 +69,6 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!callback.is_null());
 
-  if (base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)) {
-    StartFetchingOnLoaderThread(std::move(callback));
-  } else {
-    base::PostTask(
-        FROM_HERE, {BrowserThread::IO},
-        base::BindOnce(&BrowsingDataAppCacheHelper::StartFetchingOnLoaderThread,
-                       this, std::move(callback)));
-  }
-}
-
-void BrowsingDataAppCacheHelper::DeleteAppCaches(const url::Origin& origin) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)) {
-    DeleteAppCachesOnLoaderThread(origin);
-  } else {
-    base::PostTask(
-        FROM_HERE, {BrowserThread::IO},
-        base::BindOnce(
-            &BrowsingDataAppCacheHelper::DeleteAppCachesOnLoaderThread, this,
-            origin));
-  }
-}
-
-BrowsingDataAppCacheHelper::~BrowsingDataAppCacheHelper() {}
-
-void BrowsingDataAppCacheHelper::StartFetchingOnLoaderThread(
-    FetchCallback callback) {
-  DCHECK(!callback.is_null());
-
   scoped_refptr<content::AppCacheInfoCollection> info_collection =
       new content::AppCacheInfoCollection();
 
@@ -113,12 +78,14 @@
                      info_collection));
 }
 
-void BrowsingDataAppCacheHelper::DeleteAppCachesOnLoaderThread(
-    const url::Origin& origin) {
+void BrowsingDataAppCacheHelper::DeleteAppCaches(const url::Origin& origin) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   appcache_service_->DeleteAppCachesForOrigin(origin,
                                               net::CompletionOnceCallback());
 }
 
+BrowsingDataAppCacheHelper::~BrowsingDataAppCacheHelper() {}
+
 CannedBrowsingDataAppCacheHelper::CannedBrowsingDataAppCacheHelper(
     AppCacheService* appcache_service)
     : BrowsingDataAppCacheHelper(appcache_service) {}
diff --git a/chrome/browser/browsing_data/browsing_data_appcache_helper.h b/chrome/browser/browsing_data/browsing_data_appcache_helper.h
index ae536da..557f2648 100644
--- a/chrome/browser/browsing_data/browsing_data_appcache_helper.h
+++ b/chrome/browser/browsing_data/browsing_data_appcache_helper.h
@@ -41,9 +41,6 @@
   virtual ~BrowsingDataAppCacheHelper();
 
  private:
-  void StartFetchingOnLoaderThread(FetchCallback completion_callback);
-  void DeleteAppCachesOnLoaderThread(const url::Origin& origin);
-
   // Owned by the profile.
   content::AppCacheService* appcache_service_;
 
diff --git a/chrome/browser/captive_portal/captive_portal_browsertest.cc b/chrome/browser/captive_portal/captive_portal_browsertest.cc
index 61547350..86715b2 100644
--- a/chrome/browser/captive_portal/captive_portal_browsertest.cc
+++ b/chrome/browser/captive_portal/captive_portal_browsertest.cc
@@ -16,7 +16,6 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/compiler_specific.h"
-#include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
@@ -67,7 +66,6 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
@@ -83,7 +81,6 @@
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "net/url_request/url_request_status.h"
-#include "services/network/public/cpp/features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using captive_portal::CaptivePortalResult;
@@ -158,12 +155,6 @@
 // captive portal.
 const char* const kInternetConnectedTitle = "Title Of Awesomeness";
 
-BrowserThread::ID GetInterceptorThreadID() {
-  return base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)
-             ? BrowserThread::UI
-             : BrowserThread::IO;
-}
-
 // Creates a server-side redirect for use with the TestServer.
 std::string CreateServerRedirect(const std::string& dest_url) {
   const char* const kServerRedirectBase = "/server-redirect?";
@@ -762,11 +753,11 @@
 
   // Waits for exactly |num_jobs| kMockHttps* requests.
   void WaitForJobs(int num_jobs) {
-    if (BrowserThread::CurrentlyOn(GetInterceptorThreadID())) {
+    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
       SetNumJobsToWaitForOnInterceptorThread(num_jobs);
     } else {
       base::PostTaskWithTraits(
-          FROM_HERE, {GetInterceptorThreadID()},
+          FROM_HERE, {BrowserThread::UI},
           base::BindOnce(
               &CaptivePortalBrowserTest::SetNumJobsToWaitForOnInterceptorThread,
               base::Unretained(this), num_jobs));
@@ -779,7 +770,7 @@
   }
 
   void SetNumJobsToWaitForOnInterceptorThread(int num_jobs) {
-    DCHECK_CURRENTLY_ON(GetInterceptorThreadID());
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
     DCHECK(!num_jobs_to_wait_for_);
 
     int num_ongoing_jobs = static_cast<int>(ongoing_mock_requests_.size());
@@ -800,9 +791,9 @@
   // failure.  The only way to guarantee this is with an earlier call to
   // WaitForJobs, so makes sure there has been a matching WaitForJobs call.
   void FailJobs(int expected_num_jobs) {
-    if (!BrowserThread::CurrentlyOn(GetInterceptorThreadID())) {
+    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
       base::PostTaskWithTraits(
-          FROM_HERE, {GetInterceptorThreadID()},
+          FROM_HERE, {BrowserThread::UI},
           base::BindOnce(&CaptivePortalBrowserTest::FailJobs,
                          base::Unretained(this), expected_num_jobs));
       return;
@@ -821,9 +812,9 @@
   // |expected_num_jobs| behaves just as in FailJobs.
   void FailJobsWithCertError(int expected_num_jobs,
                              const net::SSLInfo& ssl_info) {
-    if (!BrowserThread::CurrentlyOn(GetInterceptorThreadID())) {
+    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
       base::PostTaskWithTraits(
-          FROM_HERE, {GetInterceptorThreadID()},
+          FROM_HERE, {BrowserThread::UI},
           base::BindOnce(&CaptivePortalBrowserTest::FailJobsWithCertError,
                          base::Unretained(this), expected_num_jobs, ssl_info));
       return;
@@ -863,9 +854,9 @@
   // Abandon all active kMockHttps* requests.  |expected_num_jobs|
   // behaves just as in FailJobs.
   void AbandonJobs(int expected_num_jobs) {
-    if (!BrowserThread::CurrentlyOn(GetInterceptorThreadID())) {
+    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
       base::PostTaskWithTraits(
-          FROM_HERE, {GetInterceptorThreadID()},
+          FROM_HERE, {BrowserThread::UI},
           base::BindOnce(&CaptivePortalBrowserTest::AbandonJobs,
                          base::Unretained(this), expected_num_jobs));
       return;
@@ -898,7 +889,7 @@
  protected:
   std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
   std::unique_ptr<base::RunLoop> run_loop_;
-  // Only accessed on the |GetInterceptorThreadID()| thread.
+  // Only accessed on the UI thread.
   int num_jobs_to_wait_for_ = 0;
   std::vector<content::URLLoaderInterceptor::RequestParams>
       ongoing_mock_requests_;
@@ -942,7 +933,7 @@
     content::URLLoaderInterceptor::RequestParams* params) {
   if (params->url_request.url.path() == kMockHttpsBadCertPath &&
       intercept_bad_cert_) {
-    CHECK(BrowserThread::CurrentlyOn(GetInterceptorThreadID()));
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     ongoing_mock_requests_.emplace_back(std::move(*params));
     return true;
   }
@@ -965,7 +956,7 @@
   if (url_string == kMockHttpsUrl || url_string == kMockHttpsUrl2 ||
       url_string == kMockHttpsQuickTimeoutUrl ||
       params->url_request.url.path() == kRedirectToMockHttpsPath) {
-    CHECK(BrowserThread::CurrentlyOn(GetInterceptorThreadID()));
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     if (params->url_request.url.path() == kRedirectToMockHttpsPath) {
       net::RedirectInfo redirect_info;
       redirect_info.new_url = GURL(kMockHttpsUrl);
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 951a3124..bdf21f77 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -383,6 +383,7 @@
 #include "chrome/browser/chrome_browser_main_mac.h"
 #include "services/audio/public/mojom/constants.mojom.h"
 #elif defined(OS_CHROMEOS)
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "chrome/browser/ash_service_registry.h"
 #include "chrome/browser/chromeos/arc/fileapi/arc_content_file_system_backend_delegate.h"
@@ -407,7 +408,6 @@
 #include "chrome/browser/chromeos/system/input_device_settings.h"
 #include "chrome/browser/speech/tts_chromeos.h"
 #include "chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chromeos/constants/chromeos_constants.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -638,6 +638,14 @@
 #include "device/vr/public/mojom/isolated_xr_service.mojom.h"
 #endif
 
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+#include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
+#include "components/spellcheck/common/spellcheck.mojom.h"
+#if BUILDFLAG(HAS_SPELLCHECK_PANEL)
+#include "chrome/browser/spellchecker/spell_check_panel_host_impl.h"
+#endif
+#endif
+
 #if defined(BROWSER_MEDIA_CONTROLS_MENU)
 #include "third_party/blink/public/mojom/media_controls/touchless/media_controls.mojom.h"
 #endif
@@ -1507,8 +1515,7 @@
 #if defined(OS_ANDROID)
   return true;
 #elif defined(OS_CHROMEOS)
-  return TabletModeClient::Get() &&
-         TabletModeClient::Get()->tablet_mode_enabled();
+  return ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode();
 #else
   return false;
 #endif  // defined(OS_ANDROID)
@@ -2342,17 +2349,6 @@
       out_prefs, Profile::FromBrowserContext(browser_context));
 }
 
-bool ChromeContentBrowserClient::AllowAppCacheOnIO(
-    const GURL& manifest_url,
-    const GURL& first_party,
-    content::ResourceContext* context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(!base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI));
-  ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
-  return io_data->GetCookieSettings()->IsCookieAccessAllowed(manifest_url,
-                                                             first_party);
-}
-
 bool ChromeContentBrowserClient::AllowAppCache(
     const GURL& manifest_url,
     const GURL& first_party,
@@ -2432,18 +2428,6 @@
   return allow;
 }
 
-bool ChromeContentBrowserClient::AllowSignedExchangeOnIO(
-    content::ResourceContext* resource_context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(!base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI));
-  // Null-check safe_browsing_service_ as in unit tests |resource_context| is a
-  // MockResourceContext and the cast doesn't work.
-  if (!safe_browsing_service_)
-    return false;
-  ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
-  return io_data->signed_exchange_enabled()->GetValue();
-}
-
 bool ChromeContentBrowserClient::AllowSignedExchange(
     content::BrowserContext* browser_context) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -3876,6 +3860,27 @@
     gpu_binder_registry_.TryBindInterface(interface_name, interface_pipe);
 }
 
+void ChromeContentBrowserClient::BindHostReceiverForRenderer(
+    content::RenderProcessHost* render_process_host,
+    mojo::GenericPendingReceiver receiver) {
+#if BUILDFLAG(ENABLE_SPELLCHECK)
+  if (auto host_receiver = receiver.As<spellcheck::mojom::SpellCheckHost>()) {
+    SpellCheckHostChromeImpl::Create(render_process_host->GetID(),
+                                     std::move(host_receiver));
+    return;
+  }
+
+#if BUILDFLAG(HAS_SPELLCHECK_PANEL)
+  if (auto panel_host_receiver =
+          receiver.As<spellcheck::mojom::SpellCheckPanelHost>()) {
+    SpellCheckPanelHostImpl::Create(render_process_host->GetID(),
+                                    std::move(panel_host_receiver));
+    return;
+  }
+#endif  // BUILDFLAG(HAS_SPELLCHECK_PANEL)
+#endif  // BUILDFLAG(ENABLE_SPELLCHECK)
+}
+
 void ChromeContentBrowserClient::WillStartServiceManager() {
 #if defined(OS_WIN)
   if (startup_data_) {
@@ -4516,11 +4521,10 @@
 
 namespace {
 // TODO(jam): move this to a separate file.
-template <class HandlerRegistry>
 class ProtocolHandlerThrottle : public blink::URLLoaderThrottle {
  public:
   explicit ProtocolHandlerThrottle(
-      const HandlerRegistry& protocol_handler_registry)
+      ProtocolHandlerRegistry* protocol_handler_registry)
       : protocol_handler_registry_(protocol_handler_registry) {}
   ~ProtocolHandlerThrottle() override = default;
 
@@ -4546,122 +4550,11 @@
       *url = translated_url;
   }
 
-  HandlerRegistry protocol_handler_registry_;
+  ProtocolHandlerRegistry* protocol_handler_registry_;
 };
 }  // namespace
 
 std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-ChromeContentBrowserClient::CreateURLLoaderThrottlesOnIO(
-    const network::ResourceRequest& request,
-    content::ResourceContext* resource_context,
-    const base::RepeatingCallback<content::WebContents*()>& wc_getter,
-    content::NavigationUIData* navigation_ui_data,
-    int frame_tree_node_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  // TODO(falken): Uncomment out the DCHECK when PlzWorker is moved to the UI
-  // thread.
-  // DCHECK(!base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI));
-
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> result;
-
-  ProfileIOData* io_data = nullptr;
-  // Only set |io_data| if needed, as in unit tests |resource_context| is a
-  // MockResourceContext and the cast doesn't work.
-  if (safe_browsing_service_ ||
-      data_reduction_proxy::params::IsEnabledWithNetworkService()) {
-    io_data = ProfileIOData::FromResourceContext(resource_context);
-  }
-
-  ChromeNavigationUIData* chrome_navigation_ui_data =
-      static_cast<ChromeNavigationUIData*>(navigation_ui_data);
-  if (chrome_navigation_ui_data && io_data &&
-      io_data->data_reduction_proxy_io_data() &&
-      data_reduction_proxy::params::IsEnabledWithNetworkService()) {
-    if (!data_reduction_proxy_throttle_manager_) {
-      data_reduction_proxy_throttle_manager_ = std::unique_ptr<
-          data_reduction_proxy::DataReductionProxyThrottleManager,
-          base::OnTaskRunnerDeleter>(
-          new data_reduction_proxy::DataReductionProxyThrottleManager(
-              io_data->data_reduction_proxy_io_data(),
-              io_data->data_reduction_proxy_io_data()->CreateThrottleConfig()),
-          base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
-    }
-    net::HttpRequestHeaders headers;
-    data_reduction_proxy::DataReductionProxyRequestOptions* request_options =
-        io_data->data_reduction_proxy_io_data()->request_options();
-    uint64_t page_id =
-        base::FeatureList::IsEnabled(
-            data_reduction_proxy::features::
-                kDataReductionProxyPopulatePreviewsPageIDToPingback)
-            ? chrome_navigation_ui_data->data_reduction_proxy_page_id()
-            : request_options->GeneratePageId();
-    request_options->AddPageIDRequestHeader(&headers, page_id);
-    result.push_back(std::make_unique<
-                     data_reduction_proxy::DataReductionProxyURLLoaderThrottle>(
-        headers, data_reduction_proxy_throttle_manager_.get()));
-  }
-
-#if BUILDFLAG(SAFE_BROWSING_DB_LOCAL) || BUILDFLAG(SAFE_BROWSING_DB_REMOTE)
-  if (io_data && safe_browsing_service_) {
-    bool matches_enterprise_whitelist = safe_browsing::IsURLWhitelistedByPolicy(
-        request.url, io_data->safe_browsing_whitelist_domains());
-    if (!matches_enterprise_whitelist) {
-      result.push_back(safe_browsing::BrowserURLLoaderThrottle::Create(
-          base::BindOnce(
-              &ChromeContentBrowserClient::GetSafeBrowsingUrlCheckerDelegate,
-              base::Unretained(this)),
-          wc_getter, frame_tree_node_id, resource_context));
-    }
-  }
-#endif
-
-  if (chrome_navigation_ui_data &&
-      chrome_navigation_ui_data->prerender_mode() != prerender::NO_PRERENDER) {
-    result.push_back(std::make_unique<prerender::PrerenderURLLoaderThrottle>(
-        chrome_navigation_ui_data->prerender_mode(),
-        chrome_navigation_ui_data->prerender_histogram_prefix(),
-        base::BindOnce(GetPrerenderCanceller, wc_getter),
-        base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI})));
-  }
-
-  if (io_data) {
-    bool is_off_the_record = io_data->IsOffTheRecord();
-    bool is_signed_in =
-        !is_off_the_record &&
-        !io_data->google_services_account_id()->GetValue().empty();
-
-    chrome::mojom::DynamicParams dynamic_params = {
-        io_data->force_google_safesearch()->GetValue(),
-        io_data->force_youtube_restrict()->GetValue(),
-        io_data->allowed_domains_for_apps()->GetValue(),
-        variations::VariationsHttpHeaderProvider::GetInstance()
-            ->GetClientDataHeader(is_signed_in)};
-    result.push_back(std::make_unique<GoogleURLLoaderThrottle>(
-        is_off_the_record, std::move(dynamic_params)));
-
-    result.push_back(
-        std::make_unique<ProtocolHandlerThrottle<
-            scoped_refptr<ProtocolHandlerRegistry::IOThreadDelegate>>>(
-            io_data->protocol_handler_registry_io_thread_delegate()));
-  }
-
-#if BUILDFLAG(ENABLE_PLUGINS)
-  result.push_back(std::make_unique<PluginResponseInterceptorURLLoaderThrottle>(
-      resource_context, request.resource_type, frame_tree_node_id));
-#endif
-
-  auto delegate =
-      std::make_unique<signin::HeaderModificationDelegateOnIOThreadImpl>(
-          resource_context);
-  auto signin_throttle = signin::URLLoaderThrottle::MaybeCreate(
-      std::move(delegate), navigation_ui_data, wc_getter);
-  if (signin_throttle)
-    result.push_back(std::move(signin_throttle));
-
-  return result;
-}
-
-std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
 ChromeContentBrowserClient::CreateURLLoaderThrottles(
     const network::ResourceRequest& request,
     content::BrowserContext* browser_context,
@@ -4739,10 +4632,8 @@
   result.push_back(std::make_unique<GoogleURLLoaderThrottle>(
       is_off_the_record, std::move(dynamic_params)));
 
-  result.push_back(
-      std::make_unique<ProtocolHandlerThrottle<ProtocolHandlerRegistry*>>(
-          ProtocolHandlerRegistryFactory::GetForBrowserContext(
-              browser_context)));
+  result.push_back(std::make_unique<ProtocolHandlerThrottle>(
+      ProtocolHandlerRegistryFactory::GetForBrowserContext(browser_context)));
 
 #if BUILDFLAG(ENABLE_PLUGINS)
   result.push_back(std::make_unique<PluginResponseInterceptorURLLoaderThrottle>(
@@ -5267,24 +5158,6 @@
   return content::OverlayWindow::Create(controller);
 }
 
-bool ChromeContentBrowserClient::IsSafeRedirectTargetOnIO(
-    const GURL& url,
-    content::ResourceContext* context) {
-  DCHECK(!base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI));
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  if (url.SchemeIs(extensions::kExtensionScheme)) {
-    ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
-    const Extension* extension =
-        io_data->GetExtensionInfoMap()->extensions().GetByID(url.host());
-    if (!extension)
-      return false;
-    return extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
-        extension, url.path());
-  }
-#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
-  return true;
-}
-
 bool ChromeContentBrowserClient::IsSafeRedirectTarget(
     const GURL& url,
     content::BrowserContext* context) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 1825e4d..0b9c225 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -217,9 +217,6 @@
   void UpdateRendererPreferencesForWorker(
       content::BrowserContext* browser_context,
       blink::mojom::RendererPreferences* out_prefs) override;
-  bool AllowAppCacheOnIO(const GURL& manifest_url,
-                         const GURL& first_party,
-                         content::ResourceContext* context) override;
   bool AllowAppCache(const GURL& manifest_url,
                      const GURL& first_party,
                      content::BrowserContext* context) override;
@@ -236,8 +233,6 @@
                          content::BrowserContext* context,
                          int render_process_id,
                          int render_frame_id) override;
-  bool AllowSignedExchangeOnIO(
-      content::ResourceContext* resource_context) override;
   bool AllowSignedExchange(content::BrowserContext* browser_context) override;
   void AllowWorkerFileSystem(
       const GURL& url,
@@ -397,6 +392,9 @@
       const service_manager::BindSourceInfo& source_info,
       const std::string& interface_name,
       mojo::ScopedMessagePipeHandle* interface_pipe) override;
+  void BindHostReceiverForRenderer(
+      content::RenderProcessHost* render_process_host,
+      mojo::GenericPendingReceiver receiver) override;
   void WillStartServiceManager() override;
   void RunServiceInstance(
       const service_manager::Identity& identity,
@@ -439,13 +437,6 @@
   base::FilePath GetLoggingFileName(
       const base::CommandLine& command_line) override;
   std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-  CreateURLLoaderThrottlesOnIO(
-      const network::ResourceRequest& request,
-      content::ResourceContext* resource_context,
-      const base::RepeatingCallback<content::WebContents*()>& wc_getter,
-      content::NavigationUIData* navigation_ui_data,
-      int frame_tree_node_id) override;
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
   CreateURLLoaderThrottles(
       const network::ResourceRequest& request,
       content::BrowserContext* browser_context,
@@ -542,8 +533,6 @@
       network::mojom::URLLoaderFactoryPtr* out_factory) override;
   std::unique_ptr<content::OverlayWindow> CreateWindowForPictureInPicture(
       content::PictureInPictureWindowController* controller) override;
-  bool IsSafeRedirectTargetOnIO(const GURL& url,
-                                content::ResourceContext* context) override;
   bool IsSafeRedirectTarget(const GURL& url,
                             content::BrowserContext* context) override;
   void RegisterRendererPreferenceWatcher(
diff --git a/chrome/browser/chrome_service.cc b/chrome/browser/chrome_service.cc
index 629de36..94edf7b 100644
--- a/chrome/browser/chrome_service.cc
+++ b/chrome/browser/chrome_service.cc
@@ -10,7 +10,6 @@
 #include "base/task/post_task.h"
 #include "chrome/browser/chrome_browser_main_extra_parts.h"
 #include "chrome/common/constants.mojom.h"
-#include "components/spellcheck/spellcheck_buildflags.h"
 #include "components/startup_metric_utils/browser/startup_metric_host_impl.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -23,13 +22,6 @@
 #include "services/service_manager/public/cpp/service.h"
 #include "services/service_manager/public/cpp/service_binding.h"
 
-#if BUILDFLAG(ENABLE_SPELLCHECK)
-#include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
-#if BUILDFLAG(HAS_SPELLCHECK_PANEL)
-#include "chrome/browser/spellchecker/spell_check_panel_host_impl.h"
-#endif
-#endif
-
 class ChromeService::IOThreadContext : public service_manager::Service {
  public:
   IOThreadContext() {
@@ -38,14 +30,6 @@
 
     registry_.AddInterface(base::BindRepeating(
         &startup_metric_utils::StartupMetricHostImpl::Create));
-#if BUILDFLAG(ENABLE_SPELLCHECK)
-    registry_with_source_info_.AddInterface(
-        base::BindRepeating(&SpellCheckHostChromeImpl::Create), ui_task_runner);
-#if BUILDFLAG(HAS_SPELLCHECK_PANEL)
-    registry_.AddInterface(
-        base::BindRepeating(&SpellCheckPanelHostImpl::Create), ui_task_runner);
-#endif
-#endif
   }
   ~IOThreadContext() override = default;
 
@@ -87,7 +71,6 @@
                        const std::string& name,
                        mojo::ScopedMessagePipeHandle handle) override {
     DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-    content::OverrideOnBindInterface(remote_info, name, &handle);
     if (!handle.is_valid())
       return;
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 95a7177b..2735f64 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -684,6 +684,9 @@
     "certificate_provider/certificate_requests.h",
     "certificate_provider/pin_dialog_manager.cc",
     "certificate_provider/pin_dialog_manager.h",
+    "certificate_provider/security_token_pin_dialog_host.h",
+    "certificate_provider/security_token_pin_dialog_host_popup_impl.cc",
+    "certificate_provider/security_token_pin_dialog_host_popup_impl.h",
     "certificate_provider/sign_requests.cc",
     "certificate_provider/sign_requests.h",
     "certificate_provider/thread_safe_certificate_map.cc",
diff --git a/chrome/browser/chromeos/android_sms/android_sms_app_manager_impl.cc b/chrome/browser/chromeos/android_sms/android_sms_app_manager_impl.cc
index e70df52..c0ef2c5 100644
--- a/chrome/browser/chromeos/android_sms/android_sms_app_manager_impl.cc
+++ b/chrome/browser/chromeos/android_sms/android_sms_app_manager_impl.cc
@@ -9,11 +9,11 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/callback.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_app_setup_controller.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_urls.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -44,9 +44,7 @@
 
 content::WebContents* AndroidSmsAppManagerImpl::PwaDelegate::OpenApp(
     const AppLaunchParams& params) {
-  // Note: OpenApplications() is not namespaced and is defined in
-  // application_launch.h.
-  return OpenApplication(params);
+  return apps::LaunchService::Get(params.profile)->OpenApplication(params);
 }
 
 bool AndroidSmsAppManagerImpl::PwaDelegate::TransferItemAttributes(
diff --git a/chrome/browser/chromeos/app_mode/startup_app_launcher.cc b/chrome/browser/chromeos/app_mode/startup_app_launcher.cc
index 9e1086de..ee088d5a 100644
--- a/chrome/browser/chromeos/app_mode/startup_app_launcher.cc
+++ b/chrome/browser/chromeos/app_mode/startup_app_launcher.cc
@@ -13,6 +13,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_diagnosis_runner.h"
 #include "chrome/browser/chromeos/app_mode/startup_app_launcher_update_checker.h"
@@ -22,8 +24,6 @@
 #include "chrome/browser/extensions/install_tracker_factory.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/crx_file/id_util.h"
 #include "components/session_manager/core/session_manager.h"
@@ -427,11 +427,11 @@
   SYSLOG(INFO) << "Attempt to launch app.";
 
   // Always open the app in a window.
-  OpenApplication(
+  apps::LaunchService::Get(profile_)->OpenApplication(
       AppLaunchParams(profile_, extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerWindow,
+                      apps::mojom::LaunchContainer::kLaunchContainerWindow,
                       WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceKiosk));
+                      apps::mojom::AppLaunchSource::kSourceKiosk));
 
   KioskAppManager::Get()->InitSession(profile_, app_id_);
   session_manager::SessionManager::Get()->SessionStarted();
diff --git a/chrome/browser/chromeos/arc/arc_support_host.cc b/chrome/browser/chromeos/arc/arc_support_host.cc
index 22dbaf2d..a4b54285 100644
--- a/chrome/browser/chromeos/arc/arc_support_host.cc
+++ b/chrome/browser/chromeos/arc/arc_support_host.cc
@@ -15,6 +15,7 @@
 #include "base/json/json_writer.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/consent_auditor/consent_auditor_factory.h"
@@ -25,7 +26,6 @@
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/consent_auditor/consent_auditor.h"
@@ -127,9 +127,10 @@
           arc::kPlayStoreAppId);
   DCHECK(extension);
   DCHECK(extensions::util::IsAppLaunchable(arc::kPlayStoreAppId, profile));
-  OpenApplication(CreateAppLaunchParamsUserContainer(
-      profile, extension, WindowOpenDisposition::NEW_WINDOW,
-      extensions::AppLaunchSource::kSourceChromeInternal));
+  apps::LaunchService::Get(profile)->OpenApplication(
+      CreateAppLaunchParamsUserContainer(
+          profile, extension, WindowOpenDisposition::NEW_WINDOW,
+          apps::mojom::AppLaunchSource::kSourceChromeInternal));
 }
 
 std::ostream& operator<<(std::ostream& os, ArcSupportHost::UIPage ui_page) {
diff --git a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc
index 0d16305..8f7c5c0 100644
--- a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc
+++ b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc
@@ -9,6 +9,8 @@
 
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/keyboard/keyboard_switches.h"
+#include "ash/public/cpp/tablet_mode.h"
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/singleton.h"
@@ -20,8 +22,6 @@
 #include "chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_bridge_impl.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.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "chrome/common/pref_names.h"
 #include "components/arc/arc_browser_context_keyed_service_factory_base.h"
 #include "components/arc/common/ime_struct_traits.h"
@@ -194,18 +194,22 @@
 };
 
 class ArcInputMethodManagerService::TabletModeObserver
-    : public TabletModeClientObserver {
+    : public ash::TabletModeObserver {
  public:
   explicit TabletModeObserver(ArcInputMethodManagerService* owner)
       : owner_(owner) {}
   ~TabletModeObserver() override = default;
 
-  void OnTabletModeToggled(bool enabled) override {
+  // ash::TabletModeObserver overrides:
+  void OnTabletModeStarted() override { OnTabletModeToggled(true); }
+  void OnTabletModeEnded() override { OnTabletModeToggled(false); }
+
+ private:
+  void OnTabletModeToggled(bool enabled) {
     owner_->UpdateArcIMEAllowed();
     owner_->NotifyInputMethodManagerObservers(enabled);
   }
 
- private:
   ArcInputMethodManagerService* owner_;
 
   DISALLOW_COPY_AND_ASSIGN(TabletModeObserver);
@@ -253,10 +257,7 @@
       std::make_unique<InputMethodEngineObserver>(this),
       proxy_ime_extension_id_.c_str(), profile_);
 
-  // TabletModeClient should be already created here because it's created in
-  // PreProfileInit() and this service is created in PostProfileInit().
-  DCHECK(TabletModeClient::Get());
-  TabletModeClient::Get()->AddObserver(tablet_mode_observer_.get());
+  ash::TabletMode::Get()->AddObserver(tablet_mode_observer_.get());
 
   chromeos::AccessibilityManager* accessibility_manager =
       chromeos::AccessibilityManager::Get();
@@ -304,8 +305,8 @@
   if (ui::IMEBridge::Get())
     ui::IMEBridge::Get()->RemoveObserver(this);
 
-  if (TabletModeClient::Get())
-    TabletModeClient::Get()->RemoveObserver(tablet_mode_observer_.get());
+  if (ash::TabletMode::Get())
+    ash::TabletMode::Get()->RemoveObserver(tablet_mode_observer_.get());
 
   auto* imm = chromeos::input_method::InputMethodManager::Get();
   imm->RemoveImeMenuObserver(this);
@@ -335,7 +336,6 @@
 }
 
 void ArcInputMethodManagerService::OnImeDisabled(const std::string& ime_id) {
-
   const std::string component_id =
       chromeos::extension_ime_util::GetArcInputMethodID(proxy_ime_extension_id_,
                                                         ime_id);
@@ -743,7 +743,7 @@
   const bool is_normal_vk_enabled =
       !profile_->GetPrefs()->GetBoolean(
           ash::prefs::kAccessibilityVirtualKeyboardEnabled) &&
-      TabletModeClient::Get()->tablet_mode_enabled();
+      ash::TabletMode::Get()->InTabletMode();
   return is_command_line_flag_enabled || is_normal_vk_enabled;
 }
 
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 bcdf699..3eb9d4c 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
@@ -11,6 +11,8 @@
 
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/keyboard/keyboard_switches.h"
+#include "ash/public/cpp/tablet_mode.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
@@ -21,7 +23,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/chromeos/arc/input_method_manager/test_input_method_manager_bridge.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/chrome_ash_test_base.h"
 #include "chrome/test/base/testing_profile.h"
@@ -35,6 +36,7 @@
 #include "ui/base/ime/ime_bridge.h"
 #include "ui/base/ime/mock_ime_input_context_handler.h"
 #include "ui/base/ime/mock_input_method.h"
+#include "ui/views/widget/widget.h"
 
 namespace arc {
 namespace {
@@ -184,7 +186,7 @@
   TestingProfile* profile() { return profile_.get(); }
 
   void ToggleTabletMode(bool enabled) {
-    tablet_mode_client_->OnTabletModeToggled(enabled);
+    ash::ShellTestApi().SetTabletModeEnabledForTest(enabled);
   }
 
   void SetUp() override {
@@ -194,7 +196,10 @@
     chromeos::input_method::InputMethodManager::Initialize(
         input_method_manager_);
     profile_ = std::make_unique<TestingProfile>();
-    tablet_mode_client_ = std::make_unique<TabletModeClient>();
+
+    // Create a test widget so that enabling tablet mode does not show app list
+    // whose search box could mess with the tests.
+    widget_ = CreateTestWidget();
 
     chrome_keyboard_controller_client_test_helper_ =
         ChromeKeyboardControllerClientTestHelper::InitializeForAsh();
@@ -215,17 +220,14 @@
     chromeos::input_method::InputMethodManager::Shutdown();
     ui::IMEBridge::Shutdown();
     ChromeAshTestBase::TearDown();
-    // To match ChromeBrowserMainExtraPartsAsh, shut down the TabletModeClient
-    // after Shell.
-    tablet_mode_client_.reset();
   }
 
  private:
   std::unique_ptr<ArcServiceManager> arc_service_manager_;
   std::unique_ptr<TestingProfile> profile_;
-  std::unique_ptr<TabletModeClient> tablet_mode_client_;
   std::unique_ptr<ChromeKeyboardControllerClientTestHelper>
       chrome_keyboard_controller_client_test_helper_;
+  std::unique_ptr<views::Widget> widget_;
   TestInputMethodManager* input_method_manager_ = nullptr;
   TestInputMethodManagerBridge* test_bridge_ = nullptr;  // Owned by |service_|
   ArcInputMethodManagerService* service_ = nullptr;
diff --git a/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.cc b/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.cc
index 43fe3c9a..adafaa7 100644
--- a/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.cc
+++ b/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.cc
@@ -4,30 +4,9 @@
 
 #include "chrome/browser/chromeos/certificate_provider/pin_dialog_manager.h"
 
-#include "chrome/browser/chromeos/login/ui/login_display_host.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "ui/aura/window.h"
-#include "ui/gfx/native_widget_types.h"
-#include "ui/views/window/dialog_delegate.h"
-
-namespace {
-
-gfx::NativeWindow GetBrowserParentWindow() {
-  if (chromeos::LoginDisplayHost::default_host())
-    return chromeos::LoginDisplayHost::default_host()->GetNativeWindow();
-
-  Browser* browser =
-      chrome::FindTabbedBrowser(ProfileManager::GetPrimaryUserProfile(), true);
-  if (browser)
-    return browser->window()->GetNativeWindow();
-
-  return nullptr;
-}
-
-}  // namespace
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
 
 namespace chromeos {
 
@@ -37,11 +16,7 @@
 
 PinDialogManager::PinDialogManager() = default;
 
-PinDialogManager::~PinDialogManager() {
-  // Close the active dialog if present to avoid leaking callbacks.
-  if (active_pin_dialog_)
-    CloseDialog(active_dialog_extension_id_);
-}
+PinDialogManager::~PinDialogManager() = default;
 
 void PinDialogManager::AddSignRequestId(const std::string& extension_id,
                                         int sign_request_id) {
@@ -54,81 +29,78 @@
     const std::string& extension_id,
     const std::string& extension_name,
     int sign_request_id,
-    RequestPinView::RequestPinCodeType code_type,
-    RequestPinView::RequestPinErrorType error_type,
+    PinCodeType code_type,
+    PinErrorLabel error_label,
     int attempts_left,
     RequestPinCallback callback) {
-  bool accept_input = (attempts_left != 0);
-  // If active dialog exists already, we need to make sure it belongs to the
-  // same extension and the user submitted some input.
-  if (active_pin_dialog_ != nullptr) {
-    DCHECK(!active_dialog_extension_id_.empty());
-    if (extension_id != active_dialog_extension_id_)
+  DCHECK_GE(attempts_left, -1);
+  const bool accept_input = (attempts_left != 0);
+
+  // Start from sanity checks, as the extension might have issued this call
+  // incorrectly.
+  if (active_dialog_state_) {
+    // The active dialog exists already, so we need to make sure it belongs to
+    // the same extension and the user submitted some input.
+    if (extension_id != active_dialog_state_->extension_id)
       return RequestPinResult::kOtherFlowInProgress;
-
-    // Extension requests a PIN without having received any input from its
-    // previous request. Reject the new request.
-    if (!active_pin_dialog_->IsLocked())
+    if (active_dialog_state_->request_pin_callback ||
+        active_dialog_state_->stop_pin_request_callback) {
+      // Extension requests a PIN without having received any input from its
+      // previous request. Reject the new request.
       return RequestPinResult::kDialogDisplayedAlready;
+    }
+  } else {
+    // Check the validity of sign_request_id
+    const ExtensionNameRequestIdPair key(extension_id, sign_request_id);
+    if (sign_request_times_.find(key) == sign_request_times_.end())
+      return RequestPinResult::kInvalidId;
+    const base::Time current_time = base::Time::Now();
+    if (current_time - sign_request_times_[key] > kSignRequestIdTimeout)
+      return RequestPinResult::kInvalidId;
 
-    DCHECK(!active_request_pin_callback_);
-    active_request_pin_callback_ = std::move(callback);
-
-    active_pin_dialog_->SetDialogParameters(code_type, error_type,
-                                            attempts_left, accept_input);
-    active_pin_dialog_->DialogModelChanged();
-    return RequestPinResult::kSuccess;
+    // A new dialog will be opened, so initialize the related internal state.
+    active_dialog_state_.emplace(GetHostForNewDialog(), extension_id,
+                                 extension_name, code_type);
   }
 
-  // Check the validity of sign_request_id
-  const ExtensionNameRequestIdPair key(extension_id, sign_request_id);
-  if (sign_request_times_.find(key) == sign_request_times_.end())
-    return RequestPinResult::kInvalidId;
-
-  const base::Time current_time = base::Time::Now();
-  if (current_time - sign_request_times_[key] > kSignRequestIdTimeout)
-    return RequestPinResult::kInvalidId;
-
-  DCHECK(!active_request_pin_callback_);
-  active_request_pin_callback_ = std::move(callback);
-
-  active_dialog_extension_id_ = extension_id;
-  active_pin_dialog_ =
-      new RequestPinView(extension_name, code_type, attempts_left,
-                         base::BindRepeating(&PinDialogManager::OnPinEntered,
-                                             weak_factory_.GetWeakPtr()),
-                         base::BindOnce(&PinDialogManager::OnViewDestroyed,
-                                        weak_factory_.GetWeakPtr()));
-
-  const gfx::NativeWindow parent = GetBrowserParentWindow();
-  // If there is no parent, falls back to the root window for new windows.
-  active_window_ = views::DialogDelegate::CreateDialogWidget(
-      active_pin_dialog_, /*context=*/ nullptr, parent);
-  active_window_->Show();
+  active_dialog_state_->request_pin_callback = std::move(callback);
+  active_dialog_state_->host->ShowSecurityTokenPinDialog(
+      extension_name, code_type, accept_input, error_label, attempts_left,
+      base::BindOnce(&PinDialogManager::OnPinEntered,
+                     weak_factory_.GetWeakPtr()),
+      base::BindOnce(&PinDialogManager::OnPinDialogClosed,
+                     weak_factory_.GetWeakPtr()));
 
   return RequestPinResult::kSuccess;
 }
 
 PinDialogManager::StopPinRequestResult
-PinDialogManager::StopPinRequestWithError(
-    const std::string& extension_id,
-    RequestPinView::RequestPinErrorType error_type,
-    StopPinRequestCallback callback) {
-  if (active_pin_dialog_ == nullptr ||
-      extension_id != active_dialog_extension_id_) {
+PinDialogManager::StopPinRequestWithError(const std::string& extension_id,
+                                          PinErrorLabel error_label,
+                                          StopPinRequestCallback callback) {
+  DCHECK_NE(error_label, PinErrorLabel::kNone);
+
+  // Perform sanity checks, as the extension might have issued this call
+  // incorrectly.
+  if (!active_dialog_state_ ||
+      active_dialog_state_->extension_id != extension_id) {
     return StopPinRequestResult::kNoActiveDialog;
   }
-
-  if (!active_pin_dialog_->IsLocked())
+  if (active_dialog_state_->request_pin_callback ||
+      active_dialog_state_->stop_pin_request_callback) {
     return StopPinRequestResult::kNoUserInput;
+  }
 
-  DCHECK(!active_stop_pin_request_callback_);
-  active_stop_pin_request_callback_ = std::move(callback);
-  active_pin_dialog_->SetDialogParameters(
-      RequestPinView::RequestPinCodeType::UNCHANGED, error_type,
+  active_dialog_state_->stop_pin_request_callback = std::move(callback);
+  active_dialog_state_->host->ShowSecurityTokenPinDialog(
+      active_dialog_state_->extension_name, active_dialog_state_->code_type,
+      /*enable_user_input=*/false, error_label,
       /*attempts_left=*/-1,
-      /*accept_input=*/false);
-  active_pin_dialog_->DialogModelChanged();
+      base::BindOnce(&PinDialogManager::OnPinEntered,
+                     weak_factory_.GetWeakPtr()),
+      base::BindOnce(&PinDialogManager::OnPinDialogClosed,
+                     weak_factory_.GetWeakPtr()));
+
   return StopPinRequestResult::kSuccess;
 }
 
@@ -139,26 +111,29 @@
 }
 
 bool PinDialogManager::CloseDialog(const std::string& extension_id) {
-  if (extension_id != active_dialog_extension_id_ ||
-      active_pin_dialog_ == nullptr) {
+  // Perform sanity checks, as the extension might have issued this call
+  // incorrectly.
+  if (!active_dialog_state_ ||
+      extension_id != active_dialog_state_->extension_id) {
     LOG(ERROR) << "StopPinRequest called by unexpected extension: "
                << extension_id;
     return false;
   }
 
-  // The view destruction may happen asynchronously after the Close() call. For
-  // simplicity, clear our state and execute the callback immediately.
-  active_window_->Close();
-  if (active_pin_dialog_) {
-    weak_factory_.InvalidateWeakPtrs();
-    OnPinDialogClosed();
-  }
+  active_dialog_state_->host->CloseSecurityTokenPinDialog();
+
+  // The active dialog state should have been cleared by OnPinDialogClosed().
+  DCHECK(!active_dialog_state_);
+
+  last_response_closed_[extension_id] = true;
   return true;
 }
 
 void PinDialogManager::ExtensionUnloaded(const std::string& extension_id) {
-  if (active_pin_dialog_ && active_dialog_extension_id_ == extension_id)
+  if (active_dialog_state_ &&
+      active_dialog_state_->extension_id == extension_id) {
     CloseDialog(extension_id);
+  }
 
   last_response_closed_[extension_id] = false;
 
@@ -171,29 +146,59 @@
   }
 }
 
-void PinDialogManager::OnPinEntered(const std::string& user_input) {
-  DCHECK(!active_stop_pin_request_callback_);
-  last_response_closed_[active_dialog_extension_id_] = false;
-  if (active_request_pin_callback_)
-    std::move(active_request_pin_callback_).Run(user_input);
+void PinDialogManager::AddPinDialogHost(
+    SecurityTokenPinDialogHost* pin_dialog_host) {
+  DCHECK(!base::Contains(added_dialog_hosts_, pin_dialog_host));
+  added_dialog_hosts_.push_back(pin_dialog_host);
 }
 
-void PinDialogManager::OnViewDestroyed() {
-  OnPinDialogClosed();
+void PinDialogManager::RemovePinDialogHost(
+    SecurityTokenPinDialogHost* pin_dialog_host) {
+  if (active_dialog_state_ && active_dialog_state_->host == pin_dialog_host) {
+    pin_dialog_host->CloseSecurityTokenPinDialog();
+    DCHECK(!active_dialog_state_);
+  }
+  DCHECK(base::Contains(added_dialog_hosts_, pin_dialog_host));
+  base::Erase(added_dialog_hosts_, pin_dialog_host);
+}
+
+PinDialogManager::ActiveDialogState::ActiveDialogState(
+    SecurityTokenPinDialogHost* host,
+    const std::string& extension_id,
+    const std::string& extension_name,
+    PinCodeType code_type)
+    : host(host),
+      extension_id(extension_id),
+      extension_name(extension_name),
+      code_type(code_type) {}
+
+PinDialogManager::ActiveDialogState::~ActiveDialogState() = default;
+
+void PinDialogManager::OnPinEntered(const std::string& user_input) {
+  DCHECK(!active_dialog_state_->stop_pin_request_callback);
+  last_response_closed_[active_dialog_state_->extension_id] = false;
+  if (active_dialog_state_->request_pin_callback)
+    std::move(active_dialog_state_->request_pin_callback).Run(user_input);
 }
 
 void PinDialogManager::OnPinDialogClosed() {
-  DCHECK(active_pin_dialog_);
-  DCHECK(!active_request_pin_callback_ || !active_stop_pin_request_callback_);
+  DCHECK(!active_dialog_state_->request_pin_callback ||
+         !active_dialog_state_->stop_pin_request_callback);
 
-  active_pin_dialog_ = nullptr;
-  active_window_ = nullptr;
-  last_response_closed_[active_dialog_extension_id_] = true;
-  active_dialog_extension_id_.clear();
-  if (active_request_pin_callback_)
-    std::move(active_request_pin_callback_).Run(/*user_input=*/std::string());
-  if (active_stop_pin_request_callback_)
-    std::move(active_stop_pin_request_callback_).Run();
+  last_response_closed_[active_dialog_state_->extension_id] = true;
+  if (active_dialog_state_->request_pin_callback) {
+    std::move(active_dialog_state_->request_pin_callback)
+        .Run(/*user_input=*/std::string());
+  }
+  if (active_dialog_state_->stop_pin_request_callback)
+    std::move(active_dialog_state_->stop_pin_request_callback).Run();
+  active_dialog_state_.reset();
+}
+
+SecurityTokenPinDialogHost* PinDialogManager::GetHostForNewDialog() {
+  if (added_dialog_hosts_.empty())
+    return &default_dialog_host_;
+  return added_dialog_hosts_.back();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.h b/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.h
index 29976ea..13f63dc5 100644
--- a/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.h
+++ b/chrome/browser/chromeos/certificate_provider/pin_dialog_manager.h
@@ -9,12 +9,14 @@
 #include <string>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/time/time.h"
-#include "chrome/browser/chromeos/ui/request_pin_view.h"
-#include "ui/views/widget/widget.h"
+#include "chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.h"
+#include "chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.h"
 
 namespace chromeos {
 
@@ -36,6 +38,9 @@
     kNoUserInput,
   };
 
+  using PinCodeType = SecurityTokenPinDialogHost::SecurityTokenPinCodeType;
+  using PinErrorLabel = SecurityTokenPinDialogHost::SecurityTokenPinErrorLabel;
+
   using RequestPinCallback =
       base::OnceCallback<void(const std::string& user_input)>;
   using StopPinRequestCallback = base::OnceClosure;
@@ -48,16 +53,16 @@
   // Stores internally the |signRequestId| along with current timestamp.
   void AddSignRequestId(const std::string& extension_id, int sign_request_id);
 
-  // Creates a new RequestPinView object and displays it in a dialog or reuses
-  // the old dialog if active one exists just updating the parameters.
+  // Creates and displays a new PIN dialog, or reuses the old dialog with just
+  // updating the parameters if active one exists.
   // |extension_id| - the ID of the extension requesting the dialog.
   // |extension_name| - the name of the extension requesting the dialog.
   // |sign_request_id| - the ID given by Chrome when the extension was asked to
   //     sign the data. It should be a valid, not expired ID at the time the
   //     extension is requesting PIN the first time.
   // |code_type| - the type of input requested: either "PIN" or "PUK".
-  // |error_type| - the error template to be displayed inside the dialog. If
-  //     NONE, no error is displayed.
+  // |error_label| - the error template to be displayed inside the dialog. If
+  //     |kNone|, no error is displayed.
   // |attempts_left| - the number of attempts the user has to try the code. It
   //     is informational only, and enforced on Chrome side only in case it's
   //     zero. In that case the textfield is disabled and the user can't provide
@@ -70,18 +75,18 @@
   RequestPinResult RequestPin(const std::string& extension_id,
                               const std::string& extension_name,
                               int sign_request_id,
-                              RequestPinView::RequestPinCodeType code_type,
-                              RequestPinView::RequestPinErrorType error_type,
+                              PinCodeType code_type,
+                              PinErrorLabel error_label,
                               int attempts_left,
                               RequestPinCallback callback);
 
-  // Updates the existing dialog with new error message. Runs |callback| when
-  // user closes the dialog. Returns whether the provided |extension_id| matches
-  // the extension owning the active dialog.
-  StopPinRequestResult StopPinRequestWithError(
-      const std::string& extension_id,
-      RequestPinView::RequestPinErrorType error_type,
-      StopPinRequestCallback callback);
+  // Updates the existing dialog with the error message. Returns whether the
+  // provided |extension_id| matches the extension owning the active dialog.
+  // When it is, the |callback| will be executed once the UI is completed (e.g.,
+  // the dialog with the error message is closed by the user).
+  StopPinRequestResult StopPinRequestWithError(const std::string& extension_id,
+                                               PinErrorLabel error_label,
+                                               StopPinRequestCallback callback);
 
   // Returns whether the last PIN dialog from this extension was closed by the
   // user.
@@ -95,22 +100,53 @@
   // Resets the manager data related to the extension.
   void ExtensionUnloaded(const std::string& extension_id);
 
-  RequestPinView* active_view_for_testing() { return active_pin_dialog_; }
-  views::Widget* active_window_for_testing() { return active_window_; }
+  // Dynamically adds the dialog host that can be used by this instance for
+  // showing new dialogs. There may be multiple hosts added, in which case the
+  // most recently added is used. Before any hosts have been added, the default
+  // (popup-based) host is used.
+  void AddPinDialogHost(SecurityTokenPinDialogHost* pin_dialog_host);
+  // Removes the previously added dialog host. If a dialog is still opened in
+  // this host, closes it beforehand.
+  void RemovePinDialogHost(SecurityTokenPinDialogHost* pin_dialog_host);
+
+  SecurityTokenPinDialogHostPopupImpl* default_dialog_host_for_testing() {
+    return &default_dialog_host_;
+  }
 
  private:
+  // Holds information related to the currently opened PIN dialog.
+  struct ActiveDialogState {
+    ActiveDialogState(SecurityTokenPinDialogHost* host,
+                      const std::string& extension_id,
+                      const std::string& extension_name,
+                      PinCodeType code_type);
+    ~ActiveDialogState();
+
+    // Remember the host that was used to open the active dialog, as new hosts
+    // could have been added since the dialog was opened, but we want to
+    // continue calling the same host when dealing with the same active dialog.
+    SecurityTokenPinDialogHost* const host;
+
+    const std::string extension_id;
+    const std::string extension_name;
+    const PinCodeType code_type;
+    RequestPinCallback request_pin_callback;
+    StopPinRequestCallback stop_pin_request_callback;
+  };
+
   using ExtensionNameRequestIdPair = std::pair<std::string, int>;
 
   // The callback that gets invoked once the user sends some input into the PIN
   // dialog.
   void OnPinEntered(const std::string& user_input);
-  // The callback that gets invoked once the PIN dialog's view gets destroyed.
-  void OnViewDestroyed();
-
-  // Called when the PIN dialog is closed. Cleans up the internal state and runs
-  // the needed callbacks.
+  // The callback that gets invoked once the PIN dialog gets closed.
   void OnPinDialogClosed();
 
+  // Returns the dialog host that should own the new dialog. Currently returns
+  // the most recently added dialog host (falling back to the default one when
+  // no host has been added).
+  SecurityTokenPinDialogHost* GetHostForNewDialog();
+
   // Tells whether user closed the last request PIN dialog issued by an
   // extension. The extension_id is the key and value is true if user closed the
   // dialog. Used to determine if the limit of dialogs rejected by the user has
@@ -121,13 +157,14 @@
   // the time when the id was generated is the value.
   std::map<ExtensionNameRequestIdPair, base::Time> sign_request_times_;
 
-  // There can be only one active dialog to request the PIN at some point in
-  // time. Owned by |active_window_|.
-  RequestPinView* active_pin_dialog_ = nullptr;
-  std::string active_dialog_extension_id_;
-  views::Widget* active_window_ = nullptr;
-  RequestPinCallback active_request_pin_callback_;
-  StopPinRequestCallback active_stop_pin_request_callback_;
+  SecurityTokenPinDialogHostPopupImpl default_dialog_host_;
+  // The list of dynamically added dialog hosts, in the same order as they were
+  // added.
+  std::vector<SecurityTokenPinDialogHost*> added_dialog_hosts_;
+
+  // There can be only one active dialog to request the PIN at any point of
+  // time.
+  base::Optional<ActiveDialogState> active_dialog_state_;
 
   base::WeakPtrFactory<PinDialogManager> weak_factory_{this};
 };
diff --git a/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.h b/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.h
new file mode 100644
index 0000000..01a4aa462
--- /dev/null
+++ b/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.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_CHROMEOS_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_H_
+#define CHROME_BROWSER_CHROMEOS_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+
+namespace chromeos {
+
+// The interface that allows showing a PIN dialog requested by a certificate
+// provider extension (see the chrome.certificateProvider API).
+//
+// Only one dialog is supported at a time by a single host instance.
+class SecurityTokenPinDialogHost {
+ public:
+  // Type of the information requested from the user.
+  enum class SecurityTokenPinCodeType {
+    kPin,
+    kPuk,
+  };
+
+  // Error to be displayed in the dialog.
+  enum class SecurityTokenPinErrorLabel {
+    kNone,
+    kUnknown,
+    kInvalidPin,
+    kInvalidPuk,
+    kMaxAttemptsExceeded,
+  };
+
+  using SecurityTokenPinEnteredCallback =
+      base::OnceCallback<void(const std::string& user_input)>;
+  using SecurityTokenPinDialogClosedCallback = base::OnceClosure;
+
+  // Note that this does NOT execute the callback.
+  virtual ~SecurityTokenPinDialogHost() = default;
+
+  // Shows the PIN dialog, or updates the existing one if it's already shown.
+  //
+  // Note that the caller is still responsible for closing the opened dialog, by
+  // calling ClosePinDialog(), after the |callback| got executed with a
+  // non-empty |user_input|.
+  //
+  // Note also that when the existing dialog is updated, its old callbacks will
+  // NOT be called at all.
+  //
+  // |caller_extension_name| - name of the extension that requested this dialog.
+  // |code_type| - type of the code requested from the user.
+  // |enable_user_input| - when false, the UI will disable the controls that
+  //     allow user to enter the PIN/PUK. MUST be |false| when |attempts_left|
+  //     is zero.
+  // |error_label| - optionally, specifies the error that the UI should display
+  //     (note that a non-empty error does NOT disable the user input per se).
+  // |attempts_left| - when non-negative, the UI should indicate this number to
+  //     the user; otherwise must be equal to -1.
+  // |pin_entered_callback| - will be called when the user submits the input.
+  // |pin_dialog_closed_callback| - will be called when the dialog is closed
+  //     (either by the user or programmatically).
+  virtual void ShowSecurityTokenPinDialog(
+      const std::string& caller_extension_name,
+      SecurityTokenPinCodeType code_type,
+      bool enable_user_input,
+      SecurityTokenPinErrorLabel error_label,
+      int attempts_left,
+      SecurityTokenPinEnteredCallback pin_entered_callback,
+      SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) = 0;
+
+  // Closes the currently shown PIN dialog, if there's any. This will
+  // immediately trigger |pin_dialog_closed_callback|.
+  virtual void CloseSecurityTokenPinDialog() = 0;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_H_
diff --git a/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.cc b/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.cc
new file mode 100644
index 0000000..fb7a168
--- /dev/null
+++ b/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.cc
@@ -0,0 +1,111 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chrome/browser/chromeos/ui/request_pin_view.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace chromeos {
+
+namespace {
+
+gfx::NativeWindow GetBrowserParentWindow() {
+  if (LoginDisplayHost::default_host())
+    return LoginDisplayHost::default_host()->GetNativeWindow();
+
+  Browser* browser =
+      chrome::FindTabbedBrowser(ProfileManager::GetPrimaryUserProfile(), true);
+  if (browser)
+    return browser->window()->GetNativeWindow();
+
+  return nullptr;
+}
+
+}  // namespace
+
+SecurityTokenPinDialogHostPopupImpl::SecurityTokenPinDialogHostPopupImpl() =
+    default;
+
+SecurityTokenPinDialogHostPopupImpl::~SecurityTokenPinDialogHostPopupImpl() =
+    default;
+
+void SecurityTokenPinDialogHostPopupImpl::ShowSecurityTokenPinDialog(
+    const std::string& caller_extension_name,
+    SecurityTokenPinCodeType code_type,
+    bool enable_user_input,
+    SecurityTokenPinErrorLabel error_label,
+    int attempts_left,
+    SecurityTokenPinEnteredCallback pin_entered_callback,
+    SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) {
+  DCHECK(!caller_extension_name.empty());
+  DCHECK(!enable_user_input || attempts_left);
+  DCHECK_GE(attempts_left, -1);
+
+  pin_entered_callback_ = std::move(pin_entered_callback);
+  pin_dialog_closed_callback_ = std::move(pin_dialog_closed_callback);
+
+  if (active_pin_dialog_) {
+    active_pin_dialog_->SetDialogParameters(code_type, error_label,
+                                            attempts_left, enable_user_input);
+    active_pin_dialog_->SetExtensionName(caller_extension_name);
+    active_pin_dialog_->DialogModelChanged();
+  } else {
+    active_pin_dialog_ = new RequestPinView(
+        caller_extension_name, code_type, attempts_left,
+        base::BindRepeating(&SecurityTokenPinDialogHostPopupImpl::OnPinEntered,
+                            weak_ptr_factory_.GetWeakPtr()),
+        base::BindOnce(&SecurityTokenPinDialogHostPopupImpl::OnViewDestroyed,
+                       weak_ptr_factory_.GetWeakPtr()));
+    // If there is no parent, falls back to the root window for new windows.
+    active_window_ = views::DialogDelegate::CreateDialogWidget(
+        active_pin_dialog_, /*context=*/nullptr, GetBrowserParentWindow());
+    active_window_->Show();
+  }
+}
+
+void SecurityTokenPinDialogHostPopupImpl::CloseSecurityTokenPinDialog() {
+  if (!active_pin_dialog_)
+    return;
+  active_window_->Close();
+  // The view destruction may happen asynchronously, so clear our state and
+  // execute the callback immediately in order to follow our own API contract.
+  active_pin_dialog_ = nullptr;
+  active_window_ = nullptr;
+  weak_ptr_factory_.InvalidateWeakPtrs();
+  if (pin_dialog_closed_callback_)
+    std::move(pin_dialog_closed_callback_).Run();
+}
+
+void SecurityTokenPinDialogHostPopupImpl::OnPinEntered(
+    const std::string& user_input) {
+  DCHECK(active_pin_dialog_);
+  DCHECK(active_window_);
+  std::move(pin_entered_callback_).Run(user_input);
+}
+
+void SecurityTokenPinDialogHostPopupImpl::OnViewDestroyed() {
+  DCHECK(active_pin_dialog_);
+  DCHECK(active_window_);
+
+  active_pin_dialog_ = nullptr;
+  active_window_ = nullptr;
+  if (pin_dialog_closed_callback_)
+    std::move(pin_dialog_closed_callback_).Run();
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.h b/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.h
new file mode 100644
index 0000000..2178217a
--- /dev/null
+++ b/chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host_popup_impl.h
@@ -0,0 +1,68 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_POPUP_IMPL_H_
+#define CHROME_BROWSER_CHROMEOS_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_POPUP_IMPL_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.h"
+
+namespace views {
+class Widget;
+}
+
+namespace chromeos {
+
+class RequestPinView;
+
+// The default implementation of the PIN dialog host. It renders the PIN dialog
+// as a popup with the RequestPinView view.
+class SecurityTokenPinDialogHostPopupImpl final
+    : public SecurityTokenPinDialogHost {
+ public:
+  SecurityTokenPinDialogHostPopupImpl();
+  SecurityTokenPinDialogHostPopupImpl(
+      const SecurityTokenPinDialogHostPopupImpl&) = delete;
+  SecurityTokenPinDialogHostPopupImpl& operator=(
+      const SecurityTokenPinDialogHostPopupImpl&) = delete;
+  ~SecurityTokenPinDialogHostPopupImpl() override;
+
+  // SecurityTokenPinDialogHost:
+  void ShowSecurityTokenPinDialog(
+      const std::string& caller_extension_name,
+      SecurityTokenPinCodeType code_type,
+      bool enable_user_input,
+      SecurityTokenPinErrorLabel error_label,
+      int attempts_left,
+      SecurityTokenPinEnteredCallback pin_entered_callback,
+      SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) override;
+  void CloseSecurityTokenPinDialog() override;
+
+  RequestPinView* active_view_for_testing() { return active_pin_dialog_; }
+  views::Widget* active_window_for_testing() { return active_window_; }
+
+ private:
+  // Called every time the user submits some input.
+  void OnPinEntered(const std::string& user_input);
+  // Called when the |active_pin_dialog_| view is being destroyed.
+  void OnViewDestroyed();
+
+  SecurityTokenPinEnteredCallback pin_entered_callback_;
+  SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback_;
+
+  // Owned by |active_window_|.
+  RequestPinView* active_pin_dialog_ = nullptr;
+  // Owned by the UI code (NativeWidget).
+  views::Widget* active_window_ = nullptr;
+
+  base::WeakPtrFactory<SecurityTokenPinDialogHostPopupImpl> weak_ptr_factory_{
+      this};
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_POPUP_IMPL_H_
diff --git a/chrome/browser/chromeos/chrome_content_browser_client_chromeos_part.cc b/chrome/browser/chromeos/chrome_content_browser_client_chromeos_part.cc
index ae99152b..ed2903e 100644
--- a/chrome/browser/chromeos/chrome_content_browser_client_chromeos_part.cc
+++ b/chrome/browser/chromeos/chrome_content_browser_client_chromeos_part.cc
@@ -4,11 +4,11 @@
 
 #include "chrome/browser/chromeos/chrome_content_browser_client_chromeos_part.h"
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/feature_list.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/search.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -73,10 +73,8 @@
 void OverrideWebkitPrefsForTabletMode(content::WebContents* contents,
                                       content::WebPreferences* web_prefs) {
   // Enable some mobile-like behaviors when in tablet mode on Chrome OS.
-  if (!TabletModeClient::Get() ||
-      !TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (!ash::TabletMode::Get() || !ash::TabletMode::Get()->InTabletMode())
     return;
-  }
 
   // Do this only for webcontents displayed in browsers and are not of hosted
   // apps.
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import.cc b/chrome/browser/chromeos/crostini/crostini_export_import.cc
index 3f1fb1f..5fb6ebc 100644
--- a/chrome/browser/chromeos/crostini/crostini_export_import.cc
+++ b/chrome/browser/chromeos/crostini/crostini_export_import.cc
@@ -175,6 +175,9 @@
     return;
   } else {
     notifications_.emplace_hint(it, container_id, notification);
+    for (auto& observer : observers_) {
+      observer.OnCrostiniExportImportOperationStatusChanged(true);
+    }
   }
 
   switch (type) {
@@ -504,6 +507,9 @@
   DCHECK(it != notifications_.end());
   auto& notification = *it->second;
   notifications_.erase(it);
+  for (auto& observer : observers_) {
+    observer.OnCrostiniExportImportOperationStatusChanged(false);
+  }
   return notification;
 }
 
@@ -532,6 +538,11 @@
   }
 }
 
+bool CrostiniExportImport::GetExportImportOperationStatus() const {
+  ContainerId id(kCrostiniDefaultVmName, kCrostiniDefaultContainerName);
+  return notifications_.find(id) != notifications_.end();
+}
+
 CrostiniExportImportNotification*
 CrostiniExportImport::GetNotificationForTesting(ContainerId container_id) {
   auto it = notifications_.find(container_id);
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import.h b/chrome/browser/chromeos/crostini/crostini_export_import.h
index 6a00039..7284cdc7 100644
--- a/chrome/browser/chromeos/crostini/crostini_export_import.h
+++ b/chrome/browser/chromeos/crostini/crostini_export_import.h
@@ -60,11 +60,25 @@
                              public crostini::ExportContainerProgressObserver,
                              public crostini::ImportContainerProgressObserver {
  public:
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called immediately before operation begins with |in_progress|=true, and
+    // again immediately after the operation completes with |in_progress|=false.
+    virtual void OnCrostiniExportImportOperationStatusChanged(
+        bool in_progress) = 0;
+  };
+
   static CrostiniExportImport* GetForProfile(Profile* profile);
 
   explicit CrostiniExportImport(Profile* profile);
   ~CrostiniExportImport() override;
 
+  void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
+
+  void RemoveObserver(Observer* observer) {
+    observers_.RemoveObserver(observer);
+  }
+
   // KeyedService:
   void Shutdown() override;
 
@@ -82,9 +96,12 @@
                        base::FilePath path,
                        CrostiniManager::CrostiniResultCallback callback);
 
-  // Cancel currently running export/import
+  // Cancel currently running export/import.
   void CancelOperation(ExportImportType type, ContainerId id);
 
+  // Whether an export or import is currently in progress.
+  bool GetExportImportOperationStatus() const;
+
   CrostiniExportImportNotification* GetNotificationForTesting(
       ContainerId container_id);
 
@@ -173,6 +190,7 @@
   // Notifications must have unique-per-profile identifiers.
   // A non-static member on a profile-keyed-service will suffice.
   int next_notification_id_;
+  base::ObserverList<Observer> observers_;
   // weak_ptr_factory_ should always be last member.
   base::WeakPtrFactory<CrostiniExportImport> weak_ptr_factory_;
 
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 dedcf49..12f6fc1 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -56,7 +56,6 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_controller.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/views/crostini/crostini_installer_view.h"
 #include "chrome/browser/ui/views/crostini/crostini_uninstaller_view.h"
 #include "chrome/common/chrome_features.h"
@@ -1674,8 +1673,8 @@
 AutotestPrivateIsTabletModeEnabledFunction::Run() {
   DVLOG(1) << "AutotestPrivateIsTabletModeEnabledFunction";
 
-  return RespondNow(OneArgument(std::make_unique<base::Value>(
-      TabletModeClient::Get()->tablet_mode_enabled())));
+  return RespondNow(OneArgument(
+      std::make_unique<base::Value>(ash::TabletMode::Get()->InTabletMode())));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/chromeos/file_manager/file_tasks.cc b/chrome/browser/chromeos/file_manager/file_tasks.cc
index 340d52d5..2d22b6e 100644
--- a/chrome/browser/chromeos/file_manager/file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/file_tasks.cc
@@ -18,6 +18,8 @@
 #include "base/strings/string_split.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/drive/file_system_util.h"
 #include "chrome/browser/chromeos/file_manager/app_id.h"
@@ -32,7 +34,6 @@
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
 #include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
@@ -430,10 +431,10 @@
       AppLaunchParams params(extension_task_profile, task.app_id,
                              launch_container,
                              WindowOpenDisposition::NEW_FOREGROUND_TAB,
-                             extensions::AppLaunchSource::kSourceFileHandler);
+                             apps::mojom::AppLaunchSource::kSourceFileHandler);
       params.override_url = GURL(task.action_id);
       params.launch_files = std::move(paths);
-      OpenApplication(params);
+      apps::LaunchService::Get(extension_task_profile)->OpenApplication(params);
     }
     if (!done.is_null())
       std::move(done).Run(
diff --git a/chrome/browser/chromeos/first_run/first_run.cc b/chrome/browser/chromeos/first_run/first_run.cc
index d433fa7..823d7b81 100644
--- a/chrome/browser/chromeos/first_run/first_run.cc
+++ b/chrome/browser/chromeos/first_run/first_run.cc
@@ -4,9 +4,12 @@
 
 #include "chrome/browser/chromeos/first_run/first_run.h"
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/command_line.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/arc/arc_util.h"
@@ -19,9 +22,6 @@
 #include "chrome/browser/prefs/pref_service_syncable_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/common/pref_names.h"
@@ -58,11 +58,11 @@
   if (!extension)
     return;
 
-  OpenApplication(
+  apps::LaunchService::Get(profile)->OpenApplication(
       AppLaunchParams(profile, extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerWindow,
+                      apps::mojom::LaunchContainer::kLaunchContainerWindow,
                       WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceChromeInternal));
+                      apps::mojom::AppLaunchSource::kSourceChromeInternal));
   profile->GetPrefs()->SetBoolean(prefs::kFirstRunTutorialShown, true);
 }
 
@@ -77,8 +77,8 @@
     return;
   }
 
-  // TabletModeClient does not exist in some tests.
-  if (TabletModeClient::Get() && TabletModeClient::Get()->tablet_mode_enabled())
+  // ash::TabletMode does not exist in some tests.
+  if (ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode())
     return;
 
   if (profile->GetProfilePolicyConnector()->IsManaged())
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_app_launcher.cc b/chrome/browser/chromeos/login/demo_mode/demo_app_launcher.cc
index cbcd6ef..0170865 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_app_launcher.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_app_launcher.cc
@@ -7,14 +7,14 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/grit/browser_resources.h"
@@ -92,11 +92,11 @@
       NetworkTypePattern::Physical(), false,
       chromeos::network_handler::ErrorCallback());
 
-  OpenApplication(AppLaunchParams(
+  apps::LaunchService::Get(profile)->OpenApplication(AppLaunchParams(
       profile, extension_id,
-      extensions::LaunchContainer::kLaunchContainerWindow,
+      apps::mojom::LaunchContainer::kLaunchContainerWindow,
       WindowOpenDisposition::NEW_WINDOW,
-      extensions::AppLaunchSource::kSourceChromeInternal, true));
+      apps::mojom::AppLaunchSource::kSourceChromeInternal, true));
   KioskAppManager::Get()->InitSession(profile, extension_id);
 
   session_manager::SessionManager::Get()->SessionStarted();
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_session.cc b/chrome/browser/chromeos/login/demo_mode/demo_session.cc
index 5cc475ca..fe666a0 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_session.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_session.cc
@@ -22,6 +22,8 @@
 #include "base/system/sys_info.h"
 #include "base/task/post_task.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/app_load_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
@@ -33,8 +35,6 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/system_tray_client.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/tpm/install_attributes.h"
@@ -570,11 +570,11 @@
     return;
   Profile* profile = ProfileManager::GetActiveUserProfile();
   DCHECK(profile);
-  OpenApplication(
+  apps::LaunchService::Get(profile)->OpenApplication(
       AppLaunchParams(profile, extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerWindow,
+                      apps::mojom::LaunchContainer::kLaunchContainerWindow,
                       WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceChromeInternal));
+                      apps::mojom::AppLaunchSource::kSourceChromeInternal));
 }
 
 void DemoSession::OnAppWindowActivated(extensions::AppWindow* app_window) {
diff --git a/chrome/browser/chromeos/login/login_auth_recorder.cc b/chrome/browser/chromeos/login/login_auth_recorder.cc
index c365434..9deb323 100644
--- a/chrome/browser/chromeos/login/login_auth_recorder.cc
+++ b/chrome/browser/chromeos/login/login_auth_recorder.cc
@@ -4,8 +4,8 @@
 
 #include "chrome/browser/chromeos/login/login_auth_recorder.h"
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/metrics/histogram_macros.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "components/session_manager/core/session_manager.h"
 
 namespace chromeos {
@@ -116,7 +116,7 @@
   }
 
   // Record usage of the authentication method in lock screen.
-  const bool is_tablet_mode = TabletModeClient::Get()->tablet_mode_enabled();
+  const bool is_tablet_mode = ash::TabletMode::Get()->InTabletMode();
   if (is_tablet_mode) {
     UMA_HISTOGRAM_ENUMERATION("Ash.Login.Lock.AuthMethod.Used.TabletMode",
                               method);
diff --git a/chrome/browser/chromeos/login/login_auth_recorder_browsertest.cc b/chrome/browser/chromeos/login/login_auth_recorder_browsertest.cc
index 4f9f960..e665f248 100644
--- a/chrome/browser/chromeos/login/login_auth_recorder_browsertest.cc
+++ b/chrome/browser/chromeos/login/login_auth_recorder_browsertest.cc
@@ -4,9 +4,9 @@
 
 #include "chrome/browser/chromeos/login/login_auth_recorder.h"
 
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/ui/ash/login_screen_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/session_manager/core/session_manager.h"
 
@@ -40,7 +40,7 @@
   }
 
   void EnableTabletMode(bool enable) {
-    TabletModeClient::Get()->OnTabletModeToggled(enable);
+    ash::ShellTestApi().SetTabletModeEnabledForTest(enable);
   }
 
   LoginAuthRecorder* metrics_recorder() {
diff --git a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
index c7e67c4a..45b5bbf 100644
--- a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "ash/public/cpp/ash_switches.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/macros.h"
@@ -31,7 +32,6 @@
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/webui/chromeos/login/app_downloading_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
@@ -50,6 +50,7 @@
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "ui/display/display_switches.h"
 
 using net::test_server::BasicHttpResponse;
 using net::test_server::HttpRequest;
@@ -380,8 +381,13 @@
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    if (params_.is_tablet)
+    if (params_.is_tablet) {
+      // Makes the device capable to entering tablet mode.
       command_line->AppendSwitch(ash::switches::kAshEnableTabletMode);
+      // Having an active internal display so that tablet mode does not end
+      // on display config change.
+      command_line->AppendSwitch(::switches::kUseFirstDisplayAsInternal);
+    }
 
     if (params_.arc_state != ArcState::kNotAvailable) {
       arc::SetArcAvailableCommandLineForTesting(command_line);
@@ -409,7 +415,7 @@
 
   void SetUpOnMainThread() override {
     if (params_.is_tablet)
-      TabletModeClient::Get()->OnTabletModeToggled(true);
+      ash::ShellTestApi().SetTabletModeEnabledForTest(true);
 
     if (params_.arc_state != ArcState::kNotAvailable) {
       // Init ArcSessionManager for testing.
diff --git a/chrome/browser/chromeos/login/screens/discover_screen.cc b/chrome/browser/chromeos/login/screens/discover_screen.cc
index 590f2f48..fd788654 100644
--- a/chrome/browser/chromeos/login/screens/discover_screen.cc
+++ b/chrome/browser/chromeos/login/screens/discover_screen.cc
@@ -4,11 +4,11 @@
 
 #include "chrome/browser/chromeos/login/screens/discover_screen.h"
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/logging.h"
 #include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
 #include "chrome/browser/chromeos/login/users/chrome_user_manager_util.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/webui/chromeos/login/discover_screen_handler.h"
 #include "components/prefs/pref_service.h"
 
@@ -34,7 +34,7 @@
 void DiscoverScreen::Show() {
   PrefService* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
   if (chrome_user_manager_util::IsPublicSessionOrEphemeralLogin() ||
-      !TabletModeClient::Get()->tablet_mode_enabled() ||
+      !ash::TabletMode::Get()->InTabletMode() ||
       !chromeos::quick_unlock::IsPinEnabled(prefs) ||
       chromeos::quick_unlock::IsPinDisabledByPolicy(prefs)) {
     exit_callback_.Run();
diff --git a/chrome/browser/chromeos/login/screens/error_screen.cc b/chrome/browser/chromeos/login/screens/error_screen.cc
index c53f7de..3e47620 100644
--- a/chrome/browser/chromeos/login/screens/error_screen.cc
+++ b/chrome/browser/chromeos/login/screens/error_screen.cc
@@ -10,6 +10,8 @@
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/app_mode/certificate_manager_dialog.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
@@ -27,8 +29,6 @@
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/webui/chromeos/login/error_screen_handler.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/grit/browser_resources.h"
@@ -293,11 +293,11 @@
       IDR_CONNECTIVITY_DIAGNOSTICS_MANIFEST,
       base::FilePath(extension_misc::kConnectivityDiagnosticsPath));
 
-  OpenApplication(
+  apps::LaunchService::Get(profile)->OpenApplication(
       AppLaunchParams(profile, extension_id,
-                      extensions::LaunchContainer::kLaunchContainerWindow,
+                      apps::mojom::LaunchContainer::kLaunchContainerWindow,
                       WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceChromeInternal));
+                      apps::mojom::AppLaunchSource::kSourceChromeInternal));
   KioskAppManager::Get()->InitSession(profile, extension_id);
 
   LoginDisplayHost::default_host()->Finalize(base::BindOnce(
diff --git a/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc b/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc
index 2ef6566..48488b9 100644
--- a/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc
+++ b/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc
@@ -11,13 +11,13 @@
 #include "ash/public/cpp/login_screen.h"
 #include "ash/public/cpp/login_screen_model.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host_mojo.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/browser/ui/ash/login_screen_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/webui/chrome_web_contents_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
@@ -209,7 +209,7 @@
     : controller_(controller),
       size_(gfx::Size(kGaiaDialogWidth, kGaiaDialogHeight)) {
   display_observer_.Add(display::Screen::GetScreen());
-  tablet_mode_observer_.Add(TabletModeClient::Get());
+  tablet_mode_observer_.Add(ash::TabletMode::Get());
   keyboard_observer_.Add(ChromeKeyboardControllerClient::Get());
 
   accel_map_[ui::Accelerator(
@@ -366,6 +366,14 @@
     UpdateSizeAndPosition(size_.width(), size_.height());
 }
 
+void OobeUIDialogDelegate::OnTabletModeStarted() {
+  OnTabletModeToggled(true);
+}
+
+void OobeUIDialogDelegate::OnTabletModeEnded() {
+  OnTabletModeToggled(false);
+}
+
 void OobeUIDialogDelegate::OnTabletModeToggled(bool enabled) {
   if (!dialog_widget_)
     return;
diff --git a/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.h b/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.h
index edac1eb..2467600d 100644
--- a/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.h
+++ b/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.h
@@ -8,19 +8,21 @@
 #include <string>
 
 #include "ash/public/cpp/login_types.h"
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "base/strings/string16.h"
 #include "chrome/browser/chromeos/login/screens/error_screen.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h"
 #include "components/web_modal/web_contents_modal_dialog_host.h"
 #include "ui/display/display_observer.h"
 #include "ui/web_dialogs/web_dialog_delegate.h"
 
-class TabletModeClient;
+namespace ash {
+class TabletMode;
+}
 
 namespace content {
 class WebContents;
@@ -54,7 +56,7 @@
 //         V
 //   clientView---->Widget's view hierarchy
 class OobeUIDialogDelegate : public display::DisplayObserver,
-                             public TabletModeClientObserver,
+                             public ash::TabletModeObserver,
                              public ui::WebDialogDelegate,
                              public ChromeKeyboardControllerClient::Observer,
                              public CaptivePortalWindowProxy::Observer {
@@ -93,8 +95,11 @@
   // display::DisplayObserver:
   void OnDisplayMetricsChanged(const display::Display& display,
                                uint32_t changed_metrics) override;
-  // TabletModeClientObserver:
-  void OnTabletModeToggled(bool enabled) override;
+  // ash::TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
+
+  void OnTabletModeToggled(bool enabled);
 
   // ui::WebDialogDelegate:
   ui::ModalType GetDialogModalType() const override;
@@ -135,7 +140,7 @@
 
   ScopedObserver<display::Screen, display::DisplayObserver> display_observer_{
       this};
-  ScopedObserver<TabletModeClient, TabletModeClientObserver>
+  ScopedObserver<ash::TabletMode, ash::TabletModeObserver>
       tablet_mode_observer_{this};
   ScopedObserver<ChromeKeyboardControllerClient,
                  ChromeKeyboardControllerClient::Observer>
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 68df516..a8906adf 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -14,6 +14,7 @@
 
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/login_screen.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
@@ -88,7 +89,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/login_screen_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/webui/chromeos/login/app_downloading_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/arc_kiosk_splash_screen_handler.h"
@@ -189,7 +189,7 @@
 // Checks if device is in tablet mode, and that HID-detection screen is not
 // disabled by flag.
 bool CanShowHIDDetectionScreen() {
-  return !TabletModeClient::Get()->tablet_mode_enabled() &&
+  return !ash::TabletMode::Get()->InTabletMode() &&
          !base::CommandLine::ForCurrentProcess()->HasSwitch(
              chromeos::switches::kDisableHIDDetectionOnOOBE) &&
          !base::CommandLine::ForCurrentProcess()->HasSwitch(
diff --git a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
index 5be3239e..6b821a19 100644
--- a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
@@ -40,6 +40,8 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
@@ -83,8 +85,6 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_list_observer.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
@@ -1599,10 +1599,10 @@
 
   // Start the platform app, causing it to open a window.
   run_loop_.reset(new base::RunLoop);
-  OpenApplication(AppLaunchParams(
-      profile, app->id(), extensions::LaunchContainer::kLaunchContainerNone,
+  apps::LaunchService::Get(profile)->OpenApplication(AppLaunchParams(
+      profile, app->id(), apps::mojom::LaunchContainer::kLaunchContainerNone,
       WindowOpenDisposition::NEW_WINDOW,
-      extensions::AppLaunchSource::kSourceTest));
+      apps::mojom::AppLaunchSource::kSourceTest));
   run_loop_->Run();
   EXPECT_EQ(1U, app_window_registry->app_windows().size());
 
diff --git a/chrome/browser/chromeos/ui/request_pin_view.cc b/chrome/browser/chromeos/ui/request_pin_view.cc
index 4bd914c..90fac492 100644
--- a/chrome/browser/chromeos/ui/request_pin_view.cc
+++ b/chrome/browser/chromeos/ui/request_pin_view.cc
@@ -35,17 +35,16 @@
 
 RequestPinView::RequestPinView(
     const std::string& extension_name,
-    RequestPinView::RequestPinCodeType code_type,
+    PinCodeType code_type,
     int attempts_left,
     const PinEnteredCallback& pin_entered_callback,
     ViewDestructionCallback view_destruction_callback)
     : pin_entered_callback_(pin_entered_callback),
       view_destruction_callback_(std::move(view_destruction_callback)) {
-  DCHECK_NE(code_type, RequestPinCodeType::UNCHANGED);
   Init();
   SetExtensionName(extension_name);
   const bool accept_input = (attempts_left != 0);
-  SetDialogParameters(code_type, RequestPinErrorType::NONE, attempts_left,
+  SetDialogParameters(code_type, PinErrorLabel::kNone, attempts_left,
                       accept_input);
   chrome::RecordDialogCreation(chrome::DialogIdentifier::REQUEST_PIN);
 }
@@ -125,28 +124,21 @@
   return gfx::Size(default_width, GetHeightForWidth(default_width));
 }
 
-bool RequestPinView::IsLocked() const {
-  return locked_;
-}
-
-void RequestPinView::SetDialogParameters(
-    RequestPinView::RequestPinCodeType code_type,
-    RequestPinView::RequestPinErrorType error_type,
-    int attempts_left,
-    bool accept_input) {
+void RequestPinView::SetDialogParameters(PinCodeType code_type,
+                                         PinErrorLabel error_label,
+                                         int attempts_left,
+                                         bool accept_input) {
   locked_ = false;
-  SetErrorMessage(error_type, attempts_left);
+  SetErrorMessage(error_label, attempts_left);
   SetAcceptInput(accept_input);
 
   switch (code_type) {
-    case RequestPinCodeType::PIN:
+    case PinCodeType::kPin:
       code_type_ = l10n_util::GetStringUTF16(IDS_REQUEST_PIN_DIALOG_PIN);
       break;
-    case RequestPinCodeType::PUK:
+    case PinCodeType::kPuk:
       code_type_ = l10n_util::GetStringUTF16(IDS_REQUEST_PIN_DIALOG_PUK);
       break;
-    case RequestPinCodeType::UNCHANGED:
-      break;
   }
 
   UpdateHeaderText();
@@ -180,7 +172,7 @@
                         views::GridLayout::USE_PREF, 0, 0);
   layout->StartRow(0, column_view_set_id);
 
-  // Infomation label.
+  // Information label.
   int label_text_id = IDS_REQUEST_PIN_DIALOG_HEADER;
   base::string16 label_text = l10n_util::GetStringUTF16(label_text_id);
   auto header_label = std::make_unique<views::Label>(label_text);
@@ -232,27 +224,27 @@
   }
 }
 
-void RequestPinView::SetErrorMessage(RequestPinErrorType error_type,
+void RequestPinView::SetErrorMessage(PinErrorLabel error_label,
                                      int attempts_left) {
   base::string16 error_message;
-  switch (error_type) {
-    case RequestPinErrorType::INVALID_PIN:
+  switch (error_label) {
+    case PinErrorLabel::kInvalidPin:
       error_message =
           l10n_util::GetStringUTF16(IDS_REQUEST_PIN_DIALOG_INVALID_PIN_ERROR);
       break;
-    case RequestPinErrorType::INVALID_PUK:
+    case PinErrorLabel::kInvalidPuk:
       error_message =
           l10n_util::GetStringUTF16(IDS_REQUEST_PIN_DIALOG_INVALID_PUK_ERROR);
       break;
-    case RequestPinErrorType::MAX_ATTEMPTS_EXCEEDED:
+    case PinErrorLabel::kMaxAttemptsExceeded:
       error_message = l10n_util::GetStringUTF16(
           IDS_REQUEST_PIN_DIALOG_MAX_ATTEMPTS_EXCEEDED_ERROR);
       break;
-    case RequestPinErrorType::UNKNOWN_ERROR:
+    case PinErrorLabel::kUnknown:
       error_message =
           l10n_util::GetStringUTF16(IDS_REQUEST_PIN_DIALOG_UNKNOWN_ERROR);
       break;
-    case RequestPinErrorType::NONE:
+    case PinErrorLabel::kNone:
       if (attempts_left < 0) {
         error_label_->SetVisible(false);
         textfield_->SetInvalid(false);
diff --git a/chrome/browser/chromeos/ui/request_pin_view.h b/chrome/browser/chromeos/ui/request_pin_view.h
index b2158e0..5621f40 100644
--- a/chrome/browser/chromeos/ui/request_pin_view.h
+++ b/chrome/browser/chromeos/ui/request_pin_view.h
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string16.h"
+#include "chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -25,29 +26,21 @@
 namespace chromeos {
 
 // A dialog box for requesting PIN code. Instances of this class are managed by
-// PinDialogManager.
+// SecurityTokenPinDialogHostPopupImpl.
 class RequestPinView final : public views::DialogDelegateView,
                              public views::TextfieldController {
  public:
-  enum RequestPinCodeType { PIN, PUK, UNCHANGED };
-
-  enum RequestPinErrorType {
-    NONE,
-    INVALID_PIN,
-    INVALID_PUK,
-    MAX_ATTEMPTS_EXCEEDED,
-    UNKNOWN_ERROR
-  };
+  using PinCodeType = SecurityTokenPinDialogHost::SecurityTokenPinCodeType;
+  using PinErrorLabel = SecurityTokenPinDialogHost::SecurityTokenPinErrorLabel;
 
   using PinEnteredCallback =
       base::RepeatingCallback<void(const std::string& user_input)>;
   using ViewDestructionCallback = base::OnceClosure;
 
-  // Creates the view to be embeded in the dialog that requests the PIN/PUK.
+  // Creates the view to be embedded in the dialog that requests the PIN/PUK.
   // |extension_name| - the name of the extension making the request. Displayed
   //     in the title and in the header of the view.
-  // |code_type| - the type of code requested, PIN or PUK. UNCHANGED is not
-  //     accepted here.
+  // |code_type| - the type of code requested, PIN or PUK.
   // |attempts_left| - the number of attempts user has to try the code. When
   //     zero the textfield is disabled and user cannot provide any input. When
   //     -1 the user is allowed to provide the input and no information about
@@ -55,7 +48,7 @@
   // |pin_entered_callback| - called every time the user submits the input.
   // |view_destruction_callback| - called by the destructor.
   RequestPinView(const std::string& extension_name,
-                 RequestPinCodeType code_type,
+                 PinCodeType code_type,
                  int attempts_left,
                  const PinEnteredCallback& pin_entered_callback,
                  ViewDestructionCallback view_destruction_callback);
@@ -78,21 +71,15 @@
   // views::View
   gfx::Size CalculatePreferredSize() const override;
 
-  // Returns whether the view is locked while waiting the extension to process
-  // the user input data.
-  bool IsLocked() const;
-
-  // |code_type| - specifies whether the user is asked to enter PIN or PUK. If
-  //     UNCHANGED value is provided, the dialog displays the same value that
-  //     was last set.
-  // |error_type| - the error template to be displayed in red in the dialog. If
-  //     NONE, no error is displayed.
+  // |code_type| - specifies whether the user is asked to enter PIN or PUK.
+  // |error_label| - the error template to be displayed in red in the dialog. If
+  //     |kNone|, no error is displayed.
   // |attempts_left| - included in the view as the number of attepts user can
   //     have to enter correct code.
   // |accept_input| - specifies whether the textfield is enabled. If disabled
   //     the user is unable to provide input.
-  void SetDialogParameters(RequestPinCodeType code_type,
-                           RequestPinErrorType error_type,
+  void SetDialogParameters(PinCodeType code_type,
+                           PinErrorLabel error_label,
                            int attempts_left,
                            bool accept_input);
 
@@ -107,7 +94,7 @@
   // This initializes the view, with all the UI components.
   void Init();
   void SetAcceptInput(bool accept_input);
-  void SetErrorMessage(RequestPinErrorType error_type, int attempts_left);
+  void SetErrorMessage(PinErrorLabel error_label, int attempts_left);
   // Updates the header text |header_label_| based on values from
   // |window_title_| and |code_type_|.
   void UpdateHeaderText();
diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc
index 89b0d75..ad5c439c 100644
--- a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc
+++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h"
 #include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h"
 #include "chrome/browser/chromeos/certificate_provider/pin_dialog_manager.h"
+#include "chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.h"
 #include "chrome/common/extensions/api/certificate_provider.h"
 #include "chrome/common/extensions/api/certificate_provider_internal.h"
 #include "net/cert/x509_certificate.h"
@@ -24,28 +25,30 @@
 
 namespace api_cp = extensions::api::certificate_provider;
 namespace api_cpi = extensions::api::certificate_provider_internal;
+using PinCodeType =
+    chromeos::SecurityTokenPinDialogHost::SecurityTokenPinCodeType;
+using PinErrorLabel =
+    chromeos::SecurityTokenPinDialogHost::SecurityTokenPinErrorLabel;
 
 namespace {
 
-chromeos::RequestPinView::RequestPinErrorType GetErrorTypeForView(
-    api_cp::PinRequestErrorType error_type) {
+PinErrorLabel GetErrorLabelForDialog(api_cp::PinRequestErrorType error_type) {
   switch (error_type) {
     case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_INVALID_PIN:
-      return chromeos::RequestPinView::RequestPinErrorType::INVALID_PIN;
+      return PinErrorLabel::kInvalidPin;
     case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_INVALID_PUK:
-      return chromeos::RequestPinView::RequestPinErrorType::INVALID_PUK;
+      return PinErrorLabel::kInvalidPuk;
     case api_cp::PinRequestErrorType::
         PIN_REQUEST_ERROR_TYPE_MAX_ATTEMPTS_EXCEEDED:
-      return chromeos::RequestPinView::RequestPinErrorType::
-          MAX_ATTEMPTS_EXCEEDED;
+      return PinErrorLabel::kMaxAttemptsExceeded;
     case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_UNKNOWN_ERROR:
-      return chromeos::RequestPinView::RequestPinErrorType::UNKNOWN_ERROR;
+      return PinErrorLabel::kUnknown;
     case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_NONE:
-      return chromeos::RequestPinView::RequestPinErrorType::NONE;
+      return PinErrorLabel::kNone;
   }
 
   NOTREACHED();
-  return chromeos::RequestPinView::RequestPinErrorType::NONE;
+  return PinErrorLabel::kNone;
 }
 
 }  // namespace
@@ -216,11 +219,11 @@
 
   // Extension provided an error, which means it intends to notify the user with
   // the error and not allow any more input.
-  chromeos::RequestPinView::RequestPinErrorType error_type =
-      GetErrorTypeForView(params->details.error_type);
+  const PinErrorLabel error_label =
+      GetErrorLabelForDialog(params->details.error_type);
   const chromeos::PinDialogManager::StopPinRequestResult stop_request_result =
       service->pin_dialog_manager()->StopPinRequestWithError(
-          extension()->id(), error_type,
+          extension()->id(), error_label,
           base::BindOnce(
               &CertificateProviderStopPinRequestFunction::OnPinRequestStopped,
               this));
@@ -238,11 +241,6 @@
 }
 
 void CertificateProviderStopPinRequestFunction::OnPinRequestStopped() {
-  chromeos::CertificateProviderService* const service =
-      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
-          browser_context());
-  DCHECK(service);
-
   Respond(NoArguments());
 }
 
@@ -273,19 +271,19 @@
       api_cp::RequestPin::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
 
-  api_cp::PinRequestType pin_request_type =
+  const api_cp::PinRequestType pin_request_type =
       params->details.request_type ==
               api_cp::PinRequestType::PIN_REQUEST_TYPE_NONE
           ? api_cp::PinRequestType::PIN_REQUEST_TYPE_PIN
           : params->details.request_type;
 
-  chromeos::RequestPinView::RequestPinErrorType error_type =
-      GetErrorTypeForView(params->details.error_type);
+  const PinErrorLabel error_label =
+      GetErrorLabelForDialog(params->details.error_type);
 
-  chromeos::RequestPinView::RequestPinCodeType code_type =
+  const PinCodeType code_type =
       (pin_request_type == api_cp::PinRequestType::PIN_REQUEST_TYPE_PIN)
-          ? chromeos::RequestPinView::RequestPinCodeType::PIN
-          : chromeos::RequestPinView::RequestPinCodeType::PUK;
+          ? PinCodeType::kPin
+          : PinCodeType::kPuk;
 
   chromeos::CertificateProviderService* const service =
       chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
@@ -302,7 +300,8 @@
   const chromeos::PinDialogManager::RequestPinResult result =
       service->pin_dialog_manager()->RequestPin(
           extension()->id(), extension()->name(),
-          params->details.sign_request_id, code_type, error_type, attempts_left,
+          params->details.sign_request_id, code_type, error_label,
+          attempts_left,
           base::BindOnce(
               &CertificateProviderRequestPinFunction::OnInputReceived, this));
   switch (result) {
diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
index 2a26615..808fad9 100644
--- a/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
+++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
@@ -183,11 +183,13 @@
 
   chromeos::RequestPinView* GetActivePinDialogView() {
     return cert_provider_service_->pin_dialog_manager()
+        ->default_dialog_host_for_testing()
         ->active_view_for_testing();
   }
 
   views::Widget* GetActivePinDialogWindow() {
     return cert_provider_service_->pin_dialog_manager()
+        ->default_dialog_host_for_testing()
         ->active_window_for_testing();
   }
 
diff --git a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
index eb9da3e..9f27581 100644
--- a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
+++ b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
@@ -11,6 +11,8 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/launch_util.h"
@@ -24,8 +26,6 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
@@ -257,10 +257,11 @@
   // returned.
   extensions::LaunchContainer launch_container =
       GetLaunchContainer(extensions::ExtensionPrefs::Get(context), extension);
-  OpenApplication(AppLaunchParams(
-      Profile::FromBrowserContext(context), extension->id(), launch_container,
-      WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      extensions::AppLaunchSource::kSourceManagementApi));
+  Profile* profile = Profile::FromBrowserContext(context);
+  apps::LaunchService::Get(profile)->OpenApplication(
+      AppLaunchParams(profile, extension->id(), launch_container,
+                      WindowOpenDisposition::NEW_FOREGROUND_TAB,
+                      apps::mojom::AppLaunchSource::kSourceManagementApi));
 
 #if defined(OS_CHROMEOS)
   chromeos::DemoSession::RecordAppLaunchSourceIfInDemoMode(
diff --git a/chrome/browser/extensions/api/notifications/notifications_apitest.cc b/chrome/browser/extensions/api/notifications/notifications_apitest.cc
index 4826ad1..427be63b 100644
--- a/chrome/browser/extensions/api/notifications/notifications_apitest.cc
+++ b/chrome/browser/extensions/api/notifications/notifications_apitest.cc
@@ -10,6 +10,8 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/extensions/api/notifications/extension_notification_display_helper.h"
 #include "chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h"
@@ -22,8 +24,6 @@
 #include "chrome/browser/notifications/notifier_state_tracker.h"
 #include "chrome/browser/notifications/notifier_state_tracker_factory.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "content/public/browser/notification_service.h"
@@ -205,11 +205,12 @@
   }
 
   void LaunchPlatformApp(const Extension* extension) {
-    OpenApplication(
-        AppLaunchParams(browser()->profile(), extension->id(),
-                        extensions::LaunchContainer::kLaunchContainerNone,
-                        WindowOpenDisposition::NEW_WINDOW,
-                        extensions::AppLaunchSource::kSourceTest));
+    apps::LaunchService::Get(browser()->profile())
+        ->OpenApplication(
+            AppLaunchParams(browser()->profile(), extension->id(),
+                            apps::mojom::LaunchContainer::kLaunchContainerNone,
+                            WindowOpenDisposition::NEW_WINDOW,
+                            apps::mojom::AppLaunchSource::kSourceTest));
   }
 
   std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
diff --git a/chrome/browser/extensions/api/system_display/system_display_chromeos_apitest.cc b/chrome/browser/extensions/api/system_display/system_display_chromeos_apitest.cc
index ccf9916..b2ce3b0c 100644
--- a/chrome/browser/extensions/api/system_display/system_display_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/system_display/system_display_chromeos_apitest.cc
@@ -35,7 +35,7 @@
           extensions::DisplayInfoProvider::Get());
 
   // Change Tablet Mode then ensure that OnDisplayChangedEvent is triggered
-  provider->OnTabletModeToggled(true);
+  provider->OnTabletModeStarted();
 
   extensions::ResultCatcher catcher;
   EXPECT_TRUE(catcher.GetNextResult());
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.cc b/chrome/browser/extensions/bookmark_app_extension_util.cc
index 24eb5ed2..fe3a7db7 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.cc
+++ b/chrome/browser/extensions/bookmark_app_extension_util.cc
@@ -49,9 +49,9 @@
 }
 
 void BookmarkAppReparentTab(content::WebContents* contents,
-                            const Extension* extension) {
+                            const std::string& app_id) {
   // Reparent the tab into an app window immediately when opening as a window.
-  ReparentWebContentsIntoAppBrowser(contents, extension);
+  ReparentWebContentsIntoAppBrowser(contents, app_id);
 }
 
 bool CanBookmarkAppRevealAppShim() {
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.h b/chrome/browser/extensions/bookmark_app_extension_util.h
index 1429146..7c496bd 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.h
+++ b/chrome/browser/extensions/bookmark_app_extension_util.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_EXTENSION_UTIL_H_
 #define CHROME_BROWSER_EXTENSIONS_BOOKMARK_APP_EXTENSION_UTIL_H_
 
+#include <string>
+
 #include "base/callback_forward.h"
 
 class Profile;
@@ -21,7 +23,7 @@
                                const Extension* extension,
                                bool shortcut_created);
 void BookmarkAppReparentTab(content::WebContents* contents,
-                            const Extension* extension);
+                            const std::string& app_id);
 
 bool CanBookmarkAppRevealAppShim();
 void BookmarkAppRevealAppShim(Profile* profile, const Extension* extension);
diff --git a/chrome/browser/extensions/browsertest_util.cc b/chrome/browser/extensions/browsertest_util.cc
index e5f9fd4..bb117a9 100644
--- a/chrome/browser/extensions/browsertest_util.cc
+++ b/chrome/browser/extensions/browsertest_util.cc
@@ -11,12 +11,12 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/web_applications/components/install_manager.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
@@ -86,9 +86,10 @@
 }
 
 Browser* LaunchAppBrowser(Profile* profile, const Extension* extension_app) {
-  EXPECT_TRUE(OpenApplication(AppLaunchParams(
-      profile, extension_app->id(), LaunchContainer::kLaunchContainerWindow,
-      WindowOpenDisposition::CURRENT_TAB, AppLaunchSource::kSourceTest)));
+  EXPECT_TRUE(
+      apps::LaunchService::Get(profile)->OpenApplication(AppLaunchParams(
+          profile, extension_app->id(), LaunchContainer::kLaunchContainerWindow,
+          WindowOpenDisposition::CURRENT_TAB, AppLaunchSource::kSourceTest)));
 
   Browser* browser = chrome::FindLastActive();
   bool is_correct_app_browser =
@@ -101,9 +102,11 @@
 
 Browser* LaunchBrowserForAppInTab(Profile* profile,
                                   const Extension* extension_app) {
-  content::WebContents* web_contents = OpenApplication(AppLaunchParams(
-      profile, extension_app->id(), LaunchContainer::kLaunchContainerTab,
-      WindowOpenDisposition::NEW_FOREGROUND_TAB, AppLaunchSource::kSourceTest));
+  content::WebContents* web_contents =
+      apps::LaunchService::Get(profile)->OpenApplication(AppLaunchParams(
+          profile, extension_app->id(), LaunchContainer::kLaunchContainerTab,
+          WindowOpenDisposition::NEW_FOREGROUND_TAB,
+          AppLaunchSource::kSourceTest));
   DCHECK(web_contents);
 
   web_app::WebAppTabHelperBase* tab_helper =
diff --git a/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc b/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
index b55f9571..ab2fde0b 100644
--- a/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
+++ b/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
@@ -14,6 +14,8 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/api/tabs/tabs_api.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
@@ -23,8 +25,6 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/render_frame_host.h"
@@ -997,10 +997,12 @@
   content::WebContents* app_contents = nullptr;
   {
     content::WebContentsAddedObserver new_contents_observer;
-    OpenApplication(AppLaunchParams(browser()->profile(), app->id(),
-                                    LaunchContainer::kLaunchContainerNone,
-                                    WindowOpenDisposition::NEW_WINDOW,
-                                    extensions::AppLaunchSource::kSourceTest));
+    apps::LaunchService::Get(browser()->profile())
+        ->OpenApplication(
+            AppLaunchParams(browser()->profile(), app->id(),
+                            LaunchContainer::kLaunchContainerNone,
+                            WindowOpenDisposition::NEW_WINDOW,
+                            apps::mojom::AppLaunchSource::kSourceTest));
     app_contents = new_contents_observer.GetWebContents();
   }
   ASSERT_TRUE(content::WaitForLoadStop(app_contents));
diff --git a/chrome/browser/extensions/extension_apitest.cc b/chrome/browser/extensions/extension_apitest.cc
index dce2c4e..504d98f 100644
--- a/chrome/browser/extensions/extension_apitest.cc
+++ b/chrome/browser/extensions/extension_apitest.cc
@@ -16,12 +16,12 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/unpacked_installer.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/common/content_switches.h"
 #include "extensions/browser/api/test/test_api.h"
@@ -298,7 +298,7 @@
                            WindowOpenDisposition::NEW_WINDOW,
                            AppLaunchSource::kSourceTest);
     params.command_line = *base::CommandLine::ForCurrentProcess();
-    OpenApplication(params);
+    apps::LaunchService::Get(browser()->profile())->OpenApplication(params);
   }
 
   if (!catcher.GetNextResult()) {
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 33571be4..ce4679a 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -23,6 +23,8 @@
 #include "base/task/post_task.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/browsertest_util.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/component_loader.h"
@@ -40,7 +42,6 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/extensions/extension_message_bubble_factory.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
@@ -428,7 +429,7 @@
       profile(), app->id(), LaunchContainer::kLaunchContainerNone,
       WindowOpenDisposition::NEW_WINDOW, AppLaunchSource::kSourceTest);
   params.command_line = *base::CommandLine::ForCurrentProcess();
-  OpenApplication(params);
+  apps::LaunchService::Get(profile())->OpenApplication(params);
   app_loaded_observer.Wait();
 
   return app;
diff --git a/chrome/browser/extensions/extension_protocols_unittest.cc b/chrome/browser/extensions/extension_protocols_unittest.cc
index f267299..0b5103e 100644
--- a/chrome/browser/extensions/extension_protocols_unittest.cc
+++ b/chrome/browser/extensions/extension_protocols_unittest.cc
@@ -131,7 +131,6 @@
   request.referrer_policy = content::Referrer::GetDefaultReferrerPolicy();
   request.resource_type = static_cast<int>(resource_type);
   request.is_main_frame = resource_type == content::ResourceType::kMainFrame;
-  request.allow_download = true;
   return request;
 }
 
diff --git a/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc b/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc
index a3d7072..fe86a10 100644
--- a/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc
+++ b/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 #include <cmath>
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/cros_display_config.mojom.h"
 #include "base/bind.h"
@@ -14,7 +15,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/extensions/system_display/display_info_provider.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "content/public/browser/system_connector.h"
 #include "extensions/common/api/system_display.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -605,7 +605,7 @@
 void DisplayInfoProviderChromeOS::StartObserving() {
   DisplayInfoProvider::StartObserving();
 
-  TabletModeClient* client = TabletModeClient::Get();
+  ash::TabletMode* client = ash::TabletMode::Get();
   if (client)
     client->AddObserver(this);
 }
@@ -613,12 +613,16 @@
 void DisplayInfoProviderChromeOS::StopObserving() {
   DisplayInfoProvider::StopObserving();
 
-  TabletModeClient* client = TabletModeClient::Get();
+  ash::TabletMode* client = ash::TabletMode::Get();
   if (client)
     client->RemoveObserver(this);
 }
 
-void DisplayInfoProviderChromeOS::OnTabletModeToggled(bool enabled) {
+void DisplayInfoProviderChromeOS::OnTabletModeStarted() {
+  DispatchOnDisplayChangedEvent();
+}
+
+void DisplayInfoProviderChromeOS::OnTabletModeEnded() {
   DispatchOnDisplayChangedEvent();
 }
 
diff --git a/chrome/browser/extensions/system_display/display_info_provider_chromeos.h b/chrome/browser/extensions/system_display/display_info_provider_chromeos.h
index 8742e39..f3c69c5c 100644
--- a/chrome/browser/extensions/system_display/display_info_provider_chromeos.h
+++ b/chrome/browser/extensions/system_display/display_info_provider_chromeos.h
@@ -8,10 +8,10 @@
 #include <map>
 #include <memory>
 
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "ash/public/interfaces/cros_display_config.mojom.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "extensions/browser/api/system_display/display_info_provider.h"
 
 namespace service_manager {
@@ -21,7 +21,7 @@
 namespace extensions {
 
 class DisplayInfoProviderChromeOS : public DisplayInfoProvider,
-                                    public TabletModeClientObserver {
+                                    public ash::TabletModeObserver {
  public:
   explicit DisplayInfoProviderChromeOS(service_manager::Connector* connector);
   ~DisplayInfoProviderChromeOS() override;
@@ -57,8 +57,9 @@
   void StartObserving() override;
   void StopObserving() override;
 
-  // TabletModeClientObserver implementation.
-  void OnTabletModeToggled(bool enabled) override;
+  // ash::TabletModeObserver implementation.
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
 
  private:
   void CallSetDisplayLayoutInfo(ash::mojom::DisplayLayoutInfoPtr layout_info,
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 6795758..82459eb 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
@@ -10,16 +10,16 @@
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
 #include "ash/public/cpp/ash_switches.h"
+#include "ash/public/cpp/tablet_mode.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/shell.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/extensions/system_display/display_info_provider_chromeos.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/test/base/chrome_ash_test_base.h"
 #include "content/public/test/test_service_manager_context.h"
 #include "extensions/common/api/system_display.h"
@@ -79,20 +79,10 @@
     DisplayInfoProvider::InitializeForTesting(
         new DisplayInfoProviderChromeOS(connector_.get()));
 
-    tablet_mode_client_ = std::make_unique<TabletModeClient>();
-    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()->InTabletMode());
-  }
-
-  void TearDown() override {
-    ChromeAshTestBase::TearDown();
-    // To match ChromeBrowserMainExtraPartsAsh, shut down the TabletModeClient
-    // after Shell.
-    tablet_mode_client_.reset();
+    EXPECT_FALSE(ash::TabletMode::Get()->InTabletMode());
   }
 
   void AddCrosDisplayConfigControllerBinding(
@@ -125,9 +115,7 @@
   }
 
   void EnableTabletMode(bool enable) {
-    ash::TabletModeController* controller =
-        ash::Shell::Get()->tablet_mode_controller();
-    controller->SetEnabledForTest(enable);
+    ash::ShellTestApi().SetTabletModeEnabledForTest(enable);
   }
 
   display::DisplayManager* GetDisplayManager() const {
@@ -205,7 +193,6 @@
   content::TestServiceManagerContext service_manager_context_;
   std::unique_ptr<service_manager::Connector> connector_;
   std::unique_ptr<ash::CrosDisplayConfig> cros_display_config_;
-  std::unique_ptr<TabletModeClient> tablet_mode_client_;
 
   DISALLOW_COPY_AND_ASSIGN(DisplayInfoProviderChromeosTest);
 };
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
index 441c4df..74c0724 100644
--- a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
+++ b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
@@ -546,8 +546,10 @@
   CheckPageInfoUkmMetrics(url, true);
 }
 
+// TODO(https://crbug.com/990148): Re-enable on Win and Linux once not flaky.
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
+#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
+    defined(OS_WIN) || defined(OS_LINUX)
 #define MAYBE_FetchAndEmitMetricsWithExtensions \
   DISABLED_FetchAndEmitMetricsWithExtensions
 #else
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
index 4623260..48e685c3 100644
--- a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
 
+#include <string>
 #include <utility>
 
 #include "base/base_paths.h"
@@ -11,11 +12,13 @@
 #include "base/path_service.h"
 #include "base/task/post_task.h"
 #include "build/build_config.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/native_file_system/native_file_system_permission_context_factory.h"
 #include "chrome/browser/native_file_system/native_file_system_permission_request_manager.h"
-#include "chrome/browser/permissions/permission_util.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/common/chrome_paths.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
@@ -275,130 +278,115 @@
 ChromeNativeFileSystemPermissionContext::Grants&
 ChromeNativeFileSystemPermissionContext::Grants::operator=(Grants&&) = default;
 
-class ChromeNativeFileSystemPermissionContext::PermissionGrantImpl
-    : public content::NativeFileSystemPermissionGrant {
- public:
-  // In the current implementation permission grants are scoped to the frame
-  // they are requested in. Within a frame we only want to have one grant pe
-  // path. The Key struct contains these fields. Keys are comparable so they can
-  // be used with sorted containers like std::map and std::set.
-  // TODO(https://crbug.com/984769): Eliminate process_id and frame_id and
-  // replace usage of this struct with just a file path when grants stop being
-  // scoped to a frame.
-  struct Key {
-    base::FilePath path;
-    int process_id = 0;
-    int frame_id = 0;
+bool ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::Key::
+operator==(const Key& rhs) const {
+  return std::tie(process_id, frame_id, path) ==
+         std::tie(rhs.process_id, rhs.frame_id, rhs.path);
+}
+bool ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::Key::
+operator<(const Key& rhs) const {
+  return std::tie(process_id, frame_id, path) <
+         std::tie(rhs.process_id, rhs.frame_id, rhs.path);
+}
 
-    bool operator==(const Key& rhs) const {
-      return std::tie(process_id, frame_id, path) ==
-             std::tie(rhs.process_id, rhs.frame_id, rhs.path);
-    }
-    bool operator<(const Key& rhs) const {
-      return std::tie(process_id, frame_id, path) <
-             std::tie(rhs.process_id, rhs.frame_id, rhs.path);
-    }
-  };
+ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::
+    WritePermissionGrantImpl(
+        scoped_refptr<ChromeNativeFileSystemPermissionContext> context,
+        ContentSettingsType content_settings_guard_type,
+        const url::Origin& origin,
+        const Key& key,
+        bool is_directory)
+    : context_(std::move(context)),
+      write_guard_content_setting_type_(content_settings_guard_type),
+      origin_(origin),
+      key_(key),
+      is_directory_(is_directory) {}
 
-  PermissionGrantImpl(
-      scoped_refptr<ChromeNativeFileSystemPermissionContext> context,
-      const url::Origin& origin,
-      const Key& key,
-      bool is_directory)
-      : context_(std::move(context)),
-        origin_(origin),
-        key_(key),
-        is_directory_(is_directory) {}
+blink::mojom::PermissionStatus
+ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::GetStatus() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return status_;
+}
 
-  const url::Origin& origin() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return origin_;
-  }
-
-  bool is_directory() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return is_directory_;
-  }
-
-  const base::FilePath& path() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return key_.path;
-  }
-
-  const Key& key() const { return key_; }
-
-  PermissionStatus GetStatus() override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return status_;
-  }
-
-  void SetStatus(PermissionStatus status) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (status_ == status)
-      return;
-    status_ = status;
-    NotifyPermissionStatusChanged();
-  }
-
-  void RequestPermission(int process_id,
-                         int frame_id,
-                         base::OnceClosure callback) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (GetStatus() != PermissionStatus::ASK) {
-      std::move(callback).Run();
-      return;
-    }
-
-    auto result_callback = BindResultCallbackToCurrentSequence(
-        base::BindOnce(&PermissionGrantImpl::OnPermissionRequestComplete, this,
-                       std::move(callback)));
-
-    base::PostTaskWithTraits(
-        FROM_HERE, {content::BrowserThread::UI},
-        base::BindOnce(&ShowWritePermissionPromptOnUIThread, process_id,
-                       frame_id, origin_, path(), is_directory_,
-                       std::move(result_callback)));
-  }
-
- protected:
-  ~PermissionGrantImpl() override { context_->PermissionGrantDestroyed(this); }
-
- private:
-  void OnPermissionRequestComplete(base::OnceClosure callback,
-                                   PermissionAction result) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    switch (result) {
-      case PermissionAction::GRANTED:
-        SetStatus(PermissionStatus::GRANTED);
-        break;
-      case PermissionAction::DENIED:
-        SetStatus(PermissionStatus::DENIED);
-        break;
-      case PermissionAction::DISMISSED:
-      case PermissionAction::IGNORED:
-        break;
-      case PermissionAction::REVOKED:
-      case PermissionAction::NUM:
-        NOTREACHED();
-        break;
-    }
-
+void ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::
+    RequestPermission(int process_id,
+                      int frame_id,
+                      base::OnceClosure callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Check if a permission request has already been processed previously. This
+  // check is done first because we don't want to reset the status of a write
+  // permission if it has already been granted.
+  if (GetStatus() != PermissionStatus::ASK) {
     std::move(callback).Run();
+    return;
   }
 
-  SEQUENCE_CHECKER(sequence_checker_);
+  // Check if |write_guard_content_setting_type_| is blocked by the user and
+  // update the status if it is.
+  if (!CanRequestPermission()) {
+    SetStatus(PermissionStatus::DENIED);
+    std::move(callback).Run();
+    return;
+  }
 
-  scoped_refptr<ChromeNativeFileSystemPermissionContext> const context_;
-  const url::Origin origin_;
-  const Key key_;
-  const bool is_directory_;
+  auto result_callback = BindResultCallbackToCurrentSequence(
+      base::BindOnce(&WritePermissionGrantImpl::OnPermissionRequestComplete,
+                     this, std::move(callback)));
 
-  // This member should only be updated via SetStatus(), to make sure observers
-  // are properly notified about any change in status.
-  PermissionStatus status_ = PermissionStatus::ASK;
+  base::PostTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::UI},
+      base::BindOnce(&ShowWritePermissionPromptOnUIThread, process_id, frame_id,
+                     origin_, path(), is_directory_,
+                     std::move(result_callback)));
+}
 
-  DISALLOW_COPY_AND_ASSIGN(PermissionGrantImpl);
-};
+bool ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::
+    CanRequestPermission() {
+  HostContentSettingsMap* content_settings = context_->content_settings();
+  ContentSetting content_setting = content_settings->GetContentSetting(
+      origin_.GetURL(), origin_.GetURL(), write_guard_content_setting_type_,
+      /*provider_id=*/std::string());
+  DCHECK(content_setting == CONTENT_SETTING_ASK ||
+         content_setting == CONTENT_SETTING_BLOCK);
+  return content_setting == CONTENT_SETTING_ASK;
+}
+
+void ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::
+    SetStatus(PermissionStatus status) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (status_ == status)
+    return;
+  status_ = status;
+  NotifyPermissionStatusChanged();
+}
+
+ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::
+    ~WritePermissionGrantImpl() {
+  context_->PermissionGrantDestroyed(this);
+}
+
+void ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl::
+    OnPermissionRequestComplete(base::OnceClosure callback,
+                                PermissionAction result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  switch (result) {
+    case PermissionAction::GRANTED:
+      SetStatus(PermissionStatus::GRANTED);
+      break;
+    case PermissionAction::DENIED:
+      SetStatus(PermissionStatus::DENIED);
+      break;
+    case PermissionAction::DISMISSED:
+    case PermissionAction::IGNORED:
+      break;
+    case PermissionAction::REVOKED:
+    case PermissionAction::NUM:
+      NOTREACHED();
+      break;
+  }
+
+  std::move(callback).Run();
+}
 
 struct ChromeNativeFileSystemPermissionContext::OriginState {
   // Raw pointers, owned collectively by all the handles that reference this
@@ -406,7 +394,7 @@
   // PermissionGrantDestroyed().
   // TODO(mek): Lifetime of grants might change depending on the outcome of
   // the discussions surrounding lifetime of non-persistent permissions.
-  std::map<PermissionGrantImpl::Key, PermissionGrantImpl*> grants;
+  std::map<WritePermissionGrantImpl::Key, WritePermissionGrantImpl*> grants;
 
   // Read permissions are keyed off of the tab they are associated with.
   // TODO(https://crbug.com/984769): This will change when permissions are no
@@ -417,8 +405,11 @@
 };
 
 ChromeNativeFileSystemPermissionContext::
-    ChromeNativeFileSystemPermissionContext(content::BrowserContext*) {
+    ChromeNativeFileSystemPermissionContext(content::BrowserContext* context) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
+  auto* profile = Profile::FromBrowserContext(context);
+  content_settings_ = base::WrapRefCounted(
+      HostContentSettingsMapFactory::GetForProfile(profile));
 }
 
 scoped_refptr<content::NativeFileSystemPermissionGrant>
@@ -445,6 +436,11 @@
   return existing_grant;
 }
 
+bool ChromeNativeFileSystemPermissionContext::CanRequestWritePermission(
+    WritePermissionGrantImpl* grant) {
+  return grant->CanRequestPermission();
+}
+
 scoped_refptr<content::NativeFileSystemPermissionGrant>
 ChromeNativeFileSystemPermissionContext::GetWritePermissionGrant(
     const url::Origin& origin,
@@ -461,17 +457,30 @@
   // this newly returned grant should also be writable.
   // TODO(https://crbug.com/984769): Process ID and frame ID should not be used
   // to identify grants.
-  PermissionGrantImpl::Key grant_key{path, process_id, frame_id};
+  WritePermissionGrantImpl::Key grant_key{path, process_id, frame_id};
   auto*& existing_grant = origin_state.grants[grant_key];
   if (existing_grant) {
-    if (user_action == UserAction::kSave)
-      existing_grant->SetStatus(PermissionGrantImpl::PermissionStatus::GRANTED);
+    if (existing_grant->CanRequestPermission() &&
+        user_action == UserAction::kSave) {
+      existing_grant->SetStatus(
+          WritePermissionGrantImpl::PermissionStatus::GRANTED);
+    }
     return existing_grant;
   }
-  auto result = base::MakeRefCounted<PermissionGrantImpl>(
-      this, origin, grant_key, is_directory);
-  if (user_action == UserAction::kSave)
-    result->SetStatus(PermissionGrantImpl::PermissionStatus::GRANTED);
+
+  // If a grant does not exist for |origin|, create one, compute the permission
+  // status, and store a reference to it in |origin_state| by assigning
+  // |existing_grant|.
+  auto result = base::MakeRefCounted<WritePermissionGrantImpl>(
+      this, CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD, origin,
+      grant_key, is_directory);
+  if (result->CanRequestPermission()) {
+    if (user_action == UserAction::kSave) {
+      result->SetStatus(WritePermissionGrantImpl::PermissionStatus::GRANTED);
+    }
+  } else {
+    result->SetStatus(WritePermissionGrantImpl::PermissionStatus::DENIED);
+  }
   existing_grant = result.get();
   return result;
 }
@@ -541,7 +550,7 @@
       continue;
     }
     if (entry.second->GetStatus() ==
-        PermissionGrantImpl::PermissionStatus::GRANTED) {
+        WritePermissionGrantImpl::PermissionStatus::GRANTED) {
       if (entry.second->is_directory()) {
         grants.directory_write_grants.push_back(entry.second->path());
       } else {
@@ -614,7 +623,7 @@
   // first grant to remove (if any), and all other grants to remove are
   // immediately after it.
   auto grant_it = origin_state.grants.lower_bound(
-      PermissionGrantImpl::Key{base::FilePath(), process_id, frame_id});
+      WritePermissionGrantImpl::Key{base::FilePath(), process_id, frame_id});
   while (grant_it != origin_state.grants.end() &&
          grant_it->first.process_id == process_id &&
          grant_it->first.frame_id == frame_id) {
@@ -655,7 +664,7 @@
     ~ChromeNativeFileSystemPermissionContext() = default;
 
 void ChromeNativeFileSystemPermissionContext::PermissionGrantDestroyed(
-    PermissionGrantImpl* grant) {
+    WritePermissionGrantImpl* grant) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto it = origins_.find(grant->origin());
   DCHECK(it != origins_.end());
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
index c9e824c..8d60208 100644
--- a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
@@ -6,12 +6,17 @@
 #define CHROME_BROWSER_NATIVE_FILE_SYSTEM_CHROME_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_H_
 
 #include <map>
+#include <vector>
 
 #include "base/sequence_checker.h"
+#include "chrome/browser/permissions/permission_util.h"
+#include "components/content_settings/core/common/content_settings_types.h"
 #include "components/keyed_service/core/refcounted_keyed_service.h"
 #include "content/public/browser/native_file_system_permission_context.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
+class HostContentSettingsMap;
+
 namespace content {
 class BrowserContext;
 }  // namespace content
@@ -40,6 +45,85 @@
   explicit ChromeNativeFileSystemPermissionContext(
       content::BrowserContext* context);
 
+  class WritePermissionGrantImpl
+      : public content::NativeFileSystemPermissionGrant {
+   public:
+    // In the current implementation permission grants are scoped to the frame
+    // they are requested in. Within a frame we only want to have one grant per
+    // path. The Key struct contains these fields. Keys are comparable so they
+    // can be used with sorted containers like std::map and std::set.
+    // TODO(https://crbug.com/984769): Eliminate process_id and frame_id and
+    // replace usage of this struct with just a file path when grants stop being
+    // scoped to a frame.
+    struct Key {
+      base::FilePath path;
+      int process_id = 0;
+      int frame_id = 0;
+
+      bool operator==(const Key& rhs) const;
+      bool operator<(const Key& rhs) const;
+    };
+
+    WritePermissionGrantImpl(
+        scoped_refptr<ChromeNativeFileSystemPermissionContext> context,
+        ContentSettingsType content_settings_guard_type,
+        const url::Origin& origin,
+        const Key& key,
+        bool is_directory);
+
+    // content::NativeFileSystemPermissionGrant implementation:
+    PermissionStatus GetStatus() override;
+    void RequestPermission(int process_id,
+                           int frame_id,
+                           base::OnceClosure callback) override;
+
+    const url::Origin& origin() const {
+      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+      return origin_;
+    }
+
+    bool is_directory() const {
+      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+      return is_directory_;
+    }
+
+    const base::FilePath& path() const {
+      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+      return key_.path;
+    }
+
+    const Key& key() const {
+      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+      return key_;
+    }
+
+    // Returns true if the |content_setting_guard_type_| has not been blocked.
+    bool CanRequestPermission();
+
+    void SetStatus(PermissionStatus status);
+
+   protected:
+    ~WritePermissionGrantImpl() override;
+
+   private:
+    void OnPermissionRequestComplete(base::OnceClosure callback,
+                                     PermissionAction result);
+
+    SEQUENCE_CHECKER(sequence_checker_);
+
+    scoped_refptr<ChromeNativeFileSystemPermissionContext> const context_;
+    const ContentSettingsType write_guard_content_setting_type_;
+    const url::Origin origin_;
+    const Key key_;
+    const bool is_directory_;
+
+    // This member should only be updated via SetStatus(), to make sure
+    // observers are properly notified about any change in status.
+    PermissionStatus status_ = PermissionStatus::ASK;
+
+    DISALLOW_COPY_AND_ASSIGN(WritePermissionGrantImpl);
+  };
+
   // content::NativeFileSystemPermissionContext:
   scoped_refptr<content::NativeFileSystemPermissionGrant>
   GetReadPermissionGrant(const url::Origin& origin,
@@ -47,6 +131,8 @@
                          bool is_directory,
                          int process_id,
                          int frame_id) override;
+
+  bool CanRequestWritePermission(WritePermissionGrantImpl* grant);
   scoped_refptr<content::NativeFileSystemPermissionGrant>
   GetWritePermissionGrant(const url::Origin& origin,
                           const base::FilePath& path,
@@ -111,12 +197,13 @@
   // RefcountedKeyedService:
   void ShutdownOnUIThread() override;
 
+  HostContentSettingsMap* content_settings() { return content_settings_.get(); }
+
  private:
   // Destructor is private because this class is refcounted.
   ~ChromeNativeFileSystemPermissionContext() override;
 
-  class PermissionGrantImpl;
-  void PermissionGrantDestroyed(PermissionGrantImpl* grant);
+  void PermissionGrantDestroyed(WritePermissionGrantImpl* grant);
 
   void DidConfirmSensitiveDirectoryAccess(
       const url::Origin& origin,
@@ -132,6 +219,8 @@
   struct OriginState;
   std::map<url::Origin, OriginState> origins_;
 
+  scoped_refptr<HostContentSettingsMap> content_settings_;
+
   DISALLOW_COPY_AND_ASSIGN(ChromeNativeFileSystemPermissionContext);
 };
 
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context_unittest.cc b/chrome/browser/native_file_system/chrome_native_file_system_permission_context_unittest.cc
index b74b254..709801ab 100644
--- a/chrome/browser/native_file_system/chrome_native_file_system_permission_context_unittest.cc
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
 
+#include <memory>
+#include <string>
+
 #include "base/base_paths.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
@@ -11,12 +14,23 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_path_override.h"
 #include "build/build_config.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
+using content::BrowserContext;
+using content::WebContents;
+using content::WebContentsTester;
 using UserAction = ChromeNativeFileSystemPermissionContext::UserAction;
 using PermissionStatus =
     content::NativeFileSystemPermissionGrant::PermissionStatus;
@@ -25,17 +39,27 @@
 
 class ChromeNativeFileSystemPermissionContextTest : public testing::Test {
  public:
-  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    web_contents_ =
+        content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);
+    permission_context_ =
+        base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(
+            browser_context());
+  }
 
-  void TearDown() override { ASSERT_TRUE(temp_dir_.Delete()); }
+  void TearDown() override {
+    ASSERT_TRUE(temp_dir_.Delete());
+    web_contents_.reset();
+  }
 
   SensitiveDirectoryResult ConfirmSensitiveDirectoryAccessSync(
       ChromeNativeFileSystemPermissionContext* context,
       const std::vector<base::FilePath>& paths) {
     base::RunLoop loop;
     SensitiveDirectoryResult out_result;
-    context->ConfirmSensitiveDirectoryAccess(
-        kTestOrigin, paths, 0, 0,
+    permission_context_->ConfirmSensitiveDirectoryAccess(
+        kTestOrigin, paths, /*process_id=*/0, /*frame_id=*/0,
         base::BindLambdaForTesting([&](SensitiveDirectoryResult result) {
           out_result = result;
           loop.Quit();
@@ -44,84 +68,246 @@
     return out_result;
   }
 
- protected:
-  content::TestBrowserThreadBundle scoped_task_environment_;
-  base::ScopedTempDir temp_dir_;
+  void SetDefaultContentSettingValue(ContentSettingsType type,
+                                     ContentSetting value) {
+    HostContentSettingsMap* content_settings =
+        HostContentSettingsMapFactory::GetForProfile(&profile_);
+    content_settings->SetDefaultContentSetting(type, value);
+  }
 
+  void SetContentSettingValueForOrigin(url::Origin origin,
+                                       ContentSettingsType type,
+                                       ContentSetting value) {
+    HostContentSettingsMap* content_settings =
+        HostContentSettingsMapFactory::GetForProfile(&profile_);
+    content_settings->SetContentSettingDefaultScope(
+        origin.GetURL(), origin.GetURL(), type,
+        /*resource_identifier=*/std::string(), value);
+  }
+
+  ChromeNativeFileSystemPermissionContext* permission_context() {
+    return permission_context_.get();
+  }
+  BrowserContext* browser_context() { return &profile_; }
+  WebContents* web_contents() { return web_contents_.get(); }
+
+  int process_id() {
+    return web_contents()->GetMainFrame()->GetProcess()->GetID();
+  }
+
+  int frame_id() { return web_contents()->GetMainFrame()->GetRoutingID(); }
+
+  void ExpectCanRequestWritePermission(
+      content::NativeFileSystemPermissionGrant* actual_grant,
+      bool expected) {
+    auto* grant = static_cast<
+        ChromeNativeFileSystemPermissionContext::WritePermissionGrantImpl*>(
+        actual_grant);
+    EXPECT_EQ(expected, permission_context_->CanRequestWritePermission(grant));
+  }
+
+ protected:
   const url::Origin kTestOrigin =
       url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kTestOrigin2 =
+      url::Origin::Create(GURL("https://test.com"));
   const base::FilePath kTestPath =
       base::FilePath(FILE_PATH_LITERAL("/foo/bar"));
-  const int kProcessId = 1;
-  const int kFrameId = 2;
+
+  content::TestBrowserThreadBundle scoped_task_environment_;
+  base::ScopedTempDir temp_dir_;
+  scoped_refptr<ChromeNativeFileSystemPermissionContext> permission_context_;
+  content::RenderViewHostTestEnabler render_view_host_test_enabler_;
+  TestingProfile profile_;
+  std::unique_ptr<WebContents> web_contents_;
 };
 
+#if !defined(OS_ANDROID)
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        GetWritePermissionGrant_InitialState_OpenAction) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
-  auto grant = context->GetWritePermissionGrant(
-      kTestOrigin, kTestPath,
-      /*is_directory=*/false, kProcessId, kFrameId, UserAction::kOpen);
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/true);
   EXPECT_EQ(PermissionStatus::ASK, grant->GetStatus());
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        GetWritePermissionGrant_InitialState_WritableImplicitState) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
-  auto grant = context->GetWritePermissionGrant(
-      kTestOrigin, kTestPath, /*is_directory=*/false, kProcessId, kFrameId,
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
       UserAction::kSave);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/true);
+  EXPECT_EQ(PermissionStatus::GRANTED, grant->GetStatus());
+
+  // The existing grant should not change if the permission is blocked globally.
+  SetDefaultContentSettingValue(
+      CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/false);
   EXPECT_EQ(PermissionStatus::GRANTED, grant->GetStatus());
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        GetWritePermissionGrant_WriteGrantedChangesExistingGrant) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
-  auto grant1 = context->GetWritePermissionGrant(
-      kTestOrigin, kTestPath, /*is_directory=*/false, kProcessId, kFrameId,
+  auto grant1 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
       UserAction::kOpen);
-  auto grant2 = context->GetWritePermissionGrant(
-      kTestOrigin, kTestPath, /*is_directory=*/false, kProcessId, kFrameId,
+  auto grant2 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
       UserAction::kSave);
-  auto grant3 = context->GetWritePermissionGrant(
-      kTestOrigin, kTestPath, /*is_directory=*/false, kProcessId, kFrameId,
+  auto grant3 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
       UserAction::kOpen);
   // All grants should be the same grant, and be granted.
   EXPECT_EQ(grant1, grant2);
   EXPECT_EQ(grant1, grant3);
+  ExpectCanRequestWritePermission(grant1.get(), /*expected=*/true);
   EXPECT_EQ(PermissionStatus::GRANTED, grant1->GetStatus());
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        GetWritePermissionGrant_GrantIsRevokedWhenNoLongerUsed) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
-  auto grant = context->GetWritePermissionGrant(
-      kTestOrigin, kTestPath, /*is_directory=*/false, kProcessId, kFrameId,
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
       UserAction::kSave);
   EXPECT_EQ(PermissionStatus::GRANTED, grant->GetStatus());
   grant.reset();
 
   // After reset grant should go away, so new grant request should be in ASK
   // state.
-  grant = context->GetWritePermissionGrant(kTestOrigin, kTestPath,
-                                           /*is_directory=*/false, kProcessId,
-                                           kFrameId, UserAction::kOpen);
+  grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/true);
+  EXPECT_EQ(PermissionStatus::ASK, grant->GetStatus());
+}
+
+TEST_F(ChromeNativeFileSystemPermissionContextTest,
+       GetWritePermissionGrant_InitialState_OpenAction_GlobalGuardBlocked) {
+  SetDefaultContentSettingValue(
+      CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/false);
+  EXPECT_EQ(PermissionStatus::DENIED, grant->GetStatus());
+  grant.reset();
+
+  SetContentSettingValueForOrigin(
+      kTestOrigin, CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_ASK);
+
+  grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/true);
+  EXPECT_EQ(PermissionStatus::ASK, grant->GetStatus());
+}
+
+TEST_F(
+    ChromeNativeFileSystemPermissionContextTest,
+    GetWritePermissionGrant_InitialState_WritableImplicitState_GlobalGuardBlocked) {
+  SetDefaultContentSettingValue(
+      CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kSave);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/false);
+  EXPECT_EQ(PermissionStatus::DENIED, grant->GetStatus());
+  grant.reset();
+
+  SetContentSettingValueForOrigin(
+      kTestOrigin, CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_ASK);
+
+  grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kSave);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/true);
+  EXPECT_EQ(PermissionStatus::GRANTED, grant->GetStatus());
+}
+
+TEST_F(
+    ChromeNativeFileSystemPermissionContextTest,
+    GetWritePermissionGrant_WriteGrantedChangesExistingGrant_GlobalGuardBlocked) {
+  SetContentSettingValueForOrigin(
+      kTestOrigin, CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+
+  auto grant1 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  auto grant2 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kSave);
+  auto grant3 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  // All grants should be the same grant, and be denied.
+  EXPECT_EQ(grant1, grant2);
+  EXPECT_EQ(grant1, grant3);
+  ExpectCanRequestWritePermission(grant1.get(), /*expected=*/false);
+  EXPECT_EQ(PermissionStatus::DENIED, grant1->GetStatus());
+}
+
+TEST_F(
+    ChromeNativeFileSystemPermissionContextTest,
+    GetWritePermissionGrant_GrantIsRevokedWhenNoLongerUsed_GlobalGuardBlockedBeforeNewGrant) {
+  SetDefaultContentSettingValue(
+      CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kSave);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/false);
+  EXPECT_EQ(PermissionStatus::DENIED, grant->GetStatus());
+  grant.reset();
+
+  // After reset grant should go away, but the new grant request should be in
+  // DENIED state.
+  grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/false);
+  EXPECT_EQ(PermissionStatus::DENIED, grant->GetStatus());
+}
+
+TEST_F(
+    ChromeNativeFileSystemPermissionContextTest,
+    GetWritePermissionGrant_GrantIsRevokedWhenNoLongerUsed_GlobalGuardBlockedAfterNewGrant) {
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kSave);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/true);
+  EXPECT_EQ(PermissionStatus::GRANTED, grant->GetStatus());
+  grant.reset();
+
+  // After reset grant should go away, but the new grant request should be in
+  // ASK state.
+  grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/true);
+  EXPECT_EQ(PermissionStatus::ASK, grant->GetStatus());
+
+  SetDefaultContentSettingValue(
+      CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+
+  // After the guard is blocked, the permission status for |grant| should remain
+  // unchanged, but |CanRequestPermission()| should return false.
+  ExpectCanRequestWritePermission(grant.get(), /*expected=*/false);
   EXPECT_EQ(PermissionStatus::ASK, grant->GetStatus());
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        ConfirmSensitiveDirectoryAccess_NoSpecialPath) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
   const base::FilePath kTestPath =
 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
       base::FilePath(FILE_PATH_LITERAL("c:\\foo\\bar"));
@@ -130,72 +316,66 @@
 #endif
 
   // Path outside any special directories should be allowed.
-  EXPECT_EQ(SensitiveDirectoryResult::kAllowed,
-            ConfirmSensitiveDirectoryAccessSync(context.get(), {kTestPath}));
+  EXPECT_EQ(
+      SensitiveDirectoryResult::kAllowed,
+      ConfirmSensitiveDirectoryAccessSync(permission_context(), {kTestPath}));
 
   // Empty set of paths should also be allowed.
   EXPECT_EQ(SensitiveDirectoryResult::kAllowed,
-            ConfirmSensitiveDirectoryAccessSync(context.get(), {}));
+            ConfirmSensitiveDirectoryAccessSync(permission_context(), {}));
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        ConfirmSensitiveDirectoryAccess_DontBlockAllChildren) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
   base::FilePath home_dir = temp_dir_.GetPath().AppendASCII("home");
   base::ScopedPathOverride home_override(base::DIR_HOME, home_dir, true, true);
 
   // Home directory itself should not be allowed.
-  EXPECT_EQ(SensitiveDirectoryResult::kAbort,
-            ConfirmSensitiveDirectoryAccessSync(context.get(), {home_dir}));
+  EXPECT_EQ(
+      SensitiveDirectoryResult::kAbort,
+      ConfirmSensitiveDirectoryAccessSync(permission_context(), {home_dir}));
   // Parent of home directory should also not be allowed.
   EXPECT_EQ(SensitiveDirectoryResult::kAbort,
-            ConfirmSensitiveDirectoryAccessSync(context.get(),
+            ConfirmSensitiveDirectoryAccessSync(permission_context(),
                                                 {temp_dir_.GetPath()}));
   // Paths inside home directory should be allowed.
   EXPECT_EQ(SensitiveDirectoryResult::kAllowed,
-            ConfirmSensitiveDirectoryAccessSync(context.get(),
+            ConfirmSensitiveDirectoryAccessSync(permission_context(),
                                                 {home_dir.AppendASCII("foo")}));
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        ConfirmSensitiveDirectoryAccess_BlockAllChildren) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
   base::FilePath app_dir = temp_dir_.GetPath().AppendASCII("app");
   base::ScopedPathOverride app_override(chrome::DIR_APP, app_dir, true, true);
 
   // App directory itself should not be allowed.
-  EXPECT_EQ(SensitiveDirectoryResult::kAbort,
-            ConfirmSensitiveDirectoryAccessSync(context.get(), {app_dir}));
+  EXPECT_EQ(
+      SensitiveDirectoryResult::kAbort,
+      ConfirmSensitiveDirectoryAccessSync(permission_context(), {app_dir}));
   // Parent of App directory should also not be allowed.
   EXPECT_EQ(SensitiveDirectoryResult::kAbort,
-            ConfirmSensitiveDirectoryAccessSync(context.get(),
+            ConfirmSensitiveDirectoryAccessSync(permission_context(),
                                                 {temp_dir_.GetPath()}));
   // Paths inside App directory should also not be allowed.
   EXPECT_EQ(SensitiveDirectoryResult::kAbort,
-            ConfirmSensitiveDirectoryAccessSync(context.get(),
+            ConfirmSensitiveDirectoryAccessSync(permission_context(),
                                                 {app_dir.AppendASCII("foo")}));
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
        ConfirmSensitiveDirectoryAccess_RelativePathBlock) {
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
   base::FilePath home_dir = temp_dir_.GetPath().AppendASCII("home");
   base::ScopedPathOverride home_override(base::DIR_HOME, home_dir, true, true);
 
   // ~/.ssh should be blocked
   EXPECT_EQ(SensitiveDirectoryResult::kAbort,
             ConfirmSensitiveDirectoryAccessSync(
-                context.get(), {home_dir.AppendASCII(".ssh")}));
+                permission_context(), {home_dir.AppendASCII(".ssh")}));
   // And anything inside ~/.ssh should also be blocked
   EXPECT_EQ(SensitiveDirectoryResult::kAbort,
             ConfirmSensitiveDirectoryAccessSync(
-                context.get(), {home_dir.AppendASCII(".ssh/id_rsa")}));
+                permission_context(), {home_dir.AppendASCII(".ssh/id_rsa")}));
 }
 
 TEST_F(ChromeNativeFileSystemPermissionContextTest,
@@ -203,17 +383,137 @@
 // Linux is the only OS where we have some blocked directories with explicit
 // paths (as opposed to PathService provided paths).
 #if defined(OS_LINUX)
-  auto context =
-      base::MakeRefCounted<ChromeNativeFileSystemPermissionContext>(nullptr);
-
   // /dev should be blocked.
-  EXPECT_EQ(SensitiveDirectoryResult::kAbort,
-            ConfirmSensitiveDirectoryAccessSync(
-                context.get(), {base::FilePath(FILE_PATH_LITERAL("/dev"))}));
-  // As well as children of /dev.
   EXPECT_EQ(
       SensitiveDirectoryResult::kAbort,
       ConfirmSensitiveDirectoryAccessSync(
-          context.get(), {base::FilePath(FILE_PATH_LITERAL("/dev/foo"))}));
+          permission_context(), {base::FilePath(FILE_PATH_LITERAL("/dev"))}));
+  // As well as children of /dev.
+  EXPECT_EQ(SensitiveDirectoryResult::kAbort,
+            ConfirmSensitiveDirectoryAccessSync(
+                permission_context(),
+                {base::FilePath(FILE_PATH_LITERAL("/dev/foo"))}));
 #endif
 }
+
+TEST_F(ChromeNativeFileSystemPermissionContextTest, RequestPermission) {
+  // The test environment auto-dismisses prompts, as a result, a call to
+  // RequestPermission() should not change PermissionStatus.
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+
+  base::RunLoop loop;
+  grant->RequestPermission(process_id(), frame_id(), loop.QuitClosure());
+  loop.Run();
+  EXPECT_EQ(PermissionStatus::ASK, grant->GetStatus());
+}
+
+TEST_F(ChromeNativeFileSystemPermissionContextTest,
+       RequestPermission_AlreadyGranted) {
+  // If the permission has already been granted, a call to RequestPermission()
+  // should call the passed-in callback and return immediately without showing a
+  // prompt.
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kSave);
+
+  base::RunLoop loop;
+  grant->RequestPermission(process_id(), frame_id(), loop.QuitClosure());
+  loop.Run();
+  EXPECT_EQ(PermissionStatus::GRANTED, grant->GetStatus());
+}
+
+TEST_F(ChromeNativeFileSystemPermissionContextTest,
+       RequestPermission_GlobalGuardBlockedBeforeOpenGrant) {
+  // If the guard content setting is blocked, a call to RequestPermission()
+  // should update the PermissionStatus to DENIED, call the passed-in
+  // callback, and return immediately without showing a prompt.
+  SetDefaultContentSettingValue(
+      CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+
+  base::RunLoop loop;
+  grant->RequestPermission(process_id(), frame_id(), loop.QuitClosure());
+  loop.Run();
+  EXPECT_EQ(PermissionStatus::DENIED, grant->GetStatus());
+
+  auto grant2 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin2, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+
+  base::RunLoop loop2;
+  grant2->RequestPermission(process_id(), frame_id(), loop2.QuitClosure());
+  loop2.Run();
+  EXPECT_EQ(PermissionStatus::DENIED, grant2->GetStatus());
+
+  grant2.reset();
+  SetContentSettingValueForOrigin(
+      kTestOrigin2, CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_ASK);
+
+  grant2 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin2, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+
+  base::RunLoop loop3;
+  grant2->RequestPermission(process_id(), frame_id(), loop3.QuitClosure());
+  loop3.Run();
+  EXPECT_EQ(PermissionStatus::ASK, grant2->GetStatus());
+}
+
+TEST_F(ChromeNativeFileSystemPermissionContextTest,
+       RequestPermission_GlobalGuardBlockedAfterOpenGrant) {
+  // If the guard content setting is blocked, a call to RequestPermission()
+  // should update the PermissionStatus to DENIED, call the passed-in
+  // callback, and return immediately without showing a prompt.
+  auto grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  auto grant2 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin2, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+
+  SetDefaultContentSettingValue(
+      CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_BLOCK);
+
+  base::RunLoop loop;
+  grant->RequestPermission(process_id(), frame_id(), loop.QuitClosure());
+  loop.Run();
+  EXPECT_EQ(PermissionStatus::DENIED, grant->GetStatus());
+
+  base::RunLoop loop2;
+  grant2->RequestPermission(process_id(), frame_id(), loop2.QuitClosure());
+  loop2.Run();
+  EXPECT_EQ(PermissionStatus::DENIED, grant2->GetStatus());
+
+  grant.reset();
+  grant2.reset();
+
+  SetContentSettingValueForOrigin(
+      kTestOrigin, CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+      CONTENT_SETTING_ASK);
+  grant = permission_context()->GetWritePermissionGrant(
+      kTestOrigin, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+  grant2 = permission_context()->GetWritePermissionGrant(
+      kTestOrigin2, kTestPath, /*is_directory=*/false, process_id(), frame_id(),
+      UserAction::kOpen);
+
+  base::RunLoop loop3;
+  grant->RequestPermission(process_id(), frame_id(), loop3.QuitClosure());
+  loop3.Run();
+  EXPECT_EQ(PermissionStatus::ASK, grant->GetStatus());
+
+  base::RunLoop loop4;
+  grant2->RequestPermission(process_id(), frame_id(), loop4.QuitClosure());
+  loop4.Run();
+  EXPECT_EQ(PermissionStatus::DENIED, grant2->GetStatus());
+}
+
+#endif  // !defined(OS_ANDROID)
diff --git a/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc b/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc
index e5942f8..4741d21 100644
--- a/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc
+++ b/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/native_file_system/native_file_system_permission_context_factory.h"
 
 #include "base/no_destructor.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -36,7 +37,9 @@
     NativeFileSystemPermissionContextFactory()
     : RefcountedBrowserContextKeyedServiceFactory(
           "NativeFileSystemPermissionContext",
-          BrowserContextDependencyManager::GetInstance()) {}
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(HostContentSettingsMapFactory::GetInstance());
+}
 
 NativeFileSystemPermissionContextFactory::
     ~NativeFileSystemPermissionContextFactory() = default;
diff --git a/chrome/browser/notifications/proto/impression.proto b/chrome/browser/notifications/proto/impression.proto
index d753c33..1ad9c67 100644
--- a/chrome/browser/notifications/proto/impression.proto
+++ b/chrome/browser/notifications/proto/impression.proto
@@ -8,10 +8,12 @@
 
 package notifications.proto;
 
+import "notification_data.proto";
+
 // Contains data to determine when a notification should be shown to the user
 // and the user impression towards this notification. Should match Impression in
 // impression_types.h.
-// Next tag: 8
+// Next tag: 9
 message Impression {
   // The type of user feedback from a displayed notification. Should match
   // UserFeedback in notification_scheduler_types.h.
@@ -67,5 +69,9 @@
   // The unique identifier of the notification.
   optional string guid = 6;
 
+  // Used to generate impression result.
   repeated ImpressionMapping impression_mapping = 7;
+
+  // Custom data associated with a notification.
+  repeated CustomData custom_data = 8;
 }
diff --git a/chrome/browser/notifications/scheduler/internal/impression_types.cc b/chrome/browser/notifications/scheduler/internal/impression_types.cc
index 681c017..09517cb 100644
--- a/chrome/browser/notifications/scheduler/internal/impression_types.cc
+++ b/chrome/browser/notifications/scheduler/internal/impression_types.cc
@@ -21,7 +21,8 @@
   return create_time == other.create_time && feedback == other.feedback &&
          impression == other.impression && integrated == other.integrated &&
          task_start_time == other.task_start_time && guid == other.guid &&
-         type == other.type && impression_mapping == other.impression_mapping;
+         type == other.type && impression_mapping == other.impression_mapping &&
+         custom_data == other.custom_data;
 }
 
 SuppressionInfo::SuppressionInfo(const base::Time& last_trigger,
diff --git a/chrome/browser/notifications/scheduler/internal/impression_types.h b/chrome/browser/notifications/scheduler/internal/impression_types.h
index f01f05083..aeba9ab 100644
--- a/chrome/browser/notifications/scheduler/internal/impression_types.h
+++ b/chrome/browser/notifications/scheduler/internal/impression_types.h
@@ -25,6 +25,7 @@
 // an impression result, which may affect notification exposure.
 // 4. The impression is deleted after it expires.
 struct Impression {
+  using CustomData = std::map<std::string, std::string>;
   Impression();
   Impression(SchedulerClientType type,
              const std::string& guid,
@@ -63,6 +64,10 @@
 
   // Used to override default impression result.
   std::map<UserFeedback, ImpressionResult> impression_mapping;
+
+  // Custom data associated with a notification. Send back to the client when
+  // the user interacts with the notification.
+  CustomData custom_data;
 };
 
 // Contains details about supression and recovery after suppression expired.
diff --git a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
index 9d392f9..48a3c9da 100644
--- a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
+++ b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
@@ -325,6 +325,12 @@
       proto_impression_mapping->set_impression_result(
           ToImpressionResult(mapping.second));
     }
+
+    for (const auto& pair : impression.custom_data) {
+      auto* data = impression_ptr->add_custom_data();
+      data->set_key(pair.first);
+      data->set_value(pair.second);
+    }
   }
 
   if (client_state->suppression_info.has_value()) {
@@ -367,6 +373,11 @@
       impression.impression_mapping[user_feedback] = impression_result;
     }
 
+    for (int i = 0; i < proto_impression.custom_data_size(); ++i) {
+      const auto& pair = proto_impression.custom_data(i);
+      impression.custom_data.emplace(pair.key(), pair.value());
+    }
+
     client_state->impressions.emplace_back(std::move(impression));
   }
 
diff --git a/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc b/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
index b95c372b..a49cd38 100644
--- a/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
@@ -154,6 +154,10 @@
   first_impression.impression_mapping[UserFeedback::kClick] =
       ImpressionResult::kNeutral;
   TestClientStateConversion(&client_state);
+
+  // Verify custom data.
+  first_impression.custom_data = {{"url", "https://www.example.com"}};
+  TestClientStateConversion(&client_state);
 }
 
 // Verifies multiple impressions are serialized correctly.
diff --git a/chrome/browser/offline_pages/offline_page_request_handler.cc b/chrome/browser/offline_pages/offline_page_request_handler.cc
index 4b41389..daf99a96 100644
--- a/chrome/browser/offline_pages/offline_page_request_handler.cc
+++ b/chrome/browser/offline_pages/offline_page_request_handler.cc
@@ -31,7 +31,6 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/resource_type.h"
 #include "net/base/file_stream.h"
 #include "net/base/filename_util.h"
@@ -77,12 +76,6 @@
 // Consistent with the buffer size used in url request data reading.
 const size_t kMaxBufferSizeForValidation = 4096;
 
-content::BrowserThread::ID GetJobThreadID() {
-  return base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)
-             ? content::BrowserThread::UI
-             : content::BrowserThread::IO;
-}
-
 void GetFileSize(const base::FilePath& file_path, int64_t* file_size) {
   bool succeeded = base::GetFileSize(file_path, file_size);
   if (!succeeded) {
@@ -262,31 +255,14 @@
                       : nullptr;
 }
 
-void NotifyAvailableOfflinePagesOnJobThread(
-    base::WeakPtr<OfflinePageRequestHandler> job,
-    const std::vector<OfflinePageRequestHandler::Candidate>& candidates) {
-  DCHECK_CURRENTLY_ON(GetJobThreadID());
-
-  if (job)
-    job->OnOfflinePagesAvailable(candidates);
-}
-
 // Notifies OfflinePageRequestHandler about all the matched offline pages.
 void NotifyAvailableOfflinePagesOnUI(
     base::WeakPtr<OfflinePageRequestHandler> job,
     const std::vector<OfflinePageRequestHandler::Candidate>& candidates) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)) {
-    NotifyAvailableOfflinePagesOnJobThread(job, candidates);
-  } else {
-    // Delegates to IO thread since OfflinePageRequestHandler should only be
-    // accessed from IO thread.
-    base::PostTaskWithTraits(
-        FROM_HERE, {content::BrowserThread::IO},
-        base::BindOnce(&NotifyAvailableOfflinePagesOnJobThread, job,
-                       candidates));
-  }
+  if (job)
+    job->OnOfflinePagesAvailable(candidates);
 }
 
 // Failed to find an offline page.
@@ -487,7 +463,7 @@
       network_state_(NetworkState::CONNECTED_NETWORK),
       candidate_index_(0),
       weak_ptr_factory_(this) {
-  DCHECK_CURRENTLY_ON(GetJobThreadID());
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   std::string offline_header_value;
   extra_request_headers.GetHeader(kOfflinePageHeader, &offline_header_value);
   // Note that |offline_header| will be empty if parsing from the header value
@@ -499,7 +475,7 @@
 
 OfflinePageRequestHandler::NetworkState
 OfflinePageRequestHandler::GetNetworkState() const {
-  DCHECK_CURRENTLY_ON(GetJobThreadID());
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   if (offline_header_.reason == OfflinePageHeader::Reason::NET_ERROR)
     return OfflinePageRequestHandler::NetworkState::FLAKY_NETWORK;
diff --git a/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc b/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
index 48ab504a..067b590c 100644
--- a/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
+++ b/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/memory/weak_ptr.h"
@@ -40,7 +39,6 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/previews_state.h"
 #include "content/public/common/resource_type.h"
 #include "content/public/test/test_browser_thread_bundle.h"
@@ -97,14 +95,6 @@
 
 const int64_t kDownloadId = 42LL;
 
-// Returns the thread the navigation URL loader will run on. This determines
-// where the OfflinePageURLLoader should be created.
-content::BrowserThread::ID GetNavigationLoaderThreadID() {
-  return base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)
-             ? content::BrowserThread::UI
-             : content::BrowserThread::IO;
-}
-
 struct ResponseInfo {
   explicit ResponseInfo(int request_status) : request_status(request_status) {
     DCHECK_NE(net::OK, request_status);
@@ -270,16 +260,15 @@
 
  private:
   void OnHandleReady(MojoResult result, const mojo::HandleSignalsState& state);
-  void InterceptRequestOnLoaderThread(
-      const GURL& url,
-      const std::string& method,
-      const net::HttpRequestHeaders& extra_headers,
-      bool is_main_frame);
+  void InterceptRequestInternal(const GURL& url,
+                                const std::string& method,
+                                const net::HttpRequestHeaders& extra_headers,
+                                bool is_main_frame);
   void MaybeStartLoader(
       const network::ResourceRequest& request,
       content::URLLoaderRequestInterceptor::RequestHandler request_handler);
   void ReadBody();
-  void ReadCompletedOnLoaderThread(const ResponseInfo& response);
+  void ReadCompleted(const ResponseInfo& response);
 
   OfflinePageRequestHandlerTest* test_;
   std::unique_ptr<ChromeNavigationUIData> navigation_ui_data_;
@@ -950,8 +939,8 @@
 
 void OfflinePageURLLoaderBuilder::OnReceiveRedirect(
     const GURL& redirected_url) {
-  InterceptRequestOnLoaderThread(redirected_url, "GET",
-                                 net::HttpRequestHeaders(), true);
+  InterceptRequestInternal(redirected_url, "GET", net::HttpRequestHeaders(),
+                           true);
 }
 
 void OfflinePageURLLoaderBuilder::OnReceiveResponse(
@@ -968,19 +957,19 @@
     mime_type_.clear();
     body_.clear();
   }
-  ReadCompletedOnLoaderThread(
+  ReadCompleted(
       ResponseInfo(client_->completion_status().error_code, mime_type_, body_));
   // Clear intermediate data in preparation for next potential page loading.
   mime_type_.clear();
   body_.clear();
 }
 
-void OfflinePageURLLoaderBuilder::InterceptRequestOnLoaderThread(
+void OfflinePageURLLoaderBuilder::InterceptRequestInternal(
     const GURL& url,
     const std::string& method,
     const net::HttpRequestHeaders& extra_headers,
     bool is_main_frame) {
-  DCHECK_CURRENTLY_ON(GetNavigationLoaderThreadID());
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   client_ = std::make_unique<TestURLLoaderClient>(this);
 
@@ -1009,26 +998,17 @@
     const net::HttpRequestHeaders& extra_headers,
     bool is_main_frame) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  if (GetNavigationLoaderThreadID() == content::BrowserThread::UI) {
-    InterceptRequestOnLoaderThread(url, method, extra_headers, is_main_frame);
-  } else {
-    base::PostTaskWithTraits(
-        FROM_HERE, {content::BrowserThread::IO},
-        base::BindOnce(
-            &OfflinePageURLLoaderBuilder::InterceptRequestOnLoaderThread,
-            base::Unretained(this), url, method, extra_headers, is_main_frame));
-  }
+  InterceptRequestInternal(url, method, extra_headers, is_main_frame);
   base::RunLoop().Run();
 }
 
 void OfflinePageURLLoaderBuilder::MaybeStartLoader(
     const network::ResourceRequest& request,
     content::URLLoaderRequestInterceptor::RequestHandler request_handler) {
-  DCHECK_CURRENTLY_ON(GetNavigationLoaderThreadID());
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   if (!request_handler) {
-    ReadCompletedOnLoaderThread(ResponseInfo(net::ERR_FAILED));
+    ReadCompleted(ResponseInfo(net::ERR_FAILED));
     return;
   }
 
@@ -1063,7 +1043,7 @@
 
     // The pipe was closed.
     if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
-      ReadCompletedOnLoaderThread(ResponseInfo(net::ERR_FAILED));
+      ReadCompleted(ResponseInfo(net::ERR_FAILED));
       return;
     }
 
@@ -1078,15 +1058,14 @@
     MojoResult result,
     const mojo::HandleSignalsState& state) {
   if (result != MOJO_RESULT_OK) {
-    ReadCompletedOnLoaderThread(ResponseInfo(net::ERR_FAILED));
+    ReadCompleted(ResponseInfo(net::ERR_FAILED));
     return;
   }
   ReadBody();
 }
 
-void OfflinePageURLLoaderBuilder::ReadCompletedOnLoaderThread(
-    const ResponseInfo& response) {
-  DCHECK_CURRENTLY_ON(GetNavigationLoaderThreadID());
+void OfflinePageURLLoaderBuilder::ReadCompleted(const ResponseInfo& response) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   handle_watcher_.reset();
   client_.reset();
@@ -1099,15 +1078,7 @@
   if (offline_page_data && offline_page_data->is_offline_page())
     is_offline_page_set_in_navigation_data = true;
 
-  if (GetNavigationLoaderThreadID() == content::BrowserThread::UI) {
-    test()->ReadCompleted(response, is_offline_page_set_in_navigation_data);
-  } else {
-    base::PostTaskWithTraits(
-        FROM_HERE, {content::BrowserThread::UI},
-        base::BindOnce(&OfflinePageRequestHandlerTest::ReadCompleted,
-                       base::Unretained(test()), response,
-                       is_offline_page_set_in_navigation_data));
-  }
+  test()->ReadCompleted(response, is_offline_page_set_in_navigation_data);
 }
 
 TEST_F(OfflinePageRequestHandlerTest, FailedToCreateRequestJob) {
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index 0890c18..4259f49b 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -284,8 +284,8 @@
 
 #if !defined(OS_MACOSX)
 #include "base/compiler_specific.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/browser/app_window/native_app_window.h"
@@ -3544,11 +3544,12 @@
   // Launch an app that tries to open a fullscreen window.
   TestAddAppWindowObserver add_window_observer(
       extensions::AppWindowRegistry::Get(browser()->profile()));
-  OpenApplication(
-      AppLaunchParams(browser()->profile(), extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerNone,
-                      WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(
+          AppLaunchParams(browser()->profile(), extension->id(),
+                          apps::mojom::LaunchContainer::kLaunchContainerNone,
+                          WindowOpenDisposition::NEW_WINDOW,
+                          apps::mojom::AppLaunchSource::kSourceTest));
   extensions::AppWindow* window = add_window_observer.WaitForAppWindow();
   ASSERT_TRUE(window);
 
diff --git a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
index 1585e08..fc6bebf 100644
--- a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
+++ b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
@@ -37,7 +37,6 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test_utils.h"
@@ -130,16 +129,8 @@
             ->GetAppCacheService();
     do {
       base::RunLoop wait_loop;
-      if (base::FeatureList::IsEnabled(features::kNavigationLoaderOnUI)) {
-        WaitForAppcacheOnLoaderThread(manifest_url, appcache_service,
-                                      base::DoNothing(), &found_manifest);
-      } else {
-        base::PostTaskWithTraits(
-            FROM_HERE, {content::BrowserThread::IO},
-            base::BindOnce(WaitForAppcacheOnLoaderThread, manifest_url,
-                           appcache_service, wait_loop.QuitClosure(),
-                           &found_manifest));
-      }
+      WaitForAppcache(manifest_url, appcache_service, base::DoNothing(),
+                      &found_manifest);
       // There seems to be some flakiness in the appcache getting back to us, so
       // use a timeout task to try the appcache query again.
       base::PostDelayedTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
@@ -185,11 +176,10 @@
   // |found_manifest| if an appcache exists for |manifest_url|. |callback| will
   // be called on the UI thread after the info is retrieved, whether or not the
   // manifest exists.
-  static void WaitForAppcacheOnLoaderThread(
-      const GURL& manifest_url,
-      content::AppCacheService* appcache_service,
-      base::Closure callback,
-      bool* found_manifest) {
+  static void WaitForAppcache(const GURL& manifest_url,
+                              content::AppCacheService* appcache_service,
+                              base::Closure callback,
+                              bool* found_manifest) {
     scoped_refptr<content::AppCacheInfoCollection> info_collection =
         new content::AppCacheInfoCollection();
     appcache_service->GetAllAppCacheInfo(
diff --git a/chrome/browser/prerender/prerender_test_utils.cc b/chrome/browser/prerender/prerender_test_utils.cc
index 103e936..d0ff9fb4 100644
--- a/chrome/browser/prerender/prerender_test_utils.cc
+++ b/chrome/browser/prerender/prerender_test_utils.cc
@@ -168,13 +168,6 @@
       << " (Expected: " << NameFromFinalStatus(expected_final_status_)
       << ", Actual: " << NameFromFinalStatus(final_status()) << ")";
 
-  // Prerendering RenderViewHosts should be hidden before the first
-  // navigation, so this should be happen for every PrerenderContents for
-  // which a RenderViewHost is created, regardless of whether or not it's
-  // used.
-  if (new_render_view_host_)
-    EXPECT_TRUE(was_hidden_);
-
   // A used PrerenderContents will only be destroyed when we swap out
   // WebContents, at the end of a navigation caused by a call to
   // NavigateToURLImpl().
@@ -210,9 +203,9 @@
 
   if (!became_visible) {
     was_hidden_ = true;
-  } else if (became_visible && was_hidden_) {
-    // Once hidden, a prerendered RenderViewHost should only be shown after
-    // being removed from the PrerenderContents for display.
+  } else {
+    // A prerendered RenderViewHost should only be shown after being removed
+    // from the PrerenderContents for display.
     EXPECT_FALSE(GetRenderViewHost());
     was_shown_ = true;
   }
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 55083525c..7af7d439 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -153,10 +153,10 @@
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/devtools_util.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_registry.h"
@@ -2615,11 +2615,11 @@
 
   AppLaunchParams launch_params(
       GetProfile(), pwa->id(),
-      extensions::LaunchContainer::kLaunchContainerWindow,
+      apps::mojom::LaunchContainer::kLaunchContainerWindow,
       WindowOpenDisposition::CURRENT_TAB,
-      extensions::AppLaunchSource::kSourceContextMenu);
+      apps::mojom::AppLaunchSource::kSourceContextMenu);
   launch_params.override_url = params_.link_url;
-  OpenApplication(launch_params);
+  apps::LaunchService::Get(GetProfile())->OpenApplication(launch_params);
 }
 
 void RenderViewContextMenu::ExecProtocolHandler(int event_flags,
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 00a20a6..1423615 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -42,6 +42,9 @@
     if (enable_extensions) {
       deps += [ "extensions:closure_compile" ]
     }
+    if (enable_webui_tab_strip) {
+      deps += [ "tab_strip:closure_compile" ]
+    }
     if (is_android) {
       deps += [
         "explore_sites_internals:closure_compile",
@@ -310,6 +313,10 @@
   grit("tab_strip_resources") {
     source = "tab_strip/tab_strip_resources.grd"
     defines = chrome_grit_defines
+    deps = [
+      "tab_strip:tab_strip_modules",
+    ]
+    source_is_generated = true
     outputs = [
       "grit/tab_strip_resources.h",
       "grit/tab_strip_resources_map.cc",
@@ -317,5 +324,9 @@
       "tab_strip_resources.pak",
     ]
     output_dir = "$root_gen_dir/chrome"
+    grit_flags = [
+      "-E",
+      "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
+    ]
   }
 }
diff --git a/chrome/browser/resources/chromeos/add_supervision/add_supervision.js b/chrome/browser/resources/chromeos/add_supervision/add_supervision.js
index 81f7a755..0c5a114 100644
--- a/chrome/browser/resources/chromeos/add_supervision/add_supervision.js
+++ b/chrome/browser/resources/chromeos/add_supervision/add_supervision.js
@@ -33,7 +33,8 @@
 }
 
 let server = null;
-const proxy = addSupervision.mojom.AddSupervisionHandler.getProxy();
+const addSupervisionHandler =
+    addSupervision.mojom.AddSupervisionHandler.getRemote();
 
 Polymer({
   is: 'add-supervision-ui',
@@ -62,7 +63,7 @@
       this.offlineContentDiv.hidden = false;
     });
 
-    proxy.getOAuthToken().then((result) => {
+    addSupervisionHandler.getOAuthToken().then((result) => {
       const webviewUrl = loadTimeData.getString('webviewUrl');
       const eventOriginFilter = loadTimeData.getString('eventOriginFilter');
       const webview =
diff --git a/chrome/browser/resources/chromeos/add_supervision/add_supervision_api_server.js b/chrome/browser/resources/chromeos/add_supervision/add_supervision_api_server.js
index a112831..a22524b9 100644
--- a/chrome/browser/resources/chromeos/add_supervision/add_supervision_api_server.js
+++ b/chrome/browser/resources/chromeos/add_supervision/add_supervision_api_server.js
@@ -28,7 +28,8 @@
   constructor(webviewElement, targetURL, originURLPrefix) {
     super(webviewElement, METHOD_LIST, targetURL, originURLPrefix);
 
-    this.proxy_ = addSupervision.mojom.AddSupervisionHandler.getProxy();
+    this.addSupervisionHandler_ =
+        addSupervision.mojom.AddSupervisionHandler.getRemote();
 
     this.registerMethod('logOut', this.logOut.bind(this));
     this.registerMethod(
@@ -43,7 +44,7 @@
    * @param {!Array} unused Placeholder unused empty parameter.
    */
   logOut(unused) {
-    return this.proxy_.logOut();
+    return this.addSupervisionHandler_.logOut();
   }
 
   /**
@@ -54,7 +55,7 @@
    *     apps installed on the device.
    */
   getInstalledArcApps(unused) {
-    return this.proxy_.getInstalledArcApps();
+    return this.addSupervisionHandler_.getInstalledArcApps();
   }
 
   /**
@@ -67,7 +68,7 @@
    * resolve with boolean result indicating whether the dialog was closed.
    */
   requestClose(unused) {
-    return this.proxy_.requestClose();
+    return this.addSupervisionHandler_.requestClose();
   }
 
   /**
@@ -75,6 +76,6 @@
    * @param {!Array} unused Placeholder unused empty parameter.
    */
   notifySupervisionEnabled(unused) {
-    return this.proxy_.notifySupervisionEnabled();
+    return this.addSupervisionHandler_.notifySupervisionEnabled();
   }
 }
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/OWNERS b/chrome/browser/resources/chromeos/internet_detail_dialog/OWNERS
new file mode 100644
index 0000000..c970217
--- /dev/null
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/OWNERS
@@ -0,0 +1,4 @@
+khorimoto@chromium.org
+stevenjb@chromium.org
+
+# COMPONENT: UI>Shell>Networking
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
index a65ce0b..14500c54 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
@@ -88,6 +88,34 @@
     this.getNetworkDetails_();
   },
 
+  /** @override */
+  ready: function() {
+    CrOncStrings = {
+      OncTypeCellular: loadTimeData.getString('OncTypeCellular'),
+      OncTypeEthernet: loadTimeData.getString('OncTypeEthernet'),
+      OncTypeMobile: loadTimeData.getString('OncTypeMobile'),
+      OncTypeTether: loadTimeData.getString('OncTypeTether'),
+      OncTypeVPN: loadTimeData.getString('OncTypeVPN'),
+      OncTypeWiFi: loadTimeData.getString('OncTypeWiFi'),
+      OncTypeWiMAX: loadTimeData.getString('OncTypeWiMAX'),
+      networkListItemConnected:
+          loadTimeData.getString('networkListItemConnected'),
+      networkListItemConnecting:
+          loadTimeData.getString('networkListItemConnecting'),
+      networkListItemConnectingTo:
+          loadTimeData.getString('networkListItemConnectingTo'),
+      networkListItemInitializing:
+          loadTimeData.getString('networkListItemInitializing'),
+      networkListItemScanning:
+          loadTimeData.getString('networkListItemScanning'),
+      networkListItemNotConnected:
+          loadTimeData.getString('networkListItemNotConnected'),
+      networkListItemNoNetwork:
+          loadTimeData.getString('networkListItemNoNetwork'),
+      vpnNameTemplate: loadTimeData.getString('vpnNameTemplate'),
+    };
+  },
+
   /** @private */
   close_: function() {
     chrome.send('dialogClose');
diff --git a/chrome/browser/resources/chromeos/network_ui/network_ui.js b/chrome/browser/resources/chromeos/network_ui/network_ui.js
index 3312e8cd..21eab80 100644
--- a/chrome/browser/resources/chromeos/network_ui/network_ui.js
+++ b/chrome/browser/resources/chromeos/network_ui/network_ui.js
@@ -517,9 +517,10 @@
                              .getMojoServiceProxy();
 
     /** Set the refresh rate if the interval is found in the url. */
-    const interval = parseQueryParams(window.location)['refresh'];
-    if (interval && interval != '')
+    const interval = new URL(window.location.href).searchParams.get('refresh');
+    if (interval) {
       setInterval(requestNetworks, parseInt(interval, 10) * 1000);
+    }
   };
 
   /**
diff --git a/chrome/browser/resources/chromeos/set_time_dialog/set_time_dialog.js b/chrome/browser/resources/chromeos/set_time_dialog/set_time_dialog.js
index 577c945..3dfc945 100644
--- a/chrome/browser/resources/chromeos/set_time_dialog/set_time_dialog.js
+++ b/chrome/browser/resources/chromeos/set_time_dialog/set_time_dialog.js
@@ -267,15 +267,11 @@
   applyTime_: function() {
     const now = this.getInputTime_();
 
-    if (this.isTimezoneVisible_) {
-      // Add timezone offset to get real time. This is only necessary when the
-      // timezone was updated, which is only possible when the dropdown is
-      // visible.
-      const timezoneDelta = getTimezoneDelta(
-          /** @type {string} */ (loadTimeData.getValue('currentTimezoneId')),
-          this.selectedTimezone_);
-      now.setMilliseconds(now.getMilliseconds() + timezoneDelta);
-    }
+    // Add timezone offset to get real time.
+    const timezoneDelta = getTimezoneDelta(
+        /** @type {string} */ (loadTimeData.getValue('currentTimezoneId')),
+        this.selectedTimezone_);
+    now.setMilliseconds(now.getMilliseconds() + timezoneDelta);
 
     const seconds = Math.floor(now / 1000);
     this.browserProxy_.setTimeInSeconds(seconds);
diff --git a/chrome/browser/resources/device_log_ui/device_log_ui.js b/chrome/browser/resources/device_log_ui/device_log_ui.js
index 1110307..2e41811 100644
--- a/chrome/browser/resources/device_log_ui/device_log_ui.js
+++ b/chrome/browser/resources/device_log_ui/device_log_ui.js
@@ -124,9 +124,9 @@
    * Sets refresh rate if the interval is found in the url.
    */
   const setRefresh = function() {
-    const interval = parseQueryParams(window.location)['refresh'];
-    if (interval && interval != '') {
-      setInterval(requestLog, parseInt(interval) * 1000);
+    const interval = new URL(window.location).searchParams.get('refresh');
+    if (interval) {
+      setInterval(requestLog, parseInt(interval, 10) * 1000);
     }
   };
 
diff --git a/chrome/browser/resources/local_discovery/local_discovery.js b/chrome/browser/resources/local_discovery/local_discovery.js
index 7094e5c..1d2a545 100644
--- a/chrome/browser/resources/local_discovery/local_discovery.js
+++ b/chrome/browser/resources/local_discovery/local_discovery.js
@@ -520,8 +520,7 @@
 
   function getOverlayIDFromPath() {
     if (document.location.pathname == '/register') {
-      const params = parseQueryParams(document.location);
-      return params['id'] || null;
+      return new URL(document.location).searchParams.get('id');
     }
   }
 
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index 5bc5a2d..1238151 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -952,9 +952,9 @@
   }
 }
 
-#backgrounds-menu .bg-sel-tile-bg.selected .bg-sel-tile:focus,
-#backgrounds-image-menu .bg-sel-tile-bg.selected .bg-sel-tile:focus {
-  outline: none;
+:not(.using-mouse-nav) :-webkit-any(#backgrounds-menu, #backgrounds-image-menu)
+  .bg-sel-tile-bg.selected:focus-within {
+  outline: auto -webkit-focus-ring-color;
 }
 
 .using-mouse-nav .bg-sel-tile:focus {
diff --git a/chrome/browser/resources/local_ntp/most_visited_util.js b/chrome/browser/resources/local_ntp/most_visited_util.js
index 0ee762a..8ba70cd 100644
--- a/chrome/browser/resources/local_ntp/most_visited_util.js
+++ b/chrome/browser/resources/local_ntp/most_visited_util.js
@@ -60,6 +60,12 @@
  * Parses query parameters from Location.
  * @param {!Location} location The URL to generate the CSS url for.
  * @return {Object} Dictionary containing name value pairs for URL.
+ *
+ * TODO(dbeam): we should update callers of this method to use
+ * URLSearchParams#get() instead (which I have a higher confidence handles
+ * escaping and edge cases correctly). Note: that calling URLSearchParams#get()
+ * also has the behavior of only returning the first &param= in the URL (i.e.
+ * ?param=1&param=2 + .get('param') would return '1').
  */
 function parseQueryParams(location) {
   const params = Object.create(null);
diff --git a/chrome/browser/resources/settings/crostini_page/BUILD.gn b/chrome/browser/resources/settings/crostini_page/BUILD.gn
index db0a920..2e4035e 100644
--- a/chrome/browser/resources/settings/crostini_page/BUILD.gn
+++ b/chrome/browser/resources/settings/crostini_page/BUILD.gn
@@ -24,6 +24,7 @@
 js_library("crostini_export_import") {
   deps = [
     ":crostini_browser_proxy",
+    "//ui/webui/resources/js:web_ui_listener_behavior",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js b/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
index 225da32..f9b965ea 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
+++ b/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
@@ -51,14 +51,26 @@
      */
     removeCrostiniSharedPath(vmName, path) {}
 
-    /* Request chrome send a crostini-installer-status-changed event with the
-    current installer status */
+    /**
+     * Request chrome send a crostini-installer-status-changed event with the
+     * current installer status
+     */
     requestCrostiniInstallerStatus() {}
 
-    /* Export crostini container. */
+    /**
+     * Request chrome send a crostini-export-import-operation-status-changed
+     * event with the current operation status
+     */
+    requestCrostiniExportImportOperationStatus() {}
+
+    /**
+     * Export crostini container.
+     */
     exportCrostiniContainer() {}
 
-    /* Import crostini container. */
+    /**
+     * Import crostini container.
+     */
     importCrostiniContainer() {}
   }
 
@@ -100,6 +112,11 @@
     }
 
     /** @override */
+    requestCrostiniExportImportOperationStatus() {
+      chrome.send('requestCrostiniExportImportOperationStatus');
+    }
+
+    /** @override */
     exportCrostiniContainer() {
       chrome.send('exportCrostiniContainer');
     }
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_export_import.html b/chrome/browser/resources/settings/crostini_page/crostini_export_import.html
index 88989b88..43c4d73d 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_export_import.html
+++ b/chrome/browser/resources/settings/crostini_page/crostini_export_import.html
@@ -2,6 +2,7 @@
 
 <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="crostini_browser_proxy.html">
 <link rel="import" href="crostini_import_confirmation_dialog.html">
 <link rel="import" href="../i18n_setup.html">
@@ -15,18 +16,18 @@
         <div id="exportCrostiniLabel" class="start secondary">
           $i18n{crostiniExportLabel}
         </div>
-        <cr-button on-click="onExportClick_"
-          aria-labelledby="exportCrostiniLabel">
-           $i18n{crostiniExport}
+        <cr-button on-click="onExportClick_" disabled="[[!enableButtons_]]"
+            aria-labelledby="exportCrostiniLabel">
+          $i18n{crostiniExport}
         </cr-button>
       </div>
       <div id="import" class="list-item">
         <div id="importCrostiniLabel" class="start secondary">
           $i18n{crostiniImportLabel}
         </div>
-        <cr-button on-click="onImportClick_"
-          aria-labelledby="importCrostiniLabel">
-           $i18n{crostiniImport}
+        <cr-button on-click="onImportClick_" disabled="[[!enableButtons_]]"
+            aria-labelledby="importCrostiniLabel">
+          $i18n{crostiniImport}
         </cr-button>
       </div>
     </div>
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_export_import.js b/chrome/browser/resources/settings/crostini_page/crostini_export_import.js
index fa1be63..1cb7d34 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_export_import.js
+++ b/chrome/browser/resources/settings/crostini_page/crostini_export_import.js
@@ -11,12 +11,33 @@
 Polymer({
   is: 'settings-crostini-export-import',
 
+  behaviors: [WebUIListenerBehavior],
+
   properties: {
     /** @private */
     showImportConfirmationDialog_: {
       type: Boolean,
       value: false,
     },
+
+    /**
+     * Whether the export import buttons should be enabled. Initially false
+     * until status has been confirmed.
+     * @private {boolean}
+     */
+    enableButtons_: {
+      type: Boolean,
+      value: false,
+    },
+  },
+
+  attached: function() {
+    this.addWebUIListener(
+        'crostini-export-import-operation-status-changed', inProgress => {
+          this.enableButtons_ = !inProgress;
+        });
+    settings.CrostiniBrowserProxyImpl.getInstance()
+        .requestCrostiniExportImportOperationStatus();
   },
 
   /** @private */
diff --git a/chrome/browser/resources/tab_strip/BUILD.gn b/chrome/browser/resources/tab_strip/BUILD.gn
new file mode 100644
index 0000000..cc9cf06
--- /dev/null
+++ b/chrome/browser/resources/tab_strip/BUILD.gn
@@ -0,0 +1,40 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
+
+js_type_check("closure_compile") {
+  deps = [
+    ":custom_element",
+    ":tab",
+    ":tab_list",
+  ]
+}
+
+js_library("custom_element") {
+}
+js_library("tab") {
+}
+js_library("tab_list") {
+}
+
+group("tab_strip_modules") {
+  deps = [
+    ":tab_list_module",
+    ":tab_module",
+  ]
+}
+
+polymer_modulizer("tab") {
+  js_file = "tab.js"
+  html_file = "tab.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("tab_list") {
+  js_file = "tab_list.js"
+  html_file = "tab_list.html"
+  html_type = "v3-ready"
+}
diff --git a/chrome/browser/resources/tab_strip/custom_element.js b/chrome/browser/resources/tab_strip/custom_element.js
new file mode 100644
index 0000000..2d8e4e9
--- /dev/null
+++ b/chrome/browser/resources/tab_strip/custom_element.js
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Super class for all custom elements defined in the tab strip.
+ */
+export class CustomElement extends HTMLElement {
+  constructor() {
+    super();
+
+    this.attachShadow({mode: 'open'});
+    const template = document.createElement('template');
+    template.innerHTML = this.constructor.template || '';
+    this.shadowRoot.appendChild(template.content.cloneNode(true));
+  }
+}
diff --git a/chrome/browser/resources/tab_strip/tab.html b/chrome/browser/resources/tab_strip/tab.html
new file mode 100644
index 0000000..1855bec
--- /dev/null
+++ b/chrome/browser/resources/tab_strip/tab.html
@@ -0,0 +1,84 @@
+<style>
+  :host {
+    border-radius: 8px;
+    box-shadow: var(--tabstrip-elevation-box-shadow);
+    cursor: pointer;
+    display: flex;
+    flex-direction: column;
+    height: 312px;
+    overflow: hidden;
+    transition: box-shadow 125ms;
+    width: 216px;
+  }
+
+  :host(:focus) {
+    box-shadow: 0 0 0 2px var(--tabstrip-focus-color);
+    outline: none;
+  }
+
+  #title {
+    align-items: center;
+    background: var(--tabstrip-card-background-color);
+    border-block-end: 1px solid rgb(var(--google-grey-300-rgb));
+    box-sizing: border-box;
+    display: flex;
+    height: 40px;
+    margin: 0;
+    padding-inline-end: 4px;
+    padding-inline-start: 12px;
+  }
+
+  #favicon {
+    flex-shrink: 0;
+    height: 16px;
+    margin-inline-end: 8px;
+    width: 16px;
+  }
+
+  #titleText {
+    font-size: 100%;
+    font-weight: normal;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  #close {
+    -webkit-appearance: none;
+    align-items: center;
+    background-color: transparent;
+    border: 0;
+    display: flex;
+    flex-shrink: 0;
+    height: 32px;
+    justify-content: center;
+    margin-inline-start: auto;
+    padding: 0;
+    position: relative;
+    width: 32px;
+  }
+
+  #closeIcon {
+    background:
+        url(chrome://resources/images/icon_clear.svg) center/contain no-repeat;
+    display: block;
+    height: 24px;
+    position: relative;
+    width: 24px;
+  }
+
+  #thumbnail {
+    background: var(--tabstrip-card-background-color);
+    flex: 1;
+  }
+</style>
+
+<header id="title">
+  <span id="favicon"></span>
+  <h2 id="titleText">Page title</h2>
+  <button id="close" aria-label="Close tab">
+    <span id="closeIcon"></span>
+  </button>
+</header>
+
+<div id="thumbnail"></div>
diff --git a/chrome/browser/resources/tab_strip/tab.js b/chrome/browser/resources/tab_strip/tab.js
new file mode 100644
index 0000000..587656e
--- /dev/null
+++ b/chrome/browser/resources/tab_strip/tab.js
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {CustomElement} from './custom_element.js';
+
+class Tab extends CustomElement {
+  static get template() {
+    return `{__html_template__}`;
+  }
+
+  connectedCallback() {
+    this.setAttribute('tabindex', 0);
+  }
+}
+
+customElements.define('tabstrip-tab', Tab);
diff --git a/chrome/browser/resources/tab_strip/tab_list.html b/chrome/browser/resources/tab_strip/tab_list.html
new file mode 100644
index 0000000..5e15634a
--- /dev/null
+++ b/chrome/browser/resources/tab_strip/tab_list.html
@@ -0,0 +1,23 @@
+<style>
+  :host {
+    display: flex;
+    padding: 16px;
+    width: fit-content;
+  }
+
+  tabstrip-tab {
+    flex-shrink: 0;
+    margin-inline-end: 16px;
+  }
+
+  tabstrip-tab:last-child {
+    margin-inline-end: 0;
+  }
+</style>
+
+<tabstrip-tab></tabstrip-tab>
+<tabstrip-tab></tabstrip-tab>
+<tabstrip-tab></tabstrip-tab>
+<tabstrip-tab></tabstrip-tab>
+<tabstrip-tab></tabstrip-tab>
+<tabstrip-tab></tabstrip-tab>
diff --git a/chrome/browser/resources/tab_strip/tab_list.js b/chrome/browser/resources/tab_strip/tab_list.js
new file mode 100644
index 0000000..681652b
--- /dev/null
+++ b/chrome/browser/resources/tab_strip/tab_list.js
@@ -0,0 +1,15 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import './tab.js';
+
+import {CustomElement} from './custom_element.js';
+
+class TabList extends CustomElement {
+  static get template() {
+    return `{__html_template__}`;
+  }
+}
+
+customElements.define('tabstrip-tab-list', TabList);
diff --git a/chrome/browser/resources/tab_strip/tab_strip.html b/chrome/browser/resources/tab_strip/tab_strip.html
index 8ddf6a3..c2054bff 100644
--- a/chrome/browser/resources/tab_strip/tab_strip.html
+++ b/chrome/browser/resources/tab_strip/tab_strip.html
@@ -2,7 +2,35 @@
 <html dir="$i18n{textdirection}" lang="$i18n{language}">
   <head>
     <meta charset="utf-8">
+    <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+    <style>
+      html {
+        --google-grey-50-rgb: 248, 249, 250;
+        --google-grey-200-rgb: 232, 234, 237;
+        --google-grey-300-rgb: 218, 220, 224;
+        --google-grey-800-rgb: 60, 64, 67;
+        --google-grey-900-rgb: 32, 33, 36;
+        --google-blue-300-rgb: 138, 180, 248;
+        --google-blue-500-rgb: 66, 133, 244;
+
+        --tabstrip-background-color: rgb(var(--google-grey-50-rgb));
+        --tabstrip-card-background-color: white;
+        --tabstrip-elevation-box-shadow:
+            0 0 0 1px rgb(var(--google-grey-300-rgb));
+        --tabstrip-focus-color: rgb(var(--google-blue-500-rgb));
+        --tabstrip-primary-text-color: rgb(var(--google-grey-900-rgb));
+      }
+
+      body {
+        background: var(--tabstrip-background-color);
+        color: var(--tabstrip-primary-text-color);
+        margin: 0;
+        padding: 0;
+      }
+    </style>
   </head>
   <body>
+    <tabstrip-tab-list></tabstrip-tab-list>
+    <script src="tab_list.js" type="module"></script>
   </body>
 </html>
diff --git a/chrome/browser/resources/tab_strip/tab_strip_resources.grd b/chrome/browser/resources/tab_strip/tab_strip_resources.grd
index e9805a9..db7625d 100644
--- a/chrome/browser/resources/tab_strip/tab_strip_resources.grd
+++ b/chrome/browser/resources/tab_strip/tab_strip_resources.grd
@@ -17,6 +17,23 @@
           file="tab_strip.html"
           type="chrome_html"
           compress="gzip"/>
+      <structure
+          name="IDR_TAB_STRIP_CUSTOM_ELEMENT_JS"
+          file="custom_element.js"
+          type="chrome_html"
+          compress="gzip"/>
+      <structure
+          name="IDR_TAB_STRIP_TAB_LIST_JS"
+          file="${root_gen_dir}/chrome/browser/resources/tab_strip/tab_list.js"
+          use_base_dir="false"
+          type="chrome_html"
+          compress="gzip"/>
+      <structure
+          name="IDR_TAB_STRIP_TAB_JS"
+          file="${root_gen_dir}/chrome/browser/resources/tab_strip/tab.js"
+          use_base_dir="false"
+          type="chrome_html"
+          compress="gzip"/>
     </structures>
   </release>
 </grit>
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
index 3062fbf..4d67e7e 100644
--- a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
+++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
@@ -294,10 +294,6 @@
     bool download_file_has_koly_signature) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  UMA_HISTOGRAM_BOOLEAN(
-      "SBClientDownload."
-      "DownloadFileWithoutDiskImageExtensionHasKolySignature",
-      download_file_has_koly_signature);
   if (download_file_has_koly_signature)
     StartExtractDmgFeatures();
   else
@@ -333,21 +329,9 @@
                              uma_file_type);
     results_.type = ClientDownloadRequest::MAC_EXECUTABLE;
   } else {
-    base::UmaHistogramSparse("SBClientDownload.DmgFileFailureByType",
-                             uma_file_type);
     results_.type = ClientDownloadRequest::MAC_ARCHIVE_FAILED_PARSING;
   }
 
-  if (results_.archived_executable) {
-    base::UmaHistogramSparse("SBClientDownload.DmgFileHasExecutableByType",
-                             uma_file_type);
-    UMA_HISTOGRAM_COUNTS_1M("SBClientDownload.DmgFileArchivedBinariesCount",
-                            archive_results.archived_binary.size());
-  } else {
-    base::UmaHistogramSparse("SBClientDownload.DmgFileHasNoExecutableByType",
-                             uma_file_type);
-  }
-
   UMA_HISTOGRAM_MEDIUM_TIMES("SBClientDownload.ExtractDmgFeaturesTimeMedium",
                              base::TimeTicks::Now() - dmg_analysis_start_time_);
 
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index 7cbdbbea..acaadef5 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -37,7 +37,6 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/browser/safe_browsing_network_context.h"
-#include "components/safe_browsing/browser/safe_browsing_url_request_context_getter.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/common/safebrowsing_constants.h"
 #include "components/safe_browsing/db/database_manager.h"
@@ -108,7 +107,7 @@
 
   network_context_ =
       std::make_unique<safe_browsing::SafeBrowsingNetworkContext>(
-          nullptr, user_data_dir,
+          user_data_dir,
           base::BindRepeating(&SafeBrowsingService::CreateNetworkContextParams,
                               base::Unretained(this)));
 
diff --git a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
index 53557460..b94a70a7 100644
--- a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
+++ b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
@@ -6,6 +6,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
@@ -20,6 +21,7 @@
 #include "chrome/browser/external_protocol/external_protocol_handler.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
+#include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
@@ -69,7 +71,7 @@
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #if BUILDFLAG(HAS_SPELLCHECK_PANEL)
-#include "chrome/browser/spellchecker/test/spellcheck_content_browser_client.h"
+#include "chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.h"
 #include "components/spellcheck/common/spellcheck_panel.mojom.h"
 #endif  // BUILDFLAG(HAS_SPELLCHECK_PANEL)
 #endif
@@ -967,28 +969,16 @@
   DISALLOW_COPY_AND_ASSIGN(MockSpellCheckHost);
 };
 
-class TestBrowserClientForSpellCheck : public ChromeContentBrowserClient {
+class SpellCheckBrowserTestHelper {
  public:
-  TestBrowserClientForSpellCheck() = default;
+  SpellCheckBrowserTestHelper() {
+    SpellCheckHostChromeImpl::OverrideBinderForTesting(
+        base::BindRepeating(&SpellCheckBrowserTestHelper::BindSpellCheckHost,
+                            base::Unretained(this)));
+  }
 
-  // ContentBrowserClient overrides.
-  void OverrideOnBindInterface(
-      const service_manager::BindSourceInfo& remote_info,
-      const std::string& name,
-      mojo::ScopedMessagePipeHandle* handle) override {
-    if (name != spellcheck::mojom::SpellCheckHost::Name_)
-      return;
-
-    spellcheck::mojom::SpellCheckHostRequest request(std::move(*handle));
-
-    // Override the default SpellCheckHost interface.
-    auto ui_task_runner = base::CreateSingleThreadTaskRunnerWithTraits(
-        {content::BrowserThread::UI});
-    ui_task_runner->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            &TestBrowserClientForSpellCheck::BindSpellCheckHostRequest,
-            base::Unretained(this), std::move(request), remote_info));
+  ~SpellCheckBrowserTestHelper() {
+    SpellCheckHostChromeImpl::OverrideBinderForTesting(base::NullCallback());
   }
 
   // Retrieves the registered MockSpellCheckHost for the given
@@ -1021,7 +1011,7 @@
         {content::BrowserThread::UI});
     ui_task_runner->PostDelayedTask(
         FROM_HERE,
-        base::BindOnce(&TestBrowserClientForSpellCheck::Timeout,
+        base::BindOnce(&SpellCheckBrowserTestHelper::Timeout,
                        base::Unretained(this)),
         base::TimeDelta::FromSeconds(1));
 
@@ -1031,14 +1021,13 @@
   }
 
  private:
-  void BindSpellCheckHostRequest(
-      spellcheck::mojom::SpellCheckHostRequest request,
-      const service_manager::BindSourceInfo& source_info) {
+  void BindSpellCheckHost(
+      int render_process_id,
+      mojo::PendingReceiver<spellcheck::mojom::SpellCheckHost> receiver) {
     content::RenderProcessHost* host =
-        content::RenderProcessHost::FromRendererInstanceId(
-            source_info.identity.instance_id());
+        content::RenderProcessHost::FromID(render_process_id);
     auto spell_check_host = std::make_unique<MockSpellCheckHost>(host);
-    spell_check_host->SpellCheckHostRequest(std::move(request));
+    spell_check_host->SpellCheckHostRequest(std::move(receiver));
     spell_check_hosts_.push_back(std::move(spell_check_host));
     if (quit_on_bind_closure_)
       std::move(quit_on_bind_closure_).Run();
@@ -1052,20 +1041,18 @@
   base::OnceClosure quit_on_bind_closure_;
   std::vector<std::unique_ptr<MockSpellCheckHost>> spell_check_hosts_;
 
-  DISALLOW_COPY_AND_ASSIGN(TestBrowserClientForSpellCheck);
+  DISALLOW_COPY_AND_ASSIGN(SpellCheckBrowserTestHelper);
 };
 
 // Tests that spelling in out-of-process subframes is checked.
 // See crbug.com/638361 for details.
 IN_PROC_BROWSER_TEST_F(ChromeSitePerProcessTest, OOPIFSpellCheckTest) {
-  TestBrowserClientForSpellCheck browser_client;
-  content::ContentBrowserClient* old_browser_client =
-      content::SetBrowserClientForTesting(&browser_client);
+  SpellCheckBrowserTestHelper spell_check_helper;
 
   GURL main_url(embedded_test_server()->GetURL(
       "a.com", "/page_with_contenteditable_in_cross_site_subframe.html"));
   ui_test_utils::NavigateToURL(browser(), main_url);
-  browser_client.RunUntilBind();
+  spell_check_helper.RunUntilBind();
 
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -1073,22 +1060,18 @@
       ChildFrameAt(web_contents->GetMainFrame(), 0);
 
   MockSpellCheckHost* spell_check_host =
-      browser_client.GetSpellCheckHostForProcess(
+      spell_check_helper.GetSpellCheckHostForProcess(
           cross_site_subframe->GetProcess());
   spell_check_host->Wait();
 
   EXPECT_EQ(base::ASCIIToUTF16("zz."), spell_check_host->text());
-
-  content::SetBrowserClientForTesting(old_browser_client);
 }
 
 // Tests that after disabling spellchecking, spelling in new out-of-process
 // subframes is not checked. See crbug.com/789273 for details.
 // https://crbug.com/944428
 IN_PROC_BROWSER_TEST_F(ChromeSitePerProcessTest, OOPIFDisabledSpellCheckTest) {
-  TestBrowserClientForSpellCheck browser_client;
-  content::ContentBrowserClient* old_browser_client =
-      content::SetBrowserClientForTesting(&browser_client);
+  SpellCheckBrowserTestHelper spell_check_helper;
 
   content::BrowserContext* browser_context =
       static_cast<content::BrowserContext*>(browser()->profile());
@@ -1104,7 +1087,7 @@
   GURL main_url(embedded_test_server()->GetURL(
       "a.com", "/page_with_contenteditable_in_cross_site_subframe.html"));
   ui_test_utils::NavigateToURL(browser(), main_url);
-  browser_client.RunUntilBindOrTimeout();
+  spell_check_helper.RunUntilBindOrTimeout();
 
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -1112,14 +1095,13 @@
       ChildFrameAt(web_contents->GetMainFrame(), 0);
 
   MockSpellCheckHost* spell_check_host =
-      browser_client.GetSpellCheckHostForProcess(
+      spell_check_helper.GetSpellCheckHostForProcess(
           cross_site_subframe->GetProcess());
 
   // The renderer makes no SpellCheckHostRequest at all, in which case no
   // SpellCheckHost is bound and no spellchecking will be done.
   EXPECT_FALSE(spell_check_host);
 
-  content::SetBrowserClientForTesting(old_browser_client);
   prefs->SetBoolean(spellcheck::prefs::kSpellCheckEnable, true);
 }
 
@@ -1128,9 +1110,7 @@
 // Tests that the OSX spell check panel can be opened from an out-of-process
 // subframe, crbug.com/712395
 IN_PROC_BROWSER_TEST_F(ChromeSitePerProcessTest, OOPIFSpellCheckPanelTest) {
-  spellcheck::SpellCheckContentBrowserClient browser_client;
-  content::ContentBrowserClient* old_browser_client =
-      content::SetBrowserClientForTesting(&browser_client);
+  spellcheck::SpellCheckPanelBrowserTestHelper test_helper;
 
   GURL main_url(embedded_test_server()->GetURL(
       "a.com", "/page_with_contenteditable_in_cross_site_subframe.html"));
@@ -1147,14 +1127,12 @@
   cross_site_subframe->GetRemoteInterfaces()->GetInterface(
       &spell_check_panel_client);
   spell_check_panel_client->ToggleSpellPanel(false);
-  browser_client.RunUntilBind();
+  test_helper.RunUntilBind();
 
   spellcheck::SpellCheckMockPanelHost* host =
-      browser_client.GetSpellCheckMockPanelHostForProcess(
+      test_helper.GetSpellCheckMockPanelHostForProcess(
           cross_site_subframe->GetProcess());
   EXPECT_TRUE(host->SpellingPanelVisible());
-
-  content::SetBrowserClientForTesting(old_browser_client);
 }
 #endif  // BUILDFLAG(HAS_SPELLCHECK_PANEL)
 
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
index 2a8968b..0a656b5c 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
 
 #include "base/bind.h"
+#include "base/no_destructor.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
 #include "chrome/browser/spellchecker/spellcheck_factory.h"
@@ -13,25 +14,45 @@
 #include "components/spellcheck/browser/spellcheck_platform.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "content/public/browser/render_process_host.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 
 #if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
 #include "chrome/browser/spellchecker/spelling_request.h"
 #endif
 
-SpellCheckHostChromeImpl::SpellCheckHostChromeImpl(
-    const service_manager::Identity& renderer_identity)
-    : renderer_identity_(renderer_identity) {}
+namespace {
+
+SpellCheckHostChromeImpl::Binder& GetSpellCheckHostBinderOverride() {
+  static base::NoDestructor<SpellCheckHostChromeImpl::Binder> binder;
+  return *binder;
+}
+
+}  // namespace
+
+SpellCheckHostChromeImpl::SpellCheckHostChromeImpl(int render_process_id)
+    : render_process_id_(render_process_id) {}
 
 SpellCheckHostChromeImpl::~SpellCheckHostChromeImpl() = default;
 
 // static
 void SpellCheckHostChromeImpl::Create(
-    spellcheck::mojom::SpellCheckHostRequest request,
-    const service_manager::BindSourceInfo& source_info) {
-  mojo::MakeStrongBinding(
-      std::make_unique<SpellCheckHostChromeImpl>(source_info.identity),
-      std::move(request));
+    int render_process_id,
+    mojo::PendingReceiver<spellcheck::mojom::SpellCheckHost> receiver) {
+  auto& binder = GetSpellCheckHostBinderOverride();
+  if (binder) {
+    binder.Run(render_process_id, std::move(receiver));
+    return;
+  }
+
+  mojo::MakeSelfOwnedReceiver(
+      std::make_unique<SpellCheckHostChromeImpl>(render_process_id),
+      std::move(receiver));
+}
+
+// static
+void SpellCheckHostChromeImpl::OverrideBinderForTesting(Binder binder) {
+  GetSpellCheckHostBinderOverride() = std::move(binder);
 }
 
 void SpellCheckHostChromeImpl::RequestDictionary() {
@@ -47,7 +68,9 @@
 
   // The spellchecker initialization already started and finished; just
   // send it to the renderer.
-  spellcheck->InitForRenderer(renderer_identity_);
+  auto* host = content::RenderProcessHost::FromID(render_process_id_);
+  if (host)
+    spellcheck->InitForRenderer(host);
 
   // TODO(rlp): Ensure that we do not initialize the hunspell dictionary
   // more than once if we get requests from different renderers.
@@ -80,11 +103,11 @@
   // service if a user enables the "Use enhanced spell check" option. When
   // a response is received (including an error) from the remote Spelling
   // service, calls CallSpellingServiceDone.
-  content::BrowserContext* context =
-      content::BrowserContext::GetBrowserContextForServiceInstanceGroup(
-          renderer_identity_.instance_group());
+  auto* host = content::RenderProcessHost::FromID(render_process_id_);
+  if (!host)
+    return;
   client_.RequestTextCheck(
-      context, SpellingServiceClient::SPELLCHECK, text,
+      host->GetBrowserContext(), SpellingServiceClient::SPELLCHECK, text,
       base::BindOnce(&SpellCheckHostChromeImpl::CallSpellingServiceDone,
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
@@ -158,7 +181,7 @@
   // |SpellingRequest| self-destructs on completion.
   // OK to store unretained |this| in a |SpellingRequest| owned by |this|.
   requests_.insert(std::make_unique<SpellingRequest>(
-      &client_, text, renderer_identity_, route_id, std::move(callback),
+      &client_, text, render_process_id_, route_id, std::move(callback),
       base::BindOnce(&SpellCheckHostChromeImpl::OnRequestFinished,
                      base::Unretained(this))));
 }
@@ -193,5 +216,8 @@
 #endif  // defined(OS_MACOSX)
 
 SpellcheckService* SpellCheckHostChromeImpl::GetSpellcheckService() const {
-  return SpellcheckServiceFactory::GetForRenderer(renderer_identity_);
+  auto* host = content::RenderProcessHost::FromID(render_process_id_);
+  if (!host)
+    return nullptr;
+  return SpellcheckServiceFactory::GetForContext(host->GetBrowserContext());
 }
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl.h b/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
index dd3af7f..bacbb71 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl.h
@@ -5,11 +5,12 @@
 #ifndef CHROME_BROWSER_SPELLCHECKER_SPELL_CHECK_HOST_CHROME_IMPL_H_
 #define CHROME_BROWSER_SPELLCHECKER_SPELL_CHECK_HOST_CHROME_IMPL_H_
 
+#include "base/callback.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "build/build_config.h"
 #include "components/spellcheck/browser/spell_check_host_impl.h"
 #include "components/spellcheck/browser/spelling_service_client.h"
-#include "services/service_manager/public/cpp/bind_source_info.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 
 class SpellcheckCustomDictionary;
 class SpellcheckService;
@@ -20,12 +21,19 @@
 // Implementation of SpellCheckHost involving Chrome-only features.
 class SpellCheckHostChromeImpl : public SpellCheckHostImpl {
  public:
-  explicit SpellCheckHostChromeImpl(
-      const service_manager::Identity& renderer_identity);
+  explicit SpellCheckHostChromeImpl(int render_process_id);
   ~SpellCheckHostChromeImpl() override;
 
-  static void Create(spellcheck::mojom::SpellCheckHostRequest request,
-                     const service_manager::BindSourceInfo& source_info);
+  static void Create(
+      int render_process_id,
+      mojo::PendingReceiver<spellcheck::mojom::SpellCheckHost> receiver);
+
+  // Allows tests to override how |Create()| is implemented to bind a process
+  // hosts's SpellCheckHost receiver.
+  using Binder = base::RepeatingCallback<void(
+      int /* render_process_id */,
+      mojo::PendingReceiver<spellcheck::mojom::SpellCheckHost>)>;
+  static void OverrideBinderForTesting(Binder binder);
 
  private:
   friend class TestSpellCheckHostChromeImpl;
@@ -90,8 +98,8 @@
   // is null if the render process is being shut down.
   virtual SpellcheckService* GetSpellcheckService() const;
 
-  // The identity of the renderer service.
-  const service_manager::Identity renderer_identity_;
+  // The process ID of the renderer.
+  const int render_process_id_;
 
   // A JSON-RPC client that calls the remote Spelling service.
   SpellingServiceClient client_;
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc b/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc
index 461ee3c..f78aa4f 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc
@@ -18,11 +18,8 @@
   void SetUpOnMainThread() override {
     content::BrowserContext* context = browser()->profile();
     renderer_.reset(new content::MockRenderProcessHost(context));
-
-    service_manager::BindSourceInfo source_info;
-    source_info.identity = renderer_->GetChildIdentity();
-    SpellCheckHostChromeImpl::Create(mojo::MakeRequest(&spell_check_host_),
-                                     source_info);
+    SpellCheckHostChromeImpl::Create(renderer_->GetID(),
+                                     mojo::MakeRequest(&spell_check_host_));
   }
 
   void TearDownOnMainThread() override { renderer_.reset(); }
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc b/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
index f1c65ad..9ceeb4d 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc
@@ -23,14 +23,11 @@
     content::BrowserContext* context = browser()->profile();
     renderer_.reset(new content::MockRenderProcessHost(context));
 
-    service_manager::BindSourceInfo source_info;
-    source_info.identity = renderer_->GetChildIdentity();
-
     base::test::ScopedFeatureList feature_list;
     feature_list.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker);
 
-    SpellCheckHostChromeImpl::Create(mojo::MakeRequest(&spell_check_host_),
-                                     source_info);
+    SpellCheckHostChromeImpl::Create(renderer_->GetID(),
+                                     mojo::MakeRequest(&spell_check_host_));
   }
 
   void TearDownOnMainThread() override { renderer_.reset(); }
diff --git a/chrome/browser/spellchecker/spell_check_panel_host_impl.cc b/chrome/browser/spellchecker/spell_check_panel_host_impl.cc
index 5c0e423..4218ae0 100644
--- a/chrome/browser/spellchecker/spell_check_panel_host_impl.cc
+++ b/chrome/browser/spellchecker/spell_check_panel_host_impl.cc
@@ -5,9 +5,19 @@
 #include "chrome/browser/spellchecker/spell_check_panel_host_impl.h"
 
 #include "base/bind.h"
+#include "base/no_destructor.h"
 #include "components/spellcheck/browser/spellcheck_platform.h"
 #include "content/public/browser/browser_thread.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+
+namespace {
+
+SpellCheckPanelHostImpl::Binder& GetPanelHostBinderOverride() {
+  static base::NoDestructor<SpellCheckPanelHostImpl::Binder> binder;
+  return *binder;
+}
+
+}  // namespace
 
 SpellCheckPanelHostImpl::SpellCheckPanelHostImpl() = default;
 
@@ -15,9 +25,21 @@
 
 // static
 void SpellCheckPanelHostImpl::Create(
-    spellcheck::mojom::SpellCheckPanelHostRequest request) {
-  mojo::MakeStrongBinding(std::make_unique<SpellCheckPanelHostImpl>(),
-                          std::move(request));
+    int render_process_id,
+    mojo::PendingReceiver<spellcheck::mojom::SpellCheckPanelHost> receiver) {
+  auto& binder = GetPanelHostBinderOverride();
+  if (binder) {
+    binder.Run(render_process_id, std::move(receiver));
+    return;
+  }
+
+  mojo::MakeSelfOwnedReceiver(std::make_unique<SpellCheckPanelHostImpl>(),
+                              std::move(receiver));
+}
+
+// static
+void SpellCheckPanelHostImpl::OverrideBinderForTesting(Binder binder) {
+  GetPanelHostBinderOverride() = std::move(binder);
 }
 
 void SpellCheckPanelHostImpl::ShowSpellingPanel(bool show) {
diff --git a/chrome/browser/spellchecker/spell_check_panel_host_impl.h b/chrome/browser/spellchecker/spell_check_panel_host_impl.h
index 07e738b..68ed965 100644
--- a/chrome/browser/spellchecker/spell_check_panel_host_impl.h
+++ b/chrome/browser/spellchecker/spell_check_panel_host_impl.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_SPELLCHECKER_SPELL_CHECK_PANEL_HOST_IMPL_H_
 #define CHROME_BROWSER_SPELLCHECKER_SPELL_CHECK_PANEL_HOST_IMPL_H_
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "components/spellcheck/common/spellcheck_panel.mojom.h"
 #include "components/spellcheck/spellcheck_buildflags.h"
@@ -18,7 +19,16 @@
   SpellCheckPanelHostImpl();
   ~SpellCheckPanelHostImpl() override;
 
-  static void Create(spellcheck::mojom::SpellCheckPanelHostRequest request);
+  static void Create(
+      int render_process_id,
+      mojo::PendingReceiver<spellcheck::mojom::SpellCheckPanelHost> receiver);
+
+  // Allows tests to override how |Create()| is implemented to bind a process
+  // hosts's SpellCheckPanelHost receiver.
+  using Binder = base::RepeatingCallback<void(
+      int /* render_process_id */,
+      mojo::PendingReceiver<spellcheck::mojom::SpellCheckPanelHost>)>;
+  static void OverrideBinderForTesting(Binder binder);
 
  private:
   // spellcheck::mojom::SpellCheckPanelHost:
diff --git a/chrome/browser/spellchecker/spellcheck_factory.cc b/chrome/browser/spellchecker/spellcheck_factory.cc
index 8f070a2..5253d1f 100644
--- a/chrome/browser/spellchecker/spellcheck_factory.cc
+++ b/chrome/browser/spellchecker/spellcheck_factory.cc
@@ -14,7 +14,6 @@
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_process_host.h"
-#include "services/service_manager/public/cpp/identity.h"
 #include "ui/base/l10n/l10n_util.h"
 
 // static
@@ -25,17 +24,6 @@
 }
 
 // static
-SpellcheckService* SpellcheckServiceFactory::GetForRenderer(
-    const service_manager::Identity& renderer_identity) {
-  content::BrowserContext* context =
-      content::BrowserContext::GetBrowserContextForServiceInstanceGroup(
-          renderer_identity.instance_group());
-  if (!context)
-    return nullptr;
-  return GetForContext(context);
-}
-
-// static
 SpellcheckServiceFactory* SpellcheckServiceFactory::GetInstance() {
   return base::Singleton<SpellcheckServiceFactory>::get();
 }
diff --git a/chrome/browser/spellchecker/spellcheck_factory.h b/chrome/browser/spellchecker/spellcheck_factory.h
index fd4e664..7adf9db 100644
--- a/chrome/browser/spellchecker/spellcheck_factory.h
+++ b/chrome/browser/spellchecker/spellcheck_factory.h
@@ -12,10 +12,6 @@
 
 class SpellcheckService;
 
-namespace service_manager {
-class Identity;
-}
-
 // Entry into the SpellCheck system.
 //
 // Internally, this owns all SpellcheckService objects.
@@ -25,9 +21,6 @@
   // if it does not already exist. This can return NULL.
   static SpellcheckService* GetForContext(content::BrowserContext* context);
 
-  static SpellcheckService* GetForRenderer(
-      const service_manager::Identity& renderer_identity);
-
   static SpellcheckServiceFactory* GetInstance();
 
  private:
diff --git a/chrome/browser/spellchecker/spellcheck_mac_view_browsertest.mm b/chrome/browser/spellchecker/spellcheck_mac_view_browsertest.mm
index ac1a4e5a..4ab4943 100644
--- a/chrome/browser/spellchecker/spellcheck_mac_view_browsertest.mm
+++ b/chrome/browser/spellchecker/spellcheck_mac_view_browsertest.mm
@@ -12,7 +12,7 @@
 #include "content/public/browser/web_contents.h"
 
 #if BUILDFLAG(ENABLE_SPELLCHECK)
-#include "chrome/browser/spellchecker/test/spellcheck_content_browser_client.h"
+#include "chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.h"
 #endif
 
 namespace {
@@ -24,9 +24,7 @@
 
 #if BUILDFLAG(ENABLE_SPELLCHECK)
 IN_PROC_BROWSER_TEST_F(SpellCheckMacViewBrowserTest, SpellCheckPanelVisible) {
-  spellcheck::SpellCheckContentBrowserClient browser_client;
-  content::ContentBrowserClient* old_browser_client =
-      content::SetBrowserClientForTesting(&browser_client);
+  spellcheck::SpellCheckPanelBrowserTestHelper test_helper;
 
   ASSERT_TRUE(embedded_test_server()->Start());
   auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
@@ -36,12 +34,11 @@
   SEL show_guess_panel = NSSelectorFromString(@"showGuessPanel:");
   [web_contents->GetRenderWidgetHostView()->GetNativeView().GetNativeNSView()
       performSelector:show_guess_panel];
-  browser_client.RunUntilBind();
+  test_helper.RunUntilBind();
   spellcheck::SpellCheckMockPanelHost* host =
-      browser_client.GetSpellCheckMockPanelHostForProcess(
+      test_helper.GetSpellCheckMockPanelHostForProcess(
           web_contents->GetMainFrame()->GetProcess());
   EXPECT_TRUE(host->SpellingPanelVisible());
-  content::SetBrowserClientForTesting(old_browser_client);
 }
 #endif
 
diff --git a/chrome/browser/spellchecker/spellcheck_service.cc b/chrome/browser/spellchecker/spellcheck_service.cc
index c408235..f5b38b4 100644
--- a/chrome/browser/spellchecker/spellcheck_service.cc
+++ b/chrome/browser/spellchecker/spellcheck_service.cc
@@ -10,16 +10,15 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/no_destructor.h"
 #include "base/stl_util.h"
 #include "base/strings/string_split.h"
 #include "base/supports_user_data.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/values.h"
 #include "build/build_config.h"
-#include "chrome/browser/chrome_service.h"
 #include "chrome/browser/spellchecker/spellcheck_factory.h"
 #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
-#include "chrome/common/constants.mojom.h"
 #include "chrome/common/pref_names.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_member.h"
@@ -39,10 +38,19 @@
 #include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
-#include "services/service_manager/public/cpp/connector.h"
+#include "mojo/public/cpp/bindings/remote.h"
 
 using content::BrowserThread;
 
+namespace {
+
+SpellcheckService::SpellCheckerBinder& GetSpellCheckerBinderOverride() {
+  static base::NoDestructor<SpellcheckService::SpellCheckerBinder> binder;
+  return *binder;
+}
+
+}  // namespace
+
 // TODO(rlp): I do not like globals, but keeping these for now during
 // transition.
 // An event used by browser tests to receive status events from this class and
@@ -187,13 +195,10 @@
 #endif  // defined(OS_WIN)
 }
 
-void SpellcheckService::InitForRenderer(
-    const service_manager::Identity& renderer_identity) {
+void SpellcheckService::InitForRenderer(content::RenderProcessHost* host) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  content::BrowserContext* context =
-      content::BrowserContext::GetBrowserContextForServiceInstanceGroup(
-          renderer_identity.instance_group());
+  content::BrowserContext* context = host->GetBrowserContext();
   if (SpellcheckServiceFactory::GetForContext(context) != this)
     return;
 
@@ -215,13 +220,8 @@
                         custom_dictionary_->GetWords().end());
   }
 
-  spellcheck::mojom::SpellCheckerPtr spellchecker;
-  ChromeService::GetInstance()->connector()->BindInterface(
-      service_manager::ServiceFilter::ByNameWithIdInGroup(
-          chrome::mojom::kRendererServiceName, renderer_identity.instance_id(),
-          renderer_identity.instance_group()),
-      &spellchecker);
-  spellchecker->Initialize(std::move(dictionaries), custom_words, enable);
+  GetSpellCheckerForProcess(host)->Initialize(std::move(dictionaries),
+                                              custom_words, enable);
 }
 
 SpellCheckHostMetrics* SpellcheckService::GetMetrics() const {
@@ -296,9 +296,7 @@
                                 const content::NotificationSource& source,
                                 const content::NotificationDetails& details) {
   DCHECK_EQ(content::NOTIFICATION_RENDERER_PROCESS_CREATED, type);
-  InitForRenderer(content::Source<content::RenderProcessHost>(source)
-                      .ptr()
-                      ->GetChildIdentity());
+  InitForRenderer(content::Source<content::RenderProcessHost>(source).ptr());
 }
 
 void SpellcheckService::OnCustomDictionaryLoaded() {
@@ -319,16 +317,8 @@
     content::RenderProcessHost* process = it.GetCurrentValue();
     if (!process->IsInitializedAndNotDead())
       continue;
-
-    service_manager::Identity renderer_identity = process->GetChildIdentity();
-    spellcheck::mojom::SpellCheckerPtr spellchecker;
-    ChromeService::GetInstance()->connector()->BindInterface(
-        service_manager::ServiceFilter::ByNameWithIdInGroup(
-            chrome::mojom::kRendererServiceName,
-            renderer_identity.instance_id(),
-            renderer_identity.instance_group()),
-        &spellchecker);
-    spellchecker->CustomDictionaryChanged(additions, deletions);
+    GetSpellCheckerForProcess(process)->CustomDictionaryChanged(additions,
+                                                                deletions);
   }
 }
 
@@ -350,6 +340,11 @@
 }
 
 // static
+void SpellcheckService::OverrideBinderForTesting(SpellCheckerBinder binder) {
+  GetSpellCheckerBinderOverride() = std::move(binder);
+}
+
+// static
 void SpellcheckService::AttachStatusEvent(base::WaitableEvent* status_event) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -362,6 +357,18 @@
   return g_status_type;
 }
 
+mojo::Remote<spellcheck::mojom::SpellChecker>
+SpellcheckService::GetSpellCheckerForProcess(content::RenderProcessHost* host) {
+  mojo::Remote<spellcheck::mojom::SpellChecker> spellchecker;
+  auto receiver = spellchecker.BindNewPipeAndPassReceiver();
+  auto binder = GetSpellCheckerBinderOverride();
+  if (binder)
+    binder.Run(std::move(receiver));
+  else
+    host->BindReceiver(std::move(receiver));
+  return spellchecker;
+}
+
 void SpellcheckService::InitForAllRenderers() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   for (content::RenderProcessHost::iterator i(
@@ -369,7 +376,7 @@
        !i.IsAtEnd(); i.Advance()) {
     content::RenderProcessHost* process = i.GetCurrentValue();
     if (process && process->GetProcess().Handle())
-      InitForRenderer(process->GetChildIdentity());
+      InitForRenderer(process);
   }
 }
 
diff --git a/chrome/browser/spellchecker/spellcheck_service.h b/chrome/browser/spellchecker/spellcheck_service.h
index 39a2932..d5af9d582 100644
--- a/chrome/browser/spellchecker/spellcheck_service.h
+++ b/chrome/browser/spellchecker/spellcheck_service.h
@@ -11,6 +11,7 @@
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -19,8 +20,10 @@
 #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "components/spellcheck/common/spellcheck.mojom.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "mojo/public/cpp/bindings/remote.h"
 
 class SpellCheckHostMetrics;
 
@@ -33,10 +36,7 @@
 class BrowserContext;
 class NotificationDetails;
 class NotificationSource;
-}
-
-namespace service_manager {
-class Identity;
+class RenderProcessHost;
 }
 
 // Encapsulates the browser side spellcheck service. There is one of these per
@@ -98,7 +98,7 @@
 
   // Pass the renderer some basic initialization information. Note that the
   // renderer will not load Hunspell until it needs to.
-  void InitForRenderer(const service_manager::Identity& renderer_identity);
+  void InitForRenderer(content::RenderProcessHost* host);
 
   // Returns a metrics counter associated with this object,
   // or null when metrics recording is disabled.
@@ -143,6 +143,12 @@
   void OnHunspellDictionaryDownloadFailure(
       const std::string& language) override;
 
+  // Allows tests to override how SpellcheckService binds its interface
+  // receiver, instead of going through a RenderProcessHost by default.
+  using SpellCheckerBinder = base::RepeatingCallback<void(
+      mojo::PendingReceiver<spellcheck::mojom::SpellChecker>)>;
+  static void OverrideBinderForTesting(SpellCheckerBinder binder);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT);
 
@@ -152,6 +158,9 @@
   // Returns the status event type.
   static EventType GetStatusEvent();
 
+  mojo::Remote<spellcheck::mojom::SpellChecker> GetSpellCheckerForProcess(
+      content::RenderProcessHost* host);
+
   // Pass all renderers some basic initialization information.
   void InitForAllRenderers();
 
diff --git a/chrome/browser/spellchecker/spellcheck_service_browsertest.cc b/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
index 38b5207..12cf6ad 100644
--- a/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
+++ b/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
@@ -19,7 +19,6 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/values.h"
-#include "chrome/browser/chrome_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
 #include "chrome/browser/spellchecker/spellcheck_factory.h"
@@ -38,8 +37,8 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/test_utils.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "services/service_manager/public/cpp/connector.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 
 using content::BrowserContext;
 using content::RenderProcessHost;
@@ -47,7 +46,7 @@
 class SpellcheckServiceBrowserTest : public InProcessBrowserTest,
                                      public spellcheck::mojom::SpellChecker {
  public:
-  SpellcheckServiceBrowserTest() : binding_(this) {}
+  SpellcheckServiceBrowserTest() = default;
 
   void SetUpOnMainThread() override {
     renderer_.reset(new content::MockRenderProcessHost(GetContext()));
@@ -63,10 +62,13 @@
     // forever if the service already existed). So disable sync of the custom
     // dictionary for these tests.
     command_line->AppendSwitchASCII(switches::kDisableSyncTypes, "Dictionary");
+    SpellcheckService::OverrideBinderForTesting(base::BindRepeating(
+        &SpellcheckServiceBrowserTest::Bind, base::Unretained(this)));
   }
 
   void TearDownOnMainThread() override {
-    binding_.Close();
+    SpellcheckService::OverrideBinderForTesting(base::NullCallback());
+    receiver_.reset();
     prefs_ = nullptr;
     renderer_.reset();
   }
@@ -91,21 +93,9 @@
                           base::SPLIT_WANT_NONEMPTY));
     prefs_->Set(spellcheck::prefs::kSpellCheckDictionaries, dictionaries_value);
 
-    service_manager::Identity renderer_identity = renderer_->GetChildIdentity();
     SpellcheckService* spellcheck =
-        SpellcheckServiceFactory::GetForRenderer(renderer_identity);
+        SpellcheckServiceFactory::GetForContext(renderer_->GetBrowserContext());
     ASSERT_NE(nullptr, spellcheck);
-
-    // Override requests for the spellcheck::mojom::SpellChecker interface so we
-    // can test the SpellChecker request flow.
-    ChromeService::GetInstance()->connector()->OverrideBinderForTesting(
-        service_manager::ServiceFilter::ByNameWithIdInGroup(
-            chrome::mojom::kRendererServiceName,
-            renderer_identity.instance_id(),
-            renderer_identity.instance_group()),
-        spellcheck::mojom::SpellChecker::Name_,
-        base::BindRepeating(&SpellcheckServiceBrowserTest::Bind,
-                            base::Unretained(this)));
   }
 
   void EnableSpellcheck(bool enable_spellcheck) {
@@ -114,7 +104,7 @@
 
   void ChangeCustomDictionary() {
     SpellcheckService* spellcheck =
-        SpellcheckServiceFactory::GetForRenderer(renderer_->GetChildIdentity());
+        SpellcheckServiceFactory::GetForContext(renderer_->GetBrowserContext());
     ASSERT_NE(nullptr, spellcheck);
 
     SpellcheckCustomDictionary::Change change;
@@ -181,9 +171,10 @@
   }
 
   // Binds requests for the SpellChecker interface.
-  void Bind(mojo::ScopedMessagePipeHandle handle) {
-    binding_.Bind(spellcheck::mojom::SpellCheckerRequest(std::move(handle)));
-    binding_.set_connection_error_handler(
+  void Bind(mojo::PendingReceiver<spellcheck::mojom::SpellChecker> receiver) {
+    receiver_.reset();
+    receiver_.Bind(std::move(receiver));
+    receiver_.set_disconnect_handler(
         base::BindOnce(&SpellcheckServiceBrowserTest::BoundConnectionClosed,
                        base::Unretained(this)));
   }
@@ -191,7 +182,7 @@
   // The requester closes (disconnects) when done.
   void BoundConnectionClosed() {
     bound_connection_closed_ = true;
-    binding_.Close();
+    receiver_.reset();
     if (quit_)
       std::move(quit_).Run();
   }
@@ -225,7 +216,7 @@
   PrefService* prefs_;
 
   // Binding to receive the SpellChecker request flow.
-  mojo::Binding<spellcheck::mojom::SpellChecker> binding_;
+  mojo::Receiver<spellcheck::mojom::SpellChecker> receiver_{this};
 
   // Used to verify the SpellChecker request flow.
   bool bound_connection_closed_;
@@ -276,9 +267,8 @@
 
  private:
   void RequestSpellCheckHost(spellcheck::mojom::SpellCheckHostPtr* interface) {
-    service_manager::BindSourceInfo source_info;
-    source_info.identity = GetRenderer()->GetChildIdentity();
-    SpellCheckHostChromeImpl::Create(mojo::MakeRequest(interface), source_info);
+    SpellCheckHostChromeImpl::Create(GetRenderer()->GetID(),
+                                     mojo::MakeRequest(interface));
   }
 
   void SpellingServiceDone(bool success,
diff --git a/chrome/browser/spellchecker/spelling_request.cc b/chrome/browser/spellchecker/spelling_request.cc
index 74536510..a105477b 100644
--- a/chrome/browser/spellchecker/spelling_request.cc
+++ b/chrome/browser/spellchecker/spelling_request.cc
@@ -15,7 +15,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "content/public/browser/render_process_host.h"
 
 namespace {
 
@@ -25,13 +25,12 @@
 
 }  // namespace
 
-SpellingRequest::SpellingRequest(
-    SpellingServiceClient* client,
-    const base::string16& text,
-    const service_manager::Identity& renderer_identity,
-    int document_tag,
-    RequestTextCheckCallback callback,
-    DestructionCallback destruction_callback)
+SpellingRequest::SpellingRequest(SpellingServiceClient* client,
+                                 const base::string16& text,
+                                 int render_process_id,
+                                 int document_tag,
+                                 RequestTextCheckCallback callback,
+                                 DestructionCallback destruction_callback)
     : remote_success_(false),
       text_(text),
       callback_(std::move(callback)),
@@ -43,7 +42,7 @@
   completion_barrier_ =
       BarrierClosure(2, base::BindOnce(&SpellingRequest::OnCheckCompleted,
                                        weak_factory_.GetWeakPtr()));
-  RequestRemoteCheck(client, renderer_identity);
+  RequestRemoteCheck(client, render_process_id);
   RequestLocalCheck(document_tag);
 }
 
@@ -75,16 +74,15 @@
   }
 }
 
-void SpellingRequest::RequestRemoteCheck(
-    SpellingServiceClient* client,
-    const service_manager::Identity& renderer_identity) {
-  content::BrowserContext* context =
-      content::BrowserContext::GetBrowserContextForServiceInstanceGroup(
-          renderer_identity.instance_group());
+void SpellingRequest::RequestRemoteCheck(SpellingServiceClient* client,
+                                         int render_process_id) {
+  auto* host = content::RenderProcessHost::FromID(render_process_id);
+  if (!host)
+    return;
 
   // |this| may be gone at callback invocation if the owner has been removed.
   client->RequestTextCheck(
-      context, SpellingServiceClient::SPELLCHECK, text_,
+      host->GetBrowserContext(), SpellingServiceClient::SPELLCHECK, text_,
       base::BindOnce(&SpellingRequest::OnRemoteCheckCompleted,
                      weak_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/spellchecker/spelling_request.h b/chrome/browser/spellchecker/spelling_request.h
index 7877e93..abbf5571 100644
--- a/chrome/browser/spellchecker/spelling_request.h
+++ b/chrome/browser/spellchecker/spelling_request.h
@@ -8,7 +8,6 @@
 #include "base/containers/unique_ptr_adapters.h"
 #include "components/spellcheck/browser/spell_check_host_impl.h"
 #include "components/spellcheck/browser/spelling_service_client.h"
-#include "services/service_manager/public/cpp/bind_source_info.h"
 
 class SpellingRequest;
 
@@ -23,7 +22,7 @@
 
   SpellingRequest(SpellingServiceClient* client,
                   const base::string16& text,
-                  const service_manager::Identity& renderer_identity,
+                  int render_process_id,
                   int document_tag,
                   RequestTextCheckCallback callback,
                   DestructionCallback destruction_callback);
@@ -37,8 +36,7 @@
 
  private:
   // Request server-side checking for |text_|.
-  void RequestRemoteCheck(SpellingServiceClient* client,
-                          const service_manager::Identity& renderer_identity);
+  void RequestRemoteCheck(SpellingServiceClient* client, int render_process_id);
 
   // Request a check for |text_| from local spell checker.
   void RequestLocalCheck(int document_tag);
diff --git a/chrome/browser/spellchecker/test/spellcheck_content_browser_client.cc b/chrome/browser/spellchecker/test/spellcheck_content_browser_client.cc
deleted file mode 100644
index eba56003..0000000
--- a/chrome/browser/spellchecker/test/spellcheck_content_browser_client.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/spellchecker/test/spellcheck_content_browser_client.h"
-
-#include "base/bind.h"
-#include "base/task/post_task.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/common/service_names.mojom.h"
-#include "services/service_manager/public/cpp/bind_source_info.h"
-
-namespace spellcheck {
-
-SpellCheckContentBrowserClient::SpellCheckContentBrowserClient() {}
-SpellCheckContentBrowserClient::~SpellCheckContentBrowserClient() {}
-
-void SpellCheckContentBrowserClient::OverrideOnBindInterface(
-    const service_manager::BindSourceInfo& remote_info,
-    const std::string& name,
-    mojo::ScopedMessagePipeHandle* handle) {
-  if (name != spellcheck::mojom::SpellCheckPanelHost::Name_)
-    return;
-
-  spellcheck::mojom::SpellCheckPanelHostRequest request(std::move(*handle));
-
-  // Override the default SpellCheckHost interface.
-  auto ui_task_runner = base::CreateSingleThreadTaskRunnerWithTraits(
-      {content::BrowserThread::UI});
-  ui_task_runner->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &SpellCheckContentBrowserClient::BindSpellCheckPanelHostRequest,
-          base::Unretained(this), base::Passed(&request), remote_info));
-}
-
-SpellCheckMockPanelHost*
-SpellCheckContentBrowserClient::GetSpellCheckMockPanelHostForProcess(
-    content::RenderProcessHost* render_process_host) const {
-  for (const auto& host : hosts_) {
-    if (host->process_host() == render_process_host)
-      return host.get();
-  }
-  return nullptr;
-}
-
-void SpellCheckContentBrowserClient::RunUntilBind() {
-  base::RunLoop run_loop;
-  quit_on_bind_closure_ = run_loop.QuitClosure();
-  run_loop.Run();
-}
-
-void SpellCheckContentBrowserClient::BindSpellCheckPanelHostRequest(
-    spellcheck::mojom::SpellCheckPanelHostRequest request,
-    const service_manager::BindSourceInfo& source_info) {
-  content::RenderProcessHost* render_process_host =
-      content::RenderProcessHost::FromRendererInstanceId(
-          source_info.identity.instance_id());
-  auto spell_check_panel_host =
-      std::make_unique<SpellCheckMockPanelHost>(render_process_host);
-  spell_check_panel_host->SpellCheckPanelHostRequest(std::move(request));
-  hosts_.push_back(std::move(spell_check_panel_host));
-  std::move(quit_on_bind_closure_).Run();
-}
-}  // namespace spellcheck
diff --git a/chrome/browser/spellchecker/test/spellcheck_content_browser_client.h b/chrome/browser/spellchecker/test/spellcheck_content_browser_client.h
deleted file mode 100644
index 39afe1d..0000000
--- a/chrome/browser/spellchecker/test/spellcheck_content_browser_client.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_SPELLCHECKER_TEST_SPELLCHECK_CONTENT_BROWSER_CLIENT_H_
-#define CHROME_BROWSER_SPELLCHECKER_TEST_SPELLCHECK_CONTENT_BROWSER_CLIENT_H_
-
-#include <vector>
-
-#include "chrome/browser/chrome_content_browser_client.h"
-#include "chrome/browser/spellchecker/test/spellcheck_mock_panel_host.h"
-
-namespace spellcheck {
-class SpellCheckContentBrowserClient : public ChromeContentBrowserClient {
- public:
-  SpellCheckContentBrowserClient();
-  ~SpellCheckContentBrowserClient() override;
-
-  // ContentBrowserClient overrides.
-  void OverrideOnBindInterface(
-      const service_manager::BindSourceInfo& remote_info,
-      const std::string& name,
-      mojo::ScopedMessagePipeHandle* handle) override;
-
-  SpellCheckMockPanelHost* GetSpellCheckMockPanelHostForProcess(
-      content::RenderProcessHost* render_process_host) const;
-
-  void RunUntilBind();
-
- private:
-  void BindSpellCheckPanelHostRequest(
-      spellcheck::mojom::SpellCheckPanelHostRequest request,
-      const service_manager::BindSourceInfo& source_info);
-
-  base::OnceClosure quit_on_bind_closure_;
-  std::vector<std::unique_ptr<SpellCheckMockPanelHost>> hosts_;
-
-  DISALLOW_COPY_AND_ASSIGN(SpellCheckContentBrowserClient);
-};
-}  // namespace spellcheck
-
-#endif  // CHROME_BROWSER_SPELLCHECKER_TEST_SPELLCHECK_CONTENT_BROWSER_CLIENT_H_
diff --git a/chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.cc b/chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.cc
new file mode 100644
index 0000000..fe8808a1
--- /dev/null
+++ b/chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/spellchecker/spell_check_panel_host_impl.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/service_names.mojom.h"
+#include "services/service_manager/public/cpp/bind_source_info.h"
+
+namespace spellcheck {
+
+SpellCheckPanelBrowserTestHelper::SpellCheckPanelBrowserTestHelper() {
+  SpellCheckPanelHostImpl::OverrideBinderForTesting(base::BindRepeating(
+      &SpellCheckPanelBrowserTestHelper::BindSpellCheckPanelHost,
+      base::Unretained(this)));
+}
+
+SpellCheckPanelBrowserTestHelper::~SpellCheckPanelBrowserTestHelper() {
+  SpellCheckPanelHostImpl::OverrideBinderForTesting(base::NullCallback());
+}
+
+SpellCheckMockPanelHost*
+SpellCheckPanelBrowserTestHelper::GetSpellCheckMockPanelHostForProcess(
+    content::RenderProcessHost* render_process_host) const {
+  for (const auto& host : hosts_) {
+    if (host->process_host() == render_process_host)
+      return host.get();
+  }
+  return nullptr;
+}
+
+void SpellCheckPanelBrowserTestHelper::RunUntilBind() {
+  base::RunLoop run_loop;
+  quit_on_bind_closure_ = run_loop.QuitClosure();
+  run_loop.Run();
+}
+
+void SpellCheckPanelBrowserTestHelper::BindSpellCheckPanelHost(
+    int render_process_id,
+    mojo::PendingReceiver<spellcheck::mojom::SpellCheckPanelHost> receiver) {
+  content::RenderProcessHost* render_process_host =
+      content::RenderProcessHost::FromID(render_process_id);
+  auto spell_check_panel_host =
+      std::make_unique<SpellCheckMockPanelHost>(render_process_host);
+  spell_check_panel_host->SpellCheckPanelHostRequest(std::move(receiver));
+  hosts_.push_back(std::move(spell_check_panel_host));
+  std::move(quit_on_bind_closure_).Run();
+}
+}  // namespace spellcheck
diff --git a/chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.h b/chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.h
new file mode 100644
index 0000000..6ac7819
--- /dev/null
+++ b/chrome/browser/spellchecker/test/spellcheck_panel_browsertest_helper.h
@@ -0,0 +1,38 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SPELLCHECKER_TEST_SPELLCHECK_PANEL_BROWSERTEST_HELPER_H_
+#define CHROME_BROWSER_SPELLCHECKER_TEST_SPELLCHECK_PANEL_BROWSERTEST_HELPER_H_
+
+#include <vector>
+
+#include "chrome/browser/spellchecker/test/spellcheck_mock_panel_host.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace spellcheck {
+
+class SpellCheckPanelBrowserTestHelper {
+ public:
+  SpellCheckPanelBrowserTestHelper();
+  ~SpellCheckPanelBrowserTestHelper();
+
+  SpellCheckMockPanelHost* GetSpellCheckMockPanelHostForProcess(
+      content::RenderProcessHost* render_process_host) const;
+
+  void RunUntilBind();
+
+ private:
+  void BindSpellCheckPanelHost(
+      int render_process_id,
+      mojo::PendingReceiver<spellcheck::mojom::SpellCheckPanelHost> receiver);
+
+  base::OnceClosure quit_on_bind_closure_;
+  std::vector<std::unique_ptr<SpellCheckMockPanelHost>> hosts_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpellCheckPanelBrowserTestHelper);
+};
+
+}  // namespace spellcheck
+
+#endif  // CHROME_BROWSER_SPELLCHECKER_TEST_SPELLCHECK_PANEL_BROWSERTEST_HELPER_H_
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index c0417fa..faafa5e 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1532,9 +1532,8 @@
       "ash/system_tray_client.h",
       "ash/tab_scrubber.cc",
       "ash/tab_scrubber.h",
-      "ash/tablet_mode_client.cc",
-      "ash/tablet_mode_client.h",
-      "ash/tablet_mode_client_observer.h",
+      "ash/tablet_mode_page_behavior.cc",
+      "ash/tablet_mode_page_behavior.h",
       "ash/test_ime_controller.cc",
       "ash/test_ime_controller.h",
       "ash/vpn_list_forwarder.cc",
@@ -3708,8 +3707,8 @@
       "views/extensions/media_gallery_checkbox_view.h",
       "web_applications/app_browser_controller.cc",
       "web_applications/app_browser_controller.h",
-      "web_applications/system_web_app_ui_utils_chromeos.cc",
-      "web_applications/system_web_app_ui_utils_chromeos.h",
+      "web_applications/system_web_app_ui_utils.cc",
+      "web_applications/system_web_app_ui_utils.h",
       "web_applications/web_app_browser_controller.cc",
       "web_applications/web_app_browser_controller.h",
       "web_applications/web_app_dialog_manager.cc",
@@ -3993,8 +3992,6 @@
     sources += [
       "ash/ash_test_util.cc",
       "ash/ash_test_util.h",
-      "ash/fake_tablet_mode_controller.cc",
-      "ash/fake_tablet_mode_controller.h",
       "ash/test_login_screen.cc",
       "ash/test_login_screen.h",
       "ash/test_login_screen_model.cc",
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.cc b/chrome/browser/ui/app_list/app_list_client_impl.cc
index 24f5929..9e132d3 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "ash/public/cpp/app_list/app_list_controller.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/bind.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
@@ -31,7 +32,6 @@
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
@@ -48,8 +48,7 @@
 AppListClientImpl* g_app_list_client_instance = nullptr;
 
 bool IsTabletMode() {
-  return TabletModeClient::Get() &&
-         TabletModeClient::Get()->tablet_mode_enabled();
+  return ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode();
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
index dd92629..7f36fed 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
@@ -17,6 +17,8 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
@@ -35,8 +37,6 @@
 #include "chrome/browser/ui/app_list/test/chrome_app_list_test_support.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
@@ -72,11 +72,11 @@
     content::WindowedNotificationObserver app_loaded_observer(
         content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
         content::NotificationService::AllSources());
-    OpenApplication(
+    apps::LaunchService::Get(profile())->OpenApplication(
         AppLaunchParams(profile(), extension_app->id(),
-                        extensions::LaunchContainer::kLaunchContainerWindow,
+                        apps::mojom::LaunchContainer::kLaunchContainerWindow,
                         WindowOpenDisposition::NEW_WINDOW,
-                        extensions::AppLaunchSource::kSourceTest));
+                        apps::mojom::AppLaunchSource::kSourceTest));
     app_loaded_observer.Wait();
   }
   EXPECT_TRUE(delegate->IsAppOpen(extension_app->id()));
@@ -122,6 +122,27 @@
   EXPECT_TRUE(client->GetAppListWindow());
 }
 
+IN_PROC_BROWSER_TEST_F(AppListClientImplBrowserTest, ShowAppInfo) {
+  AppListClientImpl* client = AppListClientImpl::GetInstance();
+  const extensions::Extension* app = InstallPlatformApp("minimal");
+
+  // Bring up the app list.
+  EXPECT_FALSE(client->GetAppListWindow());
+  client->ShowAppList();
+  EXPECT_TRUE(client->GetAppListWindow());
+  EXPECT_TRUE(wm::GetTransientChildren(client->GetAppListWindow()).empty());
+
+  // Open the app info dialog.
+  base::RunLoop run_loop;
+  client->DoShowAppInfoFlow(profile(), app->id());
+  run_loop.RunUntilIdle();
+  EXPECT_FALSE(wm::GetTransientChildren(client->GetAppListWindow()).empty());
+
+  // The app list should not be dismissed when the dialog is shown.
+  EXPECT_TRUE(client->app_list_visible());
+  EXPECT_TRUE(client->GetAppListWindow());
+}
+
 // Test the CreateNewWindow function of the controller delegate.
 IN_PROC_BROWSER_TEST_F(AppListClientImplBrowserTest, CreateNewWindow) {
   AppListClientImpl* client = AppListClientImpl::GetInstance();
diff --git a/chrome/browser/ui/app_list/app_list_controller_delegate.cc b/chrome/browser/ui/app_list/app_list_controller_delegate.cc
index 41acf0f..cc01334 100644
--- a/chrome/browser/ui/app_list/app_list_controller_delegate.cc
+++ b/chrome/browser/ui/app_list/app_list_controller_delegate.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/extension_uninstaller.h"
 #include "chrome/browser/ui/apps/app_info_dialog.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "chrome/browser/ui/ash/tablet_mode_page_behavior.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -118,7 +118,8 @@
         const extensions::Extension* extension =
             GetExtension(profile, extension_id);
         DCHECK(extension);
-        ShowAppInfoInAppList(bounds, profile, extension);
+        ShowAppInfoInAppList(self->GetAppListWindow(), bounds, profile,
+                             extension);
       },
       weak_ptr_factory_.GetWeakPtr(), profile, extension_id));
 }
diff --git a/chrome/browser/ui/app_list/arc/arc_app_context_menu.cc b/chrome/browser/ui/app_list/arc/arc_app_context_menu.cc
index 1c87187e..85406c4 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_context_menu.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_context_menu.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcuts_menu_builder.h"
@@ -16,7 +17,6 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
@@ -139,8 +139,7 @@
   if (arc::ShowPackageInfo(app_info->package_name,
                            arc::mojom::ShowPackageInfoPage::MAIN,
                            controller()->GetAppListDisplayId()) &&
-      !(TabletModeClient::Get() &&
-        TabletModeClient::Get()->tablet_mode_enabled())) {
+      !(ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode())) {
     controller()->DismissView();
   }
 }
diff --git a/chrome/browser/ui/app_list/chrome_app_list_item.cc b/chrome/browser/ui/app_list/chrome_app_list_item.cc
index 25708f2..cff1af5f 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_item.cc
+++ b/chrome/browser/ui/app_list/chrome_app_list_item.cc
@@ -6,11 +6,11 @@
 
 #include <utility>
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_model_updater.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "extensions/browser/app_sorting.h"
 #include "extensions/browser/extension_system.h"
 #include "ui/gfx/color_utils.h"
@@ -125,8 +125,7 @@
 void ChromeAppListItem::MaybeDismissAppList() {
   // Launching apps can take some time. It looks nicer to dismiss the app list.
   // Do not close app list for home launcher.
-  if (!TabletModeClient::Get() ||
-      !TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (!ash::TabletMode::Get() || !ash::TabletMode::Get()->InTabletMode()) {
     GetController()->DismissView();
   }
 }
diff --git a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc
index fe4ca725..d6f49c4 100644
--- a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc
+++ b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc
@@ -18,6 +18,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -27,7 +28,7 @@
 #include "chrome/browser/ui/app_list/extension_app_utils.h"
 #include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/ui/extensions/app_launch_params.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/webui/chromeos/camera/camera_ui.h"
 #include "chrome/browser/ui/webui/chromeos/login/discover/discover_window_manager.h"
@@ -167,10 +168,10 @@
     AppListClientImpl* controller = AppListClientImpl::GetInstance();
     AppLaunchParams params = CreateAppLaunchParamsWithEventFlags(
         profile, extension, event_flags,
-        extensions::AppLaunchSource::kSourceAppLauncher,
+        apps::mojom::AppLaunchSource::kSourceAppLauncher,
         controller->GetAppListDisplayId());
     params.launch_id = ash::ShelfID(extension->id()).launch_id;
-    OpenApplication(params);
+    apps::LaunchService::Get(profile)->OpenApplication(params);
     VLOG(1) << "Launched CCA.";
   } else {
     LOG(ERROR) << "CCA not found on device";
diff --git a/chrome/browser/ui/app_list/search/search_controller.cc b/chrome/browser/ui/app_list/search/search_controller.cc
index e502199f5..d98a62e1 100644
--- a/chrome/browser/ui/app_list/search/search_controller.cc
+++ b/chrome/browser/ui/app_list/search/search_controller.cc
@@ -12,12 +12,12 @@
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_metrics.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
@@ -26,7 +26,6 @@
 #include "chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "content/public/browser/system_connector.h"
 #include "third_party/metrics_proto/chrome_os_app_list_launch_event.pb.h"
 
@@ -73,11 +72,8 @@
     : mixer_(std::make_unique<Mixer>(model_updater)),
       list_controller_(list_controller) {
   std::unique_ptr<SearchResultRanker> ranker =
-      std::make_unique<SearchResultRanker>(
-          profile,
-          HistoryServiceFactory::GetForProfile(
-              profile, ServiceAccessType::EXPLICIT_ACCESS),
-          content::GetSystemConnector());
+      std::make_unique<SearchResultRanker>(profile,
+                                           content::GetSystemConnector());
   ranker->InitializeRankers();
   mixer_->SetNonAppSearchResultRanker(std::move(ranker));
 }
@@ -119,10 +115,8 @@
 
   // Launching apps can take some time. It looks nicer to dismiss the app list.
   // Do not close app list for home launcher.
-  if (!TabletModeClient::Get() ||
-      !TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (!ash::TabletMode::Get() || !ash::TabletMode::Get()->InTabletMode())
     list_controller_->DismissView();
-  }
 }
 
 void SearchController::InvokeResultAction(ChromeSearchResult* result,
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc
index bbb65ab8b..04cc0ff2 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.cc
@@ -16,13 +16,10 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
 #include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
 #include "chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
@@ -129,15 +126,9 @@
 }  // namespace
 
 SearchResultRanker::SearchResultRanker(Profile* profile,
-                                       history::HistoryService* history_service,
                                        service_manager::Connector* connector)
-    : config_converter_(connector),
-      history_service_observer_(this),
-      profile_(profile),
-      weak_factory_(this) {
+    : config_converter_(connector), profile_(profile) {
   DCHECK(profile);
-  DCHECK(history_service);
-  history_service_observer_.Add(history_service);
   if (auto* notifier =
           file_manager::file_tasks::FileTasksNotifier::GetForProfile(
               profile_)) {
@@ -337,43 +328,4 @@
                               GetTypeFromFileTaskNotifier(file_open.open_type));
 }
 
-void SearchResultRanker::OnURLsDeleted(
-    history::HistoryService* history_service,
-    const history::DeletionInfo& deletion_info) {
-  if (!query_based_mixed_types_ranker_)
-    return;
-
-  if (deletion_info.IsAllHistory()) {
-    // TODO(931149): We clear the whole model because we expect most targets to
-    // be URLs. In future, consider parsing the targets and only deleting URLs.
-    query_based_mixed_types_ranker_->GetTargetData()->clear();
-  } else {
-    for (const auto& row : deletion_info.deleted_rows()) {
-      // In order to perform URL normalization, NormalizeId requires any omnibox
-      // item type as argument. Pass kOmniboxGeneric here as we don't know the
-      // specific type.
-      query_based_mixed_types_ranker_->RemoveTarget(
-          NormalizeId(row.url().spec(), RankingItemType::kOmniboxGeneric));
-    }
-  }
-
-  // Force a save to disk. It is possible to get many calls to OnURLsDeleted in
-  // quick succession, eg. when all history is cleared from a different device.
-  // So delay the save slightly and only perform one save for all updates during
-  // that delay.
-  if (!query_mixed_ranker_save_queued_) {
-    query_mixed_ranker_save_queued_ = true;
-    base::PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&SearchResultRanker::SaveQueryMixedRankerAfterDelete,
-                       weak_factory_.GetWeakPtr()),
-        TimeDelta::FromSeconds(3));
-  }
-}
-
-void SearchResultRanker::SaveQueryMixedRankerAfterDelete() {
-  query_based_mixed_types_ranker_->SaveToDisk();
-  query_mixed_ranker_save_queued_ = false;
-}
-
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.h b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.h
index bfd6cb3..24348a9 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.h
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.h
@@ -13,7 +13,6 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
@@ -24,8 +23,6 @@
 #include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_event_logger.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker_util.h"
-#include "components/history/core/browser/history_service.h"
-#include "components/history/core/browser/history_service_observer.h"
 
 namespace app_list {
 
@@ -38,12 +35,9 @@
 // FetchRankings queries each model for ranking results. Rank modifies the
 // scores of provided search results, which are intended to be the output of a
 // search provider.
-class SearchResultRanker : file_manager::file_tasks::FileTasksObserver,
-                           history::HistoryServiceObserver {
+class SearchResultRanker : file_manager::file_tasks::FileTasksObserver {
  public:
-  SearchResultRanker(Profile* profile,
-                     history::HistoryService* history_service,
-                     service_manager::Connector* connector);
+  SearchResultRanker(Profile* profile, service_manager::Connector* connector);
   ~SearchResultRanker() override;
 
   // Performs all setup of rankers. This is separated from the constructor for
@@ -70,10 +64,6 @@
   // file_manager::file_tasks::FileTaskObserver:
   void OnFilesOpened(const std::vector<FileOpenEvent>& file_opens) override;
 
-  // history::HistoryServiceObserver:
-  void OnURLsDeleted(history::HistoryService* history_service,
-                     const history::DeletionInfo& deletion_info) override;
-
   RecurrenceRanker* get_zero_state_mixed_types_ranker() {
     return zero_state_mixed_types_ranker_.get();
   }
@@ -87,12 +77,6 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(SearchResultRankerTest,
                            QueryMixedModelConfigDeployment);
-  FRIEND_TEST_ALL_PREFIXES(SearchResultRankerTest,
-                           QueryMixedModelDeletesURLCorrectly);
-
-  // Saves |query_based_mixed_types_ranker_| to disk. Called after a delay when
-  // URLs get deleted.
-  void SaveQueryMixedRankerAfterDelete();
 
   // Records the time of the last call to FetchRankings() and is used to
   // limit the number of queries to the models within a short timespan.
@@ -116,9 +100,6 @@
   // these are local files and omnibox results.
   std::unique_ptr<RecurrenceRanker> query_based_mixed_types_ranker_;
   std::map<std::string, float> query_mixed_ranks_;
-  // Flag set when a delayed task to save the model is created. This is used to
-  // prevent several delayed tasks from being created.
-  bool query_mixed_ranker_save_queued_ = false;
 
   // Ranks files and previous queries for launcher zero-state.
   std::unique_ptr<RecurrenceRanker> zero_state_mixed_types_ranker_;
@@ -132,9 +113,6 @@
   // Logs launch events and stores feature data for aggregated model.
   app_list::AppLaunchEventLogger app_launch_event_logger_;
 
-  ScopedObserver<history::HistoryService, history::HistoryServiceObserver>
-      history_service_observer_;
-
   // TODO(931149): Move the AppSearchResultRanker instance and associated logic
   // to here.
 
@@ -143,8 +121,6 @@
   base::flat_map<std::string, float> app_ranks_;
 
   Profile* profile_;
-
-  base::WeakPtrFactory<SearchResultRanker> weak_factory_;
 };
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker_unittest.cc b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker_unittest.cc
index b15f1a1c..5b0a5e64 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker_unittest.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker_unittest.cc
@@ -14,7 +14,6 @@
 
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/stl_util.h"
 #include "base/strings/string16.h"
@@ -27,11 +26,6 @@
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/history/core/browser/history_database_params.h"
-#include "components/history/core/browser/history_service.h"
-#include "components/history/core/browser/history_types.h"
-#include "components/history/core/test/history_service_test_util.h"
-#include "components/history/core/test/test_history_database.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "services/data_decoder/public/cpp/test_data_decoder_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -45,7 +39,6 @@
 using base::ScopedTempDir;
 using base::test::ScopedFeatureList;
 using testing::ElementsAre;
-using testing::UnorderedElementsAre;
 using testing::WhenSorted;
 
 class TestSearchResult : public ChromeSearchResult {
@@ -78,10 +71,6 @@
   return base::UTF16ToUTF8(arg.result->title()) == id;
 }
 
-MATCHER_P2(HasIdScore, id, score, "") {
-  return base::UTF16ToUTF8(arg.result->title()) == id && arg.score == score;
-}
-
 }  // namespace
 
 class SearchResultRankerTest : public testing::Test {
@@ -98,11 +87,6 @@
     profile_builder.SetProfileName("testuser@gmail.com");
     profile_builder.SetPath(temp_dir_.GetPath().AppendASCII("TestProfile"));
     profile_ = profile_builder.Build();
-
-    history_service_ = std::make_unique<history::HistoryService>();
-    history_service_->Init(
-        history::TestHistoryDatabaseParamsForPath(temp_dir_.GetPath()));
-    Wait();
   }
 
   std::unique_ptr<SearchResultRanker> MakeRanker(
@@ -116,8 +100,8 @@
           app_list_features::kEnableQueryBasedMixedTypesRanker);
     }
 
-    auto ranker = std::make_unique<SearchResultRanker>(
-        profile_.get(), history_service_.get(), dd_service_.connector());
+    auto ranker = std::make_unique<SearchResultRanker>(profile_.get(),
+                                                       dd_service_.connector());
     return ranker;
   }
 
@@ -132,10 +116,9 @@
     return results;
   }
 
-  history::HistoryService* history_service() { return history_service_.get(); }
-
   void Wait() { thread_bundle_.RunUntilIdle(); }
 
+ private:
   content::TestBrowserThreadBundle thread_bundle_;
 
   // This is used only to make the ownership clear for the TestSearchResult
@@ -147,11 +130,9 @@
 
   ScopedFeatureList scoped_feature_list_;
   ScopedTempDir temp_dir_;
-  std::unique_ptr<history::HistoryService> history_service_;
 
   std::unique_ptr<Profile> profile_;
 
- private:
   DISALLOW_COPY_AND_ASSIGN(SearchResultRankerTest);
 };
 
@@ -355,108 +336,4 @@
             "ExponentialWeightsEnsemble");
 }
 
-// Tests that, when a URL is deleted from the history service, the query-based
-// mixed-types model deletes it in memory and from disk.
-TEST_F(SearchResultRankerTest, QueryMixedModelDeletesURLCorrectly) {
-  // Create ranker.
-  const std::string json = R"({
-      "min_seconds_between_saves": 1000,
-      "target_limit": 100,
-      "target_decay": 0.5,
-      "condition_limit": 10,
-      "condition_decay": 0.5,
-      "predictor": {
-        "predictor_type": "fake"
-      }
-    })";
-
-  base::RunLoop run_loop;
-  auto ranker =
-      MakeRanker(true, {{"boost_coefficient", "1.0"}, {"config", json}});
-  ranker->set_json_config_parsed_for_testing(run_loop.QuitClosure());
-  ranker->InitializeRankers();
-  run_loop.Run();
-  Wait();
-
-  const base::FilePath model_path =
-      profile_->GetPath().AppendASCII("query_based_mixed_types_ranker.pb");
-
-  // Train the model on two URLs.
-  const std::string url_1 = "http://www.google.com/testing";
-  AppLaunchData url_1_data;
-  url_1_data.id = url_1;
-  url_1_data.ranking_item_type = RankingItemType::kOmniboxHistory;
-  url_1_data.query = "query";
-  ranker->Train(url_1_data);
-  ranker->Train(url_1_data);
-
-  const std::string url_2 = "http://www.other.com";
-  AppLaunchData url_2_data;
-  url_2_data.id = url_2;
-  url_2_data.ranking_item_type = RankingItemType::kOmniboxHistory;
-  url_2_data.query = "query";
-  ranker->Train(url_2_data);
-
-  // Expect the scores of the urls to reflect their training.
-  {
-    ranker->FetchRankings(base::UTF8ToUTF16("query"));
-    auto results = MakeSearchResults(
-        {url_1, url_2, "untrained"},
-        {ResultType::kOmnibox, ResultType::kOmnibox, ResultType::kOmnibox},
-        {0.0f, 0.0f, 0.5f});
-    ranker->Rank(&results);
-    EXPECT_THAT(results, UnorderedElementsAre(HasIdScore(url_1, 2.0f),
-                                              HasIdScore(url_2, 1.0f),
-                                              HasIdScore("untrained", 0.5f)));
-  }
-
-  // Now delete |url_1| from the history service and ensure we save the model to
-  // disk.
-  EXPECT_FALSE(base::PathExists(model_path));
-  history_service()->AddPage(GURL(url_1), base::Time::Now(),
-                             history::VisitSource::SOURCE_BROWSED);
-  history_service()->DeleteURL(GURL(url_1));
-  history::BlockUntilHistoryProcessesPendingRequests(history_service());
-  Wait();
-  EXPECT_TRUE(base::PathExists(model_path));
-
-  // Force cache expiry.
-  ranker->time_of_last_fetch_ = base::Time();
-
-  // Expect the score of |url_1| to be 0.0, it should have been deleted from
-  // the model.
-  {
-    ranker->FetchRankings(base::UTF8ToUTF16("query"));
-    auto results = MakeSearchResults(
-        {url_1, url_2, "untrained"},
-        {ResultType::kOmnibox, ResultType::kOmnibox, ResultType::kOmnibox},
-        {0.0f, 0.0f, 0.5f});
-    ranker->Rank(&results);
-    EXPECT_THAT(results, UnorderedElementsAre(HasIdScore(url_1, 0.0f),
-                                              HasIdScore(url_2, 1.0f),
-                                              HasIdScore("untrained", 0.5f)));
-  }
-
-  // Load a new ranker from disk and ensure |url_1| hasn't been retained.
-  base::RunLoop new_run_loop;
-  auto new_ranker = std::make_unique<SearchResultRanker>(
-      profile_.get(), history_service(), dd_service_.connector());
-  new_ranker->set_json_config_parsed_for_testing(new_run_loop.QuitClosure());
-  new_ranker->InitializeRankers();
-  new_run_loop.Run();
-  Wait();
-
-  {
-    new_ranker->FetchRankings(base::UTF8ToUTF16("query"));
-    auto results = MakeSearchResults(
-        {url_1, url_2, "untrained"},
-        {ResultType::kOmnibox, ResultType::kOmnibox, ResultType::kOmnibox},
-        {0.0f, 0.0f, 0.5f});
-    new_ranker->Rank(&results);
-    EXPECT_THAT(results, UnorderedElementsAre(HasIdScore(url_1, 0.0f),
-                                              HasIdScore(url_2, 1.0f),
-                                              HasIdScore("untrained", 0.5f)));
-  }
-}
-
 }  // namespace app_list
diff --git a/chrome/browser/ui/apps/app_info_dialog.h b/chrome/browser/ui/apps/app_info_dialog.h
index ac3fb95..8bf40367 100644
--- a/chrome/browser/ui/apps/app_info_dialog.h
+++ b/chrome/browser/ui/apps/app_info_dialog.h
@@ -8,6 +8,10 @@
 #include "base/callback_forward.h"
 #include "chrome/common/buildflags.h"
 
+#if BUILDFLAG(ENABLE_APP_LIST)
+#include "ui/gfx/native_widget_types.h"
+#endif
+
 class Profile;
 
 namespace content {
@@ -37,7 +41,8 @@
 #if BUILDFLAG(ENABLE_APP_LIST)
 // Shows the chrome app information as a frameless window for the given |app|
 // and |profile| at the given |app_info_bounds|.
-void ShowAppInfoInAppList(const gfx::Rect& app_info_bounds,
+void ShowAppInfoInAppList(gfx::NativeWindow parent,
+                          const gfx::Rect& app_info_bounds,
                           Profile* profile,
                           const extensions::Extension* app);
 #endif
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index c9a73db7..dcde18b 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -41,7 +41,7 @@
 #include "chrome/browser/ui/ash/session_controller_client_impl.h"
 #include "chrome/browser/ui/ash/system_tray_client.h"
 #include "chrome/browser/ui/ash/tab_scrubber.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "chrome/browser/ui/ash/tablet_mode_page_behavior.h"
 #include "chrome/browser/ui/ash/vpn_list_forwarder.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
@@ -109,12 +109,7 @@
 ChromeBrowserMainExtraPartsAsh::ChromeBrowserMainExtraPartsAsh()
     : notification_observer_(std::make_unique<NotificationObserver>()) {}
 
-ChromeBrowserMainExtraPartsAsh::~ChromeBrowserMainExtraPartsAsh() {
-  // Views code observes TabletModeClient and may not be destroyed until
-  // ash::Shell is, so destroy |tablet_mode_client_| after ash::Shell.
-  // Also extensions need to remove observers after PostMainMessageLoopRun().
-  tablet_mode_client_.reset();
-}
+ChromeBrowserMainExtraPartsAsh::~ChromeBrowserMainExtraPartsAsh() = default;
 
 void ChromeBrowserMainExtraPartsAsh::PreProfileInit() {
   // NetworkConnect handles the network connection state machine for the UI.
@@ -146,11 +141,7 @@
   session_controller_client_->Init();
 
   system_tray_client_ = std::make_unique<SystemTrayClient>();
-
-  // Makes mojo request to TabletModeController in ash.
-  tablet_mode_client_ = std::make_unique<TabletModeClient>();
-  tablet_mode_client_->Init();
-
+  tablet_mode_page_behavior_ = std::make_unique<TabletModePageBehavior>();
   vpn_list_forwarder_ = std::make_unique<VpnListForwarder>();
 
   wallpaper_controller_client_ = std::make_unique<WallpaperControllerClient>();
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h
index 31b405b6..9121998 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h
@@ -35,7 +35,7 @@
 class ScreenOrientationDelegateChromeos;
 class SessionControllerClientImpl;
 class SystemTrayClient;
-class TabletModeClient;
+class TabletModePageBehavior;
 class VpnListForwarder;
 class WallpaperControllerClient;
 
@@ -84,7 +84,7 @@
       screen_orientation_delegate_;
   std::unique_ptr<SessionControllerClientImpl> session_controller_client_;
   std::unique_ptr<SystemTrayClient> system_tray_client_;
-  std::unique_ptr<TabletModeClient> tablet_mode_client_;
+  std::unique_ptr<TabletModePageBehavior> tablet_mode_page_behavior_;
   std::unique_ptr<VpnListForwarder> vpn_list_forwarder_;
   std::unique_ptr<WallpaperControllerClient> wallpaper_controller_client_;
   // TODO(stevenjb): Move NetworkPortalNotificationController to c/b/ui/ash and
diff --git a/chrome/browser/ui/ash/chrome_new_window_client.cc b/chrome/browser/ui/ash/chrome_new_window_client.cc
index e49888c2..b4c90ce 100644
--- a/chrome/browser/ui/ash/chrome_new_window_client.cc
+++ b/chrome/browser/ui/ash/chrome_new_window_client.cc
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/timer/elapsed_timer.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chromeos/arc/arc_web_contents_data.h"
 #include "chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.h"
 #include "chrome/browser/chromeos/file_manager/app_id.h"
@@ -36,7 +37,6 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/webui/chrome_web_contents_handler.h"
@@ -418,9 +418,10 @@
 
   const extensions::Extension* const extension =
       service->GetInstalledExtension(kFileManagerAppId);
-  OpenApplication(CreateAppLaunchParamsUserContainer(
-      profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      extensions::AppLaunchSource::kSourceKeyboard));
+  apps::LaunchService::Get(profile)->OpenApplication(
+      CreateAppLaunchParamsUserContainer(
+          profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+          apps::mojom::AppLaunchSource::kSourceKeyboard));
 }
 
 void ChromeNewWindowClient::OpenCrosh() {
@@ -543,9 +544,10 @@
 
   AppLaunchParams params = CreateAppLaunchParamsUserContainer(
       profile, extension, WindowOpenDisposition::NEW_WINDOW,
-      extensions::AppLaunchSource::kSourceArc);
+      apps::mojom::AppLaunchSource::kSourceArc);
   params.override_url = url;
-  content::WebContents* tab = OpenApplication(params);
+  content::WebContents* tab =
+      apps::LaunchService::Get(profile)->OpenApplication(params);
   if (!tab)
     return;
 
diff --git a/chrome/browser/ui/ash/fake_tablet_mode_controller.cc b/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
deleted file mode 100644
index b789e0f3..0000000
--- a/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/ash/fake_tablet_mode_controller.h"
-
-#include <utility>
-#include "base/logging.h"
-
-FakeTabletModeController::FakeTabletModeController() = default;
-
-FakeTabletModeController::~FakeTabletModeController() = default;
-
-void FakeTabletModeController::AddObserver(ash::TabletModeObserver* observer) {
-  observer_ = observer;
-}
-
-void FakeTabletModeController::RemoveObserver(
-    ash::TabletModeObserver* observer) {
-  DCHECK_EQ(observer_, observer);
-  observer_ = nullptr;
-}
-
-bool FakeTabletModeController::InTabletMode() const {
-  return 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
deleted file mode 100644
index e5562ad..0000000
--- a/chrome/browser/ui/ash/fake_tablet_mode_controller.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_FAKE_TABLET_MODE_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_FAKE_TABLET_MODE_CONTROLLER_H_
-
-#include "ash/public/cpp/tablet_mode.h"
-#include "base/macros.h"
-
-// Simulates the TabletModeController in ash.
-class FakeTabletModeController : public ash::TabletMode {
- public:
-  FakeTabletModeController();
-
-  ~FakeTabletModeController() override;
-
-  bool has_observer() const { return !!observer_; }
-
-  // ash::TabletMode:
-  void AddObserver(ash::TabletModeObserver* observer) override;
-  void RemoveObserver(ash::TabletModeObserver* observer) override;
-  bool InTabletMode() const override;
-  void SetEnabledForTest(bool enabled) override;
-
- private:
-  bool enabled_ = false;
-  ash::TabletModeObserver* observer_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(FakeTabletModeController);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_FAKE_TABLET_MODE_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index 3cd3bb8..be591b3cf 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -14,6 +14,7 @@
 #include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shelf_prefs.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/window_animation_types.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -56,7 +57,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/session_controller_client_impl.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -557,8 +557,7 @@
     return ash::SHELF_ACTION_WINDOW_MINIMIZED;
   }
 
-  if (TabletModeClient::Get() &&
-      TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode()) {
     // Run slide down animation to show the window.
     wm::SetWindowVisibilityAnimationType(
         native_window, ash::WINDOW_VISIBILITY_ANIMATION_TYPE_SLIDE_DOWN);
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
index bc65648..35ed470 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
@@ -26,6 +26,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
 #include "chrome/browser/extensions/extension_apitest.h"
@@ -47,7 +48,6 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_app_window_icon_observer.h"
@@ -216,9 +216,9 @@
         service->GetExtensionById(last_loaded_extension_id(), false);
     EXPECT_TRUE(extension);
 
-    OpenApplication(AppLaunchParams(profile(), extension->id(), container,
-                                    disposition,
-                                    extensions::AppLaunchSource::kSourceTest));
+    apps::LaunchService::Get(profile())->OpenApplication(
+        AppLaunchParams(profile(), extension->id(), container, disposition,
+                        apps::mojom::AppLaunchSource::kSourceTest));
     return extension;
   }
 
@@ -1177,11 +1177,11 @@
   EXPECT_EQ(0u, NumberOfDetectedLauncherBrowsers(false));
   EXPECT_EQ(++running_browser, chrome::GetTotalBrowserCount());
 
-  OpenApplication(
+  apps::LaunchService::Get(profile())->OpenApplication(
       AppLaunchParams(profile(), extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerTab,
+                      apps::mojom::LaunchContainer::kLaunchContainerTab,
                       WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceTest));
+                      apps::mojom::AppLaunchSource::kSourceTest));
 
   // A new browser should get detected and one more should be running.
   EXPECT_EQ(NumberOfDetectedLauncherBrowsers(false), 1u);
@@ -1702,9 +1702,9 @@
   AppLaunchParams params = CreateAppLaunchParamsUserContainer(
       profile(), GetExtensionForAppID(extensions::kWebStoreAppId, profile()),
       WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      extensions::AppLaunchSource::kSourceTest);
-  params.container = extensions::LaunchContainer::kLaunchContainerWindow;
-  OpenApplication(params);
+      apps::mojom::AppLaunchSource::kSourceTest);
+  params.container = apps::mojom::LaunchContainer::kLaunchContainerWindow;
+  apps::LaunchService::Get(profile())->OpenApplication(params);
   EXPECT_EQ(ash::STATUS_RUNNING, shelf_model()->ItemByID(id)->status);
 
   // Find the browser which holds our app.
diff --git a/chrome/browser/ui/ash/launcher/launcher_context_menu.cc b/chrome/browser/ui/ash/launcher/launcher_context_menu.cc
index 5441814..613b14c 100644
--- a/chrome/browser/ui/ash/launcher/launcher_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_context_menu.cc
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "ash/public/cpp/shelf_model.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/metrics/user_metrics.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
@@ -20,7 +21,6 @@
 #include "chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.h"
 #include "chrome/browser/ui/ash/launcher/extension_launcher_context_menu.h"
 #include "chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -107,7 +107,7 @@
         controller_->Close(item_.id);
       }
       base::RecordAction(base::UserMetricsAction("CloseFromContextMenu"));
-      if (TabletModeClient::Get()->tablet_mode_enabled()) {
+      if (ash::TabletMode::Get()->InTabletMode()) {
         base::RecordAction(
             base::UserMetricsAction("Tablet_WindowCloseFromContextMenu"));
       }
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 43d10ded..7c6535e 100644
--- a/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
@@ -33,7 +33,6 @@
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/extension_launcher_context_menu.h"
 #include "chrome/browser/ui/ash/launcher/internal_app_shelf_context_menu.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/test/base/chrome_ash_test_base.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
@@ -80,8 +79,6 @@
     launcher_controller_ =
         std::make_unique<ChromeLauncherController>(&profile_, model_.get());
 
-    tablet_mode_client_ = std::make_unique<TabletModeClient>();
-
     // Disable safe icon decoding to ensure ArcAppShortcutRequests returns in
     // the test environment.
     arc::IconDecodeRequest::DisableSafeDecodingForTesting();
@@ -97,7 +94,7 @@
   }
 
   // Creates app window and set optional ARC application id.
-  views::Widget* CreateArcWindow(std::string& window_app_id) {
+  views::Widget* CreateArcWindow(const std::string& window_app_id) {
     views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
     views::Widget* widget = new views::Widget();
     params.context = CurrentContext();
@@ -139,9 +136,6 @@
   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_; }
@@ -159,8 +153,6 @@
   std::unique_ptr<ash::ShelfModel> model_;
   std::unique_ptr<ChromeLauncherController> launcher_controller_;
 
-  std::unique_ptr<TabletModeClient> tablet_mode_client_;
-
   DISALLOW_COPY_AND_ASSIGN(LauncherContextMenuTest);
 };
 
diff --git a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
index 5f118f0..494e6b8 100644
--- a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
@@ -7,6 +7,7 @@
 #include <vector>
 
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/arc/arc_session_manager.h"
 #include "chrome/browser/chromeos/arc/arc_util.h"
@@ -26,7 +27,6 @@
 #include "chrome/browser/ui/ash/launcher/arc_app_window_launcher_controller.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
@@ -253,7 +253,7 @@
   // The app will be created for the currently active profile.
   AppLaunchParams params = CreateAppLaunchParamsWithEventFlags(
       profile_, extension, event_flags,
-      extensions::AppLaunchSource::kSourceAppLauncher, display_id);
+      apps::mojom::AppLaunchSource::kSourceAppLauncher, display_id);
   if ((source == ash::LAUNCH_FROM_APP_LIST ||
        source == ash::LAUNCH_FROM_APP_LIST_SEARCH) &&
       app_id == extensions::kWebStoreAppId) {
@@ -267,7 +267,7 @@
   }
   params.launch_id = id.launch_id;
 
-  OpenApplication(params);
+  apps::LaunchService::Get(profile_)->OpenApplication(params);
 }
 
 ArcAppListPrefs* LauncherControllerHelper::GetArcAppListPrefs() const {
diff --git a/chrome/browser/ui/ash/screen_orientation_delegate_chromeos.cc b/chrome/browser/ui/ash/screen_orientation_delegate_chromeos.cc
index 48d404ef..58578d99 100644
--- a/chrome/browser/ui/ash/screen_orientation_delegate_chromeos.cc
+++ b/chrome/browser/ui/ash/screen_orientation_delegate_chromeos.cc
@@ -5,8 +5,8 @@
 #include "chrome/browser/ui/ash/screen_orientation_delegate_chromeos.h"
 
 #include "ash/display/screen_orientation_controller.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/shell.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "content/public/browser/web_contents.h"
 
 namespace {
@@ -59,8 +59,7 @@
 }
 
 bool ScreenOrientationDelegateChromeos::ScreenOrientationProviderSupported() {
-  return TabletModeClient::Get() &&
-         TabletModeClient::Get()->tablet_mode_enabled();
+  return ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode();
 }
 
 void ScreenOrientationDelegateChromeos::Unlock(
diff --git a/chrome/browser/ui/ash/tablet_mode_client_observer.h b/chrome/browser/ui/ash/tablet_mode_client_observer.h
deleted file mode 100644
index 65af83a..0000000
--- a/chrome/browser/ui/ash/tablet_mode_client_observer.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_OBSERVER_H_
-#define CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_OBSERVER_H_
-
-// Observer for tablet mode changes inside chrome.
-class TabletModeClientObserver {
- public:
-  // Fired after the tablet mode has been toggled.
-  virtual void OnTabletModeToggled(bool enabled) = 0;
-
- protected:
-  virtual ~TabletModeClientObserver() {}
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_OBSERVER_H_
diff --git a/chrome/browser/ui/ash/tablet_mode_client_unittest.cc b/chrome/browser/ui/ash/tablet_mode_client_unittest.cc
deleted file mode 100644
index ebf0bb6..0000000
--- a/chrome/browser/ui/ash/tablet_mode_client_unittest.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
-
-#include "base/macros.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"
-
-namespace {
-
-class TestTabletModeClientObserver : public TabletModeClientObserver {
- public:
-  TestTabletModeClientObserver() = default;
-  ~TestTabletModeClientObserver() override = default;
-
-  void OnTabletModeToggled(bool enabled) override {
-    ++toggle_count_;
-    last_toggle_ = enabled;
-  }
-
-  int toggle_count_ = 0;
-  bool last_toggle_ = false;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TestTabletModeClientObserver);
-};
-
-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;
-  client.Init();
-
-  // Singleton was initialized.
-  EXPECT_EQ(&client, TabletModeClient::Get());
-
-  // Object was set as client.
-  EXPECT_TRUE(controller->has_observer());
-
-  controller = nullptr;
-}
-
-TEST_F(TabletModeClientTest, Observers) {
-  auto controller = std::make_unique<FakeTabletModeController>();
-  TestTabletModeClientObserver observer;
-  TabletModeClient client;
-  client.Init();
-  client.AddObserver(&observer);
-
-  // Observer is not notified with state when added.
-  EXPECT_EQ(0, observer.toggle_count_);
-
-  // Setting state notifies observer.
-  client.OnTabletModeToggled(true);
-  EXPECT_EQ(1, observer.toggle_count_);
-  EXPECT_TRUE(observer.last_toggle_);
-
-  client.OnTabletModeToggled(false);
-  EXPECT_EQ(2, observer.toggle_count_);
-  EXPECT_FALSE(observer.last_toggle_);
-
-  client.RemoveObserver(&observer);
-
-  controller = nullptr;
-}
-
-}  // namespace
diff --git a/chrome/browser/ui/ash/tablet_mode_client.cc b/chrome/browser/ui/ash/tablet_mode_page_behavior.cc
similarity index 73%
rename from chrome/browser/ui/ash/tablet_mode_client.cc
rename to chrome/browser/ui/ash/tablet_mode_page_behavior.cc
index 5dc07946..63286ccb 100644
--- a/chrome/browser/ui/ash/tablet_mode_client.cc
+++ b/chrome/browser/ui/ash/tablet_mode_page_behavior.cc
@@ -2,14 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "chrome/browser/ui/ash/tablet_mode_page_behavior.h"
 
 #include <utility>
 
 #include "ash/public/cpp/tablet_mode.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"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_tab_strip_tracker.h"
@@ -21,72 +20,38 @@
 #include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/material_design/material_design_controller.h"
 
-namespace {
-
-TabletModeClient* g_tablet_mode_client_instance = nullptr;
-
-}  // namespace
-
-TabletModeClient::TabletModeClient() {
-  DCHECK(!g_tablet_mode_client_instance);
-  g_tablet_mode_client_instance = this;
-}
-
-TabletModeClient::~TabletModeClient() {
-  DCHECK_EQ(this, g_tablet_mode_client_instance);
-  g_tablet_mode_client_instance = nullptr;
-  // The Ash Shell and TabletMode instance should have been destroyed by now.
-  DCHECK(!ash::TabletMode::Get());
-}
-
-void TabletModeClient::Init() {
+TabletModePageBehavior::TabletModePageBehavior() {
   ash::TabletMode::Get()->AddObserver(this);
   OnTabletModeToggled(ash::TabletMode::Get()->InTabletMode());
 }
 
-// static
-TabletModeClient* TabletModeClient::Get() {
-  return g_tablet_mode_client_instance;
+TabletModePageBehavior::~TabletModePageBehavior() {
+  // The Ash Shell and TabletMode instance should have been destroyed by now.
+  DCHECK(!ash::TabletMode::Get());
 }
 
-void TabletModeClient::AddObserver(TabletModeClientObserver* observer) {
-  observers_.AddObserver(observer);
-}
-
-void TabletModeClient::RemoveObserver(TabletModeClientObserver* observer) {
-  observers_.RemoveObserver(observer);
-}
-
-void TabletModeClient::OnTabletModeToggled(bool enabled) {
-  if (tablet_mode_enabled_ == enabled)
-    return;
-
-  tablet_mode_enabled_ = enabled;
-
+void TabletModePageBehavior::OnTabletModeToggled(bool enabled) {
   SetMobileLikeBehaviorEnabled(enabled);
-
   ui::MaterialDesignController::OnTabletModeToggled(enabled);
-  for (auto& observer : observers_)
-    observer.OnTabletModeToggled(enabled);
 }
 
-void TabletModeClient::OnTabletModeStarted() {
+void TabletModePageBehavior::OnTabletModeStarted() {
   OnTabletModeToggled(true);
 }
 
-void TabletModeClient::OnTabletModeEnded() {
+void TabletModePageBehavior::OnTabletModeEnded() {
   OnTabletModeToggled(false);
 }
 
-void TabletModeClient::OnTabletControllerDestroyed() {
+void TabletModePageBehavior::OnTabletControllerDestroyed() {
   ash::TabletMode::Get()->RemoveObserver(this);
 }
 
-bool TabletModeClient::ShouldTrackBrowser(Browser* browser) {
-  return tablet_mode_enabled_;
+bool TabletModePageBehavior::ShouldTrackBrowser(Browser* browser) {
+  return ash::TabletMode::Get()->InTabletMode();
 }
 
-void TabletModeClient::OnTabStripModelChanged(
+void TabletModePageBehavior::OnTabStripModelChanged(
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
     const TabStripSelectionChange& selection) {
@@ -102,7 +67,7 @@
     contents.contents->NotifyPreferencesChanged();
 }
 
-void TabletModeClient::SetMobileLikeBehaviorEnabled(bool enabled) {
+void TabletModePageBehavior::SetMobileLikeBehaviorEnabled(bool enabled) {
   // Toggling tablet mode on/off should trigger refreshing the WebKit
   // preferences, since in tablet mode, we enable certain mobile-like features
   // such as "double tap to zoom", "shrink page contents to fit", ... etc.
diff --git a/chrome/browser/ui/ash/tablet_mode_client.h b/chrome/browser/ui/ash/tablet_mode_page_behavior.h
similarity index 65%
rename from chrome/browser/ui/ash/tablet_mode_client.h
rename to chrome/browser/ui/ash/tablet_mode_page_behavior.h
index 1cc76d64..7e8e4b92 100644
--- a/chrome/browser/ui/ash/tablet_mode_client.h
+++ b/chrome/browser/ui/ash/tablet_mode_page_behavior.h
@@ -2,41 +2,27 @@
 // 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_H_
-#define CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_H_
+#ifndef CHROME_BROWSER_UI_ASH_TABLET_MODE_PAGE_BEHAVIOR_H_
+#define CHROME_BROWSER_UI_ASH_TABLET_MODE_PAGE_BEHAVIOR_H_
 
 #include <memory>
 
 #include "ash/public/cpp/tablet_mode_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"
 
 class BrowserTabStripTracker;
-class TabletModeClientObserver;
 
 // 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::TabletModeObserver,
-                         public BrowserTabStripTrackerDelegate,
-                         public TabStripModelObserver {
+class TabletModePageBehavior : public ash::TabletModeObserver,
+                               public BrowserTabStripTrackerDelegate,
+                               public TabStripModelObserver {
  public:
-  TabletModeClient();
-  ~TabletModeClient() override;
-
-  // Initializes and connects to ash.
-  void Init();
-
-  static TabletModeClient* Get();
-
-  bool tablet_mode_enabled() const { return tablet_mode_enabled_; }
-
-  // Adds the observer and immediately triggers it with the initial state.
-  void AddObserver(TabletModeClientObserver* observer);
-
-  void RemoveObserver(TabletModeClientObserver* observer);
+  TabletModePageBehavior();
+  ~TabletModePageBehavior() override;
 
   // Notify the tablet mode change.
   void OnTabletModeToggled(bool enabled);
@@ -60,8 +46,6 @@
   // well as starts observing new browser pages if |enabled| is true.
   void SetMobileLikeBehaviorEnabled(bool enabled);
 
-  bool tablet_mode_enabled_ = false;
-
   // We only override the WebKit preferences of webcontents that belong to
   // tabstrips in browsers. When a webcontents is newly created, its WebKit
   // preferences are refreshed *before* it's added to any tabstrip, hence
@@ -71,10 +55,7 @@
   // a refresh of its WebKit prefs.
   std::unique_ptr<BrowserTabStripTracker> tab_strip_tracker_;
 
-  base::ObserverList<TabletModeClientObserver,
-                     true /* check_empty */>::Unchecked observers_;
-
-  DISALLOW_COPY_AND_ASSIGN(TabletModeClient);
+  DISALLOW_COPY_AND_ASSIGN(TabletModePageBehavior);
 };
 
-#endif  // CHROME_BROWSER_UI_ASH_TABLET_MODE_CLIENT_H_
+#endif  // CHROME_BROWSER_UI_ASH_TABLET_MODE_PAGE_BEHAVIOR_H_
diff --git a/chrome/browser/ui/ash/tablet_mode_page_behavior_browsertest.cc b/chrome/browser/ui/ash/tablet_mode_page_behavior_browsertest.cc
index bc7e4456..5146dfed 100644
--- a/chrome/browser/ui/ash/tablet_mode_page_behavior_browsertest.cc
+++ b/chrome/browser/ui/ash/tablet_mode_page_behavior_browsertest.cc
@@ -3,10 +3,11 @@
 // found in the LICENSE file.
 
 #include "ash/public/cpp/ash_switches.h"
+#include "ash/public/cpp/tablet_mode.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "base/command_line.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/browser_features.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -39,13 +40,11 @@
   }
 
   void ToggleTabletMode() {
-    auto* tablet_mode_client = TabletModeClient::Get();
-    tablet_mode_client->OnTabletModeToggled(
-        !tablet_mode_client->tablet_mode_enabled());
+    ash::ShellTestApi().SetTabletModeEnabledForTest(!GetTabletModeEnabled());
   }
 
   bool GetTabletModeEnabled() const {
-    return TabletModeClient::Get()->tablet_mode_enabled();
+    return ash::TabletMode::Get()->InTabletMode();
   }
 
   content::WebContents* GetActiveWebContents(Browser* browser) const {
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.cc b/chrome/browser/ui/ash/wallpaper_controller_client.cc
index 1e37d89..2ab8287 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.cc
@@ -8,14 +8,14 @@
 #include "base/hash/sha1.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/customization/customization_wallpaper_util.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/policy/device_local_account.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/common/pref_names.h"
@@ -492,11 +492,11 @@
   if (!extension)
     return;
 
-  OpenApplication(
+  apps::LaunchService::Get(profile)->OpenApplication(
       AppLaunchParams(profile, extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerWindow,
+                      apps::mojom::LaunchContainer::kLaunchContainerWindow,
                       WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceChromeInternal));
+                      apps::mojom::AppLaunchSource::kSourceChromeInternal));
 }
 
 bool WallpaperControllerClient::ShouldShowUserNamesOnLogin() const {
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index b62e3ca..d06c38ab 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -2630,6 +2630,10 @@
   if (web_app::AppBrowserController::IsForWebAppBrowser(this))
     features |= FEATURE_TOOLBAR;
 
+  // Some types of web apps will have a tabstrip.
+  if (app_controller_ && app_controller_->HasTabStrip())
+    features |= FEATURE_TABSTRIP;
+
   return !!(features & feature);
 }
 
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 57c6536..c4ac889 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -123,6 +123,10 @@
   enum Type {
     // If you add a new type, consider updating the test
     // BrowserTest.StartMaximized.
+    // TODO(crbug.com/990158): It is now possible that even TYPE_POPUP can have
+    // tabs.  Rename TYPE_TABBED to TYPE_DEFAULT, and replace calls to
+    // |is_type_tabbed()| with SupportsWindowFeature(Browser::FEATURE_TABSTRIP)
+    // where the client needs to know if tabs are supported in the browser.
     TYPE_TABBED = 1,
     TYPE_POPUP = 2
   };
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc
index 5837a67..6123e43 100644
--- a/chrome/browser/ui/browser_browsertest.cc
+++ b/chrome/browser/ui/browser_browsertest.cc
@@ -27,6 +27,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/command_updater.h"
 #include "chrome/browser/defaults.h"
@@ -52,8 +54,6 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/javascript_dialogs/javascript_dialog_tab_helper.h"
 #include "chrome/browser/ui/search/local_ntp_test_utils.h"
 #include "chrome/browser/ui/search/search_tab_helper.h"
@@ -1328,11 +1328,13 @@
   const Extension* extension_app = GetExtension();
 
   // Launch it in a window, as AppLauncherHandler::HandleLaunchApp() would.
-  WebContents* app_window = OpenApplication(
-      AppLaunchParams(browser()->profile(), extension_app->id(),
-                      extensions::LaunchContainer::kLaunchContainerWindow,
-                      WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceTest));
+  WebContents* app_window =
+      apps::LaunchService::Get(browser()->profile())
+          ->OpenApplication(AppLaunchParams(
+              browser()->profile(), extension_app->id(),
+              apps::mojom::LaunchContainer::kLaunchContainerWindow,
+              WindowOpenDisposition::NEW_WINDOW,
+              apps::mojom::AppLaunchSource::kSourceTest));
   ASSERT_TRUE(app_window);
 
   DevToolsWindow* devtools_window =
@@ -1498,11 +1500,13 @@
   ASSERT_TRUE(extension_app);
 
   // Launch it in a window, as AppLauncherHandler::HandleLaunchApp() would.
-  WebContents* app_window = OpenApplication(
-      AppLaunchParams(browser()->profile(), extension_app->id(),
-                      extensions::LaunchContainer::kLaunchContainerWindow,
-                      WindowOpenDisposition::NEW_WINDOW,
-                      extensions::AppLaunchSource::kSourceTest));
+  WebContents* app_window =
+      apps::LaunchService::Get(browser()->profile())
+          ->OpenApplication(AppLaunchParams(
+              browser()->profile(), extension_app->id(),
+              apps::mojom::LaunchContainer::kLaunchContainerWindow,
+              WindowOpenDisposition::NEW_WINDOW,
+              apps::mojom::AppLaunchSource::kSourceTest));
   ASSERT_TRUE(app_window);
 
   // Apps launched in a window from the NTP have an extensions tab helper with
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 52f8843..3ece2ed7 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -630,7 +630,7 @@
       ReopenTabInProductHelpFactory::GetForProfile(browser->profile());
   reopen_tab_iph->NewTabOpened();
 
-  if (browser->is_type_tabbed()) {
+  if (browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP)) {
     TabStripModel* const model = browser->tab_strip_model();
     const auto group_id = model->GetTabGroupForTab(model->active_index());
     AddTabAt(browser, GURL(), -1, true, group_id);
diff --git a/chrome/browser/ui/chrome_pages.cc b/chrome/browser/ui/chrome_pages.cc
index 4e15e6e..6650862 100644
--- a/chrome/browser/ui/chrome_pages.cc
+++ b/chrome/browser/ui/chrome_pages.cc
@@ -19,6 +19,7 @@
 #include "base/system/sys_info.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/chromeos/extensions/default_web_app_ids.h"
 #include "chrome/browser/download/download_shelf.h"
@@ -32,7 +33,6 @@
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -140,10 +140,10 @@
           extensions::ExtensionRegistry::EVERYTHING);
   if (extension) {
     AppLaunchParams params = CreateAppLaunchParamsWithEventFlags(
-        profile, extension, 0, extensions::AppLaunchSource::kSourceUntracked,
+        profile, extension, 0, apps::mojom::AppLaunchSource::kSourceUntracked,
         -1);
     params.override_url = GURL(BuildQueryString(profile));
-    OpenApplication(params);
+    apps::LaunchService::Get(profile)->OpenApplication(params);
     return;
   }
   DVLOG(1) << "ReleaseNotes App Not Found";
@@ -183,7 +183,7 @@
     default:
       NOTREACHED() << "Unhandled help source" << source;
   }
-  OpenApplication(AppLaunchParams(
+  apps::LaunchService::Get(profile)->OpenApplication(AppLaunchParams(
       profile, extension_misc::kGeniusAppId,
       extensions::GetLaunchContainer(extensions::ExtensionPrefs::Get(profile),
                                      extension),
diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
index 30a4180..afea5a7 100644
--- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
@@ -13,15 +13,15 @@
 #import "base/mac/scoped_nsobject.h"
 #import "base/mac/sdk_forward_declarations.h"
 #include "base/macros.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h"
 #include "chrome/browser/apps/app_shim/app_shim_host_mac.h"
 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
 #include "chrome/browser/apps/app_shim/test/app_shim_host_manager_test_api_mac.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/common/chrome_switches.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
@@ -62,11 +62,11 @@
       content::WindowedNotificationObserver app_loaded_observer(
           content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
           content::NotificationService::AllSources());
-      OpenApplication(
+      apps::LaunchService::Get(profile())->OpenApplication(
           AppLaunchParams(profile(), app_->id(),
-                          extensions::LaunchContainer::kLaunchContainerNone,
+                          apps::mojom::LaunchContainer::kLaunchContainerNone,
                           WindowOpenDisposition::NEW_WINDOW,
-                          extensions::AppLaunchSource::kSourceTest));
+                          apps::mojom::AppLaunchSource::kSourceTest));
       app_loaded_observer.Wait();
     }
   }
diff --git a/chrome/browser/ui/extensions/application_launch.cc b/chrome/browser/ui/extensions/application_launch.cc
index 8598e59..ade9f90 100644
--- a/chrome/browser/ui/extensions/application_launch.cc
+++ b/chrome/browser/ui/extensions/application_launch.cc
@@ -490,15 +490,14 @@
   return feature && feature->IsAvailableToExtension(extension).is_available();
 }
 
-Browser* ReparentWebContentsIntoAppBrowser(
-    content::WebContents* contents,
-    const extensions::Extension* extension) {
+Browser* ReparentWebContentsIntoAppBrowser(content::WebContents* contents,
+                                           const std::string& app_id) {
   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
   // Incognito tabs reparent correctly, but remain incognito without any
   // indication to the user, so disallow it.
   DCHECK(!profile->IsOffTheRecord());
   Browser::CreateParams browser_params(Browser::CreateParams::CreateForApp(
-      web_app::GenerateApplicationNameFromAppId(extension->id()),
+      web_app::GenerateApplicationNameFromAppId(app_id),
       true /* trusted_source */, gfx::Rect(), profile,
       true /* user_gesture */));
   return ReparentWebContentsWithBrowserCreateParams(contents, browser_params);
@@ -523,5 +522,5 @@
   if (!extension)
     return nullptr;
   return ReparentWebContentsIntoAppBrowser(
-      browser->tab_strip_model()->GetActiveWebContents(), extension);
+      browser->tab_strip_model()->GetActiveWebContents(), extension->id());
 }
diff --git a/chrome/browser/ui/extensions/application_launch.h b/chrome/browser/ui/extensions/application_launch.h
index 8867a26..189e0cea 100644
--- a/chrome/browser/ui/extensions/application_launch.h
+++ b/chrome/browser/ui/extensions/application_launch.h
@@ -54,10 +54,11 @@
 // chrome.app.runtime.onLaunched event.
 bool CanLaunchViaEvent(const extensions::Extension* extension);
 
-// Reparents |contents| into a new app browser for |extension|.
-Browser* ReparentWebContentsIntoAppBrowser(
-    content::WebContents* contents,
-    const extensions::Extension* extension);
+// Reparents |contents| into a new app browser for |app_id|.
+// TODO(loyso): Move this util to ui/web_applications/ directory. It is
+// universal for any AppBrowserController.
+Browser* ReparentWebContentsIntoAppBrowser(content::WebContents* contents,
+                                           const std::string& app_id);
 
 // Reparents contents to a new app browser when entering the Focus Mode.
 Browser* ReparentWebContentsForFocusMode(content::WebContents* contents);
diff --git a/chrome/browser/ui/extensions/extension_installed_notification.cc b/chrome/browser/ui/extensions/extension_installed_notification.cc
index 4daa61ad..186a6d6 100644
--- a/chrome/browser/ui/extensions/extension_installed_notification.cc
+++ b/chrome/browser/ui/extensions/extension_installed_notification.cc
@@ -7,12 +7,12 @@
 #include "ash/public/cpp/notification_utils.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/notifications/notification_common.h"
 #include "chrome/browser/notifications/notification_display_service.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/browser_thread.h"
@@ -74,6 +74,6 @@
 
   AppLaunchParams params = CreateAppLaunchParamsUserContainer(
       profile_, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      extensions::AppLaunchSource::kSourceInstalledNotification);
-  OpenApplication(params);
+      apps::mojom::AppLaunchSource::kSourceInstalledNotification);
+  apps::LaunchService::Get(profile_)->OpenApplication(params);
 }
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index 58fa4fa..5c8a6ec 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -538,7 +538,8 @@
       browser()->tab_strip_model()->GetActiveWebContents();
   CheckWebContentsDoesNotHaveAppPrefs(current_tab);
 
-  Browser* app_browser = ReparentWebContentsIntoAppBrowser(current_tab, app_);
+  Browser* app_browser =
+      ReparentWebContentsIntoAppBrowser(current_tab, app_->id());
   ASSERT_NE(browser(), app_browser);
 
   CheckWebContentsHasAppPrefs(
@@ -1346,7 +1347,8 @@
   EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
             kNotPresent);
 
-  Browser* app_browser = ReparentWebContentsIntoAppBrowser(tab_contents, app_);
+  Browser* app_browser =
+      ReparentWebContentsIntoAppBrowser(tab_contents, app_->id());
 
   ASSERT_NE(app_browser, browser());
   ASSERT_EQ(GetMixedContentAppURL(), app_browser->tab_strip_model()
@@ -1392,7 +1394,7 @@
   CheckMixedContentFailedToLoad(browser());
 
   app_browser_ = ReparentWebContentsIntoAppBrowser(
-      browser()->tab_strip_model()->GetActiveWebContents(), app_);
+      browser()->tab_strip_model()->GetActiveWebContents(), app_->id());
   CheckMixedContentFailedToLoad(app_browser_);
 
   content::RenderFrameHost* main_frame =
diff --git a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
index 55829ae..5368ce4c 100644
--- a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
+++ b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
@@ -5,13 +5,13 @@
 #include "chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.h"
 
 #include "base/command_line.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/permissions/mock_permission_request.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/grit/generated_resources.h"
@@ -61,11 +61,12 @@
 
   AppLaunchParams params(
       browser()->profile(), extension->id(),
-      extensions::LaunchContainer::kLaunchContainerPanelDeprecated,
+      apps::mojom::LaunchContainer::kLaunchContainerPanelDeprecated,
       WindowOpenDisposition::NEW_WINDOW,
-      extensions::AppLaunchSource::kSourceTest);
+      apps::mojom::AppLaunchSource::kSourceTest);
 
-  content::WebContents* app_window = OpenApplication(params);
+  content::WebContents* app_window =
+      apps::LaunchService::Get(browser()->profile())->OpenApplication(params);
   CHECK(app_window);
 
   Browser* app_browser = chrome::FindBrowserWithWebContents(app_window);
diff --git a/chrome/browser/ui/settings_window_manager_chromeos.cc b/chrome/browser/ui/settings_window_manager_chromeos.cc
index f09d370f..427de40 100644
--- a/chrome/browser/ui/settings_window_manager_chromeos.cc
+++ b/chrome/browser/ui/settings_window_manager_chromeos.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/ui/settings_window_manager_observer_chromeos.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
-#include "chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.h"
+#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 #include "chrome/browser/web_applications/system_web_app_manager.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chromeos/constants/chromeos_features.h"
diff --git a/chrome/browser/ui/tabs/tab_group_visual_data.cc b/chrome/browser/ui/tabs/tab_group_visual_data.cc
index 226357f..572c2bd 100644
--- a/chrome/browser/ui/tabs/tab_group_visual_data.cc
+++ b/chrome/browser/ui/tabs/tab_group_visual_data.cc
@@ -17,3 +17,6 @@
   static SkRandom rand;
   color_ = rand.nextU() | 0xff000000;
 }
+
+TabGroupVisualData::TabGroupVisualData(base::string16 title, SkColor color)
+    : title_(title), color_(color) {}
diff --git a/chrome/browser/ui/tabs/tab_group_visual_data.h b/chrome/browser/ui/tabs/tab_group_visual_data.h
index 9c4869a..37920e85 100644
--- a/chrome/browser/ui/tabs/tab_group_visual_data.h
+++ b/chrome/browser/ui/tabs/tab_group_visual_data.h
@@ -15,9 +15,14 @@
  public:
   // Construct a TabGroupVisualData with placeholder name and random color.
   TabGroupVisualData();
+
+  TabGroupVisualData(base::string16 title, SkColor color);
+
   TabGroupVisualData(const TabGroupVisualData& other) = default;
   TabGroupVisualData(TabGroupVisualData&& other) = default;
-  ~TabGroupVisualData() = default;
+
+  TabGroupVisualData& operator=(const TabGroupVisualData& other) = default;
+  TabGroupVisualData& operator=(TabGroupVisualData&& other) = default;
 
   base::string16 title() const { return title_; }
   SkColor color() const { return color_; }
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 1f578d57..00bb862 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -291,6 +291,10 @@
       : visual_data_(std::move(visual_data)) {}
 
   const TabGroupVisualData& visual_data() const { return visual_data_; }
+  void SetVisualData(TabGroupVisualData visual_data) {
+    visual_data_ = std::move(visual_data);
+  }
+
   bool empty() const { return tab_count_ == 0; }
 
   void TabAdded() { ++tab_count_; }
@@ -300,7 +304,7 @@
   }
 
  private:
-  const TabGroupVisualData visual_data_;
+  TabGroupVisualData visual_data_;
   int tab_count_ = 0;
 };
 
@@ -803,6 +807,21 @@
   return &group_data_.at(group).visual_data();
 }
 
+void TabStripModel::SetVisualDataForGroup(TabGroupId group,
+                                          TabGroupVisualData data) {
+  DCHECK(!reentrancy_guard_);
+  base::AutoReset<bool> resetter(&reentrancy_guard_, true);
+
+  DCHECK(base::Contains(group_data_, group));
+  auto data_it = group_data_.find(group);
+  data_it->second.SetVisualData(data);
+
+  for (auto& observer : observers_) {
+    observer.OnTabGroupVisualDataChanged(this, group,
+                                         &data_it->second.visual_data());
+  }
+}
+
 base::Optional<TabGroupId> TabStripModel::GetTabGroupForTab(int index) const {
   return ContainsIndex(index) ? contents_data_[index]->group() : base::nullopt;
 }
@@ -1779,7 +1798,10 @@
       contents_data_.cbegin(), contents_data_.cend(),
       [new_group](const auto& datum) { return datum->group() == new_group; }));
 
-  group_data_.emplace(new_group, GroupData(TabGroupVisualData()));
+  // Create initial visual data and save the iterator so we can notify observer
+  // later.
+  const auto data_it =
+      group_data_.emplace(new_group, GroupData(TabGroupVisualData())).first;
 
   // Find a destination for the first tab that's not inside another group. We
   // will stack the rest of the tabs up to its right.
@@ -1800,6 +1822,12 @@
     new_indices = SetTabsPinned(new_indices, true);
 
   MoveTabsIntoGroup(new_indices, destination_index, new_group);
+
+  // Notify observers about the initial visual data.
+  for (auto& observer : observers_) {
+    observer.OnTabGroupVisualDataChanged(this, new_group,
+                                         &data_it->second.visual_data());
+  }
 }
 
 void TabStripModel::AddToExistingGroupImpl(const std::vector<int>& indices,
diff --git a/chrome/browser/ui/tabs/tab_strip_model.h b/chrome/browser/ui/tabs/tab_strip_model.h
index ee22a1e..f67ce57d 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.h
+++ b/chrome/browser/ui/tabs/tab_strip_model.h
@@ -325,9 +325,13 @@
   base::Optional<TabGroupId> GetTabGroupForTab(int index) const;
 
   // Returns the TabGroupVisualData instance for the given |group|. The returned
-  // pointer is valid until all tabs in |group| are destroyed.
+  // pointer is valid until all tabs in |group| are destroyed or until
+  // SetVisualDataForGroup is called for |group|.
   const TabGroupVisualData* GetVisualDataForGroup(TabGroupId group) const;
 
+  // Sets the visual data for |group|. Notifies observers of the change.
+  void SetVisualDataForGroup(TabGroupId group, TabGroupVisualData data);
+
   // Returns a list of tab groups that contain at least one tab in this strip.
   std::vector<TabGroupId> ListTabGroups() const;
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.cc b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
index 555b5eb..3f59037 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 
+#include <utility>
+
 #include "base/logging.h"
 
 using content::WebContents;
@@ -120,6 +122,11 @@
     const TabStripModelChange& change,
     const TabStripSelectionChange& selection) {}
 
+void TabStripModelObserver::OnTabGroupVisualDataChanged(
+    TabStripModel* tab_strip_model,
+    TabGroupId group,
+    const TabGroupVisualData* visual_data) {}
+
 void TabStripModelObserver::TabChangedAt(WebContents* contents,
                                          int index,
                                          TabChangeType change_type) {
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.h b/chrome/browser/ui/tabs/tab_strip_model_observer.h
index 0d777fa..e583769 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.h
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/tabs/tab_group_id.h"
 #include "ui/base/models/list_selection_model.h"
 
+class TabGroupVisualData;
 class TabStripModel;
 
 namespace content {
@@ -258,6 +259,15 @@
                                       const TabStripModelChange& change,
                                       const TabStripSelectionChange& selection);
 
+  // Called when the TabGroupVisualData associated with a group changes. |group|
+  // identifies which group changed. |visual_data| is a pointer to the new
+  // TabGroupVisualData and is valid until either the data associated with
+  // |group| changes again or |group| is destroyed.
+  virtual void OnTabGroupVisualDataChanged(
+      TabStripModel* tab_strip_model,
+      TabGroupId group,
+      const TabGroupVisualData* visual_data);
+
   // The specified WebContents at |index| changed in some way. |contents|
   // may be an entirely different object and the old value is no longer
   // available by the time this message is delivered.
diff --git a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
index 1592e187..8f347bba 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
@@ -35,6 +35,7 @@
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
 
 using content::WebContents;
 
@@ -200,6 +201,15 @@
     states_.push_back(s);
   }
 
+  struct TabGroupVisualDataUpdate {
+    TabGroupId group;
+    TabGroupVisualData visual_data;
+  };
+
+  const std::vector<TabGroupVisualDataUpdate>& visual_data_updates() const {
+    return visual_data_updates_;
+  }
+
   // TabStripModelObserver overrides:
   void OnTabStripModelChanged(
       TabStripModel* tab_strip_model,
@@ -258,6 +268,14 @@
     }
   }
 
+  void OnTabGroupVisualDataChanged(
+      TabStripModel* tab_strip_model,
+      TabGroupId group,
+      const TabGroupVisualData* visual_data) override {
+    visual_data_updates_.push_back(
+        TabGroupVisualDataUpdate{group, *visual_data});
+  }
+
   void TabChangedAt(WebContents* contents,
                     int index,
                     TabChangeType change_type) override {
@@ -287,6 +305,7 @@
 
  private:
   std::vector<State> states_;
+  std::vector<TabGroupVisualDataUpdate> visual_data_updates_;
 
   DISALLOW_COPY_AND_ASSIGN(MockTabStripModelObserver);
 };
@@ -3381,3 +3400,42 @@
 
   strip.CloseAllTabs();
 }
+
+TEST_F(TabStripModelTest, SetVisualDataForGroup) {
+  TestTabStripModelDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+  PrepareTabs(&strip, 1);
+  const TabGroupId group = strip.AddToNewGroup({0});
+
+  const TabGroupVisualData new_data(base::ASCIIToUTF16("Foo"), SK_ColorCYAN);
+  strip.SetVisualDataForGroup(group, new_data);
+  const TabGroupVisualData* data = strip.GetVisualDataForGroup(group);
+  EXPECT_EQ(data->title(), new_data.title());
+  EXPECT_EQ(data->color(), new_data.color());
+
+  strip.CloseAllTabs();
+}
+
+TEST_F(TabStripModelTest, VisualDataChangeNotifiesObservers) {
+  TestTabStripModelDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+  MockTabStripModelObserver observer;
+  strip.AddObserver(&observer);
+  PrepareTabs(&strip, 1);
+  const TabGroupId group = strip.AddToNewGroup({0});
+
+  // Check that we are notified about the placeholder TabGroupVisualData.
+  ASSERT_EQ(1u, observer.visual_data_updates().size());
+  EXPECT_EQ(group, observer.visual_data_updates()[0].group);
+
+  const TabGroupVisualData new_data(base::ASCIIToUTF16("Foo"), SK_ColorBLUE);
+  strip.SetVisualDataForGroup(group, new_data);
+
+  // Now check that we are notified when we change it.
+  ASSERT_EQ(2u, observer.visual_data_updates().size());
+  EXPECT_EQ(group, observer.visual_data_updates()[1].group);
+  EXPECT_EQ(new_data.title(),
+            observer.visual_data_updates()[1].visual_data.title());
+
+  strip.CloseAllTabs();
+}
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 6c4c734..2ff10a67 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -80,7 +80,7 @@
 #endif
 
 #if defined(OS_CHROMEOS)
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #endif
@@ -800,8 +800,7 @@
   // Always show this option if we're in tablet mode on Chrome OS.
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           chromeos::switches::kEnableRequestTabletSite) ||
-      (TabletModeClient::Get() &&
-       TabletModeClient::Get()->tablet_mode_enabled())) {
+      (ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode())) {
     AddCheckItemWithStringId(IDC_TOGGLE_REQUEST_TABLET_SITE,
                              IDS_TOGGLE_REQUEST_TABLET_SITE);
   }
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
index 8555423..c7616940 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
@@ -45,21 +45,12 @@
 #include "chrome/browser/ui/views/apps/app_info_dialog/arc_app_info_links_panel.h"
 #endif
 
-#if BUILDFLAG(ENABLE_APP_LIST)
-#include "ui/aura/window.h"
-#endif
-
 namespace {
 
 // The color of the separator used inside the dialog - should match the app
 // list's app_list::kDialogSeparatorColor
 constexpr SkColor kDialogSeparatorColor = SkColorSetRGB(0xD1, 0xD1, 0xD1);
 
-#if BUILDFLAG(ENABLE_APP_LIST)
-// The elevation used for dialog shadow effect.
-constexpr int kDialogShadowElevation = 24;
-#endif
-
 }  // namespace
 
 bool CanShowAppInfoDialog() {
@@ -71,20 +62,16 @@
 }
 
 #if BUILDFLAG(ENABLE_APP_LIST)
-void ShowAppInfoInAppList(const gfx::Rect& app_info_bounds,
+void ShowAppInfoInAppList(gfx::NativeWindow parent,
+                          const gfx::Rect& app_info_bounds,
                           Profile* profile,
                           const extensions::Extension* app) {
   views::DialogDelegate* dialog = CreateAppListContainerForView(
       std::make_unique<AppInfoDialog>(profile, app));
-  views::Widget* dialog_widget = new views::Widget();
-  views::Widget::InitParams params =
-      views::DialogDelegate::GetDialogWidgetInitParams(dialog, nullptr, nullptr,
-                                                       app_info_bounds);
-  params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_DEFAULT;
-  params.shadow_elevation = kDialogShadowElevation;
-  dialog_widget->Init(std::move(params));
-  // The title is not shown on the dialog, but it is used for overview mode.
-  dialog_widget->GetNativeWindow()->SetTitle(base::UTF8ToUTF16(app->name()));
+
+  views::Widget* dialog_widget =
+      constrained_window::CreateBrowserModalDialogViews(dialog, parent);
+  dialog_widget->SetBounds(app_info_bounds);
   dialog_widget->Show();
 }
 #endif
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
index bf178b2..1261998 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
@@ -14,6 +14,7 @@
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "ash/public/interfaces/constants.mojom.h"
@@ -25,7 +26,6 @@
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_context_menu.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/views/exclusive_access_bubble_views.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -67,13 +67,13 @@
 ChromeNativeAppWindowViewsAuraAsh::ChromeNativeAppWindowViewsAuraAsh()
     : exclusive_access_manager_(
           std::make_unique<ExclusiveAccessManager>(this)) {
-  if (TabletModeClient::Get())
-    TabletModeClient::Get()->AddObserver(this);
+  if (ash::TabletMode::Get())
+    ash::TabletMode::Get()->AddObserver(this);
 }
 
 ChromeNativeAppWindowViewsAuraAsh::~ChromeNativeAppWindowViewsAuraAsh() {
-  if (TabletModeClient::Get())
-    TabletModeClient::Get()->RemoveObserver(this);
+  if (ash::TabletMode::Get())
+    ash::TabletMode::Get()->RemoveObserver(this);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -299,11 +299,13 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// TabletModeClientObserver implementation:
-void ChromeNativeAppWindowViewsAuraAsh::OnTabletModeToggled(bool enabled) {
-  tablet_mode_enabled_ = enabled;
-  UpdateImmersiveMode();
-  widget()->non_client_view()->Layout();
+// TabletModeObserver implementation:
+void ChromeNativeAppWindowViewsAuraAsh::OnTabletModeStarted() {
+  OnTabletModeToggled(true);
+}
+
+void ChromeNativeAppWindowViewsAuraAsh::OnTabletModeEnded() {
+  OnTabletModeToggled(false);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -491,6 +493,12 @@
   observed_window_.Remove(window);
 }
 
+void ChromeNativeAppWindowViewsAuraAsh::OnTabletModeToggled(bool enabled) {
+  tablet_mode_enabled_ = enabled;
+  UpdateImmersiveMode();
+  widget()->non_client_view()->Layout();
+}
+
 void ChromeNativeAppWindowViewsAuraAsh::OnMenuClosed() {
   menu_runner_.reset();
   menu_model_.reset();
@@ -509,7 +517,6 @@
   if (app_window()->IsOsFullscreen())
     return true;
 
-  TabletModeClient* client = TabletModeClient::Get();
   // Windows in tablet mode which are resizable have their title bars
   // hidden in ash for more size, so enable immersive mode so users
   // have access to window controls. Non resizable windows do not gain
@@ -517,8 +524,8 @@
   // is no need for immersive mode.
   // TODO(crbug.com/801619): This adds a little extra animation
   // when minimizing or unminimizing window.
-  return client && client->tablet_mode_enabled() && CanResize() &&
-         !IsMinimized();
+  return ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode() &&
+         CanResize() && !IsMinimized();
 }
 
 void ChromeNativeAppWindowViewsAuraAsh::UpdateImmersiveMode() {
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h
index 6a66f87..a1bff9d3 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h
@@ -8,10 +8,10 @@
 #include <memory>
 #include <vector>
 
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "ash/wm/window_state_observer.h"
 #include "base/gtest_prod_util.h"
 #include "base/scoped_observer.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
 #include "chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.h"
 #include "chrome/browser/ui/views/exclusive_access_bubble_views_context.h"
@@ -35,7 +35,7 @@
 class ChromeNativeAppWindowViewsAuraAsh
     : public ChromeNativeAppWindowViewsAura,
       public views::ContextMenuController,
-      public TabletModeClientObserver,
+      public ash::TabletModeObserver,
       public ui::AcceleratorProvider,
       public ExclusiveAccessContext,
       public ExclusiveAccessBubbleViewsContext,
@@ -78,8 +78,9 @@
   void SetFullscreen(int fullscreen_types) override;
   void SetActivateOnPointer(bool activate_on_pointer) override;
 
-  // ash:TabletModeObserver:
-  void OnTabletModeToggled(bool enabled) override;
+  // ash::TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
 
   // ui::AcceleratorProvider:
   bool GetAcceleratorForCommandId(int command_id,
@@ -149,6 +150,9 @@
   FRIEND_TEST_ALL_PREFIXES(ShapedAppWindowTargeterTest,
                            ResizeInsetsWithinBounds);
 
+  // Invoked to handle tablet mode change.
+  void OnTabletModeToggled(bool enabled);
+
   // Callback for MenuRunner
   void OnMenuClosed();
 
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 c6533cb..4098a0e9 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
@@ -13,7 +13,7 @@
 #include "build/build_config.h"
 #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_page_behavior.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"
diff --git a/chrome/browser/ui/views/collected_cookies_views.cc b/chrome/browser/ui/views/collected_cookies_views.cc
index 7f98d40..c8a2b5b 100644
--- a/chrome/browser/ui/views/collected_cookies_views.cc
+++ b/chrome/browser/ui/views/collected_cookies_views.cc
@@ -279,13 +279,23 @@
 void CollectedCookiesViews::CreateAndShowForWebContents(
     content::WebContents* web_contents) {
   CollectedCookiesViews* instance = FromWebContents(web_contents);
-  if (instance) {
-    web_modal::WebContentsModalDialogManager::FromWebContents(web_contents)
-        ->FocusTopmostDialog();
+  if (!instance) {
+    CreateForWebContents(web_contents);
     return;
   }
 
-  CreateForWebContents(web_contents);
+  // On rare occasions, |instance| may have started, but not finished,
+  // closing. In this case, the modal dialog manager will have removed the
+  // dialog from its list of tracked dialogs, and therefore might not have any
+  // active dialog. This should be rare enough that it's not worth trying to
+  // re-open the dialog. See https://crbug.com/989888
+  if (instance->GetWidget()->IsClosed())
+    return;
+
+  auto* dialog_manager =
+      web_modal::WebContentsModalDialogManager::FromWebContents(web_contents);
+  CHECK(dialog_manager->IsDialogActive());
+  dialog_manager->FocusTopmostDialog();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/views/collected_cookies_views_browsertest.cc b/chrome/browser/ui/views/collected_cookies_views_browsertest.cc
index 92f58ff..e8c7d4f 100644
--- a/chrome/browser/ui/views/collected_cookies_views_browsertest.cc
+++ b/chrome/browser/ui/views/collected_cookies_views_browsertest.cc
@@ -41,7 +41,7 @@
   // Closing dialog with modified data will shows infobar.
   void SetDialogChanged() { cookies_dialog_->status_changed_ = true; }
 
-  void CloseCookiesDialog() { cookies_dialog_->Close(); }
+  void CloseCookiesDialog() { cookies_dialog_->GetWidget()->Close(); }
 
   size_t infobar_count() const {
     content::WebContents* web_contents =
@@ -93,3 +93,14 @@
 
   EXPECT_EQ(0u, infobar_count());
 }
+
+// Closing the widget asynchronously destroys the CollectedCookiesViews object,
+// but synchronously removes it from the WebContentsModalDialogManager. Make
+// sure there's no crash when trying to re-open the CollectedCookiesViews right
+// after closing it. Regression test for https://crbug.com/989888
+IN_PROC_BROWSER_TEST_F(CollectedCookiesViewsTest, CloseDialogAndReopen) {
+  CloseCookiesDialog();
+  auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
+  CollectedCookiesViews::CreateAndShowForWebContents(web_contents);
+  // If the test didn't crash, it has passed.
+}
diff --git a/chrome/browser/ui/views/extensions/extension_dialog.cc b/chrome/browser/ui/views/extensions/extension_dialog.cc
index 87c17b85..852a8ab 100644
--- a/chrome/browser/ui/views/extensions/extension_dialog.cc
+++ b/chrome/browser/ui/views/extensions/extension_dialog.cc
@@ -29,7 +29,7 @@
 #include "url/gurl.h"
 
 #if defined(OS_CHROMEOS)
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "ash/public/cpp/tablet_mode.h"
 #endif
 
 using content::BrowserContext;
@@ -175,8 +175,7 @@
 bool ExtensionDialog::CanResize() const {
 #if defined(OS_CHROMEOS)
   // Prevent dialog resize mouse cursor in tablet mode, crbug.com/453634.
-  const auto* client = TabletModeClient::Get();
-  if (client && client->tablet_mode_enabled())
+  if (ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode())
     return false;
 #endif
   // Can resize only if minimum contents size set.
diff --git a/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc b/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
index b044212..f954684 100644
--- a/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
@@ -83,7 +83,7 @@
         // visible, and will fall back on the default case on showing the
         // installed dialog anchored to the general extensions toolbar button.
         reference_view =
-            browser_view->toolbar()->extensions_container()->GetViewForId(
+            browser_view->toolbar_button_provider()->GetToolbarActionViewForId(
                 controller->extension()->id());
       } else {
         BrowserActionsContainer* container =
@@ -107,12 +107,8 @@
 
   // Default case.
   if (!reference_view || !reference_view->GetVisible()) {
-    if (base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu)) {
-      return browser_view->toolbar()
-          ->extensions_container()
-          ->extensions_button();
-    }
-    return browser_view->toolbar_button_provider()->GetAppMenuButton();
+    return browser_view->toolbar_button_provider()
+        ->GetDefaultExtensionDialogAnchorView();
   }
   return reference_view;
 }
diff --git a/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc
index 63ccb0b..1bbd79a 100644
--- a/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc
@@ -45,12 +45,11 @@
   if (!browser_view)
     return nullptr;
   DCHECK(browser_view->toolbar_button_provider());
-  BrowserActionsContainer* const browser_actions_container =
-      browser_view->toolbar_button_provider()->GetBrowserActionsContainer();
-  if (!browser_actions_container)
-    return nullptr;
+  // TODO(pbos): Pop out extensions so that they can become visible before
+  // showing the uninstall dialog.
   ToolbarActionView* const reference_view =
-      browser_actions_container->GetViewForId(extension_id);
+      browser_view->toolbar_button_provider()->GetToolbarActionViewForId(
+          extension_id);
   return reference_view && reference_view->GetVisible() ? reference_view
                                                         : nullptr;
 }
diff --git a/chrome/browser/ui/views/frame/browser_frame_header_ash.cc b/chrome/browser/ui/views/frame/browser_frame_header_ash.cc
index 73c9ec9..5422842 100644
--- a/chrome/browser/ui/views/frame/browser_frame_header_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_header_ash.cc
@@ -7,9 +7,9 @@
 #include "ash/public/cpp/ash_constants.h"
 #include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
 #include "ash/public/cpp/frame_utils.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/logging.h"
 #include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "third_party/skia/include/core/SkPaint.h"
@@ -153,8 +153,8 @@
 views::CaptionButtonLayoutSize BrowserFrameHeaderAsh::GetButtonLayoutSize()
     const {
   return target_widget()->IsMaximized() || target_widget()->IsFullscreen() ||
-                 (TabletModeClient::Get() &&
-                  TabletModeClient::Get()->tablet_mode_enabled())
+                 (ash::TabletMode::Get() &&
+                  ash::TabletMode::Get()->InTabletMode())
              ? views::CaptionButtonLayoutSize::kBrowserCaptionMaximized
              : views::CaptionButtonLayoutSize::kBrowserCaptionRestored;
 }
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
index 50f533fd..e68e94b 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
@@ -13,6 +13,7 @@
 #include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
 #include "ash/public/cpp/default_frame_header.h"
 #include "ash/public/cpp/frame_utils.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/touch_uma.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/cpp/window_state_type.h"
@@ -27,7 +28,6 @@
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
 #include "chrome/browser/ui/ash/session_util.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
@@ -123,9 +123,7 @@
   browser_view()->browser()->command_controller()->RemoveCommandObserver(
       IDC_BACK, this);
 
-  if (TabletModeClient::Get())
-    TabletModeClient::Get()->RemoveObserver(this);
-
+  ash::TabletMode::Get()->RemoveObserver(this);
   ash::SplitViewNotifier::Get()->RemoveObserver(this);
 
   ImmersiveModeController* immersive_controller =
@@ -164,9 +162,7 @@
   if (browser->profile()->IsOffTheRecord())
     window->SetProperty(ash::kBlockedForAssistantSnapshotKey, true);
 
-  // TabletModeClient may not be initialized during unit tests.
-  if (TabletModeClient::Get())
-    TabletModeClient::Get()->AddObserver(this);
+  ash::TabletMode::Get()->AddObserver(this);
 
   if (browser->is_app() && IsV1AppBackButtonEnabled()) {
     browser->command_controller()->AddCommandObserver(IDC_BACK, this);
@@ -493,7 +489,15 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// ash::mojom::TabletModeClient:
+// ash::TabletModeToggleObserver:
+
+void BrowserNonClientFrameViewAsh::OnTabletModeStarted() {
+  OnTabletModeToggled(true);
+}
+
+void BrowserNonClientFrameViewAsh::OnTabletModeEnded() {
+  OnTabletModeToggled(false);
+}
 
 void BrowserNonClientFrameViewAsh::OnTabletModeToggled(bool enabled) {
   if (!enabled && browser_view()->immersive_mode_controller()->IsRevealed()) {
@@ -649,8 +653,8 @@
   // minimize all windows when pressing the Launcher button on the shelf.
   const bool hide_caption_buttons_in_tablet_mode =
       !UsePackagedAppHeaderStyle(browser_view()->browser());
-  if (hide_caption_buttons_in_tablet_mode && TabletModeClient::Get() &&
-      TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (hide_caption_buttons_in_tablet_mode &&
+      ash::TabletMode::Get()->InTabletMode()) {
     return false;
   }
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
index 69d507c2..6799cf0 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
@@ -8,12 +8,12 @@
 #include <memory>
 
 #include "ash/public/cpp/split_view.h"
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/command_observer.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "chrome/browser/ui/views/frame/browser_frame_header_ash.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
@@ -40,7 +40,7 @@
 class BrowserNonClientFrameViewAsh
     : public BrowserNonClientFrameView,
       public BrowserFrameHeaderAsh::AppearanceProvider,
-      public TabletModeClientObserver,
+      public ash::TabletModeObserver,
       public TabIconViewModel,
       public CommandObserver,
       public ash::SplitViewObserver,
@@ -91,8 +91,11 @@
   int GetFrameHeaderImageYInset() override;
   gfx::ImageSkia GetFrameHeaderOverlayImage(bool active) override;
 
-  // TabletModeClientObserver:
-  void OnTabletModeToggled(bool enabled) override;
+  // ash::TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
+
+  void OnTabletModeToggled(bool enabled);
 
   // TabIconViewModel:
   bool ShouldTabIconViewAnimate() const override;
diff --git a/chrome/browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc b/chrome/browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc
index 5b4818a..7da5f6a 100644
--- a/chrome/browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc
+++ b/chrome/browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc
@@ -16,6 +16,8 @@
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/scoped_propvariant.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile.h"
@@ -28,8 +30,6 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/web_applications/components/web_app_shortcut.h"
 #include "chrome/browser/web_applications/components/web_app_shortcut_win.h"
 #include "chrome/common/chrome_switches.h"
@@ -198,11 +198,12 @@
       LoadExtension(test_data_dir_.AppendASCII("app/"));
   EXPECT_TRUE(extension);
 
-  OpenApplication(
-      AppLaunchParams(browser()->profile(), extension->id(),
-                      extensions::LaunchContainer::kLaunchContainerWindow,
-                      WindowOpenDisposition::NEW_FOREGROUND_TAB,
-                      extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(
+          AppLaunchParams(browser()->profile(), extension->id(),
+                          apps::mojom::LaunchContainer::kLaunchContainerWindow,
+                          WindowOpenDisposition::NEW_FOREGROUND_TAB,
+                          apps::mojom::AppLaunchSource::kSourceTest));
 
   // Check that the new browser has an app name.
   // The launch should have created a new browser.
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.cc b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
index d77910b..eda720b6 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.cc
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/content_settings/content_setting_image_model.h"
 #include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/hosted_app_menu_button.h"
@@ -364,9 +365,23 @@
 
 BrowserActionsContainer*
 HostedAppButtonContainer::GetBrowserActionsContainer() {
+  CHECK(!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
   return browser_actions_container_;
 }
 
+ToolbarActionView* HostedAppButtonContainer::GetToolbarActionViewForId(
+    const std::string& id) {
+  // TODO(pbos): Implement this for kExtensionsToolbarMenu.
+  CHECK(!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
+  return browser_actions_container_->GetViewForId(id);
+}
+
+views::View* HostedAppButtonContainer::GetDefaultExtensionDialogAnchorView() {
+  // TODO(pbos): Implement this for kExtensionsToolbarMenu.
+  CHECK(!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
+  return GetAppMenuButton();
+}
+
 OmniboxPageActionIconContainerView*
 HostedAppButtonContainer::GetOmniboxPageActionIconContainerView() {
   return omnibox_page_action_icon_container_view_;
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.h b/chrome/browser/ui/views/frame/hosted_app_button_container.h
index f499ac9c..65d95d1 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.h
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.h
@@ -114,6 +114,8 @@
 
   // ToolbarButtonProvider:
   BrowserActionsContainer* GetBrowserActionsContainer() override;
+  ToolbarActionView* GetToolbarActionViewForId(const std::string& id) override;
+  views::View* GetDefaultExtensionDialogAnchorView() override;
   OmniboxPageActionIconContainerView* GetOmniboxPageActionIconContainerView()
       override;
   AppMenuButton* GetAppMenuButton() override;
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_ash.cc b/chrome/browser/ui/views/frame/immersive_mode_controller_ash.cc
index 30a104a..2ed88183 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_ash.cc
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_ash.cc
@@ -5,10 +5,11 @@
 #include "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h"
 
 #include "ash/public/cpp/immersive/immersive_revealed_lock.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/window_properties.h"
 #include "base/macros.h"
 #include "chrome/browser/platform_util.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "chrome/browser/ui/ash/tablet_mode_page_behavior.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/top_container_view.h"
@@ -124,7 +125,7 @@
 
 bool ImmersiveModeControllerAsh::ShouldStayImmersiveAfterExitingFullscreen() {
   return !browser_view_->IsBrowserTypeNormal() &&
-         TabletModeClient::Get()->tablet_mode_enabled();
+         ash::TabletMode::Get()->InTabletMode();
 }
 
 void ImmersiveModeControllerAsh::OnWidgetActivationChanged(
@@ -133,7 +134,7 @@
   if (browser_view_->IsBrowserTypeNormal())
     return;
 
-  if (!TabletModeClient::Get()->tablet_mode_enabled())
+  if (!ash::TabletMode::Get()->InTabletMode())
     return;
 
   // Don't use immersive mode as long as we are in the locked fullscreen mode
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 c6769c6..0ae0bf8 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
@@ -9,7 +9,7 @@
 #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/ui/ash/tablet_mode_client.h"
+#include "chrome/browser/ui/ash/tablet_mode_page_behavior.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
diff --git a/chrome/browser/ui/views/frame/toolbar_button_provider.h b/chrome/browser/ui/views/frame/toolbar_button_provider.h
index fa48e214..d46bba2 100644
--- a/chrome/browser/ui/views/frame/toolbar_button_provider.h
+++ b/chrome/browser/ui/views/frame/toolbar_button_provider.h
@@ -8,6 +8,7 @@
 class AppMenuButton;
 class BrowserActionsContainer;
 class OmniboxPageActionIconContainerView;
+class ToolbarActionView;
 
 namespace gfx {
 class Rect;
@@ -23,8 +24,17 @@
 class ToolbarButtonProvider {
  public:
   // Gets the browser actions container.
+  // TODO(pbos): Transition callers off of this function.
   virtual BrowserActionsContainer* GetBrowserActionsContainer() = 0;
 
+  // Gets the associated ToolbarActionView for this id.
+  virtual ToolbarActionView* GetToolbarActionViewForId(
+      const std::string& id) = 0;
+
+  // Gets the default view to use as an anchor for extension dialogs if the
+  // ToolbarActionView is not visible or available.
+  virtual views::View* GetDefaultExtensionDialogAnchorView() = 0;
+
   // Gets the omnibox page action icon container.
   virtual OmniboxPageActionIconContainerView*
   GetOmniboxPageActionIconContainerView() = 0;
diff --git a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
index a9f1865c..24caa2c 100644
--- a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
+++ b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
@@ -4,11 +4,11 @@
 
 #include "chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.h"
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/bind.h"
 #include "chrome/browser/permissions/permission_request_manager.h"
 #include "chrome/browser/search/search.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/top_container_view.h"
 #include "chrome/common/chrome_render_frame.mojom.h"
@@ -35,8 +35,7 @@
 namespace {
 
 bool IsTabletModeEnabled() {
-  return TabletModeClient::Get() &&
-         TabletModeClient::Get()->tablet_mode_enabled();
+  return ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode();
 }
 
 bool IsSpokenFeedbackEnabled() {
@@ -304,8 +303,8 @@
   registrar_.Add(this, content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE,
                  content::NotificationService::AllSources());
 
-  if (TabletModeClient::Get())
-    TabletModeClient::Get()->AddObserver(this);
+  if (ash::TabletMode::Get())
+    ash::TabletMode::Get()->AddObserver(this);
 
   browser_view_->browser()->tab_strip_model()->AddObserver(this);
 
@@ -326,8 +325,8 @@
 
   browser_view_->browser()->tab_strip_model()->RemoveObserver(this);
 
-  if (TabletModeClient::Get())
-    TabletModeClient::Get()->RemoveObserver(this);
+  if (ash::TabletMode::Get())
+    ash::TabletMode::Get()->RemoveObserver(this);
 }
 
 bool TopControlsSlideControllerChromeOS::IsEnabled() const {
@@ -436,8 +435,11 @@
   return is_gesture_scrolling_in_progress_;
 }
 
-void TopControlsSlideControllerChromeOS::OnTabletModeToggled(
-    bool tablet_mode_enabled) {
+void TopControlsSlideControllerChromeOS::OnTabletModeStarted() {
+  OnEnabledStateChanged(CanEnable(base::nullopt));
+}
+
+void TopControlsSlideControllerChromeOS::OnTabletModeEnded() {
   OnEnabledStateChanged(CanEnable(base::nullopt));
 }
 
diff --git a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.h b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.h
index f2a23afd..d5c4151 100644
--- a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.h
+++ b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.h
@@ -7,11 +7,11 @@
 
 #include <memory>
 
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/optional.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/views/frame/top_controls_slide_controller.h"
 #include "content/public/browser/notification_observer.h"
@@ -38,7 +38,7 @@
 // - Page security level changes.
 class TopControlsSlideControllerChromeOS
     : public TopControlsSlideController,
-      public TabletModeClientObserver,
+      public ash::TabletModeObserver,
       public TabStripModelObserver,
       public content::NotificationObserver {
  public:
@@ -55,8 +55,9 @@
   void SetTopControlsGestureScrollInProgress(bool in_progress) override;
   bool IsTopControlsGestureScrollInProgress() const override;
 
-  // TabletModeClientObserver:
-  void OnTabletModeToggled(bool tablet_mode_enabled) override;
+  // ash::TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
 
   // TabStripModelObserver:
   void OnTabStripModelChanged(
diff --git a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc
index 407eeaf..0698f172 100644
--- a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc
+++ b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc
@@ -9,6 +9,8 @@
 #include <vector>
 
 #include "ash/public/cpp/ash_switches.h"
+#include "ash/public/cpp/tablet_mode.h"
+#include "ash/public/cpp/test/shell_test_api.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/cros_display_config.mojom-test-utils.h"
 #include "ash/public/interfaces/cros_display_config.mojom.h"
@@ -21,7 +23,6 @@
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
 #include "chrome/browser/permissions/permission_request_impl.h"
 #include "chrome/browser/permissions/permission_request_manager.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -37,6 +38,7 @@
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/display/display.h"
+#include "ui/display/display_switches.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/controls/native/native_view_host.h"
@@ -258,7 +260,12 @@
   void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
     InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
 
+    // Mark the device is capable of entering tablet mode.
     command_line->AppendSwitch(ash::switches::kAshEnableTabletMode);
+
+    // Use first display as internal display. Otherwise, tablet mode is ended
+    // on display change.
+    command_line->AppendSwitch(switches::kUseFirstDisplayAsInternal);
   }
 
   void SetUpOnMainThread() override {
@@ -288,13 +295,11 @@
   }
 
   void ToggleTabletMode() {
-    auto* tablet_mode_client = TabletModeClient::Get();
-    tablet_mode_client->OnTabletModeToggled(
-        !tablet_mode_client->tablet_mode_enabled());
+    ash::ShellTestApi().SetTabletModeEnabledForTest(!GetTabletModeEnabled());
   }
 
   bool GetTabletModeEnabled() const {
-    return TabletModeClient::Get()->tablet_mode_enabled();
+    return ash::TabletMode::Get()->InTabletMode();
   }
 
   void CheckBrowserLayout(BrowserView* browser_view,
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 60cbc2c..d9c8b03 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -325,8 +325,7 @@
       browser_view_->browser()->profile());
   reopen_tab_iph->NewTabOpened();
 
-  const auto group_id = model_->GetTabGroupForTab(model_->active_index());
-  model_->delegate()->AddTabAt(GURL(), -1, true, group_id);
+  model_->delegate()->AddTabAt(GURL(), -1, true);
 
 #if BUILDFLAG(ENABLE_LEGACY_DESKTOP_IN_PRODUCT_HELP)
   auto* new_tab_tracker =
@@ -518,6 +517,13 @@
     tabstrip_->SetSelection(selection.new_model);
 }
 
+void BrowserTabStripController::OnTabGroupVisualDataChanged(
+    TabStripModel* tab_strip_model,
+    TabGroupId group,
+    const TabGroupVisualData* visual_data) {
+  tabstrip_->GroupVisualsChanged(group);
+}
+
 void BrowserTabStripController::TabChangedAt(WebContents* contents,
                                              int model_index,
                                              TabChangeType change_type) {
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
index 7a664823..cdca152d 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
@@ -95,6 +95,10 @@
       TabStripModel* tab_strip_model,
       const TabStripModelChange& change,
       const TabStripSelectionChange& selection) override;
+  void OnTabGroupVisualDataChanged(
+      TabStripModel* tab_strip_model,
+      TabGroupId group,
+      const TabGroupVisualData* visual_data) override;
   void TabChangedAt(content::WebContents* contents,
                     int model_index,
                     TabChangeType change_type) override;
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 1694edac..21624383 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -685,8 +685,7 @@
   if (group_ == group)
     return;
   group_ = group;
-  UpdateForegroundColors();
-  SchedulePaint();
+  GroupColorChanged();
 }
 
 base::Optional<SkColor> Tab::GetGroupColor() const {
@@ -696,6 +695,11 @@
              : base::nullopt;
 }
 
+void Tab::GroupColorChanged() {
+  UpdateForegroundColors();
+  SchedulePaint();
+}
+
 SkColor Tab::GetAlertIndicatorColor(TabAlertState state) const {
   // If theme provider is not yet available, return the default button
   // color.
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index 9fd0a21c..a1cbf3b9 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -123,6 +123,10 @@
   // Returns the color for the tab's group, if any.
   base::Optional<SkColor> GetGroupColor() const;
 
+  // Should be called when the result of
+  // |TabController::GetVisualDataForGroup()| changes.
+  void GroupColorChanged();
+
   // Returns the color used for the alert indicator icon.
   SkColor GetAlertIndicatorColor(TabAlertState state) const;
 
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index ad42f3f..b3663a6 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -49,9 +49,9 @@
 
 #if defined(OS_CHROMEOS)
 #include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/window_properties.h"               // nogncheck
 #include "ash/public/cpp/window_state_type.h"               // nogncheck
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "ui/wm/core/coordinate_conversion.h"
 #endif
 
@@ -118,7 +118,7 @@
 void StoreCurrentDraggedBrowserBoundsInTabletMode(
     aura::Window* window,
     const gfx::Rect& bounds_in_screen) {
-  if (TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (ash::TabletMode::Get()->InTabletMode()) {
     // The bounds that is stored in ash::kRestoreBoundsOverrideKey will be used
     // by DragDetails to calculate the window bounds during dragging in tablet
     // mode.
@@ -1845,7 +1845,7 @@
   }
 
 #if defined(OS_CHROMEOS)
-  if (TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (ash::TabletMode::Get()->InTabletMode()) {
     new_bounds = GetDraggedBrowserBoundsInTabletMode(
         source->AsView()->GetWidget()->GetNativeWindow());
   }
@@ -1885,7 +1885,7 @@
     const gfx::Point& point_in_screen) {
   gfx::Rect bounds = widget->GetWindowBoundsInScreen();
 #if defined(OS_CHROMEOS)
-  if (TabletModeClient::Get()->tablet_mode_enabled())
+  if (ash::TabletMode::Get()->InTabletMode())
     bounds = GetDraggedBrowserBoundsInTabletMode(widget->GetNativeWindow());
 #endif
 
diff --git a/chrome/browser/ui/views/tabs/tab_group_header.cc b/chrome/browser/ui/views/tabs/tab_group_header.cc
index 6884ec6d..edd87422 100644
--- a/chrome/browser/ui/views/tabs/tab_group_header.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_header.cc
@@ -38,30 +38,30 @@
       .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
       .SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
 
-  const TabGroupVisualData* data = GetGroupVisualData();
-  const SkColor color = GetGroupVisualData()->color();
   const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
 
-  auto title_chip = std::make_unique<views::View>();
-  title_chip->SetBackground(views::CreateRoundedRectBackground(
-      color, provider->GetCornerRadiusMetric(views::EMPHASIS_LOW)));
-  title_chip->SetBorder(views::CreateEmptyBorder(
+  title_chip_ = AddChildView(std::make_unique<views::View>());
+  title_chip_->SetBorder(views::CreateEmptyBorder(
       provider->GetInsetsMetric(INSETS_TAB_GROUP_TITLE_CHIP)));
-  title_chip->SetLayoutManager(std::make_unique<views::FillLayout>());
-  auto* title_chip_ptr = AddChildView(std::move(title_chip));
-  title_chip_ptr->SetProperty(views::kFlexBehaviorKey,
-                              views::FlexSpecification::ForSizeRule(
-                                  views::MinimumFlexSizeRule::kScaleToZero,
-                                  views::MaximumFlexSizeRule::kPreferred));
+  title_chip_->SetLayoutManager(std::make_unique<views::FillLayout>());
+  title_chip_->SetProperty(views::kFlexBehaviorKey,
+                           views::FlexSpecification::ForSizeRule(
+                               views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kPreferred));
 
-  auto title = std::make_unique<views::Label>(data->title());
-  title->SetAutoColorReadabilityEnabled(false);
-  title->SetHorizontalAlignment(gfx::ALIGN_CENTER);
-  title->SetElideBehavior(gfx::FADE_TAIL);
-  title->SetEnabledColor(color_utils::GetColorWithMaxContrast(color));
-  title_chip_ptr->AddChildView(std::move(title));
+  title_ = title_chip_->AddChildView(std::make_unique<views::Label>());
+  title_->SetAutoColorReadabilityEnabled(false);
+  title_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+  title_->SetElideBehavior(gfx::FADE_TAIL);
+
+  VisualsChanged();
 }
 
-const TabGroupVisualData* TabGroupHeader::GetGroupVisualData() {
-  return controller_->GetVisualDataForGroup(group_);
+void TabGroupHeader::VisualsChanged() {
+  const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
+  const TabGroupVisualData* data = controller_->GetVisualDataForGroup(group_);
+  title_chip_->SetBackground(views::CreateRoundedRectBackground(
+      data->color(), provider->GetCornerRadiusMetric(views::EMPHASIS_LOW)));
+  title_->SetEnabledColor(color_utils::GetColorWithMaxContrast(data->color()));
+  title_->SetText(data->title());
 }
diff --git a/chrome/browser/ui/views/tabs/tab_group_header.h b/chrome/browser/ui/views/tabs/tab_group_header.h
index d78f523..e6e427d 100644
--- a/chrome/browser/ui/views/tabs/tab_group_header.h
+++ b/chrome/browser/ui/views/tabs/tab_group_header.h
@@ -11,6 +11,10 @@
 class TabController;
 class TabGroupVisualData;
 
+namespace views {
+class Label;
+}
+
 // View for tab group headers in the tab strip, which are tab-shaped markers of
 // group boundaries. There is one header for each group, which is included in
 // the tab strip flow and positioned left of the leftmost tab in the group.
@@ -18,12 +22,16 @@
  public:
   TabGroupHeader(TabController* controller, TabGroupId group);
 
- private:
-  const TabGroupVisualData* GetGroupVisualData();
+  // Updates our visual state according to the TabGroupVisualData for our group.
+  void VisualsChanged();
 
+ private:
   TabController* const controller_;
   const TabGroupId group_;
 
+  views::View* title_chip_;
+  views::Label* title_;
+
   DISALLOW_COPY_AND_ASSIGN(TabGroupHeader);
 };
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 6b37b6f0..181c5c6c 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -328,7 +328,7 @@
                       int model_index,
                       const ui::LocatedEvent& event,
                       const ui::ListSelectionModel& original_selection) {
-    Tabs tabs;
+    std::vector<Tab*> tabs;
     int x = tab->GetMirroredXInView(event.x());
     int y = event.y();
     // Build the set of selected tabs to drag and calculate the offset from the
@@ -586,7 +586,7 @@
   }
 
   std::vector<gfx::Rect> CalculateBoundsForDraggedTabs(
-      const Tabs& tabs) override {
+      const std::vector<Tab*>& tabs) override {
     DCHECK(!tabs.empty());
 
     std::vector<gfx::Rect> bounds;
@@ -611,7 +611,7 @@
     tab_strip_->last_layout_size_ = gfx::Size();
   }
 
-  void StartedDraggingTabs(const Tabs& tabs) override {
+  void StartedDraggingTabs(const std::vector<Tab*>& tabs) override {
     // Let the controller know that the user started dragging tabs.
     tab_strip_->controller_->OnStartedDraggingTabs();
 
@@ -648,7 +648,7 @@
     SetNewTabButtonVisible(true);
   }
 
-  void StoppedDraggingTabs(const Tabs& tabs,
+  void StoppedDraggingTabs(const std::vector<Tab*>& tabs,
                            const std::vector<int>& initial_positions,
                            bool move_only,
                            bool completed) override {
@@ -667,7 +667,7 @@
       tab_strip_->StoppedDraggingTab(tabs[i], &is_first_tab);
   }
 
-  void LayoutDraggedTabsAt(const Tabs& tabs,
+  void LayoutDraggedTabsAt(const std::vector<Tab*>& tabs,
                            Tab* active_tab,
                            const gfx::Point& location,
                            bool initial_drag) override {
@@ -979,7 +979,6 @@
   Tab* tab = new Tab(this);
   AddChildViewAt(tab, view_index);
   const bool pinned = data.pinned;
-  UpdateTabsClosingMap(model_index, 1);
   tabs_.Add(tab, model_index);
   selected_tabs_.IncrementFrom(model_index);
 
@@ -1010,7 +1009,7 @@
     StartInsertTabAnimation(model_index, activeness, pinnedness);
   } else {
     layout_helper_->InsertTabAtNoAnimation(
-        model_index,
+        model_index, tab,
         base::BindOnce(&TabStrip::OnTabCloseAnimationCompleted,
                        base::Unretained(this), base::Unretained(tab)),
         activeness, pinnedness);
@@ -1139,7 +1138,7 @@
                              UpdateIdealBoundsForPinnedTabs(nullptr), old_x);
   }
 
-  layout_helper_->RemoveTabAt(model_index);
+  layout_helper_->CloseTabAt(model_index);
   UpdateIdealBounds();
   AnimateToIdealBounds();
 
@@ -1247,6 +1246,12 @@
   AnimateToIdealBounds();
 }
 
+void TabStrip::GroupVisualsChanged(TabGroupId group) {
+  group_headers_[group]->VisualsChanged();
+  for (int i : controller_->ListTabsInGroup(group))
+    tabs_.view_at(i)->GroupColorChanged();
+}
+
 bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
   // Detached tabs should always be invisible (as they close).
   if (tab->detached())
@@ -1506,7 +1511,14 @@
     // If the tab is already closing, close the next tab. We do this so that the
     // user can rapidly close tabs by clicking the close button and not have
     // the animations interfere with that.
-    model_index = FindClosingTab(tab).first->first;
+    std::vector<Tab*> all_tabs = layout_helper_->GetTabs();
+    auto it = std::find(all_tabs.begin(), all_tabs.end(), tab);
+    while (it < all_tabs.end() && (*it)->closing()) {
+      it++;
+    }
+
+    model_index =
+        it != all_tabs.end() ? GetModelIndexOfTab(*it) : TabStripModel::kNoTab;
   }
 
   if (!IsValidModelIndex(model_index))
@@ -1875,13 +1887,12 @@
   // This is used to log to UMA. NO EARLY RETURNS!
   base::ElapsedTimer paint_timer;
 
-  // The view order doesn't match the paint order (tabs_ contains the tab
-  // ordering). Additionally we need to paint the tabs that are closing in
-  // |tabs_closing_map_|.
+  // The view order doesn't match the paint order (layout_helper_ contains the
+  // view ordering).
   bool is_dragging = false;
   Tab* active_tab = nullptr;
-  Tabs tabs_dragging;
-  Tabs selected_and_hovered_tabs;
+  std::vector<Tab*> tabs_dragging;
+  std::vector<Tab*> selected_and_hovered_tabs;
 
   // When background tab shapes are visible, as for hovered or selected tabs,
   // the paint order must be handled carefully to avoid Z-order errors, so
@@ -1895,18 +1906,11 @@
     }
   };
 
-  const auto paint_closing_tabs = [=](int index) {
-    if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
-      return;
-    for (Tab* tab : base::Reversed(tabs_closing_map_[index]))
-      paint_or_add_to_tabs(tab);
-  };
-
-  paint_closing_tabs(tab_count());
+  std::vector<Tab*> all_tabs = layout_helper_->GetTabs();
 
   int active_tab_index = -1;
-  for (int i = tab_count() - 1; i >= 0; --i) {
-    Tab* tab = tab_at(i);
+  for (int i = all_tabs.size() - 1; i >= 0; --i) {
+    Tab* tab = all_tabs[i];
     if (tab->dragging() && !stacked_layout_) {
       is_dragging = true;
       if (tab->IsActive()) {
@@ -1921,18 +1925,17 @@
     } else if (!stacked_layout_) {
       paint_or_add_to_tabs(tab);
     }
-    paint_closing_tabs(i);
   }
 
   // Draw from the left and then the right if we're in touch mode.
   if (stacked_layout_ && active_tab_index >= 0) {
     for (int i = 0; i < active_tab_index; ++i) {
-      Tab* tab = tab_at(i);
+      Tab* tab = all_tabs[i];
       tab->Paint(paint_info);
     }
 
-    for (int i = tab_count() - 1; i > active_tab_index; --i) {
-      Tab* tab = tab_at(i);
+    for (int i = all_tabs.size() - 1; i > active_tab_index; --i) {
+      Tab* tab = all_tabs[i];
       tab->Paint(paint_info);
     }
   }
@@ -2139,7 +2142,7 @@
     TabAnimationState::TabPinnedness pinnedness) {
   if (!bounds_animator_.IsAnimating() && !in_tab_close_) {
     layout_helper_->InsertTabAt(
-        model_index,
+        model_index, tab_at(model_index),
         base::BindOnce(&TabStrip::OnTabCloseAnimationCompleted,
                        base::Unretained(this),
                        base::Unretained(tab_at(model_index))),
@@ -2148,7 +2151,7 @@
     // TODO(958173): Delete this branch once |TabStripLayoutHelper::animator_|
     // has taken over all animation responsibilities.
     layout_helper_->InsertTabAtNoAnimation(
-        model_index,
+        model_index, tab_at(model_index),
         base::BindOnce(&TabStrip::OnTabCloseAnimationCompleted,
                        base::Unretained(this),
                        base::Unretained(tab_at(model_index))),
@@ -2257,7 +2260,13 @@
 }
 
 void TabStrip::CompleteAnimationAndLayout() {
-  layout_helper_->CompleteAnimations();
+  // If |bounds_animator_| is running, it owns destroying tabs when close
+  // animations complete.  Otherwise, |layout_helper_| does.
+  if (bounds_animator_.IsAnimating()) {
+    layout_helper_->CompleteAnimationsWithoutDestroyingTabs();
+  } else {
+    layout_helper_->CompleteAnimations();
+  }
   LayoutToCurrentBounds();
 
   UpdateIdealBounds();
@@ -2299,14 +2308,8 @@
   // we could e.g. binary-search for the changeover point.  But since we have to
   // iterate through all the tabs to call SetVisible() anyway, it doesn't seem
   // worth it.
-  for (int i = 0; i < tab_count(); ++i) {
-    Tab* tab = tab_at(i);
+  for (Tab* tab : layout_helper_->GetTabs())
     tab->SetVisible(ShouldTabBeVisible(tab));
-  }
-  for (const auto& closing_tab : tabs_closing_map_) {
-    for (Tab* tab : closing_tab.second)
-      tab->SetVisible(ShouldTabBeVisible(tab));
-  }
 }
 
 void TabStrip::UpdateAccessibleTabIndices() {
@@ -2353,10 +2356,8 @@
 
   UpdateHoverCard(nullptr);
 
-  // We still need to paint the tab until we actually remove it. Put it
-  // in tabs_closing_map_ so we can find it.
-  tabs_closing_map_[index].push_back(closing_tab);
-  UpdateTabsClosingMap(index + 1, -1);
+  // We still need to keep the tab alive until the remove tab animation
+  // completes. Defer destroying it until then.
   tabs_.Remove(index);
   selected_tabs_.DecrementFrom(index);
 
@@ -2368,10 +2369,7 @@
   DCHECK(tab->closing());
 
   std::unique_ptr<Tab> deleter(tab);
-  FindClosingTabResult res(FindClosingTab(tab));
-  res.first->second.erase(res.second);
-  if (res.first->second.empty())
-    tabs_closing_map_.erase(res.first);
+  layout_helper_->OnTabDestroyed(tab);
 
   // Send the Container a message to simulate a mouse moved event at the current
   // mouse position. This tickles the Tab the mouse is currently over to show
@@ -2393,29 +2391,6 @@
   // we do in OnTabCloseAnimationCompleted.
 }
 
-void TabStrip::UpdateTabsClosingMap(int index, int delta) {
-  if (tabs_closing_map_.empty())
-    return;
-
-  if (delta == -1 &&
-      tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
-      tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
-    const Tabs& tabs(tabs_closing_map_[index]);
-    tabs_closing_map_[index - 1].insert(tabs_closing_map_[index - 1].end(),
-                                        tabs.begin(), tabs.end());
-  }
-  TabsClosingMap updated_map;
-  for (auto& i : tabs_closing_map_) {
-    if (i.first > index)
-      updated_map[i.first + delta] = i.second;
-    else if (i.first < index)
-      updated_map[i.first] = i.second;
-  }
-  if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
-    updated_map[index + delta] = tabs_closing_map_[index];
-  tabs_closing_map_.swap(updated_map);
-}
-
 void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
   int tab_data_index = GetModelIndexOfTab(tab);
   if (tab_data_index == -1) {
@@ -2439,17 +2414,6 @@
       std::make_unique<ResetDraggingStateDelegate>(this, tab));
 }
 
-TabStrip::FindClosingTabResult TabStrip::FindClosingTab(const Tab* tab) {
-  DCHECK(tab->closing());
-  for (auto i = tabs_closing_map_.begin(); i != tabs_closing_map_.end(); ++i) {
-    auto j = std::find(i->second.begin(), i->second.end(), tab);
-    if (j != i->second.end())
-      return FindClosingTabResult(i, j);
-  }
-  NOTREACHED();
-  return FindClosingTabResult(tabs_closing_map_.end(), Tabs::iterator());
-}
-
 void TabStrip::UpdateStackedLayoutFromMouseEvent(views::View* source,
                                                  const ui::MouseEvent& event) {
   if (!adjust_layout_)
@@ -2806,20 +2770,24 @@
 }
 
 Tab* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
+  // Check all tabs, even closing tabs. Mouse events need to reach closing tabs
+  // for users to be able to rapidly middle-click close several tabs.
+  std::vector<Tab*> all_tabs = layout_helper_->GetTabs();
+
   // The display order doesn't necessarily match the child order, so we iterate
   // in display order.
-  for (int i = 0; i < tab_count(); ++i) {
+  for (size_t i = 0; i < all_tabs.size(); ++i) {
     // If we don't first exclude points outside the current tab, the code below
     // will return the wrong tab if the next tab is selected, the following tab
     // is active, and |point| is in the overlap region between the two.
-    Tab* tab = tab_at(i);
+    Tab* tab = all_tabs[i];
     if (!IsPointInTab(tab, point))
       continue;
 
     // Selected tabs render atop unselected ones, and active tabs render atop
     // everything.  Check whether the next tab renders atop this one and |point|
     // is in the overlap region.
-    Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : nullptr;
+    Tab* next_tab = i < (all_tabs.size() - 1) ? all_tabs[i + 1] : nullptr;
     if (next_tab &&
         (next_tab->IsActive() ||
          (next_tab->IsSelected() && !tab->IsSelected())) &&
@@ -2830,17 +2798,6 @@
     return tab;
   }
 
-  // Also check closing tabs.  Mouse events need to reach closing tabs for users
-  // to be able to rapidly middle-click close several tabs. Since closing tabs
-  // are never selected or active, the check here can be simpler than the one
-  // above.
-  for (const auto& index_and_tabs : tabs_closing_map_) {
-    for (Tab* tab : index_and_tabs.second) {
-      if (IsPointInTab(tab, point))
-        return tab;
-    }
-  }
-
   return nullptr;
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index a052adc..8135190 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -165,6 +165,11 @@
                       base::Optional<TabGroupId> old_group,
                       base::Optional<TabGroupId> new_group);
 
+  // Updates the group's tabs and header when its associated TabGroupVisualData
+  // changes. This should be called when the result of
+  // |TabStripController::GetVisualDataForGroup(group)| changes.
+  void GroupVisualsChanged(TabGroupId group);
+
   // Returns true if the tab is not partly or fully clipped (due to overflow),
   // and the tab couldn't become partly clipped due to changing the selected tab
   // (for example, if currently the strip has the last tab selected, and
@@ -298,11 +303,6 @@
   void HandleDragExited() override;
 
  private:
-  using Tabs = std::vector<Tab*>;
-  using TabsClosingMap = std::map<int, Tabs>;
-  using FindClosingTabResult =
-      std::pair<TabsClosingMap::iterator, Tabs::iterator>;
-
   class RemoveTabDelegate;
   class TabDragContextImpl;
 
@@ -403,8 +403,7 @@
   // the actual last tab unless the strip is in the overflow node_data.
   const Tab* GetLastVisibleTab() const;
 
-  // Adds the tab at |index| to |tabs_closing_map_| and removes the tab from
-  // |tabs_|.
+  // Removes the tab at |index| from |tabs_|.
   void RemoveTabFromViewModel(int index);
 
   // Cleans up the Tab from the TabStrip. This is called from the tab animation
@@ -415,18 +414,10 @@
   // from the tab animation code and is not a general-purpose method.
   void OnGroupCloseAnimationCompleted(TabGroupId group);
 
-  // Adjusts the indices of all tabs in |tabs_closing_map_| whose index is
-  // >= |index| to have a new index of |index + delta|.
-  void UpdateTabsClosingMap(int index, int delta);
-
   // Invoked from StoppedDraggingTabs to cleanup |tab|. If |tab| is known
   // |is_first_tab| is set to true.
   void StoppedDraggingTab(Tab* tab, bool* is_first_tab);
 
-  // Finds |tab| in the |tab_closing_map_| and returns a pair of iterators
-  // indicating precisely where it is.
-  FindClosingTabResult FindClosingTab(const Tab* tab);
-
   // Invoked when a mouse event occurs over |source|. Potentially switches the
   // |stacked_layout_|.
   void UpdateStackedLayoutFromMouseEvent(views::View* source,
@@ -563,12 +554,9 @@
   // There is a one-to-one mapping between each of the tabs in the
   // TabStripController (TabStripModel) and |tabs_|. Because we animate tab
   // removal there exists a period of time where a tab is displayed but not in
-  // the model. When this occurs the tab is removed from |tabs_| and placed in
-  // |tabs_closing_map_|. When the animation completes the tab is removed from
-  // |tabs_closing_map_|. The painting code ensures both sets of tabs are
-  // painted, and the event handling code ensures only tabs in |tabs_| are used.
+  // the model. When this occurs the tab is removed from |tabs_|, but remains
+  // in |layout_helper_| until the remove animation completes.
   views::ViewModelT<Tab> tabs_;
-  TabsClosingMap tabs_closing_map_;
 
   // Map associating each group to its TabGroupHeader instance.
   std::map<TabGroupId, std::unique_ptr<TabGroupHeader>> group_headers_;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
index bf938b4..75420fa 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
@@ -54,11 +54,13 @@
 
 struct TabStripLayoutHelper::TabSlot {
   static TabStripLayoutHelper::TabSlot CreateForTab(
+      Tab* tab,
       TabAnimationState::TabOpenness openness,
       TabAnimationState::TabPinnedness pinned,
       base::OnceClosure removed_callback) {
     TabStripLayoutHelper::TabSlot slot;
     slot.type = ViewType::kTab;
+    slot.tab = tab;
     TabAnimationState initial_state = TabAnimationState::ForIdealTabState(
         openness, pinned, TabAnimationState::TabActiveness::kInactive, 0);
     slot.animation = std::make_unique<TabAnimation>(
@@ -86,9 +88,15 @@
     return group.value();
   }
 
+  Tab* GetTab() const {
+    DCHECK(tab.has_value());
+    return tab.value();
+  }
+
   ViewType type;
   std::unique_ptr<TabAnimation> animation;
   base::Optional<TabGroupId> group;
+  base::Optional<Tab*> tab;
 };
 
 TabStripLayoutHelper::TabStripLayoutHelper(
@@ -107,6 +115,17 @@
 
 TabStripLayoutHelper::~TabStripLayoutHelper() = default;
 
+std::vector<Tab*> TabStripLayoutHelper::GetTabs() {
+  std::vector<Tab*> tabs;
+  for (const TabSlot& slot : slots_) {
+    if (slot.type == ViewType::kTab) {
+      tabs.push_back(slot.GetTab());
+    }
+  }
+
+  return tabs;
+}
+
 bool TabStripLayoutHelper::IsAnimating() const {
   return animation_timer_.IsRunning();
 }
@@ -123,32 +142,48 @@
 
 void TabStripLayoutHelper::InsertTabAtNoAnimation(
     int model_index,
+    Tab* tab,
     base::OnceClosure tab_removed_callback,
     TabAnimationState::TabActiveness active,
     TabAnimationState::TabPinnedness pinned) {
   const int slot_index = GetSlotIndexForTabModelIndex(model_index);
-  slots_.insert(slots_.begin() + slot_index,
-                TabSlot::CreateForTab(TabAnimationState::TabOpenness::kOpen,
-                                      pinned, std::move(tab_removed_callback)));
+  slots_.insert(
+      slots_.begin() + slot_index,
+      TabSlot::CreateForTab(tab, TabAnimationState::TabOpenness::kOpen, pinned,
+                            std::move(tab_removed_callback)));
 }
 
 void TabStripLayoutHelper::InsertTabAt(
     int model_index,
+    Tab* tab,
     base::OnceClosure tab_removed_callback,
     TabAnimationState::TabActiveness active,
     TabAnimationState::TabPinnedness pinned) {
   const int slot_index = GetSlotIndexForTabModelIndex(model_index);
-  slots_.insert(slots_.begin() + slot_index,
-                TabSlot::CreateForTab(TabAnimationState::TabOpenness::kClosed,
-                                      pinned, std::move(tab_removed_callback)));
+  slots_.insert(
+      slots_.begin() + slot_index,
+      TabSlot::CreateForTab(tab, TabAnimationState::TabOpenness::kClosed,
+                            pinned, std::move(tab_removed_callback)));
   AnimateSlot(slot_index,
               slots_[slot_index].animation->target_state().WithOpenness(
                   TabAnimationState::TabOpenness::kOpen));
 }
 
-void TabStripLayoutHelper::RemoveTabAt(int model_index) {
+void TabStripLayoutHelper::CloseTabAt(int model_index) {
   // TODO(958173): Animate closed.
-  slots_.erase(slots_.begin() + GetSlotIndexForTabModelIndex(model_index));
+  TabAnimation* animation =
+      slots_[GetSlotIndexForTabModelIndex(model_index)].animation.get();
+  animation->AnimateTo(animation->target_state().WithOpenness(
+      TabAnimationState::TabOpenness::kClosed));
+}
+
+void TabStripLayoutHelper::OnTabDestroyed(Tab* tab) {
+  auto it =
+      std::find_if(slots_.begin(), slots_.end(), [tab](const TabSlot& slot) {
+        return slot.type == ViewType::kTab && slot.GetTab() == tab;
+      });
+  DCHECK(it != slots_.end());
+  slots_.erase(it);
 }
 
 void TabStripLayoutHelper::MoveTab(
@@ -254,8 +289,11 @@
     auto pinned = i <= last_pinned_tab_slot_index
                       ? TabAnimationState::TabPinnedness::kPinned
                       : TabAnimationState::TabPinnedness::kUnpinned;
-    ideal_animation_states.push_back(TabAnimationState::ForIdealTabState(
-        TabAnimationState::TabOpenness::kOpen, pinned, active, 0));
+    auto open = slots_[i].animation->target_state().IsFullyClosed()
+                    ? TabAnimationState::TabOpenness::kClosed
+                    : TabAnimationState::TabOpenness::kOpen;
+    ideal_animation_states.push_back(
+        TabAnimationState::ForIdealTabState(open, pinned, active, 0));
   }
 
   const std::vector<gfx::Rect> bounds = CalculateTabBounds(
@@ -271,9 +309,12 @@
     const TabSlot& slot = slots_[i];
     switch (slot.type) {
       case ViewType::kTab:
-        tabs->set_ideal_bounds(current_tab_model_index, bounds[i]);
-        UpdateCachedTabWidth(i, bounds[i].width(), i == active_tab_slot_index);
-        ++current_tab_model_index;
+        if (!slot.animation->target_state().IsFullyClosed()) {
+          tabs->set_ideal_bounds(current_tab_model_index, bounds[i]);
+          UpdateCachedTabWidth(i, bounds[i].width(),
+                               i == active_tab_slot_index);
+          ++current_tab_model_index;
+        }
         break;
       case ViewType::kGroupHeader:
         group_headers[slot.GetGroup()]->SetBoundsRect(bounds[i]);
@@ -314,8 +355,15 @@
   std::vector<gfx::Rect> bounds = CalculateTabBounds(
       GetTabSizeInfo(), GetCurrentTabStates(), available_width);
 
-  // TODO(958173): Assume for now that there are no closing tabs or headers.
-  DCHECK_EQ(bounds.size(), tabs->view_size() + group_headers.size());
+  if (DCHECK_IS_ON()) {
+    int num_closing_tabs = 0;
+    for (Tab* tab : GetTabs()) {
+      if (tab->closing())
+        ++num_closing_tabs;
+    }
+    DCHECK_EQ(bounds.size(),
+              tabs->view_size() + num_closing_tabs + group_headers.size());
+  }
 
   int trailing_x = 0;
 
@@ -325,8 +373,9 @@
   for (size_t i = 0; i < bounds.size(); i++) {
     switch (slots_[i].type) {
       case ViewType::kTab: {
-        if (!tabs->view_at(current_tab_model_index)->dragging()) {
-          tabs->view_at(current_tab_model_index)->SetBoundsRect(bounds[i]);
+        Tab* tab = slots_[i].GetTab();
+        if (!tab->dragging()) {
+          tab->SetBoundsRect(bounds[i]);
           trailing_x = std::max(trailing_x, bounds[i].right());
           // TODO(958173): We shouldn't need to update the cached widths here,
           // since they're also updated in UpdateIdealBounds. However, tests
@@ -335,7 +384,8 @@
               current_tab_model_index, bounds[i].width(),
               current_tab_model_index == active_tab_model_index);
         }
-        ++current_tab_model_index;
+        if (!slots_[i].animation->target_state().IsFullyClosed())
+          ++current_tab_model_index;
         break;
       }
       case ViewType::kGroupHeader: {
@@ -353,7 +403,8 @@
 int TabStripLayoutHelper::GetSlotIndexForTabModelIndex(int model_index) const {
   int current_model_index = 0;
   for (size_t i = 0; i < slots_.size(); i++) {
-    if (slots_[i].type == ViewType::kTab) {
+    if (slots_[i].type == ViewType::kTab &&
+        !slots_[i].animation->target_state().IsFullyClosed()) {
       if (current_model_index == model_index)
         return i;
       ++current_model_index;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
index a97bc45..d00ee96 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
@@ -36,6 +36,10 @@
                        base::RepeatingClosure on_animation_progressed);
   ~TabStripLayoutHelper();
 
+  // Returns a vector of all tabs in the strip, including both closing tabs
+  // and tabs still in the model.
+  std::vector<Tab*> GetTabs();
+
   // Returns whether any animations for tabs or group headers are in progress.
   bool IsAnimating() const;
 
@@ -50,6 +54,7 @@
   // Inserts a new tab at |index|, without animation. |tab_removed_callback|
   // will be invoked if the tab is removed at the end of a remove animation.
   void InsertTabAtNoAnimation(int model_index,
+                              Tab* tab,
                               base::OnceClosure tab_removed_callback,
                               TabAnimationState::TabActiveness active,
                               TabAnimationState::TabPinnedness pinned);
@@ -57,14 +62,18 @@
   // Inserts a new tab at |index|, with animation. |tab_removed_callback| will
   // be invoked if the tab is removed at the end of a remove animation.
   void InsertTabAt(int model_index,
+                   Tab* tab,
                    base::OnceClosure tab_removed_callback,
                    TabAnimationState::TabActiveness active,
                    TabAnimationState::TabPinnedness pinned);
 
-  // Removes the tab at |index|. TODO(958173): This should invoke the associated
-  // |tab_removed_callback| but currently does not, as it would duplicate
-  // TabStrip::RemoveTabDelegate::AnimationEnded.
-  void RemoveTabAt(int model_index);
+  // Marks the tab at |model_index| as closing. Invoked when the remove
+  // animation begins and the tab is removed from the model.
+  void CloseTabAt(int model_index);
+
+  // Invoked when |tab| has been destroyed by TabStrip (i.e. the remove
+  // animation has completed).
+  void OnTabDestroyed(Tab* tab);
 
   // Moves the tab at |prev_index| with group |group_at_prev_index| to
   // |new_index|. Also updates the group header's location if necessary.
@@ -90,9 +99,11 @@
   // Finishes all in-progress animations.
   void CompleteAnimations();
 
-  // TODO(958173): Temporary method. Like CompleteAnimations, but does not call
-  // any associated |tab_removed_callback| or |header_removed_callback|. See
-  // comment on TabStripAnimator::CompleteAnimationsWithoutDestroyingTabs.
+  // TODO(958173): Temporary method that completes running animations
+  // without invoking the callback to destroy removed tabs. Use to hand
+  // off animation (and removed tab destruction) responsibilities from
+  // this animator to elsewhere without teleporting tabs or destroying
+  // the same tab more than once.
   void CompleteAnimationsWithoutDestroyingTabs();
 
   // Generates and sets the ideal bounds for the views in |tabs| and
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 606e215..5b1b940 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -108,12 +108,14 @@
 
 // Gets the display mode for a given browser.
 ToolbarView::DisplayMode GetDisplayMode(Browser* browser) {
-  if (browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP))
-    return ToolbarView::DisplayMode::NORMAL;
-
+  // Checked in this order because even tabbed PWAs use the CUSTOM_TAB
+  // display mode.
   if (web_app::AppBrowserController::IsForWebAppBrowser(browser))
     return ToolbarView::DisplayMode::CUSTOM_TAB;
 
+  if (browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP))
+    return ToolbarView::DisplayMode::NORMAL;
+
   return ToolbarView::DisplayMode::LOCATION;
 }
 
@@ -784,9 +786,23 @@
 
 // ToolbarButtonProvider:
 BrowserActionsContainer* ToolbarView::GetBrowserActionsContainer() {
+  CHECK(!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
   return browser_actions_;
 }
 
+ToolbarActionView* ToolbarView::GetToolbarActionViewForId(
+    const std::string& id) {
+  if (extensions_container_)
+    return extensions_container_->GetViewForId(id);
+  return GetBrowserActionsContainer()->GetViewForId(id);
+}
+
+views::View* ToolbarView::GetDefaultExtensionDialogAnchorView() {
+  if (extensions_container_)
+    return extensions_container_;
+  return GetAppMenuButton();
+}
+
 OmniboxPageActionIconContainerView*
 ToolbarView::GetOmniboxPageActionIconContainerView() {
   return location_bar_->omnibox_page_action_icon_container_view();
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h
index 4d3dcb65..48399c3 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -227,6 +227,8 @@
 
   // ToolbarButtonProvider:
   BrowserActionsContainer* GetBrowserActionsContainer() override;
+  ToolbarActionView* GetToolbarActionViewForId(const std::string& id) override;
+  views::View* GetDefaultExtensionDialogAnchorView() override;
   OmniboxPageActionIconContainerView* GetOmniboxPageActionIconContainerView()
       override;
   AppMenuButton* GetAppMenuButton() override;
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.cc b/chrome/browser/ui/web_applications/app_browser_controller.cc
index 67018a1..a975ce5e 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/app_browser_controller.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
 #include "chrome/browser/ui/manifest_web_app_browser_controller.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/web_applications/web_app_browser_controller.h"
 #include "chrome/browser/web_applications/components/app_registrar.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
@@ -88,6 +89,13 @@
   return false;
 }
 
+bool AppBrowserController::HasTabStrip() const {
+  // Show tabs for Terminal only.
+  // TODO(crbug.com/846546): Generalise this as a SystemWebApp capability.
+  return GetAppIdForSystemWebApp(browser()->profile(),
+                                 SystemAppType::TERMINAL) == GetAppId();
+}
+
 bool AppBrowserController::IsInstalled() const {
   return false;
 }
@@ -176,21 +184,21 @@
     for (const auto& contents : change.GetRemove()->contents)
       OnTabRemoved(contents.contents);
   }
+  if (selection.active_tab_changed()) {
+    content::WebContentsObserver::Observe(selection.new_contents);
+    DidChangeThemeColor(GetThemeColor());
+  }
+
+  // WebContents should be null when the last tab is closed.
+  DCHECK_EQ(web_contents() == nullptr, tab_strip_model->empty());
 }
 
 void AppBrowserController::OnTabInserted(content::WebContents* contents) {
-  DCHECK(!web_contents()) << " App windows are single tabbed only";
-  content::WebContentsObserver::Observe(contents);
-  DidChangeThemeColor(GetThemeColor());
-
-  if (!contents->GetVisibleURL().is_empty())
+  if (!contents->GetVisibleURL().is_empty() && initial_url_.is_empty())
     SetInitialURL(contents->GetVisibleURL());
 }
 
-void AppBrowserController::OnTabRemoved(content::WebContents* contents) {
-  DCHECK_EQ(contents, web_contents());
-  content::WebContentsObserver::Observe(nullptr);
-}
+void AppBrowserController::OnTabRemoved(content::WebContents* contents) {}
 
 void AppBrowserController::SetInitialURL(const GURL& initial_url) {
   DCHECK(initial_url_.is_empty());
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.h b/chrome/browser/ui/web_applications/app_browser_controller.h
index 0f8efd1..e24c9bb 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller.h
+++ b/chrome/browser/ui/web_applications/app_browser_controller.h
@@ -57,6 +57,9 @@
   // Whether the custom tab bar should be visible.
   virtual bool ShouldShowCustomTabBar() const = 0;
 
+  // Whether the browser should include the tab strip.
+  virtual bool HasTabStrip() const;
+
   // Returns true if the hosted app buttons should be shown in the frame for
   // this BrowserView.
   virtual bool ShouldShowHostedAppButtonContainer() const = 0;
diff --git a/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc b/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
new file mode 100644
index 0000000..de011a8
--- /dev/null
+++ b/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
@@ -0,0 +1,130 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/web_applications/app_browser_controller.h"
+
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/installable/installable_metrics.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
+#include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
+#include "chrome/browser/web_applications/components/install_manager.h"
+#include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/browser/web_applications/system_web_app_manager.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/web_application_info.h"
+#include "extensions/browser/extension_registry.h"
+
+namespace web_app {
+
+class AppBrowserControllerBrowserTest
+    : public extensions::ExtensionBrowserTest {
+ public:
+  AppBrowserControllerBrowserTest() {
+    scoped_feature_list_.InitWithFeatures({}, {features::kTerminalSystemApp});
+  }
+
+  ~AppBrowserControllerBrowserTest() override {}
+
+  void SetUp() override { extensions::ExtensionBrowserTest::SetUp(); }
+
+ protected:
+  void InstallAndLaunchTerminalApp() {
+    GURL app_url = GetAppURL();
+    auto web_app_info = std::make_unique<WebApplicationInfo>();
+    web_app_info->app_url = app_url;
+    web_app_info->scope = app_url.GetWithoutFilename();
+    web_app_info->open_as_window = true;
+    AppId app_id = InstallWebApp(std::move(web_app_info));
+
+    auto* provider = WebAppProvider::Get(profile());
+    provider->system_web_app_manager().SetSystemAppsForTesting(
+        {{SystemAppType::TERMINAL, {app_url, app_id}}});
+    web_app::ExternallyInstalledWebAppPrefs(profile()->GetPrefs())
+        .Insert(app_url, app_id,
+                web_app::ExternalInstallSource::kInternalDefault);
+    ASSERT_EQ(web_app::GetAppIdForSystemWebApp(browser()->profile(),
+                                               SystemAppType::TERMINAL),
+              app_id);
+
+    const extensions::Extension* extension =
+        extensions::ExtensionRegistry::Get(profile())->GetInstalledExtension(
+            app_id);
+    ASSERT_TRUE(extension);
+
+    app_browser_ = LaunchAppBrowser(extension);
+
+    ASSERT_TRUE(app_browser_);
+    ASSERT_NE(app_browser_, browser());
+  }
+
+  std::string InstallWebApp(
+      std::unique_ptr<WebApplicationInfo>&& web_app_info) {
+    std::string app_id;
+    base::RunLoop run_loop;
+    auto* provider = WebAppProvider::Get(profile());
+    DCHECK(provider);
+    provider->install_manager().InstallWebAppFromInfo(
+        std::move(web_app_info),
+        /*no_network_install=*/true, WebappInstallSource::OMNIBOX_INSTALL_ICON,
+        base::BindLambdaForTesting(
+            [&](const std::string& installed_app_id, InstallResultCode code) {
+              EXPECT_EQ(InstallResultCode::kSuccess, code);
+              app_id = installed_app_id;
+              run_loop.Quit();
+            }));
+
+    run_loop.Run();
+    return app_id;
+  }
+
+  GURL GetAppURL() {
+    return embedded_test_server()->GetURL("app.com", "/simple.html");
+  }
+
+  GURL GetActiveTabURL() {
+    return app_browser_->tab_strip_model()
+        ->GetActiveWebContents()
+        ->GetVisibleURL();
+  }
+
+  Browser* app_browser_ = nullptr;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(AppBrowserControllerBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(AppBrowserControllerBrowserTest, SomeTest) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  InstallAndLaunchTerminalApp();
+
+  EXPECT_TRUE(app_browser_->SupportsWindowFeature(Browser::FEATURE_TABSTRIP));
+
+  // Check URL of tab1.
+  EXPECT_EQ(GetActiveTabURL(), GetAppURL());
+  // Create tab2, check URL, number of tabs.
+  chrome::NewTab(app_browser_);
+  EXPECT_EQ(app_browser_->tab_strip_model()->count(), 2);
+  EXPECT_EQ(GetActiveTabURL(), GURL("chrome://newtab/"));
+  // Switch to tab1, check URL.
+  chrome::SelectNextTab(app_browser_);
+  EXPECT_EQ(app_browser_->tab_strip_model()->count(), 2);
+  EXPECT_EQ(GetActiveTabURL(), GetAppURL());
+  // Switch to tab2, check URL.
+  chrome::SelectNextTab(app_browser_);
+  EXPECT_EQ(app_browser_->tab_strip_model()->count(), 2);
+  EXPECT_EQ(GetActiveTabURL(), GURL("chrome://newtab/"));
+  // Close tab2, check number of tabs.
+  chrome::CloseTab(app_browser_);
+  EXPECT_EQ(app_browser_->tab_strip_model()->count(), 1);
+  EXPECT_EQ(GetActiveTabURL(), GetAppURL());
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.cc b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
similarity index 99%
rename from chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.cc
rename to chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
index 9cb7cc4..814f929 100644
--- a/chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.cc
+++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.h"
+#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 
 #include <string>
 #include <utility>
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.h b/chrome/browser/ui/web_applications/system_web_app_ui_utils.h
similarity index 96%
rename from chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.h
rename to chrome/browser/ui/web_applications/system_web_app_ui_utils.h
index 83f7b41..d7fcac6 100644
--- a/chrome/browser/ui/web_applications/system_web_app_ui_utils_chromeos.h
+++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEB_APPLICATIONS_SYSTEM_WEB_APP_UI_UTILS_CHROMEOS_H_
-#define CHROME_BROWSER_UI_WEB_APPLICATIONS_SYSTEM_WEB_APP_UI_UTILS_CHROMEOS_H_
+#ifndef CHROME_BROWSER_UI_WEB_APPLICATIONS_SYSTEM_WEB_APP_UI_UTILS_H_
+#define CHROME_BROWSER_UI_WEB_APPLICATIONS_SYSTEM_WEB_APP_UI_UTILS_H_
 
 #include <utility>
 
@@ -35,4 +35,4 @@
 
 }  // namespace web_app
 
-#endif  // CHROME_BROWSER_UI_WEB_APPLICATIONS_SYSTEM_WEB_APP_UI_UTILS_CHROMEOS_H_
+#endif  // CHROME_BROWSER_UI_WEB_APPLICATIONS_SYSTEM_WEB_APP_UI_UTILS_H_
diff --git a/chrome/browser/ui/webui/chromeos/add_supervision/BUILD.gn b/chrome/browser/ui/webui/chromeos/add_supervision/BUILD.gn
index 2829e6d..54b63f6b 100644
--- a/chrome/browser/ui/webui/chromeos/add_supervision/BUILD.gn
+++ b/chrome/browser/ui/webui/chromeos/add_supervision/BUILD.gn
@@ -8,7 +8,4 @@
   sources = [
     "add_supervision.mojom",
   ]
-
-  # TODO(https://crbug.com/968369): Change to use new names.
-  use_old_js_lite_bindings_names = true
 }
diff --git a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
index 5326b0b..12aa9301 100644
--- a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
@@ -8,6 +8,7 @@
 
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/event_rewriter_controller.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/shell.h"
 #include "base/bind.h"
@@ -32,7 +33,6 @@
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/webui/chromeos/login/demo_setup_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
@@ -98,8 +98,7 @@
       base::Bind(&CoreOobeHandler::OnAccessibilityStatusChanged,
                  base::Unretained(this)));
 
-  TabletModeClient* tablet_mode_client = TabletModeClient::Get();
-  tablet_mode_client->AddObserver(this);
+  ash::TabletMode::Get()->AddObserver(this);
 
   // |connector| may be null in tests.
   auto* connector = content::GetSystemConnector();
@@ -112,7 +111,10 @@
 
 CoreOobeHandler::~CoreOobeHandler() {
   OobeConfiguration::Get()->RemoveObserver(this);
-  TabletModeClient::Get()->RemoveObserver(this);
+
+  // Ash may be released before us.
+  if (ash::TabletMode::Get())
+    ash::TabletMode::Get()->RemoveObserver(this);
 }
 
 void CoreOobeHandler::DeclareLocalizedValues(
@@ -178,7 +180,7 @@
 
 void CoreOobeHandler::GetAdditionalParameters(base::DictionaryValue* dict) {
   dict->SetKey("isInTabletMode",
-               base::Value(TabletModeClient::Get()->tablet_mode_enabled()));
+               base::Value(ash::TabletMode::Get()->InTabletMode()));
   dict->SetKey("isDemoModeEnabled",
                base::Value(DemoSetupController::IsDemoModeAllowed()));
   dict->SetKey("showTechnologyBadge",
@@ -534,8 +536,12 @@
   SetVirtualKeyboardShown(is_keyboard_shown);
 }
 
-void CoreOobeHandler::OnTabletModeToggled(bool enabled) {
-  CallJS("cr.ui.Oobe.setTabletModeState", enabled);
+void CoreOobeHandler::OnTabletModeStarted() {
+  CallJS("cr.ui.Oobe.setTabletModeState", true);
+}
+
+void CoreOobeHandler::OnTabletModeEnded() {
+  CallJS("cr.ui.Oobe.setTabletModeState", false);
 }
 
 void CoreOobeHandler::UpdateClientAreaSize() {
diff --git a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h
index cb7db18..dd2d01d 100644
--- a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "ash/public/interfaces/cros_display_config.mojom.h"
 #include "base/callback.h"
 #include "base/macros.h"
@@ -21,7 +22,6 @@
 #include "chrome/browser/chromeos/login/oobe_configuration.h"
 #include "chrome/browser/chromeos/login/version_info_updater.h"
 #include "chrome/browser/chromeos/tpm_firmware_update.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "chrome/browser/ui/webui/chromeos/login/base_webui_handler.h"
 #include "ui/events/event_source.h"
 
@@ -72,7 +72,7 @@
                         public VersionInfoUpdater::Delegate,
                         public CoreOobeView,
                         public ui::EventSource,
-                        public TabletModeClientObserver,
+                        public ash::TabletModeObserver,
                         public OobeConfiguration::Observer {
  public:
   explicit CoreOobeHandler(JSCallsContainer* js_calls_container);
@@ -146,8 +146,9 @@
   void StopDemoModeDetection() override;
   void UpdateKeyboardState() override;
 
-  // TabletModeClientObserver:
-  void OnTabletModeToggled(bool enabled) override;
+  // ash::TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
 
   // OobeConfiguration::Observer:
   void OnOobeConfigurationChanged() override;
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index eca0146a..ab2a3c4 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "ash/public/cpp/login_constants.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/wallpaper_types.h"
 #include "ash/public/interfaces/tray_action.mojom.h"
 #include "base/bind.h"
@@ -64,7 +65,6 @@
 #include "chrome/browser/profiles/profile_metrics.h"
 #include "chrome/browser/ui/ash/ime_controller_client.h"
 #include "chrome/browser/ui/ash/session_controller_client_impl.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/browser/ui/webui/chromeos/internet_detail_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h"
@@ -266,9 +266,9 @@
           base::Bind(&SigninScreenHandler::OnAllowedInputMethodsChanged,
                      base::Unretained(this)));
 
-  TabletModeClient* tablet_mode_client = TabletModeClient::Get();
-  tablet_mode_client->AddObserver(this);
-  OnTabletModeToggled(tablet_mode_client->tablet_mode_enabled());
+  ash::TabletMode* tablet_mode = ash::TabletMode::Get();
+  tablet_mode->AddObserver(this);
+  OnTabletModeToggled(tablet_mode->InTabletMode());
 
   WallpaperControllerClient::Get()->AddObserver(this);
 }
@@ -276,7 +276,9 @@
 SigninScreenHandler::~SigninScreenHandler() {
   if (auto* wallpaper_controller_client = WallpaperControllerClient::Get())
     wallpaper_controller_client->RemoveObserver(this);
-  TabletModeClient::Get()->RemoveObserver(this);
+  // Ash maybe released before us.
+  if (ash::TabletMode::Get())
+    ash::TabletMode::Get()->RemoveObserver(this);
   OobeUI* oobe_ui = GetOobeUI();
   if (oobe_ui && oobe_ui_observer_added_)
     oobe_ui->RemoveObserver(this);
@@ -1016,6 +1018,14 @@
   }
 }
 
+void SigninScreenHandler::OnTabletModeStarted() {
+  OnTabletModeToggled(true);
+}
+
+void SigninScreenHandler::OnTabletModeEnded() {
+  OnTabletModeToggled(false);
+}
+
 void SigninScreenHandler::OnTabletModeToggled(bool enabled) {
   CallJS("login.AccountPickerScreen.setTabletModeState", enabled);
 }
@@ -1367,7 +1377,7 @@
 
 void SigninScreenHandler::HandleGetTabletModeState() {
   CallJS("login.AccountPickerScreen.setTabletModeState",
-         TabletModeClient::Get()->tablet_mode_enabled());
+         ash::TabletMode::Get()->InTabletMode());
 }
 
 void SigninScreenHandler::HandleGetDemoModeState() {
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
index 48988d3..7447adf 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -10,6 +10,7 @@
 #include <set>
 #include <string>
 
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "ash/public/cpp/wallpaper_controller_observer.h"
 #include "base/callback.h"
 #include "base/compiler_specific.h"
@@ -21,7 +22,6 @@
 #include "chrome/browser/chromeos/login/signin_specifics.h"
 #include "chrome/browser/chromeos/login/ui/login_display.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
-#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
 #include "chrome/browser/ui/webui/chromeos/login/base_webui_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
@@ -183,7 +183,7 @@
       public NetworkStateInformer::NetworkStateInformerObserver,
       public PowerManagerClient::Observer,
       public input_method::ImeKeyboard::Observer,
-      public TabletModeClientObserver,
+      public ash::TabletModeObserver,
       public OobeUI::Observer,
       public ash::WallpaperControllerObserver {
  public:
@@ -302,8 +302,11 @@
   // PowerManagerClient::Observer implementation:
   void SuspendDone(const base::TimeDelta& sleep_duration) override;
 
-  // TabletModeClientObserver:
-  void OnTabletModeToggled(bool enabled) override;
+  // ash::TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
+
+  void OnTabletModeToggled(bool enabled);
 
   // Restore input focus to current user pod.
   void RefocusCurrentPod();
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
index 2543d329..d3ca7d1 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
@@ -20,6 +20,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/crx_installer.h"
@@ -36,7 +37,6 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/webui/extensions/extension_basic_info.h"
@@ -526,11 +526,11 @@
     AppLaunchParams params(
         profile, extension_id,
         disposition == WindowOpenDisposition::NEW_WINDOW
-            ? extensions::LaunchContainer::kLaunchContainerWindow
-            : extensions::LaunchContainer::kLaunchContainerTab,
-        disposition, extensions::AppLaunchSource::kSourceNewTabPage);
+            ? apps::mojom::LaunchContainer::kLaunchContainerWindow
+            : apps::mojom::LaunchContainer::kLaunchContainerTab,
+        disposition, apps::mojom::AppLaunchSource::kSourceNewTabPage);
     params.override_url = override_url;
-    OpenApplication(params);
+    apps::LaunchService::Get(profile)->OpenApplication(params);
   } else {
     // To give a more "launchy" experience when using the NTP launcher, we close
     // it automatically.
@@ -546,7 +546,8 @@
                      : WindowOpenDisposition::NEW_FOREGROUND_TAB,
         extensions::AppLaunchSource::kSourceNewTabPage);
     params.override_url = override_url;
-    WebContents* new_contents = OpenApplication(params);
+    WebContents* new_contents =
+        apps::LaunchService::Get(profile)->OpenApplication(params);
 
     // This will also destroy the handler, so do not perform any actions after.
     if (new_contents != old_contents && browser &&
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
index 3a2b3bc5b..5277d3b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
@@ -6,12 +6,9 @@
 
 #include <string>
 #include <utility>
-#include <vector>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
-#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
@@ -67,6 +64,11 @@
       base::BindRepeating(
           &CrostiniHandler::HandleCrostiniInstallerStatusRequest,
           weak_ptr_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
+      "requestCrostiniExportImportOperationStatus",
+      base::BindRepeating(
+          &CrostiniHandler::HandleCrostiniExportImportOperationStatusRequest,
+          weak_ptr_factory_.GetWeakPtr()));
 }
 
 void CrostiniHandler::OnJavascriptAllowed() {
@@ -75,6 +77,7 @@
   if (chromeos::CrosUsbDetector::Get()) {
     chromeos::CrosUsbDetector::Get()->AddUsbDeviceObserver(this);
   }
+  crostini::CrostiniExportImport::GetForProfile(profile_)->AddObserver(this);
 }
 
 void CrostiniHandler::OnJavascriptDisallowed() {
@@ -86,6 +89,7 @@
   if (chromeos::CrosUsbDetector::Get()) {
     chromeos::CrosUsbDetector::Get()->RemoveUsbDeviceObserver(this);
   }
+  crostini::CrostiniExportImport::GetForProfile(profile_)->RemoveObserver(this);
 }
 
 void CrostiniHandler::HandleRequestCrostiniInstallerView(
@@ -225,6 +229,15 @@
   OnCrostiniInstallerViewStatusChanged(status);
 }
 
+void CrostiniHandler::HandleCrostiniExportImportOperationStatusRequest(
+    const base::ListValue* args) {
+  AllowJavascript();
+  CHECK_EQ(0U, args->GetSize());
+  bool in_progress = crostini::CrostiniExportImport::GetForProfile(profile_)
+                         ->GetExportImportOperationStatus();
+  OnCrostiniExportImportOperationStatusChanged(in_progress);
+}
+
 void CrostiniHandler::OnCrostiniInstallerViewStatusChanged(bool status) {
   // It's technically possible for this to be called before Javascript is
   // enabled, in which case we must not call FireWebUIListener
@@ -234,5 +247,12 @@
   }
 }
 
+void CrostiniHandler::OnCrostiniExportImportOperationStatusChanged(
+    bool in_progress) {
+  // Other side listens with cr.addWebUIListener
+  FireWebUIListener("crostini-export-import-operation-status-changed",
+                    base::Value(in_progress));
+}
+
 }  // namespace settings
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h
index 97f4605..b5baeae 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/usb/cros_usb_detector.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
@@ -23,6 +24,7 @@
 
 class CrostiniHandler : public ::settings::SettingsPageUIHandler,
                         public crostini::InstallerViewStatusObserver,
+                        public crostini::CrostiniExportImport::Observer,
                         public chromeos::CrosUsbDeviceObserver {
  public:
   explicit CrostiniHandler(Profile* profile);
@@ -56,6 +58,11 @@
   void HandleCrostiniInstallerStatusRequest(const base::ListValue* args);
   // Handle the CrostiniInstallerView opening/closing.
   void OnCrostiniInstallerViewStatusChanged(bool open) override;
+  // Handle a request for the CrostiniExportImport operation status.
+  void HandleCrostiniExportImportOperationStatusRequest(
+      const base::ListValue* args);
+  // CrostiniExportImport::Observer:
+  void OnCrostiniExportImportOperationStatusChanged(bool in_progress) override;
 
   Profile* profile_;
   // weak_ptr_factory_ should always be last member.
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
index de342d5a..1363650 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
@@ -5,11 +5,11 @@
 #include "chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.h"
 
 #include "ash/public/cpp/keyboard_shortcut_viewer.h"
+#include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/values.h"
-#include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/common/service_manager_connection.h"
@@ -114,8 +114,7 @@
 void KeyboardHandler::UpdateKeyboards() {
   bool physical_keyboard = false;
   // In tablet mode, physical keybards are disabled / ignored.
-  if (!TabletModeClient::Get() ||
-      !TabletModeClient::Get()->tablet_mode_enabled()) {
+  if (!ash::TabletMode::Get() || !ash::TabletMode::Get()->InTabletMode()) {
     physical_keyboard = true;
   }
   if (!physical_keyboard) {
diff --git a/chrome/browser/ui/webui/site_settings_helper.cc b/chrome/browser/ui/webui/site_settings_helper.cc
index 701e6da..3430d83 100644
--- a/chrome/browser/ui/webui/site_settings_helper.cc
+++ b/chrome/browser/ui/webui/site_settings_helper.cc
@@ -90,6 +90,8 @@
     {CONTENT_SETTINGS_TYPE_BLUETOOTH_SCANNING, "bluetooth-scanning"},
     {CONTENT_SETTINGS_TYPE_HID_GUARD, "hid-devices"},
     {CONTENT_SETTINGS_TYPE_HID_CHOOSER_DATA, kHidChooserDataGroupType},
+    {CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+     "native-file-system-write"},
 
     // Add new content settings here if a corresponding Javascript string
     // representation for it is not required. Note some exceptions do have UI in
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc
index b4fe116..7be5ada 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc
@@ -16,9 +16,15 @@
   content::WebUIDataSource* html_source =
       content::WebUIDataSource::Create(chrome::kChromeUITabStripHost);
 
+  std::string generated_path =
+      "@out_folder@/gen/chrome/browser/resources/tab_strip/";
+
   for (size_t i = 0; i < kTabStripResourcesSize; ++i) {
-    html_source->AddResourcePath(kTabStripResources[i].name,
-                                 kTabStripResources[i].value);
+    std::string path = kTabStripResources[i].name;
+    if (path.rfind(generated_path, 0) == 0) {
+      path = path.substr(generated_path.length());
+    }
+    html_source->AddResourcePath(path, kTabStripResources[i].value);
   }
 
   html_source->SetDefaultResource(IDR_TAB_STRIP_HTML);
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index e5cbac3c..4533f89d 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -775,6 +775,11 @@
       ]
 
       data_deps += [ "//device/vr:openvr_mock" ]
+
+      if (enable_openxr) {
+        deps += [ "//third_party/openxr" ]
+        data_deps += [ "//device/vr:openxr_mock" ]
+      }
     }
   }
 
diff --git a/chrome/browser/vr/test/multi_class_browser_test.h b/chrome/browser/vr/test/multi_class_browser_test.h
index 8aeb9c6..05ac882 100644
--- a/chrome/browser/vr/test/multi_class_browser_test.h
+++ b/chrome/browser/vr/test/multi_class_browser_test.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_VR_TEST_MULTI_CLASS_BROWSER_TEST_H_
 
 #include "content/public/test/browser_test.h"
+#include "device/vr/buildflags/buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 // A collection of macros to automatically run a browser test multiple times
@@ -70,6 +71,15 @@
   void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
       base_class* t)
 
+#define IN_PROC_MULTI_CLASS_BROWSER_TEST_F3(                             \
+    test_class1, test_class2, test_class3, base_class, test_name)        \
+  DEFINE_RUN_TEST_IMPL_(test_name, base_class)                           \
+  DEFINE_BROWSER_TEST_(test_class1, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class2, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class3, test_name)                           \
+  void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
+      base_class* t)
+
 #define IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F2(              \
     test_class1, test_class2, base_class, test_name)                     \
   DEFINE_RUN_TEST_IMPL_(test_name, base_class)                           \
@@ -80,19 +90,45 @@
   void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
       base_class* t)
 
+#define IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F3(              \
+    test_class1, test_class2, test_class3, base_class, test_name)        \
+  DEFINE_RUN_TEST_IMPL_(test_name, base_class)                           \
+  DEFINE_BROWSER_TEST_(test_class1, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class2, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class3, test_name)                           \
+  DEFINE_INCOGNITO_BROWSER_TEST_(test_class1, test_name)                 \
+  DEFINE_INCOGNITO_BROWSER_TEST_(test_class2, test_name)                 \
+  DEFINE_INCOGNITO_BROWSER_TEST_(test_class3, test_name)                 \
+  void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
+      base_class* t)
+
 // Helper macro to cut down on duplicate code since most uses of
-// IN_PROC_MULTI_CLASS_BROWSER_TEST_F2 are passed the same OpenVR and WMR
-// classes and the same base class
+// IN_PROC_MULTI_CLASS_BROWSER_TEST_F3 are passed the same OpenVR, WMR, and
+// OpenXR classes and the same base class
+#if BUILDFLAG(ENABLE_OPENXR)
+#define WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(test_name) \
+  IN_PROC_MULTI_CLASS_BROWSER_TEST_F3(                  \
+      WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest,  \
+      WebXrVrOpenXrBrowserTest, WebXrVrBrowserTestBase, test_name)
+#else
 #define WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(test_name)         \
   IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest, \
                                       WebXrVrWmrBrowserTest,    \
                                       WebXrVrBrowserTestBase, test_name)
+#endif  // BUILDFLAG(ENABLE_OPENXR)
 
 // The same as WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F, but runs the tests in
 // incognito mode as well.
+#if BUILDFLAG(ENABLE_OPENXR)
+#define WEBXR_VR_ALL_RUNTIMES_PLUS_INCOGNITO_BROWSER_TEST_F(test_name) \
+  IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F3(                  \
+      WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest,                 \
+      WebXrVrOpenXrBrowserTest, WebXrVrBrowserTestBase, test_name)
+#else
 #define WEBXR_VR_ALL_RUNTIMES_PLUS_INCOGNITO_BROWSER_TEST_F(test_name)         \
   IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F2(                          \
       WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest, WebXrVrBrowserTestBase, \
       test_name)
+#endif  // ENABLE_OPENXR
 
 #endif  // CHROME_BROWSER_VR_TEST_MULTI_CLASS_BROWSER_TEST_H_
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.cc b/chrome/browser/vr/test/webxr_vr_browser_test.cc
index 863b159..6da016f 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.cc
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.cc
@@ -14,6 +14,10 @@
 
 namespace vr {
 
+WebXrVrBrowserTestBase::WebXrVrBrowserTestBase() {
+  enable_features_.push_back(features::kWebXr);
+}
+
 void WebXrVrBrowserTestBase::EnterSessionWithUserGesture(
     content::WebContents* web_contents) {
 #if defined(OS_WIN)
@@ -69,7 +73,26 @@
   return gfx::Vector3dF();
 }
 
+WebXrVrRuntimelessBrowserTest::WebXrVrRuntimelessBrowserTest() {
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+  disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
+WebXrVrRuntimelessBrowserTestSensorless::
+    WebXrVrRuntimelessBrowserTestSensorless() {
+  disable_features_.push_back(device::kWebXrOrientationSensorDevice);
+}
+
 #if defined(OS_WIN)
+
+WebXrVrOpenVrBrowserTestBase::WebXrVrOpenVrBrowserTestBase() {
+  enable_features_.push_back(features::kOpenVR);
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+  disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
 XrBrowserTestBase::RuntimeType WebXrVrOpenVrBrowserTestBase::GetRuntimeType()
     const {
   return XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR;
@@ -95,6 +118,56 @@
     const {
   return XrBrowserTestBase::RuntimeType::RUNTIME_WMR;
 }
+
+#if BUILDFLAG(ENABLE_OPENXR)
+
+WebXrVrOpenXrBrowserTestBase::WebXrVrOpenXrBrowserTestBase() {
+  enable_features_.push_back(features::kOpenXR);
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+  disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
+WebXrVrOpenXrBrowserTestBase::~WebXrVrOpenXrBrowserTestBase() = default;
+
+XrBrowserTestBase::RuntimeType WebXrVrOpenXrBrowserTestBase::GetRuntimeType()
+    const {
+  return XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR;
+}
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
+WebXrVrOpenVrBrowserTest::WebXrVrOpenVrBrowserTest() {
+  // We know at this point that we're going to be running with both OpenVR and
+  // WebXR enabled, so enforce the DirectX 11.1 requirement.
+  runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+
+WebXrVrWmrBrowserTest::WebXrVrWmrBrowserTest() {
+  // WMR already enabled by default.
+  runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+WebXrVrOpenXrBrowserTest::WebXrVrOpenXrBrowserTest() {
+  runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
+// Test classes with WebXR disabled.
+WebXrVrOpenVrBrowserTestWebXrDisabled::WebXrVrOpenVrBrowserTestWebXrDisabled() {
+  disable_features_.push_back(features::kWebXr);
+}
+
+WebXrVrWmrBrowserTestWebXrDisabled::WebXrVrWmrBrowserTestWebXrDisabled() {
+  disable_features_.push_back(features::kWebXr);
+}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+WebXrVrOpenXrBrowserTestWebXrDisabled::WebXrVrOpenXrBrowserTestWebXrDisabled() {
+  disable_features_.push_back(features::kWebXr);
+}
+#endif  // BUIDFLAG(ENABLE_OPENXR)
+
 #endif  // OS_WIN
 
 }  // namespace vr
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.h b/chrome/browser/vr/test/webxr_vr_browser_test.h
index 71fcc1e..4c37eddd 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.h
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.h
@@ -27,7 +27,7 @@
 // WebXR for VR-specific test base class without any particular runtime.
 class WebXrVrBrowserTestBase : public WebXrBrowserTestBase {
  public:
-  WebXrVrBrowserTestBase() { enable_features_.push_back(features::kWebXr); }
+  WebXrVrBrowserTestBase();
   void EnterSessionWithUserGesture(content::WebContents* web_contents) override;
   void EnterSessionWithUserGestureOrFail(
       content::WebContents* web_contents) override;
@@ -52,11 +52,7 @@
 // Test class with OpenVR disabled.
 class WebXrVrRuntimelessBrowserTest : public WebXrVrBrowserTestBase {
  public:
-  WebXrVrRuntimelessBrowserTest() {
-#if BUILDFLAG(ENABLE_WINDOWS_MR)
-    disable_features_.push_back(features::kWindowsMixedReality);
-#endif
-  }
+  WebXrVrRuntimelessBrowserTest();
 };
 
 // WebXrOrientationSensorDevice is only defined when the enable_vr flag is set.
@@ -64,9 +60,7 @@
 class WebXrVrRuntimelessBrowserTestSensorless
     : public WebXrVrRuntimelessBrowserTest {
  public:
-  WebXrVrRuntimelessBrowserTestSensorless() {
-    disable_features_.push_back(device::kWebXrOrientationSensorDevice);
-  }
+  WebXrVrRuntimelessBrowserTestSensorless();
 };
 #endif  // BUILDFLAG(ENABLE_VR)
 
@@ -75,12 +69,7 @@
 // OpenVR-specific subclass of WebXrVrBrowserTestBase.
 class WebXrVrOpenVrBrowserTestBase : public WebXrVrBrowserTestBase {
  public:
-  WebXrVrOpenVrBrowserTestBase() {
-    enable_features_.push_back(features::kOpenVR);
-#if BUILDFLAG(ENABLE_WINDOWS_MR)
-    disable_features_.push_back(features::kWindowsMixedReality);
-#endif
-  }
+  WebXrVrOpenVrBrowserTestBase();
   XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
   gfx::Vector3dF GetControllerOffset() const override;
 };
@@ -102,39 +91,54 @@
   std::unique_ptr<MockXRDeviceHookBase> dummy_hook_;
 };
 
+#if BUILDFLAG(ENABLE_OPENXR)
+// OpenXR-specific subclass of WebXrVrBrowserTestBase.
+class WebXrVrOpenXrBrowserTestBase : public WebXrVrBrowserTestBase {
+ public:
+  WebXrVrOpenXrBrowserTestBase();
+  ~WebXrVrOpenXrBrowserTestBase() override;
+  XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
+};
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
 // Test class with standard features enabled: WebXR and OpenVR.
 class WebXrVrOpenVrBrowserTest : public WebXrVrOpenVrBrowserTestBase {
  public:
-  WebXrVrOpenVrBrowserTest() {
-    // We know at this point that we're going to be running with both OpenVR and
-    // WebXR enabled, so enforce the DirectX 11.1 requirement.
-    runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
-  }
+  WebXrVrOpenVrBrowserTest();
 };
 
 class WebXrVrWmrBrowserTest : public WebXrVrWmrBrowserTestBase {
  public:
-  WebXrVrWmrBrowserTest() {
-    // WMR already enabled by default.
-    runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
-  }
+  WebXrVrWmrBrowserTest();
 };
 
+#if BUILDFLAG(ENABLE_OPENXR)
+class WebXrVrOpenXrBrowserTest : public WebXrVrOpenXrBrowserTestBase {
+ public:
+  WebXrVrOpenXrBrowserTest();
+};
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
 // Test classes with WebXR disabled.
 class WebXrVrOpenVrBrowserTestWebXrDisabled
     : public WebXrVrOpenVrBrowserTestBase {
  public:
-  WebXrVrOpenVrBrowserTestWebXrDisabled() {
-    disable_features_.push_back(features::kWebXr);
-  }
+  WebXrVrOpenVrBrowserTestWebXrDisabled();
 };
 
 class WebXrVrWmrBrowserTestWebXrDisabled : public WebXrVrWmrBrowserTestBase {
  public:
-  WebXrVrWmrBrowserTestWebXrDisabled() {
-    disable_features_.push_back(features::kWebXr);
-  }
+  WebXrVrWmrBrowserTestWebXrDisabled();
 };
+
+#if BUILDFLAG(ENABLE_OPENXR)
+class WebXrVrOpenXrBrowserTestWebXrDisabled
+    : public WebXrVrOpenXrBrowserTestBase {
+ public:
+  WebXrVrOpenXrBrowserTestWebXrDisabled();
+};
+#endif  // BUIDFLAG(ENABLE_OPENXR)
+
 #endif  // OS_WIN
 
 }  // namespace vr
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index 2cac9c3..648c624 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -41,6 +41,8 @@
 constexpr char XrBrowserTestBase::kVrConfigPathVal[];
 constexpr char XrBrowserTestBase::kVrLogPathEnvVar[];
 constexpr char XrBrowserTestBase::kVrLogPathVal[];
+constexpr char XrBrowserTestBase::kOpenXrConfigPathEnvVar[];
+constexpr char XrBrowserTestBase::kOpenXrConfigPathVal[];
 constexpr char XrBrowserTestBase::kTestFileDir[];
 constexpr char XrBrowserTestBase::kSwitchIgnoreRuntimeRequirements[];
 const std::vector<std::string> XrBrowserTestBase::kRequiredTestSwitches{
@@ -135,6 +137,16 @@
       env_->SetVar(kVrLogPathEnvVar, MakeExecutableRelative(kVrLogPathVal)))
       << "Failed to set OpenVR log location environment variable";
 
+  // Set the environment variable to use the mock OpenXR client.
+  // If the kOpenXrConfigPathEnvVar environment variable is set, the OpenXR
+  // loader will look for the OpenXR runtime specified in that json file. The
+  // json file contains the path to the runtime, relative to the json file
+  // itself. Otherwise, the OpenXR loader loads the active OpenXR runtime
+  // installed on the system, which is specified by a registry key.
+  ASSERT_TRUE(env_->SetVar(kOpenXrConfigPathEnvVar,
+                           MakeExecutableRelative(kOpenXrConfigPathVal)))
+      << "Failed to set OpenXR JSON location environment variable";
+
   // Set any command line flags that subclasses have set, e.g. enabling WebVR
   // and OpenVR support.
   for (const auto& switch_string : append_switches_) {
@@ -164,6 +176,7 @@
     case XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR:
       return device::XrAxisType::kTrackpad;
     case XrBrowserTestBase::RuntimeType::RUNTIME_WMR:
+    case XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR:
       return device::XrAxisType::kJoystick;
     case XrBrowserTestBase::RuntimeType::RUNTIME_NONE:
       return device::XrAxisType::kNone;
@@ -177,6 +190,7 @@
     case XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR:
       return device::XrAxisType::kJoystick;
     case XrBrowserTestBase::RuntimeType::RUNTIME_WMR:
+    case XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR:
       return device::XrAxisType::kTrackpad;
     case XrBrowserTestBase::RuntimeType::RUNTIME_NONE:
       return device::XrAxisType::kNone;
diff --git a/chrome/browser/vr/test/xr_browser_test.h b/chrome/browser/vr/test/xr_browser_test.h
index ad29ac4c..f321f5c 100644
--- a/chrome/browser/vr/test/xr_browser_test.h
+++ b/chrome/browser/vr/test/xr_browser_test.h
@@ -55,6 +55,9 @@
   static constexpr char kVrConfigPathVal[] = "./";
   static constexpr char kVrLogPathEnvVar[] = "VR_LOG_PATH";
   static constexpr char kVrLogPathVal[] = "./";
+  static constexpr char kOpenXrConfigPathEnvVar[] = "XR_RUNTIME_JSON";
+  static constexpr char kOpenXrConfigPathVal[] =
+      "./mock_vr_clients/bin/openxr/openxr.json";
   static constexpr char kTestFileDir[] =
       "chrome/test/data/xr/e2e_test_files/html/";
   static constexpr char kSwitchIgnoreRuntimeRequirements[] =
@@ -71,7 +74,8 @@
   enum class RuntimeType {
     RUNTIME_NONE = 0,
     RUNTIME_OPENVR = 1,
-    RUNTIME_WMR = 2
+    RUNTIME_WMR = 2,
+    RUNTIME_OPENXR = 3
   };
 
   XrBrowserTestBase();
diff --git a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
index dcf6660..75ecdb5 100644
--- a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
@@ -159,6 +159,7 @@
 
 }  // namespace
 
+// TODO(crbug.com/986621) - OpenXR currently hard codes data
 // Pixel test for WebXR - start presentation, submit frames, get data back out.
 // Validates that submitted frames used expected pose.
 WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestPresentationPoses) {
diff --git a/chrome/browser/vr/webxr_vr_indicators_browser_test.cc b/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
index 7ba1d1e..435f707 100644
--- a/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
@@ -160,7 +160,11 @@
            UserFriendlyElementName::kWebXrLocationPermissionIndicator, false}});
 }
 
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(
+// TODO(crbug.com/986621) - Enable for OpenXR
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(
+    WebXrVrOpenVrBrowserTest,
+    WebXrVrWmrBrowserTest,
+    WebXrVrBrowserTestBase,
     TestLocationIndicatorWhenUserAskedToPrompt) {
   TestForInitialIndicatorForContentType(
       t, {{CONTENT_SETTINGS_TYPE_GEOLOCATION, CONTENT_SETTING_ASK,
@@ -182,7 +186,12 @@
       });
 }
 
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(
+// TODO(crbug.com/986621) - Enable for OpenXR
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(
+    WebXrVrOpenVrBrowserTest,
+    WebXrVrWmrBrowserTest,
+    WebXrVrBrowserTestBase,
+
     TestMultipleInitialIndicators_OneDeviceAllowed) {
   TestForInitialIndicatorForContentType(
       t,
diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc
index 38d30db1..b06e14a3 100644
--- a/chrome/browser/vr/webxr_vr_input_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc
@@ -214,9 +214,13 @@
   std::move(callback).Run();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Ensure that when an input source's handedness changes, an input source change
 // event is fired and a new input source is created.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputHandednessChange) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestInputHandednessChange) {
   WebXrControllerInputMock my_mock;
   unsigned int controller_index =
       my_mock.CreateAndConnectMinimalGamepad(t->GetPrimaryAxisType());
@@ -254,12 +258,16 @@
   t->EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Test that inputsourceschange events contain only the expected added/removed
 // input sources when a mock controller is connected/disconnected.
 // Also validates that if an input source changes substantially we get an event
 // containing both the removal of the old one and the additon of the new one,
 // rather than two events.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputSourcesChange) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestInputSourcesChange) {
   WebXrControllerInputMock my_mock;
 
   // TODO(crbug.com/963676): Figure out if the race is a product or test bug.
@@ -424,10 +432,14 @@
   EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Ensure that if a Gamepad has the minimum required number of axes/buttons to
 // be considered an xr-standard Gamepad, that it is exposed as such, and that
 // we can check the state of it's priamry axes/button.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadMinimumData) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestGamepadMinimumData) {
   WebXrControllerInputMock my_mock;
 
   unsigned int controller_index =
@@ -467,10 +479,14 @@
   t->EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Ensure that if a Gamepad has all of the required and optional buttons as
 // specified by the xr-standard mapping, that those buttons are plumbed up
 // in their required places.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadCompleteData) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestGamepadCompleteData) {
   WebXrControllerInputMock my_mock;
 
   // Create a controller that supports all reserved buttons.
@@ -682,10 +698,14 @@
   EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Test that controller input is registered via WebXR's input method.
 // Equivalent to
 // WebXrVrInputTest#testControllerClicksRegisteredOnDaydream_WebXr.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerInputRegistered) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestControllerInputRegistered) {
   WebXrControllerInputMock my_mock;
 
   unsigned int controller_index =
@@ -768,9 +788,13 @@
   return array_string;
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Test that changes in controller position are properly plumbed through to
 // WebXR.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerPositionTracking) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestControllerPositionTracking) {
   WebXrControllerInputMock my_mock;
 
   auto controller_data = my_mock.CreateValidController(
diff --git a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
index 3ee0462d..4ab26731 100644
--- a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
@@ -91,6 +91,8 @@
 IN_PROC_BROWSER_TEST_F(WebVrOpenVrBrowserTest, TestPresentationPixels) {
   TestPresentationPixelsImpl(this, "test_webvr_pixels");
 }
+
+// TODO(crbug.com/986621) - OpenXR currently hard codes data
 WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestPresentationPixels) {
   TestPresentationPixelsImpl(t, "test_webxr_pixels");
 }
diff --git a/chrome/browser/vr/webxr_vr_transition_browser_test.cc b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
index 3c593c0..4dbdac09 100644
--- a/chrome/browser/vr/webxr_vr_transition_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/vr/test/multi_class_browser_test.h"
 #include "chrome/browser/vr/test/webxr_vr_browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "device/vr/buildflags/buildflags.h"
 
 // Browser test equivalent of
 // chrome/android/javatests/src/.../browser/vr/WebXrVrTransitionTest.java.
@@ -53,10 +54,19 @@
   TestApiDisabledWithoutFlagSetImpl(this,
                                     "test_webvr_disabled_without_flag_set");
 }
+
+#if BUILDFLAG(ENABLE_OPENXR)
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F3(WebXrVrOpenVrBrowserTestWebXrDisabled,
+                                    WebXrVrWmrBrowserTestWebXrDisabled,
+                                    WebXrVrOpenXrBrowserTestWebXrDisabled,
+                                    WebXrVrBrowserTestBase,
+                                    TestWebXrDisabledWithoutFlagSet) {
+#else
 IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTestWebXrDisabled,
                                     WebXrVrWmrBrowserTestWebXrDisabled,
                                     WebXrVrBrowserTestBase,
                                     TestWebXrDisabledWithoutFlagSet) {
+#endif  // BUILDFLAG(ENABLE_OPENXR)
   TestApiDisabledWithoutFlagSetImpl(t, "test_webxr_disabled_without_flag_set");
 }
 
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
index 80ae48a..be9a890 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
@@ -216,8 +216,7 @@
     content::WebContents* web_contents) {
   DCHECK(web_contents);
   DCHECK(!profile_->IsOffTheRecord());
-  const Extension* app = GetExtensionById(profile_, app_id);
-  BookmarkAppReparentTab(web_contents, app);
+  BookmarkAppReparentTab(web_contents, app_id);
 }
 
 bool BookmarkAppInstallFinalizer::CanRevealAppShim() const {
diff --git a/chrome/browser/web_applications/system_web_app_manager.h b/chrome/browser/web_applications/system_web_app_manager.h
index a658873..14ffdb4d 100644
--- a/chrome/browser/web_applications/system_web_app_manager.h
+++ b/chrome/browser/web_applications/system_web_app_manager.h
@@ -93,8 +93,8 @@
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  // Returns the app id for the given System App |id|.
-  base::Optional<AppId> GetAppIdForSystemApp(SystemAppType id) const;
+  // Returns the app id for the given System App |type|.
+  base::Optional<AppId> GetAppIdForSystemApp(SystemAppType type) const;
 
   // Returns whether |app_id| points to an installed System App.
   bool IsSystemWebApp(const AppId& app_id) const;
@@ -103,11 +103,12 @@
     return *on_apps_synchronized_;
   }
 
- protected:
   void SetSystemAppsForTesting(
       base::flat_map<SystemAppType, SystemAppInfo> system_apps);
+
   void SetUpdatePolicyForTesting(UpdatePolicy policy);
 
+ protected:
   virtual const base::Version& CurrentVersion() const;
 
  private:
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 5c4620a..c875a23 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -221,11 +221,11 @@
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
-#if defined(OS_CHROMEOS) || defined(OS_LINUX)
-// Enable chrome://terminal in Chrome OS or Linux.
+// Enable chrome://terminal.  Terminal System App will only run on
+// OS_CHROMEOS, but this flag must be defined for all platforms since
+// it is required for SystemWebApp tests.
 const base::Feature kTerminalSystemApp{"TerminalSystemApp",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
-#endif
 
 // Enable using tab sharing infobars for desktop capture.
 const base::Feature kDesktopCaptureTabSharingInfobar{
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index b41450a..c00528d 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -129,9 +129,7 @@
 extern const base::Feature kUploadZippedSystemLogs;
 #endif
 
-#if defined(OS_CHROMEOS) || defined(OS_LINUX)
 COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kTerminalSystemApp;
-#endif
 
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kDesktopCaptureTabSharingInfobar;
diff --git a/chrome/common/mac/app_mode_common.h b/chrome/common/mac/app_mode_common.h
index ca3bef96..2c49f80 100644
--- a/chrome/common/mac/app_mode_common.h
+++ b/chrome/common/mac/app_mode_common.h
@@ -43,13 +43,6 @@
 // of the full profile directory path.
 extern const char kAppShimBootstrapNameFragment[];
 
-// TODO(rsesek): Delete this after ensuring the file has been cleaned up.
-// The name of a file placed in the profile directory to indicate that app
-// shims should connect over Mach IPC rather than a UNIX domain socket. If
-// a file named this does not exist, app shims will fall back to the UNIX
-// domain socket.
-extern const char kMojoChannelMacSignalFile[];
-
 // A symlink used to store the version string of the currently running Chrome.
 // The shim will read this to determine which version of the framework to load.
 extern const char kRunningChromeVersionSymlinkName[];
diff --git a/chrome/common/mac/app_mode_common.mm b/chrome/common/mac/app_mode_common.mm
index 969b769a..18ded7a 100644
--- a/chrome/common/mac/app_mode_common.mm
+++ b/chrome/common/mac/app_mode_common.mm
@@ -10,7 +10,6 @@
 namespace app_mode {
 
 const char kAppShimBootstrapNameFragment[] = "apps";
-const char kMojoChannelMacSignalFile[] = "apps_use_mojo_channel_mac";
 
 const char kRunningChromeVersionSymlinkName[] = "RunningChromeVersion";
 
diff --git a/chrome/common/safe_browsing/zip_analyzer.cc b/chrome/common/safe_browsing/zip_analyzer.cc
index 1bf41d94f..ac14ff6 100644
--- a/chrome/common/safe_browsing/zip_analyzer.cc
+++ b/chrome/common/safe_browsing/zip_analyzer.cc
@@ -47,8 +47,6 @@
   bool too_big_to_unpack =
       base::checked_cast<uint64_t>(zip_file.GetLength()) >
       FileTypePolicies::GetInstance()->GetMaxFileSizeToAnalyze("zip");
-  UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipTooBigToUnpack",
-                        too_big_to_unpack);
   if (too_big_to_unpack) {
     results->success = false;
     return;
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 3059498..70cf91a 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -111,6 +111,7 @@
 #include "ipc/ipc_sync_channel.h"
 #include "media/base/media_switches.h"
 #include "media/media_buildflags.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "net/base/net_errors.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "ppapi/shared_impl/ppapi_switches.h"
@@ -1085,9 +1086,13 @@
 void ChromeContentRendererClient::GetInterface(
     const std::string& interface_name,
     mojo::ScopedMessagePipeHandle interface_pipe) {
-  service_binding_.GetConnector()->BindInterface(
-      service_manager::ServiceFilter::ByName(chrome::mojom::kServiceName),
-      interface_name, std::move(interface_pipe));
+  // TODO(crbug.com/977637): Get rid of the use of this implementation of
+  // |service_manager::LocalInterfaceProvider|. This was done only to avoid
+  // churning spellcheck code while eliminting the "chrome" and
+  // "chrome_renderer" services. Spellcheck is (and should remain) the only
+  // consumer of this implementation.
+  RenderThread::Get()->BindHostReceiver(
+      mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe)));
 }
 
 #if BUILDFLAG(ENABLE_NACL)
@@ -1335,11 +1340,6 @@
   return prescient_networking_dispatcher_.get();
 }
 
-bool ChromeContentRendererClient::IsPrerenderingFrame(
-    const content::RenderFrame* render_frame) {
-  return prerender::PrerenderHelper::IsPrerendering(render_frame);
-}
-
 bool ChromeContentRendererClient::IsExternalPepperPlugin(
     const std::string& module_name) {
   // TODO(bbudge) remove this when the trusted NaCl plugin has been removed.
@@ -1642,6 +1642,15 @@
   return url.SchemeIs(content::kChromeUIScheme) && !can_use_polyfill;
 }
 
+void ChromeContentRendererClient::BindReceiverOnMainThread(
+    mojo::GenericPendingReceiver receiver) {
+  // TODO(crbug.com/977637): Get rid of the use of BinderRegistry here. This is
+  // only used to bind a spellcheck interface.
+  std::string interface_name = *receiver.interface_name();
+  auto pipe = receiver.PassPipe();
+  registry_.TryBindInterface(interface_name, &pipe);
+}
+
 void ChromeContentRendererClient::OnWebRtcLoggingAgentRequest(
     mojo::InterfaceRequest<chrome::mojom::WebRtcLoggingAgent> request) {
   if (!webrtc_logging_agent_impl_) {
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index c5b898da..e90038aed 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -27,6 +27,7 @@
 #include "extensions/buildflags/buildflags.h"
 #include "ipc/ipc_channel_proxy.h"
 #include "media/media_buildflags.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "printing/buildflags/buildflags.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
@@ -157,7 +158,6 @@
   uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override;
   bool IsLinkVisited(uint64_t link_hash) override;
   blink::WebPrescientNetworking* GetPrescientNetworking() override;
-  bool IsPrerenderingFrame(const content::RenderFrame* render_frame) override;
   bool IsExternalPepperPlugin(const std::string& module_name) override;
   bool IsOriginIsolatedPepperPlugin(const base::FilePath& plugin_path) override;
   std::unique_ptr<content::WebSocketHandshakeThrottleProvider>
@@ -217,6 +217,7 @@
   bool IsSafeRedirectTarget(const GURL& url) override;
   void DidSetUserAgent(const std::string& user_agent) override;
   bool RequiresHtmlImports(const GURL& url) override;
+  void BindReceiverOnMainThread(mojo::GenericPendingReceiver receiver) override;
 
 #if BUILDFLAG(ENABLE_PLUGINS)
   static chrome::mojom::PluginInfoHostAssociatedPtr& GetPluginInfoHost();
diff --git a/chrome/services/file_util/public/cpp/sandboxed_dmg_analyzer_mac.cc b/chrome/services/file_util/public/cpp/sandboxed_dmg_analyzer_mac.cc
index bc751c4..37e8398 100644
--- a/chrome/services/file_util/public/cpp/sandboxed_dmg_analyzer_mac.cc
+++ b/chrome/services/file_util/public/cpp/sandboxed_dmg_analyzer_mac.cc
@@ -53,9 +53,6 @@
   uint64_t size = file.GetLength();
 
   bool too_big_to_unpack = base::checked_cast<uint64_t>(size) > max_size_;
-  UMA_HISTOGRAM_BOOLEAN("SBClientDownload.DmgTooBigToUnpack",
-                        too_big_to_unpack);
-
   if (too_big_to_unpack) {
     DLOG(ERROR) << "File is too big: " << file_path_.value();
     ReportFileFailure();
diff --git a/chrome/services/isolated_xr_device/BUILD.gn b/chrome/services/isolated_xr_device/BUILD.gn
index 0474d93..33a5b14 100644
--- a/chrome/services/isolated_xr_device/BUILD.gn
+++ b/chrome/services/isolated_xr_device/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//device/vr/buildflags/buildflags.gni")
+
 source_set("lib") {
   sources = [
     "xr_device_service.cc",
@@ -14,6 +16,10 @@
     "xr_test_hook_wrapper.h",
   ]
 
+  if (enable_openxr) {
+    configs += [ "//third_party/openxr:config" ]
+  }
+
   deps = [
     "//base",
     "//chrome/common",
diff --git a/chrome/services/isolated_xr_device/xr_service_test_hook.cc b/chrome/services/isolated_xr_device/xr_service_test_hook.cc
index fe5d4f1..a8fcfbd 100644
--- a/chrome/services/isolated_xr_device/xr_service_test_hook.cc
+++ b/chrome/services/isolated_xr_device/xr_service_test_hook.cc
@@ -17,18 +17,27 @@
 #include "device/vr/windows_mixed_reality/mixed_reality_statics.h"
 #endif  // BUILDFLAG(ENABLE_WINDOWS_MR)
 
+#if BUILDFLAG(ENABLE_OPENXR)
+#include "device/vr/openxr/openxr_api_wrapper.h"
+#endif  // BUIDLFLAG(ENABLE_OPENXR)
+
 namespace {
 
 void UnsetTestHook(std::unique_ptr<device::XRTestHookWrapper> wrapper) {
+  // Unset the testhook wrapper with the VR runtimes,
+  // so any future calls to them don't use it.
+
 #if BUILDFLAG(ENABLE_OPENVR)
-  // Unset the testhook wrapper with OpenVR, so any
-  // future calls to OpenVR don't use it.
   device::OpenVRWrapper::SetTestHook(nullptr);
 #endif  // BUILDFLAG(ENABLE_OPENVR)
 
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
   device::MixedRealityDeviceStatics::SetTestHook(nullptr);
 #endif  // BUILDFLAG(ENABLE_WINDOWS_MR)
+
+#if BUILDFLAG(ENABLE_OPENXR)
+  device::OpenXrApiWrapper::SetTestHook(nullptr);
+#endif  // BUILDFLAG(ENABLE_OPENXR)
 }
 
 }  // namespace
@@ -43,7 +52,7 @@
       hook ? std::make_unique<XRTestHookWrapper>(hook.PassInterface())
            : nullptr;
 
-  // Register the wrapper testhook with OpenVR and WMR.
+  // Register the wrapper testhook with the VR runtimes
 #if BUILDFLAG(ENABLE_OPENVR)
   OpenVRWrapper::SetTestHook(wrapper.get());
 #endif  // BUILDFLAG(ENABLE_OPENVR)
@@ -52,6 +61,10 @@
   MixedRealityDeviceStatics::SetTestHook(wrapper.get());
 #endif  // BUILDFLAG(ENABLE_WINDOWS_MR)
 
+#if BUILDFLAG(ENABLE_OPENXR)
+  OpenXrApiWrapper::SetTestHook(wrapper.get());
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
   // Store the new wrapper, so we keep it alive.
   wrapper_ = std::move(wrapper);
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 402d173..0b1c9c5e 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1233,6 +1233,7 @@
       "../browser/ui/views/webauthn/authenticator_dialog_view_browsertest.cc",
       "../browser/ui/views/webauthn/authenticator_qr_code_test.cc",
       "../browser/ui/views/webview_accessibility_browsertest.cc",
+      "../browser/ui/web_applications/app_browser_controller_browsertest.cc",
       "../browser/ui/web_applications/bookmark_app_browsertest.cc",
       "../browser/ui/web_applications/web_app_badging_browsertest.cc",
       "../browser/ui/web_applications/web_app_file_handling_browsertest.cc",
@@ -4015,7 +4016,6 @@
       "../browser/ui/ash/network/network_state_notifier_unittest.cc",
       "../browser/ui/ash/network/tether_notification_presenter_unittest.cc",
       "../browser/ui/ash/session_controller_client_impl_unittest.cc",
-      "../browser/ui/ash/tablet_mode_client_unittest.cc",
       "../browser/ui/ash/wallpaper_controller_client_unittest.cc",
       "../browser/ui/window_sizer/window_sizer_ash_unittest.cc",
     ]
diff --git a/chrome/test/base/interactive_ui_tests_main.cc b/chrome/test/base/interactive_ui_tests_main.cc
index 5e097dc..8d9c162 100644
--- a/chrome/test/base/interactive_ui_tests_main.cc
+++ b/chrome/test/base/interactive_ui_tests_main.cc
@@ -55,7 +55,7 @@
 #elif defined(USE_OZONE) && defined(OS_LINUX)
     ui::OzonePlatform::InitParams params;
     params.single_process = true;
-    ui::OzonePlatform::EnsureInstance()->InitializeForUI(std::move(params));
+    ui::OzonePlatform::InitializeForUI(params);
 #elif defined(OS_LINUX)
     ui_controls::InstallUIControlsAura(
         views::test::CreateUIControlsDesktopAura());
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index 24d306e7..8bd1cc00 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -633,3 +633,23 @@
   def RemoveVirtualAuthenticator(self, authenticatorId):
     params = {'authenticatorId': authenticatorId}
     return self.ExecuteCommand(Command.REMOVE_VIRTUAL_AUTHENTICATOR, params)
+
+  def AddCredential(self, authenticatorId=None, credentialId=None,
+                    isResidentCredential=None, rpId=None, privateKey=None,
+                    userHandle=None, signCount=None):
+    options = {}
+    if authenticatorId is not None:
+      options['authenticatorId'] = authenticatorId
+    if credentialId is not None:
+      options['credentialId'] = credentialId
+    if isResidentCredential is not None:
+      options['isResidentCredential'] = isResidentCredential
+    if rpId is not None:
+      options['rpId'] = rpId
+    if privateKey is not None:
+      options['privateKey'] = privateKey
+    if userHandle is not None:
+      options['userHandle'] = userHandle
+    if signCount is not None:
+      options['signCount'] = signCount
+    return self.ExecuteCommand(Command.ADD_CREDENTIAL, options)
diff --git a/chrome/test/chromedriver/client/command_executor.py b/chrome/test/chromedriver/client/command_executor.py
index 17cceeb..de27e1a 100644
--- a/chrome/test/chromedriver/client/command_executor.py
+++ b/chrome/test/chromedriver/client/command_executor.py
@@ -173,6 +173,9 @@
   REMOVE_VIRTUAL_AUTHENTICATOR = (
       _Method.DELETE,
       '/session/:sessionId/webauthn/authenticator/:authenticatorId')
+  ADD_CREDENTIAL = (
+      _Method.POST,
+      '/session/:sessionId/webauthn/authenticator/:authenticatorId/credential')
 
   # Custom Chrome commands.
   IS_LOADING = (_Method.GET, '/session/:sessionId/is_loading')
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index 5b3028c..0c9a001 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -756,6 +756,15 @@
                   &ExecuteWebAuthnCommand,
                   base::BindRepeating(&ExecuteRemoveVirtualAuthenticator)))),
 
+      CommandMapping(
+          kPost,
+          "session/:sessionId/webauthn/authenticator/:authenticatorId/"
+          "credential",
+          WrapToCommand(
+              "AddCredential",
+              base::BindRepeating(&ExecuteWebAuthnCommand,
+                                  base::BindRepeating(&ExecuteAddCredential)))),
+
       //
       // Non-standard extension commands
       //
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 05e0114c..d5965000 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -225,6 +225,7 @@
         # Android does not support the virtual authenticator environment.
         'ChromeDriverSecureContextTest.testAddVirtualAuthenticator',
         'ChromeDriverSecureContextTest.testRemoveVirtualAuthenticator',
+        'ChromeDriverSecureContextTest.testAddCredential',
     ]
 )
 _ANDROID_NEGATIVE_FILTER['chrome_stable'] = (
@@ -2078,6 +2079,42 @@
         'Could not find a Virtual Authenticator matching the ID',
         self._driver.RemoveVirtualAuthenticator, response['authenticatorId'])
 
+  def testAddCredential(self):
+    # The example attestation private key from the U2F spec at
+    # https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-example
+    # PKCS.8 encoded without encryption.
+    privateKey = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8/zMDQDYAxlU+Qhk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwURmgsJYxGP//fWN/S+j5sN4tT15XEpN/7QZnt14YvI6uvAgO0uJEboFaZlOEB"
+
+    script = """
+      let done = arguments[0];
+      getCredential({
+        type: "public-key",
+        id: new TextEncoder().encode("cred-1"),
+        transports: ["usb"],
+      }).then(done);
+    """
+    self._driver.Load(self.GetHttpsUrlForFile(
+        '/chromedriver/webauthn_test.html', 'chromedriver.test'))
+
+    authenticatorId = self._driver.AddVirtualAuthenticator(
+        protocol = 'ctap2',
+        transport = 'usb',
+        hasResidentKey = False,
+        hasUserVerification = False,
+    )['authenticatorId']
+
+    # Register a credential and try authenticating with it.
+    self._driver.AddCredential(
+      authenticatorId = authenticatorId,
+      credentialId = base64.b64encode("cred-1"),
+      isResidentCredential=False,
+      rpId="chromedriver.test",
+      privateKey=privateKey,
+      signCount=1,
+    )
+
+    result = self._driver.ExecuteAsyncScript(script)
+    self.assertEquals('OK', result['status'])
 
 # Tests in the following class are expected to be moved to ChromeDriverTest
 # class when we no longer support the legacy mode.
diff --git a/chrome/test/chromedriver/util.py b/chrome/test/chromedriver/util.py
index 470e860..9d69eb4 100644
--- a/chrome/test/chromedriver/util.py
+++ b/chrome/test/chromedriver/util.py
@@ -87,7 +87,10 @@
   for root, dirs, files in os.walk(path, topdown=False):
     for name in files:
       filename = os.path.join(root, name)
-      os.chmod(filename, stat.S_IWRITE)
+      try:
+        os.chmod(filename, stat.S_IWRITE)
+      except OSError:
+        pass
       os.remove(filename)
     for name in dirs:
       os.rmdir(os.path.join(root, name))
diff --git a/chrome/test/chromedriver/webauthn_commands.cc b/chrome/test/chromedriver/webauthn_commands.cc
index 105b279..32c8a4a 100644
--- a/chrome/test/chromedriver/webauthn_commands.cc
+++ b/chrome/test/chromedriver/webauthn_commands.cc
@@ -13,6 +13,24 @@
 #include "chrome/test/chromedriver/chrome/web_view.h"
 #include "chrome/test/chromedriver/session.h"
 
+namespace {
+
+// Creates a base::DictionaryValue by cloning the parameters specified by
+// |mapping| from |params|.
+base::DictionaryValue MapParams(
+    const base::flat_map<const char*, const char*>& mapping,
+    const base::Value& params) {
+  base::DictionaryValue options;
+  for (const std::pair<const char*, const char*>& pair : mapping) {
+    const base::Value* value = params.FindKey(pair.second);
+    if (value)
+      options.SetPath(pair.first, value->Clone());
+  }
+  return options;
+}
+
+}  // namespace
+
 Status ExecuteWebAuthnCommand(const WebAuthnCommand& command,
                               Session* session,
                               const base::DictionaryValue& params,
@@ -36,32 +54,43 @@
 Status ExecuteAddVirtualAuthenticator(WebView* web_view,
                                       const base::Value& params,
                                       std::unique_ptr<base::Value>* value) {
-  base::DictionaryValue options;
-  const base::flat_map<const char*, const char*> mapping = {
-      {"options.protocol", "protocol"},
-      {"options.transport", "transport"},
-      {"options.hasResidentKey", "hasResidentKey"},
-      {"options.hasUserVerification", "hasUserVerification"},
-      {"options.automaticPresenceSimulation", "isUserVerified"},
-  };
-  for (const std::pair<const char*, const char*>& pair : mapping) {
-    const base::Value* value = params.FindKey(pair.second);
-    if (value)
-      options.SetPath(pair.first, value->Clone());
-  }
-
-  return web_view->SendCommandAndGetResult("WebAuthn.addVirtualAuthenticator",
-                                           options, value);
+  return web_view->SendCommandAndGetResult(
+      "WebAuthn.addVirtualAuthenticator",
+      MapParams(
+          {
+              {"options.protocol", "protocol"},
+              {"options.transport", "transport"},
+              {"options.hasResidentKey", "hasResidentKey"},
+              {"options.hasUserVerification", "hasUserVerification"},
+              {"options.automaticPresenceSimulation", "isUserVerified"},
+          },
+          params),
+      value);
 }
 
 Status ExecuteRemoveVirtualAuthenticator(WebView* web_view,
                                          const base::Value& params,
                                          std::unique_ptr<base::Value>* value) {
-  base::DictionaryValue options;
-  const base::Value* authenticatorId = params.FindKey("authenticatorId");
-  if (authenticatorId)
-    options.SetKey("authenticatorId", authenticatorId->Clone());
-
   return web_view->SendCommandAndGetResult(
-      "WebAuthn.removeVirtualAuthenticator", options, value);
+      "WebAuthn.removeVirtualAuthenticator",
+      MapParams({{"authenticatorId", "authenticatorId"}}, params), value);
+}
+
+Status ExecuteAddCredential(WebView* web_view,
+                            const base::Value& params,
+                            std::unique_ptr<base::Value>* value) {
+  return web_view->SendCommandAndGetResult(
+      "WebAuthn.addCredential",
+      MapParams(
+          {
+              {"authenticatorId", "authenticatorId"},
+              {"credential.credentialId", "credentialId"},
+              {"credential.isResidentCredential", "isResidentCredential"},
+              {"credential.rpId", "rpId"},
+              {"credential.privateKey", "privateKey"},
+              {"credential.userHandle", "userHandle"},
+              {"credential.signCount", "signCount"},
+          },
+          params),
+      value);
 }
diff --git a/chrome/test/chromedriver/webauthn_commands.h b/chrome/test/chromedriver/webauthn_commands.h
index 7537abc..fd75ecf 100644
--- a/chrome/test/chromedriver/webauthn_commands.h
+++ b/chrome/test/chromedriver/webauthn_commands.h
@@ -39,4 +39,9 @@
                                          const base::Value& params,
                                          std::unique_ptr<base::Value>* value);
 
+// Inject a credential into an authenticator.
+Status ExecuteAddCredential(WebView* web_view,
+                            const base::Value& params,
+                            std::unique_ptr<base::Value>* value);
+
 #endif  // CHROME_TEST_CHROMEDRIVER_WEBAUTHN_COMMANDS_H_
diff --git a/chrome/test/data/chromedriver/webauthn_test.html b/chrome/test/data/chromedriver/webauthn_test.html
index 680f7c0..b614e88 100644
--- a/chrome/test/data/chromedriver/webauthn_test.html
+++ b/chrome/test/data/chromedriver/webauthn_test.html
@@ -34,5 +34,24 @@
     return {status: error.toString()};
   }
 }
+
+async function getCredential(credential, options = {}) {
+  options = Object.assign({
+    challenge: Uint8Array.from("Winter is Coming"),
+    rpId: "chromedriver.test",
+    allowCredentials: [credential],
+    userVerification: "preferred",
+  }, options);
+
+  try {
+    const attestation = await navigator.credentials.get({publicKey: options});
+    return {
+      status: "OK",
+      attestation,
+    };
+  } catch (error) {
+    return {status: error.toString()};
+  }
+}
   </script>
 </html>
diff --git a/chrome/test/data/webui/set_time_dialog_browsertest.js b/chrome/test/data/webui/set_time_dialog_browsertest.js
index 5507340..0ae1342 100644
--- a/chrome/test/data/webui/set_time_dialog_browsertest.js
+++ b/chrome/test/data/webui/set_time_dialog_browsertest.js
@@ -187,38 +187,6 @@
             assertGT(timeInSeconds, todaySeconds);
           });
     });
-
-    suite('NullTimezone', () => {
-      suiteSetup(() => {
-        loadTimeData.overrideValues({
-          currentTimezoneId: '',
-          timezoneList: [],
-        });
-      });
-
-      test('SetDateNullTimezone', () => {
-        const dateInput = setTimeElement.$$('#dateInput');
-        assertTrue(!!dateInput);
-
-        assertEquals(null, setTimeElement.$$('#timezoneSelect'));
-
-        // Simulate the user changing the date picker backward by one hour.
-        const today = dateInput.valueAsDate;
-        const nextWeek = new Date(today.getTime() - 60 * 60 * 1000);
-        dateInput.focus();
-        dateInput.valueAsDate = nextWeek;
-        setTimeElement.$$('#doneButton').click();
-
-        // Verify the page sends a request to move time backward.
-        return testBrowserProxy.whenCalled('setTimeInSeconds')
-            .then(timeInSeconds => {
-              const todaySeconds = today.getTime() / 1000;
-              // The exact value isn't important (it depends on the current
-              // time).
-              assertGT(todaySeconds, timeInSeconds);
-            });
-      });
-    });
   });
 
   mocha.run();
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
index 16f5d8e..9a2ceb1 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
@@ -151,6 +151,33 @@
           });
     });
 
+    test('ExportImportButtonsGetDisabledOnOperationStatus', function() {
+      assertTrue(!!subpage.$$('#crostini-export-import'));
+      subpage.$$('#crostini-export-import').click();
+      return flushAsync()
+          .then(() => {
+            subpage = crostiniPage.$$('settings-crostini-export-import');
+            assertFalse(subpage.$$('#export cr-button').disabled);
+            assertFalse(subpage.$$('#import cr-button').disabled);
+            cr.webUIListenerCallback(
+                'crostini-export-import-operation-status-changed', true);
+            return flushAsync();
+          })
+          .then(() => {
+            subpage = crostiniPage.$$('settings-crostini-export-import');
+            assertTrue(subpage.$$('#export cr-button').disabled);
+            assertTrue(subpage.$$('#import cr-button').disabled);
+            cr.webUIListenerCallback(
+                'crostini-export-import-operation-status-changed', false);
+            return flushAsync();
+          })
+          .then(() => {
+            subpage = crostiniPage.$$('settings-crostini-export-import');
+            assertFalse(subpage.$$('#export cr-button').disabled);
+            assertFalse(subpage.$$('#import cr-button').disabled);
+          });
+    });
+
     test('Remove', function() {
       assertTrue(!!subpage.$$('#remove cr-button'));
       subpage.$$('#remove cr-button').click();
diff --git a/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js b/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js
index af3ac42..8e36a47 100644
--- a/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js
+++ b/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js
@@ -55,6 +55,12 @@
     cr.webUIListenerCallback('crostini-installer-status-changed', false);
   }
 
+  /** @override */
+  requestCrostiniExportImportOperationStatus() {
+    cr.webUIListenerCallback(
+        'crostini-export-import-operation-status-changed', false);
+  }
+
   /** override */
   exportCrostiniContainer() {
     this.methodCalled('exportCrostiniContainer');
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index 1ef561c..a31338ea5 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -14,13 +14,13 @@
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -2182,11 +2182,11 @@
     ASSERT_TRUE(extension);
 
     AppLaunchParams params(browser()->profile(), extension->id(),
-                           extensions::LaunchContainer::kLaunchContainerNone,
+                           apps::mojom::LaunchContainer::kLaunchContainerNone,
                            WindowOpenDisposition::NEW_WINDOW,
-                           extensions::AppLaunchSource::kSourceTest);
+                           apps::mojom::AppLaunchSource::kSourceTest);
     params.command_line = *base::CommandLine::ForCurrentProcess();
-    OpenApplication(params);
+    apps::LaunchService::Get(browser()->profile())->OpenApplication(params);
   }
 
   void RunTests(const std::string& extension_dirname) {
diff --git a/chrome/test/remoting/remote_desktop_browsertest.cc b/chrome/test/remoting/remote_desktop_browsertest.cc
index 2b2ae90..a194610 100644
--- a/chrome/test/remoting/remote_desktop_browsertest.cc
+++ b/chrome/test/remoting/remote_desktop_browsertest.cc
@@ -12,10 +12,10 @@
 #include "base/json/json_reader.h"
 #include "base/macros.h"
 #include "base/path_service.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/unpacked_installer.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/webui/signin/login_ui_test_utils.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -224,11 +224,12 @@
     window_open_disposition = WindowOpenDisposition::NEW_WINDOW;
   }
 
-  OpenApplication(AppLaunchParams(
-      browser()->profile(), extension_->id(),
-      is_platform_app() ? extensions::LaunchContainer::kLaunchContainerNone
-                        : extensions::LaunchContainer::kLaunchContainerTab,
-      window_open_disposition, extensions::AppLaunchSource::kSourceTest));
+  apps::LaunchService::Get(browser()->profile())
+      ->OpenApplication(AppLaunchParams(
+          browser()->profile(), extension_->id(),
+          is_platform_app() ? apps::mojom::LaunchContainer::kLaunchContainerNone
+                            : apps::mojom::LaunchContainer::kLaunchContainerTab,
+          window_open_disposition, apps::mojom::AppLaunchSource::kSourceTest));
 
   observer.Wait();
 
diff --git a/chromecast/media/cma/backend/post_processor_factory.cc b/chromecast/media/cma/backend/post_processor_factory.cc
index a1e5fbe..c98fd4a3 100644
--- a/chromecast/media/cma/backend/post_processor_factory.cc
+++ b/chromecast/media/cma/backend/post_processor_factory.cc
@@ -23,7 +23,7 @@
 const char kV1SoCreateFunction[] = "AudioPostProcessorShlib_Create";
 const char kV2SoCreateFunction[] = "AudioPostProcessor2Shlib_Create";
 const char kPreferredLibraryPath[] = "/system/chrome/lib/processors";
-const char kPreferredOemLibraryPath[] = "/oem_cast_shlibs/processors";
+const char kPreferredOemLibraryPath[] = "/oem_cast_shlib/processors";
 
 base::FilePath FindLibrary(const std::string& library_name) {
   base::FilePath relative_path(library_name);
diff --git a/chromecast/media/cma/backend/post_processor_factory.h b/chromecast/media/cma/backend/post_processor_factory.h
index b53de24..afc5584 100644
--- a/chromecast/media/cma/backend/post_processor_factory.h
+++ b/chromecast/media/cma/backend/post_processor_factory.h
@@ -38,7 +38,7 @@
   // Creates an instance of AudioPostProcessor2 or a wrapped AudioPostProcessor.
   // By default, will attempt to find the library in
   // /system/chrome/lib/processors/|library_name|. Will fall back to
-  // searching for /oem_cast_shlibs/processors/|library_name|, and finally
+  // searching for /oem_cast_shlib/processors/|library_name|, and finally
   // searching for |library_name| in LD_LIBRARY_PATH.
   std::unique_ptr<AudioPostProcessor2> CreatePostProcessor(
       const std::string& library_name,
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index ec1ecad6..4679bea 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -609,22 +609,6 @@
 }
 
 // static
-void AutofillMetrics::LogUploadDisallowedForNetworkMetric(
-    const std::string& network) {
-  UploadDisallowedForNetworkMetric metric;
-  if (network == kEloCard) {
-    metric = DISALLOWED_ELO;
-  } else if (network == kJCBCard) {
-    metric = DISALLOWED_JCB;
-  } else {
-    NOTREACHED();
-    return;
-  }
-  UMA_HISTOGRAM_ENUMERATION("Autofill.CreditCardUploadDisallowedForNetwork",
-                            metric);
-}
-
-// static
 void AutofillMetrics::LogUploadOfferedCardOriginMetric(
     UploadOfferedCardOriginMetric metric) {
   DCHECK_LT(metric, NUM_UPLOAD_OFFERED_CARD_ORIGIN_METRICS);
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 4429de73..98dc224 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -197,14 +197,6 @@
     kMaxValue = SERVER,
   };
 
-  // Metric to measure volume of cards that are disallowed for upload by their
-  // network, most likely due to their network being blocked by Google Payments.
-  enum UploadDisallowedForNetworkMetric {
-    DISALLOWED_ELO = 0,
-    DISALLOWED_JCB = 1,
-    kMaxValue = DISALLOWED_JCB,
-  };
-
   // Metric to measure if a card for which upload was offered is already stored
   // as a local card on the device or if it has not yet been seen.
   enum UploadOfferedCardOriginMetric {
@@ -916,10 +908,6 @@
   static void LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
       SaveTypeMetric metric);
 
-  // When credit card upload is disallowed for a particular network, logs which
-  // network was blocked.
-  static void LogUploadDisallowedForNetworkMetric(const std::string& network);
-
   // When credit card upload is offered, logs whether the card being offered is
   // already a local card on the device or not.
   static void LogUploadOfferedCardOriginMetric(
diff --git a/components/autofill/core/browser/form_data_importer.cc b/components/autofill/core/browser/form_data_importer.cc
index de81a38..5f473ea5 100644
--- a/components/autofill/core/browser/form_data_importer.cc
+++ b/components/autofill/core/browser/form_data_importer.cc
@@ -155,21 +155,6 @@
   if (!imported_credit_card)
     return;
 
-  // Check if the imported card is from a network that is currently disallowed,
-  // often due to Google Payments not accepting a certain card network. If so,
-  // don't offer to upload the card. We can fall back to local save as long as
-  // it's a new card instead of an existing local card.
-  if (!credit_card_save_manager_->IsUploadEnabledForNetwork(
-          imported_credit_card->network())) {
-    is_credit_card_upstream_enabled = false;
-    AutofillMetrics::LogUploadDisallowedForNetworkMetric(
-        imported_credit_card->network());
-    if (imported_credit_card_record_type_ ==
-        ImportedCreditCardRecordType::LOCAL_CARD) {
-      return;
-    }
-  }
-
   // We have a card to save; decide what type of save flow to display.
   if (is_credit_card_upstream_enabled) {
     // Attempt to offer upload save. Because we pass
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager.cc b/components/autofill/core/browser/payments/credit_card_save_manager.cc
index 8e6d097..2be0432 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager.cc
@@ -276,19 +276,6 @@
       personal_data_manager_->GetSyncSigninState(), client_->GetLogManager());
 }
 
-bool CreditCardSaveManager::IsUploadEnabledForNetwork(
-    const std::string& network) {
-  if (network == kEloCard &&
-      base::FeatureList::IsEnabled(features::kAutofillUpstreamDisallowElo)) {
-    return false;
-  } else if (network == kJCBCard &&
-             base::FeatureList::IsEnabled(
-                 features::kAutofillUpstreamDisallowJcb)) {
-    return false;
-  }
-  return true;
-}
-
 void CreditCardSaveManager::OnDidUploadCard(
     AutofillClient::PaymentsRpcResult result,
     const std::string& server_id) {
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager.h b/components/autofill/core/browser/payments/credit_card_save_manager.h
index 7154e7f..0fe7b67 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager.h
+++ b/components/autofill/core/browser/payments/credit_card_save_manager.h
@@ -120,11 +120,6 @@
   // are satisfied.
   virtual bool IsCreditCardUploadEnabled();
 
-  // Returns true if the given |network| is allowed for upload to Google
-  // Payments, false otherwise. Mainly used for blacklisting upload of certain
-  // networks.
-  bool IsUploadEnabledForNetwork(const std::string& network);
-
   // For testing.
   void SetAppLocale(std::string app_locale) { app_locale_ = app_locale; }
 
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
index 123c9bc..3a2b87a 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
@@ -75,9 +75,6 @@
 const base::Time kArbitraryTime = base::Time::FromDoubleT(25);
 const base::Time kMuchLaterTime = base::Time::FromDoubleT(5000);
 
-const char kEloCardNumber[] = "5067111111111112";
-const char kJcbCardNumber[] = "3528111111111110";
-
 std::string NextYear() {
   base::Time::Exploded now;
   base::Time::Now().LocalExplode(&now);
@@ -4157,165 +4154,6 @@
             payments_client_->upload_card_source_in_request());
 }
 
-TEST_F(CreditCardSaveManagerTest, UploadCreditCard_EloDisallowed) {
-  scoped_feature_list_.InitAndEnableFeature(
-      features::kAutofillUpstreamDisallowElo);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16(kEloCardNumber);
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  // With Elo disallowed, local save should be offered and upload save should
-  // not.
-  FormSubmitted(credit_card_form);
-  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the correct histogram entry was logged.
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.CreditCardUploadDisallowedForNetwork",
-      AutofillMetrics::DISALLOWED_ELO, 1);
-}
-
-TEST_F(CreditCardSaveManagerTest, UploadCreditCard_EloAllowed) {
-  scoped_feature_list_.InitAndDisableFeature(
-      features::kAutofillUpstreamDisallowElo);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16(kEloCardNumber);
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  // With the feature flag off, the Elo card should be allowed to be uploaded as
-  // normal.
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that no histogram entry was logged.
-  histogram_tester.ExpectTotalCount(
-      "Autofill.CreditCardUploadDisallowedForNetwork", 0);
-}
-
-TEST_F(CreditCardSaveManagerTest, UploadCreditCard_JcbDisallowed) {
-  scoped_feature_list_.InitAndEnableFeature(
-      features::kAutofillUpstreamDisallowJcb);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16(kJcbCardNumber);
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  // With JCB disallowed, local save should be offered and upload save should
-  // not.
-  FormSubmitted(credit_card_form);
-  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the correct histogram entry was logged.
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.CreditCardUploadDisallowedForNetwork",
-      AutofillMetrics::DISALLOWED_JCB, 1);
-}
-
-TEST_F(CreditCardSaveManagerTest, UploadCreditCard_JcbAllowed) {
-  scoped_feature_list_.InitAndDisableFeature(
-      features::kAutofillUpstreamDisallowJcb);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16(kJcbCardNumber);
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  // With the feature flag off, the JCB card should be allowed to be uploaded as
-  // normal.
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that no histogram entry was logged.
-  histogram_tester.ExpectTotalCount(
-      "Autofill.CreditCardUploadDisallowedForNetwork", 0);
-}
-
-// We can't tell what network a card is until *after* FormDataImporter imports
-// it, making it possible to deny upload save for a pre-existing local card.
-// This test ensures that we do not offer local save (again) for the card that
-// FormDataImporter imported.
-TEST_F(CreditCardSaveManagerTest, UploadCreditCard_DisallowedLocalCard) {
-  scoped_feature_list_.InitAndEnableFeature(
-      features::kAutofillUpstreamDisallowElo);
-
-  // Add a local credit card that will match what we will enter below.
-  CreditCard local_card;
-  test::SetCreditCardInfo(&local_card, "Flo Master", kEloCardNumber,
-                          NextMonth().c_str(), NextYear().c_str(), "1");
-  local_card.set_record_type(CreditCard::LOCAL_CARD);
-  personal_data_.AddCreditCard(local_card);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16(kEloCardNumber);
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  // The card is disallowed, but because it is already a local card, local save
-  // should not be offered again.
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the correct histogram entry was logged.
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.CreditCardUploadDisallowedForNetwork",
-      AutofillMetrics::DISALLOWED_ELO, 1);
-}
-
 // Tests that a card with some strikes (but not max strikes) should still show
 // the save bubble/infobar.
 TEST_F(CreditCardSaveManagerTest,
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index c2a8427..cd6984b 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -142,14 +142,6 @@
     "AutofillUpstreamBlankCardholderNameField",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Controls whether ELO cards should be uploaded to Google Payments.
-const base::Feature kAutofillUpstreamDisallowElo{
-    "AutofillUpstreamDisallowElo", base::FEATURE_DISABLED_BY_DEFAULT};
-
-// Controls whether JCB cards should be uploaded to Google Payments.
-const base::Feature kAutofillUpstreamDisallowJcb{
-    "AutofillUpstreamDisallowJcb", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // If enabled, Chrome Upstream can request the user to enter/confirm cardholder
 // name in the offer-to-save bubble if it was not detected or was conflicting
 // during the checkout flow and the user is NOT a Google Payments customer.
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h
index 24f4b176..b07d5a8 100644
--- a/components/autofill/core/common/autofill_payments_features.h
+++ b/components/autofill/core/common/autofill_payments_features.h
@@ -38,8 +38,6 @@
 extern const base::Feature kAutofillUpstreamAllowAllEmailDomains;
 extern const base::Feature kAutofillUpstreamAlwaysRequestCardholderName;
 extern const base::Feature kAutofillUpstreamBlankCardholderNameField;
-extern const base::Feature kAutofillUpstreamDisallowElo;
-extern const base::Feature kAutofillUpstreamDisallowJcb;
 extern const base::Feature kAutofillUpstreamEditableCardholderName;
 extern const base::Feature kAutofillUpstreamEditableExpirationDate;
 
diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
index dc67564c..fed77bf 100644
--- a/components/content_settings/core/browser/content_settings_registry.cc
+++ b/components/content_settings/core/browser/content_settings_registry.cc
@@ -491,6 +491,16 @@
            ContentSettingsInfo::INHERIT_IF_LESS_PERMISSIVE,
            ContentSettingsInfo::PERSISTENT,
            ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY);
+
+  Register(CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+           "native-file-system-write-guard", CONTENT_SETTING_ASK,
+           WebsiteSettingsInfo::UNSYNCABLE, WhitelistedSchemes(),
+           ValidSettings(CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK),
+           WebsiteSettingsInfo::SINGLE_ORIGIN_ONLY_SCOPE,
+           WebsiteSettingsRegistry::DESKTOP,
+           ContentSettingsInfo::INHERIT_IF_LESS_PERMISSIVE,
+           ContentSettingsInfo::PERSISTENT,
+           ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY);
 }
 
 void ContentSettingsRegistry::Register(
diff --git a/components/content_settings/core/common/content_settings.cc b/components/content_settings/core/common/content_settings.cc
index f8c6e6a..5fc4d9f 100644
--- a/components/content_settings/core/common/content_settings.cc
+++ b/components/content_settings/core/common/content_settings.cc
@@ -78,6 +78,7 @@
     {CONTENT_SETTINGS_TYPE_WAKE_LOCK_SCREEN, 54},
     {CONTENT_SETTINGS_TYPE_WAKE_LOCK_SYSTEM, 55},
     {CONTENT_SETTINGS_TYPE_LEGACY_COOKIE_ACCESS, 56},
+    {CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD, 57},
 };
 
 }  // namespace
diff --git a/components/content_settings/core/common/content_settings_types.h b/components/content_settings/core/common/content_settings_types.h
index 8f7b2004d..8bb5011 100644
--- a/components/content_settings/core/common/content_settings_types.h
+++ b/components/content_settings/core/common/content_settings_types.h
@@ -167,6 +167,11 @@
   // in cookie handling are introduced.
   CONTENT_SETTINGS_TYPE_LEGACY_COOKIE_ACCESS,
 
+  // Content settings which stores whether to allow sites to ask for permission
+  // to save changes to an original file selected by the user through the Native
+  // File System API.
+  CONTENT_SETTINGS_TYPE_NATIVE_FILE_SYSTEM_WRITE_GUARD,
+
   CONTENT_SETTINGS_NUM_TYPES,
 };
 
diff --git a/components/cronet/native/generated/cronet.idl_c.h b/components/cronet/native/generated/cronet.idl_c.h
index cf74264..80a9efe 100644
--- a/components/cronet/native/generated/cronet.idl_c.h
+++ b/components/cronet/native/generated/cronet.idl_c.h
@@ -163,7 +163,7 @@
 // Concrete interface Cronet_Buffer.
 
 // Create an instance of Cronet_Buffer.
-CRONET_EXPORT Cronet_BufferPtr Cronet_Buffer_Create();
+CRONET_EXPORT Cronet_BufferPtr Cronet_Buffer_Create(void);
 // Destroy an instance of Cronet_Buffer.
 CRONET_EXPORT void Cronet_Buffer_Destroy(Cronet_BufferPtr self);
 // Set and get app-specific Cronet_ClientContext.
@@ -291,7 +291,7 @@
 // Concrete interface Cronet_Engine.
 
 // Create an instance of Cronet_Engine.
-CRONET_EXPORT Cronet_EnginePtr Cronet_Engine_Create();
+CRONET_EXPORT Cronet_EnginePtr Cronet_Engine_Create(void);
 // Destroy an instance of Cronet_Engine.
 CRONET_EXPORT void Cronet_Engine_Destroy(Cronet_EnginePtr self);
 // Set and get app-specific Cronet_ClientContext.
@@ -485,7 +485,7 @@
 // Concrete interface Cronet_UploadDataSink.
 
 // Create an instance of Cronet_UploadDataSink.
-CRONET_EXPORT Cronet_UploadDataSinkPtr Cronet_UploadDataSink_Create();
+CRONET_EXPORT Cronet_UploadDataSinkPtr Cronet_UploadDataSink_Create(void);
 // Destroy an instance of Cronet_UploadDataSink.
 CRONET_EXPORT void Cronet_UploadDataSink_Destroy(Cronet_UploadDataSinkPtr self);
 // Set and get app-specific Cronet_ClientContext.
@@ -584,7 +584,7 @@
 // Concrete interface Cronet_UrlRequest.
 
 // Create an instance of Cronet_UrlRequest.
-CRONET_EXPORT Cronet_UrlRequestPtr Cronet_UrlRequest_Create();
+CRONET_EXPORT Cronet_UrlRequestPtr Cronet_UrlRequest_Create(void);
 // Destroy an instance of Cronet_UrlRequest.
 CRONET_EXPORT void Cronet_UrlRequest_Destroy(Cronet_UrlRequestPtr self);
 // Set and get app-specific Cronet_ClientContext.
@@ -688,7 +688,7 @@
 
 ///////////////////////
 // Struct Cronet_Error.
-CRONET_EXPORT Cronet_ErrorPtr Cronet_Error_Create();
+CRONET_EXPORT Cronet_ErrorPtr Cronet_Error_Create(void);
 CRONET_EXPORT void Cronet_Error_Destroy(Cronet_ErrorPtr self);
 // Cronet_Error setters.
 CRONET_EXPORT
@@ -721,7 +721,7 @@
 
 ///////////////////////
 // Struct Cronet_QuicHint.
-CRONET_EXPORT Cronet_QuicHintPtr Cronet_QuicHint_Create();
+CRONET_EXPORT Cronet_QuicHintPtr Cronet_QuicHint_Create(void);
 CRONET_EXPORT void Cronet_QuicHint_Destroy(Cronet_QuicHintPtr self);
 // Cronet_QuicHint setters.
 CRONET_EXPORT
@@ -742,7 +742,7 @@
 
 ///////////////////////
 // Struct Cronet_PublicKeyPins.
-CRONET_EXPORT Cronet_PublicKeyPinsPtr Cronet_PublicKeyPins_Create();
+CRONET_EXPORT Cronet_PublicKeyPinsPtr Cronet_PublicKeyPins_Create(void);
 CRONET_EXPORT void Cronet_PublicKeyPins_Destroy(Cronet_PublicKeyPinsPtr self);
 // Cronet_PublicKeyPins setters.
 CRONET_EXPORT
@@ -778,7 +778,7 @@
 
 ///////////////////////
 // Struct Cronet_EngineParams.
-CRONET_EXPORT Cronet_EngineParamsPtr Cronet_EngineParams_Create();
+CRONET_EXPORT Cronet_EngineParamsPtr Cronet_EngineParams_Create(void);
 CRONET_EXPORT void Cronet_EngineParams_Destroy(Cronet_EngineParamsPtr self);
 // Cronet_EngineParams setters.
 CRONET_EXPORT
@@ -885,7 +885,7 @@
 
 ///////////////////////
 // Struct Cronet_HttpHeader.
-CRONET_EXPORT Cronet_HttpHeaderPtr Cronet_HttpHeader_Create();
+CRONET_EXPORT Cronet_HttpHeaderPtr Cronet_HttpHeader_Create(void);
 CRONET_EXPORT void Cronet_HttpHeader_Destroy(Cronet_HttpHeaderPtr self);
 // Cronet_HttpHeader setters.
 CRONET_EXPORT
@@ -902,7 +902,7 @@
 
 ///////////////////////
 // Struct Cronet_UrlResponseInfo.
-CRONET_EXPORT Cronet_UrlResponseInfoPtr Cronet_UrlResponseInfo_Create();
+CRONET_EXPORT Cronet_UrlResponseInfoPtr Cronet_UrlResponseInfo_Create(void);
 CRONET_EXPORT void Cronet_UrlResponseInfo_Destroy(
     Cronet_UrlResponseInfoPtr self);
 // Cronet_UrlResponseInfo setters.
@@ -982,7 +982,7 @@
 
 ///////////////////////
 // Struct Cronet_UrlRequestParams.
-CRONET_EXPORT Cronet_UrlRequestParamsPtr Cronet_UrlRequestParams_Create();
+CRONET_EXPORT Cronet_UrlRequestParamsPtr Cronet_UrlRequestParams_Create(void);
 CRONET_EXPORT void Cronet_UrlRequestParams_Destroy(
     Cronet_UrlRequestParamsPtr self);
 // Cronet_UrlRequestParams setters.
@@ -1071,7 +1071,7 @@
 
 ///////////////////////
 // Struct Cronet_DateTime.
-CRONET_EXPORT Cronet_DateTimePtr Cronet_DateTime_Create();
+CRONET_EXPORT Cronet_DateTimePtr Cronet_DateTime_Create(void);
 CRONET_EXPORT void Cronet_DateTime_Destroy(Cronet_DateTimePtr self);
 // Cronet_DateTime setters.
 CRONET_EXPORT
@@ -1082,7 +1082,7 @@
 
 ///////////////////////
 // Struct Cronet_Metrics.
-CRONET_EXPORT Cronet_MetricsPtr Cronet_Metrics_Create();
+CRONET_EXPORT Cronet_MetricsPtr Cronet_Metrics_Create(void);
 CRONET_EXPORT void Cronet_Metrics_Destroy(Cronet_MetricsPtr self);
 // Cronet_Metrics setters.
 CRONET_EXPORT
@@ -1225,7 +1225,8 @@
 
 ///////////////////////
 // Struct Cronet_RequestFinishedInfo.
-CRONET_EXPORT Cronet_RequestFinishedInfoPtr Cronet_RequestFinishedInfo_Create();
+CRONET_EXPORT Cronet_RequestFinishedInfoPtr
+Cronet_RequestFinishedInfo_Create(void);
 CRONET_EXPORT void Cronet_RequestFinishedInfo_Destroy(
     Cronet_RequestFinishedInfoPtr self);
 // Cronet_RequestFinishedInfo setters.
diff --git a/components/cronet/tools/generators/c_templates/module_c.h.tmpl b/components/cronet/tools/generators/c_templates/module_c.h.tmpl
index 231fb44f..8bccaece 100644
--- a/components/cronet/tools/generators/c_templates/module_c.h.tmpl
+++ b/components/cronet/tools/generators/c_templates/module_c.h.tmpl
@@ -81,7 +81,7 @@
 // Concrete interface {{interface_name}}.
 
 // Create an instance of {{interface_name}}.
-{{export_macro}} {{interface_name}}Ptr {{interface_name}}_Create();
+{{export_macro}} {{interface_name}}Ptr {{interface_name}}_Create(void);
 {%-   endif %}
 // Destroy an instance of {{interface_name}}.
 {{export_macro}} void {{interface_name}}_Destroy({{interface_name}}Ptr self);
@@ -149,7 +149,7 @@
 {% set struct_name = struct|get_name_for_kind %}
 ///////////////////////
 // Struct {{struct_name}}.
-{{export_macro}} {{struct_name}}Ptr {{struct_name}}_Create();
+{{export_macro}} {{struct_name}}Ptr {{struct_name}}_Create(void);
 {{export_macro}} void {{struct_name}}_Destroy({{struct_name}}Ptr self);
 // {{struct_name}} setters.
 {%- for packed_field in struct.packed.packed_fields_in_ordinal_order %}
diff --git a/components/download/internal/common/download_utils.cc b/components/download/internal/common/download_utils.cc
index b1c816f..6ee818d 100644
--- a/components/download/internal/common/download_utils.cc
+++ b/components/download/internal/common/download_utils.cc
@@ -253,7 +253,6 @@
   request->site_for_cookies = params->url();
   request->referrer = params->referrer();
   request->referrer_policy = params->referrer_policy();
-  request->allow_download = true;
   request->is_main_frame = true;
 
   // Downloads should be treated as navigations from Fetch spec perspective.
diff --git a/components/gwp_asan/buildflags/buildflags.gni b/components/gwp_asan/buildflags/buildflags.gni
index c6ad77d..9fe77972 100644
--- a/components/gwp_asan/buildflags/buildflags.gni
+++ b/components/gwp_asan/buildflags/buildflags.gni
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//base/allocator/allocator.gni")
 import("//build/config/allocator.gni")
 
 # Windows/x86 is disabled due to https://crbug.com/969146
diff --git a/components/gwp_asan/client/BUILD.gn b/components/gwp_asan/client/BUILD.gn
index 6769a8e3..0b7b81b 100644
--- a/components/gwp_asan/client/BUILD.gn
+++ b/components/gwp_asan/client/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//base/allocator/allocator.gni")
 import("//build/config/allocator.gni")
 
 component("client") {
diff --git a/components/safe_browsing/android/remote_database_manager.cc b/components/safe_browsing/android/remote_database_manager.cc
index 9976bfa..ddfe2e4 100644
--- a/components/safe_browsing/android/remote_database_manager.cc
+++ b/components/safe_browsing/android/remote_database_manager.cc
@@ -22,10 +22,6 @@
 
 using content::BrowserThread;
 
-namespace net {
-class URLRequestContextGetter;
-}  // namespace net
-
 namespace safe_browsing {
 
 namespace {
diff --git a/components/safe_browsing/browser/BUILD.gn b/components/safe_browsing/browser/BUILD.gn
index c075386b0..4af7415f2 100644
--- a/components/safe_browsing/browser/BUILD.gn
+++ b/components/safe_browsing/browser/BUILD.gn
@@ -13,8 +13,6 @@
     "mojo_safe_browsing_impl.h",
     "safe_browsing_url_checker_impl.cc",
     "safe_browsing_url_checker_impl.h",
-    "safe_browsing_url_request_context_getter.cc",
-    "safe_browsing_url_request_context_getter.h",
     "threat_details.cc",
     "threat_details.h",
     "threat_details_cache.cc",
diff --git a/components/safe_browsing/browser/safe_browsing_network_context.cc b/components/safe_browsing/browser/safe_browsing_network_context.cc
index 67e1f71..8d3caad 100644
--- a/components/safe_browsing/browser/safe_browsing_network_context.cc
+++ b/components/safe_browsing/browser/safe_browsing_network_context.cc
@@ -15,7 +15,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/network_service_instance.h"
 #include "net/net_buildflags.h"
-#include "net/url_request/url_request_context_getter.h"
 #include "services/network/network_context.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 
@@ -25,11 +24,9 @@
     : public network::SharedURLLoaderFactory {
  public:
   SharedURLLoaderFactory(
-      scoped_refptr<net::URLRequestContextGetter> request_context_getter,
       const base::FilePath& user_data_dir,
       NetworkContextParamsFactory network_context_params_factory)
-      : request_context_getter_(request_context_getter),
-        user_data_dir_(user_data_dir),
+      : user_data_dir_(user_data_dir),
         network_context_params_factory_(
             std::move(network_context_params_factory)) {}
 
@@ -37,7 +34,6 @@
     DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
     url_loader_factory_.reset();
     network_context_.reset();
-    request_context_getter_ = nullptr;
   }
 
   network::mojom::NetworkContext* GetNetworkContext() {
@@ -121,7 +117,6 @@
     return network_context_params;
   }
 
-  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
   base::FilePath user_data_dir_;
   NetworkContextParamsFactory network_context_params_factory_;
   network::mojom::NetworkContextPtr network_context_;
@@ -131,13 +126,11 @@
 };
 
 SafeBrowsingNetworkContext::SafeBrowsingNetworkContext(
-    scoped_refptr<net::URLRequestContextGetter> request_context_getter,
     const base::FilePath& user_data_dir,
     NetworkContextParamsFactory network_context_params_factory) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
   url_loader_factory_ = base::MakeRefCounted<SharedURLLoaderFactory>(
-      request_context_getter, user_data_dir,
-      std::move(network_context_params_factory));
+      user_data_dir, std::move(network_context_params_factory));
 }
 
 SafeBrowsingNetworkContext::~SafeBrowsingNetworkContext() {
diff --git a/components/safe_browsing/browser/safe_browsing_network_context.h b/components/safe_browsing/browser/safe_browsing_network_context.h
index 8d8bbee..5e57a5e 100644
--- a/components/safe_browsing/browser/safe_browsing_network_context.h
+++ b/components/safe_browsing/browser/safe_browsing_network_context.h
@@ -11,10 +11,6 @@
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/mojom/network_service.mojom.h"
 
-namespace net {
-class URLRequestContextGetter;
-}
-
 namespace network {
 namespace mojom {
 class NetworkContext;
@@ -26,19 +22,13 @@
 // This class owns the NetworkContext that is used for requests by Safe
 // Browsing.
 // All methods are called on the UI thread.
-// Note: temporarily this is wrapping SafeBrowsingURLRequestContextGetter,
-// however once all requests are converted to using the network service mojo
-// APIs we can delete SafeBrowsingURLRequestContextGetter and this object will
-// create the NetworkContext directly.  http://crbug.com/825242
 class SafeBrowsingNetworkContext {
  public:
-  // |request_context_getter| is used only if network service is disabled.
-  // Otherwise |user_dtaa_dir| and |network_context_params_factory| are used
+  // |user_data_dir| and |network_context_params_factory| are used
   // to construct a URLRequestContext through the network service.
   using NetworkContextParamsFactory =
       base::RepeatingCallback<network::mojom::NetworkContextParamsPtr()>;
   SafeBrowsingNetworkContext(
-      scoped_refptr<net::URLRequestContextGetter> request_context_getter,
       const base::FilePath& user_data_dir,
       NetworkContextParamsFactory network_context_params_factory);
   ~SafeBrowsingNetworkContext();
@@ -52,8 +42,7 @@
   // Flushes NetworkContext and URLLoaderFactory pipes.
   void FlushForTesting();
 
-  // Called at shutdown to ensure that the URLRequestContextGetter reference is
-  // destroyed..
+  // Called at shutdown to ensure that the URLLoaderFactory is cleaned up.
   void ServiceShuttingDown();
 
  private:
diff --git a/components/safe_browsing/browser/safe_browsing_url_request_context_getter.cc b/components/safe_browsing/browser/safe_browsing_url_request_context_getter.cc
deleted file mode 100644
index 30d22f1..0000000
--- a/components/safe_browsing/browser/safe_browsing_url_request_context_getter.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/safe_browsing/browser/safe_browsing_url_request_context_getter.h"
-
-#include "base/single_thread_task_runner.h"
-#include "base/task/post_task.h"
-#include "components/safe_browsing/common/safebrowsing_constants.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/cookie_store_factory.h"
-#include "net/cookies/cookie_store.h"
-#include "net/extras/sqlite/sqlite_persistent_cookie_store.h"
-#include "net/http/http_network_layer.h"
-#include "net/http/http_transaction_factory.h"
-#include "net/url_request/url_request_context.h"
-
-using content::BrowserThread;
-
-namespace safe_browsing {
-
-SafeBrowsingURLRequestContextGetter::SafeBrowsingURLRequestContextGetter(
-    scoped_refptr<net::URLRequestContextGetter> system_context_getter,
-    const base::FilePath& user_data_dir)
-    : shut_down_(false),
-      user_data_dir_(user_data_dir),
-      system_context_getter_(system_context_getter),
-      network_task_runner_(
-          base::CreateSingleThreadTaskRunner({BrowserThread::IO})) {
-  DCHECK(!user_data_dir.empty());
-  DCHECK(system_context_getter_);
-}
-
-net::URLRequestContext*
-SafeBrowsingURLRequestContextGetter::GetURLRequestContext() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  // Check if the service has been shut down.
-  if (shut_down_)
-    return nullptr;
-
-  if (!safe_browsing_request_context_) {
-    safe_browsing_request_context_.reset(new net::URLRequestContext());
-    safe_browsing_request_context_->CopyFrom(
-        system_context_getter_->GetURLRequestContext());
-    scoped_refptr<base::SequencedTaskRunner> background_task_runner =
-        base::CreateSequencedTaskRunner(
-            {base::ThreadPool(), base::MayBlock(),
-             net::GetCookieStoreBackgroundSequencePriority(),
-             base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
-
-    // Set up the CookieStore
-    content::CookieStoreConfig cookie_config(CookieFilePath(), false, false,
-                                             nullptr);
-    cookie_config.background_task_runner = background_task_runner;
-    safe_browsing_cookie_store_ =
-        content::CreateCookieStore(cookie_config, nullptr /* netlog */);
-    safe_browsing_request_context_->set_cookie_store(
-        safe_browsing_cookie_store_.get());
-
-    safe_browsing_request_context_->set_name("safe_browsing");
-  }
-
-  return safe_browsing_request_context_.get();
-}
-
-scoped_refptr<base::SingleThreadTaskRunner>
-SafeBrowsingURLRequestContextGetter::GetNetworkTaskRunner() const {
-  return network_task_runner_;
-}
-
-void SafeBrowsingURLRequestContextGetter::ServiceShuttingDown() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  shut_down_ = true;
-  URLRequestContextGetter::NotifyContextShuttingDown();
-  safe_browsing_request_context_.reset();
-}
-
-void SafeBrowsingURLRequestContextGetter::DisableQuicOnIOThread() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  // |http_network_session_| is only initialized once GetURLRequestContext() is
-  // (lazily) called. With most consumers shifting to use
-  // SafeBrowsingNetworkContext instead of this class directly, now on startup
-  // GetURLRequestContext() might not have been called yet. So expliclity call
-  // it to make sure http_network_session_ is initialized. Don't call it though
-  // if shutdown has already started.
-  if (!http_network_session_ && !shut_down_)
-    GetURLRequestContext();
-
-  if (http_network_session_)
-    http_network_session_->DisableQuic();
-}
-
-base::FilePath SafeBrowsingURLRequestContextGetter::GetBaseFilename() {
-  base::FilePath path(user_data_dir_);
-  return path.Append(kSafeBrowsingBaseFilename);
-}
-
-base::FilePath SafeBrowsingURLRequestContextGetter::CookieFilePath() {
-  return base::FilePath(GetBaseFilename().value() + kCookiesFile);
-}
-
-SafeBrowsingURLRequestContextGetter::~SafeBrowsingURLRequestContextGetter() {}
-
-}  // namespace safe_browsing
diff --git a/components/safe_browsing/browser/safe_browsing_url_request_context_getter.h b/components/safe_browsing/browser/safe_browsing_url_request_context_getter.h
deleted file mode 100644
index 9cc6a50..0000000
--- a/components/safe_browsing/browser/safe_browsing_url_request_context_getter.h
+++ /dev/null
@@ -1,64 +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 COMPONENTS_SAFE_BROWSING_BROWSER_SAFE_BROWSING_URL_REQUEST_CONTEXT_GETTER_H_
-#define COMPONENTS_SAFE_BROWSING_BROWSER_SAFE_BROWSING_URL_REQUEST_CONTEXT_GETTER_H_
-
-#include "base/files/file_path.h"
-#include "net/url_request/url_request_context_getter.h"
-
-namespace base {
-class SingleThreadTaskRunner;
-}
-
-namespace net {
-class CookieStore;
-class HttpNetworkSession;
-class HttpTransactionFactory;
-class URLRequestContext;
-}
-
-namespace safe_browsing {
-
-class SafeBrowsingURLRequestContextGetter
-    : public net::URLRequestContextGetter {
- public:
-  explicit SafeBrowsingURLRequestContextGetter(
-      scoped_refptr<net::URLRequestContextGetter> system_context_getter,
-      const base::FilePath& user_data_dir);
-
-  // Implementation for net::UrlRequestContextGetter.
-  net::URLRequestContext* GetURLRequestContext() override;
-  scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
-      const override;
-
-  // Shuts down any pending requests using the getter, and sets |shut_down_| to
-  // true.
-  void ServiceShuttingDown();
-
-  // Disables QUIC. This should not be necessary anymore when
-  // http://crbug.com/678653 is implemented.
-  void DisableQuicOnIOThread();
-
- protected:
-  ~SafeBrowsingURLRequestContextGetter() override;
-
- private:
-  base::FilePath GetBaseFilename();
-  base::FilePath CookieFilePath();
-
-  bool shut_down_;
-  base::FilePath user_data_dir_;
-
-  scoped_refptr<net::URLRequestContextGetter> system_context_getter_;
-  scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
-  std::unique_ptr<net::URLRequestContext> safe_browsing_request_context_;
-  std::unique_ptr<net::CookieStore> safe_browsing_cookie_store_;
-  std::unique_ptr<net::HttpNetworkSession> http_network_session_;
-  std::unique_ptr<net::HttpTransactionFactory> http_transaction_factory_;
-};
-
-}  // namespace safe_browsing
-
-#endif  // COMPONENTS_SAFE_BROWSING_BROWSER_SAFE_BROWSING_URL_REQUEST_CONTEXT_GETTER_H_
diff --git a/components/safe_browsing/db/v4_protocol_manager_util.cc b/components/safe_browsing/db/v4_protocol_manager_util.cc
index 59736f6..05b7933 100644
--- a/components/safe_browsing/db/v4_protocol_manager_util.cc
+++ b/components/safe_browsing/db/v4_protocol_manager_util.cc
@@ -16,6 +16,7 @@
 #include "google_apis/google_api_keys.h"
 #include "net/base/escape.h"
 #include "net/base/ip_address.h"
+#include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
 #include "url/url_util.h"
 
diff --git a/components/safe_browsing/db/v4_protocol_manager_util.h b/components/safe_browsing/db/v4_protocol_manager_util.h
index e5b34ad..2b7381a 100644
--- a/components/safe_browsing/db/v4_protocol_manager_util.h
+++ b/components/safe_browsing/db/v4_protocol_manager_util.h
@@ -22,7 +22,6 @@
 #include "base/strings/string_piece.h"
 #include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/db/safebrowsing.pb.h"
-#include "net/url_request/url_request_status.h"
 #include "url/gurl.h"
 
 namespace net {
diff --git a/components/safe_browsing/ping_manager.cc b/components/safe_browsing/ping_manager.cc
index 4cbb6881..f578f39 100644
--- a/components/safe_browsing/ping_manager.cc
+++ b/components/safe_browsing/ping_manager.cc
@@ -17,10 +17,6 @@
 #include "net/base/escape.h"
 #include "net/base/load_flags.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_request_context.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "net/url_request/url_request_status.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "url/gurl.h"
 
@@ -85,8 +81,6 @@
 
 PingManager::~PingManager() {}
 
-// net::URLFetcherDelegate implementation ----------------------------------
-
 // All SafeBrowsing request responses are handled here.
 void PingManager::OnURLLoaderComplete(
     network::SimpleURLLoader* source,
diff --git a/components/security_interstitials_strings.grdp b/components/security_interstitials_strings.grdp
index ce7e2efa..33e9121 100644
--- a/components/security_interstitials_strings.grdp
+++ b/components/security_interstitials_strings.grdp
@@ -248,7 +248,7 @@
     Privacy policy
   </message>
   <message name="IDS_SAFE_BROWSING_SCOUT_REPORTING_AGREE" desc="SafeBrowsing Scout label next to checkbox">
-    Help improve Safe Browsing by sending some <ph name="BEGIN_WHITEPAPER_LINK">&lt;a href="#" id="whitepaper-link"&gt;</ph>system information and page content<ph name="END_WHITEPAPER_LINK">&lt;/a&gt;</ph> to Google. <ph name="PRIVACY_PAGE_LINK">$1</ph>
+    Help improve Chrome security by sending <ph name="BEGIN_WHITEPAPER_LINK">&lt;a href="#" id="whitepaper-link"&gt;</ph>URLs of some pages you visit, limited system information, and some page content<ph name="END_WHITEPAPER_LINK">&lt;/a&gt;</ph> to Google. <ph name="PRIVACY_PAGE_LINK">$1</ph>
   </message>
 
   <!-- Harmful download interstitial -->
diff --git a/components/security_interstitials_strings_grdp/IDS_SAFE_BROWSING_SCOUT_REPORTING_AGREE.png.sha1 b/components/security_interstitials_strings_grdp/IDS_SAFE_BROWSING_SCOUT_REPORTING_AGREE.png.sha1
new file mode 100644
index 0000000..7b0c883
--- /dev/null
+++ b/components/security_interstitials_strings_grdp/IDS_SAFE_BROWSING_SCOUT_REPORTING_AGREE.png.sha1
@@ -0,0 +1 @@
+540ac19f59fd8051492e34fb594618d0c368f934
\ No newline at end of file
diff --git a/components/viz/host/gpu_host_impl.cc b/components/viz/host/gpu_host_impl.cc
index acdcfb5..6ef2f1f 100644
--- a/components/viz/host/gpu_host_impl.cc
+++ b/components/viz/host/gpu_host_impl.cc
@@ -272,6 +272,7 @@
 }
 
 #if defined(USE_OZONE)
+
 void GpuHostImpl::InitOzone() {
   // Ozone needs to send the primary DRM device to GPU service as early as
   // possible to ensure the latter always has a valid device.
@@ -280,9 +281,8 @@
   // The Ozone/Wayland requires mojo communication to be established to be
   // functional with a separate gpu process. Thus, using the PlatformProperties,
   // check if there is such a requirement.
-  if (features::IsOzoneDrmMojo() || ui::OzonePlatform::EnsureInstance()
-                                        ->GetPlatformProperties()
-                                        .requires_mojo) {
+  if (features::IsOzoneDrmMojo() ||
+      ui::OzonePlatform::GetInstance()->GetPlatformProperties().requires_mojo) {
     // TODO(rjkroege): Remove the legacy IPC code paths when no longer
     // necessary. https://crbug.com/806092
     auto interface_binder = base::BindRepeating(&GpuHostImpl::BindInterface,
diff --git a/components/viz/service/display/display_unittest.cc b/components/viz/service/display/display_unittest.cc
index e2f3b6f..b286ebfe 100644
--- a/components/viz/service/display/display_unittest.cc
+++ b/components/viz/service/display/display_unittest.cc
@@ -3409,9 +3409,12 @@
     EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
     UpdateBeginFrameTime(support_.get(), frame_time);
     submit_frame();
-    // Immediately after submitting frame, because there is presentation
-    // feedback queued up, ShouldSendBeginFrame should always return true.
-    EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
+    // If we've submitted more than one frame without drawing, there will
+    // always be presentation feedback so ShouldSendBeginFrame should always
+    // return true.
+    if (i > 0)
+      EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
+
     // Clear the presentation feedbacks.
     UpdateBeginFrameTime(support_.get(), frame_time);
   }
@@ -3564,9 +3567,11 @@
     UpdateBeginFrameTime(sub_support.get(), frame_time);
     sub_support->SubmitCompositorFrame(sub_local_surface_id,
                                        MakeDefaultCompositorFrame());
-    // Immediately after submitting frame, because there is presentation
-    // feedback queued up, ShouldSendBeginFrame should always return true.
-    EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
+    // If we've submitted more than one frame without drawing, there will
+    // always be presentation feedback so ShouldSendBeginFrame should always
+    // return true.
+    if (i > 0)
+      EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
     // Clear the presentation feedbacks.
     UpdateBeginFrameTime(sub_support.get(), frame_time);
   }
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index a4f0b5b..b6d0075 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -14,6 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_timing_details_map.h"
 #include "components/viz/common/quads/compositor_frame.h"
@@ -67,7 +68,13 @@
   // exceeded, we throttle sBeginFrames to 1 per second. Limit must be at least
   // 1, as the relative ordering of renderer / browser frame submissions allows
   // us to have one outstanding undrawn frame under normal operation.
+#if defined(OS_ANDROID)
+  // TODO(ericrk): Revert this. Setting to 0 temporarily to test the impact of
+  // this change on a latency regression. See https://crbug.com/959048
+  static constexpr uint32_t kUndrawnFrameLimit = 0;
+#else
   static constexpr uint32_t kUndrawnFrameLimit = 3;
+#endif
 
   CompositorFrameSinkSupport(mojom::CompositorFrameSinkClient* client,
                              FrameSinkManagerImpl* frame_sink_manager,
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index 709ccfae..6ab839b 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -941,11 +941,11 @@
         }
       }
 #endif
+    }
 
-      if (force_in_process) {
-        // This must be called before creating the ServiceManagerContext.
-        ForceInProcessNetworkService(true);
-      }
+    if (force_in_process) {
+      // This must be called before creating the ServiceManagerContext.
+      ForceInProcessNetworkService(true);
     }
 
     discardable_shared_memory_manager_ =
diff --git a/content/app/strings/content_strings.grd b/content/app/strings/content_strings.grd
index 0a9b2b76..9dfcdc1 100644
--- a/content/app/strings/content_strings.grd
+++ b/content/app/strings/content_strings.grd
@@ -466,6 +466,10 @@
       <message name="IDS_AX_ROLE_SWITCH" desc="Accessibility role description for switch">
         switch
       </message>
+      <!-- https://w3c.github.io/html-aam/#el-input-tel -->
+      <message name="IDS_AX_ROLE_TELEPHONE" desc="Accessibility role description for telephone number input">
+        telephone
+      </message>
       <message name="IDS_AX_ROLE_WEB_AREA" desc="Accessibility role description for web area">
         HTML content
       </message>
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 577ee93..5bf01d1 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -1848,6 +1848,16 @@
     case ax::mojom::Role::kSearchBox:
       return content_client->GetLocalizedString(IDS_AX_ROLE_SEARCH_BOX);
 
+    case ax::mojom::Role::kTextField: {
+      std::string input_type;
+      if (data.GetStringAttribute(ax::mojom::StringAttribute::kInputType,
+                                  &input_type) &&
+          input_type == "tel") {
+        return content_client->GetLocalizedString(IDS_AX_ROLE_TELEPHONE);
+      }
+      return {};
+    }
+
     default:
       return {};
   }
diff --git a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
index 583a8f13..771845b 100644
--- a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
+++ b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
@@ -700,6 +700,41 @@
 }
 
 IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
+                       LocalizedRoleDescription) {
+  AccessibilityNotificationWaiter waiter(shell()->web_contents(),
+                                         ui::kAXModeComplete,
+                                         ax::mojom::Event::kLoadComplete);
+  GURL url(
+      "data:text/html,"
+      "<input>"
+      "<input type='tel'>");
+
+  NavigateToURL(shell(), url);
+  waiter.WaitForNotification();
+
+  BrowserAccessibility* root = GetManager()->GetRoot();
+  ASSERT_NE(nullptr, root);
+  ASSERT_EQ(1u, root->PlatformChildCount());
+
+  BrowserAccessibility* body = root->PlatformGetChild(0);
+  ASSERT_EQ(2u, body->PlatformChildCount());
+
+  auto TestLocalizedRoleDescription =
+      [body](int child_index,
+             const base::string16& expected_localized_role_description = {}) {
+        BrowserAccessibility* node = body->PlatformGetChild(child_index);
+        ASSERT_NE(nullptr, node);
+
+        EXPECT_EQ(expected_localized_role_description,
+                  node->GetLocalizedStringForRoleDescription());
+      };
+
+  // For testing purposes, assume we get en-US localized strings.
+  TestLocalizedRoleDescription(0, base::ASCIIToUTF16(""));
+  TestLocalizedRoleDescription(1, base::ASCIIToUTF16("telephone"));
+}
+
+IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
                        GetStyleNameAttributeAsLocalizedString) {
   AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                          ui::kAXModeComplete,
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 42714520..d506a1f4 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -1309,6 +1309,20 @@
   RunHtmlTest(FILE_PATH_LITERAL("heading.html"));
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityHidden) {
+  RunAriaTest(FILE_PATH_LITERAL("hidden.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityHiddenDescribedBy) {
+  RunAriaTest(FILE_PATH_LITERAL("hidden-described-by.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityHiddenLabeledBy) {
+  RunAriaTest(FILE_PATH_LITERAL("hidden-labelled-by.html"));
+}
+
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityHR) {
   RunHtmlTest(FILE_PATH_LITERAL("hr.html"));
 }
diff --git a/content/browser/appcache/appcache_request_handler_unittest.cc b/content/browser/appcache/appcache_request_handler_unittest.cc
index 3ba178b2c..dcf43e4a 100644
--- a/content/browser/appcache/appcache_request_handler_unittest.cc
+++ b/content/browser/appcache/appcache_request_handler_unittest.cc
@@ -23,7 +23,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/post_task.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/appcache/appcache.h"
@@ -34,15 +33,11 @@
 #include "content/browser/appcache/mock_appcache_policy.h"
 #include "content/browser/appcache/mock_appcache_service.h"
 #include "content/public/browser/browser_task_traits.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/base/net_errors.h"
-#include "net/base/request_priority.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_util.h"
-#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
-#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
@@ -68,24 +63,10 @@
     (this->*method)();
   }
 
-  static void SetUpTestCase() {
-    thread_bundle_ = std::make_unique<TestBrowserThreadBundle>(
-        TestBrowserThreadBundle::REAL_IO_THREAD);
-    io_task_runner_ = base::CreateSingleThreadTaskRunner({BrowserThread::IO});
-  }
-
-  static void TearDownTestCase() {
-    thread_bundle_.reset();
-    io_task_runner_ = nullptr;
-  }
-
   // Test harness --------------------------------------------------
 
   AppCacheRequestHandlerTest() : host_(nullptr), request_(nullptr) {
     AppCacheRequestHandler::SetRunningInTests(true);
-    // TODO(http://crbug.com/824840): Enable NavigationLoaderOnUI for these
-    // tests.
-    feature_list_.InitWithFeatures({}, {features::kNavigationLoaderOnUI});
   }
 
   ~AppCacheRequestHandlerTest() override {
@@ -93,19 +74,17 @@
   }
 
   template <class Method>
-  void RunTestOnIOThread(Method method) {
-    test_finished_event_ = std::make_unique<base::WaitableEvent>(
-        base::WaitableEvent::ResetPolicy::AUTOMATIC,
-        base::WaitableEvent::InitialState::NOT_SIGNALED);
-    io_task_runner_->PostTask(
-        FROM_HERE,
+  void RunTestOnUIThread(Method method) {
+    base::RunLoop run_loop;
+    test_finished_cb_ = run_loop.QuitClosure();
+    base::PostTask(
+        FROM_HERE, {BrowserThread::UI},
         base::BindOnce(&AppCacheRequestHandlerTest::MethodWrapper<Method>,
                        base::Unretained(this), method));
-    test_finished_event_->Wait();
+    run_loop.Run();
   }
 
   void SetUpTest() {
-    DCHECK(io_task_runner_->BelongsToCurrentThread());
     mock_service_ = std::make_unique<MockAppCacheService>();
     mock_policy_ = std::make_unique<MockAppCachePolicy>();
     mock_service_->set_appcache_policy(mock_policy_.get());
@@ -120,13 +99,11 @@
   }
 
   void TearDownTest() {
-    DCHECK(io_task_runner_->BelongsToCurrentThread());
     if (appcache_url_loader_job_)
       appcache_url_loader_job_->DeleteIfNeeded();
     appcache_url_loader_job_.reset();
     handler_.reset();
     request_ = nullptr;
-    url_request_.reset();
     mock_service_.reset();
     mock_policy_.reset();
     host_remote_.reset();
@@ -136,7 +113,6 @@
   void TestFinished() {
     // We unwind the stack prior to finishing up to let stack
     // based objects get deleted.
-    DCHECK(io_task_runner_->BelongsToCurrentThread());
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(&AppCacheRequestHandlerTest::TestFinishedUnwound,
@@ -145,7 +121,7 @@
 
   void TestFinishedUnwound() {
     TearDownTest();
-    test_finished_event_->Signal();
+    std::move(test_finished_cb_).Run();
   }
 
   void PushNextTask(base::OnceClosure task) {
@@ -153,7 +129,6 @@
   }
 
   void ScheduleNextTask() {
-    DCHECK(io_task_runner_->BelongsToCurrentThread());
     if (task_stack_.empty()) {
       TestFinished();
       return;
@@ -730,100 +705,89 @@
   }
 
   // Data members --------------------------------------------------
+  TestBrowserThreadBundle thread_bundle_;
 
-  std::unique_ptr<base::WaitableEvent> test_finished_event_;
+  base::OnceClosure test_finished_cb_;
   base::stack<base::OnceClosure> task_stack_;
   std::unique_ptr<MockAppCacheService> mock_service_;
   std::unique_ptr<MockAppCachePolicy> mock_policy_;
   AppCacheHost* host_;
   mojo::Remote<blink::mojom::AppCacheHost> host_remote_;
   AppCacheRequest* request_;
-  std::unique_ptr<net::URLRequest> url_request_;
   std::unique_ptr<AppCacheRequestHandler> handler_;
   base::WeakPtr<AppCacheURLLoaderJob> appcache_url_loader_job_;
-
-  static std::unique_ptr<TestBrowserThreadBundle> thread_bundle_;
-  static scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
-
-  base::test::ScopedFeatureList feature_list_;
 };
 
-// static
-std::unique_ptr<TestBrowserThreadBundle>
-    AppCacheRequestHandlerTest::thread_bundle_;
-scoped_refptr<base::SingleThreadTaskRunner>
-    AppCacheRequestHandlerTest::io_task_runner_;
-
 TEST_F(AppCacheRequestHandlerTest, MainResource_Miss) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_Miss);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::MainResource_Miss);
 }
 
 TEST_F(AppCacheRequestHandlerTest, MainResource_Hit) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_Hit);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::MainResource_Hit);
 }
 
 TEST_F(AppCacheRequestHandlerTest, MainResource_Fallback) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_Fallback);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::MainResource_Fallback);
 }
 
 TEST_F(AppCacheRequestHandlerTest, MainResource_FallbackOverride) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_FallbackOverride);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::MainResource_FallbackOverride);
 }
 
 TEST_F(AppCacheRequestHandlerTest, SubResource_Miss_WithNoCacheSelected) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheRequestHandlerTest::SubResource_Miss_WithNoCacheSelected);
 }
 
 TEST_F(AppCacheRequestHandlerTest, SubResource_Miss_WithCacheSelected) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheRequestHandlerTest::SubResource_Miss_WithCacheSelected);
 }
 
 TEST_F(AppCacheRequestHandlerTest, SubResource_Miss_WithWaitForCacheSelection) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheRequestHandlerTest::SubResource_Miss_WithWaitForCacheSelection);
 }
 
 TEST_F(AppCacheRequestHandlerTest, SubResource_Hit) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::SubResource_Hit);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::SubResource_Hit);
 }
 
 TEST_F(AppCacheRequestHandlerTest, SubResource_RedirectFallback) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::SubResource_RedirectFallback);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::SubResource_RedirectFallback);
 }
 
 TEST_F(AppCacheRequestHandlerTest, SubResource_NoRedirectFallback) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheRequestHandlerTest::SubResource_NoRedirectFallback);
 }
 
 TEST_F(AppCacheRequestHandlerTest, SubResource_Network) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::SubResource_Network);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::SubResource_Network);
 }
 
 TEST_F(AppCacheRequestHandlerTest, DestroyedHost) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::DestroyedHost);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::DestroyedHost);
 }
 
 TEST_F(AppCacheRequestHandlerTest, DestroyedHostWithWaitingJob) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::DestroyedHostWithWaitingJob);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::DestroyedHostWithWaitingJob);
 }
 
 TEST_F(AppCacheRequestHandlerTest, DestroyedService) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::DestroyedService);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::DestroyedService);
 }
 
 TEST_F(AppCacheRequestHandlerTest, UnsupportedScheme) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::UnsupportedScheme);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::UnsupportedScheme);
 }
 
 TEST_F(AppCacheRequestHandlerTest, CanceledRequest) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::CanceledRequest);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::CanceledRequest);
 }
 
 TEST_F(AppCacheRequestHandlerTest, MainResource_Blocked) {
-  RunTestOnIOThread(&AppCacheRequestHandlerTest::MainResource_Blocked);
+  RunTestOnUIThread(&AppCacheRequestHandlerTest::MainResource_Blocked);
 }
 
 }  // namespace content
diff --git a/content/browser/appcache/appcache_storage_impl_unittest.cc b/content/browser/appcache/appcache_storage_impl_unittest.cc
index 0c55e06..cf4c1a9 100644
--- a/content/browser/appcache/appcache_storage_impl_unittest.cc
+++ b/content/browser/appcache/appcache_storage_impl_unittest.cc
@@ -23,7 +23,6 @@
 #include "base/stl_util.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/post_task.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
@@ -36,23 +35,17 @@
 #include "content/browser/appcache/appcache_service_impl.h"
 #include "content/browser/appcache/appcache_url_loader_request.h"
 #include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_task_traits.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/url_loader_interceptor.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/base/net_errors.h"
-#include "net/base/request_priority.h"
 #include "net/http/http_response_headers.h"
-#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
-#include "net/url_request/url_request_error_job.h"
-#include "net/url_request/url_request_job_factory_impl.h"
-#include "net/url_request/url_request_test_job.h"
-#include "net/url_request/url_request_test_util.h"
-#include "services/network/test/test_url_loader_factory.h"
 #include "services/network/test/test_utils.h"
 #include "sql/test/test_helpers.h"
 #include "storage/browser/quota/quota_manager.h"
@@ -78,12 +71,21 @@
   return GURL("http://mockhost/" + path);
 }
 
-std::unique_ptr<TestBrowserContext> browser_context;
 const int kProcessId = 1;
-std::unique_ptr<base::test::ScopedTaskEnvironment> scoped_task_environment;
-scoped_refptr<base::SingleThreadTaskRunner> io_runner;
 std::unique_ptr<base::Thread> background_thread;
 
+bool InterceptRequest(URLLoaderInterceptor::RequestParams* params) {
+  if (params->url_request.url == GetMockUrl("manifest")) {
+    URLLoaderInterceptor::WriteResponse("", "CACHE MANIFEST\n",
+                                        params->client.get());
+    return true;
+  } else if (params->url_request.url == GetMockUrl("empty.html")) {
+    URLLoaderInterceptor::WriteResponse("", "", params->client.get());
+    return true;
+  }
+  return false;
+}
+
 }  // namespace
 
 class AppCacheStorageImplTest : public testing::Test {
@@ -241,7 +243,7 @@
     FlushAllTasks();
 
     // We also have to wait for InitTask completion call to be performed
-    // on the IO thread prior to running the test. Its guaranteed to be
+    // on the UI thread prior to running the test. Its guaranteed to be
     // queued by this time.
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(&AppCacheStorageImplTest::RunMethod<Method>,
@@ -249,15 +251,6 @@
   }
 
   static void SetUpTestCase() {
-    scoped_task_environment = std::make_unique<TestBrowserThreadBundle>(
-        TestBrowserThreadBundle::REAL_IO_THREAD);
-
-    browser_context = std::make_unique<TestBrowserContext>();
-    ChildProcessSecurityPolicyImpl::GetInstance()->Add(kProcessId,
-                                                       browser_context.get());
-
-    io_runner = base::CreateSingleThreadTaskRunner({BrowserThread::IO});
-
     // We start the background thread as TYPE_IO because we also use the
     // db_thread for the disk_cache which needs to be of TYPE_IO.
     base::Thread::Options options(base::MessagePumpType::IO, 0);
@@ -267,44 +260,35 @@
   }
 
   static void TearDownTestCase() {
-    io_runner.reset();
     background_thread.reset();
-    ChildProcessSecurityPolicyImpl::GetInstance()->Remove(kProcessId);
-    browser_context.reset();
-    scoped_task_environment.reset();
   }
 
   // Test harness --------------------------------------------------
 
-  AppCacheStorageImplTest() {
-    auto head = network::CreateResourceResponseHead(net::HTTP_OK);
-    network::URLLoaderCompletionStatus status;
+  AppCacheStorageImplTest()
+      : interceptor_(base::BindRepeating(&InterceptRequest)),
+        weak_partition_factory_(static_cast<StoragePartitionImpl*>(
+            BrowserContext::GetDefaultStoragePartition(&browser_context_))) {
+    ChildProcessSecurityPolicyImpl::GetInstance()->Add(kProcessId,
+                                                       &browser_context_);
+  }
 
-    head.mime_type = "text/cache-manifest";
-    mock_url_loader_factory_.AddResponse(GetMockUrl("manifest"), head,
-                                         "CACHE MANIFEST\n", status);
-
-    head.mime_type = "text/html";
-    mock_url_loader_factory_.AddResponse(GetMockUrl("empty.html"), head, "",
-                                         status);
-    // TODO(http://crbug.com/824840): Enable NavigationLoaderOnUI for these
-    // tests.
-    feature_list_.InitWithFeatures({}, {features::kNavigationLoaderOnUI});
+  ~AppCacheStorageImplTest() override {
+    ChildProcessSecurityPolicyImpl::GetInstance()->Remove(kProcessId);
   }
 
   template <class Method>
-  void RunTestOnIOThread(Method method) {
+  void RunTestOnUIThread(Method method) {
     base::RunLoop run_loop;
     test_finished_cb_ = run_loop.QuitClosure();
-    io_runner->PostTask(
-        FROM_HERE,
+    base::PostTask(
+        FROM_HERE, {BrowserThread::UI},
         base::BindOnce(&AppCacheStorageImplTest::MethodWrapper<Method>,
                        base::Unretained(this), method));
     run_loop.Run();
   }
 
   void SetUpTest() {
-    DCHECK(io_runner->BelongsToCurrentThread());
     service_ = std::make_unique<AppCacheServiceImpl>(nullptr, nullptr);
     service_->Initialize(base::FilePath());
     mock_quota_manager_proxy_ = base::MakeRefCounted<MockQuotaManagerProxy>();
@@ -313,7 +297,6 @@
   }
 
   void TearDownTest() {
-    DCHECK(io_runner->BelongsToCurrentThread());
     scoped_refptr<base::SequencedTaskRunner> db_runner =
         storage()->db_task_runner_;
     storage()->CancelDelegateCallbacks(delegate());
@@ -332,7 +315,6 @@
   void TestFinished() {
     // We unwind the stack prior to finishing up to let stack
     // based objects get deleted.
-    DCHECK(io_runner->BelongsToCurrentThread());
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(&AppCacheStorageImplTest::TestFinishedUnwound,
                                   base::Unretained(this)));
@@ -348,7 +330,6 @@
   }
 
   void ScheduleNextTask() {
-    DCHECK(io_runner->BelongsToCurrentThread());
     if (task_stack_.empty()) {
       return;
     }
@@ -1623,11 +1604,8 @@
     }
 
     // Recreate the service to point at the db and corruption on disk.
-    service_ = std::make_unique<AppCacheServiceImpl>(nullptr, nullptr);
-    auto loader_factory_getter = base::MakeRefCounted<URLLoaderFactoryGetter>();
-    loader_factory_getter->SetNetworkFactoryForTesting(
-        &mock_url_loader_factory_, /* is_corb_enabled = */ true);
-    service_->set_url_loader_factory_getter(loader_factory_getter.get());
+    service_ = std::make_unique<AppCacheServiceImpl>(
+        nullptr, weak_partition_factory_.GetWeakPtr());
 
     service_->Initialize(temp_directory_.GetPath());
     mock_quota_manager_proxy_ = base::MakeRefCounted<MockQuotaManagerProxy>();
@@ -1799,6 +1777,7 @@
   }
 
   // Data members --------------------------------------------------
+  TestBrowserThreadBundle scoped_task_environment_;
 
   base::OnceClosure test_finished_cb_;
   base::stack<base::OnceClosure> task_stack_;
@@ -1814,13 +1793,14 @@
   mojo::Remote<blink::mojom::AppCacheHost> host_remote_;
 
   // Specifically for the Reinitalize test.
-  base::test::ScopedFeatureList feature_list_;
   base::ScopedTempDir temp_directory_;
   std::unique_ptr<MockServiceObserver> observer_;
   MockAppCacheFrontend frontend_;
   mojo::ReceiverSet<blink::mojom::AppCacheFrontend> frontend_receivers_;
   std::unique_ptr<AppCacheRequestHandler> handler_;
-  network::TestURLLoaderFactory mock_url_loader_factory_;
+  URLLoaderInterceptor interceptor_;
+  TestBrowserContext browser_context_;
+  base::WeakPtrFactory<StoragePartitionImpl> weak_partition_factory_;
 
   // Test data
   const base::Time kZeroTime;
@@ -1867,135 +1847,135 @@
 };
 
 TEST_F(AppCacheStorageImplTest, LoadCache_Miss) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::LoadCache_Miss);
+  RunTestOnUIThread(&AppCacheStorageImplTest::LoadCache_Miss);
 }
 
 TEST_F(AppCacheStorageImplTest, LoadCache_NearHit) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::LoadCache_NearHit);
+  RunTestOnUIThread(&AppCacheStorageImplTest::LoadCache_NearHit);
 }
 
 TEST_F(AppCacheStorageImplTest, CreateGroupInEmptyOrigin) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::CreateGroupInEmptyOrigin);
+  RunTestOnUIThread(&AppCacheStorageImplTest::CreateGroupInEmptyOrigin);
 }
 
 TEST_F(AppCacheStorageImplTest, CreateGroupInPopulatedOrigin) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::CreateGroupInPopulatedOrigin);
+  RunTestOnUIThread(&AppCacheStorageImplTest::CreateGroupInPopulatedOrigin);
 }
 
 TEST_F(AppCacheStorageImplTest, LoadGroupAndCache_FarHit) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::LoadGroupAndCache_FarHit);
+  RunTestOnUIThread(&AppCacheStorageImplTest::LoadGroupAndCache_FarHit);
 }
 
 TEST_F(AppCacheStorageImplTest, StoreNewGroup) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::StoreNewGroup);
+  RunTestOnUIThread(&AppCacheStorageImplTest::StoreNewGroup);
 }
 
 TEST_F(AppCacheStorageImplTest, StoreExistingGroup) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::StoreExistingGroup);
+  RunTestOnUIThread(&AppCacheStorageImplTest::StoreExistingGroup);
 }
 
 TEST_F(AppCacheStorageImplTest, StoreExistingGroupExistingCache) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::StoreExistingGroupExistingCache);
+  RunTestOnUIThread(&AppCacheStorageImplTest::StoreExistingGroupExistingCache);
 }
 
 TEST_F(AppCacheStorageImplTest, FailStoreGroup_SizeTooBig) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::FailStoreGroup_SizeTooBig);
+  RunTestOnUIThread(&AppCacheStorageImplTest::FailStoreGroup_SizeTooBig);
 }
 
 TEST_F(AppCacheStorageImplTest, FailStoreGroup_PaddingTooBig) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::FailStoreGroup_PaddingTooBig);
+  RunTestOnUIThread(&AppCacheStorageImplTest::FailStoreGroup_PaddingTooBig);
 }
 
 TEST_F(AppCacheStorageImplTest, MakeGroupObsolete) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::MakeGroupObsolete);
+  RunTestOnUIThread(&AppCacheStorageImplTest::MakeGroupObsolete);
 }
 
 TEST_F(AppCacheStorageImplTest, MarkEntryAsForeign) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::MarkEntryAsForeign);
+  RunTestOnUIThread(&AppCacheStorageImplTest::MarkEntryAsForeign);
 }
 
 TEST_F(AppCacheStorageImplTest, MarkEntryAsForeignWithLoadInProgress) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::MarkEntryAsForeignWithLoadInProgress);
 }
 
 TEST_F(AppCacheStorageImplTest, FindNoMainResponse) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::FindNoMainResponse);
+  RunTestOnUIThread(&AppCacheStorageImplTest::FindNoMainResponse);
 }
 
 TEST_F(AppCacheStorageImplTest, BasicFindMainResponseInDatabase) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::BasicFindMainResponseInDatabase);
+  RunTestOnUIThread(&AppCacheStorageImplTest::BasicFindMainResponseInDatabase);
 }
 
 TEST_F(AppCacheStorageImplTest, BasicFindMainResponseInWorkingSet) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::BasicFindMainResponseInWorkingSet);
 }
 
 TEST_F(AppCacheStorageImplTest, BasicFindMainFallbackResponseInDatabase) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::BasicFindMainFallbackResponseInDatabase);
 }
 
 TEST_F(AppCacheStorageImplTest, BasicFindMainFallbackResponseInWorkingSet) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::BasicFindMainFallbackResponseInWorkingSet);
 }
 
 TEST_F(AppCacheStorageImplTest, BasicFindMainInterceptResponseInDatabase) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::BasicFindMainInterceptResponseInDatabase);
 }
 
 TEST_F(AppCacheStorageImplTest, BasicFindMainInterceptResponseInWorkingSet) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::BasicFindMainInterceptResponseInWorkingSet);
 }
 
 TEST_F(AppCacheStorageImplTest, FindMainResponseWithMultipleHits) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::FindMainResponseWithMultipleHits);
+  RunTestOnUIThread(&AppCacheStorageImplTest::FindMainResponseWithMultipleHits);
 }
 
 TEST_F(AppCacheStorageImplTest, FindMainResponseExclusionsInDatabase) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::FindMainResponseExclusionsInDatabase);
 }
 
 TEST_F(AppCacheStorageImplTest, FindMainResponseExclusionsInWorkingSet) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::FindMainResponseExclusionsInWorkingSet);
 }
 
 TEST_F(AppCacheStorageImplTest, FindInterceptPatternMatchInWorkingSet) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::FindInterceptPatternMatchInWorkingSet);
 }
 
 TEST_F(AppCacheStorageImplTest, FindInterceptPatternMatchInDatabase) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::FindInterceptPatternMatchInDatabase);
 }
 
 TEST_F(AppCacheStorageImplTest, FindFallbackPatternMatchInWorkingSet) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::FindFallbackPatternMatchInWorkingSet);
 }
 
 TEST_F(AppCacheStorageImplTest, FindFallbackPatternMatchInDatabase) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheStorageImplTest::FindFallbackPatternMatchInDatabase);
 }
 
 TEST_F(AppCacheStorageImplTest, Reinitialize1) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::Reinitialize1);
+  RunTestOnUIThread(&AppCacheStorageImplTest::Reinitialize1);
 }
 
 TEST_F(AppCacheStorageImplTest, Reinitialize2) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::Reinitialize2);
+  RunTestOnUIThread(&AppCacheStorageImplTest::Reinitialize2);
 }
 
 TEST_F(AppCacheStorageImplTest, Reinitialize3) {
-  RunTestOnIOThread(&AppCacheStorageImplTest::Reinitialize3);
+  RunTestOnUIThread(&AppCacheStorageImplTest::Reinitialize3);
 }
 
 // That's all folks!
diff --git a/content/browser/appcache/appcache_update_job_unittest.cc b/content/browser/appcache/appcache_update_job_unittest.cc
index 84efbba..d0ad7a94 100644
--- a/content/browser/appcache/appcache_update_job_unittest.cc
+++ b/content/browser/appcache/appcache_update_job_unittest.cc
@@ -21,7 +21,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/post_task.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/appcache/appcache_group.h"
@@ -30,22 +29,18 @@
 #include "content/browser/appcache/appcache_update_url_loader_request.h"
 #include "content/browser/appcache/mock_appcache_service.h"
 #include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/storage_partition_impl.h"
 #include "content/browser/url_loader_factory_getter.h"
 #include "content/public/browser/browser_task_traits.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/url_loader_interceptor.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_util.h"
-#include "net/url_request/url_request_error_job.h"
-#include "net/url_request/url_request_job_factory_impl.h"
-#include "net/url_request/url_request_test_job.h"
-#include "net/url_request/url_request_test_util.h"
-#include "services/network/public/cpp/features.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -89,19 +84,6 @@
     return GURL("https://cross_origin_host/" + path);
   }
 
-  static net::URLRequestJob* JobFactory(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) {
-    if (request->url().host() != "mockhost" &&
-        request->url().host() != "cross_origin_host")
-      return new net::URLRequestErrorJob(request, network_delegate, -100);
-
-    std::string headers, body;
-    GetMockResponse(request->url().path(), &headers, &body);
-    return new net::URLRequestTestJob(request, network_delegate, headers, body,
-                                      true);
-  }
-
   static void GetMockResponse(const std::string& path,
                               std::string* headers,
                               std::string* body) {
@@ -238,16 +220,6 @@
   }
 };
 
-class MockHttpServerJobFactory
-    : public net::URLRequestJobFactory::ProtocolHandler {
- public:
-  net::URLRequestJob* MaybeCreateJob(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) const override {
-    return MockHttpServer::JobFactory(request, network_delegate);
-  }
-};
-
 inline bool operator==(const AppCacheNamespace& lhs,
                        const AppCacheNamespace& rhs) {
   return lhs.type == rhs.type && lhs.namespace_url == rhs.namespace_url &&
@@ -373,7 +345,7 @@
 };
 
 // Helper class to simulate a URL that returns retry or success.
-class RetryRequestTestJob : public net::URLRequestTestJob {
+class RetryRequestTestJob {
  public:
   enum RetryHeader {
     NO_RETRY_AFTER,
@@ -400,14 +372,6 @@
     expected_requests_ = 0;
   }
 
-  static net::URLRequestJob* RetryFactory(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) {
-    std::string headers;
-    GetResponseForURL(request->original_url(), &headers, nullptr);
-    return new RetryRequestTestJob(request, network_delegate, headers);
-  }
-
   static void GetResponseForURL(const GURL& url,
                                 std::string* headers,
                                 std::string* data) {
@@ -423,8 +387,6 @@
   }
 
  private:
-  ~RetryRequestTestJob() override {}
-
   static std::string retry_headers() {
     const char no_retry_after[] =
         "HTTP/1.1 503 BOO HOO\n"
@@ -463,15 +425,6 @@
         "http://retry\r");  // must be same as kRetryUrl
   }
 
-  RetryRequestTestJob(net::URLRequest* request,
-                      net::NetworkDelegate* network_delegate,
-                      const std::string& headers)
-      : net::URLRequestTestJob(request,
-                               network_delegate,
-                               headers,
-                               data(),
-                               true) {}
-
   static int num_requests_;
   static int num_retries_;
   static RetryHeader retry_after_;
@@ -486,7 +439,7 @@
 int RetryRequestTestJob::expected_requests_ = 0;
 
 // Helper class to check for certain HTTP headers.
-class HttpHeadersRequestTestJob : public net::URLRequestTestJob {
+class HttpHeadersRequestTestJob {
  public:
   // Call this at the start of each HTTP header-related test.
   static void Initialize(const std::string& expect_if_modified_since,
@@ -510,13 +463,6 @@
     already_checked_ = false;
   }
 
-  static net::URLRequestJob* IfModifiedSinceFactory(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) {
-    ValidateExtraHeaders(request->extra_request_headers());
-    return MockHttpServer::JobFactory(request, network_delegate);
-  }
-
   static void ValidateExtraHeaders(
       const net::HttpRequestHeaders& extra_headers) {
     if (already_checked_)
@@ -536,9 +482,6 @@
         header_value == expect_if_none_match_;
   }
 
- protected:
-  ~HttpHeadersRequestTestJob() override {}
-
  private:
   static std::string expect_if_modified_since_;
   static bool saw_if_modified_since_;
@@ -554,116 +497,52 @@
 bool HttpHeadersRequestTestJob::saw_if_none_match_ = false;
 bool HttpHeadersRequestTestJob::already_checked_ = false;
 
-class IfModifiedSinceJobFactory
-    : public net::URLRequestJobFactory::ProtocolHandler {
- public:
-  net::URLRequestJob* MaybeCreateJob(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) const override {
-    return HttpHeadersRequestTestJob::IfModifiedSinceFactory(request,
-                                                             network_delegate);
-  }
-};
-
-// Provides a test URLLoaderFactory which serves content using the
-// MockHttpServer and RetryRequestTestJob classes.
 // TODO(ananta/michaeln). Remove dependencies on URLRequest based
 // classes by refactoring the response headers/data into a common class.
-class MockURLLoaderFactory : public network::mojom::URLLoaderFactory {
- public:
-  MockURLLoaderFactory() {}
-
-  // network::mojom::URLLoaderFactory implementation.
-  void CreateLoaderAndStart(network::mojom::URLLoaderRequest request,
-                            int32_t routing_id,
-                            int32_t request_id,
-                            uint32_t options,
-                            const network::ResourceRequest& url_request,
-                            network::mojom::URLLoaderClientPtr client,
-                            const net::MutableNetworkTrafficAnnotationTag&
-                                traffic_annotation) override {
-    if (url_request.url.host() == "failme" ||
-        url_request.url.host() == "testme") {
-      client->OnComplete(network::URLLoaderCompletionStatus(-100));
-      return;
-    }
-
-    HttpHeadersRequestTestJob::ValidateExtraHeaders(url_request.headers);
-
-    std::string headers;
-    std::string body;
-    if (url_request.url == RetryRequestTestJob::kRetryUrl) {
-      RetryRequestTestJob::GetResponseForURL(url_request.url, &headers, &body);
-    } else {
-      MockHttpServer::GetMockResponse(url_request.url.path(), &headers, &body);
-    }
-
-    net::HttpResponseInfo info;
-    info.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
-        net::HttpUtil::AssembleRawHeaders(headers));
-
-    network::ResourceResponseHead response;
-    response.headers = info.headers;
-    response.headers->GetMimeType(&response.mime_type);
-
-    client->OnReceiveResponse(response);
-
-    mojo::DataPipe data_pipe;
-
-    uint32_t bytes_written = body.size();
-    data_pipe.producer_handle->WriteData(body.data(), &bytes_written,
-                                         MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
-    client->OnStartLoadingResponseBody(std::move(data_pipe.consumer_handle));
+bool InterceptRequest(URLLoaderInterceptor::RequestParams* params) {
+  const auto& url_request = params->url_request;
+  if (url_request.url.host() == "failme" ||
+      url_request.url.host() == "testme") {
+    params->client->OnComplete(network::URLLoaderCompletionStatus(-100));
+    return true;
   }
 
-  void Clone(network::mojom::URLLoaderFactoryRequest factory) override {
-    NOTREACHED();
+  HttpHeadersRequestTestJob::ValidateExtraHeaders(url_request.headers);
+
+  std::string headers;
+  std::string body;
+  if (url_request.url == RetryRequestTestJob::kRetryUrl) {
+    RetryRequestTestJob::GetResponseForURL(url_request.url, &headers, &body);
+  } else {
+    MockHttpServer::GetMockResponse(url_request.url.path(), &headers, &body);
   }
 
- private:
-  DISALLOW_COPY_AND_ASSIGN(MockURLLoaderFactory);
-};
+  net::HttpResponseInfo info;
+  info.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+      net::HttpUtil::AssembleRawHeaders(headers));
 
-class IOThread {
- public:
-  IOThread() {}
-  ~IOThread() {}
+  network::ResourceResponseHead response;
+  response.headers = info.headers;
+  response.headers->GetMimeType(&response.mime_type);
 
-  net::URLRequestContext* request_context() { return request_context_.get(); }
+  params->client->OnReceiveResponse(response);
 
-  void SetNewJobFactory(net::URLRequestJobFactory* job_factory) {
-    DCHECK(job_factory);
-    job_factory_.reset(job_factory);
-    request_context_->set_job_factory(job_factory_.get());
-  }
+  mojo::ScopedDataPipeProducerHandle producer_handle;
+  mojo::ScopedDataPipeConsumerHandle consumer_handle;
+  mojo::CreateDataPipe(nullptr, &producer_handle, &consumer_handle);
 
-  void Init() {
-    auto factory = std::make_unique<net::URLRequestJobFactoryImpl>();
-    factory->SetProtocolHandler("http",
-                                std::make_unique<MockHttpServerJobFactory>());
-    factory->SetProtocolHandler("https",
-                                std::make_unique<MockHttpServerJobFactory>());
-    job_factory_ = std::move(factory);
-    request_context_ = std::make_unique<net::TestURLRequestContext>();
-    request_context_->set_job_factory(job_factory_.get());
-  }
-
-  void CleanUp() {
-    request_context_.reset();
-    job_factory_.reset();
-  }
-
- private:
-  std::unique_ptr<net::URLRequestJobFactory> job_factory_;
-  std::unique_ptr<net::URLRequestContext> request_context_;
-};
+  uint32_t bytes_written = body.size();
+  producer_handle->WriteData(body.data(), &bytes_written,
+                             MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
+  params->client->OnStartLoadingResponseBody(std::move(consumer_handle));
+  return true;
+}
 
 class AppCacheUpdateJobTest : public testing::Test,
                               public AppCacheGroup::UpdateObserver {
  public:
   AppCacheUpdateJobTest()
-      : io_thread_(std::make_unique<IOThread>()),
-        do_checks_after_update_finished_(false),
+      : do_checks_after_update_finished_(false),
         expect_group_obsolete_(false),
         expect_group_has_cache_(false),
         expect_group_is_being_deleted_(false),
@@ -674,30 +553,10 @@
         expect_non_null_update_time_(false),
         tested_manifest_(NONE),
         tested_manifest_path_override_(nullptr),
-        thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD),
-        process_id_(123) {
-    // TODO(http://crbug.com/824840): Enable NavigationLoaderOnUI for these
-    // tests.
-    feature_list_.InitAndDisableFeature(features::kNavigationLoaderOnUI);
-    base::PostTask(
-        FROM_HERE, {BrowserThread::IO},
-        base::BindOnce(&IOThread::Init, base::Unretained(io_thread_.get())));
-
-    loader_factory_getter_ = base::MakeRefCounted<URLLoaderFactoryGetter>();
-    base::PostTask(FROM_HERE, {BrowserThread::IO},
-                   base::BindOnce(&AppCacheUpdateJobTest::InitializeFactory,
-                                  base::Unretained(this)));
-  }
-
-  ~AppCacheUpdateJobTest() override {
-    loader_factory_getter_ = nullptr;
-    // The TestBrowserThreadBundle dtor guarantees that all posted tasks are
-    // executed before the IO thread shuts down. It is safe to use the
-    // Unretained pointer here.
-    base::PostTask(
-        FROM_HERE, {BrowserThread::IO},
-        base::BindOnce(&IOThread::CleanUp, base::Unretained(io_thread_.get())));
-  }
+        weak_partition_factory_(static_cast<StoragePartitionImpl*>(
+            BrowserContext::GetDefaultStoragePartition(&browser_context_))),
+        interceptor_(base::BindRepeating(&InterceptRequest)),
+        process_id_(123) {}
 
   void SetUp() override {
     ChildProcessSecurityPolicyImpl::GetInstance()->Add(process_id_,
@@ -710,24 +569,15 @@
   // Use a separate IO thread to run a test. Thread will be destroyed
   // when it goes out of scope.
   template <class Method>
-  void RunTestOnIOThread(Method method) {
+  void RunTestOnUIThread(Method method) {
     base::RunLoop run_loop;
     test_completed_cb_ = run_loop.QuitClosure();
-    base::PostTask(FROM_HERE, {BrowserThread::IO},
+    base::PostTask(FROM_HERE, {BrowserThread::UI},
                    base::BindOnce(method, base::Unretained(this)));
     run_loop.Run();
   }
 
-  void InitializeFactory() {
-    if (!loader_factory_getter_.get())
-      return;
-    loader_factory_getter_->SetNetworkFactoryForTesting(
-        &mock_url_loader_factory_, /* is_corb_enabled = */ true);
-  }
-
   void StartCacheAttemptTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), GURL("http://failme"),
@@ -763,8 +613,6 @@
   }
 
   void StartUpgradeAttemptTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     {
       MakeService();
       group_ = base::MakeRefCounted<AppCacheGroup>(
@@ -841,8 +689,6 @@
   }
 
   void CacheAttemptFetchManifestFailTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), GURL("http://failme"),
@@ -866,8 +712,6 @@
   }
 
   void UpgradeFetchManifestFailTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/servererror"),
@@ -909,8 +753,6 @@
   }
 
   void ManifestRedirectTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), GURL("http://testme"),
@@ -934,8 +776,6 @@
   }
 
   void ManifestMissingMimeTypeTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(),
@@ -974,8 +814,6 @@
   }
 
   void ManifestNotFoundTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/nosuchfile"),
@@ -1012,8 +850,6 @@
   }
 
   void ManifestGoneTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/gone"),
@@ -1037,8 +873,6 @@
   }
 
   void CacheAttemptNotModifiedTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/notmodified"),
@@ -1062,8 +896,6 @@
   }
 
   void UpgradeNotModifiedTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/notmodified"),
@@ -1106,8 +938,6 @@
   }
 
   void UpgradeManifestDataUnchangedTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -1157,8 +987,6 @@
 
   // See http://code.google.com/p/chromium/issues/detail?id=95101
   void Bug95101Test() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/empty-manifest"),
@@ -1202,8 +1030,6 @@
   }
 
   void BasicCacheAttemptSuccessTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     GURL manifest_url = MockHttpServer::GetMockUrl("files/manifest1");
 
     MakeService();
@@ -1232,7 +1058,6 @@
 
   void DownloadInterceptEntriesTest() {
     // Ensures we download intercept entries too.
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
     GURL manifest_url =
         MockHttpServer::GetMockUrl("files/manifest-with-intercept");
     MakeService();
@@ -1258,8 +1083,6 @@
   }
 
   void BasicUpgradeSuccessTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -1331,8 +1154,6 @@
   }
 
   void UpgradeLoadFromNewestCacheTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -1401,8 +1222,6 @@
   }
 
   void UpgradeNoLoadFromNewestCacheTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -1469,8 +1288,6 @@
   }
 
   void UpgradeLoadFromNewestCacheVaryHeaderTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -1537,8 +1354,6 @@
   }
 
   void UpgradeLoadFromNewestCacheReuseVaryHeaderTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -1609,8 +1424,6 @@
   }
 
   void UpgradeSuccessMergedTypesTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(),
@@ -1671,8 +1484,6 @@
   }
 
   void CacheAttemptFailUrlFetchTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(),
@@ -1697,8 +1508,6 @@
   }
 
   void UpgradeFailUrlFetchTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(),
@@ -1746,8 +1555,6 @@
   }
 
   void UpgradeFailMasterUrlFetchTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     tested_manifest_path_override_ = "files/manifest1-with-notmodified";
 
     MakeService();
@@ -1856,8 +1663,6 @@
   }
 
   void EmptyManifestTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/empty-manifest"),
@@ -1906,8 +1711,6 @@
   }
 
   void EmptyFileTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(),
@@ -1945,8 +1748,6 @@
   }
 
   void RetryRequestTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Set some large number of times to return retry.
     // Expect 1 manifest fetch and 3 retries.
     RetryRequestTestJob::Initialize(5, RetryRequestTestJob::RETRY_AFTER_0, 4);
@@ -1974,8 +1775,6 @@
   }
 
   void RetryNoRetryAfterTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Set some large number of times to return retry.
     // Expect 1 manifest fetch and 0 retries.
     RetryRequestTestJob::Initialize(5, RetryRequestTestJob::NO_RETRY_AFTER, 1);
@@ -2003,8 +1802,6 @@
   }
 
   void RetryNonzeroRetryAfterTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Set some large number of times to return retry.
     // Expect 1 request and 0 retry attempts.
     RetryRequestTestJob::Initialize(5, RetryRequestTestJob::NONZERO_RETRY_AFTER,
@@ -2033,8 +1830,6 @@
   }
 
   void RetrySuccessTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Set 2 as the retry limit (does not exceed the max).
     // Expect 1 manifest fetch, 2 retries, 1 url fetch, 1 manifest refetch.
     RetryRequestTestJob::Initialize(2, RetryRequestTestJob::RETRY_AFTER_0, 5);
@@ -2062,8 +1857,6 @@
   }
 
   void RetryUrlTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Set 1 as the retry limit (does not exceed the max).
     // Expect 1 manifest fetch, 1 url fetch, 1 url retry, 1 manifest refetch.
     RetryRequestTestJob::Initialize(1, RetryRequestTestJob::RETRY_AFTER_0, 4);
@@ -2091,8 +1884,6 @@
   }
 
   void FailStoreNewestCacheTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     MockAppCacheStorage* storage =
         reinterpret_cast<MockAppCacheStorage*>(service_->storage());
@@ -2120,8 +1911,6 @@
   }
 
   void UpgradeFailStoreNewestCacheTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     MockAppCacheStorage* storage =
         reinterpret_cast<MockAppCacheStorage*>(service_->storage());
@@ -2175,8 +1964,6 @@
   }
 
   void MasterEntryFailStoreNewestCacheTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     MockAppCacheStorage* storage =
         reinterpret_cast<MockAppCacheStorage*>(service_->storage());
@@ -2222,8 +2009,6 @@
   }
 
   void UpgradeFailMakeGroupObsoleteTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     MockAppCacheStorage* storage =
         reinterpret_cast<MockAppCacheStorage*>(service_->storage());
@@ -2265,8 +2050,6 @@
   }
 
   void MasterEntryFetchManifestFailTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(service_->storage(),
                                                  GURL("http://failme"), 111);
@@ -2292,8 +2075,6 @@
   }
 
   void MasterEntryBadManifestTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/bad-manifest"),
@@ -2320,8 +2101,6 @@
   }
 
   void MasterEntryManifestNotFoundTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/nosuchfile"),
@@ -2349,8 +2128,6 @@
   }
 
   void MasterEntryFailUrlFetchTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(),
@@ -2381,8 +2158,6 @@
   }
 
   void MasterEntryAllFailTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -2427,8 +2202,6 @@
   }
 
   void UpgradeMasterEntryAllFailTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -2491,8 +2264,6 @@
   }
 
   void MasterEntrySomeFailTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -2546,8 +2317,6 @@
   }
 
   void UpgradeMasterEntrySomeFailTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -2618,8 +2387,6 @@
   }
 
   void MasterEntryNoUpdateTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/notmodified"),
@@ -2677,8 +2444,6 @@
   }
 
   void StartUpdateMidCacheAttemptTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -2785,8 +2550,6 @@
   }
 
   void StartUpdateMidNoUpdateTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/notmodified"),
@@ -2877,8 +2640,6 @@
   }
 
   void StartUpdateMidDownloadTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -2986,8 +2747,6 @@
   }
 
   void QueueMasterEntryTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), MockHttpServer::GetMockUrl("files/manifest1"),
@@ -3045,8 +2804,6 @@
   }
 
   void IfModifiedSinceTestCache() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     MakeService();
     group_ = base::MakeRefCounted<AppCacheGroup>(
         service_->storage(), GURL("http://headertest"), 111);
@@ -3065,7 +2822,7 @@
     update->StartUpdate(&host, GURL());
 
     // We need to wait for the URL load requests to make it to the
-    // MockURLLoaderFactory.
+    // URLLoaderInterceptor.
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(&AppCacheUpdateJobTest::VerifyHeadersAndDeleteUpdate,
@@ -3077,8 +2834,6 @@
   }
 
   void IfModifiedTestRefetch() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Now simulate a refetch manifest request. Will start fetch request
     // synchronously.
     const char data[] =
@@ -3103,7 +2858,7 @@
     update->RefetchManifest();
 
     // We need to wait for the URL load requests to make it to the
-    // MockURLLoaderFactory.
+    // URLLoaderInterceptor.
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(&AppCacheUpdateJobTest::VerifyHeadersAndDeleteUpdate,
@@ -3115,8 +2870,6 @@
   }
 
   void IfModifiedTestLastModified() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Change the headers to include a Last-Modified header. Manifest refetch
     // should include If-Modified-Since header.
     const char data2[] =
@@ -3142,7 +2895,7 @@
     update->RefetchManifest();
 
     // We need to wait for the URL load requests to make it to the
-    // MockURLLoaderFactory.
+    // URLLoaderInterceptor.
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(&AppCacheUpdateJobTest::VerifyHeadersAndDeleteUpdate,
@@ -3154,8 +2907,6 @@
   }
 
   void IfModifiedSinceUpgradeTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     HttpHeadersRequestTestJob::Initialize("Sat, 29 Oct 1994 19:43:31 GMT",
                                           std::string());
 
@@ -3221,8 +2972,6 @@
   }
 
   void IfNoneMatchUpgradeTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     HttpHeadersRequestTestJob::Initialize(std::string(), "\"LadeDade\"");
 
     MakeService();
@@ -3287,8 +3036,6 @@
   }
 
   void IfNoneMatchRefetchTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     HttpHeadersRequestTestJob::Initialize(std::string(), "\"LadeDade\"");
 
     MakeService();
@@ -3313,7 +3060,7 @@
     update->RefetchManifest();
 
     // We need to wait for the URL load requests to make it to the
-    // MockURLLoaderFactory.
+    // URLLoaderInterceptor.
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(&AppCacheUpdateJobTest::VerifyHeadersAndDeleteUpdate,
@@ -3325,8 +3072,6 @@
   }
 
   void MultipleHeadersRefetchTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     // Verify that code is correct when building multiple extra headers.
     HttpHeadersRequestTestJob::Initialize("Sat, 29 Oct 1994 19:43:31 GMT",
                                           "\"LadeDade\"");
@@ -3354,7 +3099,7 @@
     update->RefetchManifest();
 
     // We need to wait for the URL load requests to make it to the
-    // MockURLLoaderFactory.
+    // URLLoaderInterceptor.
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(&AppCacheUpdateJobTest::VerifyHeadersAndDeleteUpdate,
@@ -3366,8 +3111,6 @@
   }
 
   void CrossOriginHttpsSuccessTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     GURL manifest_url = MockHttpServer::GetMockHttpsUrl(
         "files/valid_cross_origin_https_manifest");
 
@@ -3394,8 +3137,6 @@
   }
 
   void CrossOriginHttpsDeniedTest() {
-    ASSERT_TRUE(base::MessageLoopCurrentForIO::IsSet());
-
     GURL manifest_url = MockHttpServer::GetMockHttpsUrl(
         "files/invalid_cross_origin_https_manifest");
 
@@ -3460,8 +3201,8 @@
   }
 
   void MakeService() {
-    service_ = std::make_unique<MockAppCacheService>();
-    service_->set_url_loader_factory_getter(loader_factory_getter_.get());
+    service_ = std::make_unique<MockAppCacheService>(
+        weak_partition_factory_.GetWeakPtr());
   }
 
   AppCache* MakeCacheForGroup(int64_t cache_id, int64_t manifest_response_id) {
@@ -3803,10 +3544,7 @@
     MANIFEST_WITH_INTERCEPT
   };
 
-  base::test::ScopedFeatureList feature_list_;
-
-  // base::test::ScopedTaskEnvironment scoped_task_environment_;
-  std::unique_ptr<IOThread> io_thread_;
+  content::TestBrowserThreadBundle thread_bundle_;
 
   std::unique_ptr<MockAppCacheService> service_;
   scoped_refptr<AppCacheGroup> group_;
@@ -3842,10 +3580,9 @@
   AppCache::EntryMap expect_extra_entries_;
   std::map<GURL, int64_t> expect_response_ids_;
 
-  MockURLLoaderFactory mock_url_loader_factory_;
-  scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter_;
-  content::TestBrowserThreadBundle thread_bundle_;
   content::TestBrowserContext browser_context_;
+  base::WeakPtrFactory<StoragePartitionImpl> weak_partition_factory_;
+  URLLoaderInterceptor interceptor_;
   const int process_id_;
 };
 
@@ -3909,230 +3646,230 @@
 }
 
 TEST_F(AppCacheUpdateJobTest, StartCacheAttempt) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::StartCacheAttemptTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::StartCacheAttemptTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, StartUpgradeAttempt) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpgradeAttemptTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::StartUpgradeAttemptTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, CacheAttemptFetchManifestFail) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::CacheAttemptFetchManifestFailTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::CacheAttemptFetchManifestFailTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeFetchManifestFail) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFetchManifestFailTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeFetchManifestFailTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, ManifestRedirect) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestRedirectTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::ManifestRedirectTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, ManifestMissingMimeTypeTest) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestMissingMimeTypeTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::ManifestMissingMimeTypeTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, ManifestNotFound) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestNotFoundTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::ManifestNotFoundTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, ManifestGone) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::ManifestGoneTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::ManifestGoneTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, CacheAttemptNotModified) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::CacheAttemptNotModifiedTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::CacheAttemptNotModifiedTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeNotModified) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeNotModifiedTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeNotModifiedTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeManifestDataUnchanged) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeManifestDataUnchangedTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeManifestDataUnchangedTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, Bug95101Test) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::Bug95101Test);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::Bug95101Test);
 }
 
 TEST_F(AppCacheUpdateJobTest, BasicCacheAttemptSuccess) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::BasicCacheAttemptSuccessTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::BasicCacheAttemptSuccessTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, DownloadInterceptEntriesTest) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::DownloadInterceptEntriesTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::DownloadInterceptEntriesTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, BasicUpgradeSuccess) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::BasicUpgradeSuccessTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::BasicUpgradeSuccessTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeLoadFromNewestCache) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeLoadFromNewestCacheTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeLoadFromNewestCacheTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeNoLoadFromNewestCache) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeNoLoadFromNewestCacheTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeNoLoadFromNewestCacheTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeLoadFromNewestCacheVaryHeader) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheUpdateJobTest::UpgradeLoadFromNewestCacheVaryHeaderTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeLoadFromNewestCacheReuseVaryHeader) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheUpdateJobTest::UpgradeLoadFromNewestCacheReuseVaryHeaderTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeSuccessMergedTypes) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeSuccessMergedTypesTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeSuccessMergedTypesTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, CacheAttemptFailUrlFetch) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::CacheAttemptFailUrlFetchTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::CacheAttemptFailUrlFetchTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeFailUrlFetch) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailUrlFetchTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeFailUrlFetchTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeFailMasterUrlFetch) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailMasterUrlFetchTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeFailMasterUrlFetchTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, EmptyManifest) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::EmptyManifestTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::EmptyManifestTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, EmptyFile) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::EmptyFileTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::EmptyFileTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, RetryRequest) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::RetryRequestTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::RetryRequestTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, RetryNoRetryAfter) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::RetryNoRetryAfterTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::RetryNoRetryAfterTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, RetryNonzeroRetryAfter) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::RetryNonzeroRetryAfterTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::RetryNonzeroRetryAfterTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, RetrySuccess) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::RetrySuccessTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::RetrySuccessTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, RetryUrl) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::RetryUrlTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::RetryUrlTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, FailStoreNewestCache) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::FailStoreNewestCacheTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::FailStoreNewestCacheTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntryFailStoreNewestCacheTest) {
-  RunTestOnIOThread(
+  RunTestOnUIThread(
       &AppCacheUpdateJobTest::MasterEntryFailStoreNewestCacheTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeFailStoreNewestCache) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailStoreNewestCacheTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeFailStoreNewestCacheTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeFailMakeGroupObsolete) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeFailMakeGroupObsoleteTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeFailMakeGroupObsoleteTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntryFetchManifestFail) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryFetchManifestFailTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MasterEntryFetchManifestFailTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntryBadManifest) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryBadManifestTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MasterEntryBadManifestTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntryManifestNotFound) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryManifestNotFoundTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MasterEntryManifestNotFoundTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntryFailUrlFetch) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryFailUrlFetchTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MasterEntryFailUrlFetchTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntryAllFail) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryAllFailTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MasterEntryAllFailTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeMasterEntryAllFail) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeMasterEntryAllFailTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeMasterEntryAllFailTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntrySomeFail) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntrySomeFailTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MasterEntrySomeFailTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, UpgradeMasterEntrySomeFail) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::UpgradeMasterEntrySomeFailTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::UpgradeMasterEntrySomeFailTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MasterEntryNoUpdate) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MasterEntryNoUpdateTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MasterEntryNoUpdateTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, StartUpdateMidCacheAttempt) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpdateMidCacheAttemptTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::StartUpdateMidCacheAttemptTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, StartUpdateMidNoUpdate) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpdateMidNoUpdateTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::StartUpdateMidNoUpdateTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, StartUpdateMidDownload) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::StartUpdateMidDownloadTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::StartUpdateMidDownloadTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, QueueMasterEntry) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::QueueMasterEntryTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::QueueMasterEntryTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, IfModifiedSinceCache) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::IfModifiedSinceTestCache);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::IfModifiedSinceTestCache);
 }
 
 TEST_F(AppCacheUpdateJobTest, IfModifiedRefetch) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::IfModifiedTestRefetch);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::IfModifiedTestRefetch);
 }
 
 TEST_F(AppCacheUpdateJobTest, IfModifiedLastModified) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::IfModifiedTestLastModified);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::IfModifiedTestLastModified);
 }
 
 TEST_F(AppCacheUpdateJobTest, IfModifiedSinceUpgrade) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::IfModifiedSinceUpgradeTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::IfModifiedSinceUpgradeTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, IfNoneMatchUpgrade) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::IfNoneMatchUpgradeTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::IfNoneMatchUpgradeTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, IfNoneMatchRefetch) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::IfNoneMatchRefetchTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::IfNoneMatchRefetchTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, MultipleHeadersRefetch) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::MultipleHeadersRefetchTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::MultipleHeadersRefetchTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, CrossOriginHttpsSuccess) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::CrossOriginHttpsSuccessTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::CrossOriginHttpsSuccessTest);
 }
 
 TEST_F(AppCacheUpdateJobTest, CrossOriginHttpsDenied) {
-  RunTestOnIOThread(&AppCacheUpdateJobTest::CrossOriginHttpsDeniedTest);
+  RunTestOnUIThread(&AppCacheUpdateJobTest::CrossOriginHttpsDeniedTest);
 }
 
 }  // namespace appcache_update_job_unittest
diff --git a/content/browser/appcache/mock_appcache_service.h b/content/browser/appcache/mock_appcache_service.h
index a7cc48e4..37d4907 100644
--- a/content/browser/appcache/mock_appcache_service.h
+++ b/content/browser/appcache/mock_appcache_service.h
@@ -15,12 +15,13 @@
 // For use by unit tests.
 class MockAppCacheService : public AppCacheServiceImpl {
  public:
-  MockAppCacheService()
-      : AppCacheServiceImpl(nullptr, nullptr),
+  explicit MockAppCacheService(base::WeakPtr<StoragePartitionImpl> partition)
+      : AppCacheServiceImpl(nullptr, std::move(partition)),
         mock_delete_appcaches_for_origin_result_(net::OK),
         delete_called_count_(0) {
     storage_ = std::make_unique<MockAppCacheStorage>(this);
   }
+  MockAppCacheService() : MockAppCacheService(nullptr) {}
 
   // Just returns a canned completion code without actually
   // removing groups and caches in our mock storage instance.
diff --git a/content/browser/background_sync/background_sync_manager.cc b/content/browser/background_sync/background_sync_manager.cc
index 8be9e28..eab7111 100644
--- a/content/browser/background_sync/background_sync_manager.cc
+++ b/content/browser/background_sync/background_sync_manager.cc
@@ -918,8 +918,7 @@
         DevToolsBackgroundService::kPeriodicBackgroundSync,
         /* event_name= */ "Got next event delay",
         /* instance_id= */ registration.options()->tag,
-        {{"Next Attempt Delay (ms)",
-          GetDelayAsString(registration.delay_until() - clock_->Now())}});
+        {{"Next Attempt Delay (ms)", GetDelayAsString(delay)}});
   }
 
   AddOrUpdateActiveRegistration(
@@ -1981,8 +1980,7 @@
     std::string event_name = GetSyncEventName(registration->sync_type()) +
                              (succeeded ? " event completed" : " event failed");
     std::map<std::string, std::string> event_metadata = {
-        {"Next Attempt Delay (ms)",
-         GetDelayAsString(registration->delay_until() - clock_->Now())}};
+        {"Next Attempt Delay (ms)", GetDelayAsString(delay)}};
     if (!succeeded) {
       event_metadata.emplace("Failure Reason",
                              GetEventStatusString(status_code));
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.cc b/content/browser/blob_storage/chrome_blob_storage_context.cc
index 859cf37..805b0e5 100644
--- a/content/browser/blob_storage/chrome_blob_storage_context.cc
+++ b/content/browser/blob_storage/chrome_blob_storage_context.cc
@@ -119,7 +119,7 @@
     // disk on the storage context.
     if (!context->IsOffTheRecord() && io_thread_valid) {
       file_task_runner = base::CreateTaskRunnerWithTraits(
-          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
            base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
       // Removes our old blob directories if they exist.
       BrowserThread::PostBestEffortTask(
diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc
index 98ef88a..0dc44d6 100644
--- a/content/browser/child_process_launcher.cc
+++ b/content/browser/child_process_launcher.cc
@@ -49,11 +49,10 @@
 #endif
 {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CHECK(BrowserThread::GetCurrentThreadIdentifier(&client_thread_id_));
 
-  helper_ = new ChildProcessLauncherHelper(
-      child_process_id, client_thread_id_, std::move(command_line),
-      std::move(delegate), weak_factory_.GetWeakPtr(), terminate_on_shutdown,
+  helper_ = base::MakeRefCounted<ChildProcessLauncherHelper>(
+      child_process_id, std::move(command_line), std::move(delegate),
+      weak_factory_.GetWeakPtr(), terminate_on_shutdown,
 #if defined(OS_ANDROID)
       client_->CanUseWarmUpConnection(),
 #endif
diff --git a/content/browser/child_process_launcher.h b/content/browser/child_process_launcher.h
index 79daec1..0e5f2250 100644
--- a/content/browser/child_process_launcher.h
+++ b/content/browser/child_process_launcher.h
@@ -226,7 +226,6 @@
               int error_code);
 
   Client* client_;
-  BrowserThread::ID client_thread_id_;
 
   // The process associated with this ChildProcessLauncher. Set in Notify by
   // ChildProcessLauncherHelper once the process was started.
diff --git a/content/browser/child_process_launcher_helper.cc b/content/browser/child_process_launcher_helper.cc
index 6ace0f9..ae11046 100644
--- a/content/browser/child_process_launcher_helper.cc
+++ b/content/browser/child_process_launcher_helper.cc
@@ -13,6 +13,7 @@
 #include "base/task/post_task.h"
 #include "base/task/single_thread_task_runner_thread_mode.h"
 #include "base/task/task_traits.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "content/browser/child_process_launcher.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/child_process_launcher_utils.h"
@@ -66,7 +67,6 @@
 
 ChildProcessLauncherHelper::ChildProcessLauncherHelper(
     int child_process_id,
-    BrowserThread::ID client_thread_id,
     std::unique_ptr<base::CommandLine> command_line,
     std::unique_ptr<SandboxedProcessLauncherDelegate> delegate,
     const base::WeakPtr<ChildProcessLauncher>& child_process_launcher,
@@ -77,7 +77,7 @@
     mojo::OutgoingInvitation mojo_invitation,
     const mojo::ProcessErrorCallback& process_error_callback)
     : child_process_id_(child_process_id),
-      client_thread_id_(client_thread_id),
+      client_task_runner_(base::SequencedTaskRunnerHandle::Get()),
       command_line_(std::move(command_line)),
       delegate_(std::move(delegate)),
       child_process_launcher_(child_process_launcher),
@@ -94,7 +94,7 @@
 ChildProcessLauncherHelper::~ChildProcessLauncherHelper() = default;
 
 void ChildProcessLauncherHelper::StartLaunchOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
 
   BeforeLaunchOnClientThread();
 
@@ -173,8 +173,8 @@
     }
   }
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {client_thread_id_},
+  client_task_runner_->PostTask(
+      FROM_HERE,
       base::BindOnce(&ChildProcessLauncherHelper::PostLaunchOnClientThread,
                      this, std::move(process), launch_result));
 }
diff --git a/content/browser/child_process_launcher_helper.h b/content/browser/child_process_launcher_helper.h
index 3694dcd..9a7ca16 100644
--- a/content/browser/child_process_launcher_helper.h
+++ b/content/browser/child_process_launcher_helper.h
@@ -10,11 +10,12 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/process/kill.h"
 #include "base/process/process.h"
+#include "base/sequenced_task_runner.h"
 #include "build/build_config.h"
-#include "content/public/browser/browser_thread.h"
 #include "content/public/common/result_codes.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/system/invitation.h"
@@ -93,7 +94,6 @@
 
   ChildProcessLauncherHelper(
       int child_process_id,
-      BrowserThread::ID client_thread_id,
       std::unique_ptr<base::CommandLine> command_line,
       std::unique_ptr<SandboxedProcessLauncherDelegate> delegate,
       const base::WeakPtr<ChildProcessLauncher>& child_process_launcher,
@@ -161,8 +161,6 @@
   void PostLaunchOnClientThread(ChildProcessLauncherHelper::Process process,
                                 int error_code);
 
-  int client_thread_id() const { return client_thread_id_; }
-
   // See ChildProcessLauncher::GetChildTerminationInfo for more info.
   ChildProcessTerminationInfo GetTerminationInfo(
       const ChildProcessLauncherHelper::Process& process,
@@ -221,7 +219,7 @@
 #endif
 
   const int child_process_id_;
-  const BrowserThread::ID client_thread_id_;
+  const scoped_refptr<base::SequencedTaskRunner> client_task_runner_;
   base::TimeTicks begin_launch_time_;
   std::unique_ptr<base::CommandLine> command_line_;
   std::unique_ptr<SandboxedProcessLauncherDelegate> delegate_;
diff --git a/content/browser/child_process_launcher_helper_android.cc b/content/browser/child_process_launcher_helper_android.cc
index 81ff192..5557569 100644
--- a/content/browser/child_process_launcher_helper_android.cc
+++ b/content/browser/child_process_launcher_helper_android.cc
@@ -140,8 +140,8 @@
       env, reinterpret_cast<intptr_t>(this), j_argv, j_file_infos,
       can_use_warm_up_connection));
   AddRef();  // Balanced by OnChildProcessStarted.
-  base::PostTaskWithTraits(
-      FROM_HERE, {client_thread_id_},
+  client_task_runner_->PostTask(
+      FROM_HERE,
       base::BindOnce(
           &ChildProcessLauncherHelper::set_java_peer_available_on_client_thread,
           this));
diff --git a/content/browser/child_process_launcher_helper_fuchsia.cc b/content/browser/child_process_launcher_helper_fuchsia.cc
index 0d3f7adf..ea9bef7 100644
--- a/content/browser/child_process_launcher_helper_fuchsia.cc
+++ b/content/browser/child_process_launcher_helper_fuchsia.cc
@@ -48,7 +48,7 @@
 }
 
 void ChildProcessLauncherHelper::BeforeLaunchOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
 
   sandbox_policy_.Initialize(delegate_->GetSandboxType());
 }
diff --git a/content/browser/child_process_launcher_helper_linux.cc b/content/browser/child_process_launcher_helper_linux.cc
index 5b82388..720b92a 100644
--- a/content/browser/child_process_launcher_helper_linux.cc
+++ b/content/browser/child_process_launcher_helper_linux.cc
@@ -26,12 +26,12 @@
 
 base::Optional<mojo::NamedPlatformChannel>
 ChildProcessLauncherHelper::CreateNamedPlatformChannelOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
   return base::nullopt;
 }
 
 void ChildProcessLauncherHelper::BeforeLaunchOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
 }
 
 std::unique_ptr<FileMappedForLaunch>
diff --git a/content/browser/child_process_launcher_helper_mac.cc b/content/browser/child_process_launcher_helper_mac.cc
index 4698c0f..7e3572ac 100644
--- a/content/browser/child_process_launcher_helper_mac.cc
+++ b/content/browser/child_process_launcher_helper_mac.cc
@@ -33,12 +33,12 @@
 
 base::Optional<mojo::NamedPlatformChannel>
 ChildProcessLauncherHelper::CreateNamedPlatformChannelOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
   return base::nullopt;
 }
 
 void ChildProcessLauncherHelper::BeforeLaunchOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
 }
 
 std::unique_ptr<PosixFileDescriptorInfo>
diff --git a/content/browser/child_process_launcher_helper_win.cc b/content/browser/child_process_launcher_helper_win.cc
index 8e6b272d..c27d205 100644
--- a/content/browser/child_process_launcher_helper_win.cc
+++ b/content/browser/child_process_launcher_helper_win.cc
@@ -24,12 +24,12 @@
 namespace internal {
 
 void ChildProcessLauncherHelper::BeforeLaunchOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
 }
 
 base::Optional<mojo::NamedPlatformChannel>
 ChildProcessLauncherHelper::CreateNamedPlatformChannelOnClientThread() {
-  DCHECK_CURRENTLY_ON(client_thread_id_);
+  DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
 
   if (!delegate_->ShouldLaunchElevated())
     return base::nullopt;
diff --git a/content/browser/child_process_task_port_provider_mac_unittest.cc b/content/browser/child_process_task_port_provider_mac_unittest.cc
index 7deb9cb..94e48e3 100644
--- a/content/browser/child_process_task_port_provider_mac_unittest.cc
+++ b/content/browser/child_process_task_port_provider_mac_unittest.cc
@@ -24,6 +24,8 @@
 
 class MockChildProcess : public mojom::ChildProcess {
  public:
+  MOCK_METHOD1(Initialize,
+               void(mojo::PendingRemote<mojom::ChildProcessHostBootstrap>));
   MOCK_METHOD0(ProcessShutdown, void());
   MOCK_METHOD1(GetTaskPort, void(GetTaskPortCallback));
 #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
@@ -38,6 +40,7 @@
                     mojo::PendingReceiver<service_manager::mojom::Service>));
   MOCK_METHOD1(BindServiceInterface,
                void(mojo::GenericPendingReceiver receiver));
+  MOCK_METHOD1(BindReceiver, void(mojo::GenericPendingReceiver receiver));
 };
 
 class ChildProcessTaskPortProviderTest : public testing::Test,
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index 79df0a3..636f6081 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -1328,7 +1328,7 @@
   auto request_info = BuildRequestInfo(&head);
   const network::ResourceRequest& request = create_loader_params_->request;
   request_info->is_download =
-      request_info->is_navigation && request.allow_download &&
+      request_info->is_navigation &&
       (is_download_ || download_utils::IsDownload(
                            request.url, head.headers.get(), head.mime_type));
   NotifyClient(std::move(request_info));
diff --git a/content/browser/frame_host/frame_tree.cc b/content/browser/frame_host/frame_tree.cc
index 635a9c2..3ebe2a8 100644
--- a/content/browser/frame_host/frame_tree.cc
+++ b/content/browser/frame_host/frame_tree.cc
@@ -371,8 +371,7 @@
     int32_t routing_id,
     int32_t main_frame_routing_id,
     int32_t widget_routing_id,
-    bool swapped_out,
-    bool hidden) {
+    bool swapped_out) {
   scoped_refptr<RenderViewHostImpl> existing_rvh =
       GetRenderViewHost(site_instance);
   if (existing_rvh)
@@ -381,8 +380,7 @@
   RenderViewHostImpl* rvh =
       static_cast<RenderViewHostImpl*>(RenderViewHostFactory::Create(
           site_instance, render_view_delegate_, render_widget_delegate_,
-          routing_id, main_frame_routing_id, widget_routing_id, swapped_out,
-          hidden));
+          routing_id, main_frame_routing_id, widget_routing_id, swapped_out));
   render_view_host_map_[site_instance->GetId()] = rvh;
   return base::WrapRefCounted(rvh);
 }
diff --git a/content/browser/frame_host/frame_tree.h b/content/browser/frame_host/frame_tree.h
index a9c298a3..e1a920a 100644
--- a/content/browser/frame_host/frame_tree.h
+++ b/content/browser/frame_host/frame_tree.h
@@ -213,8 +213,7 @@
       int32_t routing_id,
       int32_t main_frame_routing_id,
       int32_t widget_routing_id,
-      bool swapped_out,
-      bool hidden);
+      bool swapped_out);
 
   // Returns the existing RenderViewHost for a new RenderFrameHost.
   // There should always be such a RenderViewHost, because the main frame
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index b435945..1c67319 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -2450,9 +2450,6 @@
 
   if (current_size > 0) {
     // Prune any entries which are in front of the current entry.
-    // last_committed_entry_index_ must be updated here since calls to
-    // NotifyPrunedEntries() below may re-enter and we must make sure
-    // last_committed_entry_index_ is not left in an invalid state.
     int num_pruned = 0;
     while (last_committed_entry_index_ < (current_size - 1)) {
       num_pruned++;
diff --git a/content/browser/frame_host/navigation_handle_impl_unittest.cc b/content/browser/frame_host/navigation_handle_impl_unittest.cc
index 0ef32ec..9a06f82 100644
--- a/content/browser/frame_host/navigation_handle_impl_unittest.cc
+++ b/content/browser/frame_host/navigation_handle_impl_unittest.cc
@@ -223,10 +223,7 @@
   // NavigationRequest and NavigationHandleImpl.
   void CreateNavigationHandle() {
     auto frame_entry = base::MakeRefCounted<FrameNavigationEntry>();
-    mojom::CommonNavigationParamsPtr common_params =
-        mojom::CommonNavigationParams::New();
-    common_params->navigation_start = base::TimeTicks::Now();
-    common_params->referrer = blink::mojom::Referrer::New();
+    auto common_params = CreateCommonNavigationParams();
     common_params->initiator_origin =
         url::Origin::Create(GURL("https://initiator.example.com"));
     request_ = NavigationRequest::CreateBrowserInitiated(
diff --git a/content/browser/frame_host/render_frame_host_factory.cc b/content/browser/frame_host/render_frame_host_factory.cc
index ef71766..9a2e987 100644
--- a/content/browser/frame_host/render_frame_host_factory.cc
+++ b/content/browser/frame_host/render_frame_host_factory.cc
@@ -24,18 +24,17 @@
     FrameTreeNode* frame_tree_node,
     int32_t routing_id,
     int32_t widget_routing_id,
-    bool hidden,
     bool renderer_initiated_creation) {
   if (factory_) {
     return factory_->CreateRenderFrameHost(
         site_instance, std::move(render_view_host), delegate, frame_tree,
-        frame_tree_node, routing_id, widget_routing_id, hidden,
+        frame_tree_node, routing_id, widget_routing_id,
         renderer_initiated_creation);
   }
-  return base::WrapUnique(new RenderFrameHostImpl(
-      site_instance, std::move(render_view_host), delegate, frame_tree,
-      frame_tree_node, routing_id, widget_routing_id, hidden,
-      renderer_initiated_creation));
+  return base::WrapUnique(
+      new RenderFrameHostImpl(site_instance, std::move(render_view_host),
+                              delegate, frame_tree, frame_tree_node, routing_id,
+                              widget_routing_id, renderer_initiated_creation));
 }
 
 // static
diff --git a/content/browser/frame_host/render_frame_host_factory.h b/content/browser/frame_host/render_frame_host_factory.h
index 182b6c72..c229c2d 100644
--- a/content/browser/frame_host/render_frame_host_factory.h
+++ b/content/browser/frame_host/render_frame_host_factory.h
@@ -37,7 +37,6 @@
       FrameTreeNode* frame_tree_node,
       int32_t routing_id,
       int32_t widget_routing_id,
-      bool hidden,
       bool renderer_initiated_creation);
 
   // Returns true if there is currently a globally-registered factory.
@@ -57,7 +56,6 @@
       FrameTreeNode* frame_tree_node,
       int32_t routing_id,
       int32_t widget_routing_id,
-      bool hidden,
       bool renderer_initiated_creation) = 0;
 
   // Registers a factory to be called when new RenderFrameHostImpls are created.
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 40f74d8..9e18718 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -800,7 +800,6 @@
     FrameTreeNode* frame_tree_node,
     int32_t routing_id,
     int32_t widget_routing_id,
-    bool hidden,
     bool renderer_initiated_creation)
     : render_view_host_(std::move(render_view_host)),
       delegate_(delegate),
@@ -895,7 +894,7 @@
                                                       widget_routing_id));
       owned_render_widget_host_ = RenderWidgetHostFactory::Create(
           frame_tree_->render_widget_delegate(), GetProcess(),
-          widget_routing_id, std::move(widget), hidden);
+          widget_routing_id, std::move(widget), /*hidden=*/true);
       owned_render_widget_host_->set_owned_by_render_frame_host(true);
     }
 
@@ -1814,12 +1813,10 @@
   if (GetLocalRenderWidgetHost()) {
     params->widget_params->routing_id =
         GetLocalRenderWidgetHost()->GetRoutingID();
-    params->widget_params->hidden = GetLocalRenderWidgetHost()->is_hidden();
   } else {
     // MSG_ROUTING_NONE will prevent a new RenderWidget from being created in
     // the renderer process.
     params->widget_params->routing_id = MSG_ROUTING_NONE;
-    params->widget_params->hidden = true;
   }
 
   GetProcess()->GetRendererInterface()->CreateFrame(std::move(params));
@@ -1834,7 +1831,8 @@
     DCHECK_NE(parent_routing_id, MSG_ROUTING_NONE);
     RenderWidgetHostView* rwhv =
         RenderWidgetHostViewChildFrame::Create(owned_render_widget_host_.get());
-    rwhv->Hide();
+    // The child frame should be created hidden.
+    DCHECK(!rwhv->IsShowing());
   }
 
   if (previous_routing_id != MSG_ROUTING_NONE) {
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index d1e35b39..04c8af57 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -1021,7 +1021,6 @@
                       FrameTreeNode* frame_tree_node,
                       int32_t routing_id,
                       int32_t widget_routing_id,
-                      bool hidden,
                       bool renderer_initiated_creation);
 
   // The SendCommit* functions below are wrappers for commit calls
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index e9a5979..b95a5cb5 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -182,7 +182,6 @@
   DCHECK(site_instance);
   SetRenderFrameHost(CreateRenderFrameHost(site_instance, view_routing_id,
                                            frame_routing_id, widget_routing_id,
-                                           delegate_->IsHidden(),
                                            renderer_initiated_creation));
 
   // Notify the delegate of the creation of the current RenderFrameHost.
@@ -330,17 +329,11 @@
   if (!speculative_render_frame_host_) {
     // There's no speculative RenderFrameHost so it must be that the current
     // renderer process completed a navigation.
-
-    // We should only hear this from our current renderer.
+    // TODO(danakj): Make this a CHECK and stop handling it. Then make it a
+    // DCHECK when we're sure.
     DCHECK_EQ(render_frame_host_.get(), render_frame_host);
-
-    // If the current RenderFrameHost has a pending WebUI it must be committed.
-    // Note: When one tries to move same-site commit logic into RenderFrameHost
-    // itself, mind that the focus setting logic inside CommitPending also needs
-    // to be moved there.
-    if (render_frame_host_->pending_web_ui())
-      CommitPendingWebUI();
-    return;
+    if (render_frame_host != render_frame_host_.get())
+      return;
   }
 
   if (render_frame_host == speculative_render_frame_host_.get()) {
@@ -350,29 +343,37 @@
     // below.
     CommitPending(std::move(speculative_render_frame_host_));
     frame_tree_node_->ResetNavigationRequest(false, true);
-  } else if (render_frame_host == render_frame_host_.get()) {
-    // A same-process navigation committed while a simultaneous cross-process
-    // navigation is still ongoing.
+    return;
+  }
 
-    // If the current RenderFrameHost has a pending WebUI it must be committed.
-    if (render_frame_host_->pending_web_ui())
-      CommitPendingWebUI();
+  // A same-process navigation committed. A cross-process navigation may also
+  // be ongoing.
 
-    // A navigation in the original process has taken place.  This should
-    // cancel the ongoing cross-process navigation if the commit is
-    // cross-document and has a user gesture (since the user might have clicked
-    // on a new link while waiting for a slow navigation), but it should not
-    // cancel it for same-document navigations (which might happen as
-    // bookkeeping) or when there is no user gesture (which might abusively try
-    // to prevent the user from leaving).  See https://crbug.com/825677 and
-    // https://crbug.com/75195 for examples.
-    if (!is_same_document_navigation && was_caused_by_user_gesture) {
-      frame_tree_node_->ResetNavigationRequest(false, true);
-      CleanUpNavigation();
-    }
-  } else {
-    // No one else should be sending us DidNavigate in this state.
-    NOTREACHED();
+  // If the current RenderFrameHost has a pending WebUI it must be committed.
+  if (render_frame_host_->pending_web_ui())
+    CommitPendingWebUI();
+
+  // A navigation in the original process has taken place, while a
+  // cross-process navigation is ongoing.  This should cancel the ongoing
+  // cross-process navigation if the commit is cross-document and has a user
+  // gesture (since the user might have clicked on a new link while waiting for
+  // a slow navigation), but it should not cancel it for same-document
+  // navigations (which might happen as bookkeeping) or when there is no user
+  // gesture (which might abusively try to prevent the user from leaving).
+  // See https://crbug.com/825677 and https://crbug.com/75195 for examples.
+  if (speculative_render_frame_host_ && !is_same_document_navigation &&
+      was_caused_by_user_gesture) {
+    frame_tree_node_->ResetNavigationRequest(false, true);
+    CleanUpNavigation();
+  }
+
+  if (render_frame_host_->is_local_root() && render_frame_host_->GetView()) {
+    // RenderFrames are created with a hidden RenderWidgetHost. When
+    // navigation finishes, we show it if the delegate is shown. CommitPending()
+    // takes care of this in the cross-process case, as well as other cases
+    // where a RenderFrameHost is swapped in.
+    if (!delegate_->IsHidden())
+      render_frame_host_->GetView()->Show();
   }
 }
 
@@ -782,7 +783,6 @@
     notify_webui_of_rf_creation = true;
 
     if (navigation_rfh == render_frame_host_.get()) {
-      EnsureRenderFrameHostVisibilityConsistent();
       EnsureRenderFrameHostPageFocusConsistent();
       // TODO(nasko): This is a very ugly hack. The Chrome extensions process
       // manager still uses NotificationService and expects to see a
@@ -1315,8 +1315,6 @@
   if (render_frame_host != render_frame_host_.get())
     return;
 
-  EnsureRenderFrameHostVisibilityConsistent();
-
   // TODO(jam): uncomment this when the method is shared. Not adding the call
   // now to make merge to 63 easier.
   // EnsureRenderFrameHostPageFocusConsistent();
@@ -1910,7 +1908,6 @@
     int32_t view_routing_id,
     int32_t frame_routing_id,
     int32_t widget_routing_id,
-    bool hidden,
     bool renderer_initiated_creation) {
   if (frame_routing_id == MSG_ROUTING_NONE)
     frame_routing_id = site_instance->GetProcess()->GetNextRoutingID();
@@ -1921,7 +1918,7 @@
   if (frame_tree_node_->IsMainFrame()) {
     render_view_host = frame_tree->CreateRenderViewHost(
         site_instance, view_routing_id, frame_routing_id, widget_routing_id,
-        false, hidden);
+        false);
     // TODO(avi): It's a bit bizarre that this logic lives here instead of in
     // CreateRenderFrame(). It turns out that FrameTree::CreateRenderViewHost
     // doesn't /always/ create a new RenderViewHost. It first tries to find an
@@ -1944,7 +1941,7 @@
 
   return RenderFrameHostFactory::Create(
       site_instance, render_view_host, frame_tree->render_frame_delegate(),
-      frame_tree, frame_tree_node_, frame_routing_id, widget_routing_id, hidden,
+      frame_tree, frame_tree_node_, frame_routing_id, widget_routing_id,
       renderer_initiated_creation);
 }
 
@@ -1963,8 +1960,7 @@
 
   CreateProxiesForNewRenderFrameHost(old_instance, new_instance);
 
-  speculative_render_frame_host_ =
-      CreateRenderFrame(new_instance, delegate_->IsHidden());
+  speculative_render_frame_host_ = CreateRenderFrame(new_instance);
 
   // If RenderViewHost was created along with the speculative RenderFrameHost,
   // ensure that RenderViewCreated is fired for it.  It is important to do this
@@ -1980,8 +1976,7 @@
 }
 
 std::unique_ptr<RenderFrameHostImpl> RenderFrameHostManager::CreateRenderFrame(
-    SiteInstance* instance,
-    bool hidden) {
+    SiteInstance* instance) {
   CHECK(instance);
 
   // We are creating a pending or speculative RFH here. We should never create
@@ -2001,7 +1996,7 @@
 
   std::unique_ptr<RenderFrameHostImpl> new_render_frame_host =
       CreateRenderFrameHost(instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE,
-                            widget_routing_id, hidden, false);
+                            widget_routing_id, false);
   DCHECK_EQ(new_render_frame_host->GetSiteInstance(), instance);
 
   // Prevent the process from exiting while we're trying to navigate in it.
@@ -2018,12 +2013,9 @@
     if (!render_view_host->GetWidget()->GetView())
       delegate_->CreateRenderWidgetHostViewForRenderManager(render_view_host);
 
-    // Don't show the main frame's view until we get a DidNavigate from it.
-    // Only the RenderViewHost for the top-level RenderFrameHost has a
-    // RenderWidgetHostView; RenderWidgetHosts for out-of-process iframes
-    // will be created later and hidden.
-    if (render_view_host->GetWidget()->GetView())
-      render_view_host->GetWidget()->GetView()->Hide();
+    // And since we are reusing the RenderViewHost make sure it is hidden, like
+    // a new RenderViewHost would be, until navigation commits.
+    render_view_host->GetWidget()->GetView()->Hide();
   } else {
     DCHECK(render_view_host->IsRenderViewLive());
   }
@@ -2063,7 +2055,7 @@
     scoped_refptr<RenderViewHostImpl> render_view_host =
         frame_tree_node_->frame_tree()->CreateRenderViewHost(
             instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE, MSG_ROUTING_NONE,
-            true, true);
+            /*swapped_out=*/true);
 
     proxy = CreateRenderFrameProxyHost(instance, std::move(render_view_host));
   }
@@ -2339,6 +2331,18 @@
   }
 
   DCHECK(render_frame_host->IsRenderFrameLive());
+
+  // The RenderWidgetHostView goes away with the render process. Initializing a
+  // RenderFrame means we'll be creating (or reusing, https://crbug.com/419087)
+  // a RenderWidgetHostView. The new RenderWidgetHostView should take its
+  // visibility from the RenderWidgetHostImpl, but this call exists to handle
+  // cases where it did not during a same-process navigation.
+  // TODO(danakj): We now hide the widget unconditionally (treating main frame
+  // and child frames alike) and show in DidFinishNavigation() always, so this
+  // should be able to go away. Try to remove this.
+  if (render_frame_host == render_frame_host_.get())
+    EnsureRenderFrameHostVisibilityConsistent();
+
   return true;
 }
 
@@ -2551,29 +2555,11 @@
   if (proxy_to_parent)
     proxy_to_parent->SetChildRWHView(new_view, old_size ? &*old_size : nullptr);
 
-  if (new_view) {
-    if (!delegate_->IsHidden()) {
-      // Show the new RenderWidgetHost if the new frame is a local root and not
-      // hidden or crashed. If the frame is not a local root this is redundant
-      // as the ancestor RenderWidgetHost would already be shown.
-      // TODO(danakj): Is this only really needed for a main frame, because the
-      // RenderWidget is not being newly recreated for the new frame due to
-      // https://crbug.com/419087 ? Would sub frames be marked correctly as
-      // shown from creation of their RenderWidgetHost?
+  if (render_frame_host_->is_local_root() && new_view) {
+    // RenderFrames are created with a hidden RenderWidgetHost. When navigation
+    // finishes, we show it if the delegate is shown.
+    if (!delegate_->IsHidden())
       new_view->Show();
-    } else {
-      // The Browser side RWHI is initialized as hidden, but the RenderWidget is
-      // not if it is going to be visible after navigation commit. We are
-      // bringing their visibility state into alignment here either by showing
-      // the RWHI or hiding the RenderWidget. See https://crbug/936858 for more
-      // details.
-      // TODO(jonross): This is not needed once https://crbug/419087 is
-      // resolved.
-      RenderWidgetHostImpl::From(new_view->GetRenderWidgetHost())
-          ->SetHiddenOnCommit();
-      // TODO(ejoe): This can be removed if the RenderWidget can be always
-      // created as hidden by default.
-    }
   }
 
   // The process will no longer try to exit, so we can decrement the count.
diff --git a/content/browser/frame_host/render_frame_host_manager.h b/content/browser/frame_host/render_frame_host_manager.h
index b34a544..65b6e5a 100644
--- a/content/browser/frame_host/render_frame_host_manager.h
+++ b/content/browser/frame_host/render_frame_host_manager.h
@@ -289,8 +289,8 @@
                        SiteInstance* source_site_instance);
 
   // Creates and initializes a RenderFrameHost.
-  std::unique_ptr<RenderFrameHostImpl> CreateRenderFrame(SiteInstance* instance,
-                                                         bool hidden);
+  std::unique_ptr<RenderFrameHostImpl> CreateRenderFrame(
+      SiteInstance* instance);
 
   // Helper method to create and initialize a RenderFrameProxyHost.
   void CreateRenderFrameProxy(SiteInstance* instance);
@@ -711,7 +711,6 @@
       int32_t view_routing_id,
       int32_t frame_routing_id,
       int32_t widget_routing_id,
-      bool hidden,
       bool renderer_initiated_creation);
 
   // Create and initialize a speculative RenderFrameHost for an ongoing
diff --git a/content/browser/frame_host/render_frame_host_manager_unittest.cc b/content/browser/frame_host/render_frame_host_manager_unittest.cc
index 04b8bd3..36a481e 100644
--- a/content/browser/frame_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_unittest.cc
@@ -972,45 +972,6 @@
             *manager->GetRenderWidgetHostView()->GetBackgroundColor());
 }
 
-// Test that we properly set the RenderWidget to be hidden if the visibility of
-// the web contents becomes hidden after the start of navigation but before we
-// commit the navigation. See https://crbug.com/936858 for more details.
-TEST_F(RenderFrameHostManagerTest, WebContentVisibilityHiddenBeforeCommit) {
-  set_should_create_webui(true);
-  scoped_refptr<SiteInstance> blank_instance =
-      SiteInstance::Create(browser_context());
-  blank_instance->GetProcess()->Init();
-
-  // Create a blank tab.
-  std::unique_ptr<TestWebContents> web_contents(
-      TestWebContents::Create(browser_context(), blank_instance));
-  RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
-  manager->current_host()->CreateRenderView(-1, MSG_ROUTING_NONE,
-                                            base::UnguessableToken::Create(),
-                                            FrameReplicationState(), false);
-  EXPECT_TRUE(manager->current_host()->IsRenderViewLive());
-  EXPECT_TRUE(manager->current_frame_host()->IsRenderFrameLive());
-
-  // Start navigation.
-  const GURL kUrl(GetWebUIURL("foo"));
-  NavigationEntryImpl entry(
-      nullptr /* instance */, kUrl, Referrer(), base::nullopt,
-      base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
-      false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
-  RenderFrameHostImpl* host = NavigateToEntry(manager, &entry);
-
-  // Hide the web contents before commit.
-  web_contents->WasHidden();
-
-  // Commit.
-  manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */,
-                            false /* is_same_document_navigation */);
-
-  // We should send a WidgetMsg_WasHidden message to the Renderer.
-  auto* rph = static_cast<MockRenderProcessHost*>(host->GetProcess());
-  EXPECT_TRUE(rph->sink().GetUniqueMessageMatching(WidgetMsg_WasHidden::ID));
-}
-
 // Tests WebUI creation.
 TEST_F(RenderFrameHostManagerTest, WebUI) {
   set_should_create_webui(true);
diff --git a/content/browser/loader/navigation_loader_interceptor.h b/content/browser/loader/navigation_loader_interceptor.h
index 73f734d..27accae 100644
--- a/content/browser/loader/navigation_loader_interceptor.h
+++ b/content/browser/loader/navigation_loader_interceptor.h
@@ -66,6 +66,9 @@
   // indicates whether to discard the subresource loader params previously
   // returned by MaybeCreateSubresourceLoaderParams().
   //
+  // |callback| and |fallback_callback| must not be invoked after the
+  // destruction of this interceptor.
+  //
   // |browser_context| will only be non-null when this interceptor is running on
   // the UI thread, and |resource_context| will only be non-null when running on
   // the IO thread.
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index efbd167..d845bf06 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -170,8 +170,7 @@
 
 std::unique_ptr<network::ResourceRequest> CreateResourceRequest(
     NavigationRequestInfo* request_info,
-    int frame_tree_node_id,
-    bool allow_download) {
+    int frame_tree_node_id) {
   // TODO(scottmg): Port over stuff from RDHI::BeginNavigationRequest() here.
   auto new_request = std::make_unique<network::ResourceRequest>();
 
@@ -228,7 +227,6 @@
 
   new_request->request_body = request_info->common_params->post_data.get();
   new_request->report_raw_headers = request_info->report_raw_headers;
-  new_request->allow_download = allow_download;
   new_request->has_user_gesture = request_info->common_params->has_user_gesture;
   new_request->enable_load_timing = true;
 
@@ -698,18 +696,13 @@
         browser_context_to_use = browser_context_;
       else
         resource_context_to_use = resource_context_;
-      // TODO(crbug.com/984460): Don't use WeakPtrs for callbacks. These
-      // WeakPtrs are to work around a case where ServiceWorkerNavigationLoader
-      // outlives the interceptor. We may want to reset fallback callback in
-      // ServiceWorkerNavigationLoader when the URLLoaderRequestController is
-      // going away.
       next_interceptor->MaybeCreateLoader(
           *resource_request_, browser_context_to_use, resource_context_to_use,
           base::BindOnce(&URLLoaderRequestController::MaybeStartLoader,
-                         weak_factory_.GetWeakPtr(), next_interceptor),
+                         base::Unretained(this), next_interceptor),
           base::BindOnce(
               &URLLoaderRequestController::FallbackToNonInterceptedRequest,
-              weak_factory_.GetWeakPtr()));
+              base::Unretained(this)));
       return;
     }
 
@@ -1393,8 +1386,7 @@
       appcache_handle ? appcache_handle->core() : nullptr;
 
   std::unique_ptr<network::ResourceRequest> new_request =
-      CreateResourceRequest(request_info.get(), frame_tree_node_id,
-                            download_policy_.IsDownloadAllowed());
+      CreateResourceRequest(request_info.get(), frame_tree_node_id);
 
   auto* partition = static_cast<StoragePartitionImpl*>(storage_partition);
   scoped_refptr<SignedExchangePrefetchMetricRecorder>
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index bce9f6d..55a12549 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -175,9 +175,7 @@
             GURL() /* client_side_redirect_url */,
             base::nullopt /* devtools_initiator_info */);
 
-    auto common_params = mojom::CommonNavigationParams::New();
-    common_params->referrer = blink::mojom::Referrer::New();
-    common_params->navigation_start = base::TimeTicks::Now();
+    auto common_params = CreateCommonNavigationParams();
     common_params->url = url;
     common_params->initiator_origin = url::Origin::Create(url);
     common_params->method = method;
diff --git a/content/browser/loader/navigation_url_loader_unittest.cc b/content/browser/loader/navigation_url_loader_unittest.cc
index c10b1bc..9089896 100644
--- a/content/browser/loader/navigation_url_loader_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_unittest.cc
@@ -69,9 +69,7 @@
             std::string() /* searchable_form_encoding */,
             GURL() /* client_side_redirect_url */,
             base::nullopt /* devtools_initiator_info */);
-    auto common_params = mojom::CommonNavigationParams::New();
-    common_params->referrer = blink::mojom::Referrer::New();
-    common_params->navigation_start = base::TimeTicks::Now();
+    auto common_params = CreateCommonNavigationParams();
     common_params->url = url;
     common_params->initiator_origin = url::Origin::Create(url);
 
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index eb2bb0c..dfc6de6 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -134,6 +134,7 @@
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/tracing/background_tracing_manager_impl.h"
 #include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/common/child_process.mojom.h"
 #include "content/common/child_process_host_impl.h"
 #include "content/common/content_switches_internal.h"
 #include "content/common/frame_messages.h"
@@ -188,6 +189,8 @@
 #include "media/media_buildflags.h"
 #include "media/webrtc/webrtc_switches.h"
 #include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -1273,6 +1276,49 @@
   DISALLOW_COPY_AND_ASSIGN(ConnectionFilterImpl);
 };
 
+// A RenderProcessHostImpl's IO thread implementation of the
+// |mojom::ChildProcessHost| interface. This exists to allow the process host
+// to bind incoming receivers on the IO-thread without a main-thread hop if
+// necessary. Also owns the RPHI's |mojom::ChildProcess| remote.
+class RenderProcessHostImpl::IOThreadHostImpl
+    : public mojom::ChildProcessHostBootstrap,
+      public mojom::ChildProcessHost {
+ public:
+  IOThreadHostImpl(base::WeakPtr<RenderProcessHostImpl> weak_host,
+                   mojo::PendingReceiver<mojom::ChildProcessHostBootstrap>
+                       bootstrap_receiver)
+      : weak_host_(std::move(weak_host)),
+        bootstrap_receiver_(this, std::move(bootstrap_receiver)) {}
+  ~IOThreadHostImpl() override = default;
+
+ private:
+  // mojom::ChildProcessHostBootstrap implementation:
+  void BindProcessHost(
+      mojo::PendingReceiver<mojom::ChildProcessHost> receiver) override {
+    receiver_.Bind(std::move(receiver));
+  }
+
+  // mojom::ChildProcessHost implementation:
+  void BindHostReceiver(mojo::GenericPendingReceiver receiver) override {
+    base::PostTask(FROM_HERE, {BrowserThread::UI},
+                   base::BindOnce(&IOThreadHostImpl::BindHostReceiverOnUIThread,
+                                  weak_host_, std::move(receiver)));
+  }
+
+  static void BindHostReceiverOnUIThread(
+      base::WeakPtr<RenderProcessHostImpl> weak_host,
+      mojo::GenericPendingReceiver receiver) {
+    if (weak_host)
+      weak_host->OnBindHostReceiver(std::move(receiver));
+  }
+
+  const base::WeakPtr<RenderProcessHostImpl> weak_host_;
+  mojo::Receiver<mojom::ChildProcessHostBootstrap> bootstrap_receiver_;
+  mojo::Receiver<mojom::ChildProcessHost> receiver_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(IOThreadHostImpl);
+};
+
 void RenderProcessHostImpl::ConnectionFilterController::DisableFilter() {
   base::AutoLock lock(lock_);
   if (filter_)
@@ -1464,8 +1510,7 @@
       channel_connected_(false),
       sent_render_process_ready_(false),
       renderer_host_binding_(this),
-      instance_weak_factory_(
-          new base::WeakPtrFactory<RenderProcessHostImpl>(this)),
+      instance_weak_factory_(base::in_place, this),
       frame_sink_provider_(id_),
       shutdown_exit_code_(-1) {
   widget_helper_ = new RenderWidgetHelper();
@@ -1763,6 +1808,12 @@
       this,
       mojom::ChildProcessRequest(child_process_.BindNewPipeAndPassReceiver()));
 
+  mojo::PendingRemote<mojom::ChildProcessHostBootstrap> bootstrap_remote;
+  io_thread_host_impl_.emplace(
+      io_task_runner, instance_weak_factory_->GetWeakPtr(),
+      bootstrap_remote.InitWithNewPipeAndPassReceiver());
+  child_process_->Initialize(std::move(bootstrap_remote));
+
   ResetChannelProxy();
 
   // Do NOT expand ifdef or run time condition checks here! Synchronous
@@ -2270,6 +2321,11 @@
   child_connection_->BindInterface(interface_name, std::move(interface_pipe));
 }
 
+void RenderProcessHostImpl::BindReceiver(
+    mojo::GenericPendingReceiver receiver) {
+  child_process_->BindReceiver(std::move(receiver));
+}
+
 const service_manager::Identity& RenderProcessHostImpl::GetChildIdentity() {
   // GetChildIdentity should only be called if the RPH is (or soon will be)
   // backed by an actual renderer process.  This helps prevent leaks similar to
@@ -3409,8 +3465,7 @@
   // reused in between now and when the Delete task runs.
   UnregisterHost(GetID());
 
-  instance_weak_factory_ =
-      std::make_unique<base::WeakPtrFactory<RenderProcessHostImpl>>(this);
+  instance_weak_factory_.emplace(this);
 }
 
 void RenderProcessHostImpl::PopulateTerminationInfoRendererFields(
@@ -4536,4 +4591,10 @@
   bp_message_filter_->SetSubFilterForTesting(std::move(message_filter));
 }
 
+void RenderProcessHostImpl::OnBindHostReceiver(
+    mojo::GenericPendingReceiver receiver) {
+  GetContentClient()->browser()->BindHostReceiverForRenderer(
+      this, std::move(receiver));
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index 64c1b0f..6e7166e 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -23,10 +23,12 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
+#include "base/optional.h"
 #include "base/process/process.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/post_task.h"
+#include "base/threading/sequence_bound.h"
 #include "build/build_config.h"
 #include "content/browser/child_process_launcher.h"
 #include "content/browser/dom_storage/session_storage_namespace_impl.h"
@@ -50,6 +52,7 @@
 #include "media/media_buildflags.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
 #include "mojo/public/cpp/bindings/associated_binding_set.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "mojo/public/cpp/bindings/interface_ptr.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/system/invitation.h"
@@ -205,6 +208,7 @@
   void DisableWebRtcEventLogOutput(int lid) override;
   void BindInterface(const std::string& interface_name,
                      mojo::ScopedMessagePipeHandle interface_pipe) override;
+  void BindReceiver(mojo::GenericPendingReceiver receiver) override;
   const service_manager::Identity& GetChildIdentity() override;
   std::unique_ptr<base::PersistentMemoryAllocator> TakeMetricsAllocator()
       override;
@@ -684,6 +688,11 @@
   void CreateURLLoaderFactoryForRendererProcess(
       network::mojom::URLLoaderFactoryRequest request);
 
+  // Handles incoming requests to bind a process-scoped receiver from the
+  // renderer process. This is posted to the main thread by IOThreadHostImpl
+  // if the request isn't handled on the IO thread.
+  void OnBindHostReceiver(mojo::GenericPendingReceiver receiver);
+
   mojo::OutgoingInvitation mojo_invitation_;
 
   std::unique_ptr<ChildConnection> child_connection_;
@@ -876,7 +885,7 @@
 
   // A WeakPtrFactory which is reset every time Cleanup() runs. Used to vend
   // WeakPtrs which are invalidated any time the RPHI is recycled.
-  std::unique_ptr<base::WeakPtrFactory<RenderProcessHostImpl>>
+  base::Optional<base::WeakPtrFactory<RenderProcessHostImpl>>
       instance_weak_factory_;
 
   FrameSinkProviderImpl frame_sink_provider_;
@@ -894,6 +903,13 @@
 
   IpcSendWatcher ipc_send_watcher_for_testing_;
 
+  // IOThreadHostImpl owns some IO-thread state associated with this
+  // RenderProcessHostImpl. This is mainly to allow various IPCs from the
+  // renderer to be handled on the IO thread without a hop to the UI thread.
+  class IOThreadHostImpl;
+  friend class IOThreadHostImpl;
+  base::Optional<base::SequenceBound<IOThreadHostImpl>> io_thread_host_impl_;
+
   base::WeakPtrFactory<RenderProcessHostImpl> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(RenderProcessHostImpl);
diff --git a/content/browser/renderer_host/render_view_host_factory.cc b/content/browser/renderer_host/render_view_host_factory.cc
index 9daf7a5..540a2d61 100644
--- a/content/browser/renderer_host/render_view_host_factory.cc
+++ b/content/browser/renderer_host/render_view_host_factory.cc
@@ -27,8 +27,7 @@
     int32_t routing_id,
     int32_t main_frame_routing_id,
     int32_t widget_routing_id,
-    bool swapped_out,
-    bool hidden) {
+    bool swapped_out) {
   // RenderViewHost creation can be either browser-driven (by the user opening a
   // new tab) or renderer-driven (by script calling window.open, etc).
   //
@@ -54,7 +53,8 @@
   return new RenderViewHostImpl(
       instance,
       RenderWidgetHostFactory::Create(widget_delegate, instance->GetProcess(),
-                                      widget_routing_id, nullptr, hidden),
+                                      widget_routing_id, nullptr,
+                                      /*hidden=*/true),
       delegate, routing_id, main_frame_routing_id, swapped_out,
       true /* has_initialized_audio_host */);
 }
diff --git a/content/browser/renderer_host/render_view_host_factory.h b/content/browser/renderer_host/render_view_host_factory.h
index 6987d4e8..6098562c 100644
--- a/content/browser/renderer_host/render_view_host_factory.h
+++ b/content/browser/renderer_host/render_view_host_factory.h
@@ -30,8 +30,7 @@
                                 int32_t routing_id,
                                 int32_t main_frame_routing_id,
                                 int32_t widget_routing_id,
-                                bool swapped_out,
-                                bool hidden);
+                                bool swapped_out);
 
   // Returns true if there is currently a globally-registered factory.
   static bool has_factory() {
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 0cda89d4..f81232a 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -266,12 +266,7 @@
         GetWidget()->GetRoutingID());
   }
 
-  // Destroy the RenderWidgetHost.
   GetWidget()->ShutdownAndDestroyWidget(false);
-  if (IsRenderViewLive()) {
-    // Destroy the RenderView, which will also destroy the RenderWidget.
-    GetProcess()->GetRendererInterface()->DestroyView(GetRoutingID());
-  }
 
   ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this);
 
@@ -378,9 +373,6 @@
   GetWidget()->GetVisualProperties(&params->visual_properties, &needs_ack);
   GetWidget()->SetInitialVisualProperties(params->visual_properties, needs_ack);
 
-  // The RenderView is owned by this process. This call must be accompanied by a
-  // DestroyView [see destructor] or else there will be a leak in the renderer
-  // process.
   GetProcess()->GetRendererInterface()->CreateView(std::move(params));
 
   // Let our delegate know that we created a RenderView.
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 5f9c72d..af5be0a 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -599,10 +599,8 @@
   CancelKeyboardLock();
   RejectMouseLockOrUnlockIfNecessary();
 
-  if (process_->IsInitializedAndNotDead() && !owner_delegate()) {
-    // Tell the RendererWidget to close. We only want to do this if the
-    // RenderWidget is the root of the renderer object graph, which is for
-    // pepper fullscreen and popups.
+  if (process_->IsInitializedAndNotDead()) {
+    // Tell the RendererWidget to close.
     bool rv = Send(new WidgetMsg_Close(routing_id_));
     DCHECK(rv);
   }
@@ -752,10 +750,6 @@
   SynchronizeVisualProperties();
 }
 
-void RenderWidgetHostImpl::SetHiddenOnCommit() {
-  Send(new WidgetMsg_WasHidden(routing_id_));
-}
-
 #if defined(OS_ANDROID)
 void RenderWidgetHostImpl::SetImportance(ChildProcessImportance importance) {
   if (importance_ == importance)
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index f16ebcf4d..ed6447bd 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -120,34 +120,6 @@
 
 // This implements the RenderWidgetHost interface that is exposed to
 // embedders of content, and adds things only visible to content.
-//
-// Several core rendering primitives are mirrored between the browser and
-// renderer. These are RenderWidget, RenderFrame and RenderView. Their browser
-// counterparts are RenderWidgetHost, RenderFrameHost and RenderViewHost.
-//
-// For simplicity and clarity, we want the object ownership graph in the
-// renderer to mirror the object ownership graph in the browser. The IPC message
-// that tears down the renderer object graph should be targeted at the root
-// object, and should be sent by the destructor/finalizer of the root object in
-// the browser.
-//
-// Note: We must tear down the renderer object graph with a single IPC to avoid
-// inconsistencies in renderer state.
-//
-// RenderWidget represents a surface that can paint and receive input. It is
-// used in four contexts:
-//   * Main frame for webpage
-//   * Child frame for webpage
-//   * Popups
-//   * Pepper Fullscreen
-//
-// In the first two cases, the RenderFrame is not the root of the renderer
-// object graph. For the main frame, the root is the RenderView. For child
-// frames, the root is RenderFrame. As such, for the first two cases,
-// destruction of the RenderWidgetHost will not trigger destruction of the
-// RenderWidget.
-//
-// Note: We want to converge on RenderFrame always being the root.
 class CONTENT_EXPORT RenderWidgetHostImpl
     : public RenderWidgetHost,
       public FrameTokenMessageQueue::Client,
@@ -344,11 +316,6 @@
   void WasShown(const base::Optional<RecordTabSwitchTimeRequest>&
                     record_tab_switch_time_request);
 
-  // Send a WidgetMsg_WasHidden message to the RenderWidget, without caring
-  // about the visibility state of the RenderWidgetHostImpl. This is used to
-  // address https://crbug.com/936858. See the bug for more details.
-  void SetHiddenOnCommit();
-
 #if defined(OS_ANDROID)
   // Set the importance of widget. The importance is passed onto
   // RenderProcessHost which aggregates importance of all of its widgets.
@@ -993,8 +960,6 @@
   RenderWidgetHostDelegate* delegate_;
 
   // The delegate of the owner of this object.
-  // This member is non-null if and only if this RenderWidgetHost is associated
-  // with a main frame RenderWidget.
   RenderWidgetHostOwnerDelegate* owner_delegate_ = nullptr;
 
   // Created during construction and guaranteed never to be NULL, but its
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index 31fb44db..3b898c91 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -151,13 +151,10 @@
   request.referrer_policy = Referrer::GetDefaultReferrerPolicy();
   request.request_initiator = url::Origin();
   request.load_flags = 0;
-  request.plugin_child_id = -1;
   request.resource_type = static_cast<int>(ResourceType::kXhr);
-  request.appcache_host_id = base::nullopt;
   request.should_reset_appcache = false;
   request.is_main_frame = true;
   request.transition_type = ui::PAGE_TRANSITION_LINK;
-  request.allow_download = true;
   return request;
 }
 
diff --git a/content/browser/service_worker/embedded_worker_test_helper.cc b/content/browser/service_worker/embedded_worker_test_helper.cc
index e187617..8698fcc4 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.cc
+++ b/content/browser/service_worker/embedded_worker_test_helper.cc
@@ -40,7 +40,6 @@
     NOTREACHED();
   }
   void CreateView(mojom::CreateViewParamsPtr) override { NOTREACHED(); }
-  void DestroyView(int32_t) override { NOTREACHED(); }
   void CreateFrame(mojom::CreateFrameParamsPtr) override { NOTREACHED(); }
   void SetUpEmbeddedWorkerChannelForServiceWorker(
       blink::mojom::EmbeddedWorkerInstanceClientRequest client_request)
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 4b447c9..db35296 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -102,6 +102,9 @@
 
 void ServiceWorkerNavigationLoader::DetachedFromRequest() {
   is_detached_ = true;
+  // Clear |fallback_callback_| since it's no longer safe to invoke it because
+  // the bound object has been destroyed.
+  fallback_callback_.Reset();
   DeleteIfNeeded();
 }
 
@@ -283,8 +286,10 @@
     // page, but the risk is that the user will be stuck if there's a persistent
     // failure.
     provider_host_->NotifyControllerLost();
-    std::move(fallback_callback_)
-        .Run(true /* reset_subresource_loader_params */);
+    if (fallback_callback_) {
+      std::move(fallback_callback_)
+          .Run(true /* reset_subresource_loader_params */);
+    }
     return;
   }
 
@@ -294,8 +299,10 @@
     RecordTimingMetrics(false);
     // TODO(falken): Propagate the timing info to the renderer somehow, or else
     // Navigation Timing etc APIs won't know about service worker.
-    std::move(fallback_callback_)
-        .Run(false /* reset_subresource_loader_params */);
+    if (fallback_callback_) {
+      std::move(fallback_callback_)
+          .Run(false /* reset_subresource_loader_params */);
+    }
     return;
   }
 
diff --git a/content/browser/service_worker/service_worker_request_handler_unittest.cc b/content/browser/service_worker/service_worker_request_handler_unittest.cc
index 820797b..daff3d84 100644
--- a/content/browser/service_worker/service_worker_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_request_handler_unittest.cc
@@ -79,15 +79,12 @@
         CreateNavigationHandleCore(helper_->context_wrapper());
     base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host;
     GURL gurl(url);
-    auto common_params = mojom::CommonNavigationParams::New();
-    common_params->referrer = blink::mojom::Referrer::New();
-    common_params->navigation_start = base::TimeTicks::Now();
     auto begin_params = mojom::BeginNavigationParams::New();
     begin_params->request_context_type =
         blink::mojom::RequestContextType::HYPERLINK;
     url::Origin origin = url::Origin::Create(gurl);
     NavigationRequestInfo request_info(
-        std::move(common_params), std::move(begin_params), gurl,
+        CreateCommonNavigationParams(), std::move(begin_params), gurl,
         net::NetworkIsolationKey(origin, origin), true /* is_main_frame */,
         false /* parent_is_main_frame */, true /* are_ancestors_secure */,
         -1 /* frame_tree_node_id */, false /* is_for_guests_only */,
diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc
index 3430552..360e86f 100644
--- a/content/browser/service_worker/service_worker_test_utils.cc
+++ b/content/browser/service_worker/service_worker_test_utils.cc
@@ -217,11 +217,8 @@
           },
           loop.QuitClosure(), &received_info)),
       mojo::MakeRequest(&navigation_client_));
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
   navigation_client_->CommitNavigation(
-      std::move(common_params), CreateCommitNavigationParams(),
+      CreateCommonNavigationParams(), CreateCommitNavigationParams(),
       network::ResourceResponseHead(), mojo::ScopedDataPipeConsumerHandle(),
       nullptr, nullptr, base::nullopt, nullptr, std::move(info),
       mojo::NullRemote(), base::UnguessableToken::Create(),
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index e0c1a98..71ae4e1 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -6214,7 +6214,6 @@
     params->previous_sibling_routing_id = IPC::mojom::kRoutingIdNone;
     params->widget_params = mojom::CreateFrameWidgetParams::New();
     params->widget_params->routing_id = IPC::mojom::kRoutingIdNone;
-    params->widget_params->hidden = true;
     params->replication_state.name = "name";
     params->replication_state.unique_name = "name";
     params->devtools_frame_token = base::UnguessableToken::Create();
@@ -6286,7 +6285,6 @@
     params->previous_sibling_routing_id = IPC::mojom::kRoutingIdNone;
     params->widget_params = mojom::CreateFrameWidgetParams::New();
     params->widget_params->routing_id = widget_routing_id;
-    params->widget_params->hidden = true;
     params->replication_state.name = "name";
     params->replication_state.unique_name = "name";
     params->devtools_frame_token = base::UnguessableToken::Create();
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 0553fec..bc53deb9a 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2801,6 +2801,9 @@
     return;
   }
 
+  bool renderer_started_hidden =
+      params.disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB;
+
   // Create the new web contents. This will automatically create the new
   // WebContentsView. In the future, we may want to create the view separately.
   CreateParams create_params(GetBrowserContext(), site_instance.get());
@@ -2811,8 +2814,7 @@
   create_params.opener_render_process_id = render_process_id;
   create_params.opener_render_frame_id = opener->GetRoutingID();
   create_params.opener_suppressed = params.opener_suppressed;
-  if (params.disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB)
-    create_params.initially_hidden = true;
+  create_params.initially_hidden = renderer_started_hidden;
   create_params.renderer_initiated_creation =
       main_frame_route_id != MSG_ROUTING_NONE;
 
@@ -2847,8 +2849,13 @@
 
       // TODO(brettw): It seems bogus that we have to call this function on the
       // newly created object and give it one of its own member variables.
-      new_view->CreateViewForWidget(
+      RenderWidgetHostView* widget_view = new_view->CreateViewForWidget(
           new_contents_impl->GetRenderViewHost()->GetWidget(), false);
+      if (!renderer_started_hidden) {
+        // RenderWidgets for frames always initialize as hidden. If the renderer
+        // created this window as visible, then we show it here.
+        widget_view->Show();
+      }
     }
     // Save the created window associated with the route so we can show it
     // later.
diff --git a/content/browser/webrtc/webrtc_capture_from_element_browsertest.cc b/content/browser/webrtc/webrtc_capture_from_element_browsertest.cc
index 107cf85..b44c7a40 100644
--- a/content/browser/webrtc/webrtc_capture_from_element_browsertest.cc
+++ b/content/browser/webrtc/webrtc_capture_from_element_browsertest.cc
@@ -107,17 +107,12 @@
   MakeTypicalCall("testCanvasCapture(drawWebGL);", kCanvasCaptureTestHtmlFile);
 }
 
-#if defined(OS_WIN)
 // https://crbug.com/869723
 // Flaky on Windows 10 with Viz (i.e. in viz_content_browsertests).
-#define MAYBE_VerifyCanvasCaptureOffscreenCanvasFrames \
-  DISABLED_VerifyCanvasCaptureOffscreenCanvasFrames
-#else
-#define MAYBE_VerifyCanvasCaptureOffscreenCanvasFrames \
-  VerifyCanvasCaptureOffscreenCanvasFrames
-#endif
+// https://crbug.com/989759
+// Flaky on other platforms due to frame delivery for offscreen canvases.
 IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest,
-                       MAYBE_VerifyCanvasCaptureOffscreenCanvasFrames) {
+                       DISABLED_VerifyCanvasCaptureOffscreenCanvasFrames) {
   MakeTypicalCall("testCanvasCapture(drawOffscreenCanvas);",
                   kCanvasCaptureTestHtmlFile);
 }
diff --git a/content/child/child_thread_impl.cc b/content/child/child_thread_impl.cc
index adbdcf9..e24118f 100644
--- a/content/child/child_thread_impl.cc
+++ b/content/child/child_thread_impl.cc
@@ -275,15 +275,25 @@
       scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner,
       base::WeakPtr<ChildThreadImpl> weak_main_thread,
       base::RepeatingClosure quit_closure,
-      ChildThreadImpl::Options::ServiceBinder service_binder)
+      ChildThreadImpl::Options::ServiceBinder service_binder,
+      mojo::PendingReceiver<mojom::ChildProcessHost> host_receiver)
       : main_thread_task_runner_(std::move(main_thread_task_runner)),
         weak_main_thread_(std::move(weak_main_thread)),
         quit_closure_(std::move(quit_closure)),
-        service_binder_(std::move(service_binder)) {}
+        service_binder_(std::move(service_binder)),
+        host_receiver_(std::move(host_receiver)) {}
   ~ChildProcessImpl() override = default;
 
  private:
   // mojom::ChildProcess:
+  void Initialize(mojo::PendingRemote<mojom::ChildProcessHostBootstrap>
+                      bootstrap) override {
+    // The browser only calls this method once.
+    DCHECK(host_receiver_);
+    mojo::Remote<mojom::ChildProcessHostBootstrap>(std::move(bootstrap))
+        ->BindProcessHost(std::move(host_receiver_));
+  }
+
   void ProcessShutdown() override {
     main_thread_task_runner_->PostTask(FROM_HERE,
                                        base::BindOnce(quit_closure_));
@@ -341,11 +351,32 @@
       service_binder_.Run(std::move(receiver));
   }
 
+  void BindReceiver(mojo::GenericPendingReceiver receiver) override {
+    std::string interface_name = *receiver.interface_name();
+    mojo::ScopedMessagePipeHandle pipe = receiver.PassPipe();
+    // TODO(crbug.com/977637): Update BindChildProcessInterface to take a
+    // GenericPendingReceiver* so we don't have to unpack and re-pack |receiver|
+    // to call this.
+    GetContentClient()->BindChildProcessInterface(interface_name, &pipe);
+    if (!pipe)
+      return;
+    receiver = mojo::GenericPendingReceiver(interface_name, std::move(pipe));
+
+    // TODO(crbug.com/977637): Support something like ServiceBinder for general
+    // interface receiver binding on the IO thread by different ChildThreadImpl
+    // subclasses.
+
+    main_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&ChildThreadImpl::OnBindReceiver,
+                                  weak_main_thread_, std::move(receiver)));
+  }
+
   const scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
   const base::WeakPtr<ChildThreadImpl> weak_main_thread_;
   const base::RepeatingClosure quit_closure_;
 
   ChildThreadImpl::Options::ServiceBinder service_binder_;
+  mojo::PendingReceiver<mojom::ChildProcessHost> host_receiver_;
 
   DISALLOW_COPY_AND_ASSIGN(ChildProcessImpl);
 };
@@ -355,11 +386,13 @@
     base::WeakPtr<ChildThreadImpl> weak_main_thread,
     base::RepeatingClosure quit_closure,
     ChildThreadImpl::Options::ServiceBinder service_binder,
+    mojo::PendingReceiver<mojom::ChildProcessHost> host_receiver,
     mojom::ChildProcessRequest request) {
   mojo::MakeSelfOwnedReceiver<mojom::ChildProcess>(
       std::make_unique<ChildProcessImpl>(
           std::move(main_thread_task_runner), std::move(weak_main_thread),
-          std::move(quit_closure), std::move(service_binder)),
+          std::move(quit_closure), std::move(service_binder),
+          std::move(host_receiver)),
       std::move(request));
 }
 
@@ -557,10 +590,12 @@
   registry->AddInterface(base::Bind(&ChildHistogramFetcherFactoryImpl::Create),
                          GetIOTaskRunner());
 
+  auto host_receiver = child_process_host_.BindNewPipeAndPassReceiver();
   registry->AddInterface(
-      base::BindRepeating(
-          &BindChildProcessImpl, base::ThreadTaskRunnerHandle::Get(),
-          weak_factory_.GetWeakPtr(), quit_closure_, options.service_binder),
+      base::BindRepeating(&BindChildProcessImpl,
+                          base::ThreadTaskRunnerHandle::Get(),
+                          weak_factory_.GetWeakPtr(), quit_closure_,
+                          options.service_binder, base::Passed(&host_receiver)),
       GetIOTaskRunner());
   GetServiceManagerConnection()->AddConnectionFilter(
       std::make_unique<SimpleConnectionFilter>(std::move(registry)));
@@ -733,6 +768,10 @@
   return service_manager_connection_->GetConnector();
 }
 
+void ChildThreadImpl::BindHostReceiver(mojo::GenericPendingReceiver receiver) {
+  child_process_host_->BindHostReceiver(std::move(receiver));
+}
+
 IPC::MessageRouter* ChildThreadImpl::GetRouter() {
   DCHECK(main_thread_runner_->BelongsToCurrentThread());
   return &router_;
@@ -817,6 +856,8 @@
   DLOG(ERROR) << "Ignoring unhandled request to run service: " << service_name;
 }
 
+void ChildThreadImpl::OnBindReceiver(mojo::GenericPendingReceiver receiver) {}
+
 ChildThreadImpl* ChildThreadImpl::current() {
   return g_lazy_child_thread_impl_tls.Pointer()->Get();
 }
diff --git a/content/child/child_thread_impl.h b/content/child/child_thread_impl.h
index fa2607d..38f54cd 100644
--- a/content/child/child_thread_impl.h
+++ b/content/child/child_thread_impl.h
@@ -21,6 +21,7 @@
 #include "components/tracing/common/background_tracing_agent.mojom.h"
 #include "components/variations/child_process_field_trial_syncer.h"
 #include "content/common/associated_interfaces.mojom.h"
+#include "content/common/child_process.mojom.h"
 #include "content/common/content_export.h"
 #include "content/public/child/child_thread.h"
 #include "ipc/ipc.mojom.h"
@@ -30,6 +31,7 @@
 #include "mojo/public/cpp/bindings/associated_binding.h"
 #include "mojo/public/cpp/bindings/associated_binding_set.h"
 #include "mojo/public/cpp/bindings/generic_pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "services/service_manager/public/mojom/service.mojom.h"
 #include "third_party/blink/public/mojom/associated_interfaces/associated_interfaces.mojom.h"
 
@@ -95,6 +97,7 @@
   void RecordComputedAction(const std::string& action) override;
   ServiceManagerConnection* GetServiceManagerConnection() override;
   service_manager::Connector* GetConnector() override;
+  void BindHostReceiver(mojo::GenericPendingReceiver receiver) override;
   scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() override;
   void SetFieldTrialGroup(const std::string& trial_name,
                           const std::string& group_name) override;
@@ -135,10 +138,13 @@
   void GetBackgroundTracingAgentProvider(
       mojo::PendingReceiver<tracing::mojom::BackgroundTracingAgentProvider>
           receiver);
+
   virtual void RunService(
       const std::string& service_name,
       mojo::PendingReceiver<service_manager::mojom::Service> receiver);
 
+  virtual void OnBindReceiver(mojo::GenericPendingReceiver receiver);
+
  protected:
   friend class ChildProcess;
 
@@ -245,6 +251,9 @@
 
   scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
 
+  // An interface to the browser's process host object.
+  mojo::Remote<mojom::ChildProcessHost> child_process_host_;
+
   base::WeakPtrFactory<ChildThreadImpl> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ChildThreadImpl);
diff --git a/content/common/child_process.mojom b/content/common/child_process.mojom
index e00f47f3..565c66b01e 100644
--- a/content/common/child_process.mojom
+++ b/content/common/child_process.mojom
@@ -8,9 +8,39 @@
 import "mojo/public/mojom/base/generic_pending_receiver.mojom";
 import "services/service_manager/public/mojom/service.mojom";
 
+// The primordial interface child processes use to push requests to their
+// browser-side host object.
+interface ChildProcessHost {
+  // Requests that the browser bind |receiver| on behalf of the child process.
+  // Whether or not the interface type encapsulated by |receiver| is supported
+  // depends on the process type and potentially on the Content embedder.
+  BindHostReceiver(mojo_base.mojom.GenericPendingReceiver receiver);
+};
+
+// An interface bound on the browser's IO thread to accept a ChildProcessHost
+// receiver from the child process. A remote to this interface is sent to child
+// processes via |ChildProcess.Initialize()| so that the child process may in
+// turn send back a ChildProcessHost receiver for the browser to bind. This
+// allows the child process to begin queuing messages on its ChildProcessHost
+// immediately upon startup without first waiting for
+// |ChildProcess.Initialize()|.
+//
+// NOTE: This is a separate interface and message rather than a simple reply to
+// |ChildProcess.Initialize()| because RenderProcessHostImpl binds its
+// ChildProcess remote on the main thread, and the ChildProcessHost receiver
+// needs to be accepted and bound on the IO thread without a main-thread hop.
+interface ChildProcessHostBootstrap {
+  BindProcessHost(pending_receiver<ChildProcessHost> receiver);
+};
+
 // A control interface the browser uses to drive the behavior of all types of
 // Content child processes.
 interface ChildProcess {
+  // The first message sent by the process host to the child process. This is
+  // sent to allow the child process to bootstrap its own ChildProcessHost
+  // interface, to which it may have already queued messages.
+  Initialize(pending_remote<ChildProcessHostBootstrap> boostrap);
+
   // Tells the child process that it's safe to shutdown.
   ProcessShutdown();
 
@@ -41,5 +71,18 @@
 
   // Requests that the process bind a receiving pipe targeting the service
   // interface named by |receiver|.
+  //
+  // TODO(crbug.com/977637): Rename this to |RunService()| once the above method
+  // is removed.
   BindServiceInterface(mojo_base.mojom.GenericPendingReceiver receiver);
+
+  // Requests that the process bind a receiving pipe targeting the interface
+  // named by |receiver|. Unlike |BindServiceInterface()| this may be used to
+  // bind arbitrary interfaces on many different types of child processes.
+  // Calls to this method generally end up in
+  // |ChildThreadImpl::OnBindReceiver()|.
+  //
+  // Whether or not the interface type encapsulated by |receiver| is supported
+  // depends on the process type and potentially on the Content embedder.
+  BindReceiver(mojo_base.mojom.GenericPendingReceiver receiver);
 };
diff --git a/content/common/navigation_params.cc b/content/common/navigation_params.cc
index dd256ac..aa493dc0 100644
--- a/content/common/navigation_params.cc
+++ b/content/common/navigation_params.cc
@@ -35,6 +35,14 @@
 
 InitiatorCSPInfo::~InitiatorCSPInfo() = default;
 
+mojom::CommonNavigationParamsPtr CreateCommonNavigationParams() {
+  auto common_params = mojom::CommonNavigationParams::New();
+  common_params->referrer = blink::mojom::Referrer::New();
+  common_params->navigation_start = base::TimeTicks::Now();
+
+  return common_params;
+}
+
 mojom::CommitNavigationParamsPtr CreateCommitNavigationParams() {
   auto commit_params = mojom::CommitNavigationParams::New();
   commit_params->navigation_token = base::UnguessableToken::Create();
diff --git a/content/common/navigation_params.h b/content/common/navigation_params.h
index 4da72d1..6c89ffb 100644
--- a/content/common/navigation_params.h
+++ b/content/common/navigation_params.h
@@ -81,6 +81,7 @@
   base::Optional<CSPSource> initiator_self_source;
 };
 
+CONTENT_EXPORT mojom::CommonNavigationParamsPtr CreateCommonNavigationParams();
 CONTENT_EXPORT mojom::CommitNavigationParamsPtr CreateCommitNavigationParams();
 
 }  // namespace content
diff --git a/content/common/renderer.mojom b/content/common/renderer.mojom
index d34c59f..462be9b 100644
--- a/content/common/renderer.mojom
+++ b/content/common/renderer.mojom
@@ -92,9 +92,6 @@
   // new RenderFrame. If the RenderFrame does not need a RenderWidget, this
   // is MSG_ROUTING_NONE and the other parameters are not read.
   int32 routing_id;
-
-  // Tells the new RenderWidget whether it is initially hidden.
-  bool hidden;
 };
 
 struct CreateFrameParams {
@@ -183,11 +180,6 @@
   // Tells the renderer to create a new view.
   CreateView(CreateViewParams params);
 
-  // Tells the renderer to destroy an existing view. This method must be called
-  // exactly once for each invocation of CreateView. |view_id| is synonymous
-  // with |routing_id|.
-  DestroyView(int32 view_id);
-
   // Tells the renderer to create a new RenderFrame.
   CreateFrame(CreateFrameParams params);
 
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 8321179..3f3a36c 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -44,13 +44,6 @@
 
 namespace content {
 
-void OverrideOnBindInterface(const service_manager::BindSourceInfo& remote_info,
-                             const std::string& name,
-                             mojo::ScopedMessagePipeHandle* handle) {
-  GetContentClient()->browser()->OverrideOnBindInterface(remote_info, name,
-                                                         handle);
-}
-
 std::unique_ptr<BrowserMainParts> ContentBrowserClient::CreateBrowserMainParts(
     const MainFunctionParams& parameters) {
   return nullptr;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 1ef8cbe..eeb0a01b 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -42,6 +42,7 @@
 #include "media/cdm/cdm_proxy.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/interfaces/remoting.mojom.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "net/base/mime_util.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -192,11 +193,6 @@
 struct Referrer;
 struct WebPreferences;
 
-CONTENT_EXPORT void OverrideOnBindInterface(
-    const service_manager::BindSourceInfo& remote_info,
-    const std::string& name,
-    mojo::ScopedMessagePipeHandle* handle);
-
 // Embedder API (or SPI) for participating in browser logic, to be implemented
 // by the client of the content browser. See ChromeContentBrowserClient for the
 // principal implementation. The methods are assumed to be called on the UI
@@ -968,10 +964,11 @@
       const std::string& interface_name,
       mojo::ScopedMessagePipeHandle* interface_pipe) {}
 
-  virtual void OverrideOnBindInterface(
-      const service_manager::BindSourceInfo& remote_info,
-      const std::string& name,
-      mojo::ScopedMessagePipeHandle* handle) {}
+  // Called on the main thread to handle an unhandled interface receiver binding
+  // request from a render process. See |RenderThread::BindHostReceiver()|.
+  virtual void BindHostReceiverForRenderer(
+      RenderProcessHost* render_process_host,
+      mojo::GenericPendingReceiver receiver) {}
 
   // Called just before the Service Manager is initialized.
   virtual void WillStartServiceManager() {}
diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h
index a03ea7c..ae7e5b3c 100644
--- a/content/public/browser/render_process_host.h
+++ b/content/public/browser/render_process_host.h
@@ -24,6 +24,7 @@
 #include "ipc/ipc_channel_proxy.h"
 #include "ipc/ipc_sender.h"
 #include "media/media_buildflags.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "net/base/network_isolation_key.h"
 #include "services/network/public/mojom/network_context.mojom-forward.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
@@ -323,9 +324,21 @@
   virtual void DisableWebRtcEventLogOutput(int lid) = 0;
 
   // Binds interfaces exposed to the browser process from the renderer.
+  //
+  // DEPRECATED: Use |BindReceiver()| instead.
   virtual void BindInterface(const std::string& interface_name,
                              mojo::ScopedMessagePipeHandle interface_pipe) = 0;
 
+  // Asks the renderer process to bind |receiver|. |receiver| arrives in the
+  // renderer process and is carried through the following flow, stopping if any
+  // step decides to bind it:
+  //
+  //   1. IO thread, |ChildProcessImpl::BindReceiver()| (child_thread_impl.cc)
+  //   2. IO thread ,|ContentClient::BindChildProcessInterface()|
+  //   3. Main thread, |ChildThreadImpl::OnBindReceiver()| (virtual)
+  //   4. Possibly more stpes, depending on the ChildThreadImpl subclass.
+  virtual void BindReceiver(mojo::GenericPendingReceiver receiver) = 0;
+
   virtual const service_manager::Identity& GetChildIdentity() = 0;
 
   // Extracts any persistent-memory-allocator used for renderer metrics.
diff --git a/content/public/child/child_thread.h b/content/public/child/child_thread.h
index 8a534950..4337829 100644
--- a/content/public/child/child_thread.h
+++ b/content/public/child/child_thread.h
@@ -11,6 +11,7 @@
 #include "build/build_config.h"
 #include "content/common/content_export.h"
 #include "ipc/ipc_sender.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 
 #if defined(OS_WIN)
 #include <windows.h>
@@ -64,8 +65,29 @@
 
   // Returns a connector that can be used to bind interfaces exposed by other
   // services.
+  //
+  // DEPRECATED: Do not introduce new calls to |GetConnector()|. To bind
+  // browser process receivers scoped to this child process, use
+  // |BindHostReceiver()| below.
   virtual service_manager::Connector* GetConnector() = 0;
 
+  // Asks the browser-side process host object to bind |receiver|. Whether or
+  // not the interface type encapsulated by |receiver| is supported depends on
+  // the process type and potentially on the Content embedder.
+  //
+  // Receivers passed into this method arrive in the browser process and are
+  // taken through one of the following flows, stopping if any step decides to
+  // bind the receiver:
+  //
+  //   For renderers:
+  //       1. IO thread, IOThreadHostImpl::BindHostReceiver.
+  //       2. Main thread, RenderProcessHostImpl::BindHostReceiver.
+  //       3. Main thread, ContentBrowserClient::BindHostReceiverForRenderer.
+  //
+  // TODO(crbug.com/977637): Document behavior for other process types when
+  // their support is added.
+  virtual void BindHostReceiver(mojo::GenericPendingReceiver receiver) = 0;
+
   virtual scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() = 0;
 
   // Tells the child process that a field trial was activated.
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index e3c556b..bfbb5e4 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -147,11 +147,6 @@
   return nullptr;
 }
 
-bool ContentRendererClient::IsPrerenderingFrame(
-    const RenderFrame* render_frame) {
-  return false;
-}
-
 bool ContentRendererClient::IsExternalPepperPlugin(
     const std::string& module_name) {
   return false;
@@ -271,4 +266,7 @@
   return base::nullopt;
 }
 
+void ContentRendererClient::BindReceiverOnMainThread(
+    mojo::GenericPendingReceiver receiver) {}
+
 }  // namespace content
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index bfc62d17..09a7c1f 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -24,6 +24,7 @@
 #include "content/public/renderer/websocket_handshake_throttle_provider.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/supported_types.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "services/service_manager/public/mojom/service.mojom.h"
 #include "third_party/blink/public/platform/web_content_settings_client.h"
 #include "third_party/blink/public/web/web_navigation_policy.h"
@@ -252,7 +253,6 @@
   virtual uint64_t VisitedLinkHash(const char* canonical_url, size_t length);
   virtual bool IsLinkVisited(uint64_t link_hash);
   virtual blink::WebPrescientNetworking* GetPrescientNetworking();
-  virtual bool IsPrerenderingFrame(const RenderFrame* render_frame);
 
   // Returns true if the given Pepper plugin is external (requiring special
   // startup steps).
@@ -433,6 +433,10 @@
   virtual base::Optional<::media::AudioRendererAlgorithmParameters>
   GetAudioRendererAlgorithmParameters(
       ::media::AudioParameters audio_parameters);
+
+  // Handles a request from the browser to bind a receiver on the renderer
+  // processes's main thread.
+  virtual void BindReceiverOnMainThread(mojo::GenericPendingReceiver receiver);
 };
 
 }  // namespace content
diff --git a/content/public/test/mock_render_process_host.cc b/content/public/test/mock_render_process_host.cc
index ddea967..8430e1d 100644
--- a/content/public/test/mock_render_process_host.cc
+++ b/content/public/test/mock_render_process_host.cc
@@ -13,6 +13,7 @@
 #include "base/location.h"
 #include "base/process/process_handle.h"
 #include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/token.h"
@@ -325,8 +326,16 @@
 void MockRenderProcessHost::BindInterface(
     const std::string& interface_name,
     mojo::ScopedMessagePipeHandle interface_pipe) {
-  if (binder_overrides_.count(interface_name) > 0)
-    binder_overrides_[interface_name].Run(std::move(interface_pipe));
+  auto it = binder_overrides_.find(interface_name);
+  if (it != binder_overrides_.end())
+    it->second.Run(std::move(interface_pipe));
+}
+
+void MockRenderProcessHost::BindReceiver(
+    mojo::GenericPendingReceiver receiver) {
+  auto it = binder_overrides_.find(*receiver.interface_name());
+  if (it != binder_overrides_.end())
+    it->second.Run(receiver.PassPipe());
 }
 
 const service_manager::Identity& MockRenderProcessHost::GetChildIdentity() {
diff --git a/content/public/test/mock_render_process_host.h b/content/public/test/mock_render_process_host.h
index c8652d08..ee18b229 100644
--- a/content/public/test/mock_render_process_host.h
+++ b/content/public/test/mock_render_process_host.h
@@ -131,6 +131,7 @@
   void DisableWebRtcEventLogOutput(int lid) override;
   void BindInterface(const std::string& interface_name,
                      mojo::ScopedMessagePipeHandle interface_pipe) override;
+  void BindReceiver(mojo::GenericPendingReceiver receiver) override;
   const service_manager::Identity& GetChildIdentity() override;
   std::unique_ptr<base::PersistentMemoryAllocator> TakeMetricsAllocator()
       override;
diff --git a/content/public/test/mock_render_thread.cc b/content/public/test/mock_render_thread.cc
index 61a5d3d4..664675a 100644
--- a/content/public/test/mock_render_thread.cc
+++ b/content/public/test/mock_render_thread.cc
@@ -145,6 +145,9 @@
   return scoped_refptr<base::SingleThreadTaskRunner>();
 }
 
+void MockRenderThread::BindHostReceiver(mojo::GenericPendingReceiver receiver) {
+}
+
 void MockRenderThread::AddRoute(int32_t routing_id, IPC::Listener* listener) {}
 
 void MockRenderThread::RemoveRoute(int32_t routing_id) {}
diff --git a/content/public/test/mock_render_thread.h b/content/public/test/mock_render_thread.h
index 0c59102..5b55f5bf 100644
--- a/content/public/test/mock_render_thread.h
+++ b/content/public/test/mock_render_thread.h
@@ -60,6 +60,7 @@
   std::string GetLocale() override;
   IPC::SyncMessageFilter* GetSyncMessageFilter() override;
   scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() override;
+  void BindHostReceiver(mojo::GenericPendingReceiver receiver) override;
   void AddRoute(int32_t routing_id, IPC::Listener* listener) override;
   void RemoveRoute(int32_t routing_id) override;
   int GenerateRoutingID() override;
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index 4b4bc5a..b24af4e 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -84,29 +84,24 @@
 
 namespace {
 
-// This class records, and then tears down all existing RenderViews. It's
-// important to do this in two steps, since tearing down a RenderView will
-// mutate the container that RenderView::ForEach() iterates over.
 class CloseMessageSendingRenderViewVisitor : public RenderViewVisitor {
  public:
   CloseMessageSendingRenderViewVisitor() = default;
   ~CloseMessageSendingRenderViewVisitor() override = default;
 
-  void CloseRenderViews() {
-    for (RenderView* render_view : live_render_views) {
-      RenderViewImpl* view_impl = static_cast<RenderViewImpl*>(render_view);
-      view_impl->Destroy();
-    }
-  }
-
  protected:
   bool Visit(RenderView* render_view) override {
-    live_render_views.push_back(render_view);
+    // Simulate the Widget receiving a close message. This should result on
+    // releasing the internal reference counts and destroying the internal
+    // state.
+    RenderWidget* render_widget =
+        static_cast<RenderViewImpl*>(render_view)->GetWidget();
+    WidgetMsg_Close msg(render_widget->routing_id());
+    render_widget->OnMessageReceived(msg);
     return true;
   }
 
  private:
-  std::vector<RenderView*> live_render_views;
   DISALLOW_COPY_AND_ASSIGN(CloseMessageSendingRenderViewVisitor);
 };
 
@@ -447,9 +442,17 @@
   view_params->never_visible = false;
   view_params->visual_properties = *InitialVisualProperties();
 
-  view_ = RenderViewImpl::Create(compositor_deps_.get(), std::move(view_params),
-                                 RenderWidget::ShowCallback(),
-                                 base::ThreadTaskRunnerHandle::Get());
+  RenderViewImpl* view_impl = RenderViewImpl::Create(
+      compositor_deps_.get(), std::move(view_params),
+      RenderWidget::ShowCallback(), base::ThreadTaskRunnerHandle::Get());
+
+  WidgetMsg_WasShown msg(view_impl->GetWidget()->routing_id(),
+                         /* show_request_timestamp=*/base::TimeTicks(),
+                         /* was_evicted=*/false,
+                         /*record_tab_switch_time_request=*/base::nullopt);
+  view_impl->GetWidget()->OnMessageReceived(msg);
+
+  view_ = view_impl;
 }
 
 void RenderViewTest::TearDown() {
@@ -463,7 +466,6 @@
   // opened by the test.
   CloseMessageSendingRenderViewVisitor closing_visitor;
   RenderView::ForEach(&closing_visitor);
-  closing_visitor.CloseRenderViews();
 
   // |view_| is ref-counted and deletes itself during the RunUntilIdle() call
   // below.
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 988c1d46..9135655 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -1042,6 +1042,16 @@
       if (FindExactlyOneInnerImageInMaxDepthTwo(src, &inner_image))
         AddImageAnnotations(inner_image, dst);
     }
+
+    WebNode node = src.GetNode();
+    if (!node.IsNull() && node.IsElementNode()) {
+      WebElement element = node.To<WebElement>();
+      if (element.HasHTMLTagName("input") && element.HasAttribute("type")) {
+        TruncateAndAddStringAttribute(dst,
+                                      ax::mojom::StringAttribute::kInputType,
+                                      element.GetAttribute("type").Utf8());
+      }
+    }
   }
 
   // The majority of the rest of this code computes attributes needed for
@@ -1211,6 +1221,11 @@
   if (!base::FeatureList::IsEnabled(features::kExperimentalAccessibilityLabels))
     return;
 
+  // Reject ignored objects
+  if (src.AccessibilityIsIgnored()) {
+    return;
+  }
+
   // Reject images that are explicitly empty, or that have a name already.
   //
   // In the future, we may annotate some images that have a name
diff --git a/content/renderer/input/widget_input_handler_manager.cc b/content/renderer/input/widget_input_handler_manager.cc
index cc82b0f..a527fddc 100644
--- a/content/renderer/input/widget_input_handler_manager.cc
+++ b/content/renderer/input/widget_input_handler_manager.cc
@@ -469,11 +469,6 @@
     bool sync_compositing) {
   DCHECK(InputThreadTaskRunner()->BelongsToCurrentThread());
 
-  // It is possible that the input_handle has already been destroyed before this
-  // Init() call was invoked. If so, early out.
-  if (!input_handler)
-    return;
-
   // If there's no compositor thread (i.e. we're in a LayoutTest), force input
   // to go through the main thread.
   bool force_input_handling_on_main = !compositor_task_runner_;
diff --git a/content/renderer/loader/navigation_body_loader_unittest.cc b/content/renderer/loader/navigation_body_loader_unittest.cc
index 3dd7878..f15dcc3 100644
--- a/content/renderer/loader/navigation_body_loader_unittest.cc
+++ b/content/renderer/loader/navigation_body_loader_unittest.cc
@@ -45,9 +45,7 @@
     auto endpoints = network::mojom::URLLoaderClientEndpoints::New();
     endpoints->url_loader_client = mojo::MakeRequest(&client_ptr_);
     blink::WebNavigationParams navigation_params;
-    auto common_params = mojom::CommonNavigationParams::New();
-    common_params->referrer = blink::mojom::Referrer::New();
-    common_params->navigation_start = base::TimeTicks::Now();
+    auto common_params = CreateCommonNavigationParams();
     auto commit_params = CreateCommitNavigationParams();
     NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
         *common_params, *commit_params, 1 /* request_id */,
@@ -313,9 +311,7 @@
   net::SSLConnectionStatusSetVersion(net::SSL_CONNECTION_VERSION_TLS1_2,
                                      &response.ssl_info->connection_status);
 
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL("https://example.test");
   auto commit_params = CreateCommitNavigationParams();
 
diff --git a/content/renderer/loader/request_extra_data.cc b/content/renderer/loader/request_extra_data.cc
index 5cd2ba4..690d8e9 100644
--- a/content/renderer/loader/request_extra_data.cc
+++ b/content/renderer/loader/request_extra_data.cc
@@ -15,15 +15,12 @@
 
 void RequestExtraData::CopyToResourceRequest(
     network::ResourceRequest* request) const {
-  request->is_prerendering = is_prerendering_;
   request->render_frame_id = render_frame_id_;
   request->is_main_frame = is_main_frame_;
 
-  request->allow_download = allow_download_;
   request->transition_type = transition_type_;
   request->originated_from_service_worker = originated_from_service_worker_;
 
-  request->initiated_in_secure_context = initiated_in_secure_context_;
   request->attach_same_site_cookies = attach_same_site_cookies_;
 }
 
diff --git a/content/renderer/loader/web_url_loader_impl.cc b/content/renderer/loader/web_url_loader_impl.cc
index aa7687d..73959d75 100644
--- a/content/renderer/loader/web_url_loader_impl.cc
+++ b/content/renderer/loader/web_url_loader_impl.cc
@@ -745,16 +745,8 @@
     resource_request->load_flags |= net::LOAD_DO_NOT_USE_EMBEDDED_IDENTITY;
   }
 
-  // |plugin_child_id| only needs to be non-zero if the request originates
-  // outside the render process, so we can use requestorProcessID even
-  // for requests from in-process plugins.
-  resource_request->plugin_child_id = request.GetPluginChildID();
   resource_request->priority =
       ConvertWebKitPriorityToNetPriority(request.GetPriority());
-  resource_request->appcache_host_id =
-      request.AppCacheHostID().is_empty()
-          ? base::nullopt
-          : base::make_optional(request.AppCacheHostID());
   resource_request->should_reset_appcache = request.ShouldResetAppCache();
   resource_request->is_external_request = request.IsExternalRequest();
   resource_request->cors_preflight_policy = request.GetCorsPreflightPolicy();
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.cc b/content/renderer/loader/web_worker_fetch_context_impl.cc
index 669fd4a0..d19033be 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.cc
+++ b/content/renderer/loader/web_worker_fetch_context_impl.cc
@@ -395,7 +395,6 @@
   auto extra_data = std::make_unique<RequestExtraData>();
   extra_data->set_render_frame_id(ancestor_frame_id_);
   extra_data->set_frame_request_blocker(frame_request_blocker_);
-  extra_data->set_initiated_in_secure_context(is_secure_context_);
   if (throttle_provider_) {
     extra_data->set_url_loader_throttles(throttle_provider_->CreateThrottles(
         ancestor_frame_id_, request, WebURLRequestToResourceType(request)));
@@ -409,7 +408,6 @@
     extra_data->set_navigation_response_override(std::move(response_override_));
   }
   request.SetExtraData(std::move(extra_data));
-  request.SetAppCacheHostID(appcache_host_id_);
 
   if (g_rewrite_url)
     request.SetUrl(g_rewrite_url(request.Url().GetString().Utf8(), false));
@@ -507,10 +505,6 @@
   top_frame_origin_ = top_frame_origin;
 }
 
-void WebWorkerFetchContextImpl::set_is_secure_context(bool flag) {
-  is_secure_context_ = flag;
-}
-
 void WebWorkerFetchContextImpl::set_origin_url(const GURL& origin_url) {
   origin_url_ = origin_url;
 }
@@ -525,11 +519,6 @@
   response_override_ = std::move(response_override);
 }
 
-void WebWorkerFetchContextImpl::SetApplicationCacheHostID(
-    const base::UnguessableToken& id) {
-  appcache_host_id_ = id;
-}
-
 void WebWorkerFetchContextImpl::OnControllerChanged(
     blink::mojom::ControllerServiceWorkerMode mode) {
   set_controller_service_worker_mode(mode);
@@ -562,7 +551,6 @@
   new_context->is_on_sub_frame_ = is_on_sub_frame_;
   new_context->ancestor_frame_id_ = ancestor_frame_id_;
   new_context->frame_request_blocker_ = frame_request_blocker_;
-  new_context->appcache_host_id_ = appcache_host_id_;
   new_context->top_frame_origin_ = top_frame_origin_;
   child_preference_watchers_.AddPtr(std::move(preference_watcher));
   return new_context;
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.h b/content/renderer/loader/web_worker_fetch_context_impl.h
index 84e8fa53..6c5c58a 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.h
+++ b/content/renderer/loader/web_worker_fetch_context_impl.h
@@ -109,7 +109,6 @@
   void DidDisplayContentWithCertificateErrors() override;
   void DidRunInsecureContent(const blink::WebSecurityOrigin&,
                              const blink::WebURL& insecure_url) override;
-  void SetApplicationCacheHostID(const base::UnguessableToken& id) override;
   void SetSubresourceFilterBuilder(
       std::unique_ptr<blink::WebDocumentSubresourceFilter::Builder>) override;
   std::unique_ptr<blink::WebDocumentSubresourceFilter> TakeSubresourceFilter()
@@ -134,7 +133,6 @@
   void set_top_frame_origin(const blink::WebSecurityOrigin& top_frame_origin);
   // Sets whether the worker context is a secure context.
   // https://w3c.github.io/webappsec-secure-contexts/
-  void set_is_secure_context(bool flag);
   void set_origin_url(const GURL& origin_url);
   void set_client_id(const std::string& client_id);
 
@@ -266,7 +264,6 @@
   scoped_refptr<FrameRequestBlocker> frame_request_blocker_;
   GURL site_for_cookies_;
   base::Optional<url::Origin> top_frame_origin_;
-  bool is_secure_context_ = false;
   GURL origin_url_;
   base::UnguessableToken appcache_host_id_;
 
diff --git a/content/renderer/navigation_state.cc b/content/renderer/navigation_state.cc
index 66413a2..d32bfe45 100644
--- a/content/renderer/navigation_state.cc
+++ b/content/renderer/navigation_state.cc
@@ -38,11 +38,8 @@
 
 // static
 std::unique_ptr<NavigationState> NavigationState::CreateContentInitiated() {
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
   return base::WrapUnique(new NavigationState(
-      std::move(common_params), CreateCommitNavigationParams(),
+      CreateCommonNavigationParams(), CreateCommitNavigationParams(),
       base::TimeTicks(), true,
       content::mojom::FrameNavigationControl::CommitNavigationCallback(),
       content::mojom::NavigationClient::CommitNavigationCallback(), nullptr,
diff --git a/content/renderer/pepper/pepper_url_loader_host.cc b/content/renderer/pepper/pepper_url_loader_host.cc
index 7466353c..fe0726f3 100644
--- a/content/renderer/pepper/pepper_url_loader_host.cc
+++ b/content/renderer/pepper/pepper_url_loader_host.cc
@@ -252,7 +252,6 @@
   }
 
   web_request.SetRequestContext(blink::mojom::RequestContextType::PLUGIN);
-  web_request.SetPluginChildID(renderer_ppapi_host_->GetPluginChildId());
 
   // Requests from plug-ins must skip service workers, see the comment in
   // CreateWebURLRequest.
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 27dd917..2c544b6 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1422,7 +1422,6 @@
     mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>
         browser_interface_broker,
     int32_t widget_routing_id,
-    bool hidden,
     const ScreenInfo& screen_info,
     CompositorDependencies* compositor_deps,
     blink::WebFrame* opener,
@@ -1664,8 +1663,8 @@
     scoped_refptr<RenderWidget> render_widget = RenderWidget::CreateForFrame(
         widget_params.routing_id, compositor_deps, screen_info_from_main_frame,
         blink::kWebDisplayModeUndefined,
-        /*is_frozen=*/false, widget_params.hidden,
-        /*never_visible=*/false, /*widget_request=*/nullptr);
+        /*is_frozen=*/false, /*never_visible=*/false,
+        /*widget_request=*/nullptr);
 
     // Non-owning pointer that is self-referencing and destroyed by calling
     // Close(). We use the new RenderWidget as the client for this
@@ -4194,8 +4193,6 @@
       frame_->GetDocument().SiteForCookies());
   worker_fetch_context->set_top_frame_origin(
       frame_->GetDocument().TopFrameOrigin());
-  worker_fetch_context->set_is_secure_context(
-      frame_->GetDocument().IsSecureContext());
   worker_fetch_context->set_origin_url(
       GURL(frame_->GetDocument().Url()).GetOrigin());
 
@@ -4226,8 +4223,6 @@
       frame_->GetDocument().SiteForCookies());
   worker_fetch_context->set_top_frame_origin(
       frame_->GetDocument().TopFrameOrigin());
-  worker_fetch_context->set_is_secure_context(
-      frame_->GetDocument().IsSecureContext());
   worker_fetch_context->set_origin_url(
       GURL(frame_->GetDocument().Url()).GetOrigin());
 
@@ -5411,19 +5406,14 @@
   if (!request.GetExtraData())
     request.SetExtraData(std::make_unique<RequestExtraData>());
   auto* extra_data = static_cast<RequestExtraData*>(request.GetExtraData());
-  extra_data->set_is_preprerendering(
-      GetContentClient()->renderer()->IsPrerenderingFrame(this));
   extra_data->set_custom_user_agent(custom_user_agent);
   extra_data->set_render_frame_id(routing_id_);
   extra_data->set_is_main_frame(IsMainFrame());
-  extra_data->set_allow_download(
-      navigation_state->common_params().download_policy.IsDownloadAllowed());
   extra_data->set_transition_type(transition_type);
   extra_data->set_navigation_response_override(std::move(response_override));
   bool is_for_no_state_prefetch =
       GetContentClient()->renderer()->IsPrefetchOnly(this, request);
   extra_data->set_is_for_no_state_prefetch(is_for_no_state_prefetch);
-  extra_data->set_initiated_in_secure_context(frame_document.IsSecureContext());
   extra_data->set_attach_same_site_cookies(attach_same_site_cookies);
   extra_data->set_frame_request_blocker(frame_request_blocker_);
   extra_data->set_allow_cross_origin_auth_prompt(
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index dcf012ae..0d6c43c5 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -199,7 +199,6 @@
       mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>
           browser_interface_broker,
       int32_t widget_routing_id,
-      bool hidden,
       const ScreenInfo& screen_info,
       CompositorDependencies* compositor_deps,
       blink::WebFrame* opener,
diff --git a/content/renderer/render_frame_impl_browsertest.cc b/content/renderer/render_frame_impl_browsertest.cc
index a0f2cb1..d3dec25 100644
--- a/content/renderer/render_frame_impl_browsertest.cc
+++ b/content/renderer/render_frame_impl_browsertest.cc
@@ -99,7 +99,6 @@
   void LoadChildFrame() {
     mojom::CreateFrameWidgetParams widget_params;
     widget_params.routing_id = kSubframeWidgetRouteId;
-    widget_params.hidden = false;
 
     FrameReplicationState frame_replication_state;
     frame_replication_state.name = "frame";
@@ -448,9 +447,7 @@
   // Verifies navigation to a URL with preset zoom level indeed sets the level.
   // Regression test for http://crbug.com/139559, where the level was not
   // properly set when it is out of the default zoom limits of WebView.
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL("data:text/html,min_zoomlimit_test");
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   GetMainRenderFrame()->SetHostZoomLevel(common_params->url, kMinZoomLevel);
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 97b0d7cb..ee1544d 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1481,6 +1481,10 @@
   return ChildProcess::current()->io_task_runner();
 }
 
+void RenderThreadImpl::OnBindReceiver(mojo::GenericPendingReceiver receiver) {
+  GetContentClient()->renderer()->BindReceiverOnMainThread(std::move(receiver));
+}
+
 bool RenderThreadImpl::IsGpuRasterizationForced() {
   return is_gpu_rasterization_forced_;
 }
@@ -2023,12 +2027,6 @@
                          GetWebMainThreadScheduler()->DefaultTaskRunner());
 }
 
-void RenderThreadImpl::DestroyView(int32_t view_id) {
-  RenderViewImpl* view = RenderViewImpl::FromRoutingID(view_id);
-  DCHECK(view);
-  view->Destroy();
-}
-
 void RenderThreadImpl::CreateFrame(mojom::CreateFrameParamsPtr params) {
   CompositorDependencies* compositor_deps = this;
   service_manager::mojom::InterfaceProviderPtr interface_provider(
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index 05da31b..82e05e9a 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -211,6 +211,9 @@
   // ChildThread implementation via ChildThreadImpl:
   scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() override;
 
+  // ChildThreadImpl implementation:
+  void OnBindReceiver(mojo::GenericPendingReceiver receiver) override;
+
   // CompositorDependencies implementation.
   bool IsGpuRasterizationForced() override;
   int GetGpuRasterizationMSAASampleCount() override;
@@ -506,7 +509,6 @@
   void CreateEmbedderRendererService(
       service_manager::mojom::ServiceRequest service_request) override;
   void CreateView(mojom::CreateViewParamsPtr params) override;
-  void DestroyView(int32_t view_id) override;
   void CreateFrame(mojom::CreateFrameParamsPtr params) override;
   void CreateFrameProxy(
       int32_t routing_id,
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 1b8b9cf9e..118a7dc 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -217,9 +217,7 @@
 // with navigation_start set to Now() plus the given offset.
 mojom::CommonNavigationParamsPtr MakeCommonNavigationParams(
     TimeDelta navigation_start_offset) {
-  mojom::CommonNavigationParamsPtr params =
-      mojom::CommonNavigationParams::New();
-  params->referrer = blink::mojom::Referrer::New();
+  auto params = CreateCommonNavigationParams();
   params->url = GURL("data:text/html,<div>Page</div>");
   params->navigation_start = base::TimeTicks::Now() + navigation_start_offset;
   params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
@@ -435,6 +433,17 @@
            webview->MainFrame()->VisibleContentRect().width;
   }
 
+  // Closes a view created during the test, i.e. not the |view()|. Checks that
+  // the main frame is detached and deleted, and makes sure the view does not
+  // leak.
+  void CloseRenderWidget(RenderWidget* widget) {
+    WidgetMsg_Close msg(widget->routing_id());
+    widget->OnMessageReceived(msg);
+
+    // WidgetMsg_Close posts a task to do the actual closing. Let that run.
+    base::RunLoop().RunUntilIdle();
+  }
+
  private:
   std::unique_ptr<MockKeyboard> mock_keyboard_;
 };
@@ -622,9 +631,7 @@
 
 TEST_F(RenderViewImplTest, OnNavigationHttpPost) {
   // An http url will trigger a resource load so cannot be used here.
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL("data:text/html,<div>Page</div>");
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->transition = ui::PAGE_TRANSITION_TYPED;
@@ -669,9 +676,7 @@
 
 #if defined(OS_ANDROID)
 TEST_F(RenderViewImplTest, OnNavigationLoadDataWithBaseURL) {
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL("data:text/html,");
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->transition = ui::PAGE_TRANSITION_TYPED;
@@ -861,6 +866,9 @@
       ->BeginNavigation(std::move(popup_navigation_info));
   EXPECT_TRUE(render_thread_->sink().GetUniqueMessageMatching(
       FrameHostMsg_OpenURL::ID));
+
+  RenderWidget* render_widget = new_view->GetWidget();
+  CloseRenderWidget(render_widget);
 }
 
 class AlwaysForkingRenderViewTest : public RenderViewImplTest {
@@ -1052,7 +1060,6 @@
 
   mojom::CreateFrameWidgetParams widget_params;
   widget_params.routing_id = view()->GetRoutingID();
-  widget_params.hidden = false;
   RenderFrameImpl::CreateFrame(
       routing_id, std::move(stub_interface_provider),
       std::move(stub_document_interface_broker_content),
@@ -1066,9 +1073,7 @@
   EXPECT_TRUE(provisional_frame);
 
   // Navigate to other page, which triggers the swap in.
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL("data:text/html,<div>Page</div>");
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->transition = ui::PAGE_TRANSITION_TYPED;
@@ -1127,7 +1132,6 @@
 
   mojom::CreateFrameWidgetParams widget_params;
   widget_params.routing_id = MSG_ROUTING_NONE;
-  widget_params.hidden = false;
   RenderFrameImpl::CreateFrame(
       routing_id, std::move(stub_interface_provider),
       std::move(stub_document_interface_broker_content),
@@ -1482,9 +1486,7 @@
 
   // Start a load that will reach provisional state synchronously,
   // but won't complete synchronously.
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->url = GURL("data:text/html,test data");
   frame()->Navigate(std::move(common_params), CreateCommitNavigationParams());
@@ -1864,9 +1866,7 @@
   LoadHTML("hello <iframe srcdoc='fail' name='frame'></iframe>");
 
   // Navigate the frame only.
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL("data:text/html,world");
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->transition = ui::PAGE_TRANSITION_TYPED;
@@ -1980,9 +1980,7 @@
 #endif
 
 TEST_F(RendererErrorPageTest, MAYBE_Suppresses) {
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->url = GURL("http://example.com/suppress");
   TestRenderFrame* main_frame = static_cast<TestRenderFrame*>(frame());
@@ -2004,9 +2002,7 @@
 #endif
 
 TEST_F(RendererErrorPageTest, MAYBE_DoesNotSuppress) {
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->url = GURL("http://example.com/dont-suppress");
   TestRenderFrame* main_frame = static_cast<TestRenderFrame*>(frame());
@@ -2033,9 +2029,7 @@
 TEST_F(RendererErrorPageTest, MAYBE_HttpStatusCodeErrorWithEmptyBody) {
   // Start a load that will reach provisional state synchronously,
   // but won't complete synchronously.
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->navigation_type = mojom::NavigationType::DIFFERENT_DOCUMENT;
   common_params->url = GURL("data:text/html,test data");
 
@@ -2207,9 +2201,7 @@
   base::RunLoop().RunUntilIdle();
   render_thread_->sink().ClearMessages();
 
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
+  auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL(url_string);
   common_params->navigation_type =
       mojom::NavigationType::RELOAD_ORIGINAL_REQUEST_URL;
@@ -2238,9 +2230,7 @@
   render_thread_->sink().ClearMessages();
 
   // Go back.
-  auto common_params_back = mojom::CommonNavigationParams::New();
-  common_params_back->referrer = blink::mojom::Referrer::New();
-  common_params_back->navigation_start = base::TimeTicks::Now();
+  auto common_params_back = CreateCommonNavigationParams();
   common_params_back->url =
       GURL("data:text/html;charset=utf-8,<div id=pagename>Page B</div>");
   common_params_back->transition = ui::PAGE_TRANSITION_FORWARD_BACK;
@@ -2256,9 +2246,7 @@
             navigation_state->common_params().navigation_start);
 
   // Go forward.
-  auto common_params_forward = mojom::CommonNavigationParams::New();
-  common_params_forward->referrer = blink::mojom::Referrer::New();
-  common_params_forward->navigation_start = base::TimeTicks::Now();
+  auto common_params_forward = CreateCommonNavigationParams();
   common_params_forward->url =
       GURL("data:text/html;charset=utf-8,<div id=pagename>Page C</div>");
   common_params_forward->transition = ui::PAGE_TRANSITION_FORWARD_BACK;
@@ -2348,10 +2336,7 @@
   auto commit_params = CreateCommitNavigationParams();
   commit_params->current_history_list_offset = 1;
   commit_params->current_history_list_length = 2;
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
-  frame()->Navigate(std::move(common_params), std::move(commit_params));
+  frame()->Navigate(CreateCommonNavigationParams(), std::move(commit_params));
 
   // The current history list in RenderView is updated.
   EXPECT_EQ(1, view()->HistoryBackListCount());
@@ -2372,10 +2357,7 @@
   commit_params->current_history_list_length = 25;
   commit_params->pending_history_list_offset = 12;
   commit_params->nav_entry_id = 777;
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
-  frame()->Navigate(std::move(common_params), std::move(commit_params));
+  frame()->Navigate(CreateCommonNavigationParams(), std::move(commit_params));
 
   // The current history list in RenderView is updated.
   EXPECT_EQ(12, view()->HistoryBackListCount());
@@ -2395,10 +2377,7 @@
   commit_params->current_history_list_offset = 12;
   commit_params->current_history_list_length = 25;
   commit_params->should_clear_history_list = true;
-  auto common_params = mojom::CommonNavigationParams::New();
-  common_params->referrer = blink::mojom::Referrer::New();
-  common_params->navigation_start = base::TimeTicks::Now();
-  frame()->Navigate(std::move(common_params), std::move(commit_params));
+  frame()->Navigate(CreateCommonNavigationParams(), std::move(commit_params));
 
   // The current history list in RenderView is updated.
   EXPECT_EQ(0, view()->HistoryBackListCount());
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 689d764..035801fb 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -476,8 +476,14 @@
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   DCHECK(RenderThread::IsMainThread());
 
+  // RenderView used to inherit from RenderWidget. Creating a delegate
+  // interface and explicitly passing ownership of ourselves to the
+  // RenderWidget preserves the lifetime semantics. This is a stepping
+  // stone to having RenderWidget creation taken out of the RenderViewImpl
+  // constructor. See the corresponding explicit reset() of the delegate
+  // in the ~RenderWidget(). Also, I hate inheritance.
   render_widget_ = render_widget;
-  GetWidget()->set_delegate(this);
+  GetWidget()->set_delegate(base::WrapUnique(this));
   RenderThread::Get()->AddRoute(routing_id_, this);
 
 #if defined(OS_ANDROID)
@@ -487,8 +493,6 @@
   WebFrame* opener_frame =
       RenderFrameImpl::ResolveOpener(params->opener_frame_route_id);
 
-  // Pass WidgetClient(), not |this|, as the WebWidgetClient. The method may
-  // be overridden in web tests to inject a test-only WebWidgetClient.
   webview_ = WebView::Create(this, params->hidden,
                              /*compositing_enabled=*/true,
                              opener_frame ? opener_frame->View() : nullptr);
@@ -529,10 +533,10 @@
                           ->document_interface_broker_blink)),
         std::move(
             params->main_frame_interface_bundle->browser_interface_broker),
-        params->main_frame_widget_routing_id, params->hidden,
-        GetWidget()->GetWebScreenInfo(), GetWidget()->compositor_deps(),
-        opener_frame, params->devtools_main_frame_token,
-        params->replicated_frame_state, params->has_committed_real_load);
+        params->main_frame_widget_routing_id, GetWidget()->GetWebScreenInfo(),
+        GetWidget()->compositor_deps(), opener_frame,
+        params->devtools_main_frame_token, params->replicated_frame_state,
+        params->has_committed_real_load);
   } else {
     RenderFrameProxy::CreateFrameProxy(params->proxy_routing_id, GetRoutingID(),
                                        opener_frame, MSG_ROUTING_NONE,
@@ -1031,7 +1035,7 @@
       params->visual_properties.screen_info,
       params->visual_properties.display_mode,
       /*is_frozen=*/params->main_frame_routing_id == MSG_ROUTING_NONE,
-      params->hidden, params->never_visible,
+      params->never_visible,
       /*widget_request=*/nullptr);
 
   if (g_create_render_view_impl) {
@@ -1049,12 +1053,6 @@
   return render_view;
 }
 
-void RenderViewImpl::Destroy() {
-  GetWidget()->PrepareForClose();
-  GetWidget()->Close();
-  delete this;
-}
-
 // static
 void RenderViewImpl::InstallCreateHook(RenderViewImpl* (
     *create_render_view_impl)(CompositorDependencies* compositor_deps,
@@ -1448,8 +1446,6 @@
       opener_feature_state;
   view_params->replicated_frame_state.name = frame_name_utf8;
   view_params->devtools_main_frame_token = reply->devtools_main_frame_token;
-  // Even if the main frame has a name, the main frame's unique name is always
-  // the empty string.
   view_params->hidden = is_background_tab;
   view_params->never_visible = never_visible;
   view_params->visual_properties = visual_properties;
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 3e525b05..caf21b4 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -103,6 +103,8 @@
                                       public RenderWidgetDelegate,
                                       public RenderView {
  public:
+  ~RenderViewImpl() override;
+
   // Creates a new RenderView. Note that if the original opener has been closed,
   // |params.window_was_created_with_opener| will be true and
   // |params.opener_frame_route_id| will be MSG_ROUTING_NONE.
@@ -117,11 +119,6 @@
       RenderWidget::ShowCallback show_callback,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
 
-  // Instances of this object are created by and destroyed by the browser
-  // process. This method must be called exactly once by the IPC subsystem when
-  // the browser wishes the object to be destroyed.
-  void Destroy();
-
   // Used by web_test_support to hook into the creation of RenderViewImpls.
   static void InstallCreateHook(RenderViewImpl* (*create_render_view_impl)(
       CompositorDependencies* compositor_deps,
@@ -296,7 +293,6 @@
  protected:
   RenderViewImpl(CompositorDependencies* compositor_deps,
                  const mojom::CreateViewParams& params);
-  ~RenderViewImpl() override;
 
   // Called when Page visibility is changed, to update the View/Page in blink.
   // This is separate from the IPC handlers as tests may call this and need to
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index e4d0c2f..799cc64 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -409,18 +409,17 @@
     const ScreenInfo& screen_info,
     blink::WebDisplayMode display_mode,
     bool is_frozen,
-    bool hidden,
     bool never_visible,
     mojom::WidgetRequest widget_request) {
   if (g_create_render_widget_for_frame) {
     return g_create_render_widget_for_frame(
         widget_routing_id, compositor_deps, screen_info, display_mode,
-        is_frozen, hidden, never_visible, std::move(widget_request));
+        is_frozen, never_visible, std::move(widget_request));
   }
 
   return base::WrapRefCounted(new RenderWidget(
       widget_routing_id, compositor_deps, screen_info, display_mode, is_frozen,
-      hidden, never_visible, std::move(widget_request)));
+      /*hidden=*/true, never_visible, std::move(widget_request)));
 }
 
 scoped_refptr<RenderWidget> RenderWidget::CreateForPopup(
@@ -448,7 +447,6 @@
     : routing_id_(widget_routing_id),
       compositor_deps_(compositor_deps),
       webwidget_internal_(nullptr),
-      delegate_(nullptr),
       auto_resize_mode_(false),
       is_hidden_(hidden),
       compositor_never_visible_(never_visible),
@@ -526,13 +524,7 @@
 
 void RenderWidget::CloseForFrame() {
   DCHECK(for_child_local_root_frame_);
-  PrepareForClose();
-
-  // The RenderWidget may be deattached from JS, which in turn may be called
-  // in a re-entrant context. We cannot synchronously destroy the object, so we
-  // post a task to do so later.
-  GetCleanupTaskRunner()->PostNonNestableTask(
-      FROM_HERE, base::BindOnce(&RenderWidget::Close, this));
+  OnClose();
 }
 
 void RenderWidget::Init(ShowCallback show_callback, WebWidget* web_widget) {
@@ -578,9 +570,8 @@
   mouse_lock_dispatcher_.reset(new RenderWidgetMouseLockDispatcher(this));
 
   RenderThread::Get()->AddRoute(routing_id_, this);
-  // Take a reference. This object is either owned by the browser process, or by
-  // RenderFrame. Both will eventually call Close() when they wish to destroy
-  // this object.
+  // Take a reference on behalf of the RenderThread.  This will be balanced
+  // when we receive WidgetMsg_Close.
   AddRef();
 }
 
@@ -713,14 +704,6 @@
 }
 
 void RenderWidget::OnClose() {
-  // It is always safe to synchronously destroy this object from an IPC message.
-  // That's because the IPC message is asynchronous, which means it can never be
-  // called from a nested context.
-  PrepareForClose();
-  Close();
-}
-
-void RenderWidget::PrepareForClose() {
   DCHECK(RenderThread::IsMainThread());
   if (closing_)
     return;
@@ -748,6 +731,23 @@
     // already-deleted frame.
     CloseWebWidget();
   }
+  // If there is a Send call on the stack, then it could be dangerous to close
+  // now.  Post a task that only gets invoked when there are no nested message
+  // loops.
+  //
+  // The asynchronous Close() takes an owning reference to |this| keeping the
+  // object alive beyond the Release() below. It is the last reference to this
+  // object.
+  //
+  // TODO(https://crbug.com/545684): The actual lifetime for RenderWidget
+  // seems to be single-owner. It is either owned by "IPC" events (popup,
+  // mainframe, and fullscreen), or a RenderFrame. If Close() self-deleting,
+  // all the ref-counting mess could be removed.
+  GetCleanupTaskRunner()->PostNonNestableTask(
+      FROM_HERE, base::BindOnce(&RenderWidget::Close, this));
+
+  // Balances the AddRef taken when we called AddRoute.
+  Release();
 }
 
 void RenderWidget::OnSynchronizeVisualProperties(
@@ -1101,6 +1101,8 @@
   if (!GetWebWidget())
     return;
 
+  DCHECK(!is_frozen_);
+
   // We record metrics only when running in multi-threaded mode, not
   // single-thread mode for testing.
   bool record_main_frame_metrics =
@@ -1298,6 +1300,8 @@
   if (!GetWebWidget())
     return;
 
+  DCHECK(!is_frozen_);
+
   // We record metrics only when running in multi-threaded mode, not
   // single-thread mode for testing.
   bool record_main_frame_metrics =
@@ -1827,6 +1831,11 @@
   if (compositor_never_visible_)
     return;
 
+  // TODO(danakj): We should start the compositor before becoming shown for
+  // *all* provisional frames not just provisional main frames (as they become
+  // thawed). However we need to also prevent BeginMainFrame from occurring, in
+  // both cases, until the frame is attached at least. And in the case of main
+  // frames, until blink requests them through StopDeferringMainFrameUpdate().
   if (is_frozen_) {
     layer_tree_view_->SetVisible(false);
     // Drop all gpu resources, this makes SetVisible(true) more expensive/slower
@@ -1944,9 +1953,6 @@
   // Note the ACK is a control message going to the RenderProcessHost.
   RenderThread::Get()->Send(new WidgetHostMsg_Close_ACK(routing_id()));
   closed_ = true;
-
-  // Balances the AddRef in Init.
-  Release();
 }
 
 void RenderWidget::CloseWebWidget() {
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 3b3fac5b..72ab6536 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -169,9 +169,8 @@
                                       CompositorDependencies*,
                                       const ScreenInfo&,
                                       blink::WebDisplayMode display_mode,
-                                      bool,
-                                      bool,
-                                      bool,
+                                      bool is_frozen,
+                                      bool never_visible,
                                       mojom::WidgetRequest widget_request);
   // Overrides the implementation of CreateForFrame() function below. Used by
   // web tests to return a partial fake of RenderWidget.
@@ -187,7 +186,6 @@
       const ScreenInfo& screen_info,
       blink::WebDisplayMode display_mode,
       bool is_frozen,
-      bool hidden,
       bool never_visible,
       mojom::WidgetRequest widget_request);
 
@@ -217,13 +215,16 @@
   void InitForChildLocalRoot(blink::WebFrameWidget* web_frame_widget);
 
   // Sets a delegate to handle certain RenderWidget operations that need an
-  // escape to the RenderView.
-  void set_delegate(RenderWidgetDelegate* delegate) {
+  // escape to the RenderView. Also take ownership until RenderWidget lifetime
+  // has been reassociated with the RenderFrame. This is only set on Widgets
+  // that are associated with the RenderView.
+  // TODO(ajwong): Do not have RenderWidget own the delegate.
+  void set_delegate(std::unique_ptr<RenderWidgetDelegate> delegate) {
     DCHECK(!delegate_);
-    delegate_ = delegate;
+    delegate_ = std::move(delegate);
   }
 
-  RenderWidgetDelegate* delegate() const { return delegate_; }
+  RenderWidgetDelegate* delegate() const { return delegate_.get(); }
 
   // Returns the RenderWidget for the given routing ID.
   static RenderWidget* FromRoutingID(int32_t routing_id);
@@ -231,15 +232,6 @@
   // Closes a RenderWidget that was created by |CreateForFrame|.
   void CloseForFrame();
 
-  // RenderWidgets cannot always be synchronously destroyed, since that may
-  // happen in a re-entrancy scenario, and there may be existing references on
-  // the stack. This method shuts down further sources of input to the
-  // RenderWidget. This must be called before Close().
-  void PrepareForClose();
-
-  // Close the underlying WebWidget and stop the compositor.
-  virtual void Close();
-
   int32_t routing_id() const { return routing_id_; }
 
   CompositorDependencies* compositor_deps() const { return compositor_deps_; }
@@ -705,6 +697,9 @@
                mojom::WidgetRequest widget_request);
   ~RenderWidget() override;
 
+  // Close the underlying WebWidget and stop the compositor.
+  virtual void Close();
+
   // Notify subclasses that we initiated the paint operation.
   virtual void DidInitiatePaint() {}
 
@@ -939,9 +934,7 @@
   blink::WebWidget* webwidget_internal_;
 
   // The delegate for this object which is just a RenderViewImpl.
-  // This member is non-null if and only if the RenderWidget is associated with
-  // a RenderViewImpl.
-  RenderWidgetDelegate* delegate_;
+  std::unique_ptr<RenderWidgetDelegate> delegate_;
 
   // This is lazily constructed and must not outlive webwidget_.
   std::unique_ptr<LayerTreeView> layer_tree_view_;
diff --git a/content/renderer/render_widget_unittest.cc b/content/renderer/render_widget_unittest.cc
index f3db3ef..86699f8c 100644
--- a/content/renderer/render_widget_unittest.cc
+++ b/content/renderer/render_widget_unittest.cc
@@ -226,6 +226,7 @@
 
  protected:
   ~InteractiveRenderWidget() override {
+    Close();
     webwidget_internal_ = nullptr;
   }
 
@@ -269,18 +270,13 @@
   // testing::Test implementation.
   void SetUp() override {
     widget_ = new InteractiveRenderWidget(&compositor_deps_);
+    // RenderWidget::Init does an AddRef that's balanced by a browser-initiated
+    // Close IPC. That Close will never happen in this test, so do a Release
+    // here to ensure |widget_| is properly freed.
+    widget_->Release();
+    DCHECK(widget_->HasOneRef());
   }
 
-  void DestroyWidget() {
-    if (widget_) {
-      widget_->PrepareForClose();
-      widget_->Close();
-      widget_.reset();
-    }
-  }
-
-  void TearDown() override { DestroyWidget(); }
-
   InteractiveRenderWidget* widget() const { return widget_.get(); }
 
   const base::HistogramTester& histogram_tester() const {
@@ -505,6 +501,11 @@
   // testing::Test implementation.
   void SetUp() override {
     widget_ = new PopupRenderWidget(&compositor_deps_);
+    // RenderWidget::Init does an AddRef that's balanced by a browser-initiated
+    // Close IPC. That Close will never happen in this test, so do a Release
+    // here to ensure |widget_| is properly freed.
+    widget_->Release();
+    DCHECK(widget_->HasOneRef());
   }
 
   PopupRenderWidget* widget() const { return widget_.get(); }
@@ -578,9 +579,7 @@
   EXPECT_FALSE(layer_tree_host->is_external_pinch_gesture_active_for_testing());
 
   // Repeat with a 'mainframe' widget.
-  std::unique_ptr<StubRenderWidgetDelegate> delegate =
-      std::make_unique<StubRenderWidgetDelegate>();
-  widget()->set_delegate(delegate.get());
+  widget()->set_delegate(std::make_unique<StubRenderWidgetDelegate>());
   visual_properties.is_pinch_gesture_active = true;
   widget()->OnSynchronizeVisualProperties(visual_properties);
   // We do not expect the |is_pinch_gesture_active| value to propagate to the
@@ -589,7 +588,6 @@
   // not a pinch gesture is active, and so we shouldn't propagate this
   // information to the layer tree for a main-frame's widget.
   EXPECT_FALSE(layer_tree_host->is_external_pinch_gesture_active_for_testing());
-  DestroyWidget();
 }
 
 TEST_F(RenderWidgetPopupUnittest, EmulatingPopupRect) {
@@ -618,11 +616,10 @@
 
   scoped_refptr<PopupRenderWidget> parent_widget(
       new PopupRenderWidget(&compositor_deps_));
+  parent_widget->Release();  // Balance Init().
 
   // Emulation only happens for RenderWidgets with a delegate.
-  std::unique_ptr<StubRenderWidgetDelegate> delegate =
-      std::make_unique<StubRenderWidgetDelegate>();
-  parent_widget->set_delegate(delegate.get());
+  parent_widget->set_delegate(std::make_unique<StubRenderWidgetDelegate>());
 
   // Setup emulation on the |parent_widget|.
   parent_widget->OnSynchronizeVisualProperties(visual_properties);
diff --git a/content/renderer/service_worker/service_worker_fetch_context_impl.cc b/content/renderer/service_worker/service_worker_fetch_context_impl.cc
index cfe8bea..fb9218f 100644
--- a/content/renderer/service_worker/service_worker_fetch_context_impl.cc
+++ b/content/renderer/service_worker/service_worker_fetch_context_impl.cc
@@ -106,7 +106,6 @@
   }
   auto extra_data = std::make_unique<RequestExtraData>();
   extra_data->set_originated_from_service_worker(true);
-  extra_data->set_initiated_in_secure_context(true);
   if (throttle_provider_) {
     extra_data->set_url_loader_throttles(throttle_provider_->CreateThrottles(
         MSG_ROUTING_NONE, request, WebURLRequestToResourceType(request)));
diff --git a/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc b/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc
index cef498cb..ff00bb2 100644
--- a/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc
+++ b/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc
@@ -32,9 +32,6 @@
     blink::WebURLRequest& request) {
   auto extra_data = std::make_unique<RequestExtraData>();
   extra_data->set_originated_from_service_worker(true);
-  // Service workers are only available in secure contexts, so all requests
-  // are initiated in a secure context.
-  extra_data->set_initiated_in_secure_context(true);
 
   // The RenderThreadImpl or its URLLoaderThrottleProvider member may not be
   // valid in some tests.
diff --git a/content/renderer/worker/embedded_shared_worker_stub.cc b/content/renderer/worker/embedded_shared_worker_stub.cc
index 3806794..57c147bc 100644
--- a/content/renderer/worker/embedded_shared_worker_stub.cc
+++ b/content/renderer/worker/embedded_shared_worker_stub.cc
@@ -185,12 +185,6 @@
   // (crbug.com/723553)
   // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-07#section-2.1.2
   worker_fetch_context->set_site_for_cookies(url_);
-  // TODO(horo): Currently we treat the worker context as secure if the origin
-  // of the shared worker script url is secure. But according to the spec, if
-  // the creation context is not secure, we should treat the worker as
-  // non-secure. crbug.com/723575
-  // https://w3c.github.io/webappsec-secure-contexts/#examples-shared-workers
-  worker_fetch_context->set_is_secure_context(IsOriginSecure(url_));
   worker_fetch_context->set_origin_url(url_.GetOrigin());
 
   DCHECK(response_override_);
diff --git a/content/test/data/accessibility/aria/hidden-described-by-expected-blink.txt b/content/test/data/accessibility/aria/hidden-described-by-expected-blink.txt
new file mode 100644
index 0000000..035d2fd5
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-described-by-expected-blink.txt
@@ -0,0 +1,12 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer ignored
+++++++genericContainer ignored name='span-A1'
+++++++++staticText ignored
+++++++++genericContainer ignored name='span-A2'
+++++++++staticText ignored
+++++++++genericContainer description='span-A4' name='span-A3' descriptionFrom=relatedElement describedbyIds=genericContainer
+++++++++++genericContainer ignored name='span-A4'
+++++++++staticText ignored
+++++++genericContainer description='span-A2' name='span-B' descriptionFrom=relatedElement describedbyIds=genericContainer
+++++++genericContainer name='span-C'
diff --git a/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt b/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
new file mode 100644
index 0000000..9d3a6a6
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
@@ -0,0 +1,4 @@
+document
+++group Name='span-A3' DescribedBy='span-A4'
+++group Name='span-B' DescribedBy='span-A2'
+++group Name='span-C'
diff --git a/content/test/data/accessibility/aria/hidden-described-by-expected-win.txt b/content/test/data/accessibility/aria/hidden-described-by-expected-win.txt
new file mode 100644
index 0000000..6e77a04
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-described-by-expected-win.txt
@@ -0,0 +1,4 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
+++IA2_ROLE_SECTION name='span-A3' description='span-A4'
+++IA2_ROLE_SECTION name='span-B' description='span-A2'
+++IA2_ROLE_SECTION name='span-C'
diff --git a/content/test/data/accessibility/aria/hidden-described-by.html b/content/test/data/accessibility/aria/hidden-described-by.html
new file mode 100644
index 0000000..9fd3f8b
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-described-by.html
@@ -0,0 +1,29 @@
+<!--
+@UIA-WIN-ALLOW:Descr*
+@BLINK-ALLOW:descr*
+@WIN-ALLOW:descr*
+-->
+<!DOCTYPE html>
+<html>
+<style>
+  .hide {
+    visibility: hidden;
+  }
+  .visible {
+    visibility: visible;
+  }
+</style>
+<body>
+  <div>
+    <span class="hide" aria-label="span-A1">
+      <span aria-label="span-A2" id="a2"></span>
+      <span class="visible" aria-label="span-A3" aria-describedby="a4">
+        <span class="hide" aria-label="span-A4" id="a4"></span>
+      </span>
+    </span>
+
+    <span aria-label="span-B" aria-describedby="a2"></span>
+    <span aria-label="span-C"></span>
+  </div>
+</body>
+</html>
diff --git a/content/test/data/accessibility/aria/hidden-expected-blink.txt b/content/test/data/accessibility/aria/hidden-expected-blink.txt
new file mode 100644
index 0000000..20e0cec1
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-expected-blink.txt
@@ -0,0 +1,9 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer ignored
+++++++genericContainer ignored name='a1'
+++++++++staticText ignored
+++++++++genericContainer ignored name='a2'
+++++++++staticText ignored
+++++++genericContainer name='b'
+++++++genericContainer name='c'
diff --git a/content/test/data/accessibility/aria/hidden-expected-uia-win.txt b/content/test/data/accessibility/aria/hidden-expected-uia-win.txt
new file mode 100644
index 0000000..ef2fe77
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-expected-uia-win.txt
@@ -0,0 +1,3 @@
+document
+++group Name='b'
+++group Name='c'
diff --git a/content/test/data/accessibility/aria/hidden-expected-win.txt b/content/test/data/accessibility/aria/hidden-expected-win.txt
new file mode 100644
index 0000000..1d262c97
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-expected-win.txt
@@ -0,0 +1,3 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
+++IA2_ROLE_SECTION name='b'
+++IA2_ROLE_SECTION name='c'
diff --git a/content/test/data/accessibility/aria/hidden-labelled-by-expected-blink.txt b/content/test/data/accessibility/aria/hidden-labelled-by-expected-blink.txt
new file mode 100644
index 0000000..3164933d
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-labelled-by-expected-blink.txt
@@ -0,0 +1,9 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer ignored
+++++++genericContainer ignored name='span-A1'
+++++++genericContainer name='span-A2'
+++++++genericContainer name='span-A4' labelledbyIds=genericContainer
+++++++++genericContainer ignored name='span-A4'
+++++++genericContainer name='span-A2' labelledbyIds=genericContainer
+++++++genericContainer name='span-C'
diff --git a/content/test/data/accessibility/aria/hidden-labelled-by-expected-uia-win.txt b/content/test/data/accessibility/aria/hidden-labelled-by-expected-uia-win.txt
new file mode 100644
index 0000000..f21bba3
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-labelled-by-expected-uia-win.txt
@@ -0,0 +1,5 @@
+document
+++group Name='span-A2'
+++group Name='span-A4' LabeledBy='span-A4'
+++group Name='span-A2' LabeledBy='span-A2'
+++group Name='span-C'
diff --git a/content/test/data/accessibility/aria/hidden-labelled-by-expected-win.txt b/content/test/data/accessibility/aria/hidden-labelled-by-expected-win.txt
new file mode 100644
index 0000000..54cba32
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-labelled-by-expected-win.txt
@@ -0,0 +1,5 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
+++IA2_ROLE_SECTION name='span-A2'
+++IA2_ROLE_SECTION name='span-A4'
+++IA2_ROLE_SECTION name='span-A2'
+++IA2_ROLE_SECTION name='span-C'
diff --git a/content/test/data/accessibility/aria/hidden-labelled-by.html b/content/test/data/accessibility/aria/hidden-labelled-by.html
new file mode 100644
index 0000000..a865bf8d
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden-labelled-by.html
@@ -0,0 +1,29 @@
+<!--
+@UIA-WIN-ALLOW:Label*
+@BLINK-ALLOW:label*
+@WIN-ALLOW:label*
+-->
+<!DOCTYPE html>
+<html>
+<style>
+  .hide {
+    visibility: hidden;
+  }
+  .visible {
+    visibility: visible;
+  }
+</style>
+<body>
+  <div>
+    <span class="hide" aria-label="span-A1"></span>
+      <span aria-label="span-A2" id="a2"></span>
+      <span class="visible" aria-label="span-A3" aria-labelledby="a4">
+        <span class="hide" aria-label="span-A4" id="a4"></span>
+      </span>
+    </span>
+
+    <span aria-label="span-B" aria-labelledby="a2"></span>
+    <span aria-label="span-C"></span>
+  </div>
+</body>
+</html>
diff --git a/content/test/data/accessibility/aria/hidden.html b/content/test/data/accessibility/aria/hidden.html
new file mode 100644
index 0000000..4d8f972
--- /dev/null
+++ b/content/test/data/accessibility/aria/hidden.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<style>
+  .hide {
+    visibility: hidden;
+  }
+</style>
+<body>
+  <div>
+    <span class="hide" aria-label="a1">
+      <span aria-label="a2"></span>
+    </span>
+
+    <span aria-label="b"></span>
+    <span aria-label="c"></span>
+  </div>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/css-visibility-expected-win.txt b/content/test/data/accessibility/event/css-visibility-expected-win.txt
index a267f80..d04c621 100644
--- a/content/test/data/accessibility/event/css-visibility-expected-win.txt
+++ b/content/test/data/accessibility/event/css-visibility-expected-win.txt
@@ -1,4 +1,4 @@
-EVENT_OBJECT_HIDE on <div.a> role=DIV level=2
+EVENT_OBJECT_HIDE on <div.a> role=DIV name="Heading" level=2
 EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
 EVENT_OBJECT_SHOW on <div.b> role=ROLE_SYSTEM_GROUPING name="Banner"
 IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL new_text={'<obj>' start=0 end=1}
diff --git a/content/test/data/accessibility/html/input-tel-expected-blink.txt b/content/test/data/accessibility/html/input-tel-expected-blink.txt
index aab317c..8bead69 100644
--- a/content/test/data/accessibility/html/input-tel-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-tel-expected-blink.txt
@@ -1,6 +1,6 @@
 rootWebArea
 ++genericContainer
-++++textField value='123-456-7890'
+++++textField inputType='tel' value='123-456-7890'
 ++++++genericContainer
 ++++++++staticText name='123-456-7890'
 ++++++++++inlineTextBox name='123-456-7890'
diff --git a/content/test/data/accessibility/html/input-tel-expected-uia-win.txt b/content/test/data/accessibility/html/input-tel-expected-uia-win.txt
index 9a17b528..f3fbeab 100644
--- a/content/test/data/accessibility/html/input-tel-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-tel-expected-uia-win.txt
@@ -1,3 +1,3 @@
-document
-++group
-++++textbox Value.Value='123-456-7890'
+document LocalizedControlType='document'
+++group LocalizedControlType='group'
+++++textbox LocalizedControlType='telephone' Value.Value='123-456-7890'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/input-tel-expected-win.txt b/content/test/data/accessibility/html/input-tel-expected-win.txt
index e512259..2c617092 100644
--- a/content/test/data/accessibility/html/input-tel-expected-win.txt
+++ b/content/test/data/accessibility/html/input-tel-expected-win.txt
@@ -1,3 +1,3 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0>' n_selections=0
 ++IA2_ROLE_SECTION ia2_hypertext='<obj0>' n_selections=0
-++++ROLE_SYSTEM_TEXT value='123-456-7890' FOCUSABLE text-input-type:tel ia2_hypertext='123-456-7890' caret_offset=0 n_selections=0
+++++ROLE_SYSTEM_TEXT value='123-456-7890' FOCUSABLE text-input-type:tel ia2_hypertext='123-456-7890' caret_offset=0 n_selections=0 localized_extended_role='telephone'
diff --git a/content/test/data/accessibility/html/input-tel.html b/content/test/data/accessibility/html/input-tel.html
index 5e13ef03..c95c882 100644
--- a/content/test/data/accessibility/html/input-tel.html
+++ b/content/test/data/accessibility/html/input-tel.html
@@ -1,6 +1,9 @@
 <!--
+@BLINK-ALLOW:inputType=*
+@UIA-WIN-ALLOW:LocalizedControlType=*
 @WIN-ALLOW:caret_offset*
 @WIN-ALLOW:ia2_hypertext=*
+@WIN-ALLOW:localized_extended_role=*
 @WIN-ALLOW:n_selections*
 @WIN-ALLOW:selection_start*
 @WIN-ALLOW:selection_end*
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index fc8ea1e..c5bdfb3 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -61,8 +61,7 @@
     FrameTree* frame_tree,
     FrameTreeNode* frame_tree_node,
     int32_t routing_id,
-    int32_t widget_routing_id,
-    int flags)
+    int32_t widget_routing_id)
     : RenderFrameHostImpl(site_instance,
                           std::move(render_view_host),
                           delegate,
@@ -70,8 +69,7 @@
                           frame_tree_node,
                           routing_id,
                           widget_routing_id,
-                          flags,
-                          false),
+                          /*renderer_initiated_creation=*/false),
       child_creation_observer_(delegate ? delegate->GetAsWebContents()
                                         : nullptr),
       simulate_history_list_was_cleared_(false),
diff --git a/content/test/test_render_frame_host.h b/content/test/test_render_frame_host.h
index a340798..b2e3d89 100644
--- a/content/test/test_render_frame_host.h
+++ b/content/test/test_render_frame_host.h
@@ -52,8 +52,7 @@
                       FrameTree* frame_tree,
                       FrameTreeNode* frame_tree_node,
                       int32_t routing_id,
-                      int32_t widget_routing_id,
-                      int flags);
+                      int32_t widget_routing_id);
   ~TestRenderFrameHost() override;
 
   // RenderFrameHostImpl overrides (same values, but in Test*/Mock* types)
diff --git a/content/test/test_render_frame_host_factory.cc b/content/test/test_render_frame_host_factory.cc
index c79d386..e808fab 100644
--- a/content/test/test_render_frame_host_factory.cc
+++ b/content/test/test_render_frame_host_factory.cc
@@ -27,11 +27,11 @@
     FrameTreeNode* frame_tree_node,
     int32_t routing_id,
     int32_t widget_routing_id,
-    bool hidden,
     bool renderer_initiated_creation) {
+  DCHECK(!renderer_initiated_creation);
   return std::make_unique<TestRenderFrameHost>(
       site_instance, std::move(render_view_host), delegate, frame_tree,
-      frame_tree_node, routing_id, widget_routing_id, hidden);
+      frame_tree_node, routing_id, widget_routing_id);
 }
 
 }  // namespace content
diff --git a/content/test/test_render_frame_host_factory.h b/content/test/test_render_frame_host_factory.h
index ee29dfd..f27d534 100644
--- a/content/test/test_render_frame_host_factory.h
+++ b/content/test/test_render_frame_host_factory.h
@@ -35,7 +35,6 @@
       FrameTreeNode* frame_tree_node,
       int32_t routing_id,
       int32_t widget_routing_id,
-      bool hidden,
       bool renderer_initiated_creation) override;
 
  private:
diff --git a/content/test/web_test_support.cc b/content/test/web_test_support.cc
index 7405ba5..c3238df 100644
--- a/content/test/web_test_support.cc
+++ b/content/test/web_test_support.cc
@@ -93,12 +93,11 @@
     const ScreenInfo& screen_info,
     blink::WebDisplayMode display_mode,
     bool swapped_out,
-    bool hidden,
     bool never_visible,
     mojom::WidgetRequest widget_request) {
   return base::MakeRefCounted<test_runner::WebWidgetTestProxy>(
       routing_id, compositor_deps, screen_info, display_mode, swapped_out,
-      hidden, never_visible, std::move(widget_request));
+      /*hidden=*/true, never_visible, std::move(widget_request));
 }
 
 RenderFrameImpl* CreateWebFrameTestProxy(RenderFrameImpl::CreateParams params) {
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 946f6bf..34c3dff 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -332,6 +332,52 @@
   }
 }
 
+if (enable_openxr) {
+  # The OpenXR Loader by default looks for the path to the OpenXR Runtime from a
+  # registry key, which typically points to the OpenXR runtime installed on the
+  # system. In test, we want to use the mock OpenXR runtime that is created
+  # below in :openxr_mock. If the XR_RUNTIME_JSON environment variable is set,
+  # the OpenXR loader instead looks for the path to the OpenXR runtime in the
+  # json file instead of the registry key. This json file copied to the output
+  # folder points to our mock OpenXR runtime.
+  copy("json_mock") {
+    sources = [
+      "openxr/test/openxr.json",
+    ]
+    outputs = [
+      "$root_out_dir/mock_vr_clients/bin/openxr/openxr.json",
+    ]
+  }
+
+  shared_library("openxr_mock") {
+    testonly = true
+    output_name = "mock_vr_clients/bin/openxr/openxrruntime"
+
+    sources = [
+      "openxr/openxr_util.cc",
+      "openxr/openxr_util.h",
+      "openxr/test/fake_openxr_impl_api.cc",
+      "openxr/test/openxr_negotiate.h",
+      "openxr/test/openxr_test_helper.cc",
+      "openxr/test/openxr_test_helper.h",
+      "test/test_hook.h",
+    ]
+
+    configs += [ "//third_party/openxr:config" ]
+
+    libs = [
+      "d3d11.lib",
+      "DXGI.lib",
+    ]
+
+    deps = [
+      "//base",
+      "//device/vr:json_mock",
+      "//device/vr/public/mojom:test_mojom",
+    ]
+  }
+}
+
 if (enable_gvr_services) {
   java_sources_needing_jni =
       [ "android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java" ]
diff --git a/device/vr/openxr/openxr_api_wrapper.cc b/device/vr/openxr/openxr_api_wrapper.cc
index 1680c0b..a8ec334 100644
--- a/device/vr/openxr/openxr_api_wrapper.cc
+++ b/device/vr/openxr/openxr_api_wrapper.cc
@@ -12,6 +12,7 @@
 #include "base/logging.h"
 #include "device/vr/openxr/openxr_gamepad_helper.h"
 #include "device/vr/openxr/openxr_util.h"
+#include "device/vr/test/test_hook.h"
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/quaternion.h"
 
@@ -130,6 +131,17 @@
   }
 
   DCHECK(IsInitialized());
+
+  if (test_hook_) {
+    // Allow our mock implementation of OpenXR to be controlled by tests.
+    // The mock implementation of xrCreateInstance returns a pointer to the
+    // service test hook (g_test_helper) as the instance.
+    service_test_hook_ = reinterpret_cast<ServiceTestHook*>(instance_);
+    service_test_hook_->SetTestHook(test_hook_);
+
+    test_hook_->AttachCurrentThread();
+  }
+
   return true;
 }
 
@@ -145,6 +157,9 @@
     xrDestroyInstance(instance_);
   }
 
+  if (test_hook_)
+    test_hook_->DetachCurrentThread();
+
   Reset();
 }
 
@@ -403,7 +418,7 @@
   wait_info.timeout = XR_INFINITE_DURATION;
 
   RETURN_IF_XR_FAILED(xrWaitSwapchainImage(color_swapchain_, &wait_info));
-  RETURN_IF_XR_FAILED(UpdateProjectionLayers());
+  RETURN_IF_XR_FAILED(UpdateProjectionLayers(frame_state.predictedDisplayTime));
 
   *texture = color_swapchain_images_[color_swapchain_image_index].texture;
   frame_state_ = frame_state;
@@ -445,13 +460,14 @@
   return xr_result;
 }
 
-XrResult OpenXrApiWrapper::UpdateProjectionLayers() {
+XrResult OpenXrApiWrapper::UpdateProjectionLayers(
+    XrTime predicted_display_time) {
   XrResult xr_result;
 
   XrViewState view_state = {XR_TYPE_VIEW_STATE};
 
   XrViewLocateInfo view_locate_info = {XR_TYPE_VIEW_LOCATE_INFO};
-  view_locate_info.displayTime = frame_state_.predictedDisplayTime;
+  view_locate_info.displayTime = predicted_display_time;
   view_locate_info.space = local_space_;
 
   uint32_t view_count = 0;
@@ -506,6 +522,9 @@
   RETURN_IF_XR_FAILED(xrLocateSpace(
       view_space_, local_space_, frame_state_.predictedDisplayTime, &relation));
 
+  DCHECK(relation.relationFlags & XR_SPACE_RELATION_ORIENTATION_VALID_BIT);
+  DCHECK(relation.relationFlags & XR_SPACE_RELATION_POSITION_VALID_BIT);
+
   orientation->set_x(relation.pose.orientation.x);
   orientation->set_y(relation.pose.orientation.y);
   orientation->set_z(relation.pose.orientation.z);
@@ -559,4 +578,16 @@
       ->recommendedSwapchainSampleCount;
 }
 
+VRTestHook* OpenXrApiWrapper::test_hook_ = nullptr;
+ServiceTestHook* OpenXrApiWrapper::service_test_hook_ = nullptr;
+void OpenXrApiWrapper::SetTestHook(VRTestHook* hook) {
+  // This may be called from any thread - tests are responsible for
+  // maintaining thread safety, typically by not changing the test hook
+  // while presenting.
+  test_hook_ = hook;
+  if (service_test_hook_) {
+    service_test_hook_->SetTestHook(test_hook_);
+  }
+}
+
 }  // namespace device
diff --git a/device/vr/openxr/openxr_api_wrapper.h b/device/vr/openxr/openxr_api_wrapper.h
index dcb97426..1fc638dc 100644
--- a/device/vr/openxr/openxr_api_wrapper.h
+++ b/device/vr/openxr/openxr_api_wrapper.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "device/vr/vr_export.h"
 #include "third_party/openxr/include/openxr/openxr.h"
 #include "third_party/openxr/include/openxr/openxr_platform.h"
 
@@ -23,6 +24,8 @@
 namespace device {
 
 class OpenXrGamepadHelper;
+class VRTestHook;
+class ServiceTestHook;
 
 class OpenXrApiWrapper {
  public:
@@ -34,6 +37,8 @@
   static bool IsHardwareAvailable();
   static bool IsApiAvailable();
 
+  static VRTestHook* GetTestHook();
+
   XrResult StartSession(const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device,
                         std::unique_ptr<OpenXrGamepadHelper>* gamepad_helper);
 
@@ -45,6 +50,8 @@
 
   XrTime GetPredictedDisplayTime() const;
 
+  static void DEVICE_VR_EXPORT SetTestHook(VRTestHook* hook);
+
   void GetViewSize(uint32_t* width, uint32_t* height) const;
   XrResult GetLuid(LUID* luid) const;
 
@@ -66,7 +73,7 @@
       std::unique_ptr<OpenXrGamepadHelper>* gamepad_helper);
 
   XrResult BeginSession();
-  XrResult UpdateProjectionLayers();
+  XrResult UpdateProjectionLayers(XrTime predicted_display_time);
 
   bool HasInstance() const;
   bool HasSystem() const;
@@ -78,7 +85,11 @@
 
   uint32_t GetRecommendedSwapchainSampleCount() const;
 
-  // OpenXr objects
+  // Testing objects
+  static VRTestHook* test_hook_;
+  static ServiceTestHook* service_test_hook_;
+
+  // OpenXR objects
 
   // These objects are valid on successful initialization.
   XrInstance instance_;
diff --git a/device/vr/openxr/openxr_render_loop.cc b/device/vr/openxr/openxr_render_loop.cc
index 1c656b1..8da8e69 100644
--- a/device/vr/openxr/openxr_render_loop.cc
+++ b/device/vr/openxr/openxr_render_loop.cc
@@ -20,17 +20,16 @@
 }
 
 mojom::XRFrameDataPtr OpenXrRenderLoop::GetNextFrameData() {
-  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
+  mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
+  frame_data->frame_id = next_frame_id_;
 
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
   if (XR_FAILED(openxr_->BeginFrame(&texture))) {
-    return nullptr;
+    return frame_data;
   }
 
   texture_helper_.SetBackbuffer(texture.Get());
 
-  mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
-  frame_data->frame_id = next_frame_id_;
-
   frame_data->time_delta =
       base::TimeDelta::FromNanoseconds(openxr_->GetPredictedDisplayTime());
 
diff --git a/device/vr/openxr/openxr_util.h b/device/vr/openxr/openxr_util.h
index 663fb7d..91969e0 100644
--- a/device/vr/openxr/openxr_util.h
+++ b/device/vr/openxr/openxr_util.h
@@ -30,6 +30,14 @@
       return XR_STATE_UNAVAILABLE;                \
   } while (false)
 
+#define RETURN_IF_FALSE(condition, error_code, msg) \
+  do {                                              \
+    if (!(condition)) {                             \
+      LOG(ERROR) << __FUNCTION__ << ": " << msg;    \
+      return error_code;                            \
+    }                                               \
+  } while (false)
+
 // Returns the identity pose, where the position is {0, 0, 0} and the
 // orientation is {0, 0, 0, 1}.
 XrPosef PoseIdentity();
diff --git a/device/vr/openxr/test/DEPS b/device/vr/openxr/test/DEPS
new file mode 100644
index 0000000..11b30de
--- /dev/null
+++ b/device/vr/openxr/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+third_party/openxr/include/openxr",
+]
diff --git a/device/vr/openxr/test/fake_openxr_impl_api.cc b/device/vr/openxr/test/fake_openxr_impl_api.cc
new file mode 100644
index 0000000..4a63bd0
--- /dev/null
+++ b/device/vr/openxr/test/fake_openxr_impl_api.cc
@@ -0,0 +1,488 @@
+// 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 <directxmath.h>
+#include <wrl.h>
+
+#include "device/vr/openxr/openxr_util.h"
+#include "device/vr/openxr/test/openxr_negotiate.h"
+#include "device/vr/openxr/test/openxr_test_helper.h"
+
+namespace {
+// Global test helper that communicates with the test and contains the mock
+// OpenXR runtime state/properties. A reference to this is returned as the
+// instance handle through xrCreateInstance.
+OpenXrTestHelper g_test_helper;
+}  // namespace
+
+// Mock implementations of openxr runtime.dll APIs.
+// Please add new APIs in alphabetical order.
+
+XrResult xrAcquireSwapchainImage(
+    XrSwapchain swapchain,
+    const XrSwapchainImageAcquireInfo* acquire_info,
+    uint32_t* index) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(acquire_info->type == XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrAcquireSwapchainImage type invalid");
+
+  *index = g_test_helper.NextSwapchainImageIndex();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrBeginFrame(XrSession session,
+                      const XrFrameBeginInfo* frame_begin_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(frame_begin_info->type == XR_TYPE_FRAME_BEGIN_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "XrFrameBeginInfo type invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult xrBeginSession(XrSession session,
+                        const XrSessionBeginInfo* begin_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(begin_info->type == XR_TYPE_SESSION_BEGIN_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSessionBeginInfo type invalid");
+  RETURN_IF_FALSE(begin_info->primaryViewConfigurationType ==
+                      XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSessionBeginInfo primaryViewConfigurationType invalid");
+
+  RETURN_IF_XR_FAILED(g_test_helper.BeginSession());
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateInstance(const XrInstanceCreateInfo* create_info,
+                          XrInstance* instance) {
+  DLOG(INFO) << __FUNCTION__;
+
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_INSTANCE_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrInstanceCreateInfo type invalid");
+
+  RETURN_IF_FALSE(create_info->enabledExtensionCount ==
+                      OpenXrTestHelper::NumExtensionsSupported(),
+                  XR_ERROR_VALIDATION_FAILURE, "enabledExtensionCount invalid");
+
+  for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) {
+    bool valid_extension = false;
+    for (size_t j = 0; j < OpenXrTestHelper::NumExtensionsSupported(); j++) {
+      if (strcmp(create_info->enabledExtensionNames[i],
+                 OpenXrTestHelper::kExtensions[j]) == 0) {
+        valid_extension = true;
+        break;
+      }
+    }
+
+    RETURN_IF_FALSE(valid_extension, XR_ERROR_VALIDATION_FAILURE,
+                    "enabledExtensionNames contains invalid extensions");
+  }
+
+  // Return the test helper object back to the OpenXrAPIWrapper so it can use
+  // it as the TestHookRegistration.
+  *instance = reinterpret_cast<XrInstance>(&g_test_helper);
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateReferenceSpace(XrSession session,
+                                const XrReferenceSpaceCreateInfo* create_info,
+                                XrSpace* space) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrReferenceSpaceCreateInfo type invalid");
+  RETURN_IF_FALSE(
+      create_info->referenceSpaceType == XR_REFERENCE_SPACE_TYPE_LOCAL ||
+          create_info->referenceSpaceType == XR_REFERENCE_SPACE_TYPE_VIEW,
+      XR_ERROR_VALIDATION_FAILURE,
+      "XrReferenceSpaceCreateInfo referenceSpaceType invalid");
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateXrPosefIsIdentity(
+      create_info->poseInReferenceSpace));
+
+  switch (create_info->referenceSpaceType) {
+    case XR_REFERENCE_SPACE_TYPE_LOCAL:
+      *space = g_test_helper.CreateLocalSpace();
+      break;
+    case XR_REFERENCE_SPACE_TYPE_VIEW:
+      *space = g_test_helper.CreateViewSpace();
+      break;
+    default:
+      RETURN_IF_FALSE(false, XR_ERROR_VALIDATION_FAILURE,
+                      "XrReferenceSpaceCreateInfo referenceSpaceType invalid");
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateSession(XrInstance instance,
+                         const XrSessionCreateInfo* create_info,
+                         XrSession* session) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_SESSION_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSessionCreateInfo type invalid");
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(create_info->systemId));
+
+  const XrGraphicsBindingD3D11KHR* binding =
+      static_cast<const XrGraphicsBindingD3D11KHR*>(create_info->next);
+  RETURN_IF_FALSE(binding->type == XR_TYPE_GRAPHICS_BINDING_D3D11_KHR,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrGraphicsBindingD3D11KHR type invalid");
+  RETURN_IF_FALSE(binding->device != nullptr, XR_ERROR_VALIDATION_FAILURE,
+                  "D3D11Device is null");
+
+  g_test_helper.SetD3DDevice(binding->device);
+  *session = g_test_helper.GetSession();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateSwapchain(XrSession session,
+                           const XrSwapchainCreateInfo* create_info,
+                           XrSwapchain* swapchain) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_SWAPCHAIN_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo type invalid");
+  RETURN_IF_FALSE(create_info->arraySize == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo arraySize invalid");
+  RETURN_IF_FALSE(create_info->format == DXGI_FORMAT_R8G8B8A8_UNORM,
+                  XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED,
+                  "XrSwapchainCreateInfo format unsupported");
+  RETURN_IF_FALSE(create_info->width == OpenXrTestHelper::kDimension * 2,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo width is not dimension * 2");
+  RETURN_IF_FALSE(create_info->height == OpenXrTestHelper::kDimension,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo height is not dimension");
+  RETURN_IF_FALSE(create_info->mipCount == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo mipCount is not 1");
+  RETURN_IF_FALSE(create_info->faceCount == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo faceCount is not 1");
+  RETURN_IF_FALSE(create_info->sampleCount == OpenXrTestHelper::kSwapCount,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo sampleCount invalid");
+  RETURN_IF_FALSE(
+      create_info->usageFlags == XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
+      XR_ERROR_VALIDATION_FAILURE,
+      "XrSwapchainCreateInfo usageFlags is not "
+      "XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT");
+
+  *swapchain = g_test_helper.GetSwapchain();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrDestroyInstance(XrInstance instance) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEndFrame(XrSession session, const XrFrameEndInfo* frame_end_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(frame_end_info->type == XR_TYPE_FRAME_END_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "frame_end_info type invalid");
+  RETURN_IF_FALSE(frame_end_info->environmentBlendMode ==
+                      OpenXrTestHelper::kEnvironmentBlendMode,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "frame_end_info environmentBlendMode invalid");
+  RETURN_IF_FALSE(frame_end_info->layerCount == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "frame_end_info layerCount invalid");
+  RETURN_IF_XR_FAILED(
+      g_test_helper.ValidatePredictedDisplayTime(frame_end_info->displayTime));
+
+  g_test_helper.OnPresentedFrame();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEndSession(XrSession session) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_XR_FAILED(g_test_helper.EndSession());
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateEnvironmentBlendModes(
+    XrInstance instance,
+    XrSystemId system_id,
+    uint32_t environmentBlendModeCapacityInput,
+    uint32_t* environmentBlendModeCountOutput,
+    XrEnvironmentBlendMode* environmentBlendModes) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+
+  *environmentBlendModeCountOutput = 1;
+
+  if (environmentBlendModeCapacityInput != 0) {
+    *environmentBlendModes = OpenXrTestHelper::kEnvironmentBlendMode;
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateInstanceExtensionProperties(
+    const char* layer_name,
+    uint32_t property_capacity_input,
+    uint32_t* property_count_output,
+    XrExtensionProperties* properties) {
+  DLOG(INFO) << __FUNCTION__;
+
+  RETURN_IF_FALSE(
+      property_capacity_input >= OpenXrTestHelper::NumExtensionsSupported() ||
+          property_capacity_input == 0,
+      XR_ERROR_SIZE_INSUFFICIENT, "XrExtensionProperties array is too small");
+
+  *property_count_output = OpenXrTestHelper::NumExtensionsSupported();
+
+  if (property_capacity_input != 0) {
+    for (uint32_t i = 0; i < OpenXrTestHelper::NumExtensionsSupported(); i++) {
+      errno_t error = strcpy_s(properties[i].extensionName,
+                               OpenXrTestHelper::kExtensions[i]);
+      DCHECK(error == 0);
+      properties[i].specVersion = 1;
+    }
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateViewConfigurationViews(
+    XrInstance instance,
+    XrSystemId system_id,
+    XrViewConfigurationType view_configuration_type,
+    uint32_t view_capacity_input,
+    uint32_t* view_count_output,
+    XrViewConfigurationView* views) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+  RETURN_IF_FALSE(
+      view_configuration_type == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
+      XR_ERROR_VALIDATION_FAILURE,
+      "xrEnumerateViewConfigurationViews viewConfigurationType invalid");
+
+  *view_count_output = OpenXrTestHelper::NumViews();
+
+  if (view_capacity_input != 0) {
+    views[0] = OpenXrTestHelper::kViewConfigurationViews[0];
+    views[1] = OpenXrTestHelper::kViewConfigurationViews[1];
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateSwapchainImages(XrSwapchain swapchain,
+                                    uint32_t image_capacity_input,
+                                    uint32_t* image_count_output,
+                                    XrSwapchainImageBaseHeader* images) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(
+      image_capacity_input == OpenXrTestHelper::kMinSwapchainBuffering ||
+          image_capacity_input == 0,
+      XR_ERROR_SIZE_INSUFFICIENT,
+      "xrEnumerateSwapchainImages does not equal length returned by "
+      "xrCreateSwapchain");
+
+  *image_count_output = OpenXrTestHelper::kMinSwapchainBuffering;
+
+  if (image_capacity_input != 0) {
+    const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>& textures =
+        g_test_helper.GetSwapchainTextures();
+    DCHECK(textures.size() == image_capacity_input);
+
+    for (uint32_t i = 0; i < image_capacity_input; i++) {
+      XrSwapchainImageD3D11KHR& image =
+          reinterpret_cast<XrSwapchainImageD3D11KHR*>(images)[i];
+
+      RETURN_IF_FALSE(image.type == XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR,
+                      XR_ERROR_VALIDATION_FAILURE,
+                      "XrSwapchainImageD3D11KHR type invalid");
+
+      image.texture = textures[i].Get();
+    }
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrGetD3D11GraphicsRequirementsKHR(
+    XrInstance instance,
+    XrSystemId system_id,
+    XrGraphicsRequirementsD3D11KHR* graphics_requirements) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+  RETURN_IF_FALSE(
+      graphics_requirements->type == XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR,
+      XR_ERROR_VALIDATION_FAILURE,
+      "XrGraphicsRequirementsD3D11KHR type invalid");
+
+  Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
+  Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
+  HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory));
+  DCHECK(SUCCEEDED(hr));
+  for (int i = 0; SUCCEEDED(dxgi_factory->EnumAdapters(i, &adapter)); i++) {
+    DXGI_ADAPTER_DESC desc;
+    adapter->GetDesc(&desc);
+    graphics_requirements->adapterLuid = desc.AdapterLuid;
+
+    // Require D3D11.1 to support shared NT handles.
+    graphics_requirements->minFeatureLevel = D3D_FEATURE_LEVEL_11_1;
+
+    return XR_SUCCESS;
+  }
+
+  RETURN_IF_FALSE(false, XR_ERROR_VALIDATION_FAILURE,
+                  "Unable to create query DXGI Adapter");
+}
+
+XrResult xrGetSystem(XrInstance instance,
+                     const XrSystemGetInfo* get_info,
+                     XrSystemId* system_id) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_FALSE(get_info->type == XR_TYPE_SYSTEM_GET_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "XrSystemGetInfo type invalid");
+  RETURN_IF_FALSE(get_info->formFactor == XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSystemGetInfo formFactor invalid");
+
+  *system_id = g_test_helper.GetSystemId();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrLocateSpace(XrSpace space,
+                       XrSpace baseSpace,
+                       XrTime time,
+                       XrSpaceRelation* relation) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(space));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(baseSpace));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidatePredictedDisplayTime(time));
+
+  g_test_helper.GetPose(&(relation->pose));
+
+  relation->relationFlags = XR_SPACE_RELATION_ORIENTATION_VALID_BIT |
+                            XR_SPACE_RELATION_POSITION_VALID_BIT;
+
+  return XR_SUCCESS;
+}
+
+XrResult xrLocateViews(XrSession session,
+                       const XrViewLocateInfo* view_locate_info,
+                       XrViewState* view_state,
+                       uint32_t view_capacity_input,
+                       uint32_t* view_count_output,
+                       XrView* views) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidatePredictedDisplayTime(
+      view_locate_info->displayTime));
+  RETURN_IF_FALSE(view_locate_info->type == XR_TYPE_VIEW_LOCATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrLocateViews view_locate_info type invalid");
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(view_locate_info->space));
+
+  return XR_SUCCESS;
+}
+
+XrResult xrReleaseSwapchainImage(
+    XrSwapchain swapchain,
+    const XrSwapchainImageReleaseInfo* release_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(release_info->type == XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrReleaseSwapchainImage type invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult xrWaitFrame(XrSession session,
+                     const XrFrameWaitInfo* frame_wait_info,
+                     XrFrameState* frame_state) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(frame_wait_info->type == XR_TYPE_FRAME_WAIT_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "frame_wait_info type invalid");
+  RETURN_IF_FALSE(frame_state->type == XR_TYPE_FRAME_STATE,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XR_TYPE_FRAME_STATE type invalid");
+
+  frame_state->predictedDisplayTime = g_test_helper.NextPredictedDisplayTime();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrWaitSwapchainImage(XrSwapchain swapchain,
+                              const XrSwapchainImageWaitInfo* wait_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(wait_info->type == XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrWaitSwapchainImage type invalid");
+  RETURN_IF_FALSE(wait_info->timeout == XR_INFINITE_DURATION,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrWaitSwapchainImage timeout not XR_INFINITE_DURATION");
+
+  return XR_SUCCESS;
+}
diff --git a/device/vr/openxr/test/openxr.json b/device/vr/openxr/test/openxr.json
new file mode 100644
index 0000000..af26106b
--- /dev/null
+++ b/device/vr/openxr/test/openxr.json
@@ -0,0 +1,6 @@
+{
+  "file_format_version": "1.0.0",
+  "runtime": {
+    "library_path": ".\\OpenXrRuntime.dll"
+  }
+}
diff --git a/device/vr/openxr/test/openxr_negotiate.h b/device/vr/openxr/test/openxr_negotiate.h
new file mode 100644
index 0000000..fd5dfdb9
--- /dev/null
+++ b/device/vr/openxr/test/openxr_negotiate.h
@@ -0,0 +1,93 @@
+// 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 DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
+#define DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
+
+#include <d3d11.h>
+#include <unknwn.h>
+
+#include "third_party/openxr/include/openxr/loader_interfaces.h"
+#include "third_party/openxr/include/openxr/openxr.h"
+#include "third_party/openxr/include/openxr/openxr_platform.h"
+
+// This file contains functions that are used by the openxr_loader.dll to call
+// into the fake OpenXR Runtime. Used for testing purposes only, so this should
+// only be used to call the fake OpenXR APIs defined in
+// fake_openxr_impl_api.cc.
+
+// Please add new OpenXR APIs below in alphabetical order.
+XrResult GetInstanceProcAddress(XrInstance instance,
+                                const char* name,
+                                PFN_xrVoidFunction* function) {
+  if (strcmp(name, "xrAcquireSwapchainImage") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrAcquireSwapchainImage);
+  } else if (strcmp(name, "xrBeginFrame") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrBeginFrame);
+  } else if (strcmp(name, "xrBeginSession") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrBeginSession);
+  } else if (strcmp(name, "xrCreateInstance") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateInstance);
+  } else if (strcmp(name, "xrCreateReferenceSpace") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateReferenceSpace);
+  } else if (strcmp(name, "xrCreateSession") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateSession);
+  } else if (strcmp(name, "xrCreateSwapchain") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateSwapchain);
+  } else if (strcmp(name, "xrDestroyInstance") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrDestroyInstance);
+  } else if (strcmp(name, "xrEndFrame") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrEndFrame);
+  } else if (strcmp(name, "xrEndSession") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrEndSession);
+  } else if (strcmp(name, "xrEnumerateEnvironmentBlendModes") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateEnvironmentBlendModes);
+  } else if (strcmp(name, "xrEnumerateInstanceExtensionProperties") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(
+        xrEnumerateInstanceExtensionProperties);
+  } else if (strcmp(name, "xrEnumerateSwapchainImages") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateSwapchainImages);
+  } else if (strcmp(name, "xrEnumerateViewConfigurationViews") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateViewConfigurationViews);
+  } else if (strcmp(name, "xrGetD3D11GraphicsRequirementsKHR") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrGetD3D11GraphicsRequirementsKHR);
+  } else if (strcmp(name, "xrGetSystem") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrGetSystem);
+  } else if (strcmp(name, "xrLocateSpace") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrLocateSpace);
+  } else if (strcmp(name, "xrLocateViews") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrLocateViews);
+  } else if (strcmp(name, "xrReleaseSwapchainImage") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrReleaseSwapchainImage);
+  } else if (strcmp(name, "xrWaitFrame") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrWaitFrame);
+  } else if (strcmp(name, "xrWaitSwapchainImage") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrWaitSwapchainImage);
+  } else {
+    return XR_ERROR_FUNCTION_UNSUPPORTED;
+  }
+
+  return XR_SUCCESS;
+}
+
+// The single exported function in fake OpenXR Runtime DLL which the OpenXR
+// loader calls for negotiation. GetInstanceProcAddress is returned to the
+// loader, which is then used by the loader to call OpenXR APIs.
+// extern "C" is needed because the OpenXR Loader expects the name of this
+// function to be unmangled. The real OpenXR runtime does this as well.
+extern "C" __declspec(dllexport) XrResult xrNegotiateLoaderRuntimeInterface(
+    const XrNegotiateLoaderInfo* loaderInfo,
+    XrNegotiateRuntimeRequest* runtimeRequest) {
+  runtimeRequest->runtimeInterfaceVersion = 1;
+  runtimeRequest->runtimeXrVersion = XR_MAKE_VERSION(0, 1, 0);
+  runtimeRequest->getInstanceProcAddr = GetInstanceProcAddress;
+
+  return XR_SUCCESS;
+}
+
+#endif  // DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
diff --git a/device/vr/openxr/test/openxr_test_helper.cc b/device/vr/openxr/test/openxr_test_helper.cc
new file mode 100644
index 0000000..6a1dad9
--- /dev/null
+++ b/device/vr/openxr/test/openxr_test_helper.cc
@@ -0,0 +1,280 @@
+// 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 "device/vr/openxr/test/openxr_test_helper.h"
+
+#include <cmath>
+#include <limits>
+
+#include "device/vr/openxr/openxr_util.h"
+#include "third_party/openxr/include/openxr/openxr_platform.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform_util.h"
+
+// Initialize static variables in OpenXrTestHelper.
+const char* OpenXrTestHelper::kExtensions[] = {
+    XR_KHR_D3D11_ENABLE_EXTENSION_NAME};
+const uint32_t OpenXrTestHelper::kDimension = 128;
+const uint32_t OpenXrTestHelper::kSwapCount = 1;
+const uint32_t OpenXrTestHelper::kMinSwapchainBuffering = 3;
+const uint32_t OpenXrTestHelper::kMaxViewCount = 2;
+const XrViewConfigurationView OpenXrTestHelper::kViewConfigView = {
+    XR_TYPE_VIEW_CONFIGURATION_VIEW, nullptr,
+    OpenXrTestHelper::kDimension,    OpenXrTestHelper::kDimension,
+    OpenXrTestHelper::kDimension,    OpenXrTestHelper::kDimension,
+    OpenXrTestHelper::kSwapCount,    OpenXrTestHelper::kSwapCount};
+XrViewConfigurationView OpenXrTestHelper::kViewConfigurationViews[] = {
+    OpenXrTestHelper::kViewConfigView, OpenXrTestHelper::kViewConfigView};
+const XrEnvironmentBlendMode OpenXrTestHelper::kEnvironmentBlendMode =
+    XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+
+uint32_t OpenXrTestHelper::NumExtensionsSupported() {
+  return sizeof(kExtensions) / sizeof(kExtensions[0]);
+}
+
+uint32_t OpenXrTestHelper::NumViews() {
+  return sizeof(kViewConfigurationViews) / sizeof(kViewConfigurationViews[0]);
+}
+
+OpenXrTestHelper::OpenXrTestHelper()
+    : system_id_(0),
+      session_(XR_NULL_HANDLE),
+      swapchain_(XR_NULL_HANDLE),
+      local_space_(XR_NULL_HANDLE),
+      view_space_(XR_NULL_HANDLE),
+      session_running_(false),
+      acquired_swapchain_texture_(0),
+      next_action_space_(0),
+      next_predicted_display_time_(0) {}
+
+OpenXrTestHelper::~OpenXrTestHelper() = default;
+
+void OpenXrTestHelper::TestFailure() {
+  NOTREACHED();
+}
+
+void OpenXrTestHelper::SetTestHook(device::VRTestHook* hook) {
+  base::AutoLock auto_lock(lock_);
+  test_hook_ = hook;
+}
+
+void OpenXrTestHelper::OnPresentedFrame() {
+  static uint32_t frame_id = 1;
+
+  base::AutoLock auto_lock(lock_);
+  if (!test_hook_)
+    return;
+
+  // TODO(https://crbug.com/986621): The frame color is currently hard-coded to
+  // what the pixel tests expects. We should instead store the actual WebGL
+  // texture and read from it, which will also verify the correct swapchain
+  // texture was used.
+
+  device::DeviceConfig device_config = test_hook_->WaitGetDeviceConfig();
+  device::SubmittedFrameData frame_data = {};
+
+  if (std::abs(device_config.interpupillary_distance - 0.2f) <
+      std::numeric_limits<float>::epsilon()) {
+    // TestPresentationPoses sets the ipd to 0.2f, whereas tests by default have
+    // an ipd of 0.1f. This test has specific formulas to determine the colors,
+    // specified in test_webxr_poses.html.
+    frame_data.color = {
+        frame_id % 256, ((frame_id - frame_id % 256) / 256) % 256,
+        ((frame_id - frame_id % (256 * 256)) / (256 * 256)) % 256, 255};
+  } else {
+    // The WebXR tests by default clears to blue. TestPresentationPixels
+    // verifies this color.
+    frame_data.color = {0, 0, 255, 255};
+  }
+
+  frame_data.left_eye = true;
+  test_hook_->OnFrameSubmitted(frame_data);
+
+  frame_data.left_eye = false;
+  test_hook_->OnFrameSubmitted(frame_data);
+
+  frame_id++;
+}
+
+XrSystemId OpenXrTestHelper::GetSystemId() {
+  system_id_ = 1;
+  return system_id_;
+}
+
+XrSession OpenXrTestHelper::GetSession() {
+  // reinterpret_cast needed because XrSession is a pointer type.
+  session_ = reinterpret_cast<XrSession>(2);
+  return session_;
+}
+
+XrSwapchain OpenXrTestHelper::GetSwapchain() {
+  // reinterpret_cast needed because XrSwapchain is a pointer type.
+  swapchain_ = reinterpret_cast<XrSwapchain>(3);
+  return swapchain_;
+}
+
+XrSpace OpenXrTestHelper::CreateLocalSpace() {
+  // reinterpret_cast needed because XrSpace is a pointer type.
+  local_space_ = reinterpret_cast<XrSpace>(++next_action_space_);
+  return local_space_;
+}
+
+XrSpace OpenXrTestHelper::CreateViewSpace() {
+  // reinterpret_cast needed because XrSpace is a pointer type.
+  view_space_ = reinterpret_cast<XrSpace>(++next_action_space_);
+  return view_space_;
+}
+
+XrResult OpenXrTestHelper::BeginSession() {
+  RETURN_IF_FALSE(!session_running_, XR_ERROR_SESSION_RUNNING,
+                  "Session is already running");
+
+  session_running_ = true;
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::EndSession() {
+  RETURN_IF_FALSE(session_running_, XR_ERROR_SESSION_NOT_RUNNING,
+                  "Session is not currently running");
+
+  session_running_ = false;
+  return XR_SUCCESS;
+}
+
+void OpenXrTestHelper::SetD3DDevice(ID3D11Device* d3d_device) {
+  DCHECK(d3d_device_ == nullptr);
+  DCHECK(d3d_device != nullptr);
+  d3d_device_ = d3d_device;
+
+  D3D11_TEXTURE2D_DESC desc{};
+  desc.Width = kDimension * 2;  // Using a double wide texture
+  desc.Height = kDimension;
+  desc.MipLevels = 1;
+  desc.ArraySize = 1;
+  desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+  desc.SampleDesc.Count = 1;
+  desc.Usage = D3D11_USAGE_DEFAULT;
+  desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+
+  for (uint32_t i = 0; i < kMinSwapchainBuffering; i++) {
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
+    HRESULT hr = d3d_device_->CreateTexture2D(&desc, nullptr, &texture);
+    DCHECK(hr == S_OK);
+
+    textures_arr_.push_back(texture);
+  }
+}
+
+const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>&
+OpenXrTestHelper::GetSwapchainTextures() const {
+  return textures_arr_;
+}
+
+uint32_t OpenXrTestHelper::NextSwapchainImageIndex() {
+  acquired_swapchain_texture_ =
+      (acquired_swapchain_texture_ + 1) % textures_arr_.size();
+  return acquired_swapchain_texture_;
+}
+
+XrTime OpenXrTestHelper::NextPredictedDisplayTime() {
+  return ++next_predicted_display_time_;
+}
+
+void OpenXrTestHelper::GetPose(XrPosef* pose) {
+  *pose = device::PoseIdentity();
+
+  base::AutoLock lock(lock_);
+  if (test_hook_) {
+    device::PoseFrameData pose_data = test_hook_->WaitGetPresentingPose();
+    if (pose_data.is_valid) {
+      gfx::Transform transform = PoseFrameDataToTransform(pose_data);
+
+      gfx::DecomposedTransform decomposed_transform;
+      bool decomposable =
+          gfx::DecomposeTransform(&decomposed_transform, transform);
+      DCHECK(decomposable);
+
+      pose->orientation.x = decomposed_transform.quaternion.x();
+      pose->orientation.y = decomposed_transform.quaternion.y();
+      pose->orientation.z = decomposed_transform.quaternion.z();
+      pose->orientation.w = decomposed_transform.quaternion.w();
+
+      pose->position.x = decomposed_transform.translate[0];
+      pose->position.y = decomposed_transform.translate[1];
+      pose->position.z = decomposed_transform.translate[2];
+    }
+  }
+}
+
+XrResult OpenXrTestHelper::ValidateInstance(XrInstance instance) const {
+  // The Fake OpenXR Runtime returns this global OpenXrTestHelper object as the
+  // instance value on xrCreateInstance.
+  RETURN_IF_FALSE(reinterpret_cast<OpenXrTestHelper*>(instance) == this,
+                  XR_ERROR_VALIDATION_FAILURE, "XrInstance invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSystemId(XrSystemId system_id) const {
+  RETURN_IF_FALSE(system_id_ != 0, XR_ERROR_SYSTEM_INVALID,
+                  "XrSystemId has not been queried");
+  RETURN_IF_FALSE(system_id == system_id_, XR_ERROR_SYSTEM_INVALID,
+                  "XrSystemId invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSession(XrSession session) const {
+  RETURN_IF_FALSE(session_ != XR_NULL_HANDLE, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSession has not been queried");
+  RETURN_IF_FALSE(session == session_, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSession invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSwapchain(XrSwapchain swapchain) const {
+  RETURN_IF_FALSE(swapchain_ != XR_NULL_HANDLE, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchain has not been queried");
+  RETURN_IF_FALSE(swapchain == swapchain_, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchain invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSpace(XrSpace space) const {
+  RETURN_IF_FALSE(space != XR_NULL_HANDLE, XR_ERROR_HANDLE_INVALID,
+                  "XrSpace has not been queried");
+  RETURN_IF_FALSE(reinterpret_cast<uint32_t>(space) <= next_action_space_,
+                  XR_ERROR_HANDLE_INVALID, "XrSpace invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidatePredictedDisplayTime(XrTime time) const {
+  RETURN_IF_FALSE(time != 0, XR_ERROR_VALIDATION_FAILURE,
+                  "XrTime has not been queried");
+  RETURN_IF_FALSE(time <= next_predicted_display_time_,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrTime predicted display time invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateXrPosefIsIdentity(
+    const XrPosef& pose) const {
+  XrPosef identity = device::PoseIdentity();
+  bool is_identity = true;
+  is_identity &= pose.orientation.x == identity.orientation.x;
+  is_identity &= pose.orientation.y == identity.orientation.y;
+  is_identity &= pose.orientation.z == identity.orientation.z;
+  is_identity &= pose.orientation.w == identity.orientation.w;
+  is_identity &= pose.position.x == identity.position.x;
+  is_identity &= pose.position.y == identity.position.y;
+  is_identity &= pose.position.z == identity.position.z;
+  RETURN_IF_FALSE(is_identity, XR_ERROR_VALIDATION_FAILURE,
+                  "XrPosef is not an identity");
+
+  return XR_SUCCESS;
+}
diff --git a/device/vr/openxr/test/openxr_test_helper.h b/device/vr/openxr/test/openxr_test_helper.h
new file mode 100644
index 0000000..38e960f4
--- /dev/null
+++ b/device/vr/openxr/test/openxr_test_helper.h
@@ -0,0 +1,98 @@
+// 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 DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_
+#define DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_
+
+#include <d3d11.h>
+#include <unknwn.h>
+#include <wrl.h>
+#include <vector>
+
+#include "base/synchronization/lock.h"
+#include "device/vr/test/test_hook.h"
+#include "third_party/openxr/include/openxr/openxr.h"
+
+class OpenXrTestHelper : public device::ServiceTestHook {
+ public:
+  OpenXrTestHelper();
+  ~OpenXrTestHelper();
+
+  void TestFailure();
+
+  // TestHookRegistration
+  void SetTestHook(device::VRTestHook* hook) final;
+
+  // Helper methods called by the mock OpenXR runtime. These methods will
+  // call back into the test hook, thus communicating with the test object
+  // on the browser process side.
+  void OnPresentedFrame();
+
+  // Helper methods called by the mock OpenXR runtime to query or set the
+  // state of the runtime.
+
+  XrSystemId GetSystemId();
+  XrSession GetSession();
+  XrSwapchain GetSwapchain();
+  XrSpace CreateLocalSpace();
+  XrSpace CreateViewSpace();
+
+  XrResult BeginSession();
+  XrResult EndSession();
+
+  void SetD3DDevice(ID3D11Device* d3d_device);
+  const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>&
+  GetSwapchainTextures() const;
+  uint32_t NextSwapchainImageIndex();
+  XrTime NextPredictedDisplayTime();
+
+  void GetPose(XrPosef* pose);
+
+  // Methods that validate the parameter with the current state of the runtime.
+  XrResult ValidateInstance(XrInstance instance) const;
+  XrResult ValidateSystemId(XrSystemId system_id) const;
+  XrResult ValidateSession(XrSession session) const;
+  XrResult ValidateSwapchain(XrSwapchain swapchain) const;
+  XrResult ValidateSpace(XrSpace space) const;
+  XrResult ValidatePredictedDisplayTime(XrTime time) const;
+  XrResult ValidateXrPosefIsIdentity(const XrPosef& pose) const;
+
+  // Properties of the mock OpenXR runtime that does not change are created
+  // as static variables.
+  static uint32_t NumExtensionsSupported();
+  static uint32_t NumViews();
+  static const char* kExtensions[];
+  static const uint32_t kDimension;
+  static const uint32_t kSwapCount;
+  static const uint32_t kMinSwapchainBuffering;
+  static const uint32_t kMaxViewCount;
+  static const XrViewConfigurationView kViewConfigView;
+  static XrViewConfigurationView kViewConfigurationViews[];
+  static const XrEnvironmentBlendMode kEnvironmentBlendMode;
+
+ private:
+  // Properties of the mock OpenXR runtime that doesn't change throughout the
+  // lifetime of the instance. However, these aren't static because they are
+  // initialized to an invalid value and set to their actual value in their
+  // respective Get*/Create* functions. This allows these variables to be used
+  // to validate that they were queried before being used.
+  XrSystemId system_id_;
+  XrSession session_;
+  XrSwapchain swapchain_;
+  XrSpace local_space_;
+  XrSpace view_space_;
+
+  // Properties that changes depending on the state of the runtime.
+  bool session_running_;
+  Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_;
+  std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>> textures_arr_;
+  uint32_t acquired_swapchain_texture_;
+  uint32_t next_action_space_;
+  XrTime next_predicted_display_time_;
+
+  device::VRTestHook* test_hook_ GUARDED_BY(lock_) = nullptr;
+  base::Lock lock_;
+};
+
+#endif  // DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_
diff --git a/docs/security/ipc-reviews.md b/docs/security/ipc-reviews.md
new file mode 100644
index 0000000..33240fb6
--- /dev/null
+++ b/docs/security/ipc-reviews.md
@@ -0,0 +1,118 @@
+# IPC Reviews
+
+[TOC]
+
+## What is IPC review and why is it needed?
+
+A critical part of Chrome’s security is built on process isolation.
+Untrustworthy content is isolated into unprivileged and sandboxed child
+processes (e.g. renderer process, GPU process). In contrast, the browser
+process is fully privileged and is not sandboxed, as it is considered
+trustworthy.
+
+In this model, the browser process acts as an operating system kernel for the
+untrustworthy child processes. The interfaces defined by IPC are system calls
+exposed to these (potentially compromised and) untrustworthy child processes,
+allowing them to use system capabilities (such as the network) as appropriate.
+Because IPCs cross trust and privilege boundaries between processes, it is
+critical that IPCs themselves are robust against malicious input.
+
+Consider the example interface below, assuming it is exposed to renderer
+processes:
+
+```
+interface CookieJar {
+  GetCookies(url.mojom.Url) => (string);
+};
+```
+
+In normal circumstances, this isn’t problematic: a well-behaved renderer won’t
+pass arbitrary URLs to `GetCookies()`; it will only pass URLs of Documents it’s
+rendering. If the renderer is displaying email, it won’t need (or request) the
+bank login cookies. However, an attacker with full control over the renderer
+process can pass any URL to `GetCookies()`. An interface like this that
+incorrectly trusts the renderer process would allow an attacker to exfiltrate
+much more data than they should be able to.
+
+IPC review is intended to catch these types of bugs. An adversary with full
+control over an untrustworthy process must not be able to exploit IPC to escape
+the sandboxed process. The goal is to ensure:
+
+- IPC invariants are easy to maintain
+- IPC interfaces are documented and understandable
+- IPC handlers are resistant to malicious input
+
+Note that IPC review is distinct from [security review for launches or major
+changes to Chrome][chrome-security-review]; the latter is generally focused on
+evaluating feature security at a high level, while IPC review is focused on
+specific implementation details.
+
+## What does an IPC reviewer look for?
+
+- **What are the endpoints of the changed IPCs?** Almost all IPCs cross trust
+  and privilege boundaries, so it is important to understand what processes are
+  communicating, which way data flows, and if any of the endpoints are
+  untrustworthy. This is often documented with interface-level comments, e.g.
+  “the Widget interface is used by the browser process to inform the renderer
+  process of UI state changes”.
+- **What are the changes in capabilities being exposed over IPC?** Many changes
+  provide new capabilities to an untrustworthy process. An IPC reviewer will
+  evaluate if the new capabilities can be abused by an attacker (e.g. retrieve
+  cookies of an unrelated page, write to arbitrary files, et cetera).
+- **What breaks if an attacker provides malicious input?** If an array argument
+  represents a point in 3D space, one assumption might be that it should contain
+  exactly three elements. An IPC handler processing input from an untrustworthy
+  process must not assume this though; it must validate that the sender actually
+  provided exactly three elements. Note: in this case, an even better
+  alternative would be define a `struct Point` with an `x`, `y`, and `z` fields:
+  then it would be impossible for even a malicious sender to pass a malformed
+  point!
+
+To answer these questions, it’s often necessary to evaluate both the code
+sending and the code reviewing the IPC. Avoid sending out changes where the IPC
+handler is simply marked `NOTIMPLEMENTED()`: without an actual implementation,
+it is often impossible to evaluate for potential security issues.
+
+Please also keep in mind that an IPC reviewer often will not have all the
+domain-specific knowledge in a given area (whether that be accessibility, GPU,
+XR, or something else). Ensure that a change has appropriate context (in the CL
+description and associated bugs), link to design documents, and thoroughly
+document interfaces/methods/structs. Good documentation also helps future
+readers of the code understand the system more easily.
+
+## Guidelines and Best Practices for IPC
+
+Please see the security team’s [Mojo guide][mojo-best-practices] for specific
+guidelines and recommendations on how to structure IPC.
+
+## When should IPC review happen?
+
+In general, include an IPC reviewer when sending a change out for review. Even
+if a change is under active development, it’s still OK to add an IPC reviewer.
+While the IPC reviewer might not be actively involved if the design is still in
+flux with other reviewers, simply being in the loop often provides useful
+context for the change (and can sometimes save significant future pain).
+
+It’s important to note that IPC review isn’t just a rubberstamp; as mentioned
+above, an IPC reviewer’s focus is on reviewing cross-process interactions, from
+the perspective of a hostile attacker trying to hijack a user’s machine. Adding
+an IPC reviewer only after receiving all other LGTMs can sometimes be
+frustrating for everyone involved, especially if significant revisions are
+requested.
+
+## Is it OK to TBR a simple change in IPC?
+
+Avoid TBRing CLs with IPC changes. If the change is simple, ping an IPC reviewer
+directly and ask them for a quick LGTM. Erring on the side of safety is
+preferred for security-critical changes.
+
+## IPC review is slow!
+
+Please reach out to <ipc-security-reviewers@chromium.org> (for public issues)
+or <chrome-security-ipc@google.com> (for internal issues). Large and complex
+features can be difficult to evaluate on a change by change basis; reaching out
+can help provide IPC reviewers with better context on the security properties
+of the overall system, making it much easier to evaluate individual changes.
+
+[chrome-security-review]: https://www.chromium.org/Home/chromium-security/security-reviews
+[mojo-best-practices]: https://chromium.googlesource.com/chromium/src/+/master/docs/security/mojo.md
diff --git a/fuchsia/BUILD.gn b/fuchsia/BUILD.gn
index 07bc5b6..56841a8 100644
--- a/fuchsia/BUILD.gn
+++ b/fuchsia/BUILD.gn
@@ -101,7 +101,7 @@
     "engine:web_engine_browsertests",
     "engine:web_engine_unittests",
     "http:http_service_tests",
-    "mojo:fuchsia_mojo_unittests",
+    "mojom:fuchsia_mojo_unittests",
     "runners:cast_runner",
     "runners:cast_runner_browsertests",
     "runners:cast_runner_integration_tests",
diff --git a/fuchsia/mojo/BUILD.gn b/fuchsia/mojom/BUILD.gn
similarity index 100%
rename from fuchsia/mojo/BUILD.gn
rename to fuchsia/mojom/BUILD.gn
diff --git a/fuchsia/mojo/DEPS b/fuchsia/mojom/DEPS
similarity index 100%
rename from fuchsia/mojo/DEPS
rename to fuchsia/mojom/DEPS
diff --git a/fuchsia/mojo/OWNERS b/fuchsia/mojom/OWNERS
similarity index 100%
rename from fuchsia/mojo/OWNERS
rename to fuchsia/mojom/OWNERS
diff --git a/fuchsia/mojo/example.mojom b/fuchsia/mojom/example.mojom
similarity index 100%
rename from fuchsia/mojo/example.mojom
rename to fuchsia/mojom/example.mojom
diff --git a/fuchsia/mojo/example.typemap b/fuchsia/mojom/example.typemap
similarity index 68%
rename from fuchsia/mojo/example.typemap
rename to fuchsia/mojom/example.typemap
index b1d1512..6795be5 100644
--- a/fuchsia/mojo/example.typemap
+++ b/fuchsia/mojom/example.typemap
@@ -2,15 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-mojom = "//fuchsia/mojo/example.mojom"
+mojom = "//fuchsia/mojom/example.mojom"
 os_whitelist = [ "fuchsia" ]
 public_headers = [ "base/fuchsia/testfidl/cpp/fidl.h" ]
-traits_headers = [ "//fuchsia/mojo/test_interface_request_mojom_traits.h" ]
+traits_headers = [ "//fuchsia/mojom/test_interface_request_mojom_traits.h" ]
 sources = [
-  "//fuchsia/mojo/test_interface_request_mojom_traits.h",
+  "//fuchsia/mojom/test_interface_request_mojom_traits.h",
 ]
 public_deps = [
   "//base:testfidl",
-  "//fuchsia/mojo:traits",
+  "//fuchsia/mojom:traits",
 ]
 type_mappings = [ "fuchsia.test.mojom.TestInterfaceRequest=fidl::InterfaceRequest<base::fuchsia::testfidl::TestInterface>[move_only]" ]
diff --git a/fuchsia/mojo/fidl_interface_request_mojom_traits.h b/fuchsia/mojom/fidl_interface_request_mojom_traits.h
similarity index 87%
rename from fuchsia/mojo/fidl_interface_request_mojom_traits.h
rename to fuchsia/mojom/fidl_interface_request_mojom_traits.h
index 2c38438..d40f3699 100644
--- a/fuchsia/mojo/fidl_interface_request_mojom_traits.h
+++ b/fuchsia/mojom/fidl_interface_request_mojom_traits.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef FUCHSIA_MOJO_FIDL_INTERFACE_REQUEST_MOJOM_TRAITS_H_
-#define FUCHSIA_MOJO_FIDL_INTERFACE_REQUEST_MOJOM_TRAITS_H_
+#ifndef FUCHSIA_MOJOM_FIDL_INTERFACE_REQUEST_MOJOM_TRAITS_H_
+#define FUCHSIA_MOJOM_FIDL_INTERFACE_REQUEST_MOJOM_TRAITS_H_
 
 #include <lib/fidl/cpp/interface_request.h>
 
@@ -40,4 +40,4 @@
 
 }  // namespace mojo
 
-#endif  // FUCHSIA_MOJO_FIDL_INTERFACE_REQUEST_MOJOM_TRAITS_H_
+#endif  // FUCHSIA_MOJOM_FIDL_INTERFACE_REQUEST_MOJOM_TRAITS_H_
diff --git a/fuchsia/mojo/fidl_interface_request_mojom_traits_unittest.cc b/fuchsia/mojom/fidl_interface_request_mojom_traits_unittest.cc
similarity index 90%
rename from fuchsia/mojo/fidl_interface_request_mojom_traits_unittest.cc
rename to fuchsia/mojom/fidl_interface_request_mojom_traits_unittest.cc
index 8e34582c..f5ca105 100644
--- a/fuchsia/mojo/fidl_interface_request_mojom_traits_unittest.cc
+++ b/fuchsia/mojom/fidl_interface_request_mojom_traits_unittest.cc
@@ -4,8 +4,8 @@
 
 #include "base/fuchsia/testfidl/cpp/fidl.h"
 #include "base/message_loop/message_loop.h"
-#include "fuchsia/mojo/example.mojom.h"
-#include "fuchsia/mojo/test_interface_request_mojom_traits.h"
+#include "fuchsia/mojom/example.mojom.h"
+#include "fuchsia/mojom/test_interface_request_mojom_traits.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/fuchsia/mojo/test_interface_request_mojom_traits.h b/fuchsia/mojom/test_interface_request_mojom_traits.h
similarity index 68%
rename from fuchsia/mojo/test_interface_request_mojom_traits.h
rename to fuchsia/mojom/test_interface_request_mojom_traits.h
index 0f0aeb6..f9be179d 100644
--- a/fuchsia/mojo/test_interface_request_mojom_traits.h
+++ b/fuchsia/mojom/test_interface_request_mojom_traits.h
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef FUCHSIA_MOJO_TEST_INTERFACE_REQUEST_MOJOM_TRAITS_H_
-#define FUCHSIA_MOJO_TEST_INTERFACE_REQUEST_MOJOM_TRAITS_H_
+#ifndef FUCHSIA_MOJOM_TEST_INTERFACE_REQUEST_MOJOM_TRAITS_H_
+#define FUCHSIA_MOJOM_TEST_INTERFACE_REQUEST_MOJOM_TRAITS_H_
 
-#include "fuchsia/mojo/fidl_interface_request_mojom_traits.h"
+#include "fuchsia/mojom/fidl_interface_request_mojom_traits.h"
 
 namespace mojo {
 
@@ -19,4 +19,4 @@
 
 }  // namespace mojo
 
-#endif  // FUCHSIA_MOJO_TEST_INTERFACE_REQUEST_MOJOM_TRAITS_H_
+#endif  // FUCHSIA_MOJOM_TEST_INTERFACE_REQUEST_MOJOM_TRAITS_H_
diff --git a/fuchsia/mojo/test_typemaps.gni b/fuchsia/mojom/test_typemaps.gni
similarity index 76%
rename from fuchsia/mojo/test_typemaps.gni
rename to fuchsia/mojom/test_typemaps.gni
index 0fb662f..521c8d4 100644
--- a/fuchsia/mojo/test_typemaps.gni
+++ b/fuchsia/mojom/test_typemaps.gni
@@ -2,4 +2,4 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-typemaps = [ "//fuchsia/mojo/example.typemap" ]
+typemaps = [ "//fuchsia/mojom/example.typemap" ]
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
index 7ca5cc52..c8d080d5 100644
--- a/gpu/command_buffer/service/feature_info.cc
+++ b/gpu/command_buffer/service/feature_info.cc
@@ -1113,12 +1113,6 @@
         gfx::BufferFormat::YUV_420_BIPLANAR);
   }
 
-  if (gfx::HasExtension(extensions, "GL_APPLE_ycbcr_422")) {
-    AddExtensionString("GL_CHROMIUM_ycbcr_422_image");
-    feature_flags_.chromium_image_ycbcr_422 = true;
-    feature_flags_.gpu_memory_buffer_formats.Add(gfx::BufferFormat::UYVY_422);
-  }
-
 #if defined(OS_MACOSX)
   // Mac can create GLImages out of XR30 IOSurfaces only after High Sierra.
   feature_flags_.chromium_image_xr30 = base::mac::IsAtLeastOS10_13();
diff --git a/gpu/command_buffer/service/feature_info_unittest.cc b/gpu/command_buffer/service/feature_info_unittest.cc
index e28c3b2b..9245613 100644
--- a/gpu/command_buffer/service/feature_info_unittest.cc
+++ b/gpu/command_buffer/service/feature_info_unittest.cc
@@ -1715,11 +1715,6 @@
       info_->validators()->texture_internal_format_storage.IsValid(GL_R16_EXT));
 }
 
-TEST_P(FeatureInfoTest, InitializeCHROMIUM_ycbcr_422_imageTrue) {
-  SetupInitExpectations("GL_APPLE_ycbcr_422");
-  EXPECT_TRUE(info_->feature_flags().chromium_image_ycbcr_422);
-}
-
 TEST_P(FeatureInfoTest, InitializeCHROMIUM_unpremultiply_and_dither_copy) {
   SetupInitExpectations("");
   switch (GetParam()) {
diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc
index 43a18d6..98e7535 100644
--- a/gpu/ipc/service/gpu_init.cc
+++ b/gpu/ipc/service/gpu_init.cc
@@ -246,10 +246,7 @@
   // may also have started at this point.
   ui::OzonePlatform::InitParams params;
   params.single_process = false;
-  params.using_mojo =
-      features::IsOzoneDrmMojo() || ui::OzonePlatform::EnsureInstance()
-                                        ->GetPlatformProperties()
-                                        .requires_mojo;
+  params.using_mojo = features::IsOzoneDrmMojo();
   params.viz_display_compositor = features::IsVizDisplayCompositorEnabled();
   ui::OzonePlatform::InitializeForGPU(params);
   const std::vector<gfx::BufferFormat> supported_buffer_formats_for_texturing =
@@ -481,10 +478,7 @@
 #if defined(USE_OZONE)
   ui::OzonePlatform::InitParams params;
   params.single_process = true;
-  params.using_mojo =
-      features::IsOzoneDrmMojo() || ui::OzonePlatform::EnsureInstance()
-                                        ->GetPlatformProperties()
-                                        .requires_mojo;
+  params.using_mojo = features::IsOzoneDrmMojo();
   params.viz_display_compositor = features::IsVizDisplayCompositorEnabled();
   ui::OzonePlatform::InitializeForGPU(params);
   const std::vector<gfx::BufferFormat> supported_buffer_formats_for_texturing =
diff --git a/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm b/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
index be5fdd7..f28936b 100644
--- a/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
+++ b/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
@@ -671,6 +671,12 @@
 
 // Tests that opening links in a new tab will not evict the source tab.
 - (void)testOpenLinkInNewTab {
+  // TODO(crbug.com/989550) Disable broken context menu tests on Xc11b5.
+  if (@available(iOS 13, *)) {
+    if ([ChromeEarlGrey isIPadIdiom]) {
+      EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
+    }
+  }
   // Create map of canned responses and set up the test HTML server.
   std::map<GURL, std::string> responses;
   const GURL initialURL =
diff --git a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm b/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
index 33a145e9..cffb013d 100644
--- a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
+++ b/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
@@ -71,14 +71,11 @@
 
 @implementation ActivityServiceControllerTestCase
 
-- (void)testActivityServiceControllerCantPrintUnprintablePages {
-  // TODO(crbug.com/747622): re-enable this test on once earl grey can interact
-  // with the share menu.
-  EARL_GREY_TEST_DISABLED(@"Disabled until EG can use share menu.");
-
-  // TODO(crbug.com/864597): Reenable this test.
-  EARL_GREY_TEST_DISABLED(@"Test should be rewritten to use Offline Version.");
-
+// TODO(crbug.com/747622): re-enable this test on once earl grey can interact
+// with the share menu.
+// TODO(crbug.com/864597): Reenable this test. This test should be rewritten
+// to use Offline Version.
+- (void)DISABLED_testActivityServiceControllerCantPrintUnprintablePages {
   std::unique_ptr<web::DataResponseProvider> provider(
       new ErrorPageResponseProvider());
   web::test::SetUpHttpServer(std::move(provider));
@@ -116,11 +113,9 @@
                             UIAccessibilityTraitNotEnabled)];
 }
 
-- (void)testOpenActivityServiceControllerAndCopy {
-  // TODO(crbug.com/747622): re-enable this test once earl grey can interact
-  // with the share menu.
-  EARL_GREY_TEST_DISABLED(@"Disabled until EG can use share menu.");
-
+// TODO(crbug.com/747622): re-enable this test once earl grey can interact
+// with the share menu.
+- (void)DISABLED_testOpenActivityServiceControllerAndCopy {
   // Set up mock http server.
   std::map<GURL, std::string> responses;
   GURL url = web::test::HttpServer::MakeUrl("http://potato");
diff --git a/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
index c290751..1b213f4 100644
--- a/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
+++ b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
@@ -581,9 +581,7 @@
 - (void)testShowJavaScriptAfterNewTabAnimation {
   // TODO(crbug.com/989550) Disable broken context menu tests on Xc11b5.
   if (@available(iOS 13, *)) {
-    if ([ChromeEarlGrey isIPadIdiom]) {
-      EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
-    }
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS13.");
   }
 
   // Load the test page with a link to kOnLoadAlertURL and long tap on the link.
diff --git a/ios/chrome/browser/ui/download/download_manager_egtest.mm b/ios/chrome/browser/ui/download/download_manager_egtest.mm
index c6bde17c..8a7358298 100644
--- a/ios/chrome/browser/ui/download/download_manager_egtest.mm
+++ b/ios/chrome/browser/ui/download/download_manager_egtest.mm
@@ -176,6 +176,13 @@
 
 // Tests "Open in New Tab" on download link.
 - (void)testDownloadInNewTab {
+  // TODO(crbug.com/989550) Disable broken context menu tests on Xc11b5.
+  if (@available(iOS 13, *)) {
+    if ([ChromeEarlGrey isIPadIdiom]) {
+      EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
+    }
+  }
+
   [ChromeEarlGrey loadURL:self.testServer->GetURL("/")];
   [ChromeEarlGrey waitForWebStateContainingText:"Download"];
 
diff --git a/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm b/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
index a35a999..0a8506d 100644
--- a/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
+++ b/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
@@ -189,6 +189,10 @@
 // Tests that requesting mobile site of a page works and going back re-opens
 // desktop version of the page.
 - (void)testRequestMobileSiteGoBackToDesktop {
+  // TODO(crbug.com/990186): Re-enable this test.
+  if ([ChromeEarlGrey isSlimNavigationManagerEnabled])
+    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
+
   std::unique_ptr<web::DataResponseProvider> provider(
       new UserAgentResponseProvider());
   web::test::SetUpHttpServer(std::move(provider));
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
index d8c0442..be36564 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
@@ -625,6 +625,12 @@
 // Verifies that the back/forward buttons are working and are correctly enabled
 // during navigations.
 - (void)testNavigationButtons {
+  // TODO(crbug.com/989550) Disable broken context menu tests on Xc11b5.
+  if (@available(iOS 13, *)) {
+    if ([ChromeEarlGrey isIPadIdiom]) {
+      EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
+    }
+  }
   // Setup the server.
   self.testServer->RegisterRequestHandler(
       base::BindRepeating(&StandardResponse));
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 78979fce..4e924567 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -67,6 +67,7 @@
 }
 
 source_set("eg_test_support") {
+  defines = [ "CHROME_EARL_GREY_1" ]
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
   sources = [
@@ -74,12 +75,31 @@
     "scoped_eg_synchronization_disabler.mm",
   ]
   deps = [
-    ":test_support",
-    "//base:base",
+    "//base",
+    "//ios/testing/earl_grey:earl_grey_support",
     "//ios/third_party/earl_grey:earl_grey+link",
   ]
 }
 
+source_set("eg_test_support+eg2") {
+  defines = [ "CHROME_EARL_GREY_2" ]
+  configs += [
+    "//build/config/compiler:enable_arc",
+    "//build/config/ios:xctest_config",
+  ]
+  testonly = true
+  sources = [
+    "scoped_eg_synchronization_disabler.h",
+    "scoped_eg_synchronization_disabler.mm",
+  ]
+  deps = [
+    "//base",
+    "//ios/testing/earl_grey:eg_test_support+eg2",
+    "//ios/third_party/earl_grey2:test_lib",
+  ]
+  libs = [ "XCTest.framework" ]
+}
+
 source_set("block_cleanup_test") {
   # TODO(crbug.com/733237): Replace this comment with an explicit disable_arc config.
   # The files in this target depend on non-ARC memory management.
diff --git a/ios/chrome/test/scoped_eg_synchronization_disabler.mm b/ios/chrome/test/scoped_eg_synchronization_disabler.mm
index 3de3c91..c51afafa 100644
--- a/ios/chrome/test/scoped_eg_synchronization_disabler.mm
+++ b/ios/chrome/test/scoped_eg_synchronization_disabler.mm
@@ -4,12 +4,26 @@
 
 #import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
 
-#import <EarlGrey/EarlGrey.h>
+#import "ios/testing/earl_grey/earl_grey_test.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+namespace {
+// EG1 and EG2 have different API to obtain shared GREYConfiguration object.
+// This function abstracts the API access.
+GREYConfiguration* GetSharedGREYConfiguration() {
+#if defined(CHROME_EARL_GREY_1)
+  return [GREYConfiguration sharedInstance];
+#elif defined(CHROME_EARL_GREY_2)
+  return [GREYConfiguration sharedConfiguration];
+#else
+#error Either CHROME_EARL_GREY_1 or CHROME_EARL_GREY_2 must be defined
+#endif
+}
+}  // namespace
+
 ScopedSynchronizationDisabler::ScopedSynchronizationDisabler()
     : saved_eg_synchronization_enabled_value_(GetEgSynchronizationEnabled()) {
   SetEgSynchronizationEnabled(NO);
@@ -20,12 +34,11 @@
 }
 
 bool ScopedSynchronizationDisabler::GetEgSynchronizationEnabled() {
-  return [[GREYConfiguration sharedInstance]
+  return [GetSharedGREYConfiguration()
       boolValueForConfigKey:kGREYConfigKeySynchronizationEnabled];
 }
 
 void ScopedSynchronizationDisabler::SetEgSynchronizationEnabled(BOOL flag) {
-  [[GREYConfiguration sharedInstance]
-          setValue:@(flag)
-      forConfigKey:kGREYConfigKeySynchronizationEnabled];
+  [GetSharedGREYConfiguration() setValue:@(flag)
+                            forConfigKey:kGREYConfigKeySynchronizationEnabled];
 }
diff --git a/ios/web/web_state/bad_ssl_response_inttest.mm b/ios/web/web_state/bad_ssl_response_inttest.mm
index b040694..78cd015 100644
--- a/ios/web/web_state/bad_ssl_response_inttest.mm
+++ b/ios/web/web_state/bad_ssl_response_inttest.mm
@@ -191,7 +191,8 @@
 // Tests navigation to a page with self signed SSL cert and allowing the load
 // via WebClient. Subsequent navigation should not call AllowCertificateError
 // but always allow the load.
-TEST_P(BadSslResponseTest, RememberCertDecision) {
+// TODO(crbug.com/973635): fix and reenable this test.
+TEST_P(BadSslResponseTest, DISABLED_RememberCertDecision) {
   // Allow the load via WebClient.
   web_client()->SetAllowCertificateErrors(true);
   GURL url(https_server_.GetURL("/echo"));
diff --git a/media/gpu/test/video_test_environment.cc b/media/gpu/test/video_test_environment.cc
index db830a1..a0f20df 100644
--- a/media/gpu/test/video_test_environment.cc
+++ b/media/gpu/test/video_test_environment.cc
@@ -60,7 +60,8 @@
   // video decode acceleration.
   LOG(WARNING) << "Initializing Ozone Platform...\n"
                   "If this hangs indefinitely please call 'stop ui' first!";
-  ui::OzonePlatform::InitParams params = {.single_process = false};
+  ui::OzonePlatform::InitParams params;
+  params.single_process = false;
   ui::OzonePlatform::InitializeForUI(params);
   ui::OzonePlatform::InitializeForGPU(params);
   ui::OzonePlatform::GetInstance()->AfterSandboxEntry();
diff --git a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
index 5e927557..e518704 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
@@ -239,9 +239,7 @@
 
   video_profile_ = config.profile;
 
-  // TODO(posciak): This needs to be queried once supported.
   input_planes_count_ = 1;
-  output_planes_count_ = 1;
 
   input_format_fourcc_ =
       V4L2Device::VideoCodecProfileToV4L2PixFmt(video_profile_, true);
@@ -466,10 +464,13 @@
   memset(&format, 0, sizeof(format));
   format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
   format.fmt.pix_mp.pixelformat = output_format_fourcc_;
-  format.fmt.pix_mp.num_planes = output_planes_count_;
+  format.fmt.pix_mp.num_planes = V4L2Device::GetNumPlanesOfV4L2PixFmt(output_format_fourcc_);
   IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format);
   DCHECK_EQ(format.fmt.pix_mp.pixelformat, output_format_fourcc_);
 
+  DCHECK_EQ(V4L2Device::GetNumPlanesOfV4L2PixFmt(output_format_fourcc_), static_cast<size_t>(format.fmt.pix_mp.num_planes));
+  output_planes_count_ = format.fmt.pix_mp.num_planes;
+
   return true;
 }
 
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index 74ae897..1dae886 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -22,7 +22,7 @@
   "//device/bluetooth/public/mojom/typemaps.gni",
   "//device/bluetooth/public/mojom/test/typemaps.gni",
   "//device/gamepad/public/cpp/typemaps.gni",
-  "//fuchsia/mojo/test_typemaps.gni",
+  "//fuchsia/mojom/test_typemaps.gni",
   "//gpu/ipc/common/typemaps.gni",
   "//ipc/typemaps.gni",
   "//media/capture/mojom/typemaps.gni",
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index 5567ada..2253e47 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -27,6 +27,7 @@
 #include "base/synchronization/lock.h"
 #include "base/trace_event/trace_event.h"
 #include "base/values.h"
+#include "build/build_config.h"
 #include "crypto/ec_private_key.h"
 #include "crypto/openssl_util.h"
 #include "net/base/features.h"
@@ -69,8 +70,10 @@
 
 namespace {
 
+#if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64)
 constexpr base::FeatureParam<std::string> kPostQuantumGroup{
     &features::kPostQuantumCECPQ2, "group", ""};
+#endif
 
 // This constant can be any non-negative/non-zero value (eg: it does not
 // overlap with any value of the net::Error range, including net::OK).
@@ -316,7 +319,6 @@
     // Deduplicate all certificates minted from the SSL_CTX in memory.
     SSL_CTX_set0_buffer_pool(ssl_ctx_.get(), x509_util::GetBufferPool());
 
-    SSL_CTX_set_info_callback(ssl_ctx_.get(), InfoCallback);
     SSL_CTX_set_msg_callback(ssl_ctx_.get(), MessageCallback);
 
 #if !defined(NET_DISABLE_BROTLI)
@@ -325,6 +327,9 @@
         nullptr /* compression not supported */, DecompressBrotliCert);
 #endif
 
+#if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64)
+    // CECPQ2b is only optimised for x86-64 and aarch64, and is too slow on
+    // other CPUs to even experiment with.
     const std::string post_quantum_group = kPostQuantumGroup.Get();
     if (!post_quantum_group.empty()) {
       bool send_signal = false;
@@ -346,6 +351,7 @@
         SSL_CTX_enable_pq_experiment_signal(ssl_ctx_.get());
       }
     }
+#endif
   }
 
   static int ClientCertRequestCallback(SSL* ssl, void* arg) {
@@ -383,11 +389,6 @@
     GetInstance()->ssl_key_logger_->WriteLine(line);
   }
 
-  static void InfoCallback(const SSL* ssl, int type, int value) {
-    SSLClientSocketImpl* socket = GetInstance()->GetClientSocketFromSSL(ssl);
-    socket->InfoCallback(type, value);
-  }
-
   static void MessageCallback(int is_write,
                               int version,
                               int content_type,
@@ -1768,13 +1769,6 @@
   RetryAllOperations();
 }
 
-void SSLClientSocketImpl::InfoCallback(int type, int value) {
-  if (type == SSL_CB_HANDSHAKE_START && completed_connect_) {
-    UMA_HISTOGRAM_BOOLEAN("Net.SSLSecureRenegotiation",
-                          SSL_get_secure_renegotiation_support(ssl_.get()));
-  }
-}
-
 void SSLClientSocketImpl::MessageCallback(int is_write,
                                           int content_type,
                                           const void* buf,
diff --git a/net/socket/ssl_client_socket_impl.h b/net/socket/ssl_client_socket_impl.h
index a360025e..88bbc58 100644
--- a/net/socket/ssl_client_socket_impl.h
+++ b/net/socket/ssl_client_socket_impl.h
@@ -184,9 +184,6 @@
 
   void OnPrivateKeyComplete(Error error, const std::vector<uint8_t>& signature);
 
-  // Called from the BoringSSL info callback. (See |SSL_CTX_set_info_callback|.)
-  void InfoCallback(int type, int value);
-
   // Called whenever BoringSSL processes a protocol message.
   void MessageCallback(int is_write,
                        int content_type,
diff --git a/pdf/draw_utils/coordinates.cc b/pdf/draw_utils/coordinates.cc
index 9162076..cd3171e9 100644
--- a/pdf/draw_utils/coordinates.cc
+++ b/pdf/draw_utils/coordinates.cc
@@ -17,6 +17,12 @@
   bottom_gap->set_width(bottom_gap->width() / 2);
 }
 
+void CenterRectHorizontally(int doc_width, pp::Rect* rect) {
+  DCHECK_GE(doc_width, rect->width());
+
+  rect->set_x((doc_width - rect->width()) / 2);
+}
+
 void ExpandDocumentSize(const pp::Size& rect_size, pp::Size* doc_size) {
   int width_diff = std::max(0, rect_size.width() - doc_size->width());
   doc_size->Enlarge(width_diff, rect_size.height());
diff --git a/pdf/draw_utils/coordinates.h b/pdf/draw_utils/coordinates.h
index fe862a2..5873197 100644
--- a/pdf/draw_utils/coordinates.h
+++ b/pdf/draw_utils/coordinates.h
@@ -32,6 +32,10 @@
 // drawn left page and the empty space to the right of the page.
 void AdjustBottomGapForRightSidePage(int page_x, pp::Rect* bottom_gap);
 
+// Given |doc_width|, horizontally center |rect| within the document.
+// |doc_width| must be greater than or equal to |rect|.
+void CenterRectHorizontally(int doc_width, pp::Rect* rect);
+
 // Given |rect_size|, sets the width of |doc_size| to the max of |rect_size|'s
 // width and |doc_size|'s width. Also adds the height of |rect_size| to
 // |doc_size|'s height.
diff --git a/pdf/draw_utils/coordinates_unittest.cc b/pdf/draw_utils/coordinates_unittest.cc
index 8734c609..abb6747 100644
--- a/pdf/draw_utils/coordinates_unittest.cc
+++ b/pdf/draw_utils/coordinates_unittest.cc
@@ -54,6 +54,20 @@
   CompareRect({450, 40, 475, 200}, bottom_gap);
 }
 
+TEST(CoordinateTest, CenterRectHorizontally) {
+  pp::Rect page_rect(10, 20, 400, 300);
+  CenterRectHorizontally(600, &page_rect);
+  CompareRect({100, 20, 400, 300}, page_rect);
+
+  page_rect.SetRect(300, 450, 500, 700);
+  CenterRectHorizontally(800, &page_rect);
+  CompareRect({150, 450, 500, 700}, page_rect);
+
+  page_rect.SetRect(800, 100, 200, 250);
+  CenterRectHorizontally(350, &page_rect);
+  CompareRect({75, 100, 200, 250}, page_rect);
+}
+
 TEST(CoordinateTest, ExpandDocumentSize) {
   pp::Size doc_size(100, 400);
 
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index a6c5874..9239996b 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -2395,8 +2395,7 @@
 void PDFiumEngine::LoadPagesInSingleView(std::vector<pp::Rect> page_rects,
                                          bool reload) {
   for (size_t i = 0; i < page_rects.size(); ++i) {
-    // Center pages relative to the entire document.
-    page_rects[i].set_x((layout_.size().width() - page_rects[i].width()) / 2);
+    draw_utils::CenterRectHorizontally(layout_.size().width(), &page_rects[i]);
     InsetPage(i, page_rects.size(), /*multiplier=*/1, &page_rects[i]);
     AppendPageRectToPages(page_rects[i], i, reload);
   }
diff --git a/sandbox/win/src/broker_services.h b/sandbox/win/src/broker_services.h
index 14cfed5f..bc9cb70 100644
--- a/sandbox/win/src/broker_services.h
+++ b/sandbox/win/src/broker_services.h
@@ -88,8 +88,7 @@
   std::list<std::unique_ptr<JobTracker>> tracker_list_;
 
   // Provides a fast lookup to identify sandboxed processes that belong to a
-  // job. Consult |jobless_process_handles_| for handles of processes without
-  // jobs.
+  // job.
   std::set<DWORD> child_process_ids_;
 
   DISALLOW_COPY_AND_ASSIGN(BrokerServicesBase);
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index abd5eec9..dc44674 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -5698,7 +5698,6 @@
   request.site_for_cookies = url;  // bypass third-party cookie blocking
   request.request_initiator =
       url::Origin::Create(url);  // ensure initiator is set
-  request.allow_download = true;
   return request;
 }
 
diff --git a/services/network/public/cpp/resource_request.cc b/services/network/public/cpp/resource_request.cc
index e56d823..da63e75 100644
--- a/services/network/public/cpp/resource_request.cc
+++ b/services/network/public/cpp/resource_request.cc
@@ -26,15 +26,12 @@
          request_initiator == request.request_initiator &&
          referrer == request.referrer &&
          referrer_policy == request.referrer_policy &&
-         is_prerendering == request.is_prerendering &&
          headers.ToString() == request.headers.ToString() &&
          cors_exempt_headers.ToString() ==
              request.cors_exempt_headers.ToString() &&
          load_flags == request.load_flags &&
-         plugin_child_id == request.plugin_child_id &&
          resource_type == request.resource_type &&
          priority == request.priority &&
-         appcache_host_id == request.appcache_host_id &&
          should_reset_appcache == request.should_reset_appcache &&
          is_external_request == request.is_external_request &&
          cors_preflight_policy == request.cors_preflight_policy &&
@@ -56,10 +53,8 @@
          render_frame_id == request.render_frame_id &&
          is_main_frame == request.is_main_frame &&
          transition_type == request.transition_type &&
-         allow_download == request.allow_download &&
          report_raw_headers == request.report_raw_headers &&
          previews_state == request.previews_state &&
-         initiated_in_secure_context == request.initiated_in_secure_context &&
          upgrade_if_insecure == request.upgrade_if_insecure &&
          is_revalidating == request.is_revalidating &&
          should_also_use_factory_bound_origin_for_cors ==
diff --git a/services/network/public/cpp/resource_request.h b/services/network/public/cpp/resource_request.h
index e0a8ae2..b472b5e 100644
--- a/services/network/public/cpp/resource_request.h
+++ b/services/network/public/cpp/resource_request.h
@@ -51,14 +51,11 @@
   GURL referrer;
   net::URLRequest::ReferrerPolicy referrer_policy =
       net::URLRequest::NEVER_CLEAR_REFERRER;
-  bool is_prerendering = false;
   net::HttpRequestHeaders headers;
   net::HttpRequestHeaders cors_exempt_headers;
   int load_flags = 0;
-  int plugin_child_id = -1;
   int resource_type = 0;
   net::RequestPriority priority = net::IDLE;
-  base::Optional<base::UnguessableToken> appcache_host_id;
   bool should_reset_appcache = false;
   bool is_external_request = false;
   mojom::CorsPreflightPolicy cors_preflight_policy =
@@ -81,10 +78,8 @@
   int render_frame_id = MSG_ROUTING_NONE;
   bool is_main_frame = false;
   int transition_type = 0;
-  bool allow_download = false;
   bool report_raw_headers = false;
   int previews_state = 0;
-  bool initiated_in_secure_context = false;
   bool upgrade_if_insecure = false;
   bool is_revalidating = false;
   bool should_also_use_factory_bound_origin_for_cors = false;
diff --git a/services/network/public/cpp/url_request_mojom_traits.cc b/services/network/public/cpp/url_request_mojom_traits.cc
index 2d50980..0a98ea3f 100644
--- a/services/network/public/cpp/url_request_mojom_traits.cc
+++ b/services/network/public/cpp/url_request_mojom_traits.cc
@@ -177,8 +177,7 @@
       !data.ReadCustomProxyPostCacheHeaders(
           &out->custom_proxy_post_cache_headers) ||
       !data.ReadFetchWindowId(&out->fetch_window_id) ||
-      !data.ReadDevtoolsRequestId(&out->devtools_request_id) ||
-      !data.ReadAppcacheHostId(&out->appcache_host_id)) {
+      !data.ReadDevtoolsRequestId(&out->devtools_request_id)) {
     return false;
   }
 
@@ -187,9 +186,7 @@
   out->attach_same_site_cookies = data.attach_same_site_cookies();
   out->update_first_party_url_on_redirect =
       data.update_first_party_url_on_redirect();
-  out->is_prerendering = data.is_prerendering();
   out->load_flags = data.load_flags();
-  out->plugin_child_id = data.plugin_child_id();
   out->resource_type = data.resource_type();
   out->should_reset_appcache = data.should_reset_appcache();
   out->is_external_request = data.is_external_request();
@@ -206,10 +203,8 @@
   out->render_frame_id = data.render_frame_id();
   out->is_main_frame = data.is_main_frame();
   out->transition_type = data.transition_type();
-  out->allow_download = data.allow_download();
   out->report_raw_headers = data.report_raw_headers();
   out->previews_state = data.previews_state();
-  out->initiated_in_secure_context = data.initiated_in_secure_context();
   out->upgrade_if_insecure = data.upgrade_if_insecure();
   out->is_revalidating = data.is_revalidating();
   out->should_also_use_factory_bound_origin_for_cors =
diff --git a/services/network/public/cpp/url_request_mojom_traits.h b/services/network/public/cpp/url_request_mojom_traits.h
index 03e6c4f..c547de1 100644
--- a/services/network/public/cpp/url_request_mojom_traits.h
+++ b/services/network/public/cpp/url_request_mojom_traits.h
@@ -88,9 +88,6 @@
       const network::ResourceRequest& request) {
     return request.referrer_policy;
   }
-  static bool is_prerendering(const network::ResourceRequest& request) {
-    return request.is_prerendering;
-  }
   static const net::HttpRequestHeaders& headers(
       const network::ResourceRequest& request) {
     return request.headers;
@@ -102,9 +99,6 @@
   static int32_t load_flags(const network::ResourceRequest& request) {
     return request.load_flags;
   }
-  static int32_t plugin_child_id(const network::ResourceRequest& request) {
-    return request.plugin_child_id;
-  }
   static int32_t resource_type(const network::ResourceRequest& request) {
     return request.resource_type;
   }
@@ -112,10 +106,6 @@
       const network::ResourceRequest& request) {
     return request.priority;
   }
-  static const base::Optional<base::UnguessableToken>& appcache_host_id(
-      const network::ResourceRequest& request) {
-    return request.appcache_host_id;
-  }
   static bool should_reset_appcache(const network::ResourceRequest& request) {
     return request.should_reset_appcache;
   }
@@ -187,19 +177,12 @@
   static int32_t transition_type(const network::ResourceRequest& request) {
     return request.transition_type;
   }
-  static bool allow_download(const network::ResourceRequest& request) {
-    return request.allow_download;
-  }
   static bool report_raw_headers(const network::ResourceRequest& request) {
     return request.report_raw_headers;
   }
   static int32_t previews_state(const network::ResourceRequest& request) {
     return request.previews_state;
   }
-  static bool initiated_in_secure_context(
-      const network::ResourceRequest& request) {
-    return request.initiated_in_secure_context;
-  }
   static bool upgrade_if_insecure(const network::ResourceRequest& request) {
     return request.upgrade_if_insecure;
   }
diff --git a/services/network/public/cpp/url_request_mojom_traits_unittest.cc b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
index 1d2c919..cc4b846 100644
--- a/services/network/public/cpp/url_request_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
@@ -61,14 +61,11 @@
   original.referrer = GURL("https://referrer.com/");
   original.referrer_policy =
       net::URLRequest::ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN;
-  original.is_prerendering = false;
   original.headers.SetHeader("Accept", "text/xml");
   original.cors_exempt_headers.SetHeader("X-Requested-With", "ForTesting");
   original.load_flags = 3;
-  original.plugin_child_id = 5;
   original.resource_type = 2;
   original.priority = net::IDLE;
-  original.appcache_host_id = base::UnguessableToken::Create();
   original.should_reset_appcache = true;
   original.is_external_request = false;
   original.cors_preflight_policy =
@@ -88,10 +85,8 @@
   original.render_frame_id = 5;
   original.is_main_frame = true;
   original.transition_type = 0;
-  original.allow_download = false;
   original.report_raw_headers = true;
   original.previews_state = 0;
-  original.initiated_in_secure_context = false;
   original.upgrade_if_insecure = true;
   original.is_revalidating = false;
   original.throttling_profile_id = base::UnguessableToken::Create();
diff --git a/services/network/public/mojom/url_loader.mojom b/services/network/public/mojom/url_loader.mojom
index d47cfe2..4bb4fad 100644
--- a/services/network/public/mojom/url_loader.mojom
+++ b/services/network/public/mojom/url_loader.mojom
@@ -153,9 +153,6 @@
   // The referrer policy to use.
   URLRequestReferrerPolicy referrer_policy;
 
-  // Whether the frame that initiated this request is used for prerendering.
-  bool is_prerendering;
-
   // Additional HTTP request headers.
   HttpRequestHeaders headers;
 
@@ -178,14 +175,6 @@
   // net::URLRequest load flags.
   int32 load_flags;
 
-  // If this request originated from a pepper plugin running in a child
-  // process, this identifies which process it came from. Otherwise, it
-  // is zero.
-  // -1 to match ChildProcessHost::kInvalidUniqueID
-  // TODO(jam): remove this from the struct since network service shouldn't know
-  // about this.
-  int32 plugin_child_id;
-
   // What this resource load is for (main frame, sub-frame, sub-resource,
   // object).
   // Note: this is an enum of type content::ResourceType.
@@ -196,9 +185,6 @@
   // The priority of this request determined by Blink.
   RequestPriority priority;
 
-  // Indicates which frame (or worker context) the request is being loaded into.
-  mojo_base.mojom.UnguessableToken? appcache_host_id;
-
   // True if corresponding AppCache group should be reset.
   bool should_reset_appcache;
 
@@ -289,9 +275,6 @@
   // about this.
   int32 transition_type;
 
-  // Whether or not we should allow the URL to download.
-  bool allow_download;
-
   // Whether to intercept headers to pass back to the renderer.
   // This also enables reporting of SSLInfo in URLLoaderClient's
   // OnResponseReceived and OnComplete, as well as invocation of
@@ -305,9 +288,6 @@
   // about this.
   int32 previews_state;
 
-  // Whether or not the initiator of this request is a secure context.
-  bool initiated_in_secure_context;
-
   // Whether or not this request (including redirects) should be upgraded to
   // HTTPS due to an Upgrade-Insecure-Requests requirement.
   bool upgrade_if_insecure;
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index bfa3d58..bef9234b 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -120,7 +120,6 @@
   request.request_initiator =
       url::Origin::Create(url);  // ensure initiator is set
   request.is_main_frame = true;
-  request.allow_download = true;
   return request;
 }
 
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index b19c7b0..0163135 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -16050,6 +16050,39 @@
         }
       },
       {
+        "args": [
+          "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--use-gl=swiftshader",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-gl=swiftshader"
+        ],
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "skia_renderer_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-14.04"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -16132,6 +16165,40 @@
         }
       },
       {
+        "args": [
+          "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--additional-driver-flag=--use-vulkan=swiftshader",
+          "--additional-driver-flag=--disable-vulkan-fallback-to-gl-for-testing",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-vulkan=swiftshader"
+        ],
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "vulkan_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-14.04"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "webdriver_wpt_tests",
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 2a0c5c9..aafbcdf 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -8877,234 +8877,10 @@
     ]
   },
   "Linux FYI Experimental Release (NVIDIA)": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "shards": 4
-        },
-        "test": "angle_end2end_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--no-xvfb"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "angle_unittests"
-      },
-      {
-        "args": [
-          "--test-launcher-retry-limit=0"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "angle_white_box_tests"
-      },
-      {
-        "args": [
-          "--enable-gpu",
-          "--test-launcher-bot-mode",
-          "--test-launcher-jobs=1",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/vulkan.content_browsertests.filter",
-          "--enable-features=VizDisplayCompositor,UseSkiaRenderer,UiGpuRasterization",
-          "--use-vulkan=native",
-          "--enable-oop-rasterization",
-          "--enable-gpu-rasterization",
-          "--force-gpu-rasterization",
-          "--disable-software-compositing-fallback",
-          "--disable-vulkan-fallback-to-gl-for-testing",
-          "--disable-headless-mode",
-          "--no-xvfb"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "vulkan_content_browsertests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "content_browsertests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--use-cmd-decoder=validating"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "gl_tests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests",
-          "--no-xvfb"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "gl_unittests"
-      },
-      {
-        "args": [
-          "--use-gpu-in-tests"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "gles2_conform_test"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "swiftshader_unittests"
-      }
-    ],
     "isolated_scripts": [
       {
         "args": [
-          "--gtest-benchmark-name=angle_perftests",
-          "-v",
-          "--one-frame-only"
-        ],
-        "isolate_name": "angle_perftests",
-        "merge": {
-          "args": [
-            "--smoke-test-mode"
-          ],
-          "script": "//tools/perf/process_perf_results.py"
-        },
-        "name": "angle_perftests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        }
-      },
-      {
-        "args": [
-          "context_lost",
+          "noop_sleep",
           "--show-stdout",
           "--browser=release",
           "--passthrough",
@@ -9116,7 +8892,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "context_lost_tests",
+        "name": "noop_sleep_tests",
         "should_retry_with_patch": false,
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -9131,529 +8907,6 @@
           "expiration": 21600,
           "idempotent": false
         }
-      },
-      {
-        "args": [
-          "depth_capture",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "depth_capture_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "gpu_process",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "gpu_process_launch_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "hardware_accelerated_feature",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "hardware_accelerated_feature_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "info_collection",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--expected-vendor-id",
-          "10de",
-          "--expected-device-id",
-          "1cb3"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "info_collection_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "maps",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "maps_pixel_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "pixel_skia_gold_test",
-        "precommit_args": [
-          "--review-patch-issue",
-          "${patch_issue}",
-          "--review-patch-set",
-          "${patch_set}",
-          "--buildbucket-build-id",
-          "${buildbucket_build_id}"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false,
-          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
-        }
-      },
-      {
-        "args": [
-          "screenshot_sync",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--dont-restore-color-profile-after-test"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "screenshot_sync_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "--num-retries=3",
-          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
-          "--additional-driver-flag=--use-gl=any",
-          "--additional-driver-flag=--enable-gpu-rasterization",
-          "--additional-driver-flag=--force-gpu-rasterization",
-          "--additional-driver-flag=--enable-oop-rasterization",
-          "--additional-driver-flag=--disable-software-compositing-fallback",
-          "--additional-driver-flag=--disable-headless-mode",
-          "--no-xvfb",
-          "--fuzzy-diff",
-          "--skipped=always",
-          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
-          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
-          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization"
-        ],
-        "isolate_name": "blink_web_tests_exparchive",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "skia_renderer_gl_blink_web_tests",
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        }
-      },
-      {
-        "args": [
-          "trace_test",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "trace_test",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false
-        }
-      },
-      {
-        "args": [
-          "--num-retries=3",
-          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
-          "--additional-driver-flag=--use-gl=any",
-          "--additional-driver-flag=--enable-gpu-rasterization",
-          "--additional-driver-flag=--force-gpu-rasterization",
-          "--additional-driver-flag=--enable-oop-rasterization",
-          "--additional-driver-flag=--disable-software-compositing-fallback",
-          "--additional-driver-flag=--use-vulkan=native",
-          "--additional-driver-flag=--disable-vulkan-fallback-to-gl-for-testing",
-          "--additional-driver-flag=--disable-headless-mode",
-          "--no-xvfb",
-          "--fuzzy-diff",
-          "--skipped=always",
-          "--driver-logging",
-          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
-          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
-          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-vulkan=native"
-        ],
-        "isolate_name": "blink_web_tests_exparchive",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "vulkan_native_blink_web_tests",
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600
-        }
-      },
-      {
-        "args": [
-          "pixel",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc \"--use-vulkan=native --disable-vulkan-fallback-to-gl-for-testing --enable-features=UseSkiaRenderer --disable-software-compositing-fallback\"",
-          "--dont-restore-color-profile-after-test",
-          "--os-type",
-          "linux",
-          "--build-revision",
-          "${got_revision}",
-          "--test-machine-name",
-          "${buildername}"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "vulkan_pixel_skia_gold_test",
-        "precommit_args": [
-          "--review-patch-issue",
-          "${patch_issue}",
-          "--review-patch-set",
-          "${patch_set}",
-          "--buildbucket-build-id",
-          "${buildbucket_build_id}"
-        ],
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false,
-          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "webgl2_conformance_gl_passthrough_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
-          "--webgl-conformance-version=2.0.1",
-          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "webgl2_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false,
-          "shards": 20
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "webgl_conformance_gl_passthrough_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false,
-          "shards": 2
-        }
-      },
-      {
-        "args": [
-          "webgl_conformance",
-          "--show-stdout",
-          "--browser=release",
-          "--passthrough",
-          "-v",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
-        ],
-        "isolate_name": "telemetry_gpu_integration_test",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "webgl_conformance_tests",
-        "should_retry_with_patch": false,
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "Chrome-GPU"
-            }
-          ],
-          "expiration": 21600,
-          "idempotent": false,
-          "shards": 2
-        }
       }
     ]
   },
@@ -11973,6 +11226,92 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         }
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--use-gl=any",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--additional-driver-flag=--disable-software-compositing-fallback",
+          "--additional-driver-flag=--use-vulkan=native",
+          "--additional-driver-flag=--disable-vulkan-fallback-to-gl-for-testing",
+          "--additional-driver-flag=--disable-headless-mode",
+          "--no-xvfb",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--driver-logging",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-vulkan=native"
+        ],
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "vulkan_native_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "nvidia-quadro-p400-ubuntu-stable",
+              "os": "linux-nvidia-stable",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc \"--use-vulkan=native --disable-vulkan-fallback-to-gl-for-testing --enable-features=UseSkiaRenderer --disable-software-compositing-fallback\"",
+          "--dont-restore-color-profile-after-test",
+          "--os-type",
+          "linux",
+          "--build-revision",
+          "${got_revision}",
+          "--test-machine-name",
+          "${buildername}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "vulkan_pixel_skia_gold_test",
+        "precommit_args": [
+          "--review-patch-issue",
+          "${patch_issue}",
+          "--review-patch-set",
+          "${patch_set}",
+          "--buildbucket-build-id",
+          "${buildbucket_build_id}"
+        ],
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "nvidia-quadro-p400-ubuntu-stable",
+              "os": "linux-nvidia-stable",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        }
       }
     ]
   },
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index 2ab0c1e..5abda53 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -3837,6 +3837,40 @@
         }
       },
       {
+        "args": [
+          "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--use-gl=swiftshader",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-gl=swiftshader"
+        ],
+        "isolate_coverage_data": true,
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "skia_renderer_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
         "isolate_coverage_data": true,
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
@@ -3925,6 +3959,41 @@
       {
         "args": [
           "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--additional-driver-flag=--use-vulkan=swiftshader",
+          "--additional-driver-flag=--disable-vulkan-fallback-to-gl-for-testing",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-vulkan=swiftshader"
+        ],
+        "isolate_coverage_data": true,
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "vulkan_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "--num-retries=3",
           "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw"
         ],
         "isolate_coverage_data": true,
@@ -7258,6 +7327,40 @@
         }
       },
       {
+        "args": [
+          "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--use-gl=swiftshader",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-gl=swiftshader"
+        ],
+        "isolate_coverage_data": true,
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "skia_renderer_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
         "isolate_coverage_data": true,
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
@@ -7346,6 +7449,41 @@
       {
         "args": [
           "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--additional-driver-flag=--use-vulkan=swiftshader",
+          "--additional-driver-flag=--disable-vulkan-fallback-to-gl-for-testing",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-vulkan=swiftshader"
+        ],
+        "isolate_coverage_data": true,
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "vulkan_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "--num-retries=3",
           "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw"
         ],
         "isolate_coverage_data": true,
@@ -9018,6 +9156,39 @@
         }
       },
       {
+        "args": [
+          "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--use-gl=swiftshader",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-gl=swiftshader"
+        ],
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "skia_renderer_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-14.04"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -9100,6 +9271,40 @@
         }
       },
       {
+        "args": [
+          "--num-retries=3",
+          "--additional-driver-flag=--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--additional-driver-flag=--enable-gpu-rasterization",
+          "--additional-driver-flag=--force-gpu-rasterization",
+          "--additional-driver-flag=--enable-oop-rasterization",
+          "--additional-driver-flag=--use-vulkan=swiftshader",
+          "--additional-driver-flag=--disable-vulkan-fallback-to-gl-for-testing",
+          "--fuzzy-diff",
+          "--skipped=always",
+          "--test-list=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_blink_web_tests.filter",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization",
+          "--additional-expectations=../../third_party/blink/web_tests/FlagExpectations/use-vulkan=swiftshader"
+        ],
+        "isolate_name": "blink_web_tests_exparchive",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "vulkan_swiftshader_blink_web_tests",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-14.04"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "webdriver_wpt_tests",
         "merge": {
           "args": [],
diff --git a/testing/buildbot/filters/fuchsia.services_unittests.filter b/testing/buildbot/filters/fuchsia.services_unittests.filter
index 85c0dff..804aefc 100644
--- a/testing/buildbot/filters/fuchsia.services_unittests.filter
+++ b/testing/buildbot/filters/fuchsia.services_unittests.filter
@@ -29,3 +29,8 @@
 
 # https://crbug.com/986544 - Flakes with mismatched state expectations.
 -URLLoaderTest.ResourceSchedulerIntegration
+
+# https://crbug.com/989223 - connect()ed UDP socket's getsockname() is wrong.
+-UDPSocketTest.TestReceiveMoreWithBufferSize
+-UDPSocketTest.TestReadSend
+-NetworkContextTest.CreateUDPSocket
diff --git a/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter b/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter
index 55f6cff..2e58ca51 100644
--- a/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter
+++ b/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter
@@ -90,3 +90,9 @@
 -MSE_ClearKey/EncryptedMediaTest.FrameSizeChangeVideo/0
 -MSE_ExternalClearKey/EncryptedMediaTest.FrameSizeChangeVideo/0
 -File/MediaTest.VideoBearSilentMp4/0
+
+# crbug.com/990116: vulkan_content_browsertests experience flaky failures for
+# these tests.
+-MediaSourceTest.Playback_VideoAudio_WebM
+-WebRtcDataBrowserTest.CallWithSctpDataAndMedia
+-WebRtcDataBrowserTest.CallWithDataAndMedia
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index c1ce02a..b56ffb7b 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1416,7 +1416,6 @@
       'Win7 FYI Debug (AMD)',
       # Disabled due to dbus crashes crbug.com/927465
       'Linux FYI Experimental Release (Intel HD 630)',
-      'Linux FYI Experimental Release (NVIDIA)',
       'Linux FYI Release (Intel HD 630)',
       'Linux FYI Release (NVIDIA)',
       'Linux FYI SkiaRenderer Vulkan (NVIDIA)',
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 2a2e515d..064b75b 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3568,20 +3568,18 @@
       },
     },
 
-    # Temporarily disabled, since all experimental bots are active now.
-    # TODO(ynovikov): Re-enable once Win10 Intel is switched to x64 crbug.com/988833
-    #'gpu_noop_sleep_telemetry_test': {
+    'gpu_noop_sleep_telemetry_test': {
       # The former GPU-specific generator script contained logic to
       # detect whether the so-called "experimental" GPU bots, which test
       # newer driver versions, were identical to the "stable" versions
       # of the bots, and if so to mirror their configurations. We prefer
       # to keep this new script simpler and to just configure this by
       # hand in waterfalls.pyl.
-      #'noop_sleep': {
-      #}
-    #},
+      'noop_sleep': {
+      }
+    },
 
-    'gpu_skia_renderer_telemetry_tests': {
+    'gpu_skia_renderer_non_vulkan_telemetry_tests': {
       'skia_renderer_pixel_skia_gold_test': {
         'name': 'skia_renderer_pixel_skia_gold_test',
         'args': [
@@ -3653,6 +3651,11 @@
       },
     },
 
+    'gpu_skia_renderer_telemetry_tests': [
+      'gpu_skia_renderer_non_vulkan_telemetry_tests',
+      'gpu_skia_renderer_vulkan_telemetry_tests',
+    ],
+
     'gpu_skia_renderer_vulkan_gtests': {
       'vulkan_content_browsertests': {
         'args': [
@@ -4988,6 +4991,7 @@
       'chromedriver_py_tests_isolated_scripts',
       'desktop_chromium_isolated_scripts',
       'linux_specific_chromium_isolated_scripts',
+      'skia_renderer_isolated_scripts',
       'telemetry_perf_unittests_isolated_scripts',
     ],
 
@@ -5204,12 +5208,6 @@
       'gpu_blink_web_tests_vulkan',
     ],
 
-    'gpu_blink_web_tests_and_angle_perf_isolated_scripts': [
-      'gpu_angle_perf_isolated_scripts',
-      'gpu_blink_web_tests_skia_renderer',
-      'gpu_blink_web_tests_vulkan',
-    ],
-
     'gpu_chromeos_telemetry_tests': [
       'gpu_webgl_conformance_telemetry_tests',
     ],
@@ -5254,9 +5252,8 @@
       'gpu_swiftshader_gtests',
     ],
 
-    'gpu_fyi_linux_intel_and_nvidia_release_experimental_telemetry_tests': [
+    'gpu_fyi_linux_intel_and_nvidia_release_telemetry_tests': [
       'gpu_common_and_optional_telemetry_tests',
-      'gpu_skia_renderer_vulkan_telemetry_tests',
       'gpu_telemetry_tests',
       'gpu_webgl2_conformance_gl_passthrough_telemetry_tests',
       'gpu_webgl2_conformance_telemetry_tests',
@@ -5264,8 +5261,9 @@
       'gpu_webgl_conformance_telemetry_tests',
     ],
 
-    'gpu_fyi_linux_intel_and_nvidia_release_telemetry_tests': [
+    'gpu_fyi_linux_intel_release_experimental_telemetry_tests': [
       'gpu_common_and_optional_telemetry_tests',
+      'gpu_skia_renderer_vulkan_telemetry_tests',
       'gpu_telemetry_tests',
       'gpu_webgl2_conformance_gl_passthrough_telemetry_tests',
       'gpu_webgl2_conformance_telemetry_tests',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 66ad7a4f..8b75ad4 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2631,7 +2631,7 @@
         'test_suites': {
           'gtest_tests': 'gpu_fyi_linux_release_gtests',
           'isolated_scripts': 'gpu_blink_web_tests',
-          'gpu_telemetry_tests': 'gpu_fyi_linux_intel_and_nvidia_release_experimental_telemetry_tests',
+          'gpu_telemetry_tests': 'gpu_fyi_linux_intel_release_experimental_telemetry_tests',
         }
       },
       'Linux FYI Experimental Release (NVIDIA)': {
@@ -2641,10 +2641,11 @@
           'limited_capacity_bot',
           'linux_nvidia_quadro_p400_experimental',
         ],
+        # Currently the experimental driver is identical to the stable
+        # driver. If it's upgraded, change these test_suites to be the same as
+        # 'Linux FYI Release (NVIDIA)'.
         'test_suites': {
-          'gtest_tests': 'gpu_fyi_linux_release_gtests',
-          'isolated_scripts': 'gpu_blink_web_tests_and_angle_perf_isolated_scripts',
-          'gpu_telemetry_tests': 'gpu_fyi_linux_intel_and_nvidia_release_experimental_telemetry_tests',
+          'gpu_telemetry_tests': 'gpu_noop_sleep_telemetry_test',
         },
       },
       'Linux FYI GPU TSAN Release': {
@@ -2718,7 +2719,7 @@
         ],
         'test_suites': {
           'gtest_tests': 'gpu_fyi_linux_release_gtests',
-          'isolated_scripts': 'gpu_blink_web_tests_skia_renderer',
+          'isolated_scripts': 'gpu_blink_web_tests',
           'gpu_telemetry_tests': 'gpu_skia_renderer_telemetry_tests',
         },
       },
diff --git a/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc b/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc
index 313f82d..63729da 100644
--- a/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc
+++ b/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc
@@ -159,7 +159,9 @@
 
   base::ScopedMachMsgDestroy scoped_message(result.message.header);
 
-  result.kr = mach_msg_send(result.message.header);
+  result.kr = mach_msg(result.message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT,
+                       result.message.header->msgh_size, 0, MACH_PORT_NULL,
+                       /*timeout=*/0, MACH_PORT_NULL);
 
   if (result.kr == KERN_SUCCESS) {
     scoped_message.Disarm();
diff --git a/third_party/blink/public/platform/web_url_request.h b/third_party/blink/public/platform/web_url_request.h
index 2a09fa67..1e261f6 100644
--- a/third_party/blink/public/platform/web_url_request.h
+++ b/third_party/blink/public/platform/web_url_request.h
@@ -118,18 +118,12 @@
 
   class ExtraData {
    public:
-    void set_is_preprerendering(bool is_prerendering) {
-      is_prerendering_ = is_prerendering;
-    }
     void set_render_frame_id(int render_frame_id) {
       render_frame_id_ = render_frame_id;
     }
     void set_is_main_frame(bool is_main_frame) {
       is_main_frame_ = is_main_frame;
     }
-    void set_allow_download(bool allow_download) {
-      allow_download_ = allow_download;
-    }
     ui::PageTransition transition_type() const { return transition_type_; }
     void set_transition_type(ui::PageTransition transition_type) {
       transition_type_ = transition_type;
@@ -148,9 +142,6 @@
         bool originated_from_service_worker) {
       originated_from_service_worker_ = originated_from_service_worker;
     }
-    void set_initiated_in_secure_context(bool secure) {
-      initiated_in_secure_context_ = secure;
-    }
 
     // Determines whether SameSite cookies will be attached to the request
     // even when the request looks cross-site.
@@ -162,14 +153,11 @@
     virtual ~ExtraData() = default;
 
    protected:
-    bool is_prerendering_ = false;
     int render_frame_id_ = MSG_ROUTING_NONE;
     bool is_main_frame_ = false;
-    bool allow_download_ = true;
     ui::PageTransition transition_type_ = ui::PAGE_TRANSITION_LINK;
     bool is_for_no_state_prefetch_ = false;
     bool originated_from_service_worker_ = false;
-    bool initiated_in_secure_context_ = false;
     bool attach_same_site_cookies_ = false;
   };
 
@@ -256,17 +244,6 @@
   BLINK_PLATFORM_EXPORT int RequestorID() const;
   BLINK_PLATFORM_EXPORT void SetRequestorID(int);
 
-  // The unique child id (not PID) of the process from which this request
-  // originated. In the case of out-of-process plugins, this allows to link back
-  // the request to the plugin process (as it is processed through a render view
-  // process).
-  BLINK_PLATFORM_EXPORT int GetPluginChildID() const;
-  BLINK_PLATFORM_EXPORT void SetPluginChildID(int);
-
-  // Allows the request to be matched up with its app cache host.
-  BLINK_PLATFORM_EXPORT const base::UnguessableToken& AppCacheHostID() const;
-  BLINK_PLATFORM_EXPORT void SetAppCacheHostID(const base::UnguessableToken&);
-
   // If true, the client expects to receive the raw response pipe. Similar to
   // UseStreamOnResponse but the stream will be a mojo DataPipe rather than a
   // WebDataConsumerHandle.
diff --git a/third_party/blink/public/platform/web_worker_fetch_context.h b/third_party/blink/public/platform/web_worker_fetch_context.h
index 7d647804..4c6f954 100644
--- a/third_party/blink/public/platform/web_worker_fetch_context.h
+++ b/third_party/blink/public/platform/web_worker_fetch_context.h
@@ -119,8 +119,6 @@
   virtual void DidRunInsecureContent(const WebSecurityOrigin&,
                                      const WebURL& insecure_url) {}
 
-  virtual void SetApplicationCacheHostID(const base::UnguessableToken& id) {}
-
   // Sets the builder object of WebDocumentSubresourceFilter on the main thread
   // which will be used in TakeSubresourceFilter() to create a
   // WebDocumentSubresourceFilter on the worker thread.
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index f047854..0a88b3c 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -4970,7 +4970,8 @@
     {
       name: "marker",
       longhands: ["marker-start", "marker-mid", "marker-end"],
-      property_methods: ["ParseShorthand"],
+      property_methods: ["ParseShorthand", "CSSValueFromComputedStyleInternal"],
+      svg: true,
     },
     {
       name: "offset",
diff --git a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
index 8dd364f..81707c6 100644
--- a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
@@ -2110,6 +2110,22 @@
   return true;
 }
 
+const CSSValue* Marker::CSSValueFromComputedStyleInternal(
+    const ComputedStyle& style,
+    const SVGComputedStyle& svg_style,
+    const LayoutObject* layout_object,
+    bool allow_visited_style) const {
+  const CSSValue* marker_start =
+      ComputedStyleUtils::ValueForSVGResource(svg_style.MarkerStartResource());
+  if (*marker_start == *ComputedStyleUtils::ValueForSVGResource(
+                           svg_style.MarkerMidResource()) &&
+      *marker_start == *ComputedStyleUtils::ValueForSVGResource(
+                           svg_style.MarkerEndResource())) {
+    return marker_start;
+  }
+  return nullptr;
+}
+
 bool Offset::ParseShorthand(
     bool important,
     CSSParserTokenRange& range,
diff --git a/third_party/blink/renderer/core/css/style_property_serializer.cc b/third_party/blink/renderer/core/css/style_property_serializer.cc
index d338cbd..87baec6e 100644
--- a/third_party/blink/renderer/core/css/style_property_serializer.cc
+++ b/third_party/blink/renderer/core/css/style_property_serializer.cc
@@ -548,9 +548,15 @@
     case CSSPropertyID::kWebkitTextStroke:
       return GetShorthandValue(webkitTextStrokeShorthand());
     case CSSPropertyID::kMarker: {
-      if (const CSSValue* value =
-              property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerStart()))
-        return value->CssText();
+      if (const CSSValue* start =
+              property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerStart())) {
+        const CSSValue* mid =
+            property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerMid());
+        const CSSValue* end =
+            property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerEnd());
+        if (mid && end && *start == *mid && *start == *end)
+          return start->CssText();
+      }
       return String();
     }
     case CSSPropertyID::kBorderRadius:
diff --git a/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc b/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc
index 1c69f37d..a9faf55 100644
--- a/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc
@@ -264,8 +264,6 @@
   scoped_refptr<WebWorkerFetchContext> web_worker_fetch_context =
       client_->CreateWorkerFetchContext();
   DCHECK(web_worker_fetch_context);
-  web_worker_fetch_context->SetApplicationCacheHostID(
-      appcache_host_->GetHostID());
 
   // TODO(nhiroki); Set |script_type| to mojom::ScriptType::kModule for module
   // fetch (https://crbug.com/824646).
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 4ecbcc2..cd5faa5 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -695,6 +695,8 @@
           continue;
         LayoutFromRootObject(*root);
 
+        root->PaintingLayer()->UpdateLayerPositionsAfterLayout();
+
         // We need to ensure that we mark up all layoutObjects up to the
         // LayoutView for paint invalidation. This simplifies our code as we
         // just always do a full tree walk.
@@ -764,6 +766,7 @@
 
   FontCachePurgePreventer font_cache_purge_preventer;
   StyleRetainScope style_retain_scope;
+  bool in_subtree_layout = IsSubtreeLayout();
   {
     base::AutoReset<bool> change_scheduling_enabled(&layout_scheduling_enabled_,
                                                     false);
@@ -776,8 +779,6 @@
       ClearLayoutSubtreeRootsAndMarkContainingBlocks();
     GetLayoutView()->ClearHitTestCache();
 
-    bool in_subtree_layout = IsSubtreeLayout();
-
     // TODO(crbug.com/460956): The notion of a single root for layout is no
     // longer applicable. Remove or update this code.
     if (in_subtree_layout)
@@ -873,9 +874,8 @@
 
   frame_timing_requests_dirty_ = true;
 
-  // FIXME: Could find the common ancestor layer of all dirty subtrees and
-  // mark from there. crbug.com/462719
-  GetLayoutView()->EnclosingLayer()->UpdateLayerPositionsAfterLayout();
+  if (!in_subtree_layout)
+    GetLayoutView()->EnclosingLayer()->UpdateLayerPositionsAfterLayout();
 
   TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
       TRACE_DISABLED_BY_DEFAULT("blink.debug.layout.trees"), "LayoutTree", this,
diff --git a/third_party/blink/renderer/core/loader/appcache/application_cache_host.h b/third_party/blink/renderer/core/loader/appcache/application_cache_host.h
index 70cc19a..00d0369 100644
--- a/third_party/blink/renderer/core/loader/appcache/application_cache_host.h
+++ b/third_party/blink/renderer/core/loader/appcache/application_cache_host.h
@@ -51,7 +51,6 @@
 namespace blink {
 class ApplicationCache;
 class DocumentLoader;
-class ResourceRequest;
 
 // TODO(nhiroki): Move virtual functions in this class into
 // ApplicationCacheHostForFrame after making DocumentLoader own only
@@ -121,7 +120,6 @@
   void SetSubresourceFactory(
       network::mojom::blink::URLLoaderFactoryPtr url_loader_factory) override {}
 
-  virtual void WillStartLoading(ResourceRequest&) {}
   virtual void WillStartLoadingMainResource(DocumentLoader* loader,
                                             const KURL& url,
                                             const String& method) {}
diff --git a/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.cc b/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.cc
index a33b224..55171682 100644
--- a/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.cc
+++ b/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.cc
@@ -134,14 +134,6 @@
       std::move(pending_factories));
 }
 
-void ApplicationCacheHostForFrame::WillStartLoading(ResourceRequest& request) {
-  if (!IsApplicationCacheEnabled() || !backend_host_.is_bound())
-    return;
-  const base::UnguessableToken& host_id = GetHostID();
-  if (!host_id.is_empty())
-    request.SetAppCacheHostID(host_id);
-}
-
 void ApplicationCacheHostForFrame::WillStartLoadingMainResource(
     DocumentLoader* loader,
     const KURL& url,
diff --git a/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.h b/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.h
index 8d0b7aa..19ca0ba 100644
--- a/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.h
+++ b/third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.h
@@ -32,7 +32,6 @@
   void SetSubresourceFactory(
       network::mojom::blink::URLLoaderFactoryPtr url_loader_factory) override;
 
-  void WillStartLoading(ResourceRequest&) override;
   void WillStartLoadingMainResource(DocumentLoader* loader,
                                     const KURL& url,
                                     const String& method) override;
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 52d540c..9b5ce27 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -435,12 +435,6 @@
     MasterDocumentLoader()->GetServiceWorkerNetworkProvider()->WillSendRequest(
         webreq);
   }
-
-  // If it's not for redirect, hook up ApplicationCache here too.
-  if (!for_redirect && GetDocumentLoader() && !GetDocumentLoader()->Archive() &&
-      request.Url().IsValid()) {
-    GetDocumentLoader()->GetApplicationCacheHost()->WillStartLoading(request);
-  }
 }
 
 void FrameFetchContext::AddResourceTiming(const ResourceTimingInfo& info) {
diff --git a/third_party/blink/renderer/core/script/classic_script.h b/third_party/blink/renderer/core/script/classic_script.h
index e64a211..2142a27 100644
--- a/third_party/blink/renderer/core/script/classic_script.h
+++ b/third_party/blink/renderer/core/script/classic_script.h
@@ -36,9 +36,6 @@
   }
   void RunScript(LocalFrame*, const SecurityOrigin*) override;
   void RunScriptOnWorker(WorkerGlobalScope&) override;
-  String InlineSourceTextForCSP() const override {
-    return script_source_code_.Source().ToString();
-  }
 
   const ScriptSourceCode script_source_code_;
   const SanitizeScriptErrors sanitize_script_errors_;
diff --git a/third_party/blink/renderer/core/script/js_module_script.cc b/third_party/blink/renderer/core/script/js_module_script.cc
index 8b12c6ac..694f6fe 100644
--- a/third_party/blink/renderer/core/script/js_module_script.cc
+++ b/third_party/blink/renderer/core/script/js_module_script.cc
@@ -57,8 +57,8 @@
   // immediately turn the script into errored state. Thus the members will not
   // be used for the speced algorithms, but may be used from inspector.
   JSModuleScript* script =
-      CreateInternal(source_text, modulator, result, source_url, base_url,
-                     options, start_position, produce_cache_data);
+      CreateInternal(source_text.length(), modulator, result, source_url,
+                     base_url, options, start_position, produce_cache_data);
 
   // <spec step="8">If result is a list of errors, then:</spec>
   if (exception_state.HadException()) {
@@ -108,17 +108,15 @@
     ModuleRecord record,
     const KURL& base_url,
     const ScriptFetchOptions& options) {
-  ParkableString dummy_source_text(String("").ReleaseImpl());
   KURL dummy_source_url;
-  return CreateInternal(dummy_source_text, modulator, record, dummy_source_url,
-                        base_url, options, TextPosition::MinimumPosition(),
-                        nullptr);
+  return CreateInternal(0, modulator, record, dummy_source_url, base_url,
+                        options, TextPosition::MinimumPosition(), nullptr);
 }
 
 // <specdef
 // href="https://html.spec.whatwg.org/C/#creating-a-javascript-module-script">
 JSModuleScript* JSModuleScript::CreateInternal(
-    const ParkableString& source_text,
+    size_t source_text_length,
     Modulator* modulator,
     ModuleRecord result,
     const KURL& source_url,
@@ -134,10 +132,8 @@
   // <spec step="4">Set script's base URL to baseURL.</spec>
   //
   // <spec step="5">Set script's fetch options to options.</spec>
-  //
-  // [nospec] |source_text| is saved for CSP checks.
   JSModuleScript* module_script = MakeGarbageCollected<JSModuleScript>(
-      modulator, result, source_url, base_url, options, source_text,
+      modulator, result, source_url, base_url, options, source_text_length,
       start_position, produce_cache_data);
 
   // Step 7, a part of ParseModule(): Passing script as the last parameter
@@ -152,7 +148,7 @@
                                const KURL& source_url,
                                const KURL& base_url,
                                const ScriptFetchOptions& fetch_options,
-                               const ParkableString& source_text,
+                               size_t source_text_length,
                                const TextPosition& start_position,
                                ModuleRecordProduceCacheData* produce_cache_data)
     : ModuleScript(settings_object,
@@ -160,14 +156,10 @@
                    source_url,
                    base_url,
                    fetch_options),
-      source_text_(source_text),
+      source_text_length_(source_text_length),
       start_position_(start_position),
       produce_cache_data_(produce_cache_data) {}
 
-String JSModuleScript::InlineSourceTextForCSP() const {
-  return source_text_.ToString();
-}
-
 void JSModuleScript::ProduceCache() {
   if (!produce_cache_data_)
     return;
@@ -176,7 +168,7 @@
   v8::Isolate* isolate = script_state->GetIsolate();
   ScriptState::Scope scope(script_state);
 
-  V8CodeCache::ProduceCache(isolate, produce_cache_data_, source_text_.length(),
+  V8CodeCache::ProduceCache(isolate, produce_cache_data_, source_text_length_,
                             SourceURL(), StartPosition());
 
   produce_cache_data_ = nullptr;
diff --git a/third_party/blink/renderer/core/script/js_module_script.h b/third_party/blink/renderer/core/script/js_module_script.h
index 61089f8..5d188c8b 100644
--- a/third_party/blink/renderer/core/script/js_module_script.h
+++ b/third_party/blink/renderer/core/script/js_module_script.h
@@ -50,7 +50,7 @@
                  const KURL& source_url,
                  const KURL& base_url,
                  const ScriptFetchOptions&,
-                 const ParkableString& source_text,
+                 size_t source_text_length,
                  const TextPosition& start_position,
                  ModuleRecordProduceCacheData*);
   ~JSModuleScript() override = default;
@@ -64,7 +64,7 @@
   friend class ModuleScriptTest;
 
   static JSModuleScript* CreateInternal(
-      const ParkableString& source_text,
+      size_t source_text_length,
       Modulator*,
       ModuleRecord,
       const KURL& source_url,
@@ -74,10 +74,9 @@
       ModuleRecordProduceCacheData* produce_cache_data);
 
   const TextPosition& StartPosition() const { return start_position_; }
-  String InlineSourceTextForCSP() const override;
 
-  // For CSP check.
-  const ParkableString source_text_;
+  // For V8CodeCache statistics.
+  const size_t source_text_length_;
 
   const TextPosition start_position_;
 
diff --git a/third_party/blink/renderer/core/script/pending_script.cc b/third_party/blink/renderer/core/script/pending_script.cc
index b8fe2d8a..5fefbfc 100644
--- a/third_party/blink/renderer/core/script/pending_script.cc
+++ b/third_party/blink/renderer/core/script/pending_script.cc
@@ -29,7 +29,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/document_parser_timing.h"
-#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/script/ignore_destructive_write_count_incrementer.h"
 #include "third_party/blink/renderer/core/script/script_element_base.h"
@@ -155,20 +154,6 @@
 
   Script* script = GetSource(document_url);
 
-  if (script && !IsExternal()) {
-    AtomicString nonce = element_->GetNonceForElement();
-    if (!element_->AllowInlineScriptForCSP(nonce, StartingPosition().line_,
-                                           script->InlineSourceTextForCSP())) {
-      // Consider as if:
-      //
-      // <spec step="2">If the script's script is null, ...</spec>
-      //
-      // retrospectively, if the CSP check fails, which is considered as load
-      // failure.
-      script = nullptr;
-    }
-  }
-
   const bool was_canceled = WasCanceled();
   const bool is_external = IsExternal();
   const bool created_during_document_write = WasCreatedDuringDocumentWrite();
diff --git a/third_party/blink/renderer/core/script/script.h b/third_party/blink/renderer/core/script/script.h
index d7951dc1..2226029 100644
--- a/third_party/blink/renderer/core/script/script.h
+++ b/third_party/blink/renderer/core/script/script.h
@@ -35,9 +35,6 @@
   virtual void RunScript(LocalFrame*, const SecurityOrigin*) = 0;
   virtual void RunScriptOnWorker(WorkerGlobalScope&) = 0;
 
-  // For CSP check for inline scripts.
-  virtual String InlineSourceTextForCSP() const = 0;
-
   const ScriptFetchOptions& FetchOptions() const { return fetch_options_; }
   const KURL& BaseURL() const { return base_url_; }
 
diff --git a/third_party/blink/renderer/core/script/script_loader.cc b/third_party/blink/renderer/core/script/script_loader.cc
index 4666d85..98dc905 100644
--- a/third_party/blink/renderer/core/script/script_loader.cc
+++ b/third_party/blink/renderer/core/script/script_loader.cc
@@ -415,7 +415,19 @@
   TextPosition position =
       is_in_document_write ? TextPosition() : script_start_position;
 
-  // 13.
+  // <spec step="13">If the script element does not have a src content
+  // attribute, and the Should element's inline behavior be blocked by Content
+  // Security Policy? algorithm returns "Blocked" when executed upon the script
+  // element, "script", and source text, then return. The script is not
+  // executed. [CSP]</spec>
+  if (!element_->HasSourceAttribute() &&
+      !element_->AllowInlineScriptForCSP(element_->GetNonceForElement(),
+                                         position.line_,
+                                         element_->TextFromChildren())) {
+    return false;
+  }
+
+  // 14.
   if (!IsScriptForEventSupported())
     return false;
 
diff --git a/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.cc b/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.cc
index 17f28ce..24bf5bb2 100644
--- a/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.cc
+++ b/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.cc
@@ -18,8 +18,7 @@
 ValueWrapperSyntheticModuleScript*
 ValueWrapperSyntheticModuleScript::CreateJSONWrapperSyntheticModuleScript(
     const base::Optional<ModuleScriptCreationParams>& params,
-    Modulator* settings_object,
-    const ScriptFetchOptions options_) {
+    Modulator* settings_object) {
   DCHECK(settings_object->HasValidContext());
   ScriptState::Scope scope(settings_object->GetScriptState());
   v8::Local<v8::Context> context =
@@ -51,12 +50,12 @@
     v8::Local<v8::Value> error = exception_state.GetException();
     exception_state.ClearException();
     return ValueWrapperSyntheticModuleScript::CreateWithError(
-        parsed_json, settings_object, params->GetResponseUrl(),
-        params->GetResponseUrl(), options_, error);
+        parsed_json, settings_object, params->GetResponseUrl(), KURL(),
+        ScriptFetchOptions(), error);
   } else {
     return ValueWrapperSyntheticModuleScript::CreateWithDefaultExport(
-        parsed_json, settings_object, params->GetResponseUrl(),
-        params->GetResponseUrl(), options_);
+        parsed_json, settings_object, params->GetResponseUrl(), KURL(),
+        ScriptFetchOptions());
   }
 }
 
@@ -149,17 +148,6 @@
   return v8::Undefined(reinterpret_cast<v8::Isolate*>(isolate));
 }
 
-String ValueWrapperSyntheticModuleScript::InlineSourceTextForCSP() const {
-  // We don't construct a ValueWrapperSyntheticModuleScript with the original
-  // source, but instead construct it from the originally parsed
-  // text. If a need arises for the original module source to be used later,
-  // ValueWrapperSyntheticModuleScript will need to be modified such that its
-  // constructor takes this source text as an additional parameter and stashes
-  // it on the ValueWrapperSyntheticModuleScript.
-  NOTREACHED();
-  return "";
-}
-
 void ValueWrapperSyntheticModuleScript::Trace(Visitor* visitor) {
   visitor->Trace(export_value_);
   ModuleScript::Trace(visitor);
diff --git a/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h b/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h
index ae19c095..1200e84e 100644
--- a/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h
+++ b/third_party/blink/renderer/core/script/value_wrapper_synthetic_module_script.h
@@ -28,8 +28,7 @@
   static ValueWrapperSyntheticModuleScript*
   CreateJSONWrapperSyntheticModuleScript(
       const base::Optional<ModuleScriptCreationParams>& params,
-      Modulator* settings_object,
-      const ScriptFetchOptions options_);
+      Modulator* settings_object);
 
   static ValueWrapperSyntheticModuleScript* CreateWithDefaultExport(
       v8::Local<v8::Value> value,
@@ -66,7 +65,6 @@
       v8::Local<v8::Context> context,
       v8::Local<v8::Module> module);
 
-  String InlineSourceTextForCSP() const override;
   void Trace(blink::Visitor* visitor) override;
 
  private:
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.cc b/third_party/blink/renderer/core/workers/dedicated_worker.cc
index 98a5bc6..2445a76 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.cc
@@ -483,11 +483,6 @@
               factory_client_.get());
     } else {
       web_worker_fetch_context = frame->Client()->CreateWorkerFetchContext();
-      web_worker_fetch_context->SetApplicationCacheHostID(
-          frame->Loader()
-              .GetDocumentLoader()
-              ->GetApplicationCacheHost()
-              ->GetHostID());
     }
     web_worker_fetch_context->SetIsOnSubframe(!frame->IsMainFrame());
     return web_worker_fetch_context;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 41358473..7f80058 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -1178,14 +1178,28 @@
   if (!GetNode())
     return false;
 
+  // If the node is part of the user agent shadow dom, or has the explicit
+  // internal Role::kIgnored, they aren't interesting for paragraph navigation
+  // or LabelledBy/DescribedBy relationships.
+  if (RoleValue() == ax::mojom::Role::kIgnored ||
+      GetNode()->IsInUserAgentShadowRoot()) {
+    return false;
+  }
+
   // Always pass through Line Breaking objects, this is necessary to
   // detect paragraph edges, which are defined as hard-line breaks.
-  //
-  // Though if the node is part of the shadow dom, or has the explicit
-  // internal Role::kIgnored, they aren't interesting for paragraph
-  // navigation so exclude those cases.
-  return RoleValue() != ax::mojom::Role::kIgnored &&
-         !GetNode()->IsInUserAgentShadowRoot() && IsLineBreakingObject();
+  if (IsLineBreakingObject())
+    return true;
+
+  // Allow the browser side ax tree to access "visibility: hidden" nodes. This
+  // is useful for APIs that return the node referenced by aria-labeledby and
+  // aria-descibedby
+  if (GetLayoutObject() &&
+      GetLayoutObject()->Style()->Visibility() == EVisibility::kHidden) {
+    return true;
+  }
+
+  return false;
 }
 
 const AXObject* AXObject::DatetimeAncestor(int max_levels_to_check) const {
@@ -1491,8 +1505,15 @@
                          AXObject::AXObjectVector* name_objects) const {
   HeapHashSet<Member<const AXObject>> visited;
   AXRelatedObjectVector related_objects;
-  String text = TextAlternative(false, false, visited, name_from,
-                                &related_objects, nullptr);
+  // For purposes of computing a text alternative, if an ignored node is
+  // included in the tree, assume that it is the target of aria-labelledby or
+  // aria-describedby, since we can't tell yet whether that's the case. If it
+  // isn't exposed, the AT will never see the name anyways.
+  bool hidden_and_ignored_but_included_in_tree =
+      IsHiddenForTextAlternativeCalculation() &&
+      AccessibilityIsIgnoredButIncludedInTree();
+  String text = TextAlternative(false, hidden_and_ignored_but_included_in_tree,
+                                visited, name_from, &related_objects, nullptr);
 
   ax::mojom::Role role = RoleValue();
   if (!GetNode() ||
@@ -1513,8 +1534,16 @@
   AXObjectSet visited;
   ax::mojom::NameFrom tmp_name_from;
   AXRelatedObjectVector tmp_related_objects;
-  String text = TextAlternative(false, false, visited, tmp_name_from,
-                                &tmp_related_objects, name_sources);
+  // For purposes of computing a text alternative, if an ignored node is
+  // included in the tree, assume that it is the target of aria-labelledby or
+  // aria-describedby, since we can't tell yet whether that's the case. If it
+  // isn't exposed, the AT will never see the name anyways.
+  bool hidden_and_ignored_but_included_in_tree =
+      IsHiddenForTextAlternativeCalculation() &&
+      AccessibilityIsIgnoredButIncludedInTree();
+  String text =
+      TextAlternative(false, hidden_and_ignored_but_included_in_tree, visited,
+                      tmp_name_from, &tmp_related_objects, name_sources);
   text = text.SimplifyWhiteSpace(IsHTMLSpace<UChar>);
   return text;
 }
diff --git a/third_party/blink/renderer/platform/exported/web_url_request.cc b/third_party/blink/renderer/platform/exported/web_url_request.cc
index e3314fe..ddae9cf 100644
--- a/third_party/blink/renderer/platform/exported/web_url_request.cc
+++ b/third_party/blink/renderer/platform/exported/web_url_request.cc
@@ -248,23 +248,6 @@
   resource_request_->SetRequestorID(requestor_id);
 }
 
-int WebURLRequest::GetPluginChildID() const {
-  return resource_request_->GetPluginChildID();
-}
-
-void WebURLRequest::SetPluginChildID(int plugin_child_id) {
-  resource_request_->SetPluginChildID(plugin_child_id);
-}
-
-const base::UnguessableToken& WebURLRequest::AppCacheHostID() const {
-  return resource_request_->AppCacheHostID();
-}
-
-void WebURLRequest::SetAppCacheHostID(
-    const base::UnguessableToken& app_cache_host_id) {
-  resource_request_->SetAppCacheHostID(app_cache_host_id);
-}
-
 bool WebURLRequest::PassResponsePipeToClient() const {
   return resource_request_->DownloadToBlob();
 }
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
index ea4a3d5..b67b3f43 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
@@ -64,7 +64,6 @@
       priority_(ResourceLoadPriority::kUnresolved),
       intra_priority_value_(0),
       requestor_id_(0),
-      plugin_child_id_(-1),
       previews_state_(WebURLRequest::kPreviewsUnspecified),
       request_context_(mojom::RequestContextType::UNSPECIFIED),
       mode_(network::mojom::RequestMode::kNoCors),
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.h b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
index 741469f..d61a3c8 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
@@ -214,23 +214,6 @@
   int RequestorID() const { return requestor_id_; }
   void SetRequestorID(int requestor_id) { requestor_id_ = requestor_id; }
 
-  // The unique child id (not PID) of the process from which this request
-  // originated. In the case of out-of-process plugins, this allows to link back
-  // the request to the plugin process (as it is processed through a render view
-  // process).
-  int GetPluginChildID() const { return plugin_child_id_; }
-  void SetPluginChildID(int plugin_child_id) {
-    plugin_child_id_ = plugin_child_id;
-  }
-
-  // Allows the request to be matched up with its app cache host.
-  const base::UnguessableToken& AppCacheHostID() const {
-    return app_cache_host_id_;
-  }
-  void SetAppCacheHostID(const base::UnguessableToken& id) {
-    app_cache_host_id_ = id;
-  }
-
   // True if request was user initiated.
   bool HasUserGesture() const { return has_user_gesture_; }
   void SetHasUserGesture(bool);
@@ -484,8 +467,6 @@
   ResourceLoadPriority priority_;
   int intra_priority_value_;
   int requestor_id_;
-  int plugin_child_id_;
-  base::UnguessableToken app_cache_host_id_;
   WebURLRequest::PreviewsState previews_state_;
   scoped_refptr<SharableExtraData> sharable_extra_data_;
   mojom::RequestContextType request_context_;
diff --git a/third_party/blink/renderer/platform/network/http_parsers.cc b/third_party/blink/renderer/platform/network/http_parsers.cc
index 02ab74671..bd39711 100644
--- a/third_party/blink/renderer/platform/network/http_parsers.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers.cc
@@ -281,7 +281,7 @@
   HeaderFieldTokenizer tokenizer(header);
 
   StringView toggle;
-  if (!tokenizer.ConsumeToken(Mode::kNormal, toggle)) {
+  if (!tokenizer.ConsumeToken(ParsedContentType::Mode::kNormal, toggle)) {
     if (tokenizer.IsConsumed())
       return kReflectedXSSUnset;
   }
@@ -314,7 +314,7 @@
     // At start of next directive.
     StringView token;
     unsigned token_start = tokenizer.Index();
-    if (!tokenizer.ConsumeToken(Mode::kNormal, token)) {
+    if (!tokenizer.ConsumeToken(ParsedContentType::Mode::kNormal, token)) {
       failure_reason = failure_reason_invalid_directive;
       failure_position = token_start;
       return kReflectedXSSInvalid;
@@ -333,7 +333,8 @@
       }
       String value;
       unsigned value_start = tokenizer.Index();
-      if (!tokenizer.ConsumeTokenOrQuotedString(Mode::kNormal, value) ||
+      if (!tokenizer.ConsumeTokenOrQuotedString(
+              ParsedContentType::Mode::kNormal, value) ||
           !EqualIgnoringASCIICase(value, "block")) {
         failure_reason = failure_reason_invalid_mode;
         failure_position = value_start;
@@ -356,7 +357,8 @@
       failure_position = tokenizer.Index();
       String value;
       // Relaxed mode - unquoted URLs contain colons and such.
-      if (!tokenizer.ConsumeTokenOrQuotedString(Mode::kRelaxed, value)) {
+      if (!tokenizer.ConsumeTokenOrQuotedString(
+              ParsedContentType::Mode::kRelaxed, value)) {
         failure_reason = failure_reason_invalid_report;
         return kReflectedXSSInvalid;
       }
@@ -677,7 +679,7 @@
     HeaderFieldTokenizer tokenizer(headerValue);
     while (!tokenizer.IsConsumed()) {
       StringView name;
-      if (!tokenizer.ConsumeToken(Mode::kNormal, name)) {
+      if (!tokenizer.ConsumeToken(ParsedContentType::Mode::kNormal, name)) {
         break;
       }
 
@@ -685,13 +687,15 @@
 
       while (tokenizer.Consume(';')) {
         StringView parameter_name;
-        if (!tokenizer.ConsumeToken(Mode::kNormal, parameter_name)) {
+        if (!tokenizer.ConsumeToken(ParsedContentType::Mode::kNormal,
+                                    parameter_name)) {
           break;
         }
 
         String value = "";
         if (tokenizer.Consume('=')) {
-          tokenizer.ConsumeTokenOrQuotedString(Mode::kNormal, value);
+          tokenizer.ConsumeTokenOrQuotedString(ParsedContentType::Mode::kNormal,
+                                               value);
           tokenizer.ConsumeBeforeAnyCharMatch({',', ';'});
         }
         header.SetParameter(parameter_name, value);
diff --git a/third_party/blink/renderer/platform/network/http_parsers.h b/third_party/blink/renderer/platform/network/http_parsers.h
index e86c7c59..84f9508 100644
--- a/third_party/blink/renderer/platform/network/http_parsers.h
+++ b/third_party/blink/renderer/platform/network/http_parsers.h
@@ -164,9 +164,6 @@
 
 PLATFORM_EXPORT std::unique_ptr<ServerTimingHeaderVector>
 ParseServerTimingHeader(const String&);
-
-using Mode = blink::ParsedContentType::Mode;
-
 }  // namespace blink
 
 #endif
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 2d7ceb3..cb9ac21d 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6503,3 +6503,6 @@
 crbug.com/989863 [ Linux ] virtual/streaming-preload/http/tests/fetch/serviceworker/thorough/nocors-base-https-other-https.html [ Pass Failure ]
 crbug.com/989863 [ Linux ] virtual/streaming-preload/http/tests/fetch/serviceworker/thorough/redirect-credentials-base-https-other-https.html [ Pass Failure ]
 crbug.com/989863 [ Linux ] virtual/streaming-preload/http/tests/fetch/serviceworker/thorough/redirect-loop-base-https-other-https.html [ Pass Failure ]
+crbug.com/990145 [ Linux ] virtual/blink-cors/http/tests/fetch/serviceworker-proxied/thorough/cookie-base-https-other-https.html [ Pass Failure ]
+crbug.com/990145 [ Linux ] virtual/streaming-preload/http/tests/fetch/serviceworker-proxied/thorough/cookie-base-https-other-https.html [ Pass Failure ]
+crbug.com/990146 [ Linux ] virtual/blink-cors/http/tests/fetch/serviceworker/thorough/cookie-nocors-base-https-other-https.html [ Pass Failure ]
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 c8faf2f0..64d131e 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
@@ -169900,6 +169900,9 @@
    "tools/ci/tcdownload.py": [
     []
    ],
+   "tools/ci/update_pr_preview.py": [
+    []
+   ],
    "tools/ci/website_build.sh": [
     []
    ],
@@ -313185,7 +313188,7 @@
  },
  "paths": {
   ".github/main.workflow": [
-   "4f4447fdf0ff12f0910d20f16e6e31c9dd696749",
+   "20a0f33256d791dcb59a057b319fadd2b58bd663",
    "support"
   ],
   ".gitignore": [
@@ -322221,7 +322224,7 @@
    "support"
   ],
   "common/security-features/resources/common.js": [
-   "10a3190767f264cc8ec6e332ea11f3bbead59614",
+   "7afa6068bf427641fb09a4b6f84f4cc9ba70e420",
    "support"
   ],
   "common/security-features/resources/common.js.headers": [
@@ -322341,7 +322344,7 @@
    "support"
   ],
   "common/security-features/tools/generate.py": [
-   "fc10e635dbd0d52469e5a105764fa681c86dc654",
+   "a97a396e304717f763c313d07f541a69fd7304d0",
    "support"
   ],
   "common/security-features/tools/spec_validator.py": [
@@ -322365,7 +322368,7 @@
    "support"
   ],
   "common/security-features/tools/util.py": [
-   "1f6c86e6efcc3bb5dbde41ba484a026d55a5961e",
+   "fc8d3b30ef71ca3b63c325837cf58f768f5805f4",
    "support"
   ],
   "common/sleep.py": [
@@ -446465,7 +446468,7 @@
    "support"
   ],
   "mixed-content/generic/tools/generate.py": [
-   "ed978bcef9cc9322ef600e5217ae1d754588d50b",
+   "9c911aa6c4102a411c1b134e33afeab69f600647",
    "support"
   ],
   "mixed-content/generic/tools/regenerate": [
@@ -458389,7 +458392,7 @@
    "support"
   ],
   "referrer-policy/generic/tools/generate.py": [
-   "98374982a8d17909e7f192c6d90ae7998e21953b",
+   "ba1e963787bef98bdd0a3eb2016296995f631f97",
    "support"
   ],
   "referrer-policy/generic/tools/regenerate": [
@@ -476312,6 +476315,10 @@
    "91c763a7acdd3416d7eff55407bf8f73b840d96a",
    "support"
   ],
+  "tools/ci/update_pr_preview.py": [
+   "9b104c66bb6a8089e81f633ad5e64cb33ea4f7c1",
+   "support"
+  ],
   "tools/ci/website_build.sh": [
    "aadfcbd6a212574fa5462447072b996ae214de6b",
    "support"
@@ -480537,7 +480544,7 @@
    "support"
   ],
   "tools/tox.ini": [
-   "63fb8ed9832ba9d11dae965e3c335e3d26289720",
+   "52e749e4bdc4c7626df8ba67fb08a6209ea35ff2",
    "support"
   ],
   "tools/webdriver/README.md": [
@@ -480905,11 +480912,11 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/formatters/chromium.py": [
-   "15e8e0b0ce9d09ef4783cd429610d221c91255f3",
+   "707a8519a55950143137cd04038a67b9ad387f38",
    "support"
   ],
   "tools/wptrunner/wptrunner/formatters/tests/test_chromium.py": [
-   "b2c261f92f7e9b4ef8b225e337044d50dfa0c46c",
+   "87691cf5a980b8d00b1b4e77eed80429009b46ee",
    "support"
   ],
   "tools/wptrunner/wptrunner/formatters/wptreport.py": [
diff --git a/third_party/blink/web_tests/external/wpt/.github/main.workflow b/third_party/blink/web_tests/external/wpt/.github/main.workflow
index 4f4447f..20a0f33 100644
--- a/third_party/blink/web_tests/external/wpt/.github/main.workflow
+++ b/third_party/blink/web_tests/external/wpt/.github/main.workflow
@@ -19,3 +19,14 @@
   runs = ["/bin/bash", "tools/ci/website_build.sh"]
   secrets = ["DEPLOY_TOKEN"]
 }
+
+workflow "Synchronize the Pull Request Preview" {
+  on = "pull_request"
+  resolves = "update-pr-preview"
+}
+
+action "update-pr-preview" {
+  uses = "./tools/docker/github"
+  runs = ["python", "tools/ci/update_pr_preview.py", "https://api.github.com"]
+  secrets = ["GITHUB_TOKEN"]
+}
diff --git a/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js b/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
index 10a31907..7afa606 100644
--- a/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
+++ b/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
@@ -1104,7 +1104,7 @@
 */
 function invokeFromWorker(isDataUrl, workerOptions,
                           subresource, sourceContextList) {
-  const currentSourceContext = sourceContextList.shift();
+  const currentSourceContext = sourceContextList[0];
   let workerUrl =
     "/common/security-features/scope/worker.py?policyDeliveries=" +
     encodeURIComponent(JSON.stringify(
@@ -1128,7 +1128,7 @@
     .then(url => {
       const worker = new Worker(url, workerOptions);
       worker.postMessage({subresource: subresource,
-                          sourceContextList: sourceContextList});
+                          sourceContextList: sourceContextList.slice(1)});
       return bindEvents2(worker, "message", worker, "error", window, "error");
     })
     .then(event => {
@@ -1139,7 +1139,7 @@
 }
 
 function invokeFromIframe(subresource, sourceContextList) {
-  const currentSourceContext = sourceContextList.shift();
+  const currentSourceContext = sourceContextList[0];
   const frameUrl =
     "/common/security-features/scope/document.py?policyDeliveries=" +
     encodeURIComponent(JSON.stringify(
@@ -1165,7 +1165,7 @@
                   window, "message", iframe, "error", window, "error");
               iframe.contentWindow.postMessage(
                   {subresource: subresource,
-                   sourceContextList: sourceContextList},
+                   sourceContextList: sourceContextList.slice(1)},
                   "*");
               return promise;
             })
diff --git a/third_party/blink/web_tests/external/wpt/common/security-features/tools/generate.py b/third_party/blink/web_tests/external/wpt/common/security-features/tools/generate.py
index fc10e63..0b18a39 100644
--- a/third_party/blink/web_tests/external/wpt/common/security-features/tools/generate.py
+++ b/third_party/blink/web_tests/external/wpt/common/security-features/tools/generate.py
@@ -57,6 +57,57 @@
         selection, indent=2, separators=(',', ': '), sort_keys=True)
 
 
+def get_test_filename(config, selection):
+    '''Returns the filname for the main test HTML file'''
+
+    selection_for_filename = copy.deepcopy(selection)
+    # Use 'unset' rather than 'None' in test filenames.
+    if selection_for_filename['delivery_value'] is None:
+        selection_for_filename['delivery_value'] = 'unset'
+
+    return os.path.join(config.spec_directory,
+                        config.test_file_path_pattern % selection_for_filename)
+
+
+def handle_deliveries(policy_deliveries):
+    '''
+    Generate <meta> elements and HTTP headers for the given list of
+    PolicyDelivery.
+    TODO(hiroshige): Merge duplicated code here, scope/document.py, etc.
+    '''
+
+    meta = ''
+    headers = {}
+
+    for delivery in policy_deliveries:
+        if delivery.value is None:
+            continue
+        if delivery.key == 'referrerPolicy':
+            if delivery.delivery_type == 'meta':
+                meta += \
+                    '<meta name="referrer" content="%s">' % delivery.value
+            elif delivery.delivery_type == 'http-rp':
+                headers['Referrer-Policy'] = delivery.value
+                # TODO(kristijanburnik): Limit to WPT origins.
+                headers['Access-Control-Allow-Origin'] = '*'
+            else:
+                raise Exception(
+                    'Invalid delivery_type: %s' % delivery.delivery_type)
+        elif delivery.key == 'mixedContent':
+            assert (delivery.value == 'opt-in')
+            if delivery.delivery_type == 'meta':
+                meta += '<meta http-equiv="Content-Security-Policy" ' + \
+                       'content="block-all-mixed-content">'
+            elif delivery.delivery_type == 'http-rp':
+                headers['Content-Security-Policy'] = 'block-all-mixed-content'
+            else:
+                raise Exception(
+                    'Invalid delivery_type: %s' % delivery.delivery_type)
+        else:
+            raise Exception('Invalid delivery_key: %s' % delivery.key)
+    return {"meta": meta, "headers": headers}
+
+
 def generate_selection(config, selection, spec, test_html_template_basename):
     test_parameters = dump_test_parameters(selection)
     # Adjust the template for the test invoking JS. Indent it to look nice.
@@ -80,8 +131,7 @@
     selection['sanity_checker_js'] = config.sanity_checker_js
     selection['spec_json_js'] = config.spec_json_js
 
-    test_filename = os.path.join(config.spec_directory,
-                                 config.test_file_path_pattern % selection)
+    test_filename = get_test_filename(config, selection)
     test_headers_filename = test_filename + ".headers"
     test_directory = os.path.dirname(test_filename)
 
@@ -109,13 +159,16 @@
     except:
         pass
 
-    delivery = config.handleDelivery(selection, spec)
+    delivery = handle_deliveries([
+        util.PolicyDelivery(selection['delivery_type'],
+                            selection['delivery_key'],
+                            selection['delivery_value'])
+    ])
 
     if len(delivery['headers']) > 0:
         with open(test_headers_filename, "w") as f:
             for header in delivery['headers']:
-                f.write(header)
-                f.write('\n')
+                f.write('%s: %s\n' % (header, delivery['headers'][header]))
 
     selection['meta_delivery_method'] = delivery['meta']
     # Obey the lint and pretty format.
diff --git a/third_party/blink/web_tests/external/wpt/common/security-features/tools/util.py b/third_party/blink/web_tests/external/wpt/common/security-features/tools/util.py
index 1f6c86e..fc8d3b3 100644
--- a/third_party/blink/web_tests/external/wpt/common/security-features/tools/util.py
+++ b/third_party/blink/web_tests/external/wpt/common/security-features/tools/util.py
@@ -39,3 +39,14 @@
                 print(read_nth_line(f, line_number).rstrip())
                 print(" " * (column - 1) + "^")
             sys.exit(1)
+
+
+class PolicyDelivery(object):
+    '''
+    See `@typedef PolicyDelivery` comments in `resources/common.js`.
+    '''
+
+    def __init__(self, delivery_type, key, value):
+        self.delivery_type = delivery_type
+        self.key = key
+        self.value = value
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/scripthash-basic-blocked-error-event.html b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-basic-blocked-error-event.html
similarity index 81%
rename from third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/scripthash-basic-blocked-error-event.html
rename to third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-basic-blocked-error-event.html
index d0edacc1..62b8693 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/scripthash-basic-blocked-error-event.html
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-basic-blocked-error-event.html
@@ -6,5 +6,5 @@
     <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-deadbeef'"></meta>
 </head>
 <body>
-    <script src="../resources/fail-to-inject-script.js"></script>
+    <script src="support/inline-script-should-be-blocked.js"></script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-changed-1.html b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-changed-1.html
new file mode 100644
index 0000000..9da41dd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-changed-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<head>
+    <title>CSP inline script check is done at #prepare-a-script (hash)</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!--
+      'log1 += 'scr1 at #prepare-a-script';' => 'sha256-sI+xsvqqUw0LQQGgsgkYoXKWhlGgaCqsqVbPx0Z2A4s=' (allowed)
+      'log1 += 'scr1 at #execute-the-script-block';' => 'sha256-Vtap5AhPN9kbQAbWqObJexCvNDexqoIwo4XsABQBqcg=' (blocked)
+    -->
+    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-abc' 'sha256-sI+xsvqqUw0LQQGgsgkYoXKWhlGgaCqsqVbPx0Z2A4s='"></meta>
+</head>
+<!--
+  "Should element's inline behavior be blocked by Content Security Policy?"
+  is executed at the time of https://html.spec.whatwg.org/C/#prepare-a-script,
+  not at https://html.spec.whatwg.org/C/#execute-the-script-block.
+  So when innerText is modified after #prepare-a-script, the text BEFORE
+  the modification is used for hash check.
+-->
+<script nonce="abc">
+let log1 = '';
+</script>
+
+<!--  Execution order:
+  async script is executed
+  -> stylesheet is loaded
+  -> inline script is executed. -->
+<link rel="stylesheet" href="support/empty.css?dummy=1&pipe=trickle(d2)" type="text/css">
+<script src="support/change-scripthash-before-execute.js?dummy=1&pipe=trickle(d1)" async></script>
+<script id="scr1">log1 += 'scr1 at #prepare-a-script';</script>
+
+<script nonce="abc">
+test(() => {
+  assert_equals(log1, 'scr1 at #prepare-a-script');
+}, 'scr1.innerText before modification should not be blocked');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-changed-2.html b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-changed-2.html
new file mode 100644
index 0000000..927d60a8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scripthash-changed-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<head>
+    <title>CSP inline script check is done at #prepare-a-script (hash)</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!--
+      'log2 += 'scr2 at #prepare-a-script';' => 'sha256-9vE5NuHfEDoLvk3nPZPDX2/mnG+ZwKhpPuwQZwCDGc4=' (blocked)
+      'log2 += 'scr2 at #execute-the-script-block';' => 'sha256-3AdhWTFuyxSUPxmqpTJaFRx3R5WNcyGw57lFoj1rTXw=' (allowed)
+    -->
+    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-abc' 'sha256-3AdhWTFuyxSUPxmqpTJaFRx3R5WNcyGw57lFoj1rTXw='"></meta>
+</head>
+<!--
+  "Should element's inline behavior be blocked by Content Security Policy?"
+  is executed at the time of https://html.spec.whatwg.org/C/#prepare-a-script,
+  not at https://html.spec.whatwg.org/C/#execute-the-script-block.
+  So when innerText is modified after #prepare-a-script, the text BEFORE
+  the modification is used for hash check.
+-->
+<script nonce="abc">
+let log2 = '';
+</script>
+
+<!--  Execution order:
+  async script is executed
+  -> stylesheet is loaded
+  -> inline script is executed. -->
+<link rel="stylesheet" href="support/empty.css?dummy=2&pipe=trickle(d2)" type="text/css">
+<script src="support/change-scripthash-before-execute.js?dummy=2&pipe=trickle(d1)" async></script>
+<script id="scr2">log2 += 'scr2 at #prepare-a-script';</script>
+
+<script nonce="abc">
+test(() => {
+  assert_equals(log2, '');
+}, 'scr2.innerText before modification should be blocked');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scriptnonce-changed-1.html b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scriptnonce-changed-1.html
new file mode 100644
index 0000000..75f92f35
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scriptnonce-changed-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<head>
+    <title>CSP inline script check is done at #prepare-a-script (nonce)</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-abc' 'sha256-deadbeef'"></meta>
+</head>
+<!--
+  "Should element's inline behavior be blocked by Content Security Policy?"
+  is executed at the time of https://html.spec.whatwg.org/C/#prepare-a-script,
+  not at https://html.spec.whatwg.org/C/#execute-the-script-block.
+  So when nonce is modified after #prepare-a-script, the nonce BEFORE
+  the modification is used for hash check.
+-->
+<script nonce="abc">
+let log1 = '';
+</script>
+
+<!--  Execution order:
+  async script is executed
+  -> stylesheet is loaded
+  -> inline script is executed. -->
+<link rel="stylesheet" href="support/empty.css?dummy=3&pipe=trickle(d2)" type="text/css">
+<script src="support/change-scriptnonce-before-execute.js?dummy=3&pipe=trickle(d1)" async></script>
+<script id="scr1" nonce="abc">log1 += 'scr1 executed';</script>
+
+<script nonce="abc">
+test(() => {
+  assert_equals(log1, 'scr1 executed');
+}, 'scr1 nonce before modification should not be blocked');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scriptnonce-changed-2.html b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scriptnonce-changed-2.html
new file mode 100644
index 0000000..f2321dd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/scriptnonce-changed-2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<head>
+    <title>CSP inline script check is done at #prepare-a-script (nonce)</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-abc' 'sha256-deadbeef'"></meta>
+</head>
+<!--
+  "Should element's inline behavior be blocked by Content Security Policy?"
+  is executed at the time of https://html.spec.whatwg.org/C/#prepare-a-script,
+  not at https://html.spec.whatwg.org/C/#execute-the-script-block.
+  So when nonce is modified after #prepare-a-script, the nonce BEFORE
+  the modification is used for hash check.
+-->
+<script nonce="abc">
+let log2 = '';
+</script>
+
+<!--  Execution order:
+  async script is executed
+  -> stylesheet is loaded
+  -> inline script is executed. -->
+<link rel="stylesheet" href="support/empty.css?dummy=4&pipe=trickle(d2)" type="text/css">
+<script src="support/change-scriptnonce-before-execute.js?dummy=4&pipe=trickle(d1)" async></script>
+<script id="scr2" nonce="wrong">log2 += 'scr2 executed';</script>
+
+<script nonce="abc">
+test(() => {
+  assert_equals(log2, '');
+}, 'scr2 nonce before modification should be blocked');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/change-scripthash-before-execute.js b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/change-scripthash-before-execute.js
new file mode 100644
index 0000000..a04e857
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/change-scripthash-before-execute.js
@@ -0,0 +1,10 @@
+// This script is executed after |scr1| and |scr2| are inserted into DOM
+// before their execution (if not blocked by CSP).
+if (document.getElementById("scr1")) {
+  document.getElementById("scr1").innerText =
+    "log1 += 'scr1 at #execute-the-script-block';";
+}
+if (document.getElementById("scr2")) {
+  document.getElementById("scr2").innerText =
+    "log2 += 'scr2 at #execute-the-script-block';";
+}
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/change-scriptnonce-before-execute.js b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/change-scriptnonce-before-execute.js
new file mode 100644
index 0000000..2676b34
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/change-scriptnonce-before-execute.js
@@ -0,0 +1,8 @@
+// This script is executed after |scr1| and |scr2| are inserted into DOM
+// before their execution (if not blocked by CSP).
+if (document.getElementById('scr1')) {
+  document.getElementById('scr1').setAttribute('nonce', 'wrong');
+}
+if (document.getElementById('scr2')) {
+  document.getElementById('scr2').setAttribute('nonce', 'abc');
+}
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/empty.css b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/empty.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/empty.css
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/inline-script-should-be-blocked.js b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/inline-script-should-be-blocked.js
new file mode 100644
index 0000000..f32d2507
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/script-src/support/inline-script-should-be-blocked.js
@@ -0,0 +1,14 @@
+var t;
+async_test(t => {
+  self.t = t;
+  const s = document.createElement('script');
+  s.onerror = t.step_func(function() {
+    assert_unreached('Script error event should not be fired.');
+  });
+  s.onload = t.step_func(function() {
+    assert_unreached('Script load event should not be fired.');
+  });
+  s.innerText = 'self.t.assert_unreached("Script should not run.");'
+  document.body.appendChild(s);
+  setTimeout(() => t.done(), 2000);
+});
diff --git a/third_party/blink/web_tests/external/wpt/mixed-content/generic/tools/generate.py b/third_party/blink/web_tests/external/wpt/mixed-content/generic/tools/generate.py
index ed978bc..9c911aa6 100755
--- a/third_party/blink/web_tests/external/wpt/mixed-content/generic/tools/generate.py
+++ b/third_party/blink/web_tests/external/wpt/mixed-content/generic/tools/generate.py
@@ -48,25 +48,6 @@
         self.spec_directory = os.path.abspath(
             os.path.join(script_directory, '..', '..'))
 
-    def handleDelivery(self, selection, spec):
-        delivery_type = selection['delivery_type']
-        delivery_value = selection['delivery_value']
-
-        meta = ''
-        headers = []
-
-        if delivery_value is not None:
-            if delivery_type == 'meta':
-                meta = '<meta http-equiv="Content-Security-Policy" ' + \
-                       'content="block-all-mixed-content">'
-            elif delivery_type == 'http-rp':
-                headers.append(
-                    "Content-Security-Policy: block-all-mixed-content")
-            else:
-                raise ValueError("Invalid delivery_type %s" % delivery_type)
-
-        return {"meta": meta, "headers": headers}
-
 
 if __name__ == '__main__':
     generate.main(MixedContentConfig())
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/tools/generate.py b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/tools/generate.py
index 9837498..ba1e963 100755
--- a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/tools/generate.py
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/tools/generate.py
@@ -42,33 +42,6 @@
         self.spec_directory = os.path.abspath(
             os.path.join(script_directory, '..', '..'))
 
-    def handleDelivery(self, selection, spec):
-        delivery_type = selection['delivery_type']
-        delivery_value = selection['delivery_value']
-
-        meta = ''
-        headers = []
-        if delivery_value != None:
-            if delivery_type == 'meta':
-                meta = \
-                    '<meta name="referrer" content="%s">' % delivery_value
-            elif delivery_type == 'http-rp':
-                meta = \
-                    "<!-- No meta: Referrer policy delivered via HTTP headers. -->"
-                headers.append('Referrer-Policy: ' + '%s' % delivery_value)
-                # TODO(kristijanburnik): Limit to WPT origins.
-                headers.append('Access-Control-Allow-Origin: *')
-            elif delivery_type == 'attr':
-                # attr-referrer is supported by the JS test wrapper.
-                pass
-            elif delivery_type == 'rel-noref':
-                # rel=noreferrer is supported by the JS test wrapper.
-                pass
-            else:
-                raise ValueError('Not implemented delivery_type: ' \
-                                  + delivery_type)
-        return {"meta": meta, "headers": headers}
-
 
 if __name__ == '__main__':
     generate.main(ReferrerPolicyConfig())
diff --git a/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-computed.svg b/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-computed.svg
new file mode 100644
index 0000000..329dedb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-computed.svg
@@ -0,0 +1,27 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     width="800px" height="800px">
+  <title>SVG Painting: getComputedStyle().marker</title>
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/painting.html#MarkerProperty"/>
+    <h:meta name="assert" content="marker computed value is as specified, with url values absolute."/>
+  </metadata>
+  <g id="target"></g>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <h:script src="/css/support/computed-testcommon.js"/>
+  <script><![CDATA[
+
+test_computed_value("marker", "none");
+test_computed_value("marker", 'url("https://example.com/")');
+
+test(() => {
+  const target = document.getElementById('target');
+  target.style['marker'] = 'url("a.b#c")';
+  const result = getComputedStyle(target)['marker'];
+  const resolved = new URL("a.b#c", document.URL).href;
+  assert_equals(result, 'url("' + resolved + '")');
+}, 'url values are made absolute');
+
+  ]]></script>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-invalid.svg b/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-invalid.svg
new file mode 100644
index 0000000..64112cd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-invalid.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     width="800px" height="800px">
+  <title>SVG Painting: parsing marker with invalid values</title>
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/painting.html#MarkerProperty"/>
+    <h:meta name="assert" content="marker supports only the paint grammar 'none | marker-ref'."/>
+  </metadata>
+  <g id="target"></g>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <h:script src="/css/support/parsing-testcommon.js"/>
+  <script><![CDATA[
+
+test_invalid_value("marker", "auto");
+test_invalid_value("marker", 'none url("https://example.com/")');
+
+  ]]></script>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-valid.svg b/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-valid.svg
new file mode 100644
index 0000000..23d3f24
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/painting/parsing/marker-valid.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     width="800px" height="800px">
+  <title>SVG Painting: parsing marker with valid values</title>
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/painting.html#MarkerProperty"/>
+    <h:meta name="assert" content="marker supports the full paint grammar 'none | marker-ref'."/>
+  </metadata>
+  <g id="target"></g>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <h:script src="/css/support/parsing-testcommon.js"/>
+  <script><![CDATA[
+
+test_valid_value("marker", "none");
+
+test_valid_value("marker", 'url("https://example.com/")');
+
+  ]]></script>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/update_pr_preview.py b/third_party/blink/web_tests/external/wpt/tools/ci/update_pr_preview.py
new file mode 100644
index 0000000..9b104c6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/update_pr_preview.py
@@ -0,0 +1,255 @@
+# wpt-submissions.live is a public deployment of WPT, maintained in an external
+# repository. It automatically fetches and deploys all refs in the WPT
+# repository which match a certain pattern. This behavior is intended to be
+# used for pull requests so that reviewers can preview changes without running
+# the WPT server locally.
+#
+# This script facilitates the service by maintaining the git refs. It creates
+# and updates refs in response to GitHub events. It does this automatically for
+# pull requests from GitHub users who have "collaborator" access permissions to
+# the WPT repository. It also does this for any pull requests which bear the
+# `pull-request-has-preview` label. Collaborators can add or remove this label
+# to enable or disable the preview for submissions from non-collaborators.
+#
+# Although the script relies on a secret access token, it is *not* limited to
+# use for pull requests from trusted collaborators due to the way GitHub
+# Actions are executed:
+#
+# > # Pull request events for forked repositories
+# >
+# > [...]
+# >
+# > ## Pull request with base and head branches in different repositories
+# >
+# > The base repository receives a `pull_request` event where the SHA is the
+# > latest commit of base branch and ref is the base branch.
+#
+# https://developer.github.com/actions/managing-workflows/workflow-configuration-options/#pull-request-events-for-forked-repositories
+import json
+import logging
+import os
+import sys
+
+import requests
+
+active_label = 'pull-request-has-preview'
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class Status(object):
+    SUCCESS = 0
+    FAIL = 1
+    NEUTRAL = 78
+
+
+def request(url, method_name, data=None, json_data=None, ignore_body=False):
+    github_token = os.environ.get('GITHUB_TOKEN')
+
+    kwargs = {
+        'headers': {
+            'Authorization': 'token {}'.format(github_token),
+            'Accept': 'application/vnd.github.machine-man-preview+json'
+        }
+    }
+    method = getattr(requests, method_name)
+
+    logger.info('Issuing request: {} {}'.format(method_name.upper(), url))
+    if json_data is not None or data is not None:
+        kwargs['json'] = json_data
+        kwargs['data'] = data
+
+    resp = method(url, **kwargs)
+
+    resp.raise_for_status()
+
+    if not ignore_body:
+        return resp.json()
+
+
+def resource_exists(url):
+    try:
+        request(url, 'get', ignore_body=True)
+    except requests.HTTPError as exception:
+        if exception.response.status_code == 404:
+            return False
+        raise
+
+    return True
+
+
+class GitHub(object):
+    def __init__(self, api_root, owner, repo):
+        self.api_root = api_root
+        self.owner = owner
+        self.repo = repo
+
+    def is_collaborator(self, login):
+        return resource_exists(
+            '{}/repos/{}/{}/collaborators/{}'.format(
+                self.api_root, self.owner, self.repo, login
+            )
+        )
+
+    def ref_exists(self, ref):
+        return resource_exists(
+            '{}/repos/{}/{}/git/refs/{}'.format(
+                self.api_root, self.owner, self.repo, ref
+            )
+        )
+
+    def create_ref(self, ref, sha):
+        data = {
+            'ref': 'refs/{}'.format(ref),
+            'sha': sha
+        }
+        url = '{}/repos/{}/{}/git/refs'.format(
+            self.api_root, self.owner, self.repo
+        )
+
+        logger.info('Creating ref "{}" as {}'.format(ref, sha))
+
+        request(url, 'post', json_data=data)
+
+    def update_ref(self, ref, sha):
+        data = {
+            'force': True,
+            'sha': sha
+        }
+        url = '{}/repos/{}/{}/git/refs/{}'.format(
+            self.api_root, self.owner, self.repo, ref
+        )
+
+        logger.info('Updating ref "{}" as {}'.format(ref, sha))
+
+        request(url, 'patch', json_data=data)
+
+    def delete_ref(self, ref):
+        url = '{}/repos/{}/{}/git/refs/{}'.format(
+            self.api_root, self.owner, self.repo, ref
+        )
+
+        logger.info('Deleting ref "{}"'.format(ref))
+
+        try:
+            request(url, 'delete', ignore_body=True)
+        except requests.HTTPError as exception:
+            if exception.response.status_code != 404:
+                raise
+
+            logger.info(
+                'Attempted to delete non-existent ref: {}'.format(ref)
+            )
+
+    def set_ref(self, ref, sha):
+        if self.ref_exists(ref):
+            self.update_ref(ref, sha)
+        else:
+            self.create_ref(ref, sha)
+
+    def add_label(self, pr_number, label_name):
+        data = {
+            'labels': [label_name]
+        }
+        url = '{}/repos/{}/{}/issues/{}/labels'.format(
+            self.api_root, self.owner, self.repo, pr_number
+        )
+
+        logger.info('Adding label')
+
+        request(url, 'post', json_data=data)
+
+    def remove_label(self, pr_number, label_name):
+        url = '{}/repos/{}/{}/issues/{}/labels/{}'.format(
+            self.api_root, self.owner, self.repo, pr_number, label_name
+        )
+
+        logger.info('Removing label')
+
+        try:
+            request(url, 'delete')
+        except requests.HTTPError as exception:
+            if exception.response.status_code != 404:
+                raise
+
+            logger.info(
+                'Attempted to remove non-existent label: {}'.format(label_name)
+            )
+
+
+def main(api_root):
+    with open(os.environ['GITHUB_EVENT_PATH']) as handle:
+        event = json.load(handle)
+        logger.info(json.dumps(event, indent=2))
+
+    if 'pull_request' not in event:
+        logger.info('Unexpected event data')
+        return Status.FAIL
+
+    owner, repo = os.environ['GITHUB_REPOSITORY'].split('/', 1)
+    github = GitHub(api_root, owner, repo)
+    action = event['action']
+    pr_number = event['pull_request']['number']
+    ref_open = 'prs-open/gh-{}'.format(pr_number)
+    ref_labeled = 'prs-labeled-for-preview/gh-{}'.format(pr_number)
+    sha = event['pull_request']['head']['sha']
+    login = event['pull_request']['user']['login']
+    has_label = any([
+        label['name'] == active_label
+        for label in event['pull_request']['labels']
+    ])
+    target_label = event.get('label', {}).get('name')
+
+    if action == 'closed':
+        if has_label:
+            github.remove_label(pr_number, active_label)
+
+        # Removing a label from a GitHub Action will not trigger another
+        # Workflow, so the corresponding ref must be deleted while processing
+        # the "closed" action.
+        #
+        # > An action can't trigger other workflows. For example, a push,
+        # > deployment, or any task performed within an action with the
+        # > provided `GITHUB_TOKEN` will not trigger a workflow listening on
+        # > push, deploy, or any other supported action triggers.
+        #
+        # https://developer.github.com/actions/managing-workflows/workflow-configuration-options/
+        github.delete_ref(ref_open)
+
+        return Status.SUCCESS
+    elif action in ('opened', 'reopened') and has_label:
+        github.set_ref(ref_open, sha)
+        github.set_ref(ref_labeled, sha)
+    elif action in ('opened', 'reopened') and github.is_collaborator(login):
+        github.add_label(pr_number, active_label)
+
+        # Removing a label from a GitHub Action will not trigger another
+        # Workflow, so the corresponding ref must be created while processing
+        # the "opened" and "reopened" actions.
+        #
+        # > An action can't trigger other workflows. For example, a push,
+        # > deployment, or any task performed within an action with the
+        # > provided `GITHUB_TOKEN` will not trigger a workflow listening on
+        # > push, deploy, or any other supported action triggers.
+        #
+        # https://developer.github.com/actions/managing-workflows/workflow-configuration-options/
+        github.set_ref(ref_open, sha)
+        github.set_ref(ref_labeled, sha)
+    elif action == 'labeled' and target_label == active_label:
+        github.set_ref(ref_labeled, sha)
+    elif action == 'unlabeled' and target_label == active_label:
+        github.delete_ref(ref_labeled)
+    elif action == 'synchronize' and has_label:
+        github.set_ref(ref_open, sha)
+        github.set_ref(ref_labeled, sha)
+    else:
+        return Status.NEUTRAL
+
+    return Status.SUCCESS
+
+
+if __name__ == '__main__':
+    code = main(sys.argv[1])
+    assert isinstance(code, int)
+    sys.exit(code)
diff --git a/third_party/blink/web_tests/external/wpt/tools/tox.ini b/third_party/blink/web_tests/external/wpt/tools/tox.ini
index 63fb8ed9..52e749e 100644
--- a/third_party/blink/web_tests/external/wpt/tools/tox.ini
+++ b/third_party/blink/web_tests/external/wpt/tools/tox.ini
@@ -8,6 +8,8 @@
   pytest-cov
   mock
   hypothesis
+  # `requests` is required by `update_pr_preview.py`
+  requests
 
 commands = pytest {posargs}
 
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py
index 15e8e0b0..707a8519 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py
@@ -28,11 +28,15 @@
         # the subtest messages for this test.
         self.messages = defaultdict(str)
 
+        # List of tests that have failing subtests.
+        self.tests_with_subtest_fails = set()
+
     def _append_test_message(self, test, subtest, status, message):
         """
         Appends the message data for a test.
         :param str test: the name of the test
         :param str subtest: the name of the subtest with the message
+        :param str status: the subtest status
         :param str message: the string to append to the message for this test
         """
         if not message:
@@ -107,15 +111,25 @@
                                         else time.time())
 
     def test_status(self, data):
+        test_name = data["test"]
+        if data["status"] != "PASS" and test_name not in self.tests_with_subtest_fails:
+            self.tests_with_subtest_fails.add(test_name)
         if "message" in data:
-            self._append_test_message(data["test"], data["subtest"],
+            self._append_test_message(test_name, data["subtest"],
                                       data["status"], data["message"])
 
     def test_end(self, data):
-        actual_status = self._map_status_name(data["status"])
         expected_status = (self._map_status_name(data["expected"])
                            if "expected" in data else "PASS")
         test_name = data["test"]
+        actual_status = self._map_status_name(data["status"])
+        if actual_status == "PASS" and test_name in self.tests_with_subtest_fails:
+            # This test passed but it has failing subtests, so we flip the status
+            # to FAIL.
+            actual_status = "FAIL"
+            # Clean up the test list to avoid accumulating too many.
+            self.tests_with_subtest_fails.remove(test_name)
+
         if "message" in data:
             self._append_test_message(test_name, None, actual_status,
                                       data["message"])
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py
index b2c261f..87691cf 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py
@@ -162,3 +162,52 @@
 
     t2_log = output_json["tests"]["t2"]["artifacts"]["log"]
     assert t2_log == "[TIMEOUT] t2_message\n"
+
+
+def test_subtest_failure(capfd):
+    # Tests that a test fails if a subtest fails
+
+    # Set up the handler.
+    output = StringIO()
+    logger = structuredlog.StructuredLogger("test_a")
+    formatter = ChromiumFormatter()
+    logger.add_handler(handlers.StreamHandler(output, formatter))
+
+    # Run a test with some subtest failures.
+    logger.suite_start(["t1"], run_info={}, time=123)
+    logger.test_start("t1")
+    logger.test_status("t1", status="FAIL", subtest="t1_a",
+                       message="t1_a_message")
+    logger.test_status("t1", status="PASS", subtest="t1_b",
+                       message="t1_b_message")
+    logger.test_status("t1", status="TIMEOUT", subtest="t1_c",
+                       message="t1_c_message")
+
+    # Make sure the test name was added to the set of tests with subtest fails
+    assert "t1" in formatter.tests_with_subtest_fails
+
+    # The test status is reported as a pass here because the harness was able to
+    # run the test to completion.
+    logger.test_end("t1", status="PASS", expected="PASS")
+    logger.suite_end()
+
+    # check nothing got output to stdout/stderr
+    # (note that mozlog outputs exceptions during handling to stderr!)
+    captured = capfd.readouterr()
+    assert captured.out == ""
+    assert captured.err == ""
+
+    # check the actual output of the formatter
+    output.seek(0)
+    output_json = json.load(output)
+
+    test_obj = output_json["tests"]["t1"]
+    t1_log = test_obj["artifacts"]["log"]
+    assert t1_log == "[FAIL] t1_a: t1_a_message\n" \
+                     "[PASS] t1_b: t1_b_message\n" \
+                     "[TIMEOUT] t1_c: t1_c_message\n"
+    # The status of the test in the output is a failure because subtests failed,
+    # despite the harness reporting that the test passed.
+    assert test_obj["actual"] == "FAIL"
+    # Also ensure that the formatter cleaned up its internal state
+    assert "t1" not in formatter.tests_with_subtest_fails
diff --git a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-redirect-upgrade.https-expected.txt b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-redirect-upgrade.https-expected.txt
new file mode 100644
index 0000000..4942237
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-redirect-upgrade.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS secure/same-origin => secure/same-origin shared-worker
+FAIL insecure/same-origin => secure/same-origin shared-worker promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'SharedWorker': Script at 'http://web-platform.test:8444/upgrade-insecure-requests/support/redirect-cors.py?location=https%3A%2F%2Fweb-platform.test%3A8444%2Fcommon%2Fsecurity-features%2Fsubresource%2Fshared-worker.py' cannot be accessed from origin 'https://web-platform.test:8444'."
+PASS secure/same-origin => insecure/same-origin shared-worker
+FAIL insecure/same-origin => insecure/same-origin shared-worker promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'SharedWorker': Script at 'http://web-platform.test:8444/upgrade-insecure-requests/support/redirect-cors.py?location=http%3A%2F%2Fweb-platform.test%3A8444%2Fcommon%2Fsecurity-features%2Fsubresource%2Fshared-worker.py' cannot be accessed from origin 'https://web-platform.test:8444'."
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-redirect-upgrade.https.html b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-redirect-upgrade.https.html
new file mode 100644
index 0000000..5bfcb13
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-redirect-upgrade.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Generated by wpt/upgrade-insecure-requests/support/generate.py -->
+<title>Upgrade Insecure Requests: shared-worker.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./support/testharness-helper.sub.js"></script>
+<script src="/common/security-features/resources/common.js"></script>
+
+<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
+</head>
+<body>
+<script>
+const tests = generateRedirectTests(ResourceType.SHARED_WORKER, true);
+tests.forEach(test => testMap['shared-worker'](test));
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-upgrade.https-expected.txt b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-upgrade.https-expected.txt
new file mode 100644
index 0000000..453ab343
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-upgrade.https-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+PASS secure/same-origin shared-worker
+FAIL insecure/same-origin shared-worker promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'SharedWorker': Script at 'http://web-platform.test:8444/common/security-features/subresource/shared-worker.py' cannot be accessed from origin 'https://web-platform.test:8444'."
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-upgrade.https.html b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-upgrade.https.html
new file mode 100644
index 0000000..7be1042f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/shared-worker-upgrade.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Generated by wpt/upgrade-insecure-requests/support/generate.py -->
+<title>Upgrade Insecure Requests: shared-worker.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./support/testharness-helper.sub.js"></script>
+<script src="/common/security-features/resources/common.js"></script>
+
+<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
+</head>
+<body>
+<script>
+const tests = generateTests(ResourceType.SHARED_WORKER, true);
+tests.forEach(test => testMap['shared-worker'](test));
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/generate.py b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/generate.py
index 1b0c289..ee27203 100644
--- a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/generate.py
+++ b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/generate.py
@@ -40,11 +40,19 @@
   ('worker', 'WORKER'),
   ('module-worker', 'WORKER'),
   ('worker-subresource-xhr', 'FETCH'),
-  ('worker-subresource-fetch', 'FETCH')]:
-  sameOriginOnly = 'true' if resourceType == 'WORKER' else 'false'
+  ('worker-subresource-fetch', 'FETCH'),
+  ('shared-worker', 'SHARED_WORKER')]:
+  # TODO(https://crbug.com/989399): Add tests for subresource requests on shared
+  # workers, and main/subresource requests on service workers.
+
+  sameOriginOnly = 'false'
+  if resourceType == 'WORKER' or resourceType == 'SHARED_WORKER':
+    sameOriginOnly = 'true'
+
   types = [('', 'generateTests'), ('-redirect', 'generateRedirectTests')]
   if name == 'module-worker' or resourceType == 'WORKLET':
     types.append(('-import', 'generateModuleImportTests'))
+
   for typeName, generatorName in types:
     filename = '%s%s-upgrade.https.html' % (name, typeName)
     with open(filename, 'w') as html_file:
diff --git a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/testharness-helper.sub.js b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/testharness-helper.sub.js
index f578e898..da20d26 100644
--- a/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/testharness-helper.sub.js
+++ b/third_party/blink/web_tests/external/wpt/upgrade-insecure-requests/support/testharness-helper.sub.js
@@ -12,6 +12,7 @@
   IMAGE: "image",
   FRAME: "frame",
   WORKER: "worker",
+  SHARED_WORKER: "shared-worker",
   WORKLET: "worklet",
   WEBSOCKET: "websocket",
   FETCH: "fetch",
@@ -36,6 +37,8 @@
     url.pathname = "echo";
   } else if (resourceType == ResourceType.WORKER) {
     url.pathname += "worker.py";
+  } else if (resourceType == ResourceType.SHARED_WORKER) {
+    url.pathname += "shared-worker.py";
   } else if (resourceType == ResourceType.WORKLET) {
     url.pathname += "worker.py";
   } else if (resourceType == ResourceType.FETCH) {
@@ -184,6 +187,10 @@
                                         {}),
         test.name),
 
+  "shared-worker":
+    test => promise_test(
+        () => requestViaSharedWorker(test.url), test.name),
+
   "audio-worklet":
     test => promise_test(
         () => requestViaWorklet('audio', test.url), test.name),
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/resources/fail-to-inject-script.js b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/resources/fail-to-inject-script.js
deleted file mode 100644
index 1f896d1..0000000
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/resources/fail-to-inject-script.js
+++ /dev/null
@@ -1,9 +0,0 @@
-var s = document.createElement('script');
-s.onerror = function() {
-    done();
-};
-s.onload = function() {
-    assert_unreached('Script loaded.');
-};
-document.body.appendChild(s);
-s.innerText = 'assert_unreached("Script should not run.");'
diff --git a/third_party/blink/web_tests/svg/css/marker-shorthand.html b/third_party/blink/web_tests/svg/css/marker-shorthand.html
new file mode 100644
index 0000000..cc2928a
--- /dev/null
+++ b/third_party/blink/web_tests/svg/css/marker-shorthand.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Marker shorthand can be empty string</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<body>
+  <svg id="container">
+    <g id="target"></g>
+  </svg>
+<script>
+'use strict';
+const target = document.getElementById('target');
+const resource = 'url("https://example.com/")';
+const resource2 = 'url("https://example.com/2")';
+test(() => {
+  target.style.marker = resource;
+  assert_equals(target.style.markerStart, resource);
+  assert_equals(target.style.markerMid, resource);
+  assert_equals(target.style.markerEnd, resource);
+  assert_equals(target.style.marker, resource);
+  assert_equals(getComputedStyle(target).marker, resource);
+
+  target.style.markerStart = resource2;
+  assert_equals(target.style.marker, '');
+  assert_equals(getComputedStyle(target).marker, '');
+  target.style.markerMid = 'none';
+  assert_equals(target.style.marker, '');
+  assert_equals(getComputedStyle(target).marker, '');
+  target.style.markerEnd = 'none';
+  assert_equals(target.style.marker, '');
+  assert_equals(getComputedStyle(target).marker, '');
+
+  target.style.markerStart = 'none';
+  assert_equals(target.style.marker, 'none');
+  assert_equals(getComputedStyle(target).marker, 'none');
+}, 'marker is empty string when marker longhands do not match');
+</script>
+</body>
diff --git a/third_party/openh264/OWNERS b/third_party/openh264/OWNERS
index f9316a7af..8ae0710 100644
--- a/third_party/openh264/OWNERS
+++ b/third_party/openh264/OWNERS
@@ -1,2 +1,4 @@
 sprang@chromium.org
 ssilkin@chromium.org
+# COMPONENT: Blink>WebRTC>Video
+# TEAM: webrtc-reviews@webrtc.org
diff --git a/third_party/tcmalloc/gperftools-2.0/chromium/src/malloc_extension.cc b/third_party/tcmalloc/gperftools-2.0/chromium/src/malloc_extension.cc
index c143f13..5550220 100644
--- a/third_party/tcmalloc/gperftools-2.0/chromium/src/malloc_extension.cc
+++ b/third_party/tcmalloc/gperftools-2.0/chromium/src/malloc_extension.cc
@@ -42,6 +42,7 @@
 #include <sys/types.h>
 #endif
 #include <string>
+#include "base/allocator/buildflags.h"
 #include "base/dynamic_annotations.h"
 #include "base/sysinfo.h"    // for FillProcSelfMaps
 #ifndef NO_HEAP_CHECK
@@ -51,7 +52,7 @@
 #include "gperftools/malloc_extension_c.h"
 #include "maybe_threads.h"
 
-#ifdef USE_TCMALLOC
+#if BUILDFLAG(USE_TCMALLOC)
 // Note that malloc_extension can be used without tcmalloc if gperftools'
 // heap-profiler is enabled without the tcmalloc memory allocator.
 #include "thread_cache.h"
@@ -228,7 +229,7 @@
 
 unsigned int MallocExtension::GetBytesAllocatedOnCurrentThread() {
   // This function is added in Chromium for profiling.
-#ifdef USE_TCMALLOC
+#if BUILDFLAG(USE_TCMALLOC)
   // Note that malloc_extension can be used without tcmalloc if gperftools'
   // heap-profiler is enabled without the tcmalloc memory allocator.
   return tcmalloc::ThreadCache::GetBytesAllocatedOnCurrentThread();
diff --git a/tools/binary_size/generate_milestone_reports.py b/tools/binary_size/generate_milestone_reports.py
index f69a6c5..291c64f 100755
--- a/tools/binary_size/generate_milestone_reports.py
+++ b/tools/binary_size/generate_milestone_reports.py
@@ -67,6 +67,7 @@
     '74.0.3729.112',
     '75.0.3770.143',
     '76.0.3809.62',
+    '77.0.3861.0',  # Canary
 ]
 
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 6923d61..2e6cfe2 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -9787,6 +9787,7 @@
   <int value="54" label="Screen wake lock"/>
   <int value="55" label="System wake lock"/>
   <int value="56" label="Legacy cookie access"/>
+  <int value="57" label="Native file system write guard"/>
 </enum>
 
 <enum name="ContentTypeParseableResult">
@@ -62609,6 +62610,34 @@
   <int value="77" label="stopLoading"/>
 </enum>
 
+<enum name="WebViewAppProvidedMimeType">
+  <int value="0"
+      label="Null MIME type for content supplied by an app ContentProvider."/>
+  <int value="1"
+      label="Nonnull MIME type for content supplied by an app
+             ContentProvider."/>
+  <int value="2"
+      label="Could not guess MIME type for Android asset from file extension."/>
+  <int value="3"
+      label="Successfully guessed MIME type for Android asset from file
+             extension."/>
+  <int value="4" label="Could not guess MIME type due to generic Exception."/>
+  <int value="5"
+      label="Could not guess MIME type for Android asset from InputStream
+             content. This is not ideal, but we have a fallback to try to
+             guess by sniffing the InputStream."/>
+  <int value="6"
+      label="Successfully guessed MIME type for Android asset from
+             InputStream content. Although this means we've guessed a MIME
+             type, this is not ideal because we would prefer to guess from
+             the file extension only."/>
+  <int value="7" label="Could not guess MIME type due to IOException."/>
+  <int value="8"
+      label="Null MIME type from shouldInterceptRequest return value."/>
+  <int value="9"
+      label="Nonnull MIME type from shouldInterceptRequest return value."/>
+</enum>
+
 <enum name="WebViewCallbackType">
   <int value="0" label="onReceivedLoginRequest"/>
   <int value="1" label="onReceivedClientCertRequest"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index aec23023..ca2cf27 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -3723,6 +3723,21 @@
   </summary>
 </histogram>
 
+<histogram name="Android.WebView.Mimetype.AppProvided"
+    enum="WebViewAppProvidedMimeType" expires_after="2020-05-14">
+  <owner>ntfschr@chromium.org</owner>
+  <owner>timvolodine@chromium.org</owner>
+  <summary>
+    Records information about the specified MIME types for app-provided content
+    loaded in Android WebView. At the moment, this focuses on
+    shouldInterceptRequest, ContentProvider-supplied content, and app
+    assets/resources (where we expect the app to use proper file extensions to
+    indicate MIME type), and is recorded as part of the process of loading each
+    resource. This may apply to any type of resource (frame HTML, subresource,
+    etc.).
+  </summary>
+</histogram>
+
 <histogram name="Android.WebView.onReceivedError.ErrorCode"
     enum="WebViewClientErrorCode" expires_after="2020-04-11">
   <owner>timvolodine@chromium.org</owner>
@@ -9586,7 +9601,11 @@
 </histogram>
 
 <histogram name="Autofill.CreditCardUploadDisallowedForNetwork"
-    enum="CreditCardUploadDisallowedNetwork">
+    enum="CreditCardUploadDisallowedNetwork" expires_after="M75">
+  <obsolete>
+    Was replaced by the AutofillDoNotUploadSaveUnsupportedCards feature in M75.
+    Removed 2019/07.
+  </obsolete>
   <owner>jsaul@google.com</owner>
   <summary>
     When a credit card is not allowed to be offered upload save due to its
@@ -11712,6 +11731,22 @@
   </summary>
 </histogram>
 
+<histogram name="AutoScreenBrightness.DailyUserAdjustment.Nocturne"
+    units="count" expires_after="2020-03-31">
+  <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <summary>
+    Number of times that a user has made brightness adjustments on a Nocturne
+    device with an ambient sensor that is supported by the auto screen
+    brightness model. Reported daily. The count is accumulated through the day,
+    spanning reboots, and sent once the system clock indicates that a full day
+    or more has passed since the last report. If the system is suspended or off
+    for more than a day, the current count will be reported immediately the next
+    time the system boots, but the skipped days will not be reported. Chrome OS
+    only.
+  </summary>
+</histogram>
+
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.SupportedAls"
     units="count" expires_after="2019-12-31">
   <owner>jiameng@chromium.org</owner>
@@ -78328,6 +78363,9 @@
 
 <histogram name="Net.SSLSecureRenegotiation" enum="BooleanSecure"
     expires_after="M77">
+  <obsolete>
+    Removed in August 2019
+  </obsolete>
   <owner>davidben@chromium.org</owner>
   <summary>
     For each attempted SSL renegotiation (non-initial handshake), whether the
@@ -116612,6 +116650,10 @@
 
 <histogram name="SBClientDownload.DmgFileArchivedBinariesCount" units="count"
     expires_after="M77">
+  <obsolete>
+    Removed in M77, since the metric was not being actively used and there were
+    no near-term plans to begin using it.
+  </obsolete>
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -116623,6 +116665,10 @@
 
 <histogram name="SBClientDownload.DmgFileFailureByType"
     enum="SBClientDownloadExtensions" expires_after="M77">
+  <obsolete>
+    Removed in M77, since the metric was not being actively used and there were
+    no near-term plans to begin using it.
+  </obsolete>
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -116645,6 +116691,10 @@
 
 <histogram name="SBClientDownload.DmgFileHasExecutableByType"
     enum="SBClientDownloadExtensions" expires_after="M77">
+  <obsolete>
+    Removed in M77, since the metric was not being actively used and there were
+    no near-term plans to begin using it.
+  </obsolete>
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -116655,6 +116705,10 @@
 
 <histogram name="SBClientDownload.DmgFileHasNoExecutableByType"
     enum="SBClientDownloadExtensions" expires_after="M77">
+  <obsolete>
+    Removed in M77, since the metric was not being actively used and there were
+    no near-term plans to begin using it.
+  </obsolete>
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -116688,6 +116742,10 @@
 
 <histogram name="SBClientDownload.DmgTooBigToUnpack" enum="Boolean"
     expires_after="M77">
+  <obsolete>
+    Removed in M77, since the metric was not being actively used and there were
+    no near-term plans to begin using it.
+  </obsolete>
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -116733,6 +116791,10 @@
 <histogram
     name="SBClientDownload.DownloadFileWithoutDiskImageExtensionHasKolySignature"
     enum="Boolean" expires_after="M78">
+  <obsolete>
+    Removed in M77, since the metric was not being actively used and there were
+    no near-term plans to begin using it.
+  </obsolete>
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -116809,8 +116871,8 @@
 </histogram>
 
 <histogram name="SBClientDownload.DownloadRequestTimeoutDuration" units="ms"
-    expires_after="M77">
-  <owner>vakh@chromium.org</owner>
+    expires_after="M80">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <owner>mattm@chromium.org</owner>
   <summary>
@@ -116822,8 +116884,8 @@
 </histogram>
 
 <histogram name="SBClientDownload.DownloadRequestTimeoutStats"
-    enum="SBClientDownloadCheckDownloadStats" expires_after="M77">
-  <owner>vakh@chromium.org</owner>
+    enum="SBClientDownloadCheckDownloadStats" expires_after="M80">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <owner>mattm@chromium.org</owner>
   <summary>
@@ -116897,8 +116959,8 @@
 </histogram>
 
 <histogram name="SBClientDownload.ExtractSignatureFeaturesTime" units="ms"
-    expires_after="M77">
-  <owner>vakh@chromium.org</owner>
+    expires_after="M80">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <owner>mattm@chromium.org</owner>
   <summary>
@@ -117041,8 +117103,8 @@
 </histogram>
 
 <histogram name="SBClientDownload.SignedBinaryDownload"
-    enum="SBClientDownloadIsSignedBinary" expires_after="M77">
-  <owner>vakh@chromium.org</owner>
+    enum="SBClientDownloadIsSignedBinary" expires_after="M80">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <owner>mattm@chromium.org</owner>
   <summary>
@@ -117202,6 +117264,10 @@
 
 <histogram name="SBClientDownload.ZipTooBigToUnpack" enum="Boolean"
     expires_after="M78">
+  <obsolete>
+    Removed in M77, since the metric was not being actively used and there were
+    no near-term plans to begin using it.
+  </obsolete>
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index 19a39fa9..84e2fdb 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -1380,6 +1380,8 @@
       return "imageDataUrl";
     case ax::mojom::StringAttribute::kInnerHtml:
       return "innerHtml";
+    case ax::mojom::StringAttribute::kInputType:
+      return "inputType";
     case ax::mojom::StringAttribute::kKeyShortcuts:
       return "keyShortcuts";
     case ax::mojom::StringAttribute::kLanguage:
@@ -1438,6 +1440,8 @@
     return ax::mojom::StringAttribute::kImageDataUrl;
   if (0 == strcmp(string_attribute, "innerHtml"))
     return ax::mojom::StringAttribute::kInnerHtml;
+  if (0 == strcmp(string_attribute, "inputType"))
+    return ax::mojom::StringAttribute::kInputType;
   if (0 == strcmp(string_attribute, "keyShortcuts"))
     return ax::mojom::StringAttribute::kKeyShortcuts;
   if (0 == strcmp(string_attribute, "language"))
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index af772c2..05a962d 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -488,6 +488,7 @@
   kImageAnnotation,
   kImageDataUrl,
   kInnerHtml,
+  kInputType,
   kKeyShortcuts,
   // Only present when different from parent.
   kLanguage,
diff --git a/ui/accessibility/ax_node_data.cc b/ui/accessibility/ax_node_data.cc
index bccc6a5..7775c4a 100644
--- a/ui/accessibility/ax_node_data.cc
+++ b/ui/accessibility/ax_node_data.cc
@@ -1305,6 +1305,9 @@
       case ax::mojom::StringAttribute::kInnerHtml:
         result += " inner_html=" + value;
         break;
+      case ax::mojom::StringAttribute::kInputType:
+        result += " input_type=" + value;
+        break;
       case ax::mojom::StringAttribute::kKeyShortcuts:
         result += " key_shortcuts=" + value;
         break;
diff --git a/ui/accessibility/ax_node_position.cc b/ui/accessibility/ax_node_position.cc
index 671ac56..3bb7eb1c 100644
--- a/ui/accessibility/ax_node_position.cc
+++ b/ui/accessibility/ax_node_position.cc
@@ -43,6 +43,30 @@
   return text;
 }
 
+int AXNodePosition::MaxTextOffset() const {
+  if (IsNullPosition())
+    return INVALID_INDEX;
+
+  const AXNode* anchor = GetAnchor();
+  DCHECK(anchor);
+  const std::string& value =
+      anchor->data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
+  if (!value.empty())
+    return value.length();
+
+  if (anchor->IsText()) {
+    return anchor->data()
+        .GetStringAttribute(ax::mojom::StringAttribute::kName)
+        .length();
+  }
+
+  int max_text_offset = 0;
+  for (int i = 0; i < AnchorChildCount(); ++i)
+    max_text_offset += CreateChildPositionAt(i)->MaxTextOffset();
+
+  return max_text_offset;
+}
+
 void AXNodePosition::AnchorChild(int child_index,
                                  AXTreeID* tree_id,
                                  int32_t* child_id) const {
diff --git a/ui/accessibility/ax_node_position.h b/ui/accessibility/ax_node_position.h
index 83ee8138..dc20fc2 100644
--- a/ui/accessibility/ax_node_position.h
+++ b/ui/accessibility/ax_node_position.h
@@ -27,6 +27,7 @@
 
   AXPositionInstance Clone() const override;
 
+  int MaxTextOffset() const override;
   bool IsInLineBreak() const override;
   bool IsInTextObject() const override;
   bool IsInWhiteSpace() const override;
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index 9a454066..82053f9 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -50,6 +50,38 @@
   void SetUp() override;
   void TearDown() override;
 
+  void AssertTextLengthEquals(const AXTree* tree,
+                              int32_t node_id,
+                              int expected_text_length) {
+    TestPositionType text_position = AXNodePosition::CreateTextPosition(
+        tree->data().tree_id, node_id, 0 /* text_offset */,
+        ax::mojom::TextAffinity::kUpstream);
+    ASSERT_NE(nullptr, text_position);
+    ASSERT_TRUE(text_position->IsTextPosition());
+    ASSERT_EQ(expected_text_length, text_position->MaxTextOffset());
+    ASSERT_EQ(expected_text_length,
+              static_cast<int>(text_position->GetText().length()));
+  }
+
+  // Creates a new AXTree from a vector of nodes.
+  // Assumes the first node in the vector is the root.
+  std::unique_ptr<AXTree> CreateAXTree(
+      const std::vector<ui::AXNodeData>& nodes) {
+    ui::AXTreeUpdate update;
+    ui::AXTreeData tree_data;
+    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+    update.tree_data = tree_data;
+    update.has_tree_data = true;
+    update.root_id = nodes[0].id;
+    update.nodes = nodes;
+
+    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+    update.tree_data = tree_data;
+    std::unique_ptr<AXTree> tree = std::make_unique<AXTree>(update);
+    AXNodePosition::SetTreeForTesting(tree.get());
+    return tree;
+  }
+
   AXNodeData root_;
   AXNodeData button_;
   AXNodeData check_box_;
@@ -416,6 +448,49 @@
   ASSERT_EQ(1, text_position->MaxTextOffset());
 }
 
+TEST_F(AXPositionTest, GetMaxTextOffsetUpdate) {
+  AXNodePosition::SetTreeForTesting(nullptr);
+
+  ui::AXNodeData root_data;
+  root_data.id = 1;
+  root_data.role = ax::mojom::Role::kRootWebArea;
+
+  ui::AXNodeData text_data;
+  text_data.id = 2;
+  text_data.role = ax::mojom::Role::kStaticText;
+  text_data.SetName("some text");
+
+  ui::AXNodeData more_text_data;
+  more_text_data.id = 3;
+  more_text_data.role = ax::mojom::Role::kStaticText;
+  more_text_data.SetName("more text");
+
+  root_data.child_ids = {2, 3};
+
+  std::unique_ptr<AXTree> new_tree =
+      CreateAXTree({root_data, text_data, more_text_data});
+
+  AssertTextLengthEquals(new_tree.get(), text_data.id, 9);
+  AssertTextLengthEquals(new_tree.get(), root_data.id, 18);
+
+  text_data.SetName("Adjusted line 1");
+  new_tree = CreateAXTree({root_data, text_data, more_text_data});
+  AssertTextLengthEquals(new_tree.get(), text_data.id, 15);
+  AssertTextLengthEquals(new_tree.get(), root_data.id, 24);
+
+  // Value should override name
+  text_data.SetValue("Value should override name");
+  new_tree = CreateAXTree({root_data, text_data, more_text_data});
+  AssertTextLengthEquals(new_tree.get(), text_data.id, 26);
+  AssertTextLengthEquals(new_tree.get(), root_data.id, 35);
+
+  // An empty value should fall back to name
+  text_data.SetValue("");
+  new_tree = CreateAXTree({root_data, text_data, more_text_data});
+  AssertTextLengthEquals(new_tree.get(), text_data.id, 15);
+  AssertTextLengthEquals(new_tree.get(), root_data.id, 24);
+}
+
 TEST_F(AXPositionTest, AtStartOfAnchorWithNullPosition) {
   TestPositionType null_position = AXNodePosition::CreateNullPosition();
   ASSERT_NE(nullptr, null_position);
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index fdd872c..3c2aeee 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -1777,7 +1777,7 @@
 
   // Returns the length of the text that is present inside the anchor node,
   // including any text found in descendant text nodes.
-  int MaxTextOffset() const {
+  virtual int MaxTextOffset() const {
     if (IsNullPosition())
       return INVALID_INDEX;
     return static_cast<int>(GetText().length());
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index ae6c3d7..79abe65 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -558,6 +558,16 @@
     case ax::mojom::Role::kSearchBox:
       return base::ASCIIToUTF16("search box");
 
+    case ax::mojom::Role::kTextField: {
+      std::string input_type;
+      if (data.GetStringAttribute(ax::mojom::StringAttribute::kInputType,
+                                  &input_type) &&
+          input_type == "tel") {
+        return base::ASCIIToUTF16("telephone");
+      }
+      return {};
+    }
+
     default:
       return {};
   }
diff --git a/ui/base/material_design/material_design_controller.cc b/ui/base/material_design/material_design_controller.cc
index 97e42d5..261c0c5 100644
--- a/ui/base/material_design/material_design_controller.cc
+++ b/ui/base/material_design/material_design_controller.cc
@@ -66,7 +66,7 @@
   if (!touch && (switch_value != switches::kTopChromeTouchUiDisabled) &&
       features::IsAutomaticUiAdjustmentsForTouchEnabled()) {
 #if defined(OS_CHROMEOS)
-    // TabletModeClient's default state is in non-tablet mode.
+    // TabletModePageBehavior's default state is in non-tablet mode.
     automatic_touch_ui_ = true;
 #elif defined(OS_WIN)
     if (base::win::GetVersion() >= base::win::Version::WIN10) {
diff --git a/ui/base/material_design/material_design_controller.h b/ui/base/material_design/material_design_controller.h
index 1e27b70..2ec2d5f 100644
--- a/ui/base/material_design/material_design_controller.h
+++ b/ui/base/material_design/material_design_controller.h
@@ -35,7 +35,7 @@
 
   static bool touch_ui() { return touch_ui_; }
 
-  // Exposed for TabletModeClient on ChromeOS + ash.
+  // Exposed for TabletModePageBehavior on ChromeOS + ash.
   static void OnTabletModeToggled(bool enabled);
 
   static MaterialDesignController* GetInstance();
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index 3a894775..486a0b1 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -774,13 +774,7 @@
   EXPECT_TRUE(d3.painted());
 }
 
-// crbug.com/989936
-#if defined(OS_LINUX) && defined(UNDEFINED_SANITIZER)
-#define MAYBE_Cloning DISABLED_Cloning
-#else
-#define MAYBE_Cloning Cloning
-#endif
-TEST_F(LayerWithDelegateTest, MAYBE_Cloning) {
+TEST_F(LayerWithDelegateTest, Cloning) {
   std::unique_ptr<Layer> layer = CreateLayer(LAYER_SOLID_COLOR);
 
   gfx::Transform transform;
@@ -1045,14 +1039,8 @@
   ASSERT_TRUE(roundtrip);
   EXPECT_EQ(name, *roundtrip);
 }
-// crbug.com/989936
-#if defined(OS_LINUX) && defined(UNDEFINED_SANITIZER)
-#define MAYBE_SwitchLayerPreservesCCLayerState \
-  DISABLED_SwitchLayerPreservesCCLayerState
-#else
-#define MAYBE_SwitchLayerPreservesCCLayerState SwitchLayerPreservesCCLayerState
-#endif
-TEST_F(LayerWithNullDelegateTest, MAYBE_SwitchLayerPreservesCCLayerState) {
+
+TEST_F(LayerWithNullDelegateTest, SwitchLayerPreservesCCLayerState) {
   std::unique_ptr<Layer> l1 = CreateLayer(LAYER_SOLID_COLOR);
   l1->SetFillsBoundsOpaquely(true);
   l1->SetVisible(false);
diff --git a/ui/message_center/BUILD.gn b/ui/message_center/BUILD.gn
index eb0016c..ce4ea2bf 100644
--- a/ui/message_center/BUILD.gn
+++ b/ui/message_center/BUILD.gn
@@ -129,6 +129,9 @@
         "//ui/events",
         "//ui/views",
       ]
+      if (is_mac) {
+        libs = [ "Foundation.framework" ]
+      }
     }
   } else {
     # Notification service disabled.
diff --git a/ui/ozone/demo/ozone_demo.cc b/ui/ozone/demo/ozone_demo.cc
index 07e8344..005b89b 100644
--- a/ui/ozone/demo/ozone_demo.cc
+++ b/ui/ozone/demo/ozone_demo.cc
@@ -76,9 +76,6 @@
 
   ui::OzonePlatform::InitParams params;
   params.single_process = true;
-  params.using_mojo = ui::OzonePlatform::EnsureInstance()
-                          ->GetPlatformProperties()
-                          .requires_mojo;
   ui::OzonePlatform::InitializeForUI(params);
   ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine()
       ->SetCurrentLayoutByName("us");
@@ -87,7 +84,9 @@
   ui::OzonePlatform::GetInstance()->AfterSandboxEntry();
 
   std::unique_ptr<ui::OzoneGpuTestHelper> gpu_helper;
-  if (!params.using_mojo) {
+  if (!ui::OzonePlatform::GetInstance()
+           ->GetPlatformProperties()
+           .requires_mojo) {
     // OzoneGpuTestHelper transports Chrome IPC messages between host & gpu code
     // in single process mode. We don't use both Chrome IPC and mojo, so only
     // initialize it for non-mojo platforms.
diff --git a/ui/ozone/demo/skia/skia_demo.cc b/ui/ozone/demo/skia/skia_demo.cc
index bc523f7..4610ad4f8 100644
--- a/ui/ozone/demo/skia/skia_demo.cc
+++ b/ui/ozone/demo/skia/skia_demo.cc
@@ -51,9 +51,6 @@
 
   ui::OzonePlatform::InitParams params;
   params.single_process = true;
-  params.using_mojo = ui::OzonePlatform::EnsureInstance()
-                          ->GetPlatformProperties()
-                          .requires_mojo;
   ui::OzonePlatform::InitializeForUI(params);
   ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine()
       ->SetCurrentLayoutByName("us");
@@ -62,7 +59,9 @@
   ui::OzonePlatform::GetInstance()->AfterSandboxEntry();
 
   std::unique_ptr<ui::OzoneGpuTestHelper> gpu_helper;
-  if (!params.using_mojo) {
+  if (!ui::OzonePlatform::GetInstance()
+           ->GetPlatformProperties()
+           .requires_mojo) {
     // OzoneGpuTestHelper transports Chrome IPC messages between host & gpu code
     // in single process mode. We don't use both Chrome IPC and mojo, so only
     // initialize it for non-mojo platforms.
diff --git a/ui/ozone/platform/cast/ozone_platform_cast.cc b/ui/ozone/platform/cast/ozone_platform_cast.cc
index fc04766..3240ff37 100644
--- a/ui/ozone/platform/cast/ozone_platform_cast.cc
+++ b/ui/ozone/platform/cast/ozone_platform_cast.cc
@@ -69,7 +69,7 @@
               "allow-dummy-software-rendering");
       if (allow_dummy_software_rendering) {
         LOG(INFO) << "Using dummy SurfaceFactoryCast";
-        surface_factory_.reset(new SurfaceFactoryCast());
+        surface_factory_ = std::make_unique<SurfaceFactoryCast>();
         return surface_factory_.get();
       }
 
@@ -96,8 +96,7 @@
   std::unique_ptr<PlatformWindow> CreatePlatformWindow(
       PlatformWindowDelegate* delegate,
       PlatformWindowInitProperties properties) override {
-    return base::WrapUnique<PlatformWindow>(
-        new PlatformWindowCast(delegate, properties.bounds));
+    return std::make_unique<PlatformWindowCast>(delegate, properties.bounds);
   }
   std::unique_ptr<display::NativeDisplayDelegate> CreateNativeDisplayDelegate()
       override {
@@ -117,8 +116,8 @@
 
   void InitializeUI(const InitParams& params) override {
     device_manager_ = CreateDeviceManager();
-    overlay_manager_.reset(new OverlayManagerCast());
-    cursor_factory_.reset(new CursorFactoryOzone());
+    overlay_manager_ = std::make_unique<OverlayManagerCast>();
+    cursor_factory_ = std::make_unique<CursorFactoryOzone>();
     gpu_platform_support_host_.reset(CreateStubGpuPlatformSupportHost());
 
     // Enable dummy software rendering support if GPU process disabled
@@ -134,15 +133,16 @@
         std::make_unique<StubKeyboardLayoutEngine>());
     ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine()
         ->SetCurrentLayoutByName("us");
-    event_factory_ozone_.reset(new EventFactoryEvdev(
+    event_factory_ozone_ = std::make_unique<EventFactoryEvdev>(
         nullptr, device_manager_.get(),
-        KeyboardLayoutEngineManager::GetKeyboardLayoutEngine()));
+        KeyboardLayoutEngineManager::GetKeyboardLayoutEngine());
 
     if (enable_dummy_software_rendering)
-      surface_factory_.reset(new SurfaceFactoryCast());
+      surface_factory_ = std::make_unique<SurfaceFactoryCast>();
   }
   void InitializeGPU(const InitParams& params) override {
-    surface_factory_.reset(new SurfaceFactoryCast(std::move(egl_platform_)));
+    surface_factory_ =
+        std::make_unique<SurfaceFactoryCast>(std::move(egl_platform_));
   }
 
  private:
diff --git a/ui/ozone/platform/drm/ozone_platform_gbm.cc b/ui/ozone/platform/drm/ozone_platform_gbm.cc
index c4b89a8..8da64fb 100644
--- a/ui/ozone/platform/drm/ozone_platform_gbm.cc
+++ b/ui/ozone/platform/drm/ozone_platform_gbm.cc
@@ -190,8 +190,8 @@
     host_thread_ = base::PlatformThread::CurrentRef();
 
     device_manager_ = CreateDeviceManager();
-    window_manager_.reset(new DrmWindowHostManager());
-    cursor_.reset(new DrmCursor(window_manager_.get()));
+    window_manager_ = std::make_unique<DrmWindowHostManager>();
+    cursor_ = std::make_unique<DrmCursor>(window_manager_.get());
 
 #if BUILDFLAG(USE_XKBCOMMON)
     KeyboardLayoutEngineManager::SetKeyboardLayoutEngine(
@@ -201,9 +201,9 @@
         std::make_unique<StubKeyboardLayoutEngine>());
 #endif
 
-    event_factory_ozone_.reset(new EventFactoryEvdev(
+    event_factory_ozone_ = std::make_unique<EventFactoryEvdev>(
         cursor_.get(), device_manager_.get(),
-        KeyboardLayoutEngineManager::GetKeyboardLayoutEngine()));
+        KeyboardLayoutEngineManager::GetKeyboardLayoutEngine());
 
     GpuThreadAdapter* adapter;
 
@@ -213,8 +213,8 @@
           std::make_unique<DrmDeviceConnector>(host_drm_device_);
       adapter = host_drm_device_.get();
     } else {
-      gpu_platform_support_host_.reset(
-          new DrmGpuPlatformSupportHost(cursor_.get()));
+      gpu_platform_support_host_ =
+          std::make_unique<DrmGpuPlatformSupportHost>(cursor_.get());
       adapter = gpu_platform_support_host_.get();
     }
 
@@ -227,7 +227,7 @@
     display_manager_ = std::make_unique<DrmDisplayHostManager>(
         adapter, device_manager_.get(), &host_properties_,
         overlay_manager_host.get(), event_factory_ozone_->input_controller());
-    cursor_factory_ozone_.reset(new BitmapCursorFactoryOzone);
+    cursor_factory_ozone_ = std::make_unique<BitmapCursorFactoryOzone>();
 
     if (using_mojo_) {
       host_drm_device_->ProvideManagers(display_manager_.get(),
@@ -251,9 +251,10 @@
 
     // NOTE: Can't start the thread here since this is called before sandbox
     // initialization in multi-process Chrome.
-    drm_thread_proxy_.reset(new DrmThreadProxy());
+    drm_thread_proxy_ = std::make_unique<DrmThreadProxy>();
 
-    surface_factory_.reset(new GbmSurfaceFactory(drm_thread_proxy_.get()));
+    surface_factory_ =
+        std::make_unique<GbmSurfaceFactory>(drm_thread_proxy_.get());
     if (!using_mojo_) {
       drm_thread_proxy_->BindThreadIntoMessagingProxy(itmp);
     }
diff --git a/ui/ozone/platform/scenic/ozone_platform_scenic.cc b/ui/ozone/platform/scenic/ozone_platform_scenic.cc
index dc92285..d9157ad5 100644
--- a/ui/ozone/platform/scenic/ozone_platform_scenic.cc
+++ b/ui/ozone/platform/scenic/ozone_platform_scenic.cc
@@ -61,7 +61,7 @@
     : public OzonePlatform,
       public base::MessageLoopCurrent::DestructionObserver {
  public:
-  OzonePlatformScenic() {}
+  OzonePlatformScenic() = default;
   ~OzonePlatformScenic() override = default;
 
   // OzonePlatform implementation.
diff --git a/ui/ozone/platform/wayland/ozone_platform_wayland.cc b/ui/ozone/platform/wayland/ozone_platform_wayland.cc
index 83d8ab0..3ac2572 100644
--- a/ui/ozone/platform/wayland/ozone_platform_wayland.cc
+++ b/ui/ozone/platform/wayland/ozone_platform_wayland.cc
@@ -132,9 +132,10 @@
     // it is set at this point if none exists
     if (!LinuxInputMethodContextFactory::instance() &&
         !input_method_context_factory_) {
-      auto* factory = new WaylandInputMethodContextFactory(connection_.get());
-      input_method_context_factory_.reset(factory);
-      LinuxInputMethodContextFactory::SetInstance(factory);
+      input_method_context_factory_ =
+          std::make_unique<WaylandInputMethodContextFactory>(connection_.get());
+      LinuxInputMethodContextFactory::SetInstance(
+          input_method_context_factory_.get());
     }
 
     return std::make_unique<InputMethodAuraLinux>(delegate);
diff --git a/ui/ozone/public/ozone_platform.cc b/ui/ozone/public/ozone_platform.cc
index 506694a..d45de899 100644
--- a/ui/ozone/public/ozone_platform.cc
+++ b/ui/ozone/public/ozone_platform.cc
@@ -35,11 +35,10 @@
 
 // static
 void OzonePlatform::InitializeForUI(const InitParams& args) {
-  EnsureInstance();
   if (g_platform_initialized_ui)
     return;
   g_platform_initialized_ui = true;
-  g_instance->InitializeUI(args);
+  EnsureInstance()->InitializeUI(args);
   // This is deliberately created after initializing so that the platform can
   // create its own version of DDM.
   DeviceDataManager::CreateInstance();
@@ -47,11 +46,10 @@
 
 // static
 void OzonePlatform::InitializeForGPU(const InitParams& args) {
-  EnsureInstance();
   if (g_platform_initialized_gpu)
     return;
   g_platform_initialized_gpu = true;
-  g_instance->InitializeGPU(args);
+  EnsureInstance()->InitializeGPU(args);
 }
 
 // static
diff --git a/ui/ozone/public/ozone_platform.h b/ui/ozone/public/ozone_platform.h
index ed14575f..c345533 100644
--- a/ui/ozone/public/ozone_platform.h
+++ b/ui/ozone/public/ozone_platform.h
@@ -72,7 +72,10 @@
 
     // Setting this to true indicates that the platform implementation should
     // use mojo. Setting this to true requires calling |AddInterfaces|
-    // afterwards in the Viz process and providing a connector as part.
+    // afterwards in the Viz process. Note that this param is only checked in
+    // Ozone DRM. Other platforms either never use mojo or always use mojo
+    // regardless of this param.
+    // TODO(crbug.com/806092): Remove after legacy IPC-based Ozone is removed.
     bool using_mojo = false;
 
     // Setting this to true indicates the display compositor will run in the GPU
diff --git a/ui/views/controls/button/menu_button.h b/ui/views/controls/button/menu_button.h
index d55a47526..c03b827 100644
--- a/ui/views/controls/button/menu_button.h
+++ b/ui/views/controls/button/menu_button.h
@@ -25,10 +25,6 @@
  public:
   METADATA_HEADER(MenuButton);
 
-  // How much padding to put on the left and right of the menu marker.
-  static constexpr int kMenuMarkerPaddingLeft = 3;
-  static constexpr int kMenuMarkerPaddingRight = -1;
-
   // Create a Button.
   MenuButton(const base::string16& text,
              MenuButtonListener* menu_button_listener,
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
index a3d331c..d38cdd9 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
@@ -52,6 +52,7 @@
     ":mojo_interface_provider",
     ":provisioning_page",
     ":sim_detect_page",
+    "//chromeos/services/cellular_setup/public/mojom:mojom_js_library_for_compile",
     "//ui/webui/resources/js:i18n_behavior",
   ]
 }
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js
index 5069bb3..9f1849d 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('cellular_setup', function() {
+cr.define('cellularSetup', function() {
   /** @enum{string} */
   const PageName = {
     SIM_DETECT: 'sim-detect-page',
@@ -10,7 +10,53 @@
     FINAL: 'final-page',
   };
 
-  return {PageName: PageName};
+  /** @enum{string} */
+  const State = {
+    IDLE: 'idle',
+    STARTING_ACTIVATION: 'starting-activation',
+    WAITING_FOR_ACTIVATION_TO_START: 'waiting-for-activation-to-start',
+    TIMEOUT_START_ACTIVATION: 'timeout-start-activation',
+    WAITING_FOR_PORTAL_TO_LOAD: 'waiting-for-portal-to-load',
+    TIMEOUT_PORTAL_LOAD: 'timeout-portal-load',
+    WAITING_FOR_USER_PAYMENT: 'waiting-for-user-payment',
+    WAITING_FOR_ACTIVATION_TO_FINISH: 'waiting-for-activation-to-finish',
+    TIMEOUT_FINISH_ACTIVATION: 'timeout-finish-activation',
+    ACTIVATION_SUCCESS: 'activation-success',
+    ALREADY_ACTIVATED: 'already-activated',
+    ACTIVATION_FAILURE: 'activation-failure',
+  };
+
+  /**
+   * @param {!cellularSetup.State} state
+   * @return {?number} The time delta, in ms, for the timeout corresponding to
+   *     |state|. If no timeout is applicable for this state, null is returned.
+   */
+  function getTimeoutMsForState(state) {
+    // In some cases, starting activation may require power-cycling the device's
+    // modem, a process that can take several seconds.
+    if (state === State.STARTING_ACTIVATION) {
+      return 10000;  // 10 seconds.
+    }
+
+    // The portal is a website served by the mobile carrier.
+    if (state === State.WAITING_FOR_PORTAL_TO_LOAD) {
+      return 5000;  // 5 seconds.
+    }
+
+    // Finishing activation only requires sending a D-Bus message to Shill.
+    if (state === State.WAITING_FOR_ACTIVATION_TO_FINISH) {
+      return 1000;  // 1 second.
+    }
+
+    // No other states require timeouts.
+    return null;
+  }
+
+  return {
+    PageName: PageName,
+    State: State,
+    getTimeoutMsForState: getTimeoutMsForState
+  };
 });
 
 /**
@@ -24,13 +70,19 @@
   behaviors: [I18nBehavior],
 
   properties: {
+    /** @private {!cellularSetup.State} */
+    state_: {
+      type: String,
+      value: cellularSetup.State.IDLE,
+    },
+
     /**
      * Element name of the current selected sub-page.
-     * @private {!cellular_setup.PageName}
+     * @private {!cellularSetup.PageName}
      */
     selectedPageName_: {
       type: String,
-      value: cellular_setup.PageName.SIM_DETECT,
+      value: cellularSetup.PageName.SIM_DETECT,
       notify: true,
     },
 
@@ -66,6 +118,12 @@
     showCancelButton_: {type: Boolean, value: false}
   },
 
+  observers: [
+    'updateShowError_(state_)',
+    'updateSelectedPage_(state_)',
+    'handleStateChange_(state_)',
+  ],
+
   listeners: {
     'backward-nav-requested': 'onBackwardNavRequested_',
     'retry-requested': 'onRetryRequested_',
@@ -78,12 +136,187 @@
    */
   mojoInterfaceProvider_: null,
 
+  /**
+   * Delegate responsible for routing activation started/finished events.
+   * @private {?chromeos.cellularSetup.mojom.ActivationDelegateReceiver}
+   */
+  activationDelegateReceiver_: null,
+
+  /**
+   * The timeout ID corresponding to a timeout for the current state. If no
+   * timeout is active, this value is null.
+   * @private {?number}
+   */
+  currentTimeoutId_: null,
+
+  /**
+   * Cellular metadata received via the onActivationStarted() callback. If that
+   * callback has not occurred, this field is null.
+   * @private {?chromeos.cellularSetup.mojom.CellularMetadata}
+   */
+  cellularMetadata_: null,
+
   /** @override */
   created: function() {
     this.mojoInterfaceProvider_ =
         cellular_setup.MojoInterfaceProviderImpl.getInstance();
   },
 
+  /** @override */
+  ready: function() {
+    this.state_ = cellularSetup.State.STARTING_ACTIVATION;
+  },
+
+  /**
+   * Overrides chromeos.cellularSetup.mojom.ActivationDelegateInterface.
+   * @param {!chromeos.cellularSetup.mojom.CellularMetadata} metadata
+   * @private
+   */
+  onActivationStarted(metadata) {
+    this.clearTimer_();
+    this.cellularMetadata_ = metadata;
+    this.state_ = cellularSetup.State.WAITING_FOR_PORTAL_TO_LOAD;
+  },
+
+  /**
+   * Overrides chromeos.cellularSetup.mojom.ActivationDelegateInterface.
+   * @param {!chromeos.cellularSetup.mojom.ActivationResult} result
+   * @private
+   */
+  onActivationFinished(result) {
+    this.closeActivationConnection_();
+
+    const ActivationResult = chromeos.cellularSetup.mojom.ActivationResult;
+    switch (result) {
+      case ActivationResult.kSuccessfullyStartedActivation:
+        this.state_ = cellularSetup.State.ALREADY_ACTIVATED;
+        break;
+      case ActivationResult.kAlreadyActivated:
+        this.state_ = cellularSetup.State.ACTIVATION_SUCCESS;
+        break;
+      case ActivationResult.kFailedToActivate:
+        this.state_ = cellularSetup.State.ACTIVATION_FAILURE;
+        break;
+      default:
+        assertNotReached();
+    }
+  },
+
+  /** @private */
+  updateShowError_: function() {
+    switch (this.state_) {
+      case cellularSetup.State.TIMEOUT_START_ACTIVATION:
+      case cellularSetup.State.TIMEOUT_PORTAL_LOAD:
+      case cellularSetup.State.TIMEOUT_FINISH_ACTIVATION:
+      case cellularSetup.State.ACTIVATION_FAILURE:
+        this.showError_ = true;
+        return;
+      default:
+        this.showError_ = false;
+        return;
+    }
+  },
+
+  /** @private */
+  updateSelectedPage_: function() {
+    switch (this.state_) {
+      case cellularSetup.State.IDLE:
+      case cellularSetup.State.STARTING_ACTIVATION:
+      case cellularSetup.State.WAITING_FOR_ACTIVATION_TO_START:
+      case cellularSetup.State.TIMEOUT_START_ACTIVATION:
+        this.selectedPageName_ = cellularSetup.PageName.SIM_DETECT;
+        return;
+      case cellularSetup.State.WAITING_FOR_PORTAL_TO_LOAD:
+      case cellularSetup.State.TIMEOUT_PORTAL_LOAD:
+      case cellularSetup.State.WAITING_FOR_USER_PAYMENT:
+        this.selectedPageName_ = cellularSetup.PageName.PROVISIONING;
+        return;
+      case cellularSetup.State.WAITING_FOR_ACTIVATION_TO_FINISH:
+      case cellularSetup.State.TIMEOUT_FINISH_ACTIVATION:
+      case cellularSetup.State.ACTIVATION_SUCCESS:
+      case cellularSetup.State.ALREADY_ACTIVATED:
+      case cellularSetup.State.ACTIVATION_FAILURE:
+        this.selectedPageName_ = cellularSetup.PageName.FINAL;
+        return;
+      default:
+        assertNotReached();
+    }
+  },
+
+  /** @private */
+  handleStateChange_: function() {
+    // Since the state has changed, the previous state did not time out, so
+    // clear any active timeout.
+    this.clearTimer_();
+
+    // If the new state has an associated timeout, set it.
+    const timeoutMs = cellularSetup.getTimeoutMsForState(this.state_);
+    if (timeoutMs !== null) {
+      this.currentTimeoutId_ =
+          setTimeout(this.onTimeout_.bind(this), timeoutMs);
+    }
+
+    if (this.state_ === cellularSetup.State.STARTING_ACTIVATION) {
+      this.startActivation_();
+      return;
+    }
+  },
+
+  /** @private */
+  onTimeout_: function() {
+    // The activation attempt failed, so close the connection to the service.
+    this.closeActivationConnection_();
+
+    switch (this.state_) {
+      case cellularSetup.State.STARTING_ACTIVATION:
+        this.state_ = cellularSetup.State.TIMEOUT_START_ACTIVATION;
+        return;
+      case cellularSetup.State.WAITING_FOR_PORTAL_TO_LOAD:
+        this.state_ = cellularSetup.State.TIMEOUT_PORTAL_LOAD;
+        return;
+      case cellularSetup.State.WAITING_FOR_ACTIVATION_TO_FINISH:
+        this.state_ = cellularSetup.State.TIMEOUT_FINISH_ACTIVATION;
+        return;
+      default:
+        // Only the above states are expected to time out.
+        assertNotReached();
+    }
+  },
+
+  /** @private */
+  startActivation_: function() {
+    assert(!this.activationDelegateReceiver_);
+    this.activationDelegateReceiver_ =
+        new chromeos.cellularSetup.mojom.ActivationDelegateReceiver(
+            /**
+             * @type {!chromeos.cellularSetup.mojom.ActivationDelegateInterface}
+             */
+            (this));
+
+    this.mojoInterfaceProvider_.getMojoServiceRemote()
+        .startActivation(
+            this.activationDelegateReceiver_.$.bindNewPipeAndPassRemote())
+        .then((handler) => {
+          this.carrierPortalHandler_ = handler;
+        });
+  },
+
+  /** @private */
+  closeActivationConnection_: function() {
+    assert(!!this.activationDelegateReceiver_);
+    this.activationDelegateReceiver_.$.close();
+    this.activationDelegateReceiver_ = null;
+    this.carrierPortalHandler_ = null;
+  },
+
+  /** @private */
+  clearTimer_: function() {
+    if (this.currentTimeoutId_) {
+      clearTimeout(this.currentTimeoutId_);
+    }
+    this.currentTimeoutId_ = null;
+  },
+
   /** @private */
   onBackwardNavRequested_: function() {
     // TODO(azeemarshad): Add back navigation.
diff --git a/ui/webui/resources/js/icon.js b/ui/webui/resources/js/icon.js
index eb2d405..55aaf91b 100644
--- a/ui/webui/resources/js/icon.js
+++ b/ui/webui/resources/js/icon.js
@@ -129,19 +129,26 @@
    * @return {string} -webkit-image-set for the favicon.
    */
   function getFavicon(url, isSyncedUrlForHistoryUi, remoteIconUrlForUma = '') {
-    const isIconUrl = FAVICON_URL_REGEX.test(url);
-    // Note: Literal strings used below must match those in the description of
+    // Note: URL param keys used below must match those in the description of
     // chrome://favicon2 format in components/favicon_base/favicon_url_parser.h.
-    let path = 'chrome://favicon2/?size=16&scale_factor=SCALEFACTORx' +
-        (isIconUrl ? '&icon_url=' : '&page_url=') + encodeURIComponent(url);
-    if (!isIconUrl) {
-      path += '&allow_google_server_fallback=' +
-          (isSyncedUrlForHistoryUi ? '1' : '0');
+    const faviconUrl = new URL('chrome://favicon2/');
+    faviconUrl.searchParams.set('size', '16');
+    faviconUrl.searchParams.set('scale_factor', 'SCALEFACTORx');
+
+    if (FAVICON_URL_REGEX.test(url)) {
+      faviconUrl.searchParams.set('icon_url', url);
+    } else {
+      faviconUrl.searchParams.set('page_url', url);
+      // TODO(dbeam): use the presence of 'allow_google_server_fallback' to
+      // indicate true, otherwise false.
+      const fallback = isSyncedUrlForHistoryUi ? '1' : '0';
+      faviconUrl.searchParams.set('allow_google_server_fallback', fallback);
       if (isSyncedUrlForHistoryUi) {
-        path += '&icon_url=' + encodeURIComponent(remoteIconUrlForUma);
+        faviconUrl.searchParams.set('icon_url', remoteIconUrlForUma);
       }
     }
-    return getImageSet(path);
+
+    return getImageSet(faviconUrl.toString());
   }
 
   return {
diff --git a/ui/webui/resources/js/util.js b/ui/webui/resources/js/util.js
index 1aa5b4b..c9a9c35 100644
--- a/ui/webui/resources/js/util.js
+++ b/ui/webui/resources/js/util.js
@@ -70,42 +70,6 @@
 // </if>
 
 /**
- * Parses query parameters from Location.
- * @param {Location} location The URL to generate the CSS url for.
- * @return {Object} Dictionary containing name value pairs for URL
- */
-/* #export */ function parseQueryParams(location) {
-  const params = {};
-  const query = unescape(location.search.substring(1));
-  const vars = query.split('&');
-  for (let i = 0; i < vars.length; i++) {
-    const pair = vars[i].split('=');
-    params[pair[0]] = pair[1];
-  }
-  return params;
-}
-
-/**
- * Creates a new URL by appending or replacing the given query key and value.
- * Not supporting URL with username and password.
- * @param {Location} location The original URL.
- * @param {string} key The query parameter name.
- * @param {string} value The query parameter value.
- * @return {string} The constructed new URL.
- */
-/* #export */ function setQueryParam(location, key, value) {
-  const query = parseQueryParams(location);
-  query[encodeURIComponent(key)] = encodeURIComponent(value);
-
-  let newQuery = '';
-  for (const q in query) {
-    newQuery += (newQuery ? '&' : '?') + q + '=' + query[q];
-  }
-
-  return location.origin + location.pathname + newQuery + location.hash;
-}
-
-/**
  * @param {Node} el A node to search for ancestors with |className|.
  * @param {string} className A class to search for.
  * @return {Element} A node with class of |className| or null if none is found.