diff --git a/DEPS b/DEPS
index 226d73b..3de63a4c 100644
--- a/DEPS
+++ b/DEPS
@@ -129,11 +129,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': 'bb482ab871a29fad004f318210e0e01d44110a37',
+  'skia_revision': 'b6a3a3b245a5f97a2c1325278dab90193f69e9df',
   # 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': '6859f97f97d4fce1850b2256b82435e7f412d5f5',
+  'v8_revision': 'e160126cb3cb635a47b992812a6983ee4a6fdc6a',
   # 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.
@@ -141,19 +141,19 @@
   # 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': '3f7ace324e178a9466f3f1a002e3e5a025c070df',
+  'angle_revision': '9cce3cd9e376ff5889bad52fb197774ca6b3bc52',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '52a67b6495ce4973c4e17830107f09c35f7abbcc',
+  'swiftshader_revision': '37904182dd242a2734576cf11151a8350fca3031',
   # 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': '21d000094adeb6be3b97f3758523c6117f334e82',
+  'pdfium_revision': '010a4da72d171ad386d1475e4f2e9e1620ed765c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
-  'openmax_dl_revision': '3b18bf8338abbb9cc0dd5de4c69609c38eee9c8b',
+  'openmax_dl_revision': '25492ab7195649acce190708e095692402593e2b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -196,7 +196,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '1ca62263a6307ab8ffcf90fdb00c9d8933d15d65',
+  'catapult_revision': '83131f4f49335945ea647db8f35fbe286a185152',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -252,7 +252,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': '320a7de5c9a58912edd4167d80523f26e2f57ccd',
+  'spv_tools_revision': '0300a464a4ccdfac0052fcc0524a67592e9b19e8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -805,7 +805,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '507bf397d15ce43076ded2298db4a23bdecc2b1d',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'f6cbd3926d95820760927770b3c9fbde84a17999',
       'condition': 'checkout_linux',
   },
 
@@ -1343,7 +1343,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '688fbfe33779392aa210d67d4aa12cb012f112c2',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '4e2d015be8f7898151d0b76366a00d856899fb61',
+    Var('webrtc_git') + '/src.git' + '@' + '6c072efe9f0ea1d4a286d71e85013f5c43ab919f',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1384,7 +1384,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7ecd04321b1bf725d3745fe909363a6eade9a1f1',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@096bd921bbabae23f5b8826106e82e3c95ffbadf',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index aebf419..0a8e33ab 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -597,6 +597,8 @@
     "browser/net/aw_network_change_notifier_factory.h",
     "browser/net/aw_network_delegate.cc",
     "browser/net/aw_network_delegate.h",
+    "browser/net/aw_proxy_config_monitor.cc",
+    "browser/net/aw_proxy_config_monitor.h",
     "browser/net/aw_request_interceptor.cc",
     "browser/net/aw_request_interceptor.h",
     "browser/net/aw_url_request_context_getter.cc",
diff --git a/android_webview/browser/aw_browser_context.h b/android_webview/browser/aw_browser_context.h
index ae00e7b..a22e50f 100644
--- a/android_webview/browser/aw_browser_context.h
+++ b/android_webview/browser/aw_browser_context.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "android_webview/browser/aw_ssl_host_state_delegate.h"
+#include "android_webview/browser/net/aw_proxy_config_monitor.h"
 #include "android_webview/browser/safe_browsing/aw_safe_browsing_ui_manager.h"
 #include "base/compiler_specific.h"
 #include "base/files/file_path.h"
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index 4889f81..0a40e96 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -27,6 +27,7 @@
 #include "android_webview/browser/aw_speech_recognition_manager_delegate.h"
 #include "android_webview/browser/aw_web_contents_view_delegate.h"
 #include "android_webview/browser/cookie_manager.h"
+#include "android_webview/browser/net/aw_proxy_config_monitor.h"
 #include "android_webview/browser/net/aw_url_request_context_getter.h"
 #include "android_webview/browser/network_service/aw_cookie_manager_wrapper.h"
 #include "android_webview/browser/network_service/aw_proxying_url_loader_factory.h"
@@ -359,6 +360,11 @@
   g_created_network_context_params = true;
 #endif
   context_params->check_clear_text_permitted = g_check_cleartext_permitted;
+
+  // Add proxy settings
+  AwProxyConfigMonitor::GetInstance()->AddProxyToNetworkContextParams(
+      context_params);
+
   return context_params;
 }
 
diff --git a/android_webview/browser/aw_content_browser_client_unittest.cc b/android_webview/browser/aw_content_browser_client_unittest.cc
index e72a1927..af3e83a 100644
--- a/android_webview/browser/aw_content_browser_client_unittest.cc
+++ b/android_webview/browser/aw_content_browser_client_unittest.cc
@@ -3,12 +3,26 @@
 // found in the LICENSE file.
 
 #include "android_webview/browser/aw_content_browser_client.h"
+
 #include "android_webview/browser/aw_feature_list_creator.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/core/embedder/embedder.h"
+#include "services/network/public/cpp/features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace android_webview {
 
-class AwContentBrowserClientTest : public testing::Test {};
+class AwContentBrowserClientTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    mojo::core::Init();
+    feature_list_.InitAndEnableFeature(network::features::kNetworkService);
+  }
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::test::ScopedFeatureList feature_list_;
+};
 
 TEST_F(AwContentBrowserClientTest, DisableCreatingTaskScheduler) {
   AwFeatureListCreator aw_feature_list_creator;
diff --git a/android_webview/browser/aw_proxy_controller.cc b/android_webview/browser/aw_proxy_controller.cc
index 087ca95..bbf9e6c4 100644
--- a/android_webview/browser/aw_proxy_controller.cc
+++ b/android_webview/browser/aw_proxy_controller.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "android_webview/browser/aw_browser_context.h"
+#include "android_webview/browser/net/aw_proxy_config_monitor.h"
 #include "android_webview/browser/net/aw_url_request_context_getter.h"
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
@@ -70,11 +71,14 @@
   std::vector<std::string> bypass_rules;
   base::android::AppendJavaStringArrayToStringVector(env, jbypass_rules,
                                                      &bypass_rules);
-
   std::string result;
   if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-    // TODO(laisminchillo): implement the Network Service code path
-    // (http://crbug.com/902658).
+    result = AwProxyConfigMonitor::GetInstance()->SetProxyOverride(
+        proxy_rules, bypass_rules,
+        base::BindOnce(&ProxyOverrideChanged,
+                       ScopedJavaGlobalRef<jobject>(env, obj),
+                       ScopedJavaGlobalRef<jobject>(env, listener),
+                       ScopedJavaGlobalRef<jobject>(env, executor)));
   } else {
     result =
         AwBrowserContext::GetDefault()
@@ -95,8 +99,10 @@
     const JavaParamRef<jobject>& listener,
     const JavaParamRef<jobject>& executor) {
   if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-    // TODO(laisminchillo): implement the Network Service code path
-    // (http://crbug.com/902658).
+    AwProxyConfigMonitor::GetInstance()->ClearProxyOverride(base::BindOnce(
+        &ProxyOverrideChanged, ScopedJavaGlobalRef<jobject>(env, obj),
+        ScopedJavaGlobalRef<jobject>(env, listener),
+        ScopedJavaGlobalRef<jobject>(env, executor)));
   } else {
     AwBrowserContext::GetDefault()
         ->GetAwURLRequestContext()
diff --git a/android_webview/browser/gfx/aw_draw_fn_impl.cc b/android_webview/browser/gfx/aw_draw_fn_impl.cc
index eb687069..4299b44 100644
--- a/android_webview/browser/gfx/aw_draw_fn_impl.cc
+++ b/android_webview/browser/gfx/aw_draw_fn_impl.cc
@@ -500,10 +500,12 @@
   // If we have a |gl_done_fd|, create a Skia GrBackendSemaphore from
   // |gl_done_fd| and wait.
   if (gl_done_fd.is_valid()) {
-    VkSemaphore gl_done_semaphore;
-    if (!vulkan_context_provider_->implementation()->ImportSemaphoreFdKHR(
-            vulkan_context_provider_->device(), std::move(gl_done_fd),
-            &gl_done_semaphore)) {
+    VkSemaphore gl_done_semaphore =
+        vulkan_context_provider_->implementation()->ImportSemaphoreHandle(
+            vulkan_context_provider_->device(),
+            gpu::SemaphoreHandle(VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+                                 std::move(gl_done_fd)));
+    if (gl_done_semaphore == VK_NULL_HANDLE) {
       LOG(ERROR) << "Could not create Vulkan semaphore for GL completion.";
       return;
     }
@@ -624,12 +626,15 @@
     LOG(ERROR) << "Skia could not submit GrSemaphore.";
     return;
   }
-  if (!vulkan_context_provider_->implementation()->GetSemaphoreFdKHR(
-          vulkan_context_provider_->device(), pending_draw->post_draw_semaphore,
-          &pending_draw->sync_fd)) {
+  gpu::SemaphoreHandle semaphore_handle =
+      vulkan_context_provider_->implementation()->GetSemaphoreHandle(
+          vulkan_context_provider_->device(),
+          pending_draw->post_draw_semaphore);
+  if (!semaphore_handle.is_valid()) {
     LOG(ERROR) << "Could not retrieve SyncFD from |post_draw_semaphore|.";
     return;
   }
+  pending_draw->sync_fd = semaphore_handle.TakeHandle();
 
   DCHECK(VK_NULL_HANDLE == pending_draw->post_draw_fence);
 
diff --git a/android_webview/browser/net/aw_proxy_config_monitor.cc b/android_webview/browser/net/aw_proxy_config_monitor.cc
new file mode 100644
index 0000000..c9dd5b5
--- /dev/null
+++ b/android_webview/browser/net/aw_proxy_config_monitor.cc
@@ -0,0 +1,105 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "android_webview/browser/net/aw_proxy_config_monitor.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "services/network/public/cpp/features.h"
+
+namespace android_webview {
+
+namespace {
+base::LazyInstance<AwProxyConfigMonitor>::Leaky g_instance;
+}  // namespace
+
+AwProxyConfigMonitor::AwProxyConfigMonitor() {
+  proxy_config_service_android_ =
+      std::make_unique<net::ProxyConfigServiceAndroid>(
+          base::ThreadTaskRunnerHandle::Get(),
+          base::ThreadTaskRunnerHandle::Get());
+  proxy_config_service_android_->AddObserver(this);
+}
+
+AwProxyConfigMonitor::~AwProxyConfigMonitor() {
+  proxy_config_service_android_->RemoveObserver(this);
+}
+
+AwProxyConfigMonitor* AwProxyConfigMonitor::GetInstance() {
+  return g_instance.Pointer();
+}
+
+void AwProxyConfigMonitor::AddProxyToNetworkContextParams(
+    network::mojom::NetworkContextParamsPtr& network_context_params) {
+  network::mojom::ProxyConfigClientPtr proxy_config_client;
+  network_context_params->proxy_config_client_request =
+      mojo::MakeRequest(&proxy_config_client);
+  proxy_config_client_set_.AddPtr(std::move(proxy_config_client));
+
+  net::ProxyConfigWithAnnotation proxy_config;
+  net::ProxyConfigService::ConfigAvailability availability =
+      proxy_config_service_android_->GetLatestProxyConfig(&proxy_config);
+  if (availability == net::ProxyConfigService::CONFIG_VALID) {
+    network_context_params->initial_proxy_config = proxy_config;
+  }
+}
+
+void AwProxyConfigMonitor::OnProxyConfigChanged(
+    const net::ProxyConfigWithAnnotation& config,
+    net::ProxyConfigService::ConfigAvailability availability) {
+  proxy_config_client_set_.ForAllPtrs(
+      [config,
+       availability](network::mojom::ProxyConfigClient* proxy_config_client) {
+        switch (availability) {
+          case net::ProxyConfigService::CONFIG_VALID:
+            proxy_config_client->OnProxyConfigUpdated(config);
+            break;
+          case net::ProxyConfigService::CONFIG_UNSET:
+            proxy_config_client->OnProxyConfigUpdated(
+                net::ProxyConfigWithAnnotation::CreateDirect());
+            break;
+          case net::ProxyConfigService::CONFIG_PENDING:
+            NOTREACHED();
+            break;
+        }
+      });
+}
+
+std::string AwProxyConfigMonitor::SetProxyOverride(
+    const std::vector<net::ProxyConfigServiceAndroid::ProxyOverrideRule>&
+        proxy_rules,
+    const std::vector<std::string>& bypass_rules,
+    base::OnceClosure callback) {
+  return proxy_config_service_android_->SetProxyOverride(
+      proxy_rules, bypass_rules,
+      base::BindOnce(&AwProxyConfigMonitor::FlushProxyConfig,
+                     base::Unretained(this), std::move(callback)));
+}
+
+void AwProxyConfigMonitor::ClearProxyOverride(base::OnceClosure callback) {
+  proxy_config_service_android_->ClearProxyOverride(
+      base::BindOnce(&AwProxyConfigMonitor::FlushProxyConfig,
+                     base::Unretained(this), std::move(callback)));
+}
+
+void AwProxyConfigMonitor::FlushProxyConfig(base::OnceClosure callback) {
+  int count = 0;
+  proxy_config_client_set_.ForAllPtrs(
+      [&count](network::mojom::ProxyConfigClient* proxy_config_client) {
+        ++count;
+      });
+
+  base::RepeatingClosure closure =
+      base::BarrierClosure(count, std::move(callback));
+  proxy_config_client_set_.ForAllPtrs(
+      [closure](network::mojom::ProxyConfigClient* proxy_config_client) {
+        proxy_config_client->FlushProxyConfig(closure);
+      });
+}
+
+}  // namespace android_webview
diff --git a/android_webview/browser/net/aw_proxy_config_monitor.h b/android_webview/browser/net/aw_proxy_config_monitor.h
new file mode 100644
index 0000000..00e28340
--- /dev/null
+++ b/android_webview/browser/net/aw_proxy_config_monitor.h
@@ -0,0 +1,56 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ANDROID_WEBVIEW_BROWSER_NET_AW_PROXY_CONFIG_MONITOR_H_
+#define ANDROID_WEBVIEW_BROWSER_NET_AW_PROXY_CONFIG_MONITOR_H_
+
+#include <memory>
+#include <vector>
+
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "net/proxy_resolution/proxy_config_service_android.h"
+#include "net/url_request/url_request_context_builder.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+
+namespace android_webview {
+
+// This class configures proxy settings for NetworkContext if network service
+// is enabled.
+class AwProxyConfigMonitor : public net::ProxyConfigService::Observer {
+ public:
+  static AwProxyConfigMonitor* GetInstance();
+
+  void AddProxyToNetworkContextParams(
+      network::mojom::NetworkContextParamsPtr& network_context_params);
+  std::string SetProxyOverride(
+      const std::vector<net::ProxyConfigServiceAndroid::ProxyOverrideRule>&
+          proxy_rules,
+      const std::vector<std::string>& bypass_rules,
+      base::OnceClosure callback);
+  void ClearProxyOverride(base::OnceClosure callback);
+
+ private:
+  AwProxyConfigMonitor();
+  ~AwProxyConfigMonitor() override;
+
+  AwProxyConfigMonitor(const AwProxyConfigMonitor&) = delete;
+  AwProxyConfigMonitor& operator=(const AwProxyConfigMonitor&) = delete;
+
+  friend struct base::LazyInstanceTraitsBase<AwProxyConfigMonitor>;
+
+  // net::ProxyConfigService::Observer implementation:
+  void OnProxyConfigChanged(
+      const net::ProxyConfigWithAnnotation& config,
+      net::ProxyConfigService::ConfigAvailability availability) override;
+
+  void FlushProxyConfig(base::OnceClosure callback);
+
+  std::unique_ptr<net::ProxyConfigServiceAndroid> proxy_config_service_android_;
+  mojo::InterfacePtrSet<network::mojom::ProxyConfigClient>
+      proxy_config_client_set_;
+};
+
+}  // namespace android_webview
+
+#endif  // ANDROID_WEBVIEW_BROWSER_NET_AW_PROXY_CONFIG_MONITOR_H_
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
index 4c676927..a21570c4 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
@@ -556,7 +556,7 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
-    public void testThirdPartyCookie_redirectFromThirdToFirst() throws Throwable {
+    public void testThirdPartyCookie_redirectFromThirdPartyToFirst() throws Throwable {
         TestWebServer webServer = TestWebServer.start();
         try {
             allowFirstPartyCookies();
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index b2dddca..117b4507 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -284,10 +284,6 @@
   return false;
 }
 
-void SearchResultView::ChildPreferredSizeChanged(views::View* child) {
-  Layout();
-}
-
 void SearchResultView::PaintButtonContents(gfx::Canvas* canvas) {
   gfx::Rect rect(GetContentsBounds());
   if (rect.IsEmpty())
diff --git a/ash/app_list/views/search_result_view.h b/ash/app_list/views/search_result_view.h
index b3b88c8..f9b7cd2 100644
--- a/ash/app_list/views/search_result_view.h
+++ b/ash/app_list/views/search_result_view.h
@@ -102,7 +102,6 @@
   gfx::Size CalculatePreferredSize() const override;
   void Layout() override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
-  void ChildPreferredSizeChanged(views::View* child) override;
   void PaintButtonContents(gfx::Canvas* canvas) override;
   void OnFocus() override;
   void OnBlur() override;
diff --git a/ash/media/media_notification_view.cc b/ash/media/media_notification_view.cc
index c382e941..d8bb08d0 100644
--- a/ash/media/media_notification_view.cc
+++ b/ash/media/media_notification_view.cc
@@ -44,13 +44,6 @@
 constexpr gfx::Size kMediaButtonSize = gfx::Size(36, 36);
 constexpr int kMediaButtonRowSeparator = 8;
 constexpr gfx::Insets kMediaTitleArtistInsets = gfx::Insets(8, 8, 0, 8);
-constexpr gfx::Insets kMediaNotificationMainRowInsets =
-    gfx::Insets(0, kDefaultMarginSize, 14, kRightMarginSize);
-constexpr gfx::Insets kMediaNotificationExpandedMainRowInsets =
-    gfx::Insets(kDefaultMarginSize,
-                kDefaultMarginSize,
-                kDefaultMarginSize,
-                kRightMarginExpandedSize);
 constexpr int kMediaNotificationHeaderTopInset = 6;
 constexpr int kMediaNotificationHeaderRightInset = 6;
 constexpr int kMediaNotificationHeaderInset = 0;
@@ -285,6 +278,13 @@
 void MediaNotificationView::UpdateWithMediaArtwork(
     const gfx::ImageSkia& image) {
   GetMediaNotificationBackground()->UpdateArtwork(image);
+
+  has_artwork_ = !image.isNull();
+  UpdateViewForExpandedState();
+
+  PreferredSizeChanged();
+  Layout();
+  SchedulePaint();
 }
 
 void MediaNotificationView::UpdateWithMediaIcon(const gfx::ImageSkia& image) {
@@ -337,16 +337,23 @@
     main_row_
         ->SetLayoutManager(std::make_unique<views::BoxLayout>(
             views::BoxLayout::kVertical,
-            kMediaNotificationExpandedMainRowInsets, kDefaultMarginSize))
+            gfx::Insets(
+                kDefaultMarginSize, kDefaultMarginSize, kDefaultMarginSize,
+                has_artwork_ ? kRightMarginExpandedSize : kDefaultMarginSize),
+            kDefaultMarginSize))
         ->SetDefaultFlex(1);
   } else {
     main_row_
         ->SetLayoutManager(std::make_unique<views::BoxLayout>(
-            views::BoxLayout::kHorizontal, kMediaNotificationMainRowInsets,
+            views::BoxLayout::kHorizontal,
+            gfx::Insets(0, kDefaultMarginSize, 14,
+                        has_artwork_ ? kRightMarginSize : kDefaultMarginSize),
             kDefaultMarginSize, true))
         ->SetDefaultFlex(1);
   }
 
+  main_row_->Layout();
+
   GetMediaNotificationBackground()->UpdateArtworkMaxWidthPct(
       expanded_ ? kMediaImageMaxWidthExpandedPct : kMediaImageMaxWidthPct);
 
diff --git a/ash/media/media_notification_view.h b/ash/media/media_notification_view.h
index 9256a5e8..2405f47 100644
--- a/ash/media/media_notification_view.h
+++ b/ash/media/media_notification_view.h
@@ -94,6 +94,8 @@
   std::unique_ptr<message_center::NotificationControlButtonsView>
       control_buttons_view_;
 
+  bool has_artwork_ = false;
+
   // Whether this notification is expanded or not.
   bool expanded_ = false;
 
diff --git a/ash/media/media_notification_view_unittest.cc b/ash/media/media_notification_view_unittest.cc
index 613607d..04c8dab 100644
--- a/ash/media/media_notification_view_unittest.cc
+++ b/ash/media/media_notification_view_unittest.cc
@@ -621,6 +621,7 @@
 }
 
 TEST_F(MediaNotificationViewTest, UpdateArtworkFromItem) {
+  int title_artist_width = title_artist_row()->width();
   gfx::Size size = view()->size();
 
   SkBitmap image;
@@ -632,6 +633,10 @@
   GetItem()->MediaControllerImageChanged(
       media_session::mojom::MediaSessionImageType::kArtwork, image);
 
+  // Ensure the title artist row has a small width than before now that we
+  // have artwork.
+  EXPECT_GT(title_artist_width, title_artist_row()->width());
+
   // Ensure that the image is displayed in the background artwork and that the
   // size of the notification was not affected.
   EXPECT_FALSE(GetArtworkImage().isNull());
@@ -641,6 +646,10 @@
   GetItem()->MediaControllerImageChanged(
       media_session::mojom::MediaSessionImageType::kArtwork, SkBitmap());
 
+  // Ensure the title artist row goes back to the original width now that we
+  // do not have any artwork.
+  EXPECT_EQ(title_artist_width, title_artist_row()->width());
+
   // Ensure that the background artwork was reset and the size was still not
   // affected.
   EXPECT_TRUE(GetArtworkImage().isNull());
diff --git a/ash/public/cpp/caption_buttons/frame_back_button.cc b/ash/public/cpp/caption_buttons/frame_back_button.cc
index a8cdb436..09e990b 100644
--- a/ash/public/cpp/caption_buttons/frame_back_button.cc
+++ b/ash/public/cpp/caption_buttons/frame_back_button.cc
@@ -10,7 +10,6 @@
 #include "ui/base/hit_test.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/events/event_sink.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/window/caption_button_layout_constants.h"
@@ -29,15 +28,14 @@
 
 void FrameBackButton::ButtonPressed(Button* sender, const ui::Event& event) {
   // Send up event as well as down event as ARC++ clients expect this sequence.
+  // TODO: Investigate if we should be using the current modifiers.
   aura::Window* root_window = GetWidget()->GetNativeWindow()->GetRootWindow();
   ui::KeyEvent press_key_event(ui::ET_KEY_PRESSED, ui::VKEY_BROWSER_BACK,
                                ui::EF_NONE);
-  ignore_result(root_window->GetHost()->event_sink()->OnEventFromSource(
-      &press_key_event));
+  ignore_result(root_window->GetHost()->SendEventToSink(&press_key_event));
   ui::KeyEvent release_key_event(ui::ET_KEY_RELEASED, ui::VKEY_BROWSER_BACK,
                                  ui::EF_NONE);
-  ignore_result(root_window->GetHost()->event_sink()->OnEventFromSource(
-      &release_key_event));
+  ignore_result(root_window->GetHost()->SendEventToSink(&release_key_event));
 }
 
 }  // namespace ash
diff --git a/ash/public/cpp/frame_header.h b/ash/public/cpp/frame_header.h
index a644e30..3c1f0f8 100644
--- a/ash/public/cpp/frame_header.h
+++ b/ash/public/cpp/frame_header.h
@@ -34,6 +34,10 @@
 
   ~FrameHeader() override;
 
+  const base::string16& frame_text_override() const {
+    return frame_text_override_;
+  }
+
   // Returns the header's minimum width.
   int GetMinimumHeaderWidth() const;
 
diff --git a/ash/shelf/back_button.cc b/ash/shelf/back_button.cc
index a36bcc6..31b2595e 100644
--- a/ash/shelf/back_button.cc
+++ b/ash/shelf/back_button.cc
@@ -5,24 +5,12 @@
 #include "ash/shelf/back_button.h"
 
 #include "ash/resources/vector_icons/vector_icons.h"
-#include "ash/shelf/shelf.h"
-#include "ash/shelf/shelf_constants.h"
-#include "ash/shelf/shelf_view.h"
-#include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/system/tray/tray_popup_utils.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "base/metrics/user_metrics.h"
-#include "base/metrics/user_metrics_action.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/events/event_sink.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/paint_vector_icon.h"
-#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
-#include "ui/views/animation/ink_drop_impl.h"
-#include "ui/views/animation/ink_drop_mask.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -36,15 +24,18 @@
 
 BackButton::~BackButton() = default;
 
-void BackButton::OnGestureEvent(ui::GestureEvent* event) {
-  ShelfButton::OnGestureEvent(event);
-  if (event->type() == ui::ET_GESTURE_TAP)
-    GenerateAndSendBackEvent();
-}
+void BackButton::NotifyClick(const ui::Event& event) {
+  Button::NotifyClick(event);
 
-void BackButton::OnMouseReleased(const ui::MouseEvent& event) {
-  ShelfButton::OnMouseReleased(event);
-  GenerateAndSendBackEvent();
+  // Send up event as well as down event as ARC++ clients expect this sequence.
+  // TODO: Investigate if we should be using the current modifiers.
+  aura::Window* root_window = GetWidget()->GetNativeWindow()->GetRootWindow();
+  ui::KeyEvent press_key_event(ui::ET_KEY_PRESSED, ui::VKEY_BROWSER_BACK,
+                               ui::EF_NONE);
+  ignore_result(root_window->GetHost()->SendEventToSink(&press_key_event));
+  ui::KeyEvent release_key_event(ui::ET_KEY_RELEASED, ui::VKEY_BROWSER_BACK,
+                                 ui::EF_NONE);
+  ignore_result(root_window->GetHost()->SendEventToSink(&release_key_event));
 }
 
 void BackButton::PaintButtonContents(gfx::Canvas* canvas) {
@@ -59,18 +50,4 @@
   return kViewClassName;
 }
 
-void BackButton::GenerateAndSendBackEvent() {
-  base::RecordAction(base::UserMetricsAction("Tablet_BackButton"));
-
-  // Send the back event to the root window of the back button's widget.
-  const views::Widget* widget = GetWidget();
-  if (widget && widget->GetNativeWindow()) {
-    aura::Window* root_window = widget->GetNativeWindow()->GetRootWindow();
-    ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_BROWSER_BACK,
-                           ui::EF_NONE);
-    ignore_result(
-        root_window->GetHost()->event_sink()->OnEventFromSource(&key_event));
-  }
-}
-
 }  // namespace ash
diff --git a/ash/shelf/back_button.h b/ash/shelf/back_button.h
index 7a8119a1..8eab8f2 100644
--- a/ash/shelf/back_button.h
+++ b/ash/shelf/back_button.h
@@ -25,18 +25,11 @@
 
  protected:
   // views::Button:
-  void OnGestureEvent(ui::GestureEvent* event) override;
-  void OnMouseReleased(const ui::MouseEvent& event) override;
+  void NotifyClick(const ui::Event& event) override;
   void PaintButtonContents(gfx::Canvas* canvas) override;
   const char* GetClassName() const override;
 
  private:
-  // Generate and send a VKEY_BROWSER_BACK key event sequence when the back
-  // button is pressed. This should on be called on a tap down or mouse release
-  // event, and will only send a key down event since that is the one which
-  // triggers the back event.
-  void GenerateAndSendBackEvent();
-
   DISALLOW_COPY_AND_ASSIGN(BackButton);
 };
 
diff --git a/ash/shelf/back_button_unittest.cc b/ash/shelf/back_button_unittest.cc
index d50767a0..c757909 100644
--- a/ash/shelf/back_button_unittest.cc
+++ b/ash/shelf/back_button_unittest.cc
@@ -117,7 +117,7 @@
 
   generator->ReleaseLeftButton();
   EXPECT_EQ(1, target_back_press.accelerator_count());
-  EXPECT_EQ(0, target_back_release.accelerator_count());
+  EXPECT_EQ(1, target_back_release.accelerator_count());
 }
 
 }  // namespace ash
diff --git a/ash/shelf/shelf_view.h b/ash/shelf/shelf_view.h
index 94fa12c6..67daa75 100644
--- a/ash/shelf/shelf_view.h
+++ b/ash/shelf/shelf_view.h
@@ -294,12 +294,13 @@
   // or the overflow shelf.
   ShelfView* main_shelf() { return main_shelf_ ? main_shelf_ : this; }
   // Returns the overflow shelf. This can be called on either the main shelf
-  // or the overflow shelf. Returns nullptr if there is no overflow shelf.
+  // or the overflow shelf. Returns nullptr if the overflow shelf isn't visible.
   ShelfView* overflow_shelf() {
     if (is_overflow_mode())
       return this;
-    return overflow_bubble_ ? overflow_bubble_->bubble_view()->shelf_view()
-                            : nullptr;
+    return IsShowingOverflowBubble()
+               ? overflow_bubble_->bubble_view()->shelf_view()
+               : nullptr;
   }
 
   const ShelfAppButton* drag_view() const { return drag_view_; }
diff --git a/ash/system/accessibility/tray_accessibility.cc b/ash/system/accessibility/tray_accessibility.cc
index 139bc6d..e2e8c4f 100644
--- a/ash/system/accessibility/tray_accessibility.cc
+++ b/ash/system/accessibility/tray_accessibility.cc
@@ -77,11 +77,9 @@
   TrayPopupUtils::UpdateCheckMarkVisibility(select_to_speak_view_,
                                             select_to_speak_enabled_);
 
-  if (dictation_view_) {
-    dictation_enabled_ = controller->dictation_enabled();
-    TrayPopupUtils::UpdateCheckMarkVisibility(dictation_view_,
-                                              dictation_enabled_);
-  }
+  dictation_enabled_ = controller->dictation_enabled();
+  TrayPopupUtils::UpdateCheckMarkVisibility(dictation_view_,
+                                            dictation_enabled_);
 
   high_contrast_enabled_ = controller->high_contrast_enabled();
   TrayPopupUtils::UpdateCheckMarkVisibility(high_contrast_view_,
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index f51eb24e1..58bed79 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -773,24 +773,27 @@
       Shell::Get()->split_view_controller()->end_reason() ==
           SplitViewController::EndReason::kUnsnappableWindowActivated;
 
-  if (state != SplitViewController::NO_SNAP || unsnappable_window_activated) {
-    // Do not restore focus if a window was just snapped and activated or
-    // splitview mode is ended by activating an unsnappable window.
+  // Restore focus unless either a window was just snapped (and activated) or
+  // split view mode was ended by activating an unsnappable window.
+  if (state != SplitViewController::NO_SNAP || unsnappable_window_activated)
     ResetFocusRestoreWindow(false);
-  }
 
+  // If two windows were snapped to both sides of the screen or an unsnappable
+  // window was just activated, end overview mode and bail out.
   if (state == SplitViewController::BOTH_SNAPPED ||
       unsnappable_window_activated) {
-    // If two windows were snapped to both sides of the screen or an unsnappable
-    // window was just activated, end overview mode.
     CancelSelection();
-  } else {
-    // Otherwise adjust the overview window grid bounds if overview mode is
-    // active at the moment.
-    OnDisplayBoundsChanged();
-    for (auto& grid : grid_list_)
-      grid->UpdateCannotSnapWarningVisibility();
+    return;
   }
+
+  // Adjust the overview window grid bounds if overview mode is active.
+  OnDisplayBoundsChanged();
+  for (auto& grid : grid_list_)
+    grid->UpdateCannotSnapWarningVisibility();
+
+  // Notify |split_view_drag_indicators_| if split view mode ended.
+  if (split_view_drag_indicators_ && state == SplitViewController::NO_SNAP)
+    split_view_drag_indicators_->OnSplitViewModeEnded();
 }
 
 void OverviewSession::OnSplitViewDividerPositionChanged() {
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index 81d0950..32d5acdf 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -71,7 +71,9 @@
 constexpr float kBlackScrimFadeInRatio = 0.1f;
 constexpr float kBlackScrimOpacity = 0.4f;
 
-// The duration of the divider snap animation, in milliseconds.
+// The duration of the divider snap animation, in milliseconds. Before you
+// change this value, read the comment on kIsWindowMovedTimeoutMs in
+// tablet_mode_window_drag_delegate.cc.
 constexpr int kDividerSnapDurationMs = 300;
 
 // Toast data.
diff --git a/ash/wm/splitview/split_view_drag_indicators.cc b/ash/wm/splitview/split_view_drag_indicators.cc
index 979a3c9..959d2c3 100644
--- a/ash/wm/splitview/split_view_drag_indicators.cc
+++ b/ash/wm/splitview/split_view_drag_indicators.cc
@@ -314,24 +314,26 @@
 
   ~SplitViewDragIndicatorsView() override {}
 
-  // Called by parent widget when the state machine changes. Handles setting the
-  // opacity and bounds of the highlights and labels based on the state.
+  // Called by parent widget when the state machine changes.
   void OnIndicatorTypeChanged(IndicatorState indicator_state) {
     DCHECK_NE(indicator_state_, indicator_state);
-
     previous_indicator_state_ = indicator_state_;
     indicator_state_ = indicator_state;
+    Update();
+  }
 
-    left_rotated_view_->OnIndicatorTypeChanged(indicator_state,
+  // Handles setting the opacity and bounds of the highlights and labels.
+  void Update() {
+    left_rotated_view_->OnIndicatorTypeChanged(indicator_state_,
                                                previous_indicator_state_);
-    right_rotated_view_->OnIndicatorTypeChanged(indicator_state,
+    right_rotated_view_->OnIndicatorTypeChanged(indicator_state_,
                                                 previous_indicator_state_);
-    left_highlight_view_->OnIndicatorTypeChanged(indicator_state,
+    left_highlight_view_->OnIndicatorTypeChanged(indicator_state_,
                                                  previous_indicator_state_);
-    right_highlight_view_->OnIndicatorTypeChanged(indicator_state,
+    right_highlight_view_->OnIndicatorTypeChanged(indicator_state_,
                                                   previous_indicator_state_);
 
-    if (indicator_state != IndicatorState::kNone ||
+    if (indicator_state_ != IndicatorState::kNone ||
         IsPreviewAreaState(previous_indicator_state_))
       Layout(previous_indicator_state_ != IndicatorState::kNone);
   }
@@ -559,6 +561,14 @@
   indicators_view_->OnIndicatorTypeChanged(current_indicator_state_);
 }
 
+void SplitViewDragIndicators::OnSplitViewModeEnded() {
+  if (current_indicator_state_ == IndicatorState::kNone ||
+      IsPreviewAreaState(current_indicator_state_)) {
+    return;
+  }
+  indicators_view_->Update();
+}
+
 void SplitViewDragIndicators::OnDisplayBoundsChanged() {
   aura::Window* root_window = widget_->GetNativeView()->GetRootWindow();
   widget_->SetBounds(GetWorkAreaBoundsNoOverlapWithShelf(root_window));
diff --git a/ash/wm/splitview/split_view_drag_indicators.h b/ash/wm/splitview/split_view_drag_indicators.h
index e2f1710..a6b4c4a 100644
--- a/ash/wm/splitview/split_view_drag_indicators.h
+++ b/ash/wm/splitview/split_view_drag_indicators.h
@@ -82,6 +82,11 @@
   void SetIndicatorState(IndicatorState indicator_state,
                          const gfx::Point& event_location);
 
+  // Called by owner of this class when split view mode ends, so that this class
+  // can show the drag indicators which are usually hidden in split view mode.
+  // https://crbug.com/946601
+  void OnSplitViewModeEnded();
+
   // Called by owner of this class when display bounds changes are observed, so
   // that this class can relayout accordingly.
   void OnDisplayBoundsChanged();
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index c8f2f3b..d6943f27d 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -41,7 +41,10 @@
 // tablet mode.
 constexpr float kIndicatorsThresholdRatio = 0.1;
 
-// Duration of a drag that it will be considered as an intended drag.
+// Duration of a drag that it will be considered as an intended drag. Must be at
+// least the duration of the split view divider snap animation, or else there
+// will be an issue similar to https://crbug.com/946601 but involving dragging a
+// snapped window from the top.
 constexpr base::TimeDelta kIsWindowMovedTimeoutMs =
     base::TimeDelta::FromMilliseconds(300);
 
diff --git a/base/profiler/register_context.h b/base/profiler/register_context.h
index 92d2e51..7c7dfd8b 100644
--- a/base/profiler/register_context.h
+++ b/base/profiler/register_context.h
@@ -54,6 +54,16 @@
 #endif
 }
 
+inline uintptr_t& RegisterContextInstructionPointer(::CONTEXT* context) {
+#if defined(ARCH_CPU_X86_64)
+  return context->Rip;
+#elif defined(ARCH_CPU_ARM64)
+  return context->Pc;
+#else
+  return AsUintPtr(&context->Eip);
+#endif
+}
+
 #elif defined(OS_MACOSX) && !defined(OS_IOS)  // #if defined(OS_WIN)
 
 using RegisterContext = x86_thread_state64_t;
@@ -66,12 +76,18 @@
   return AsUintPtr(&context->__rbp);
 }
 
+inline uintptr_t& RegisterContextInstructionPointer(
+    x86_thread_state64_t* context) {
+  return AsUintPtr(&context->__rip);
+}
+
 #else  // #if defined(OS_WIN)
 
 // Placeholders for other platforms.
 struct RegisterContext {
   uintptr_t stack_pointer;
   uintptr_t frame_pointer;
+  uintptr_t instruction_pointer;
 };
 
 inline uintptr_t& RegisterContextStackPointer(RegisterContext* context) {
@@ -82,6 +98,10 @@
   return context->frame_pointer;
 }
 
+inline uintptr_t& RegisterContextInstructionPointer(RegisterContext* context) {
+  return context->instruction_pointer;
+}
+
 #endif  // #if defined(OS_WIN)
 
 #endif  // BASE_PROFILER_REGISTER_CONTEXT_H_
diff --git a/base/profiler/stack_sampler_impl.cc b/base/profiler/stack_sampler_impl.cc
index 563829e..f89be3c 100644
--- a/base/profiler/stack_sampler_impl.cc
+++ b/base/profiler/stack_sampler_impl.cc
@@ -163,6 +163,11 @@
   // fewer.
   stack.reserve(128);
 
+  // Record the first frame from the context values.
+  stack.emplace_back(RegisterContextInstructionPointer(thread_context),
+                     module_cache_->GetModuleForAddress(
+                         RegisterContextInstructionPointer(thread_context)));
+
   thread_delegate_->WalkNativeFrames(thread_context, stack_top, module_cache_,
                                      &stack);
 
diff --git a/base/profiler/thread_delegate.h b/base/profiler/thread_delegate.h
index 17e55a9..d415106 100644
--- a/base/profiler/thread_delegate.h
+++ b/base/profiler/thread_delegate.h
@@ -68,7 +68,9 @@
       RegisterContext* thread_context) = 0;
 
   // Walks the native frames on the stack pointed to by the stack pointer in
-  // |thread_context|, appending the frames to |stack|.
+  // |thread_context|, appending the frames to |stack|. When invoked
+  // stack->back() contains the frame corresponding to the state in
+  // |thread_context|.
   // TODO(wittman): Move the unwinding support into a separate UnwindDelegate.
   virtual UnwindResult WalkNativeFrames(
       RegisterContext* thread_context,
diff --git a/base/profiler/thread_delegate_mac.cc b/base/profiler/thread_delegate_mac.cc
index 1426bd41..33e8a13 100644
--- a/base/profiler/thread_delegate_mac.cc
+++ b/base/profiler/thread_delegate_mac.cc
@@ -231,11 +231,9 @@
     uintptr_t stack_top,
     ModuleCache* module_cache,
     std::vector<ProfileBuilder::Frame>* stack) {
-  // Record the first frame from the context values.
-  unw_word_t instruction_pointer = thread_context->__rip;
-  const ModuleCache::Module* module =
-      module_cache->GetModuleForAddress(instruction_pointer);
-  stack->emplace_back(instruction_pointer, module);
+  // We expect the frame corresponding to the |thread_context| register state to
+  // exist within |stack|.
+  DCHECK_GT(stack->size(), 0u);
 
   // There isn't an official way to create a unw_context other than to create it
   // from the current state of the current thread's stack. Since we're walking a
@@ -250,20 +248,17 @@
   // Avoid an out-of-bounds read bug in libunwind that can crash us in some
   // circumstances. If we're subject to that case, just record the first frame
   // and bail. See MayTriggerUnwInitLocalCrash for details.
-  const ModuleCache::Module* leaf_frame_module =
-      module_cache->GetModuleForAddress(instruction_pointer);
-  if (leaf_frame_module && MayTriggerUnwInitLocalCrash(leaf_frame_module))
+  if (stack->back().module && MayTriggerUnwInitLocalCrash(stack->back().module))
     return UnwindResult::ABORTED;
 
   unw_cursor_t unwind_cursor;
   unw_init_local(&unwind_cursor, &unwind_context);
 
-  bool at_top_frame = true;
   int step_result;
   for (;;) {
     // First frame unwind step, check pre-conditions for attempting a frame
     // unwind.
-    if (!module) {
+    if (!stack->back().module) {
       // There's no loaded module containing the instruction pointer. This is
       // due to either executing code that is not in a module (e.g. V8
       // runtime-generated code), or to a previous bad unwind.
@@ -289,8 +284,8 @@
     // is very fragile. It's a complex DWARF unwind that needs to restore the
     // entire thread context which was saved by the kernel when the interrupt
     // occurred.
-    if (instruction_pointer >= sigtramp_start_ &&
-        instruction_pointer < sigtramp_end_) {
+    if (stack->back().instruction_pointer >= sigtramp_start_ &&
+        stack->back().instruction_pointer < sigtramp_end_) {
       return UnwindResult::ABORTED;
     }
 
@@ -304,13 +299,13 @@
     unw_get_reg(&unwind_cursor, UNW_REG_SP, &prev_stack_pointer);
     step_result = unw_step(&unwind_cursor);
 
-    if (step_result == 0 && at_top_frame) {
+    if (step_result == 0 && stack->size() == 1u) {
       // libunwind is designed to be triggered by user code on their own thread,
       // if it hits a library that has no unwind info for the function that is
       // being executed, it just stops. This isn't a problem in the normal case,
-      // but in this case, it's quite possible that the stack being walked is
-      // stopped in a function that bridges to the kernel and thus is missing
-      // the unwind info.
+      // but in the case where this is the first frame unwind, it's quite
+      // possible that the stack being walked is stopped in a function that
+      // bridges to the kernel and thus is missing the unwind info.
 
       // For now, just unwind the single case where the thread is stopped in a
       // function in libsystem_kernel.
@@ -331,9 +326,11 @@
       return UnwindResult::ABORTED;
 
     // Fourth frame unwind step: record the frame to which we just unwound.
+    unw_word_t instruction_pointer;
     unw_get_reg(&unwind_cursor, UNW_REG_IP, &instruction_pointer);
     unw_word_t stack_pointer;
     unw_get_reg(&unwind_cursor, UNW_REG_SP, &stack_pointer);
+
     // Record the frame if the last step was successful.
     if (step_result > 0 ||
         // libunwind considers the unwind complete and returns 0 if no unwind
@@ -344,8 +341,9 @@
         // whether the stack pointer was moved by unw_step. If so, record the
         // new frame to enable non-native unwinders to continue the unwinding.
         (step_result == 0 && stack_pointer > prev_stack_pointer)) {
-      module = module_cache->GetModuleForAddress(instruction_pointer);
-      stack->emplace_back(instruction_pointer, module);
+      stack->emplace_back(
+          instruction_pointer,
+          module_cache->GetModuleForAddress(instruction_pointer));
     }
 
     // libunwind returns 0 if it can't continue because no unwind info was found
@@ -359,8 +357,6 @@
     // signify that we couldn't unwind further.
     if (step_result == 0)
       return UnwindResult::UNRECOGNIZED_FRAME;
-
-    at_top_frame = false;
   }
 
   NOTREACHED();
diff --git a/base/profiler/thread_delegate_win.cc b/base/profiler/thread_delegate_win.cc
index 5eef627b..e37d560b 100644
--- a/base/profiler/thread_delegate_win.cc
+++ b/base/profiler/thread_delegate_win.cc
@@ -205,15 +205,14 @@
     uintptr_t stack_top,
     ModuleCache* module_cache,
     std::vector<ProfileBuilder::Frame>* stack) {
-  // Record the first frame from the context values.
-  const ModuleCache::Module* module =
-      module_cache->GetModuleForAddress(ContextPC(thread_context));
-  stack->emplace_back(ContextPC(thread_context), module);
+  // We expect the frame corresponding to the |thread_context| register state to
+  // exist within |stack|.
+  DCHECK_GT(stack->size(), 0u);
 
   Win32StackFrameUnwinder frame_unwinder;
   for (;;) {
-    if (!module) {
-      // There's no loaded module containing the instruction pointer. This can
+    if (!stack->back().module) {
+      // There's no loaded module corresponding to the current frame. This can
       // be due to executing code that is not in a module (e.g. V8 generated
       // code or runtime-generated code associated with third-party injected
       // DLLs). It can also be due to the the module having been unloaded since
@@ -233,15 +232,18 @@
       return UnwindResult::UNRECOGNIZED_FRAME;
     }
 
-    if (!frame_unwinder.TryUnwind(stack->size() == 1u, thread_context, module))
+    if (!frame_unwinder.TryUnwind(stack->size() == 1u, thread_context,
+                                  stack->back().module)) {
       return UnwindResult::ABORTED;
+    }
 
     if (ContextPC(thread_context) == 0)
       return UnwindResult::COMPLETED;
 
     // Record the frame to which we just unwound.
-    module = module_cache->GetModuleForAddress(ContextPC(thread_context));
-    stack->emplace_back(ContextPC(thread_context), module);
+    stack->emplace_back(
+        ContextPC(thread_context),
+        module_cache->GetModuleForAddress(ContextPC(thread_context)));
   }
 
   NOTREACHED();
diff --git a/base/task/common/intrusive_heap.h b/base/task/common/intrusive_heap.h
index 084afcaf..7e725b1a 100644
--- a/base/task/common/intrusive_heap.h
+++ b/base/task/common/intrusive_heap.h
@@ -54,6 +54,15 @@
     }
   }
 
+  IntrusiveHeap& operator=(IntrusiveHeap&& other) {
+    nodes_ = std::move(other.nodes_);
+    size_ = other.size_;
+
+    other.size_ = 0;
+
+    return *this;
+  }
+
   bool empty() const { return size_ == 0; }
 
   size_t size() const { return size_; }
diff --git a/base/task/sequence_manager/task_queue_selector.cc b/base/task/sequence_manager/task_queue_selector.cc
index 706f8ac..d898f71 100644
--- a/base/task/sequence_manager/task_queue_selector.cc
+++ b/base/task/sequence_manager/task_queue_selector.cc
@@ -181,7 +181,7 @@
   // the highest priority for which we have work, unless we are starving a lower
   // priority.
   TaskQueue::QueuePriority priority = active_priorities_.min_id();
-  bool chose_delayed_over_immediate = false;
+  bool chose_delayed_over_immediate;
 
   // Control tasks are allowed to indefinitely stave out other work and any
   // control tasks we run should not be counted for task starvation purposes.
diff --git a/base/task/sequence_manager/task_queue_selector.h b/base/task/sequence_manager/task_queue_selector.h
index db3e766b8..334237fa 100644
--- a/base/task/sequence_manager/task_queue_selector.h
+++ b/base/task/sequence_manager/task_queue_selector.h
@@ -187,11 +187,12 @@
                                 bool* out_chose_delayed_over_immediate) const {
     // Select an immediate work queue if we are starving immediate tasks.
     if (immediate_starvation_count_ >= kMaxDelayedStarvationTasks) {
+      *out_chose_delayed_over_immediate = false;
       WorkQueue* queue =
-          ChooseImmediateTaskWithPriority<SetOperation>(priority);
+          SetOperation::GetWithPriority(immediate_work_queue_sets_, priority);
       if (queue)
         return queue;
-      return ChooseDelayedTaskWithPriority<SetOperation>(priority);
+      return SetOperation::GetWithPriority(delayed_work_queue_sets_, priority);
     }
     return ChooseImmediateOrDelayedTaskWithPriority<SetOperation>(
         priority, out_chose_delayed_over_immediate);
@@ -209,23 +210,11 @@
 #endif
 
   template <typename SetOperation>
-  WorkQueue* ChooseImmediateTaskWithPriority(
-      TaskQueue::QueuePriority priority) const {
-    return SetOperation::GetWithPriority(immediate_work_queue_sets_, priority);
-  }
-
-  template <typename SetOperation>
-  WorkQueue* ChooseDelayedTaskWithPriority(
-      TaskQueue::QueuePriority priority) const {
-    return SetOperation::GetWithPriority(delayed_work_queue_sets_, priority);
-  }
-
-  template <typename SetOperation>
   WorkQueue* ChooseImmediateOrDelayedTaskWithPriority(
       TaskQueue::QueuePriority priority,
       bool* out_chose_delayed_over_immediate) const {
-    DCHECK_EQ(*out_chose_delayed_over_immediate, false);
     EnqueueOrder immediate_enqueue_order;
+    *out_chose_delayed_over_immediate = false;
     WorkQueue* immediate_queue = SetOperation::GetWithPriorityAndEnqueueOrder(
         immediate_work_queue_sets_, priority, &immediate_enqueue_order);
     if (immediate_queue) {
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index cf56afb..97428587 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -23,4 +23,9 @@
 const Feature kMayBlockWithoutDelay = {"MayBlockWithoutDelay",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
+#if defined(OS_WIN) || defined(OS_MACOSX)
+const Feature kUseNativeThreadPool = {"UseNativeThreadPool",
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
 }  // namespace base
diff --git a/base/task/task_features.h b/base/task/task_features.h
index 2026835..6f70af7 100644
--- a/base/task/task_features.h
+++ b/base/task/task_features.h
@@ -7,6 +7,7 @@
 
 #include "base/base_export.h"
 #include "base/metrics/field_trial_params.h"
+#include "build/build_config.h"
 
 namespace base {
 
@@ -23,6 +24,13 @@
 // instead of waiting for a threshold.
 extern const BASE_EXPORT Feature kMayBlockWithoutDelay;
 
+#if defined(OS_WIN) || defined(OS_MACOSX)
+// Under this feature, TaskScheduler will use a SchedulerWorkerPool backed by a
+// native thread pool implementation. The Windows Thread Pool API and
+// libdispatch are used on Windows and macOS/iOS respectively.
+extern const BASE_EXPORT Feature kUseNativeThreadPool;
+#endif
+
 }  // namespace base
 
 #endif  // BASE_TASK_TASK_FEATURES_H_
diff --git a/base/task/task_scheduler/platform_native_worker_pool.cc b/base/task/task_scheduler/platform_native_worker_pool.cc
index f47efb5..243c413f 100644
--- a/base/task/task_scheduler/platform_native_worker_pool.cc
+++ b/base/task/task_scheduler/platform_native_worker_pool.cc
@@ -39,8 +39,11 @@
 
 PlatformNativeWorkerPool::PlatformNativeWorkerPool(
     TrackedRef<TaskTracker> task_tracker,
-    TrackedRef<Delegate> delegate)
-    : SchedulerWorkerPool(std::move(task_tracker), std::move(delegate)) {}
+    TrackedRef<Delegate> delegate,
+    SchedulerWorkerPool* predecessor_pool)
+    : SchedulerWorkerPool(std::move(task_tracker),
+                          std::move(delegate),
+                          predecessor_pool) {}
 
 PlatformNativeWorkerPool::~PlatformNativeWorkerPool() {
 #if DCHECK_IS_ON()
diff --git a/base/task/task_scheduler/platform_native_worker_pool.h b/base/task/task_scheduler/platform_native_worker_pool.h
index e5a0d86..ca6a639 100644
--- a/base/task/task_scheduler/platform_native_worker_pool.h
+++ b/base/task/task_scheduler/platform_native_worker_pool.h
@@ -29,7 +29,8 @@
 
  protected:
   PlatformNativeWorkerPool(TrackedRef<TaskTracker> task_tracker,
-                           TrackedRef<Delegate> delegate);
+                           TrackedRef<Delegate> delegate,
+                           SchedulerWorkerPool* predecessor_pool);
 
   // Runs a task off the next sequence on the |priority_queue_|. Called by
   // callbacks posted to platform native thread pools.
diff --git a/base/task/task_scheduler/platform_native_worker_pool_mac.h b/base/task/task_scheduler/platform_native_worker_pool_mac.h
index 06c76579..a091808 100644
--- a/base/task/task_scheduler/platform_native_worker_pool_mac.h
+++ b/base/task/task_scheduler/platform_native_worker_pool_mac.h
@@ -26,7 +26,8 @@
     : public PlatformNativeWorkerPool {
  public:
   PlatformNativeWorkerPoolMac(TrackedRef<TaskTracker> task_tracker,
-                              TrackedRef<Delegate> delegate);
+                              TrackedRef<Delegate> delegate,
+                              SchedulerWorkerPool* predecessor_pool = nullptr);
 
   ~PlatformNativeWorkerPoolMac() override;
 
diff --git a/base/task/task_scheduler/platform_native_worker_pool_mac.mm b/base/task/task_scheduler/platform_native_worker_pool_mac.mm
index 3c1acb2..fbccf5d 100644
--- a/base/task/task_scheduler/platform_native_worker_pool_mac.mm
+++ b/base/task/task_scheduler/platform_native_worker_pool_mac.mm
@@ -11,8 +11,11 @@
 
 PlatformNativeWorkerPoolMac::PlatformNativeWorkerPoolMac(
     TrackedRef<TaskTracker> task_tracker,
-    TrackedRef<Delegate> delegate)
-    : PlatformNativeWorkerPool(std::move(task_tracker), std::move(delegate)) {}
+    TrackedRef<Delegate> delegate,
+    SchedulerWorkerPool* predecessor_pool)
+    : PlatformNativeWorkerPool(std::move(task_tracker),
+                               std::move(delegate),
+                               predecessor_pool) {}
 
 PlatformNativeWorkerPoolMac::~PlatformNativeWorkerPoolMac() {}
 
diff --git a/base/task/task_scheduler/platform_native_worker_pool_win.cc b/base/task/task_scheduler/platform_native_worker_pool_win.cc
index 466aff2e..4178f33109 100644
--- a/base/task/task_scheduler/platform_native_worker_pool_win.cc
+++ b/base/task/task_scheduler/platform_native_worker_pool_win.cc
@@ -11,8 +11,11 @@
 
 PlatformNativeWorkerPoolWin::PlatformNativeWorkerPoolWin(
     TrackedRef<TaskTracker> task_tracker,
-    TrackedRef<Delegate> delegate)
-    : PlatformNativeWorkerPool(std::move(task_tracker), std::move(delegate)) {}
+    TrackedRef<Delegate> delegate,
+    SchedulerWorkerPool* predecessor_pool)
+    : PlatformNativeWorkerPool(std::move(task_tracker),
+                               std::move(delegate),
+                               predecessor_pool) {}
 
 PlatformNativeWorkerPoolWin::~PlatformNativeWorkerPoolWin() {
   ::DestroyThreadpoolEnvironment(&environment_);
diff --git a/base/task/task_scheduler/platform_native_worker_pool_win.h b/base/task/task_scheduler/platform_native_worker_pool_win.h
index 0e119ac8a..db94bf0 100644
--- a/base/task/task_scheduler/platform_native_worker_pool_win.h
+++ b/base/task/task_scheduler/platform_native_worker_pool_win.h
@@ -28,7 +28,8 @@
     : public PlatformNativeWorkerPool {
  public:
   PlatformNativeWorkerPoolWin(TrackedRef<TaskTracker> task_tracker,
-                              TrackedRef<Delegate> delegate);
+                              TrackedRef<Delegate> delegate,
+                              SchedulerWorkerPool* predecessor_pool = nullptr);
 
   ~PlatformNativeWorkerPoolWin() override;
 
diff --git a/base/task/task_scheduler/priority_queue.cc b/base/task/task_scheduler/priority_queue.cc
index a2176c7a..00a6954 100644
--- a/base/task/task_scheduler/priority_queue.cc
+++ b/base/task/task_scheduler/priority_queue.cc
@@ -85,6 +85,8 @@
   }
 }
 
+PriorityQueue& PriorityQueue::operator=(PriorityQueue&& other) = default;
+
 void PriorityQueue::Push(scoped_refptr<Sequence> sequence,
                          const SequenceSortKey& sequence_sort_key) {
   container_.insert(SequenceAndSortKey(std::move(sequence), sequence_sort_key));
diff --git a/base/task/task_scheduler/priority_queue.h b/base/task/task_scheduler/priority_queue.h
index 0571de69..3bb7d3db 100644
--- a/base/task/task_scheduler/priority_queue.h
+++ b/base/task/task_scheduler/priority_queue.h
@@ -25,6 +25,8 @@
   PriorityQueue();
   ~PriorityQueue();
 
+  PriorityQueue& operator=(PriorityQueue&& other);
+
   // Inserts |sequence| in the PriorityQueue with |sequence_sort_key|. Note:
   // |sequence_sort_key| is required as a parameter instead of being extracted
   // from |sequence| in Push() to avoid this Transaction having a lock
@@ -80,8 +82,8 @@
 
   ContainerType container_;
 
-  size_t num_sequences_per_priority_[static_cast<int>(TaskPriority::HIGHEST) +
-                                     1] = {};
+  std::array<size_t, static_cast<int>(TaskPriority::HIGHEST) + 1>
+      num_sequences_per_priority_ = {};
 
   // Should only be enabled by EnableFlushSequencesOnDestroyForTesting().
   bool is_flush_sequences_on_destroy_enabled_ = false;
diff --git a/base/task/task_scheduler/scheduler_worker_pool.cc b/base/task/task_scheduler/scheduler_worker_pool.cc
index 96cd114..14e5e36 100644
--- a/base/task/task_scheduler/scheduler_worker_pool.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool.cc
@@ -49,8 +49,11 @@
 }
 
 SchedulerWorkerPool::SchedulerWorkerPool(TrackedRef<TaskTracker> task_tracker,
-                                         TrackedRef<Delegate> delegate)
-    : task_tracker_(std::move(task_tracker)), delegate_(std::move(delegate)) {
+                                         TrackedRef<Delegate> delegate,
+                                         SchedulerWorkerPool* predecessor_pool)
+    : task_tracker_(std::move(task_tracker)),
+      delegate_(std::move(delegate)),
+      lock_(predecessor_pool ? &predecessor_pool->lock_ : nullptr) {
   DCHECK(task_tracker_);
 }
 
@@ -72,6 +75,11 @@
 
 void SchedulerWorkerPool::OnCanScheduleSequence(
     scoped_refptr<Sequence> sequence) {
+  if (replacement_pool_) {
+    replacement_pool_->OnCanScheduleSequence(std::move(sequence));
+    return;
+  }
+
   PushSequenceAndWakeUpWorkers(
       SequenceAndTransaction::FromSequence(std::move(sequence)));
 }
@@ -134,10 +142,19 @@
     BaseScopedWorkersExecutor* executor,
     SequenceAndTransaction sequence_and_transaction) {
   AutoSchedulerLock auto_lock(lock_);
+  DCHECK(!replacement_pool_);
   priority_queue_.Push(std::move(sequence_and_transaction.sequence),
                        sequence_and_transaction.transaction.GetSortKey());
   EnsureEnoughWorkersLockRequired(executor);
 }
 
+void SchedulerWorkerPool::InvalidateAndHandoffAllSequencesToOtherPool(
+    SchedulerWorkerPool* destination_pool) {
+  AutoSchedulerLock current_pool_lock(lock_);
+  AutoSchedulerLock destination_pool_lock(destination_pool->lock_);
+  destination_pool->priority_queue_ = std::move(priority_queue_);
+  replacement_pool_ = destination_pool;
+}
+
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/task_scheduler/scheduler_worker_pool.h b/base/task/task_scheduler/scheduler_worker_pool.h
index d5cb31e..bb6cf27c 100644
--- a/base/task/task_scheduler/scheduler_worker_pool.h
+++ b/base/task/task_scheduler/scheduler_worker_pool.h
@@ -76,6 +76,15 @@
   virtual void PushSequenceAndWakeUpWorkers(
       SequenceAndTransaction sequence_and_transaction) = 0;
 
+  // Removes all sequences from this pool's PriorityQueue and enqueues them in
+  // another |destination_pool|. After this method is called, any sequences
+  // posted to this pool will be forwarded to |destination_pool|.
+  //
+  // TODO(crbug.com/756547): Remove this method once the UseNativeThreadPool
+  // experiment is complete.
+  void InvalidateAndHandoffAllSequencesToOtherPool(
+      SchedulerWorkerPool* destination_pool);
+
   // Prevents new tasks from starting to run and waits for currently running
   // tasks to complete their execution. It is guaranteed that no thread will do
   // work on behalf of this SchedulerWorkerPool after this returns. It is
@@ -123,8 +132,19 @@
     DISALLOW_COPY_AND_ASSIGN(ScopedReenqueueExecutor);
   };
 
+  // |predecessor_pool| is a pool whose lock can be acquired before the
+  // constructed pool's lock. This is necessary to move all sequences from
+  // |predecessor_pool| to the constructed pool and support the
+  // UseNativeThreadPool experiment.
+  //
+  // TODO(crbug.com/756547): Remove |predecessor_pool| once the experiment is
+  // complete.
   SchedulerWorkerPool(TrackedRef<TaskTracker> task_tracker,
-                      TrackedRef<Delegate> delegate);
+                      TrackedRef<Delegate> delegate,
+                      SchedulerWorkerPool* predecessor_pool = nullptr);
+
+  const TrackedRef<TaskTracker> task_tracker_;
+  const TrackedRef<Delegate> delegate_;
 
   // Ensures that there are enough workers to run queued sequences. |executor|
   // is forwarded from the one received in PushSequenceAndWakeUpWorkersImpl()
@@ -155,8 +175,10 @@
   // PriorityQueue from which all threads of this worker pool get work.
   PriorityQueue priority_queue_ GUARDED_BY(lock_);
 
-  const TrackedRef<TaskTracker> task_tracker_;
-  const TrackedRef<Delegate> delegate_;
+  // If |replacement_pool_| is non-null, this pool is invalid and all sequences
+  // should be scheduled on |replacement_pool_|. Used to support the
+  // UseNativeThreadPool experiment.
+  SchedulerWorkerPool* replacement_pool_ = nullptr;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(SchedulerWorkerPool);
diff --git a/base/task/task_scheduler/scheduler_worker_pool_impl.cc b/base/task/task_scheduler/scheduler_worker_pool_impl.cc
index e961e30..280d9b86 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_impl.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool_impl.cc
@@ -399,6 +399,8 @@
     SchedulerWorkerObserver* scheduler_worker_observer,
     WorkerEnvironment worker_environment,
     Optional<TimeDelta> may_block_threshold) {
+  DCHECK(!replacement_pool_);
+
   ScopedWorkersExecutor executor(this);
 
   AutoSchedulerLock auto_lock(lock_);
diff --git a/base/task/task_scheduler/scheduler_worker_pool_unittest.cc b/base/task/task_scheduler/scheduler_worker_pool_unittest.cc
index 6d208a4..14f7a504 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_unittest.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool_unittest.cc
@@ -56,15 +56,8 @@
 constexpr size_t kNumThreadsPostingTasks = 4;
 constexpr size_t kNumTasksPostedPerThread = 150;
 
-enum class PoolType {
-  GENERIC,
-#if defined(OS_WIN) || defined(OS_MACOSX)
-  NATIVE,
-#endif
-};
-
 struct PoolExecutionType {
-  PoolType pool_type;
+  test::PoolType pool_type;
   test::ExecutionMode execution_mode;
 };
 
@@ -128,14 +121,14 @@
   void CreateWorkerPool() {
     ASSERT_FALSE(worker_pool_);
     switch (GetParam().pool_type) {
-      case PoolType::GENERIC:
+      case test::PoolType::GENERIC:
         worker_pool_ = std::make_unique<SchedulerWorkerPoolImpl>(
             "TestWorkerPool", "A", ThreadPriority::NORMAL,
             task_tracker_.GetTrackedRef(),
             tracked_ref_factory_.GetTrackedRef());
         break;
 #if defined(OS_WIN) || defined(OS_MACOSX)
-      case PoolType::NATIVE:
+      case test::PoolType::NATIVE:
         worker_pool_ = std::make_unique<PlatformNativeWorkerPoolType>(
             task_tracker_.GetTrackedRef(),
             tracked_ref_factory_.GetTrackedRef());
@@ -150,7 +143,7 @@
   void StartWorkerPool() {
     ASSERT_TRUE(worker_pool_);
     switch (GetParam().pool_type) {
-      case PoolType::GENERIC: {
+      case test::PoolType::GENERIC: {
         SchedulerWorkerPoolImpl* scheduler_worker_pool_impl =
             static_cast<SchedulerWorkerPoolImpl*>(worker_pool_.get());
         scheduler_worker_pool_impl->Start(
@@ -160,7 +153,7 @@
         break;
       }
 #if defined(OS_WIN) || defined(OS_MACOSX)
-      case PoolType::NATIVE: {
+      case test::PoolType::NATIVE: {
         PlatformNativeWorkerPoolType* scheduler_worker_pool_native_impl =
             static_cast<PlatformNativeWorkerPoolType*>(worker_pool_.get());
         scheduler_worker_pool_native_impl->Start();
@@ -414,23 +407,24 @@
 INSTANTIATE_TEST_SUITE_P(GenericParallel,
                          TaskSchedulerWorkerPoolTest,
                          ::testing::Values(PoolExecutionType{
-                             PoolType::GENERIC,
+                             test::PoolType::GENERIC,
                              test::ExecutionMode::PARALLEL}));
 INSTANTIATE_TEST_SUITE_P(GenericSequenced,
                          TaskSchedulerWorkerPoolTest,
                          ::testing::Values(PoolExecutionType{
-                             PoolType::GENERIC,
+                             test::PoolType::GENERIC,
                              test::ExecutionMode::SEQUENCED}));
 
 #if defined(OS_WIN) || defined(OS_MACOSX)
 INSTANTIATE_TEST_SUITE_P(NativeParallel,
                          TaskSchedulerWorkerPoolTest,
                          ::testing::Values(PoolExecutionType{
-                             PoolType::NATIVE, test::ExecutionMode::PARALLEL}));
+                             test::PoolType::NATIVE,
+                             test::ExecutionMode::PARALLEL}));
 INSTANTIATE_TEST_SUITE_P(NativeSequenced,
                          TaskSchedulerWorkerPoolTest,
                          ::testing::Values(PoolExecutionType{
-                             PoolType::NATIVE,
+                             test::PoolType::NATIVE,
                              test::ExecutionMode::SEQUENCED}));
 #endif
 
diff --git a/base/task/task_scheduler/task_scheduler_impl.cc b/base/task/task_scheduler/task_scheduler_impl.cc
index ff50bc8..df1b4bd 100644
--- a/base/task/task_scheduler/task_scheduler_impl.cc
+++ b/base/task/task_scheduler/task_scheduler_impl.cc
@@ -83,6 +83,9 @@
   // Reset worker pools to release held TrackedRefs, which block teardown.
   foreground_pool_.reset();
   background_pool_.reset();
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  native_foreground_pool_.reset();
+#endif
 }
 
 void TaskSchedulerImpl::Start(
@@ -95,6 +98,16 @@
   if (FeatureList::IsEnabled(kAllTasksUserBlocking))
     all_tasks_user_blocking_.Set();
 
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  if (FeatureList::IsEnabled(kUseNativeThreadPool)) {
+    native_foreground_pool_.emplace(task_tracker_->GetTrackedRef(),
+                                    tracked_ref_factory_.GetTrackedRef(),
+                                    &foreground_pool_.value());
+    foreground_pool_->InvalidateAndHandoffAllSequencesToOtherPool(
+        &native_foreground_pool_.value());
+  }
+#endif
+
   // Start the service thread. On platforms that support it (POSIX except NaCL
   // SFI), the service thread runs a MessageLoopForIO which is used to support
   // FileDescriptorWatcher in the scope in which tasks run.
@@ -131,18 +144,26 @@
       SchedulerWorkerPoolImpl::WorkerEnvironment::NONE;
 #endif
 
-  // On platforms that can't use the background thread priority, best-effort
-  // tasks run in foreground pools. A cap is set on the number of background
-  // tasks that can run in foreground pools to ensure that there is always room
-  // for incoming foreground tasks and to minimize the performance impact of
-  // best-effort tasks.
-  const int max_best_effort_tasks_in_foreground_pool = std::max(
-      1, std::min(init_params.background_worker_pool_params.max_tasks(),
-                  init_params.foreground_worker_pool_params.max_tasks() / 2));
-  foreground_pool_->Start(init_params.foreground_worker_pool_params,
-                          max_best_effort_tasks_in_foreground_pool,
-                          service_thread_task_runner, scheduler_worker_observer,
-                          worker_environment);
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  if (native_foreground_pool_) {
+    native_foreground_pool_->Start();
+  } else
+#endif
+  {
+    // On platforms that can't use the background thread priority, best-effort
+    // tasks run in foreground pools. A cap is set on the number of background
+    // tasks that can run in foreground pools to ensure that there is always
+    // room for incoming foreground tasks and to minimize the performance impact
+    // of best-effort tasks.
+
+    const int max_best_effort_tasks_in_foreground_pool = std::max(
+        1, std::min(init_params.background_worker_pool_params.max_tasks(),
+                    init_params.foreground_worker_pool_params.max_tasks() / 2));
+    foreground_pool_->Start(init_params.foreground_worker_pool_params,
+                            max_best_effort_tasks_in_foreground_pool,
+                            service_thread_task_runner,
+                            scheduler_worker_observer, worker_environment);
+  }
 
   if (background_pool_.has_value()) {
     background_pool_->Start(
@@ -235,7 +256,7 @@
   // https://crbug.com/771701.
   service_thread_->Stop();
   single_thread_task_runner_manager_.JoinForTesting();
-  foreground_pool_->JoinForTesting();
+  GetForegroundWorkerPool()->JoinForTesting();
   if (background_pool_.has_value())
     background_pool_->JoinForTesting();
 #if DCHECK_IS_ON()
@@ -330,6 +351,20 @@
       background_pool_.has_value()) {
     return &background_pool_.value();
   }
+
+  return GetForegroundWorkerPool();
+}
+
+const SchedulerWorkerPool* TaskSchedulerImpl::GetForegroundWorkerPool() const {
+  return const_cast<TaskSchedulerImpl*>(this)->GetForegroundWorkerPool();
+}
+
+SchedulerWorkerPool* TaskSchedulerImpl::GetForegroundWorkerPool() {
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  if (native_foreground_pool_) {
+    return &native_foreground_pool_.value();
+  }
+#endif
   return &foreground_pool_.value();
 }
 
@@ -341,7 +376,7 @@
 }
 
 void TaskSchedulerImpl::ReportHeartbeatMetrics() const {
-  foreground_pool_->ReportHeartbeatMetrics();
+  GetForegroundWorkerPool()->ReportHeartbeatMetrics();
   if (background_pool_.has_value())
     background_pool_->ReportHeartbeatMetrics();
 }
diff --git a/base/task/task_scheduler/task_scheduler_impl.h b/base/task/task_scheduler/task_scheduler_impl.h
index 528101d..19dc61a1 100644
--- a/base/task/task_scheduler/task_scheduler_impl.h
+++ b/base/task/task_scheduler/task_scheduler_impl.h
@@ -33,9 +33,14 @@
 #endif
 
 #if defined(OS_WIN)
+#include "base/task/task_scheduler/platform_native_worker_pool_win.h"
 #include "base/win/com_init_check_hook.h"
 #endif
 
+#if defined(OS_MACOSX)
+#include "base/task/task_scheduler/platform_native_worker_pool_mac.h"
+#endif
+
 namespace base {
 
 class Thread;
@@ -103,6 +108,10 @@
 
   void ReportHeartbeatMetrics() const;
 
+  // Returns the thread pool responsible for foreground execution.
+  const SchedulerWorkerPool* GetForegroundWorkerPool() const;
+  SchedulerWorkerPool* GetForegroundWorkerPool();
+
   const SchedulerWorkerPool* GetWorkerPoolForTraits(
       const TaskTraits& traits) const;
 
@@ -133,6 +142,12 @@
   Optional<SchedulerWorkerPoolImpl> foreground_pool_;
   Optional<SchedulerWorkerPoolImpl> background_pool_;
 
+#if defined(OS_WIN)
+  Optional<PlatformNativeWorkerPoolWin> native_foreground_pool_;
+#elif defined(OS_MACOSX)
+  Optional<PlatformNativeWorkerPoolMac> native_foreground_pool_;
+#endif
+
 #if DCHECK_IS_ON()
   // Set once JoinForTesting() has returned.
   AtomicFlag join_for_testing_returned_;
diff --git a/base/task/task_scheduler/task_scheduler_impl_unittest.cc b/base/task/task_scheduler/task_scheduler_impl_unittest.cc
index dd57776..506f24e2 100644
--- a/base/task/task_scheduler/task_scheduler_impl_unittest.cc
+++ b/base/task/task_scheduler/task_scheduler_impl_unittest.cc
@@ -61,11 +61,13 @@
 
 struct TaskSchedulerImplTestParams {
   TaskSchedulerImplTestParams(const TaskTraits& traits,
-                              test::ExecutionMode execution_mode)
-      : traits(traits), execution_mode(execution_mode) {}
+                              test::ExecutionMode execution_mode,
+                              test::PoolType pool_type)
+      : traits(traits), execution_mode(execution_mode), pool_type(pool_type) {}
 
   TaskTraits traits;
   test::ExecutionMode execution_mode;
+  test::PoolType pool_type;
 };
 
 #if DCHECK_IS_ON()
@@ -80,7 +82,7 @@
 // Verify that the current thread priority and I/O restrictions are appropriate
 // to run a Task with |traits|.
 // Note: ExecutionMode is verified inside TestTaskFactory.
-void VerifyTaskEnvironment(const TaskTraits& traits) {
+void VerifyTaskEnvironment(const TaskTraits& traits, test::PoolType pool_type) {
   EXPECT_EQ(CanUseBackgroundPriorityForSchedulerWorker() &&
                     traits.priority() == TaskPriority::BEST_EFFORT
                 ? ThreadPriority::BACKGROUND
@@ -93,60 +95,71 @@
   EXPECT_EQ(traits.may_block(), GetIOAllowed());
 #endif
 
-  // Verify that the thread the task is running on is named as expected.
   const std::string current_thread_name(PlatformThread::GetName());
+  const bool is_single_threaded =
+      (current_thread_name.find("SingleThread") != std::string::npos);
+  const bool is_best_effort = (traits.priority() == TaskPriority::BEST_EFFORT);
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  // Native thread pools do not provide the ability to name threads.
+  if (pool_type == test::PoolType::NATIVE && !is_single_threaded &&
+      !is_best_effort) {
+    return;
+  }
+#endif
+
+  // Verify that the thread the task is running on is named as expected.
   EXPECT_NE(std::string::npos, current_thread_name.find("TaskScheduler"));
 
-  if (current_thread_name.find("SingleThread") != std::string::npos) {
+  if (is_single_threaded) {
     // For now, single-threaded best-effort tasks run on their own threads.
     // TODO(fdoray): Run single-threaded best-effort tasks on foreground workers
     // on platforms that don't support background thread priority.
     EXPECT_NE(
         std::string::npos,
-        current_thread_name.find(traits.priority() == TaskPriority::BEST_EFFORT
-                                     ? "Background"
-                                     : "Foreground"));
-  } else {
-    EXPECT_NE(std::string::npos,
-              current_thread_name.find(
-                  CanUseBackgroundPriorityForSchedulerWorker() &&
-                          traits.priority() == TaskPriority::BEST_EFFORT
-                      ? "Background"
-                      : "Foreground"));
-  }
-  if (current_thread_name.find("SingleThread") == std::string::npos) {
-    EXPECT_EQ(std::string::npos, current_thread_name.find("Blocking"));
-  } else {
+        current_thread_name.find(is_best_effort ? "Background" : "Foreground"));
+
     // SingleThread workers discriminate blocking/non-blocking tasks.
     EXPECT_EQ(traits.may_block(),
               current_thread_name.find("Blocking") != std::string::npos);
+  } else {
+    EXPECT_NE(std::string::npos,
+              current_thread_name.find(
+                  CanUseBackgroundPriorityForSchedulerWorker() && is_best_effort
+                      ? "Background"
+                      : "Foreground"));
+
+    EXPECT_EQ(std::string::npos, current_thread_name.find("Blocking"));
   }
 }
 
 void VerifyTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
+                                         test::PoolType pool_type,
                                          WaitableEvent* event) {
   DCHECK(event);
-  VerifyTaskEnvironment(traits);
+  VerifyTaskEnvironment(traits, pool_type);
   event->Signal();
 }
 
 void VerifyTimeAndTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
+                                                test::PoolType pool_type,
                                                 TimeTicks expected_time,
                                                 WaitableEvent* event) {
   DCHECK(event);
   EXPECT_LE(expected_time, TimeTicks::Now());
-  VerifyTaskEnvironment(traits);
+  VerifyTaskEnvironment(traits, pool_type);
   event->Signal();
 }
 
 void VerifyOrderAndTaskEnvironmentAndSignalEvent(
     const TaskTraits& traits,
+    test::PoolType pool_type,
     WaitableEvent* expected_previous_event,
     WaitableEvent* event) {
   DCHECK(event);
   if (expected_previous_event)
     EXPECT_TRUE(expected_previous_event->IsSignaled());
-  VerifyTaskEnvironment(traits);
+  VerifyTaskEnvironment(traits, pool_type);
   event->Signal();
 }
 
@@ -176,9 +189,11 @@
   // |execution_mode|.
   ThreadPostingTasks(TaskSchedulerImpl* scheduler,
                      const TaskTraits& traits,
+                     test::PoolType pool_type,
                      test::ExecutionMode execution_mode)
       : SimpleThread("ThreadPostingTasks"),
         traits_(traits),
+        pool_type_(pool_type),
         factory_(CreateTaskRunnerWithTraitsAndExecutionMode(scheduler,
                                                             traits,
                                                             execution_mode),
@@ -193,18 +208,19 @@
     const size_t kNumTasksPerThread = 150;
     for (size_t i = 0; i < kNumTasksPerThread; ++i) {
       factory_.PostTask(test::TestTaskFactory::PostNestedTask::NO,
-                        BindOnce(&VerifyTaskEnvironment, traits_));
+                        BindOnce(&VerifyTaskEnvironment, traits_, pool_type_));
     }
   }
 
   const TaskTraits traits_;
+  test::PoolType pool_type_;
   test::TestTaskFactory factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ThreadPostingTasks);
 };
 
 // Returns a vector with a TaskSchedulerImplTestParams for each valid
-// combination of {ExecutionMode, TaskPriority, MayBlock()}.
+// combination of {PoolType, ExecutionMode, TaskPriority, MayBlock()}.
 std::vector<TaskSchedulerImplTestParams> GetTaskSchedulerImplTestParams() {
   std::vector<TaskSchedulerImplTestParams> params;
 
@@ -212,14 +228,24 @@
       test::ExecutionMode::PARALLEL, test::ExecutionMode::SEQUENCED,
       test::ExecutionMode::SINGLE_THREADED};
 
-  for (test::ExecutionMode execution_mode : execution_modes) {
-    for (size_t priority_index = static_cast<size_t>(TaskPriority::LOWEST);
-         priority_index <= static_cast<size_t>(TaskPriority::HIGHEST);
-         ++priority_index) {
-      const TaskPriority priority = static_cast<TaskPriority>(priority_index);
-      params.push_back(TaskSchedulerImplTestParams({priority}, execution_mode));
-      params.push_back(
-          TaskSchedulerImplTestParams({priority, MayBlock()}, execution_mode));
+  const test::PoolType pool_types[] = {
+    test::PoolType::GENERIC,
+#if defined(OS_WIN) || defined(OS_MACOSX)
+    test::PoolType::NATIVE,
+#endif
+  };
+
+  for (test::PoolType pool_type : pool_types) {
+    for (test::ExecutionMode execution_mode : execution_modes) {
+      for (size_t priority_index = static_cast<size_t>(TaskPriority::LOWEST);
+           priority_index <= static_cast<size_t>(TaskPriority::HIGHEST);
+           ++priority_index) {
+        const TaskPriority priority = static_cast<TaskPriority>(priority_index);
+        params.push_back(
+            TaskSchedulerImplTestParams({priority}, execution_mode, pool_type));
+        params.push_back(TaskSchedulerImplTestParams(
+            {priority, MayBlock()}, execution_mode, pool_type));
+      }
     }
   }
 
@@ -232,7 +258,7 @@
   TaskSchedulerImplTest() : scheduler_("Test") {}
 
   void EnableAllTasksUserBlocking() {
-    feature_list_.InitWithFeatures({kAllTasksUserBlocking}, {});
+    should_enable_all_tasks_user_blocking_ = true;
   }
 
   void set_scheduler_worker_observer(
@@ -244,6 +270,8 @@
     constexpr int kMaxNumBackgroundThreads = 1;
     constexpr int kMaxNumForegroundThreads = 4;
 
+    SetupFeatures();
+
     scheduler_.Start({{kMaxNumBackgroundThreads, reclaim_time},
                       {kMaxNumForegroundThreads, reclaim_time}},
                      scheduler_worker_observer_);
@@ -261,9 +289,25 @@
   TaskSchedulerImpl scheduler_;
 
  private:
+  void SetupFeatures() {
+    std::vector<base::Feature> features;
+
+    if (should_enable_all_tasks_user_blocking_)
+      features.push_back(kAllTasksUserBlocking);
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+    if (GetParam().pool_type == test::PoolType::NATIVE)
+      features.push_back(kUseNativeThreadPool);
+#endif
+
+    if (!features.empty())
+      feature_list_.InitWithFeatures(features, {});
+  }
+
   base::test::ScopedFeatureList feature_list_;
   SchedulerWorkerObserver* scheduler_worker_observer_ = nullptr;
   bool did_tear_down_ = false;
+  bool should_enable_all_tasks_user_blocking_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TaskSchedulerImplTest);
 };
@@ -279,7 +323,7 @@
   scheduler_.PostDelayedTaskWithTraits(
       FROM_HERE, GetParam().traits,
       BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits,
-               Unretained(&task_ran)),
+               GetParam().pool_type, Unretained(&task_ran)),
       TimeDelta());
   task_ran.Wait();
 }
@@ -294,6 +338,7 @@
   scheduler_.PostDelayedTaskWithTraits(
       FROM_HERE, GetParam().traits,
       BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetParam().traits,
+               GetParam().pool_type,
                TimeTicks::Now() + TestTimeouts::tiny_timeout(),
                Unretained(&task_ran)),
       TestTimeouts::tiny_timeout());
@@ -314,7 +359,8 @@
   const size_t kNumTasksPerTest = 150;
   for (size_t i = 0; i < kNumTasksPerTest; ++i) {
     factory.PostTask(test::TestTaskFactory::PostNestedTask::NO,
-                     BindOnce(&VerifyTaskEnvironment, GetParam().traits));
+                     BindOnce(&VerifyTaskEnvironment, GetParam().traits,
+                              GetParam().pool_type));
   }
 
   factory.WaitForAllTasksToRun();
@@ -327,7 +373,7 @@
   scheduler_.PostDelayedTaskWithTraits(
       FROM_HERE, GetParam().traits,
       BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits,
-               Unretained(&task_running)),
+               GetParam().pool_type, Unretained(&task_running)),
       TimeDelta());
 
   // Wait a little bit to make sure that the task doesn't run before Start().
@@ -348,6 +394,7 @@
   scheduler_.PostDelayedTaskWithTraits(
       FROM_HERE, GetParam().traits,
       BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetParam().traits,
+               GetParam().pool_type,
                TimeTicks::Now() + TestTimeouts::tiny_timeout(),
                Unretained(&task_running)),
       TestTimeouts::tiny_timeout());
@@ -370,7 +417,7 @@
   CreateTaskRunnerWithTraitsAndExecutionMode(&scheduler_, GetParam().traits,
                                              GetParam().execution_mode)
       ->PostTask(FROM_HERE, BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
-                                     GetParam().traits,
+                                     GetParam().traits, GetParam().pool_type,
                                      Unretained(&task_running)));
 
   // Wait a little bit to make sure that the task doesn't run before Start().
@@ -400,7 +447,7 @@
                  BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
                           TaskTraits::Override(GetParam().traits,
                                                {TaskPriority::USER_BLOCKING}),
-                          Unretained(&task_running)));
+                          GetParam().pool_type, Unretained(&task_running)));
   task_running.Wait();
 }
 
@@ -418,7 +465,7 @@
       BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
                TaskTraits::Override(GetParam().traits,
                                     {TaskPriority::USER_BLOCKING}),
-               Unretained(&task_running)),
+               GetParam().pool_type, Unretained(&task_running)),
       TimeDelta());
   task_running.Wait();
 }
@@ -455,12 +502,12 @@
 // TaskTraits and ExecutionModes. Verifies that each Task runs on a thread with
 // the expected priority and I/O restrictions and respects the characteristics
 // of its ExecutionMode.
-TEST_F(TaskSchedulerImplTest, MultipleTaskSchedulerImplTestParams) {
+TEST_P(TaskSchedulerImplTest, MultipleTaskSchedulerImplTestParams) {
   StartTaskScheduler();
   std::vector<std::unique_ptr<ThreadPostingTasks>> threads_posting_tasks;
   for (const auto& test_params : GetTaskSchedulerImplTestParams()) {
     threads_posting_tasks.push_back(std::make_unique<ThreadPostingTasks>(
-        &scheduler_, test_params.traits,
+        &scheduler_, test_params.traits, GetParam().pool_type,
         test_params.execution_mode));
     threads_posting_tasks.back()->Start();
   }
@@ -471,10 +518,15 @@
   }
 }
 
-TEST_F(TaskSchedulerImplTest,
+TEST_P(TaskSchedulerImplTest,
        GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated) {
   StartTaskScheduler();
 
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  if (GetParam().pool_type == test::PoolType::NATIVE)
+    return;
+#endif
+
   // GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated() does not support
   // TaskPriority::BEST_EFFORT.
   testing::GTEST_FLAG(death_test_style) = "threadsafe";
@@ -499,7 +551,7 @@
 
 // Verify that the RunsTasksInCurrentSequence() method of a SequencedTaskRunner
 // returns false when called from a task that isn't part of the sequence.
-TEST_F(TaskSchedulerImplTest, SequencedRunsTasksInCurrentSequence) {
+TEST_P(TaskSchedulerImplTest, SequencedRunsTasksInCurrentSequence) {
   StartTaskScheduler();
   auto single_thread_task_runner =
       scheduler_.CreateSingleThreadTaskRunnerWithTraits(
@@ -523,7 +575,7 @@
 // Verify that the RunsTasksInCurrentSequence() method of a
 // SingleThreadTaskRunner returns false when called from a task that isn't part
 // of the sequence.
-TEST_F(TaskSchedulerImplTest, SingleThreadRunsTasksInCurrentSequence) {
+TEST_P(TaskSchedulerImplTest, SingleThreadRunsTasksInCurrentSequence) {
   StartTaskScheduler();
   auto sequenced_task_runner =
       scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits());
@@ -546,7 +598,7 @@
 }
 
 #if defined(OS_WIN)
-TEST_F(TaskSchedulerImplTest, COMSTATaskRunnersRunWithCOMSTA) {
+TEST_P(TaskSchedulerImplTest, COMSTATaskRunnersRunWithCOMSTA) {
   StartTaskScheduler();
   auto com_sta_task_runner = scheduler_.CreateCOMSTATaskRunnerWithTraits(
       TaskTraits(), SingleThreadTaskRunnerThreadMode::SHARED);
@@ -563,7 +615,7 @@
 }
 #endif  // defined(OS_WIN)
 
-TEST_F(TaskSchedulerImplTest, DelayedTasksNotRunAfterShutdown) {
+TEST_P(TaskSchedulerImplTest, DelayedTasksNotRunAfterShutdown) {
   StartTaskScheduler();
   // As with delayed tasks in general, this is racy. If the task does happen to
   // run after Shutdown within the timeout, it will fail this test.
@@ -586,7 +638,7 @@
 
 #if defined(OS_POSIX)
 
-TEST_F(TaskSchedulerImplTest, FileDescriptorWatcherNoOpsAfterShutdown) {
+TEST_P(TaskSchedulerImplTest, FileDescriptorWatcherNoOpsAfterShutdown) {
   StartTaskScheduler();
 
   int pipes[2];
@@ -633,7 +685,7 @@
 
 // Verify that tasks posted on the same sequence access the same values on
 // SequenceLocalStorage, and tasks on different sequences see different values.
-TEST_F(TaskSchedulerImplTest, SequenceLocalStorage) {
+TEST_P(TaskSchedulerImplTest, SequenceLocalStorage) {
   StartTaskScheduler();
 
   SequenceLocalStorageSlot<int> slot;
@@ -664,7 +716,7 @@
   scheduler_.FlushForTesting();
 }
 
-TEST_F(TaskSchedulerImplTest, FlushAsyncNoTasks) {
+TEST_P(TaskSchedulerImplTest, FlushAsyncNoTasks) {
   StartTaskScheduler();
   bool called_back = false;
   scheduler_.FlushAsyncForTesting(
@@ -708,7 +760,7 @@
 // Integration test that verifies that workers have a frame on their stacks
 // which easily identifies the type of worker and shutdown behavior (useful to
 // diagnose issues from logs without memory dumps).
-TEST_F(TaskSchedulerImplTest, MAYBE_IdentifiableStacks) {
+TEST_P(TaskSchedulerImplTest, MAYBE_IdentifiableStacks) {
   StartTaskScheduler();
 
   // Shutdown behaviors and expected stack frames.
@@ -788,7 +840,18 @@
   scheduler_.FlushForTesting();
 }
 
-TEST_F(TaskSchedulerImplTest, SchedulerWorkerObserver) {
+TEST_P(TaskSchedulerImplTest, SchedulerWorkerObserver) {
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  // SchedulerWorkers are not created (and hence not observed) when using the
+  // native thread pools. We still start the TaskScheduler in this case since
+  // JoinForTesting is always called on TearDown, and DCHECKs that all worker
+  // pools are started.
+  if (GetParam().pool_type == test::PoolType::NATIVE) {
+    StartTaskScheduler();
+    return;
+  }
+#endif
+
   testing::StrictMock<test::MockSchedulerWorkerObserver> observer;
   set_scheduler_worker_observer(&observer);
 
@@ -922,7 +985,8 @@
   EXPECT_TRUE(was_destroyed);
 }
 
-class TaskSchedulerPriorityUpdateTest : public testing::Test {
+class TaskSchedulerPriorityUpdateTest
+    : public testing::TestWithParam<TaskSchedulerImplTestParams> {
  protected:
   struct PoolBlockingEvents {
     PoolBlockingEvents(const TaskTraits& pool_traits)
@@ -1002,6 +1066,10 @@
 };
 
 // Update the priority of a sequence when it is not scheduled.
+//
+// TODO(adityakeerthi): Parameterize this test once we have a way to prevent
+// sequences from being scheduled without flooding the pool. It is not possible
+// to flood the native pools.
 TEST_F(TaskSchedulerPriorityUpdateTest, UpdatePrioritySequenceNotScheduled) {
   StartTaskSchedulerWithNumThreadsPerPool(1);
 
@@ -1039,6 +1107,7 @@
         FROM_HERE,
         BindOnce(&VerifyOrderAndTaskEnvironmentAndSignalEvent,
                  task_runner_and_events->updated_priority,
+                 test::PoolType::GENERIC,
                  Unretained(task_runner_and_events->expected_previous_event),
                  Unretained(&task_runner_and_events->task_ran)));
   }
@@ -1063,7 +1132,16 @@
 
 // Update the priority of a sequence when it is scheduled, i.e. not currently
 // in a priority queue.
-TEST_F(TaskSchedulerPriorityUpdateTest, UpdatePrioritySequenceScheduled) {
+TEST_P(TaskSchedulerPriorityUpdateTest, UpdatePrioritySequenceScheduled) {
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  base::test::ScopedFeatureList feature_list;
+  if (GetParam().pool_type == test::PoolType::NATIVE) {
+    feature_list.InitWithFeatures({kUseNativeThreadPool}, {});
+  } else {
+    feature_list.InitWithFeatures({}, {kUseNativeThreadPool});
+  }
+#endif
+
   StartTaskSchedulerWithNumThreadsPerPool(5);
 
   CreateTaskRunnersAndEvents();
@@ -1095,6 +1173,7 @@
         FROM_HERE,
         BindOnce(&VerifyOrderAndTaskEnvironmentAndSignalEvent,
                  TaskTraits(task_runner_and_events->updated_priority),
+                 GetParam().pool_type,
                  Unretained(task_runner_and_events->expected_previous_event),
                  Unretained(&task_runner_and_events->task_ran)));
   }
@@ -1108,5 +1187,9 @@
   }
 }
 
+INSTANTIATE_TEST_SUITE_P(OneTaskSchedulerPriorityUpdateTestParams,
+                         TaskSchedulerPriorityUpdateTest,
+                         ::testing::ValuesIn(GetTaskSchedulerImplTestParams()));
+
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/task_scheduler/test_utils.h b/base/task/task_scheduler/test_utils.h
index 9ce6abe..bd82a22 100644
--- a/base/task/task_scheduler/test_utils.h
+++ b/base/task/task_scheduler/test_utils.h
@@ -15,6 +15,7 @@
 #include "base/task/task_traits.h"
 #include "base/task_runner.h"
 #include "base/thread_annotations.h"
+#include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace base {
@@ -72,6 +73,15 @@
 // TODO(etiennep): Migrate to TaskSourceExecutionMode.
 enum class ExecutionMode { PARALLEL, SEQUENCED, SINGLE_THREADED };
 
+// An enumeration of possible pool types. Used to parametrize relevant
+// task_scheduler tests.
+enum class PoolType {
+  GENERIC,
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  NATIVE,
+#endif
+};
+
 // Creates a Sequence with given |traits| and pushes |task| to it. If a
 // TaskRunner is associated with |task|, it should be be passed as |task_runner|
 // along with its |execution_mode|. Returns the created Sequence.
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
index 8776c56a..046e8e8 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
@@ -79,26 +79,22 @@
     @Override
     public Application newApplication(ClassLoader cl, String className, Context context)
             throws ClassNotFoundException, IllegalAccessException, InstantiationException {
-        // The multidex support library doesn't currently support having the test apk be multidex
-        // as well as the under-test apk being multidex. If MultiDex.install() is called for both,
-        // then re-extraction is triggered every time due to the support library caching only a
-        // single timestamp & crc.
-        //
-        // Attempt to install test apk multidex only if the apk-under-test is not multidex.
-        // It will likely continue to be true that the two are mutually exclusive because:
-        // * ProGuard enabled =>
-        //      Under-test apk is single dex.
-        //      Test apk duplicates under-test classes, so may need multidex.
-        // * ProGuard disabled =>
-        //      Under-test apk might be multidex
-        //      Test apk does not duplicate classes, so does not need multidex.
-        // When there is no under-test apk, then Application.onCreate() should trigger multidex
-        // installation.
-        // https://crbug.com/824523
-        if (!BuildConfig.IS_MULTIDEX_ENABLED) {
-            ChromiumMultiDexInstaller.install(new BaseChromiumRunnerCommon.MultiDexContextWrapper(
-                    getContext(), getTargetContext()));
-            BaseChromiumRunnerCommon.reorderDexPathElements(cl, getContext(), getTargetContext());
+        boolean hasUnderTestApk =
+                !getContext().getPackageName().equals(getTargetContext().getPackageName());
+        // When there is an under-test APK, BuildConfig belongs to it and does not indicate whether
+        // the test apk is multidex. In this case, just assume it is.
+        boolean isTestMultidex = hasUnderTestApk || BuildConfig.IS_MULTIDEX_ENABLED;
+        if (isTestMultidex) {
+            if (hasUnderTestApk) {
+                // Need hacks to have multidex work when there is an under-test apk :(.
+                ChromiumMultiDexInstaller.install(
+                        new BaseChromiumRunnerCommon.MultiDexContextWrapper(
+                                getContext(), getTargetContext()));
+                BaseChromiumRunnerCommon.reorderDexPathElements(
+                        cl, getContext(), getTargetContext());
+            } else {
+                ChromiumMultiDexInstaller.install(getContext());
+            }
         }
         return super.newApplication(cl, className, context);
     }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
index e5eb2731..31ae7dd 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
@@ -35,7 +35,7 @@
      */
     @MainDex
     static class MultiDexContextWrapper extends ContextWrapper {
-        private Context mAppContext;
+        private final Context mAppContext;
 
         MultiDexContextWrapper(Context instrContext, Context appContext) {
             super(instrContext);
@@ -49,7 +49,8 @@
 
         @Override
         public SharedPreferences getSharedPreferences(String name, int mode) {
-            return mAppContext.getSharedPreferences(name, mode);
+            // Prefix so as to not conflict with main app's multidex prefs file.
+            return mAppContext.getSharedPreferences("test-" + name, mode);
         }
 
         @Override
diff --git a/base/win/scoped_hstring.cc b/base/win/scoped_hstring.cc
index d4c01f24..7a046c2 100644
--- a/base/win/scoped_hstring.cc
+++ b/base/win/scoped_hstring.cc
@@ -7,6 +7,7 @@
 #include <winstring.h>
 
 #include "base/numerics/safe_conversions.h"
+#include "base/process/memory.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 
@@ -83,6 +84,10 @@
 
 namespace win {
 
+ScopedHString::ScopedHString(HSTRING hstr) : ScopedGeneric(hstr) {
+  DCHECK(g_load_succeeded);
+}
+
 // static
 ScopedHString ScopedHString::Create(WStringPiece str) {
   DCHECK(g_load_succeeded);
@@ -91,18 +96,20 @@
       str.data(), checked_cast<UINT32>(str.length()), &hstr);
   if (SUCCEEDED(hr))
     return ScopedHString(hstr);
+  if (hr == E_OUTOFMEMORY) {
+    // This size is an approximation. The actual size likely includes
+    // sizeof(HSTRING_HEADER) as well.
+    base::TerminateBecauseOutOfMemory((str.length() + 1) * sizeof(wchar_t));
+  }
   DLOG(ERROR) << "Failed to create HSTRING" << std::hex << hr;
   return ScopedHString(nullptr);
 }
 
+// static
 ScopedHString ScopedHString::Create(StringPiece str) {
   return Create(UTF8ToWide(str));
 }
 
-ScopedHString::ScopedHString(HSTRING hstr) : ScopedGeneric(hstr) {
-  DCHECK(g_load_succeeded);
-}
-
 // static
 bool ScopedHString::ResolveCoreWinRTStringDelayload() {
   // TODO(finnur): Add AssertIOAllowed once crbug.com/770193 is fixed.
diff --git a/base/win/scoped_hstring.h b/base/win/scoped_hstring.h
index 14ec0f2d..4635b87 100644
--- a/base/win/scoped_hstring.h
+++ b/base/win/scoped_hstring.h
@@ -63,7 +63,12 @@
   // Loads all required HSTRING functions, available from Win8 and onwards.
   static bool ResolveCoreWinRTStringDelayload();
 
+  // Returns a view into the memory buffer managed by the instance. The returned
+  // StringPiece is only valid during the lifetime of this ScopedHString
+  // instance.
   WStringPiece Get() const;
+
+  // Returns a copy of the instance as a UTF-8 string.
   std::string GetAsUTF8() const;
 };
 
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index ec273c63c..72bcec92 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -939,6 +939,11 @@
                     help='Dump the Markdown .build_config format documentation '
                     'then exit immediately.')
 
+  parser.add_option(
+      '--base-module-build-config',
+      help='Path to the base module\'s build config '
+      'if this is a feature module.')
+
   options, args = parser.parse_args(argv)
 
   if args:
@@ -1049,6 +1054,11 @@
   all_resources_deps = deps.All('android_resources')
   all_classpath_library_deps = classpath_deps.All('java_library')
 
+  base_module_build_config = None
+  if options.base_module_build_config:
+    with open(options.base_module_build_config, 'r') as f:
+      base_module_build_config = json.load(f)
+
   # Initialize some common config.
   # Any value that needs to be queryable by dependents must go within deps_info.
   config = {
@@ -1233,16 +1243,36 @@
       'android_resources', 'android_apk', 'junit_binary', 'resource_rewriter',
       'dist_aar', 'android_app_bundle_module'):
     config['resources'] = {}
-    config['resources']['dependency_zips'] = [
-        c['resources_zip'] for c in all_resources_deps]
+
+    dependency_zips = [
+        c['resources_zip'] for c in all_resources_deps if c['resources_zip']
+    ]
     extra_package_names = []
     extra_r_text_files = []
+
     if options.type != 'android_resources':
       extra_package_names = [
           c['package_name'] for c in all_resources_deps if 'package_name' in c]
       extra_r_text_files = [
           c['r_text'] for c in all_resources_deps if 'r_text' in c]
 
+    # For feature modules, remove any resources that already exist in the base
+    # module.
+    if base_module_build_config:
+      dependency_zips = [
+          c for c in dependency_zips
+          if c not in base_module_build_config['resources']['dependency_zips']
+      ]
+      extra_package_names = [
+          c for c in extra_package_names if c not in
+          base_module_build_config['resources']['extra_package_names']
+      ]
+      extra_r_text_files = [
+          c for c in extra_r_text_files if c not in
+          base_module_build_config['resources']['extra_r_text_files']
+      ]
+
+    config['resources']['dependency_zips'] = dependency_zips
     config['resources']['extra_package_names'] = extra_package_names
     config['resources']['extra_r_text_files'] = extra_r_text_files
 
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 4a93841..d8701eb 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -143,6 +143,9 @@
     if (defined(invoker.android_manifest_dep)) {
       deps += [ invoker.android_manifest_dep ]
     }
+    if (defined(invoker.base_module_build_config_target)) {
+      deps += [ invoker.base_module_build_config_target ]
+    }
 
     script = "//build/android/gyp/write_build_config.py"
     depfile = "$target_gen_dir/$target_name.d"
@@ -477,6 +480,12 @@
         invoker.main_class,
       ]
     }
+    if (defined(invoker.base_module_build_config)) {
+      _rebased_base_module_build_config =
+          rebase_path(invoker.base_module_build_config, root_build_dir)
+      args +=
+          [ "--base-module-build-config=$_rebased_base_module_build_config" ]
+    }
     if (current_toolchain != default_toolchain) {
       # This has to be a built-time error rather than a GN assert because many
       # packages have a mix of java and non-java targets. For example, the
@@ -3346,8 +3355,10 @@
       if (type == "android_app_bundle_module") {
         forward_variables_from(invoker,
                                [
-                                 "proto_resources_path",
+                                 "base_module_build_config",
+                                 "base_module_build_config_target",
                                  "module_rtxt_path",
+                                 "proto_resources_path",
                                ])
       }
       build_config = _build_config
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 0334c9e..dc13739 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2587,6 +2587,8 @@
                                "android_manifest",
                                "android_manifest_dep",
                                "apk_under_test",
+                               "base_module_build_config",
+                               "base_module_build_config_target",
                                "chromium_code",
                                "classpath_deps",
                                "emma_never_instrument",
@@ -3294,10 +3296,15 @@
         _base_module_target_gen_dir =
             get_label_info(_base_module_target, "target_gen_dir")
         _base_module_target_name = get_label_info(_base_module_target, "name")
+
         base_module_arsc_resource =
             "$_base_module_target_gen_dir/$_base_module_target_name.arsc.ap_"
         base_module_arsc_resource_target =
             "${_base_module_target}__compile_arsc_resources"
+        base_module_build_config = "$_base_module_target_gen_dir/" +
+                                   "$_base_module_target_name.build_config"
+        base_module_build_config_target =
+            "${_base_module_target}$build_config_target_suffix"
       }
     }
   }
diff --git a/build/config/fuchsia/testing_sandbox_policy b/build/config/fuchsia/testing_sandbox_policy
index db77ddf..dd660a3 100644
--- a/build/config/fuchsia/testing_sandbox_policy
+++ b/build/config/fuchsia/testing_sandbox_policy
@@ -15,6 +15,7 @@
       "fuchsia.process.Launcher",
       "fuchsia.sys.Environment",
       "fuchsia.sys.Launcher",
+      "fuchsia.sys.Loader",
       "fuchsia.ui.input.ImeService",
       "fuchsia.ui.input.ImeVisibilityService",
       "fuchsia.ui.policy.Presenter",
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
index 33abde9..1555e59 100644
--- a/build/config/win/BUILD.gn
+++ b/build/config/win/BUILD.gn
@@ -281,11 +281,20 @@
   }
 }
 
-# Sets the default Windows build version. This is separated because some
-# targets need to manually override it for their compiles.
+# Chromium supports running on Windows 7, but if these constants are set to
+# Windows 7, then newer APIs aren't made available by the Windows SDK.
+# So we set this to Windows 10 and then are careful to check at runtime
+# to only call newer APIs when they're available.
+# Some third-party libraries assume that these defines set what version of
+# Windows is available at runtime. Targets using these libraries need to
+# manually override this config for their compiles.
 config("winver") {
   defines = [
-    "NTDDI_VERSION=0x0A000003",  # NTDDI_WIN10_RS2
+    "NTDDI_VERSION=NTDDI_WIN10_RS2",
+    
+    # We can't say `=_WIN32_WINNT_WIN10` here because some files do
+    # `#if WINVER < 0x0600` without including windows.h before,
+    # and then _WIN32_WINNT_WIN10 isn't yet known to be 0x0A00.
     "_WIN32_WINNT=0x0A00",
     "WINVER=0x0A00",
   ]
diff --git a/cc/paint/paint_image.cc b/cc/paint/paint_image.cc
index 1d79404..da31320 100644
--- a/cc/paint/paint_image.cc
+++ b/cc/paint/paint_image.cc
@@ -152,6 +152,13 @@
   }
 }
 
+bool PaintImage::IsEligibleForAcceleratedDecoding() const {
+  if (!CanDecodeFromGenerator())
+    return false;
+  DCHECK(paint_image_generator_);
+  return paint_image_generator_->IsEligibleForAcceleratedDecoding();
+}
+
 SkISize PaintImage::GetSupportedDecodeSize(
     const SkISize& requested_size) const {
   // TODO(vmpstr): In some cases we do not support decoding to any other
diff --git a/cc/paint/paint_image.h b/cc/paint/paint_image.h
index 33202446..2f68f070 100644
--- a/cc/paint/paint_image.h
+++ b/cc/paint/paint_image.h
@@ -140,6 +140,18 @@
   bool operator==(const PaintImage& other) const;
   bool operator!=(const PaintImage& other) const { return !(*this == other); }
 
+  // Returns true if the image is eligible for decoding using a hardware
+  // accelerator (which would require at least that all the encoded data has
+  // been received). Returns false otherwise or if the image cannot be decoded
+  // from a PaintImageGenerator. Notice that a return value of true does not
+  // guarantee that the hardware accelerator supports the image. It only
+  // indicates that the software decoder hasn't done any work with the image, so
+  // sending it to a hardware decoder is appropriate.
+  //
+  // TODO(andrescj): consider supporting the non-PaintImageGenerator path which
+  // is expected to be rare.
+  bool IsEligibleForAcceleratedDecoding() const;
+
   // Returns the smallest size that is at least as big as the requested_size
   // such that we can decode to exactly that scale. If the requested size is
   // larger than the image, this returns the image size. Any returned value is
diff --git a/cc/paint/paint_image_generator.h b/cc/paint/paint_image_generator.h
index db33427..5e4f474 100644
--- a/cc/paint/paint_image_generator.h
+++ b/cc/paint/paint_image_generator.h
@@ -29,6 +29,14 @@
 
   PaintImageGenerator& operator=(const PaintImageGenerator&) = delete;
 
+  // Returns true if we can guarantee that the software decoder hasn't done work
+  // on the image, so it's appropriate to send the encoded image to a hardware
+  // accelerator. False if we can't guarantee this or if not applicable. For
+  // example, if the encoded data comes incrementally, and the software decoder
+  // starts working with partial data, the image shouldn't later be sent to a
+  // hardware decoder.
+  virtual bool IsEligibleForAcceleratedDecoding() const = 0;
+
   // Returns a reference to the encoded content of this image.
   virtual sk_sp<SkData> GetEncodedData() const = 0;
 
diff --git a/cc/test/fake_paint_image_generator.cc b/cc/test/fake_paint_image_generator.cc
index c0bdfbb..f26443e5 100644
--- a/cc/test/fake_paint_image_generator.cc
+++ b/cc/test/fake_paint_image_generator.cc
@@ -36,6 +36,10 @@
 
 FakePaintImageGenerator::~FakePaintImageGenerator() = default;
 
+bool FakePaintImageGenerator::IsEligibleForAcceleratedDecoding() const {
+  return false;
+}
+
 sk_sp<SkData> FakePaintImageGenerator::GetEncodedData() const {
   return nullptr;
 }
diff --git a/cc/test/fake_paint_image_generator.h b/cc/test/fake_paint_image_generator.h
index 8a18535..268c89e 100644
--- a/cc/test/fake_paint_image_generator.h
+++ b/cc/test/fake_paint_image_generator.h
@@ -33,6 +33,7 @@
   FakePaintImageGenerator& operator=(const FakePaintImageGenerator&) = delete;
 
   // PaintImageGenerator implementation.
+  bool IsEligibleForAcceleratedDecoding() const override;
   sk_sp<SkData> GetEncodedData() const override;
   bool GetPixels(const SkImageInfo& info,
                  void* pixels,
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index 3a0eb8a..60f4c43 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -2324,10 +2324,18 @@
 
 void PropertyTrees::ResetCachedData() {
   cached_data_.transform_tree_update_number = 0;
-  cached_data_.animation_scales = std::vector<AnimationScaleData>(
-      transform_tree.nodes().size(), AnimationScaleData());
-  cached_data_.draw_transforms = std::vector<std::vector<DrawTransformData>>(
-      transform_tree.nodes().size(), std::vector<DrawTransformData>(1));
+  const auto transform_count = transform_tree.nodes().size();
+  cached_data_.animation_scales.resize(transform_count);
+  for (auto& animation_scale : cached_data_.animation_scales)
+    animation_scale.update_number = -1;
+
+  cached_data_.draw_transforms.resize(transform_count,
+                                      std::vector<DrawTransformData>(1));
+  for (auto& draw_transforms_for_id : cached_data_.draw_transforms) {
+    draw_transforms_for_id.resize(1);
+    draw_transforms_for_id[0].update_number = -1;
+    draw_transforms_for_id[0].target_id = EffectTree::kInvalidNodeId;
+  }
 }
 
 void PropertyTrees::UpdateTransformTreeUpdateNumber() {
diff --git a/cc/trees/scroll_node.cc b/cc/trees/scroll_node.cc
index 069c731..69b7b33 100644
--- a/cc/trees/scroll_node.cc
+++ b/cc/trees/scroll_node.cc
@@ -63,6 +63,9 @@
   value->SetBoolean("user_scrollable_horizontal", user_scrollable_horizontal);
   value->SetBoolean("user_scrollable_vertical", user_scrollable_vertical);
 
+  value->SetBoolean("scrolls_inner_viewport", scrolls_inner_viewport);
+  value->SetBoolean("scrolls_outer_viewport", scrolls_outer_viewport);
+
   element_id.AddToTracedValue(value);
   value->SetInteger("transform_id", transform_id);
   value->SetInteger("overscroll_behavior_x", overscroll_behavior.x);
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 4d276b2f..79a8268b 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -96,7 +96,10 @@
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryBridge.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMetricsRecorder.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManager.java",
+  "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManagerImpl.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingBridge.java",
+  "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponent.java",
+  "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponentFactory.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingProperties.java",
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
index a397584..1459e80 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
@@ -89,6 +89,7 @@
 
     /** Detaches and destroys the view. */
     public void destroy() {
+        setVisible(false);
         detachAssistantView();
         mOverlayCoordinator.destroy();
     }
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
index b22fce0..8e21108 100644
--- a/chrome/android/features/tab_ui/BUILD.gn
+++ b/chrome/android/features/tab_ui/BUILD.gn
@@ -5,6 +5,14 @@
 import("//build/config/android/rules.gni")
 import("//chrome/common/features.gni")
 
+android_resources("java_resources") {
+  resource_dirs = [ "java/res" ]
+  deps = [
+    "//chrome/android:chrome_app_java_resources",
+  ]
+  custom_package = "org.chromium.chrome.tab_ui"
+}
+
 android_library("java") {
   java_files = [
     "java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java",
@@ -38,6 +46,10 @@
     "java/src/org/chromium/chrome/browser/tasks/tab_management/TabListFaviconProvider.java",
   ]
 
+  deps = [
+    ":java_resources",
+  ]
+
   classpath_deps = [
     "//base:base_java",
     "//chrome/android:chrome_java",
diff --git a/chrome/android/java/res/drawable-hdpi/tabstrip_selected.png b/chrome/android/features/tab_ui/java/res/drawable-hdpi/tabstrip_selected.png
similarity index 100%
rename from chrome/android/java/res/drawable-hdpi/tabstrip_selected.png
rename to chrome/android/features/tab_ui/java/res/drawable-hdpi/tabstrip_selected.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/tabstrip_selected.png b/chrome/android/features/tab_ui/java/res/drawable-mdpi/tabstrip_selected.png
similarity index 100%
rename from chrome/android/java/res/drawable-mdpi/tabstrip_selected.png
rename to chrome/android/features/tab_ui/java/res/drawable-mdpi/tabstrip_selected.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/tabstrip_selected.png b/chrome/android/features/tab_ui/java/res/drawable-xhdpi/tabstrip_selected.png
similarity index 100%
rename from chrome/android/java/res/drawable-xhdpi/tabstrip_selected.png
rename to chrome/android/features/tab_ui/java/res/drawable-xhdpi/tabstrip_selected.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/tabstrip_selected.png b/chrome/android/features/tab_ui/java/res/drawable-xxhdpi/tabstrip_selected.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxhdpi/tabstrip_selected.png
rename to chrome/android/features/tab_ui/java/res/drawable-xxhdpi/tabstrip_selected.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/tabstrip_selected.png b/chrome/android/features/tab_ui/java/res/drawable-xxxhdpi/tabstrip_selected.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxxhdpi/tabstrip_selected.png
rename to chrome/android/features/tab_ui/java/res/drawable-xxxhdpi/tabstrip_selected.png
Binary files differ
diff --git a/chrome/android/java/res/drawable/selected_tab_background.xml b/chrome/android/features/tab_ui/java/res/drawable/selected_tab_background.xml
similarity index 100%
rename from chrome/android/java/res/drawable/selected_tab_background.xml
rename to chrome/android/features/tab_ui/java/res/drawable/selected_tab_background.xml
diff --git a/chrome/android/java/res/drawable/tab_grid_card_background.xml b/chrome/android/features/tab_ui/java/res/drawable/tab_grid_card_background.xml
similarity index 100%
rename from chrome/android/java/res/drawable/tab_grid_card_background.xml
rename to chrome/android/features/tab_ui/java/res/drawable/tab_grid_card_background.xml
diff --git a/chrome/android/java/res/drawable/tabstrip_favicon_background.xml b/chrome/android/features/tab_ui/java/res/drawable/tabstrip_favicon_background.xml
similarity index 100%
rename from chrome/android/java/res/drawable/tabstrip_favicon_background.xml
rename to chrome/android/features/tab_ui/java/res/drawable/tabstrip_favicon_background.xml
diff --git a/chrome/android/java/res/layout/bottom_tab_grid_toolbar.xml b/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
similarity index 100%
rename from chrome/android/java/res/layout/bottom_tab_grid_toolbar.xml
rename to chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
diff --git a/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml b/chrome/android/features/tab_ui/java/res/layout/bottom_tab_strip_toolbar.xml
similarity index 100%
rename from chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml
rename to chrome/android/features/tab_ui/java/res/layout/bottom_tab_strip_toolbar.xml
diff --git a/chrome/android/java/res/layout/tab_grid_card_item.xml b/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
similarity index 100%
rename from chrome/android/java/res/layout/tab_grid_card_item.xml
rename to chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
diff --git a/chrome/android/java/res/layout/tab_list_recycler_view_layout.xml b/chrome/android/features/tab_ui/java/res/layout/tab_list_recycler_view_layout.xml
similarity index 100%
rename from chrome/android/java/res/layout/tab_list_recycler_view_layout.xml
rename to chrome/android/features/tab_ui/java/res/layout/tab_list_recycler_view_layout.xml
diff --git a/chrome/android/java/res/layout/tab_strip_item.xml b/chrome/android/features/tab_ui/java/res/layout/tab_strip_item.xml
similarity index 100%
rename from chrome/android/java/res/layout/tab_strip_item.xml
rename to chrome/android/features/tab_ui/java/res/layout/tab_strip_item.xml
diff --git a/chrome/android/features/tab_ui/java/res/values/dimens.xml b/chrome/android/features/tab_ui/java/res/values/dimens.xml
new file mode 100644
index 0000000..33c74152
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/res/values/dimens.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <dimen name="tab_grid_favicon_size">32dp</dimen>
+    <dimen name="tab_list_selected_inset">7dp</dimen>
+    <dimen name="tab_list_card_padding">8dp</dimen>
+    <dimen name="tab_list_mini_card_radius">4dp</dimen>
+    <dimen name="tab_list_mini_card_frame_size">1dp</dimen>
+    <dimen name="tab_grid_thumbnail_card_default_size">152dp</dimen>
+    <dimen name="swipe_to_dismiss_threshold">72dp</dimen>
+</resources>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
index 93f697bc..4e60f32 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
@@ -20,10 +20,10 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.base.task.PostTask;
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.File;
@@ -170,21 +170,21 @@
         // Initialize Paints to use.
         mEmptyThumbnailPaint = new Paint();
         mEmptyThumbnailPaint.setStyle(Paint.Style.FILL);
-        mEmptyThumbnailPaint.setColor(
-                ApiCompatibilityUtils.getColor(context.getResources(), R.color.modern_grey_100));
+        mEmptyThumbnailPaint.setColor(ApiCompatibilityUtils.getColor(
+                context.getResources(), org.chromium.chrome.R.color.modern_grey_100));
         mEmptyThumbnailPaint.setAntiAlias(true);
 
         mThumbnailFramePaint = new Paint();
         mThumbnailFramePaint.setStyle(Paint.Style.STROKE);
         mThumbnailFramePaint.setStrokeWidth(
                 context.getResources().getDimension(R.dimen.tab_list_mini_card_frame_size));
-        mThumbnailFramePaint.setColor(
-                ApiCompatibilityUtils.getColor(context.getResources(), R.color.modern_grey_300));
+        mThumbnailFramePaint.setColor(ApiCompatibilityUtils.getColor(
+                context.getResources(), org.chromium.chrome.R.color.modern_grey_300));
         mThumbnailFramePaint.setAntiAlias(true);
 
         mTextPaint = new Paint();
-        mTextPaint.setTextSize(
-                context.getResources().getDimension(R.dimen.compositor_tab_title_text_size));
+        mTextPaint.setTextSize(context.getResources().getDimension(
+                org.chromium.chrome.R.dimen.compositor_tab_title_text_size));
         mTextPaint.setFakeBoldText(true);
         mTextPaint.setAntiAlias(true);
         mTextPaint.setTextAlign(Paint.Align.CENTER);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/SilenceLintErrors.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/SilenceLintErrors.java
index bde5673..0035890 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/SilenceLintErrors.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/SilenceLintErrors.java
@@ -11,24 +11,21 @@
  */
 /* package */ class SilenceLintErrors {
     // TODO(yusufo): Add these resources to the DFM
-    private int[] mRes =
-            new int[] {R.dimen.tab_grid_favicon_size, R.string.tab_management_module_title,
-                    R.string.iph_tab_groups_quickly_compare_pages_text,
-                    R.string.iph_tab_groups_tap_to_see_another_tab_text,
-                    R.string.iph_tab_groups_your_tabs_together_text,
-                    R.string.bottom_tab_grid_description, R.string.bottom_tab_grid_opened_half,
-                    R.string.bottom_tab_grid_opened_full, R.string.bottom_tab_grid_closed,
-                    R.dimen.tab_list_selected_inset, R.layout.tab_strip_item,
-                    R.drawable.selected_tab_background, R.drawable.tab_grid_card_background,
-                    R.layout.tab_grid_card_item, R.layout.tab_list_recycler_view_layout,
-                    R.layout.bottom_tab_grid_toolbar, R.string.bottom_tab_grid_new_tab,
-                    R.string.bottom_tab_grid_new_tab, R.plurals.bottom_tab_grid_title_placeholder,
-                    R.string.iph_tab_groups_tap_to_see_another_tab_accessibility_text,
-                    R.string.accessibility_bottom_tab_strip_expand_tab_sheet,
-                    R.layout.bottom_tab_strip_toolbar, R.drawable.tabstrip_selected,
-                    R.dimen.tab_list_card_padding, R.dimen.tab_list_mini_card_text_size,
-                    R.dimen.tab_list_mini_card_frame_size, R.dimen.tab_list_mini_card_radius,
-                    R.drawable.tabstrip_favicon_background, R.dimen.swipe_to_dismiss_threshold};
+    private int[] mRes = new int[] {
+            R.string.iph_tab_groups_quickly_compare_pages_text,
+            R.string.iph_tab_groups_tap_to_see_another_tab_text,
+            R.string.iph_tab_groups_your_tabs_together_text,
+            R.string.bottom_tab_grid_description,
+            R.string.bottom_tab_grid_opened_half,
+            R.string.bottom_tab_grid_opened_full,
+            R.string.bottom_tab_grid_closed,
+            R.string.bottom_tab_grid_new_tab,
+            R.string.bottom_tab_grid_new_tab,
+            R.plurals.bottom_tab_grid_title_placeholder,
+            R.string.iph_tab_groups_tap_to_see_another_tab_accessibility_text,
+            R.string.accessibility_bottom_tab_strip_expand_tab_sheet,
+            R.string.accessibility_bottom_tab_grid_close_tab_sheet,
+    };
 
     private SilenceLintErrors() {}
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetToolbarCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetToolbarCoordinator.java
index 6548ef3..557c080 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetToolbarCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetToolbarCoordinator.java
@@ -9,8 +9,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
index c73373d..e381f5d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
@@ -15,7 +15,7 @@
 import android.widget.FrameLayout;
 
 import org.chromium.base.Callback;
-import org.chromium.chrome.R;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -41,7 +41,7 @@
             String title = item.get(TabProperties.TITLE);
             holder.title.setText(title);
             holder.closeButton.setContentDescription(holder.itemView.getResources().getString(
-                    R.string.accessibility_tabstrip_btn_close_tab, title));
+                    org.chromium.chrome.R.string.accessibility_tabstrip_btn_close_tab, title));
         } else if (TabProperties.IS_SELECTED == propertyKey) {
             Resources res = holder.itemView.getResources();
             Drawable drawable = new InsetDrawable(
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewHolder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewHolder.java
index b92ca55e..1641d6a 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewHolder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewHolder.java
@@ -13,7 +13,7 @@
 import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.chrome.R;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.widget.ButtonCompat;
 
 /**
@@ -35,14 +35,14 @@
         this.favicon = itemView.findViewById(R.id.tab_favicon);
         this.closeButton = itemView.findViewById(R.id.close_button);
         DrawableCompat.setTint(this.closeButton.getDrawable(),
-                ApiCompatibilityUtils.getColor(itemView.getResources(), R.color.light_icon_color));
+                ApiCompatibilityUtils.getColor(
+                        itemView.getResources(), org.chromium.chrome.R.color.light_icon_color));
         this.createGroupButton = itemView.findViewById(R.id.create_group_button);
     }
 
     public static TabGridViewHolder create(ViewGroup parent, int itemViewType) {
-        View view =
-                LayoutInflater.from(parent.getContext())
-                        .inflate(org.chromium.chrome.R.layout.tab_grid_card_item, parent, false);
+        View view = LayoutInflater.from(parent.getContext())
+                            .inflate(R.layout.tab_grid_card_item, parent, false);
         return new TabGridViewHolder(view);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
index 9ea56667..0e138cb6 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
@@ -13,7 +13,7 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import org.chromium.chrome.R;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.widget.ChromeImageView;
 
 /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index aff7246b..bd0b2740 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -14,12 +14,12 @@
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupUtils;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripToolbarCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripToolbarCoordinator.java
index a69b64a..f819ccd 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripToolbarCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripToolbarCoordinator.java
@@ -9,8 +9,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewBinder.java
index a26896a..72c89ae 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewBinder.java
@@ -10,7 +10,7 @@
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.widget.FrameLayout;
 
-import org.chromium.chrome.R;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -45,13 +45,13 @@
                     item.get(TabProperties.TAB_CLOSED_LISTENER).run(holder.getTabId());
                 });
                 holder.button.setContentDescription(holder.itemView.getContext().getString(
-                        R.string.accessibility_tabstrip_btn_close_tab, title));
+                        org.chromium.chrome.R.string.accessibility_tabstrip_btn_close_tab, title));
             } else {
                 holder.button.setOnClickListener(view -> {
                     item.get(TabProperties.TAB_SELECTED_LISTENER).run(holder.getTabId());
                 });
                 holder.button.setContentDescription(holder.itemView.getContext().getString(
-                        R.string.accessibility_tabstrip_tab, title));
+                        org.chromium.chrome.R.string.accessibility_tabstrip_tab, title));
             }
         } else if (TabProperties.FAVICON == propertyKey) {
             Drawable faviconDrawable = item.get(TabProperties.FAVICON);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewHolder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewHolder.java
index d199c08..0a1c9550 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewHolder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabStripViewHolder.java
@@ -10,7 +10,7 @@
 import android.view.ViewGroup;
 import android.widget.ImageButton;
 
-import org.chromium.chrome.R;
+import org.chromium.chrome.tab_ui.R;
 
 /**
  * {@link RecyclerView.ViewHolder} for tab strip.
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinderTest.java
index 89644e5..5375c3c 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinderTest.java
@@ -21,7 +21,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.test.util.CallbackHelper;
-import org.chromium.chrome.R;
+import org.chromium.chrome.tab_ui.R;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ui.DummyUiActivity;
 import org.chromium.chrome.test.ui.DummyUiActivityTestCase;
@@ -204,14 +204,14 @@
         mContainerModel.set(TabListContainerProperties.IS_INCOGNITO, true);
         assertThat(mRecyclerView.getBackground(), instanceOf(ColorDrawable.class));
         assertThat(((ColorDrawable) mRecyclerView.getBackground()).getColor(),
-                equalTo(ApiCompatibilityUtils.getColor(
-                        mRecyclerView.getResources(), R.color.incognito_modern_primary_color)));
+                equalTo(ApiCompatibilityUtils.getColor(mRecyclerView.getResources(),
+                        org.chromium.chrome.R.color.incognito_modern_primary_color)));
 
         mContainerModel.set(TabListContainerProperties.IS_INCOGNITO, false);
         assertThat(mRecyclerView.getBackground(), instanceOf(ColorDrawable.class));
         assertThat(((ColorDrawable) mRecyclerView.getBackground()).getColor(),
-                equalTo(ApiCompatibilityUtils.getColor(
-                        mRecyclerView.getResources(), R.color.modern_primary_color)));
+                equalTo(ApiCompatibilityUtils.getColor(mRecyclerView.getResources(),
+                        org.chromium.chrome.R.color.modern_primary_color)));
     }
 
     @Test
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 1bc2dfac..c8c45972 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -594,14 +594,4 @@
     <!-- Explicit Language Ask Prompt dimensions -->
     <dimen name="explicit_ask_checkbox_end_padding">4dp</dimen>
 
-    <!-- Tab List dimensions -->
-    <dimen name="tab_grid_favicon_size">32dp</dimen>
-    <dimen name="tab_list_selected_inset">7dp</dimen>
-    <dimen name="tab_list_card_padding">8dp</dimen>
-    <dimen name="tab_list_mini_card_radius">4dp</dimen>
-    <dimen name="tab_list_mini_card_frame_size">1dp</dimen>
-    <dimen name="tab_list_mini_card_text_size">12sp</dimen>
-    <dimen name="tab_grid_thumbnail_card_default_size">152dp</dimen>
-    <dimen name="swipe_to_dismiss_threshold">72dp</dimen>
-
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index befd101..52902964 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -62,7 +62,8 @@
 import org.chromium.chrome.browser.appmenu.AppMenuHandler;
 import org.chromium.chrome.browser.appmenu.AppMenuObserver;
 import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
-import org.chromium.chrome.browser.autofill.keyboard_accessory.ManualFillingCoordinator;
+import org.chromium.chrome.browser.autofill.keyboard_accessory.ManualFillingComponent;
+import org.chromium.chrome.browser.autofill.keyboard_accessory.ManualFillingComponentFactory;
 import org.chromium.chrome.browser.banners.AppBannerManager;
 import org.chromium.chrome.browser.bookmarks.BookmarkModel;
 import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
@@ -298,8 +299,8 @@
     private Runnable mRecordMultiWindowModeScreenWidthRunnable;
 
     private final DiscardableReferencePool mReferencePool = new DiscardableReferencePool();
-    private final ManualFillingCoordinator mManualFillingController =
-            new ManualFillingCoordinator();
+    private final ManualFillingComponent mManualFillingComponent =
+            ManualFillingComponentFactory.createComponent();
 
     private AssistStatusHandler mAssistStatusHandler;
 
@@ -497,7 +498,7 @@
 
             ((BottomContainer) findViewById(R.id.bottom_container))
                     .initialize(mFullscreenManager,
-                            mManualFillingController.getKeyboardExtensionSizeManager());
+                            mManualFillingComponent.getKeyboardExtensionSizeManager());
 
             // If onStart was called before postLayoutInflation (because inflation was done in a
             // background thread) then make sure to call the relevant methods belatedly.
@@ -779,10 +780,10 @@
     }
 
     /**
-     * @return The ManualFillingCoordinator that belongs to this activity.
+     * @return The {@link ManualFillingComponent} that belongs to this activity.
      */
-    public ManualFillingCoordinator getManualFillingController() {
-        return mManualFillingController;
+    public ManualFillingComponent getManualFillingComponent() {
+        return mManualFillingComponent;
     }
 
     /**
@@ -1031,7 +1032,7 @@
             arDelegate.registerOnResumeActivity(this);
         }
 
-        getManualFillingController().onResume();
+        getManualFillingComponent().onResume();
     }
 
     @Override
@@ -1049,7 +1050,7 @@
         RecordUserAction.record("MobileGoToBackground");
         Tab tab = getActivityTab();
         if (tab != null) getTabContentManager().cacheTabThumbnail(tab);
-        getManualFillingController().onPause();
+        getManualFillingComponent().onPause();
 
         VrModuleProvider.getDelegate().maybeUnregisterVrEntryHook();
         markSessionEnd();
@@ -1362,7 +1363,7 @@
             mTabContentManager = null;
         }
 
-        mManualFillingController.destroy();
+        mManualFillingComponent.destroy();
 
         if (mActivityTabStartupMetricsTracker != null) {
             mActivityTabStartupMetricsTracker.destroy();
@@ -1476,11 +1477,11 @@
         }
         super.finishNativeInitialization();
 
-        mManualFillingController.initialize(getWindowAndroid(),
+        mManualFillingComponent.initialize(getWindowAndroid(),
                 findViewById(R.id.keyboard_accessory_stub),
                 findViewById(R.id.keyboard_accessory_sheet_stub));
         getCompositorViewHolder().setKeyboardExtensionView(
-                mManualFillingController.getKeyboardExtensionSizeManager());
+                mManualFillingComponent.getKeyboardExtensionSizeManager());
 
         // Create after native initialization so subclasses that override this method have a chance
         // to setup.
@@ -1553,7 +1554,7 @@
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item != null) {
-            if (mManualFillingController != null) mManualFillingController.dismiss();
+            if (mManualFillingComponent != null) mManualFillingComponent.dismiss();
             if (onMenuOrKeyboardAction(item.getItemId(), true)) return true;
         }
         return super.onOptionsItemSelected(item);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 6046950..3d4601d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -231,7 +231,6 @@
     public static final String DRAW_VERTICALLY_EDGE_TO_EDGE = "DrawVerticallyEdgeToEdge";
     public static final String EPHEMERAL_TAB = "EphemeralTab";
     public static final String EXPERIMENTAL_APP_BANNERS = "ExperimentalAppBanners";
-    public static final String EXPERIMENTAL_UI = "ExperimentalUi";
     public static final String EXPLICIT_LANGUAGE_ASK = "ExplicitLanguageAsk";
     public static final String EXPLORE_SITES = "ExploreSites";
     public static final String FCM_INVALIDATIONS = "FCMInvalidations";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeKeyboardVisibilityDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeKeyboardVisibilityDelegate.java
index bc6c3424..5ba2f8b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeKeyboardVisibilityDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeKeyboardVisibilityDelegate.java
@@ -59,8 +59,8 @@
         boolean wasManualFillingViewShowing = false;
         if (activity != null) {
             wasManualFillingViewShowing =
-                    activity.getManualFillingController().isFillingViewShown(view);
-            activity.getManualFillingController().hide();
+                    activity.getManualFillingComponent().isFillingViewShown(view);
+            activity.getManualFillingComponent().hide();
         }
         return super.hideKeyboard(view) || wasManualFillingViewShowing;
     }
@@ -70,6 +70,6 @@
         ChromeActivity activity = getActivity();
         return super.isKeyboardShowing(context, view)
                 || (activity != null
-                           && activity.getManualFillingController().isFillingViewShown(view));
+                        && activity.getManualFillingComponent().isFillingViewShown(view));
     }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 35542b15..5b80bcac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -479,7 +479,6 @@
      *
      * @param intent The intent to set the component for.
      * @param component The client generated component to be validated.
-     * @param currentActivity The activity triggering the intent.
      */
     public static void setNonAliasedComponent(Intent intent, ComponentName component) {
         assert component != null;
@@ -1928,7 +1927,7 @@
             return getBottomSheet() != null && getBottomSheet().handleBackPress();
         }
 
-        if (getManualFillingController().handleBackPress()) return true;
+        if (getManualFillingComponent().handleBackPress()) return true;
 
         final Tab currentTab = getActivityTab();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java
index 9350e48..ed9b8a64 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java
@@ -48,6 +48,7 @@
     private final ObserverList<FontSizePrefsObserver> mObserverList;
 
     private Float mSystemFontScaleForTests;
+    private boolean mTouchlessMode;
 
     /**
      * Interface for observing changes in font size-related preferences.
@@ -154,6 +155,15 @@
     }
 
     /**
+     * Enables touchless mode. This overrides user's preference and always enables force enable
+     * zoom.
+     */
+    public void enableTouchlessMode() {
+        mTouchlessMode = true;
+        nativeSetForceEnableZoom(mFontSizePrefsAndroidPtr, true);
+    }
+
+    /**
      * Returns whether forceEnableZoom is enabled.
      */
     public boolean getForceEnableZoom() {
@@ -174,6 +184,9 @@
     }
 
     private void setForceEnableZoom(boolean enabled, boolean fromUser) {
+        // Force enable zoom is always enabled in touchless mode and it should not be changed.
+        if (mTouchlessMode) return;
+
         SharedPreferences.Editor sharedPreferencesEditor = mSharedPreferences.edit();
         sharedPreferencesEditor.putBoolean(PREF_USER_SET_FORCE_ENABLE_ZOOM, fromUser);
         sharedPreferencesEditor.apply();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillPopupBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillPopupBridge.java
index 0a8ae75a..48928f64 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillPopupBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillPopupBridge.java
@@ -46,7 +46,7 @@
             mAutofillPopup = new AutofillPopup(activity, anchorView, this);
             mContext = activity;
             ChromeActivity chromeActivity = (ChromeActivity) activity;
-            chromeActivity.getManualFillingController().notifyPopupAvailable(mAutofillPopup);
+            chromeActivity.getManualFillingComponent().notifyPopupAvailable(mAutofillPopup);
             mWebContentsAccessibility = WebContentsAccessibility.fromWebContents(
                     chromeActivity.getCurrentWebContents());
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryBridge.java
index 62fe1a9..b8bd9ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryBridge.java
@@ -6,11 +6,9 @@
 
 import android.content.Context;
 import android.content.DialogInterface;
-import android.support.v7.app.AlertDialog;
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ResourceId;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.data.PropertyProvider;
@@ -28,7 +26,7 @@
 public class AutofillKeyboardAccessoryBridge
         implements AutofillDelegate, DialogInterface.OnClickListener {
     private long mNativeAutofillKeyboardAccessory;
-    private ManualFillingCoordinator mManualFillingCoordinator;
+    private ManualFillingComponent mManualFillingComponent;
     private Context mContext;
     private PropertyProvider<AutofillSuggestion[]> mChipProvider =
             new PropertyProvider<>(AccessoryAction.AUTOFILL_SUGGESTION);
@@ -48,7 +46,7 @@
 
     @Override
     public void suggestionSelected(int listIndex) {
-        mManualFillingCoordinator.dismiss();
+        mManualFillingComponent.dismiss();
         if (mNativeAutofillKeyboardAccessory == 0) return;
         nativeSuggestionSelected(mNativeAutofillKeyboardAccessory, listIndex);
     }
@@ -86,8 +84,8 @@
         mContext = windowAndroid.getActivity().get();
         assert mContext != null;
         if (mContext instanceof ChromeActivity) {
-            mManualFillingCoordinator = ((ChromeActivity) mContext).getManualFillingController();
-            mManualFillingCoordinator.registerAutofillProvider(mChipProvider, this);
+            mManualFillingComponent = ((ChromeActivity) mContext).getManualFillingComponent();
+            mManualFillingComponent.registerAutofillProvider(mChipProvider, this);
         }
 
         mNativeAutofillKeyboardAccessory = nativeAutofillKeyboardAccessory;
@@ -124,14 +122,9 @@
     // eventually disappear).
 
     @CalledByNative
-    private void confirmDeletion(String title, String body) {
-        new AlertDialog.Builder(mContext, R.style.Theme_Chromium_AlertDialog)
-                .setTitle(title)
-                .setMessage(body)
-                .setNegativeButton(R.string.cancel, null)
-                .setPositiveButton(R.string.ok, this)
-                .create()
-                .show();
+    private void confirmDeletion(String title, String body) throws Exception {
+        // TODO(fhorschig): If deletion is implemented, build a ModalDialogView!
+        throw new Exception("Not implemented yet!");
     }
 
     @CalledByNative
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManager.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManager.java
index a9d2d08c..9850b8b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManager.java
@@ -6,23 +6,16 @@
 
 import android.support.annotation.Px;
 
-import org.chromium.base.ObserverList;
-
 /**
- * This class holds the size of any extension to or even replacement for a keyboard.
- * For example, it is used by {@link ManualFillingCoordinator} to provide the combined height of
- * {@link KeyboardAccessoryCoordinator} and {@link AccessorySheetCoordinator}.
- * The height can then be used to either compute an offset for bottom bars (e.g. CCTs or PWAs) or to
- * push up the content area.
+ * This class holds the size of any extension to or even replacement for a keyboard. The height can
+ * be used to either compute an offset for bottom bars (e.g. CCTs or PWAs) or to push up the content
+ * area.
  */
-public class KeyboardExtensionSizeManager {
-    private int mHeight;
-    private final ObserverList<Observer> mObservers = new ObserverList<>();
-
+public interface KeyboardExtensionSizeManager {
     /**
      * Observers are notified when the size of the keyboard extension changes.
      */
-    public interface Observer {
+    interface Observer {
         /**
          * Called when the extension height changes.
          * @param keyboardHeight The new height of the keyboard extension.
@@ -34,34 +27,19 @@
      * Returns the height of the keyboard extension.
      * @return A height in pixels.
      */
-    public @Px int getKeyboardExtensionHeight() {
-        return mHeight;
-    }
+    @Px
+    int getKeyboardExtensionHeight();
 
     /**
-     * Registered observers are called whenever the extension size changes until unregistered. Does
-     * not guarantee order.
+     * Registered observers are called whenever the extension size changes until unregistered.
+     * Does not guarantee order.
      * @param observer a {@link KeyboardExtensionSizeManager.Observer}.
      */
-    public void addObserver(Observer observer) {
-        mObservers.addObserver(observer);
-    }
+    void addObserver(Observer observer);
 
     /**
      * Removes a registered observer if present.
      * @param observer a registered {@link KeyboardExtensionSizeManager.Observer}.
      */
-    public void removeObserver(Observer observer) {
-        mObservers.removeObserver(observer);
-    }
-
-    /**
-     * Sets a new extension height and notifies observers if its value changed.
-     * @param newKeyboardExtensionHeight The height in pixels.
-     */
-    void setKeyboardExtensionHeight(@Px int newKeyboardExtensionHeight) {
-        if (mHeight == newKeyboardExtensionHeight) return;
-        mHeight = newKeyboardExtensionHeight;
-        for (Observer observer : mObservers) observer.onKeyboardExtensionHeightChanged(mHeight);
-    }
+    void removeObserver(Observer observer);
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManagerImpl.java
new file mode 100644
index 0000000..86e9a18
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardExtensionSizeManagerImpl.java
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill.keyboard_accessory;
+
+import android.support.annotation.Px;
+
+import org.chromium.base.ObserverList;
+
+/**
+ * This class is used by {@link ManualFillingMediator} to provide the combined height of
+ * KeyboardAccessoryCoordinator and AccessorySheetCoordinator.
+ */
+class KeyboardExtensionSizeManagerImpl implements KeyboardExtensionSizeManager {
+    private int mHeight;
+    private final ObserverList<Observer> mObservers = new ObserverList<>();
+
+    @Override
+    public @Px int getKeyboardExtensionHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public void addObserver(Observer observer) {
+        mObservers.addObserver(observer);
+    }
+
+    @Override
+    public void removeObserver(Observer observer) {
+        mObservers.removeObserver(observer);
+    }
+
+    void setKeyboardExtensionHeight(@Px int newKeyboardExtensionHeight) {
+        if (mHeight == newKeyboardExtensionHeight) return;
+        mHeight = newKeyboardExtensionHeight;
+        for (Observer observer : mObservers) observer.onKeyboardExtensionHeightChanged(mHeight);
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingBridge.java
index 88a4193..38c1c5b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingBridge.java
@@ -25,17 +25,17 @@
             new PropertyProvider<>();
     private final PropertyProvider<Action[]> mActionProvider =
             new PropertyProvider<>(AccessoryAction.GENERATE_PASSWORD_AUTOMATIC);
-    private final ManualFillingCoordinator mManualFillingCoordinator;
+    private final ManualFillingComponent mManualFillingComponent;
     private final ChromeActivity mActivity;
     private long mNativeView;
 
     private ManualFillingBridge(long nativeView, WindowAndroid windowAndroid) {
         mNativeView = nativeView;
         mActivity = (ChromeActivity) windowAndroid.getActivity().get();
-        mManualFillingCoordinator = mActivity.getManualFillingController();
-        mManualFillingCoordinator.registerPasswordProvider(mSheetDataProvider);
-        mManualFillingCoordinator.registerCreditCardProvider();
-        mManualFillingCoordinator.registerActionProvider(mActionProvider);
+        mManualFillingComponent = mActivity.getManualFillingComponent();
+        mManualFillingComponent.registerPasswordProvider(mSheetDataProvider);
+        mManualFillingComponent.registerCreditCardProvider();
+        mManualFillingComponent.registerActionProvider(mActionProvider);
     }
 
     @CalledByNative
@@ -76,22 +76,22 @@
 
     @CalledByNative
     void showWhenKeyboardIsVisible() {
-        mManualFillingCoordinator.showWhenKeyboardIsVisible();
+        mManualFillingComponent.showWhenKeyboardIsVisible();
     }
 
     @CalledByNative
     void hide() {
-        mManualFillingCoordinator.hide();
+        mManualFillingComponent.hide();
     }
 
     @CalledByNative
     private void closeAccessorySheet() {
-        mManualFillingCoordinator.closeAccessorySheet();
+        mManualFillingComponent.closeAccessorySheet();
     }
 
     @CalledByNative
     private void swapSheetWithKeyboard() {
-        mManualFillingCoordinator.swapSheetWithKeyboard();
+        mManualFillingComponent.swapSheetWithKeyboard();
     }
 
     @CalledByNative
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponent.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponent.java
new file mode 100644
index 0000000..4c443867
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponent.java
@@ -0,0 +1,128 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill.keyboard_accessory;
+
+import android.view.View;
+import android.view.ViewStub;
+
+import org.chromium.chrome.browser.autofill.keyboard_accessory.data.KeyboardAccessoryData;
+import org.chromium.chrome.browser.autofill.keyboard_accessory.data.PropertyProvider;
+import org.chromium.components.autofill.AutofillDelegate;
+import org.chromium.components.autofill.AutofillSuggestion;
+import org.chromium.ui.DropdownPopupWindow;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * This component handles the new, non-popup filling UI.
+ */
+public interface ManualFillingComponent {
+    /**
+     * Initializes the manual filling component. Calls to this class are NoOps until this method
+     * is called.
+     * @param windowAndroid The window needed to listen to the keyboard and to connect to
+     *         activity.
+     * @param barStub The {@link ViewStub} used to inflate the keyboard accessory bar.
+     * @param sheetStub The {@link ViewStub} used to inflate the keyboard accessory bottom
+     *         sheet.
+     */
+    void initialize(WindowAndroid windowAndroid, ViewStub barStub, ViewStub sheetStub);
+
+    /**
+     * Cleans up the manual UI by destroying the accessory bar and its bottom sheet.
+     */
+    void destroy();
+
+    /**
+     * Handles tapping on the Android back button.
+     * @return Whether tapping the back button dismissed the accessory sheet or not.
+     */
+    boolean handleBackPress();
+
+    /**
+     * Ensures that keyboard accessory and keyboard are hidden and reset.
+     */
+    void dismiss();
+
+    /**
+     * Notifies the component that a popup window exists so it can be dismissed if necessary.
+     * @param popup A {@link DropdownPopupWindow} that might be dismissed later.
+     */
+    void notifyPopupAvailable(DropdownPopupWindow popup);
+
+    /**
+     * By registering this provider, an empty tab for passwords is created. Call
+     * {@link PropertyProvider#notifyObservers(Object)} to fill or update the sheet.
+     * @param sheetDataProvider The {@link PropertyProvider} the tab will get it's data from.
+     */
+    void registerPasswordProvider(
+            PropertyProvider<KeyboardAccessoryData.AccessorySheetData> sheetDataProvider);
+
+    /**
+     * By calling this function, an empty tab for credit cards is created.
+     */
+    void registerCreditCardProvider();
+
+    /**
+     * Registers a provider, to provide actions for the keyboard accessory bar. Call
+     * {@link PropertyProvider#notifyObservers(Object)} to fill or update the actions.
+     * @param actionProvider The {@link PropertyProvider} providing actions.
+     */
+    void registerActionProvider(PropertyProvider<KeyboardAccessoryData.Action[]> actionProvider);
+
+    /**
+     * Registers a provider, to provide autofill suggestions for the keyboard accessory bar. Call
+     * {@link PropertyProvider#notifyObservers(Object)} to fill or update the suggestions.
+     * @param autofillProvider The {@link PropertyProvider} providing autofill suggestions.
+     * @param delegate The {@link AutofillDelegate} to call for interaction with the suggestions.
+     */
+    void registerAutofillProvider(
+            PropertyProvider<AutofillSuggestion[]> autofillProvider, AutofillDelegate delegate);
+
+    /**
+     * Signals that the accessory has permission to show if the user focuses a form field.
+     */
+    void showWhenKeyboardIsVisible();
+
+    /**
+     * Requests to close the active tab in the keyboard accessory. If there is no active tab, this
+     * is a NoOp.
+     */
+    void closeAccessorySheet();
+
+    /**
+     * Opens the keyboard which implicitly dismisses the sheet. Without open sheet, this is a NoOp.
+     */
+    void swapSheetWithKeyboard();
+
+    /**
+     * Hides the sheet until undone with {@link #showWhenKeyboardIsVisible()}.
+     */
+    void hide();
+
+    /**
+     * Notifies the component that the activity it's living in was resumed.
+     */
+    void onResume();
+
+    /**
+     * Notifies the component that the activity it's living in was paused.
+     */
+    void onPause();
+
+    /**
+     * Returns a size manager that allows to access the combined height of
+     * KeyboardAccessoryCoordinator and AccessorySheetCoordinator, and to be
+     * notified when it changes.
+     * @return A {@link KeyboardExtensionSizeManager}.
+     */
+    KeyboardExtensionSizeManager getKeyboardExtensionSizeManager();
+
+    /**
+     * Returns whether the Keyboard is replaced by an accessory sheet or is about to do so.
+     * @return True if an accessory sheet is (being) opened and replacing the keyboard.
+     * @param view A {@link View} that is used to find the window root.
+     */
+    boolean isFillingViewShown(View view);
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponentFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponentFactory.java
new file mode 100644
index 0000000..8aaa0858f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingComponentFactory.java
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill.keyboard_accessory;
+
+/**
+ * Use {@link #createComponent()} to instantiate a {@link ManualFillingComponent}.
+ */
+public class ManualFillingComponentFactory {
+    private ManualFillingComponentFactory() {}
+
+    /**
+     * Creates a {@link ManualFillingComponent} if the implementation is available. If it isn't,
+     * null is returned instead.
+     * @return A {@link ManualFillingComponent}.
+     */
+    public static ManualFillingComponent createComponent() {
+        return new ManualFillingCoordinator();
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java
index 869b314..36f854a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java
@@ -8,6 +8,7 @@
 import android.view.ViewStub;
 
 import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.bar_component.KeyboardAccessoryCoordinator;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.data.KeyboardAccessoryData;
@@ -26,21 +27,18 @@
  * {@link AccessorySheetCoordinator} to add and trigger surfaces that may assist users while filling
  * fields.
  */
-public class ManualFillingCoordinator {
+class ManualFillingCoordinator implements ManualFillingComponent {
     private final ManualFillingMediator mMediator = new ManualFillingMediator();
 
-    /**
-     * Initializes the manual filling component. Calls to this class are NoOps until this method is
-     * called.
-     * @param windowAndroid The window needed to listen to the keyboard and to connect to activity.
-     * @param barStub The {@link ViewStub} used to inflate the keyboard accessory bar.
-     * @param sheetStub The {@link ViewStub} used to inflate the keyboard accessory bottom sheet.
-     */
+    @Override
     public void initialize(WindowAndroid windowAndroid, ViewStub barStub, ViewStub sheetStub) {
         if (barStub == null || sheetStub == null) return; // The manual filling isn't needed.
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY)) {
-            barStub.setLayoutResource(org.chromium.chrome.R.layout.keyboard_accessory_modern);
+            barStub.setLayoutResource(R.layout.keyboard_accessory_modern);
+        } else {
+            barStub.setLayoutResource(R.layout.keyboard_accessory);
         }
+        sheetStub.setLayoutResource(R.layout.keyboard_accessory_sheet);
         initialize(windowAndroid, new KeyboardAccessoryCoordinator(mMediator, barStub),
                 new AccessorySheetCoordinator(sheetStub));
     }
@@ -51,9 +49,7 @@
         mMediator.initialize(accessoryBar, accessorySheet, windowAndroid);
     }
 
-    /**
-     * Cleans up the manual UI by destroying the accessory bar and its bottom sheet.
-     */
+    @Override
     public void destroy() {
         mMediator.destroy();
     }
@@ -62,6 +58,7 @@
      * Handles tapping on the Android back button.
      * @return Whether tapping the back button dismissed the accessory sheet or not.
      */
+    @Override
     public boolean handleBackPress() {
         return mMediator.handleBackPress();
     }
@@ -69,6 +66,7 @@
     /**
      * Ensures that keyboard accessory and keyboard are hidden and reset.
      */
+    @Override
     public void dismiss() {
         mMediator.dismiss();
     }
@@ -77,74 +75,70 @@
      * Notifies the component that a popup window exists so it can be dismissed if necessary.
      * @param popup A {@link DropdownPopupWindow} that might be dismissed later.
      */
+    @Override
     public void notifyPopupAvailable(DropdownPopupWindow popup) {
         mMediator.notifyPopupOpened(popup);
     }
 
-    /**
-     * Requests to close the active tab in the keyboard accessory. If there is no active tab, this
-     * is a NoOp.
-     */
-    void closeAccessorySheet() {
+    @Override
+    public void closeAccessorySheet() {
         mMediator.onCloseAccessorySheet();
     }
 
-    /**
-     * Opens the keyboard which implicitly dismisses the sheet. Without open sheet, this is a NoOp.
-     */
-    void swapSheetWithKeyboard() {
+    @Override
+    public void swapSheetWithKeyboard() {
         mMediator.swapSheetWithKeyboard();
     }
 
-    void registerActionProvider(PropertyProvider<KeyboardAccessoryData.Action[]> actionProvider) {
+    @Override
+    public void registerActionProvider(
+            PropertyProvider<KeyboardAccessoryData.Action[]> actionProvider) {
         mMediator.registerActionProvider(actionProvider);
     }
 
-    void registerPasswordProvider(
+    @Override
+    public void registerPasswordProvider(
             PropertyProvider<KeyboardAccessoryData.AccessorySheetData> sheetDataProvider) {
         mMediator.registerPasswordProvider(sheetDataProvider);
     }
 
-    void registerCreditCardProvider() {
+    @Override
+    public void registerCreditCardProvider() {
         mMediator.registerCreditCardProvider();
     }
 
-    void registerAutofillProvider(
+    @Override
+    public void registerAutofillProvider(
             PropertyProvider<AutofillSuggestion[]> autofillProvider, AutofillDelegate delegate) {
         mMediator.registerAutofillProvider(autofillProvider, delegate);
     }
 
-    void showWhenKeyboardIsVisible() {
+    @Override
+    public void showWhenKeyboardIsVisible() {
         mMediator.showWhenKeyboardIsVisible();
     }
 
+    @Override
     public void hide() {
         mMediator.hide();
     }
 
+    @Override
     public void onResume() {
         mMediator.resume();
     }
 
+    @Override
     public void onPause() {
         mMediator.pause();
     }
 
-    /**
-     * Returns a size manager that allows to access the combined height of
-     * {@link KeyboardAccessoryCoordinator} and {@link AccessorySheetCoordinator}, and to be
-     * notified when it changes.
-     * @return A {@link KeyboardExtensionSizeManager}.
-     */
+    @Override
     public KeyboardExtensionSizeManager getKeyboardExtensionSizeManager() {
         return mMediator.getKeyboardExtensionSizeManager();
     }
 
-    /**
-     * Returns whether the Keyboard is replaced by an accessory sheet or is about to do so.
-     * @return True if an accessory sheet is (being) opened and replacing the keyboard.
-     * @param view A {@link View} that is used to find the window root.
-     */
+    @Override
     public boolean isFillingViewShown(View view) {
         return mMediator.isFillingViewShown(view);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java
index d64925b..15be3ca6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java
@@ -65,8 +65,8 @@
     private PropertyModel mModel = ManualFillingProperties.createFillingModel();
     private WindowAndroid mWindowAndroid;
     private Supplier<InsetObserverView> mInsetObserverViewSupplier;
-    private final KeyboardExtensionSizeManager mKeyboardExtensionSizeManager =
-            new KeyboardExtensionSizeManager();
+    private final KeyboardExtensionSizeManagerImpl mKeyboardExtensionSizeManager =
+            new KeyboardExtensionSizeManagerImpl();
     private final ManualFillingStateCache mStateCache = new ManualFillingStateCache();
     private final HashSet<Tab> mObservedTabs = new HashSet<>();
     private KeyboardAccessoryCoordinator mKeyboardAccessory;
@@ -485,8 +485,7 @@
     @Nullable
     PasswordAccessorySheetCoordinator getOrCreatePasswordSheet() {
         if (!isInitialized()) return null;
-        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.EXPERIMENTAL_UI)
-                && !ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY)) {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY)) {
             return null;
         }
         WebContents webContents = mActivity.getCurrentWebContents();
@@ -515,8 +514,7 @@
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.AUTOFILL_MANUAL_FALLBACK_ANDROID)) {
             return null;
         }
-        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.EXPERIMENTAL_UI)
-                && !ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY)) {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY)) {
             return null;
         }
         WebContents webContents = mActivity.getCurrentWebContents();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
index 0404cdc..bad3caf5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
@@ -345,7 +345,7 @@
         if (mBottomBarView == null) return; // Check bottom bar view but don't inflate it.
         // Hide the container of the bottom bar while the extension is showing. This doesn't
         // affect the content.
-        boolean keyboardExtensionHidesBottomBar = mActivity.getManualFillingController()
+        boolean keyboardExtensionHidesBottomBar = mActivity.getManualFillingComponent()
                                                           .getKeyboardExtensionSizeManager()
                                                           .getKeyboardExtensionHeight()
                 > 0;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/DataReductionPromoInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/DataReductionPromoInfoBar.java
index 9bfb5b14..7554c91b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/DataReductionPromoInfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/DataReductionPromoInfoBar.java
@@ -35,7 +35,7 @@
  */
 public class DataReductionPromoInfoBar extends ConfirmInfoBar {
     private static final String M48_STABLE_RELEASE_DATE = "2016-01-26";
-    private static final String ENABLE_INFOBAR_SWITCH = "enable-data-reduction-promo-infobar";
+    private static final String FORCE_INFOBAR_SWITCH = "force-data-reduction-promo-infobar";
     private static final int NO_INSTALL_TIME = 0;
 
     private static Bitmap sIcon;
@@ -44,21 +44,11 @@
     private static String sPrimaryButtonText;
     private static String sSecondaryButtonText;
 
-    /**
-     * Launch the data reduction infobar promo, if it needs to be displayed.
-     *
-     * @param context An Android context.
-     * @param webContents The WebContents of the tab on which the infobar should show.
-     * @param url The URL of the page on which the infobar should show.
-     * @param isFragmentNavigation Whether the main frame navigation did not cause changes to the
-     *            document (for example scrolling to a named anchor PopState).
-     * @param statusCode The HTTP status code of the navigation.
-     * @return boolean Whether the promo was launched.
-     */
-    public static boolean maybeLaunchPromoInfoBar(Context context,
-            WebContents webContents, String url, boolean isErrorPage, boolean isFragmentNavigation,
-            int statusCode) {
-        ThreadUtils.assertOnUiThread();
+    private static boolean shouldLaunchPromoInfoBar(Context context, WebContents webContents,
+            String url, boolean isErrorPage, boolean isFragmentNavigation, int statusCode) {
+        // This switch is only used for testing so let it override every other check.
+        if (CommandLine.getInstance().hasSwitch(FORCE_INFOBAR_SWITCH)) return true;
+
         if (webContents.isIncognito()) return false;
         if (isErrorPage) return false;
         if (isFragmentNavigation) return false;
@@ -104,22 +94,13 @@
             // promo was displayed or the command line switch is on. If the last promo was shown
             // before M51 then |freOrSecondRunVersion| will be empty and it is safe to show the
             // infobar promo.
-            if (!CommandLine.getInstance().hasSwitch(ENABLE_INFOBAR_SWITCH)
-                    && !freOrSecondRunVersion.isEmpty()
-                    && currentMilestone < VersionNumberGetter
-                            .getMilestoneFromVersionNumber(freOrSecondRunVersion) + 2) {
+            if (!freOrSecondRunVersion.isEmpty()
+                    && currentMilestone < VersionNumberGetter.getMilestoneFromVersionNumber(
+                                                  freOrSecondRunVersion)
+                                    + 2) {
                 return false;
             }
 
-            DataReductionPromoInfoBar.launch(webContents,
-                    BitmapFactory.decodeResource(context.getResources(), R.drawable.infobar_chrome),
-                    context.getString(R.string.data_reduction_promo_infobar_title),
-                    context.getString(R.string.data_reduction_promo_infobar_text),
-                    context.getString(
-                            DataReductionBrandingResourceProvider.getDataSaverBrandedString(
-                                    R.string.data_reduction_enable_button)),
-                    context.getString(R.string.no_thanks));
-
             return true;
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
@@ -127,6 +108,37 @@
     }
 
     /**
+     * Launch the data reduction infobar promo, if it needs to be displayed.
+     *
+     * @param context An Android context.
+     * @param webContents The WebContents of the tab on which the infobar should show.
+     * @param url The URL of the page on which the infobar should show.
+     * @param isFragmentNavigation Whether the main frame navigation did not cause changes to the
+     *            document (for example scrolling to a named anchor PopState).
+     * @param statusCode The HTTP status code of the navigation.
+     * @return boolean Whether the promo was launched.
+     */
+    public static boolean maybeLaunchPromoInfoBar(Context context, WebContents webContents,
+            String url, boolean isErrorPage, boolean isFragmentNavigation, int statusCode) {
+        ThreadUtils.assertOnUiThread();
+
+        if (!shouldLaunchPromoInfoBar(
+                    context, webContents, url, isErrorPage, isFragmentNavigation, statusCode)) {
+            return false;
+        }
+
+        DataReductionPromoInfoBar.launch(webContents,
+                BitmapFactory.decodeResource(context.getResources(), R.drawable.infobar_chrome),
+                context.getString(R.string.data_reduction_promo_infobar_title),
+                context.getString(R.string.data_reduction_promo_infobar_text),
+                context.getString(DataReductionBrandingResourceProvider.getDataSaverBrandedString(
+                        R.string.data_reduction_enable_button)),
+                context.getString(R.string.no_thanks));
+
+        return true;
+    }
+
+    /**
      * Gets the time at which this app was first installed in milliseconds since January 1, 1970
      * 00:00:00.0 UTC.
      *
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/infobar/OWNERS
index 982256d..f1bb951b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/OWNERS
@@ -1,6 +1,7 @@
 mdjones@chromium.org
 
 per-file AppBannerInfoBar*=dominickn@chromium.org
+per-file DataReductionPromoInfoBar*=file://components/data_reduction_proxy/OWNERS
 per-file GeneratedPasswordSavedInfoBar*=rouslan@chromium.org
 per-file InstallableAmbientBadgeInfoBar*=dominickn@chromium.org
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
index 232a659a..49c552739 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
@@ -14,7 +14,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -105,7 +104,6 @@
      */
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/947696")
     public void testSelectSuggestionHidesKeyboardAccessory()
             throws ExecutionException, InterruptedException, TimeoutException {
         loadTestPage();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java
index 17c4ea3..d16906a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java
@@ -89,17 +89,13 @@
         mHelper.loadTestPage(false);
 
         assertNotNull("Controller for Manual filling should be available.",
-                mActivityTestRule.getActivity().getManualFillingController());
+                mHelper.getManualFillingCoordinator());
         assertNotNull("Keyboard accessory should have an instance.",
-                mActivityTestRule.getActivity()
-                        .getManualFillingController()
+                mHelper.getManualFillingCoordinator()
                         .getMediatorForTesting()
                         .getKeyboardAccessory());
         assertNotNull("Accessory Sheet should have an instance.",
-                mActivityTestRule.getActivity()
-                        .getManualFillingController()
-                        .getMediatorForTesting()
-                        .getAccessorySheet());
+                mHelper.getManualFillingCoordinator().getMediatorForTesting().getAccessorySheet());
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java
index dbccab5..2ffe243 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java
@@ -104,19 +104,16 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             ChromeTabbedActivity activity = mActivityTestRule.getActivity();
             mWebContentsRef.set(activity.getActivityTab().getWebContents());
-            activity.getManualFillingController()
-                    .getMediatorForTesting()
-                    .setInsetObserverViewSupplier(
-                            ()
-                                    -> getKeyboard().createInsetObserver(
-                                            activity.getInsetObserverView().getContext()));
+            getManualFillingCoordinator().getMediatorForTesting().setInsetObserverViewSupplier(
+                    ()
+                            -> getKeyboard().createInsetObserver(
+                                    activity.getInsetObserverView().getContext()));
             // The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is
             // never brought up.
             final ImeAdapter imeAdapter = ImeAdapter.fromWebContents(mWebContentsRef.get());
             mInputMethodManagerWrapper = TestInputMethodManagerWrapper.create(imeAdapter);
             imeAdapter.setInputMethodManagerWrapper(mInputMethodManagerWrapper);
-            activity.getManualFillingController().registerPasswordProvider(
-                    mSheetSuggestionsProvider);
+            getManualFillingCoordinator().registerPasswordProvider(mSheetSuggestionsProvider);
         });
         if (waitForNode) DOMUtils.waitForNonZeroNodeBounds(mWebContentsRef.get(), PASSWORD_NODE_ID);
         cacheCredentials(new String[0], new String[0]); // This caches the empty state.
@@ -135,6 +132,11 @@
         return mWebContentsRef.get();
     }
 
+    ManualFillingCoordinator getManualFillingCoordinator() {
+        return (ManualFillingCoordinator) mActivityTestRule.getActivity()
+                .getManualFillingComponent();
+    }
+
     public void focusPasswordField() throws TimeoutException, InterruptedException {
         DOMUtils.focusNode(mActivityTestRule.getWebContents(), PASSWORD_NODE_ID);
         TestThreadUtils.runOnUiThreadBlocking(
@@ -152,10 +154,7 @@
         DOMUtils.clickNode(mWebContentsRef.get(), USERNAME_NODE_ID);
         if (forceAccessory) {
             TestThreadUtils.runOnUiThreadBlocking(() -> {
-                mActivityTestRule.getActivity()
-                        .getManualFillingController()
-                        .getMediatorForTesting()
-                        .showWhenKeyboardIsVisible();
+                getManualFillingCoordinator().getMediatorForTesting().showWhenKeyboardIsVisible();
             });
         }
         getKeyboard().showKeyboard(mActivityTestRule.getActivity().getCurrentFocus());
@@ -190,10 +189,8 @@
 
     public void waitForKeyboardAccessoryToDisappear() {
         CriteriaHelper.pollInstrumentationThread(() -> {
-            KeyboardAccessoryCoordinator accessory = mActivityTestRule.getActivity()
-                                                             .getManualFillingController()
-                                                             .getMediatorForTesting()
-                                                             .getKeyboardAccessory();
+            KeyboardAccessoryCoordinator accessory =
+                    getManualFillingCoordinator().getMediatorForTesting().getKeyboardAccessory();
             return accessory != null && !accessory.isShown();
         });
         CriteriaHelper.pollUiThread(() -> {
@@ -204,10 +201,8 @@
 
     public void waitForKeyboardAccessoryToBeShown() {
         CriteriaHelper.pollInstrumentationThread(() -> {
-            KeyboardAccessoryCoordinator accessory = mActivityTestRule.getActivity()
-                                                             .getManualFillingController()
-                                                             .getMediatorForTesting()
-                                                             .getKeyboardAccessory();
+            KeyboardAccessoryCoordinator accessory =
+                    getManualFillingCoordinator().getMediatorForTesting().getKeyboardAccessory();
             return accessory != null && accessory.isShown();
         });
         CriteriaHelper.pollUiThread(() -> {
@@ -250,10 +245,7 @@
     }
 
     public PasswordAccessorySheetCoordinator getOrCreatePasswordAccessorySheet() {
-        return mActivityTestRule.getActivity()
-                .getManualFillingController()
-                .getMediatorForTesting()
-                .getOrCreatePasswordSheet();
+        return getManualFillingCoordinator().getMediatorForTesting().getOrCreatePasswordSheet();
     }
 
     // ----------------------------------
@@ -413,8 +405,7 @@
     public void addGenerationButton() {
         PropertyProvider<KeyboardAccessoryData.Action[]> generationActionProvider =
                 new PropertyProvider<>(AccessoryAction.GENERATE_PASSWORD_AUTOMATIC);
-        mActivityTestRule.getActivity().getManualFillingController().registerActionProvider(
-                generationActionProvider);
+        getManualFillingCoordinator().registerActionProvider(generationActionProvider);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             generationActionProvider.notifyObservers(new KeyboardAccessoryData.Action[] {
                     new KeyboardAccessoryData.Action("Generate Password",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
index 53a9c24..200f516 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
@@ -71,21 +71,8 @@
 
     @Test
     @SmallTest
-    @EnableFeatures({ChromeFeatureList.EXPERIMENTAL_UI})
-    public void testPasswordSheetIsAvailableInExperimentalUi() throws InterruptedException {
-        mHelper.loadTestPage(false);
-
-        CriteriaHelper.pollUiThread(()
-                                            -> mHelper.getOrCreatePasswordAccessorySheet() != null,
-                "Password Sheet should be bound to accessory sheet.");
-    }
-
-    @Test
-    @SmallTest
-    @DisableFeatures(
-            {ChromeFeatureList.EXPERIMENTAL_UI, ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY})
-    public void
-    testPasswordSheetUnavailableWithoutFeature() throws InterruptedException {
+    @DisableFeatures({ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY})
+    public void testPasswordSheetUnavailableWithoutFeature() throws InterruptedException {
         mHelper.loadTestPage(false);
 
         Assert.assertNull("Password Sheet should not have been created.",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/OWNERS
new file mode 100644
index 0000000..ac64cd03
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/OWNERS
@@ -0,0 +1,4 @@
+file://ui/android/java/src/org/chromium/ui/modaldialog/OWNERS
+
+# COMPONENT: UI>Browser>Mobile
+# OS: Android
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
index 4e0e5e98..6a20b33 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
@@ -60,7 +60,6 @@
 import org.chromium.chrome.browser.tab.Tab.TabHidingType;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.KeyboardVisibilityDelegate;
@@ -79,7 +78,6 @@
 @EnableFeatures({ChromeFeatureList.PASSWORDS_KEYBOARD_ACCESSORY,
         ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY,
         ChromeFeatureList.AUTOFILL_MANUAL_FALLBACK_ANDROID})
-@DisableFeatures({ChromeFeatureList.EXPERIMENTAL_UI})
 public class ManualFillingControllerTest {
     @Mock
     private ChromeWindow mMockWindow;
diff --git a/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml b/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml
index 5c228efc..51d4fbf 100644
--- a/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml
+++ b/chrome/android/touchless/java/res/drawable/ic_apps_black_24dp.xml
@@ -1,3 +1,6 @@
+<!-- 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. -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools"
         tools:targetApi="21"
@@ -6,6 +9,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:fillColor="#4267B2"
+        android:fillColor="@color/default_icon_color_blue"
         android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
 </vector>
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
index e7fa40b08a..2ab1aa3 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
@@ -48,6 +48,9 @@
     private TooltipView mTooltipView;
     private ProgressBarView mProgressBarView;
 
+    /** The class that enables zooming for all websites and handles touchless zooming. */
+    private TouchlessZoomHelper mTouchlessZoomHelper;
+
     /** The class that controls the UI for touchless devices. */
     private TouchlessUiController mUiController;
 
@@ -129,6 +132,7 @@
                 new KeyFunctionsIPHCoordinator(mTooltipView, getActivityTabProvider());
         mProgressBarCoordinator =
                 new ProgressBarCoordinator(mProgressBarView, getActivityTabProvider());
+        mTouchlessZoomHelper = new TouchlessZoomHelper(getActivityTabProvider());
 
         // By this point if we were going to restore a URL from savedInstanceState we would already
         // have done so.
@@ -238,6 +242,7 @@
         super.onDestroyInternal();
         if (mKeyFunctionsIPHCoordinator != null) mKeyFunctionsIPHCoordinator.destroy();
         if (mProgressBarCoordinator != null) mProgressBarCoordinator.destroy();
+        if (mTouchlessZoomHelper != null) mTouchlessZoomHelper.destroy();
         if (mUiController != null) {
             mUiController.destroy();
             mUiController = null;
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/OpenLastTabView.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/OpenLastTabView.java
index 8e2362782..f835517 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/OpenLastTabView.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/OpenLastTabView.java
@@ -38,6 +38,8 @@
 
         mPlaceholder = findViewById(R.id.placeholder);
         mLastTabChip = findViewById(R.id.last_tab_chip);
+        // Allow the system ui to control our background.
+        mLastTabChip.setBackground(null);
 
         TextView primaryTextView = mLastTabChip.getPrimaryTextView();
         TextView secondaryTextView = mLastTabChip.getSecondaryTextView();
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
index f0ebc57..f4029cf 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
@@ -98,8 +98,9 @@
     private TextView mTitleView;
 
     /**
-     * @param model the main property model coming from {@link SiteSuggestionsCoordinator}.
-     * @param iconGenerator an icon generator for creating icons.
+     * @param model The main property model coming from {@link SiteSuggestionsCoordinator}. Contains
+     *         properties for a list of suggestions, number of items, and current focused index.
+     * @param iconGenerator An icon generator for creating icons.
      * @param navigationDelegate delegate for navigation controls
      * @param contextMenuManager handles context menu creation
      * @param layoutManager the layout manager controlling this recyclerview and adapter
@@ -129,8 +130,7 @@
 
     @Override
     public int getItemViewType(int position) {
-        int itemCount = mModel.get(ITEM_COUNT_KEY);
-        if (itemCount == 1 || position % itemCount == 0) return ViewType.ALL_APPS_TYPE;
+        if (isAllAppsPosition(position)) return ViewType.ALL_APPS_TYPE;
         return ViewType.SUGGESTION_TYPE;
     }
 
@@ -155,9 +155,9 @@
                                     WindowOpenDisposition.CURRENT_TAB, UrlConstants.EXPLORE_URL));
         } else if (holder.getItemViewType() == ViewType.SUGGESTION_TYPE) {
             // If site suggestion, attach context menu handler; clicks navigate to site url.
-            int itemCount = mModel.get(ITEM_COUNT_KEY);
             // Subtract 1 from position % MAX_TILES to account for "all apps" taking up one space.
-            PropertyModel item = mModel.get(SUGGESTIONS_KEY).get((position % itemCount) - 1);
+            PropertyModel item =
+                    mModel.get(SUGGESTIONS_KEY).get(getModelPositionFromAdapterPosition(position));
             // Only update the icon for icon updates.
             if (payload == SiteSuggestionModel.ICON_KEY) {
                 tile.updateIcon(item.get(SiteSuggestionModel.ICON_KEY),
@@ -185,13 +185,12 @@
         if (propertyKey == CURRENT_INDEX_KEY) {
             // When the current index changes, we want to scroll to position and update the title.
             int position = mModel.get(CURRENT_INDEX_KEY);
-            int itemCount = mModel.get(ITEM_COUNT_KEY);
             mLayoutManager.scrollToPosition(position);
-            if (itemCount == 1 || position % itemCount == 0) {
+            if (isAllAppsPosition(position)) {
                 mTitleView.setText(R.string.ntp_all_apps);
             } else {
                 mTitleView.setText(mModel.get(SUGGESTIONS_KEY)
-                                           .get(position % itemCount - 1)
+                                           .get(getModelPositionFromAdapterPosition(position))
                                            .get(SiteSuggestionModel.TITLE_KEY));
             }
         }
@@ -214,7 +213,7 @@
     public void notifyItemRangeRemoved(int index, int count) {
         if (mModel.get(SUGGESTIONS_KEY).size() == 0) {
             // When we removed the last item in the model, we would go from infinite scroll
-            // back to non-scrolling. Notify Recyclerview to remove everything.
+            // back to non-scrolling. Notify RecyclerView to remove everything.
             super.notifyItemRangeRemoved(1, Integer.MAX_VALUE - 1);
         } else {
             // Otherwise we are already infinite-scrolling, so just tell recyclerview that
@@ -225,33 +224,21 @@
 
     @Override
     public void notifyItemRangeChanged(int index, int count, @Nullable PropertyKey payload) {
-        if (count > 1) {
-            // If more than 1 item was changed, then assume everything was changed. This should
-            // only happen if we are infinite-scrolling.
-            super.notifyItemRangeChanged(0, Integer.MAX_VALUE, payload);
-        } else if (mModel.get(SUGGESTIONS_KEY).size() == 0) {
-            // If itemCount is 1, then notify super.
-            // This should only happen if "All apps" icon has changed in some way and we aren't
-            // infinite-scrolling.
-            super.notifyItemRangeChanged(index, count, payload);
-        } else {
-            // Otherwise, count = 1 and we have an infinite list. We will only notify that items
-            // near the currently visible area has changed.
-            // beginIndex at the layoutManager's firstVisibleItemPosition, with buffer.
-            int beginIndex =
-                    mLayoutManager.findFirstVisibleItemPosition() - mModel.get(ITEM_COUNT_KEY);
-            // endIndex at the lastVisibleItemPosition, with buffer.
-            int endIndex =
-                    mLayoutManager.findLastVisibleItemPosition() + mModel.get(ITEM_COUNT_KEY);
-            // Find elements between begin and end such that (i % itemCount) - 1 == index.
-            // Subtract 1 because itemRangeChanged is called from the listObserver which does not
-            // have "All apps". However, i is calculated from layoutManager, which includes "All
-            // apps"
-            for (int i = beginIndex; i < endIndex; i++) {
-                if (i % mModel.get(ITEM_COUNT_KEY) - 1 == index) {
-                    super.notifyItemRangeChanged(i, 1, payload);
-                }
-            }
-        }
+        // When something has changed, assume everything has changed.
+        super.notifyItemRangeChanged(0, Integer.MAX_VALUE, payload);
+    }
+
+    public void destroy() {
+        mModel.removeObserver(this);
+        mModel.get(SUGGESTIONS_KEY).removeObserver(this);
+    }
+
+    private int getModelPositionFromAdapterPosition(int adapterPosition) {
+        return adapterPosition % mModel.get(ITEM_COUNT_KEY) - 1;
+    }
+
+    private boolean isAllAppsPosition(int adapterPosition) {
+        return mModel.get(ITEM_COUNT_KEY) == 1
+                || getModelPositionFromAdapterPosition(adapterPosition) < 0;
     }
 }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java
index 5b6044e..ece6b74b 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsCoordinator.java
@@ -35,6 +35,7 @@
             new PropertyModel.WritableIntPropertyKey();
 
     private SiteSuggestionsMediator mMediator;
+    private SiteSuggestionsAdapter mAdapterDelegate;
 
     SiteSuggestionsCoordinator(View parentView, Profile profile,
             SuggestionsNavigationDelegate navigationDelegate, ContextMenuManager contextMenuManager,
@@ -56,13 +57,13 @@
         LinearLayoutManager layoutManager = new SiteSuggestionsLayoutManager(context);
         RecyclerView recyclerView =
                 suggestionsView.findViewById(R.id.most_likely_launcher_recycler);
-        SiteSuggestionsAdapter adapterDelegate = new SiteSuggestionsAdapter(model, iconGenerator,
-                navigationDelegate, contextMenuManager, layoutManager,
+        mAdapterDelegate = new SiteSuggestionsAdapter(model, iconGenerator, navigationDelegate,
+                contextMenuManager, layoutManager,
                 suggestionsView.findViewById(R.id.most_likely_web_title_text));
 
         RecyclerViewAdapter<SiteSuggestionsViewHolderFactory.SiteSuggestionsViewHolder, PropertyKey>
                 adapter = new RecyclerViewAdapter<>(
-                        adapterDelegate, new SiteSuggestionsViewHolderFactory());
+                        mAdapterDelegate, new SiteSuggestionsViewHolderFactory());
 
         recyclerView.setLayoutManager(layoutManager);
         recyclerView.setAdapter(adapter);
@@ -72,5 +73,6 @@
 
     public void destroy() {
         mMediator.destroy();
+        mAdapterDelegate.destroy();
     }
 }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
index 0dd75be..d97592ad 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
@@ -64,6 +64,7 @@
                     protected Bitmap doInBackground() {
                         return BitmapFactory.decodeFile(suggestion.whitelistIconPath);
                     }
+
                     @Override
                     protected void onPostExecute(Bitmap icon) {
                         if (icon == null) makeIconRequest(siteSuggestion);
@@ -75,7 +76,7 @@
         // Total item count is 1 more than number of site suggestions to account for "all apps".
         mModel.set(SiteSuggestionsCoordinator.ITEM_COUNT_KEY, getItemCount() + 1);
 
-        // If we fetched site suggestions the first time, set initial scrolled position.
+        // If we fetched site suggestions for the first time, set initial scrolled position.
         // We don't want to set scrolled position if we've already set position before.
         if (siteSuggestions.size() > 0
                 && mModel.get(SiteSuggestionsCoordinator.CURRENT_INDEX_KEY) == 0) {
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessTabObserver.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessTabObserver.java
index f458c5c..d8a5440 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessTabObserver.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessTabObserver.java
@@ -8,10 +8,10 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.Tab.TabHidingType;
 import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.ui.base.TouchlessEventHandler;
+import org.chromium.ui.touchless.TouchlessEventHandler;
 
 /**
- * Bridge to org.chromium.ui.base.TouchlessEventHandler
+ * Bridge to org.chromium.ui.touchless.TouchlessEventHandler
  */
 public class TouchlessTabObserver extends EmptyTabObserver {
     @Override
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessZoomHelper.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessZoomHelper.java
new file mode 100644
index 0000000..8cebac2
--- /dev/null
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessZoomHelper.java
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.touchless;
+
+import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ZoomController;
+import org.chromium.chrome.browser.accessibility.FontSizePrefs;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.ui.touchless.TouchlessEventHandler;
+import org.chromium.ui.touchless.TouchlessEventHandler.TouchlessZoomCallback;
+
+/**
+ * Enables zooming for all websites. Implements {@link TouchlessZoomCallback} and performs zoom in
+ * and zoom out upon {@link TouchlessEventHandler}'s request.
+ */
+public class TouchlessZoomHelper
+        implements TouchlessZoomCallback, ActivityTabProvider.ActivityTabObserver {
+    private Tab mCurrentTab;
+    private ActivityTabProvider mActivityTabProvider;
+
+    public TouchlessZoomHelper(ActivityTabProvider tabProvider) {
+        mActivityTabProvider = tabProvider;
+        mActivityTabProvider.addObserverAndTrigger(this);
+        FontSizePrefs.getInstance(tabProvider.getActivityTab().getContext()).enableTouchlessMode();
+        TouchlessEventHandler.setZoomCallback(this);
+    }
+
+    @Override
+    public void onZoomInRequested() {
+        if (mCurrentTab != null) ZoomController.zoomIn(mCurrentTab.getWebContents());
+    }
+
+    @Override
+    public void onZoomOutRequested() {
+        if (mCurrentTab != null) ZoomController.zoomOut(mCurrentTab.getWebContents());
+    }
+
+    @Override
+    public void onActivityTabChanged(Tab tab, boolean hint) {
+        mCurrentTab = tab;
+    }
+
+    public void destroy() {
+        TouchlessEventHandler.removeZoomCallback(this);
+        mActivityTabProvider.removeObserver(this);
+    }
+}
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHMediator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHMediator.java
index 991ba67..418d8ca 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHMediator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHMediator.java
@@ -9,9 +9,9 @@
 import org.chromium.chrome.browser.native_page.NativePageFactory;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
-import org.chromium.ui.base.CursorObserver;
-import org.chromium.ui.base.TouchlessEventHandler;
 import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.touchless.CursorObserver;
+import org.chromium.ui.touchless.TouchlessEventHandler;
 
 import java.util.concurrent.FutureTask;
 
diff --git a/chrome/android/touchless/touchless_java_sources.gni b/chrome/android/touchless/touchless_java_sources.gni
index 949b41c..d08a7836 100644
--- a/chrome/android/touchless/touchless_java_sources.gni
+++ b/chrome/android/touchless/touchless_java_sources.gni
@@ -26,6 +26,7 @@
   "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPageTopLayout.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessTabObserver.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessUiController.java",
+  "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessZoomHelper.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogProperties.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHCoordinator.java",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 46350189..c74028d 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1785,6 +1785,12 @@
      flag_descriptions::kEnableDataSaverLiteModeRebrandName,
      flag_descriptions::kEnableDataSaverLiteModeRebrandDescription, kOsAll,
      FEATURE_VALUE_TYPE(previews::features::kDataSaverLiteModeRebranding)},
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+    {"enable-save-data", flag_descriptions::kEnableSaveDataName,
+     flag_descriptions::kEnableSaveDataDescription, kOsCrOS,
+     SINGLE_VALUE_TYPE(
+         data_reduction_proxy::switches::kEnableDataReductionProxy)},
+#endif  // OS_CHROMEOS
     {"enable-client-lo-fi", flag_descriptions::kEnableClientLoFiName,
      flag_descriptions::kEnableClientLoFiDescription, kOsAll,
      FEATURE_VALUE_TYPE(previews::features::kClientLoFi)},
diff --git a/chrome/browser/chromeos/app_mode/arc/DEPS b/chrome/browser/chromeos/app_mode/arc/DEPS
new file mode 100644
index 0000000..fa19670
--- /dev/null
+++ b/chrome/browser/chromeos/app_mode/arc/DEPS
@@ -0,0 +1,6 @@
+specific_include_rules = {
+  # crbug.com/887156
+  "arc_kiosk_app_launcher\.cc": [
+    "+ash/shell.h",
+  ],
+}
diff --git a/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_launcher.cc b/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_launcher.cc
index 2862d18..099d05d 100644
--- a/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_launcher.cc
+++ b/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_launcher.cc
@@ -9,6 +9,7 @@
 
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/interfaces/window_pin_type.mojom.h"
+#include "ash/shell.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 "components/arc/metrics/arc_metrics_constants.h"
@@ -23,7 +24,8 @@
                                          Delegate* delegate)
     : app_id_(app_id), prefs_(prefs), delegate_(delegate) {
   prefs_->AddObserver(this);
-  aura::Env::GetInstance()->AddObserver(this);
+  // crbug.com/887156
+  ash::Shell::Get()->aura_env()->AddObserver(this);
   // Launching the app by app id in landscape mode and in non-touch mode.
   arc::LaunchApp(context, app_id_, ui::EF_NONE,
                  arc::UserInteractionType::NOT_USER_INITIATED);
@@ -89,7 +91,7 @@
 }
 
 void ArcKioskAppLauncher::StopObserving() {
-  aura::Env::GetInstance()->RemoveObserver(this);
+  ash::Shell::Get()->aura_env()->AddObserver(this);
   for (auto* window : windows_)
     window->RemoveObserver(this);
   windows_.clear();
diff --git a/chrome/browser/client_hints/OWNERS b/chrome/browser/client_hints/OWNERS
index 6ebcbe0..7ad3493f 100644
--- a/chrome/browser/client_hints/OWNERS
+++ b/chrome/browser/client_hints/OWNERS
@@ -1,3 +1,4 @@
+yoavweiss@chromium.org
 tbansal@chromium.org
 ryansturm@chromium.org
 
diff --git a/chrome/browser/conflicts/incompatible_applications_browsertest.cc b/chrome/browser/conflicts/incompatible_applications_browsertest.cc
index 11e6db08..512c86bb 100644
--- a/chrome/browser/conflicts/incompatible_applications_browsertest.cc
+++ b/chrome/browser/conflicts/incompatible_applications_browsertest.cc
@@ -15,6 +15,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_reg_util_win.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/win/win_util.h"
 #include "base/win/windows_version.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/conflicts/incompatible_applications_updater_win.h"
@@ -80,7 +81,8 @@
   // The name of the application deemed incompatible.
   static constexpr wchar_t kApplicationName[] = L"FooBar123";
 
-  IncompatibleApplicationsBrowserTest() = default;
+  IncompatibleApplicationsBrowserTest() : scoped_domain_(true) {}
+
   ~IncompatibleApplicationsBrowserTest() override = default;
 
   void SetUp() override {
@@ -170,6 +172,9 @@
     ASSERT_TRUE(base::CopyFile(test_dll_path, dll_path));
   }
 
+  // The feature is always disabled on domain-joined machines.
+  base::win::ScopedDomainStateForTesting scoped_domain_;
+
   // Temp directory used to host the install directory and the module list.
   base::ScopedTempDir scoped_temp_dir_;
 
diff --git a/chrome/browser/conflicts/third_party_blocking_browsertest.cc b/chrome/browser/conflicts/third_party_blocking_browsertest.cc
index 7bef32a2..1dc1f0d 100644
--- a/chrome/browser/conflicts/third_party_blocking_browsertest.cc
+++ b/chrome/browser/conflicts/third_party_blocking_browsertest.cc
@@ -14,6 +14,7 @@
 #include "base/test/test_reg_util_win.h"
 #include "base/win/registry.h"
 #include "base/win/win_util.h"
+#include "base/win/windows_version.h"
 #include "chrome/browser/conflicts/module_blacklist_cache_updater_win.h"
 #include "chrome/browser/conflicts/module_blacklist_cache_util_win.h"
 #include "chrome/browser/conflicts/module_database_win.h"
@@ -159,6 +160,9 @@
 //       browser launch.
 IN_PROC_BROWSER_TEST_F(ThirdPartyBlockingBrowserTest,
                        CreateModuleBlacklistCache) {
+  if (base::win::GetVersion() < base::win::VERSION_WIN8)
+    return;
+
   base::FilePath module_list_path;
   ASSERT_NO_FATAL_FAILURE(CreateModuleList(&module_list_path));
   ASSERT_FALSE(module_list_path.empty());
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
index 260f582..cca5246 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
@@ -776,30 +776,6 @@
                                       BYPASS_EVENT_TYPE_MALFORMED_407, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest,
-                       ProxyBypassedForCurrentRequestOn502Error) {
-  base::HistogramTester histogram_tester;
-  net::EmbeddedTestServer test_server;
-  test_server.RegisterRequestHandler(
-      base::BindRepeating(&BasicResponse, kDummyBody));
-  ASSERT_TRUE(test_server.Start());
-
-  SetStatusCode(net::HTTP_BAD_GATEWAY);
-
-  ui_test_utils::NavigateToURL(browser(),
-                               GetURLWithMockHost(test_server, "/echo"));
-  EXPECT_THAT(GetBody(), kDummyBody);
-  histogram_tester.ExpectUniqueSample(
-      "DataReductionProxy.BlockTypePrimary",
-      BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY, 1);
-
-  // Proxy should no longer be blocked, and use first proxy.
-  SetStatusCode(net::HTTP_OK);
-  ui_test_utils::NavigateToURL(browser(),
-                               GetURLWithMockHost(test_server, "/echo"));
-  EXPECT_EQ(GetBody(), kPrimaryResponse);
-}
-
 // Tests that if using data reduction proxy results in redirect loop, then
 // the proxy is bypassed, and the request is fetched directly.
 IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, RedirectCycle) {
diff --git a/chrome/browser/download/download_frame_policy_browsertest.cc b/chrome/browser/download/download_frame_policy_browsertest.cc
index b42f3b5..f668c38e 100644
--- a/chrome/browser/download/download_frame_policy_browsertest.cc
+++ b/chrome/browser/download/download_frame_policy_browsertest.cc
@@ -32,6 +32,7 @@
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/network/public/cpp/features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 #include "url/url_constants.h"
@@ -136,10 +137,6 @@
 class DownloadFramePolicyBrowserTest
     : public subresource_filter::SubresourceFilterBrowserTest {
  public:
-  DownloadFramePolicyBrowserTest() {
-    scoped_feature_list_.InitAndEnableFeature(subresource_filter::kAdTagging);
-  }
-
   ~DownloadFramePolicyBrowserTest() override {}
 
   void SetUpOnMainThread() override {
@@ -281,7 +278,6 @@
   std::string GetSubframeId() { return "test"; }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
   std::unique_ptr<content::DownloadTestObserver> download_observer_;
   std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>
@@ -619,6 +615,48 @@
             SandboxOption::kAllowDownloadsWithoutUserActivation),
         ::testing::Bool()));
 
+class DefaultBlockSandboxDownloadBrowserTest
+    : public DownloadFramePolicyBrowserTest {
+ public:
+  DefaultBlockSandboxDownloadBrowserTest() {
+    scoped_feature_list_.InitAndDisableFeature(
+        network::features::kNetworkService);
+  }
+
+  ~DefaultBlockSandboxDownloadBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    SetRuntimeFeatureCommand(
+        true, "BlockingDownloadsInSandboxWithoutUserActivation", command_line);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// To test PDF works fine when the policy disallows just download, which
+// essentially tests that the appropriate ResourceInterceptPolicy
+// |kAllowPluginOnly| is set for resource handling.
+//
+// When NetworkService is disabled and when ResourceInterceptPolicy has been
+// incorrectly set to |kAllowNone|, no stream interceptor will be set up to
+// handle the PDF content, and the content will instead be sent to the renderer
+// where a UTF-8 GUID is expected to arrive. It'll then hit a DCHECK checking
+// the UTF-8-ness of the GUID.
+//
+// TODO(yaoxia): Use a more straightforward approach to assert that the pdf
+// content displays fine rather than relying on the DCHECK failure that would
+// happen with an incorrect implementation.
+IN_PROC_BROWSER_TEST_F(DefaultBlockSandboxDownloadBrowserTest, PdfNotBlocked) {
+  InitializeOneSubframeSetup(
+      SandboxOption::kDisallowDownloadsWithoutUserActivation,
+      false /* is_ad_frame */, false /* is_cross_origin */);
+  content::TestNavigationObserver navigation_observer(web_contents());
+  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(GetSubframeRfh(),
+                                              "top.location = 'test.pdf';"));
+  navigation_observer.Wait();
+}
+
 // Download gets blocked when LoadPolicy is DISALLOW for the navigation to
 // download. This test is technically unrelated to policy on frame, but stays
 // here for convenience.
diff --git a/chrome/browser/download/download_service_factory.cc b/chrome/browser/download/download_service_factory.cc
index 242fb363..8818a84 100644
--- a/chrome/browser/download/download_service_factory.cc
+++ b/chrome/browser/download/download_service_factory.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_constants.h"
-#include "components/download/content/factory/download_service_factory.h"
+#include "components/download/content/factory/download_service_factory_helper.h"
 #include "components/download/public/background_service/clients.h"
 #include "components/download/public/background_service/download_service.h"
 #include "components/download/public/background_service/features.h"
diff --git a/chrome/browser/extensions/extension_disabled_ui.cc b/chrome/browser/extensions/extension_disabled_ui.cc
index bc98590..03a0308 100644
--- a/chrome/browser/extensions/extension_disabled_ui.cc
+++ b/chrome/browser/extensions/extension_disabled_ui.cc
@@ -219,8 +219,8 @@
       messages.push_back(
           l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO));
   } else {
-    // TODO(treib): If NeedCustodianApprovalForPermissionIncrease, add an extra
-    // message for supervised users. crbug.com/461261
+    // TODO(crbug.com/461261): If NeedCustodianApprovalForPermissionIncrease,
+    // add an extra message for supervised users.
     messages.push_back(
         l10n_util::GetStringUTF16(IDS_EXTENSION_DISABLED_ERROR_LABEL));
   }
@@ -233,8 +233,8 @@
 
 base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
   if (util::IsExtensionSupervised(extension_, service_->profile())) {
-    // TODO(treib): Probably use a new string here once we get UX design.
-    // For now, just use "OK". crbug.com/461261
+    // TODO(crbug.com/461261): Probably use a new string here once we get UX
+    // design. For now, just use "OK".
     return l10n_util::GetStringUTF16(IDS_OK);
   }
   if (is_remote_install_) {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index ac135e5..addaf52 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -11,7 +11,8 @@
 //   used as a primary key. The value is a string.
 //
 //   owners: the person(s) or team(s) responsible for this flag. The value is a
-//   nonempty list of strings, each of which is either:
+//   nonempty list of strings, in order of specificity (i.e., the first entry
+//   on the list is the best contact). Each entry is either:
 //
 //     - A string containing '@', which is treated as an email address;
 //     - A string beginning with '//', which is treated as a path to a file
@@ -1576,6 +1577,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-save-data",
+    "owners": [ "//components/data_reduction_proxy/OWNERS" ],
+    "expiry_milestone": 77
+  },
+  {
     "name": "enable-scroll-anchor-serialization",
     "owners": [ "pnoland" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 09e6a5a0..16443a5 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -513,6 +513,11 @@
 const char kEnableDataSaverLiteModeRebrandDescription[] =
     "Enable the Data Saver rebranding to Lite Mode.";
 
+const char kEnableSaveDataName[] = "Enables save data feature";
+const char kEnableSaveDataDescription[] =
+    "Enables save data feature. May cause user's traffic to be proxied via "
+    "Google's data reduction proxy.";
+
 const char kEnableClientLoFiName[] = "Client-side Lo-Fi previews";
 
 const char kEnableClientLoFiDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index bf81bec..898d349 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -331,6 +331,9 @@
 extern const char kEnableDataSaverLiteModeRebrandName[];
 extern const char kEnableDataSaverLiteModeRebrandDescription[];
 
+extern const char kEnableSaveDataName[];
+extern const char kEnableSaveDataDescription[];
+
 extern const char kEnableClientLoFiName[];
 extern const char kEnableClientLoFiDescription[];
 
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index 249a433..e555b0c 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -1511,28 +1511,6 @@
   NavigateToDestURL();
 }
 
-// Prerenders a page that contains an automatic download triggered through an
-// iframe. This should not prerender successfully.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderDownloadIframe) {
-  PrerenderTestURL("/prerender/prerender_download_iframe.html",
-                   FINAL_STATUS_DOWNLOAD, 0);
-}
-
-// Prerenders a page that contains an automatic download triggered through
-// Javascript changing the window.location. This should not prerender
-// successfully
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderDownloadLocation) {
-  PrerenderTestURL(CreateClientRedirect("/download-test1.lib"),
-                   FINAL_STATUS_DOWNLOAD, 1);
-}
-
-// Prerenders a page that contains an automatic download triggered through a
-// client-issued redirect. This should not prerender successfully.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderDownloadClientRedirect) {
-  PrerenderTestURL("/prerender/prerender_download_refresh.html",
-                   FINAL_STATUS_DOWNLOAD, 1);
-}
-
 // Checks that the referrer is set when prerendering.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderReferrer) {
   PrerenderTestURL("/prerender/prerender_referrer.html", FINAL_STATUS_USED, 1);
@@ -1915,12 +1893,6 @@
   NavigateToDestURL();
 }
 
-// Checks that a prerender of a CRX will result in a cancellation due to
-// download.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCrx) {
-  PrerenderTestURL("/prerender/extension.crx", FINAL_STATUS_DOWNLOAD, 0);
-}
-
 // Checks that xhr GET requests allow prerenders.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderXhrGet) {
   PrerenderTestURL("/prerender/prerender_xhr_get.html", FINAL_STATUS_USED, 1);
@@ -2599,16 +2571,6 @@
   NavigateToDestURL();
 }
 
-// Checks that non-http/https main page redirects cancel the prerender.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
-                       PrerenderCancelMainFrameRedirectUnsupportedScheme) {
-  // Disable load event checks because they race with cancellation.
-  DisableLoadEventCheck();
-  GURL url = embedded_test_server()->GetURL(
-      CreateServerRedirect("invalidscheme://www.google.com/test.html"));
-  PrerenderTestURL(url, FINAL_STATUS_UNSUPPORTED_SCHEME, 0);
-}
-
 // Checks that media source video loads are deferred on prerendering.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderHTML5MediaSourceVideo) {
   PrerenderTestURL("/prerender/prerender_html5_video_media_source.html",
diff --git a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
index e6799b4..19fda2f8 100644
--- a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
+++ b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
@@ -61,6 +61,11 @@
 
 const char kExpectedPurposeHeaderOnPrefetch[] = "Purpose";
 
+std::string CreateServerRedirect(const std::string& dest_url) {
+  const char* const kServerRedirectBase = "/server-redirect?";
+  return kServerRedirectBase + net::EscapeQueryParamValue(dest_url, false);
+}
+
 }  // namespace
 
 namespace prerender {
@@ -86,6 +91,7 @@
     "/prerender/prefetch_response_csp.html";
 const char kPrefetchScript[] = "/prerender/prefetch.js";
 const char kPrefetchScript2[] = "/prerender/prefetch2.js";
+const char kPrefetchDownloadFile[] = "/download-test1.lib";
 const char kPrefetchSubresourceRedirectPage[] =
     "/prerender/prefetch_subresource_redirect.html";
 const char kServiceWorkerLoader[] = "/prerender/service_worker.html";
@@ -512,12 +518,20 @@
 
 // Checks that a 301 redirect is followed.
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, Prefetch301Redirect) {
-  PrefetchFromFile(
-      "/server-redirect/?" + net::EscapeQueryParamValue(kPrefetchPage, false),
-      FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
+  PrefetchFromFile(CreateServerRedirect(kPrefetchPage),
+                   FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
   WaitForRequestCount(src_server()->GetURL(kPrefetchScript), 1);
 }
 
+// Checks that non-HTTP(S) main resource redirects are marked as unsupported
+// scheme.
+IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
+                       PrefetchRedirectUnsupportedScheme) {
+  PrefetchFromFile(
+      CreateServerRedirect("invalidscheme://www.google.com/test.html"),
+      FINAL_STATUS_UNSUPPORTED_SCHEME);
+}
+
 // Checks that a 302 redirect is followed.
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, Prefetch302Redirect) {
   PrefetchFromFile(k302RedirectPage, FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
@@ -527,8 +541,7 @@
 // Checks that the load flags are set correctly for all resources in a 301
 // redirect chain.
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, Prefetch301LoadFlags) {
-  std::string redirect_path =
-      "/server-redirect/?" + net::EscapeQueryParamValue(kPrefetchPage, false);
+  std::string redirect_path = CreateServerRedirect(kPrefetchPage);
   GURL redirect_url = src_server()->GetURL(redirect_path);
   GURL page_url = src_server()->GetURL(kPrefetchPage);
   content::URLLoaderInterceptor interceptor(base::BindRepeating(
@@ -559,13 +572,40 @@
       FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
   ui_test_utils::NavigateToURL(current_browser(),
                                src_server()->GetURL(kPrefetchPage2));
-  // A complete load of kPrefetchPage2 is used as a sentinal. Otherwise the test
+  // A complete load of kPrefetchPage2 is used as a sentinel. Otherwise the test
   // ends before script_counter would reliably see the load of kPrefetchScript,
   // were it to happen.
   WaitForRequestCount(src_server()->GetURL(kPrefetchScript2), 1);
   WaitForRequestCount(src_server()->GetURL(kPrefetchScript), 0);
 }
 
+// Prefetches a page that contains an automatic download triggered through an
+// iframe. The request to download should not reach the server.
+IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, PrefetchDownloadIframe) {
+  PrefetchFromFile("/prerender/prerender_download_iframe.html",
+                   FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
+  // A complete load of kPrefetchPage2 is used as a sentinel as in test
+  // |PrefetchClientRedirect| above.
+  WaitForRequestCount(src_server()->GetURL(kPrefetchScript2), 1);
+  WaitForRequestCount(src_server()->GetURL(kPrefetchDownloadFile), 0);
+}
+
+IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
+                       PrefetchDownloadViaClientRedirect) {
+  PrefetchFromFile("/prerender/prerender_download_refresh.html",
+                   FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
+  // A complete load of kPrefetchPage2 is used as a sentinel as in test
+  // |PrefetchClientRedirect| above.
+  WaitForRequestCount(src_server()->GetURL(kPrefetchScript2), 1);
+  WaitForRequestCount(src_server()->GetURL(kPrefetchDownloadFile), 0);
+}
+
+// Checks that a prefetch of a CRX will result in a cancellation due to
+// download.
+IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, PrefetchCrx) {
+  PrefetchFromFile("/prerender/extension.crx", FINAL_STATUS_DOWNLOAD);
+}
+
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, PrefetchHttps) {
   UseHttpsSrcServer();
   PrefetchFromFile(kPrefetchPage, FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc
index defcbd10..2fc1cded 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc
@@ -40,7 +40,7 @@
 constexpr char kSRTPromptTrial[] = "SRTPromptFieldTrial";
 
 const base::Feature kRebootPromptDialogFeature{
-    "RebootPromptDialog", base::FEATURE_DISABLED_BY_DEFAULT};
+    "RebootPromptDialog", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kChromeCleanupDistributionFeature{
     "ChromeCleanupDistribution", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -111,7 +111,7 @@
     return REBOOT_PROMPT_TYPE_OPEN_SETTINGS_PAGE;
   if (base::GetFieldTrialParamByFeatureAsBool(kRebootPromptDialogFeature,
                                               kIsModalParam,
-                                              /*default_value=*/false)) {
+                                              /*default_value=*/true)) {
     return REBOOT_PROMPT_TYPE_SHOW_MODAL_DIALOG;
   } else {
     return REBOOT_PROMPT_TYPE_SHOW_NON_MODAL_DIALOG;
diff --git a/chrome/browser/sync/test/integration/multi_client_status_change_checker.cc b/chrome/browser/sync/test/integration/multi_client_status_change_checker.cc
index 7bade45..98f49f0b 100644
--- a/chrome/browser/sync/test/integration/multi_client_status_change_checker.cc
+++ b/chrome/browser/sync/test/integration/multi_client_status_change_checker.cc
@@ -20,9 +20,3 @@
 void MultiClientStatusChangeChecker::OnStateChanged(syncer::SyncService* sync) {
   CheckExitCondition();
 }
-
-base::TimeDelta MultiClientStatusChangeChecker::GetTimeoutDuration() {
-  // TODO(crbug.com/802025): This increased timeout seems to have become
-  // necessary with kSyncUSSTypedURL. We should figure out why.
-  return base::TimeDelta::FromSeconds(90);
-}
diff --git a/chrome/browser/sync/test/integration/multi_client_status_change_checker.h b/chrome/browser/sync/test/integration/multi_client_status_change_checker.h
index 26e5260..97b98d4 100644
--- a/chrome/browser/sync/test/integration/multi_client_status_change_checker.h
+++ b/chrome/browser/sync/test/integration/multi_client_status_change_checker.h
@@ -10,7 +10,6 @@
 
 #include "base/compiler_specific.h"
 #include "base/scoped_observer.h"
-#include "base/time/time.h"
 #include "chrome/browser/sync/test/integration/status_change_checker.h"
 #include "components/sync/driver/sync_service_observer.h"
 
@@ -39,8 +38,6 @@
   std::string GetDebugMessage() const override = 0;
 
  protected:
-  base::TimeDelta GetTimeoutDuration() override;
-
   const std::vector<browser_sync::ProfileSyncService*>& services() {
     return services_;
   }
diff --git a/chrome/browser/sync/test/integration/status_change_checker.cc b/chrome/browser/sync/test/integration/status_change_checker.cc
index 78fd447c..596970cb 100644
--- a/chrome/browser/sync/test/integration/status_change_checker.cc
+++ b/chrome/browser/sync/test/integration/status_change_checker.cc
@@ -8,6 +8,13 @@
 #include "base/logging.h"
 #include "base/run_loop.h"
 #include "base/timer/timer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(30);
+
+}  // namespace
 
 StatusChangeChecker::StatusChangeChecker()
     : run_loop_(base::RunLoop::Type::kNestableTasksAllowed),
@@ -29,10 +36,6 @@
   return timed_out_;
 }
 
-base::TimeDelta StatusChangeChecker::GetTimeoutDuration() {
-  return base::TimeDelta::FromSeconds(45);
-}
-
 void StatusChangeChecker::StopWaiting() {
   if (run_loop_.running())
     run_loop_.Quit();
@@ -50,7 +53,7 @@
   DCHECK(!run_loop_.running());
 
   base::OneShotTimer timer;
-  timer.Start(FROM_HERE, GetTimeoutDuration(),
+  timer.Start(FROM_HERE, kTimeout,
               base::BindRepeating(&StatusChangeChecker::OnTimeout,
                                   base::Unretained(this)));
 
@@ -58,7 +61,7 @@
 }
 
 void StatusChangeChecker::OnTimeout() {
-  DVLOG(1) << "Await -> Timed out: " << GetDebugMessage();
+  ADD_FAILURE() << "Await -> Timed out: " << GetDebugMessage();
   timed_out_ = true;
   StopWaiting();
 }
diff --git a/chrome/browser/sync/test/integration/status_change_checker.h b/chrome/browser/sync/test/integration/status_change_checker.h
index 71896dde..01017ba 100644
--- a/chrome/browser/sync/test/integration/status_change_checker.h
+++ b/chrome/browser/sync/test/integration/status_change_checker.h
@@ -43,9 +43,6 @@
  protected:
   virtual ~StatusChangeChecker();
 
-  // Timeout length when blocking.
-  virtual base::TimeDelta GetTimeoutDuration();
-
   // Stop the nested running of the message loop started in StartBlockingWait().
   void StopWaiting();
 
diff --git a/chrome/browser/ui/ash/tablet_mode_client_test_util.cc b/chrome/browser/ui/ash/tablet_mode_client_test_util.cc
index 19131ed1..94f5945 100644
--- a/chrome/browser/ui/ash/tablet_mode_client_test_util.cc
+++ b/chrome/browser/ui/ash/tablet_mode_client_test_util.cc
@@ -44,9 +44,9 @@
 
 }  // namespace
 
-// Enables or disables the tablet mode and waits to until the change has made
-// its way back into Chrome (from Ash). Should only be called to toggle the
-// current mode.
+// Enables or disables the tablet mode and waits until the change has made its
+// way back into Chrome (from Ash). Should only be called to toggle the current
+// mode.
 void SetAndWaitForTabletMode(bool enabled) {
   ASSERT_NE(enabled, TabletModeClient::Get()->tablet_mode_enabled());
 
diff --git a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
index bead9066..15f3efa1 100644
--- a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
@@ -329,7 +329,8 @@
 }
 
 std::string HostedAppBrowserController::GetAppShortName() const {
-  return GetExtension()->short_name();
+  const Extension* extension = GetExtension();
+  return extension ? extension->short_name() : std::string();
 }
 
 base::string16 HostedAppBrowserController::GetFormattedUrlOrigin() const {
diff --git a/chrome/browser/ui/startup/bad_flags_prompt.cc b/chrome/browser/ui/startup/bad_flags_prompt.cc
index b8e5e35..182a3db6 100644
--- a/chrome/browser/ui/startup/bad_flags_prompt.cc
+++ b/chrome/browser/ui/startup/bad_flags_prompt.cc
@@ -22,6 +22,7 @@
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/autofill/core/common/autofill_switches.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
 #include "components/infobars/core/infobar_delegate.h"
 #include "components/infobars/core/simple_alert_infobar_delegate.h"
 #include "components/invalidation/impl/invalidation_switches.h"
@@ -121,6 +122,10 @@
     // UI websites can scan for bluetooth without user intervention. Show a
     // warning until the UI is complete.
     switches::kEnableWebBluetoothScanning,
+
+    // Enables save data feature which can cause user traffic to be proxied via
+    // Google's data reduction proxy servers.
+    data_reduction_proxy::switches::kEnableDataReductionProxy,
 };
 #endif  // OS_ANDROID
 
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 e7ef893..c92b6b5 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
@@ -20,7 +20,6 @@
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "ui/aura/window.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/views/view_observer.h"
 #include "ui/wm/core/window_util.h"
@@ -30,7 +29,7 @@
 class ViewBoundsChangeWaiter : public views::ViewObserver {
  public:
   static void VerifyY(views::View* view, int y) {
-    if (features::IsMultiProcessMash() && y != view->bounds().y())
+    if (y != view->bounds().y())
       ViewBoundsChangeWaiter(view).run_loop_.Run();
 
     EXPECT_EQ(y, view->bounds().y());
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index bf28dc82..3a550d0 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -694,10 +694,8 @@
   if (old.pinned != data_.pinned)
     showing_alert_indicator_ = false;
 
-  if (data_.alert_state != old.alert_state || data_.title != old.title) {
+  if (data_.alert_state != old.alert_state || data_.title != old.title)
     TooltipTextChanged();
-    controller_->UpdateHoverCard(this, mouse_hovered_);
-  }
 
   Layout();
   SchedulePaint();
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 5821f1a..10039e9e 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -668,6 +668,9 @@
   const bool pinned_state_changed = tab->data().pinned != data.pinned;
   tab->SetData(std::move(data));
 
+  if (HoverCardIsShowingForTab(tab))
+    UpdateHoverCard(tab, true);
+
   if (pinned_state_changed) {
     if (touch_layout_) {
       int pinned_tab_count = 0;
@@ -2718,6 +2721,14 @@
       extra_vertical_space / 2, kHorizontalInset, 0, kHorizontalInset)));
 }
 
+bool TabStrip::HoverCardIsShowingForTab(Tab* tab) {
+  if (!base::FeatureList::IsEnabled(features::kTabHoverCards))
+    return false;
+
+  return hover_card_ && hover_card_->GetWidget()->IsVisible() &&
+         hover_card_->GetAnchorView() == tab;
+}
+
 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
   if (sender == new_tab_button_) {
     base::RecordAction(base::UserMetricsAction("NewTab_Button"));
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index f8e2519..8b58c6d7 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -613,6 +613,9 @@
   // whenever any input of the computation of the border's sizing changes.
   void UpdateNewTabButtonBorder();
 
+  // Returns true if the hover card is showing for the given tab.
+  bool HoverCardIsShowingForTab(Tab* tab);
+
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
diff --git a/chrome/browser/ui/views/task_manager_view.cc b/chrome/browser/ui/views/task_manager_view.cc
index 0a3a2ba..3e687e1 100644
--- a/chrome/browser/ui/views/task_manager_view.cc
+++ b/chrome/browser/ui/views/task_manager_view.cc
@@ -339,6 +339,7 @@
   table_model_.reset(new TaskManagerTableModel(this));
   tab_table->SetModel(table_model_.get());
   tab_table->SetGrouper(this);
+  tab_table->set_sort_on_paint(true);
   tab_table->set_observer(this);
   tab_table->set_context_menu_controller(this);
   set_context_menu_controller(this);
diff --git a/chrome/common/client_hints/OWNERS b/chrome/common/client_hints/OWNERS
index 6ebcbe0..7ad3493f 100644
--- a/chrome/common/client_hints/OWNERS
+++ b/chrome/common/client_hints/OWNERS
@@ -1,3 +1,4 @@
+yoavweiss@chromium.org
 tbansal@chromium.org
 ryansturm@chromium.org
 
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index acb917f..baf8d3f 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -672,7 +672,7 @@
       {"hosts6", false},        // http://a.com -> http://a.com + http://a.co.uk
       {"permissions1", false},  // tabs -> tabs
       {"permissions2", true},   // tabs -> tabs,bookmarks
-      // TODO(treib): This is wrong, kAllHosts implies kTabs. crbug.com/512344
+      // TODO(crbug.com/512344): This is wrong, kAllHosts implies kTabs.
       {"permissions3", true},          // http://*/* -> http://*/*,tabs
       {"permissions5", true},          // bookmarks -> bookmarks,history
       {"equivalent_warnings", false},  // tabs --> tabs, webNavigation
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java
index a9faf5d..53cf717 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java
@@ -8,7 +8,6 @@
 
 import android.annotation.SuppressLint;
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.support.v4.content.ContextCompat;
 
 import org.chromium.content_public.browser.test.util.Criteria;
@@ -50,8 +49,6 @@
                     @SuppressLint({"ApplySharedPref", "CommitPrefEdits"})
                     @Override
                     public boolean isSatisfied() {
-                        SharedPreferences multidexPrefs =
-                                targetContext.getSharedPreferences("multidex.version", 0);
                         if (!mDataRemoved && !removeAppData(targetContext)) {
                             return false;
                         }
@@ -60,11 +57,6 @@
                         // will try to create it otherwise and will fail for sandbox processes with
                         // a NullPointerException.
                         File cacheDir = new File(ContextCompat.getDataDir(targetContext), "cache");
-                        // Removing app data cleared out all shared prefs. Multidex uses shared
-                        // prefs to cache hashes of the secondary dexes it has extracted; without
-                        // them, it'll attempt to reextract the dexes the next time the tests
-                        // start up.
-                        multidexPrefs.edit().commit();
                         return cacheDir.exists() || cacheDir.mkdir();
                     }
                 },
@@ -74,8 +66,6 @@
     /**
      * Remove all files and directories under the given application directory, except 'lib'.
      *
-     * @param appDir the application directory to remove.
-     *
      * @return whether removal succeeded.
      */
     private static boolean removeAppData(final Context targetContext) {
@@ -84,15 +74,38 @@
         File[] files = dataDir.listFiles();
         if (files == null) return true;
         for (File file : files) {
-            if (!(file.getName().equals("lib") || file.getName().equals("incremental-install-files")
-                        || file.getName().equals(codeCacheDir.getName()))
-                    && !removeFile(file)) {
+            // Symlink to app's native libraries.
+            if (file.getName().equals("lib")) {
+                continue;
+            }
+            if (file.getName().equals("incremental-install-files")) {
+                continue;
+            }
+            if (file.getName().equals(codeCacheDir.getName())) {
+                continue;
+            }
+            // SharedPreferences are cached in memory, so clearing their files doesn't help anyways.
+            // Some preferences need to persist (e.g. multidex.version.xml).
+            if (file.getName().equals("shared_prefs")) {
+                removeSharedPrefs(file);
+                continue;
+            }
+            if (!removeFile(file)) {
                 return false;
             }
         }
         return true;
     }
 
+    // TODO(agrieve): Use InMemorySharedPrefs rather than having to delete from disk.
+    private static void removeSharedPrefs(File sharedPrefsDir) {
+        for (File f : sharedPrefsDir.listFiles()) {
+            if (!f.getName().endsWith("multidex.version.xml")) {
+                f.delete();
+            }
+        }
+    }
+
     /**
      * Remove the given file or directory.
      *
diff --git a/chrome/test/data/prerender/prerender_download_iframe.html b/chrome/test/data/prerender/prerender_download_iframe.html
index 9b31b551..66363db 100644
--- a/chrome/test/data/prerender/prerender_download_iframe.html
+++ b/chrome/test/data/prerender/prerender_download_iframe.html
@@ -3,5 +3,6 @@
   <body>
     <iframe name="download" src="../download-test1.lib" width="0" height="0"
       frameborder="0"></iframe>
+    <script src="prefetch2.js" type="text/javascript"></script>
   </body>
 </html>
diff --git a/chrome/test/data/prerender/prerender_download_refresh.html b/chrome/test/data/prerender/prerender_download_refresh.html
index aea05e3..a8735bba 100644
--- a/chrome/test/data/prerender/prerender_download_refresh.html
+++ b/chrome/test/data/prerender/prerender_download_refresh.html
@@ -2,5 +2,6 @@
   <head><title>Prerender download test - refresh</title>
     <meta http-equiv="refresh" content="1;url=../download-test1.lib"/>
   </head>
+  <script src="prefetch2.js" type="text/javascript"></script>
   <body></body>
 </html>
diff --git a/components/autofill_assistant/browser/batch_element_checker.cc b/components/autofill_assistant/browser/batch_element_checker.cc
index 9fbb8c5..b8f48b521 100644
--- a/components/autofill_assistant/browser/batch_element_checker.cc
+++ b/components/autofill_assistant/browser/batch_element_checker.cc
@@ -84,7 +84,7 @@
 
     const auto& call_arguments = entry.first;
     web_controller_->ElementCheck(
-        call_arguments.first, call_arguments.second,
+        call_arguments.first, call_arguments.second, /* strict= */ false,
         base::BindOnce(
             &BatchElementChecker::OnElementChecked,
             weak_ptr_factory_.GetWeakPtr(),
diff --git a/components/autofill_assistant/browser/mock_web_controller.h b/components/autofill_assistant/browser/mock_web_controller.h
index 2c42fe9..e2d446a 100644
--- a/components/autofill_assistant/browser/mock_web_controller.h
+++ b/components/autofill_assistant/browser/mock_web_controller.h
@@ -44,6 +44,7 @@
 
   void ElementCheck(ElementCheckType check_type,
                     const Selector& selector,
+                    bool strict,
                     base::OnceCallback<void(bool)> callback) override {
     OnElementCheck(check_type, selector, callback);
   }
diff --git a/components/autofill_assistant/browser/selector.cc b/components/autofill_assistant/browser/selector.cc
index 6e41fa6..2b2ada2 100644
--- a/components/autofill_assistant/browser/selector.cc
+++ b/components/autofill_assistant/browser/selector.cc
@@ -43,12 +43,75 @@
   return this->selectors.empty();
 }
 
+std::ostream& operator<<(std::ostream& out, PseudoType pseudo_type) {
+#ifdef NDEBUG
+  return out << static_cast<int>(pseudo_type);
+#else
+  switch (pseudo_type) {
+    case UNDEFINED:
+      out << "UNDEFINED";
+      break;
+    case FIRST_LINE:
+      out << "FIRST_LINE";
+      break;
+    case FIRST_LETTER:
+      out << "FIRST_LETTER";
+      break;
+    case BEFORE:
+      out << "BEFORE";
+      break;
+    case AFTER:
+      out << "AFTER";
+      break;
+    case BACKDROP:
+      out << "BACKDROP";
+      break;
+    case SELECTION:
+      out << "SELECTION";
+      break;
+    case FIRST_LINE_INHERITED:
+      out << "FIRST_LINE_INHERITED";
+      break;
+    case SCROLLBAR:
+      out << "SCROLLBAR";
+      break;
+    case SCROLLBAR_THUMB:
+      out << "SCROLLBAR_THUMB";
+      break;
+    case SCROLLBAR_BUTTON:
+      out << "SCROLLBAR_BUTTON";
+      break;
+    case SCROLLBAR_TRACK:
+      out << "SCROLLBAR_TRACK";
+      break;
+    case SCROLLBAR_TRACK_PIECE:
+      out << "SCROLLBAR_TRACK_PIECE";
+      break;
+    case SCROLLBAR_CORNER:
+      out << "SCROLLBAR_CORNER";
+      break;
+    case RESIZER:
+      out << "RESIZER";
+      break;
+    case INPUT_LIST_BUTTON:
+      out << "INPUT_LIST_BUTTON";
+      break;
+
+      // Intentionally no default case to make compilation fail if a new value
+      // was added to the enum but not to this list.
+  }
+  return out;
+#endif
+}
 std::ostream& operator<<(std::ostream& out, const Selector& selector) {
 #ifdef NDEBUG
   out << selector.selectors.size() << " element(s)";
   return out;
 #else
   out << "elements=[" << base::JoinString(selector.selectors, ",") << "]";
+  if (selector.pseudo_type != PseudoType::UNDEFINED) {
+    out << "::" << selector.pseudo_type;
+  }
   return out;
 #endif  // NDEBUG
 }
diff --git a/components/autofill_assistant/browser/web_controller.cc b/components/autofill_assistant/browser/web_controller.cc
index cad7111..90dd094 100644
--- a/components/autofill_assistant/browser/web_controller.cc
+++ b/components/autofill_assistant/browser/web_controller.cc
@@ -142,8 +142,9 @@
     }())
     )";
 
-// Javascript code to query all elements for a selector.
-const char* const kQuerySelectorAll =
+// Javascript code to query an elements for a selector, either the first
+// (non-strict) or a single (strict) element.
+const char* const kQuerySelector =
     R"(function (selector, strictMode) {
       var found = this.querySelectorAll(selector);
       if(found.length == 1)
@@ -153,6 +154,22 @@
       return undefined;
     })";
 
+// Javascript code to query a visible elements for a selector, either the first
+// (non-strict) or a single (strict) visible element.q
+const char* const kQuerySelectorVisible =
+    R"(function (selector, strict) {
+        var found = this.querySelectorAll(selector);
+        var found_index = -1;
+        for (let i = 0; i < found.length; i++) {
+          if (found[i].getClientRects().length > 0) {
+            if (found_index != -1) return undefined;
+            found_index = i;
+            if (!strict) break;
+          }
+        }
+        return found_index == -1 ? undefined : found[found_index];
+    })";
+
 // Javascript code to query whether the document is ready for interact.
 const char* const kIsDocumentReadyForInteract =
     R"(function () {
@@ -412,6 +429,7 @@
   ElementFinder(content::WebContents* web_contents_,
                 DevtoolsClient* devtools_client,
                 const Selector& selector,
+                ElementCheckType check_type,
                 bool strict);
   ~ElementFinder() override;
 
@@ -442,6 +460,7 @@
   content::WebContents* const web_contents_;
   DevtoolsClient* const devtools_client_;
   const Selector selector_;
+  const ElementCheckType check_type_;
   const bool strict_;
   FindElementCallback callback_;
   std::unique_ptr<FindElementResult> element_result_;
@@ -452,10 +471,12 @@
 WebController::ElementFinder::ElementFinder(content::WebContents* web_contents,
                                             DevtoolsClient* devtools_client,
                                             const Selector& selector,
+                                            ElementCheckType check_type,
                                             bool strict)
     : web_contents_(web_contents),
       devtools_client_(devtools_client),
       selector_(selector),
+      check_type_(check_type),
       strict_(strict),
       element_result_(std::make_unique<FindElementResult>()),
       weak_ptr_factory_(this) {}
@@ -499,15 +520,26 @@
                             .SetValue(base::Value::ToUniquePtrValue(
                                 base::Value(selector_.selectors[index])))
                             .Build());
+  // For finding intermediate elements, strict mode would be more appropriate,
+  // as long as the logic does not support more than one intermediate match.
+  //
+  // TODO(b/129387787): first, add logging to figure out whether it matters and
+  // decide between strict mode and full support for multiple matching
+  // intermeditate elements.
   argument.emplace_back(
       runtime::CallArgument::Builder()
           .SetValue(base::Value::ToUniquePtrValue(base::Value(strict_)))
           .Build());
+  const char* function = (index == (selector_.selectors.size() - 1) &&
+                          selector_.pseudo_type == PseudoType::UNDEFINED &&
+                          check_type_ == kVisibilityCheck)
+                             ? kQuerySelectorVisible
+                             : kQuerySelector;
   devtools_client_->GetRuntime()->CallFunctionOn(
       runtime::CallFunctionOnParams::Builder()
           .SetObjectId(object_id)
           .SetArguments(std::move(argument))
-          .SetFunctionDeclaration(std::string(kQuerySelectorAll))
+          .SetFunctionDeclaration(function)
           .Build(),
       base::BindOnce(&WebController::ElementFinder::OnQuerySelectorAll,
                      weak_ptr_factory_.GetWeakPtr(), index));
@@ -736,7 +768,8 @@
     const Selector& selector,
     base::OnceCallback<void(const ClientStatus&)> callback) {
   DCHECK(!selector.empty());
-  FindElement(selector, /* strict_mode= */ true,
+  FindElement(selector, kVisibilityCheck,
+              /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForClickOrTap,
                              weak_ptr_factory_.GetWeakPtr(),
                              std::move(callback), /* is_a_click= */ true));
@@ -746,7 +779,8 @@
     const Selector& selector,
     base::OnceCallback<void(const ClientStatus&)> callback) {
   DCHECK(!selector.empty());
-  FindElement(selector, /* strict_mode= */ true,
+  FindElement(selector, kVisibilityCheck,
+              /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForClickOrTap,
                              weak_ptr_factory_.GetWeakPtr(),
                              std::move(callback), /* is_a_click= */ false));
@@ -931,49 +965,28 @@
 
 void WebController::ElementCheck(ElementCheckType check_type,
                                  const Selector& selector,
+                                 bool strict,
                                  base::OnceCallback<void(bool)> callback) {
   DCHECK(!selector.empty());
-  // We don't use strict_mode because we only check for the existence of at
-  // least one such element and we don't act on it.
-  FindElement(selector, /* strict_mode= */ false,
-              base::BindOnce(&WebController::OnFindElementForCheck,
-                             weak_ptr_factory_.GetWeakPtr(), check_type,
-                             std::move(callback)));
-}
-
-void WebController::OnFindElementForCheck(
-    ElementCheckType check_type,
-    base::OnceCallback<void(bool)> callback,
-    const ClientStatus& status,
-    std::unique_ptr<FindElementResult> result) {
-  if (!status.ok()) {
-    std::move(callback).Run(false);
-    return;
-  }
-  if (check_type == kExistenceCheck) {
-    std::move(callback).Run(true);
-    return;
-  }
-  DCHECK_EQ(check_type, kVisibilityCheck);
-
-  devtools_client_->GetDOM()->GetBoxModel(
-      dom::GetBoxModelParams::Builder().SetObjectId(result->object_id).Build(),
-      base::BindOnce(&WebController::OnGetBoxModelForVisible,
+  FindElement(
+      selector, check_type, strict,
+      base::BindOnce(&WebController::OnFindElementForCheck,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void WebController::OnGetBoxModelForVisible(
+void WebController::OnFindElementForCheck(
     base::OnceCallback<void(bool)> callback,
-    std::unique_ptr<dom::GetBoxModelResult> result) {
-  std::move(callback).Run(result && result->GetModel() &&
-                          result->GetModel()->GetContent());
+    const ClientStatus& status,
+    std::unique_ptr<FindElementResult> result) {
+  std::move(callback).Run(status.ok());
 }
 
 void WebController::FindElement(const Selector& selector,
+                                ElementCheckType check_type,
                                 bool strict_mode,
                                 FindElementCallback callback) {
   auto finder = std::make_unique<ElementFinder>(
-      web_contents_, devtools_client_.get(), selector, strict_mode);
+      web_contents_, devtools_client_.get(), selector, check_type, strict_mode);
   ElementFinder* ptr = finder.get();
   pending_workers_[ptr] = std::move(finder);
   ptr->Start(base::BindOnce(&WebController::OnFindElementResult,
@@ -1051,7 +1064,7 @@
   auto data_to_autofill = std::make_unique<FillFormInputData>();
   data_to_autofill->profile =
       std::make_unique<autofill::AutofillProfile>(*profile);
-  FindElement(selector,
+  FindElement(selector, kExistenceCheck,
               /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForFillingForm,
                              weak_ptr_factory_.GetWeakPtr(),
@@ -1126,7 +1139,7 @@
   auto data_to_autofill = std::make_unique<FillFormInputData>();
   data_to_autofill->card = std::move(card);
   data_to_autofill->cvc = cvc;
-  FindElement(selector,
+  FindElement(selector, kExistenceCheck,
               /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForFillingForm,
                              weak_ptr_factory_.GetWeakPtr(),
@@ -1139,7 +1152,7 @@
     const std::string& selected_option,
     base::OnceCallback<void(const ClientStatus&)> callback) {
   DVLOG(3) << __func__ << " " << selector << ", option=" << selected_option;
-  FindElement(selector,
+  FindElement(selector, kExistenceCheck,
               /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForSelectOption,
                              weak_ptr_factory_.GetWeakPtr(), selected_option,
@@ -1191,7 +1204,7 @@
     base::OnceCallback<void(const ClientStatus&)> callback) {
   DVLOG(3) << __func__ << " " << selector;
   FindElement(
-      selector,
+      selector, kVisibilityCheck,
       /* strict_mode= */ true,
       base::BindOnce(&WebController::OnFindElementForHighlightElement,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
@@ -1241,8 +1254,8 @@
   DVLOG(3) << __func__ << " " << selector;
   DCHECK(!selector.empty());
   FindElement(
-      selector,
-      /* strict_mode= */ true,
+      selector, kVisibilityCheck,
+      /* strict_mode= */ false,
       base::BindOnce(&WebController::OnFindElementForFocusElement,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
@@ -1251,7 +1264,7 @@
     const Selector& selector,
     base::OnceCallback<void(bool, const std::string&)> callback) {
   FindElement(
-      selector,
+      selector, kExistenceCheck,
       /* strict_mode= */ true,
       base::BindOnce(&WebController::OnFindElementForGetFieldValue,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
@@ -1332,7 +1345,7 @@
     const Selector& selector,
     const std::string& value,
     base::OnceCallback<void(const ClientStatus&)> callback) {
-  FindElement(selector,
+  FindElement(selector, kExistenceCheck,
               /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForSetFieldValue,
                              weak_ptr_factory_.GetWeakPtr(), value,
@@ -1452,7 +1465,7 @@
 
   DCHECK(!selector.empty());
   DCHECK_GT(attribute.size(), 0u);
-  FindElement(selector,
+  FindElement(selector, kExistenceCheck,
               /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForSetAttribute,
                              weak_ptr_factory_.GetWeakPtr(), attribute, value,
@@ -1509,7 +1522,7 @@
   DVLOG(3) << __func__ << " " << selector
            << ", input=" << base::JoinString(utf8_chars, "");
   DCHECK(!selector.empty());
-  FindElement(selector,
+  FindElement(selector, kVisibilityCheck,
               /* strict_mode= */ true,
               base::BindOnce(&WebController::OnFindElementForSendKeyboardInput,
                              weak_ptr_factory_.GetWeakPtr(), selector,
@@ -1538,7 +1551,7 @@
         callback) {
   DVLOG(3) << __func__ << " " << selector;
   FindElement(
-      selector,
+      selector, kExistenceCheck,
       /* strict_mode= */ true,
       base::BindOnce(&WebController::OnFindElementForGetOuterHtml,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
@@ -1553,7 +1566,7 @@
     const Selector& selector,
     base::OnceCallback<void(bool, const RectF&)> callback) {
   FindElement(
-      selector, /* strict_mode= */ true,
+      selector, kVisibilityCheck, /* strict_mode= */ true,
       base::BindOnce(&WebController::OnFindElementForPosition,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
diff --git a/components/autofill_assistant/browser/web_controller.h b/components/autofill_assistant/browser/web_controller.h
index 3a603cf..ef52d75 100644
--- a/components/autofill_assistant/browser/web_controller.h
+++ b/components/autofill_assistant/browser/web_controller.h
@@ -166,9 +166,12 @@
   // kVisibilityCheck: Checks whether at least on element given by |selector|
   // is visible on the web page.
   //
+  // If strict, there must be exactly one element.
+  //
   // Normally done through BatchElementChecker.
   virtual void ElementCheck(ElementCheckType type,
                             const Selector& selector,
+                            bool strict,
                             base::OnceCallback<void(bool)> callback);
 
   // Get the value of |selector| and return the result through |callback|. The
@@ -289,17 +292,15 @@
   void OnDispatchTouchEventEnd(
       base::OnceCallback<void(const ClientStatus&)> callback,
       std::unique_ptr<input::DispatchTouchEventResult> result);
-  void OnFindElementForCheck(ElementCheckType check_type,
-                             base::OnceCallback<void(bool)> callback,
+  void OnFindElementForCheck(base::OnceCallback<void(bool)> callback,
                              const ClientStatus& status,
                              std::unique_ptr<FindElementResult> result);
-  void OnGetBoxModelForVisible(base::OnceCallback<void(bool)> callback,
-                               std::unique_ptr<dom::GetBoxModelResult> result);
 
   // Find the element given by |selector|. If multiple elements match
   // |selector| and if |strict_mode| is false, return the first one that is
   // found. Otherwise if |strict-mode| is true, do not return any.
   void FindElement(const Selector& selector,
+                   ElementCheckType check_type,
                    bool strict_mode,
                    FindElementCallback callback);
   void OnFindElementResult(ElementFinder* finder_to_release,
diff --git a/components/autofill_assistant/browser/web_controller_browsertest.cc b/components/autofill_assistant/browser/web_controller_browsertest.cc
index bb46c82..b447f275 100644
--- a/components/autofill_assistant/browser/web_controller_browsertest.cc
+++ b/components/autofill_assistant/browser/web_controller_browsertest.cc
@@ -12,11 +12,18 @@
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
 
 namespace autofill_assistant {
 
+using ::testing::IsEmpty;
+
 const char* kTargetWebsitePath = "/autofill_assistant_target_website.html";
 
+std::ostream& operator<<(std::ostream& out, ElementCheckType check_type) {
+  return out << (check_type == kVisibilityCheck ? "visibility" : "existence");
+}
+
 class WebControllerBrowserTest : public content::ContentBrowserTest,
                                  public content::WebContentsObserver {
  public:
@@ -66,7 +73,29 @@
     } while (page_is_loading || paint_occurred_during_last_loop_);
   }
 
+  void RunStrictElementCheck(ElementCheckType check_type,
+                             const Selector& selector,
+                             bool result) {
+    RunElementCheck(check_type, /* strict= */ true, selector, result);
+  }
+
+  void RunLaxElementCheck(ElementCheckType check_type,
+                          const Selector& selector,
+                          bool result) {
+    RunElementCheck(check_type, /* strict= */ false, selector, result);
+  }
+
+  void RunElementCheck(ElementCheckType check_type,
+                       bool strict,
+                       const Selector& selector,
+                       bool result) {
+    std::vector<Selector> selectors{selector};
+    std::vector<bool> results{result};
+    RunElementChecks(check_type, strict, selectors, results);
+  }
+
   void RunElementChecks(ElementCheckType check_type,
+                        bool strict,
                         const std::vector<Selector>& selectors,
                         const std::vector<bool> results) {
     base::RunLoop run_loop;
@@ -74,19 +103,23 @@
     size_t pending_number_of_checks = selectors.size();
     for (size_t i = 0; i < selectors.size(); i++) {
       web_controller_->ElementCheck(
-          check_type, selectors[i],
+          check_type, selectors[i], strict,
           base::BindOnce(&WebControllerBrowserTest::CheckElementVisibleCallback,
                          base::Unretained(this), run_loop.QuitClosure(),
-                         &pending_number_of_checks, results[i]));
+                         selectors[i], check_type, &pending_number_of_checks,
+                         results[i]));
     }
     run_loop.Run();
   }
 
   void CheckElementVisibleCallback(const base::Closure& done_callback,
+                                   const Selector& selector,
+                                   ElementCheckType check_type,
                                    size_t* pending_number_of_checks_output,
                                    bool expected_result,
                                    bool result) {
-    ASSERT_EQ(expected_result, result);
+    EXPECT_EQ(expected_result, result)
+        << "selector: " << selector << " " << check_type;
     *pending_number_of_checks_output -= 1;
     if (*pending_number_of_checks_output == 0) {
       done_callback.Run();
@@ -125,7 +158,7 @@
   void WaitForElementRemove(const Selector& selector) {
     base::RunLoop run_loop;
     web_controller_->ElementCheck(
-        kExistenceCheck, selector,
+        kExistenceCheck, selector, /* strict= */ false,
         base::BindOnce(&WebControllerBrowserTest::OnWaitForElementRemove,
                        base::Unretained(this), run_loop.QuitClosure(),
                        selector));
@@ -217,35 +250,68 @@
     done_callback.Run();
   }
 
-  void FindElement(const Selector& selector,
-                   size_t expected_index,
-                   bool is_main_frame) {
+  void FindElement(ElementCheckType check_type,
+                   const Selector& selector,
+                   ClientStatus* status_out,
+                   WebController::FindElementResult* result_out) {
     base::RunLoop run_loop;
     web_controller_->FindElement(
-        selector,
-        /* strict_mode= */ true,
+        selector, check_type, /* strict_mode= */ true,
         base::BindOnce(&WebControllerBrowserTest::OnFindElement,
                        base::Unretained(this), run_loop.QuitClosure(),
-                       expected_index, is_main_frame));
+                       base::Unretained(status_out),
+                       base::Unretained(result_out)));
     run_loop.Run();
   }
 
   void OnFindElement(const base::Closure& done_callback,
-                     size_t expected_index,
-                     bool is_main_frame,
+                     ClientStatus* status_out,
+                     WebController::FindElementResult* result_out,
                      const ClientStatus& status,
                      std::unique_ptr<WebController::FindElementResult> result) {
+    ASSERT_TRUE(result);
     done_callback.Run();
+    if (status_out)
+      *status_out = status;
+
+    if (result_out)
+      *result_out = *result;
+  }
+
+  void FindElementAndCheck(ElementCheckType check_type,
+                           const Selector& selector,
+                           size_t expected_index,
+                           bool is_main_frame) {
+    SCOPED_TRACE(::testing::Message() << selector << " strict, " << check_type);
+    ClientStatus status;
+    WebController::FindElementResult result;
+    FindElement(check_type, selector, &status, &result);
     EXPECT_EQ(ACTION_APPLIED, status.proto_status());
+    CheckFindElementResult(result, expected_index, is_main_frame);
+  }
+
+  void FindElementExpectEmptyResult(ElementCheckType check_type,
+                                    const Selector& selector) {
+    SCOPED_TRACE(::testing::Message() << selector << " strict, " << check_type);
+    ClientStatus status;
+    WebController::FindElementResult result;
+    FindElement(check_type, selector, &status, &result);
+    EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status());
+    EXPECT_THAT(result.object_id, IsEmpty());
+  }
+
+  void CheckFindElementResult(const WebController::FindElementResult& result,
+                              size_t expected_index,
+                              bool is_main_frame) {
     if (is_main_frame) {
       EXPECT_EQ(shell()->web_contents()->GetMainFrame(),
-                result->container_frame_host);
+                result.container_frame_host);
     } else {
       EXPECT_NE(shell()->web_contents()->GetMainFrame(),
-                result->container_frame_host);
+                result.container_frame_host);
     }
-    EXPECT_EQ(result->container_frame_selector_index, expected_index);
-    EXPECT_FALSE(result->object_id.empty());
+    EXPECT_EQ(result.container_frame_selector_index, expected_index);
+    EXPECT_FALSE(result.object_id.empty());
   }
 
   void GetFieldsValue(const std::vector<Selector>& selectors,
@@ -378,7 +444,72 @@
   DISALLOW_COPY_AND_ASSIGN(WebControllerBrowserTest);
 };
 
-IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ConcurrentElementsVisible) {
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementExistenceCheck) {
+  // A visible element
+  RunLaxElementCheck(kExistenceCheck, Selector({"#button"}), true);
+
+  // A hidden element.
+  RunLaxElementCheck(kExistenceCheck, Selector({"#hidden"}), true);
+
+  // A nonexistent element.
+  RunLaxElementCheck(kExistenceCheck, Selector({"#doesnotexist"}), false);
+
+  // A pseudo-element
+  RunLaxElementCheck(kExistenceCheck,
+                     Selector({"#terms-and-conditions"}, BEFORE), true);
+
+  // An invisible pseudo-element
+  //
+  // TODO(b/129461999): This is wrong; it should exist. Fix it.
+  RunLaxElementCheck(kExistenceCheck, Selector({"#button"}, BEFORE), false);
+
+  // A non-existent pseudo-element
+  RunLaxElementCheck(kExistenceCheck, Selector({"#button"}, AFTER), false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementVisibilityCheck) {
+  // A visible element
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#button"}), true);
+
+  // A hidden element.
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#hidden"}), false);
+
+  // A non-existent element
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#doesnotexist"}), false);
+
+  // A pseudo-element
+  RunLaxElementCheck(kVisibilityCheck,
+                     Selector({"#terms-and-conditions"}, BEFORE), true);
+
+  // An invisible pseudo-element
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#button"}, BEFORE), false);
+
+  // A non-existent pseudo-element
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#button"}, AFTER), false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, MultipleVisibleElementCheck) {
+  // both visible
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#button,#select"}), true);
+  RunStrictElementCheck(kVisibilityCheck, Selector({"#button,#select"}), false);
+
+  // one visible (first non-visible)
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#hidden,#select"}), true);
+  RunStrictElementCheck(kVisibilityCheck, Selector({"#hidden,#select"}), true);
+
+  // one visible (first visible)
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#button,#hidden"}), true);
+  RunStrictElementCheck(kVisibilityCheck, Selector({"#hidden,#select"}), true);
+
+  // one invisible, one non-existent
+  RunLaxElementCheck(kVisibilityCheck, Selector({"#doesnotexist,#hidden"}),
+                     false);
+  RunStrictElementCheck(kVisibilityCheck, Selector({"#doesnotexist,#hidden"}),
+                        false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+                       ConcurrentElementsVisibilityCheck) {
   std::vector<Selector> selectors;
   std::vector<bool> results;
 
@@ -438,33 +569,7 @@
   selectors.emplace_back(a_selector);
   results.emplace_back(false);
 
-  RunElementChecks(kVisibilityCheck, selectors, results);
-}
-
-IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementExists) {
-  std::vector<Selector> selectors;
-  std::vector<bool> results;
-
-  Selector a_selector;
-
-  // A visible element
-  a_selector.selectors.emplace_back("#button");
-  selectors.emplace_back(a_selector);
-  results.emplace_back(true);
-
-  // A hidden element.
-  a_selector.selectors.clear();
-  a_selector.selectors.emplace_back("#hidden");
-  selectors.emplace_back(a_selector);
-  results.emplace_back(true);
-
-  // A nonexistent element.
-  a_selector.selectors.clear();
-  a_selector.selectors.emplace_back("#doesnotexist");
-  selectors.emplace_back(a_selector);
-  results.emplace_back(false);
-
-  RunElementChecks(kExistenceCheck, selectors, results);
+  RunElementChecks(kVisibilityCheck, /* strict= */ false, selectors, results);
 }
 
 IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElement) {
@@ -544,25 +649,38 @@
 IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElement) {
   Selector selector;
   selector.selectors.emplace_back("#button");
-  FindElement(selector, 0, true);
+  FindElementAndCheck(kExistenceCheck, selector, 0, true);
+  FindElementAndCheck(kVisibilityCheck, selector, 0, true);
 
   // IFrame.
   selector.selectors.clear();
   selector.selectors.emplace_back("#iframe");
   selector.selectors.emplace_back("#button");
-  FindElement(selector, 0, false);
+  FindElementAndCheck(kExistenceCheck, selector, 0, false);
+  FindElementAndCheck(kVisibilityCheck, selector, 0, false);
 
   selector.selectors.clear();
   selector.selectors.emplace_back("#iframe");
   selector.selectors.emplace_back("[name=name]");
-  FindElement(selector, 0, false);
+  FindElementAndCheck(kExistenceCheck, selector, 0, false);
+  FindElementAndCheck(kVisibilityCheck, selector, 0, false);
 
   // IFrame inside IFrame.
   selector.selectors.clear();
   selector.selectors.emplace_back("#iframe");
   selector.selectors.emplace_back("#iframe");
   selector.selectors.emplace_back("#button");
-  FindElement(selector, 1, false);
+  FindElementAndCheck(kExistenceCheck, selector, 1, false);
+  FindElementAndCheck(kVisibilityCheck, selector, 1, false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElementNotFound) {
+  FindElementExpectEmptyResult(kExistenceCheck, Selector({"#notfound"}));
+  FindElementExpectEmptyResult(kVisibilityCheck, Selector({"#hidden"}));
+  FindElementExpectEmptyResult(kExistenceCheck,
+                               Selector({"#iframe", "#iframe", "#notfound"}));
+  FindElementExpectEmptyResult(kVisibilityCheck,
+                               Selector({"#iframe", "#iframe", "#hidden"}));
 }
 
 IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FocusElement) {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc
index 0000693..cd6eade 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc
@@ -643,227 +643,382 @@
     int expected_duration;
     DataReductionProxyBypassType expected_bypass_type;
   } tests[] = {
-      // Valid data reduction proxy response with no bypass message.
-      {
-          "GET",
-          "HTTP/1.1 200 OK\r\n"
-          "Server: proxy\r\n"
-          "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-          false,
-          false,
-          0u,
-          true,
-          -1,
-          BYPASS_EVENT_TYPE_MAX,
-      },
-      // Response error does not result in bypass.
-      {
-          "GET",
-          "Not an HTTP response",
-          false,
-          true,
-          0u,
-          true,
-          -1,
-          BYPASS_EVENT_TYPE_MAX,
-      },
-      // Valid data reduction proxy response with chained via header,
-      // no bypass message.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy, 1.0 some-other-proxy\r\n\r\n",
-       false, false, 0u, true, -1, BYPASS_EVENT_TYPE_MAX},
-      // Valid data reduction proxy response with a bypass message.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=0\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
-      // Valid data reduction proxy response with a bypass message.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=1\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 1, BYPASS_EVENT_TYPE_SHORT},
-      // Same as above with the OPTIONS method, which is idempotent.
-      {"OPTIONS",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=0\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
-      // Same as above with the HEAD method, which is idempotent.
-      {"HEAD",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=0\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, false, 0, BYPASS_EVENT_TYPE_MEDIUM},
-      // Same as above with the PUT method, which is idempotent.
-      {"PUT",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=0\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
-      // Same as above with the DELETE method, which is idempotent.
-      {"DELETE",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=0\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
-      // Same as above with the TRACE method, which is idempotent.
-      {"TRACE",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=0\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
-      // 500 responses should be bypassed.
-      {"GET",
-       "HTTP/1.1 500 Internal Server Error\r\n"
-       "Server: proxy\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 0,
-       BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR},
-      // 502 responses should be bypassed.
-      {"GET",
-       "HTTP/1.1 502 Internal Server Error\r\n"
-       "Server: proxy\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY},
-      // 503 responses should be bypassed.
-      {"GET",
-       "HTTP/1.1 503 Internal Server Error\r\n"
-       "Server: proxy\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 0,
-       BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE},
-      // Invalid data reduction proxy 4xx response. Missing Via header.
-      {"GET",
-       "HTTP/1.1 404 Not Found\r\n"
-       "Server: proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX},
-      // Invalid data reduction proxy response. Missing Via header.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n\r\n",
-       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER},
-      // Invalid data reduction proxy response. Wrong Via header.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Via: 1.0 some-other-proxy\r\n\r\n",
-       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER},
-      // Valid data reduction proxy response. 304 missing Via header.
-      {"GET",
-       "HTTP/1.1 304 Not Modified\r\n"
-       "Server: proxy\r\n\r\n",
-       false, false, 0u, false, 0, BYPASS_EVENT_TYPE_MAX},
-      // Valid data reduction proxy response with a bypass message. It will
-      // not be retried because the request is non-idempotent.
-      {"POST",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=0\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       false, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
-      // Valid data reduction proxy response with block message. Both proxies
-      // should be on the retry list when it completes.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block=1\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 2u, true, 1, BYPASS_EVENT_TYPE_SHORT},
-      // Valid data reduction proxy response with a block-once message. It will
-      // be
-      // retried, and there will be no proxies on the retry list since
-      // block-once
-      // only affects the current request.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
-      // Same as above with the OPTIONS method, which is idempotent.
-      {"OPTIONS",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
-      // Same as above with the HEAD method, which is idempotent.
-      {"HEAD",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, false, 0, BYPASS_EVENT_TYPE_CURRENT},
-      // Same as above with the PUT method, which is idempotent.
-      {"PUT",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
-      // Same as above with the DELETE method, which is idempotent.
-      {"DELETE",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
-      // Same as above with the TRACE method, which is idempotent.
-      {"TRACE",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
-      // Valid Data Reduction Proxy response with a block-once message. It will
-      // be retried because block-once indicates that request did not reach the
-      // origin and client should retry. Only current request is retried direct,
-      // so there should be no proxies on the retry list.
-      {"POST",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
-      // Valid Data Reduction Proxy response with a bypass message. It will
-      // not be retried because the request is non-idempotent. Both proxies
-      // should be on the retry list for 1 second.
-      {"POST",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block=1\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       false, false, 2u, true, 1, BYPASS_EVENT_TYPE_SHORT},
-      // Valid data reduction proxy response with block and block-once messages.
-      // The block message will override the block-once message, so both proxies
-      // should be on the retry list when it completes.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: block=1, block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 2u, true, 1, BYPASS_EVENT_TYPE_SHORT},
-      // Valid data reduction proxy response with bypass and block-once
-      // messages.
-      // The bypass message will override the block-once message, so one proxy
-      // should be on the retry list when it completes.
-      {"GET",
-       "HTTP/1.1 200 OK\r\n"
-       "Server: proxy\r\n"
-       "Chrome-Proxy: bypass=1, block-once\r\n"
-       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, 1u, true, 1, BYPASS_EVENT_TYPE_SHORT},
+    // Valid data reduction proxy response with no bypass message.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      false,
+      false,
+      0u,
+      true,
+      -1,
+      BYPASS_EVENT_TYPE_MAX,
+    },
+    // Response error does not result in bypass.
+    { "GET",
+      "Not an HTTP response",
+      false,
+      true,
+      0u,
+      true,
+      -1,
+      BYPASS_EVENT_TYPE_MAX,
+    },
+    // Valid data reduction proxy response with chained via header,
+    // no bypass message.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy, 1.0 some-other-proxy\r\n\r\n",
+      false,
+      false,
+      0u,
+      true,
+      -1,
+      BYPASS_EVENT_TYPE_MAX
+    },
+    // Valid data reduction proxy response with a bypass message.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=0\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MEDIUM
+    },
+    // Valid data reduction proxy response with a bypass message.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=1\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      1,
+      BYPASS_EVENT_TYPE_SHORT
+    },
+    // Same as above with the OPTIONS method, which is idempotent.
+    { "OPTIONS",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=0\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MEDIUM
+    },
+    // Same as above with the HEAD method, which is idempotent.
+    { "HEAD",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=0\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      false,
+      0,
+      BYPASS_EVENT_TYPE_MEDIUM
+    },
+    // Same as above with the PUT method, which is idempotent.
+    { "PUT",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=0\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MEDIUM
+    },
+    // Same as above with the DELETE method, which is idempotent.
+    { "DELETE",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=0\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MEDIUM
+    },
+    // Same as above with the TRACE method, which is idempotent.
+    { "TRACE",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=0\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MEDIUM
+    },
+    // 500 responses should be bypassed.
+    { "GET",
+      "HTTP/1.1 500 Internal Server Error\r\n"
+      "Server: proxy\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR
+    },
+    // 502 responses should be bypassed.
+    { "GET",
+      "HTTP/1.1 502 Internal Server Error\r\n"
+      "Server: proxy\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY
+    },
+    // 503 responses should be bypassed.
+    { "GET",
+      "HTTP/1.1 503 Internal Server Error\r\n"
+      "Server: proxy\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE
+    },
+    // Invalid data reduction proxy 4xx response. Missing Via header.
+    { "GET",
+      "HTTP/1.1 404 Not Found\r\n"
+      "Server: proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX
+    },
+    // Invalid data reduction proxy response. Missing Via header.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER
+    },
+    // Invalid data reduction proxy response. Wrong Via header.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Via: 1.0 some-other-proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER
+    },
+    // Valid data reduction proxy response. 304 missing Via header.
+    { "GET",
+      "HTTP/1.1 304 Not Modified\r\n"
+      "Server: proxy\r\n\r\n",
+      false,
+      false,
+      0u,
+      false,
+      0,
+      BYPASS_EVENT_TYPE_MAX
+    },
+    // Valid data reduction proxy response with a bypass message. It will
+    // not be retried because the request is non-idempotent.
+    { "POST",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=0\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      false,
+      false,
+      1u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_MEDIUM
+    },
+    // Valid data reduction proxy response with block message. Both proxies
+    // should be on the retry list when it completes.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block=1\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      2u,
+      true,
+      1,
+      BYPASS_EVENT_TYPE_SHORT
+    },
+    // Valid data reduction proxy response with a block-once message. It will be
+    // retried, and there will be no proxies on the retry list since block-once
+    // only affects the current request.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_CURRENT
+    },
+    // Same as above with the OPTIONS method, which is idempotent.
+    { "OPTIONS",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_CURRENT
+    },
+    // Same as above with the HEAD method, which is idempotent.
+    { "HEAD",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      false,
+      0,
+      BYPASS_EVENT_TYPE_CURRENT
+    },
+    // Same as above with the PUT method, which is idempotent.
+    { "PUT",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_CURRENT
+    },
+    // Same as above with the DELETE method, which is idempotent.
+    { "DELETE",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_CURRENT
+    },
+    // Same as above with the TRACE method, which is idempotent.
+    { "TRACE",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_CURRENT
+    },
+    // Valid Data Reduction Proxy response with a block-once message. It will
+    // be retried because block-once indicates that request did not reach the
+    // origin and client should retry. Only current request is retried direct,
+    // so there should be no proxies on the retry list.
+    { "POST",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      0u,
+      true,
+      0,
+      BYPASS_EVENT_TYPE_CURRENT
+    },
+    // Valid Data Reduction Proxy response with a bypass message. It will
+    // not be retried because the request is non-idempotent. Both proxies
+    // should be on the retry list for 1 second.
+    { "POST",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block=1\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      false,
+      false,
+      2u,
+      true,
+      1,
+      BYPASS_EVENT_TYPE_SHORT
+    },
+    // Valid data reduction proxy response with block and block-once messages.
+    // The block message will override the block-once message, so both proxies
+    // should be on the retry list when it completes.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: block=1, block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      2u,
+      true,
+      1,
+      BYPASS_EVENT_TYPE_SHORT
+    },
+    // Valid data reduction proxy response with bypass and block-once messages.
+    // The bypass message will override the block-once message, so one proxy
+    // should be on the retry list when it completes.
+    { "GET",
+      "HTTP/1.1 200 OK\r\n"
+      "Server: proxy\r\n"
+      "Chrome-Proxy: bypass=1, block-once\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+      true,
+      false,
+      1u,
+      true,
+      1,
+      BYPASS_EVENT_TYPE_SHORT
+    },
   };
   test_context_->config()->test_params()->UseNonSecureProxiesForHttp();
   std::string primary = test_context_->config()
@@ -1017,14 +1172,14 @@
       {"HTTP/1.1 502 Bad Gateway\r\n"
        "Server: proxy\r\n"
        "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, false, BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY},
+       true, true, BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY},
       {"HTTP/1.1 200 OK\r\n"
        "Server: proxy\r\n"
        "Chrome-Proxy: block=0\r\n\r\n",
        false, false, BYPASS_EVENT_TYPE_MAX},
       {"HTTP/1.1 502 Bad Gateway\r\n"
        "Server: proxy\r\n\r\n",
-       true, false, BYPASS_EVENT_TYPE_MAX},
+       false, false, BYPASS_EVENT_TYPE_MAX},
   };
 
   for (const auto& test : test_cases) {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc
index 4f929ba..bdb54b35 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc
@@ -473,22 +473,6 @@
       histogram_tester, "DataReductionProxy.BypassedBytes.ProxyOverridden");
 }
 
-TEST_F(DataReductionProxyBypassStatsEndToEndTest, NoChromeProxy502AreBypassed) {
-  InitializeContext();
-  base::HistogramTester histogram_tester;
-  CreateAndExecuteRequest(GURL("http://foo.com"), net::LOAD_NORMAL, net::OK,
-                          "HTTP/1.1 502 Bad Gateway\r\n"
-                          "Via: 1.1 Chrome-Compression-Proxy\r\n"
-                          "Chrome-Proxy: block-once\r\n\r\n",
-                          kErrorBody.c_str(), "HTTP/1.1 200 OK\r\n\r\n",
-                          kBody.c_str());
-
-  histogram_tester.ExpectUniqueSample(
-      "DataReductionProxy.BypassedBytes.Current", kBody.size(), 1);
-  ExpectOtherBypassedBytesHistogramsEmpty(
-      histogram_tester, "DataReductionProxy.BypassedBytes.Current");
-}
-
 TEST_F(DataReductionProxyBypassStatsEndToEndTest, BypassedBytesCurrent) {
   InitializeContext();
   struct TestCase {
@@ -626,6 +610,9 @@
     { "DataReductionProxy.BypassedBytes.Status500HttpInternalServerError",
       "HTTP/1.1 500 Internal Server Error\r\n\r\n",
     },
+    { "DataReductionProxy.BypassedBytes.Status502HttpBadGateway",
+      "HTTP/1.1 502 Bad Gateway\r\n\r\n",
+    },
     { "DataReductionProxy.BypassedBytes.Status503HttpServiceUnavailable",
       "HTTP/1.1 503 Service Unavailable\r\n\r\n",
     },
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
index 3c234c3..1834dee 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
@@ -46,10 +46,5 @@
     "DataReductionProxyEnabledWithNetworkService",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables block-once action when 502 is received with no Chrome-Proxy header.
-const base::Feature kDataReductionProxyBlockOnceOnBadGatewayResponse{
-    "DataReductionProxyBlockOnceOnBadGatewayResponse",
-    base::FEATURE_ENABLED_BY_DEFAULT};
-
 }  // namespace features
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
index 65802a2..3347846 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
@@ -17,7 +17,6 @@
 extern const base::Feature kDataSaverSiteBreakdownUsingPageLoadMetrics;
 extern const base::Feature kDataReductionProxyEnabledWithNetworkService;
 extern const base::Feature kDataSaverUseOnDeviceSafeBrowsing;
-extern const base::Feature kDataReductionProxyBlockOnceOnBadGatewayResponse;
 
 }  // namespace features
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
index 305286e..2764f52 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
@@ -418,16 +418,8 @@
   // Fall back if a 500, 502 or 503 is returned.
   if (headers.response_code() == net::HTTP_INTERNAL_SERVER_ERROR)
     return BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR;
-  if (headers.response_code() == net::HTTP_BAD_GATEWAY) {
-    if (base::FeatureList::IsEnabled(
-            features::kDataReductionProxyBlockOnceOnBadGatewayResponse)) {
-      data_reduction_proxy_info->bypass_all = true;
-      data_reduction_proxy_info->mark_proxies_as_bad = false;
-      data_reduction_proxy_info->bypass_duration = base::TimeDelta();
-      data_reduction_proxy_info->bypass_action = BYPASS_ACTION_TYPE_BLOCK_ONCE;
-    }
+  if (headers.response_code() == net::HTTP_BAD_GATEWAY)
     return BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY;
-  }
   if (headers.response_code() == net::HTTP_SERVICE_UNAVAILABLE)
     return BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE;
   // TODO(kundaji): Bypass if Proxy-Authenticate header value cannot be
diff --git a/components/download/content/factory/BUILD.gn b/components/download/content/factory/BUILD.gn
index 43275b0..d9eb53fa 100644
--- a/components/download/content/factory/BUILD.gn
+++ b/components/download/content/factory/BUILD.gn
@@ -4,8 +4,8 @@
 
 source_set("factory") {
   sources = [
-    "download_service_factory.cc",
-    "download_service_factory.h",
+    "download_service_factory_helper.cc",
+    "download_service_factory_helper.h",
     "navigation_monitor_factory.cc",
     "navigation_monitor_factory.h",
   ]
diff --git a/components/download/content/factory/download_service_factory.cc b/components/download/content/factory/download_service_factory_helper.cc
similarity index 99%
rename from components/download/content/factory/download_service_factory.cc
rename to components/download/content/factory/download_service_factory_helper.cc
index 571a6d9..b7a53200 100644
--- a/components/download/content/factory/download_service_factory.cc
+++ b/components/download/content/factory/download_service_factory_helper.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/download/content/factory/download_service_factory.h"
+#include "components/download/content/factory/download_service_factory_helper.h"
 
 #include "base/files/file_path.h"
 #include "build/build_config.h"
diff --git a/components/download/content/factory/download_service_factory.h b/components/download/content/factory/download_service_factory_helper.h
similarity index 96%
rename from components/download/content/factory/download_service_factory.h
rename to components/download/content/factory/download_service_factory_helper.h
index a619eb0..b09e038 100644
--- a/components/download/content/factory/download_service_factory.h
+++ b/components/download/content/factory/download_service_factory_helper.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_H_
-#define COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_H_
+#ifndef COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_HELPER_H_
+#define COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_HELPER_H_
 
 #include <memory>
 
@@ -24,7 +24,7 @@
 namespace network {
 class NetworkConnectionTracker;
 class SharedURLLoaderFactory;
-}
+}  // namespace network
 
 namespace download {
 
@@ -64,4 +64,4 @@
 
 }  // namespace download
 
-#endif  // COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_H_
+#endif  // COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_HELPER_H_
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 8803c62..afc35b88 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -518,6 +518,10 @@
 
   GetFrameView()->GetHeaderView()->GetFrameHeader()->SetFrameTextOverride(
       extra_title);
+  if (wide_frame_) {
+    wide_frame_->header_view()->GetFrameHeader()->SetFrameTextOverride(
+        extra_title);
+  }
 }
 
 void ClientControlledShellSurface::SetOrientationLock(
@@ -1002,7 +1006,13 @@
       wide_frame_ = std::make_unique<ash::WideFrameView>(widget_);
       ash::ImmersiveFullscreenController::EnableForWidget(widget_, false);
       wide_frame_->Init(immersive_fullscreen_controller_.get());
+      wide_frame_->header_view()->GetFrameHeader()->SetFrameTextOverride(
+          GetFrameView()
+              ->GetHeaderView()
+              ->GetFrameHeader()
+              ->frame_text_override());
       wide_frame_->GetWidget()->Show();
+
       // Restoring window targeter replaced by ImmersiveFullscreenController.
       InstallCustomWindowTargeter();
 
diff --git a/components/offline_pages/core/prefetch/prefetch_downloader.h b/components/offline_pages/core/prefetch/prefetch_downloader.h
index 349509fe..ca1a31944a 100644
--- a/components/offline_pages/core/prefetch/prefetch_downloader.h
+++ b/components/offline_pages/core/prefetch/prefetch_downloader.h
@@ -59,6 +59,9 @@
 
   // Called when a download fails.
   virtual void OnDownloadFailed(const std::string& download_id) = 0;
+
+  // Returns the maximum number of downloads allowed.
+  virtual int GetMaxConcurrentDownloads() = 0;
 };
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/prefetch_downloader_impl.cc b/components/offline_pages/core/prefetch/prefetch_downloader_impl.cc
index dfb00b55..45fdaf99 100644
--- a/components/offline_pages/core/prefetch/prefetch_downloader_impl.cc
+++ b/components/offline_pages/core/prefetch/prefetch_downloader_impl.cc
@@ -12,6 +12,7 @@
 #include "base/trace_event/trace_event.h"
 #include "components/download/public/background_service/download_params.h"
 #include "components/download/public/background_service/download_service.h"
+#include "components/download/public/background_service/service_config.h"
 #include "components/offline_pages/core/offline_clock.h"
 #include "components/offline_pages/core/offline_event_logger.h"
 #include "components/offline_pages/core/prefetch/prefetch_dispatcher.h"
@@ -217,6 +218,10 @@
   NotifyDispatcher(prefetch_service_, result);
 }
 
+int PrefetchDownloaderImpl::GetMaxConcurrentDownloads() {
+  return download_service_->GetConfig().GetMaxConcurrentDownloads();
+}
+
 void PrefetchDownloaderImpl::OnStartDownload(
     const std::string& download_id,
     download::DownloadParams::StartResult result) {
diff --git a/components/offline_pages/core/prefetch/prefetch_downloader_impl.h b/components/offline_pages/core/prefetch/prefetch_downloader_impl.h
index b4bc0352..c76e6fc 100644
--- a/components/offline_pages/core/prefetch/prefetch_downloader_impl.h
+++ b/components/offline_pages/core/prefetch/prefetch_downloader_impl.h
@@ -52,6 +52,7 @@
                            const base::FilePath& file_path,
                            int64_t file_size) override;
   void OnDownloadFailed(const std::string& download_id) override;
+  int GetMaxConcurrentDownloads() override;
 
  private:
   enum class DownloadServiceStatus {
diff --git a/components/offline_pages/core/prefetch/tasks/download_archives_task.cc b/components/offline_pages/core/prefetch/tasks/download_archives_task.cc
index a52c5e6..c24d3bc 100644
--- a/components/offline_pages/core/prefetch/tasks/download_archives_task.cc
+++ b/components/offline_pages/core/prefetch/tasks/download_archives_task.cc
@@ -11,6 +11,7 @@
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_store_utils.h"
 #include "components/offline_pages/core/prefetch/prefetch_downloader.h"
+#include "components/offline_pages/core/prefetch/prefetch_prefs.h"
 #include "components/offline_pages/core/prefetch/prefetch_types.h"
 #include "components/offline_pages/core/prefetch/store/prefetch_downloader_quota.h"
 #include "components/offline_pages/core/prefetch/store/prefetch_store.h"
@@ -19,10 +20,6 @@
 #include "sql/transaction.h"
 
 namespace offline_pages {
-namespace prefetch_prefs {
-bool IsLimitlessPrefetchingEnabled(PrefService*);
-}
-
 namespace {
 
 using DownloadItem = DownloadArchivesTask::DownloadItem;
@@ -30,10 +27,10 @@
 
 ItemsToDownload FindItemsReadyForDownload(sql::Database* db) {
   static const char kSql[] =
-      "SELECT offline_id, archive_body_name, operation_name,"
-      " archive_body_length"
+      "SELECT offline_id,archive_body_name,operation_name,"
+      "archive_body_length"
       " FROM prefetch_items"
-      " WHERE state = ?"
+      " WHERE state=?"
       " ORDER BY creation_time DESC";
   sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt(0, static_cast<int>(PrefetchItemState::RECEIVED_BUNDLE));
@@ -53,7 +50,7 @@
 
 std::unique_ptr<int> CountDownloadsInProgress(sql::Database* db) {
   static const char kSql[] =
-      "SELECT COUNT(offline_id) FROM prefetch_items WHERE state = ?";
+      "SELECT COUNT(offline_id) FROM prefetch_items WHERE state=?";
   sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt(0, static_cast<int>(PrefetchItemState::DOWNLOADING));
   if (!statement.Step())
@@ -69,12 +66,11 @@
   // the item longer than that, if we keep on retrying it.
   static const char kSql[] =
       "UPDATE prefetch_items"
-      " SET state = ?,"
-      "     guid = ?,"
-      "     freshness_time = CASE WHEN download_initiation_attempts = 0 THEN ?"
-      "                           ELSE freshness_time END,"
-      "     download_initiation_attempts = download_initiation_attempts + 1"
-      " WHERE offline_id = ?";
+      " SET state=?,guid=?,"
+      "freshness_time=CASE WHEN download_initiation_attempts=0 THEN ? "
+      "ELSE freshness_time END,"
+      "download_initiation_attempts=download_initiation_attempts+1"
+      " WHERE offline_id=?";
   sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt(0, static_cast<int>(PrefetchItemState::DOWNLOADING));
   statement.BindString(1, guid);
@@ -85,16 +81,12 @@
 
 std::unique_ptr<ItemsToDownload> SelectAndMarkItemsForDownloadSync(
     bool is_limitless_prefetching_enabled,
+    int max_concurrent_downloads,
     sql::Database* db) {
   sql::Transaction transaction(db);
   if (!transaction.Begin())
     return nullptr;
 
-  const int max_concurrent_downloads =
-      is_limitless_prefetching_enabled
-          ? DownloadArchivesTask::kMaxConcurrentDownloadsForLimitless
-          : DownloadArchivesTask::kMaxConcurrentDownloads;
-
   // Get current count of concurrent downloads and bail early if we are already
   // downloading more than we can.
   std::unique_ptr<int> concurrent_downloads(CountDownloadsInProgress(db));
@@ -153,16 +145,6 @@
 
 }  // namespace
 
-// static
-const int DownloadArchivesTask::kMaxConcurrentDownloads = 2;
-
-// This value matches the maximum number of concurrent downloads allowed by the
-// downloads service.
-// TODO(https://crbug.com/793158): obtain this value directly from the downloads
-// service once it is exposed.
-// static
-const int DownloadArchivesTask::kMaxConcurrentDownloadsForLimitless = 4;
-
 DownloadArchivesTask::DownloadItem::DownloadItem() = default;
 DownloadArchivesTask::DownloadItem::DownloadItem(const DownloadItem& other) =
     default;
@@ -188,10 +170,17 @@
     TaskComplete();
     return;
   }
+  const bool is_limitless_prefetching_enabled =
+      prefetch_prefs::IsLimitlessPrefetchingEnabled(prefs_);
+  const int max_concurrent_downloads =
+      is_limitless_prefetching_enabled
+          ? prefetch_downloader_->GetMaxConcurrentDownloads()
+          : DownloadArchivesTask::kMaxConcurrentDownloads;
 
   prefetch_store_->Execute(
       base::BindOnce(&SelectAndMarkItemsForDownloadSync,
-                     prefetch_prefs::IsLimitlessPrefetchingEnabled(prefs_)),
+                     is_limitless_prefetching_enabled,
+                     max_concurrent_downloads),
       base::BindOnce(&DownloadArchivesTask::SendItemsToPrefetchDownloader,
                      weak_ptr_factory_.GetWeakPtr()),
       std::unique_ptr<ItemsToDownload>());
diff --git a/components/offline_pages/core/prefetch/tasks/download_archives_task.h b/components/offline_pages/core/prefetch/tasks/download_archives_task.h
index e699652..102215a 100644
--- a/components/offline_pages/core/prefetch/tasks/download_archives_task.h
+++ b/components/offline_pages/core/prefetch/tasks/download_archives_task.h
@@ -26,10 +26,7 @@
 class DownloadArchivesTask : public Task {
  public:
   // Maximum number of parallel downloads.
-  static const int kMaxConcurrentDownloads;
-
-  // Maximum number of parallel downloads when limitless prefetching is enabled.
-  static const int kMaxConcurrentDownloadsForLimitless;
+  static constexpr int kMaxConcurrentDownloads = 2;
 
   // Represents item to be downloaded as a result of running the task.
   struct DownloadItem {
diff --git a/components/offline_pages/core/prefetch/tasks/download_archives_task_unittest.cc b/components/offline_pages/core/prefetch/tasks/download_archives_task_unittest.cc
index c4e89204..0c4a0711 100644
--- a/components/offline_pages/core/prefetch/tasks/download_archives_task_unittest.cc
+++ b/components/offline_pages/core/prefetch/tasks/download_archives_task_unittest.cc
@@ -307,15 +307,15 @@
   // Enable limitless prefetching.
   prefetch_prefs::SetLimitlessPrefetchingEnabled(prefs(), true);
 
-  // Check the concurrent downloads limit is greater for limitless.
-  ASSERT_GT(DownloadArchivesTask::kMaxConcurrentDownloadsForLimitless,
-            DownloadArchivesTask::kMaxConcurrentDownloads);
+  // Configure max concurrent downloads.
+  const size_t limitless_max_concurrent_downloads =
+      DownloadArchivesTask::kMaxConcurrentDownloads + 1;
+  prefetch_downloader()->SetMaxConcurrentDownloads(
+      limitless_max_concurrent_downloads);
 
   // Create more archives than we allow to download in parallel with limitless
   // and put them in the fresher |item_ids| in front.
-  const size_t max_concurrent_downloads = base::checked_cast<size_t>(
-      DownloadArchivesTask::kMaxConcurrentDownloadsForLimitless);
-  const size_t total_items = max_concurrent_downloads + 2;
+  const size_t total_items = limitless_max_concurrent_downloads + 1;
   std::vector<int64_t> item_ids;
   for (size_t i = 0; i < total_items; ++i)
     item_ids.insert(item_ids.begin(), InsertItemToDownload(kLargeArchiveSize));
@@ -332,11 +332,11 @@
 
   const TestPrefetchDownloader::RequestMap& requested_downloads =
       prefetch_downloader()->requested_downloads();
-  EXPECT_EQ(max_concurrent_downloads, requested_downloads.size());
+  EXPECT_EQ(limitless_max_concurrent_downloads, requested_downloads.size());
 
-  // The freshest |kMaxConcurrentDownloadsForLimitless| items should be started
+  // The freshest |limitless_max_concurrent_downloads| items should be started
   // as with limitless enabled there's no download size restrictions.
-  for (size_t i = 0; i < max_concurrent_downloads; ++i) {
+  for (size_t i = 0; i < limitless_max_concurrent_downloads; ++i) {
     const PrefetchItem* download_item =
         FindPrefetchItemByOfflineId(items_after_run, item_ids[i]);
     ASSERT_TRUE(download_item);
@@ -348,7 +348,7 @@
   }
 
   // Remaining items shouldn't have been started.
-  for (size_t i = max_concurrent_downloads; i < total_items; ++i) {
+  for (size_t i = limitless_max_concurrent_downloads; i < total_items; ++i) {
     const PrefetchItem* download_item_before =
         FindPrefetchItemByOfflineId(items_before_run, item_ids[i]);
     const PrefetchItem* download_item_after =
@@ -361,7 +361,7 @@
 
   histogram_tester.ExpectUniqueSample(
       "OfflinePages.Prefetching.DownloadExpectedFileSize",
-      kLargeArchiveSize / 1024, max_concurrent_downloads);
+      kLargeArchiveSize / 1024, limitless_max_concurrent_downloads);
 }
 
 TEST_F(DownloadArchivesTaskTest, SingleArchiveSecondAttempt) {
diff --git a/components/offline_pages/core/prefetch/test_prefetch_downloader.cc b/components/offline_pages/core/prefetch/test_prefetch_downloader.cc
index 2216c30..410ba30 100644
--- a/components/offline_pages/core/prefetch/test_prefetch_downloader.cc
+++ b/components/offline_pages/core/prefetch/test_prefetch_downloader.cc
@@ -41,6 +41,10 @@
 
 void TestPrefetchDownloader::OnDownloadFailed(const std::string& download_id) {}
 
+int TestPrefetchDownloader::GetMaxConcurrentDownloads() {
+  return max_concurrent_downloads_;
+}
+
 void TestPrefetchDownloader::Reset() {
   requested_downloads_.clear();
 }
diff --git a/components/offline_pages/core/prefetch/test_prefetch_downloader.h b/components/offline_pages/core/prefetch/test_prefetch_downloader.h
index 48d863b..bb2b8a88a 100644
--- a/components/offline_pages/core/prefetch/test_prefetch_downloader.h
+++ b/components/offline_pages/core/prefetch/test_prefetch_downloader.h
@@ -39,13 +39,19 @@
                            const base::FilePath& file_path,
                            int64_t file_size) override;
   void OnDownloadFailed(const std::string& download_id) override;
+  int GetMaxConcurrentDownloads() override;
 
   void Reset();
 
   const RequestMap& requested_downloads() const { return requested_downloads_; }
 
+  void SetMaxConcurrentDownloads(int max_concurrent_downloads) {
+    max_concurrent_downloads_ = max_concurrent_downloads;
+  }
+
  private:
   RequestMap requested_downloads_;
+  int max_concurrent_downloads_ = 4;
 };
 
 }  // namespace offline_pages
diff --git a/components/omnibox/browser/document_provider.cc b/components/omnibox/browser/document_provider.cc
index 8fa16dd..e10f49a2 100644
--- a/components/omnibox/browser/document_provider.cc
+++ b/components/omnibox/browser/document_provider.cc
@@ -540,6 +540,7 @@
   // We aim to prevent duplicate Drive URLs to appear between the Drive document
   // search provider and history/bookmark entries.
   // Drive URLs take on two core forms, and may have request parameters.
+  // Additionally, we may have redirector URLs which wrap a drive URL.
   // All URLs are canonicalized to a GURL form only used for deduplication and
   // not guaranteed to be usable for navigation.
   // URLs of the following forms are handled:
@@ -547,6 +548,7 @@
   // https://docs.google.com/document/d/(id)/edit
   // https://docs.google.com/spreadsheets/d/(id)/edit#gid=12345
   // https://docs.google.com/presentation/d/(id)/edit#slide=id.g12345a_0_26
+  // https://www.google.com/url?[...]url=https://drive.google.com/a/google.com/open?id%3D1fkxx6KYRYnSqljThxShJVliQJLdKzuJBnzogzL3n8rE&[...]
   // where id is comprised of characters in [0-9A-Za-z\-_] = [\w\-]
   std::string id;
   if (url.host() == "drive.google.com" && url.path() == "/open") {
@@ -555,6 +557,12 @@
     static re2::LazyRE2 doc_link_regex = {
         "^/(?:document|spreadsheets|presentation|forms)/d/([\\w-]+)/"};
     RE2::PartialMatch(url.path(), *doc_link_regex, &id);
+  } else if (url.host() == "www.google.com" && url.path() == "/url") {
+    // Redirect links wrapping a drive.google.com/open?id= link.
+    static re2::LazyRE2 redirect_link_regex = {
+        "^[^#]*url=https://drive\\.google\\.com/(?:a/[\\w\\.]+/"
+        ")?open\\?id%3D([^#&]*)"};
+    RE2::PartialMatch(url.query(), *redirect_link_regex, &id);
   }
 
   if (id.empty()) {
diff --git a/components/omnibox/browser/document_provider_unittest.cc b/components/omnibox/browser/document_provider_unittest.cc
index 0c1a00c..4377e769 100644
--- a/components/omnibox/browser/document_provider_unittest.cc
+++ b/components/omnibox/browser/document_provider_unittest.cc
@@ -558,9 +558,31 @@
   CheckDeduper(
       "https://docs.google.com/spreadsheets/d/the_doc-id/preview?x=1#y=2",
       "the_doc-id");
+  CheckDeduper(
+      "https://www.google.com/"
+      "url?sa=t&rct=j&esrc=s&source=appssearch&uact=8&cd=0&cad=rja&q&sig2=sig&"
+      "url=https://drive.google.com/a/google.com/"
+      "open?id%3D1fkxx6KYRYnSqljThxShJVliQJLdKzuJBnzogzL3n8rE&usg=X",
+      "1fkxx6KYRYnSqljThxShJVliQJLdKzuJBnzogzL3n8rE");
+  CheckDeduper(
+      "https://www.google.com/url?url=https://drive.google.com/a/google.com/"
+      "open?id%3Dthe_doc_id",
+      "the_doc_id");
+  CheckDeduper(
+      "https://www.google.com/url?url=https://drive.google.com/a/foo.edu/"
+      "open?id%3Dthe_doc_id",
+      "the_doc_id");
+  CheckDeduper(
+      "https://www.google.com/url?url=https://drive.google.com/"
+      "open?id%3Dthe_doc_id",
+      "the_doc_id");
 
   // URLs that do not represent documents:
   CheckDeduper("https://docs.google.com/help?id=d123", "");
   CheckDeduper("https://www.google.com", "");
   CheckDeduper("https://docs.google.com/kittens/d/d123/preview?x=1#y=2", "");
+  CheckDeduper(
+      "https://www.google.com/url?url=https://drive.google.com/homepage", "");
+  CheckDeduper("https://www.google.com/url?url=https://www.youtube.com/view",
+               "");
 }
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.cc b/components/password_manager/core/browser/sync/password_sync_bridge.cc
index 29d0b28..2f627d41 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.cc
@@ -12,6 +12,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/common/password_form.h"
+#include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/sync/model/metadata_batch.h"
 #include "components/sync/model/metadata_change_list.h"
@@ -274,6 +275,7 @@
       password_store_sync_->ReadAllLogins(&key_to_local_form_map);
 
   if (read_result == FormRetrievalResult::kDbError) {
+    metrics_util::LogPasswordSyncState(metrics_util::NOT_SYNCING_FAILED_READ);
     return syncer::ModelError(FROM_HERE,
                               "Failed to load entries from password store.");
   }
@@ -291,6 +293,7 @@
     // Clean up done successfully, try to read again.
     read_result = password_store_sync_->ReadAllLogins(&key_to_local_form_map);
     if (read_result != FormRetrievalResult::kSuccess) {
+      metrics_util::LogPasswordSyncState(metrics_util::NOT_SYNCING_FAILED_READ);
       return syncer::ModelError(
           FROM_HERE,
           "Failed to load entries from password store after cleanup.");
@@ -454,6 +457,7 @@
     password_store_sync_->NotifyLoginsChanged(password_store_changes);
   }
 
+  metrics_util::LogPasswordSyncState(metrics_util::SYNCING_OK);
   return error;
 }
 
@@ -639,10 +643,14 @@
     case DatabaseCleanupResult::kSuccess:
       break;
     case DatabaseCleanupResult::kEncryptionUnavailable:
+      metrics_util::LogPasswordSyncState(
+          metrics_util::NOT_SYNCING_FAILED_DECRYPTION);
       return syncer::ModelError(
           FROM_HERE, "Failed to get encryption key during database cleanup.");
     case DatabaseCleanupResult::kItemFailure:
     case DatabaseCleanupResult::kDatabaseUnavailable:
+      metrics_util::LogPasswordSyncState(
+          metrics_util::NOT_SYNCING_FAILED_CLEANUP);
       return syncer::ModelError(FROM_HERE, "Failed to cleanup database.");
   }
   return base::nullopt;
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index ebf6fdf..4183893 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -425,7 +425,7 @@
       'caption': '''Startup pages''',
       'desc': '''Allows you to configure the pages that are loaded on startup.
 
-      The contents of the list 'URLs to open at startup' are ignored unless you select 'Open a list of URLs' in 'Action on startup'.''',
+      The policy 'URLs to open on startup' is ignored unless you select 'Open a list of URLs' in 'Action on startup'.''',
       'policies': [
         'RestoreOnStartup',
         'RestoreOnStartupURLs',
diff --git a/components/sync/driver/about_sync_util.cc b/components/sync/driver/about_sync_util.cc
index 56d859f..e47fae5 100644
--- a/components/sync/driver/about_sync_util.cc
+++ b/components/sync/driver/about_sync_util.cc
@@ -331,7 +331,7 @@
   Stat<std::string>* username = section_identity->AddStringStat("Username");
   Stat<bool>* user_is_primary = section_identity->AddBoolStat("Is Primary");
   Stat<std::string>* auth_error = section_identity->AddStringStat("Auth Error");
-  // TODO(treib): Add the *time* of the auth error?
+  // TODO(crbug.com/948074): Add the time of the auth error.
 
   Section* section_credentials = section_list.AddSection("Credentials");
   Stat<std::string>* request_token_time =
diff --git a/components/sync/driver/profile_sync_service.cc b/components/sync/driver/profile_sync_service.cc
index 763a0a9..593bb12 100644
--- a/components/sync/driver/profile_sync_service.cc
+++ b/components/sync/driver/profile_sync_service.cc
@@ -526,11 +526,11 @@
 
   ReportPreviousSessionMemoryWarningCount();
 
-  // TODO(treib): Consider kicking off an access token fetch here. Currently,
-  // the flow goes as follows: The SyncEngine tries to connect to the server,
-  // but has no access token, so it ends up calling OnConnectionStatusChange(
-  // syncer::CONNECTION_AUTH_ERROR) which in turn causes SyncAuthManager to
-  // request a new access token. That seems needlessly convoluted.
+  // TODO(crbug.com/948148): Consider kicking off an access token fetch here.
+  // Currently, the flow goes as follows: The SyncEngine tries to connect to the
+  // server, but has no access token, so it ends up calling
+  // OnConnectionStatusChange(syncer::CONNECTION_AUTH_ERROR) which in turn
+  // causes SyncAuthManager to request a new access token.
 }
 
 void ProfileSyncService::Shutdown() {
diff --git a/components/sync/model_impl/processor_entity.cc b/components/sync/model_impl/processor_entity.cc
index f497dc4..28261929 100644
--- a/components/sync/model_impl/processor_entity.cc
+++ b/components/sync/model_impl/processor_entity.cc
@@ -24,10 +24,12 @@
 // Used for E2E latency measurements with UMA.
 const size_t kMaxTrackedCommittedServerVersions = 20;
 
-void HashSpecifics(const sync_pb::EntitySpecifics& specifics,
-                   std::string* hash) {
+std::string HashSpecifics(const sync_pb::EntitySpecifics& specifics) {
   DCHECK_GT(specifics.ByteSize(), 0);
-  base::Base64Encode(base::SHA1HashString(specifics.SerializeAsString()), hash);
+  std::string hash;
+  base::Base64Encode(base::SHA1HashString(specifics.SerializeAsString()),
+                     &hash);
+  return hash;
 }
 
 }  // namespace
@@ -110,9 +112,7 @@
   if (data.is_deleted() || metadata_.base_specifics_hash().empty()) {
     return false;
   }
-  std::string hash;
-  HashSpecifics(data.specifics, &hash);
-  return hash == metadata_.base_specifics_hash();
+  return HashSpecifics(data.specifics) == metadata_.base_specifics_hash();
 }
 
 bool ProcessorEntity::IsUnsynced() const {
@@ -327,15 +327,13 @@
     const sync_pb::EntitySpecifics& specifics) const {
   DCHECK(!metadata_.is_deleted());
   DCHECK_GT(specifics.ByteSize(), 0);
-  std::string hash;
-  HashSpecifics(specifics, &hash);
-  return hash == metadata_.specifics_hash();
+  return HashSpecifics(specifics) == metadata_.specifics_hash();
 }
 
 void ProcessorEntity::UpdateSpecificsHash(
     const sync_pb::EntitySpecifics& specifics) {
   if (specifics.ByteSize() > 0) {
-    HashSpecifics(specifics, metadata_.mutable_specifics_hash());
+    *metadata_.mutable_specifics_hash() = HashSpecifics(specifics);
   } else {
     metadata_.clear_specifics_hash();
   }
diff --git a/components/sync_sessions/favicon_cache.cc b/components/sync_sessions/favicon_cache.cc
index ca58d61f..ce4170b2d 100644
--- a/components/sync_sessions/favicon_cache.cc
+++ b/components/sync_sessions/favicon_cache.cc
@@ -226,6 +226,7 @@
                            history::HistoryService* history_service,
                            int max_sync_favicon_limit)
     : favicon_service_(favicon_service),
+      history_service_(history_service),
       max_sync_favicon_limit_(max_sync_favicon_limit),
       history_service_observer_(this),
       weak_ptr_factory_(this) {
@@ -236,6 +237,16 @@
 
 FaviconCache::~FaviconCache() {}
 
+void FaviconCache::WaitUntilReadyToSync(base::OnceClosure done) {
+  if (history_service_->backend_loaded()) {
+    std::move(done).Run();
+  } else {
+    // Wait until HistoryService's backend loads, reported via
+    // OnHistoryServiceLoaded().
+    wait_until_ready_to_sync_cb_.push_back(std::move(done));
+  }
+}
+
 syncer::SyncMergeResult FaviconCache::MergeDataAndStartSyncing(
     syncer::ModelType type,
     const syncer::SyncDataList& initial_sync_data,
@@ -1016,4 +1027,16 @@
   }
 }
 
+void FaviconCache::OnHistoryServiceLoaded(
+    history::HistoryService* history_service) {
+  // Make a copy before iterating over, in case triggering the callback has side
+  // effects.
+  std::vector<base::OnceClosure> callbacks =
+      std::move(wait_until_ready_to_sync_cb_);
+  wait_until_ready_to_sync_cb_.clear();
+
+  for (auto& cb : callbacks)
+    std::move(cb).Run();
+}
+
 }  // namespace sync_sessions
diff --git a/components/sync_sessions/favicon_cache.h b/components/sync_sessions/favicon_cache.h
index 08946fb..4d2c456 100644
--- a/components/sync_sessions/favicon_cache.h
+++ b/components/sync_sessions/favicon_cache.h
@@ -66,6 +66,7 @@
   ~FaviconCache() override;
 
   // SyncableService implementation.
+  void WaitUntilReadyToSync(base::OnceClosure done) override;
   syncer::SyncMergeResult MergeDataAndStartSyncing(
       syncer::ModelType type,
       const syncer::SyncDataList& initial_sync_data,
@@ -198,8 +199,11 @@
   // history::HistoryServiceObserver:
   void OnURLsDeleted(history::HistoryService* history_service,
                      const history::DeletionInfo& deletion_info) override;
+  void OnHistoryServiceLoaded(
+      history::HistoryService* history_service) override;
 
-  favicon::FaviconService* favicon_service_;
+  favicon::FaviconService* const favicon_service_;
+  history::HistoryService* const history_service_;
 
   // Task tracker for loading favicons.
   base::CancelableTaskTracker cancelable_task_tracker_;
@@ -226,6 +230,10 @@
   // Maximum number of favicons to sync. 0 means no limit.
   const size_t max_sync_favicon_limit_;
 
+  // A vector is needed to support concurrent calls to WaitUntilReadyToSync()
+  // because this class powers two datatypes.
+  std::vector<base::OnceClosure> wait_until_ready_to_sync_cb_;
+
   ScopedObserver<history::HistoryService, history::HistoryServiceObserver>
       history_service_observer_;
 
diff --git a/components/test/data/autofill_assistant/autofill_assistant_target_website.html b/components/test/data/autofill_assistant/autofill_assistant_target_website.html
index 65df22a..55e279b 100644
--- a/components/test/data/autofill_assistant/autofill_assistant_target_website.html
+++ b/components/test/data/autofill_assistant/autofill_assistant_target_website.html
@@ -56,6 +56,15 @@
         left: -9999px;
       }
 
+      #terms-and-conditions::before {
+          content: "before";
+      }
+
+      #button::before {
+          content: "before";
+          display: none;
+      }
+
       label[for="terms-and-conditions"] {
         padding-left: 48px;
         position: relative;
diff --git a/components/viz/common/quads/compositor_frame_metadata.h b/components/viz/common/quads/compositor_frame_metadata.h
index f516309..e2f3fbc 100644
--- a/components/viz/common/quads/compositor_frame_metadata.h
+++ b/components/viz/common/quads/compositor_frame_metadata.h
@@ -60,8 +60,7 @@
 
   CompositorFrameMetadata Clone() const;
 
-  // The device scale factor used to generate this compositor frame. Must be
-  // greater than zero.
+  // The device scale factor used to generate this compositor frame.
   float device_scale_factor = 0.f;
 
   // Scroll offset and scale of the root layer. This can be used for tasks
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 76cf2c9d..e8106a4 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -74,6 +74,13 @@
   SkPoint points[4];
 };
 
+// Additional YUV information to skia renderer to draw 9- and 10- bits color.
+struct YUVInput {
+  YUVInput() { memset(this, 0, sizeof(*this)); }
+  float offset;
+  float multiplier;
+};
+
 SkDrawRegion::SkDrawRegion(const gfx::QuadF& draw_region) {
   points[0] = gfx::PointFToSkPoint(draw_region.p1());
   points[1] = gfx::PointFToSkPoint(draw_region.p2());
@@ -771,6 +778,9 @@
     case DrawQuad::DEBUG_BORDER:
       DrawDebugBorderQuad(DebugBorderDrawQuad::MaterialCast(quad), params);
       break;
+    case DrawQuad::PICTURE_CONTENT:
+      DrawPictureQuad(PictureDrawQuad::MaterialCast(quad), params);
+      break;
     case DrawQuad::SOLID_COLOR:
       DrawSolidColorQuad(SolidColorDrawQuad::MaterialCast(quad), params);
       break;
@@ -786,6 +796,19 @@
     case DrawQuad::YUV_VIDEO_CONTENT:
       DrawYUVVideoQuad(YUVVideoDrawQuad::MaterialCast(quad), params);
       break;
+    case DrawQuad::INVALID:
+      DrawUnsupportedQuad(quad, params);
+      NOTREACHED();
+      break;
+    case DrawQuad::VIDEO_HOLE:
+      // VideoHoleDrawQuad should only be used by Cast, and should
+      // have been replaced by cast-specific OverlayProcessor before
+      // reach here. In non-cast build, an untrusted render could send such
+      // Quad and the quad would then reach here unexpectedly. Therefore
+      // we should skip NOTREACHED() so an untrusted render is not capable
+      // of causing a crash.
+      DrawUnsupportedQuad(quad, params);
+      break;
     default:
       // If we've reached here, the quad's type hasn't been updated to be
       // batch aware yet.
@@ -1028,6 +1051,68 @@
   current_canvas_->drawPath(path, paint);
 }
 
+void SkiaRenderer::DrawPictureQuad(const PictureDrawQuad* quad,
+                                   const DrawQuadParams& params) {
+  DCHECK(batched_quads_.empty());
+  TRACE_EVENT0("viz", "SkiaRenderer::DrawPictureQuad");
+
+  // If the layer is transparent or needs a non-SrcOver blend mode, saveLayer
+  // must be used so that the display list is drawn into a transient image and
+  // then blended as a single layer at the end.
+  const bool needs_transparency =
+      params.opacity < 1.f || params.blend_mode != SkBlendMode::kSrcOver;
+  const bool disable_image_filtering =
+      disable_picture_quad_image_filtering_ ||
+      params.filter_quality == kNone_SkFilterQuality;
+
+  SkAutoCanvasRestore acr(current_canvas_, true /* do_save */);
+  PrepareCanvas(params.has_scissor_rect ? &scissor_rect_ : nullptr,
+                &params.content_device_transform);
+
+  // Unlike other quads which draw visible_rect or draw_region as their geometry
+  // these represent the valid windows of content to show for the display list,
+  // so they need to be used as a clip in Skia.
+  SkRect visible_rect = gfx::RectFToSkRect(params.visible_rect);
+  SkPaint paint = params.paint();
+  if (params.draw_region.has_value()) {
+    SkPath clip;
+    clip.addPoly(params.draw_region->points, 4, true /* close */);
+    current_canvas_->clipPath(clip, paint.isAntiAlias());
+  } else {
+    current_canvas_->clipRect(visible_rect, paint.isAntiAlias());
+  }
+
+  if (needs_transparency) {
+    // Use the DrawQuadParams' paint for the layer, since that will affect the
+    // final draw of the backing image.
+    current_canvas_->saveLayer(&visible_rect, &paint);
+  }
+
+  SkCanvas* raster_canvas = current_canvas_;
+  base::Optional<skia::OpacityFilterCanvas> opacity_canvas;
+  if (disable_image_filtering) {
+    // TODO(vmpstr): Fold this canvas into playback and have raster source
+    // accept a set of settings on playback that will determine which canvas to
+    // apply. (http://crbug.com/594679)
+    // saveLayer applies the opacity, this filter is only used for quality
+    // overriding in the display list, hence the fixed 1.f for alpha.
+    opacity_canvas.emplace(raster_canvas, 1.f, disable_image_filtering);
+    raster_canvas = &*opacity_canvas;
+  }
+
+  // Treat all subnormal values as zero for performance.
+  cc::ScopedSubnormalFloatDisabler disabler;
+
+  raster_canvas->concat(SkMatrix::MakeRectToRect(
+      gfx::RectFToSkRect(quad->tex_coord_rect), gfx::RectToSkRect(quad->rect),
+      SkMatrix::kFill_ScaleToFit));
+
+  raster_canvas->translate(-quad->content_rect.x(), -quad->content_rect.y());
+  raster_canvas->clipRect(gfx::RectToSkRect(quad->content_rect));
+  raster_canvas->scale(quad->contents_scale, quad->contents_scale);
+  quad->display_item_list->Raster(raster_canvas);
+}
+
 void SkiaRenderer::DrawSolidColorQuad(const SolidColorDrawQuad* quad,
                                       const DrawQuadParams& params) {
   DrawColoredQuad(params, quad->color);
@@ -1194,7 +1279,8 @@
   gfx::ColorSpace dst_color_space =
       current_frame()->current_render_pass->color_space;
   sk_sp<SkColorFilter> color_filter =
-      GetColorFilter(src_color_space, dst_color_space);
+      GetColorFilter(src_color_space, dst_color_space, quad->resource_offset,
+                     quad->resource_multiplier);
 
   DCHECK(resource_provider_);
   ScopedYUVSkImageBuilder builder(this, quad, dst_color_space.ToSkColorSpace(),
@@ -1214,6 +1300,15 @@
   DrawSingleImage(params, image, visible_tex_coord_rect, &paint);
 }
 
+void SkiaRenderer::DrawUnsupportedQuad(const DrawQuad* quad,
+                                       const DrawQuadParams& params) {
+#ifdef NDEBUG
+  DrawColoredQuad(params, SK_ColorWHITE);
+#else
+  DrawColoredQuad(params, SK_ColorMAGENTA);
+#endif
+}
+
 void SkiaRenderer::DoSingleDrawQuad(const DrawQuad* quad,
                                     const gfx::QuadF* draw_region) {
   base::Optional<SkAutoCanvasRestore> auto_canvas_restore;
@@ -1248,7 +1343,7 @@
       NOTREACHED();
       break;
     case DrawQuad::PICTURE_CONTENT:
-      DrawPictureQuad(PictureDrawQuad::MaterialCast(quad), &paint);
+      NOTREACHED();
       break;
     case DrawQuad::RENDER_PASS:
       DrawRenderPassQuad(RenderPassDrawQuad::MaterialCast(quad), &paint);
@@ -1274,17 +1369,8 @@
       NOTREACHED();
       break;
     case DrawQuad::INVALID:
-      DrawUnsupportedQuad(quad, &paint);
-      NOTREACHED();
-      break;
     case DrawQuad::VIDEO_HOLE:
-      // VideoHoleDrawQuad should only be used by Cast, and should
-      // have been replaced by cast-specific OverlayProcessor before
-      // reach here. In non-cast build, an untrusted render could send such
-      // Quad and the quad would then reach here unexpectedly. Therefore
-      // we should skip NOTREACHED() so an untrusted render is not capable
-      // of causing a crash.
-      DrawUnsupportedQuad(quad, &paint);
+      NOTREACHED();
       break;
   }
 
@@ -1317,49 +1403,10 @@
   }
 }
 
-void SkiaRenderer::DrawPictureQuad(const PictureDrawQuad* quad,
-                                   SkPaint* paint) {
-  DCHECK(paint);
-  SkMatrix content_matrix;
-  content_matrix.setRectToRect(gfx::RectFToSkRect(quad->tex_coord_rect),
-                               gfx::RectToSkRect(quad->rect),
-                               SkMatrix::kFill_ScaleToFit);
-  current_canvas_->concat(content_matrix);
-
-  const bool needs_transparency =
-      SkScalarRoundToInt(quad->shared_quad_state->opacity * 255) < 255;
-  const bool disable_image_filtering =
-      disable_picture_quad_image_filtering_ || quad->nearest_neighbor;
-
-  TRACE_EVENT0("viz", "SkiaRenderer::DrawPictureQuad");
-
-  SkCanvas* raster_canvas = current_canvas_;
-
-  base::Optional<skia::OpacityFilterCanvas> opacity_canvas;
-  if (needs_transparency || disable_image_filtering) {
-    // TODO(aelias): This isn't correct in all cases. We should detect these
-    // cases and fall back to a persistent bitmap backing
-    // (http://crbug.com/280374).
-    // TODO(vmpstr): Fold this canvas into playback and have raster source
-    // accept a set of settings on playback that will determine which canvas to
-    // apply. (http://crbug.com/594679)
-    opacity_canvas.emplace(raster_canvas, quad->shared_quad_state->opacity,
-                           disable_image_filtering);
-    raster_canvas = &*opacity_canvas;
-  }
-
-  // Treat all subnormal values as zero for performance.
-  cc::ScopedSubnormalFloatDisabler disabler;
-
-  SkAutoCanvasRestore auto_canvas_restore(raster_canvas, true /* do_save */);
-  raster_canvas->translate(-quad->content_rect.x(), -quad->content_rect.y());
-  raster_canvas->clipRect(gfx::RectToSkRect(quad->content_rect));
-  raster_canvas->scale(quad->contents_scale, quad->contents_scale);
-  quad->display_item_list->Raster(raster_canvas);
-}
-
 sk_sp<SkColorFilter> SkiaRenderer::GetColorFilter(const gfx::ColorSpace& src,
-                                                  const gfx::ColorSpace& dst) {
+                                                  const gfx::ColorSpace& dst,
+                                                  float resource_offset,
+                                                  float resource_multiplier) {
   sk_sp<SkColorFilter>& color_filter = color_filter_cache_[dst][src];
   if (!color_filter) {
     std::unique_ptr<gfx::ColorTransform> transform =
@@ -1369,8 +1416,20 @@
     // COLOR_CONVERSION_MODE_LUT).
     if (!transform->CanGetShaderSource())
       return nullptr;
+
+    YUVInput input;
+    input.offset = resource_offset;
+    input.multiplier = resource_multiplier;
+    sk_sp<SkData> data = SkData::MakeWithCopy(&input, sizeof(input));
+
     const char* hdr = R"(
+in uniform float offset;
+in uniform float multiplier;
+
 void main(inout half4 color) {
+  color.rgb -= half(offset);
+  color.rgb *= half(multiplier);
+
   // un-premultiply alpha
   if (color.a > 0)
     color.rgb /= color.a;
@@ -1382,9 +1441,10 @@
 )";
 
     std::string shader = hdr + transform->GetSkShaderSource() + ftr;
+
     color_filter =
         SkRuntimeColorFilterFactory(SkString(shader.c_str(), shader.size()))
-            .make(SkData::MakeEmpty());
+            .make(data);
   }
   return color_filter;
 }
@@ -1625,19 +1685,6 @@
                                  paint);
 }
 
-void SkiaRenderer::DrawUnsupportedQuad(const DrawQuad* quad, SkPaint* paint) {
-  DCHECK(paint);
-  // TODO(weiliangc): Make sure unsupported quads work. (crbug.com/644851)
-  NOTIMPLEMENTED();
-#ifdef NDEBUG
-  paint->setColor(SK_ColorWHITE);
-#else
-  paint->setColor(SK_ColorMAGENTA);
-#endif
-  paint->setAlpha(quad->shared_quad_state->opacity * 255);
-  current_canvas_->drawRect(gfx::RectToSkRect(quad->rect), *paint);
-}
-
 void SkiaRenderer::CopyDrawnRenderPass(
     const copy_output::RenderPassGeometry& geometry,
     std::unique_ptr<CopyOutputRequest> request) {
diff --git a/components/viz/service/display/skia_renderer.h b/components/viz/service/display/skia_renderer.h
index a166816..2715f4a 100644
--- a/components/viz/service/display/skia_renderer.h
+++ b/components/viz/service/display/skia_renderer.h
@@ -142,7 +142,8 @@
   // the texture will have advanced paint effects (rpdq)
   void DrawDebugBorderQuad(const DebugBorderDrawQuad* quad,
                            const DrawQuadParams& params);
-  void DrawPictureQuad(const PictureDrawQuad* quad, SkPaint* paint);
+  void DrawPictureQuad(const PictureDrawQuad* quad,
+                       const DrawQuadParams& params);
   void DrawRenderPassQuad(const RenderPassDrawQuad* quad, SkPaint* paint);
   void DrawRenderPassQuadInternal(const RenderPassDrawQuad* quad,
                                   sk_sp<SkImage> content_image,
@@ -158,7 +159,8 @@
   void DrawTileDrawQuad(const TileDrawQuad* quad, const DrawQuadParams& params);
   void DrawYUVVideoQuad(const YUVVideoDrawQuad* quad,
                         const DrawQuadParams& params);
-  void DrawUnsupportedQuad(const DrawQuad* quad, SkPaint* paint);
+  void DrawUnsupportedQuad(const DrawQuad* quad, const DrawQuadParams& params);
+
   bool CalculateRPDQParams(sk_sp<SkImage> src_image,
                            const RenderPassDrawQuad* quad,
                            DrawRenderPassDrawQuadParams* params);
@@ -174,7 +176,9 @@
   bool is_using_ddl() const { return draw_mode_ == DrawMode::DDL; }
 
   sk_sp<SkColorFilter> GetColorFilter(const gfx::ColorSpace& src,
-                                      const gfx::ColorSpace& dst);
+                                      const gfx::ColorSpace& dst,
+                                      float resource_offset,
+                                      float resource_multiplier);
   // A map from RenderPass id to the texture used to draw the RenderPass from.
   struct RenderPassBacking {
     sk_sp<SkSurface> render_pass_surface;
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 4a729685a..70a6aab 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -456,11 +456,10 @@
              last_created_local_surface_id.parent_sequence_number() ||
          child_initiated_synchronization_event);
 
-    DCHECK(surface_info.is_valid());
-    if (!monotonically_increasing_id) {
-      TRACE_EVENT_INSTANT0("viz", "LocalSurfaceId decreased",
+    if (!surface_info.is_valid() || !monotonically_increasing_id) {
+      TRACE_EVENT_INSTANT0("viz", "Surface Invariants Violation",
                            TRACE_EVENT_SCOPE_THREAD);
-      return SubmitResult::SURFACE_ID_DECREASED;
+      return SubmitResult::SURFACE_INVARIANTS_VIOLATION;
     }
 
     // If the last Surface doesn't have a dependent frame, and this frame
@@ -481,9 +480,9 @@
     }
     current_surface = CreateSurface(surface_info, block_activation_on_parent);
     if (!current_surface) {
-      TRACE_EVENT_INSTANT0("viz", "Surface belongs to another client",
+      TRACE_EVENT_INSTANT0("viz", "Surface Invariants Violation",
                            TRACE_EVENT_SCOPE_THREAD);
-      return SubmitResult::SURFACE_OWNED_BY_ANOTHER_CLIENT;
+      return SubmitResult::SURFACE_INVARIANTS_VIOLATION;
     }
     last_created_surface_id_ = SurfaceId(frame_sink_id_, local_surface_id);
 
@@ -508,7 +507,7 @@
                      weak_factory_.GetWeakPtr(), frame.metadata.frame_token));
   if (!result) {
     TRACE_EVENT_INSTANT0("viz", "QueueFrame failed", TRACE_EVENT_SCOPE_THREAD);
-    return SubmitResult::SIZE_MISMATCH;
+    return SubmitResult::SURFACE_INVARIANTS_VIOLATION;
   }
 
   if (begin_frame_source_)
@@ -731,12 +730,8 @@
       return "Accepted";
     case SubmitResult::COPY_OUTPUT_REQUESTS_NOT_ALLOWED:
       return "CopyOutputRequests not allowed";
-    case SubmitResult::SIZE_MISMATCH:
-      return "CompositorFrame size doesn't match surface size";
-    case SubmitResult::SURFACE_ID_DECREASED:
-      return "LocalSurfaceId sequence numbers decreased";
-    case SubmitResult::SURFACE_OWNED_BY_ANOTHER_CLIENT:
-      return "Surface belongs to another client";
+    case SubmitResult::SURFACE_INVARIANTS_VIOLATION:
+      return "Surface invariants violation";
   }
   NOTREACHED();
   return nullptr;
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 521cb34..d2a4a937 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -40,12 +40,9 @@
 enum class SubmitResult {
   ACCEPTED = 0,
   COPY_OUTPUT_REQUESTS_NOT_ALLOWED = 1,
-  // SURFACE_INVARIANTS_VIOLATION = 2 is deprecated.
-  SIZE_MISMATCH = 3,
-  SURFACE_ID_DECREASED = 4,
-  SURFACE_OWNED_BY_ANOTHER_CLIENT = 5,
+  SURFACE_INVARIANTS_VIOLATION = 2,
   // Magic constant used by the histogram macros.
-  kMaxValue = SURFACE_OWNED_BY_ANOTHER_CLIENT,
+  kMaxValue = SURFACE_INVARIANTS_VIOLATION,
 };
 
 class VIZ_SERVICE_EXPORT CompositorFrameSinkSupport
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
index 6824d9d..cd2618a 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
@@ -630,17 +630,19 @@
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
   EXPECT_EQ(SubmitResult::ACCEPTED, result);
 
-  // LocalSurfaceId(5, 3): Submit rejected because not monotonically increasing.
+  // LocalSurfaceId(5, 3): Surface Invariants Violation. Not monotonically
+  // increasing.
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id4, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
-  EXPECT_EQ(SubmitResult::SURFACE_ID_DECREASED, result);
+  EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
 
-  // LocalSurfaceId(8, 1): Submit rejected because not monotonically increasing.
+  // LocalSurfaceId(8, 1): Surface Invariants Violation. Not monotonically
+  // increasing.
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id5, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
-  EXPECT_EQ(SubmitResult::SURFACE_ID_DECREASED, result);
+  EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
 
   // LocalSurfaceId(9, 3): Parent AND child-initiated synchronization.
   result = support->MaybeSubmitCompositorFrame(
@@ -874,6 +876,21 @@
             surface_observer_.last_surface_info().size_in_pixels());
 }
 
+// Check that if a CompositorFrame is received with device scale factor of 0, we
+// don't create a Surface for it.
+TEST_F(CompositorFrameSinkSupportTest, ZeroDeviceScaleFactor) {
+  SurfaceId id(support_->frame_sink_id(), local_surface_id_);
+  auto frame = CompositorFrameBuilder()
+                   .AddDefaultRenderPass()
+                   .SetDeviceScaleFactor(0.f)
+                   .Build();
+  const auto result = support_->MaybeSubmitCompositorFrame(
+      local_surface_id_, std::move(frame), base::nullopt, 0,
+      mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
+  EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
+  EXPECT_FALSE(GetSurfaceForId(id));
+}
+
 // Check that if the size of a CompositorFrame doesn't match the size of the
 // Surface it's being submitted to, we skip the frame.
 TEST_F(CompositorFrameSinkSupportTest, FrameSizeMismatch) {
@@ -902,7 +919,7 @@
       local_surface_id_, std::move(frame), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
 
-  EXPECT_EQ(SubmitResult::SIZE_MISMATCH, result);
+  EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
 
   // All the resources in the rejected frame should have been returned.
   CheckReturnedResourcesMatchExpected(frame_resource_ids,
@@ -935,7 +952,7 @@
   result = support_->MaybeSubmitCompositorFrame(
       local_surface_id_, std::move(frame), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
-  EXPECT_EQ(SubmitResult::SIZE_MISMATCH, result);
+  EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
 }
 
 TEST_F(CompositorFrameSinkSupportTest, PassesOnBeginFrameAcks) {
@@ -1209,7 +1226,7 @@
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
-  EXPECT_EQ(SubmitResult::SURFACE_OWNED_BY_ANOTHER_CLIENT, result);
+  EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
 }
 
 }  // namespace viz
diff --git a/content/browser/android/synchronous_compositor_host.cc b/content/browser/android/synchronous_compositor_host.cc
index 1bf3164..c5ec129 100644
--- a/content/browser/android/synchronous_compositor_host.cc
+++ b/content/browser/android/synchronous_compositor_host.cc
@@ -10,7 +10,8 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/memory/ptr_util.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/writable_shared_memory_region.h"
 #include "base/task/post_task.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/trace_event/traced_value.h"
@@ -286,7 +287,7 @@
 };
 
 struct SynchronousCompositorHost::SharedMemoryWithSize {
-  base::SharedMemory shm;
+  base::WritableSharedMemoryMapping shared_memory;
   const size_t stride;
   const size_t buffer_size;
 
@@ -343,8 +344,10 @@
   UpdateFrameMetaData(metadata_version, std::move(*metadata));
 
   SkBitmap bitmap;
-  if (!bitmap.installPixels(info, software_draw_shm_->shm.memory(), stride))
+  if (!bitmap.installPixels(info, software_draw_shm_->shared_memory.memory(),
+                            stride)) {
     return false;
+  }
 
   {
     TRACE_EVENT0("browser", "DrawBitmap");
@@ -364,20 +367,20 @@
       software_draw_shm_->buffer_size == buffer_size)
     return;
   software_draw_shm_.reset();
-  std::unique_ptr<SharedMemoryWithSize> software_draw_shm(
-      new SharedMemoryWithSize(stride, buffer_size));
+  auto software_draw_shm =
+      std::make_unique<SharedMemoryWithSize>(stride, buffer_size);
+  base::WritableSharedMemoryRegion shm_region;
   {
     TRACE_EVENT1("browser", "AllocateSharedMemory", "buffer_size", buffer_size);
-    if (!software_draw_shm->shm.CreateAndMapAnonymous(buffer_size))
+    shm_region = base::WritableSharedMemoryRegion::Create(buffer_size);
+    if (!shm_region.IsValid())
+      return;
+
+    software_draw_shm->shared_memory = shm_region.Map();
+    if (!software_draw_shm->shared_memory.IsValid())
       return;
   }
 
-  SyncCompositorSetSharedMemoryParams set_shm_params;
-  set_shm_params.buffer_size = buffer_size;
-  set_shm_params.shm_handle = software_draw_shm->shm.handle().Duplicate();
-  if (!set_shm_params.shm_handle.IsValid())
-    return;
-
   bool success = false;
   SyncCompositorCommonRendererParams common_renderer_params;
   {
@@ -385,8 +388,8 @@
     base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
         allow_base_sync_primitives;
     if (!IsReadyForSynchronousCall() ||
-        !GetSynchronousCompositor()->SetSharedMemory(set_shm_params, &success,
-                                                     &common_renderer_params) ||
+        !GetSynchronousCompositor()->SetSharedMemory(
+            std::move(shm_region), &success, &common_renderer_params) ||
         !success) {
       return;
     }
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 55f1920f..8154ea9c 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -552,37 +552,19 @@
   DCHECK(browser_context);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   base::AutoLock lock(lock_);
-  AddChild(child_id, browser_context);
+  if (security_state_.count(child_id) != 0) {
+    NOTREACHED() << "Add child process at most once.";
+    return;
+  }
+
+  security_state_[child_id] =
+      base::MakeRefCounted<SecurityState>(browser_context);
 }
 
 void ChildProcessSecurityPolicyImpl::Remove(int child_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   base::AutoLock lock(lock_);
-
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
-    return;
-
-  // Moving the existing SecurityState object into a pending map so
-  // that we can preserve permission state and avoid mutations to this
-  // state after Remove() has been called.
-  pending_remove_state_[child_id] = std::move(state->second);
   security_state_.erase(child_id);
-
-  // |child_id| could be inside tasks that are on the IO thread task queues. We
-  // need to keep the |pending_remove_state_| entry around until we have
-  // successfully executed a task on the IO thread. This should ensure that any
-  // pending tasks on the IO thread will have completed before we remove the
-  // entry.
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(
-          [](ChildProcessSecurityPolicyImpl* policy, int child_id) {
-            DCHECK_CURRENTLY_ON(BrowserThread::IO);
-            base::AutoLock lock(policy->lock_);
-            policy->pending_remove_state_.erase(child_id);
-          },
-          base::Unretained(this), child_id));
 }
 
 void ChildProcessSecurityPolicyImpl::RegisterWebSafeScheme(
@@ -675,19 +657,19 @@
 
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
   if (origin.opaque()) {
     // If it's impossible to grant commit rights to just the origin (among other
     // things, URLs with non-standard schemes will be treated as opaque
     // origins), then grant access to commit all URLs of that scheme.
-    state->second->GrantCommitScheme(url.scheme());
+    state->GrantCommitScheme(url.scheme());
   } else {
     // When the child process has been commanded to request this scheme, grant
     // it the capability to request all URLs of that scheme.
-    state->second->GrantRequestScheme(url.scheme());
+    state->GrantRequestScheme(url.scheme());
   }
 }
 
@@ -699,15 +681,15 @@
 
   {
     base::AutoLock lock(lock_);
-    auto state = security_state_.find(child_id);
-    if (state == security_state_.end())
+    auto* state = GetSecurityState(child_id);
+    if (!state)
       return;
 
     // When the child process has been commanded to request a file:// URL,
     // then we grant it the capability for that URL only.
     base::FilePath path;
     if (net::FileURLToFilePath(url, &path))
-      state->second->GrantRequestOfSpecificFile(path);
+      state->GrantRequestOfSpecificFile(path);
   }
 }
 
@@ -735,22 +717,22 @@
     int child_id, const base::FilePath& file, int permissions) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->GrantPermissionsForFile(file, permissions);
+  state->GrantPermissionsForFile(file, permissions);
 }
 
 void ChildProcessSecurityPolicyImpl::RevokeAllPermissionsForFile(
     int child_id, const base::FilePath& file) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->RevokeAllPermissionsForFile(file);
+  state->RevokeAllPermissionsForFile(file);
 }
 
 void ChildProcessSecurityPolicyImpl::GrantReadFileSystem(
@@ -787,11 +769,11 @@
 void ChildProcessSecurityPolicyImpl::GrantSendMidiSysExMessage(int child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->GrantPermissionForMidiSysEx();
+  state->GrantPermissionForMidiSysEx();
 }
 
 void ChildProcessSecurityPolicyImpl::GrantCommitOrigin(
@@ -799,11 +781,11 @@
     const url::Origin& origin) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->GrantCommitOrigin(origin);
+  state->GrantCommitOrigin(origin);
 }
 
 void ChildProcessSecurityPolicyImpl::GrantRequestOrigin(
@@ -811,11 +793,11 @@
     const url::Origin& origin) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->GrantRequestOrigin(origin);
+  state->GrantRequestOrigin(origin);
 }
 
 void ChildProcessSecurityPolicyImpl::GrantRequestScheme(
@@ -823,11 +805,11 @@
     const std::string& scheme) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->GrantRequestScheme(scheme);
+  state->GrantRequestScheme(scheme);
 }
 
 void ChildProcessSecurityPolicyImpl::GrantWebUIBindings(int child_id,
@@ -838,37 +820,37 @@
 
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->GrantBindings(bindings);
+  state->GrantBindings(bindings);
 
   // Web UI bindings need the ability to request chrome: URLs.
-  state->second->GrantRequestScheme(kChromeUIScheme);
+  state->GrantRequestScheme(kChromeUIScheme);
 
   // Web UI pages can contain links to file:// URLs.
-  state->second->GrantRequestScheme(url::kFileScheme);
+  state->GrantRequestScheme(url::kFileScheme);
 }
 
 void ChildProcessSecurityPolicyImpl::GrantReadRawCookies(int child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->GrantReadRawCookies();
+  state->GrantReadRawCookies();
 }
 
 void ChildProcessSecurityPolicyImpl::RevokeReadRawCookies(int child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
 
-  state->second->RevokeReadRawCookies();
+  state->RevokeReadRawCookies();
 }
 
 bool ChildProcessSecurityPolicyImpl::CanRequestURL(
@@ -906,13 +888,13 @@
   {
     base::AutoLock lock(lock_);
 
-    auto state = security_state_.find(child_id);
-    if (state == security_state_.end())
+    auto* state = GetSecurityState(child_id);
+    if (!state)
       return false;
 
     // Otherwise, we consult the child process's security state to see if it is
     // allowed to request the URL.
-    if (state->second->CanRequestURL(url))
+    if (state->CanRequestURL(url))
       return true;
   }
 
@@ -994,13 +976,13 @@
     if (base::ContainsKey(schemes_okay_to_commit_in_any_process_, scheme))
       return true;
 
-    auto state = security_state_.find(child_id);
-    if (state == security_state_.end())
+    auto* state = GetSecurityState(child_id);
+    if (!state)
       return false;
 
     // Otherwise, we consult the child process's security state to see if it is
     // allowed to commit the URL.
-    return state->second->CanCommitURL(url);
+    return state->CanCommitURL(url);
   }
 }
 
@@ -1256,41 +1238,29 @@
 bool ChildProcessSecurityPolicyImpl::HasWebUIBindings(int child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return false;
 
-  return state->second->has_web_ui_bindings();
+  return state->has_web_ui_bindings();
 }
 
 bool ChildProcessSecurityPolicyImpl::CanReadRawCookies(int child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return false;
 
-  return state->second->can_read_raw_cookies();
-}
-
-void ChildProcessSecurityPolicyImpl::AddChild(int child_id,
-                                              BrowserContext* browser_context) {
-  DCHECK(browser_context);
-  if (security_state_.count(child_id) != 0) {
-    NOTREACHED() << "Add child process at most once.";
-    return;
-  }
-
-  security_state_[child_id] =
-      base::MakeRefCounted<SecurityState>(browser_context);
+  return state->can_read_raw_cookies();
 }
 
 bool ChildProcessSecurityPolicyImpl::ChildProcessHasPermissionsForFile(
     int child_id, const base::FilePath& file, int permissions) {
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return false;
-  return state->second->HasPermissionsForFile(file, permissions);
+  return state->HasPermissionsForFile(file, permissions);
 }
 
 bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(
@@ -1369,27 +1339,27 @@
 #endif
 
   base::AutoLock lock(lock_);
-  auto state = security_state_.find(child_id);
-  DCHECK(state != security_state_.end());
-  state->second->LockToOrigin(gurl, context.browsing_instance_id());
+  auto* state = GetSecurityState(child_id);
+  DCHECK(state);
+  state->LockToOrigin(gurl, context.browsing_instance_id());
 }
 
 ChildProcessSecurityPolicyImpl::CheckOriginLockResult
 ChildProcessSecurityPolicyImpl::CheckOriginLock(int child_id,
                                                 const GURL& site_url) {
   base::AutoLock lock(lock_);
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return ChildProcessSecurityPolicyImpl::CheckOriginLockResult::NO_LOCK;
-  return state->second->CheckOriginLock(site_url);
+  return state->CheckOriginLock(site_url);
 }
 
 GURL ChildProcessSecurityPolicyImpl::GetOriginLock(int child_id) {
   base::AutoLock lock(lock_);
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return GURL();
-  return state->second->origin_lock();
+  return state->origin_lock();
 }
 
 void ChildProcessSecurityPolicyImpl::GrantPermissionsForFileSystem(
@@ -1398,10 +1368,10 @@
     int permission) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return;
-  state->second->GrantPermissionsForFileSystem(filesystem_id, permission);
+  state->GrantPermissionsForFileSystem(filesystem_id, permission);
 }
 
 bool ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystem(
@@ -1410,10 +1380,10 @@
     int permission) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return false;
-  return state->second->HasPermissionsForFileSystem(filesystem_id, permission);
+  return state->HasPermissionsForFileSystem(filesystem_id, permission);
 }
 
 void ChildProcessSecurityPolicyImpl::RegisterFileSystemPermissionPolicy(
@@ -1426,11 +1396,11 @@
 bool ChildProcessSecurityPolicyImpl::CanSendMidiSysExMessage(int child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
-  if (state == security_state_.end())
+  auto* state = GetSecurityState(child_id);
+  if (!state)
     return false;
 
-  return state->second->can_send_midi_sysex();
+  return state->can_send_midi_sysex();
 }
 
 void ChildProcessSecurityPolicyImpl::AddIsolatedOrigins(
@@ -1524,10 +1494,6 @@
     for (auto& itr : security_state_) {
       itr.second->ClearMatchingContexts(&browser_context);
     }
-
-    for (auto& itr : pending_remove_state_) {
-      itr.second->ClearMatchingContexts(&browser_context);
-    }
   }
 
   base::AutoLock isolated_origins_lock(isolated_origins_lock_);
@@ -1670,15 +1636,6 @@
   if (itr != security_state_.end())
     return itr->second.get();
 
-  // Check to see if |child_id| is in the pending removal map since this
-  // may be a call that was already on the IO thread's task queue when the
-  // Remove() call occurred.
-  if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
-    itr = pending_remove_state_.find(child_id);
-    if (itr != pending_remove_state_.end())
-      return itr->second.get();
-  }
-
   return nullptr;
 }
 
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index c98f180..2cf8c742 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -178,13 +178,6 @@
 
   // Upon destruction, child processes should unregister themselves by calling
   // this method exactly once. This call must be made on the UI thread.
-  //
-  // Note: Pre-Remove() permissions remain in effect on the IO thread until
-  // the task posted to the IO thread by this call runs and removes the entry
-  // from |pending_remove_state_|.
-  // This UI -> IO task sequence ensures that any pending tasks, on the IO
-  // thread, for this |child_id| are allowed to run before access is completely
-  // revoked.
   void Remove(int child_id);
 
   // Whenever the browser processes commands the child process to commit a URL,
@@ -427,10 +420,6 @@
   ChildProcessSecurityPolicyImpl();
   friend struct base::DefaultSingletonTraits<ChildProcessSecurityPolicyImpl>;
 
-  // Adds child process during registration.
-  void AddChild(int child_id, BrowserContext* browser_context)
-      EXCLUSIVE_LOCKS_REQUIRED(lock_);
-
   // Determines if certain permissions were granted for a file to given child
   // process. |permissions| is an internally defined bit-set.
   bool ChildProcessHasPermissionsForFile(int child_id,
@@ -499,14 +488,6 @@
   // not escape this class.
   SecurityStateMap security_state_ GUARDED_BY(lock_);
 
-  // This map holds the SecurityState for a child process after Remove()
-  // is called on the UI thread. An entry stays in this map until a task has
-  // run on the IO thread. This is necessary to provide consistent security
-  // decisions and avoid races between the UI & IO threads during child process
-  // shutdown. This separate map is used to preserve SecurityState info AND
-  // preventing mutation of that state after Remove() is called.
-  SecurityStateMap pending_remove_state_ GUARDED_BY(lock_);
-
   FileSystemPermissionPolicyMap file_system_policy_map_ GUARDED_BY(lock_);
 
   // You must acquire this lock before reading or writing isolated_origins_.
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index 301cae01..32e3ca2 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -1075,120 +1075,6 @@
   EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
 }
 
-// Tests behavior of CanAccessDataForOrigin() during race conditions that
-// can occur during Remove(). It verifies that permissions for a child ID are
-// preserved after a Remove() call until the task, that Remove() has posted to
-// the IO thread, has run.
-//
-// We use a combination of waitable events and extra tasks posted to the
-// threads to capture permission state from the UI & IO threads during the
-// removal process. It is intended to simulate pending tasks that could be
-// run on each thread during removal.
-TEST_F(ChildProcessSecurityPolicyTest, RemoveRace_CanAccessDataForOrigin) {
-  ChildProcessSecurityPolicyImpl* p =
-      ChildProcessSecurityPolicyImpl::GetInstance();
-
-  GURL url("file:///etc/passwd");
-
-  p->Add(kRendererID, browser_context());
-
-  base::WaitableEvent ready_for_remove_event;
-  base::WaitableEvent remove_called_event;
-  base::WaitableEvent pending_remove_complete_event;
-
-  // Keep track of the return value for CanAccessDataForOrigin at various
-  // points in time during the test.
-  bool io_before_remove = false;
-  bool io_while_io_task_pending = false;
-  bool io_after_io_task_completed = false;
-  bool ui_before_remove = false;
-  bool ui_while_io_task_pending = false;
-  bool ui_after_io_task_completed = false;
-
-  // Post a task that will run on the IO thread before the task that
-  // Remove() will post to the IO thread.
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
-        // Capture state on the IO thread before Remove() is called.
-        io_before_remove = p->CanAccessDataForOrigin(kRendererID, url);
-
-        // Tell the UI thread we are ready for Remove() to be called.
-        ready_for_remove_event.Signal();
-
-        // Wait for Remove() to be called on the UI thread.
-        remove_called_event.Wait();
-
-        // Capture state after Remove() is called, but before its task on
-        // the IO thread runs.
-        io_while_io_task_pending = p->CanAccessDataForOrigin(kRendererID, url);
-      }));
-
-  ready_for_remove_event.Wait();
-
-  ui_before_remove = p->CanAccessDataForOrigin(kRendererID, url);
-
-  p->Remove(kRendererID);
-
-  // Post a task to run after the task Remove() posted on the IO thread.
-  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
-                           base::BindLambdaForTesting([&]() {
-                             io_after_io_task_completed =
-                                 p->CanAccessDataForOrigin(kRendererID, url);
-
-                             // Tell the UI thread that the task from Remove()
-                             // has completed on the IO thread.
-                             pending_remove_complete_event.Signal();
-                           }));
-
-  // Capture state after Remove() has been called, but before its IO thread
-  // task has run. We know the IO thread task hasn't run yet because the
-  // task we posted before the Remove() call is waiting for us to signal
-  // |remove_called_event|.
-  ui_while_io_task_pending = p->CanAccessDataForOrigin(kRendererID, url);
-
-  // Unblock the IO thread so the pending remove events can run.
-  remove_called_event.Signal();
-
-  pending_remove_complete_event.Wait();
-
-  // Capture state after IO thread task has run.
-  ui_after_io_task_completed = p->CanAccessDataForOrigin(kRendererID, url);
-
-  // Run pending UI thread tasks.
-  base::RunLoop run_loop;
-  run_loop.RunUntilIdle();
-
-  bool ui_after_remove_complete = p->CanAccessDataForOrigin(kRendererID, url);
-  bool io_after_remove_complete = false;
-  base::WaitableEvent after_remove_complete_event;
-
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
-        io_after_remove_complete = p->CanAccessDataForOrigin(kRendererID, url);
-
-        // Tell the UI thread that this task has
-        // has completed on the IO thread.
-        after_remove_complete_event.Signal();
-      }));
-
-  // Wait for the task we just posted to the IO thread to complete.
-  after_remove_complete_event.Wait();
-
-  // Verify expected states at various parts of the removal.
-  // Note: IO thread is expected to keep pre-Remove() permissions until
-  // the task Remove() posted runs on the IO thread.
-  EXPECT_TRUE(io_before_remove);
-  EXPECT_TRUE(io_while_io_task_pending);
-  EXPECT_FALSE(io_after_io_task_completed);
-
-  EXPECT_TRUE(ui_before_remove);
-  EXPECT_FALSE(ui_while_io_task_pending);
-  EXPECT_FALSE(ui_after_io_task_completed);
-
-  EXPECT_FALSE(ui_after_remove_complete);
-  EXPECT_FALSE(io_after_remove_complete);
-}
-
 TEST_F(ChildProcessSecurityPolicyTest, CanAccessDataForOrigin) {
   ChildProcessSecurityPolicyImpl* p =
       ChildProcessSecurityPolicyImpl::GetInstance();
@@ -1719,11 +1605,9 @@
   // Keep track of the return value for HasSecurityState() at various
   // points in time during the test.
   bool io_before_remove = false;
-  bool io_while_io_task_pending = false;
-  bool io_after_io_task_completed = false;
+  bool io_after_remove = false;
   bool ui_before_remove = false;
-  bool ui_while_io_task_pending = false;
-  bool ui_after_io_task_completed = false;
+  bool ui_after_remove = false;
 
   // Post a task that will run on the IO thread before the task that
   // Remove() will post to the IO thread.
@@ -1740,7 +1624,9 @@
 
         // Capture state after Remove() is called, but before its task on
         // the IO thread runs.
-        io_while_io_task_pending = p->HasSecurityState(kRendererID);
+        io_after_remove = p->HasSecurityState(kRendererID);
+
+        pending_remove_complete_event.Signal();
       }));
 
   ready_for_remove_event.Wait();
@@ -1749,63 +1635,19 @@
 
   p->Remove(kRendererID);
 
-  // Post a task to run after the task Remove() posted on the IO thread.
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
-        io_after_io_task_completed = p->HasSecurityState(kRendererID);
+  ui_after_remove = p->HasSecurityState(kRendererID);
 
-        // Tell the UI thread that the task from Remove()
-        // has completed on the IO thread.
-        pending_remove_complete_event.Signal();
-      }));
-
-  // Capture state after Remove() has been called, but before its IO thread
-  // task has run. We know the IO thread task hasn't run yet because the
-  // task we posted before the Remove() call is waiting for us to signal
-  // |remove_called_event|.
-  ui_while_io_task_pending = p->HasSecurityState(kRendererID);
-
-  // Unblock the IO thread so the pending remove events can run.
+  // Unblock the IO thread so it can collect post-Remove() state.
   remove_called_event.Signal();
 
   pending_remove_complete_event.Wait();
 
-  // Capture state after IO thread task has run.
-  ui_after_io_task_completed = p->HasSecurityState(kRendererID);
-
-  // Run pending UI thread tasks.
-  base::RunLoop run_loop;
-  run_loop.RunUntilIdle();
-
-  bool ui_after_remove_complete = p->HasSecurityState(kRendererID);
-  bool io_after_remove_complete = false;
-  base::WaitableEvent after_remove_complete_event;
-
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
-        io_after_remove_complete = p->HasSecurityState(kRendererID);
-
-        // Tell the UI thread that this task has
-        // has completed on the IO thread.
-        after_remove_complete_event.Signal();
-      }));
-
-  // Wait for the task we just posted to the IO thread to complete.
-  after_remove_complete_event.Wait();
-
   // Verify expected states at various parts of the removal.
-  // Note: IO thread is expected to keep pre-Remove() permissions until
-  // the task Remove() posted runs on the IO thread.
   EXPECT_TRUE(io_before_remove);
-  EXPECT_TRUE(io_while_io_task_pending);
-  EXPECT_FALSE(io_after_io_task_completed);
+  EXPECT_FALSE(io_after_remove);
 
   EXPECT_TRUE(ui_before_remove);
-  EXPECT_FALSE(ui_while_io_task_pending);
-  EXPECT_FALSE(ui_after_io_task_completed);
-
-  EXPECT_FALSE(ui_after_remove_complete);
-  EXPECT_FALSE(io_after_remove_complete);
+  EXPECT_FALSE(ui_after_remove);
 }
 
 }  // namespace content
diff --git a/content/browser/devtools/devtools_background_services_context.cc b/content/browser/devtools/devtools_background_services_context.cc
index a3d728b7..d46e3d6 100644
--- a/content/browser/devtools/devtools_background_services_context.cc
+++ b/content/browser/devtools/devtools_background_services_context.cc
@@ -56,6 +56,12 @@
   for (const auto& expiration_time : expiration_times) {
     DCHECK(devtools::proto::BackgroundService_IsValid(expiration_time.first));
     expiration_times_[expiration_time.first] = expiration_time.second;
+
+    auto service =
+        static_cast<devtools::proto::BackgroundService>(expiration_time.first);
+    // If the recording permission for |service| has expired, set it to null.
+    if (IsRecordingExpired(service))
+      expiration_times_[expiration_time.first] = base::Time();
   }
 }
 
@@ -75,8 +81,6 @@
     devtools::proto::BackgroundService service) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  DCHECK(!IsRecording(service));
-
   // TODO(rayankans): Make the time delay finch configurable.
   base::Time expiration_time = base::Time::Now() + base::TimeDelta::FromDays(3);
   expiration_times_[service] = expiration_time;
@@ -92,9 +96,7 @@
     devtools::proto::BackgroundService service) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  DCHECK(IsRecording(service));
   expiration_times_[service] = base::Time();
-
   GetContentClient()->browser()->UpdateDevToolsBackgroundServiceExpiration(
       browser_context_, service, base::Time());
 
@@ -104,7 +106,16 @@
 
 bool DevToolsBackgroundServicesContext::IsRecording(
     devtools::proto::BackgroundService service) {
-  return base::Time::Now() < expiration_times_[service];
+  // Returns whether |service| has been enabled. When the expiration time has
+  // been met it will be lazily updated to be null.
+  return !expiration_times_[service].is_null();
+}
+
+bool DevToolsBackgroundServicesContext::IsRecordingExpired(
+    devtools::proto::BackgroundService service) {
+  // Copy the expiration time to avoid data races.
+  const base::Time expiration_time = expiration_times_[service];
+  return !expiration_time.is_null() && expiration_time < base::Time::Now();
 }
 
 void DevToolsBackgroundServicesContext::GetLoggedBackgroundServiceEvents(
@@ -198,6 +209,17 @@
   if (!IsRecording(service))
     return;
 
+  if (IsRecordingExpired(service)) {
+    // We should stop recording because of the expiration time. We should
+    // also inform the observers that we stopped recording.
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(
+            &DevToolsBackgroundServicesContext::OnRecordingTimeExpired,
+            weak_ptr_factory_.GetWeakPtr(), service));
+    return;
+  }
+
   devtools::proto::BackgroundServiceEvent event;
   event.set_timestamp(
       base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
@@ -228,4 +250,14 @@
     observer.OnEventReceived(event);
 }
 
+void DevToolsBackgroundServicesContext::OnRecordingTimeExpired(
+    devtools::proto::BackgroundService service) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // This could have been stopped by the user in the meanwhile, or we
+  // received duplicate time expiry events.
+  if (IsRecordingExpired(service))
+    StopRecording(service);
+}
+
 }  // namespace content
diff --git a/content/browser/devtools/devtools_background_services_context.h b/content/browser/devtools/devtools_background_services_context.h
index 8a6f36d6..a7d8d07 100644
--- a/content/browser/devtools/devtools_background_services_context.h
+++ b/content/browser/devtools/devtools_background_services_context.h
@@ -100,6 +100,9 @@
   friend class base::RefCountedThreadSafe<DevToolsBackgroundServicesContext>;
   ~DevToolsBackgroundServicesContext();
 
+  // Whether |service| has an expiration time and it was exceeded.
+  bool IsRecordingExpired(devtools::proto::BackgroundService service);
+
   void GetLoggedBackgroundServiceEventsOnIO(
       devtools::proto::BackgroundService service,
       GetLoggedBackgroundServiceEventsCallback callback);
@@ -115,6 +118,8 @@
   void NotifyEventObservers(
       const devtools::proto::BackgroundServiceEvent& event);
 
+  void OnRecordingTimeExpired(devtools::proto::BackgroundService service);
+
   BrowserContext* browser_context_;
   scoped_refptr<ServiceWorkerContextWrapper> service_worker_context_;
 
diff --git a/content/browser/devtools/devtools_background_services_context_unittest.cc b/content/browser/devtools/devtools_background_services_context_unittest.cc
index b160c14..cc072b6 100644
--- a/content/browser/devtools/devtools_background_services_context_unittest.cc
+++ b/content/browser/devtools/devtools_background_services_context_unittest.cc
@@ -343,6 +343,24 @@
 
   SimulateOneWeekPassing();
   EXPECT_FALSE(GetExpirationTime().is_null());
+
+  // Recording should be true, with an expired value.
+  EXPECT_TRUE(IsRecording());
+
+  // Logging should not happen.
+  EXPECT_CALL(*this, OnEventReceived(_)).Times(0);
+  LogTestBackgroundServiceEvent("f1");
+
+  // Observers should be informed that recording stopped.
+  EXPECT_CALL(
+      *this,
+      OnRecordingStateChanged(
+          false, devtools::proto::BackgroundService::TEST_BACKGROUND_SERVICE));
+
+  thread_bundle_.RunUntilIdle();
+
+  // The expiration time entry should be cleared.
+  EXPECT_TRUE(GetExpirationTime().is_null());
   EXPECT_FALSE(IsRecording());
 }
 
diff --git a/content/browser/webrtc/webrtc_internals_browsertest.cc b/content/browser/webrtc/webrtc_internals_browsertest.cc
index 8b324eb..2cd0a8d7 100644
--- a/content/browser/webrtc/webrtc_internals_browsertest.cc
+++ b/content/browser/webrtc/webrtc_internals_browsertest.cc
@@ -155,9 +155,11 @@
   MAYBE_WebRtcInternalsBrowserTest() {}
   ~MAYBE_WebRtcInternalsBrowserTest() override {}
 
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+  }
+
   void SetUpOnMainThread() override {
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        switches::kUseFakeUIForMediaStream);
     ASSERT_TRUE(base::CommandLine::ForCurrentProcess()->HasSwitch(
         switches::kUseFakeDeviceForMediaStream));
   }
diff --git a/content/common/input/sync_compositor_messages.cc b/content/common/input/sync_compositor_messages.cc
index 51de727..68c6ea7 100644
--- a/content/common/input/sync_compositor_messages.cc
+++ b/content/common/input/sync_compositor_messages.cc
@@ -18,9 +18,6 @@
 
 SyncCompositorDemandDrawHwParams::~SyncCompositorDemandDrawHwParams() {}
 
-SyncCompositorSetSharedMemoryParams::SyncCompositorSetSharedMemoryParams()
-    : buffer_size(0u) {}
-
 SyncCompositorDemandDrawSwParams::SyncCompositorDemandDrawSwParams() {}
 
 SyncCompositorDemandDrawSwParams::~SyncCompositorDemandDrawSwParams() {}
diff --git a/content/common/input/sync_compositor_messages.h b/content/common/input/sync_compositor_messages.h
index f9d291fd..e1a3d2e 100644
--- a/content/common/input/sync_compositor_messages.h
+++ b/content/common/input/sync_compositor_messages.h
@@ -7,7 +7,6 @@
 
 #include <stddef.h>
 
-#include "base/memory/shared_memory_handle.h"
 #include "content/common/content_export.h"
 #include "content/common/content_param_traits.h"
 #include "ipc/ipc_message_macros.h"
@@ -33,13 +32,6 @@
   gfx::Transform transform_for_tile_priority;
 };
 
-struct SyncCompositorSetSharedMemoryParams {
-  SyncCompositorSetSharedMemoryParams();
-
-  uint32_t buffer_size;
-  base::SharedMemoryHandle shm_handle;
-};
-
 struct SyncCompositorDemandDrawSwParams {
   SyncCompositorDemandDrawSwParams();
   ~SyncCompositorDemandDrawSwParams();
@@ -82,11 +74,6 @@
   IPC_STRUCT_TRAITS_MEMBER(transform_for_tile_priority)
 IPC_STRUCT_TRAITS_END()
 
-IPC_STRUCT_TRAITS_BEGIN(content::SyncCompositorSetSharedMemoryParams)
-  IPC_STRUCT_TRAITS_MEMBER(buffer_size)
-  IPC_STRUCT_TRAITS_MEMBER(shm_handle)
-IPC_STRUCT_TRAITS_END()
-
 IPC_STRUCT_TRAITS_BEGIN(content::SyncCompositorDemandDrawSwParams)
   IPC_STRUCT_TRAITS_MEMBER(size)
   IPC_STRUCT_TRAITS_MEMBER(clip)
diff --git a/content/common/input/synchronous_compositor.mojom b/content/common/input/synchronous_compositor.mojom
index d6d8564..aa01db13 100644
--- a/content/common/input/synchronous_compositor.mojom
+++ b/content/common/input/synchronous_compositor.mojom
@@ -4,6 +4,7 @@
 
 module content.mojom;
 
+import "mojo/public/mojom/base/shared_memory.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/viz/public/interfaces/compositing/begin_frame_args.mojom";
 import "services/viz/public/interfaces/compositing/compositor_frame.mojom";
@@ -16,9 +17,6 @@
 struct SyncCompositorDemandDrawHwParams;
 
 [Native]
-struct SyncCompositorSetSharedMemoryParams;
-
-[Native]
 struct SyncCompositorDemandDrawSwParams;
 
 [Native]
@@ -49,7 +47,8 @@
   // drawing. This mode just has the renderer send over a single bitmap of the
   // final frame, rather than sending over individual tiles (ie. resources)
   // that are then composited by the browser.
-  [Sync] SetSharedMemory(SyncCompositorSetSharedMemoryParams params) =>
+  [Sync]
+  SetSharedMemory(mojo_base.mojom.WritableSharedMemoryRegion shm_region) =>
       (bool success, SyncCompositorCommonRendererParams result);
 
   // Synchronously does a software based draw.
diff --git a/content/common/navigation_params.cc b/content/common/navigation_params.cc
index 485f143..5e5d9c6 100644
--- a/content/common/navigation_params.cc
+++ b/content/common/navigation_params.cc
@@ -73,10 +73,12 @@
 
 ResourceInterceptPolicy NavigationDownloadPolicy::GetResourceInterceptPolicy()
     const {
-  // Note: Will need to check each NavigationDownloadType case by case if some
-  // correspond to the |kAllowPluginOnly| policy. Currently every single
-  // NavigationDownloadType corresponds to |kAllowNone|, so it's fine to just
-  // check whether |disallowed_types| contains any bit at all.
+  if (disallowed_types.test(
+          static_cast<size_t>(NavigationDownloadType::kSandboxNoGesture)) ||
+      disallowed_types.test(
+          static_cast<size_t>(NavigationDownloadType::kOpenerCrossOrigin))) {
+    return ResourceInterceptPolicy::kAllowPluginOnly;
+  }
   return disallowed_types.any() ? ResourceInterceptPolicy::kAllowNone
                                 : ResourceInterceptPolicy::kAllowAll;
 }
diff --git a/content/public/test/content_browser_test.cc b/content/public/test/content_browser_test.cc
index 0d3f512b..a19fc29 100644
--- a/content/public/test/content_browser_test.cc
+++ b/content/public/test/content_browser_test.cc
@@ -20,6 +20,7 @@
 #include "content/shell/common/shell_switches.h"
 #include "content/shell/renderer/web_test/web_test_content_renderer_client.h"
 #include "content/test/test_content_client.h"
+#include "ui/events/platform/platform_event_source.h"
 
 #if defined(OS_ANDROID)
 #include "content/shell/app/shell_main_delegate.h"
@@ -103,6 +104,8 @@
   ui::InitializeInputMethodForTesting();
 #endif
 
+  ui::PlatformEventSource::SetIgnoreNativePlatformEvents(true);
+
   BrowserTestBase::SetUp();
 }
 
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 8a0a5ca..1f1eeb0 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -375,8 +375,8 @@
   WebAXObject anchor_object, focus_object;
   int anchor_offset, focus_offset;
   ax::mojom::TextAffinity anchor_affinity, focus_affinity;
-  root().Selection(anchor_object, anchor_offset, anchor_affinity, focus_object,
-                   focus_offset, focus_affinity);
+  root().SelectionDeprecated(anchor_object, anchor_offset, anchor_affinity,
+                             focus_object, focus_offset, focus_affinity);
   if (!anchor_object.IsNull() && !focus_object.IsNull() && anchor_offset >= 0 &&
       focus_offset >= 0) {
     int32_t anchor_id = anchor_object.AxID();
@@ -996,9 +996,9 @@
       if (src.IsControl() && !src.IsRichlyEditable()) {
         // Only for simple input controls -- rich editable areas use AXTreeData
         dst->AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart,
-                             src.SelectionStart());
+                             src.SelectionStartDeprecated());
         dst->AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd,
-                             src.SelectionEnd());
+                             src.SelectionEndDeprecated());
       }
     }
 
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 85ac303..b21c58b5 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -704,7 +704,8 @@
           WebPoint(data.target_point.x(), data.target_point.y()));
       break;
     case ax::mojom::Action::kSetSelection:
-      anchor.SetSelection(anchor, data.anchor_offset, focus, data.focus_offset);
+      anchor.SetSelectionDeprecated(anchor, data.anchor_offset, focus,
+                                    data.focus_offset);
       HandleAXEvent(root, ax::mojom::Event::kLayoutComplete);
       break;
     case ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint:
diff --git a/content/renderer/android/synchronous_compositor_proxy.cc b/content/renderer/android/synchronous_compositor_proxy.cc
index 862acfa..4940d75a 100644
--- a/content/renderer/android/synchronous_compositor_proxy.cc
+++ b/content/renderer/android/synchronous_compositor_proxy.cc
@@ -160,12 +160,15 @@
 }
 
 struct SynchronousCompositorProxy::SharedMemoryWithSize {
-  base::SharedMemory shm;
+  base::WritableSharedMemoryMapping shared_memory;
   const size_t buffer_size;
   bool zeroed;
 
-  SharedMemoryWithSize(base::SharedMemoryHandle shm_handle, size_t buffer_size)
-      : shm(shm_handle, false), buffer_size(buffer_size), zeroed(true) {}
+  SharedMemoryWithSize(base::WritableSharedMemoryMapping shm_mapping,
+                       size_t buffer_size)
+      : shared_memory(std::move(shm_mapping)),
+        buffer_size(buffer_size),
+        zeroed(true) {}
 };
 
 void SynchronousCompositorProxy::ZeroSharedMemory() {
@@ -175,7 +178,8 @@
   if (software_draw_shm_->zeroed)
     return;
 
-  memset(software_draw_shm_->shm.memory(), 0, software_draw_shm_->buffer_size);
+  memset(software_draw_shm_->shared_memory.memory(), 0,
+         software_draw_shm_->buffer_size);
   software_draw_shm_->zeroed = true;
 }
 
@@ -219,8 +223,10 @@
   DCHECK_EQ(software_draw_shm_->buffer_size, buffer_size);
 
   SkBitmap bitmap;
-  if (!bitmap.installPixels(info, software_draw_shm_->shm.memory(), stride))
+  if (!bitmap.installPixels(info, software_draw_shm_->shared_memory.memory(),
+                            stride)) {
     return;
+  }
   SkCanvas canvas(bitmap);
   canvas.clipRect(gfx::RectToSkRect(params.clip));
   canvas.concat(params.transform.matrix());
@@ -319,15 +325,15 @@
 }
 
 void SynchronousCompositorProxy::SetSharedMemory(
-    const SyncCompositorSetSharedMemoryParams& params,
+    base::WritableSharedMemoryRegion shm_region,
     SetSharedMemoryCallback callback) {
   bool success = false;
   SyncCompositorCommonRendererParams common_renderer_params;
-  if (base::SharedMemory::IsHandleValid(params.shm_handle)) {
-    software_draw_shm_.reset(
-        new SharedMemoryWithSize(params.shm_handle, params.buffer_size));
-    if (software_draw_shm_->shm.Map(params.buffer_size)) {
-      DCHECK(software_draw_shm_->shm.memory());
+  if (shm_region.IsValid()) {
+    base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
+    if (shm_mapping.IsValid()) {
+      software_draw_shm_ = std::make_unique<SharedMemoryWithSize>(
+          std::move(shm_mapping), shm_mapping.size());
       PopulateCommonParams(&common_renderer_params);
       success = true;
     }
diff --git a/content/renderer/android/synchronous_compositor_proxy.h b/content/renderer/android/synchronous_compositor_proxy.h
index 4eb5123b..21c401f 100644
--- a/content/renderer/android/synchronous_compositor_proxy.h
+++ b/content/renderer/android/synchronous_compositor_proxy.h
@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/writable_shared_memory_region.h"
 #include "base/optional.h"
 #include "components/viz/common/presentation_feedback_map.h"
 #include "content/common/input/synchronous_compositor.mojom.h"
@@ -30,7 +31,6 @@
 struct SyncCompositorCommonRendererParams;
 struct SyncCompositorDemandDrawHwParams;
 struct SyncCompositorDemandDrawSwParams;
-struct SyncCompositorSetSharedMemoryParams;
 
 class SynchronousCompositorProxy : public ui::SynchronousInputHandler,
                                    public SynchronousLayerTreeFrameSinkClient,
@@ -75,7 +75,7 @@
       const SyncCompositorDemandDrawHwParams& draw_params) final;
   void DemandDrawHw(const SyncCompositorDemandDrawHwParams& params,
                     DemandDrawHwCallback callback) final;
-  void SetSharedMemory(const SyncCompositorSetSharedMemoryParams& params,
+  void SetSharedMemory(base::WritableSharedMemoryRegion shm_region,
                        SetSharedMemoryCallback callback) final;
   void DemandDrawSw(const SyncCompositorDemandDrawSwParams& params,
                     DemandDrawSwCallback callback) final;
diff --git a/content/shell/browser/web_test/web_test_background_fetch_delegate.cc b/content/shell/browser/web_test/web_test_background_fetch_delegate.cc
index 0c2ccb2b..8667c860 100644
--- a/content/shell/browser/web_test/web_test_background_fetch_delegate.cc
+++ b/content/shell/browser/web_test/web_test_background_fetch_delegate.cc
@@ -10,7 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/task/post_task.h"
 #include "base/test/scoped_feature_list.h"
-#include "components/download/content/factory/download_service_factory.h"
+#include "components/download/content/factory/download_service_factory_helper.h"
 #include "components/download/public/background_service/clients.h"
 #include "components/download/public/background_service/download_metadata.h"
 #include "components/download/public/background_service/download_params.h"
diff --git a/content/test/data/font_src_local_matching.html b/content/test/data/font_src_local_matching.html
index c4f02b9..0051529 100644
--- a/content/test/data/font_src_local_matching.html
+++ b/content/test/data/font_src_local_matching.html
@@ -112,7 +112,7 @@
   return fontName.replace(/\s+/g, '');
 }
 
-function addTestNodes() {
+async function addTestNodes() {
   var containerDiv = document.createElement("div");
   var fontFaceDeclarations = "";
     for (font_name_and_testchars of getFontsWithTestCharsForOS()) {
@@ -129,6 +129,7 @@
   }
   fontfaces.innerText = fontFaceDeclarations;
   document.body.appendChild(containerDiv);
+  await document.fonts.ready;
   // Force layout so that the DevTools side of the test can start accessing
   // nodes' font information reliably.
   document.body.offsetTop;
diff --git a/fuchsia/engine/browser/message_port_impl.h b/fuchsia/engine/browser/message_port_impl.h
index 8ec47a6..b2f3e90 100644
--- a/fuchsia/engine/browser/message_port_impl.h
+++ b/fuchsia/engine/browser/message_port_impl.h
@@ -6,9 +6,9 @@
 #define FUCHSIA_ENGINE_BROWSER_MESSAGE_PORT_IMPL_H_
 
 #include <lib/fidl/cpp/binding.h>
-#include <deque>
 #include <memory>
 
+#include "base/containers/circular_deque.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "fuchsia/fidl/chromium/web/cpp/fidl.h"
@@ -61,7 +61,7 @@
   // mojo::MessageReceiver implementation.
   bool Accept(mojo::Message* message) override;
 
-  std::deque<chromium::web::WebMessage> message_queue_;
+  base::circular_deque<chromium::web::WebMessage> message_queue_;
   ReceiveMessageCallback pending_client_read_cb_;
   std::unique_ptr<mojo::Connector> connector_;
 
diff --git a/fuchsia/runners/cast/cast_channel_bindings.h b/fuchsia/runners/cast/cast_channel_bindings.h
index a0199f03..c4db948f 100644
--- a/fuchsia/runners/cast/cast_channel_bindings.h
+++ b/fuchsia/runners/cast/cast_channel_bindings.h
@@ -5,10 +5,10 @@
 #ifndef FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
 #define FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
 
-#include <deque>
 #include <string>
 
 #include "base/callback.h"
+#include "base/containers/circular_deque.h"
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
 #include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
@@ -55,7 +55,7 @@
   NamedMessagePortConnector* const connector_;
 
   // A queue of channels waiting to be sent the Cast Channel FIDL service.
-  std::deque<chromium::web::MessagePortPtr> connected_channel_queue_;
+  base::circular_deque<chromium::web::MessagePortPtr> connected_channel_queue_;
 
   // A long-lived port, used to receive new Cast Channel ports when they are
   // opened. Should be automatically  populated by the
diff --git a/gpu/command_buffer/service/external_vk_image_backing.cc b/gpu/command_buffer/service/external_vk_image_backing.cc
index 278ba4ca..8459bed 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/external_vk_image_backing.cc
@@ -69,9 +69,9 @@
 
 bool ExternalVkImageBacking::BeginAccess(
     bool readonly,
-    std::vector<base::ScopedFD>* semaphore_fds) {
-  DCHECK(semaphore_fds);
-  DCHECK(semaphore_fds->empty());
+    std::vector<SemaphoreHandle>* semaphore_handles) {
+  DCHECK(semaphore_handles);
+  DCHECK(semaphore_handles->empty());
   if (is_write_in_progress_) {
     LOG(ERROR) << "Unable to begin read or write access because another write "
                   "access is in progress";
@@ -88,31 +88,31 @@
     ++reads_in_progress_;
     // A semaphore will become unsignaled, when it has been signaled and waited,
     // so it is not safe to reuse it.
-    semaphore_fds->push_back(std::move(write_semaphore_fd_));
+    semaphore_handles->push_back(std::move(write_semaphore_handle_));
   } else {
     is_write_in_progress_ = true;
-    *semaphore_fds = std::move(read_semaphore_fds_);
-    read_semaphore_fds_.clear();
-    if (write_semaphore_fd_.is_valid())
-      semaphore_fds->push_back(std::move(write_semaphore_fd_));
+    *semaphore_handles = std::move(read_semaphore_handles_);
+    read_semaphore_handles_.clear();
+    if (write_semaphore_handle_.is_valid())
+      semaphore_handles->push_back(std::move(write_semaphore_handle_));
   }
   return true;
 }
 
 void ExternalVkImageBacking::EndAccess(bool readonly,
-                                       base::ScopedFD semaphore_fd) {
-  DCHECK(semaphore_fd.is_valid());
+                                       SemaphoreHandle semaphore_handle) {
+  DCHECK(semaphore_handle.is_valid());
 
   if (readonly) {
     DCHECK_GT(reads_in_progress_, 0u);
     --reads_in_progress_;
-    read_semaphore_fds_.push_back(std::move(semaphore_fd));
+    read_semaphore_handles_.push_back(std::move(semaphore_handle));
   } else {
     DCHECK(is_write_in_progress_);
-    DCHECK(!write_semaphore_fd_.is_valid());
-    DCHECK(read_semaphore_fds_.empty());
+    DCHECK(!write_semaphore_handle_.is_valid());
+    DCHECK(read_semaphore_handles_.empty());
     is_write_in_progress_ = false;
-    write_semaphore_fd_ = std::move(semaphore_fd);
+    write_semaphore_handle_ = std::move(semaphore_handle);
   }
 }
 
diff --git a/gpu/command_buffer/service/external_vk_image_backing.h b/gpu/command_buffer/service/external_vk_image_backing.h
index fa65e914..b740065 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.h
+++ b/gpu/command_buffer/service/external_vk_image_backing.h
@@ -8,11 +8,11 @@
 #include <memory>
 #include <vector>
 
-#include "base/files/scoped_file.h"
 #include "components/viz/common/gpu/vulkan_context_provider.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "gpu/command_buffer/service/shared_image_backing.h"
 #include "gpu/command_buffer/service/texture_manager.h"
+#include "gpu/vulkan/semaphore_handle.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
 
 namespace gpu {
@@ -47,13 +47,14 @@
 
   // Notifies the backing that an access will start. Return false if there is
   // currently any other conflict access in progress. Otherwise, returns true
-  // and semaphore fds which will ne be waited on before accessing.
-  bool BeginAccess(bool readonly, std::vector<base::ScopedFD>* semaphore_fds);
+  // and semaphore handles which will be waited on before accessing.
+  bool BeginAccess(bool readonly,
+                   std::vector<SemaphoreHandle>* semaphore_handles);
 
   // Notifies the backing that an access has ended. The representation must
-  // provide a semaphore fd that has been signalled at the end of the write
+  // provide a semaphore handle that has been signaled at the end of the write
   // access.
-  void EndAccess(bool readonly, base::ScopedFD semaphore_fd);
+  void EndAccess(bool readonly, SemaphoreHandle semaphore_handle);
 
   // SharedImageBacking implementation.
   bool IsCleared() const override;
@@ -78,8 +79,8 @@
   SharedContextState* const context_state_;
   VkImage image_;
   VkDeviceMemory memory_;
-  base::ScopedFD write_semaphore_fd_;
-  std::vector<base::ScopedFD> read_semaphore_fds_;
+  SemaphoreHandle write_semaphore_handle_;
+  std::vector<SemaphoreHandle> read_semaphore_handles_;
   size_t memory_size_;
   bool is_cleared_ = false;
   VkFormat vk_format_;
diff --git a/gpu/command_buffer/service/external_vk_image_factory.cc b/gpu/command_buffer/service/external_vk_image_factory.cc
index 927ae1c..751e1ce 100644
--- a/gpu/command_buffer/service/external_vk_image_factory.cc
+++ b/gpu/command_buffer/service/external_vk_image_factory.cc
@@ -154,13 +154,13 @@
   ExternalVkImageBacking* vk_backing =
       static_cast<ExternalVkImageBacking*>(backing.get());
 
-  std::vector<base::ScopedFD> fds;
-  if (!vk_backing->BeginAccess(false /* readonly */, &fds)) {
+  std::vector<SemaphoreHandle> handles;
+  if (!vk_backing->BeginAccess(false /* readonly */, &handles)) {
     LOG(ERROR) << "Failed to request write access of backing.";
     return nullptr;
   }
 
-  DCHECK(fds.empty());
+  DCHECK(handles.empty());
 
   // Create backend render target from the VkImage.
   GrVkAlloc alloc(vk_backing->memory(), 0 /* offset */,
@@ -179,14 +179,15 @@
   surface->writePixels(pixmap, 0, 0);
 
   VkSemaphore semaphore = vk_backing->CreateExternalVkSemaphore();
-  base::ScopedFD fd;
   auto* vk_implementation =
       context_state_->vk_context_provider()->GetVulkanImplementation();
   VkDevice device = context_state_->vk_context_provider()
                         ->GetDeviceQueue()
                         ->GetVulkanDevice();
-  if (!vk_implementation->GetSemaphoreFdKHR(device, semaphore, &fd)) {
-    LOG(ERROR) << "GetSemaphoreFdKHR failed..";
+  SemaphoreHandle semaphore_handle =
+      vk_implementation->GetSemaphoreHandle(device, semaphore);
+  if (!semaphore_handle.is_valid()) {
+    LOG(ERROR) << "GetSemaphoreHandle() failed.";
     vkDestroySemaphore(device, semaphore, nullptr /* pAllocator */);
     return nullptr;
   }
@@ -199,7 +200,7 @@
     vkDestroySemaphore(device, semaphore, nullptr /* pAllocator */);
     return nullptr;
   }
-  vk_backing->EndAccess(false /* readonly */, std::move(fd));
+  vk_backing->EndAccess(false /* readonly */, std::move(semaphore_handle));
   VkQueue queue =
       context_state_->vk_context_provider()->GetDeviceQueue()->GetVulkanQueue();
   // TODO(https://crbug.com/932260): avoid blocking CPU thread.
diff --git a/gpu/command_buffer/service/external_vk_image_gl_representation.cc b/gpu/command_buffer/service/external_vk_image_gl_representation.cc
index 2103d35..addff9d 100644
--- a/gpu/command_buffer/service/external_vk_image_gl_representation.cc
+++ b/gpu/command_buffer/service/external_vk_image_gl_representation.cc
@@ -47,13 +47,13 @@
          mode == GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
   const bool readonly = (mode == GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
 
-  std::vector<base::ScopedFD> fds;
+  std::vector<SemaphoreHandle> handles;
 
-  if (!backing_impl()->BeginAccess(readonly, &fds))
+  if (!backing_impl()->BeginAccess(readonly, &handles))
     return false;
 
-  for (auto& fd : fds) {
-    GLuint gl_semaphore = ImportVkSemaphoreIntoGL(std::move(fd));
+  for (auto& handle : handles) {
+    GLuint gl_semaphore = ImportVkSemaphoreIntoGL(std::move(handle));
     if (gl_semaphore) {
       GLenum src_layout = GL_LAYOUT_COLOR_ATTACHMENT_EXT;
       api()->glWaitSemaphoreEXTFn(gl_semaphore, 0, nullptr, 1,
@@ -91,23 +91,23 @@
     return;
   }
 
-  base::ScopedFD fd;
-  bool result =
-      vk_implementation()->GetSemaphoreFdKHR(vk_device(), semaphore, &fd);
+  SemaphoreHandle semaphore_handle =
+      vk_implementation()->GetSemaphoreHandle(vk_device(), semaphore);
   vkDestroySemaphore(backing_impl()->device(), semaphore, nullptr);
-  if (!result) {
+  if (!semaphore_handle.is_valid()) {
     LOG(FATAL) << "Unable to export VkSemaphore into GL in "
                << "ExternalVkImageGlRepresentation for synchronization with "
                << "Vulkan";
     return;
   }
 
-  base::ScopedFD dup_fd(HANDLE_EINTR(dup(fd.get())));
-  GLuint gl_semaphore = ImportVkSemaphoreIntoGL(std::move(dup_fd));
+  SemaphoreHandle dup_semaphore_handle = semaphore_handle.Duplicate();
+  GLuint gl_semaphore =
+      ImportVkSemaphoreIntoGL(std::move(dup_semaphore_handle));
 
   if (!gl_semaphore) {
-    // TODO(crbug.com/933452): We should be able to handle this failure more
-    // gracefully rather than shutting down the whole process.
+    // TODO(crbug.com/933452): We should be able to semaphore_handle this
+    // failure more gracefully rather than shutting down the whole process.
     LOG(FATAL) << "Unable to export VkSemaphore into GL in "
                << "ExternalVkImageGlRepresentation for synchronization with "
                << "Vulkan";
@@ -118,13 +118,14 @@
   api()->glSignalSemaphoreEXTFn(gl_semaphore, 0, nullptr, 1,
                                 &texture_service_id_, &dst_layout);
   api()->glDeleteSemaphoresEXTFn(1, &gl_semaphore);
-  backing_impl()->EndAccess(readonly, std::move(fd));
+  backing_impl()->EndAccess(readonly, std::move(semaphore_handle));
 }
 
 GLuint ExternalVkImageGlRepresentation::ImportVkSemaphoreIntoGL(
-    base::ScopedFD fd) {
-  if (!fd.is_valid())
+    SemaphoreHandle handle) {
+  if (!handle.is_valid())
     return 0;
+  base::ScopedFD fd = handle.TakeHandle();
   gl::GLApi* api = gl::g_current_gl_context;
   GLuint gl_semaphore;
   api->glGenSemaphoresEXTFn(1, &gl_semaphore);
diff --git a/gpu/command_buffer/service/external_vk_image_gl_representation.h b/gpu/command_buffer/service/external_vk_image_gl_representation.h
index ac2c585..13a5e32 100644
--- a/gpu/command_buffer/service/external_vk_image_gl_representation.h
+++ b/gpu/command_buffer/service/external_vk_image_gl_representation.h
@@ -55,7 +55,7 @@
 
   gl::GLApi* api() { return gl::g_current_gl_context; }
 
-  GLuint ImportVkSemaphoreIntoGL(base::ScopedFD fd);
+  GLuint ImportVkSemaphoreIntoGL(SemaphoreHandle handle);
   void DestroyEndAccessSemaphore();
 
   gles2::Texture* texture_ = nullptr;
diff --git a/gpu/command_buffer/service/external_vk_image_skia_representation.cc b/gpu/command_buffer/service/external_vk_image_skia_representation.cc
index 7c08268..34b802d2 100644
--- a/gpu/command_buffer/service/external_vk_image_skia_representation.cc
+++ b/gpu/command_buffer/service/external_vk_image_skia_representation.cc
@@ -92,14 +92,13 @@
   DestroySemaphores(std::move(begin_access_semaphores_), begin_access_fence_);
   begin_access_semaphores_.clear();
 
-  std::vector<base::ScopedFD> fds;
-  if (!backing_impl()->BeginAccess(readonly, &fds))
+  std::vector<SemaphoreHandle> handles;
+  if (!backing_impl()->BeginAccess(readonly, &handles))
     return nullptr;
 
-  for (auto& fd : fds) {
-    VkSemaphore semaphore = VK_NULL_HANDLE;
-    vk_implementation()->ImportSemaphoreFdKHR(vk_device(), std::move(fd),
-                                              &semaphore);
+  for (auto& handle : handles) {
+    VkSemaphore semaphore = vk_implementation()->ImportSemaphoreHandle(
+        vk_device(), std::move(handle));
     if (semaphore != VK_NULL_HANDLE)
       begin_access_semaphores_.push_back(semaphore);
   }
@@ -146,14 +145,14 @@
     end_access_semaphore_ = VK_NULL_HANDLE;
   }
 
-  base::ScopedFD fd;
+  SemaphoreHandle handle;
   if (end_access_semaphore_ != VK_NULL_HANDLE) {
-    if (!vk_implementation()->GetSemaphoreFdKHR(vk_device(),
-                                                end_access_semaphore_, &fd)) {
-      LOG(FATAL) << "Failed to get fd from a semaphore.";
-    }
+    handle = vk_implementation()->GetSemaphoreHandle(vk_device(),
+                                                     end_access_semaphore_);
+    if (!handle.is_valid())
+      LOG(FATAL) << "Failed to get handle from a semaphore.";
   }
-  backing_impl()->EndAccess(readonly, std::move(fd));
+  backing_impl()->EndAccess(readonly, std::move(handle));
 }
 
 void ExternalVkImageSkiaRepresentation::DestroySemaphores(
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index 072234b..f7d5e06 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -145,10 +145,16 @@
 
   DCHECK(context_->IsCurrent(nullptr));
 
+  bool use_passthrough_cmd_decoder =
+      gpu_preferences.use_passthrough_cmd_decoder &&
+      gles2::PassthroughCommandDecoderSupported();
+  // Virtualized contexts don't work with passthrough command decoder.
+  // See https://crbug.com/914976
+  DCHECK(!use_passthrough_cmd_decoder || !use_virtualized_gl_contexts_);
+
   feature_info_ = std::move(feature_info);
   feature_info_->Initialize(gpu::CONTEXT_TYPE_OPENGLES2,
-                            gpu_preferences.use_passthrough_cmd_decoder &&
-                                gles2::PassthroughCommandDecoderSupported(),
+                            use_passthrough_cmd_decoder,
                             gles2::DisallowedFeatures());
 
   auto* api = gl::g_current_gl_context;
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
index e5272cd..0dbdf0da 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
@@ -158,7 +158,9 @@
   (*surface) = nullptr;
 
   // Export a sync fd from the semaphore.
-  vk_implementation->GetSemaphoreFdKHR(vk_device, vk_semaphore, sync_fd);
+  SemaphoreHandle semaphore_handle =
+      vk_implementation->GetSemaphoreHandle(vk_device, vk_semaphore);
+  *sync_fd = semaphore_handle.TakeHandle();
 
   // TODO(vikassoni): We need to wait for the queue submission to complete
   // before we can destroy the semaphore. This will decrease the performance.
@@ -488,8 +490,11 @@
     for (size_t i = 0; i < sync_fds.size(); ++i) {
       DCHECK(sync_fds[i].is_valid());
 
-      if (!vk_implementation()->ImportSemaphoreFdKHR(
-              vk_device(), std::move(sync_fds[i]), &semaphores[i])) {
+      semaphores[i] = vk_implementation()->ImportSemaphoreHandle(
+          vk_device(),
+          SemaphoreHandle(VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+                          std::move(sync_fds[i])));
+      if (semaphores[i] == VK_NULL_HANDLE) {
         failed_to_insert_semaphores = true;
         break;
       }
@@ -561,10 +566,12 @@
     // We need to wait only if there is a valid fd.
     if (sync_fd.is_valid()) {
       // Import the above sync fd into a semaphore.
-      if (!vk_implementation()->ImportSemaphoreFdKHR(
-              vk_device(), std::move(sync_fd), &semaphore)) {
+      semaphore = vk_implementation()->ImportSemaphoreHandle(
+          vk_device(),
+          SemaphoreHandle(VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+                          std::move(sync_fd)));
+      if (semaphore == VK_NULL_HANDLE)
         return nullptr;
-      }
 
       // Submit wait semaphore to the queue. Note that Skia uses the same queue
       // exposed by vk_queue(), so this will work due to Vulkan queue ordering.
diff --git a/gpu/command_buffer/tests/fuzzer_main.cc b/gpu/command_buffer/tests/fuzzer_main.cc
index 925ba5e..91cd94a 100644
--- a/gpu/command_buffer/tests/fuzzer_main.cc
+++ b/gpu/command_buffer/tests/fuzzer_main.cc
@@ -362,8 +362,20 @@
         config_.workarounds, gpu_feature_info);
     command_buffer_.reset(new CommandBufferDirect());
 
+    if (gpu_preferences_.use_passthrough_cmd_decoder) {
+      // Virtualized contexts don't work with passthrough command decoder.
+      // See https://crbug.com/914976
+      config_.workarounds.use_virtualized_gl_contexts = false;
+    }
+    scoped_refptr<gl::GLContext> shared_context;
+    if (config_.workarounds.use_virtualized_gl_contexts) {
+      shared_context = context_;
+    } else {
+      shared_context = CreateContext();
+    }
+    shared_context->MakeCurrent(surface_.get());
     context_state_ = base::MakeRefCounted<SharedContextState>(
-        share_group_, surface_, context_,
+        share_group_, surface_, std::move(shared_context),
         config_.workarounds.use_virtualized_gl_contexts, base::DoNothing());
     context_state_->InitializeGrContext(config_.workarounds, nullptr);
     context_state_->InitializeGL(gpu_preferences_, feature_info);
@@ -391,6 +403,7 @@
           gfx::ColorSpace::CreateSRGB(), usage);
     }
 
+    context_->MakeCurrent(surface_.get());
 #if defined(GPU_FUZZER_USE_RASTER_DECODER)
     CHECK(feature_info->feature_flags().chromium_raster_transport);
     auto* context = context_state_->context();
@@ -462,7 +475,7 @@
       decoder_.reset();
 
       if (!context_lost)
-        context_state_->MakeCurrent(nullptr);
+        context_lost = !context_state_->MakeCurrent(nullptr);
       shared_image_factory_->DestroyAllSharedImages(!context_lost);
 
       shared_image_factory_.reset();
@@ -533,18 +546,22 @@
     CreateTransferBuffer(kTinyTransferBufferSize, 5);
   }
 
-  void InitContext() {
-#if !defined(GPU_FUZZER_USE_STUB)
-    context_ = new gl::GLContextEGL(share_group_.get());
-    context_->Initialize(surface_.get(), config_.gl_context_attribs);
+  scoped_refptr<gl::GLContext> CreateContext() {
+#if defined(GPU_FUZZER_USE_STUB)
+    auto stub = base::MakeRefCounted<gl::GLContextStub>(share_group_.get());
+    stub->SetGLVersionString(config_.version);
+    stub->SetExtensionsString(config_.extensions.c_str());
+    stub->SetUseStubApi(true);
+    return stub;
 #else
-    scoped_refptr<gl::GLContextStub> context_stub =
-        new gl::GLContextStub(share_group_.get());
-    context_stub->SetGLVersionString(config_.version);
-    context_stub->SetExtensionsString(config_.extensions.c_str());
-    context_stub->SetUseStubApi(true);
-    context_ = context_stub;
+    auto context = base::MakeRefCounted<gl::GLContextEGL>(share_group_.get());
+    context->Initialize(surface_.get(), config_.gl_context_attribs);
+    return context;
 #endif
+  }
+
+  void InitContext() {
+    context_ = CreateContext();
 
 // When not using the passthrough decoder, ANGLE should not be generating
 // errors (the decoder should prevent those from happening). We register a
diff --git a/gpu/ipc/in_process_command_buffer.cc b/gpu/ipc/in_process_command_buffer.cc
index bd26caf..8f25b70 100644
--- a/gpu/ipc/in_process_command_buffer.cc
+++ b/gpu/ipc/in_process_command_buffer.cc
@@ -448,6 +448,11 @@
   use_virtualized_gl_context_ |=
       context_group_->feature_info()->workarounds().use_virtualized_gl_contexts;
 
+  if (context_group_->use_passthrough_cmd_decoder()) {
+    // Virtualized contexts don't work with passthrough command decoder.
+    // See https://crbug.com/914976
+    use_virtualized_gl_context_ = false;
+  }
   // TODO(sunnyps): Should this use ScopedCrashKey instead?
   crash_keys::gpu_gl_context_is_virtual.Set(use_virtualized_gl_context_ ? "1"
                                                                         : "0");
diff --git a/gpu/ipc/service/gpu_channel_manager.cc b/gpu/ipc/service/gpu_channel_manager.cc
index 052f9fa9..3f3d6e4c 100644
--- a/gpu/ipc/service/gpu_channel_manager.cc
+++ b/gpu/ipc/service/gpu_channel_manager.cc
@@ -369,6 +369,9 @@
   scoped_refptr<gl::GLShareGroup> share_group;
   if (use_passthrough_decoder) {
     share_group = new gl::GLShareGroup();
+    // Virtualized contexts don't work with passthrough command decoder.
+    // See https://crbug.com/914976
+    use_virtualized_gl_contexts = false;
   } else {
     share_group = share_group_;
   }
diff --git a/gpu/vulkan/BUILD.gn b/gpu/vulkan/BUILD.gn
index d3751cd..bb7d7e3 100644
--- a/gpu/vulkan/BUILD.gn
+++ b/gpu/vulkan/BUILD.gn
@@ -20,6 +20,8 @@
     output_name = "vulkan_wrapper"
 
     sources = [
+      "semaphore_handle.cc",
+      "semaphore_handle.h",
       "vulkan_command_buffer.cc",
       "vulkan_command_buffer.h",
       "vulkan_command_pool.cc",
@@ -51,11 +53,21 @@
       "//base",
       "//ui/gfx",
     ]
-
+    public_deps = []
     data_deps = []
+
+    if (is_posix) {
+      sources += [
+        "vulkan_posix_util.cc",
+        "vulkan_posix_util.h",
+      ]
+    }
+
     if (is_fuchsia) {
       sources += [ "fuchsia/vulkan_fuchsia_ext.h" ]
 
+      public_deps += [ "//third_party/fuchsia-sdk/sdk:zx" ]
+
       data_deps += [ "//third_party/fuchsia-sdk:vulkan_base" ]
 
       # VulkanInstance enables validation layer in Debug builds and when DCHECKs
diff --git a/gpu/vulkan/android/vulkan_android_unittests.cc b/gpu/vulkan/android/vulkan_android_unittests.cc
index 9f00c8e..2c8ae187 100644
--- a/gpu/vulkan/android/vulkan_android_unittests.cc
+++ b/gpu/vulkan/android/vulkan_android_unittests.cc
@@ -6,7 +6,6 @@
 
 #include "base/android/android_hardware_buffer_compat.h"
 #include "base/android/scoped_hardware_buffer_handle.h"
-#include "base/files/scoped_file.h"
 #include "components/viz/common/gpu/vulkan_in_process_context_provider.h"
 #include "gpu/vulkan/android/vulkan_implementation_android.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
@@ -85,16 +84,15 @@
   EXPECT_TRUE(vk_implementation_->SubmitSignalSemaphore(
       vk_context_provider_->GetDeviceQueue()->GetVulkanQueue(), semaphore1));
 
-  // Export a sync fd from the semaphore.
-  base::ScopedFD sync_fd;
-  EXPECT_TRUE(
-      vk_implementation_->GetSemaphoreFdKHR(vk_device_, semaphore1, &sync_fd));
-  EXPECT_GT(sync_fd.get(), -1);
+  // Export a handle from the semaphore.
+  SemaphoreHandle handle =
+      vk_implementation_->GetSemaphoreHandle(vk_device_, semaphore1);
+  EXPECT_TRUE(handle.is_valid());
 
-  // Import the above sync fd into a new semaphore.
-  VkSemaphore semaphore2;
-  EXPECT_TRUE(vk_implementation_->ImportSemaphoreFdKHR(
-      vk_device_, std::move(sync_fd), &semaphore2));
+  // Import the above semaphore handle into a new semaphore.
+  VkSemaphore semaphore2 =
+      vk_implementation_->ImportSemaphoreHandle(vk_device_, std::move(handle));
+  EXPECT_NE(semaphore2, static_cast<VkSemaphore>(VK_NULL_HANDLE));
 
   // Wait for the device to be idle.
   result = vkDeviceWaitIdle(vk_device_);
diff --git a/gpu/vulkan/android/vulkan_implementation_android.cc b/gpu/vulkan/android/vulkan_implementation_android.cc
index a1e407b..f532ab8 100644
--- a/gpu/vulkan/android/vulkan_implementation_android.cc
+++ b/gpu/vulkan/android/vulkan_implementation_android.cc
@@ -11,6 +11,7 @@
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
 #include "gpu/vulkan/vulkan_instance.h"
+#include "gpu/vulkan/vulkan_posix_util.h"
 #include "gpu/vulkan/vulkan_surface.h"
 #include "ui/gfx/gpu_fence.h"
 
@@ -106,6 +107,21 @@
   return nullptr;
 }
 
+VkSemaphore VulkanImplementationAndroid::ImportSemaphoreHandle(
+    VkDevice vk_device,
+    SemaphoreHandle sync_handle) {
+  return ImportVkSemaphoreHandlePosix(vk_device, std::move(sync_handle));
+}
+
+SemaphoreHandle VulkanImplementationAndroid::GetSemaphoreHandle(
+    VkDevice vk_device,
+    VkSemaphore vk_semaphore) {
+  // VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT specifies a POSIX file
+  // descriptor handle to a Linux Sync File or Android Fence object.
+  return GetVkSemaphoreHandlePosix(
+      vk_device, vk_semaphore, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT);
+}
+
 bool VulkanImplementationAndroid::CreateVkImageAndImportAHB(
     const VkDevice& vk_device,
     const VkPhysicalDevice& vk_physical_device,
diff --git a/gpu/vulkan/android/vulkan_implementation_android.h b/gpu/vulkan/android/vulkan_implementation_android.h
index 8a856209..a84adde 100644
--- a/gpu/vulkan/android/vulkan_implementation_android.h
+++ b/gpu/vulkan/android/vulkan_implementation_android.h
@@ -35,6 +35,10 @@
   std::unique_ptr<gfx::GpuFence> ExportVkFenceToGpuFence(
       VkDevice vk_device,
       VkFence vk_fence) override;
+  VkSemaphore ImportSemaphoreHandle(VkDevice vk_device,
+                                    SemaphoreHandle handle) override;
+  SemaphoreHandle GetSemaphoreHandle(VkDevice vk_device,
+                                     VkSemaphore vk_semaphore) override;
   bool CreateVkImageAndImportAHB(
       const VkDevice& vk_device,
       const VkPhysicalDevice& vk_physical_device,
diff --git a/gpu/vulkan/semaphore_handle.cc b/gpu/vulkan/semaphore_handle.cc
new file mode 100644
index 0000000..00ee966
--- /dev/null
+++ b/gpu/vulkan/semaphore_handle.cc
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gpu/vulkan/semaphore_handle.h"
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+#include "base/posix/eintr_wrapper.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include "base/fuchsia/fuchsia_logging.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace gpu {
+
+SemaphoreHandle::SemaphoreHandle() = default;
+SemaphoreHandle::SemaphoreHandle(VkExternalSemaphoreHandleTypeFlagBits type,
+                                 PlatformHandle handle)
+    : type_(type), handle_(std::move(handle)) {}
+SemaphoreHandle::SemaphoreHandle(SemaphoreHandle&&) = default;
+
+SemaphoreHandle::~SemaphoreHandle() = default;
+
+SemaphoreHandle& SemaphoreHandle::operator=(SemaphoreHandle&&) = default;
+
+SemaphoreHandle SemaphoreHandle::Duplicate() const {
+  if (!is_valid())
+    return SemaphoreHandle();
+
+#if defined(OS_POSIX)
+  return SemaphoreHandle(type_,
+                         base::ScopedFD(HANDLE_EINTR(dup(handle_.get()))));
+#elif defined(OS_WIN)
+  HANDLE handle_dup;
+  if (!::DuplicateHandle(::GetCurrentProcess(), handle_.get(),
+                         ::GetCurrentProcess(), &handle_dup, 0, FALSE,
+                         DUPLICATE_SAME_ACCESS)) {
+    return SemaphoreHandle();
+  }
+  return SemaphoreHandle(type_, base::win::ScopedHandle(handle_dup));
+#elif defined(OS_FUCHSIA)
+  zx::event event_dup;
+  zx_status_t status = handle_.duplicate(ZX_RIGHT_SAME_RIGHTS, &event_dup);
+  if (status != ZX_OK) {
+    ZX_DLOG(ERROR, status) << "zx_handle_duplicate";
+    return SemaphoreHandle();
+  }
+  return SemaphoreHandle(type_, std::move(event_dup));
+#else
+#error Unsupported OS
+#endif
+}
+
+}  // namespace gpu
diff --git a/gpu/vulkan/semaphore_handle.h b/gpu/vulkan/semaphore_handle.h
new file mode 100644
index 0000000..c59ad7a
--- /dev/null
+++ b/gpu/vulkan/semaphore_handle.h
@@ -0,0 +1,71 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GPU_VULKAN_SEMAPHORE_HANDLE_H_
+#define GPU_VULKAN_SEMAPHORE_HANDLE_H_
+
+#include <vulkan/vulkan.h>
+#include <utility>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "gpu/vulkan/vulkan_export.h"
+
+#if defined(OS_POSIX)
+#include "base/files/scoped_file.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include <lib/zx/event.h>
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+namespace gpu {
+
+// Thin wrapper around platform-specific handles for VkSemaphores.
+// Note that handle transference depends on a handle type.
+// SYNC_FD handles that use copy transference, while reference transference is
+// used other handles types.
+class VULKAN_EXPORT SemaphoreHandle {
+ public:
+#if defined(OS_POSIX)
+  using PlatformHandle = base::ScopedFD;
+#elif defined(OS_WIN)
+  using PlatformHandle = base::win::ScopedHandle;
+#elif defined(OS_FUCHSIA)
+  using PlatformHandle = zx::event;
+#endif
+
+  SemaphoreHandle();
+  SemaphoreHandle(VkExternalSemaphoreHandleTypeFlagBits type,
+                  PlatformHandle handle);
+  SemaphoreHandle(SemaphoreHandle&&);
+
+  ~SemaphoreHandle();
+
+  SemaphoreHandle& operator=(SemaphoreHandle&&);
+
+  VkExternalSemaphoreHandleTypeFlagBits vk_handle_type() { return type_; }
+
+  bool is_valid() const { return handle_.is_valid(); }
+
+  // Returns underlying platform-specific handle for the semaphore. is_valid()
+  // becomes false after this function returns.
+  PlatformHandle TakeHandle() { return std::move(handle_); }
+
+  SemaphoreHandle Duplicate() const;
+
+ private:
+  VkExternalSemaphoreHandleTypeFlagBits type_;
+  PlatformHandle handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(SemaphoreHandle);
+};
+
+}  // namespace gpu
+
+#endif  // GPU_VULKAN_SEMAPHORE_HANDLE_H_
\ No newline at end of file
diff --git a/gpu/vulkan/vulkan_function_pointers.cc b/gpu/vulkan/vulkan_function_pointers.cc
index 7954a2d9..8ae9b81 100644
--- a/gpu/vulkan/vulkan_function_pointers.cc
+++ b/gpu/vulkan/vulkan_function_pointers.cc
@@ -279,6 +279,12 @@
   if (!vkGetImageMemoryRequirementsFn)
     return false;
 
+  vkGetImageSubresourceLayoutFn =
+      reinterpret_cast<PFN_vkGetImageSubresourceLayout>(
+          vkGetDeviceProcAddrFn(vk_device, "vkGetImageSubresourceLayout"));
+  if (!vkGetImageSubresourceLayoutFn)
+    return false;
+
   vkResetFencesFn = reinterpret_cast<PFN_vkResetFences>(
       vkGetDeviceProcAddrFn(vk_device, "vkResetFences"));
   if (!vkResetFencesFn)
@@ -328,6 +334,23 @@
 
 #endif
 
+#if defined(OS_FUCHSIA)
+  vkImportSemaphoreZirconHandleFUCHSIAFn =
+      reinterpret_cast<PFN_vkImportSemaphoreZirconHandleFUCHSIA>(
+          vkGetDeviceProcAddrFn(vk_device,
+                                "vkImportSemaphoreZirconHandleFUCHSIA"));
+  if (!vkImportSemaphoreZirconHandleFUCHSIAFn)
+    return false;
+
+  vkGetSemaphoreZirconHandleFUCHSIAFn =
+      reinterpret_cast<PFN_vkGetSemaphoreZirconHandleFUCHSIA>(
+          vkGetDeviceProcAddrFn(vk_device,
+                                "vkGetSemaphoreZirconHandleFUCHSIA"));
+  if (!vkGetSemaphoreZirconHandleFUCHSIAFn)
+    return false;
+
+#endif
+
   // Queue functions
   vkQueueSubmitFn = reinterpret_cast<PFN_vkQueueSubmit>(
       vkGetDeviceProcAddrFn(vk_device, "vkQueueSubmit"));
diff --git a/gpu/vulkan/vulkan_function_pointers.h b/gpu/vulkan/vulkan_function_pointers.h
index f4ca267..1dfe417 100644
--- a/gpu/vulkan/vulkan_function_pointers.h
+++ b/gpu/vulkan/vulkan_function_pointers.h
@@ -13,13 +13,17 @@
 
 #include <vulkan/vulkan.h>
 
+#include "base/native_library.h"
+#include "build/build_config.h"
+#include "gpu/vulkan/vulkan_export.h"
+
 #if defined(OS_ANDROID)
 #include <vulkan/vulkan_android.h>
 #endif
 
-#include "base/native_library.h"
-#include "build/build_config.h"
-#include "gpu/vulkan/vulkan_export.h"
+#if defined(OS_FUCHSIA)
+#include "gpu/vulkan/fuchsia/vulkan_fuchsia_ext.h"
+#endif
 
 namespace gpu {
 
@@ -106,6 +110,7 @@
   PFN_vkGetDeviceQueue vkGetDeviceQueueFn = nullptr;
   PFN_vkGetFenceStatus vkGetFenceStatusFn = nullptr;
   PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirementsFn = nullptr;
+  PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayoutFn = nullptr;
   PFN_vkResetFences vkResetFencesFn = nullptr;
   PFN_vkUpdateDescriptorSets vkUpdateDescriptorSetsFn = nullptr;
   PFN_vkWaitForFences vkWaitForFencesFn = nullptr;
@@ -127,6 +132,13 @@
   PFN_vkGetMemoryFdKHR vkGetMemoryFdKHRFn = nullptr;
 #endif
 
+#if defined(OS_FUCHSIA)
+  PFN_vkImportSemaphoreZirconHandleFUCHSIA
+      vkImportSemaphoreZirconHandleFUCHSIAFn = nullptr;
+  PFN_vkGetSemaphoreZirconHandleFUCHSIA vkGetSemaphoreZirconHandleFUCHSIAFn =
+      nullptr;
+#endif
+
   // Queue functions
   PFN_vkQueueSubmit vkQueueSubmitFn = nullptr;
   PFN_vkQueueWaitIdle vkQueueWaitIdleFn = nullptr;
@@ -236,6 +248,8 @@
 #define vkGetFenceStatus gpu::GetVulkanFunctionPointers()->vkGetFenceStatusFn
 #define vkGetImageMemoryRequirements \
   gpu::GetVulkanFunctionPointers()->vkGetImageMemoryRequirementsFn
+#define vkGetImageSubresourceLayout \
+  gpu::GetVulkanFunctionPointers()->vkGetImageSubresourceLayoutFn
 #define vkResetFences gpu::GetVulkanFunctionPointers()->vkResetFencesFn
 #define vkUpdateDescriptorSets \
   gpu::GetVulkanFunctionPointers()->vkUpdateDescriptorSetsFn
@@ -258,6 +272,13 @@
 #define vkGetMemoryFdKHR gpu::GetVulkanFunctionPointers()->vkGetMemoryFdKHRFn
 #endif
 
+#if defined(OS_FUCHSIA)
+#define vkImportSemaphoreZirconHandleFUCHSIA \
+  gpu::GetVulkanFunctionPointers()->vkImportSemaphoreZirconHandleFUCHSIAFn
+#define vkGetSemaphoreZirconHandleFUCHSIA \
+  gpu::GetVulkanFunctionPointers()->vkGetSemaphoreZirconHandleFUCHSIAFn
+#endif
+
 // Queue functions
 #define vkQueueSubmit gpu::GetVulkanFunctionPointers()->vkQueueSubmitFn
 #define vkQueueWaitIdle gpu::GetVulkanFunctionPointers()->vkQueueWaitIdleFn
diff --git a/gpu/vulkan/vulkan_implementation.cc b/gpu/vulkan/vulkan_implementation.cc
index b40b4e3..d47b8d5 100644
--- a/gpu/vulkan/vulkan_implementation.cc
+++ b/gpu/vulkan/vulkan_implementation.cc
@@ -66,72 +66,4 @@
   return true;
 }
 
-#if defined(OS_LINUX) || defined(OS_ANDROID)
-bool VulkanImplementation::ImportSemaphoreFdKHR(VkDevice vk_device,
-                                                base::ScopedFD sync_fd,
-                                                VkSemaphore* vk_semaphore) {
-  if (!sync_fd.is_valid())
-    return false;
-
-  VkSemaphore semaphore = VK_NULL_HANDLE;
-  VkSemaphoreCreateInfo info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
-  VkResult result = vkCreateSemaphore(vk_device, &info, nullptr, &semaphore);
-  if (result != VK_SUCCESS)
-    return false;
-
-  VkImportSemaphoreFdInfoKHR import = {
-      VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR};
-  import.semaphore = semaphore;
-#if defined(OS_ANDROID)
-  import.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR;
-  // VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT specifies a POSIX file
-  // descriptor handle to a Linux Sync File or Android Fence object.
-  import.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-#else
-  import.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
-#endif
-  import.fd = sync_fd.get();
-
-  result = vkImportSemaphoreFdKHR(vk_device, &import);
-  if (result != VK_SUCCESS) {
-    vkDestroySemaphore(vk_device, semaphore, nullptr);
-    return false;
-  }
-
-  // If import is successful, the VkSemaphore object takes the ownership of fd.
-  ignore_result(sync_fd.release());
-  *vk_semaphore = semaphore;
-  return true;
-}
-
-bool VulkanImplementation::GetSemaphoreFdKHR(VkDevice vk_device,
-                                             VkSemaphore vk_semaphore,
-                                             base::ScopedFD* sync_fd) {
-  // Create VkSemaphoreGetFdInfoKHR structure.
-  VkSemaphoreGetFdInfoKHR info = {VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR};
-  info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
-  info.semaphore = vk_semaphore;
-#if defined(OS_ANDROID)
-  // VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT specifies a POSIX file
-  // descriptor handle to a Linux Sync File or Android Fence object.
-  info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-#else
-  info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
-#endif
-
-  // Create a new sync fd from the semaphore.
-  int fd = -1;
-  VkResult result = vkGetSemaphoreFdKHR(vk_device, &info, &fd);
-  if (result != VK_SUCCESS) {
-    LOG(ERROR) << "vkGetSemaphoreFdKHR failed : " << result;
-    sync_fd->reset(-1);
-    return false;
-  }
-
-  // Transfer the ownership of the fd to the caller.
-  sync_fd->reset(fd);
-  return true;
-}
-#endif  // defined(OS_LINUX) || defined(OS_ANDROID)
-
 }  // namespace gpu
diff --git a/gpu/vulkan/vulkan_implementation.h b/gpu/vulkan/vulkan_implementation.h
index 39992f6..0d72db4 100644
--- a/gpu/vulkan/vulkan_implementation.h
+++ b/gpu/vulkan/vulkan_implementation.h
@@ -12,13 +12,10 @@
 
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "gpu/vulkan/semaphore_handle.h"
 #include "gpu/vulkan/vulkan_export.h"
 #include "ui/gfx/native_widget_types.h"
 
-#if defined(OS_POSIX)
-#include "base/files/scoped_file.h"
-#endif
-
 #if defined(OS_ANDROID)
 #include "base/android/scoped_hardware_buffer_handle.h"
 #include "ui/gfx/geometry/size.h"
@@ -93,21 +90,16 @@
     return SubmitWaitSemaphores(vk_queue, {vk_semaphore}, vk_fence);
   }
 
-#if defined(OS_LINUX) || defined(OS_ANDROID)
-  // Import a VkSemaphore from a POSIX sync file descriptor. Importing a
-  // semaphore payload from a file descriptor transfers ownership of the file
-  // descriptor from the application to the Vulkan implementation. The
-  // application must not perform any operations on the file descriptor after a
-  // successful import.
-  virtual bool ImportSemaphoreFdKHR(VkDevice vk_device,
-                                    base::ScopedFD sync_fd,
-                                    VkSemaphore* vk_semaphore);
+  // Import a VkSemaphore from a platform-specific handle.
+  // Handle types that don't allow permanent import are imported with temporary
+  // permanence (VK_SEMAPHORE_IMPORT_TEMPORARY_BIT).
+  virtual VkSemaphore ImportSemaphoreHandle(VkDevice vk_device,
+                                            SemaphoreHandle handle) = 0;
 
-  // Export a sync fd representing the payload of a semaphore.
-  virtual bool GetSemaphoreFdKHR(VkDevice vk_device,
-                                 VkSemaphore vk_semaphore,
-                                 base::ScopedFD* sync_fd);
-#endif
+  // Export a platform-specific handle for a Vulkan semaphore. Returns a null
+  // handle in case of a failure.
+  virtual SemaphoreHandle GetSemaphoreHandle(VkDevice vk_device,
+                                             VkSemaphore vk_semaphore) = 0;
 
 #if defined(OS_ANDROID)
   // Create a VkImage, import Android AHardwareBuffer object created outside of
diff --git a/gpu/vulkan/vulkan_posix_util.cc b/gpu/vulkan/vulkan_posix_util.cc
new file mode 100644
index 0000000..d55eb51
--- /dev/null
+++ b/gpu/vulkan/vulkan_posix_util.cc
@@ -0,0 +1,65 @@
+// 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 "gpu/vulkan/vulkan_posix_util.h"
+
+#include "gpu/vulkan/vulkan_function_pointers.h"
+
+namespace gpu {
+
+VkSemaphore ImportVkSemaphoreHandlePosix(VkDevice vk_device,
+                                         SemaphoreHandle handle) {
+  auto handle_type = handle.vk_handle_type();
+  if (!handle.is_valid() ||
+      (handle_type != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT &&
+       handle_type != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT)) {
+    return VK_NULL_HANDLE;
+  }
+
+  VkSemaphore semaphore = VK_NULL_HANDLE;
+  VkSemaphoreCreateInfo info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
+  VkResult result = vkCreateSemaphore(vk_device, &info, nullptr, &semaphore);
+  if (result != VK_SUCCESS)
+    return VK_NULL_HANDLE;
+
+  base::ScopedFD fd = handle.TakeHandle();
+  VkImportSemaphoreFdInfoKHR import = {
+      VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR};
+  import.semaphore = semaphore;
+  if (handle_type == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT)
+    import.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR;
+  import.handleType = handle_type;
+  import.fd = fd.get();
+
+  result = vkImportSemaphoreFdKHR(vk_device, &import);
+  if (result != VK_SUCCESS) {
+    vkDestroySemaphore(vk_device, semaphore, nullptr);
+    return VK_NULL_HANDLE;
+  }
+
+  // If import is successful, the VkSemaphore takes the ownership of the fd.
+  ignore_result(fd.release());
+
+  return semaphore;
+}
+
+SemaphoreHandle GetVkSemaphoreHandlePosix(
+    VkDevice vk_device,
+    VkSemaphore vk_semaphore,
+    VkExternalSemaphoreHandleTypeFlagBits handle_type) {
+  VkSemaphoreGetFdInfoKHR info = {VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR};
+  info.semaphore = vk_semaphore;
+  info.handleType = handle_type;
+
+  int fd = -1;
+  VkResult result = vkGetSemaphoreFdKHR(vk_device, &info, &fd);
+  if (result != VK_SUCCESS) {
+    LOG(ERROR) << "vkGetSemaphoreFdKHR failed : " << result;
+    return SemaphoreHandle();
+  }
+
+  return SemaphoreHandle(handle_type, base::ScopedFD(fd));
+}
+
+}  // namespace gpu
diff --git a/gpu/vulkan/vulkan_posix_util.h b/gpu/vulkan/vulkan_posix_util.h
new file mode 100644
index 0000000..3b5351a
--- /dev/null
+++ b/gpu/vulkan/vulkan_posix_util.h
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file contains helpers used by VulkanImplementation's on POSIX platforms
+// to import/export Vulkan objects to POSIX file descriptors. They should not
+// be used directly except in VulkanImplementation children.
+
+#ifndef GPU_VULKAN_VULKAN_POSIX_UTIL_H_
+#define GPU_VULKAN_VULKAN_POSIX_UTIL_H_
+
+#include <vulkan/vulkan.h>
+
+#include "gpu/vulkan/semaphore_handle.h"
+#include "gpu/vulkan/vulkan_export.h"
+
+namespace gpu {
+
+VULKAN_EXPORT VkSemaphore ImportVkSemaphoreHandlePosix(VkDevice vk_device,
+                                                       SemaphoreHandle handle);
+VULKAN_EXPORT SemaphoreHandle
+GetVkSemaphoreHandlePosix(VkDevice vk_device,
+                          VkSemaphore vk_semaphore,
+                          VkExternalSemaphoreHandleTypeFlagBits handle_type);
+
+}  // namespace gpu
+
+#endif  // GPU_VULKAN_VULKAN_POSIX_UTIL_H_
\ No newline at end of file
diff --git a/gpu/vulkan/win32/vulkan_implementation_win32.cc b/gpu/vulkan/win32/vulkan_implementation_win32.cc
index c36d7bc9..85201d6 100644
--- a/gpu/vulkan/win32/vulkan_implementation_win32.cc
+++ b/gpu/vulkan/win32/vulkan_implementation_win32.cc
@@ -105,4 +105,17 @@
   return nullptr;
 }
 
+VkSemaphore VulkanImplementationWin32::ImportSemaphoreHandle(
+    VkDevice vk_device,
+    SemaphoreHandle handle) {
+  NOTIMPLEMENTED();
+  return VK_NULL_HANDLE;
+}
+
+SemaphoreHandle VulkanImplementationWin32::GetSemaphoreHandle(
+    VkDevice vk_device,
+    VkSemaphore vk_semaphore) {
+  return SemaphoreHandle();
+}
+
 }  // namespace gpu
diff --git a/gpu/vulkan/win32/vulkan_implementation_win32.h b/gpu/vulkan/win32/vulkan_implementation_win32.h
index fa2b935..9768fdbe 100644
--- a/gpu/vulkan/win32/vulkan_implementation_win32.h
+++ b/gpu/vulkan/win32/vulkan_implementation_win32.h
@@ -33,6 +33,10 @@
   std::unique_ptr<gfx::GpuFence> ExportVkFenceToGpuFence(
       VkDevice vk_device,
       VkFence vk_fence) override;
+  VkSemaphore ImportSemaphoreHandle(VkDevice vk_device,
+                                    SemaphoreHandle handle) override;
+  SemaphoreHandle GetSemaphoreHandle(VkDevice vk_device,
+                                     VkSemaphore vk_semaphore) override;
 
  private:
   VulkanInstance vulkan_instance_;
diff --git a/gpu/vulkan/x/vulkan_implementation_x11.cc b/gpu/vulkan/x/vulkan_implementation_x11.cc
index f7e7ff8..be5a4523 100644
--- a/gpu/vulkan/x/vulkan_implementation_x11.cc
+++ b/gpu/vulkan/x/vulkan_implementation_x11.cc
@@ -10,6 +10,7 @@
 #include "base/optional.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
 #include "gpu/vulkan/vulkan_instance.h"
+#include "gpu/vulkan/vulkan_posix_util.h"
 #include "gpu/vulkan/vulkan_surface.h"
 #include "ui/gfx/gpu_fence.h"
 #include "ui/gfx/x/x11_types.h"
@@ -146,4 +147,17 @@
   return nullptr;
 }
 
+VkSemaphore VulkanImplementationX11::ImportSemaphoreHandle(
+    VkDevice vk_device,
+    SemaphoreHandle sync_handle) {
+  return ImportVkSemaphoreHandlePosix(vk_device, std::move(sync_handle));
+}
+
+SemaphoreHandle VulkanImplementationX11::GetSemaphoreHandle(
+    VkDevice vk_device,
+    VkSemaphore vk_semaphore) {
+  return GetVkSemaphoreHandlePosix(
+      vk_device, vk_semaphore, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT);
+}
+
 }  // namespace gpu
diff --git a/gpu/vulkan/x/vulkan_implementation_x11.h b/gpu/vulkan/x/vulkan_implementation_x11.h
index 869f63a..a252497 100644
--- a/gpu/vulkan/x/vulkan_implementation_x11.h
+++ b/gpu/vulkan/x/vulkan_implementation_x11.h
@@ -35,6 +35,10 @@
   std::unique_ptr<gfx::GpuFence> ExportVkFenceToGpuFence(
       VkDevice vk_device,
       VkFence vk_fence) override;
+  VkSemaphore ImportSemaphoreHandle(VkDevice vk_device,
+                                    SemaphoreHandle handle) override;
+  SemaphoreHandle GetSemaphoreHandle(VkDevice vk_device,
+                                     VkSemaphore vk_semaphore) override;
 
  private:
   XDisplay* const x_display_;
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index d7db0651..aaeee5c 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -3130,7 +3130,7 @@
       # Max. pending time for builds. CQ considers builds pending >2h as timed
       # out: http://shortn/_8PaHsdYmlq. Keep this in sync.
       expiration_secs: 7200 # 2h
-      execution_timeout_secs: 10800  # 3h
+      execution_timeout_secs: 14400  # 4h
       swarming_tags: "vpython:native-python-wrapper"
       build_numbers: YES
       # Adds dimension: "builder:<builder name>" to ensure builder affinity.
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index fd88dca..6d507cec 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -1,3 +1,6 @@
+# See http://luci-config.appspot.com/schemas/projects:luci-milo.cfg for schema
+# of this file and documentation.
+
 logo_url: "https://storage.googleapis.com/chrome-infra-public/logo/chromium.svg"
 
 headers {
@@ -185,6 +188,11 @@
       alt: "Blink"
     }
     links {
+      text: "chrome"
+      url: "/p/chrome/g/tryserver.chromium.chrome/builders"
+      alt: "Chrome"
+    }
+    links {
       text: "chromiumos"
       url: "/p/chromium/g/tryserver.chromium.chromiumos/builders"
       alt: "ChromiumOS"
diff --git a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h
index 12fee1f..3c1cf0d 100644
--- a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h
+++ b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h
@@ -11,6 +11,15 @@
 
 @class ChromeIdentity;
 
+typedef NS_ENUM(NSInteger, TableViewAccountMode) {
+  // The cell can be tappable, and the colors are not dimmed.
+  TableViewAccountModeEnabled,
+  // The cell is not tappable, and the colors are not dimmed.
+  TableViewAccountModeNonTappable,
+  // The cell is not tappable, and the colors are dimmed.
+  TableViewAccountModeDisabled,
+};
+
 // Item for account avatar, used everywhere an account cell is shown.
 @interface TableViewAccountItem : TableViewItem
 
@@ -19,7 +28,8 @@
 @property(nonatomic, copy) NSString* detailText;
 @property(nonatomic, assign) BOOL shouldDisplayError;
 @property(nonatomic, strong) ChromeIdentity* chromeIdentity;
-@property(nonatomic, assign, getter=isEnabled) BOOL enabled;
+// The default value is TableViewAccountModeEnabled.
+@property(nonatomic, assign) TableViewAccountMode mode;
 
 @end
 
diff --git a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
index 66c763d..8009dbb 100644
--- a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
+++ b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
@@ -34,7 +34,7 @@
   if (self) {
     self.cellClass = [TableViewAccountCell class];
     self.accessibilityTraits |= UIAccessibilityTraitButton;
-    _enabled = YES;
+    _mode = TableViewAccountModeEnabled;
   }
   return self;
 }
@@ -57,8 +57,8 @@
         UIColorFromRGB(kTableViewSecondaryLabelLightGrayTextColor);
   }
 
-  if (self.isEnabled) {
-    cell.userInteractionEnabled = YES;
+  cell.userInteractionEnabled = self.mode == TableViewAccountModeEnabled;
+  if (self.mode != TableViewAccountModeDisabled) {
     cell.contentView.alpha = 1;
     UIImageView* accessoryImage =
         base::mac::ObjCCastStrict<UIImageView>(cell.accessoryView);
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
index 8bd70eb..0baa384 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
@@ -84,7 +84,13 @@
 
 // Verifies that the content offset of the web view is set up at the correct
 // initial value when initially displaying a PDF.
-- (void)testLongPDFInitialState {
+// TODO(crbug.com/947536): Fails on iOS 12 devices.
+#if !TARGET_IPHONE_SIMULATOR
+#define MAYBE_testLongPDFInitialState DISABLED_testLongPDFInitialState
+#else
+#define MAYBE_testLongPDFInitialState testLongPDFInitialState
+#endif
+- (void)MAYBE_testLongPDFInitialState {
   web::test::SetUpFileBasedHttpServer();
   GURL URL = web::test::HttpServer::MakeUrl(
       "http://ios/testing/data/http_server_files/two_pages.pdf");
diff --git a/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm
index da1e4d9..83b8aa4 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_settings_table_view_controller.mm
@@ -374,7 +374,9 @@
       [[TableViewAccountItem alloc] initWithType:ItemTypeAccount];
   [self updateAccountItem:identityAccountItem withIdentity:identity];
 
-  identityAccountItem.enabled = _syncSetupService->IsSyncEnabled();
+  identityAccountItem.mode = _syncSetupService->IsSyncEnabled()
+                                 ? TableViewAccountModeEnabled
+                                 : TableViewAccountModeDisabled;
   ChromeIdentity* authenticatedIdentity =
       AuthenticationServiceFactory::GetForBrowserState(_browserState)
           ->GetAuthenticatedIdentity();
@@ -782,7 +784,9 @@
       TableViewAccountItem* accountItem =
           base::mac::ObjCCastStrict<TableViewAccountItem>(
               [self.tableViewModel itemAtIndexPath:indexPath]);
-      accountItem.enabled = _syncSetupService->IsSyncEnabled();
+      accountItem.mode = _syncSetupService->IsSyncEnabled()
+                             ? TableViewAccountModeEnabled
+                             : TableViewAccountModeDisabled;
       [accountsToReconfigure addObject:accountItem];
     }
     [self reconfigureCellsForItems:accountsToReconfigure];
diff --git a/ios/web/shell/test/BUILD.gn b/ios/web/shell/test/BUILD.gn
index c9664fac..eb07f38 100644
--- a/ios/web/shell/test/BUILD.gn
+++ b/ios/web/shell/test/BUILD.gn
@@ -155,4 +155,6 @@
     # Test support libraries.
     ":eg_tests+eg2",
   ]
+
+  bundle_deps = [ "//ios/testing:http_server_bundle_data" ]
 }
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index 1fa296a3..527cfaf 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -324,6 +324,8 @@
   }
   ~VideoImageGenerator() override = default;
 
+  bool IsEligibleForAcceleratedDecoding() const override { return false; }
+
   sk_sp<SkData> GetEncodedData() const override { return nullptr; }
 
   bool GetPixels(const SkImageInfo& info,
diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc
index a3112de..f021464 100644
--- a/net/base/network_change_notifier.cc
+++ b/net/base/network_change_notifier.cc
@@ -455,13 +455,11 @@
       continue;
 #endif
 #if defined(OS_MACOSX)
-    // Ignore tunnel and airdrop interfaces.
-    if (base::StartsWith(interfaces[i].friendly_name, "utun",
-                         base::CompareCase::SENSITIVE) ||
-        base::StartsWith(interfaces[i].friendly_name, "awdl",
-                         base::CompareCase::SENSITIVE)) {
+    // Ignore link-local addresses as they aren't globally routable.
+    // Mac assigns these to disconnected interfaces like tunnel interfaces
+    // ("utun"), airdrop interfaces ("awdl"), and ethernet ports ("en").
+    if (interfaces[i].address.IsLinkLocal())
       continue;
-    }
 #endif
 
     // Remove VMware network interfaces as they're internal and should not be
diff --git a/net/base/network_change_notifier_unittest.cc b/net/base/network_change_notifier_unittest.cc
index ef15630..d0bd5bff 100644
--- a/net/base/network_change_notifier_unittest.cc
+++ b/net/base/network_change_notifier_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "net/base/network_change_notifier.h"
 
+#include "build/build_config.h"
 #include "net/base/network_interfaces.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -111,6 +112,9 @@
   interface_airdrop.type = NetworkChangeNotifier::CONNECTION_ETHERNET;
   interface_airdrop.name = "awdl0";
   interface_airdrop.friendly_name = "awdl0";
+  interface_airdrop.address =
+      // Link-local IPv6 address
+      IPAddress({0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4});
   list.push_back(interface_airdrop);
 
 #if defined(OS_MACOSX)
@@ -128,6 +132,9 @@
   interface_tunnel.type = NetworkChangeNotifier::CONNECTION_ETHERNET;
   interface_tunnel.name = "utun0";
   interface_tunnel.friendly_name = "utun0";
+  interface_tunnel.address =
+      // Link-local IPv6 address
+      IPAddress({0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 3, 2, 1});
   list.push_back(interface_tunnel);
 
 #if defined(OS_MACOSX)
@@ -139,6 +146,26 @@
 #endif
 }
 
+TEST(NetworkChangeNotifierTest, IgnoreDisconnectedEthernetOnMac) {
+  NetworkInterfaceList list;
+  NetworkInterface interface_ethernet;
+  interface_ethernet.type = NetworkChangeNotifier::CONNECTION_ETHERNET;
+  interface_ethernet.name = "en5";
+  interface_ethernet.friendly_name = "en5";
+  interface_ethernet.address =
+      // Link-local IPv6 address
+      IPAddress({0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 2, 3});
+  list.push_back(interface_ethernet);
+
+#if defined(OS_MACOSX)
+  EXPECT_EQ(NetworkChangeNotifier::CONNECTION_NONE,
+            NetworkChangeNotifier::ConnectionTypeFromInterfaceList(list));
+#else
+  EXPECT_EQ(NetworkChangeNotifier::CONNECTION_ETHERNET,
+            NetworkChangeNotifier::ConnectionTypeFromInterfaceList(list));
+#endif
+}
+
 TEST(NetworkChangeNotifierTest, IgnoreVMInterfaces) {
   NetworkInterfaceList list;
   NetworkInterface interface_vmnet_linux;
diff --git a/services/device/generic_sensor/platform_sensor_and_provider_unittest_linux.cc b/services/device/generic_sensor/platform_sensor_and_provider_unittest_linux.cc
index 395843d..158d782 100644
--- a/services/device/generic_sensor/platform_sensor_and_provider_unittest_linux.cc
+++ b/services/device/generic_sensor/platform_sensor_and_provider_unittest_linux.cc
@@ -149,21 +149,17 @@
     manager_ = manager.get();
     provider_->SetSensorDeviceManagerForTesting(std::move(manager));
 
-    ASSERT_TRUE(sensors_dir_.CreateUniqueTempDir());
-
-    disallow_blocking_.reset(new base::ScopedDisallowBlocking);
+    {
+      base::ScopedAllowBlockingForTesting allow_blocking;
+      ASSERT_TRUE(sensors_dir_.CreateUniqueTempDir());
+    }
   }
 
   void TearDown() override {
-    // TODO(rakuco): It should be possible to make |disallow_blocking_| a
-    // regular, non-std::unique_ptr member once we port
-    // PlatformSensorProviderLinux and accompanying APIs to base::PostTask().
-    // At the moment we need to turn |disallow_blocking_| off here because
-    // stopping PlatformSensorProviderLinux's polling thread is a blocking
-    // operation.
-    disallow_blocking_.reset(nullptr);
-
-    ASSERT_TRUE(sensors_dir_.Delete());
+    {
+      base::ScopedAllowBlockingForTesting allow_blocking;
+      ASSERT_TRUE(sensors_dir_.Delete());
+    }
     base::RunLoop().RunUntilIdle();
   }
 
@@ -322,7 +318,7 @@
 
   // Used to simulate the non-test scenario where we're running in an IO thread
   // that forbids blocking operations.
-  std::unique_ptr<base::ScopedDisallowBlocking> disallow_blocking_;
+  base::ScopedDisallowBlocking disallow_blocking_;
 };
 
 // Tests sensor is not returned if not implemented.
diff --git a/services/network/proxy_config_service_mojo.cc b/services/network/proxy_config_service_mojo.cc
index 00c7dcf..11191b80 100644
--- a/services/network/proxy_config_service_mojo.cc
+++ b/services/network/proxy_config_service_mojo.cc
@@ -4,6 +4,8 @@
 
 #include "services/network/proxy_config_service_mojo.h"
 
+#include <utility>
+
 namespace network {
 
 ProxyConfigServiceMojo::ProxyConfigServiceMojo(
@@ -39,6 +41,11 @@
     observer.OnProxyConfigChanged(config_, CONFIG_VALID);
 }
 
+void ProxyConfigServiceMojo::FlushProxyConfig(
+    FlushProxyConfigCallback callback) {
+  std::move(callback).Run();
+}
+
 void ProxyConfigServiceMojo::AddObserver(Observer* observer) {
   observers_.AddObserver(observer);
 }
diff --git a/services/network/proxy_config_service_mojo.h b/services/network/proxy_config_service_mojo.h
index fc90f42e..0a63cc88 100644
--- a/services/network/proxy_config_service_mojo.h
+++ b/services/network/proxy_config_service_mojo.h
@@ -51,6 +51,7 @@
   // mojom::ProxyConfigClient implementation:
   void OnProxyConfigUpdated(
       const net::ProxyConfigWithAnnotation& proxy_config) override;
+  void FlushProxyConfig(FlushProxyConfigCallback callback) override;
 
   mojom::ProxyConfigPollerClientPtr proxy_poller_client_;
 
diff --git a/services/network/public/cpp/network_param.typemap b/services/network/public/cpp/network_param.typemap
index e8a71b2..69e0c62 100644
--- a/services/network/public/cpp/network_param.typemap
+++ b/services/network/public/cpp/network_param.typemap
@@ -26,11 +26,11 @@
 
 deps = [
   "//ipc",
-  "//services/network/public/cpp:cpp_base",
 ]
 
 public_deps = [
   "//net",
+  "//services/network/public/cpp:cpp_base",
 ]
 type_mappings = [
   "network.mojom.AuthChallengeInfo=scoped_refptr<net::AuthChallengeInfo>[nullable_is_same_type]",
diff --git a/services/network/public/mojom/proxy_config_with_annotation.mojom b/services/network/public/mojom/proxy_config_with_annotation.mojom
index 9528833..0b1054b8 100644
--- a/services/network/public/mojom/proxy_config_with_annotation.mojom
+++ b/services/network/public/mojom/proxy_config_with_annotation.mojom
@@ -16,6 +16,9 @@
 // Interface for pushing proxy configuration updates to a NetworkContext.
 interface ProxyConfigClient {
   OnProxyConfigUpdated(ProxyConfigWithAnnotation proxy_config);
+
+  // Flush the ProxyConfig
+  FlushProxyConfig() => ();
 };
 
 // Called periodically when the current ProxyConfig is in use, as a hint that
diff --git a/services/service_manager/sandbox/mac/gpu_v2.sb b/services/service_manager/sandbox/mac/gpu_v2.sb
index e0aa0a8..40c11e4 100644
--- a/services/service_manager/sandbox/mac/gpu_v2.sb
+++ b/services/service_manager/sandbox/mac/gpu_v2.sb
@@ -74,4 +74,6 @@
 
 (allow file-read*
   (subpath "/Library/GPUBundles")
+  (subpath "/Library/Video/Plug-Ins")
+  (subpath "/System/Library/Video/Plug-Ins")
 )
diff --git a/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.cc b/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.cc
index f058e6b..8175112 100644
--- a/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.cc
+++ b/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.cc
@@ -18,8 +18,6 @@
                   viz::CompositorFrameMetadata>::
     Read(viz::mojom::CompositorFrameMetadataDataView data,
          viz::CompositorFrameMetadata* out) {
-  if (data.device_scale_factor() <= 0)
-    return false;
   out->device_scale_factor = data.device_scale_factor();
   if (!data.ReadRootScrollOffset(&out->root_scroll_offset))
     return false;
diff --git a/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.h b/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.h
index b627c6f..4c1f33e 100644
--- a/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.h
+++ b/services/viz/public/cpp/compositing/compositor_frame_metadata_struct_traits.h
@@ -21,7 +21,6 @@
                     viz::CompositorFrameMetadata> {
   static float device_scale_factor(
       const viz::CompositorFrameMetadata& metadata) {
-    DCHECK_GT(metadata.device_scale_factor, 0);
     return metadata.device_scale_factor;
   }
 
diff --git a/storage/browser/quota/client_usage_tracker.cc b/storage/browser/quota/client_usage_tracker.cc
index b26d5e4d..a682cf7 100644
--- a/storage/browser/quota/client_usage_tracker.cc
+++ b/storage/browser/quota/client_usage_tracker.cc
@@ -257,6 +257,7 @@
 void ClientUsageTracker::AccumulateLimitedOriginUsage(AccumulateInfo* info,
                                                       UsageCallback callback,
                                                       int64_t usage) {
+  DCHECK_GT(info->pending_jobs, 0U);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   info->limited_usage += usage;
   if (--info->pending_jobs)
@@ -301,6 +302,7 @@
                                              GlobalUsageCallback callback,
                                              int64_t limited_usage,
                                              int64_t unlimited_usage) {
+  DCHECK_GT(info->pending_jobs, 0U);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   info->limited_usage += limited_usage;
   info->unlimited_usage += unlimited_usage;
@@ -358,6 +360,7 @@
     const std::string& host,
     const base::Optional<url::Origin>& origin,
     int64_t usage) {
+  DCHECK_GT(info->pending_jobs, 0U);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (origin.has_value()) {
     DCHECK(!origin->GetURL().is_empty());
diff --git a/storage/browser/quota/client_usage_tracker.h b/storage/browser/quota/client_usage_tracker.h
index c3f31fbd..3b4877d 100644
--- a/storage/browser/quota/client_usage_tracker.h
+++ b/storage/browser/quota/client_usage_tracker.h
@@ -59,7 +59,7 @@
   using UsageMap = std::map<url::Origin, int64_t>;
 
   struct AccumulateInfo {
-    int pending_jobs = 0;
+    size_t pending_jobs = 0;
     int64_t limited_usage = 0;
     int64_t unlimited_usage = 0;
   };
diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc
index 168168ae..cb34f7c 100644
--- a/storage/browser/quota/quota_manager.cc
+++ b/storage/browser/quota/quota_manager.cc
@@ -544,7 +544,7 @@
         type_(type),
         quota_client_mask_(quota_client_mask),
         error_count_(0),
-        remaining_clients_(-1),
+        remaining_clients_(0),
         skipped_clients_(0),
         is_eviction_(is_eviction),
         callback_(std::move(callback)),
@@ -599,7 +599,7 @@
  private:
   void DidDeleteOriginData(int tracing_id,
                            blink::mojom::QuotaStatusCode status) {
-    DCHECK_GT(remaining_clients_, 0);
+    DCHECK_GT(remaining_clients_, 0U);
     TRACE_EVENT_ASYNC_END0("browsing_data", "QuotaManager::OriginDataDeleter",
                            tracing_id);
 
@@ -618,7 +618,7 @@
   StorageType type_;
   int quota_client_mask_;
   int error_count_;
-  int remaining_clients_;
+  size_t remaining_clients_;
   int skipped_clients_;
   bool is_eviction_;
   StatusCallback callback_;
@@ -639,8 +639,8 @@
         type_(type),
         quota_client_mask_(quota_client_mask),
         error_count_(0),
-        remaining_clients_(-1),
-        remaining_deleters_(-1),
+        remaining_clients_(0),
+        remaining_deleters_(0),
         callback_(std::move(callback)),
         weak_factory_(this) {}
 
@@ -679,7 +679,7 @@
 
  private:
   void DidGetOriginsForHost(const std::set<url::Origin>& origins) {
-    DCHECK_GT(remaining_clients_, 0);
+    DCHECK_GT(remaining_clients_, 0U);
 
     for (const auto& origin : origins)
       origins_.insert(origin);
@@ -704,7 +704,7 @@
   }
 
   void DidDeleteOriginData(blink::mojom::QuotaStatusCode status) {
-    DCHECK_GT(remaining_deleters_, 0);
+    DCHECK_GT(remaining_deleters_, 0U);
 
     if (status != blink::mojom::QuotaStatusCode::kOk)
       ++error_count_;
@@ -722,8 +722,8 @@
   int quota_client_mask_;
   std::set<url::Origin> origins_;
   int error_count_;
-  int remaining_clients_;
-  int remaining_deleters_;
+  size_t remaining_clients_;
+  size_t remaining_deleters_;
   StatusCallback callback_;
 
   base::WeakPtrFactory<HostDataDeleter> weak_factory_;
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc
index bf20f7e..f97d7fb 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor.cc
@@ -141,7 +141,8 @@
       this, &QuotaTemporaryStorageEvictor::ReportPerHourHistogram);
 }
 
-void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(int delay_ms) {
+void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(
+    int64_t delay_ms) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (eviction_timer_.IsRunning() || timer_disabled_for_testing_)
     return;
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h
index 954c580cd..eb987dd1 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.h
+++ b/storage/browser/quota/quota_temporary_storage_evictor.h
@@ -80,7 +80,7 @@
  private:
   friend class content::QuotaTemporaryStorageEvictorTest;
 
-  void StartEvictionTimerWithDelay(int delay_ms);
+  void StartEvictionTimerWithDelay(int64_t delay_ms);
   void ConsiderEviction();
   void OnGotEvictionRoundInfo(blink::mojom::QuotaStatusCode status,
                               const QuotaSettings& settings,
diff --git a/storage/browser/quota/storage_monitor.cc b/storage/browser/quota/storage_monitor.cc
index 5aaaa61..406845f 100644
--- a/storage/browser/quota/storage_monitor.cc
+++ b/storage/browser/quota/storage_monitor.cc
@@ -37,7 +37,7 @@
   observer_state_map_.erase(observer);
 }
 
-int StorageObserverList::ObserverCount() const {
+size_t StorageObserverList::ObserverCount() const {
   return observer_state_map_.size();
 }
 
diff --git a/storage/browser/quota/storage_monitor.h b/storage/browser/quota/storage_monitor.h
index 6797d3a..17ed854 100644
--- a/storage/browser/quota/storage_monitor.h
+++ b/storage/browser/quota/storage_monitor.h
@@ -40,7 +40,7 @@
   void RemoveObserver(StorageObserver* observer);
 
   // Returns the number of observers.
-  int ObserverCount() const;
+  size_t ObserverCount() const;
 
   // Forwards a storage change to observers. The event may be dispatched
   // immediately to an observer or after a delay, depending on the desired event
diff --git a/storage/browser/quota/storage_monitor_unittest.cc b/storage/browser/quota/storage_monitor_unittest.cc
index ce306e5..92c821f 100644
--- a/storage/browser/quota/storage_monitor_unittest.cc
+++ b/storage/browser/quota/storage_monitor_unittest.cc
@@ -50,9 +50,7 @@
     return events_.back();
   }
 
-  int EventCount() const {
-    return events_.size();
-  }
+  size_t EventCount() const { return events_.size(); }
 
   // StorageObserver implementation:
   void OnStorageEvent(const StorageObserver::Event& event) override {
@@ -157,7 +155,7 @@
     SetLastNotificationTime(host_observers.observers_, observer);
   }
 
-  int GetObserverCount(const HostStorageObservers& host_observers) {
+  size_t GetObserverCount(const HostStorageObservers& host_observers) {
     return host_observers.observers_.ObserverCount();
   }
 
@@ -202,7 +200,7 @@
   event.quota = 1;
   event.usage = 1;
   observer_list.OnStorageChange(event);
-  EXPECT_EQ(1, mock_observer.EventCount());
+  EXPECT_EQ(1u, mock_observer.EventCount());
   EXPECT_EQ(event, mock_observer.LastEvent());
   EXPECT_EQ(nullptr, GetPendingEvent(observer_list));
   EXPECT_EQ(0, GetRequiredUpdatesCount(observer_list));
@@ -211,7 +209,7 @@
   event.quota = 2;
   event.usage = 2;
   observer_list.OnStorageChange(event);
-  EXPECT_EQ(1, mock_observer.EventCount());
+  EXPECT_EQ(1u, mock_observer.EventCount());
   ASSERT_TRUE(GetPendingEvent(observer_list));
   EXPECT_EQ(event, *GetPendingEvent(observer_list));
   EXPECT_EQ(1, GetRequiredUpdatesCount(observer_list));
@@ -221,7 +219,7 @@
   event.quota = 3;
   event.usage = 3;
   observer_list.OnStorageChange(event);
-  EXPECT_EQ(2, mock_observer.EventCount());
+  EXPECT_EQ(2u, mock_observer.EventCount());
   EXPECT_EQ(event, mock_observer.LastEvent());
   EXPECT_EQ(nullptr, GetPendingEvent(observer_list));
   EXPECT_EQ(0, GetRequiredUpdatesCount(observer_list));
@@ -231,7 +229,7 @@
   event.usage = 4;
   observer_list.RemoveObserver(&mock_observer);
   observer_list.OnStorageChange(event);
-  EXPECT_EQ(2, mock_observer.EventCount());
+  EXPECT_EQ(2u, mock_observer.EventCount());
   EXPECT_EQ(nullptr, GetPendingEvent(observer_list));
 }
 
@@ -257,8 +255,8 @@
   event.quota = 1;
   event.usage = 1;
   observer_list.OnStorageChange(event);
-  EXPECT_EQ(1, mock_observer1.EventCount());
-  EXPECT_EQ(1, mock_observer2.EventCount());
+  EXPECT_EQ(1u, mock_observer1.EventCount());
+  EXPECT_EQ(1u, mock_observer2.EventCount());
   EXPECT_EQ(event, mock_observer1.LastEvent());
   EXPECT_EQ(event, mock_observer2.LastEvent());
   EXPECT_EQ(nullptr, GetPendingEvent(observer_list));
@@ -270,8 +268,8 @@
   event.quota = 2;
   event.usage = 2;
   observer_list.OnStorageChange(event);
-  EXPECT_EQ(2, mock_observer1.EventCount());
-  EXPECT_EQ(1, mock_observer2.EventCount());
+  EXPECT_EQ(2u, mock_observer1.EventCount());
+  EXPECT_EQ(1u, mock_observer2.EventCount());
   EXPECT_EQ(event, mock_observer1.LastEvent());
   ASSERT_TRUE(GetPendingEvent(observer_list));
   EXPECT_EQ(event, *GetPendingEvent(observer_list));
@@ -280,8 +278,8 @@
   // Now dispatch the pending event to observer2.
   SetLastNotificationTime(observer_list, &mock_observer2);
   DispatchPendingEvents(observer_list);
-  EXPECT_EQ(2, mock_observer1.EventCount());
-  EXPECT_EQ(2, mock_observer2.EventCount());
+  EXPECT_EQ(2u, mock_observer1.EventCount());
+  EXPECT_EQ(2u, mock_observer2.EventCount());
   EXPECT_EQ(event, mock_observer1.LastEvent());
   EXPECT_EQ(event, mock_observer2.LastEvent());
   EXPECT_EQ(nullptr, GetPendingEvent(observer_list));
@@ -327,7 +325,7 @@
   // Verify that HostStorageObservers dispatches the first event correctly.
   StorageObserver::Event expected_event(params.filter, kUsage, kQuota);
   host_observers.NotifyUsageChange(params.filter, 87324);
-  EXPECT_EQ(1, mock_observer.EventCount());
+  EXPECT_EQ(1u, mock_observer.EventCount());
   EXPECT_EQ(expected_event, mock_observer.LastEvent());
   EXPECT_TRUE(host_observers.is_initialized());
 
@@ -337,7 +335,7 @@
   expected_event.usage += kDelta;
   SetLastNotificationTime(host_observers, &mock_observer);
   host_observers.NotifyUsageChange(params.filter, kDelta);
-  EXPECT_EQ(2, mock_observer.EventCount());
+  EXPECT_EQ(2u, mock_observer.EventCount());
   EXPECT_EQ(expected_event, mock_observer.LastEvent());
 }
 
@@ -357,7 +355,7 @@
   MockObserver mock_observer1;
   host_observers.AddObserver(&mock_observer1, params);
   EXPECT_FALSE(host_observers.is_initialized());
-  EXPECT_EQ(0, mock_observer1.EventCount());
+  EXPECT_EQ(0u, mock_observer1.EventCount());
 
   // |host_observers| should be initialized after the second observer is
   // added.
@@ -365,8 +363,8 @@
   params.dispatch_initial_state = true;
   host_observers.AddObserver(&mock_observer2, params);
   StorageObserver::Event expected_event(params.filter, kUsage, kQuota);
-  EXPECT_EQ(0, mock_observer1.EventCount());
-  EXPECT_EQ(1, mock_observer2.EventCount());
+  EXPECT_EQ(0u, mock_observer1.EventCount());
+  EXPECT_EQ(1u, mock_observer2.EventCount());
   EXPECT_EQ(expected_event, mock_observer2.LastEvent());
   EXPECT_TRUE(host_observers.is_initialized());
   EXPECT_EQ(nullptr, GetPendingEvent(host_observers));
@@ -377,8 +375,8 @@
   expected_event.usage += kDelta;
   SetLastNotificationTime(host_observers, &mock_observer2);
   host_observers.NotifyUsageChange(params.filter, kDelta);
-  EXPECT_EQ(1, mock_observer1.EventCount());
-  EXPECT_EQ(2, mock_observer2.EventCount());
+  EXPECT_EQ(1u, mock_observer1.EventCount());
+  EXPECT_EQ(2u, mock_observer2.EventCount());
   EXPECT_EQ(expected_event, mock_observer1.LastEvent());
   EXPECT_EQ(expected_event, mock_observer2.LastEvent());
   EXPECT_EQ(nullptr, GetPendingEvent(host_observers));
@@ -389,9 +387,9 @@
   MockObserver mock_observer3;
   params.dispatch_initial_state = true;
   host_observers.AddObserver(&mock_observer3, params);
-  EXPECT_EQ(1, mock_observer1.EventCount());
-  EXPECT_EQ(2, mock_observer2.EventCount());
-  EXPECT_EQ(1, mock_observer3.EventCount());
+  EXPECT_EQ(1u, mock_observer1.EventCount());
+  EXPECT_EQ(2u, mock_observer2.EventCount());
+  EXPECT_EQ(1u, mock_observer3.EventCount());
   EXPECT_EQ(expected_event, mock_observer3.LastEvent());
 }
 
@@ -431,7 +429,7 @@
   // Verify that |host_observers| is not initialized and an event has not been
   // dispatched.
   host_observers.NotifyUsageChange(params.filter, 9438);
-  EXPECT_EQ(0, mock_observer.EventCount());
+  EXPECT_EQ(0u, mock_observer.EventCount());
   EXPECT_FALSE(host_observers.is_initialized());
   EXPECT_EQ(nullptr, GetPendingEvent(host_observers));
   EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
@@ -440,7 +438,7 @@
   quota_manager_->SetCallbackParams(kUsage, kQuota, QuotaStatusCode::kOk);
   host_observers.NotifyUsageChange(params.filter, 9048543);
   StorageObserver::Event expected_event(params.filter, kUsage, kQuota);
-  EXPECT_EQ(1, mock_observer.EventCount());
+  EXPECT_EQ(1u, mock_observer.EventCount());
   EXPECT_EQ(expected_event, mock_observer.LastEvent());
   EXPECT_TRUE(host_observers.is_initialized());
 }
@@ -458,7 +456,7 @@
   // Trigger initialization. Leave the mock quota manager uninitialized so that
   // the callback is not invoked.
   host_observers.NotifyUsageChange(params.filter, 7645);
-  EXPECT_EQ(0, mock_observer.EventCount());
+  EXPECT_EQ(0u, mock_observer.EventCount());
   EXPECT_FALSE(host_observers.is_initialized());
   EXPECT_EQ(nullptr, GetPendingEvent(host_observers));
   EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
@@ -469,7 +467,7 @@
   const int64_t kQuota = 99585556;
   const int64_t kDelta = 327643;
   host_observers.NotifyUsageChange(params.filter, kDelta);
-  EXPECT_EQ(0, mock_observer.EventCount());
+  EXPECT_EQ(0u, mock_observer.EventCount());
   EXPECT_FALSE(host_observers.is_initialized());
   EXPECT_EQ(nullptr, GetPendingEvent(host_observers));
   EXPECT_EQ(0, GetRequiredUpdatesCount(host_observers));
@@ -478,7 +476,7 @@
   quota_manager_->SetCallbackParams(kUsage, kQuota, QuotaStatusCode::kOk);
   quota_manager_->InvokeCallback();
   StorageObserver::Event expected_event(params.filter, kUsage + kDelta, kQuota);
-  EXPECT_EQ(1, mock_observer.EventCount());
+  EXPECT_EQ(1u, mock_observer.EventCount());
   EXPECT_EQ(expected_event, mock_observer.LastEvent());
   EXPECT_TRUE(host_observers.is_initialized());
   EXPECT_EQ(nullptr, GetPendingEvent(host_observers));
@@ -515,20 +513,20 @@
   // Verify that the observers have been removed correctly.
   ASSERT_TRUE(type_observers.GetHostObservers(host1));
   ASSERT_TRUE(type_observers.GetHostObservers(host2));
-  EXPECT_EQ(2, GetObserverCount(*type_observers.GetHostObservers(host1)));
-  EXPECT_EQ(3, GetObserverCount(*type_observers.GetHostObservers(host2)));
+  EXPECT_EQ(2u, GetObserverCount(*type_observers.GetHostObservers(host1)));
+  EXPECT_EQ(3u, GetObserverCount(*type_observers.GetHostObservers(host2)));
 
   // Remove all instances of observer1.
   type_observers.RemoveObserver(&mock_observer1);
   ASSERT_TRUE(type_observers.GetHostObservers(host1));
   ASSERT_TRUE(type_observers.GetHostObservers(host2));
-  EXPECT_EQ(1, GetObserverCount(*type_observers.GetHostObservers(host1)));
-  EXPECT_EQ(2, GetObserverCount(*type_observers.GetHostObservers(host2)));
+  EXPECT_EQ(1u, GetObserverCount(*type_observers.GetHostObservers(host1)));
+  EXPECT_EQ(2u, GetObserverCount(*type_observers.GetHostObservers(host2)));
 
   // Remove all instances of observer2.
   type_observers.RemoveObserver(&mock_observer2);
   ASSERT_TRUE(type_observers.GetHostObservers(host2));
-  EXPECT_EQ(1, GetObserverCount(*type_observers.GetHostObservers(host2)));
+  EXPECT_EQ(1u, GetObserverCount(*type_observers.GetHostObservers(host2)));
   // Observers of host1 has been deleted as it is empty.
   EXPECT_FALSE(type_observers.GetHostObservers(host1));
 }
@@ -563,14 +561,15 @@
     storage_monitor_->AddObserver(&mock_observer3_, params2_);
   }
 
-  int GetObserverCount(StorageType storage_type) {
+  size_t GetObserverCount(StorageType storage_type) {
     const StorageTypeObservers* type_observers =
         storage_monitor_->GetStorageTypeObservers(storage_type);
     return StorageMonitorTestBase::GetObserverCount(
                 *type_observers->GetHostObservers(host_));
   }
 
-  void CheckObserverCount(int expected_temporary, int expected_persistent) {
+  void CheckObserverCount(size_t expected_temporary,
+                          size_t expected_persistent) {
     ASSERT_TRUE(
         storage_monitor_->GetStorageTypeObservers(StorageType::kTemporary));
     ASSERT_TRUE(
@@ -610,9 +609,9 @@
   storage_monitor_->NotifyUsageChange(params1_.filter, 9048543);
 
   StorageObserver::Event expected_event(params1_.filter, kUsage, kQuota);
-  EXPECT_EQ(1, mock_observer1_.EventCount());
-  EXPECT_EQ(1, mock_observer2_.EventCount());
-  EXPECT_EQ(0, mock_observer3_.EventCount());
+  EXPECT_EQ(1u, mock_observer1_.EventCount());
+  EXPECT_EQ(1u, mock_observer2_.EventCount());
+  EXPECT_EQ(0u, mock_observer3_.EventCount());
   EXPECT_EQ(expected_event, mock_observer1_.LastEvent());
   EXPECT_EQ(expected_event, mock_observer2_.LastEvent());
 }
@@ -671,7 +670,7 @@
   scoped_task_environment_.RunUntilIdle();
 
   // Verify that the observer receives it.
-  ASSERT_EQ(1, mock_observer.EventCount());
+  ASSERT_EQ(1u, mock_observer.EventCount());
   const StorageObserver::Event& event = mock_observer.LastEvent();
   EXPECT_EQ(params.filter, event.filter);
   EXPECT_EQ(kTestUsage, event.usage);
diff --git a/storage/browser/quota/usage_tracker.cc b/storage/browser/quota/usage_tracker.cc
index 64fb591..8f3516d 100644
--- a/storage/browser/quota/usage_tracker.cc
+++ b/storage/browser/quota/usage_tracker.cc
@@ -208,6 +208,7 @@
 
 void UsageTracker::AccumulateClientGlobalLimitedUsage(AccumulateInfo* info,
                                                       int64_t limited_usage) {
+  DCHECK_GT(info->pending_clients, 0U);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   info->usage += limited_usage;
   if (--info->pending_clients)
@@ -224,6 +225,7 @@
 void UsageTracker::AccumulateClientGlobalUsage(AccumulateInfo* info,
                                                int64_t usage,
                                                int64_t unlimited_usage) {
+  DCHECK_GT(info->pending_clients, 0U);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   info->usage += usage;
   info->unlimited_usage += unlimited_usage;
diff --git a/storage/browser/quota/usage_tracker.h b/storage/browser/quota/usage_tracker.h
index 1d19e8d..74bcb1f 100644
--- a/storage/browser/quota/usage_tracker.h
+++ b/storage/browser/quota/usage_tracker.h
@@ -73,7 +73,7 @@
   struct AccumulateInfo {
     AccumulateInfo();
     ~AccumulateInfo();
-    int pending_clients = 0;
+    size_t pending_clients = 0;
     int64_t usage = 0;
     int64_t unlimited_usage = 0;
     blink::mojom::UsageBreakdownPtr usage_breakdown =
diff --git a/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
index 2a315e0..65f56a78 100644
--- a/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
@@ -16,20 +16,12 @@
 -org.chromium.android_webview.test.CookieManagerTest.testCookieForWebSocketHandshake_thirdParty_disabled
 
 # https://crbug.com/941337
--org.chromium.android_webview.test.CookieManagerTest.testThirdPartyCookie_redirectFromThirdToFirst
+-org.chromium.android_webview.test.CookieManagerTest.testThirdPartyCookie_redirectFromThirdPartyToFirst
 -org.chromium.android_webview.test.CookieManagerTest.testThirdPartyCookie_redirectFromFirstPartyToThird
 
 # https://crbug.com/893580
 -org.chromium.android_webview.test.LoadDataWithBaseUrlTest.testLoadDataWithBaseUrlAccessingFile
 
-# https://crbug.com/902658
--org.chromium.android_webview.test.AwProxyControllerTest.testProxyOverride
--org.chromium.android_webview.test.AwProxyControllerTest.testProxyOverrideLocalhost
--org.chromium.android_webview.test.AwProxyControllerTest.testCallbacks
--org.chromium.android_webview.test.AwProxyControllerTest.testValidInput
--org.chromium.android_webview.test.AwProxyControllerTest.testInvalidProxyUrls
--org.chromium.android_webview.test.AwProxyControllerTest.testInvalidBypassRules
-
 # Flaky tests on android_mojo and android_mojo_rel bots
 # https://crbug.com/936757, https://crbug.com/939355
 -org.chromium.android_webview.test.AwContentsClientFullScreenTest.testOnShowCustomViewAndPlayWithHtmlControl_videoInsideDiv
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 4a0d8a0..ec85cb8 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -389,10 +389,6 @@
       'chromedriver_replay_unittests': {},
     },
 
-    # Identical to chromeos_device_friendly_gtests below minus
-    #   - cros_vm_sanity_test
-    #   - chrome_all_tast_tests
-
     # Tests that run in Chrome OS VMs.
     # NOTE: We only want a small subset of test suites here, because most
     # suites assume that they stub out the underlying device hardware.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 51d91fb1..ec6a0cc0 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -596,28 +596,7 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_LeadingIcon",
-                    "params": {
-                        "variant": "leading-icon"
-                    },
-                    "enable_features": [
-                        "AutofillDropdownLayout"
-                    ]
-                },
-                {
-                    "name": "Enabled_TrailingIcon",
-                    "params": {
-                        "variant": "trailing-icon"
-                    },
-                    "enable_features": [
-                        "AutofillDropdownLayout"
-                    ]
-                },
-                {
-                    "name": "Enabled_TwoLinesLeadingIcon",
-                    "params": {
-                        "variant": "two-lines-leading-icon"
-                    },
+                    "name": "Enabled",
                     "enable_features": [
                         "AutofillDropdownLayout"
                     ]
@@ -1434,6 +1413,21 @@
             ]
         }
     ],
+    "ContextualSearchDefinitions": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "ContextualSearchDefinitions",
+                    "enable_features": [
+                        "ContextualSearchDefinitions"
+                    ]
+                }
+            ]
+        }
+    ],
     "CopylessPaste": [
         {
             "platforms": [
diff --git a/third_party/blink/common/feature_policy/feature_policy.cc b/third_party/blink/common/feature_policy/feature_policy.cc
index a22cc83..916cd14 100644
--- a/third_party/blink/common/feature_policy/feature_policy.cc
+++ b/third_party/blink/common/feature_policy/feature_policy.cc
@@ -438,6 +438,9 @@
        {mojom::FeaturePolicyFeature::kTopNavigation,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll,
                             mojom::PolicyValueType::kBool)},
+       {mojom::FeaturePolicyFeature::kUnoptimizedLosslessImages,
+        FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll,
+                            mojom::PolicyValueType::kDecDouble)},
        {mojom::FeaturePolicyFeature::kUnoptimizedLossyImages,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll,
                             mojom::PolicyValueType::kDecDouble)},
diff --git a/third_party/blink/public/mojom/feature_policy/feature_policy.mojom b/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
index b5aa3fb..41a1a86 100644
--- a/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
+++ b/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
@@ -123,6 +123,7 @@
 
   // When disallowed, these policies require images to have a reasonable byte-to-pixel ratio.
   kUnoptimizedLossyImages = 45,
+  kUnoptimizedLosslessImages = 46,
 
   // Don't change assigned numbers of any item, and don't reuse removed slots.
   // Add new features at the end of the enum.
diff --git a/third_party/blink/public/mojom/web_client_hints/OWNERS b/third_party/blink/public/mojom/web_client_hints/OWNERS
index 08850f4..c02b6b0 100644
--- a/third_party/blink/public/mojom/web_client_hints/OWNERS
+++ b/third_party/blink/public/mojom/web_client_hints/OWNERS
@@ -1,2 +1,6 @@
+yoavweiss@chromium.org
+tbansal@chromium.org
+ryansturm@chromium.org
+
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/third_party/blink/public/web/web_ax_object.h b/third_party/blink/public/web/web_ax_object.h
index 690e439f..68f6a59 100644
--- a/third_party/blink/public/web/web_ax_object.h
+++ b/third_party/blink/public/web/web_ax_object.h
@@ -211,6 +211,19 @@
 
   // The following selection functions get or set the global document
   // selection and can be called on any object in the tree.
+  //
+  // Since we are gradually moving to a new selection codebase, we have two sets
+  // of functions. The deprecated ones will still be used by Chromium, except in
+  // Web Tests, which will be using the new ones.
+  // TODO(nektar): Remove deprecated functions. crbug.com/639340
+
+  BLINK_EXPORT void SelectionDeprecated(
+      WebAXObject& anchor_object,
+      int& anchor_offset,
+      ax::mojom::TextAffinity& anchor_affinity,
+      WebAXObject& focus_object,
+      int& focus_offset,
+      ax::mojom::TextAffinity& focus_affinity) const;
   BLINK_EXPORT void Selection(WebAXObject& anchor_object,
                               int& anchor_offset,
                               ax::mojom::TextAffinity& anchor_affinity,
@@ -219,10 +232,18 @@
                               ax::mojom::TextAffinity& focus_affinity) const;
 
   // The following selection functions return text offsets calculated starting
-  // the current object. They only report on a selection that is placed on
+  // from the current object. They only report on a selection that is placed on
   // the current object or on any of its descendants.
+  //
+  // Since we are gradually moving to a new selection codebase, we have two sets
+  // of functions. The deprecated ones will still be used by Chromium, except in
+  // Web Tests, which will be using the new ones.
+  // TODO(nektar): Remove deprecated functions. crbug.com/639340
+
+  BLINK_EXPORT unsigned SelectionEndDeprecated() const;
   BLINK_EXPORT unsigned SelectionEnd() const;
   BLINK_EXPORT unsigned SelectionEndLineNumber() const;
+  BLINK_EXPORT unsigned SelectionStartDeprecated() const;
   BLINK_EXPORT unsigned SelectionStart() const;
   BLINK_EXPORT unsigned SelectionStartLineNumber() const;
 
@@ -267,6 +288,10 @@
   BLINK_EXPORT bool Focus() const;
   BLINK_EXPORT bool SetAccessibilityFocus() const;
   BLINK_EXPORT bool SetSelected(bool) const;
+  BLINK_EXPORT bool SetSelectionDeprecated(const WebAXObject& anchor_object,
+                                           int anchor_offset,
+                                           const WebAXObject& focus_object,
+                                           int focus_offset) const;
   BLINK_EXPORT bool SetSelection(const WebAXObject& anchor_object,
                                  int anchor_offset,
                                  const WebAXObject& focus_object,
diff --git a/third_party/blink/renderer/bindings/bindings.gni b/third_party/blink/renderer/bindings/bindings.gni
index b36a029..fc4c7df 100644
--- a/third_party/blink/renderer/bindings/bindings.gni
+++ b/third_party/blink/renderer/bindings/bindings.gni
@@ -199,7 +199,6 @@
           "core/v8/script_promise_test.cc",
           "core/v8/script_streamer_test.cc",
           "core/v8/script_wrappable_v8_gc_integration_test.cc",
-          "core/v8/script_wrappable_visitor_test.cc",
           "core/v8/to_v8_test.cc",
           "core/v8/trace_wrapper_member_test.cc",
           "core/v8/v8_binding_for_testing.cc",
diff --git a/third_party/blink/renderer/bindings/core/v8/script_wrappable_visitor_test.cc b/third_party/blink/renderer/bindings/core/v8/script_wrappable_visitor_test.cc
deleted file mode 100644
index d736917a..0000000
--- a/third_party/blink/renderer/bindings/core/v8/script_wrappable_visitor_test.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/core/testing/death_aware_script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/trace_traits.h"
-
-namespace blink {
-
-namespace {
-
-class VerifyingScriptWrappableVisitor : public ScriptWrappableVisitor {
- public:
-  VerifyingScriptWrappableVisitor()
-      : ScriptWrappableVisitor(ThreadState::Current()) {}
-
-  // Visitor interface.
-  void Visit(const TraceWrapperV8Reference<v8::Value>&) override {}
-
-  void VisitWithWrappers(void*, TraceDescriptor desc) override {
-    visited_objects_.push_back(desc.base_object_payload);
-  }
-
-  void VisitBackingStoreStrongly(void* object,
-                                 void** object_slot,
-                                 TraceDescriptor desc) override {
-    if (!object)
-      return;
-    desc.callback(this, desc.base_object_payload);
-  }
-
-  bool DidVisitObject(const ScriptWrappable* script_wrappable) const {
-    return std::find(visited_objects_.begin(), visited_objects_.end(),
-                     script_wrappable) != visited_objects_.end();
-  }
-
- protected:
-  using Visitor::Visit;
-
- private:
-  std::vector<void*> visited_objects_;
-};
-
-class ExpectObjectsVisited {
- public:
-  ExpectObjectsVisited(VerifyingScriptWrappableVisitor* visitor,
-                       std::initializer_list<ScriptWrappable*> objects)
-      : visitor_(visitor), expected_objects_(objects) {}
-
-  ~ExpectObjectsVisited() {
-    for (const ScriptWrappable* expected_object : expected_objects_) {
-      EXPECT_TRUE(visitor_->DidVisitObject(expected_object));
-    }
-  }
-
- private:
-  VerifyingScriptWrappableVisitor* visitor_;
-  std::vector<ScriptWrappable*> expected_objects_;
-};
-
-}  // namespace
-
-TEST(ScriptWrappableVisitorTest, TraceWrapperMember) {
-  VerifyingScriptWrappableVisitor verifying_visitor;
-  DeathAwareScriptWrappable* parent = DeathAwareScriptWrappable::Create();
-  DeathAwareScriptWrappable* child = DeathAwareScriptWrappable::Create();
-  parent->SetWrappedDependency(child);
-  {
-    ExpectObjectsVisited expected(&verifying_visitor, {child});
-    TraceDescriptor desc =
-        TraceTrait<DeathAwareScriptWrappable>::GetTraceDescriptor(parent);
-    desc.callback(&verifying_visitor, parent);
-  }
-}
-
-TEST(ScriptWrappableVisitorTest, HeapVectorOfTraceWrapperMember) {
-  VerifyingScriptWrappableVisitor verifying_visitor;
-  DeathAwareScriptWrappable* parent = DeathAwareScriptWrappable::Create();
-  DeathAwareScriptWrappable* child = DeathAwareScriptWrappable::Create();
-  parent->AddWrappedVectorDependency(child);
-  {
-    ExpectObjectsVisited expected(&verifying_visitor, {child});
-    TraceDescriptor desc =
-        TraceTrait<DeathAwareScriptWrappable>::GetTraceDescriptor(parent);
-    desc.callback(&verifying_visitor, parent);
-  }
-}
-
-TEST(ScriptWrappableVisitorTest, HeapHashMapOfTraceWrapperMember) {
-  VerifyingScriptWrappableVisitor verifying_visitor;
-  DeathAwareScriptWrappable* parent = DeathAwareScriptWrappable::Create();
-  DeathAwareScriptWrappable* key = DeathAwareScriptWrappable::Create();
-  DeathAwareScriptWrappable* value = DeathAwareScriptWrappable::Create();
-  parent->AddWrappedHashMapDependency(key, value);
-  {
-    ExpectObjectsVisited expected(&verifying_visitor, {key, value});
-    TraceDescriptor desc =
-        TraceTrait<DeathAwareScriptWrappable>::GetTraceDescriptor(parent);
-    desc.callback(&verifying_visitor, parent);
-  }
-}
-
-TEST(ScriptWrappableVisitorTest, InObjectUsingTraceWrapperMember) {
-  VerifyingScriptWrappableVisitor verifying_visitor;
-  DeathAwareScriptWrappable* parent = DeathAwareScriptWrappable::Create();
-  DeathAwareScriptWrappable* child = DeathAwareScriptWrappable::Create();
-  parent->AddInObjectDependency(child);
-  {
-    ExpectObjectsVisited expected(&verifying_visitor, {child});
-    TraceDescriptor desc =
-        TraceTrait<DeathAwareScriptWrappable>::GetTraceDescriptor(parent);
-    desc.callback(&verifying_visitor, parent);
-  }
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc b/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc
index d55dd9c..17303185 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc
@@ -9,7 +9,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_node.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc b/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc
index c3d04f0..7fe921b 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc
@@ -46,7 +46,6 @@
 #include "third_party/blink/renderer/core/dom/node.h"
 #include "third_party/blink/renderer/core/html/imports/html_imports_controller.h"
 #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
 #include "third_party/blink/renderer/platform/heap/heap_stats_collector.h"
 #include "third_party/blink/renderer/platform/histogram.h"
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index 893ed77..e3e13a2 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -695,8 +695,8 @@
   // over to Blink.
   DCHECK(ThreadState::MainThreadState());
 
-    ThreadState::MainThreadState()->RegisterTraceDOMWrappers(
-        isolate, V8GCController::TraceDOMWrappers, nullptr, nullptr);
+  ThreadState::MainThreadState()->RegisterTraceDOMWrappers(
+      isolate, V8GCController::TraceDOMWrappers);
 
   InitializeV8Common(isolate);
 
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 49a8d975..02bfeb46 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -2079,6 +2079,7 @@
     "layout/ng/ng_constraint_space_builder_test.cc",
     "layout/ng/ng_fieldset_layout_algorithm_test.cc",
     "layout/ng/ng_inline_layout_test.cc",
+    "layout/ng/ng_layout_result_caching_test.cc",
     "layout/ng/ng_layout_test.h",
     "layout/ng/ng_length_utils_test.cc",
     "layout/ng/ng_out_of_flow_layout_part_test.cc",
diff --git a/third_party/blink/renderer/core/css/css_paint_image_generator.h b/third_party/blink/renderer/core/css/css_paint_image_generator.h
index 262424e..dd99919 100644
--- a/third_party/blink/renderer/core/css/css_paint_image_generator.h
+++ b/third_party/blink/renderer/core/css/css_paint_image_generator.h
@@ -58,6 +58,7 @@
   virtual bool HasAlpha() const = 0;
   virtual const Vector<CSSSyntaxDescriptor>& InputArgumentTypes() const = 0;
   virtual bool IsImageGeneratorReady() const = 0;
+  virtual int WorkletId() const = 0;
 
   virtual void Trace(blink::Visitor* visitor) {}
 };
diff --git a/third_party/blink/renderer/core/css/css_paint_value.cc b/third_party/blink/renderer/core/css/css_paint_value.cc
index c59ebff..9972737 100644
--- a/third_party/blink/renderer/core/css/css_paint_value.cc
+++ b/third_party/blink/renderer/core/css/css_paint_value.cc
@@ -55,6 +55,11 @@
   if (style.InsideLink() != EInsideLink::kNotInsideLink)
     return nullptr;
 
+  if (!generator_) {
+    generator_ = CSSPaintImageGenerator::Create(
+        GetName(), document, paint_image_generator_observer_);
+  }
+
   // For Off-Thread PaintWorklet, we just collect the necessary inputs together
   // and defer the actual JavaScript call until much later (during cc Raster).
   if (RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
@@ -72,15 +77,11 @@
             custom_properties);
     scoped_refptr<PaintWorkletInput> input =
         base::MakeRefCounted<PaintWorkletInput>(GetName(), target_size, zoom,
+                                                generator_->WorkletId(),
                                                 std::move(style_data));
     return PaintWorkletDeferredImage::Create(std::move(input), target_size);
   }
 
-  if (!generator_) {
-    generator_ = CSSPaintImageGenerator::Create(
-        GetName(), document, paint_image_generator_observer_);
-  }
-
   if (!ParseInputArguments(document))
     return nullptr;
 
diff --git a/third_party/blink/renderer/core/css/css_rule.cc b/third_party/blink/renderer/core/css/css_rule.cc
index f0faba5..31c53afc 100644
--- a/third_party/blink/renderer/core/css/css_rule.cc
+++ b/third_party/blink/renderer/core/css/css_rule.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/core/css/css_style_sheet.h"
 #include "third_party/blink/renderer/core/css/style_rule.h"
 #include "third_party/blink/renderer/core/css/style_sheet_contents.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 
 namespace blink {
 
@@ -48,14 +47,12 @@
 void CSSRule::SetParentStyleSheet(CSSStyleSheet* style_sheet) {
   parent_is_rule_ = false;
   parent_style_sheet_ = style_sheet;
-  ScriptWrappableMarkingVisitor::WriteBarrier(parent_style_sheet_);
   MarkingVisitor::WriteBarrier(parent_style_sheet_);
 }
 
 void CSSRule::SetParentRule(CSSRule* rule) {
   parent_is_rule_ = true;
   parent_rule_ = rule;
-  ScriptWrappableMarkingVisitor::WriteBarrier(parent_rule_);
   MarkingVisitor::WriteBarrier(parent_rule_);
 }
 
diff --git a/third_party/blink/renderer/core/css/cssom/paint_worklet_input.cc b/third_party/blink/renderer/core/css/cssom/paint_worklet_input.cc
index c2a4bf90..15cf35c 100644
--- a/third_party/blink/renderer/core/css/cssom/paint_worklet_input.cc
+++ b/third_party/blink/renderer/core/css/cssom/paint_worklet_input.cc
@@ -12,10 +12,12 @@
     const String& name,
     const FloatSize& container_size,
     float effective_zoom,
+    int worklet_id,
     PaintWorkletStylePropertyMap::CrossThreadData data)
     : name_(name.IsolatedCopy()),
       container_size_(container_size),
       effective_zoom_(effective_zoom),
+      worklet_id_(worklet_id),
       style_map_data_(std::move(data)) {}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/paint_worklet_input.h b/third_party/blink/renderer/core/css/cssom/paint_worklet_input.h
index df95eae..a3d3f04 100644
--- a/third_party/blink/renderer/core/css/cssom/paint_worklet_input.h
+++ b/third_party/blink/renderer/core/css/cssom/paint_worklet_input.h
@@ -39,6 +39,7 @@
   PaintWorkletInput(const String& name,
                     const FloatSize& container_size,
                     float effective_zoom,
+                    int worklet_id,
                     PaintWorkletStylePropertyMap::CrossThreadData values);
 
   ~PaintWorkletInput() override = default;
@@ -51,6 +52,7 @@
   // These accessors are safe on any thread.
   const FloatSize& ContainerSize() const { return container_size_; }
   float EffectiveZoom() const { return effective_zoom_; }
+  int WorkletId() const { return worklet_id_; }
 
   // These should only be accessed on the PaintWorklet thread.
   String NameCopy() const { return name_.IsolatedCopy(); }
@@ -62,6 +64,7 @@
   const String name_;
   const FloatSize container_size_;
   const float effective_zoom_;
+  const int worklet_id_;
   PaintWorkletStylePropertyMap::CrossThreadData style_map_data_;
 };
 
diff --git a/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc b/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc
index 0db31920..55d743b2 100644
--- a/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc
+++ b/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map_test.cc
@@ -188,7 +188,7 @@
           custom_properties);
   scoped_refptr<PaintWorkletInput> input =
       base::MakeRefCounted<PaintWorkletInput>("test", FloatSize(100, 100), 1.0f,
-                                              std::move(data));
+                                              1, std::move(data));
   DCHECK(input);
 
   thread_ = std::make_unique<WebThreadSupportingGC>(
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 38afee9..85f1033 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -4850,20 +4850,8 @@
     css_target_->PseudoStateChanged(CSSSelector::kPseudoTarget);
 }
 
-static void LiveNodeListBaseWriteBarrier(void* parent,
-                                         const LiveNodeListBase* list) {
-  if (IsHTMLCollectionType(list->GetType())) {
-    ScriptWrappableMarkingVisitor::WriteBarrier(
-        static_cast<const HTMLCollection*>(list));
-  } else {
-    ScriptWrappableMarkingVisitor::WriteBarrier(
-        static_cast<const LiveNodeList*>(list));
-  }
-}
-
 void Document::RegisterNodeList(const LiveNodeListBase* list) {
   node_lists_.Add(list, list->InvalidationType());
-  LiveNodeListBaseWriteBarrier(this, list);
   if (list->IsRootedAtTreeScope())
     lists_invalidated_at_document_.insert(list);
 }
@@ -4878,7 +4866,6 @@
 
 void Document::RegisterNodeListWithIdNameCache(const LiveNodeListBase* list) {
   node_lists_.Add(list, kInvalidateOnIdNameAttrChange);
-  LiveNodeListBaseWriteBarrier(this, list);
 }
 
 void Document::UnregisterNodeListWithIdNameCache(const LiveNodeListBase* list) {
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.h b/third_party/blink/renderer/core/dom/element_rare_data.h
index af76a107..88dcd7c 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data.h
@@ -41,7 +41,6 @@
 #include "third_party/blink/renderer/core/html/custom/custom_element_definition.h"
 #include "third_party/blink/renderer/core/html/custom/v0_custom_element_definition.h"
 #include "third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
diff --git a/third_party/blink/renderer/core/dom/events/event_listener_map.h b/third_party/blink/renderer/core/dom/events/event_listener_map.h
index 0c16cfe..681e698 100644
--- a/third_party/blink/renderer/core/dom/events/event_listener_map.h
+++ b/third_party/blink/renderer/core/dom/events/event_listener_map.h
@@ -38,7 +38,6 @@
 #include "third_party/blink/renderer/core/dom/events/add_event_listener_options_resolved.h"
 #include "third_party/blink/renderer/core/dom/events/event_listener_options.h"
 #include "third_party/blink/renderer/core/dom/events/registered_event_listener.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index ccc863c..05d8449 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -114,7 +114,6 @@
 #include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/microtask.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h"
 #include "third_party/blink/renderer/platform/instance_counters.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
@@ -363,7 +362,6 @@
 
   DCHECK(data_.rare_data_);
   SetFlag(kHasRareDataFlag);
-  ScriptWrappableMarkingVisitor::WriteBarrier(RareData());
   MarkingVisitor::WriteBarrier(RareData());
   return *RareData();
 }
diff --git a/third_party/blink/renderer/core/dom/node_rare_data.cc b/third_party/blink/renderer/core/dom/node_rare_data.cc
index acbdc03..2e0536e 100644
--- a/third_party/blink/renderer/core/dom/node_rare_data.cc
+++ b/third_party/blink/renderer/core/dom/node_rare_data.cc
@@ -37,7 +37,6 @@
 #include "third_party/blink/renderer/core/dom/mutation_observer_registration.h"
 #include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
 #include "third_party/blink/renderer/core/page/page.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/dom/shadow_root.h b/third_party/blink/renderer/core/dom/shadow_root.h
index 74d5a94e..c34d91b 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.h
+++ b/third_party/blink/renderer/core/dom/shadow_root.h
@@ -34,7 +34,6 @@
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/tree_scope.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.cc b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
index 8e14c48b..225f7dc 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
@@ -32,8 +32,13 @@
     return PolicyValue(2.0);
   }
   if (feature == mojom::FeaturePolicyFeature::kUnoptimizedLossyImages) {
+    // Lossy images default to at most 0.5 bytes per pixel.
     return PolicyValue(0.5);
   }
+  if (feature == mojom::FeaturePolicyFeature::kUnoptimizedLosslessImages) {
+    // Lossless images default to at most 1 byte per pixel.
+    return PolicyValue(1.0);
+  }
 
   return PolicyValue(false);
 }
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5 b/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5
index 3b26fcb..12ea924 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5
@@ -188,6 +188,11 @@
       depends_on: ["FeaturePolicyForSandbox"],
     },
     {
+      name: "UnoptimizedLosslessImages",
+      feature_policy_name: "unoptimized-lossless-images",
+      depends_on: ["ExperimentalProductivityFeatures"],
+    },
+    {
       name: "UnoptimizedLossyImages",
       feature_policy_name: "unoptimized-lossy-images",
       depends_on: ["UnoptimizedImagePolicies"],
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index b8d9d274..e5cd857 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -94,6 +94,7 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/create_window.h"
+#include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
 #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h"
@@ -1458,45 +1459,84 @@
   if (!features.IsEmpty())
     UseCounter::Count(*active_document, WebFeature::kDOMWindowOpenFeatures);
 
+  KURL completed_url =
+      url_string.IsEmpty()
+          ? KURL(g_empty_string)
+          : entered_window_frame->GetDocument()->CompleteURL(url_string);
+  if (!completed_url.IsEmpty() && !completed_url.IsValid()) {
+    UseCounter::Count(active_document, WebFeature::kWindowOpenWithInvalidURL);
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kSyntaxError,
+        "Unable to open a window with invalid URL '" +
+            completed_url.GetString() + "'.\n");
+    return nullptr;
+  }
+
+  WebWindowFeatures window_features = GetWindowFeaturesFromString(features);
+
   // Get the target frame for the special cases of _top and _parent.
   // In those cases, we schedule a location change right now and return early.
   Frame* target_frame = nullptr;
   if (EqualIgnoringASCIICase(target, "_top")) {
     target_frame = &GetFrame()->Tree().Top();
+  } else if (EqualIgnoringASCIICase(target, "_self")) {
+    target_frame = GetFrame();
   } else if (EqualIgnoringASCIICase(target, "_parent")) {
     if (Frame* parent = GetFrame()->Tree().Parent())
       target_frame = parent;
     else
       target_frame = GetFrame();
+  } else if (!target.IsEmpty() && !window_features.noopener) {
+    target_frame = GetFrame()->FindFrameForNavigation(
+        target, *active_document->GetFrame(), completed_url);
+    if (target_frame) {
+      Page* target_page = target_frame->GetPage();
+      if (target_page == GetFrame()->GetPage())
+        target_page->GetFocusController().SetFocusedFrame(target_frame);
+      else
+        target_page->GetChromeClient().Focus(GetFrame());
+      // Focusing can fire onblur, so check for detach.
+      if (!target_frame->GetPage())
+        return nullptr;
+    }
   }
 
-  if (target_frame) {
-    if (!active_document->GetFrame() ||
-        !active_document->GetFrame()->CanNavigate(*target_frame)) {
-      return nullptr;
-    }
+  if (!target_frame) {
+    return CreateWindow(completed_url, target, window_features,
+                        *incumbent_window, *GetFrame());
+  }
 
-    KURL completed_url =
-        entered_window_frame->GetDocument()->CompleteURL(url_string);
+  if (!active_document->GetFrame() ||
+      !active_document->GetFrame()->CanNavigate(*target_frame)) {
+    return nullptr;
+  }
 
-    if (target_frame->DomWindow()->IsInsecureScriptAccess(*incumbent_window,
-                                                          completed_url))
-      return target_frame->DomWindow();
-
-    if (url_string.IsEmpty())
-      return target_frame->DomWindow();
-
+  if (!url_string.IsEmpty() &&
+      !target_frame->DomWindow()->IsInsecureScriptAccess(*incumbent_window,
+                                                         completed_url)) {
     FrameLoadRequest request(active_document, ResourceRequest(completed_url));
     request.GetResourceRequest().SetHasUserGesture(
         LocalFrame::HasTransientUserActivation(GetFrame()));
     if (const WebInputEvent* input_event = CurrentInputEvent::Get())
       request.SetInputStartTime(input_event->TimeStamp());
     target_frame->Navigate(request, WebFrameLoadType::kStandard);
+  }
+
+  // TODO(japhet): window-open-noopener.html?_top and several tests in
+  // html/browsers/windows/browsing-context-names/ appear to require that
+  // the special case target names (_top, _parent, _self) ignore opener
+  // policy (by always returning a non-null window, and by never overriding
+  // the opener). The spec doesn't mention this.
+  if (EqualIgnoringASCIICase(target, "_top") ||
+      EqualIgnoringASCIICase(target, "_parent") ||
+      EqualIgnoringASCIICase(target, "_self")) {
     return target_frame->DomWindow();
   }
 
-  return CreateWindow(url_string, target, features, *incumbent_window,
-                      *entered_window_frame, *GetFrame(), exception_state);
+  if (window_features.noopener)
+    return nullptr;
+  target_frame->Client()->SetOpener(GetFrame());
+  return target_frame->DomWindow();
 }
 
 void LocalDOMWindow::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
index e976ae2..f328d06 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
@@ -43,7 +43,6 @@
 #include "third_party/blink/renderer/core/imagebitmap/image_bitmap_source.h"
 #include "third_party/blink/renderer/core/page/page_visibility_observer.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_host.h"
diff --git a/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc b/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc
index d8967d6..0b38c1b0 100644
--- a/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/checkbox_input_type.cc
@@ -34,6 +34,7 @@
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/input_type_names.h"
+#include "third_party/blink/renderer/core/page/spatial_navigation.h"
 #include "third_party/blink/renderer/platform/text/platform_locale.h"
 
 namespace blink {
@@ -56,9 +57,13 @@
 }
 
 void CheckboxInputType::HandleKeyupEvent(KeyboardEvent& event) {
-  if (event.key() != " ")
-    return;
-  DispatchSimulatedClickIfActive(event);
+  // Use Space key simulated click by default.
+  // Use Enter key simulated click when Spatial Navigation enabled.
+  if (event.key() == " " ||
+      (IsSpatialNavigationEnabled(GetElement().GetDocument().GetFrame()) &&
+       event.key() == "Enter")) {
+    DispatchSimulatedClickIfActive(event);
+  }
 }
 
 ClickHandlingState* CheckboxInputType::WillDispatchClick() {
diff --git a/third_party/blink/renderer/core/html/forms/radio_input_type.cc b/third_party/blink/renderer/core/html/forms/radio_input_type.cc
index 9ff420db..bc9ef07 100644
--- a/third_party/blink/renderer/core/html/forms/radio_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/radio_input_type.cc
@@ -137,13 +137,20 @@
 }
 
 void RadioInputType::HandleKeyupEvent(KeyboardEvent& event) {
-  if (event.key() != " ")
-    return;
   // If an unselected radio is tabbed into (because the entire group has nothing
   // checked, or because of some explicit .focus() call), then allow space to
   // check it.
   if (GetElement().checked())
     return;
+
+  // Use Space key simulated click by default.
+  // Use Enter key simulated click when Spatial Navigation enabled.
+  if (event.key() == " " ||
+      (IsSpatialNavigationEnabled(GetElement().GetDocument().GetFrame()) &&
+       event.key() == "Enter")) {
+    DispatchSimulatedClickIfActive(event);
+  }
+
   DispatchSimulatedClickIfActive(event);
 }
 
diff --git a/third_party/blink/renderer/core/inspector/browser_protocol.pdl b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
index 21180bc..8d86d0f 100644
--- a/third_party/blink/renderer/core/inspector/browser_protocol.pdl
+++ b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
@@ -3030,7 +3030,7 @@
       array of DataEntry objectStoreDataEntries
       # If true, there are more entries to fetch in the given range.
       boolean hasMore
-  
+
   # Gets metadata of an object store
   command getMetadata
     parameters
@@ -5786,7 +5786,7 @@
       optional array of string recommendations
 
   # Information about insecure content on the page.
-  type InsecureContentStatus extends object
+  deprecated type InsecureContentStatus extends object
     properties
       # True if the page was loaded over HTTPS and ran mixed (HTTP) content such as scripts.
       boolean ranMixedContent
@@ -5863,7 +5863,7 @@
       # `warning`, at least one corresponding explanation should be included.
       array of SecurityStateExplanation explanations
       # Information about insecure content on the page.
-      InsecureContentStatus insecureContentStatus
+      deprecated InsecureContentStatus insecureContentStatus
       # Overrides user-visible description of the state.
       optional string summary
 
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index bd49760c..99e86f50 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -2313,7 +2313,7 @@
   if (!RuntimeEnabledFeatures::LayoutNGFragmentCachingEnabled())
     return nullptr;
 
-  // TODO*cbiesinger): Support caching fragmented boxes
+  // TODO(cbiesinger): Support caching fragmented boxes.
   if (break_token)
     return nullptr;
 
@@ -2339,8 +2339,6 @@
   const NGConstraintSpace& old_space =
       cached_layout_result->GetConstraintSpaceForCaching();
 
-  // Check the BFC offset. Even if they don't match, there're some cases we can
-  // still reuse the fragment.
   base::Optional<LayoutUnit> bfc_block_offset =
       cached_layout_result->BfcBlockOffset();
   LayoutUnit bfc_line_offset = new_space.BfcOffset().line_offset;
@@ -2348,22 +2346,58 @@
   DCHECK_EQ(old_space.BfcOffset().line_offset,
             cached_layout_result->BfcLineOffset());
 
+  // Check the BFC offset. Even if they don't match, there're some cases we can
+  // still reuse the fragment.
   bool is_bfc_offset_equal = new_space.BfcOffset() == old_space.BfcOffset();
-  if (!is_bfc_offset_equal) {
-    // Earlier floats may affect this box if block offset changes.
-    if (new_space.HasFloats() || old_space.HasFloats())
+
+  // Even for the first fragment, when block fragmentation is enabled, block
+  // offset changes should cause re-layout, since we will fragment at other
+  // locations than before.
+  if (UNLIKELY(!is_bfc_offset_equal && new_space.HasBlockFragmentation())) {
+    DCHECK(old_space.HasBlockFragmentation());
+    return nullptr;
+  }
+
+  bool is_exclusion_space_equal =
+      new_space.ExclusionSpace() == old_space.ExclusionSpace();
+
+  // If anything changes within the layout regarding floats, we need to perform
+  // a series of additional checks to see if the result can still be reused.
+  if (!is_bfc_offset_equal || !is_exclusion_space_equal ||
+      new_space.ClearanceOffset() != old_space.ClearanceOffset()) {
+    // We can't reuse a result if it was previously affected by clearance.
+    //
+    // TODO(layout-dev): There may be cases where we can re-use results here.
+    // However as there are complexities regarding margin-collapsing and
+    // clearance we currently don't attempt to cache anything.
+    if (cached_layout_result->IsPushedByFloats()) {
+      DCHECK(old_space.HasFloats());
+      return nullptr;
+    }
+
+    // Check we have a descendant that *may* be positioned above the block-start
+    // edge. We abort if either the old or new space has floats, as we don't
+    // keep track of how far above the child could be. This case is relatively
+    // rare, and only occurs with negative margins.
+    if (cached_layout_result->MayHaveDescendantAboveBlockStart() &&
+        (old_space.HasFloats() || new_space.HasFloats()))
       return nullptr;
 
-    // Even for the first fragment, when block fragmentation is enabled, block
-    // offset changes should cause re-layout, since we will fragment at other
-    // locations than before.
-    if (new_space.HasBlockFragmentation() || old_space.HasBlockFragmentation())
-      return nullptr;
+    // We can now try to adjust the BFC block-offset.
+    if (bfc_block_offset) {
+      // Check if the previous position may intersect with any floats.
+      if (*bfc_block_offset <
+          old_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
+        return nullptr;
 
-    if (bfc_block_offset.has_value()) {
       bfc_block_offset = *bfc_block_offset -
                          old_space.BfcOffset().block_offset +
                          new_space.BfcOffset().block_offset;
+
+      // Check if the new position may intersect with any floats.
+      if (*bfc_block_offset <
+          new_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
+        return nullptr;
     }
   }
 
@@ -2375,10 +2409,19 @@
   // The checks above should be enough to bail if layout is incomplete, but
   // let's verify:
   DCHECK(IsBlockLayoutComplete(old_space, *cached_layout_result));
-  if (is_bfc_offset_equal)
+
+  // We can safely reuse this result if our BFC and "input" exclusion spaces
+  // were equal.
+  if (is_bfc_offset_equal && is_exclusion_space_equal) {
+    // In order not to rebuild the internal derived-geometry "cache" of float
+    // data, we need to move this to the new "output" exclusion space.
+    cached_layout_result->ExclusionSpace().MoveAndUpdateDerivedGeometry(
+        new_space.ExclusionSpace());
     return cached_layout_result;
+  }
 
   return base::AdoptRef(new NGLayoutResult(*cached_layout_result,
+                                           new_space.ExclusionSpace(),
                                            bfc_line_offset, bfc_block_offset));
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.h b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.h
index 1a67e0d..41a152e 100644
--- a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.h
+++ b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion.h
@@ -28,13 +28,30 @@
 // Struct that represents an exclusion. This currently is just a float but
 // we've named it an exclusion to potentially support other types in the future.
 struct CORE_EXPORT NGExclusion : public RefCounted<NGExclusion> {
-  static scoped_refptr<NGExclusion> Create(
+  static scoped_refptr<const NGExclusion> Create(
       const NGBfcRect& rect,
       const EFloat type,
       std::unique_ptr<NGExclusionShapeData> shape_data = nullptr) {
     return base::AdoptRef(new NGExclusion(rect, type, std::move(shape_data)));
   }
 
+  scoped_refptr<const NGExclusion> CopyWithOffset(
+      const NGBfcDelta& offset_delta) const {
+    if (!offset_delta.line_offset_delta && !offset_delta.block_offset_delta)
+      return this;
+
+    NGBfcRect new_rect = rect;
+    new_rect.start_offset += offset_delta;
+    new_rect.end_offset += offset_delta;
+
+    return base::AdoptRef(new NGExclusion(
+        new_rect, type,
+        shape_data ? std::make_unique<NGExclusionShapeData>(
+                         shape_data->layout_box, shape_data->margins,
+                         shape_data->shape_insets)
+                   : nullptr));
+  }
+
   const NGBfcRect rect;
   const EFloat type;
   const std::unique_ptr<NGExclusionShapeData> shape_data;
diff --git a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc
index ec634cd..592344b 100644
--- a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc
+++ b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.cc
@@ -174,7 +174,6 @@
 NGExclusionSpaceInternal::NGExclusionSpaceInternal()
     : exclusions_(RefVector<scoped_refptr<const NGExclusion>>::Create()),
       num_exclusions_(0),
-      both_clear_offset_(LayoutUnit::Min()),
       track_shape_exclusions_(false),
       derived_geometry_(nullptr) {}
 
@@ -182,7 +181,9 @@
     const NGExclusionSpaceInternal& other)
     : exclusions_(other.exclusions_),
       num_exclusions_(other.num_exclusions_),
-      both_clear_offset_(other.both_clear_offset_),
+      left_clear_offset_(other.left_clear_offset_),
+      right_clear_offset_(other.right_clear_offset_),
+      last_float_block_start_(other.last_float_block_start_),
       track_shape_exclusions_(other.track_shape_exclusions_),
       derived_geometry_(std::move(other.derived_geometry_)) {
   // This copy-constructor does fun things. It moves the derived_geometry_ to
@@ -197,7 +198,9 @@
     const NGExclusionSpaceInternal& other) {
   exclusions_ = other.exclusions_;
   num_exclusions_ = other.num_exclusions_;
-  both_clear_offset_ = other.both_clear_offset_;
+  left_clear_offset_ = other.left_clear_offset_;
+  right_clear_offset_ = other.right_clear_offset_;
+  last_float_block_start_ = other.last_float_block_start_;
   track_shape_exclusions_ = other.track_shape_exclusions_;
   derived_geometry_ = std::move(other.derived_geometry_);
   other.derived_geometry_ = nullptr;
@@ -209,10 +212,7 @@
 
 NGExclusionSpaceInternal::DerivedGeometry::DerivedGeometry(
     bool track_shape_exclusions)
-    : track_shape_exclusions_(track_shape_exclusions),
-      last_float_block_start_(LayoutUnit::Min()),
-      left_float_clear_offset_(LayoutUnit::Min()),
-      right_float_clear_offset_(LayoutUnit::Min()) {
+    : track_shape_exclusions_(track_shape_exclusions) {
   // The exclusion space must always have at least one shelf, at -Infinity.
   shelves_.emplace_back(/* block_offset */ LayoutUnit::Min(),
                         track_shape_exclusions_);
@@ -221,17 +221,23 @@
 void NGExclusionSpaceInternal::Add(scoped_refptr<const NGExclusion> exclusion) {
   DCHECK_LE(num_exclusions_, exclusions_->size());
 
-  // Perform a copy-on-write if the number of exclusions has gone out of sync.
-  if (num_exclusions_ != exclusions_->size()) {
-    scoped_refptr<RefVector<scoped_refptr<const NGExclusion>>> exclusions =
-        RefVector<scoped_refptr<const NGExclusion>>::Create();
-    exclusions->GetMutableVector()->AppendRange(
-        exclusions_->GetVector().begin(),
-        exclusions_->GetVector().begin() + num_exclusions_);
-    std::swap(exclusions_, exclusions);
+  bool already_exists = false;
 
-    // The derived_geometry_ member is now invalid.
-    derived_geometry_ = nullptr;
+  if (num_exclusions_ < exclusions_->size()) {
+    if (*exclusion == *exclusions_->at(num_exclusions_) &&
+        !exclusion->shape_data) {
+      // We might be adding an exclusion seen in a previous layout pass.
+      already_exists = true;
+    } else {
+      // Perform a copy-on-write if the number of exclusions has gone out of
+      // sync.
+      scoped_refptr<RefVector<scoped_refptr<const NGExclusion>>> exclusions =
+          RefVector<scoped_refptr<const NGExclusion>>::Create();
+      exclusions->GetMutableVector()->AppendRange(
+          exclusions_->GetVector().begin(),
+          exclusions_->GetVector().begin() + num_exclusions_);
+      std::swap(exclusions_, exclusions);
+    }
   }
 
   // If this is the first exclusion with shape_data, the derived_geometry_
@@ -244,35 +250,31 @@
   if (derived_geometry_)
     derived_geometry_->Add(*exclusion);
 
-  both_clear_offset_ =
-      std::max(both_clear_offset_, exclusion->rect.BlockEndOffset());
+  // Update the members used for clearance calculations.
+  LayoutUnit clear_offset = exclusion->rect.BlockEndOffset();
+  if (exclusion->type == EFloat::kLeft)
+    left_clear_offset_ = std::max(left_clear_offset_, clear_offset);
+  else if (exclusion->type == EFloat::kRight)
+    right_clear_offset_ = std::max(right_clear_offset_, clear_offset);
 
-  exclusions_->emplace_back(std::move(exclusion));
+  last_float_block_start_ =
+      std::max(last_float_block_start_, exclusion->rect.BlockStartOffset());
+
+  if (!already_exists)
+    exclusions_->emplace_back(std::move(exclusion));
   num_exclusions_++;
 }
 
 void NGExclusionSpaceInternal::DerivedGeometry::Add(
     const NGExclusion& exclusion) {
-  last_float_block_start_ =
-      std::max(last_float_block_start_, exclusion.rect.BlockStartOffset());
-
-  const LayoutUnit exclusion_end_offset = exclusion.rect.BlockEndOffset();
-
-  // Update the members used for clearance calculations.
-  if (exclusion.type == EFloat::kLeft) {
-    left_float_clear_offset_ =
-        std::max(left_float_clear_offset_, exclusion.rect.BlockEndOffset());
-  } else if (exclusion.type == EFloat::kRight) {
-    right_float_clear_offset_ =
-        std::max(right_float_clear_offset_, exclusion.rect.BlockEndOffset());
-  }
-
   // If the exclusion takes up no inline space, we shouldn't pay any further
   // attention to it. The only thing it can affect is block-axis positioning of
   // subsequent floats (dealt with above).
   if (exclusion.rect.LineEndOffset() <= exclusion.rect.LineStartOffset())
     return;
 
+  const LayoutUnit exclusion_end_offset = exclusion.rect.BlockEndOffset();
+
 #if DCHECK_IS_ON()
   bool inserted = false;
 #endif
@@ -628,24 +630,6 @@
   }
 }
 
-LayoutUnit NGExclusionSpaceInternal::DerivedGeometry::ClearanceOffset(
-    EClear clear_type) const {
-  switch (clear_type) {
-    case EClear::kNone:
-      return LayoutUnit::Min();  // Nothing to do here.
-    case EClear::kLeft:
-      return left_float_clear_offset_;
-    case EClear::kRight:
-      return right_float_clear_offset_;
-    case EClear::kBoth:
-      return std::max(left_float_clear_offset_, right_float_clear_offset_);
-    default:
-      NOTREACHED();
-  }
-
-  return LayoutUnit::Min();
-}
-
 const NGExclusionSpaceInternal::DerivedGeometry&
 NGExclusionSpaceInternal::GetDerivedGeometry() const {
   // Re-build the geometry if it isn't present.
diff --git a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
index 87b3e4ca..01a7022 100644
--- a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
+++ b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
@@ -42,7 +42,8 @@
       const NGLogicalSize& minimum_size) const {
     // If the area clears all floats, we can just return the layout opportunity
     // which matches the available space.
-    if (offset.block_offset >= both_clear_offset_) {
+    if (offset.block_offset >=
+        std::max(left_clear_offset_, right_clear_offset_)) {
       NGBfcOffset end_offset(
           offset.line_offset + available_inline_size.ClampNegativeToZero(),
           LayoutUnit::Max());
@@ -58,7 +59,8 @@
       const LayoutUnit available_inline_size) const {
     // If the area clears all floats, we can just return a single layout
     // opportunity which matches the available space.
-    if (offset.block_offset >= both_clear_offset_) {
+    if (offset.block_offset >=
+        std::max(left_clear_offset_, right_clear_offset_)) {
       NGBfcOffset end_offset(
           offset.line_offset + available_inline_size.ClampNegativeToZero(),
           LayoutUnit::Max());
@@ -71,18 +73,71 @@
   }
 
   LayoutUnit ClearanceOffset(EClear clear_type) const {
-    if (clear_type == EClear::kNone)
-      return LayoutUnit::Min();
-
-    return GetDerivedGeometry().ClearanceOffset(clear_type);
+    switch (clear_type) {
+      case EClear::kNone:
+        return LayoutUnit::Min();
+      case EClear::kLeft:
+        return left_clear_offset_;
+      case EClear::kRight:
+        return right_clear_offset_;
+      case EClear::kBoth:
+        return std::max(left_clear_offset_, right_clear_offset_);
+      default:
+        NOTREACHED();
+        return LayoutUnit::Min();
+    }
   }
 
-  LayoutUnit LastFloatBlockStart() const {
-    return GetDerivedGeometry().LastFloatBlockStart();
-  }
+  LayoutUnit LastFloatBlockStart() const { return last_float_block_start_; }
 
   bool IsEmpty() const { return !num_exclusions_; }
 
+  // Pre-initializes the exclusions vector to something used in a previous
+  // layout pass, however keeps the number of exclusions as zero.
+  void PreInitialize(const NGExclusionSpaceInternal& other) {
+    DCHECK_EQ(exclusions_->size(), 0u);
+    DCHECK_GT(other.exclusions_->size(), 0u);
+
+    exclusions_ = other.exclusions_;
+  }
+
+  // See |NGExclusionSpace::MoveAndUpdateDerivedGeometry|.
+  void MoveAndUpdateDerivedGeometry(const NGExclusionSpaceInternal& other) {
+    if (!other.derived_geometry_)
+      return;
+
+    derived_geometry_ = std::move(other.derived_geometry_);
+    other.derived_geometry_ = nullptr;
+
+    // Iterate through all the exclusions which were added by the layout, and
+    // update the DerivedGeometry.
+    for (wtf_size_t i = other.num_exclusions_; i < num_exclusions_; ++i) {
+      const NGExclusion& exclusion = *exclusions_->at(i);
+
+      // If we come across an exclusion with shape data, we opt-out of this
+      // optimization.
+      if (!track_shape_exclusions_ && exclusion.shape_data) {
+        track_shape_exclusions_ = true;
+        derived_geometry_ = nullptr;
+        return;
+      }
+
+      derived_geometry_->Add(exclusion);
+    }
+  }
+
+  // See |NGExclusionSpace::MergeExclusionSpaces|.
+  void MergeExclusionSpaces(const NGBfcDelta& offset_delta,
+                            const NGExclusionSpaceInternal& previous_output,
+                            const NGExclusionSpaceInternal* previous_input) {
+    // We need to copy all the exclusions over which were added by the cached
+    // layout result.
+    for (wtf_size_t i = previous_input ? previous_input->num_exclusions_ : 0;
+         i < previous_output.num_exclusions_; ++i) {
+      Add(previous_output.exclusions_->at(i)->CopyWithOffset(offset_delta));
+    }
+  }
+
   bool operator==(const NGExclusionSpaceInternal& other) const;
   bool operator!=(const NGExclusionSpaceInternal& other) const {
     return !(*this == other);
@@ -181,7 +236,16 @@
   // space has, which may differ to the number of exclusions in the Vector.
   scoped_refptr<RefVector<scoped_refptr<const NGExclusion>>> exclusions_;
   wtf_size_t num_exclusions_;
-  LayoutUnit both_clear_offset_;
+
+  // These members are used for keeping track of the "lowest" offset for each
+  // type of float. This is used for implementing float clearance.
+  LayoutUnit left_clear_offset_ = LayoutUnit::Min();
+  LayoutUnit right_clear_offset_ = LayoutUnit::Min();
+
+  // This member is used for implementing the "top edge alignment rule" for
+  // floats. Floats can be positioned at negative offsets, hence is initialized
+  // the minimum value.
+  LayoutUnit last_float_block_start_ = LayoutUnit::Min();
 
   // In order to reduce the amount of copies related to bookkeeping shape data,
   // we initially ignore exclusions with shape data. When we first see an
@@ -196,12 +260,12 @@
   //
   // NGExclusionSpace space1;
   // space1.Add(exclusion1);
-  // space1.LastFloatBlockStart(); // Builds derived_geometry_ to answer query.
+  // space1.FindLayoutOpportunity(); // Builds derived_geometry_.
   //
   // NGExclusionSpace space2(space1); // Moves derived_geometry_ to space2.
   // space2.Add(exclusion2); // Modifies derived_geometry_.
   //
-  // space1.LastFloatBlockStart(); // Re-builds derived_geometry_.
+  // space1.FindLayoutOpportunity(); // Re-builds derived_geometry_.
   //
   // This is efficient (desirable) as the common usage pattern is only the last
   // exclusion space in the copy-chain is used for answering queries. Only when
@@ -230,9 +294,6 @@
                                        const LayoutUnit available_inline_size,
                                        const LambdaFunc&) const;
 
-    LayoutUnit ClearanceOffset(EClear clear_type) const;
-    LayoutUnit LastFloatBlockStart() const { return last_float_block_start_; }
-
     // See NGShelf for a broad description of what shelves are. We always begin
     // with one, which has the internal value of:
     // {
@@ -269,16 +330,6 @@
     Vector<NGLayoutOpportunity, 4> opportunities_;
 
     bool track_shape_exclusions_;
-
-    // This member is used for implementing the "top edge alignment rule" for
-    // floats. Floats can be positioned at negative offsets, hence is
-    // initialized the minimum value.
-    LayoutUnit last_float_block_start_;
-
-    // These members are used for keeping track of the "lowest" offset for each
-    // type of float. This is used for implementing float clearance.
-    LayoutUnit left_float_clear_offset_;
-    LayoutUnit right_float_clear_offset_;
   };
 
   // Returns the derived_geometry_ member, potentially re-built from the
@@ -371,6 +422,83 @@
     return !exclusion_space_ || exclusion_space_->IsEmpty();
   }
 
+  // See |NGExclusionSpaceInternal::PreInitialize|.
+  void PreInitialize(const NGExclusionSpace& other) const {
+    // Don't pre-initialize if we've already got an exclusions vector.
+    if (exclusion_space_)
+      return;
+
+    // Don't pre-initialize if the other exclusion space didn't have an
+    // exclusions vector.
+    if (!other.exclusion_space_)
+      return;
+
+    exclusion_space_ = std::make_unique<NGExclusionSpaceInternal>();
+    exclusion_space_->PreInitialize(*other.exclusion_space_);
+  }
+
+  // Shifts the DerivedGeometry data-structure to this exclusion space, and
+  // adds any new exclusions.
+  void MoveAndUpdateDerivedGeometry(const NGExclusionSpace& other) const {
+    if (!exclusion_space_ || !other.exclusion_space_)
+      return;
+
+    exclusion_space_->MoveAndUpdateDerivedGeometry(*other.exclusion_space_);
+  }
+
+  // This produces a new exclusion space for a |NGLayoutResult| which is being
+  // re-used for caching purposes.
+  //
+  // It takes:
+  //  - |old_output| The exclusion space associated with the cached layout
+  //    result (the output of layout).
+  //  - |old_input| The exclusion space which produced the cached layout result
+  //    (the input into layout).
+  //  - |new_input| The exclusion space which is being used to produce a new
+  //    layout result (the new input into layout).
+  //  - |offset_delta| the amount that the layout result was moved in BFC
+  //    coordinate space.
+  //
+  // |old_output| should contain the *at least* same exclusions as |old_input|
+  // however may have added some more exclusions during its layout.
+  //
+  // This function takes those exclusions added by the cached layout-result
+  // (the difference between |old_output| and |old_input|), and adds them to
+  // |new_input|. It will additionally shift them by |offset_delta|.
+  //
+  // This produces the correct exclusion space "new_output" for the new reused
+  // layout result.
+  static NGExclusionSpace MergeExclusionSpaces(
+      const NGExclusionSpace& old_output,
+      const NGExclusionSpace& old_input,
+      const NGExclusionSpace& new_input,
+      const NGBfcDelta& offset_delta) {
+    // We start building the new exclusion space from the new input, this
+    // (should) have the derived geometry which will move to |new_output|.
+    NGExclusionSpace new_output = new_input;
+
+    // If we didn't have any floats previously, we don't need to add any new
+    // ones, just return the new output.
+    if (!old_output.exclusion_space_)
+      return new_output;
+
+    // If the layout didn't add any new exclusions, we can just return the new
+    // output.
+    if (old_input == old_output)
+      return new_output;
+
+    if (!new_output.exclusion_space_) {
+      new_output.exclusion_space_ =
+          std::make_unique<NGExclusionSpaceInternal>();
+    }
+
+    new_output.exclusion_space_->MergeExclusionSpaces(
+        offset_delta, *old_output.exclusion_space_,
+        old_input.exclusion_space_.get());
+
+    return new_output;
+  }
+
   bool operator==(const NGExclusionSpace& other) const {
     if (exclusion_space_ == other.exclusion_space_)
       return true;
@@ -383,7 +511,7 @@
   }
 
  private:
-  std::unique_ptr<NGExclusionSpaceInternal> exclusion_space_;
+  mutable std::unique_ptr<NGExclusionSpaceInternal> exclusion_space_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space_test.cc b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space_test.cc
index 66247b0..377c48f 100644
--- a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space_test.cc
@@ -341,5 +341,147 @@
                    NGBfcOffset(LayoutUnit(100), LayoutUnit::Max()));
 }
 
+TEST(NGExclusionSpaceTest, PreInitialization) {
+  NGExclusionSpace original_exclusion_space;
+
+  original_exclusion_space.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(), LayoutUnit()),
+                NGBfcOffset(LayoutUnit(20), LayoutUnit(15))),
+      EFloat::kLeft));
+  original_exclusion_space.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(65), LayoutUnit()),
+                NGBfcOffset(LayoutUnit(85), LayoutUnit(15))),
+      EFloat::kRight));
+
+  NGExclusionSpace exclusion_space1;
+  exclusion_space1.PreInitialize(original_exclusion_space);
+  EXPECT_NE(original_exclusion_space, exclusion_space1);
+
+  exclusion_space1.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(), LayoutUnit()),
+                NGBfcOffset(LayoutUnit(20), LayoutUnit(15))),
+      EFloat::kLeft));
+  EXPECT_NE(original_exclusion_space, exclusion_space1);
+
+  // Adding the same exclusions will make the spaces equal.
+  exclusion_space1.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(65), LayoutUnit()),
+                NGBfcOffset(LayoutUnit(85), LayoutUnit(15))),
+      EFloat::kRight));
+  EXPECT_EQ(original_exclusion_space, exclusion_space1);
+
+  // Adding a third exclusion will make the spaces non-equal.
+  exclusion_space1.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(10), LayoutUnit(25)),
+                NGBfcOffset(LayoutUnit(30), LayoutUnit(40))),
+      EFloat::kLeft));
+  EXPECT_NE(original_exclusion_space, exclusion_space1);
+
+  NGExclusionSpace exclusion_space2;
+  exclusion_space2.PreInitialize(original_exclusion_space);
+  EXPECT_NE(original_exclusion_space, exclusion_space2);
+
+  exclusion_space2.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(), LayoutUnit()),
+                NGBfcOffset(LayoutUnit(20), LayoutUnit(15))),
+      EFloat::kLeft));
+  EXPECT_NE(original_exclusion_space, exclusion_space2);
+
+  // Adding a different second exclusion will make the spaces non-equal.
+  exclusion_space2.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(10), LayoutUnit(25)),
+                NGBfcOffset(LayoutUnit(30), LayoutUnit(40))),
+      EFloat::kLeft));
+  EXPECT_NE(original_exclusion_space, exclusion_space2);
+}
+
+TEST(NGExclusionSpaceTest, MergeExclusionSpacesNoPreviousExclusions) {
+  NGExclusionSpace old_input;
+  NGExclusionSpace old_output = old_input;
+
+  old_output.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(10), LayoutUnit(25)),
+                NGBfcOffset(LayoutUnit(30), LayoutUnit(40))),
+      EFloat::kLeft));
+
+  NGExclusionSpace new_input;
+
+  NGExclusionSpace new_output = NGExclusionSpace::MergeExclusionSpaces(
+      old_output, old_input, new_input,
+      /* offset_delta */ {LayoutUnit(10), LayoutUnit(20)});
+
+  // To check the equality pre-initialize a new exclusion space with the
+  // |new_output|, and add the expected exclusions.
+  NGExclusionSpace expected;
+  expected.PreInitialize(new_output);
+  expected.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(20), LayoutUnit(45)),
+                NGBfcOffset(LayoutUnit(40), LayoutUnit(60))),
+      EFloat::kLeft));
+
+  EXPECT_EQ(expected, new_output);
+}
+
+TEST(NGExclusionSpaceTest, MergeExclusionSpacesPreviousExclusions) {
+  NGExclusionSpace old_input;
+  old_input.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(20), LayoutUnit(45)),
+                NGBfcOffset(LayoutUnit(40), LayoutUnit(60))),
+      EFloat::kLeft));
+
+  NGExclusionSpace old_output = old_input;
+  old_output.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(100), LayoutUnit(45)),
+                NGBfcOffset(LayoutUnit(140), LayoutUnit(60))),
+      EFloat::kRight));
+
+  NGExclusionSpace new_input;
+  new_input.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(20), LayoutUnit(45)),
+                NGBfcOffset(LayoutUnit(40), LayoutUnit(50))),
+      EFloat::kLeft));
+
+  NGExclusionSpace new_output = NGExclusionSpace::MergeExclusionSpaces(
+      old_output, old_input, new_input,
+      /* offset_delta */ {LayoutUnit(10), LayoutUnit(20)});
+
+  // To check the equality pre-initialize a new exclusion space with the
+  // |new_output|, and add the expected exclusions.
+  NGExclusionSpace expected;
+  expected.PreInitialize(new_output);
+  expected.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(20), LayoutUnit(45)),
+                NGBfcOffset(LayoutUnit(40), LayoutUnit(50))),
+      EFloat::kLeft));
+  expected.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(110), LayoutUnit(65)),
+                NGBfcOffset(LayoutUnit(150), LayoutUnit(80))),
+      EFloat::kRight));
+
+  EXPECT_EQ(expected, new_output);
+}
+
+TEST(NGExclusionSpaceTest, MergeExclusionSpacesNoOutputExclusions) {
+  NGExclusionSpace old_input;
+  old_input.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(20), LayoutUnit(45)),
+                NGBfcOffset(LayoutUnit(40), LayoutUnit(60))),
+      EFloat::kLeft));
+  old_input.Add(NGExclusion::Create(
+      NGBfcRect(NGBfcOffset(LayoutUnit(100), LayoutUnit(45)),
+                NGBfcOffset(LayoutUnit(140), LayoutUnit(60))),
+      EFloat::kRight));
+
+  NGExclusionSpace old_output = old_input;
+
+  NGExclusionSpace new_input;
+  NGExclusionSpace new_output = NGExclusionSpace::MergeExclusionSpaces(
+      old_output, old_input, new_input,
+      /* offset_delta */ {LayoutUnit(10), LayoutUnit(20)});
+
+  NGExclusionSpace expected;
+  EXPECT_EQ(expected, new_output);
+}
+
 }  // namespace
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h b/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h
index f20e112..ded1830 100644
--- a/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h
+++ b/third_party/blink/renderer/core/layout/ng/geometry/ng_bfc_offset.h
@@ -10,6 +10,16 @@
 
 namespace blink {
 
+struct CORE_EXPORT NGBfcDelta {
+  NGBfcDelta() = default;
+  NGBfcDelta(LayoutUnit line_offset_delta, LayoutUnit block_offset_delta)
+      : line_offset_delta(line_offset_delta),
+        block_offset_delta(block_offset_delta) {}
+
+  LayoutUnit line_offset_delta;
+  LayoutUnit block_offset_delta;
+};
+
 // NGBfcOffset is the position of a rect (typically a fragment) relative to
 // a block formatting context (BFC). BFCs are agnostic to text direction, and
 // uses line_offset instead of inline_offset.
@@ -24,6 +34,16 @@
   LayoutUnit line_offset;
   LayoutUnit block_offset;
 
+  NGBfcOffset& operator+=(const NGBfcDelta& delta) {
+    *this = *this + delta;
+    return *this;
+  }
+
+  NGBfcOffset operator+(const NGBfcDelta& delta) {
+    return {line_offset + delta.line_offset_delta,
+            block_offset + delta.block_offset_delta};
+  }
+
   bool operator==(const NGBfcOffset& other) const;
   bool operator!=(const NGBfcOffset& other) const;
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index feabadc3..75f72d4 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -220,6 +220,16 @@
   if (block_flow && !first_child)
     block_flow->ClearNGInlineNodeData();
 
+  // The exclusion space internally is a pointer to a shared vector, and
+  // equality of exclusion spaces is performed using pointer comparison on this
+  // internal shared vector.
+  // In order for the caching logic to work correctly we need to set the
+  // pointer to the value previous shared vector.
+  if (const NGLayoutResult* previous_result = box_->GetCachedLayoutResult()) {
+    constraint_space.ExclusionSpace().PreInitialize(
+        previous_result->GetConstraintSpaceForCaching().ExclusionSpace());
+  }
+
   scoped_refptr<const NGLayoutResult> layout_result =
       box_->CachedLayoutResult(constraint_space, break_token);
   if (layout_result) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
index 4d4d616..154213ec 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
@@ -421,16 +421,20 @@
   // to verify that any constraint space size (available size, percentage size,
   // and so on) and BFC offset changes won't require re-layout, before skipping.
   bool MaySkipLayout(const NGConstraintSpace& other) const {
-    if (HasRareData() && other.HasRareData()) {
-      if (!rare_data_->MaySkipLayout(*other.rare_data_))
-        return false;
-    } else if (HasRareData() != other.HasRareData()) {
-      // We have a bfc_offset_, and a rare_data_ (or vice-versa).
+    if (!bitfields_.MaySkipLayout(other.bitfields_))
       return false;
-    }
 
-    return exclusion_space_ == other.exclusion_space_ &&
-           bitfields_.MaySkipLayout(other.bitfields_);
+    if (!HasRareData() && !other.HasRareData())
+      return true;
+
+    if (HasRareData() && other.HasRareData())
+      return rare_data_->MaySkipLayout(*other.rare_data_);
+
+    if (HasRareData())
+      return rare_data_->IsInitialForMaySkipLayout();
+
+    DCHECK(other.HasRareData());
+    return other.rare_data_->IsInitialForMaySkipLayout();
   }
 
   bool AreSizesEqual(const NGConstraintSpace& other) const {
@@ -526,13 +530,21 @@
     bool MaySkipLayout(const RareData& other) const {
       return margin_strut == other.margin_strut &&
              floats_bfc_block_offset == other.floats_bfc_block_offset &&
-             clearance_offset == other.clearance_offset &&
              fragmentainer_block_size == other.fragmentainer_block_size &&
              fragmentainer_space_at_bfc_start ==
                  other.fragmentainer_space_at_bfc_start &&
              block_direction_fragmentation_type ==
                  other.block_direction_fragmentation_type;
     }
+
+    // Must be kept in sync with members checked within |MaySkipLayout|.
+    bool IsInitialForMaySkipLayout() const {
+      return margin_strut == NGMarginStrut() &&
+             floats_bfc_block_offset == base::nullopt &&
+             fragmentainer_block_size == NGSizeIndefinite &&
+             fragmentainer_space_at_bfc_start == NGSizeIndefinite &&
+             block_direction_fragmentation_type == kFragmentNone;
+    }
   };
 
   // This struct simply allows us easily copy, compare, and initialize all the
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
index 77f9b791..f6d3b47 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
@@ -87,6 +87,10 @@
       !child.PhysicalFragment()->IsOutOfFlowPositioned())
     has_child_that_depends_on_percentage_block_size_ = true;
 
+  if (child.MayHaveDescendantAboveBlockStart() &&
+      !child.PhysicalFragment()->IsBlockFormattingContextRoot())
+    may_have_descendant_above_block_start_ = true;
+
   return AddChild(child.PhysicalFragment(), child_offset);
 }
 
@@ -127,6 +131,9 @@
     }
   }
 
+  if (child_offset.block_offset < LayoutUnit())
+    may_have_descendant_above_block_start_ = true;
+
   if (!IsParallelWritingMode(child->Style().GetWritingMode(),
                              Style().GetWritingMode()))
     has_orthogonal_flow_roots_ = true;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
index ebff9391..6e9b284 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
@@ -248,6 +248,7 @@
   bool has_orthogonal_flow_roots_ = false;
   bool has_child_that_depends_on_percentage_block_size_ = false;
   bool has_block_fragmentation_ = false;
+  bool may_have_descendant_above_block_start_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc
index 83b2b7e0..e8c6ba8 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc
@@ -143,7 +143,7 @@
 
 // Creates an exclusion from the fragment that will be placed in the provided
 // layout opportunity.
-scoped_refptr<NGExclusion> CreateExclusion(
+scoped_refptr<const NGExclusion> CreateExclusion(
     const NGLogicalSize& float_available_size,
     const NGLogicalSize& float_percentage_size,
     const NGLogicalSize& float_replaced_percentage_size,
@@ -291,7 +291,7 @@
   }
 
   // Add the float as an exclusion.
-  scoped_refptr<NGExclusion> exclusion = CreateExclusion(
+  scoped_refptr<const NGExclusion> exclusion = CreateExclusion(
       float_available_size, float_percentage_size,
       float_replaced_percentage_size, float_fragment, float_margin_bfc_offset,
       fragment_margins, *unpositioned_float, parent_space, parent_style,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc
index 87342a0b..47ec7e2 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc
@@ -46,14 +46,19 @@
       << "Use the other constructor for successful layout";
 }
 
-NGLayoutResult::NGLayoutResult(const NGLayoutResult& other,
-                               LayoutUnit bfc_line_offset,
-                               base::Optional<LayoutUnit> bfc_block_offset)
+NGLayoutResult::NGLayoutResult(
+    const NGLayoutResult& other,
+    const NGExclusionSpace& new_input_exclusion_space,
+    LayoutUnit bfc_line_offset,
+    base::Optional<LayoutUnit> bfc_block_offset)
     : space_(other.space_),
       physical_fragment_(other.physical_fragment_),
       oof_positioned_descendants_(other.oof_positioned_descendants_),
       unpositioned_list_marker_(other.unpositioned_list_marker_),
-      exclusion_space_(other.exclusion_space_),
+      exclusion_space_(MergeExclusionSpaces(other,
+                                            new_input_exclusion_space,
+                                            bfc_line_offset,
+                                            bfc_block_offset)),
       bfc_line_offset_(bfc_line_offset),
       bfc_block_offset_(bfc_block_offset),
       end_margin_strut_(other.end_margin_strut_),
@@ -66,6 +71,8 @@
       is_pushed_by_floats_(other.is_pushed_by_floats_),
       adjoining_floats_(other.adjoining_floats_),
       has_orthogonal_flow_roots_(other.has_orthogonal_flow_roots_),
+      may_have_descendant_above_block_start_(
+          other.may_have_descendant_above_block_start_),
       depends_on_percentage_block_size_(
           other.depends_on_percentage_block_size_),
       status_(other.status_) {}
@@ -85,6 +92,8 @@
       is_pushed_by_floats_(builder->is_pushed_by_floats_),
       adjoining_floats_(builder->adjoining_floats_),
       has_orthogonal_flow_roots_(builder->has_orthogonal_flow_roots_),
+      may_have_descendant_above_block_start_(
+          builder->may_have_descendant_above_block_start_),
       depends_on_percentage_block_size_(DependsOnPercentageBlockSize(*builder)),
       status_(kSuccess) {}
 
@@ -130,4 +139,27 @@
   return false;
 }
 
+NGExclusionSpace NGLayoutResult::MergeExclusionSpaces(
+    const NGLayoutResult& other,
+    const NGExclusionSpace& new_input_exclusion_space,
+    LayoutUnit bfc_line_offset,
+    base::Optional<LayoutUnit> bfc_block_offset) {
+  // If we are merging exclusion spaces we should be copying a previous layout
+  // result. It is impossible to reach a state where bfc_block_offset has a
+  // value, and the result which we are copying doesn't (or visa versa).
+  // This would imply the result has switched its "empty" state for margin
+  // collapsing, which would mean it isn't possible to reuse the result.
+  DCHECK_EQ(bfc_block_offset.has_value(), other.bfc_block_offset_.has_value());
+
+  NGBfcDelta offset_delta = {bfc_line_offset - other.bfc_line_offset_,
+                             bfc_block_offset && other.bfc_block_offset_
+                                 ? *bfc_block_offset - *other.bfc_block_offset_
+                                 : LayoutUnit()};
+
+  return NGExclusionSpace::MergeExclusionSpaces(
+      /* old_output */ other.exclusion_space_,
+      /* old_input */ other.space_.ExclusionSpace(),
+      /* new_input */ new_input_exclusion_space, offset_delta);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_result.h b/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
index c7865c0..cb33a38 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
@@ -43,7 +43,8 @@
   // Create a copy of NGLayoutResult with |BfcBlockOffset| replaced by the given
   // parameter. Note, when |bfc_block_offset| is |nullopt|, |BfcBlockOffset| is
   // still replaced with |nullopt|.
-  NGLayoutResult(const NGLayoutResult&,
+  NGLayoutResult(const NGLayoutResult& other,
+                 const NGExclusionSpace& new_input_exclusion_space,
                  LayoutUnit bfc_line_offset,
                  base::Optional<LayoutUnit> bfc_block_offset);
   ~NGLayoutResult();
@@ -116,6 +117,12 @@
     return depends_on_percentage_block_size_;
   }
 
+  // Returns true if we have a descendant within this formatting context, which
+  // is potentially above our block-start edge.
+  bool MayHaveDescendantAboveBlockStart() const {
+    return may_have_descendant_above_block_start_;
+  }
+
   // Returns true if the space stored with this layout result, is valid.
   bool HasValidConstraintSpaceForCaching() const { return has_valid_space_; }
 
@@ -147,6 +154,12 @@
 
   static bool DependsOnPercentageBlockSize(const NGContainerFragmentBuilder&);
 
+  static NGExclusionSpace MergeExclusionSpaces(
+      const NGLayoutResult& other,
+      const NGExclusionSpace& new_input_exclusion_space,
+      LayoutUnit bfc_line_offset,
+      base::Optional<LayoutUnit> bfc_block_offset);
+
   // The constraint space which generated this layout result, may not be valid
   // as indicated by |has_valid_space_|.
   const NGConstraintSpace space_;
@@ -173,6 +186,7 @@
   unsigned adjoining_floats_ : 2;  // NGFloatTypes
 
   unsigned has_orthogonal_flow_roots_ : 1;
+  unsigned may_have_descendant_above_block_start_ : 1;
   unsigned depends_on_percentage_block_size_ : 1;
 
   unsigned status_ : 1;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc
new file mode 100644
index 0000000..c28116a
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_result_caching_test.cc
@@ -0,0 +1,398 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
+
+namespace blink {
+namespace {
+
+// These tests exercise the caching logic of |NGLayoutResult|s. They are
+// rendering tests which contain two children: "test" and "src".
+//
+// Both have layout initially performed on them, however the "src" will have a
+// different |NGConstraintSpace| which is then used to test either a cache hit
+// or miss.
+class NGLayoutResultCachingTest : public NGLayoutTest {};
+
+TEST_F(NGLayoutResultCachingTest, HitDifferentExclusionSpace) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Same BFC offset, different exclusion space.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+      <div id="test" style="height: 20px;"></div>
+    </div>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 30px;"></div>
+      </div>
+      <div id="src" style="height: 20px;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_NE(result.get(), nullptr);
+  EXPECT_EQ(result->BfcBlockOffset().value(), LayoutUnit(50));
+  EXPECT_EQ(result->BfcLineOffset(), LayoutUnit());
+}
+
+TEST_F(NGLayoutResultCachingTest, HitDifferentBFCOffset) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Different BFC offset, same exclusion space.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+      <div id="test" style="height: 20px; padding-top: 5px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+    </div>
+    <div class="bfc">
+      <div style="height: 40px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+      <div id="src" style="height: 20px; padding-top: 5px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_NE(result.get(), nullptr);
+  EXPECT_EQ(result->BfcBlockOffset().value(), LayoutUnit(40));
+  EXPECT_EQ(result->BfcLineOffset(), LayoutUnit());
+
+  // Also check that the exclusion(s) got moved correctly.
+  LayoutOpportunityVector opportunities =
+      result->ExclusionSpace().AllLayoutOpportunities(
+          /* offset */ {LayoutUnit(), LayoutUnit()},
+          /* available_inline_size */ LayoutUnit(100));
+
+  EXPECT_EQ(opportunities.size(), 3u);
+  EXPECT_EQ(opportunities[0].rect.start_offset,
+            NGBfcOffset(LayoutUnit(50), LayoutUnit()));
+  EXPECT_EQ(opportunities[0].rect.end_offset,
+            NGBfcOffset(LayoutUnit(100), LayoutUnit::Max()));
+
+  EXPECT_EQ(opportunities[1].rect.start_offset,
+            NGBfcOffset(LayoutUnit(), LayoutUnit(20)));
+  EXPECT_EQ(opportunities[1].rect.end_offset,
+            NGBfcOffset(LayoutUnit(100), LayoutUnit(45)));
+
+  EXPECT_EQ(opportunities[2].rect.start_offset,
+            NGBfcOffset(LayoutUnit(), LayoutUnit(65)));
+  EXPECT_EQ(opportunities[2].rect.end_offset,
+            NGBfcOffset(LayoutUnit(100), LayoutUnit::Max()));
+}
+
+TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart1) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Same BFC offset, different exclusion space, descendant above
+  // block start.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+      <div id="test" style="height: 20px; padding-top: 5px;">
+        <div style="height: 10px; margin-top: -10px;"></div>
+      </div>
+    </div>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 30px;"></div>
+      </div>
+      <div id="src" style="height: 20px;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+TEST_F(NGLayoutResultCachingTest, MissDescendantAboveBlockStart2) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Different BFC offset, same exclusion space, descendant above
+  // block start.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+      <div id="test" style="height: 20px; padding-top: 5px;">
+        <div style="height: 10px; margin-top: -10px;"></div>
+      </div>
+    </div>
+    <div class="bfc">
+      <div style="height: 40px;">
+        <div class="float" style="height: 20px;"></div>
+      </div>
+      <div id="src" style="height: 20px;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding1) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Same BFC offset, different exclusion space, float initially
+  // intruding.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 60px;"></div>
+      </div>
+      <div id="test" style="height: 20px;"></div>
+    </div>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 30px;"></div>
+      </div>
+      <div id="src" style="height: 20px;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+TEST_F(NGLayoutResultCachingTest, MissFloatInitiallyIntruding2) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Different BFC offset, same exclusion space, float initially
+  // intruding.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 60px;"></div>
+      </div>
+      <div id="test" style="height: 60px;"></div>
+    </div>
+    <div class="bfc">
+      <div style="height: 70px;">
+        <div class="float" style="height: 60px;"></div>
+      </div>
+      <div id="src" style="height: 20px;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude1) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Same BFC offset, different exclusion space, float will intrude.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 40px;"></div>
+      </div>
+      <div id="test" style="height: 20px;"></div>
+    </div>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 60px;"></div>
+      </div>
+      <div id="src" style="height: 20px;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+TEST_F(NGLayoutResultCachingTest, MissFloatWillIntrude2) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Different BFC offset, same exclusion space, float will intrude.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 40px;"></div>
+      </div>
+      <div id="test" style="height: 60px;"></div>
+    </div>
+    <div class="bfc">
+      <div style="height: 30px;">
+        <div class="float" style="height: 40px;"></div>
+      </div>
+      <div id="src" style="height: 20px;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+TEST_F(NGLayoutResultCachingTest, MissPushedByFloats1) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Same BFC offset, different exclusion space, pushed by floats.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 60px;"></div>
+      </div>
+      <div id="test" style="height: 20px; clear: left;"></div>
+    </div>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 40px;"></div>
+      </div>
+      <div id="src" style="height: 20px; clear: left;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+TEST_F(NGLayoutResultCachingTest, MissPushedByFloats2) {
+  ScopedLayoutNGFragmentCachingForTest layout_ng_fragment_caching(true);
+
+  // Different BFC offset, same exclusion space, pushed by floats.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .bfc { display: flow-root; width: 300px; height: 300px; }
+      .float { float: left; width: 50px; }
+    </style>
+    <div class="bfc">
+      <div style="height: 50px;">
+        <div class="float" style="height: 60px;"></div>
+      </div>
+      <div id="test" style="height: 20px; clear: left;"></div>
+    </div>
+    <div class="bfc">
+      <div style="height: 30px;">
+        <div class="float" style="height: 60px;"></div>
+      </div>
+      <div id="src" style="height: 20px; clear: left;"></div>
+    </div>
+  )HTML");
+
+  LayoutBlockFlow* test = ToLayoutBlockFlow(GetLayoutObjectByElementId("test"));
+  LayoutBlockFlow* src = ToLayoutBlockFlow(GetLayoutObjectByElementId("src"));
+
+  const NGConstraintSpace& space =
+      src->GetCachedLayoutResult()->GetConstraintSpaceForCaching();
+  scoped_refptr<const NGLayoutResult> result =
+      test->CachedLayoutResult(space, nullptr);
+
+  EXPECT_EQ(result.get(), nullptr);
+}
+
+}  // namespace
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/empty_clients.h b/third_party/blink/renderer/core/loader/empty_clients.h
index 0fe23a5..361a47fa 100644
--- a/third_party/blink/renderer/core/loader/empty_clients.h
+++ b/third_party/blink/renderer/core/loader/empty_clients.h
@@ -138,7 +138,6 @@
   Page* CreateWindowDelegate(LocalFrame*,
                              const FrameLoadRequest&,
                              const WebWindowFeatures&,
-                             NavigationPolicy,
                              SandboxFlags,
                              const FeaturePolicy::FeatureState&,
                              const SessionStorageNamespaceId&) override {
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index d7fa95b0..9188670 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -840,6 +840,7 @@
   if (!target_frame && !request.FrameName().IsEmpty() &&
       should_navigate_target_frame) {
     request.SetFrameType(network::mojom::RequestContextFrameType::kAuxiliary);
+    request.SetNavigationPolicy(kNavigationPolicyNewForegroundTab);
     CreateWindowForRequest(request, *frame_);
     return;  // Navigation will be handled by the new frame/window.
   }
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_content.cc b/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
index 43ceb0c..24b9e00 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_content.cc
@@ -246,7 +246,7 @@
 }
 
 IntSize ImageResourceContent::IntrinsicSize(
-    RespectImageOrientationEnum should_respect_image_orientation) {
+    RespectImageOrientationEnum should_respect_image_orientation) const {
   if (!image_)
     return IntSize();
   if (should_respect_image_orientation == kRespectImageOrientation &&
@@ -507,10 +507,11 @@
     resource_length = static_cast<double>(GetImage()->Data()->size());
   }
 
-  // Calculate the image's adjusted compression ratio (in bytes per pixel). A
-  // constant allowance (1024 bytes) is provided to allow room for headers and
-  // to account for small images (which are harder to compress).
-  double compression_ratio = (resource_length - 1024) / pixels;
+  // Calculate the image's compression ratio (in bytes per pixel) with both 1k
+  // and 10k overhead. The constant overhead allowance is provided to allow room
+  // for headers and to account for small images (which are harder to compress).
+  double compression_ratio_1k = (resource_length - 1024) / pixels;
+  double compression_ratio_10k = (resource_length - 10240) / pixels;
 
   // Note that this approach may not always correctly identify the image (for
   // example, due to a misconfigured web server). This approach SHOULD work in
@@ -522,7 +523,13 @@
     // Enforce the lossy image policy.
     return context.IsFeatureEnabled(
         mojom::FeaturePolicyFeature::kUnoptimizedLossyImages,
-        PolicyValue(compression_ratio), ReportOptions::kReportOnFailure);
+        PolicyValue(compression_ratio_1k), ReportOptions::kReportOnFailure);
+  }
+  if (MIMETypeRegistry::IsLosslessImageMIMEType(mime_type)) {
+    // Enforce the lossless image policy.
+    return context.IsFeatureEnabled(
+        mojom::FeaturePolicyFeature::kUnoptimizedLosslessImages,
+        PolicyValue(compression_ratio_10k), ReportOptions::kReportOnFailure);
   }
 
   return true;
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_content.h b/third_party/blink/renderer/core/loader/resource/image_resource_content.h
index b32c43ae..9e1aa362 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_content.h
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_content.h
@@ -74,7 +74,7 @@
   // object size resolved using a default object size of 300x150.
   // TODO(fs): Make SVGImages return proper intrinsic width/height.
   IntSize IntrinsicSize(
-      RespectImageOrientationEnum should_respect_image_orientation);
+      RespectImageOrientationEnum should_respect_image_orientation) const;
 
   void UpdateImageAnimationPolicy();
 
diff --git a/third_party/blink/renderer/core/loader/resource/script_resource.cc b/third_party/blink/renderer/core/loader/resource/script_resource.cc
index e684b18..dc76a049 100644
--- a/third_party/blink/renderer/core/loader/resource/script_resource.cc
+++ b/third_party/blink/renderer/core/loader/resource/script_resource.cc
@@ -249,7 +249,8 @@
 }
 
 void ScriptResource::ResponseBodyReceived(
-    ResponseBodyLoaderDrainableInterface& body_loader) {
+    ResponseBodyLoaderDrainableInterface& body_loader,
+    scoped_refptr<base::SingleThreadTaskRunner> loader_task_runner) {
   ResponseBodyLoaderClient* response_body_loader_client;
   CHECK(!data_pipe_);
   data_pipe_ = body_loader.DrainAsDataPipe(&response_body_loader_client);
@@ -258,8 +259,7 @@
 
   response_body_loader_client_ = response_body_loader_client;
   watcher_ = std::make_unique<mojo::SimpleWatcher>(
-      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL,
-      Loader()->GetLoadingTaskRunner());
+      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL, loader_task_runner);
 
   watcher_->Watch(data_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
                   MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
diff --git a/third_party/blink/renderer/core/loader/resource/script_resource.h b/third_party/blink/renderer/core/loader/resource/script_resource.h
index 9ed6d69..1aa8e72 100644
--- a/third_party/blink/renderer/core/loader/resource/script_resource.h
+++ b/third_party/blink/renderer/core/loader/resource/script_resource.h
@@ -94,7 +94,8 @@
   ~ScriptResource() override;
 
   void ResponseBodyReceived(
-      ResponseBodyLoaderDrainableInterface& body_loader) override;
+      ResponseBodyLoaderDrainableInterface& body_loader,
+      scoped_refptr<base::SingleThreadTaskRunner> loader_task_runner) override;
 
   void Trace(blink::Visitor*) override;
 
diff --git a/third_party/blink/renderer/core/page/BUILD.gn b/third_party/blink/renderer/core/page/BUILD.gn
index 0dbf16e1..ed595c3 100644
--- a/third_party/blink/renderer/core/page/BUILD.gn
+++ b/third_party/blink/renderer/core/page/BUILD.gn
@@ -91,6 +91,8 @@
     "scrolling/text_fragment_anchor.h",
     "scrolling/text_fragment_finder.cc",
     "scrolling/text_fragment_finder.h",
+    "scrolling/text_fragment_selector.cc",
+    "scrolling/text_fragment_selector.h",
     "scrolling/top_document_root_scroller_controller.cc",
     "scrolling/top_document_root_scroller_controller.h",
     "scrolling/viewport_scroll_callback.cc",
diff --git a/third_party/blink/renderer/core/page/chrome_client.cc b/third_party/blink/renderer/core/page/chrome_client.cc
index b5dc2e2..8bf9ac4 100644
--- a/third_party/blink/renderer/core/page/chrome_client.cc
+++ b/third_party/blink/renderer/core/page/chrome_client.cc
@@ -103,7 +103,6 @@
     LocalFrame* frame,
     const FrameLoadRequest& r,
     const WebWindowFeatures& features,
-    NavigationPolicy navigation_policy,
     SandboxFlags sandbox_flags,
     const FeaturePolicy::FeatureState& opener_feature_state,
     const SessionStorageNamespaceId& session_storage_namespace_id) {
@@ -112,8 +111,8 @@
     return nullptr;
   }
 
-  return CreateWindowDelegate(frame, r, features, navigation_policy,
-                              sandbox_flags, opener_feature_state,
+  return CreateWindowDelegate(frame, r, features, sandbox_flags,
+                              opener_feature_state,
                               session_storage_namespace_id);
 }
 
diff --git a/third_party/blink/renderer/core/page/chrome_client.h b/third_party/blink/renderer/core/page/chrome_client.h
index afec0ec..2478359 100644
--- a/third_party/blink/renderer/core/page/chrome_client.h
+++ b/third_party/blink/renderer/core/page/chrome_client.h
@@ -159,7 +159,6 @@
   Page* CreateWindow(LocalFrame*,
                      const FrameLoadRequest&,
                      const WebWindowFeatures&,
-                     NavigationPolicy,
                      SandboxFlags,
                      const FeaturePolicy::FeatureState&,
                      const SessionStorageNamespaceId&);
@@ -413,7 +412,6 @@
   virtual Page* CreateWindowDelegate(LocalFrame*,
                                      const FrameLoadRequest&,
                                      const WebWindowFeatures&,
-                                     NavigationPolicy,
                                      SandboxFlags,
                                      const FeaturePolicy::FeatureState&,
                                      const SessionStorageNamespaceId&) = 0;
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.cc b/third_party/blink/renderer/core/page/chrome_client_impl.cc
index 58f973b..65122bd 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.cc
@@ -252,7 +252,6 @@
     LocalFrame* frame,
     const FrameLoadRequest& r,
     const WebWindowFeatures& features,
-    NavigationPolicy navigation_policy,
     SandboxFlags sandbox_flags,
     const FeaturePolicy::FeatureState& opener_feature_state,
     const SessionStorageNamespaceId& session_storage_namespace_id) {
@@ -270,7 +269,7 @@
       static_cast<WebViewImpl*>(web_view_->Client()->CreateView(
           WebLocalFrameImpl::FromFrame(frame),
           WrappedResourceRequest(r.GetResourceRequest()), features, frame_name,
-          static_cast<WebNavigationPolicy>(navigation_policy),
+          static_cast<WebNavigationPolicy>(r.GetNavigationPolicy()),
           r.GetShouldSetOpener() == kNeverSetOpener,
           static_cast<WebSandboxFlags>(sandbox_flags), opener_feature_state,
           session_storage_namespace_id));
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.h b/third_party/blink/renderer/core/page/chrome_client_impl.h
index c118059..1e6bee5 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.h
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.h
@@ -80,7 +80,6 @@
   Page* CreateWindowDelegate(LocalFrame*,
                              const FrameLoadRequest&,
                              const WebWindowFeatures&,
-                             NavigationPolicy,
                              SandboxFlags,
                              const FeaturePolicy::FeatureState&,
                              const SessionStorageNamespaceId&) override;
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl_test.cc b/third_party/blink/renderer/core/page/chrome_client_impl_test.cc
index c72505f..1814c9f 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl_test.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl_test.cc
@@ -92,11 +92,11 @@
   ScopedPagePauser pauser;
   LocalFrame* frame = To<WebLocalFrameImpl>(main_frame_)->GetFrame();
   FrameLoadRequest request(frame->GetDocument());
+  request.SetNavigationPolicy(kNavigationPolicyNewForegroundTab);
   WebWindowFeatures features;
-  EXPECT_EQ(nullptr,
-            chrome_client_impl_->CreateWindow(
-                frame, request, features, kNavigationPolicyNewForegroundTab,
-                kSandboxNone, FeaturePolicy::FeatureState(), ""));
+  EXPECT_EQ(nullptr, chrome_client_impl_->CreateWindow(
+                         frame, request, features, kSandboxNone,
+                         FeaturePolicy::FeatureState(), ""));
 }
 
 class FakeColorChooserClient
diff --git a/third_party/blink/renderer/core/page/create_window.cc b/third_party/blink/renderer/core/page/create_window.cc
index 0cea61a..ac65f93 100644
--- a/third_party/blink/renderer/core/page/create_window.cc
+++ b/third_party/blink/renderer/core/page/create_window.cc
@@ -49,10 +49,8 @@
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
-#include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
-#include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
@@ -185,27 +183,6 @@
   return window_features;
 }
 
-static Frame* ReuseExistingWindow(LocalFrame& active_frame,
-                                  LocalFrame& lookup_frame,
-                                  const AtomicString& frame_name,
-                                  const KURL& destination_url) {
-  if (!frame_name.IsEmpty() && !EqualIgnoringASCIICase(frame_name, "_blank")) {
-    if (Frame* frame = lookup_frame.FindFrameForNavigation(
-            frame_name, active_frame, destination_url)) {
-      if (!EqualIgnoringASCIICase(frame_name, "_self")) {
-        if (Page* page = frame->GetPage()) {
-          if (page == active_frame.GetPage())
-            page->GetFocusController().SetFocusedFrame(frame);
-          else
-            page->GetChromeClient().Focus(&active_frame);
-        }
-      }
-      return frame;
-    }
-  }
-  return nullptr;
-}
-
 static void MaybeLogWindowOpen(LocalFrame& opener_frame) {
   AdTracker* ad_tracker = opener_frame.GetAdTracker();
   if (!ad_tracker)
@@ -233,15 +210,30 @@
 static Frame* CreateNewWindow(LocalFrame& opener_frame,
                               const FrameLoadRequest& request,
                               const WebWindowFeatures& features,
-                              bool force_new_foreground_tab,
                               bool& created) {
-  Page* old_page = opener_frame.GetPage();
-  if (!old_page)
-    return nullptr;
+  DCHECK(request.GetResourceRequest().RequestorOrigin() ||
+         opener_frame.GetDocument()->Url().IsEmpty());
+  DCHECK_EQ(request.GetFrameType(),
+            network::mojom::RequestContextFrameType::kAuxiliary);
 
-  NavigationPolicy policy = force_new_foreground_tab
-                                ? kNavigationPolicyNewForegroundTab
-                                : NavigationPolicyForCreateWindow(features);
+  const KURL& url = request.GetResourceRequest().Url();
+  probe::WindowOpen(opener_frame.GetDocument(), url, request.FrameName(),
+                    features,
+                    LocalFrame::HasTransientUserActivation(&opener_frame));
+  created = false;
+
+  // Sandboxed frames cannot open new auxiliary browsing contexts.
+  if (opener_frame.GetDocument()->IsSandboxed(kSandboxPopups)) {
+    // FIXME: This message should be moved off the console once a solution to
+    // https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
+    opener_frame.GetDocument()->AddConsoleMessage(ConsoleMessage::Create(
+        mojom::ConsoleMessageSource::kSecurity,
+        mojom::ConsoleMessageLevel::kError,
+        "Blocked opening '" + url.ElidedString() +
+            "' in a new window because the request was made in a sandboxed "
+            "frame whose 'allow-popups' permission is not set."));
+    return nullptr;
+  }
 
   bool propagate_sandbox = opener_frame.GetDocument()->IsSandboxed(
       kSandboxPropagatesToAuxiliaryBrowsingContexts);
@@ -258,6 +250,7 @@
   SessionStorageNamespaceId new_namespace_id =
       AllocateSessionStorageNamespaceId();
 
+  Page* old_page = opener_frame.GetPage();
   if (base::FeatureList::IsEnabled(features::kOnionSoupDOMStorage)) {
     // TODO(dmurph): Don't copy session storage when features.noopener is true:
     // https://html.spec.whatwg.org/C/#copy-session-storage
@@ -267,8 +260,8 @@
   }
 
   Page* page = old_page->GetChromeClient().CreateWindow(
-      &opener_frame, request, features, policy, sandbox_flags,
-      opener_feature_state, new_namespace_id);
+      &opener_frame, request, features, sandbox_flags, opener_feature_state,
+      new_namespace_id);
   if (!page)
     return nullptr;
 
@@ -299,90 +292,21 @@
     window_rect.SetHeight(features.height);
 
   page->GetChromeClient().SetWindowRectWithAdjustment(window_rect, frame);
-  page->GetChromeClient().Show(policy);
+  page->GetChromeClient().Show(request.GetNavigationPolicy());
 
   MaybeLogWindowOpen(opener_frame);
   created = true;
   return &frame;
 }
 
-static Frame* CreateWindowHelper(LocalFrame& opener_frame,
-                                 LocalFrame& active_frame,
-                                 LocalFrame& lookup_frame,
-                                 const FrameLoadRequest& request,
-                                 const WebWindowFeatures& features,
-                                 bool force_new_foreground_tab,
-                                 bool& created) {
-  DCHECK(request.GetResourceRequest().RequestorOrigin() ||
-         opener_frame.GetDocument()->Url().IsEmpty());
-  DCHECK_EQ(request.GetFrameType(),
-            network::mojom::RequestContextFrameType::kAuxiliary);
-  probe::WindowOpen(opener_frame.GetDocument(),
-                    request.GetResourceRequest().Url(), request.FrameName(),
-                    features,
-                    LocalFrame::HasTransientUserActivation(&opener_frame));
-  created = false;
-
-  Frame* window =
-      features.noopener || force_new_foreground_tab
-          ? nullptr
-          : ReuseExistingWindow(active_frame, lookup_frame, request.FrameName(),
-                                request.GetResourceRequest().Url());
-
-  if (!window) {
-    // Sandboxed frames cannot open new auxiliary browsing contexts.
-    if (opener_frame.GetDocument()->IsSandboxed(kSandboxPopups)) {
-      // FIXME: This message should be moved off the console once a solution to
-      // https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
-      opener_frame.GetDocument()->AddConsoleMessage(ConsoleMessage::Create(
-          mojom::ConsoleMessageSource::kSecurity,
-          mojom::ConsoleMessageLevel::kError,
-          "Blocked opening '" +
-              request.GetResourceRequest().Url().ElidedString() +
-              "' in a new window because the request was made in a sandboxed "
-              "frame whose 'allow-popups' permission is not set."));
-      return nullptr;
-    }
-  }
-
-  if (window) {
-    // JS can run inside ReuseExistingWindow (via onblur), which can detach
-    // the target window.
-    if (!window->Client())
-      return nullptr;
-    if (request.GetShouldSetOpener() == kMaybeSetOpener)
-      window->Client()->SetOpener(&opener_frame);
-    return window;
-  }
-
-  return CreateNewWindow(opener_frame, request, features,
-                         force_new_foreground_tab, created);
-}
-
-DOMWindow* CreateWindow(const String& url_string,
+DOMWindow* CreateWindow(const KURL& completed_url,
                         const AtomicString& frame_name,
-                        const String& window_features_string,
+                        const WebWindowFeatures& window_features,
                         LocalDOMWindow& incumbent_window,
-                        LocalFrame& entered_window_frame,
-                        LocalFrame& opener_frame,
-                        ExceptionState& exception_state) {
+                        LocalFrame& opener_frame) {
   LocalFrame* active_frame = incumbent_window.GetFrame();
   DCHECK(active_frame);
 
-  KURL completed_url =
-      url_string.IsEmpty()
-          ? KURL(g_empty_string)
-          : entered_window_frame.GetDocument()->CompleteURL(url_string);
-  if (!completed_url.IsEmpty() && !completed_url.IsValid()) {
-    UseCounter::Count(incumbent_window.document(),
-                      WebFeature::kWindowOpenWithInvalidURL);
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kSyntaxError,
-        "Unable to open a window with invalid URL '" +
-            completed_url.GetString() + "'.\n");
-    return nullptr;
-  }
-
   if (completed_url.ProtocolIsJavaScript() &&
       opener_frame.GetDocument()->GetContentSecurityPolicy() &&
       !ContentSecurityPolicy::ShouldBypassMainWorld(
@@ -398,11 +322,10 @@
     }
   }
 
-  WebWindowFeatures window_features =
-      GetWindowFeaturesFromString(window_features_string);
-
   FrameLoadRequest frame_request(incumbent_window.document(),
                                  ResourceRequest(completed_url), frame_name);
+  frame_request.SetNavigationPolicy(
+      NavigationPolicyForCreateWindow(window_features));
   frame_request.SetShouldSetOpener(window_features.noopener ? kNeverSetOpener
                                                             : kMaybeSetOpener);
   frame_request.SetFrameType(
@@ -431,16 +354,15 @@
   // different from the opener frame, and the name references a frame relative
   // to the opener frame.
   bool created;
-  Frame* new_frame = CreateWindowHelper(
-      opener_frame, *active_frame, opener_frame, frame_request, window_features,
-      false /* force_new_foreground_tab */, created);
+  Frame* new_frame =
+      CreateNewWindow(opener_frame, frame_request, window_features, created);
   if (!new_frame)
     return nullptr;
   if (new_frame->DomWindow()->IsInsecureScriptAccess(incumbent_window,
                                                      completed_url))
     return window_features.noopener ? nullptr : new_frame->DomWindow();
 
-  if (created || !url_string.IsEmpty()) {
+  if (created || !completed_url.IsEmpty()) {
     FrameLoadRequest request(incumbent_window.document(),
                              ResourceRequest(completed_url));
     request.GetResourceRequest().SetHasUserGesture(has_user_gesture);
@@ -469,9 +391,7 @@
   WebWindowFeatures features;
   features.noopener = request.GetShouldSetOpener() == kNeverSetOpener;
   bool created;
-  Frame* new_frame = CreateWindowHelper(
-      opener_frame, opener_frame, opener_frame, request, features,
-      true /* force_new_foreground_tab */, created);
+  Frame* new_frame = CreateNewWindow(opener_frame, request, features, created);
   if (!new_frame)
     return;
   auto* new_local_frame = DynamicTo<LocalFrame>(new_frame);
diff --git a/third_party/blink/renderer/core/page/create_window.h b/third_party/blink/renderer/core/page/create_window.h
index 5178d84..11e635ae4 100644
--- a/third_party/blink/renderer/core/page/create_window.h
+++ b/third_party/blink/renderer/core/page/create_window.h
@@ -27,31 +27,25 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_CREATE_WINDOW_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_CREATE_WINDOW_H_
 
+#include "third_party/blink/public/web/web_window_features.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
-#include "third_party/blink/renderer/core/loader/frame_loader_types.h"
-#include "third_party/blink/renderer/core/loader/navigation_policy.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 // To avoid conflicts with the CreateWindow macro from the Windows SDK...
 #undef CreateWindow
 
 namespace blink {
-class ExceptionState;
 class LocalFrame;
 struct FrameLoadRequest;
-struct WebWindowFeatures;
 
-DOMWindow* CreateWindow(const String& url_string,
+DOMWindow* CreateWindow(const KURL& completed_url,
                         const AtomicString& frame_name,
-                        const String& window_features_string,
+                        const WebWindowFeatures&,
                         LocalDOMWindow& incumbent_window,
-                        LocalFrame& entered_window_frame,
-                        LocalFrame& opener_frame,
-                        ExceptionState&);
+                        LocalFrame& opener_frame);
 
 void CreateWindowForRequest(const FrameLoadRequest&, LocalFrame& opener_frame);
 
-// Exposed for testing
 CORE_EXPORT WebWindowFeatures GetWindowFeaturesFromString(const String&);
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
index 2e5aa2b..9d417eb 100644
--- a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
+++ b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
@@ -36,15 +36,26 @@
   if (!element.GetLayoutObject())
     return false;
 
-  LayoutObject* layout_object = element.GetLayoutObject();
+  DCHECK(element.GetLayoutObject()->IsBox());
+
+  LayoutBox* layout_box = ToLayoutBox(element.GetLayoutObject());
 
   // TODO(bokan): Broken for OOPIF. crbug.com/642378.
   Document& top_document = element.GetDocument().TopDocument();
   if (!top_document.GetLayoutView())
     return false;
 
-  FloatQuad quad = layout_object->LocalToAbsoluteQuad(
-      FloatRect(ToLayoutBox(layout_object)->PhysicalPaddingBoxRect()));
+  // We need to be more strict for iframes and use the content box since the
+  // iframe will use the parent's layout size. Using the padding box would mean
+  // the content would relayout on promotion/demotion. The layout size matching
+  // the parent is done to ensure consistent semantics with respect to how the
+  // mobile URL bar affects layout, which isn't a concern for non-iframe
+  // elements because those semantics will already be applied to the element.
+  LayoutRect rect = layout_box->IsLayoutIFrame()
+                        ? layout_box->PhysicalContentBoxRect()
+                        : layout_box->PhysicalPaddingBoxRect();
+
+  FloatQuad quad = layout_box->LocalToAbsoluteQuad(FloatRect(rect));
 
   if (!quad.IsRectilinear())
     return false;
diff --git a/third_party/blink/renderer/core/page/scrolling/root_scroller_test.cc b/third_party/blink/renderer/core/page/scrolling/root_scroller_test.cc
index af53b7f..1004a99d 100644
--- a/third_party/blink/renderer/core/page/scrolling/root_scroller_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/root_scroller_test.cc
@@ -2555,6 +2555,91 @@
       << "Once loaded, the iframe should be promoted.";
 }
 
+// Ensure that we're using the content box for an iframe. Promotion will cause
+// the content to use the layout size of the parent frame so having padding or
+// a border would cause us to relayout.
+TEST_F(ImplicitRootScrollerSimTest, IframeUsesContentBox) {
+  WebView().ResizeWithBrowserControls(IntSize(800, 600), 0, 0, false);
+  SimRequest main_request("https://example.com/test.html", "text/html");
+  SimRequest child_request("https://example.com/child.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  main_request.Complete(R"HTML(
+          <!DOCTYPE>
+          <style>
+            iframe {
+              position: absolute;
+              top: 0;
+              left: 0;
+              width: 100%;
+              height: 100%;
+              border: none;
+              box-sizing: border-box;
+
+            }
+            body, html {
+              margin: 0;
+              width: 100%;
+              height: 100%;
+              overflow:hidden;
+            }
+
+          </style>
+          <iframe id="container" src="child.html">
+      )HTML");
+  child_request.Complete(R"HTML(
+        <!DOCTYPE html>
+        <style>
+          div {
+            border: 5px solid black;
+            background-color: red;
+            width: 99%;
+            height: 100px;
+          }
+          html {
+            height: 200%;
+          }
+        </style>
+        <div></div>
+  )HTML");
+
+  Compositor().BeginFrame();
+
+  Element* iframe = GetDocument().getElementById("container");
+
+  ASSERT_EQ(iframe,
+            GetDocument().GetRootScrollerController().EffectiveRootScroller())
+      << "The iframe should start off promoted.";
+
+  // Adding padding should cause the iframe to be demoted.
+  {
+    iframe->setAttribute(html_names::kStyleAttr, "padding-left: 20%");
+    Compositor().BeginFrame();
+
+    EXPECT_NE(iframe,
+              GetDocument().GetRootScrollerController().EffectiveRootScroller())
+        << "The iframe should be demoted once it has padding.";
+  }
+
+  // Replacing padding with a border should also ensure the iframe remains
+  // demoted.
+  {
+    iframe->setAttribute(html_names::kStyleAttr, "border: 5px solid black");
+    Compositor().BeginFrame();
+
+    EXPECT_NE(iframe,
+              GetDocument().GetRootScrollerController().EffectiveRootScroller())
+        << "The iframe should be demoted once it has border.";
+  }
+
+  // Removing the border should now cause the iframe to be promoted once again.
+  iframe->setAttribute(html_names::kStyleAttr, "");
+  Compositor().BeginFrame();
+
+  ASSERT_EQ(iframe,
+            GetDocument().GetRootScrollerController().EffectiveRootScroller())
+      << "The iframe should once again be promoted when border is removed";
+}
+
 // Test that we don't promote any elements implicitly if the main document has
 // vertical scrolling.
 TEST_F(ImplicitRootScrollerSimTest, OverflowInMainDocumentRestrictsImplicit) {
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
index b2c0ffa..aa93dc0 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/renderer/core/editing/visible_units.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
+#include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h"
 #include "third_party/blink/renderer/core/scroll/scroll_alignment.h"
 
 namespace blink {
@@ -20,31 +21,32 @@
 namespace {
 
 constexpr char kTextFragmentIdentifierPrefix[] = "targetText=";
-constexpr size_t kTextFragmentIdentifierPrefixLength =
+constexpr size_t kTextFragmentIdentifierPrefixArrayLength =
     base::size(kTextFragmentIdentifierPrefix);
 
-bool ParseTargetTextIdentifier(const String& fragment,
-                               String* start,
-                               String* end) {
-  if (fragment.Find(kTextFragmentIdentifierPrefix) != 0)
-    return false;
+bool ParseTargetTextIdentifier(
+    const String& fragment,
+    std::vector<TextFragmentSelector>* out_selectors) {
+  DCHECK(out_selectors);
 
-  size_t comma_pos = fragment.find(',');
-  size_t start_pos = kTextFragmentIdentifierPrefixLength - 1;
+  size_t start_pos = 0;
+  size_t end_pos = 0;
+  while (end_pos != kNotFound) {
+    if (fragment.Find(kTextFragmentIdentifierPrefix, start_pos) != start_pos)
+      return false;
 
-  if (comma_pos == kNotFound) {
-    *start = fragment.Substring(start_pos);
-    *end = "";
-  } else {
-    *start = fragment.Substring(start_pos, comma_pos - start_pos);
+    // The prefix array length includes the \0 string terminator.
+    start_pos += kTextFragmentIdentifierPrefixArrayLength - 1;
+    end_pos = fragment.find('&', start_pos);
 
-    size_t second_start_pos = comma_pos + 1;
-    size_t end_pos = fragment.find('&', comma_pos);
-
-    if (end_pos == kNotFound)
-      *end = fragment.Substring(second_start_pos);
-    else
-      *end = fragment.Substring(second_start_pos, end_pos - second_start_pos);
+    String target_text;
+    if (end_pos == kNotFound) {
+      target_text = fragment.Substring(start_pos);
+    } else {
+      target_text = fragment.Substring(start_pos, end_pos - start_pos);
+      start_pos = end_pos + 1;
+    }
+    out_selectors->push_back(TextFragmentSelector::Create(target_text));
   }
 
   return true;
@@ -54,33 +56,38 @@
 
 TextFragmentAnchor* TextFragmentAnchor::TryCreate(const KURL& url,
                                                   LocalFrame& frame) {
-  String start;
-  String end;
+  std::vector<TextFragmentSelector> selectors;
 
-  if (!ParseTargetTextIdentifier(url.FragmentIdentifier(), &start, &end))
+  if (!ParseTargetTextIdentifier(url.FragmentIdentifier(), &selectors))
     return nullptr;
 
-  String decoded_start = DecodeURLEscapeSequences(start, DecodeURLMode::kUTF8);
-  String decoded_end = DecodeURLEscapeSequences(end, DecodeURLMode::kUTF8);
-
-  return MakeGarbageCollected<TextFragmentAnchor>(decoded_start, decoded_end,
-                                                  frame);
+  return MakeGarbageCollected<TextFragmentAnchor>(selectors, frame);
 }
 
-TextFragmentAnchor::TextFragmentAnchor(const String& start,
-                                       const String& end,
-                                       LocalFrame& frame)
-    : start_(start), end_(end), finder_(*this), frame_(&frame) {
+TextFragmentAnchor::TextFragmentAnchor(
+    const std::vector<TextFragmentSelector>& text_fragment_selectors,
+    LocalFrame& frame)
+    : frame_(&frame) {
+  DCHECK(!text_fragment_selectors.empty());
   DCHECK(frame_->View());
+
+  text_fragment_finders_.reserve(text_fragment_selectors.size());
+  for (TextFragmentSelector selector : text_fragment_selectors)
+    text_fragment_finders_.emplace_back(*this, selector);
 }
 
 bool TextFragmentAnchor::Invoke() {
   if (search_finished_)
     return false;
 
-  // TODO(bokan): We need to add the capability to match a snippet based on
-  // it's start and end. https://crbug.com/924964.
-  finder_.FindMatch(start_, *frame_->GetDocument());
+  frame_->GetDocument()->Markers().RemoveMarkersOfTypes(
+      DocumentMarker::MarkerTypes::TextMatch());
+
+  if (!user_scrolled_)
+    first_match_needs_scroll_ = true;
+
+  for (auto& finder : text_fragment_finders_)
+    finder.FindMatch(*frame_->GetDocument());
 
   // Scrolling into view from the call above might cause us to set
   // search_finished_ so recompute here.
@@ -95,7 +102,7 @@
   if (!IsExplicitScrollType(type))
     return;
 
-  search_finished_ = true;
+  user_scrolled_ = true;
 }
 
 void TextFragmentAnchor::PerformPreRafActions() {}
@@ -115,30 +122,28 @@
   if (search_finished_)
     return;
 
-  Document& document = *frame_->GetDocument();
+  if (first_match_needs_scroll_) {
+    first_match_needs_scroll_ = false;
 
-  document.Markers().RemoveMarkersOfTypes(
-      DocumentMarker::MarkerTypes::TextMatch());
+    LayoutRect bounding_box = LayoutRect(ComputeTextRect(range));
 
-  LayoutRect bounding_box = LayoutRect(ComputeTextRect(range));
+    DCHECK(range.Nodes().begin() != range.Nodes().end());
 
-  DCHECK(range.Nodes().begin() != range.Nodes().end());
+    Node& node = *range.Nodes().begin();
 
-  Node& node = *range.Nodes().begin();
+    DCHECK(node.GetLayoutObject());
 
-  DCHECK(node.GetLayoutObject());
-
-  node.GetLayoutObject()->ScrollRectToVisible(
-      bounding_box,
-      WebScrollIntoViewParams(ScrollAlignment::kAlignCenterIfNeeded,
-                              ScrollAlignment::kAlignCenterIfNeeded,
-                              kProgrammaticScroll));
-
+    node.GetLayoutObject()->ScrollRectToVisible(
+        bounding_box,
+        WebScrollIntoViewParams(ScrollAlignment::kAlignCenterIfNeeded,
+                                ScrollAlignment::kAlignCenterIfNeeded,
+                                kProgrammaticScroll));
+  }
   EphemeralRange dom_range =
       EphemeralRange(ToPositionInDOMTree(range.StartPosition()),
                      ToPositionInDOMTree(range.EndPosition()));
-  document.Markers().AddTextMatchMarker(dom_range,
-                                        TextMatchMarker::MatchStatus::kActive);
+  frame_->GetDocument()->Markers().AddTextMatchMarker(
+      dom_range, TextMatchMarker::MatchStatus::kActive);
   frame_->GetEditor().SetMarkedTextMatchesAreHighlighted(true);
 }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
index e08bc4b..aab1d90 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
@@ -19,11 +19,13 @@
 class KURL;
 
 class CORE_EXPORT TextFragmentAnchor final : public FragmentAnchor,
-                                             TextFragmentFinder::Client {
+                                             public TextFragmentFinder::Client {
  public:
   static TextFragmentAnchor* TryCreate(const KURL& url, LocalFrame& frame);
 
-  TextFragmentAnchor(const String& start, const String& end, LocalFrame& frame);
+  TextFragmentAnchor(
+      const std::vector<TextFragmentSelector>& text_fragment_selectors,
+      LocalFrame& frame);
   ~TextFragmentAnchor() override = default;
 
   bool Invoke() override;
@@ -42,14 +44,13 @@
   void DidFindMatch(const EphemeralRangeInFlatTree& range) override;
 
  private:
-  const String start_;
-  const String end_;
-
-  TextFragmentFinder finder_;
+  std::vector<TextFragmentFinder> text_fragment_finders_;
 
   Member<LocalFrame> frame_;
 
   bool search_finished_ = false;
+  bool user_scrolled_ = false;
+  bool first_match_needs_scroll_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(TextFragmentAnchor);
 };
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
index 83d4609..3ca3d08 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
@@ -138,6 +138,7 @@
   Compositor().BeginFrame();
 
   EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor());
+  EXPECT_TRUE(GetDocument().Markers().Markers().IsEmpty());
 }
 
 // If the targetText=... string matches an id, we should scroll using id
@@ -244,6 +245,150 @@
       << LayoutViewport()->GetScrollOffset().ToString();
 }
 
+// Ensure multiple targetTexts are highlighted and the first is scrolled into
+// view.
+TEST_F(TextFragmentAnchorTest, MultipleTextFragments) {
+  SimRequest request(
+      "https://example.com/test.html#targetText=test&targetText=more",
+      "text/html");
+  LoadURL("https://example.com/test.html#targetText=test&targetText=more");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      body {
+        height: 1200px;
+      }
+      #first {
+        position: absolute;
+        top: 1000px;
+      }
+      #second {
+        position: absolute;
+        top: 2000px;
+      }
+    </style>
+    <p id="first">This is a test page</p>
+    <p id="second">This is some more text</p>
+  )HTML");
+  Compositor().BeginFrame();
+
+  RunAsyncMatchingTasks();
+
+  Element& first = *GetDocument().getElementById("first");
+
+  EXPECT_TRUE(ViewportRect().Contains(BoundingRectInFrame(first)))
+      << "First <p> wasn't scrolled into view, viewport's scroll offset: "
+      << LayoutViewport()->GetScrollOffset().ToString();
+
+  EXPECT_EQ(2u, GetDocument().Markers().Markers().size());
+}
+
+// Ensure we scroll the second targetText into view if the first isn't found.
+TEST_F(TextFragmentAnchorTest, FirstTextFragmentNotFound) {
+  SimRequest request(
+      "https://example.com/test.html#targetText=test&targetText=more",
+      "text/html");
+  LoadURL("https://example.com/test.html#targetText=test&targetText=more");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      body {
+        height: 1200px;
+      }
+      #first {
+        position: absolute;
+        top: 1000px;
+      }
+      #second {
+        position: absolute;
+        top: 2000px;
+      }
+    </style>
+    <p id="first">This is a page</p>
+    <p id="second">This is some more text</p>
+  )HTML");
+  Compositor().BeginFrame();
+
+  RunAsyncMatchingTasks();
+
+  Element& second = *GetDocument().getElementById("second");
+
+  EXPECT_TRUE(ViewportRect().Contains(BoundingRectInFrame(second)))
+      << "Second <p> wasn't scrolled into view, viewport's scroll offset: "
+      << LayoutViewport()->GetScrollOffset().ToString();
+
+  EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
+}
+
+// Ensure we still scroll the first targetText into view if the second isn't
+// found.
+TEST_F(TextFragmentAnchorTest, OnlyFirstTextFragmentFound) {
+  SimRequest request(
+      "https://example.com/test.html#targetText=test&targetText=more",
+      "text/html");
+  LoadURL("https://example.com/test.html#targetText=test&targetText=more");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      body {
+        height: 1200px;
+      }
+      p {
+        position: absolute;
+        top: 1000px;
+      }
+    </style>
+    <p id="text">This is a test page</p>
+  )HTML");
+  Compositor().BeginFrame();
+
+  RunAsyncMatchingTasks();
+
+  Element& p = *GetDocument().getElementById("text");
+
+  EXPECT_TRUE(ViewportRect().Contains(BoundingRectInFrame(p)))
+      << "<p> Element wasn't scrolled into view, viewport's scroll offset: "
+      << LayoutViewport()->GetScrollOffset().ToString();
+
+  EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
+}
+
+// Make sure multiple non-matching strings doesn't cause scroll and the fragment
+// is removed when completed.
+TEST_F(TextFragmentAnchorTest, MultipleNonMatchingStrings) {
+  SimRequest request(
+      "https://example.com/"
+      "test.html#targetText=unicorn&targetText=cookie&targetText=cat",
+      "text/html");
+  LoadURL(
+      "https://example.com/"
+      "test.html#targetText=unicorn&targetText=cookie&targetText=cat");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      body {
+        height: 1200px;
+      }
+      p {
+        position: absolute;
+        top: 1000px;
+      }
+    </style>
+    <p id="text">This is a test page</p>
+  )HTML");
+  Compositor().BeginFrame();
+  RunAsyncMatchingTasks();
+
+  EXPECT_EQ(ScrollOffset(), LayoutViewport()->GetScrollOffset());
+
+  // Force a layout
+  GetDocument().body()->setAttribute(html_names::kStyleAttr, "height: 1300px");
+  Compositor().BeginFrame();
+
+  EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor());
+  EXPECT_TRUE(GetDocument().Markers().Markers().IsEmpty());
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
index 48b6136..a6bf09d 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
@@ -12,14 +12,15 @@
 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
 #include "third_party/blink/renderer/core/editing/finder/find_buffer.h"
 #include "third_party/blink/renderer/core/editing/position.h"
+#include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h"
 
 namespace blink {
 
-TextFragmentFinder::TextFragmentFinder(Client& client) : client_(client) {}
+TextFragmentFinder::TextFragmentFinder(Client& client,
+                                       const TextFragmentSelector& selector)
+    : client_(client), selector_(selector) {}
 
-void TextFragmentFinder::FindMatch(const String& search_text,
-                                   Document& document) {
-  search_text_ = search_text;
+void TextFragmentFinder::FindMatch(Document& document) {
   PositionInFlatTree search_start =
       PositionInFlatTree::FirstPositionInNode(document);
 
@@ -36,8 +37,10 @@
     // Find in the whole block.
     FindBuffer buffer(EphemeralRangeInFlatTree(search_start, search_end));
     const FindOptions find_options = kCaseInsensitive;
+    // TODO(bokan): We need to add the capability to match a snippet based on
+    // it's start and end. https://crbug.com/924964.
     std::unique_ptr<FindBuffer::Results> match_results =
-        buffer.FindMatches(search_text_, find_options);
+        buffer.FindMatches(selector_.Start(), find_options);
 
     if (!match_results->IsEmpty()) {
       FindBuffer::BufferMatchResult match = match_results->front();
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
index 1c180b3..34db3f7b 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/editing/forward.h"
+#include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
@@ -24,18 +25,17 @@
   };
 
   // Client must outlive the finder.
-  TextFragmentFinder(Client& client);
+  TextFragmentFinder(Client& client, const TextFragmentSelector& selector);
   ~TextFragmentFinder() = default;
 
-  // Begins searching for the given string in the given top-level document.
-  void FindMatch(const String& search_text, Document& document);
+  // Begins searching in the given top-level document.
+  void FindMatch(Document& document);
 
  private:
   Client& client_;
+  const TextFragmentSelector selector_;
 
   String search_text_;
-
-  DISALLOW_COPY_AND_ASSIGN(TextFragmentFinder);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.cc
new file mode 100644
index 0000000..b4ac4b79
--- /dev/null
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.cc
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+
+namespace blink {
+
+TextFragmentSelector TextFragmentSelector::Create(const String& target_text) {
+  SelectorType type;
+  String start;
+  String end;
+
+  size_t comma_pos = target_text.find(',');
+
+  if (comma_pos == kNotFound) {
+    type = kExact;
+    start = target_text;
+    end = "";
+  } else {
+    type = kRange;
+    start = target_text.Substring(0, comma_pos);
+    end = target_text.Substring(comma_pos + 1);
+  }
+
+  return TextFragmentSelector(
+      type, DecodeURLEscapeSequences(start, DecodeURLMode::kUTF8),
+      DecodeURLEscapeSequences(end, DecodeURLMode::kUTF8));
+}
+
+TextFragmentSelector::TextFragmentSelector(SelectorType type,
+                                           String start,
+                                           String end)
+    : type_(type), start_(start), end_(end) {}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h
new file mode 100644
index 0000000..a10d0cf9
--- /dev/null
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_SELECTOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_SELECTOR_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+// TextFragmentSelector represents a single targetText=... selector of a
+// TextFragmentAnchor, parsed into its components.
+class CORE_EXPORT TextFragmentSelector final {
+ public:
+  static TextFragmentSelector Create(const String& target_text);
+
+  enum SelectorType {
+    // An exact selector on the string start_.
+    kExact,
+    // A range selector on a text range start_ to end_.
+    kRange,
+  };
+
+  TextFragmentSelector(SelectorType type, String start, String end);
+  ~TextFragmentSelector() = default;
+
+  SelectorType Type() const { return type_; }
+  String Start() const { return start_; }
+  String End() const { return end_; }
+
+ private:
+  const SelectorType type_;
+  String start_;
+  String end_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_SELECTOR_H_
diff --git a/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h b/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h
index 3de80a3..ff1f7e48 100644
--- a/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h
+++ b/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h
@@ -75,21 +75,18 @@
 
   void DidResizeViewport();
 
-  // Returns the ScrollableArea associated with the globalRootScroller(). Note,
-  // this isn't necessarily the PLSA belonging to the root scroller Node's
-  // LayoutBox.  If the root scroller is the documentElement then we use the
-  // LocalFrameView (or LayoutView if root-layer-scrolls).
+  // Returns the ScrollableArea associated with the globalRootScroller().
   ScrollableArea* RootScrollerArea() const;
 
-  // Returns the size we should use for the root scroller, accounting for top
-  // controls adjustment and using the root LocalFrameView.
+  // Returns the size we should use for the root scroller, accounting for
+  // browser controls adjustment and using the root LocalFrameView.
   IntSize RootScrollerVisibleArea() const;
 
  private:
-  // Calculates the Node that should be the globalRootScroller. On a simple
-  // page, this will simply the root frame's effectiveRootScroller but if the
-  // root scroller is set to an iframe, this will then descend into the iframe
-  // to find its effective root scroller.
+  // Calculates the Node that should be the global root scroller. On a simple
+  // page, this will be the root frame's effective root scroller. If the
+  // effective root scroller is an iframe, this will then recursively descend
+  // into the iframe to find its effective root scroller.
   Node* FindGlobalRootScroller();
 
   // Should be called to set a new node as the global root scroller and that
@@ -97,19 +94,19 @@
   void UpdateGlobalRootScroller(Node* new_global_root_scroller);
 
   // Updates the is_global_root_scroller bits in all the necessary places when
-  // the global root scsroller changes.
+  // the global root scroller changes.
   void UpdateCachedBits(Node* old_global, Node* new_global);
 
   Document* TopDocument() const;
 
   // The apply-scroll callback that moves browser controls and produces
   // overscroll effects. This class makes sure this callback is set on the
-  // appropriate root scroller element.
+  // global root scroller element.
   Member<ViewportScrollCallback> viewport_apply_scroll_;
 
   // The page level root scroller. i.e. The actual node for which scrolling
   // should move browser controls and produce overscroll glow. Once an
-  // m_viewportApplyScroll has been created, it will always be set on this
+  // |viewport_apply_scroll_| has been created, it will always be set on this
   // Node.
   WeakMember<Node> global_root_scroller_;
 
diff --git a/third_party/blink/renderer/core/paint/image_element_timing.cc b/third_party/blink/renderer/core/paint/image_element_timing.cc
index 8b03dfa..f8afeac 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing.cc
+++ b/third_party/blink/renderer/core/paint/image_element_timing.cc
@@ -64,6 +64,11 @@
   if (!ShouldReportElement(frame, attr, intersection_rect))
     return;
 
+  const Node* node = layout_object->GetNode();
+  DCHECK(node);
+  DCHECK(node->IsElementNode());
+  const AtomicString& id = ToElement(node)->GetIdAttribute();
+
   DCHECK(GetSupplementable()->document() == &layout_object->GetDocument());
   DCHECK(layout_object->GetDocument().GetSecurityOrigin());
   if (!Performance::PassesTimingAllowCheck(
@@ -78,7 +83,8 @@
       // Create an entry with a |startTime| of 0.
       performance->AddElementTiming(
           AtomicString(cached_image->Url().GetString()), intersection_rect,
-          TimeTicks(), cached_image->LoadResponseEnd(), attr);
+          TimeTicks(), cached_image->LoadResponseEnd(), attr,
+          cached_image->IntrinsicSize(kDoNotRespectImageOrientation), id);
     }
     return;
   }
@@ -88,9 +94,10 @@
   if (!layerTreeView)
     return;
 
-  element_timings_.emplace_back(AtomicString(cached_image->Url().GetString()),
-                                intersection_rect,
-                                cached_image->LoadResponseEnd(), attr);
+  element_timings_.emplace_back(
+      AtomicString(cached_image->Url().GetString()), intersection_rect,
+      cached_image->LoadResponseEnd(), attr,
+      cached_image->IntrinsicSize(kDoNotRespectImageOrientation), id);
   // Only queue a swap promise when |element_timings_| was empty. All of the
   // records in |element_timings_| will be processed when the promise succeeds
   // or fails, and at that time the vector is cleared.
@@ -140,9 +147,10 @@
   if (performance && (performance->HasObserverFor(PerformanceEntry::kElement) ||
                       performance->ShouldBufferEntries())) {
     for (const auto& element_timing : element_timings_) {
-      performance->AddElementTiming(element_timing.name, element_timing.rect,
-                                    timestamp, element_timing.response_end,
-                                    element_timing.identifier);
+      performance->AddElementTiming(
+          element_timing.name, element_timing.rect, timestamp,
+          element_timing.response_end, element_timing.identifier,
+          element_timing.intrinsic_size, element_timing.id);
     }
   }
   element_timings_.clear();
diff --git a/third_party/blink/renderer/core/paint/image_element_timing.h b/third_party/blink/renderer/core/paint/image_element_timing.h
index 3b710af..b17e5cd 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing.h
+++ b/third_party/blink/renderer/core/paint/image_element_timing.h
@@ -67,16 +67,22 @@
     ElementTimingInfo(const AtomicString& name,
                       const FloatRect& rect,
                       const TimeTicks& response_end,
-                      const AtomicString& identifier)
+                      const AtomicString& identifier,
+                      const IntSize& intrinsic_size,
+                      const AtomicString& id)
         : name(name),
           rect(rect),
           response_end(response_end),
-          identifier(identifier) {}
+          identifier(identifier),
+          intrinsic_size(intrinsic_size),
+          id(id) {}
 
     AtomicString name;
     FloatRect rect;
     TimeTicks response_end;
     AtomicString identifier;
+    IntSize intrinsic_size;
+    AtomicString id;
   };
   // Vector containing the element timing infos that will be reported during the
   // next swap promise callback.
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index fcb790e9..86bfd85 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -1070,10 +1070,9 @@
   // Being the global root scroller will affect clipping size due to browser
   // controls behavior so we need to update compositing based on updated clip
   // geometry.
-  if (GetLayoutBox()->GetNode()->IsElementNode()) {
+  if (GetLayoutBox()->GetNode()->IsElementNode())
     ToElement(GetLayoutBox()->GetNode())->SetNeedsCompositingUpdate();
-    GetLayoutBox()->SetNeedsPaintPropertyUpdate();
-  }
+  GetLayoutBox()->SetNeedsPaintPropertyUpdate();
 
   // On Android, where the VisualViewport supplies scrollbars, we need to
   // remove the PLSA's scrollbars if we become the global root scroller.
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 214cfac..24b014cd 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -1760,6 +1760,8 @@
       state.user_scrollable_vertical =
           scrollable_area->UserInputScrollable(kVerticalScrollbar);
 
+      state.scrolls_outer_viewport = box.IsGlobalRootScroller();
+
       auto ancestor_reasons =
           context_.current.scroll->GetMainThreadScrollingReasons();
       state.main_thread_scrolling_reasons =
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index d1fcd5c..171fb01 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -6596,4 +6596,63 @@
   EXPECT_FALSE(text->FirstFragment().PaintProperties());
 }
 
+TEST_P(PaintPropertyTreeBuilderTest, SetViewportScrollingBits) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      body, html {
+        margin: 0;
+        width: 100%;
+        height: 100%;
+      }
+      #scroller {
+       width: 100%;
+       height: 200%;
+       overflow: auto;
+      }
+    </style>
+    <div id="scroller">
+      <div style="height: 3000px"></div>
+    </div>
+  )HTML");
+
+  const auto* scroller_node = PaintPropertiesForElement("scroller")->Scroll();
+  const auto* document_node = DocScroll();
+
+  // Ensure the LayoutView's ScrollNode is marked as scrolling the "outer" or
+  // "layout" viewport.
+  {
+    EXPECT_FALSE(scroller_node->ScrollsOuterViewport());
+    EXPECT_TRUE(document_node->ScrollsOuterViewport());
+  }
+
+  // Ensure the visual viewport is the only one that sets the inner scroll bit.
+  {
+    EXPECT_TRUE(GetDocument()
+                    .GetPage()
+                    ->GetVisualViewport()
+                    .GetScrollNode()
+                    ->ScrollsInnerViewport());
+    EXPECT_FALSE(scroller_node->ScrollsInnerViewport());
+    EXPECT_FALSE(document_node->ScrollsInnerViewport());
+  }
+
+  // Make the scroller fill the viewport. This will make it eligible for root
+  // scroller promotion. Ensure the outer viewport scrolling property is
+  // correctly recomputed, moving it from the LayoutView to the scroller.
+  {
+    Element* scroller = GetDocument().getElementById("scroller");
+    scroller->setAttribute(html_names::kStyleAttr, "height: 100%");
+    LocalFrameView* frame_view = GetDocument().View();
+    frame_view->UpdateAllLifecyclePhases(
+        DocumentLifecycle::LifecycleUpdateReason::kTest);
+    ASSERT_TRUE(scroller->GetLayoutObject()->IsGlobalRootScroller());
+
+    EXPECT_TRUE(scroller_node->ScrollsOuterViewport());
+
+    // Since the document is no longer scrollable and isn't the root scroller
+    // it shouldn't have a node.
+    EXPECT_FALSE(DocScroll());
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.cc b/third_party/blink/renderer/core/timing/performance_element_timing.cc
index dfb74b0..68e55a36 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.cc
@@ -15,9 +15,15 @@
     const FloatRect& intersection_rect,
     DOMHighResTimeStamp start_time,
     DOMHighResTimeStamp response_end,
-    const AtomicString& identifier) {
+    const AtomicString& identifier,
+    int naturalWidth,
+    int naturalHeight,
+    const AtomicString& id) {
+  DCHECK_GT(naturalWidth, 0);
+  DCHECK_GT(naturalHeight, 0);
   return MakeGarbageCollected<PerformanceElementTiming>(
-      name, intersection_rect, start_time, response_end, identifier);
+      name, intersection_rect, start_time, response_end, identifier,
+      naturalWidth, naturalHeight, id);
 }
 
 PerformanceElementTiming::PerformanceElementTiming(
@@ -25,11 +31,17 @@
     const FloatRect& intersection_rect,
     DOMHighResTimeStamp start_time,
     DOMHighResTimeStamp response_end,
-    const AtomicString& identifier)
+    const AtomicString& identifier,
+    int naturalWidth,
+    int naturalHeight,
+    const AtomicString& id)
     : PerformanceEntry(name, start_time, start_time),
       intersection_rect_(DOMRectReadOnly::FromFloatRect(intersection_rect)),
       response_end_(response_end),
-      identifier_(identifier) {}
+      identifier_(identifier),
+      naturalWidth_(naturalWidth),
+      naturalHeight_(naturalHeight),
+      id_(id) {}
 
 PerformanceElementTiming::~PerformanceElementTiming() = default;
 
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.h b/third_party/blink/renderer/core/timing/performance_element_timing.h
index 190fc03..6bb3f24 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.h
@@ -24,13 +24,19 @@
                                           const FloatRect& intersection_rect,
                                           DOMHighResTimeStamp start_time,
                                           DOMHighResTimeStamp response_end,
-                                          const AtomicString& identifier);
+                                          const AtomicString& identifier,
+                                          int naturalWidth,
+                                          int naturalHeight,
+                                          const AtomicString& id);
 
   PerformanceElementTiming(const AtomicString& name,
                            const FloatRect& intersection_rect,
                            DOMHighResTimeStamp start_time,
                            DOMHighResTimeStamp response_end,
-                           const AtomicString& identifier);
+                           const AtomicString& identifier,
+                           int naturalWidth,
+                           int naturalHeight,
+                           const AtomicString& id);
   ~PerformanceElementTiming() override;
 
   AtomicString entryType() const override;
@@ -42,6 +48,12 @@
 
   AtomicString identifier() const { return identifier_; }
 
+  unsigned naturalWidth() const { return naturalWidth_; }
+
+  unsigned naturalHeight() const { return naturalHeight_; }
+
+  AtomicString id() const { return id_; }
+
   void Trace(blink::Visitor*) override;
 
  private:
@@ -50,6 +62,9 @@
   Member<DOMRectReadOnly> intersection_rect_;
   DOMHighResTimeStamp response_end_;
   AtomicString identifier_;
+  unsigned naturalWidth_;
+  unsigned naturalHeight_;
+  AtomicString id_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.idl b/third_party/blink/renderer/core/timing/performance_element_timing.idl
index 3796be6..99eb393 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.idl
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.idl
@@ -8,6 +8,9 @@
     readonly attribute DOMRectReadOnly intersectionRect;
     readonly attribute DOMHighResTimeStamp responseEnd;
     readonly attribute DOMString identifier;
+    readonly attribute unsigned long naturalWidth;
+    readonly attribute unsigned long naturalHeight;
+    readonly attribute DOMString id;
 
     // TODO(peria): toJSON is not in spec. https://crbug.com/736332
     [CallWith=ScriptState, ImplementedAs=toJSONForBinding] object toJSON();
diff --git a/third_party/blink/renderer/core/timing/performance_observer.idl b/third_party/blink/renderer/core/timing/performance_observer.idl
index 23e069d..c4348fb 100644
--- a/third_party/blink/renderer/core/timing/performance_observer.idl
+++ b/third_party/blink/renderer/core/timing/performance_observer.idl
@@ -12,7 +12,7 @@
     ConstructorCallWith=ScriptState,
     Exposed=(Window,Worker)
 ] interface PerformanceObserver {
-    [RaisesException] void observe(PerformanceObserverInit options);
+    [RaisesException] void observe(optional PerformanceObserverInit options);
     void disconnect();
     PerformanceEntryList takeRecords();
     [CallWith=ScriptState] static readonly attribute FrozenArray<DOMString> supportedEntryTypes;
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc
index 1b9a1ec..9e4bf25 100644
--- a/third_party/blink/renderer/core/timing/window_performance.cc
+++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -400,11 +400,14 @@
                                          const FloatRect& rect,
                                          TimeTicks start_time,
                                          TimeTicks response_end,
-                                         const AtomicString& identifier) {
+                                         const AtomicString& identifier,
+                                         const IntSize& intrinsic_size,
+                                         const AtomicString& id) {
   DCHECK(origin_trials::ElementTimingEnabled(GetExecutionContext()));
   PerformanceElementTiming* entry = PerformanceElementTiming::Create(
       name, rect, MonotonicTimeToDOMHighResTimeStamp(start_time),
-      MonotonicTimeToDOMHighResTimeStamp(response_end), identifier);
+      MonotonicTimeToDOMHighResTimeStamp(response_end), identifier,
+      intrinsic_size.Width(), intrinsic_size.Height(), id);
   if (HasObserverFor(PerformanceEntry::kElement)) {
     UseCounter::Count(GetFrame()->GetDocument(),
                       WebFeature::kElementTimingExplicitlyRequested);
diff --git a/third_party/blink/renderer/core/timing/window_performance.h b/third_party/blink/renderer/core/timing/window_performance.h
index 8ac6b2c6..1f0dec8 100644
--- a/third_party/blink/renderer/core/timing/window_performance.h
+++ b/third_party/blink/renderer/core/timing/window_performance.h
@@ -84,7 +84,9 @@
                         const FloatRect& rect,
                         TimeTicks start_time,
                         TimeTicks response_end,
-                        const AtomicString& identifier);
+                        const AtomicString& identifier,
+                        const IntSize& intrinsic_size,
+                        const AtomicString& id);
 
   void AddLayoutJankFraction(double jank_fraction);
 
diff --git a/third_party/blink/renderer/core/workers/worker_backing_thread.cc b/third_party/blink/renderer/core/workers/worker_backing_thread.cc
index 7952313..3ca32526 100644
--- a/third_party/blink/renderer/core/workers/worker_backing_thread.cc
+++ b/third_party/blink/renderer/core/workers/worker_backing_thread.cc
@@ -76,9 +76,7 @@
   V8Initializer::InitializeWorker(isolate_);
 
   ThreadState::Current()->RegisterTraceDOMWrappers(
-      isolate_, V8GCController::TraceDOMWrappers,
-      ScriptWrappableMarkingVisitor::InvalidateDeadObjectsInMarkingDeque,
-      ScriptWrappableMarkingVisitor::PerformCleanup);
+      isolate_, V8GCController::TraceDOMWrappers);
   if (RuntimeEnabledFeatures::V8IdleTasksEnabled()) {
     V8PerIsolateData::EnableIdleTasks(
         isolate_, std::make_unique<V8IdleTaskRunner>(scheduler));
diff --git a/third_party/blink/renderer/devtools/front_end/application_test_runner/ResourcesTestRunner.js b/third_party/blink/renderer/devtools/front_end/application_test_runner/ResourcesTestRunner.js
index 28fe125..3945403 100644
--- a/third_party/blink/renderer/devtools/front_end/application_test_runner/ResourcesTestRunner.js
+++ b/third_party/blink/renderer/devtools/front_end/application_test_runner/ResourcesTestRunner.js
@@ -12,10 +12,12 @@
  * doesn't get reset between tests.
  */
 ApplicationTestRunner.resetState = async function() {
-  const securityOrigin = new Common.ParsedURL(TestRunner.url()).securityOrigin();
-  const storageTypes =
-      ['appcache', 'cache_storage', 'cookies', 'indexeddb', 'local_storage', 'service_workers', 'websql'];
-  await TestRunner.mainTarget.storageAgent().clearDataForOrigin(securityOrigin, storageTypes.join(','));
+  const targets = SDK.targetManager.targets();
+  for (const target of targets) {
+    const securityOrigin = new Common.ParsedURL(target.inspectedURL()).securityOrigin();
+    await target.storageAgent().clearDataForOrigin(
+        securityOrigin, Resources.ClearStorageView.AllStorageTypes.join(','));
+  }
 };
 
 ApplicationTestRunner.createWebSQLDatabase = function(name) {
diff --git a/third_party/blink/renderer/devtools/front_end/security/SecurityModel.js b/third_party/blink/renderer/devtools/front_end/security/SecurityModel.js
index 1d1648b5..d380a9c 100644
--- a/third_party/blink/renderer/devtools/front_end/security/SecurityModel.js
+++ b/third_party/blink/renderer/devtools/front_end/security/SecurityModel.js
@@ -75,14 +75,12 @@
    * @param {!Protocol.Security.SecurityState} securityState
    * @param {boolean} schemeIsCryptographic
    * @param {!Array<!Protocol.Security.SecurityStateExplanation>} explanations
-   * @param {?Protocol.Security.InsecureContentStatus} insecureContentStatus
    * @param {?string} summary
    */
-  constructor(securityState, schemeIsCryptographic, explanations, insecureContentStatus, summary) {
+  constructor(securityState, schemeIsCryptographic, explanations, summary) {
     this.securityState = securityState;
     this.schemeIsCryptographic = schemeIsCryptographic;
     this.explanations = explanations;
-    this.insecureContentStatus = insecureContentStatus;
     this.summary = summary;
   }
 };
@@ -105,8 +103,8 @@
    * @param {?string=} summary
    */
   securityStateChanged(securityState, schemeIsCryptographic, explanations, insecureContentStatus, summary) {
-    const pageSecurityState = new Security.PageSecurityState(
-        securityState, schemeIsCryptographic, explanations, insecureContentStatus, summary || null);
+    const pageSecurityState =
+        new Security.PageSecurityState(securityState, schemeIsCryptographic, explanations, summary || null);
     this._model.dispatchEventToListeners(Security.SecurityModel.Events.SecurityStateChanged, pageSecurityState);
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/security/SecurityPanel.js b/third_party/blink/renderer/devtools/front_end/security/SecurityPanel.js
index 286231b..5512cc3a 100644
--- a/third_party/blink/renderer/devtools/front_end/security/SecurityPanel.js
+++ b/third_party/blink/renderer/devtools/front_end/security/SecurityPanel.js
@@ -94,13 +94,11 @@
    * @param {!Protocol.Security.SecurityState} newSecurityState
    * @param {boolean} schemeIsCryptographic
    * @param {!Array<!Protocol.Security.SecurityStateExplanation>} explanations
-   * @param {?Protocol.Security.InsecureContentStatus} insecureContentStatus
    * @param {?string} summary
    */
-  _updateSecurityState(newSecurityState, schemeIsCryptographic, explanations, insecureContentStatus, summary) {
+  _updateSecurityState(newSecurityState, schemeIsCryptographic, explanations, summary) {
     this._sidebarMainViewElement.setSecurityState(newSecurityState);
-    this._mainView.updateSecurityState(
-        newSecurityState, schemeIsCryptographic, explanations, insecureContentStatus, summary);
+    this._mainView.updateSecurityState(newSecurityState, schemeIsCryptographic, explanations, summary);
   }
 
   /**
@@ -111,9 +109,8 @@
     const securityState = /** @type {!Protocol.Security.SecurityState} */ (data.securityState);
     const schemeIsCryptographic = /** @type {boolean} */ (data.schemeIsCryptographic);
     const explanations = /** @type {!Array<!Protocol.Security.SecurityStateExplanation>} */ (data.explanations);
-    const insecureContentStatus = /** @type {?Protocol.Security.InsecureContentStatus} */ (data.insecureContentStatus);
     const summary = /** @type {?string} */ (data.summary);
-    this._updateSecurityState(securityState, schemeIsCryptographic, explanations, insecureContentStatus, summary);
+    this._updateSecurityState(securityState, schemeIsCryptographic, explanations, summary);
   }
 
   selectAndSwitchToMainView() {
@@ -645,10 +642,9 @@
    * @param {!Protocol.Security.SecurityState} newSecurityState
    * @param {boolean} schemeIsCryptographic
    * @param {!Array<!Protocol.Security.SecurityStateExplanation>} explanations
-   * @param {?Protocol.Security.InsecureContentStatus} insecureContentStatus
    * @param {?string} summary
    */
-  updateSecurityState(newSecurityState, schemeIsCryptographic, explanations, insecureContentStatus, summary) {
+  updateSecurityState(newSecurityState, schemeIsCryptographic, explanations, summary) {
     // Remove old state.
     // It's safe to call this even when this._securityState is undefined.
     this._summarySection.classList.remove('security-summary-' + this._securityState);
@@ -666,7 +662,7 @@
     // Use override summary if present, otherwise use base explanation
     this._summaryText.textContent = summary || summaryExplanationStrings[this._securityState];
 
-    this._explanations = explanations, this._insecureContentStatus = insecureContentStatus;
+    this._explanations = explanations;
     this._schemeIsCryptographic = schemeIsCryptographic;
 
     this.refreshExplanations();
diff --git a/third_party/blink/renderer/modules/accessibility/ax_position.cc b/third_party/blink/renderer/modules/accessibility/ax_position.cc
index 153d5256..5a123db 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_position.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_position.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
 #include "third_party/blink/renderer/core/editing/iterators/character_iterator.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
+#include "third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h"
 #include "third_party/blink/renderer/core/editing/position.h"
 #include "third_party/blink/renderer/core/editing/position_with_affinity.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_layout_object.h"
@@ -207,9 +208,12 @@
     // character offset.
     // TODO(nektar): Use LayoutNG offset mapping instead of
     // |TextIterator|.
+    TextIteratorBehavior::Builder iterator_builder;
+    const TextIteratorBehavior text_iterator_behavior =
+        iterator_builder.SetDoesNotEmitSpaceBeyondRangeEnd(true).Build();
     const auto first_position = Position::FirstPositionInNode(*container_node);
-    int offset =
-        TextIterator::RangeLength(first_position, parent_anchored_position);
+    int offset = TextIterator::RangeLength(
+        first_position, parent_anchored_position, text_iterator_behavior);
     ax_position.text_offset_or_child_index_ = offset;
     ax_position.affinity_ = affinity;
     DCHECK(ax_position.IsValid());
@@ -365,11 +369,15 @@
   }
 
   // TODO(nektar): Use LayoutNG offset mapping instead of |TextIterator|.
+  TextIteratorBehavior::Builder iterator_builder;
+  const TextIteratorBehavior text_iterator_behavior =
+      iterator_builder.SetDoesNotEmitSpaceBeyondRangeEnd(true).Build();
   const auto first_position =
       Position::FirstPositionInNode(*container_object_->GetNode());
   const auto last_position =
       Position::LastPositionInNode(*container_object_->GetNode());
-  return TextIterator::RangeLength(first_position, last_position);
+  return TextIterator::RangeLength(first_position, last_position,
+                                   text_iterator_behavior);
 }
 
 TextAffinity AXPosition::Affinity() const {
@@ -731,9 +739,13 @@
   }
 
   // TODO(nektar): Use LayoutNG offset mapping instead of |TextIterator|.
+  TextIteratorBehavior::Builder iterator_builder;
+  const TextIteratorBehavior text_iterator_behavior =
+      iterator_builder.SetDoesNotEmitSpaceBeyondRangeEnd(true).Build();
   const auto first_position = Position::FirstPositionInNode(*container_node);
   const auto last_position = Position::LastPositionInNode(*container_node);
-  CharacterIterator character_iterator(first_position, last_position);
+  CharacterIterator character_iterator(first_position, last_position,
+                                       text_iterator_behavior);
   const EphemeralRange range = character_iterator.CalculateCharacterSubrange(
       0, adjusted_position.text_offset_or_child_index_);
   return PositionWithAffinity(range.EndPosition(), affinity_);
diff --git a/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.cc b/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.cc
index b088fc6..d6d5842c 100644
--- a/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.cc
+++ b/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.cc
@@ -125,6 +125,10 @@
   return HasDocumentDefinition();
 }
 
+int CSSPaintImageGeneratorImpl::WorkletId() const {
+  return paint_worklet_->WorkletId();
+}
+
 void CSSPaintImageGeneratorImpl::Trace(blink::Visitor* visitor) {
   visitor->Trace(observer_);
   visitor->Trace(paint_worklet_);
diff --git a/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.h b/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.h
index 6fa7f01c..a6d8658c 100644
--- a/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.h
+++ b/third_party/blink/renderer/modules/csspaint/css_paint_image_generator_impl.h
@@ -41,6 +41,7 @@
   bool HasAlpha() const final;
   const Vector<CSSSyntaxDescriptor>& InputArgumentTypes() const final;
   bool IsImageGeneratorReady() const final;
+  int WorkletId() const final;
 
   // Should be called from the PaintWorkletGlobalScope when a javascript class
   // is registered with the same name.
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet.cc
index 2c36e3db..5f42eec8 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/csspaint/paint_worklet.h"
 
+#include "base/atomic_sequence_num.h"
 #include "base/rand_util.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -17,6 +18,15 @@
 
 namespace blink {
 
+namespace {
+base::AtomicSequenceNumber g_next_worklet_id;
+int NextId() {
+  // Start id from 1. This way it safe to use it as key in hashmap with default
+  // key traits.
+  return g_next_worklet_id.GetNext() + 1;
+}
+}  // namespace
+
 const wtf_size_t PaintWorklet::kNumGlobalScopes = 2u;
 const size_t kMaxPaintCountToSwitch = 30u;
 DocumentPaintDefinition* const kInvalidDocumentPaintDefinition = nullptr;
@@ -41,7 +51,8 @@
     : Worklet(frame->GetDocument()),
       Supplement<LocalDOMWindow>(*frame->DomWindow()),
       pending_generator_registry_(
-          MakeGarbageCollected<PaintWorkletPendingGeneratorRegistry>()) {}
+          MakeGarbageCollected<PaintWorkletPendingGeneratorRegistry>()),
+      worklet_id_(NextId()) {}
 
 PaintWorklet::~PaintWorklet() = default;
 
@@ -135,8 +146,8 @@
         pending_generator_registry_, GetNumberOfGlobalScopes() + 1);
   }
 
-  PaintWorkletProxyClient* proxy_client =
-      PaintWorkletProxyClient::Create(To<Document>(GetExecutionContext()));
+  PaintWorkletProxyClient* proxy_client = PaintWorkletProxyClient::Create(
+      To<Document>(GetExecutionContext()), worklet_id_);
   WorkerClients* worker_clients = WorkerClients::Create();
   ProvidePaintWorkletProxyClientTo(worker_clients, proxy_client);
 
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet.h b/third_party/blink/renderer/modules/csspaint/paint_worklet.h
index 6ff9115..c0d771f2 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet.h
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet.h
@@ -48,6 +48,7 @@
   DocumentDefinitionMap& GetDocumentDefinitionMap() {
     return document_definition_map_;
   }
+  int WorkletId() const { return worklet_id_; }
   void Trace(blink::Visitor*) override;
 
  protected:
@@ -81,6 +82,12 @@
   // scope. SelectGlobalScope resets this at the beginning of each frame.
   int paints_before_switching_global_scope_;
 
+  // An atomic sequence number to ensure that it is unique for each paint
+  // worklet. This id is integrated in the PaintWorkletInput which will be used
+  // in PaintWorkletPaintDispatcher::Paint, to identify the right painter, to
+  // paint the image.
+  int worklet_id_;
+
   DISALLOW_COPY_AND_ASSIGN(PaintWorklet);
 };
 
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
index cac79df..1bb542b 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
@@ -20,22 +20,6 @@
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 
 namespace blink {
-namespace {
-
-class MockPaintWorkletProxyClient : public PaintWorkletProxyClient {
- public:
-  MockPaintWorkletProxyClient()
-      : PaintWorkletProxyClient(nullptr), did_set_global_scope_(false) {}
-  void SetGlobalScope(WorkletGlobalScope*) override {
-    did_set_global_scope_ = true;
-  }
-  bool did_set_global_scope() { return did_set_global_scope_; }
-
- private:
-  bool did_set_global_scope_;
-};
-
-}  // namespace
 
 // TODO(smcgruer): Extract a common base class between this and
 // AnimationWorkletGlobalScope.
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc
index 1614a76..0a1ac7c 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc
@@ -19,19 +19,22 @@
     "PaintWorkletProxyClient";
 
 // static
-PaintWorkletProxyClient* PaintWorkletProxyClient::Create(Document* document) {
+PaintWorkletProxyClient* PaintWorkletProxyClient::Create(Document* document,
+                                                         int worklet_id) {
   WebLocalFrameImpl* local_frame =
       WebLocalFrameImpl::FromFrame(document->GetFrame());
 
   scoped_refptr<PaintWorkletPaintDispatcher> compositor_painter_dispatcher =
       local_frame->LocalRootFrameWidget()->EnsureCompositorPaintDispatcher();
   return MakeGarbageCollected<PaintWorkletProxyClient>(
-      std::move(compositor_painter_dispatcher));
+      worklet_id, std::move(compositor_painter_dispatcher));
 }
 
 PaintWorkletProxyClient::PaintWorkletProxyClient(
+    int worklet_id,
     scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee)
     : compositor_paintee_(std::move(compositor_paintee)),
+      worklet_id_(worklet_id),
       state_(RunState::kUninitialized) {
   DCHECK(IsMainThread());
 }
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h
index c8a0cccf..4ad2396 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h
@@ -33,9 +33,10 @@
  public:
   static const char kSupplementName[];
 
-  static PaintWorkletProxyClient* Create(Document*);
+  static PaintWorkletProxyClient* Create(Document*, int worklet_id);
 
   PaintWorkletProxyClient(
+      int worklet_id,
       scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee);
   virtual ~PaintWorkletProxyClient() = default;
 
@@ -51,6 +52,7 @@
                            PaintWorkletProxyClientConstruction);
 
   scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee_;
+  const int worklet_id_;
   CrossThreadPersistent<PaintWorkletGlobalScope> global_scope_;
   enum RunState { kUninitialized, kWorking, kDisposed } state_;
 };
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
index 614ed29..2aa76747 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
@@ -17,14 +17,16 @@
 
 TEST_F(PaintWorkletProxyClientTest, PaintWorkletProxyClientConstruction) {
   PaintWorkletProxyClient* proxy_client =
-      MakeGarbageCollected<PaintWorkletProxyClient>(nullptr);
+      MakeGarbageCollected<PaintWorkletProxyClient>(1, nullptr);
+  EXPECT_EQ(proxy_client->worklet_id_, 1);
   EXPECT_EQ(proxy_client->compositor_paintee_, nullptr);
 
   scoped_refptr<PaintWorkletPaintDispatcher> dispatcher =
       base::MakeRefCounted<PaintWorkletPaintDispatcher>();
 
   proxy_client =
-      MakeGarbageCollected<PaintWorkletProxyClient>(std::move(dispatcher));
+      MakeGarbageCollected<PaintWorkletProxyClient>(1, std::move(dispatcher));
+  EXPECT_EQ(proxy_client->worklet_id_, 1);
   EXPECT_NE(proxy_client->compositor_paintee_, nullptr);
 }
 
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index 5cf8cc7..4ae119aba 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -57,7 +57,9 @@
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_object.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
+#include "third_party/blink/renderer/modules/accessibility/ax_position.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_range.h"
+#include "third_party/blink/renderer/modules/accessibility/ax_selection.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
@@ -773,6 +775,55 @@
                             WebAXObject& focus_object,
                             int& focus_offset,
                             ax::mojom::TextAffinity& focus_affinity) const {
+  anchor_object = WebAXObject();
+  anchor_offset = -1;
+  anchor_affinity = ax::mojom::TextAffinity::kDownstream;
+  focus_object = WebAXObject();
+  focus_offset = -1;
+  focus_affinity = ax::mojom::TextAffinity::kDownstream;
+
+  if (IsDetached())
+    return;
+
+  WebAXObject focus = FromWebDocumentFocused(GetDocument());
+  if (focus.IsDetached())
+    return;
+
+  const auto ax_selection =
+      focus.private_->IsNativeTextControl()
+          ? AXSelection::FromCurrentSelection(
+                ToTextControl(*focus.private_->GetNode()))
+          : AXSelection::FromCurrentSelection(*focus.private_->GetDocument());
+  if (!ax_selection)
+    return;
+
+  const AXPosition base = ax_selection.Base();
+  anchor_object = WebAXObject(const_cast<AXObject*>(base.ContainerObject()));
+  const AXPosition extent = ax_selection.Extent();
+  focus_object = WebAXObject(const_cast<AXObject*>(extent.ContainerObject()));
+
+  if (base.IsTextPosition()) {
+    anchor_offset = base.TextOffset();
+    anchor_affinity = ToAXAffinity(base.Affinity());
+  } else {
+    anchor_offset = base.ChildIndex();
+  }
+
+  if (extent.IsTextPosition()) {
+    focus_offset = extent.TextOffset();
+    focus_affinity = ToAXAffinity(extent.Affinity());
+  } else {
+    focus_offset = extent.ChildIndex();
+  }
+}
+
+void WebAXObject::SelectionDeprecated(
+    WebAXObject& anchor_object,
+    int& anchor_offset,
+    ax::mojom::TextAffinity& anchor_affinity,
+    WebAXObject& focus_object,
+    int& focus_offset,
+    ax::mojom::TextAffinity& focus_affinity) const {
   if (IsDetached()) {
     anchor_object = WebAXObject();
     anchor_offset = -1;
@@ -811,6 +862,90 @@
                                int anchor_offset,
                                const WebAXObject& focus_object,
                                int focus_offset) const {
+  if (IsDetached() || anchor_object.IsDetached() || focus_object.IsDetached())
+    return false;
+
+  AXPosition ax_base, ax_extent;
+  if (static_cast<const AXObject*>(anchor_object)->IsTextObject() ||
+      static_cast<const AXObject*>(anchor_object)->IsNativeTextControl()) {
+    ax_base =
+        AXPosition::CreatePositionInTextObject(*anchor_object, anchor_offset);
+  } else if (anchor_offset <= 0) {
+    ax_base = AXPosition::CreateFirstPositionInObject(*anchor_object);
+  } else if (anchor_offset >= static_cast<int>(anchor_object.ChildCount())) {
+    ax_base = AXPosition::CreateLastPositionInObject(*anchor_object);
+  } else {
+    DCHECK_GE(anchor_offset, 0);
+    ax_base = AXPosition::CreatePositionBeforeObject(
+        *anchor_object.ChildAt(static_cast<unsigned int>(anchor_offset)));
+  }
+
+  if (static_cast<const AXObject*>(focus_object)->IsTextObject() ||
+      static_cast<const AXObject*>(focus_object)->IsNativeTextControl()) {
+    ax_extent =
+        AXPosition::CreatePositionInTextObject(*focus_object, focus_offset);
+  } else if (focus_offset <= 0) {
+    ax_extent = AXPosition::CreateFirstPositionInObject(*focus_object);
+  } else if (focus_offset >= static_cast<int>(focus_object.ChildCount())) {
+    ax_extent = AXPosition::CreateLastPositionInObject(*focus_object);
+  } else {
+    DCHECK_GE(focus_offset, 0);
+    ax_extent = AXPosition::CreatePositionBeforeObject(
+        *focus_object.ChildAt(static_cast<unsigned int>(focus_offset)));
+  }
+
+  AXSelection::Builder builder;
+  AXSelection ax_selection =
+      builder.SetBase(ax_base).SetExtent(ax_extent).Build();
+  return ax_selection.Select();
+}
+
+unsigned WebAXObject::SelectionEnd() const {
+  if (IsDetached())
+    return 0;
+
+  WebAXObject focus = FromWebDocumentFocused(GetDocument());
+  if (focus.IsDetached())
+    return 0;
+
+  const auto ax_selection =
+      focus.private_->IsNativeTextControl()
+          ? AXSelection::FromCurrentSelection(
+                ToTextControl(*focus.private_->GetNode()))
+          : AXSelection::FromCurrentSelection(*focus.private_->GetDocument());
+  if (!ax_selection)
+    return 0;
+
+  if (ax_selection.Extent().IsTextPosition())
+    return ax_selection.Extent().TextOffset();
+  return ax_selection.Extent().ChildIndex();
+}
+
+unsigned WebAXObject::SelectionStart() const {
+  if (IsDetached())
+    return 0;
+
+  WebAXObject focus = FromWebDocumentFocused(GetDocument());
+  if (focus.IsDetached())
+    return 0;
+
+  const auto ax_selection =
+      focus.private_->IsNativeTextControl()
+          ? AXSelection::FromCurrentSelection(
+                ToTextControl(*focus.private_->GetNode()))
+          : AXSelection::FromCurrentSelection(*focus.private_->GetDocument());
+  if (!ax_selection)
+    return 0;
+
+  if (ax_selection.Base().IsTextPosition())
+    return ax_selection.Base().TextOffset();
+  return ax_selection.Base().ChildIndex();
+}
+
+bool WebAXObject::SetSelectionDeprecated(const WebAXObject& anchor_object,
+                                         int anchor_offset,
+                                         const WebAXObject& focus_object,
+                                         int focus_offset) const {
   if (IsDetached())
     return false;
 
@@ -820,7 +955,7 @@
   return private_->RequestSetSelectionAction(ax_selection);
 }
 
-unsigned WebAXObject::SelectionEnd() const {
+unsigned WebAXObject::SelectionEndDeprecated() const {
   if (IsDetached())
     return 0;
 
@@ -831,7 +966,7 @@
   return ax_selection.focus_offset;
 }
 
-unsigned WebAXObject::SelectionStart() const {
+unsigned WebAXObject::SelectionStartDeprecated() const {
   if (IsDetached())
     return 0;
 
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_popup_menu_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_popup_menu_element.cc
index 3e0f605..4d0044a8 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_popup_menu_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_popup_menu_element.cc
@@ -214,11 +214,17 @@
   if (!IsWanted())
     return;
 
-  if (!GetDocument().FocusedElement() ||
-      (GetDocument().FocusedElement()->parentElement() != this &&
-       GetDocument().FocusedElement() != this)) {
-    SetIsWanted(false);
+  // Cancel hiding if the focused element is a descendent of this element
+  auto* focused_element = GetDocument().FocusedElement();
+  while (focused_element) {
+    if (focused_element == this) {
+      return;
+    }
+
+    focused_element = focused_element->parentElement();
   }
+
+  SetIsWanted(false);
 }
 
 // Focus the given item in the list if it is displayed. Returns whether it was
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc
index 421487e..fbab43b 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc
@@ -100,6 +100,9 @@
       MediaElement().DisableAutomaticTextTrackSelection();
     }
 
+    // Close the text track list,
+    // since we don't support selecting multiple tracks
+    SetIsWanted(false);
     event.SetDefaultHandled();
   }
   MediaControlPopupMenuElement::DefaultEventHandler(event);
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager.cc b/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager.cc
index 50fb3b6f2..7efcfb71 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager.cc
@@ -267,6 +267,9 @@
 std::unique_ptr<InstalledScriptsManager::ScriptData>
 ServiceWorkerInstalledScriptsManager::GetScriptData(const KURL& script_url) {
   DCHECK(!IsMainThread());
+  TRACE_EVENT1("ServiceWorker",
+               "ServiceWorkerInstalledScriptsManager::GetScriptData", "url",
+               script_url.GetString().Utf8().data());
   if (!IsScriptInstalled(script_url))
     return nullptr;
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index aeb9f00..06c08f4 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -93,7 +93,6 @@
 #include "third_party/blink/renderer/modules/webgl/webgl_vertex_array_object.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_oes.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/v8_binding_macros.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
diff --git a/third_party/blink/renderer/modules/worklet/worklet_thread_test_common.cc b/third_party/blink/renderer/modules/worklet/worklet_thread_test_common.cc
index edae41a6..a42679ef 100644
--- a/third_party/blink/renderer/modules/worklet/worklet_thread_test_common.cc
+++ b/third_party/blink/renderer/modules/worklet/worklet_thread_test_common.cc
@@ -51,7 +51,7 @@
     WorkerReportingProxy* reporting_proxy,
     PaintWorkletProxyClient* proxy_client) {
   if (!proxy_client)
-    proxy_client = MakeGarbageCollected<PaintWorkletProxyClient>(nullptr);
+    proxy_client = MakeGarbageCollected<PaintWorkletProxyClient>(1, nullptr);
   WorkerClients* clients = WorkerClients::Create();
   ProvidePaintWorkletProxyClientTo(clients, proxy_client);
 
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index ed38474..c7504e6 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -446,10 +446,6 @@
     "bindings/script_state.h",
     "bindings/script_wrappable.cc",
     "bindings/script_wrappable.h",
-    "bindings/script_wrappable_marking_visitor.cc",
-    "bindings/script_wrappable_marking_visitor.h",
-    "bindings/script_wrappable_visitor.h",
-    "bindings/script_wrappable_visitor_verifier.h",
     "bindings/shared_persistent.h",
     "bindings/string_resource.cc",
     "bindings/string_resource.h",
diff --git a/third_party/blink/renderer/platform/bindings/dom_data_store.h b/third_party/blink/renderer/platform/bindings/dom_data_store.h
index 6e1213cae..3387994c 100644
--- a/third_party/blink/renderer/platform/bindings/dom_data_store.h
+++ b/third_party/blink/renderer/platform/bindings/dom_data_store.h
@@ -35,7 +35,6 @@
 #include "base/optional.h"
 #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
 #include "third_party/blink/renderer/platform/heap/unified_heap_marking_visitor.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable.cc b/third_party/blink/renderer/platform/bindings/script_wrappable.cc
index d32c3d53..5d4d7bd 100644
--- a/third_party/blink/renderer/platform/bindings/script_wrappable.cc
+++ b/third_party/blink/renderer/platform/bindings/script_wrappable.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 
 #include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.cc b/third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.cc
deleted file mode 100644
index 03f615b3..0000000
--- a/third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.cc
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
-
-#include "base/auto_reset.h"
-#include "third_party/blink/public/platform/platform.h"
-#include "third_party/blink/renderer/platform/bindings/active_script_wrappable_base.h"
-#include "third_party/blink/renderer/platform/bindings/custom_wrappable.h"
-#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
-#include "third_party/blink/renderer/platform/bindings/scoped_persistent.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor_verifier.h"
-#include "third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h"
-#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
-#include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
-#include "third_party/blink/renderer/platform/heap/heap_compact.h"
-#include "third_party/blink/renderer/platform/heap/heap_page.h"
-#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/wtf/time.h"
-
-namespace blink {
-
-ScriptWrappableMarkingVisitor::~ScriptWrappableMarkingVisitor() = default;
-
-void ScriptWrappableMarkingVisitor::TracePrologue() {
-  // This CHECK ensures that wrapper tracing is not started from scopes
-  // that forbid GC execution, e.g., constructors.
-  CHECK(ThreadState::Current());
-  PerformCleanup();
-
-  CHECK(!tracing_in_progress_);
-  CHECK(!should_cleanup_);
-  CHECK(headers_to_unmark_.IsEmpty());
-  CHECK(marking_deque_.IsEmpty());
-  CHECK(verifier_deque_.IsEmpty());
-  tracing_in_progress_ = true;
-  ThreadState::Current()->EnableWrapperTracingBarrier();
-}
-
-void ScriptWrappableMarkingVisitor::EnterFinalPause(EmbedderStackState) {
-  CHECK(ThreadState::Current());
-  ThreadState::Current()->DisableWrapperTracingBarrier();
-  ActiveScriptWrappableBase::TraceActiveScriptWrappables(isolate(), this);
-}
-
-void ScriptWrappableMarkingVisitor::TraceEpilogue() {
-  CHECK(ThreadState::Current());
-  DCHECK(marking_deque_.IsEmpty());
-#if DCHECK_IS_ON()
-  ScriptWrappableVisitorVerifier verifier(ThreadState::Current());
-  for (auto& marking_data : verifier_deque_) {
-    // Check that all children of this object are marked.
-    marking_data.Trace(&verifier);
-  }
-#endif
-
-  should_cleanup_ = true;
-  tracing_in_progress_ = false;
-  ScheduleIdleLazyCleanup();
-}
-
-void ScriptWrappableMarkingVisitor::AbortTracingForTermination() {
-  CHECK(ThreadState::Current());
-  should_cleanup_ = true;
-  tracing_in_progress_ = false;
-  ThreadState::Current()->DisableWrapperTracingBarrier();
-  PerformCleanup();
-}
-
-bool ScriptWrappableMarkingVisitor::IsTracingDone() {
-  return marking_deque_.empty();
-}
-
-bool ScriptWrappableMarkingVisitor::IsRootForNonTracingGC(
-    const v8::TracedGlobal<v8::Value>& handle) {
-  return UnifiedHeapController::IsRootForNonTracingGCInternal(handle);
-}
-
-void ScriptWrappableMarkingVisitor::PerformCleanup() {
-  if (!should_cleanup_)
-    return;
-
-  CHECK(!tracing_in_progress_);
-  for (auto* header : headers_to_unmark_) {
-    // Dead objects residing in the marking deque may become invalid due to
-    // minor garbage collections and are therefore set to nullptr. We have
-    // to skip over such objects.
-    if (header)
-      header->UnmarkWrapperHeader();
-  }
-
-  headers_to_unmark_.clear();
-  marking_deque_.clear();
-  verifier_deque_.clear();
-  should_cleanup_ = false;
-}
-
-void ScriptWrappableMarkingVisitor::ScheduleIdleLazyCleanup() {
-  if (idle_cleanup_task_scheduled_)
-    return;
-
-  ThreadScheduler::Current()->PostIdleTask(
-      FROM_HERE, WTF::Bind(&ScriptWrappableMarkingVisitor::PerformLazyCleanup,
-                           WTF::Unretained(this)));
-  idle_cleanup_task_scheduled_ = true;
-}
-
-void ScriptWrappableMarkingVisitor::PerformLazyCleanup(TimeTicks deadline) {
-  idle_cleanup_task_scheduled_ = false;
-
-  if (!should_cleanup_)
-    return;
-
-  TRACE_EVENT1("blink_gc,devtools.timeline",
-               "ScriptWrappableMarkingVisitor::performLazyCleanup",
-               "idleDeltaInSeconds",
-               (deadline - CurrentTimeTicks()).InSecondsF());
-
-  const int kDeadlineCheckInterval = 2500;
-  int processed_wrapper_count = 0;
-  for (auto it = headers_to_unmark_.rbegin();
-       it != headers_to_unmark_.rend();) {
-    auto* header = *it;
-    // Dead objects residing in the marking deque may become invalid due to
-    // minor garbage collections and are therefore set to nullptr. We have
-    // to skip over such objects.
-    if (header)
-      header->UnmarkWrapperHeader();
-
-    ++it;
-    headers_to_unmark_.pop_back();
-
-    processed_wrapper_count++;
-    if (processed_wrapper_count % kDeadlineCheckInterval == 0) {
-      if (deadline <= CurrentTimeTicks()) {
-        ScheduleIdleLazyCleanup();
-        return;
-      }
-    }
-  }
-
-  // Unmarked all headers.
-  CHECK(headers_to_unmark_.IsEmpty());
-  marking_deque_.clear();
-  verifier_deque_.clear();
-  should_cleanup_ = false;
-}
-
-void ScriptWrappableMarkingVisitor::RegisterV8Reference(
-    const std::pair<void*, void*>& internal_fields) {
-  DCHECK(tracing_in_progress_);
-
-  WrapperTypeInfo* wrapper_type_info =
-      reinterpret_cast<WrapperTypeInfo*>(internal_fields.first);
-  if (wrapper_type_info->gin_embedder != gin::GinEmbedder::kEmbedderBlink) {
-    return;
-  }
-
-  wrapper_type_info->TraceWithWrappers(this, internal_fields.second);
-}
-
-void ScriptWrappableMarkingVisitor::RegisterV8References(
-    const std::vector<std::pair<void*, void*>>&
-        internal_fields_of_potential_wrappers) {
-  CHECK(ThreadState::Current());
-  for (auto& pair : internal_fields_of_potential_wrappers) {
-    RegisterV8Reference(pair);
-  }
-}
-
-bool ScriptWrappableMarkingVisitor::AdvanceTracing(double deadline_in_ms) {
-  constexpr int kObjectsBeforeInterrupt = 100;
-  // Do not drain the marking deque in a state where we can generally not
-  // perform a GC. This makes sure that TraceTraits and friends find
-  // themselves in a well-defined environment, e.g., properly set up vtables.
-  CHECK(ThreadState::Current());
-  CHECK(tracing_in_progress_);
-  TimeTicks deadline =
-      TimeTicks() + TimeDelta::FromMillisecondsD(deadline_in_ms);
-  while (deadline.is_max() || WTF::CurrentTimeTicks() < deadline) {
-    for (int objects = 0; objects++ < kObjectsBeforeInterrupt;) {
-      if (marking_deque_.IsEmpty()) {
-        return true;
-      }
-      marking_deque_.TakeFirst().Trace(this);
-    }
-  }
-  return false;
-}
-
-void ScriptWrappableMarkingVisitor::MarkWrapperHeader(
-    HeapObjectHeader* header) {
-  DCHECK(!header->IsWrapperHeaderMarked());
-  // Verify that no compactable & movable objects are slated for
-  // lazy unmarking.
-  DCHECK(!HeapCompact::IsCompactableArena(
-      PageFromObject(header)->Arena()->ArenaIndex()));
-  header->MarkWrapperHeader();
-  headers_to_unmark_.push_back(header);
-}
-
-void ScriptWrappableMarkingVisitor::WriteBarrier(
-    v8::Isolate* isolate,
-    const TraceWrapperV8Reference<v8::Value>& dst_object) {
-  if (dst_object.IsEmpty() || !ThreadState::IsAnyWrapperTracing())
-    return;
-  ScriptWrappableMarkingVisitor* visitor = CurrentVisitor(isolate);
-  if (!visitor->WrapperTracingInProgress())
-    return;
-
-  // Conservatively assume that the source object containing |dst_object| is
-  // marked.
-  visitor->Trace(dst_object);
-}
-
-void ScriptWrappableMarkingVisitor::WriteBarrier(
-    v8::Isolate* isolate,
-    const WrapperTypeInfo* wrapper_type_info,
-    void* object) {
-  if (!ThreadState::IsAnyWrapperTracing())
-    return;
-  ScriptWrappableMarkingVisitor* visitor = CurrentVisitor(isolate);
-  if (!visitor->WrapperTracingInProgress())
-    return;
-
-  wrapper_type_info->TraceWithWrappers(visitor, object);
-}
-
-void ScriptWrappableMarkingVisitor::Visit(
-    const TraceWrapperV8Reference<v8::Value>& traced_wrapper) {
-  // The write barrier may try to mark a wrapper because cleanup is still
-  // delayed. Bail out in this case. We also allow unconditional marking which
-  // requires us to bail out here when tracing is not in progress.
-  if (!tracing_in_progress_ || traced_wrapper.Get().IsEmpty())
-    return;
-
-  RegisterEmbedderReference(traced_wrapper.Get());
-}
-
-void ScriptWrappableMarkingVisitor::VisitWithWrappers(
-    void* object,
-    TraceDescriptor descriptor) {
-  HeapObjectHeader* header =
-      HeapObjectHeader::FromPayload(descriptor.base_object_payload);
-  if (header->IsWrapperHeaderMarked())
-    return;
-  MarkWrapperHeader(header);
-  DCHECK(tracing_in_progress_);
-  DCHECK(header->IsWrapperHeaderMarked());
-  marking_deque_.push_back(MarkingDequeItem(descriptor));
-#if DCHECK_IS_ON()
-  verifier_deque_.push_back(MarkingDequeItem(descriptor));
-#endif
-}
-
-void ScriptWrappableMarkingVisitor::VisitBackingStoreStrongly(
-    void* object,
-    void** object_slot,
-    TraceDescriptor desc) {
-  if (!object)
-    return;
-  desc.callback(this, desc.base_object_payload);
-}
-
-void ScriptWrappableMarkingVisitor::InvalidateDeadObjectsInMarkingDeque() {
-  for (auto it = marking_deque_.begin(); it != marking_deque_.end(); ++it) {
-    auto& marking_data = *it;
-    if (marking_data.ShouldBeInvalidated()) {
-      marking_data.Invalidate();
-    }
-  }
-  for (auto it = verifier_deque_.begin(); it != verifier_deque_.end(); ++it) {
-    auto& marking_data = *it;
-    if (marking_data.ShouldBeInvalidated()) {
-      marking_data.Invalidate();
-    }
-  }
-  for (auto** it = headers_to_unmark_.begin(); it != headers_to_unmark_.end();
-       ++it) {
-    auto* header = *it;
-    if (header && !header->IsMarked()) {
-      *it = nullptr;
-    }
-  }
-}
-
-void ScriptWrappableMarkingVisitor::InvalidateDeadObjectsInMarkingDeque(
-    v8::Isolate* isolate) {
-  ScriptWrappableMarkingVisitor* script_wrappable_visitor =
-      V8PerIsolateData::From(isolate)->GetScriptWrappableMarkingVisitor();
-  if (script_wrappable_visitor)
-    script_wrappable_visitor->InvalidateDeadObjectsInMarkingDeque();
-}
-
-void ScriptWrappableMarkingVisitor::PerformCleanup(v8::Isolate* isolate) {
-  ScriptWrappableMarkingVisitor* script_wrappable_visitor =
-      V8PerIsolateData::From(isolate)->GetScriptWrappableMarkingVisitor();
-  if (script_wrappable_visitor)
-    script_wrappable_visitor->PerformCleanup();
-}
-
-ScriptWrappableMarkingVisitor* ScriptWrappableMarkingVisitor::CurrentVisitor(
-    v8::Isolate* isolate) {
-  return V8PerIsolateData::From(isolate)->GetScriptWrappableMarkingVisitor();
-}
-
-bool ScriptWrappableMarkingVisitor::MarkingDequeContains(void* needle) {
-  for (auto item : marking_deque_) {
-    if (item.RawObjectPointer() == needle)
-      return true;
-  }
-  return false;
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h b/third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h
deleted file mode 100644
index 12dbd34..0000000
--- a/third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_MARKING_VISITOR_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_MARKING_VISITOR_H_
-
-#include "base/gtest_prod_util.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
-#include "third_party/blink/renderer/platform/heap/heap_page.h"
-#include "third_party/blink/renderer/platform/heap/threading_traits.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
-#include "third_party/blink/renderer/platform/wtf/deque.h"
-#include "third_party/blink/renderer/platform/wtf/time.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
-#include "v8/include/v8.h"
-
-namespace blink {
-
-class HeapObjectHeader;
-class ScriptWrappableVisitor;
-template <typename T>
-class TraceWrapperV8Reference;
-struct WrapperTypeInfo;
-
-// ScriptWrappableVisitor is used to trace through Blink's heap to find all
-// reachable wrappers. V8 calls this visitor during its garbage collection,
-// see v8::EmbedderHeapTracer.
-class PLATFORM_EXPORT ScriptWrappableMarkingVisitor
-    : public v8::EmbedderHeapTracer,
-      public ScriptWrappableVisitor {
-  DISALLOW_IMPLICIT_CONSTRUCTORS(ScriptWrappableMarkingVisitor);
-
- public:
-  static ScriptWrappableMarkingVisitor* CurrentVisitor(v8::Isolate*);
-
-  // Replace all dead objects in the marking deque with nullptr after Oilpan
-  // garbage collection.
-  static void InvalidateDeadObjectsInMarkingDeque(v8::Isolate*);
-
-  // Immediately clean up all wrappers.
-  static void PerformCleanup(v8::Isolate*);
-
-  // Conservative Dijkstra barrier.
-  //
-  // On assignment 'x.a = y' during incremental marking the Dijkstra barrier
-  // suggests checking the color of 'x' and only mark 'y' if 'x' is marked.
-  //
-  // Since checking 'x' is expensive in the current setting, as it requires
-  // either a back pointer or expensive lookup logic due to large objects and
-  // multiple inheritance, just assume that 'x' is black. We assume here that
-  // since an object 'x' is referenced for a write, it will generally also be
-  // alive in the current GC cycle.
-  template <typename T>
-  inline static void WriteBarrier(const T* dst_object);
-
-  template <typename T>
-  static void WriteBarrier(TraceWrapperMember<T>* array, size_t length);
-
-  static void WriteBarrier(v8::Isolate*, const WrapperTypeInfo*, void*);
-
-  static void WriteBarrier(v8::Isolate*,
-                           const TraceWrapperV8Reference<v8::Value>&);
-
-  explicit ScriptWrappableMarkingVisitor(ThreadState* thread_state)
-      : ScriptWrappableVisitor(thread_state) {}
-  ~ScriptWrappableMarkingVisitor() override;
-
-  bool WrapperTracingInProgress() const { return tracing_in_progress_; }
-
-  void AbortTracingForTermination();
-
-  // v8::EmbedderHeapTracer interface.
-  void TracePrologue() override;
-  void RegisterV8References(const std::vector<std::pair<void*, void*>>&
-                                internal_fields_of_potential_wrappers) override;
-  void RegisterV8Reference(const std::pair<void*, void*>& internal_fields);
-  bool AdvanceTracing(double deadline_in_ms) override;
-  void TraceEpilogue() override;
-  void EnterFinalPause(EmbedderStackState) override;
-  bool IsTracingDone() override;
-  bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>&) override;
-
-  // ScriptWrappableVisitor interface.
-  void Visit(const TraceWrapperV8Reference<v8::Value>&) override;
-  void VisitWithWrappers(void*, TraceDescriptor) override;
-  void VisitBackingStoreStrongly(void* object,
-                                 void** object_slot,
-                                 TraceDescriptor desc) override;
-
- protected:
-  using Visitor::Visit;
-
- private:
-  class MarkingDequeItem {
-    DISALLOW_NEW();
-
-   public:
-    explicit MarkingDequeItem(const TraceDescriptor& descriptor)
-        : raw_object_pointer_(descriptor.base_object_payload),
-          trace_callback_(descriptor.callback) {
-      DCHECK(raw_object_pointer_);
-      DCHECK(trace_callback_);
-    }
-
-    // Traces wrappers if the underlying object has not yet been invalidated.
-    inline void Trace(ScriptWrappableVisitor* visitor) const {
-      if (raw_object_pointer_) {
-        trace_callback_(visitor, const_cast<void*>(raw_object_pointer_));
-      }
-    }
-
-    inline const void* RawObjectPointer() { return raw_object_pointer_; }
-
-    // Returns true if the object is currently marked in Oilpan and false
-    // otherwise.
-    inline bool ShouldBeInvalidated() {
-      return raw_object_pointer_ && !GetHeapObjectHeader()->IsMarked();
-    }
-
-    // Invalidates the current wrapper marking data, i.e., calling Trace
-    // will result in a noop.
-    inline void Invalidate() { raw_object_pointer_ = nullptr; }
-
-   private:
-    inline const HeapObjectHeader* GetHeapObjectHeader() {
-      return HeapObjectHeader::FromPayload(raw_object_pointer_);
-    }
-
-    const void* raw_object_pointer_;
-    TraceCallback trace_callback_;
-  };
-
-  void MarkWrapperHeader(HeapObjectHeader*);
-
-  // Schedule an idle task to perform a lazy (incremental) clean up of
-  // wrappers.
-  void ScheduleIdleLazyCleanup();
-  void PerformLazyCleanup(TimeTicks deadline);
-
-  void InvalidateDeadObjectsInMarkingDeque();
-
-  // Immediately cleans up all wrappers if necessary.
-  void PerformCleanup();
-
-  WTF::Deque<MarkingDequeItem>* MarkingDeque() { return &marking_deque_; }
-
-  bool MarkingDequeContains(void* needle);
-
-  // Returns true if wrapper tracing is currently in progress, i.e.,
-  // TracePrologue has been called, and TraceEpilogue has not yet been called.
-  bool tracing_in_progress_ = false;
-
-  // Indicates whether an idle task for a lazy cleanup has already been
-  // scheduled. The flag is used to avoid scheduling multiple idle tasks for
-  // cleaning up.
-  bool idle_cleanup_task_scheduled_ = false;
-
-  // Indicates whether cleanup should currently happen. The flag is used to
-  // avoid cleaning up in the next GC cycle.
-  bool should_cleanup_ = false;
-
-  // Collection of objects we need to trace from. We assume it is safe to hold
-  // on to the raw pointers because:
-  // - oilpan object cannot move
-  // - oilpan gc will call invalidateDeadObjectsInMarkingDeque to delete all
-  //   obsolete objects
-  WTF::Deque<MarkingDequeItem> marking_deque_;
-
-  // Collection of objects we started tracing from. We assume it is safe to
-  // hold on to the raw pointers because:
-  // - oilpan object cannot move
-  // - oilpan gc will call invalidateDeadObjectsInMarkingDeque to delete
-  //   all obsolete objects
-  //
-  // These objects are used when TraceWrappablesVerifier feature is enabled to
-  // verify that all objects reachable in the atomic pause were marked
-  // incrementally. If not, there is one or multiple write barriers missing.
-  WTF::Deque<MarkingDequeItem> verifier_deque_;
-
-  // Collection of headers we need to unmark after the tracing finished. We
-  // assume it is safe to hold on to the headers because:
-  // - oilpan objects cannot move
-  // - objects this headers belong to are invalidated by the oilpan GC in
-  //   invalidateDeadObjectsInMarkingDeque.
-  WTF::Vector<HeapObjectHeader*> headers_to_unmark_;
-
-  FRIEND_TEST_ALL_PREFIXES(ScriptWrappableMarkingVisitorTest, MixinTracing);
-  FRIEND_TEST_ALL_PREFIXES(ScriptWrappableMarkingVisitorTest,
-                           OilpanClearsMarkingDequeWhenObjectDied);
-  FRIEND_TEST_ALL_PREFIXES(ScriptWrappableMarkingVisitorTest,
-                           ScriptWrappableMarkingVisitorTracesWrappers);
-  FRIEND_TEST_ALL_PREFIXES(ScriptWrappableMarkingVisitorTest,
-                           OilpanClearsHeadersWhenObjectDied);
-  FRIEND_TEST_ALL_PREFIXES(
-      ScriptWrappableMarkingVisitorTest,
-      MarkedObjectDoesNothingOnWriteBarrierHitWhenDependencyIsMarkedToo);
-  FRIEND_TEST_ALL_PREFIXES(
-      ScriptWrappableMarkingVisitorTest,
-      MarkedObjectMarksDependencyOnWriteBarrierHitWhenNotMarked);
-  FRIEND_TEST_ALL_PREFIXES(ScriptWrappableMarkingVisitorTest,
-                           WriteBarrierOnHeapVectorSwap1);
-  FRIEND_TEST_ALL_PREFIXES(ScriptWrappableMarkingVisitorTest,
-                           WriteBarrierOnHeapVectorSwap2);
-};
-
-template <typename T>
-inline void ScriptWrappableMarkingVisitor::WriteBarrier(const T* dst_object) {
-  if (!ThreadState::IsAnyWrapperTracing() || !dst_object)
-    return;
-
-  const ThreadState* thread_state =
-      ThreadStateFor<ThreadingTrait<T>::kAffinity>::GetState();
-  DCHECK(thread_state);
-  // Bail out if tracing is not in progress.
-  if (!thread_state->IsWrapperTracing())
-    return;
-
-  // If the wrapper is already marked we can bail out here.
-  if (TraceTrait<T>::GetHeapObjectHeader(const_cast<T*>(dst_object))
-          ->IsWrapperHeaderMarked())
-    return;
-
-  CurrentVisitor(thread_state->GetIsolate())
-      ->VisitWithWrappers(const_cast<T*>(dst_object),
-                          TraceDescriptorFor(dst_object));
-}
-
-template <typename T>
-inline void ScriptWrappableMarkingVisitor::WriteBarrier(
-    TraceWrapperMember<T>* array,
-    size_t length) {
-  if (!ThreadState::IsAnyWrapperTracing() || !array)
-    return;
-
-  const ThreadState* thread_state =
-      ThreadStateFor<ThreadingTrait<T>::kAffinity>::GetState();
-  DCHECK(thread_state);
-  // Bail out if tracing is not in progress.
-  if (!thread_state->IsWrapperTracing())
-    return;
-
-  for (size_t i = 0; i < length; ++i) {
-    CurrentVisitor(thread_state->GetIsolate())->Trace(array[i]);
-  }
-}
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_MARKING_VISITOR_H_
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h b/third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h
deleted file mode 100644
index ab4e5277..0000000
--- a/third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_VISITOR_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_VISITOR_H_
-
-#include "third_party/blink/renderer/platform/heap/visitor.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
-
-namespace blink {
-
-// Abstract visitor for visiting ScriptWrappable. Inherit from this
-// visitor and implement the remaining Visit*() methods to visit all
-// references related to wrappers.
-class PLATFORM_EXPORT ScriptWrappableVisitor : public Visitor {
- public:
-  explicit ScriptWrappableVisitor(ThreadState* thread_state)
-      : Visitor(thread_state) {}
-
-  // Unused blink::Visitor overrides. Derived visitors should still override
-  // the cross-component visitation methods. See Visitor documentation.
-  void Visit(void* object, TraceDescriptor desc) final {}
-  void VisitWeak(void* object,
-                 void** object_slot,
-                 TraceDescriptor desc,
-                 WeakCallback callback) final {}
-  void VisitBackingStoreWeakly(void*,
-                               void**,
-                               TraceDescriptor,
-                               WeakCallback,
-                               void*) final {}
-  void VisitBackingStoreOnly(void*, void**) final {}
-  void RegisterBackingStoreCallback(void**, MovingObjectCallback, void*) final {
-  }
-  void RegisterWeakCallback(void*, WeakCallback) final {}
-
- protected:
-  using Visitor::Visit;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_VISITOR_H_
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable_visitor_verifier.h b/third_party/blink/renderer/platform/bindings/script_wrappable_visitor_verifier.h
deleted file mode 100644
index 1dac8ec3..0000000
--- a/third_party/blink/renderer/platform/bindings/script_wrappable_visitor_verifier.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_VISITOR_VERIFIER_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_VISITOR_VERIFIER_H_
-
-#include "base/logging.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
-#include "third_party/blink/renderer/platform/heap/gc_info.h"
-
-namespace blink {
-
-// This visitor should be applied on wrapper members of each marked object
-// after marking is complete. The Visit method checks that the given wrapper
-// is also marked.
-class ScriptWrappableVisitorVerifier final : public ScriptWrappableVisitor {
- public:
-  ScriptWrappableVisitorVerifier(ThreadState* thread_state)
-      : ScriptWrappableVisitor(thread_state) {}
-
-  void Visit(const TraceWrapperV8Reference<v8::Value>&) final {}
-
-  void VisitWithWrappers(void* object, TraceDescriptor descriptor) final {
-    HeapObjectHeader* header =
-        HeapObjectHeader::FromPayload(descriptor.base_object_payload);
-    const char* name = GCInfoTable::Get()
-                           .GCInfoFromIndex(header->GcInfoIndex())
-                           ->name(descriptor.base_object_payload)
-                           .value;
-    // If this FATAL is hit, it means that a white (not discovered by
-    // Trace) object was assigned as a member to a black object (already
-    // processed by Trace). The black object will not be processed anymore
-    // so white object will remain undetected and therefore its wrapper
-    // and all wrappers reachable from it would be collected.
-    //
-    // This means there is a write barrier missing somewhere. Check the
-    // backtrace to see which types are causing this and review all the places
-    // where white object is set to a black object.
-    LOG_IF(FATAL, !header->IsWrapperHeaderMarked())
-        << "Write barrier missed for " << name;
-  }
-
-  void VisitBackingStoreStrongly(void* object,
-                                 void** object_slot,
-                                 TraceDescriptor desc) final {}
-
- protected:
-  using Visitor::Visit;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_SCRIPT_WRAPPABLE_VISITOR_VERIFIER_H_
diff --git a/third_party/blink/renderer/platform/bindings/trace_wrapper_member.h b/third_party/blink/renderer/platform/bindings/trace_wrapper_member.h
index 79304ef44..c4a74f1 100644
--- a/third_party/blink/renderer/platform/bindings/trace_wrapper_member.h
+++ b/third_party/blink/renderer/platform/bindings/trace_wrapper_member.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_TRACE_WRAPPER_MEMBER_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_TRACE_WRAPPER_MEMBER_H_
 
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
 
 namespace blink {
@@ -23,11 +22,7 @@
  public:
   TraceWrapperMember() : Member<T>(nullptr) {}
 
-  TraceWrapperMember(T* raw) : Member<T>(raw) {
-    // We have to use a write barrier here because of in-place construction
-    // in containers, such as HeapVector::push_back.
-    ScriptWrappableMarkingVisitor::WriteBarrier(raw);
-  }
+  TraceWrapperMember(T* raw) : Member<T>(raw) {}
 
   TraceWrapperMember(WTF::HashTableDeletedValueType x) : Member<T>(x) {}
 
@@ -36,21 +31,18 @@
   TraceWrapperMember& operator=(const TraceWrapperMember& other) {
     Member<T>::operator=(other);
     DCHECK_EQ(other.Get(), this->Get());
-    ScriptWrappableMarkingVisitor::WriteBarrier(this->Get());
     return *this;
   }
 
   TraceWrapperMember& operator=(const Member<T>& other) {
     Member<T>::operator=(other);
     DCHECK_EQ(other.Get(), this->Get());
-    ScriptWrappableMarkingVisitor::WriteBarrier(this->Get());
     return *this;
   }
 
   TraceWrapperMember& operator=(T* other) {
     Member<T>::operator=(other);
     DCHECK_EQ(other, this->Get());
-    ScriptWrappableMarkingVisitor::WriteBarrier(this->Get());
     return *this;
   }
 
@@ -78,14 +70,7 @@
   // TraceWrapperMember and Member match in vector backings.
   HeapVector<Member<T>>& a_ = reinterpret_cast<HeapVector<Member<T>>&>(a);
   a_.swap(b);
-  if (ThreadState::IsAnyWrapperTracing() &&
-      ThreadState::Current()->IsWrapperTracing()) {
-    // If incremental marking is enabled we need to emit the write barrier since
-    // the swap was performed on HeapVector<Member<T>>.
-    for (auto item : a) {
-      ScriptWrappableMarkingVisitor::WriteBarrier(item.Get());
-    }
-  }
+  // TODO(mlippautz): Remove this method
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
index 043f11b..e1868ff 100644
--- a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
+++ b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
@@ -8,7 +8,6 @@
 #include <utility>
 
 #include "base/macros.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/heap/unified_heap_marking_visitor.h"
 #include "v8/include/v8.h"
 
@@ -76,14 +75,10 @@
  protected:
   ALWAYS_INLINE void InternalSet(v8::Isolate* isolate, v8::Local<T> handle) {
     handle_.Reset(isolate, handle);
-    ScriptWrappableMarkingVisitor::WriteBarrier(isolate,
-                                                UnsafeCast<v8::Value>());
     UnifiedHeapMarkingVisitor::WriteBarrier(UnsafeCast<v8::Value>());
   }
 
   ALWAYS_INLINE void WriteBarrier() const {
-    ScriptWrappableMarkingVisitor::WriteBarrier(v8::Isolate::GetCurrent(),
-                                                UnsafeCast<v8::Value>());
     UnifiedHeapMarkingVisitor::WriteBarrier(UnsafeCast<v8::Value>());
   }
 
diff --git a/third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h b/third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h
index 5905a173..d44383a 100644
--- a/third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h
+++ b/third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h
@@ -37,7 +37,6 @@
 #include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
 #include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/v8_binding.h"
 #include "third_party/blink/renderer/platform/heap/unified_heap_marking_visitor.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
@@ -118,8 +117,6 @@
   // The following write barrier is necessary as V8 might not see the newly
   // created object during garbage collection, e.g., when the object is black
   // allocated.
-  ScriptWrappableMarkingVisitor::WriteBarrier(isolate, wrapper_type_info,
-                                              wrappable);
   UnifiedHeapMarkingVisitor::WriteBarrier(isolate, wrapper_type_info,
                                           wrappable);
 }
diff --git a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
index 1660cc4..600a10c 100644
--- a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
+++ b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
@@ -37,7 +37,6 @@
 #include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/v8_binding.h"
 #include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
 #include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
@@ -90,8 +89,6 @@
       use_counter_disabled_(false),
       is_handling_recursion_level_error_(false),
       is_reporting_exception_(false),
-      script_wrappable_visitor_(
-          new ScriptWrappableMarkingVisitor(ThreadState::Current())),
       unified_heap_controller_(
           new UnifiedHeapController(ThreadState::Current())),
       runtime_call_stats_(base::DefaultTickClock::GetInstance()) {
@@ -174,9 +171,6 @@
       BlinkGC::kHeapPointersOnStack, BlinkGC::kAtomicMarking,
       BlinkGC::kEagerSweeping, BlinkGC::GCReason::kThreadTerminationGC);
   isolate->SetEmbedderHeapTracer(nullptr);
-  if (data->script_wrappable_visitor_->WrapperTracingInProgress())
-    data->script_wrappable_visitor_->AbortTracingForTermination();
-  data->script_wrappable_visitor_.reset();
   data->unified_heap_controller_.reset();
 }
 
diff --git a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h
index 40fc3b5..30a81fd 100644
--- a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h
+++ b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h
@@ -34,7 +34,6 @@
 #include "gin/public/isolate_holder.h"
 #include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h"
 #include "third_party/blink/renderer/platform/bindings/scoped_persistent.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/bindings/v8_global_value_map.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
@@ -205,15 +204,6 @@
     return active_script_wrappables_.Get();
   }
 
-  ScriptWrappableMarkingVisitor* GetScriptWrappableMarkingVisitor() const {
-    return script_wrappable_visitor_.get();
-  }
-
-  void SwapScriptWrappableMarkingVisitor(
-      std::unique_ptr<ScriptWrappableMarkingVisitor>& other) {
-    script_wrappable_visitor_.swap(other);
-  }
-
   UnifiedHeapController* GetUnifiedHeapController() const {
     return unified_heap_controller_.get();
   }
@@ -289,7 +279,6 @@
   std::unique_ptr<Data> thread_debugger_;
 
   Persistent<ActiveScriptWrappableSet> active_script_wrappables_;
-  std::unique_ptr<ScriptWrappableMarkingVisitor> script_wrappable_visitor_;
   std::unique_ptr<UnifiedHeapController> unified_heap_controller_;
 
   RuntimeCallStats runtime_call_stats_;
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index c56be266..c96db6b 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -426,7 +426,7 @@
   }
 
   sk_sp<SkSurface> CreateSkSurface() const override {
-    if (IsGpuContextLost())
+    if (IsGpuContextLost() || !resource_)
       return nullptr;
     auto* gr = GetGrContext();
     DCHECK(gr);
diff --git a/third_party/blink/renderer/platform/graphics/decoding_image_generator.cc b/third_party/blink/renderer/platform/graphics/decoding_image_generator.cc
index ea80294..4ec7b46 100644
--- a/third_party/blink/renderer/platform/graphics/decoding_image_generator.cc
+++ b/third_party/blink/renderer/platform/graphics/decoding_image_generator.cc
@@ -67,7 +67,9 @@
   std::vector<FrameMetadata> frames = {FrameMetadata()};
   sk_sp<DecodingImageGenerator> generator = DecodingImageGenerator::Create(
       std::move(frame), info, std::move(segment_reader), std::move(frames),
-      PaintImage::GetNextContentId(), true);
+      PaintImage::GetNextContentId(), true /* all_data_received */,
+      true /* is_eligible_for_accelerated_decoding */,
+      false /* can_yuv_decode */);
   return std::make_unique<SkiaPaintImageGenerator>(
       std::move(generator), PaintImage::kDefaultFrameIndex,
       PaintImage::kDefaultGeneratorClientId);
@@ -80,10 +82,13 @@
     scoped_refptr<SegmentReader> data,
     std::vector<FrameMetadata> frames,
     PaintImage::ContentId content_id,
-    bool all_data_received) {
+    bool all_data_received,
+    bool is_eligible_for_accelerated_decoding,
+    bool can_yuv_decode) {
   return sk_sp<DecodingImageGenerator>(new DecodingImageGenerator(
       std::move(frame_generator), info, std::move(data), std::move(frames),
-      content_id, all_data_received));
+      content_id, all_data_received, is_eligible_for_accelerated_decoding,
+      can_yuv_decode));
 }
 
 DecodingImageGenerator::DecodingImageGenerator(
@@ -92,16 +97,24 @@
     scoped_refptr<SegmentReader> data,
     std::vector<FrameMetadata> frames,
     PaintImage::ContentId complete_frame_content_id,
-    bool all_data_received)
+    bool all_data_received,
+    bool is_eligible_for_accelerated_decoding,
+    bool can_yuv_decode)
     : PaintImageGenerator(info, std::move(frames)),
       frame_generator_(std::move(frame_generator)),
       data_(std::move(data)),
       all_data_received_(all_data_received),
-      can_yuv_decode_(false),
+      is_eligible_for_accelerated_decoding_(
+          is_eligible_for_accelerated_decoding),
+      can_yuv_decode_(can_yuv_decode),
       complete_frame_content_id_(complete_frame_content_id) {}
 
 DecodingImageGenerator::~DecodingImageGenerator() = default;
 
+bool DecodingImageGenerator::IsEligibleForAcceleratedDecoding() const {
+  return is_eligible_for_accelerated_decoding_;
+}
+
 sk_sp<SkData> DecodingImageGenerator::GetEncodedData() const {
   TRACE_EVENT0("blink", "DecodingImageGenerator::refEncodedData");
 
diff --git a/third_party/blink/renderer/platform/graphics/decoding_image_generator.h b/third_party/blink/renderer/platform/graphics/decoding_image_generator.h
index 5e95cdc..4602ab7 100644
--- a/third_party/blink/renderer/platform/graphics/decoding_image_generator.h
+++ b/third_party/blink/renderer/platform/graphics/decoding_image_generator.h
@@ -61,13 +61,14 @@
       scoped_refptr<SegmentReader>,
       std::vector<FrameMetadata>,
       PaintImage::ContentId,
-      bool all_data_received);
+      bool all_data_received,
+      bool is_eligible_for_accelerated_decoding,
+      bool can_yuv_decode);
 
   ~DecodingImageGenerator() override;
 
-  void SetCanYUVDecode(bool yes) { can_yuv_decode_ = yes; }
-
   // PaintImageGenerator implementation.
+  bool IsEligibleForAcceleratedDecoding() const override;
   sk_sp<SkData> GetEncodedData() const override;
   bool GetPixels(const SkImageInfo&,
                  void* pixels,
@@ -92,12 +93,15 @@
                          scoped_refptr<SegmentReader>,
                          std::vector<FrameMetadata>,
                          PaintImage::ContentId,
-                         bool all_data_received);
+                         bool all_data_received,
+                         bool is_eligible_for_accelerated_decoding,
+                         bool can_yuv_decode);
 
   scoped_refptr<ImageFrameGenerator> frame_generator_;
   const scoped_refptr<SegmentReader> data_;  // Data source.
   const bool all_data_received_;
-  bool can_yuv_decode_;
+  const bool is_eligible_for_accelerated_decoding_;
+  const bool can_yuv_decode_;
   const PaintImage::ContentId complete_frame_content_id_;
 
   DISALLOW_COPY_AND_ASSIGN(DecodingImageGenerator);
diff --git a/third_party/blink/renderer/platform/graphics/deferred_image_decoder.cc b/third_party/blink/renderer/platform/graphics/deferred_image_decoder.cc
index ab66ebaa1..6b3e6666 100644
--- a/third_party/blink/renderer/platform/graphics/deferred_image_decoder.cc
+++ b/third_party/blink/renderer/platform/graphics/deferred_image_decoder.cc
@@ -88,6 +88,7 @@
     : metadata_decoder_(std::move(metadata_decoder)),
       repetition_count_(kAnimationNone),
       all_data_received_(false),
+      first_decoding_generator_created_(false),
       can_yuv_decode_(false),
       has_hot_spot_(false),
       image_is_high_bit_depth_(false),
@@ -137,10 +138,13 @@
     frames[i].duration = FrameDurationAtIndex(i);
   }
 
+  const bool is_eligible_for_accelerated_decoding =
+      !first_decoding_generator_created_ && all_data_received_;
   auto generator = DecodingImageGenerator::Create(
       frame_generator_, info, std::move(segment_reader), std::move(frames),
-      complete_frame_content_id_, all_data_received_);
-  generator->SetCanYUVDecode(can_yuv_decode_);
+      complete_frame_content_id_, all_data_received_,
+      is_eligible_for_accelerated_decoding, can_yuv_decode_);
+  first_decoding_generator_created_ = true;
 
   return generator;
 }
diff --git a/third_party/blink/renderer/platform/graphics/deferred_image_decoder.h b/third_party/blink/renderer/platform/graphics/deferred_image_decoder.h
index 53013a4..2c08ab6 100644
--- a/third_party/blink/renderer/platform/graphics/deferred_image_decoder.h
+++ b/third_party/blink/renderer/platform/graphics/deferred_image_decoder.h
@@ -104,6 +104,7 @@
   int repetition_count_;
   bool has_embedded_color_profile_ = false;
   bool all_data_received_;
+  bool first_decoding_generator_created_;
   bool can_yuv_decode_;
   bool has_hot_spot_;
   bool image_is_high_bit_depth_;
diff --git a/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc b/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc
index 48aa0cf2..de2a0ee 100644
--- a/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc
@@ -185,6 +185,7 @@
   cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
   PaintImage image =
       CreatePaintImage(PaintImage::CompletionState::PARTIALLY_DONE);
+  ASSERT_TRUE(image);
   temp_canvas->drawImage(image, 0, 0);
   canvas_->drawPicture(recorder.finishRecordingAsPicture());
 
@@ -198,6 +199,49 @@
   EXPECT_EQ(SkColorSetARGB(255, 255, 255, 255), bitmap_.getColor(0, 0));
 }
 
+TEST_F(DeferredImageDecoderTest, isEligibleForHardwareDecodingNonIncremental) {
+  // The image is received completely. This is okay for hardware decoding since
+  // it's assumed that the software decoder hasn't done any work.
+  lazy_decoder_->SetData(data_, true);
+  PaintImage image = CreatePaintImage();
+  ASSERT_TRUE(image);
+  EXPECT_TRUE(image.IsEligibleForAcceleratedDecoding());
+}
+
+TEST_F(DeferredImageDecoderTest, isEligibleForHardwareDecodingIncremental) {
+  // The image is received in two parts, but a PaintImageGenerator is created
+  // only after all the data is received. This is okay for hardware decoding
+  // since it's assumed that the software decoder hasn't done any work before
+  // the PaintImageGenerator is created.
+  scoped_refptr<SharedBuffer> partial_data =
+      SharedBuffer::Create(data_->Data(), data_->size() - 10);
+  lazy_decoder_->SetData(partial_data, false);
+  lazy_decoder_->SetData(data_, true);
+  PaintImage image = CreatePaintImage();
+  ASSERT_TRUE(image);
+  EXPECT_TRUE(image.IsEligibleForAcceleratedDecoding());
+}
+
+TEST_F(DeferredImageDecoderTest, isNotEligibleForHardwareDecoding) {
+  // The image is received in two parts, and a PaintImageGenerator is created
+  // for each one. In real usage, it's likely that the software image decoder
+  // will start working with partial data, so there's no point in using the
+  // hardware accelerator (because if we did, we'd be doing double work: in
+  // software and in hardware).
+  scoped_refptr<SharedBuffer> partial_data =
+      SharedBuffer::Create(data_->Data(), data_->size() - 10);
+  lazy_decoder_->SetData(partial_data, false);
+  PaintImage image =
+      CreatePaintImage(PaintImage::CompletionState::PARTIALLY_DONE);
+  ASSERT_TRUE(image);
+  EXPECT_FALSE(image.IsEligibleForAcceleratedDecoding());
+
+  lazy_decoder_->SetData(data_, true);
+  image = CreatePaintImage();
+  ASSERT_TRUE(image);
+  EXPECT_FALSE(image.IsEligibleForAcceleratedDecoding());
+}
+
 static void RasterizeMain(cc::PaintCanvas* canvas, sk_sp<PaintRecord> record) {
   canvas->drawPicture(record);
 }
diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc b/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
index 7b6ef7e..9ea7b68 100644
--- a/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
+++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
@@ -17,6 +17,7 @@
 #include "third_party/skia/include/core/SkPaint.h"
 #include "third_party/skia/include/core/SkSurface.h"
 #include "third_party/skia/include/gpu/GrContext.h"
+#include "v8/include/v8.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/heap/heap_allocator.h b/third_party/blink/renderer/platform/heap/heap_allocator.h
index fad71cb..4c04c8d 100644
--- a/third_party/blink/renderer/platform/heap/heap_allocator.h
+++ b/third_party/blink/renderer/platform/heap/heap_allocator.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_HEAP_ALLOCATOR_H_
 
 #include "build/build_config.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/heap/heap_buildflags.h"
 #include "third_party/blink/renderer/platform/heap/marking_visitor.h"
@@ -140,7 +139,6 @@
   template <typename T>
   static void BackingWriteBarrier(TraceWrapperMember<T>* address, size_t size) {
     MarkingVisitor::WriteBarrier(address);
-    ScriptWrappableMarkingVisitor::WriteBarrier(address, size);
   }
 
   template <typename T>
diff --git a/third_party/blink/renderer/platform/heap/run_all_tests.cc b/third_party/blink/renderer/platform/heap/run_all_tests.cc
index ea68f22d..ac9ae033 100644
--- a/third_party/blink/renderer/platform/heap/run_all_tests.cc
+++ b/third_party/blink/renderer/platform/heap/run_all_tests.cc
@@ -43,8 +43,7 @@
   void Initialize() override {
     base::TestSuite::Initialize();
     content::SetUpBlinkTestEnvironment();
-    blink::ThreadState::Current()->RegisterTraceDOMWrappers(nullptr, nullptr,
-                                                            nullptr, nullptr);
+    blink::ThreadState::Current()->RegisterTraceDOMWrappers(nullptr, nullptr);
   }
   void Shutdown() override {
     blink::ThreadState::Current()->CollectAllGarbage();
diff --git a/third_party/blink/renderer/platform/heap/thread_state.cc b/third_party/blink/renderer/platform/heap/thread_state.cc
index 10bc122..9926bcc2 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.cc
+++ b/third_party/blink/renderer/platform/heap/thread_state.cc
@@ -176,10 +176,6 @@
       gc_state_(kNoGCScheduled),
       gc_phase_(GCPhase::kNone),
       reason_for_scheduled_gc_(BlinkGC::GCReason::kMaxValue),
-      isolate_(nullptr),
-      trace_dom_wrappers_(nullptr),
-      invalidate_dead_objects_in_wrappers_marking_deque_(nullptr),
-      perform_cleanup_(nullptr),
 #if defined(ADDRESS_SANITIZER)
       asan_fake_stack_(__asan_get_current_fake_stack()),
 #endif
@@ -190,7 +186,6 @@
   DCHECK(CheckThread());
   DCHECK(!**thread_specific_);
   **thread_specific_ = this;
-
   heap_ = std::make_unique<ThreadHeap>(this);
 }
 
@@ -1403,9 +1398,6 @@
 // static
 AtomicEntryFlag ThreadState::incremental_marking_flag_;
 
-// static
-AtomicEntryFlag ThreadState::wrapper_tracing_flag_;
-
 void ThreadState::EnableIncrementalMarkingBarrier() {
   CHECK(!IsIncrementalMarking());
   incremental_marking_flag_.Enter();
@@ -1418,18 +1410,6 @@
   SetIncrementalMarking(false);
 }
 
-void ThreadState::EnableWrapperTracingBarrier() {
-  CHECK(!IsWrapperTracing());
-  wrapper_tracing_flag_.Enter();
-  SetWrapperTracing(true);
-}
-
-void ThreadState::DisableWrapperTracingBarrier() {
-  CHECK(IsWrapperTracing());
-  wrapper_tracing_flag_.Exit();
-  SetWrapperTracing(false);
-}
-
 void ThreadState::IncrementalMarkingStart(BlinkGC::GCReason reason) {
   VLOG(2) << "[state:" << this << "] "
           << "IncrementalMarking: Start";
@@ -1719,9 +1699,6 @@
   if (marking_type == BlinkGC::kTakeSnapshot)
     BlinkGCMemoryDumpProvider::Instance()->ClearProcessDumpForCurrentGC();
 
-  if (isolate_ && perform_cleanup_)
-    perform_cleanup_(isolate_);
-
   if (stack_state == BlinkGC::kNoHeapPointersOnStack) {
     Heap().FlushNotFullyConstructedObjects();
   }
@@ -1802,9 +1779,6 @@
   Heap().stats_collector()->NotifyMarkingCompleted();
   WTF::Partitions::ReportMemoryUsageHistogram();
 
-  if (invalidate_dead_objects_in_wrappers_marking_deque_)
-    invalidate_dead_objects_in_wrappers_marking_deque_(isolate_);
-
   DEFINE_THREAD_SAFE_STATIC_LOCAL(
       CustomCountHistogram, total_object_space_histogram,
       ("BlinkGC.TotalObjectSpace", 0, 4 * 1024 * 1024, 50));
diff --git a/third_party/blink/renderer/platform/heap/thread_state.h b/third_party/blink/renderer/platform/heap/thread_state.h
index 34a98f5d..7133170 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.h
+++ b/third_party/blink/renderer/platform/heap/thread_state.h
@@ -185,15 +185,6 @@
     return incremental_marking_flag_.MightBeEntered();
   }
 
-  // Returns true if some thread (possibly the current thread) may be doing
-  // wrapper tracing. If false is returned, the *current* thread is definitely
-  // not doing wrapper tracing. See atomic_entry_flag.h for details.
-  //
-  // For an exact check, use ThreadState::IsWrapperTracing.
-  static bool IsAnyWrapperTracing() {
-    return wrapper_tracing_flag_.MightBeEntered();
-  }
-
   static void AttachMainThread();
 
   // Associate ThreadState object with the current thread. After this
@@ -252,9 +243,6 @@
            current_gc_data_.reason == BlinkGC::GCReason::kUnifiedHeapGC;
   }
 
-  void EnableWrapperTracingBarrier();
-  void DisableWrapperTracingBarrier();
-
   // Incremental GC.
   void ScheduleIncrementalMarkingStep();
   void ScheduleIncrementalMarkingFinalize();
@@ -317,9 +305,6 @@
     return in_atomic_pause() && IsSweepingInProgress();
   }
 
-  bool IsWrapperTracing() const { return wrapper_tracing_; }
-  void SetWrapperTracing(bool value) { wrapper_tracing_ = value; }
-
   bool IsIncrementalMarking() const { return incremental_marking_; }
   void SetIncrementalMarking(bool value) { incremental_marking_ = value; }
 
@@ -371,17 +356,12 @@
     Vector<size_t> dead_size;
   };
 
-  void RegisterTraceDOMWrappers(
-      v8::Isolate* isolate,
-      void (*trace_dom_wrappers)(v8::Isolate*, Visitor*),
-      void (*invalidate_dead_objects_in_wrappers_marking_deque)(v8::Isolate*),
-      void (*perform_cleanup)(v8::Isolate*)) {
+  void RegisterTraceDOMWrappers(v8::Isolate* isolate,
+                                void (*trace_dom_wrappers)(v8::Isolate*,
+                                                           Visitor*)) {
     isolate_ = isolate;
     DCHECK(!isolate_ || trace_dom_wrappers);
     trace_dom_wrappers_ = trace_dom_wrappers;
-    invalidate_dead_objects_in_wrappers_marking_deque_ =
-        invalidate_dead_objects_in_wrappers_marking_deque;
-    perform_cleanup_ = perform_cleanup;
   }
 
   void FreePersistentNode(PersistentRegion*, PersistentNode*);
@@ -447,9 +427,6 @@
   // Stores whether some ThreadState is currently in incremental marking.
   static AtomicEntryFlag incremental_marking_flag_;
 
-  // Same semantic as |incremental_marking_flag_|.
-  static AtomicEntryFlag wrapper_tracing_flag_;
-
   static WTF::ThreadSpecific<ThreadState*>* thread_specific_;
 
   // We can't create a static member of type ThreadState here because it will
@@ -567,7 +544,6 @@
   bool object_resurrection_forbidden_ = false;
   bool in_atomic_pause_ = false;
   bool sweep_forbidden_ = false;
-  bool wrapper_tracing_ = false;
   bool incremental_marking_ = false;
   bool should_optimize_for_load_time_ = false;
   size_t no_allocation_count_ = 0;
@@ -588,10 +564,8 @@
   // for an object, by processing the ordered_pre_finalizers_ back-to-front.
   LinkedHashSet<PreFinalizer> ordered_pre_finalizers_;
 
-  v8::Isolate* isolate_;
-  void (*trace_dom_wrappers_)(v8::Isolate*, Visitor*);
-  void (*invalidate_dead_objects_in_wrappers_marking_deque_)(v8::Isolate*);
-  void (*perform_cleanup_)(v8::Isolate*);
+  v8::Isolate* isolate_ = nullptr;
+  void (*trace_dom_wrappers_)(v8::Isolate*, Visitor*) = nullptr;
 
 #if defined(ADDRESS_SANITIZER)
   void* asan_fake_stack_;
diff --git a/third_party/blink/renderer/platform/heap/trace_traits.h b/third_party/blink/renderer/platform/heap/trace_traits.h
index ca13ce1..b5e315e 100644
--- a/third_party/blink/renderer/platform/heap/trace_traits.h
+++ b/third_party/blink/renderer/platform/heap/trace_traits.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_TRACE_TRAITS_H_
 
 #include "base/optional.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h"
 #include "third_party/blink/renderer/platform/heap/gc_info.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/heap/visitor.h"
diff --git a/third_party/blink/renderer/platform/heap/unified_heap_controller.h b/third_party/blink/renderer/platform/heap/unified_heap_controller.h
index 410acf3..e22350968 100644
--- a/third_party/blink/renderer/platform/heap/unified_heap_controller.h
+++ b/third_party/blink/renderer/platform/heap/unified_heap_controller.h
@@ -33,11 +33,6 @@
   DISALLOW_IMPLICIT_CONSTRUCTORS(UnifiedHeapController);
 
  public:
-  // Temporarily expose that logic to allow reuse by
-  // ScriptWrappableMarkingVisitor.
-  static bool IsRootForNonTracingGCInternal(
-      const v8::TracedGlobal<v8::Value>& handle);
-
   explicit UnifiedHeapController(ThreadState*);
 
   // v8::EmbedderHeapTracer implementation.
@@ -52,6 +47,9 @@
   ThreadState* thread_state() const { return thread_state_; }
 
  private:
+  static bool IsRootForNonTracingGCInternal(
+      const v8::TracedGlobal<v8::Value>& handle);
+
   ThreadState* const thread_state_;
 
   // Returns whether the Blink heap has been fully processed.
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
index 8d04c950..a90a7591 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
@@ -260,7 +260,8 @@
 }
 
 void RawResource::ResponseBodyReceived(
-    ResponseBodyLoaderDrainableInterface& body_loader) {
+    ResponseBodyLoaderDrainableInterface& body_loader,
+    scoped_refptr<base::SingleThreadTaskRunner> loader_task_runner) {
   DCHECK_LE(Clients().size(), 1u);
   RawResourceClient* client =
       ResourceClientWalker<RawResourceClient>(Clients()).Next();
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource.h b/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
index 93bde4e..afd8b23 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
@@ -121,7 +121,9 @@
   }
   void WillNotFollowRedirect() override;
   void ResponseReceived(const ResourceResponse&) override;
-  void ResponseBodyReceived(ResponseBodyLoaderDrainableInterface&) override;
+  void ResponseBodyReceived(
+      ResponseBodyLoaderDrainableInterface&,
+      scoped_refptr<base::SingleThreadTaskRunner> loader_task_runner) override;
   void DidSendData(uint64_t bytes_sent,
                    uint64_t total_bytes_to_be_sent) override;
   void DidDownloadData(uint64_t) override;
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource_test.cc b/third_party/blink/renderer/platform/loader/fetch/raw_resource_test.cc
index e069aa81..884a91b 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource_test.cc
@@ -260,7 +260,7 @@
   raw->MatchPreload(params, platform_->test_task_runner().get());
   raw->AddClient(dummy_client, platform_->test_task_runner().get());
 
-  raw->ResponseBodyReceived(*body_loader);
+  raw->ResponseBodyReceived(*body_loader, platform_->test_task_runner());
   raw->FinishForTest();
   EXPECT_FALSE(dummy_client->Called());
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.h b/third_party/blink/renderer/platform/loader/fetch/resource.h
index c22b9330..213b81e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.h
@@ -267,7 +267,8 @@
 
   virtual void ResponseReceived(const ResourceResponse&);
   virtual void ResponseBodyReceived(
-      ResponseBodyLoaderDrainableInterface& body_loader) {}
+      ResponseBodyLoaderDrainableInterface& body_loader,
+      scoped_refptr<base::SingleThreadTaskRunner> loader_task_runner) {}
   void SetResponse(const ResourceResponse&);
   const ResourceResponse& GetResponse() const { return response_; }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 6ecc749..d61cb18 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -466,7 +466,8 @@
   response_body_loader_ = MakeGarbageCollected<ResponseBodyLoader>(
       bytes_consumer, response_body_loader_client,
       task_runner_for_body_loader_);
-  resource_->ResponseBodyReceived(*response_body_loader_);
+  resource_->ResponseBodyReceived(*response_body_loader_,
+                                  task_runner_for_body_loader_);
   if (response_body_loader_->IsDrained()) {
     // When streaming, unpause virtual time early to prevent deadlocking
     // against stream consumer in case stream has backpressure enabled.
diff --git a/third_party/blink/renderer/platform/network/mime/mime_type_registry.cc b/third_party/blink/renderer/platform/network/mime/mime_type_registry.cc
index efd52daa..67b20abd 100644
--- a/third_party/blink/renderer/platform/network/mime/mime_type_registry.cc
+++ b/third_party/blink/renderer/platform/network/mime/mime_type_registry.cc
@@ -211,4 +211,13 @@
          EqualIgnoringASCIICase(mime_type, "image/pjpeg");
 }
 
+bool MIMETypeRegistry::IsLosslessImageMIMEType(const String& mime_type) {
+  return EqualIgnoringASCIICase(mime_type, "image/bmp") ||
+         EqualIgnoringASCIICase(mime_type, "image/gif") ||
+         EqualIgnoringASCIICase(mime_type, "image/png") ||
+         EqualIgnoringASCIICase(mime_type, "image/webp") ||
+         EqualIgnoringASCIICase(mime_type, "image/x-xbitmap") ||
+         EqualIgnoringASCIICase(mime_type, "image/x-png");
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/network/mime/mime_type_registry.h b/third_party/blink/renderer/platform/network/mime/mime_type_registry.h
index 8b54b9f..6d10852c 100644
--- a/third_party/blink/renderer/platform/network/mime/mime_type_registry.h
+++ b/third_party/blink/renderer/platform/network/mime/mime_type_registry.h
@@ -108,6 +108,11 @@
   // size will be restricted via the 'unoptimized-lossy-images' feature
   // policy. (JPEG)
   static bool IsLossyImageMIMEType(const String& mime_type);
+
+  // Checks to see if a mime type is an image type with lossless (or no)
+  // compression, whose size may be restricted via the
+  // 'unoptimized-lossless-images' feature policy. (BMP, GIF, PNG, WEBP)
+  static bool IsLosslessImageMIMEType(const String& mime_type);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/testing/testing_platform_support.cc b/third_party/blink/renderer/platform/testing/testing_platform_support.cc
index f44a0dc..6843b76068 100644
--- a/third_party/blink/renderer/platform/testing/testing_platform_support.cc
+++ b/third_party/blink/renderer/platform/testing/testing_platform_support.cc
@@ -180,8 +180,7 @@
 
   ProcessHeap::Init();
   ThreadState::AttachMainThread();
-  ThreadState::Current()->RegisterTraceDOMWrappers(nullptr, nullptr, nullptr,
-                                                   nullptr);
+  ThreadState::Current()->RegisterTraceDOMWrappers(nullptr, nullptr);
   http_names::Init();
   fetch_initiator_type_names::Init();
 
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
index 81f325f..89de3be 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
@@ -254,7 +254,7 @@
 Bug(none) virtual/threaded/fast/scroll-behavior/first-scroll-runs-on-compositor.html [ Failure ]
 Bug(none) virtual/threaded/compositing/visibility/layer-visible-content.html [ Failure ]
 Bug(none) virtual/threaded/compositing/visibility/visibility-image-layers-dynamic.html [ Failure ]
-Bug(none) virtual/threaded/synthetic_gestures/smooth-scroll-tiny-delta.html [ Failure ]
+Bug(none) virtual/threaded/synthetic_gestures/smooth-scroll-tiny-delta.html [ Failure Timeout ]
 
 Bug(none) fast/block/float/float-change-composited-scrolling.html [ Failure ]
 
@@ -400,6 +400,15 @@
 crbug.com/924680 virtual/threaded/fast/events/pinch/pinch-zoom-into-center.html [ Failure ]
 crbug.com/924680 virtual/threaded/synthetic_gestures/synthetic-pinch-zoom-gesture-touchpad.html [ Failure ]
 
+# These were introduced when we started setting the scrolls_outer_viewport bit on ScrollNodes
+Bug(none) virtual/fractional_scrolling_threaded/fast/scrolling/hover-during-scroll.html [ Failure ]
+Bug(none) virtual/fractional_scrolling_threaded/fast/scrolling/no-hover-during-scroll.html [ Failure ]
+Bug(none) virtual/threaded/external/wpt/feature-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html [ Failure ]
+Bug(none) virtual/threaded/external/wpt/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html [ Failure ]
+Bug(none) virtual/threaded/fast/scrolling/hover-during-scroll.html [ Failure ]
+Bug(none) virtual/threaded/fast/scrolling/no-hover-during-scroll.html [ Failure ]
+Bug(none) virtual/threaded/synthetic_gestures/animated-wheel-tiny-delta.html [ Failure ]
+
 # Backdrop filter
 crbug.com/923429 css3/filters/backdrop-filter-basic-blur.html [ Failure ]
 crbug.com/923429 css3/filters/backdrop-filter-border-radius.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index e2c3b56..6eaa0558 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3562,9 +3562,6 @@
 crbug.com/626703 external/wpt/css/css-scoping/shadow-directionality-002.tentative.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-scoping/shadow-directionality-001.tentative.html [ Failure ]
 crbug.com/626703 external/wpt/screen-orientation/orientation-reading.html [ Timeout ]
-crbug.com/626703 external/wpt/html/browsers/the-window-object/window-open-noopener.html?_parent [ Timeout ]
-crbug.com/626703 external/wpt/html/browsers/the-window-object/window-open-noopener.html?_top [ Timeout ]
-crbug.com/626703 external/wpt/html/browsers/the-window-object/window-open-noopener.html?_self [ Failure ]
 crbug.com/626703 external/wpt/html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-closeable.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-animations/CSSAnimation-effect.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-animations/event-dispatch.tentative.html [ Timeout ]
@@ -3907,7 +3904,6 @@
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/logical-physical-mapping-001.html [ Failure ]
 crbug.com/626703 external/wpt/encoding/eof-utf-8-three.html [ Failure ]
 crbug.com/626703 external/wpt/encoding/eof-utf-8-two.html [ Failure ]
-crbug.com/626703 external/wpt/html/browsers/the-window-object/window-open-noopener.html [ Timeout ]
 crbug.com/626703 external/wpt/html/browsers/windows/noreferrer-window-name.html [ Timeout ]
 crbug.com/626703 crbug.com/930297 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html [ Timeout Crash ]
 crbug.com/626703 crbug.com/930297 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html [ Timeout Crash ]
@@ -5995,9 +5991,6 @@
 crbug.com/932078 virtual/insecure-device-sensor-events/http/tests/security/powerfulFeatureRestrictions/device-orientation-handler-not-fired-on-insecure-origin.html [ Skip ]
 crbug.com/932078 virtual/outofblink-cors/http/tests/security/powerfulFeatureRestrictions/device-orientation-on-insecure-origin.html [ Skip ]
 
-# Wasm threads enabled by default
-crbug.com/754910 virtual/origin-trials-runtimeflags-disabled/http/tests/origin_trials/webexposed/wasm-threads-origin-trial-disabled.html [ Skip ]
-
 # These tests depend on targeting javascript: url navigations at the specified window.
 crbug.com/935064 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html [ Failure ]
 crbug.com/935064 external/wpt/content-security-policy/inheritance/iframe-all-local-schemes.sub.html [ Failure ]
diff --git a/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html b/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html
index 59d655f..7d2d059 100644
--- a/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html
+++ b/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html
@@ -16,6 +16,12 @@
         <p id="paragraph2">Line 3</p>
     </div>
 
+    <div id="contenteditable-div-2" contenteditable role="textbox"
+        style="max-width: 5px; overflow-wrap: normal;">
+        Line 1<br>
+        Line 2
+    </div>
+
 </div>
 
 <script>
@@ -45,7 +51,9 @@
         textbox.focus();
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
 
+        assert_equals(textboxAccessible.selectionAnchorObject, textboxAccessible);
         assert_equals(textboxAccessible.selectionAnchorOffset, 0);
+        assert_equals(textboxAccessible.selectionFocusObject, textboxAccessible);
         assert_equals(textboxAccessible.selectionFocusOffset, 0);
     }, "Moving the focus to an ARIA textbox should place the caret at its beginning.");
 
@@ -229,58 +237,153 @@
 
     test_after_layout_and_paint(function()
     {
-        let selection = window.getSelection();
-        let selectionRange = document.createRange();
-        let mainAccessible = accessibilityController.accessibleElementById("main");
-        let rootAccessible = accessibilityController.rootElement;
+        const selection = window.getSelection();
+        const selectionRange = document.createRange();
+        const mainAccessible = accessibilityController.accessibleElementById("main");
+        const rootAccessible = accessibilityController.rootElement;
 
-        let contenteditable = document.getElementById("contenteditable-div");
+        const contenteditable = document.getElementById("contenteditable-div");
         contenteditable.focus();
-        // The offset from the beginning of the main div to the first character of
-        // contenteditable-div.
-        var mainOffset = 9;
         // The offset from the newline character between the two lines of the
         // first paragraph to the first character of its second line.
         // (Needed for skipping wide space.)
-        let line2Offset = 13;
+        const line2StartOffset = 13;
 
-        let line1 = document.getElementById("paragraph1").firstChild;
-        let line2 = document.getElementById("paragraph1").lastChild;
-        let line3 = document.getElementById("paragraph2").firstChild;
-        let contenteditableLines = [ line1, line2, line3 ];
-        let contenteditableAccessible = accessibilityController.accessibleElementById("contenteditable-div");
+        const line1 = document.getElementById("paragraph1").firstChild;
+        const line2 = document.getElementById("paragraph1").lastChild;
+        const line3 = document.getElementById("paragraph2").firstChild;
+        const contenteditableLines = [ line1, line2, line3 ];
+        
+        const contenteditableAccessible = accessibilityController.accessibleElementById("contenteditable-div");
+        const paragraph1Accessible = accessibilityController.accessibleElementById("paragraph1");
+        const paragraph2Accessible = accessibilityController.accessibleElementById("paragraph2");
+        const line1Accessible = paragraph1Accessible.childAtIndex(0);
+        const line2Accessible = paragraph1Accessible.childAtIndex(2);
+        const line3Accessible = paragraph2Accessible.childAtIndex(0);
+        const expectations = [
+          line1Accessible, line2Accessible, line3Accessible
+        ];
 
         for (let lineNumber = 0; lineNumber < 3; ++lineNumber) {
-            let lineOffset = lineNumber * 7;
-            // Paragraphs should be separated by an empty line.
-            if (lineNumber == 2)
-                ++lineOffset;
-
             for (let characterOffset = 0; characterOffset < 7; ++characterOffset) {
-                // Any widespace in the DOM should be stripped out.
+                // Any widespace in the DOM should be stripped out in the
+                // accessibility tree.
                 let selectionOffset = characterOffset;
                 if (lineNumber == 1)
-                    selectionOffset += line2Offset;
+                    selectionOffset += line2StartOffset;
 
                 selectionRange.setStart(contenteditableLines[lineNumber], selectionOffset);
                 selectionRange.setEnd(contenteditableLines[lineNumber], selectionOffset);
                 selection.removeAllRanges();
                 selection.addRange(selectionRange);
 
+                assert_equals(contenteditableAccessible.selectionAnchorObject, expectations[lineNumber]);
                 assert_equals(contenteditableAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(contenteditableAccessible.selectionFocusObject, expectations[lineNumber]);
                 assert_equals(contenteditableAccessible.selectionFocusOffset, characterOffset);
 
+                assert_equals(mainAccessible.selectionAnchorObject, expectations[lineNumber]);
                 assert_equals(mainAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(mainAccessible.selectionFocusObject, expectations[lineNumber]);
                 assert_equals(mainAccessible.selectionFocusOffset, characterOffset);
 
-                assert_equals(rootAccessible.selectionAnchorObject.name,
-                    contenteditableLines[lineNumber].textContent.trim());
+                assert_equals(rootAccessible.selectionAnchorObject, expectations[lineNumber]);
                 assert_equals(rootAccessible.selectionAnchorOffset, characterOffset);
-                assert_equals(rootAccessible.selectionFocusObject.name,
-                    contenteditableLines[lineNumber].textContent.trim());
+                assert_equals(rootAccessible.selectionFocusObject, expectations[lineNumber]);
                 assert_equals(rootAccessible.selectionFocusOffset, characterOffset);
             }
         }
 
-    }, "Test moving the caret across two paragraphs.");
+    }, "Test moving the caret across two paragraphs by re-creating the selection.");
+
+    test_after_layout_and_paint(function()
+    {
+        const selection = window.getSelection();
+        const selectionRange = document.createRange();
+        const mainAccessible = accessibilityController.accessibleElementById("main");
+        const rootAccessible = accessibilityController.rootElement;
+
+        const contenteditable = document.getElementById("contenteditable-div");
+        contenteditable.focus();
+        
+        const line1 = document.getElementById("paragraph1").firstChild;
+        selectionRange.setStart(line1, 0);
+        selectionRange.setEnd(line1, 0);
+        selection.removeAllRanges();
+        selection.addRange(selectionRange);
+
+        const contenteditableAccessible = accessibilityController.accessibleElementById("contenteditable-div");
+        const paragraph1Accessible = accessibilityController.accessibleElementById("paragraph1");
+        const paragraph2Accessible = accessibilityController.accessibleElementById("paragraph2");
+        const line1Accessible = paragraph1Accessible.childAtIndex(0);
+        const line2Accessible = paragraph1Accessible.childAtIndex(2);
+        const line3Accessible = paragraph2Accessible.childAtIndex(0);
+        const expectations = [
+          line1Accessible, line2Accessible, line3Accessible
+        ];
+
+        for (let lineNumber = 0; lineNumber < 3; ++lineNumber) {
+            for (let characterOffset = 0; characterOffset < 7; ++characterOffset) {
+                assert_equals(contenteditableAccessible.selectionAnchorObject, expectations[lineNumber]);
+                assert_equals(contenteditableAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(contenteditableAccessible.selectionFocusObject, expectations[lineNumber]);
+                assert_equals(contenteditableAccessible.selectionFocusOffset, characterOffset);
+
+                assert_equals(mainAccessible.selectionAnchorObject, expectations[lineNumber]);
+                assert_equals(mainAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(mainAccessible.selectionFocusObject, expectations[lineNumber]);
+                assert_equals(mainAccessible.selectionFocusOffset, characterOffset);
+
+                assert_equals(rootAccessible.selectionAnchorObject, expectations[lineNumber]);
+                assert_equals(rootAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(rootAccessible.selectionFocusObject, expectations[lineNumber]);
+                assert_equals(rootAccessible.selectionFocusOffset, characterOffset);
+
+                selection.modify('move', 'forward', 'character');
+            }
+        }
+        
+    }, "Test moving the caret across two paragraphs by modifying the existing selection.");
+
+    test_after_layout_and_paint(function()
+    {
+        const selection = window.getSelection();
+        const selectionRange = document.createRange();
+        const mainAccessible = accessibilityController.accessibleElementById("main");
+        const rootAccessible = accessibilityController.rootElement;
+
+        const contenteditable = document.getElementById('contenteditable-div-2');
+        contenteditable.focus();
+        selectionRange.setStart(contenteditable, 0);
+        selectionRange.setEnd(contenteditable, 0);
+        selection.removeAllRanges();
+        selection.addRange(selectionRange);
+
+        const contenteditableAccessible = accessibilityController.accessibleElementById('contenteditable-div-2');
+        const line1Accessible = contenteditableAccessible.childAtIndex(0);
+        const line2Accessible = contenteditableAccessible.childAtIndex(2);
+        const expectations = [ line1Accessible, line2Accessible ];
+
+        for (let lineNumber = 0; lineNumber < 2; ++lineNumber) {
+            for (let characterOffset = 0; characterOffset < 7; ++characterOffset) {
+                assert_equals(contenteditableAccessible.selectionAnchorObject, expectations[lineNumber]);
+                assert_equals(contenteditableAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(contenteditableAccessible.selectionFocusObject, expectations[lineNumber]);
+                assert_equals(contenteditableAccessible.selectionFocusOffset, characterOffset);
+
+                assert_equals(mainAccessible.selectionAnchorObject, expectations[lineNumber]);
+                assert_equals(mainAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(mainAccessible.selectionFocusObject, expectations[lineNumber]);
+                assert_equals(mainAccessible.selectionFocusOffset, characterOffset);
+
+                assert_equals(rootAccessible.selectionAnchorObject, expectations[lineNumber]);
+                assert_equals(rootAccessible.selectionAnchorOffset, characterOffset);
+                assert_equals(rootAccessible.selectionFocusObject, expectations[lineNumber]);
+                assert_equals(rootAccessible.selectionFocusOffset, characterOffset);
+
+                selection.modify('move', 'forward', 'character');
+            }
+        }
+        
+    }, "Test moving the caret across two lines that wrap by modifying the existing selection.");
 </script>
diff --git a/third_party/blink/web_tests/accessibility/contenteditable-selection.html b/third_party/blink/web_tests/accessibility/contenteditable-selection.html
index 16a92ca..e2e2e90 100644
--- a/third_party/blink/web_tests/accessibility/contenteditable-selection.html
+++ b/third_party/blink/web_tests/accessibility/contenteditable-selection.html
@@ -27,8 +27,6 @@
 
         let textbox = document.getElementById("contenteditable-textbox");
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
-        let line1Accessible = accessibilityController.accessibleElementById("contenteditable-line1");
-        let line1TextAccessible = line1Accessible.childAtIndex(0);
 
         // Select the entire contents of the outer ARIA textbox.
         // These include another ARIA textbox and a textarea node
@@ -38,21 +36,22 @@
         selection.removeAllRanges();
         selection.addRange(selectionRange);
 
+        assert_equals(textboxAccessible.selectionAnchorObject, textboxAccessible);
         assert_equals(textboxAccessible.selectionAnchorOffset, 0);
-        // 7 for the "Line 1" text div + 1 for the textarea node.
-        // (The textarea node should be treated as a single unit.)
-        assert_equals(textboxAccessible.selectionFocusOffset, 8);
+        assert_equals(textboxAccessible.selectionFocusObject, textboxAccessible);
+        // Two nodes have been selected: the two text fields  inside the outer one.
+        assert_equals(textboxAccessible.selectionFocusOffset, 2);
 
         // Selection offsets should be the same when retrieved from the parent object.
-        assert_equals(mainAccessible.selectionAnchorObject, line1TextAccessible);
+        assert_equals(mainAccessible.selectionAnchorObject, textboxAccessible);
         assert_equals(mainAccessible.selectionAnchorOffset, 0);
-        assert_equals(mainAccessible.selectionAnchorObject, line1TextAccessible);
-        assert_equals(mainAccessible.selectionFocusOffset, 8);
+        assert_equals(mainAccessible.selectionFocusObject, textboxAccessible);
+        assert_equals(mainAccessible.selectionFocusOffset, 2);
 
-        assert_equals(rootAccessible.selectionAnchorObject, line1TextAccessible);
+        assert_equals(rootAccessible.selectionAnchorObject, textboxAccessible);
         assert_equals(rootAccessible.selectionAnchorOffset, 0);
         assert_equals(rootAccessible.selectionFocusObject, textboxAccessible);
-        assert_equals(rootAccessible.selectionFocusOffset, 8);
+        assert_equals(rootAccessible.selectionFocusOffset, 2);
     }, "Test selectNodeContents on an ARIA textbox.");
 
     test_after_layout_and_paint(() =>
@@ -73,18 +72,20 @@
         selection.removeAllRanges();
         selection.addRange(selectionRange);
 
+        assert_equals(contenteditableAccessible.selectionAnchorObject, contenteditableAccessible);
         assert_equals(contenteditableAccessible.selectionAnchorOffset, 0);
-        assert_equals(contenteditableAccessible.selectionFocusOffset, 6);
+        assert_equals(contenteditableAccessible.selectionFocusObject, contenteditableAccessible);
+        assert_equals(contenteditableAccessible.selectionFocusOffset, 2);
 
+        assert_equals(mainAccessible.selectionAnchorObject, contenteditableAccessible);
         assert_equals(mainAccessible.selectionAnchorOffset, 0);
-        assert_equals(mainAccessible.selectionFocusOffset, 6);
+        assert_equals(mainAccessible.selectionFocusObject, contenteditableAccessible);
+        assert_equals(mainAccessible.selectionFocusOffset, 2);
 
-        assert_equals(rootAccessible.selectionAnchorObject.name,
-            line1.textContent);
+        assert_equals(rootAccessible.selectionAnchorObject, contenteditableAccessible);
         assert_equals(rootAccessible.selectionAnchorOffset, 0);
-        assert_equals(rootAccessible.selectionFocusObject.name,
-            line3.textContent);
-        assert_equals(rootAccessible.selectionFocusOffset, 6);
+        assert_equals(rootAccessible.selectionFocusObject, contenteditableAccessible);
+        assert_equals(rootAccessible.selectionFocusOffset, 2);
     }, "Test selectNodeContents on a contenteditable.");
 
     test_after_layout_and_paint(() =>
@@ -216,28 +217,40 @@
         let line2 = document.getElementById("paragraph1").lastChild;
         let line3 = document.getElementById("paragraph2").firstChild;
         let contenteditableLines = [ line1, line2, line3 ];
+
         let contenteditableAccessible = accessibilityController.accessibleElementById("contenteditable-div");
+        let paragraph1Accessible = accessibilityController.accessibleElementById("paragraph1");
+        let paragraph2Accessible = accessibilityController.accessibleElementById("paragraph2");
+        let line1Accessible = paragraph1Accessible.childAtIndex(0);
+        let line2Accessible = paragraph1Accessible.childAtIndex(2);
+        let line3Accessible = paragraph2Accessible.childAtIndex(0);
+        let expectations = [
+          [  line1Accessible, 0, paragraph1Accessible, 1 ],
+          [ line2Accessible, 0, paragraph1Accessible, 3 ],
+          [ line3Accessible, 0, paragraph2Accessible, 1 ],
+        ];
 
         // Select entire lines in the second content editable.
         for (let testCase = 0; testCase < 2; ++testCase) {
-
             for (let i = 0; i < contenteditableLines.length; ++i) {
                 selectionRange.selectNode(contenteditableLines[i]);
                 selection.removeAllRanges();
                 selection.addRange(selectionRange);
 
-                assert_equals(contenteditableAccessible.selectionAnchorOffset, 0);
-                assert_equals(contenteditableAccessible.selectionFocusOffset, 6);
+                assert_equals(contenteditableAccessible.selectionAnchorObject, expectations[i][0]);
+                assert_equals(contenteditableAccessible.selectionAnchorOffset, expectations[i][1]);
+                assert_equals(contenteditableAccessible.selectionFocusObject, expectations[i][2]);
+                assert_equals(contenteditableAccessible.selectionFocusOffset, expectations[i][3]);
 
-                assert_equals(mainAccessible.selectionAnchorOffset, 0);
-                assert_equals(mainAccessible.selectionFocusOffset, 6);
+                assert_equals(mainAccessible.selectionAnchorObject, expectations[i][0]);
+                assert_equals(mainAccessible.selectionAnchorOffset, expectations[i][1]);
+                assert_equals(mainAccessible.selectionFocusObject, expectations[i][2]);
+                assert_equals(mainAccessible.selectionFocusOffset, expectations[i][3]);
 
-                assert_equals(rootAccessible.selectionAnchorObject.name,
-                    contenteditableLines[i].textContent);
-                assert_equals(rootAccessible.selectionAnchorOffset, 0);
-                assert_equals(rootAccessible.selectionFocusObject.name,
-                    contenteditableLines[i].textContent);
-                assert_equals(rootAccessible.selectionFocusOffset, 6);
+                assert_equals(rootAccessible.selectionAnchorObject, expectations[i][0]);
+                assert_equals(rootAccessible.selectionAnchorOffset, expectations[i][1]);
+                assert_equals(rootAccessible.selectionFocusObject, expectations[i][2]);
+                assert_equals(rootAccessible.selectionFocusOffset, expectations[i][3]);
             }
 
             // For a sanity check, try the same test with contenteditable="false".
diff --git a/third_party/blink/web_tests/accessibility/list-with-selection.html b/third_party/blink/web_tests/accessibility/list-with-selection.html
index 9c051d04..f7a6af32 100644
--- a/third_party/blink/web_tests/accessibility/list-with-selection.html
+++ b/third_party/blink/web_tests/accessibility/list-with-selection.html
@@ -35,11 +35,11 @@
 
       // Select both items in the list.
       axList.setSelectedTextRange(0, 2);
-      let selectionText = 'Item1\nItem2';
       assert_equals(axList.selectionAnchorOffset, 0, id);
-      assert_equals(axList.selectionFocusOffset, 5, id);
+      assert_equals(axList.selectionFocusOffset, 2, id);
 
       let selection = window.getSelection();
+      let selectionText = 'Item1\nItem2';
       assert_equals(selection.toString(), selectionText, id);
 
       if (window.testRunner)
diff --git a/third_party/blink/web_tests/accessibility/selection-affinity.html b/third_party/blink/web_tests/accessibility/selection-affinity.html
index dc1a25e..737fb0e 100644
--- a/third_party/blink/web_tests/accessibility/selection-affinity.html
+++ b/third_party/blink/web_tests/accessibility/selection-affinity.html
@@ -65,7 +65,9 @@
         var axRoot = accessibilityController.rootElement;
         assert_equals(axRoot.selectionAnchorObject.role, "AXRole: AXStaticText");
         assert_equals(axRoot.selectionAnchorObject.name, "a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z");
+        assert_equals(axRoot.selectionFocusOffset, endCharIndex - startCharIndex);
         assert_equals(axRoot.selectionAnchorOffset, endCharIndex - startCharIndex);
+        assert_equals(axRoot.selectionFocusAffinity, "upstream");
         assert_equals(axRoot.selectionAnchorAffinity, "upstream");
 
         // Click to place the cursor at the beginning of the next line.
@@ -83,6 +85,8 @@
         assert_equals(axRoot.selectionAnchorObject.role, "AXRole: AXStaticText");
         assert_equals(axRoot.selectionAnchorObject.name, "a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z");
         assert_equals(axRoot.selectionAnchorOffset, endCharIndex - startCharIndex);
+        assert_equals(axRoot.selectionFocusOffset, endCharIndex - startCharIndex);
         assert_equals(axRoot.selectionAnchorAffinity, "downstream");
+        assert_equals(axRoot.selectionFocusAffinity, "downstream");
     }, "Test upstream and downstream affinity of text selection.");
 </script>
diff --git a/third_party/blink/web_tests/accessibility/set-selection-child-offset.html b/third_party/blink/web_tests/accessibility/set-selection-child-offset.html
index d151d9f3..127aa76 100644
--- a/third_party/blink/web_tests/accessibility/set-selection-child-offset.html
+++ b/third_party/blink/web_tests/accessibility/set-selection-child-offset.html
@@ -3,132 +3,79 @@
 <script src="../resources/testharnessreport.js"></script>
 <script src="../resources/run-after-layout-and-paint.js"></script>
 
-<div role="region">
-    <span role="presentation" id="span1">this is a<a id="link" href="#1">test</a></span>
-    <span role="presentation" id="span2">of selection</span>
+<div role="region" id="region">
+  <span role="none" id="span1">This is a<a id="link" href="#1">test</a></span>
+  <span role="none" id="span2">of selection.</span>
 </div>
 
 <script>
-    var sel = null;
-    function updateSelectedRangeFromPage() {
-        sel = window.getSelection().getRangeAt(0);
-    }
+  function verifySelection(anchorNode, anchorOffset, focusNode, focusOffset, selectionString) {
+    const selection = getSelection();
+    assert_equals(selection.anchorNode, anchorNode, 'anchorNode');
+    assert_equals(selection.anchorOffset, anchorOffset, 'anchorOffset');
+    assert_equals(selection.focusNode, focusNode, 'focusNode');
+    assert_equals(selection.focusOffset, focusOffset, 'focusOffset');
+    assert_equals(selection.toString(), selectionString, 'getSelection.toString()');
+  }
 
-    // The accessibility tree contains a region with 3 children, select each.
-    test_after_layout_and_paint(function()
-    {
-        assert_not_equals(window.accessibilityController, undefined, 'This test requires accessibilityController');
+  setup(() => {
+    window.axRegion = accessibilityController.accessibleElementById('region');
+    window.span1 = document.querySelector('span');
+    window.text1 = span1.firstChild;
+    window.span2 = document.querySelectorAll('span')[1];
+    window.text2 = span2.firstChild;
+    window.link = document.querySelector('a');
+    window.linkText = link.firstChild;
+    window.lineBreak = span1.nextSibling;
+  });
 
-        var axRegion = accessibilityController.accessibleElementById("link").parentElement();
-        assert_equals(axRegion.role, "AXRole: AXRegion");
+  test_after_layout_and_paint(() => {
+    axRegion.setSelection(axRegion, 0, axRegion, 0);
+    verifySelection(text1, 0, text1, 0, '');
+  }, 'Test creating a collapsed selection before the first character of the first span.');
 
-        // An insertion point before the first child.
-        axRegion.setSelection(axRegion, 0, axRegion, 0);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "");
-        assert_true(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.startOffset, 0);
-        assert_equals(sel.startContainer.textContent, "this is a");
+  test(() => {
+    axRegion.setSelection(axRegion, 0, axRegion, 1);
+    verifySelection(text1, 0, span1, 1, 'This is a');
+  }, 'Test creating a selection around the text in the first span.');
 
-        // A straight-forward selection of the first child.
-        axRegion.setSelection(axRegion, 0, axRegion, 1);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "this is a");
-        assert_false(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.endContainer.constructor, Text);
-        assert_equals(sel.startOffset, 0);
-        assert_equals(sel.startContainer.textContent, "this is a");
-        assert_equals(sel.endOffset, 9);
-        assert_equals(sel.endContainer.textContent, "this is a");
+  test(() => {
+    axRegion.setSelection(axRegion, 1, axRegion, 1);
+    verifySelection(span1, 1, span1, 1, '');
+  }, 'Test creating a collapsed selection before the link.');
 
-        // Another insertion point after the first child, before the second.
-        axRegion.setSelection(axRegion, 1, axRegion, 1);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "");
-        assert_true(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.startOffset, 9);
-        assert_equals(sel.startContainer.textContent, "this is a");
+  test(() => {
+    axRegion.setSelection(axRegion, 1, axRegion, 2);
+    verifySelection(span1, 1, lineBreak, 0, 'test');
+  }, 'Test creating a selection around the link.');
 
-        // Select the second child.
-        axRegion.setSelection(axRegion, 1, axRegion, 3);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "test\n");
-        assert_false(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.endContainer.constructor, Text);
-        assert_equals(sel.startOffset, 0);
-        assert_equals(sel.startContainer.textContent, "test");
-        assert_equals(sel.startContainer.compareDocumentPosition(sel.endContainer), 4 /* before */);
-        assert_equals(sel.endOffset, 1);
-        assert_equals(sel.endContainer.textContent, "\n    ");
+  test(() => {
+    axRegion.setSelection(axRegion, 3, axRegion, 3);
+    verifySelection(text2, 0, text2, 0, '');
+  }, 'Test creating a collapsed selection before the second span.');
 
-        // Next, another insertion point between second and third child.
-        axRegion.setSelection(axRegion, 3, axRegion, 3);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "");
-        assert_true(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.startOffset, 1);
-        assert_equals(sel.startContainer.textContent, "\n    ");
+  test(() => {
+    axRegion.setSelection(axRegion, 3, axRegion, 4);
+    verifySelection(text2, 0, span2, 1, 'of selection.');
+  }, 'Test creating a selection around the second span.');
 
-        // Select the third child.
-        axRegion.setSelection(axRegion, 3, axRegion, 4);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "of selection");
-        assert_false(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.endContainer.constructor, Text);
-        assert_equals(sel.startOffset, 0);
-        assert_equals(sel.startContainer.textContent, "of selection");
-        assert_equals(sel.endOffset, 12);
-        assert_equals(sel.endContainer.textContent, "of selection");
+  test(() => {
+    axRegion.setSelection(axRegion, 0, axRegion, 3);
+    verifySelection(text1, 0, text2, 0, 'This is atest ');
+  }, 'Test creating a selection from the first span and the link up to the second span.');
 
-        // Select the first and second children.
-        axRegion.setSelection(axRegion, 0, axRegion, 3);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "this is atest\n");
-        assert_false(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.endContainer.constructor, Text);
-        assert_equals(sel.startOffset, 0);
-        assert_equals(sel.startContainer.textContent, "this is a");
-        assert_equals(sel.endOffset, 1);
-        assert_equals(sel.endContainer.textContent, "\n    ");
+  test(() => {
+    axRegion.setSelection(axRegion, 1, axRegion, 4);
+    verifySelection(span1, 1, span2, 1, 'test of selection.');
+  }, 'Test creating a selection around the link and the second span.');
 
-        // Select the second and third children.
-        axRegion.setSelection(axRegion, 1, axRegion, 4);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "test\n    of selection");
-        assert_false(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.endContainer.constructor, Text);
-        assert_equals(sel.startOffset, 0);
-        assert_equals(sel.startContainer.textContent, "test");
-        assert_equals(sel.endOffset, 12);
-        assert_equals(sel.endContainer.textContent, "of selection");
+  test(() => {
+    axRegion.setSelection(axRegion, 0, axRegion, 4);
+    verifySelection(text1, 0, span2, 1, 'This is atest of selection.');
+  }, 'Test creating a selection around both spans.');
 
-        // Select everything.
-        axRegion.setSelection(axRegion, 0, axRegion, 4);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "this is atest\n    of selection");
-        assert_false(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.endContainer.constructor, Text);
-        assert_equals(sel.startOffset, 0);
-        assert_equals(sel.startContainer.textContent, "this is a");
-        assert_equals(sel.endOffset, 12);
-        assert_equals(sel.endContainer.textContent, "of selection");
-
-        // Last, the insertion point after third child.
-        axRegion.setSelection(axRegion, 4, axRegion, 4);
-        updateSelectedRangeFromPage();
-        assert_equals(sel.toString(), "");
-        assert_true(sel.collapsed);
-        assert_equals(sel.startContainer.constructor, Text);
-        assert_equals(sel.startOffset, 12);
-        assert_equals(sel.startContainer.textContent, "of selection");
-    }, "Select child node at index.");
+  test(() => {
+    axRegion.setSelection(axRegion, 4, axRegion, 4);
+    verifySelection(span2, 1, span2, 1, '');
+  }, 'Test creating a collapsed selection after the second span.');
 </script>
diff --git a/third_party/blink/web_tests/accessibility/set-selection-link.html b/third_party/blink/web_tests/accessibility/set-selection-link.html
index 07e06478..a528e38 100644
--- a/third_party/blink/web_tests/accessibility/set-selection-link.html
+++ b/third_party/blink/web_tests/accessibility/set-selection-link.html
@@ -3,27 +3,25 @@
 <script src="../resources/testharnessreport.js"></script>
 <script src="../resources/run-after-layout-and-paint.js"></script>
 
-<div id="main" role="main">
-
-    <p id="para">This<br> is a<a href="#g">test</a>of selection</p>
-
-</div>
+<p id="para">This<br> is a<a href="#g">test</a>of selection</p>
 
 <script>
-    test_after_layout_and_paint(function()
-    {
-        assert_not_equals(window.accessibilityController, undefined, 'This test requires accessibilityController');
+  test_after_layout_and_paint(() => {
+    const lastText = document.getElementById('para').lastChild;
+    assert_class_string(lastText, 'Text');
 
-        var axPara = accessibilityController.accessibleElementById("para");
-        var axLastText = axPara.childAtIndex(4);
-        assert_equals(axLastText.role, "AXRole: AXStaticText");
-        assert_equals(axLastText.name, "of selection");
+    const axPara = accessibilityController.accessibleElementById('para');
+    const axLastText = axPara.childAtIndex(4);
+    assert_equals(axLastText.role, 'AXRole: AXStaticText');
+    assert_equals(axLastText.name, 'of selection');
 
-        axLastText.setSelectedTextRange(0, 2);
+    axLastText.setSelection(axLastText, 0, axLastText, 2);
 
-        var selection = window.getSelection();
-        var range = selection.getRangeAt(0);
-
-        assert_equals(range.toString(), "of");
-    }, "Select text after a link.");
+    const selection = getSelection();
+    assert_equals(selection.anchorNode, lastText, 'anchorNode');
+    assert_equals(selection.anchorOffset, 0, 'anchorOffset');
+    assert_equals(selection.focusNode, lastText, 'focusNode');
+    assert_equals(selection.focusOffset, 2, 'focusOffset');
+    assert_equals(selection.toString(), 'of', 'getSelection().toString()');
+  }, 'Select text after a link.');
 </script>
diff --git a/third_party/blink/web_tests/accessibility/set-selection-whitespace.html b/third_party/blink/web_tests/accessibility/set-selection-whitespace.html
index f9d9fa8..9706d0f5 100644
--- a/third_party/blink/web_tests/accessibility/set-selection-whitespace.html
+++ b/third_party/blink/web_tests/accessibility/set-selection-whitespace.html
@@ -3,71 +3,78 @@
 <script src="../resources/testharnessreport.js"></script>
 <script src="../resources/run-after-layout-and-paint.js"></script>
 
-<div id="main" role="main">
+<div id="editable1" contenteditable="true">Hello1</div>
 
-    <div id="editable1" contenteditable="true">Hello1</div>
+<div id="editable2" contenteditable="true">  Hello2  </div>
 
-    <div id="editable2" contenteditable="true">  Hello2  </div>
-
-    <h2 id="heading">  Hello2  </h2>
-
-</div>
+<h2 id="heading">  Hello2  </h2>
 
 <script>
-    test_after_layout_and_paint(function()
-    {
-        var axEditable1 = accessibilityController.accessibleElementById("editable1");
-        var axTextNode1 = axEditable1.childAtIndex(0);
-        assert_equals(axTextNode1.name, "Hello1");
-        axTextNode1.setSelectedTextRange(0, 6);
+  test_after_layout_and_paint(() => {
+    const editable1 = document.getElementById('editable1');
+    assert_class_string(editable1, 'HTMLDivElement');
+    const textNode1 = editable1.firstChild;
+    assert_class_string(textNode1, 'Text');
 
-        var selection = window.getSelection();
-        var range = selection.getRangeAt(0);
-        assert_equals(range.toString(), "Hello1");
+    const axEditable1 = accessibilityController.accessibleElementById('editable1');
+    const axTextNode1 = axEditable1.childAtIndex(0);
+    assert_equals(axTextNode1.role, 'AXRole: AXStaticText');
+    assert_equals(axTextNode1.name, 'Hello1');
 
-        var editable1 = document.getElementById("editable1");
-        var textNode1 = editable1.firstChild;
-        assert_equals(range.startContainer, textNode1);
-        assert_equals(range.startOffset, 0);
-        assert_equals(range.endContainer, textNode1);
-        assert_equals(range.endOffset, 6);
-    }, "Using accessible APIs to set selection works when there's no extra whitespace.");
+    axTextNode1.setSelection(axTextNode1, 0, axTextNode1, 6);
 
-    test_after_layout_and_paint(function()
-    {
-        var axEditable2 = accessibilityController.accessibleElementById("editable2");
-        var axTextNode2 = axEditable2.childAtIndex(0);
-        assert_equals(axTextNode2.name, "Hello2");
-        axTextNode2.setSelectedTextRange(0, 6);
+    const selection = getSelection();
+    // "Before object" positions at a text node are always adjusted to be before
+    // the first character of the text node itself.
+    // This ensures that to otherwise identical AX positions will have the same
+    // DOM position.
+    assert_equals(selection.anchorNode, textNode1, 'anchorNode');
+    assert_equals(selection.anchorOffset, 0, 'anchorOffset');
+    assert_equals(selection.focusNode, textNode1, 'focusNode');
+    assert_equals(selection.focusOffset, 6, 'focusOffset');
+    assert_equals(selection.toString(), 'Hello1', 'getSelection.toString()');
+  }, 'Using accessible APIs to set selection works when there is no extra whitespace.');
 
-        var selection = window.getSelection();
-        var range = selection.getRangeAt(0);
-        assert_equals(range.toString(), "Hello2");
+test_after_layout_and_paint(() => {
+    const editable2 = document.getElementById('editable2');
+    assert_class_string(editable2, 'HTMLDivElement');
+    const textNode2 = editable2.firstChild;
+    assert_class_string(textNode2, 'Text');
 
-        var editable2 = document.getElementById("editable2");
-        var textNode2 = editable2.firstChild;
-        assert_equals(range.startContainer, textNode2);
-        assert_equals(range.startOffset, 2);
-        assert_equals(range.endContainer, textNode2);
-        assert_equals(range.endOffset, 8);
-    }, "Using accessible APIs to set selection works even with non-visible whitespace.");
-</script>
+    const axEditable2 = accessibilityController.accessibleElementById('editable2');
+    const axTextNode2 = axEditable2.childAtIndex(0);
+    assert_equals(axTextNode2.role, 'AXRole: AXStaticText');
+    assert_equals(axTextNode2.name, 'Hello2');
 
-<script>
-    test_after_layout_and_paint(function()
-    {
-        var axHeading = accessibilityController.accessibleElementById("heading");
-        axHeading.setSelectedTextRange(0, 1);  // Should select the whole element.
+    axTextNode2.setSelection(axTextNode2, 0, axTextNode2, 6);
 
-        var selection = window.getSelection();
-        var range = selection.getRangeAt(0);
-        assert_equals(range.toString(), "Hello2");
+    const selection = getSelection();
+    // Anchor and focus offsets are DOM base and are therefore adjusted to
+    // account for whitespace.
+    assert_equals(selection.anchorNode, textNode2, 'anchorNode');
+    assert_equals(selection.anchorOffset, 2, 'anchorOffset');
+    assert_equals(selection.focusNode, textNode2, 'focusNode');
+    assert_equals(selection.focusOffset, 8, 'focusOffset');
+    assert_equals(selection.toString(), 'Hello2', 'getSelection.toString()');
+  }, 'Using accessible APIs to set selection works even with non-visible whitespace.');
 
-        var heading = document.getElementById("heading");
-        var textNode = heading.firstChild;
-        assert_equals(range.startContainer, textNode);
-        assert_equals(range.startOffset, 2);
-        assert_equals(range.endContainer, textNode);
-        assert_equals(range.endOffset, 8);
-    }, "Use accessible APIs to set selection on element rather than static text node.");
+  test_after_layout_and_paint(() => {
+    const heading = document.getElementById('heading');
+    assert_class_string(heading, 'HTMLHeadingElement');
+    const headingText = heading.firstChild;
+    assert_class_string(headingText, 'Text');
+
+    const axHeading = accessibilityController.accessibleElementById('heading');
+    assert_equals(axHeading.role, 'AXRole: AXHeading');
+
+    // Select all the children in the heading.
+    axHeading.setSelection(axHeading, 0, axHeading, 1);
+
+    const selection = getSelection();
+    assert_equals(selection.anchorNode, headingText, 'anchorNode');
+    assert_equals(selection.anchorOffset, 2, 'anchorOffset');
+    assert_equals(selection.focusNode, heading, 'focusNode');
+    assert_equals(selection.focusOffset, 1, 'focusOffset');
+    assert_equals(selection.toString(), 'Hello2', 'getSelection.toString()');
+  }, 'Use accessible APIs to set selection on all the children of an element rather than a static text node.');
 </script>
diff --git a/third_party/blink/web_tests/accessibility/textarea-caret-position-expected.txt b/third_party/blink/web_tests/accessibility/textarea-caret-position-expected.txt
index ef877e2..35a8a63 100644
--- a/third_party/blink/web_tests/accessibility/textarea-caret-position-expected.txt
+++ b/third_party/blink/web_tests/accessibility/textarea-caret-position-expected.txt
@@ -86,6 +86,10 @@
 PASS textareaAccessible.selectionAnchorOffset is 20
 PASS textareaAccessible is textareaAccessible.selectionFocusObject
 PASS textareaAccessible.selectionFocusOffset is 20
+PASS emptyTextareaAccessible is textareaAccessible.selectionAnchorObject
+PASS textareaAccessible.selectionAnchorOffset is 0
+PASS emptyTextareaAccessible is textareaAccessible.selectionFocusObject
+PASS textareaAccessible.selectionFocusOffset is 0
 PASS textareaAccessible is textareaAccessible.selectionAnchorObject
 PASS textareaAccessible.selectionAnchorOffset is 20
 PASS textareaAccessible is textareaAccessible.selectionFocusObject
diff --git a/third_party/blink/web_tests/accessibility/textarea-caret-position.html b/third_party/blink/web_tests/accessibility/textarea-caret-position.html
index b1f72e6..db265126 100644
--- a/third_party/blink/web_tests/accessibility/textarea-caret-position.html
+++ b/third_party/blink/web_tests/accessibility/textarea-caret-position.html
@@ -22,6 +22,8 @@
             textarea.focus();
             window.textareaAccessible =
                 accessibilityController.accessibleElementById('textarea');
+            window.emptyTextareaAccessible =
+                accessibilityController.accessibleElementById('textarea-empty');
 
             for (let i = 0; i < 3; ++i) {
                 for (let j = 0; j < 7; ++j) {
@@ -39,14 +41,21 @@
 
             let emptyTextarea = document.getElementById('textarea-empty');
             emptyTextarea.focus();
-            // Each textarea has its own independent caret.
+            // Each textarea remembers its own independent selection but
+            // textareas that are not focused don't expose their selection
+            // visually.
+            shouldBe("emptyTextareaAccessible", "textareaAccessible.selectionAnchorObject");
+            shouldBeEqualToNumber("textareaAccessible.selectionAnchorOffset", 0);
+            shouldBe("emptyTextareaAccessible", "textareaAccessible.selectionFocusObject");
+            shouldBeEqualToNumber("textareaAccessible.selectionFocusOffset", 0);
+
+            textarea.focus();
             shouldBe("textareaAccessible", "textareaAccessible.selectionAnchorObject");
             shouldBeEqualToNumber("textareaAccessible.selectionAnchorOffset", 20);
             shouldBe("textareaAccessible", "textareaAccessible.selectionFocusObject");
             shouldBeEqualToNumber("textareaAccessible.selectionFocusOffset", 20);
 
-            window.emptyTextareaAccessible =
-                accessibilityController.accessibleElementById('textarea-empty');
+            emptyTextarea.focus();
             shouldBe("emptyTextareaAccessible", "emptyTextareaAccessible.selectionAnchorObject");
             shouldBeZero("emptyTextareaAccessible.selectionAnchorOffset");
             shouldBe("emptyTextareaAccessible", "emptyTextareaAccessible.selectionFocusObject");
diff --git a/third_party/blink/web_tests/accessibility/textarea-selection-expected.txt b/third_party/blink/web_tests/accessibility/textarea-selection-expected.txt
index 9c6f6ac..2e758649 100644
--- a/third_party/blink/web_tests/accessibility/textarea-selection-expected.txt
+++ b/third_party/blink/web_tests/accessibility/textarea-selection-expected.txt
@@ -30,6 +30,10 @@
 PASS textareaAccessible.selectionAnchorOffset is 14
 PASS textareaAccessible is textareaAccessible.selectionFocusObject
 PASS textareaAccessible.selectionFocusOffset is 21
+PASS emptyTextareaAccessible is textareaAccessible.selectionAnchorObject
+PASS textareaAccessible.selectionAnchorOffset is 0
+PASS emptyTextareaAccessible is textareaAccessible.selectionFocusObject
+PASS textareaAccessible.selectionFocusOffset is 0
 PASS textareaAccessible is textareaAccessible.selectionAnchorObject
 PASS textareaAccessible.selectionAnchorOffset is 14
 PASS textareaAccessible is textareaAccessible.selectionFocusObject
diff --git a/third_party/blink/web_tests/accessibility/textarea-selection.html b/third_party/blink/web_tests/accessibility/textarea-selection.html
index e90d515..e58317d7 100644
--- a/third_party/blink/web_tests/accessibility/textarea-selection.html
+++ b/third_party/blink/web_tests/accessibility/textarea-selection.html
@@ -22,6 +22,8 @@
             textarea.focus();
             window.textareaAccessible =
                 accessibilityController.accessibleElementById('textarea');
+            window.emptyTextareaAccessible =
+                accessibilityController.accessibleElementById('textarea-empty');
 
             // Select the entire contents.
             textarea.select();
@@ -53,15 +55,22 @@
             }
 
             let emptyTextarea = document.getElementById('textarea-empty');
-            emptyTextarea.focus();
-            // Each textarea has its own independent selection.
+            emptyTextarea.focus();            
+            // Each textarea remembers its own independent selection but
+            // textareas that are not focused don't expose their selection
+            // visually.
+            shouldBe("emptyTextareaAccessible", "textareaAccessible.selectionAnchorObject");
+            shouldBeEqualToNumber("textareaAccessible.selectionAnchorOffset", 0);
+            shouldBe("emptyTextareaAccessible", "textareaAccessible.selectionFocusObject");
+            shouldBeEqualToNumber("textareaAccessible.selectionFocusOffset", 0);
+
+            textarea.focus();
             shouldBe("textareaAccessible", "textareaAccessible.selectionAnchorObject");
             shouldBeEqualToNumber("textareaAccessible.selectionAnchorOffset", 14);
             shouldBe("textareaAccessible", "textareaAccessible.selectionFocusObject");
             shouldBeEqualToNumber("textareaAccessible.selectionFocusOffset", 21);
-            
-            window.emptyTextareaAccessible =
-                accessibilityController.accessibleElementById('textarea-empty');
+
+            emptyTextarea.focus();
             shouldBe("emptyTextareaAccessible", "emptyTextareaAccessible.selectionAnchorObject");
             shouldBeZero("emptyTextareaAccessible.selectionAnchorOffset");
             shouldBe("emptyTextareaAccessible", "emptyTextareaAccessible.selectionFocusObject");
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index 8de0fd9c..9e0866f 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -124820,11 +124820,6 @@
      {}
     ]
    ],
-   "css/CSS2/floats/computed-float-position-absolute-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "css/CSS2/floats/float-nowrap-1-notref.html": [
     [
      {}
@@ -331463,10 +331458,6 @@
    "59843ae54b64f6ce4f7e616d4be491c911ea84cf",
    "support"
   ],
-  "css/CSS2/floats/computed-float-position-absolute-expected.txt": [
-   "f9b2c8a3ec6a6f8c4cb0c0e7097abab7f48b3303",
-   "support"
-  ],
   "css/CSS2/floats/computed-float-position-absolute.html": [
    "ad9220b3a06085c458f7100c896100fb32f562e8",
    "testharness"
@@ -469352,7 +469343,7 @@
    "support"
   ],
   "tools/ci/jobs.py": [
-   "2b54327ad20506c23f6698ddd6c5c5f1def8b09f",
+   "ddf110096440f5c798ffdf46ffb51e90b1365978",
    "support"
   ],
   "tools/ci/make_hosts_file.py": [
@@ -473736,7 +473727,7 @@
    "support"
   ],
   "tools/wptrunner/tox.ini": [
-   "d784c5b299e353f8c85aad5881caeab535522a52",
+   "5d343751c5488cdd1a73b9d3fcae94de46874a3e",
    "support"
   ],
   "tools/wptrunner/wptrunner.default.ini": [
@@ -474072,23 +474063,23 @@
    "support"
   ],
   "tools/wptrunner/wptrunner/wptmanifest/tests/test_conditional.py": [
-   "9da1a0f180ca23e8cadb2c9b430e983ba75dbca0",
+   "10b6319143230e3ffd8a1e976f804f3047771baf",
    "support"
   ],
   "tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py": [
-   "a2a7dadc043b64ff8c9fdc2c4a3cb3837c52c4a0",
+   "c00320fa5ed9beac16221d4ea14d782e52a39b68",
    "support"
   ],
   "tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py": [
-   "02280e51f845c8144b96bde8b588b9b7c376a345",
+   "f906afa34a36e5650e4c56d5b87982ca702520d9",
    "support"
   ],
   "tools/wptrunner/wptrunner/wptmanifest/tests/test_static.py": [
-   "f63869636da79cdcdc961e137d4f044055d74de4",
+   "e0e83c83e80df2aa67fa75ac8681fa0d352d0332",
    "support"
   ],
   "tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py": [
-   "e0fb8559a1e793b1f4a26b39a1333f6e23d38b4b",
+   "b7c62c0041496be4f55f06699eabf9a3cf77d654",
    "support"
   ],
   "tools/wptrunner/wptrunner/wptrunner.py": [
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/buffer-before-onload.html b/third_party/blink/web_tests/external/wpt/element-timing/buffer-before-onload.html
index 1fd0277..805777f2 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/buffer-before-onload.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/buffer-before-onload.html
@@ -18,6 +18,7 @@
     const img = document.createElement('img');
     img.src = 'resources/square20.jpg';
     img.setAttribute('elementtiming', 'my_image');
+    img.setAttribute('id', 'my_id');
     document.body.appendChild(img);
     window.onload = t.step_func_done( () => {
       const entries = performance.getEntriesByType('element');
@@ -27,7 +28,8 @@
       const index = window.location.href.lastIndexOf('/');
       const pathname = window.location.href.substring(0, index) +
           '/resources/square20.jpg';
-      checkElement(entry, pathname, 'my_image', beforeRender);
+      checkElement(entry, pathname, 'my_image', 'my_id', beforeRender);
+      checkNaturalSize(entry, 20, 20);
     });
   }, "Element Timing: image loads before onload.");
 
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/cross-origin-element.sub.html b/third_party/blink/web_tests/external/wpt/element-timing/cross-origin-element.sub.html
index a122819f..b1a5b7c 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/cross-origin-element.sub.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/cross-origin-element.sub.html
@@ -18,10 +18,11 @@
       t.step_func_done((entryList) => {
         assert_equals(entryList.getEntries().length, 1);
         const entry = entryList.getEntries()[0];
-        checkElement(entry, pathname, 'my_image', 0);
+        checkElement(entry, pathname, 'my_image', 'the_id', 0);
         assert_equals(entry.startTime, 0,
           'The startTime of a cross-origin image should be 0.');
         checkRect(entry, [0, 100, 0, 100]);
+        checkNaturalSize(entry, 100, 100);
       })
     );
     observer.observe({entryTypes: ['element']});
@@ -33,6 +34,7 @@
       const img = document.createElement('img');
       img.src = pathname;
       img.setAttribute('elementtiming', 'my_image');
+      img.setAttribute('id', 'the_id');
       document.body.appendChild(img);
     });
   }, 'Cross-origin image element is NOT observable.');
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-TAO-wildcard.sub.html b/third_party/blink/web_tests/external/wpt/element-timing/image-TAO-wildcard.sub.html
index 3ba12a7d0..0e24af06 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/image-TAO-wildcard.sub.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-TAO-wildcard.sub.html
@@ -19,9 +19,10 @@
       t.step_func_done((entryList) => {
         assert_equals(entryList.getEntries().length, 1);
         const entry = entryList.getEntries()[0];
-        checkElement(entry, img_src, 'my_image', beforeRender);
+        checkElement(entry, img_src, 'my_image', 'my_id', beforeRender);
         // Assume viewport has size at least 20, so the element is fully visible.
         checkRect(entry, [0, 20, 0, 20]);
+        checkNaturalSize(entry, 20, 20);
       })
     );
     observer.observe({entryTypes: ['element']});
@@ -32,6 +33,7 @@
       const img = document.createElement('img');
       img.src = img_src;
       img.setAttribute('elementtiming', 'my_image');
+      img.setAttribute('id', 'my_id');
       img.onload = t.step_func(() => {
         // After a short delay, assume that the entry was not dispatched.
         t.step_timeout(() => {
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html b/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html
index 0bd99ab..9f0ef79e 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html
@@ -17,10 +17,10 @@
 
 <div class="slideshow-container">
   <div class='carousel-image'>
-    <img src="resources/circle.svg" elementtiming='image0'>
+    <img src="resources/circle.svg" elementtiming='image0' id='image0'>
   </div>
   <div class='carousel-image'>
-    <img src="resources/square100.png" elementtiming='image1'>
+    <img src="resources/square100.png" elementtiming='image1' id='image1'>
   </div>
 </div>
 
@@ -37,13 +37,15 @@
     const observer = new PerformanceObserver(list => {
       list.getEntries().forEach(entry => {
         if (entry_count % 2 == 0) {
-          checkElement(entry, pathname0, 'image0', beforeRenderTimes[entry_count]);
+          checkElement(entry, pathname0, 'image0', 'image0', beforeRenderTimes[entry_count]);
           checkRect(entry, [0, 200, 0, 200]);
+          checkNaturalSize(entry, 200, 200);
           entry_count_per_element[0]++;
         }
         else {
-          checkElement(entry, pathname1, 'image1', beforeRenderTimes[entry_count]);
+          checkElement(entry, pathname1, 'image1', 'image1', beforeRenderTimes[entry_count]);
           checkRect(entry, [0, 100, 0, 100]);
+          checkNaturalSize(entry, 100, 100);
           entry_count_per_element[1]++;
         }
         entry_count++;
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-clipped-svg.html b/third_party/blink/web_tests/external/wpt/element-timing/image-clipped-svg.html
index 13c4a81..36cf1b1 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/image-clipped-svg.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-clipped-svg.html
@@ -14,9 +14,10 @@
       const index = window.location.href.lastIndexOf('/');
       const pathname = window.location.href.substring(0, index) +
           '/resources/circle.svg';
-      checkElement(entry, pathname, 'my_svg', beforeRender);
+      checkElement(entry, pathname, 'my_svg', 'SVG', beforeRender);
       // Image size is 200x200 but SVG size is 100x100 so it is clipped.
       checkRect(entry, [0, 100, 0, 100]);
+      checkNaturalSize(entry, 200, 200);
     })
   );
   observer.observe({entryTypes: ['element']});
@@ -29,5 +30,5 @@
 }
 </style>
 <svg width="100" height="100">
-  <image href='resources/circle.svg' elementtiming='my_svg'/>
+  <image href='resources/circle.svg' elementtiming='my_svg' id='SVG'/>
 </svg>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-not-fully-visible.html b/third_party/blink/web_tests/external/wpt/element-timing/image-not-fully-visible.html
index d3e2c105..279fa03 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/image-not-fully-visible.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-not-fully-visible.html
@@ -20,11 +20,12 @@
         const index = window.location.href.lastIndexOf('/');
         const pathname = window.location.href.substring(0, index) +
             '/resources/square20.png';
-        checkElement(entry, pathname, 'not_fully_visible', beforeRender);
+        checkElement(entry, pathname, 'not_fully_visible', '', beforeRender);
         // Image will not be fully visible. It should start from the top left part
         // of the document, excluding the margin, and then overflow.
         checkRect(entry,
           [100, document.documentElement.clientWidth, 200, document.documentElement.clientHeight]);
+        checkNaturalSize(entry, 20, 20);
       })
     );
     observer.observe({entryTypes: ['element']});
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-rect-iframe.html b/third_party/blink/web_tests/external/wpt/element-timing/image-rect-iframe.html
index da46d785..94c872e 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/image-rect-iframe.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-rect-iframe.html
@@ -20,6 +20,9 @@
       assert_equals(rect.right, 100);
       assert_equals(rect.top, 0);
       assert_equals(rect.bottom, 100);
+      assert_equals(e.data.naturalWidth, 100);
+      assert_equals(e.data.naturalHeight, 100);
+      assert_equals(e.data.id, 'iframe_img_id');
       t.done();
     });
   }, 'Element Timing entry in iframe has coordinates relative to the iframe.');
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-with-css-scale.html b/third_party/blink/web_tests/external/wpt/element-timing/image-with-css-scale.html
new file mode 100644
index 0000000..6d77429e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-with-css-scale.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: image with scaling.</title>
+<head>
+<style>
+/* The margin of 50px allows the rect to be fully shown when the div is scaled. */
+body {
+  margin: 50px;
+}
+.my_div {
+  width: 100px;
+  height: 50px;
+  transform: scale(2);
+}
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+  const beforeRender = performance.now();
+  async_test(function (t) {
+    const observer = new PerformanceObserver(
+      t.step_func_done(function(entryList) {
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        const index = window.location.href.lastIndexOf('/');
+        const pathname = window.location.href.substring(0, index - 14) +
+            'images/black-rectangle.png';
+        checkElement(entry, pathname, 'rectangle', 'rect_id', beforeRender);
+        checkRect(entry, [0, 200, 25, 125]);
+        checkNaturalSize(entry, 100, 50);
+      })
+    );
+    observer.observe({entryTypes: ['element']});
+  }, 'Image intersectionRect is affected by scaling, but not its intrinsic size.');
+</script>
+<div class="my_div" id='div_id'>
+  <img src="/images/black-rectangle.png" elementtiming="rectangle" id='rect_id'/>
+</div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-with-rotation.html b/third_party/blink/web_tests/external/wpt/element-timing/image-with-rotation.html
new file mode 100644
index 0000000..70b635e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-with-rotation.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: image with rotation.</title>
+<head>
+<style>
+body {
+  margin: 0px;
+}
+.my_div {
+  width: 100px;
+  height: 50px;
+  transform: rotate(45deg);
+  transform-origin: top left;
+}
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+  const beforeRender = performance.now();
+  async_test(function (t) {
+    const observer = new PerformanceObserver(
+      t.step_func_done(function(entryList) {
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        const index = window.location.href.lastIndexOf('/');
+        const pathname = window.location.href.substring(0, index - 14) +
+            'images/black-rectangle.png';
+        checkElement(entry, pathname, 'rectangle', 'rect_id', beforeRender);
+        checkNaturalSize(entry, 100, 50);
+        const rect = entry.intersectionRect;
+        // The div rotates with respect to the origin, so part of it will be invisible.
+        // The width of the visible part will be 100 / sqrt(2) and the height will be
+        // 100 / sqrt(2) + 50 / sqrt(2).
+        assert_equals(rect.left, 0);
+        // Checking precision only to the nearest integer.
+        assert_equals(Math.round(rect.right), 71);
+        assert_equals(rect.top, 0);
+        assert_equals(Math.round(rect.bottom), 106);
+      })
+    );
+    observer.observe({entryTypes: ['element']});
+  }, 'Image intersectionRect is affected by rotation, but not its intrinsic size.');
+</script>
+<div class="my_div" id="div_id">
+  <img src="/images/black-rectangle.png" elementtiming="rectangle" id="rect_id"/>
+</div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html b/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html
index 18c72cd..dbcad24 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html
@@ -22,7 +22,8 @@
     const observer = new PerformanceObserver(
       t.step_func(function(entryList) {
         entryList.getEntries().forEach(entry => {
-          checkElement(entry, pathname, entry.identifier, beforeRender);
+          checkElement(entry, pathname, entry.identifier, 'image_id', beforeRender);
+          checkNaturalSize(entry, 100, 100);
           if (entry.identifier === 'my_image') {
             ++numEntries;
             responseEnd1 = entry.responseEnd;
@@ -46,11 +47,13 @@
       const img = document.createElement('img');
       img.src = 'resources/square100.png';
       img.setAttribute('elementtiming', 'my_image');
+      img.setAttribute('id', 'image_id');
       document.body.appendChild(img);
 
       const img2 = document.createElement('img');
       img2.src = 'resources/square100.png';
       img2.setAttribute('elementtiming', 'my_image2');
+      img2.setAttribute('id', 'image_id');
       document.body.appendChild(img2);
 
       beforeRender = performance.now();
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/observe-elementtiming.html b/third_party/blink/web_tests/external/wpt/element-timing/observe-elementtiming.html
index 9170b36..39fea05 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/observe-elementtiming.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/observe-elementtiming.html
@@ -20,19 +20,21 @@
         const index = window.location.href.lastIndexOf('/');
         const pathname = window.location.href.substring(0, index) +
             '/resources/square100.png';
-        checkElement(entry, pathname, 'my_image', beforeRender);
+        checkElement(entry, pathname, 'my_image', 'my_id', beforeRender);
         // Assume viewport has size at least 100, so the element is fully visible.
         checkRect(entry, [0, 100, 0, 100]);
+        checkNaturalSize(entry, 100, 100);
       })
     );
     observer.observe({entryTypes: ['element']});
     // We add the image during onload to be sure that the observer is registered
     // in time for it to observe the element timing.
     window.onload = () => {
-      // Add image of width and height equal to 100.
+      // Add image of width equal to 100 and height equal to 100.
       const img = document.createElement('img');
       img.src = 'resources/square100.png';
       img.setAttribute('elementtiming', 'my_image');
+      img.setAttribute('id', 'my_id');
       document.body.appendChild(img);
       beforeRender = performance.now();
     };
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/observe-large-image.html b/third_party/blink/web_tests/external/wpt/element-timing/observe-large-image.html
index fb288438..a08274c 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/observe-large-image.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/observe-large-image.html
@@ -20,10 +20,11 @@
         const index = window.location.href.lastIndexOf('/');
         const pathname = window.location.href.substring(0, index) +
             '/resources/square20.jpg';
-        checkElement(entry, pathname, '', beforeRender);
+        checkElement(entry, pathname, '', 'large_one', beforeRender);
         // Assume viewport hasn't changed, so the element occupies all of it.
         checkRect(entry,
           [0, document.documentElement.clientWidth, 0, document.documentElement.clientHeight]);
+        checkNaturalSize(entry, 20, 20);
       })
     );
     observer.observe({entryTypes: ['element']});
@@ -35,6 +36,7 @@
       img.src = 'resources/square20.jpg';
       img.width = document.documentElement.clientWidth;
       img.height = document.documentElement.clientHeight;
+      img.setAttribute('id', 'large_one');
       document.body.appendChild(img);
       beforeRender = performance.now();
     };
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/observe-multiple-images.html b/third_party/blink/web_tests/external/wpt/element-timing/observe-multiple-images.html
index aa91aa83..05c54ac0 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/observe-multiple-images.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/observe-multiple-images.html
@@ -34,7 +34,8 @@
             image1Observed = 1;
             const pathname1 = window.location.href.substring(0, index) +
                 '/resources/square100.png';
-            checkElement(entry, pathname1, 'image1', beforeRender);
+            // The images do not contain ID, so expect an empty ID.
+            checkElement(entry, pathname1, 'image1', 'img1', beforeRender);
             // This image is horizontally centered.
             // Using abs and comparing to 1 because the viewport sizes could be odd.
             // If a size is odd, then image cannot be in the pure center, but left
@@ -48,6 +49,7 @@
             assert_equals(entry.intersectionRect.top, 0, 'top of rect for image1');
             assert_equals(entry.intersectionRect.bottom,
               100, 'bottom of rect for image1');
+            checkNaturalSize(entry, 100, 100);
           }
           else if (entry.identifier === 'image2') {
             if (image2Observed) {
@@ -57,9 +59,10 @@
             image2Observed = 1;
             const pathname2 = window.location.href.substring(0, index) +
                 '/resources/square20.png';
-            checkElement(entry, pathname2, 'image2', beforeRender);
+            checkElement(entry, pathname2, 'image2', 'img2', beforeRender);
             // This image should be below image 1, and should respect the margin.
             checkRect(entry, [50, 250, 250, 450], "of image2");
+            checkNaturalSize(entry, 20, 20);
           }
           else if (entry.identifier === 'image3') {
             if (image3Observed) {
@@ -69,9 +72,10 @@
             image3Observed = 1;
             const pathname3 = window.location.href.substring(0, index) +
                 '/resources/circle.svg';
-            checkElement(entry, pathname3, 'image3', beforeRender);
+            checkElement(entry, pathname3, 'image3', 'img3', beforeRender);
             // This image is just to the right of image2.
             checkRect(entry, [250, 450, 250, 450], "of image3");
+            checkNaturalSize(entry, 200, 200);
           }
           else {
             assert_unreached("Received an unexpected identifier.");
@@ -87,6 +91,7 @@
     function addImage(number, source, width=0) {
       const img = document.createElement('img');
       img.src = source;
+      // Set a different id and elementtiming value for each image.
       img.id = 'img' + number;
       img.setAttribute('elementtiming', 'image' + number);
       if (width !== 0)
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/observe-svg-image.html b/third_party/blink/web_tests/external/wpt/element-timing/observe-svg-image.html
index fdfe25ec..45e800d 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/observe-svg-image.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/observe-svg-image.html
@@ -14,9 +14,10 @@
       const index = window.location.href.lastIndexOf('/');
       const pathname = window.location.href.substring(0, index) +
           '/resources/circle.svg';
-      checkElement(entry, pathname, 'my_svg', beforeRender);
+      checkElement(entry, pathname, 'my_svg', 'svg_id', beforeRender);
       // Assume viewport has size at least 200, so the element is fully visible.
       checkRect(entry, [0, 200, 0, 200]);
+      checkNaturalSize(entry, 200, 200);
     })
   );
   observer.observe({entryTypes: ['element']});
@@ -29,5 +30,5 @@
 }
 </style>
 <svg width="300" height="300">
-  <image href='resources/circle.svg' elementtiming='my_svg'/>
+  <image href='resources/circle.svg' elementtiming='my_svg' id='svg_id'/>
 </svg>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/observe-video-poster.html b/third_party/blink/web_tests/external/wpt/element-timing/observe-video-poster.html
index 4096138..d3a6993 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/observe-video-poster.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/observe-video-poster.html
@@ -14,9 +14,10 @@
       const index = window.location.href.lastIndexOf('/');
       const pathname = window.location.href.substring(0, index) +
           '/resources/circle.svg';
-      checkElement(entry, pathname, 'my_poster', beforeRender);
+      checkElement(entry, pathname, 'my_poster', 'the_poster', beforeRender);
       // Assume viewport has size at least 200, so the element is fully visible.
       checkRect(entry, [0, 200, 0, 200]);
+      checkNaturalSize(entry, 200, 200);
     })
   );
   observer.observe({entryTypes: ['element']});
@@ -28,4 +29,4 @@
   margin: 0;
 }
 </style>
-<video elementtiming='my_poster' src='/media/test.mp4' poster='resources/circle.svg'/>
+<video elementtiming='my_poster' id='the_poster' src='/media/test.mp4' poster='resources/circle.svg'/>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/progressively-loaded-image.html b/third_party/blink/web_tests/external/wpt/element-timing/progressively-loaded-image.html
index cf54e1e..c0a7d4f 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/progressively-loaded-image.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/progressively-loaded-image.html
@@ -24,7 +24,8 @@
             img_src;
         // Since the image is only fully loaded after the sleep, the render timestamp
         // must be greater than |beforeRender| + |sleep|.
-        checkElement(entry, pathname, 'my_image', beforeRender + sleep);
+        checkElement(entry, pathname, 'my_image', '', beforeRender + sleep);
+        checkNaturalSize(entry, 20, 20);
       })
     );
     observer.observe({entryTypes: ['element']});
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/rectangular-image.html b/third_party/blink/web_tests/external/wpt/element-timing/rectangular-image.html
new file mode 100644
index 0000000..b0280845
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/element-timing/rectangular-image.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Element Timing: observe a rectangular image</title>
+<body>
+<style>
+body {
+  margin: 20px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/element-timing-helpers.js"></script>
+<script>
+  let beforeRender;
+  async_test(function (t) {
+    const observer = new PerformanceObserver(
+      t.step_func_done(function(entryList) {
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        const index = window.location.href.lastIndexOf('/');
+        // Subtracting 14 to remove 'element-timing'.
+        const pathname = window.location.href.substring(0, index - 14) +
+            'images/black-rectangle.png';
+        checkElement(entry, pathname, 'my_image', 'rectangle', beforeRender);
+        // Assume viewport has size at least 100, so the element is fully visible.
+        checkRect(entry, [20, 120, 20, 70]);
+        checkNaturalSize(entry, 100, 50);
+      })
+    );
+    observer.observe({entryTypes: ['element']});
+    // We add the image during onload to be sure that the observer is registered
+    // in time for it to observe the element timing.
+    window.onload = () => {
+      // Add image of width equal to 100 and height equal to 50.
+      const img = document.createElement('img');
+      img.src = '/images/black-rectangle.png';
+      img.id = 'rectangle';
+      img.setAttribute('elementtiming', 'my_image');
+      document.body.appendChild(img);
+      beforeRender = performance.now();
+    };
+  }, 'Element with rectangular image has correct rect and instrinsic size.');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js b/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js
index e952930..66605df 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js
+++ b/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js
@@ -1,10 +1,11 @@
 // Checks that this is an ElementTiming entry with name |expectedName|. It also
 // does a very basic check on |startTime|: after |beforeRender| and before now().
-function checkElement(entry, expectedName, expectedIdentifier, beforeRender) {
+function checkElement(entry, expectedName, expectedIdentifier, expectedID, beforeRender) {
   assert_equals(entry.entryType, 'element');
   assert_equals(entry.name, expectedName);
   assert_equals(entry.identifier, expectedIdentifier);
   assert_equals(entry.duration, 0);
+  assert_equals(entry.id, expectedID);
   assert_greater_than_equal(entry.startTime, beforeRender);
   assert_greater_than_equal(performance.now(), entry.startTime);
   const rt_entries = performance.getEntriesByName(expectedName, 'resource');
@@ -12,7 +13,7 @@
   assert_equals(rt_entries[0].responseEnd, entry.responseEnd);
 }
 
-// Checks that the rect matches the desired values [left right top bottom]
+// Checks that the rect matches the desired values [left right top bottom].
 function checkRect(entry, expected, description="") {
   assert_equals(entry.intersectionRect.left, expected[0],
     'left of rect ' + description);
@@ -23,3 +24,9 @@
   assert_equals(entry.intersectionRect.bottom, expected[3],
     'bottom of rect ' + description);
 }
+
+// Checks that the intrinsic size matches the desired values.
+function checkNaturalSize(entry, width, height) {
+  assert_equals(entry.naturalWidth, width);
+  assert_equals(entry.naturalHeight, height);
+}
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/resources/iframe-with-square-sends-entry.html b/third_party/blink/web_tests/external/wpt/element-timing/resources/iframe-with-square-sends-entry.html
index 3c43a41c..25bd6793 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/resources/iframe-with-square-sends-entry.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/resources/iframe-with-square-sends-entry.html
@@ -12,10 +12,13 @@
       'length' : entryList.getEntries().length,
       'entryType' : entryList.getEntries()[0].entryType,
       'rect' : entryList.getEntries()[0].intersectionRect,
+      'naturalWidth' : entryList.getEntries()[0].naturalWidth,
+      'naturalHeight' : entryList.getEntries()[0].naturalHeight,
+      'id': entryList.getEntries()[0].id,
     }, '*');
   });
   observer.observe({entryTypes: ['element']});
 </script>
-<img src=square100.png elementtiming=my_image/>
+<img src=square100.png id=iframe_img_id elementtiming=my_image/>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/fullscreen/api/element-request-fullscreen-options.html b/third_party/blink/web_tests/external/wpt/fullscreen/api/element-request-fullscreen-options.html
index c6ce1fdc..6a0bfa1 100644
--- a/third_party/blink/web_tests/external/wpt/fullscreen/api/element-request-fullscreen-options.html
+++ b/third_party/blink/web_tests/external/wpt/fullscreen/api/element-request-fullscreen-options.html
@@ -8,11 +8,14 @@
 // no normative requirements on what navigationUI should do, just test for
 // basic support. (One could also check that the three allowed enum valid are
 // supported and no others, but that would overlap with UA-specific tests.)
-test(() => {
+promise_test(() => {
   let invoked = false;
-  document.body.requestFullscreen({
+  return document.body.requestFullscreen({
     get navigationUI() { invoked = true; return "irrelevant-value"; }
+  }).then(() => {
+    assert_unreached("promise should be rejected due to invalid navigationUI value");
+  }, () => {
+    assert_true(invoked, "navigationUI getter invoked");
   });
-  assert_true(invoked, "navigationUI getter invoked");
 });
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
index 32878d3..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
+PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
index 32878d3..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
+PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
index 32878d3..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
+PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
index 32878d3..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
+PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/jobs.py b/third_party/blink/web_tests/external/wpt/tools/ci/jobs.py
index 2b54327..ddf11009 100644
--- a/third_party/blink/web_tests/external/wpt/tools/ci/jobs.py
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/jobs.py
@@ -25,7 +25,7 @@
     "manifest_upload": [".*"],
     "resources_unittest": ["resources/", "tools/"],
     "tools_unittest": ["tools/"],
-    "wptrunner_unittest": ["tools/wptrunner/*"],
+    "wptrunner_unittest": ["tools/"],
     "build_css": ["css/"],
     "update_built": ["2dcontext/",
                      "html/",
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/tox.ini b/third_party/blink/web_tests/external/wpt/tools/wptrunner/tox.ini
index d784c5b..5d34375 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/tox.ini
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/tox.ini
@@ -2,7 +2,8 @@
 xfail_strict=true
 
 [tox]
-envlist = py27-{base,chrome,edge,firefox,ie,opera,safari,sauce,servo}
+envlist = py27-{base,chrome,edge,firefox,ie,opera,safari,sauce,servo},py36-base
+skip_missing_interpreters = true
 
 [testenv]
 deps =
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_conditional.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_conditional.py
index 9da1a0f..10b63191 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_conditional.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_conditional.py
@@ -1,9 +1,13 @@
+import pytest
+import sys
 import unittest
 
 from ..backends import conditional
 from ..node import BinaryExpressionNode, BinaryOperatorNode, VariableNode, NumberNode
 
 
+@pytest.mark.xfail(sys.version[0] == "3",
+                   reason="wptmanifest.parser doesn't support py3")
 class TestConditional(unittest.TestCase):
     def compile(self, input_text):
         return conditional.compile(input_text)
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py
index a2a7dad..c00320f 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_parser.py
@@ -1,3 +1,5 @@
+import pytest
+import sys
 import unittest
 
 from six.moves import cStringIO as StringIO
@@ -8,6 +10,8 @@
 # use test_serializer for the majority of cases
 
 
+@pytest.mark.xfail(sys.version[0] == "3",
+                   reason="wptmanifest.parser doesn't support py3")
 class TestExpression(unittest.TestCase):
     def setUp(self):
         self.parser = parser.Parser()
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py
index 02280e5..f906afa 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py
@@ -6,6 +6,8 @@
 from .. import parser, serializer
 
 
+@pytest.mark.xfail(sys.version[0] == "3",
+                   reason="wptmanifest.parser doesn't support py3")
 class TokenizerTest(unittest.TestCase):
     def setUp(self):
         self.serializer = serializer.ManifestSerializer()
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_static.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_static.py
index f638696..e0e83c83 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_static.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_static.py
@@ -1,3 +1,5 @@
+import pytest
+import sys
 import unittest
 
 from ..backends import static
@@ -6,6 +8,8 @@
 # use test_serializer for the majority of cases
 
 
+@pytest.mark.xfail(sys.version[0] == "3",
+                   reason="wptmanifest.parser doesn't support py3")
 class TestStatic(unittest.TestCase):
     def compile(self, input_text, input_data):
         return static.compile(input_text, input_data)
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py
index e0fb855..b7c62c00 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py
@@ -1,3 +1,5 @@
+import sys
+import pytest
 import unittest
 
 from six.moves import cStringIO as StringIO
@@ -6,6 +8,8 @@
 from ..parser import token_types
 
 
+@pytest.mark.xfail(sys.version[0] == "3",
+                   reason="Tokenizer doesn't support py3")
 class TokenizerTest(unittest.TestCase):
     def setUp(self):
         self.tokenizer = parser.Tokenizer()
diff --git a/third_party/blink/web_tests/fast/spatial-navigation/snav-checkbox-radio.html b/third_party/blink/web_tests/fast/spatial-navigation/snav-checkbox-radio.html
new file mode 100644
index 0000000..d4b7a8f
--- /dev/null
+++ b/third_party/blink/web_tests/fast/spatial-navigation/snav-checkbox-radio.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<script src='../../resources/testharness.js'></script>
+<script src='../../resources/testharnessreport.js'></script>
+<script src='resources/snav-testharness.js'></script>
+
+<div>
+  <input type='checkbox' id='cb' name='checkbox' checked=''>
+  <label for='checkbox'>checkbox</label>
+</div>
+
+<div>
+  <input type='radio' name='r' value='A' id='a' checked=''> A<br>
+  <input type='radio' name='r' value='B' id='b'> B<br>
+  <input type='radio' name='r' value='C' id='c'> C
+</div>
+
+<script>
+  snav.assertSnavEnabledAndTestable(false /* focuslessSpatNav */ );
+  let cb = document.getElementById('cb');
+  let a = document.getElementById('a');
+  let b = document.getElementById('b');
+  let c = document.getElementById('c');
+
+  test(() => {
+    snav.triggerMove('Down'); // Move interest to cb.
+
+    assert_true(cb.checked);
+
+    eventSender.keyDown('Enter');
+
+    assert_false(cb.checked);
+
+    assert_true(a.checked);
+    assert_false(b.checked);
+    assert_false(c.checked);
+
+    snav.triggerMove('Down');
+    snav.triggerMove('Down'); // Move interest to b.
+
+    eventSender.keyDown('Enter');
+
+    assert_false(a.checked);
+    assert_true(b.checked);
+    assert_false(c.checked);
+
+  }, 'Ensure press enter key on checkbox and radio change the check state.');
+
+</script>
diff --git a/third_party/blink/web_tests/fast/spatial-navigation/snav-focusless-checkbox-radio.html b/third_party/blink/web_tests/fast/spatial-navigation/snav-focusless-checkbox-radio.html
new file mode 100644
index 0000000..d3a7d9d7
--- /dev/null
+++ b/third_party/blink/web_tests/fast/spatial-navigation/snav-focusless-checkbox-radio.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<script src='../../resources/testharness.js'></script>
+<script src='../../resources/testharnessreport.js'></script>
+<script src='resources/snav-testharness.js'></script>
+
+<div>
+  <input type='checkbox' id='cb' name='checkbox' checked=''>
+  <label for='checkbox'>checkbox</label>
+</div>
+
+<div>
+  <input type='radio' name='r' value='A' id='a' checked=''> A<br>
+  <input type='radio' name='r' value='B' id='b'> B<br>
+  <input type='radio' name='r' value='C' id='c'> C
+</div>
+
+<script>
+  snav.assertSnavEnabledAndTestable(true /* focuslessSpatNav */ );
+  let cb = document.getElementById('cb');
+  let a = document.getElementById('a');
+  let b = document.getElementById('b');
+  let c = document.getElementById('c');
+
+  test(() => {
+    snav.triggerMove('Down'); // Move interest to cb.
+
+    assert_true(cb.checked);
+
+    eventSender.keyDown('Enter');
+
+    assert_false(cb.checked);
+
+    assert_true(a.checked);
+    assert_false(b.checked);
+    assert_false(c.checked);
+
+    snav.triggerMove('Down');
+    snav.triggerMove('Down'); // Move interest to b.
+
+    eventSender.keyDown('Enter');
+
+    assert_false(a.checked);
+    assert_true(b.checked);
+    assert_false(c.checked);
+
+  }, 'Ensure press enter key on checkbox and radio change the check state.');
+
+</script>
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/blocked-mixed-content.js b/third_party/blink/web_tests/http/tests/devtools/security/blocked-mixed-content.js
index ae898f8..c1a3ad177 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/blocked-mixed-content.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/blocked-mixed-content.js
@@ -7,20 +7,11 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  /** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: false,
-    displayedMixedContent: false,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: true,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
   TestRunner.mainTarget.model(Security.SecurityModel)
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Secure, true, [], insecureContentStatus, null));
+              Protocol.Security.SecurityState.Secure, true, [], null));
 
   var request = new SDK.NetworkRequest(0, 'http://foo.test', 'https://foo.test', 0, 0, null);
   request.setBlockedReason(Protocol.Network.BlockedReason.MixedContent);
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/main-origin-assigned-despite-request-missing.js b/third_party/blink/web_tests/http/tests/devtools/security/main-origin-assigned-despite-request-missing.js
index ed39d1b4a..ddb95fe 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/main-origin-assigned-despite-request-missing.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/main-origin-assigned-despite-request-missing.js
@@ -7,20 +7,11 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  //** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: false,
-    displayedMixedContent: false,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: false,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
   TestRunner.mainTarget.model(Security.SecurityModel)
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Secure, true, [], insecureContentStatus, null));
+              Protocol.Security.SecurityState.Secure, true, [], null));
 
   const page_url = TestRunner.resourceTreeModel.mainFrame.url;
   const page_origin = Common.ParsedURL.extractOrigin(page_url);
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-active-and-passive-reload.js b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-active-and-passive-reload.js
index c196517..21cfa7e 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-active-and-passive-reload.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-active-and-passive-reload.js
@@ -8,16 +8,6 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  /** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: true,
-    displayedMixedContent: true,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: false,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
-
   TestRunner.addResult('\nBefore Refresh --------------');
 
   var mixedExplanations = [
@@ -40,7 +30,7 @@
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, insecureContentStatus, null));
+              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, null));
 
   // At this point, the page has mixed content but no mixed requests have been recorded, so the user should be prompted to refresh.
   var explanations =
@@ -55,7 +45,7 @@
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, insecureContentStatus, null));
+              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, null));
 
   var passive = new SDK.NetworkRequest(0, 'http://foo.test', 'https://foo.test', 0, 0, null);
   passive.mixedContentType = 'optionally-blockable';
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-reload.js b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-reload.js
index 6501110e..7e9af0d9 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-reload.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-reload.js
@@ -8,16 +8,6 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  /** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: false,
-    displayedMixedContent: true,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: false,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
-
   TestRunner.addResult('\nBefore Refresh --------------');
 
   var mixedExplanations = [{
@@ -31,7 +21,7 @@
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, insecureContentStatus, null));
+              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, null));
 
   // At this point, the page has mixed content but no mixed requests have been recorded, so the user should be prompted to refresh.
   var explanations =
@@ -46,7 +36,7 @@
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, insecureContentStatus, null));
+              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, null));
 
   var request = new SDK.NetworkRequest(0, 'http://foo.test', 'https://foo.test', 0, 0, null);
   request.mixedContentType = 'optionally-blockable';
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar.js b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar.js
index eb988775..ed2bbae 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/mixed-content-sidebar.js
@@ -8,16 +8,6 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  /** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: true,
-    displayedMixedContent: true,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: false,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
-
   var mixedExplanations = [
     {
       securityState: Protocol.Security.SecurityState.Neutral,
@@ -38,7 +28,7 @@
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, insecureContentStatus, null));
+              Protocol.Security.SecurityState.Neutral, true, mixedExplanations, null));
 
   var passive = new SDK.NetworkRequest(0, 'http://foo.test', 'https://foo.test', 0, 0, null);
   passive.mixedContentType = 'optionally-blockable';
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/security-blocked-mixed-content.js b/third_party/blink/web_tests/http/tests/devtools/security/security-blocked-mixed-content.js
index 90b9bdb6..cd45af7 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/security-blocked-mixed-content.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/security-blocked-mixed-content.js
@@ -7,20 +7,11 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  /** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: false,
-    displayedMixedContent: false,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: false,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
   TestRunner.mainTarget.model(Security.SecurityModel)
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Secure, true, [], insecureContentStatus, null));
+              Protocol.Security.SecurityState.Secure, true, [], null));
 
   var request = new SDK.NetworkRequest(0, 'http://foo.test', 'https://foo.test', 0, 0, null);
   request.setBlockedReason(Protocol.Network.BlockedReason.MixedContent);
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering.js b/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering.js
index 0a63665..439b148 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering.js
@@ -7,16 +7,6 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  /** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: false,
-    displayedMixedContent: false,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: false,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
-
   // Explanations from https://cbc.badssl.com/ as of 2016-06-13.
   // We explicitly place the explanation with the security state "info"
   // first to make sure it gets reordered.
@@ -52,7 +42,7 @@
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Secure, true, explanations, insecureContentStatus, null));
+              Protocol.Security.SecurityState.Secure, true, explanations, null));
 
   var request = new SDK.NetworkRequest(0, 'http://foo.test', 'https://foo.test', 0, 0, null);
   SecurityTestRunner.dispatchRequestFinished(request);
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/security-summary.js b/third_party/blink/web_tests/http/tests/devtools/security/security-summary.js
index 20c506c..1fa7c88a 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/security-summary.js
+++ b/third_party/blink/web_tests/http/tests/devtools/security/security-summary.js
@@ -7,20 +7,11 @@
   await TestRunner.loadModule('security_test_runner');
   await TestRunner.showPanel('security');
 
-  /** @type {!Protocol.Security.InsecureContentStatus} */
-  var insecureContentStatus = {
-    ranMixedContent: false,
-    displayedMixedContent: false,
-    ranContentWithCertErrors: false,
-    displayedContentWithCertErrors: false,
-    ranInsecureContentStyle: Protocol.Security.SecurityState.Insecure,
-    displayedInsecureContentStyle: Protocol.Security.SecurityState.Neutral
-  };
   TestRunner.mainTarget.model(Security.SecurityModel)
       .dispatchEventToListeners(
           Security.SecurityModel.Events.SecurityStateChanged,
           new Security.PageSecurityState(
-              Protocol.Security.SecurityState.Secure, true, [], insecureContentStatus, 'Test: Summary Override Text'));
+              Protocol.Security.SecurityState.Secure, true, [], 'Test: Summary Override Text'));
 
   TestRunner.dumpDeepInnerHTML(
       Security.SecurityPanel._instance()._mainView.contentElement.getElementsByClassName('security-summary-text')[0]);
diff --git a/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png b/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
new file mode 100644
index 0000000..a632452
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images-expected.txt b/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images-expected.txt
new file mode 100644
index 0000000..fa5ae9f
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images-expected.txt
@@ -0,0 +1 @@
+CONSOLE ERROR: Feature policy violation: unoptimized-lossless-images is not allowed in this document.
diff --git a/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images.html b/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images.html
new file mode 100644
index 0000000..f42833d
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/images/feature-policy-unoptimized-lossless-images.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+body {
+  font: 10px Ahem;
+}
+</style>
+<body><iframe src="resources/frame-with-compression-test-images.html" allow="unoptimized-lossless-images 'none'" width="700" height="500"></iframe></body>
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added-expected.txt b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added-expected.txt
deleted file mode 100644
index 3309d6c..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL InstalledApp related properties is not on interfaces before adding trial token via script. assert_equals: Property getInstalledRelatedApps exists on Navigator expected false but got true
-PASS InstalledApp related properties is on interfaces after adding trial token via script.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added.html
index 87490ba..5465f78 100644
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added.html
+++ b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added.html
@@ -11,9 +11,14 @@
 
 let properties_to_check = {'Navigator': ['getInstalledRelatedApps']};
 
-test(t => {
-  OriginTrialsHelper.check_properties_missing(this, properties_to_check);
-}, "InstalledApp related properties is not on interfaces before adding trial token via script.");
+// Can only run this test if feature is not enabled via a Chrome flag.
+// That is only the case when running this in a virtual test suite (by default,
+// runtime enabled features are on for layout tests).
+if (!self.internals.runtimeFlags.installedAppEnabled) {
+  test(t => {
+    OriginTrialsHelper.check_properties_missing(this, properties_to_check);
+  }, "InstalledApp related properties is not on interfaces before adding trial token via script.");
+}
 
 OriginTrialsHelper.add_token(token);
 
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/wasm-threads-origin-trial-disabled.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/wasm-threads-origin-trial-disabled.html
deleted file mode 100644
index b7d7e278..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/wasm-threads-origin-trial-disabled.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-
-<script src="../../../../resources/testharness.js"></script>
-<script src="../../../../resources/testharnessreport.js"></script>
-
-<script src="../../wasm/resources/wasm-constants.js"></script>
-<script src="../../wasm/resources/wasm-module-builder.js"></script>
-<script src="resources/wasm-threads-origin-trial.js"></script>
-<script>
-test(testWasmThreadsDisabled,
-     "Test that WebAssembly threads are disabled without origin trial token");
-promise_test(testWasmThreadsDisabledOnWorker,
-     "Test that WebAssembly threads are disabled in worker without origin trial token");
-</script>
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/wasm-threads-origin-trial-enabled.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/wasm-threads-origin-trial-enabled.html
deleted file mode 100644
index 4a0892d0..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/wasm-threads-origin-trial-enabled.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<!-- Generate this token with the command:
-generate_token.py http://127.0.0.1:8000 WebAssemblyThreads --expire-timestamp=2000000000
--->
-
-<meta http-equiv="origin-trial" content="AvA8hsoHU+5qiE3oR2wNRZKcI7MOkHl4Vdu3IX8IKv90k4tpNFIaC3KblOaOh9ND63YD/539iVGJ930KCYwotgoAAABaeyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiV2ViQXNzZW1ibHlUaHJlYWRzIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9" />
-
-<script src="../../../../resources/testharness.js"></script>
-<script src="../../../../resources/testharnessreport.js"></script>
-
-<script src="../../wasm/resources/wasm-constants.js"></script>
-<script src="../../wasm/resources/wasm-module-builder.js"></script>
-<script src="resources/wasm-threads-origin-trial.js"></script>
-<script>
-test(testWasmThreadsEnabled,
-     "Test that WebAssembly threads are enabled with origin trial token");
-promise_test(testWasmThreadsEnabledOnWorker,
-     "Test that WebAssembly threads are enabled in worker with origin trial token");
-</script>
diff --git a/third_party/blink/web_tests/platform/linux/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png b/third_party/blink/web_tests/platform/linux/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
new file mode 100644
index 0000000..a632452
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png b/third_party/blink/web_tests/platform/mac/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
new file mode 100644
index 0000000..c119fc3
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png b/third_party/blink/web_tests/platform/win/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
new file mode 100644
index 0000000..0243b0e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/http/tests/images/feature-policy-unoptimized-lossless-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/origin-trials-runtimeflags-disabled/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added-expected.txt b/third_party/blink/web_tests/virtual/origin-trials-runtimeflags-disabled/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added-expected.txt
deleted file mode 100644
index d5604bd..0000000
--- a/third_party/blink/web_tests/virtual/origin-trials-runtimeflags-disabled/http/tests/origin_trials/webexposed/installedapp-origin-trial-interfaces-script-added-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-PASS InstalledApp related properties is not on interfaces before adding trial token via script.
-PASS InstalledApp related properties is on interfaces after adding trial token via script.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
index d779f94..324358f4 100644
--- a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
@@ -34,6 +34,7 @@
 sync-script
 sync-xhr
 top-navigation
+unoptimized-lossless-images
 unoptimized-lossy-images
 unsized-media
 usb
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index f5eeda5..7ee797b 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -5291,8 +5291,11 @@
     setter onresourcetimingbufferfull
 interface PerformanceElementTiming : PerformanceEntry
     attribute @@toStringTag
+    getter id
     getter identifier
     getter intersectionRect
+    getter naturalHeight
+    getter naturalWidth
     getter responseEnd
     method constructor
     method toJSON
diff --git a/third_party/sqlite/BUILD.gn b/third_party/sqlite/BUILD.gn
index d099d89..62fde59 100644
--- a/third_party/sqlite/BUILD.gn
+++ b/third_party/sqlite/BUILD.gn
@@ -20,6 +20,10 @@
 # https://www.sqlite.org/compile.html
 config("chromium_sqlite3_compile_options") {
   defines = [
+    # Skip writing transaction rollback journals on f2fs.
+    # f2fs tends to be used on Android, and may be used on ChromeOS.
+    "SQLITE_ENABLE_BATCH_ATOMIC_WRITE",
+
     "SQLITE_ENABLE_FTS3",
 
     # New unicode61 tokenizer with built-in tables.
diff --git a/third_party/sqlite/amalgamation/sqlite3.c b/third_party/sqlite/amalgamation/sqlite3.c
index 74e2713..a1529a6f 100644
--- a/third_party/sqlite/amalgamation/sqlite3.c
+++ b/third_party/sqlite/amalgamation/sqlite3.c
@@ -158303,7 +158303,6 @@
 ** query logic likewise merges doclists so that newer data knocks out
 ** older data.
 */
-#define CHROMIUM_FTS3_CHANGES 1
 
 /************** Include fts3Int.h in the middle of fts3.c ********************/
 /************** Begin file fts3Int.h *****************************************/
@@ -162951,11 +162950,7 @@
   ** module with sqlite.
   */
   if( SQLITE_OK==rc
-#if CHROMIUM_FTS3_CHANGES && !SQLITE_TEST
-      /* fts3_tokenizer() disabled for security reasons. */
-#else
    && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))
-#endif
    && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1))
    && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1))
    && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1))
@@ -162965,9 +162960,6 @@
     rc = sqlite3_create_module_v2(
         db, "fts3", &fts3Module, (void *)pHash, hashDestroy
     );
-#if CHROMIUM_FTS3_CHANGES && !SQLITE_TEST
-    /* Disable fts4 and tokenizer vtab pending review. */
-#else
     if( rc==SQLITE_OK ){
       rc = sqlite3_create_module_v2(
           db, "fts4", &fts3Module, (void *)pHash, 0
@@ -162976,7 +162968,6 @@
     if( rc==SQLITE_OK ){
       rc = sqlite3Fts3InitTok(db, (void *)pHash);
     }
-#endif
     return rc;
   }
 
@@ -221440,7 +221431,7 @@
 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
 
 /************** End of stmt.c ************************************************/
-#if __LINE__!=221443
+#if __LINE__!=221434
 #undef SQLITE_SOURCE_ID
 #define SQLITE_SOURCE_ID      "2019-02-25 16:06:06 bd49a8271d650fa89e446b42e513b595a717b9212c91dd384aab871fc1d0alt2"
 #endif
diff --git a/third_party/sqlite/patched/ext/fts3/fts3.c b/third_party/sqlite/patched/ext/fts3/fts3.c
index c371d3e8..823e1b6a 100644
--- a/third_party/sqlite/patched/ext/fts3/fts3.c
+++ b/third_party/sqlite/patched/ext/fts3/fts3.c
@@ -287,7 +287,6 @@
 ** query logic likewise merges doclists so that newer data knocks out
 ** older data.
 */
-#define CHROMIUM_FTS3_CHANGES 1
 
 #include "fts3Int.h"
 #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
@@ -4017,11 +4016,7 @@
   ** module with sqlite.
   */
   if( SQLITE_OK==rc
-#if CHROMIUM_FTS3_CHANGES && !SQLITE_TEST
-      /* fts3_tokenizer() disabled for security reasons. */
-#else
    && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))
-#endif
    && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1))
    && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1))
    && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1))
@@ -4031,9 +4026,6 @@
     rc = sqlite3_create_module_v2(
         db, "fts3", &fts3Module, (void *)pHash, hashDestroy
     );
-#if CHROMIUM_FTS3_CHANGES && !SQLITE_TEST
-    /* Disable fts4 and tokenizer vtab pending review. */
-#else
     if( rc==SQLITE_OK ){
       rc = sqlite3_create_module_v2(
           db, "fts4", &fts3Module, (void *)pHash, 0
@@ -4042,7 +4034,6 @@
     if( rc==SQLITE_OK ){
       rc = sqlite3Fts3InitTok(db, (void *)pHash);
     }
-#endif
     return rc;
   }
 
diff --git a/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch b/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch
index 602fc1f..c5c57d4 100644
--- a/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch
+++ b/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Scott Hess <shess@chromium.org>
 Date: Sat, 20 Jul 2013 11:42:21 -0700
-Subject: [PATCH 01/12] Virtual table supporting recovery of corrupted
+Subject: [PATCH 01/11] Virtual table supporting recovery of corrupted
  databases.
 
 "recover" implements a virtual table which uses the SQLite pager layer
@@ -40,11 +40,11 @@
 @@ -77,6 +77,8 @@ LIBOBJ+= vdbe.o parse.o \
  	 vdbetrace.o wal.o walker.o where.o wherecode.o whereexpr.o \
           utf.o vtab.o window.o
-
+ 
 +LIBOBJ += recover.o recover_varint.o
 +
  LIBOBJ += sqlite3session.o
-
+ 
  # All of the source code files.
 @@ -410,6 +412,8 @@ TESTSRC2 = \
    $(TOP)/src/prepare.c \
@@ -60,7 +60,7 @@
  TESTFIXTURE_FLAGS += -DSQLITE_ENABLE_DBPAGE_VTAB
  TESTFIXTURE_FLAGS += -DTCLSH_INIT_PROC=sqlite3TestInit
 +TESTFIXTURE_FLAGS += -DDEFAULT_ENABLE_RECOVER=1
-
+ 
  testfixture$(EXE): $(TESTSRC2) libsqlite3.a $(TESTSRC) $(TOP)/src/tclsqlite.c
  	$(TCCX) $(TCL_FLAGS) $(TESTFIXTURE_FLAGS)                            \
 diff --git a/third_party/sqlite/patched/src/main.c b/third_party/sqlite/patched/src/main.c
@@ -70,7 +70,7 @@
 @@ -3244,6 +3244,14 @@ static int openDatabase(
    }
  #endif
-
+ 
 +#ifdef DEFAULT_ENABLE_RECOVER
 +  /* Initialize recover virtual table for testing. */
 +  extern int chrome_sqlite3_recoverVtableInit(sqlite3 *db);
@@ -3900,6 +3900,6 @@
 +} [list 4 1024 1 text [string length $substr] $substr]
 +
 +finish_test
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch b/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
index 1ee7be3..38b19e17 100644
--- a/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
+++ b/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: "tc@google.com" <tc@google.com>
 Date: Tue, 6 Jan 2009 22:39:41 +0000
-Subject: [PATCH 02/12] Custom shell.c helpers to load Chromium's ICU data.
+Subject: [PATCH 02/11] Custom shell.c helpers to load Chromium's ICU data.
 
 History uses fts3 with an icu-based segmenter.  These changes allow building a
 sqlite3 binary for Linux or Windows which can read those files.
@@ -24,7 +24,7 @@
 @@ -60,6 +60,13 @@ TLIBS =
  OPTS = -DNDEBUG=1
  OPTS += -DHAVE_FDATASYNC=1
-
+ 
 +# Support for loading Chromium ICU data in sqlite3.
 +ifeq ($(shell uname -s),Darwin)
 +SHELL_ICU =
@@ -40,12 +40,12 @@
 --- a/third_party/sqlite/patched/main.mk
 +++ b/third_party/sqlite/patched/main.mk
 @@ -556,7 +556,7 @@ libsqlite3.a:	$(LIBOBJ)
-
+ 
  sqlite3$(EXE):	shell.c libsqlite3.a sqlite3.h
  	$(TCCX) $(READLINE_FLAGS) -o sqlite3$(EXE) $(SHELL_OPT) \
 -		shell.c libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB)
 +		shell.c $(SHELL_ICU) libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB)
-
+ 
  sqldiff$(EXE):	$(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
  	$(TCCX) -o sqldiff$(EXE) -DSQLITE_THREADSAFE=0 \
 diff --git a/third_party/sqlite/patched/src/shell.c.in b/third_party/sqlite/patched/src/shell.c.in
@@ -55,7 +55,7 @@
 @@ -8891,6 +8891,16 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
    }
  #endif
-
+ 
 +  /* Begin evanm patch. */
 +#if !defined(__APPLE__)
 +  extern int sqlite_shell_init_icu();
@@ -140,6 +140,6 @@
 +
 +  return 1;
 +}
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0004-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch b/third_party/sqlite/patches/0003-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
similarity index 90%
rename from third_party/sqlite/patches/0004-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
rename to third_party/sqlite/patches/0003-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
index 4989c3c7..6cd9acc 100644
--- a/third_party/sqlite/patches/0004-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
+++ b/third_party/sqlite/patches/0003-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Victor Costan <pwnall@chromium.org>
 Date: Sun, 10 Feb 2019 13:12:57 -0800
-Subject: [PATCH 04/12] Fix compilation with SQLITE_OMIT_WINDOWFUNC.
+Subject: [PATCH 03/11] Fix compilation with SQLITE_OMIT_WINDOWFUNC.
 
 ---
  third_party/sqlite/patched/src/resolve.c | 2 ++
@@ -14,7 +14,7 @@
 @@ -1556,6 +1556,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
        }
      }
-
+ 
 +#ifndef SQLITE_OMIT_WINDOWFUNC
      if( IN_RENAME_OBJECT ){
        Window *pWin;
@@ -24,9 +24,9 @@
        }
      }
 +#endif
-
+ 
      /* If this is part of a compound SELECT, check that it has the right
      ** number of expressions in the select list. */
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0003-fts3-Disable-fts3_tokenizer-and-fts4.patch b/third_party/sqlite/patches/0003-fts3-Disable-fts3_tokenizer-and-fts4.patch
deleted file mode 100644
index 1a9b16c..0000000
--- a/third_party/sqlite/patches/0003-fts3-Disable-fts3_tokenizer-and-fts4.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Scott Hess <shess@chromium.org>
-Date: Tue, 16 Dec 2014 13:02:27 -0800
-Subject: [PATCH 03/12] [fts3] Disable fts3_tokenizer and fts4.
-
-fts3_tokenizer allows a SQLite user to specify a pointer to call as a
-function, which has obvious sercurity implications.  Disable fts4 until
-someone explicitly decides to own support for it.  Disable fts3tokenize
-virtual table until someone explicitly decides to own support for it.
-
-No original review URL because this was part of the initial Chromium commit.
----
- third_party/sqlite/patched/ext/fts3/fts3.c | 9 +++++++++
- 1 file changed, 9 insertions(+)
-
-diff --git a/third_party/sqlite/patched/ext/fts3/fts3.c b/third_party/sqlite/patched/ext/fts3/fts3.c
-index 823e1b6a81fe..c371d3e8f0b5 100644
---- a/third_party/sqlite/patched/ext/fts3/fts3.c
-+++ b/third_party/sqlite/patched/ext/fts3/fts3.c
-@@ -287,6 +287,7 @@
- ** query logic likewise merges doclists so that newer data knocks out
- ** older data.
- */
-+#define CHROMIUM_FTS3_CHANGES 1
-
- #include "fts3Int.h"
- #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
-@@ -4016,7 +4017,11 @@ int sqlite3Fts3Init(sqlite3 *db){
-   ** module with sqlite.
-   */
-   if( SQLITE_OK==rc
-+#if CHROMIUM_FTS3_CHANGES && !SQLITE_TEST
-+      /* fts3_tokenizer() disabled for security reasons. */
-+#else
-    && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))
-+#endif
-    && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1))
-    && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1))
-    && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1))
-@@ -4026,6 +4031,9 @@ int sqlite3Fts3Init(sqlite3 *db){
-     rc = sqlite3_create_module_v2(
-         db, "fts3", &fts3Module, (void *)pHash, hashDestroy
-     );
-+#if CHROMIUM_FTS3_CHANGES && !SQLITE_TEST
-+    /* Disable fts4 and tokenizer vtab pending review. */
-+#else
-     if( rc==SQLITE_OK ){
-       rc = sqlite3_create_module_v2(
-           db, "fts4", &fts3Module, (void *)pHash, 0
-@@ -4034,6 +4042,7 @@ int sqlite3Fts3Init(sqlite3 *db){
-     if( rc==SQLITE_OK ){
-       rc = sqlite3Fts3InitTok(db, (void *)pHash);
-     }
-+#endif
-     return rc;
-   }
-
---
-2.21.0.392.gf8f6787159e-goog
-
diff --git a/third_party/sqlite/patches/0005-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch b/third_party/sqlite/patches/0004-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
similarity index 92%
rename from third_party/sqlite/patches/0005-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
rename to third_party/sqlite/patches/0004-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
index 39f54908..d2ee401 100644
--- a/third_party/sqlite/patches/0005-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
+++ b/third_party/sqlite/patches/0004-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Victor Costan <pwnall@chromium.org>
 Date: Sun, 10 Feb 2019 15:18:43 -0800
-Subject: [PATCH 05/12] Fix dbfuzz2.c compilation errors on Windows.
+Subject: [PATCH 04/11] Fix dbfuzz2.c compilation errors on Windows.
 
 ---
  third_party/sqlite/patched/test/dbfuzz2.c | 4 ++++
@@ -20,7 +20,7 @@
  #include <sys/resource.h>
 +#endif
  #include "sqlite3.h"
-
+ 
  /*
 @@ -261,6 +263,7 @@ int LLVMFuzzerInitialize(int *pArgc, char ***pArgv){
          szMax = strtol(argv[++i], 0, 0);
@@ -38,6 +38,6 @@
      }
      argv[j++] = argv[i];
    }
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0006-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch b/third_party/sqlite/patches/0005-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch
similarity index 90%
rename from third_party/sqlite/patches/0006-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch
rename to third_party/sqlite/patches/0005-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch
index 95ffdc6..f10b0df 100644
--- a/third_party/sqlite/patches/0006-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch
+++ b/third_party/sqlite/patches/0005-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Tue, 5 Mar 2019 13:49:51 -0800
-Subject: [PATCH 06/12] Fix Heap-buffer-overflow in vdbeRecordCompareInt
+Subject: [PATCH 05/11] Fix Heap-buffer-overflow in vdbeRecordCompareInt
 
 This backports https://www.sqlite.org/src/info/c1ac00706bae45fe
 
@@ -23,6 +23,6 @@
            sqlite3_free(pCellKey);
          }
          assert(
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0007-fix-heap-buffer-overflow-in-cellsizeptr.patch b/third_party/sqlite/patches/0006-fix-heap-buffer-overflow-in-cellsizeptr.patch
similarity index 92%
rename from third_party/sqlite/patches/0007-fix-heap-buffer-overflow-in-cellsizeptr.patch
rename to third_party/sqlite/patches/0006-fix-heap-buffer-overflow-in-cellsizeptr.patch
index 777daa3d..b265b86 100644
--- a/third_party/sqlite/patches/0007-fix-heap-buffer-overflow-in-cellsizeptr.patch
+++ b/third_party/sqlite/patches/0006-fix-heap-buffer-overflow-in-cellsizeptr.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Tue, 5 Mar 2019 14:13:19 -0800
-Subject: [PATCH 07/12] fix heap-buffer-overflow in cellsizeptr
+Subject: [PATCH 06/11] fix heap-buffer-overflow in cellsizeptr
 
 This backports https://www.sqlite.org/src/info/e7aca0714bc475e0
 
@@ -29,8 +29,8 @@
 +        memset(pNew+pageSize, 0, 8);
 +      }
      }
-
+ 
      if( rc==SQLITE_OK ){
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0008-fix-integer-overflow-in-checkList.patch b/third_party/sqlite/patches/0007-fix-integer-overflow-in-checkList.patch
similarity index 94%
rename from third_party/sqlite/patches/0008-fix-integer-overflow-in-checkList.patch
rename to third_party/sqlite/patches/0007-fix-integer-overflow-in-checkList.patch
index ce027b73..2c67373 100644
--- a/third_party/sqlite/patches/0008-fix-integer-overflow-in-checkList.patch
+++ b/third_party/sqlite/patches/0007-fix-integer-overflow-in-checkList.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Tue, 5 Mar 2019 14:17:05 -0800
-Subject: [PATCH 08/12] fix integer overflow in checkList
+Subject: [PATCH 07/11] fix integer overflow in checkList
 
 This backports https://www.sqlite.org/src/info/05b87e0755638d31
 
@@ -28,7 +28,7 @@
    while( iPage!=0 && pCheck->mxErr ){
      DbPage *pOvflPage;
 @@ -9797,7 +9797,7 @@ static int checkTreePage(
-
+ 
      /* Check the content overflow list */
      if( info.nPayload>info.nLocal ){
 -      int nPage;       /* Number of pages on the overflow chain */
@@ -36,6 +36,6 @@
        Pgno pgnoOvfl;   /* First page of the overflow chain */
        assert( pc + info.nSize - 4 <= usableSize );
        nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4);
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0009-Fix-Heap-use-after-free-in-releasePageNotNull.patch b/third_party/sqlite/patches/0008-Fix-Heap-use-after-free-in-releasePageNotNull.patch
similarity index 92%
rename from third_party/sqlite/patches/0009-Fix-Heap-use-after-free-in-releasePageNotNull.patch
rename to third_party/sqlite/patches/0008-Fix-Heap-use-after-free-in-releasePageNotNull.patch
index d2f603319..cce0e58 100644
--- a/third_party/sqlite/patches/0009-Fix-Heap-use-after-free-in-releasePageNotNull.patch
+++ b/third_party/sqlite/patches/0008-Fix-Heap-use-after-free-in-releasePageNotNull.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Tue, 12 Mar 2019 17:30:33 -0700
-Subject: [PATCH 09/12] Fix Heap-use-after-free in releasePageNotNull
+Subject: [PATCH 08/11] Fix Heap-use-after-free in releasePageNotNull
 
 This backports https://www.sqlite.org/src/info/b0d5cf40bba34e45
 
@@ -28,6 +28,6 @@
      pPg->flags |= (pPgOld->flags&PGHDR_NEED_SYNC);
      if( pPager->tempFile ){
        /* Do not discard pages from an in-memory database since we might
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0010-Fix-dangling-pointer-dereference.patch b/third_party/sqlite/patches/0009-Fix-dangling-pointer-dereference.patch
similarity index 95%
rename from third_party/sqlite/patches/0010-Fix-dangling-pointer-dereference.patch
rename to third_party/sqlite/patches/0009-Fix-dangling-pointer-dereference.patch
index 728c884..d9b34b88 100644
--- a/third_party/sqlite/patches/0010-Fix-dangling-pointer-dereference.patch
+++ b/third_party/sqlite/patches/0009-Fix-dangling-pointer-dereference.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Thu, 21 Mar 2019 13:19:11 -0700
-Subject: [PATCH 10/12] Fix dangling pointer dereference
+Subject: [PATCH 09/11] Fix dangling pointer dereference
 
 This backports https://www.sqlite.org/src/info/b9e2393cf201e3fc
 
@@ -32,7 +32,7 @@
 @@ -79,7 +79,31 @@ do_execsql_test 3.2 {
    SELECT sql FROM sqlite_master WHERE name = 'v1'
  } {{CREATE VIEW v1 AS SELECT * FROM t1 WHERE a=1 OR (bbb IN ())}}
-
+ 
 +#-------------------------------------------------------------------------
 +reset_db
 +do_execsql_test 5.0 {
@@ -44,7 +44,7 @@
 +do_execsql_test 5.1 {
 +  ALTER TABLE t1 RENAME c1 TO c3;
 +}
-
+ 
 +#-------------------------------------------------------------------------
 +reset_db
 +do_execsql_test 6.0 {
@@ -58,9 +58,9 @@
 +do_execsql_test 6.1 {
 +  ALTER TABLE Table0 RENAME Col0 TO Col0;
 +}
-
+ 
  finish_test
-
---
-2.21.0.392.gf8f6787159e-goog
+ 
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0011-Fix-faulty-assert-statement.patch b/third_party/sqlite/patches/0010-Fix-faulty-assert-statement.patch
similarity index 92%
rename from third_party/sqlite/patches/0011-Fix-faulty-assert-statement.patch
rename to third_party/sqlite/patches/0010-Fix-faulty-assert-statement.patch
index 3f53c85..a0c2235 100644
--- a/third_party/sqlite/patches/0011-Fix-faulty-assert-statement.patch
+++ b/third_party/sqlite/patches/0010-Fix-faulty-assert-statement.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 27 Mar 2019 12:05:31 -0700
-Subject: [PATCH 11/12] Fix faulty assert statement
+Subject: [PATCH 10/11] Fix faulty assert statement
 
 This backports https://www.sqlite.org/src/info/bcbe7d96df3c9515
 
@@ -24,6 +24,6 @@
        || rc!=SQLITE_OK
      );
      copyNodeContent(apNew[0], pParent, &rc);
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/third_party/sqlite/patches/0012-Add-dbfuzz2-progress-handler-patch.patch b/third_party/sqlite/patches/0011-Add-dbfuzz2-progress-handler-patch.patch
similarity index 96%
rename from third_party/sqlite/patches/0012-Add-dbfuzz2-progress-handler-patch.patch
rename to third_party/sqlite/patches/0011-Add-dbfuzz2-progress-handler-patch.patch
index f141ac5..32fb274 100644
--- a/third_party/sqlite/patches/0012-Add-dbfuzz2-progress-handler-patch.patch
+++ b/third_party/sqlite/patches/0011-Add-dbfuzz2-progress-handler-patch.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 27 Mar 2019 12:10:17 -0700
-Subject: [PATCH 12/12] Add dbfuzz2 progress handler patch
+Subject: [PATCH 11/11] Add dbfuzz2 progress handler patch
 
 This backports https://www.sqlite.org/src/info/b99f8512c06b9d47
 
@@ -17,7 +17,7 @@
 @@ -74,6 +74,10 @@ static int bVdbeDebug = 0;
  /* Maximum size of the in-memory database file */
  static sqlite3_int64 szMax = 104857600;
-
+ 
 +/* Progress handler callback data */
 +static int nCb = 0;                  /* Number of callbacks seen so far */
 +static int mxCb = 250000;            /* Maximum allowed callbacks */
@@ -28,7 +28,7 @@
 @@ -157,6 +161,21 @@ int sqlite3MemTraceDeactivate(void){
  }
  /***** End copy/paste from ext/misc/memtrace.c ***************************/
-
+ 
 +/*
 +** Progress handler callback
 +**
@@ -79,6 +79,6 @@
        if( strcmp(z,"memtrace")==0 ){
          sqlite3MemTraceActivate(stdout);
          continue;
---
-2.21.0.392.gf8f6787159e-goog
+-- 
+2.20.1
 
diff --git a/tools/chrome_proxy/webdriver/lite_page.py b/tools/chrome_proxy/webdriver/lite_page.py
index c247792b..5afe2da 100644
--- a/tools/chrome_proxy/webdriver/lite_page.py
+++ b/tools/chrome_proxy/webdriver/lite_page.py
@@ -30,7 +30,9 @@
       test_driver.AddChromeArg('--enable-spdy-proxy-auth')
       test_driver.AddChromeArg('--enable-features=NetworkQualityEstimator'
                                '<NetworkQualityEstimator,'
-                               'Previews,DataReductionProxyDecidesTransform')
+                               'Previews,DataReductionProxyDecidesTransform,'
+                               'NetworkService,'
+                               'DataReductionProxyEnabledWithNetworkService')
       test_driver.AddChromeArg(
           '--force-fieldtrial-params=NetworkQualityEstimator.Enabled:'
           'force_effective_connection_type/2G,'
@@ -43,11 +45,14 @@
       test_driver.LoadURL('http://check.googlezip.net/test.html')
 
       lite_page_responses = 0
+      checked_chrome_proxy_header = False
       for response in test_driver.GetHTTPResponses():
-        # Verify client sends ignore directive on every request for session.
-        self.assertIn('exp=ignore_preview_blacklist',
-          response.request_headers['chrome-proxy'])
-        self.assertEqual('2G', response.request_headers['chrome-proxy-ect'])
+        if response.request_headers:
+          # Verify client sends ignore directive on main frame request.
+          self.assertIn('exp=ignore_preview_blacklist',
+            response.request_headers['chrome-proxy'])
+          self.assertEqual('2G', response.request_headers['chrome-proxy-ect'])
+          checked_chrome_proxy_header = True
         if response.url.endswith('html'):
           self.assertTrue(self.checkLitePageResponse(response))
           lite_page_responses = lite_page_responses + 1
@@ -59,6 +64,7 @@
           # No subresources should accept transforms.
           self.assertNotIn('chrome-proxy-accept-transform',
             response.request_headers)
+      self.assertTrue(checked_chrome_proxy_header)
 
       # Verify that a Lite Page response for the main frame was seen.
       self.assertEqual(1, lite_page_responses)
@@ -286,7 +292,9 @@
       test_driver.AddChromeArg('--enable-spdy-proxy-auth')
       test_driver.AddChromeArg('--enable-features=NetworkQualityEstimator'
                                '<NetworkQualityEstimator,'
-                               'Previews,DataReductionProxyDecidesTransform')
+                               'Previews,DataReductionProxyDecidesTransform,'
+                               'NetworkService,'
+                               'DataReductionProxyEnabledWithNetworkService')
       test_driver.AddChromeArg('--force-fieldtrial-params='
                                'NetworkQualityEstimator.Enabled:'
                                'force_effective_connection_type/Slow2G')
@@ -405,7 +413,9 @@
     with TestDriver() as test_driver:
       test_driver.AddChromeArg('--enable-spdy-proxy-auth')
       test_driver.AddChromeArg('--enable-features=NetworkQualityEstimator'
-                               '<NetworkQualityEstimator')
+                               '<NetworkQualityEstimator,'
+                               'NetworkService,'
+                               'DataReductionProxyEnabledWithNetworkService')
       test_driver.AddChromeArg('--force-fieldtrial-params='
                                'NetworkQualityEstimator.Enabled:'
                                'force_effective_connection_type/2G')
@@ -417,6 +427,8 @@
       test_driver.LoadURL('http://check.googlezip.net/test.html')
 
       for response in test_driver.GetHTTPResponses():
+        if not response.request_headers:
+          continue
         self.assertEqual('2G', response.request_headers['chrome-proxy-ect'])
         if response.url.endswith('html'):
           if ParseFlags().android:
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index 10b061f..4e9b284 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -35,7 +35,7 @@
 # Do NOT CHANGE this if you don't know what you're doing -- see
 # https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang.md
 # Reverting problematic clang rolls is safe, though.
-CLANG_REVISION = '356356'
+CLANG_REVISION = '357316'
 
 use_head_revision = bool(os.environ.get('LLVM_FORCE_HEAD_REVISION', '0')
                          in ('1', 'YES'))
@@ -43,7 +43,7 @@
   CLANG_REVISION = 'HEAD'
 
 # This is incremented when pushing a new build of Clang at the same revision.
-CLANG_SUB_REVISION=3
+CLANG_SUB_REVISION=1
 
 PACKAGE_VERSION = "%s-%s" % (CLANG_REVISION, CLANG_SUB_REVISION)
 
diff --git a/tools/metrics/common/pretty_print_xml.py b/tools/metrics/common/pretty_print_xml.py
index 712bbd9..ae97ff7 100644
--- a/tools/metrics/common/pretty_print_xml.py
+++ b/tools/metrics/common/pretty_print_xml.py
@@ -78,6 +78,11 @@
     self.tags_that_allow_single_line = tags_that_allow_single_line
     self.tags_alphabetization_rules = tags_alphabetization_rules
 
+    self.wrapper = textwrap.TextWrapper()
+    self.wrapper.break_on_hyphens = False
+    self.wrapper.break_long_words = False
+    self.wrapper.width = WRAP_COLUMN
+
   def PrettyPrintXml(self, tree):
     tree = self._TransformByAlphabetizing(tree)
     tree = self.PrettyPrintNode(tree)
@@ -158,6 +163,127 @@
       self._TransformByAlphabetizing(c)
     return node
 
+  def _PrettyPrintText(self, node, indent):
+    # Wrap each paragraph in the text to fit in the 80 column limit.
+    self.wrapper.initial_indent = ' ' * indent
+    self.wrapper.subsequent_indent = ' ' * indent
+    text = XmlEscape(node.data)
+    paragraphs = SplitParagraphs(text)
+    # Wrap each paragraph and separate with two newlines.
+    return '\n\n'.join(self.wrapper.fill(p) for p in paragraphs)
+
+  def _PrettyPrintElement(self, node, indent):
+    # Check if tag name is valid.
+    if node.tagName not in self.attribute_order:
+      logging.error('Unrecognized tag "%s"', node.tagName)
+      raise Error('Unrecognized tag "%s"', node.tagName)
+
+    # Newlines.
+    newlines_after_open, newlines_before_close, newlines_after_close = (
+        self.tags_that_have_extra_newline.get(node.tagName, (1, 1, 0)))
+    # Open the tag.
+    s = ' ' * indent + '<' + node.tagName
+
+    # Calculate how much space to allow for the '>' or '/>'.
+    closing_chars = 1
+    if not node.childNodes:
+      closing_chars = 2
+
+    attributes = node.attributes.keys()
+    required_attributes = [attribute for attribute in self.required_attributes
+                           if attribute in self.attribute_order[node.tagName]]
+    missing_attributes = [attribute for attribute in required_attributes
+                          if attribute not in attributes]
+
+    for attribute in missing_attributes:
+      logging.error(
+          'Missing attribute "%s" in tag "%s"', attribute, node.tagName)
+    if missing_attributes:
+      missing_attributes_str = (
+          ', '.join('"%s"' % attribute for attribute in missing_attributes))
+      present_attributes = [
+          ' {0}="{1}"'.format(name, value)
+          for name, value in node.attributes.items()]
+      node_str = '<{0}{1}>'.format(node.tagName, ''.join(present_attributes))
+      raise Error(
+          'Missing attributes {0} in tag "{1}"'.format(
+              missing_attributes_str, node_str))
+
+    # Pretty-print the attributes.
+    if attributes:
+      # Reorder the attributes.
+      unrecognized_attributes = (
+          [a for a in attributes
+           if a not in self.attribute_order[node.tagName]])
+      attributes = [a for a in self.attribute_order[node.tagName]
+                    if a in attributes]
+
+      for a in unrecognized_attributes:
+        logging.error(
+            'Unrecognized attribute "%s" in tag "%s"', a, node.tagName)
+      if unrecognized_attributes:
+        raise Error(
+            'Unrecognized attributes {0} in tag "{1}"'.format(
+                ', '.join('"{0}"'.format(a) for a in unrecognized_attributes),
+                node.tagName))
+
+      for a in attributes:
+        value = XmlEscape(node.attributes[a].value)
+        # Replace sequences of whitespace with single spaces.
+        words = value.split()
+        a_str = ' %s="%s"' % (a, ' '.join(words))
+        # Start a new line if the attribute will make this line too long.
+        if LastLineLength(s) + len(a_str) + closing_chars > WRAP_COLUMN:
+          s += '\n' + ' ' * (indent + 3)
+        # Output everything up to the first quote.
+        s += ' %s="' % (a)
+        value_indent_level = LastLineLength(s)
+        # Output one word at a time, splitting to the next line where
+        # necessary.
+        column = value_indent_level
+        for i, word in enumerate(words):
+          # This is slightly too conservative since not every word will be
+          # followed by the closing characters...
+          if i > 0 and (column + len(word) + 1 + closing_chars > WRAP_COLUMN):
+            s = s.rstrip()  # remove any trailing whitespace
+            s += '\n' + ' ' * value_indent_level
+            column = value_indent_level
+          s += word + ' '
+          column += len(word) + 1
+        s = s.rstrip()  # remove any trailing whitespace
+        s += '"'
+      s = s.rstrip()  # remove any trailing whitespace
+
+    # Pretty-print the child nodes.
+    if node.childNodes:
+      s += '>'
+      # Calculate the new indent level for child nodes.
+      new_indent = indent
+      if node.tagName not in self.tags_that_dont_indent:
+        new_indent += 2
+      child_nodes = node.childNodes
+
+      # Recursively pretty-print the child nodes.
+      child_nodes = [self.PrettyPrintNode(n, indent=new_indent)
+                     for n in child_nodes]
+      child_nodes = [c for c in child_nodes if c.strip()]
+
+      # Determine whether we can fit the entire node on a single line.
+      close_tag = '</%s>' % node.tagName
+      space_left = WRAP_COLUMN - LastLineLength(s) - len(close_tag)
+      if (node.tagName in self.tags_that_allow_single_line and
+          len(child_nodes) == 1 and
+          len(child_nodes[0].strip()) <= space_left):
+        s += child_nodes[0].strip()
+      else:
+        s += '\n' * newlines_after_open + '\n'.join(child_nodes)
+        s += '\n' * newlines_before_close + ' ' * indent
+      s += close_tag
+    else:
+      s += '/>'
+    s += '\n' * newlines_after_close
+    return s
+
   def PrettyPrintNode(self, node, indent=0):
     """Pretty-prints the given XML node at the given indent level.
 
@@ -177,130 +303,11 @@
 
     # Handle text nodes.
     if node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
-      # Wrap each paragraph in the text to fit in the 80 column limit.
-      wrapper = textwrap.TextWrapper()
-      wrapper.initial_indent = ' ' * indent
-      wrapper.subsequent_indent = ' ' * indent
-      wrapper.break_on_hyphens = False
-      wrapper.break_long_words = False
-      wrapper.width = WRAP_COLUMN
-      text = XmlEscape(node.data)
-      paragraphs = SplitParagraphs(text)
-      # Wrap each paragraph and separate with two newlines.
-      return '\n\n'.join(wrapper.fill(p) for p in paragraphs)
+      return self._PrettyPrintText(node, indent)
 
     # Handle element nodes.
     if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
-      # Check if tag name is valid.
-      if node.tagName not in self.attribute_order:
-        logging.error('Unrecognized tag "%s"', node.tagName)
-        raise Error('Unrecognized tag "%s"', node.tagName)
-
-      # Newlines.
-      newlines_after_open, newlines_before_close, newlines_after_close = (
-          self.tags_that_have_extra_newline.get(node.tagName, (1, 1, 0)))
-      # Open the tag.
-      s = ' ' * indent + '<' + node.tagName
-
-      # Calculate how much space to allow for the '>' or '/>'.
-      closing_chars = 1
-      if not node.childNodes:
-        closing_chars = 2
-
-      attributes = node.attributes.keys()
-      required_attributes = [attribute for attribute in self.required_attributes
-                             if attribute in self.attribute_order[node.tagName]]
-      missing_attributes = [attribute for attribute in required_attributes
-                            if attribute not in attributes]
-
-      for attribute in missing_attributes:
-        logging.error(
-            'Missing attribute "%s" in tag "%s"', attribute, node.tagName)
-      if missing_attributes:
-        missing_attributes_str = (
-            ', '.join('"%s"' % attribute for attribute in missing_attributes))
-        present_attributes = [
-            ' {0}="{1}"'.format(name, value)
-            for name, value in node.attributes.items()]
-        node_str = '<{0}{1}>'.format(node.tagName, ''.join(present_attributes))
-        raise Error(
-            'Missing attributes {0} in tag "{1}"'.format(
-                missing_attributes_str, node_str))
-
-      # Pretty-print the attributes.
-      if attributes:
-        # Reorder the attributes.
-        unrecognized_attributes = (
-            [a for a in attributes
-             if a not in self.attribute_order[node.tagName]])
-        attributes = [a for a in self.attribute_order[node.tagName]
-                      if a in attributes]
-
-        for a in unrecognized_attributes:
-          logging.error(
-              'Unrecognized attribute "%s" in tag "%s"', a, node.tagName)
-        if unrecognized_attributes:
-          raise Error(
-              'Unrecognized attributes {0} in tag "{1}"'.format(
-                  ', '.join('"{0}"'.format(a) for a in unrecognized_attributes),
-                  node.tagName))
-
-        for a in attributes:
-          value = XmlEscape(node.attributes[a].value)
-          # Replace sequences of whitespace with single spaces.
-          words = value.split()
-          a_str = ' %s="%s"' % (a, ' '.join(words))
-          # Start a new line if the attribute will make this line too long.
-          if LastLineLength(s) + len(a_str) + closing_chars > WRAP_COLUMN:
-            s += '\n' + ' ' * (indent + 3)
-          # Output everything up to the first quote.
-          s += ' %s="' % (a)
-          value_indent_level = LastLineLength(s)
-          # Output one word at a time, splitting to the next line where
-          # necessary.
-          column = value_indent_level
-          for i, word in enumerate(words):
-            # This is slightly too conservative since not every word will be
-            # followed by the closing characters...
-            if i > 0 and (column + len(word) + 1 + closing_chars > WRAP_COLUMN):
-              s = s.rstrip()  # remove any trailing whitespace
-              s += '\n' + ' ' * value_indent_level
-              column = value_indent_level
-            s += word + ' '
-            column += len(word) + 1
-          s = s.rstrip()  # remove any trailing whitespace
-          s += '"'
-        s = s.rstrip()  # remove any trailing whitespace
-
-      # Pretty-print the child nodes.
-      if node.childNodes:
-        s += '>'
-        # Calculate the new indent level for child nodes.
-        new_indent = indent
-        if node.tagName not in self.tags_that_dont_indent:
-          new_indent += 2
-        child_nodes = node.childNodes
-
-        # Recursively pretty-print the child nodes.
-        child_nodes = [self.PrettyPrintNode(n, indent=new_indent)
-                       for n in child_nodes]
-        child_nodes = [c for c in child_nodes if c.strip()]
-
-        # Determine whether we can fit the entire node on a single line.
-        close_tag = '</%s>' % node.tagName
-        space_left = WRAP_COLUMN - LastLineLength(s) - len(close_tag)
-        if (node.tagName in self.tags_that_allow_single_line and
-            len(child_nodes) == 1 and
-            len(child_nodes[0].strip()) <= space_left):
-          s += child_nodes[0].strip()
-        else:
-          s += '\n' * newlines_after_open + '\n'.join(child_nodes)
-          s += '\n' * newlines_before_close + ' ' * indent
-        s += close_tag
-      else:
-        s += '/>'
-      s += '\n' * newlines_after_close
-      return s
+      return self._PrettyPrintElement(node, indent)
 
     # Handle comment nodes.
     if node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 2e9e178..bb8a743 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -8510,10 +8510,7 @@
 <enum name="CompositorFrameSinkSubmitResult">
   <int value="0" label="Accepted"/>
   <int value="1" label="CopyOutputResults not allowed"/>
-  <int value="2" label="Surface Invariants Violation (deprecated)"/>
-  <int value="3" label="Size mismatch"/>
-  <int value="4" label="SurfaceId sequence numbers decreased"/>
-  <int value="5" label="Surface owned by another client"/>
+  <int value="2" label="Surface Invariants Violation"/>
 </enum>
 
 <enum name="CompositorScrollResult">
@@ -22732,6 +22729,7 @@
   <int value="43" label="Hid"/>
   <int value="44" label="IdleDetection"/>
   <int value="45" label="UnoptimizedLossyImages"/>
+  <int value="46" label="UnoptimizedLosslessImages"/>
 </enum>
 
 <enum name="FeedbackSource">
@@ -32718,6 +32716,7 @@
   <int value="-966290456" label="WebAuthenticationCtap2:enabled"/>
   <int value="-965842218" label="MultiDeviceApi:disabled"/>
   <int value="-964676765" label="enable-accelerated-mjpeg-decode"/>
+  <int value="-957200826" label="enable-spdy-proxy-auth"/>
   <int value="-956696029" label="scheduler-configuration"/>
   <int value="-951394314" label="top-chrome-md"/>
   <int value="-950793721" label="TranslateUI2016Q2:disabled"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 622fc06d..9b1b5f8 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -119031,7 +119031,7 @@
 
 <histogram
     name="Sync.Crypto.CustomPassphraseKeyDerivationMethodOnNewPassphrase"
-    enum="SyncCustomPassphraseKeyDerivationMethodState" expires_after="M75">
+    enum="SyncCustomPassphraseKeyDerivationMethodState" expires_after="M77">
   <owner>vitaliii@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <summary>
@@ -119044,7 +119044,7 @@
 
 <histogram
     name="Sync.Crypto.CustomPassphraseKeyDerivationMethodOnSuccessfulDecryption"
-    enum="SyncCustomPassphraseKeyDerivationMethodState" expires_after="M75">
+    enum="SyncCustomPassphraseKeyDerivationMethodState" expires_after="M77">
   <owner>vitaliii@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <summary>
@@ -119057,7 +119057,7 @@
 </histogram>
 
 <histogram name="Sync.Crypto.CustomPassphraseKeyDerivationMethodStateOnStartup"
-    enum="SyncCustomPassphraseKeyDerivationMethodState" expires_after="M75">
+    enum="SyncCustomPassphraseKeyDerivationMethodState" expires_after="M77">
   <owner>vitaliii@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <summary>
diff --git a/tools/perf/page_sets/data/system_health_mobile.json b/tools/perf/page_sets/data/system_health_mobile.json
index 496be5c..3509983 100644
--- a/tools/perf/page_sets/data/system_health_mobile.json
+++ b/tools/perf/page_sets/data/system_health_mobile.json
@@ -27,15 +27,15 @@
         "browse:media:flickr_infinite_scroll": {
             "DEFAULT": "system_health_mobile_062.wprgo"
         },
+        "browse:media:googleplaystore:2019": {
+            "DEFAULT": "system_health_mobile_4413a28712.wprgo"
+        },
         "browse:media:imgur": {
             "DEFAULT": "system_health_mobile_035.wprgo"
         },
         "browse:media:youtube": {
             "DEFAULT": "system_health_mobile_037.wprgo"
         },
-        "browse:media:googleplaystore:2019": {
-            "DEFAULT": "system_health_mobile_4413a28712.wprgo"
-        },
         "browse:news:cnn": {
             "DEFAULT": "system_health_mobile_014.wprgo"
         },
@@ -72,6 +72,9 @@
         "browse:search:amp:2018": {
             "DEFAULT": "system_health_mobile_0483ae239d.wprgo"
         },
+        "browse:search:amp:sxg:2019": {
+            "DEFAULT": "system_health_mobile_d7a7409e72.wprgo"
+        },
         "browse:shopping:amazon": {
             "DEFAULT": "system_health_mobile_053.wprgo"
         },
@@ -294,4 +297,4 @@
     },
     "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
     "platform_specific": true
-}
+}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/system_health_mobile_d7a7409e72.wprgo.sha1 b/tools/perf/page_sets/data/system_health_mobile_d7a7409e72.wprgo.sha1
new file mode 100644
index 0000000..c09f3aa6
--- /dev/null
+++ b/tools/perf/page_sets/data/system_health_mobile_d7a7409e72.wprgo.sha1
@@ -0,0 +1 @@
+d7a7409e72e323d0b2c3b01e60b412c970702e6f
\ No newline at end of file
diff --git a/tools/perf/page_sets/system_health/browsing_stories.py b/tools/perf/page_sets/system_health/browsing_stories.py
index c91e49a..21eaf48 100644
--- a/tools/perf/page_sets/system_health/browsing_stories.py
+++ b/tools/perf/page_sets/system_health/browsing_stories.py
@@ -397,6 +397,32 @@
     action_runner.ClickElement(element_function=element_function)
     action_runner.Wait(2)
 
+class GoogleAmpSXGStory2019(_ArticleBrowsingStory):
+  """ Story for Google's Signed Exchange (SXG) Accelerated Mobile Pages (AMP).
+  """
+  NAME = 'browse:search:amp:sxg:2019'
+  # Specific URL for site that supports SXG, travel.yahoo.co.jp
+  # pylint: disable=line-too-long
+  URL='https://www.google.com/search?q=%E5%85%AD%E6%9C%AC%E6%9C%A8%E3%80%80%E3%83%A4%E3%83%95%E3%83%BC%E3%80%80%E3%83%9B%E3%83%86%E3%83%AB&esrch=SignedExchange::Demo'
+  # Need to find the SXG AMPlink in the results
+  ITEM_SELECTOR = 'a > div > span[aria-label="AMP logo"]'
+  SUPPORTED_PLATFORMS = platforms.MOBILE_ONLY
+  TAGS = [story_tags.YEAR_2019]
+
+  def _DidLoadDocument(self, action_runner):
+    # Waiting manually for the search results to load here and below.
+    # Telemetry's action_runner.WaitForNavigate has some difficulty with amp
+    # pages as it waits for a frameId without a parent id.
+    action_runner.Wait(2)
+    # Click on the yahoo amp link and then just wait for it to load.
+    element_function = js_template.Render(
+        'document.querySelectorAll({{ selector }})[{{ index }}]',
+        selector=self.ITEM_SELECTOR, index=0)
+    action_runner.WaitForElement(element_function=element_function)
+    action_runner.ClickElement(element_function=element_function)
+    # Waiting for the document to fully render
+    action_runner.Wait(2)
+
 
 class GoogleDesktopStory2018(_ArticleBrowsingStory):
   """
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 5eaf229..128568358 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -244,10 +244,8 @@
     "java/src/org/chromium/ui/base/ActivityWindowAndroid.java",
     "java/src/org/chromium/ui/base/AndroidPermissionDelegate.java",
     "java/src/org/chromium/ui/base/Clipboard.java",
-    "java/src/org/chromium/ui/base/CursorObserver.java",
     "java/src/org/chromium/ui/base/DeviceFormFactor.java",
     "java/src/org/chromium/ui/base/EventForwarder.java",
-    "java/src/org/chromium/ui/base/TouchlessEventHandler.java",
     "java/src/org/chromium/ui/base/IdleDetector.java",
     "java/src/org/chromium/ui/base/LocalizationUtils.java",
     "java/src/org/chromium/ui/base/PermissionCallback.java",
@@ -312,6 +310,8 @@
     "java/src/org/chromium/ui/resources/system/SystemResourceLoader.java",
     "java/src/org/chromium/ui/text/NoUnderlineClickableSpan.java",
     "java/src/org/chromium/ui/text/SpanApplier.java",
+    "java/src/org/chromium/ui/touchless/CursorObserver.java",
+    "java/src/org/chromium/ui/touchless/TouchlessEventHandler.java",
     "java/src/org/chromium/ui/widget/AnchoredPopupWindow.java",
     "java/src/org/chromium/ui/widget/ButtonCompat.java",
     "java/src/org/chromium/ui/widget/CheckableImageView.java",
diff --git a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
index 1599613..d383c50 100644
--- a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
+++ b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
@@ -22,6 +22,7 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.compat.ApiHelperForN;
 import org.chromium.blink_public.web.WebCursorInfoType;
+import org.chromium.ui.touchless.TouchlessEventHandler;
 
 /**
  * Class to acquire, position, and remove anchor views from the implementing View.
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
index 74cb1b6..f558a4f8 100644
--- a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -44,6 +44,8 @@
 import org.chromium.ui.VSyncMonitor;
 import org.chromium.ui.display.DisplayAndroid;
 import org.chromium.ui.display.DisplayAndroid.DisplayAndroidObserver;
+import org.chromium.ui.touchless.CursorObserver;
+import org.chromium.ui.touchless.TouchlessEventHandler;
 import org.chromium.ui.widget.Toast;
 
 import java.lang.ref.WeakReference;
diff --git a/ui/android/java/src/org/chromium/ui/modaldialog/ModalDialogManager.java b/ui/android/java/src/org/chromium/ui/modaldialog/ModalDialogManager.java
index 2e4d8af..12a51d2 100644
--- a/ui/android/java/src/org/chromium/ui/modaldialog/ModalDialogManager.java
+++ b/ui/android/java/src/org/chromium/ui/modaldialog/ModalDialogManager.java
@@ -10,6 +10,7 @@
 import android.util.SparseArray;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ObserverList;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -25,6 +26,24 @@
  */
 public class ModalDialogManager {
     /**
+     * An observer of the ModalDialogManager intended to broadcast notifications about any dialog
+     * being shown. Observers will know if something is overlaying the screen.
+     */
+    public interface ModalDialogManagerObserver {
+        /**
+         * A notification that the manager started showing a modal dialog.
+         * @param model The model that describes the dialog that was shown.
+         */
+        void onDialogShown(PropertyModel model);
+
+        /**
+         * A notification that the manager hid a modal dialog.
+         * @param model The model that describes the dialog that was hidden.
+         */
+        void onDialogHidden(PropertyModel model);
+    }
+
+    /**
      * Present a {@link PropertyModel} in a container.
      */
     public static abstract class Presenter {
@@ -135,6 +154,9 @@
      */
     private boolean mDismissingCurrentDialog;
 
+    /** Observers of this manager. */
+    private final ObserverList<ModalDialogManagerObserver> mObserverList = new ObserverList<>();
+
     /**
      * Constructor for initializing default {@link Presenter}.
      * @param defaultPresenter The default presenter to be used when no presenter specified.
@@ -149,6 +171,23 @@
     /** Clears any dependencies on the showing or pending dialogs. */
     public void destroy() {
         dismissAllDialogs(DialogDismissalCause.ACTIVITY_DESTROYED);
+        mObserverList.clear();
+    }
+
+    /**
+     * Add an observer to this manager.
+     * @param observer The observer to add.
+     */
+    public void addObserver(ModalDialogManagerObserver observer) {
+        mObserverList.addObserver(observer);
+    }
+
+    /**
+     * Remove an observer of this manager.
+     * @param observer The observer to remove.
+     */
+    public void removeObserver(ModalDialogManagerObserver observer) {
+        mObserverList.removeObserver(observer);
     }
 
     /**
@@ -207,6 +246,7 @@
         mCurrentPresenter = mPresenters.get(dialogType, mDefaultPresenter);
         mCurrentPresenter.setDialogModel(
                 model, (dismissalCause) -> dismissDialog(model, dismissalCause));
+        for (ModalDialogManagerObserver o : mObserverList) o.onDialogShown(model);
     }
 
     /**
@@ -240,6 +280,7 @@
         if (mDismissingCurrentDialog) return;
         mDismissingCurrentDialog = true;
         model.get(ModalDialogProperties.CONTROLLER).onDismiss(model, dismissalCause);
+        for (ModalDialogManagerObserver o : mObserverList) o.onDialogHidden(model);
         mCurrentPresenter.setDialogModel(null, null);
         mCurrentPresenter = null;
         mDismissingCurrentDialog = false;
diff --git a/ui/android/java/src/org/chromium/ui/base/CursorObserver.java b/ui/android/java/src/org/chromium/ui/touchless/CursorObserver.java
similarity index 90%
rename from ui/android/java/src/org/chromium/ui/base/CursorObserver.java
rename to ui/android/java/src/org/chromium/ui/touchless/CursorObserver.java
index ac8d58e..f94424e 100644
--- a/ui/android/java/src/org/chromium/ui/base/CursorObserver.java
+++ b/ui/android/java/src/org/chromium/ui/touchless/CursorObserver.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.ui.base;
+package org.chromium.ui.touchless;
 
 /**
  * Observer Android cursor state.
diff --git a/ui/android/java/src/org/chromium/ui/touchless/OWNERS b/ui/android/java/src/org/chromium/ui/touchless/OWNERS
new file mode 100644
index 0000000..faf17b0
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/touchless/OWNERS
@@ -0,0 +1,3 @@
+mdjones@chromium.org
+mthiesse@chromium.org
+yfriedman@chromium.org
diff --git a/ui/android/java/src/org/chromium/ui/base/TouchlessEventHandler.java b/ui/android/java/src/org/chromium/ui/touchless/TouchlessEventHandler.java
similarity index 76%
rename from ui/android/java/src/org/chromium/ui/base/TouchlessEventHandler.java
rename to ui/android/java/src/org/chromium/ui/touchless/TouchlessEventHandler.java
index b3983e1..5720dad1 100644
--- a/ui/android/java/src/org/chromium/ui/base/TouchlessEventHandler.java
+++ b/ui/android/java/src/org/chromium/ui/touchless/TouchlessEventHandler.java
@@ -2,14 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.ui.base;
+package org.chromium.ui.touchless;
 
 /**
- * org.chromium.ui.base.TouchlessEventHandler
+ * org.chromium.ui.touchless.TouchlessEventHandler
  */
 public class TouchlessEventHandler {
+    /**
+     * Provides an interface for handling zoom in and zoom out requests for touchless devices.
+     */
+    public interface TouchlessZoomCallback {
+        void onZoomInRequested();
+        void onZoomOutRequested();
+    }
+
     private static final String EVENT_HANDLER_INTERNAL =
-            "org.chromium.ui.base.TouchlessEventHandlerInternal";
+            "org.chromium.ui.touchless.TouchlessEventHandlerInternal";
     private static TouchlessEventHandler sInstance;
 
     static {
@@ -43,6 +51,14 @@
         }
     }
 
+    public static void setZoomCallback(TouchlessZoomCallback callback) {
+        if (sInstance != null) sInstance.setZoomCallbackInternal(callback);
+    }
+
+    public static void removeZoomCallback(TouchlessZoomCallback callback) {
+        if (sInstance != null) sInstance.removeZoomCallbackInternal(callback);
+    }
+
     public static void onDidFinishNavigation() {
         if (sInstance != null) {
             sInstance.onDidFinishNavigationInternal();
@@ -74,6 +90,10 @@
 
     protected void removeCursorObserverInternal(CursorObserver observer) {}
 
+    protected void setZoomCallbackInternal(TouchlessZoomCallback callback) {}
+
+    protected void removeZoomCallbackInternal(TouchlessZoomCallback callback) {}
+
     protected void onDidFinishNavigationInternal() {}
 
     protected void onActivityHiddenInternal() {}
diff --git a/ui/android/junit/src/org/chromium/ui/modaldialog/ModalDialogManagerTest.java b/ui/android/junit/src/org/chromium/ui/modaldialog/ModalDialogManagerTest.java
index 241bc14a..3980d5b 100644
--- a/ui/android/junit/src/org/chromium/ui/modaldialog/ModalDialogManagerTest.java
+++ b/ui/android/junit/src/org/chromium/ui/modaldialog/ModalDialogManagerTest.java
@@ -26,6 +26,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Feature;
+import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogManagerObserver;
 import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -48,11 +49,15 @@
     private ModalDialogManager mModalDialogManager;
     private List<PropertyModel> mDialogModels = new ArrayList<>();
 
+    @Mock
+    private ModalDialogManagerObserver mObserver;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mModalDialogManager = new ModalDialogManager(mAppModalPresenter, ModalDialogType.APP);
         mModalDialogManager.registerPresenter(mTabModalPresenter, ModalDialogType.TAB);
+        mModalDialogManager.addObserver(mObserver);
 
         for (int i = 0; i < MAX_DIALOGS; ++i) {
             ModalDialogProperties.Controller controller = new ModalDialogProperties.Controller() {
@@ -69,6 +74,23 @@
         }
     }
 
+    /** Tests that the events on the {@link ModalDialogManagerObserver} are called correctly. */
+    @Test
+    @Feature({"ModalDialogManagerObserver"})
+    public void testModalDialogObserver() {
+        // Show two dialogs and make sure show is only called on one until it is hidden.
+        verify(mObserver, times(0)).onDialogShown(mDialogModels.get(0));
+        mModalDialogManager.showDialog(mDialogModels.get(0), ModalDialogType.APP);
+        mModalDialogManager.showDialog(mDialogModels.get(1), ModalDialogType.APP);
+        verify(mObserver, times(1)).onDialogShown(mDialogModels.get(0));
+        verify(mObserver, times(0)).onDialogShown(mDialogModels.get(1));
+
+        verify(mObserver, times(0)).onDialogHidden(mDialogModels.get(0));
+        mModalDialogManager.dismissDialog(mDialogModels.get(0), ModalDialogType.APP);
+        verify(mObserver, times(1)).onDialogHidden(mDialogModels.get(0));
+        verify(mObserver, times(1)).onDialogShown(mDialogModels.get(1));
+    }
+
     /** Tests showing a dialog when no dialog is currently showing. */
     @Test
     @Feature({"ModalDialog"})
diff --git a/ui/android/junit/src/org/chromium/ui/modaldialog/OWNERS b/ui/android/junit/src/org/chromium/ui/modaldialog/OWNERS
new file mode 100644
index 0000000..ac64cd03
--- /dev/null
+++ b/ui/android/junit/src/org/chromium/ui/modaldialog/OWNERS
@@ -0,0 +1,4 @@
+file://ui/android/java/src/org/chromium/ui/modaldialog/OWNERS
+
+# COMPONENT: UI>Browser>Mobile
+# OS: Android
diff --git a/ui/file_manager/base/js/filtered_volume_manager.js b/ui/file_manager/base/js/filtered_volume_manager.js
index 8d1f466..0fa54f01 100644
--- a/ui/file_manager/base/js/filtered_volume_manager.js
+++ b/ui/file_manager/base/js/filtered_volume_manager.js
@@ -51,425 +51,422 @@
  * for example, Drive volumes are dropped if Drive is disabled, and read-only
  * volumes are dropped in save-as dialogs.
  *
- * @constructor
- * @extends {cr.EventTarget}
  * @implements {VolumeManager}
- *
- * @param {!AllowedPaths} allowedPaths Which paths are supported in the Files
- *     app dialog.
- * @param {boolean} writableOnly If true, only writable volumes are returned.
- * @param {Window=} opt_backgroundPage Window object of the background
- *     page. If this is specified, the class skips to get background page.
- *     TODO(hirono): Let all clients of the class pass the background page and
- *     make the argument not optional.
  */
-function FilteredVolumeManager(allowedPaths, writableOnly, opt_backgroundPage) {
-  cr.EventTarget.call(this);
+class FilteredVolumeManager extends cr.EventTarget {
+  /**
+   *
+   * @param {!AllowedPaths} allowedPaths Which paths are supported in the Files
+   *     app dialog.
+   * @param {boolean} writableOnly If true, only writable volumes are returned.
+   * @param {Window=} opt_backgroundPage Window object of the background
+   *     page. If this is specified, the class skips to get background page.
+   *     TODO(hirono): Let all clients of the class pass the background page and
+   *     make the argument not optional.
+   */
+  constructor(allowedPaths, writableOnly, opt_backgroundPage) {
+    super();
 
-  this.allowedPaths_ = allowedPaths;
-  this.writableOnly_ = writableOnly;
-  // Internal list holds filtered VolumeInfo instances.
-  /** @private */
-  this.list_ = new cr.ui.ArrayDataModel([]);
-  // Public VolumeManager.volumeInfoList property accessed by callers.
-  this.volumeInfoList = new FilteredVolumeInfoList(this.list_);
+    this.allowedPaths_ = allowedPaths;
+    this.writableOnly_ = writableOnly;
+    // Internal list holds filtered VolumeInfo instances.
+    /** @private */
+    this.list_ = new cr.ui.ArrayDataModel([]);
+    // Public VolumeManager.volumeInfoList property accessed by callers.
+    this.volumeInfoList = new FilteredVolumeInfoList(this.list_);
 
-  this.volumeManager_ = null;
-  this.pendingTasks_ = [];
-  this.onEventBound_ = this.onEvent_.bind(this);
-  this.onVolumeInfoListUpdatedBound_ = this.onVolumeInfoListUpdated_.bind(this);
+    this.volumeManager_ = null;
+    this.pendingTasks_ = [];
+    this.onEventBound_ = this.onEvent_.bind(this);
+    this.onVolumeInfoListUpdatedBound_ =
+        this.onVolumeInfoListUpdated_.bind(this);
 
-  this.disposed_ = false;
+    this.disposed_ = false;
 
-  // Start initialize the VolumeManager.
-  const queue = new AsyncUtil.Queue();
+    // Start initialize the VolumeManager.
+    const queue = new AsyncUtil.Queue();
 
-  if (opt_backgroundPage) {
-    this.backgroundPage_ = opt_backgroundPage;
-  } else {
-    queue.run(callNextStep => {
-      chrome.runtime.getBackgroundPage(
-          /** @type {function(Window=)} */ (opt_backgroundPage => {
-            this.backgroundPage_ = opt_backgroundPage;
-            callNextStep();
-          }));
-    });
-  }
-
-  queue.run(callNextStep => {
-    this.backgroundPage_.volumeManagerFactory.getInstance(volumeManager => {
-      this.onReady_(volumeManager);
-      callNextStep();
-    });
-  });
-}
-
-/**
- * Extends cr.EventTarget.
- */
-FilteredVolumeManager.prototype.__proto__ = cr.EventTarget.prototype;
-
-/**
- * Checks if a volume type is allowed.
- *
- * Note that even if a volume type is allowed, a volume of that type might be
- * disallowed for other restrictions. To check if a specific volume is allowed
- * or not, use isAllowedVolume_() instead.
- *
- * @param {VolumeManagerCommon.VolumeType} volumeType
- * @return {boolean}
- */
-FilteredVolumeManager.prototype.isAllowedVolumeType_ = function(volumeType) {
-  switch (this.allowedPaths_) {
-    case AllowedPaths.ANY_PATH:
-    case AllowedPaths.ANY_PATH_OR_URL:
-      return true;
-    case AllowedPaths.NATIVE_OR_DRIVE_PATH:
-      return (
-          VolumeManagerCommon.VolumeType.isNative(volumeType) ||
-          volumeType == VolumeManagerCommon.VolumeType.DRIVE);
-    case AllowedPaths.NATIVE_PATH:
-      return VolumeManagerCommon.VolumeType.isNative(volumeType);
-  }
-  return false;
-};
-
-/**
- * Checks if a volume is allowed.
- *
- * @param {!VolumeInfo} volumeInfo
- * @return {boolean}
- */
-FilteredVolumeManager.prototype.isAllowedVolume_ = function(volumeInfo) {
-  if (!this.isAllowedVolumeType_(volumeInfo.volumeType)) {
-    return false;
-  }
-  if (this.writableOnly_ && volumeInfo.isReadOnly) {
-    return false;
-  }
-  return true;
-};
-
-/**
- * Called when the VolumeManager gets ready for post initialization.
- * @param {VolumeManager} volumeManager The initialized VolumeManager instance.
- * @private
- */
-FilteredVolumeManager.prototype.onReady_ = function(volumeManager) {
-  if (this.disposed_) {
-    return;
-  }
-
-  this.volumeManager_ = volumeManager;
-
-  // Subscribe to VolumeManager.
-  this.volumeManager_.addEventListener(
-      'drive-connection-changed', this.onEventBound_);
-  this.volumeManager_.addEventListener(
-      'externally-unmounted', this.onEventBound_);
-  this.volumeManager_.addEventListener(
-      VolumeManagerCommon.ARCHIVE_OPENED_EVENT_TYPE, this.onEventBound_);
-
-  // Dispatch 'drive-connection-changed' to listeners, since the return value of
-  // FilteredVolumeManager.getDriveConnectionState() can be changed by setting
-  // this.volumeManager_.
-  cr.dispatchSimpleEvent(this, 'drive-connection-changed');
-
-  // Cache volumeInfoList.
-  const volumeInfoList = [];
-  for (var i = 0; i < this.volumeManager_.volumeInfoList.length; i++) {
-    const volumeInfo = this.volumeManager_.volumeInfoList.item(i);
-    // TODO(hidehiko): Filter mounted volumes located on Drive File System.
-    if (!this.isAllowedVolume_(volumeInfo)) {
-      continue;
-    }
-    volumeInfoList.push(volumeInfo);
-  }
-  this.list_.splice.apply(
-      this.list_, [0, this.volumeInfoList.length].concat(volumeInfoList));
-
-  // Subscribe to VolumeInfoList.
-  // In VolumeInfoList, we only use 'splice' event.
-  this.volumeManager_.volumeInfoList.addEventListener(
-      'splice', this.onVolumeInfoListUpdatedBound_);
-
-  // Run pending tasks.
-  const pendingTasks = this.pendingTasks_;
-  this.pendingTasks_ = null;
-  for (var i = 0; i < pendingTasks.length; i++) {
-    pendingTasks[i]();
-  }
-};
-
-/**
- * Disposes the instance. After the invocation of this method, any other
- * method should not be called.
- */
-FilteredVolumeManager.prototype.dispose = function() {
-  this.disposed_ = true;
-
-  if (!this.volumeManager_) {
-    return;
-  }
-  this.volumeManager_.removeEventListener(
-      'drive-connection-changed', this.onEventBound_);
-  this.volumeManager_.removeEventListener(
-      'externally-unmounted', this.onEventBound_);
-  this.volumeManager_.volumeInfoList.removeEventListener(
-      'splice', this.onVolumeInfoListUpdatedBound_);
-};
-
-/**
- * Called on events sent from VolumeManager. This has responsibility to
- * re-dispatch the event to the listeners.
- * @param {!Event} event Event object sent from VolumeManager.
- * @private
- */
-FilteredVolumeManager.prototype.onEvent_ = function(event) {
-  switch (event.type) {
-    case 'drive-connection-changed':
-      if (this.isAllowedVolumeType_(VolumeManagerCommon.VolumeType.DRIVE)) {
-        this.dispatchEvent(event);
-      }
-      break;
-    case 'externally-unmounted':
-      event = /** @type {!ExternallyUnmountedEvent} */ (event);
-      if (this.isAllowedVolume_(event.volumeInfo)) {
-        this.dispatchEvent(event);
-      }
-      break;
-    case VolumeManagerCommon.ARCHIVE_OPENED_EVENT_TYPE:
-      this.dispatchEvent(event);
-      break;
-  }
-};
-
-/**
- * Called on events of modifying VolumeInfoList.
- * @param {Event} event Event object sent from VolumeInfoList.
- * @private
- */
-FilteredVolumeManager.prototype.onVolumeInfoListUpdated_ = function(event) {
-  // Filters some volumes.
-  let index = event.index;
-  for (var i = 0; i < event.index; i++) {
-    var volumeInfo = this.volumeManager_.volumeInfoList.item(i);
-    if (!this.isAllowedVolume_(volumeInfo)) {
-      index--;
-    }
-  }
-
-  let numRemovedVolumes = 0;
-  for (var i = 0; i < event.removed.length; i++) {
-    var volumeInfo = event.removed[i];
-    if (this.isAllowedVolume_(volumeInfo)) {
-      numRemovedVolumes++;
-    }
-  }
-
-  const addedVolumes = [];
-  for (var i = 0; i < event.added.length; i++) {
-    var volumeInfo = event.added[i];
-    if (this.isAllowedVolume_(volumeInfo)) {
-      addedVolumes.push(volumeInfo);
-    }
-  }
-
-  this.list_.splice.apply(
-      this.list_, [index, numRemovedVolumes].concat(addedVolumes));
-};
-
-/**
- * Returns whether the VolumeManager is initialized or not.
- * @return {boolean} True if the VolumeManager is initialized.
- */
-FilteredVolumeManager.prototype.isInitialized = function() {
-  return this.pendingTasks_ === null;
-};
-
-/**
- * Ensures the VolumeManager is initialized, and then invokes callback.
- * If the VolumeManager is already initialized, callback will be called
- * immediately.
- * @param {function()} callback Called on initialization completion.
- */
-FilteredVolumeManager.prototype.ensureInitialized = function(callback) {
-  if (!this.isInitialized()) {
-    this.pendingTasks_.push(this.ensureInitialized.bind(this, callback));
-    return;
-  }
-
-  callback();
-};
-
-/**
- * @return {VolumeManagerCommon.DriveConnectionState} Current drive connection
- *     state.
- */
-FilteredVolumeManager.prototype.getDriveConnectionState = function() {
-  if (!this.isAllowedVolumeType_(VolumeManagerCommon.VolumeType.DRIVE) ||
-      !this.volumeManager_) {
-    return {
-      type: VolumeManagerCommon.DriveConnectionType.OFFLINE,
-      reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE
-    };
-  }
-
-  return this.volumeManager_.getDriveConnectionState();
-};
-
-/** @override */
-FilteredVolumeManager.prototype.getVolumeInfo = function(entry) {
-  return this.filterDisallowedVolume_(
-      this.volumeManager_ && this.volumeManager_.getVolumeInfo(entry));
-};
-
-/**
- * Obtains a volume information of the current profile.
- * @param {VolumeManagerCommon.VolumeType} volumeType Volume type.
- * @return {VolumeInfo} Found volume info.
- */
-FilteredVolumeManager.prototype.getCurrentProfileVolumeInfo = function(
-    volumeType) {
-  return this.filterDisallowedVolume_(
-      this.volumeManager_ &&
-      this.volumeManager_.getCurrentProfileVolumeInfo(volumeType));
-};
-
-/** @override */
-FilteredVolumeManager.prototype.getDefaultDisplayRoot = function(callback) {
-  this.ensureInitialized(() => {
-    const defaultVolume = this.getCurrentProfileVolumeInfo(
-        VolumeManagerCommon.VolumeType.DOWNLOADS);
-    if (defaultVolume) {
-      defaultVolume.resolveDisplayRoot(callback, () => {
-        // defaultVolume is DOWNLOADS and resolveDisplayRoot should succeed.
-        throw new Error(
-            'Unexpectedly failed to obtain the default display root.');
-      });
+    if (opt_backgroundPage) {
+      this.backgroundPage_ = opt_backgroundPage;
     } else {
-      console.warn('Unexpectedly failed to obtain the default display root.');
-      callback(null);
+      queue.run(callNextStep => {
+        chrome.runtime.getBackgroundPage(
+            /** @type {function(Window=)} */ (opt_backgroundPage => {
+              this.backgroundPage_ = opt_backgroundPage;
+              callNextStep();
+            }));
+      });
     }
-  });
-};
 
-/**
- * Obtains location information from an entry.
- *
- * @param {(!Entry|!FilesAppEntry)} entry File or directory entry.
- * @return {EntryLocation} Location information.
- */
-FilteredVolumeManager.prototype.getLocationInfo = function(entry) {
-  const locationInfo =
-      this.volumeManager_ && this.volumeManager_.getLocationInfo(entry);
-  if (!locationInfo) {
-    return null;
-  }
-  if (locationInfo.volumeInfo &&
-      !this.filterDisallowedVolume_(locationInfo.volumeInfo)) {
-    return null;
-  }
-  return locationInfo;
-};
-
-/** @override */
-FilteredVolumeManager.prototype.findByDevicePath = function(devicePath) {
-  for (let i = 0; i < this.volumeInfoList.length; i++) {
-    const volumeInfo = this.volumeInfoList.item(i);
-    if (volumeInfo.devicePath && volumeInfo.devicePath === devicePath) {
-      return this.filterDisallowedVolume_(volumeInfo);
-    }
-  }
-  return null;
-};
-
-/**
- * Returns a promise that will be resolved when volume info, identified
- * by {@code volumeId} is created.
- *
- * @param {string} volumeId
- * @return {!Promise<!VolumeInfo>} The VolumeInfo. Will not resolve
- *     if the volume is never mounted.
- */
-FilteredVolumeManager.prototype.whenVolumeInfoReady = function(volumeId) {
-  return new Promise(resolve => {
-    this.volumeManager_.whenVolumeInfoReady(volumeId).then((volumeInfo) => {
-      volumeInfo = this.filterDisallowedVolume_(volumeInfo);
-      if (volumeInfo) {
-        resolve(volumeInfo);
-      }
-    });
-  });
-};
-
-/**
- * Requests to mount the archive file.
- * @param {string} fileUrl The path to the archive file to be mounted.
- * @param {function(VolumeInfo)} successCallback Called with the VolumeInfo
- *     instance.
- * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Called when
- *     an error occurs.
- */
-FilteredVolumeManager.prototype.mountArchive = function(
-    fileUrl, successCallback, errorCallback) {
-  if (this.pendingTasks_) {
-    this.pendingTasks_.push(
-        this.mountArchive.bind(this, fileUrl, successCallback, errorCallback));
-    return;
-  }
-
-  this.volumeManager_.mountArchive(fileUrl, successCallback, errorCallback);
-};
-
-/**
- * Requests unmount the specified volume.
- * @param {!VolumeInfo} volumeInfo Volume to be unmounted.
- * @param {function()} successCallback Called on success.
- * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Called when
- *     an error occurs.
- */
-FilteredVolumeManager.prototype.unmount = function(
-    volumeInfo, successCallback, errorCallback) {
-  if (this.pendingTasks_) {
-    this.pendingTasks_.push(
-        this.unmount.bind(this, volumeInfo, successCallback, errorCallback));
-    return;
-  }
-
-  this.volumeManager_.unmount(volumeInfo, successCallback, errorCallback);
-};
-
-/**
- * Requests configuring of the specified volume.
- * @param {!VolumeInfo} volumeInfo Volume to be configured.
- * @return {!Promise} Fulfilled on success, otherwise rejected with an error
- *     message.
- */
-FilteredVolumeManager.prototype.configure = function(volumeInfo) {
-  if (this.pendingTasks_) {
-    return new Promise((fulfill, reject) => {
-      this.pendingTasks_.push(() => {
-        return this.volumeManager_.configure(volumeInfo).then(fulfill, reject);
+    queue.run(callNextStep => {
+      this.backgroundPage_.volumeManagerFactory.getInstance(volumeManager => {
+        this.onReady_(volumeManager);
+        callNextStep();
       });
     });
   }
 
-  return this.volumeManager_.configure(volumeInfo);
-};
+  /**
+   * Checks if a volume type is allowed.
+   *
+   * Note that even if a volume type is allowed, a volume of that type might be
+   * disallowed for other restrictions. To check if a specific volume is allowed
+   * or not, use isAllowedVolume_() instead.
+   *
+   * @param {VolumeManagerCommon.VolumeType} volumeType
+   * @return {boolean}
+   */
+  isAllowedVolumeType_(volumeType) {
+    switch (this.allowedPaths_) {
+      case AllowedPaths.ANY_PATH:
+      case AllowedPaths.ANY_PATH_OR_URL:
+        return true;
+      case AllowedPaths.NATIVE_OR_DRIVE_PATH:
+        return (
+            VolumeManagerCommon.VolumeType.isNative(volumeType) ||
+            volumeType == VolumeManagerCommon.VolumeType.DRIVE);
+      case AllowedPaths.NATIVE_PATH:
+        return VolumeManagerCommon.VolumeType.isNative(volumeType);
+    }
+    return false;
+  }
 
-/**
- * Filters volume info by isAllowedVolume_().
- *
- * @param {VolumeInfo} volumeInfo Volume info.
- * @return {VolumeInfo} Null if the volume is disallowed. Otherwise just returns
- *     the volume.
- * @private
- */
-FilteredVolumeManager.prototype.filterDisallowedVolume_ = function(volumeInfo) {
-  if (volumeInfo && this.isAllowedVolume_(volumeInfo)) {
-    return volumeInfo;
-  } else {
+  /**
+   * Checks if a volume is allowed.
+   *
+   * @param {!VolumeInfo} volumeInfo
+   * @return {boolean}
+   */
+  isAllowedVolume_(volumeInfo) {
+    if (!this.isAllowedVolumeType_(volumeInfo.volumeType)) {
+      return false;
+    }
+    if (this.writableOnly_ && volumeInfo.isReadOnly) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Called when the VolumeManager gets ready for post initialization.
+   * @param {VolumeManager} volumeManager The initialized VolumeManager
+   *     instance.
+   * @private
+   */
+  onReady_(volumeManager) {
+    if (this.disposed_) {
+      return;
+    }
+
+    this.volumeManager_ = volumeManager;
+
+    // Subscribe to VolumeManager.
+    this.volumeManager_.addEventListener(
+        'drive-connection-changed', this.onEventBound_);
+    this.volumeManager_.addEventListener(
+        'externally-unmounted', this.onEventBound_);
+    this.volumeManager_.addEventListener(
+        VolumeManagerCommon.ARCHIVE_OPENED_EVENT_TYPE, this.onEventBound_);
+
+    // Dispatch 'drive-connection-changed' to listeners, since the return value
+    // of FilteredVolumeManager.getDriveConnectionState() can be changed by
+    // setting this.volumeManager_.
+    cr.dispatchSimpleEvent(this, 'drive-connection-changed');
+
+    // Cache volumeInfoList.
+    const volumeInfoList = [];
+    for (var i = 0; i < this.volumeManager_.volumeInfoList.length; i++) {
+      const volumeInfo = this.volumeManager_.volumeInfoList.item(i);
+      // TODO(hidehiko): Filter mounted volumes located on Drive File System.
+      if (!this.isAllowedVolume_(volumeInfo)) {
+        continue;
+      }
+      volumeInfoList.push(volumeInfo);
+    }
+    this.list_.splice.apply(
+        this.list_, [0, this.volumeInfoList.length].concat(volumeInfoList));
+
+    // Subscribe to VolumeInfoList.
+    // In VolumeInfoList, we only use 'splice' event.
+    this.volumeManager_.volumeInfoList.addEventListener(
+        'splice', this.onVolumeInfoListUpdatedBound_);
+
+    // Run pending tasks.
+    const pendingTasks = this.pendingTasks_;
+    this.pendingTasks_ = null;
+    for (var i = 0; i < pendingTasks.length; i++) {
+      pendingTasks[i]();
+    }
+  }
+
+  /**
+   * Disposes the instance. After the invocation of this method, any other
+   * method should not be called.
+   */
+  dispose() {
+    this.disposed_ = true;
+
+    if (!this.volumeManager_) {
+      return;
+    }
+    this.volumeManager_.removeEventListener(
+        'drive-connection-changed', this.onEventBound_);
+    this.volumeManager_.removeEventListener(
+        'externally-unmounted', this.onEventBound_);
+    this.volumeManager_.volumeInfoList.removeEventListener(
+        'splice', this.onVolumeInfoListUpdatedBound_);
+  }
+
+  /**
+   * Called on events sent from VolumeManager. This has responsibility to
+   * re-dispatch the event to the listeners.
+   * @param {!Event} event Event object sent from VolumeManager.
+   * @private
+   */
+  onEvent_(event) {
+    switch (event.type) {
+      case 'drive-connection-changed':
+        if (this.isAllowedVolumeType_(VolumeManagerCommon.VolumeType.DRIVE)) {
+          this.dispatchEvent(event);
+        }
+        break;
+      case 'externally-unmounted':
+        event = /** @type {!ExternallyUnmountedEvent} */ (event);
+        if (this.isAllowedVolume_(event.volumeInfo)) {
+          this.dispatchEvent(event);
+        }
+        break;
+      case VolumeManagerCommon.ARCHIVE_OPENED_EVENT_TYPE:
+        this.dispatchEvent(event);
+        break;
+    }
+  }
+
+  /**
+   * Called on events of modifying VolumeInfoList.
+   * @param {Event} event Event object sent from VolumeInfoList.
+   * @private
+   */
+  onVolumeInfoListUpdated_(event) {
+    // Filters some volumes.
+    let index = event.index;
+    for (var i = 0; i < event.index; i++) {
+      var volumeInfo = this.volumeManager_.volumeInfoList.item(i);
+      if (!this.isAllowedVolume_(volumeInfo)) {
+        index--;
+      }
+    }
+
+    let numRemovedVolumes = 0;
+    for (var i = 0; i < event.removed.length; i++) {
+      var volumeInfo = event.removed[i];
+      if (this.isAllowedVolume_(volumeInfo)) {
+        numRemovedVolumes++;
+      }
+    }
+
+    const addedVolumes = [];
+    for (var i = 0; i < event.added.length; i++) {
+      var volumeInfo = event.added[i];
+      if (this.isAllowedVolume_(volumeInfo)) {
+        addedVolumes.push(volumeInfo);
+      }
+    }
+
+    this.list_.splice.apply(
+        this.list_, [index, numRemovedVolumes].concat(addedVolumes));
+  }
+
+  /**
+   * Returns whether the VolumeManager is initialized or not.
+   * @return {boolean} True if the VolumeManager is initialized.
+   */
+  isInitialized() {
+    return this.pendingTasks_ === null;
+  }
+
+  /**
+   * Ensures the VolumeManager is initialized, and then invokes callback.
+   * If the VolumeManager is already initialized, callback will be called
+   * immediately.
+   * @param {function()} callback Called on initialization completion.
+   */
+  ensureInitialized(callback) {
+    if (!this.isInitialized()) {
+      this.pendingTasks_.push(this.ensureInitialized.bind(this, callback));
+      return;
+    }
+
+    callback();
+  }
+
+  /**
+   * @return {VolumeManagerCommon.DriveConnectionState} Current drive connection
+   *     state.
+   */
+  getDriveConnectionState() {
+    if (!this.isAllowedVolumeType_(VolumeManagerCommon.VolumeType.DRIVE) ||
+        !this.volumeManager_) {
+      return {
+        type: VolumeManagerCommon.DriveConnectionType.OFFLINE,
+        reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE
+      };
+    }
+
+    return this.volumeManager_.getDriveConnectionState();
+  }
+
+  /** @override */
+  getVolumeInfo(entry) {
+    return this.filterDisallowedVolume_(
+        this.volumeManager_ && this.volumeManager_.getVolumeInfo(entry));
+  }
+
+  /**
+   * Obtains a volume information of the current profile.
+   * @param {VolumeManagerCommon.VolumeType} volumeType Volume type.
+   * @return {VolumeInfo} Found volume info.
+   */
+  getCurrentProfileVolumeInfo(volumeType) {
+    return this.filterDisallowedVolume_(
+        this.volumeManager_ &&
+        this.volumeManager_.getCurrentProfileVolumeInfo(volumeType));
+  }
+
+  /** @override */
+  getDefaultDisplayRoot(callback) {
+    this.ensureInitialized(() => {
+      const defaultVolume = this.getCurrentProfileVolumeInfo(
+          VolumeManagerCommon.VolumeType.DOWNLOADS);
+      if (defaultVolume) {
+        defaultVolume.resolveDisplayRoot(callback, () => {
+          // defaultVolume is DOWNLOADS and resolveDisplayRoot should succeed.
+          throw new Error(
+              'Unexpectedly failed to obtain the default display root.');
+        });
+      } else {
+        console.warn('Unexpectedly failed to obtain the default display root.');
+        callback(null);
+      }
+    });
+  }
+
+  /**
+   * Obtains location information from an entry.
+   *
+   * @param {(!Entry|!FilesAppEntry)} entry File or directory entry.
+   * @return {EntryLocation} Location information.
+   */
+  getLocationInfo(entry) {
+    const locationInfo =
+        this.volumeManager_ && this.volumeManager_.getLocationInfo(entry);
+    if (!locationInfo) {
+      return null;
+    }
+    if (locationInfo.volumeInfo &&
+        !this.filterDisallowedVolume_(locationInfo.volumeInfo)) {
+      return null;
+    }
+    return locationInfo;
+  }
+
+  /** @override */
+  findByDevicePath(devicePath) {
+    for (let i = 0; i < this.volumeInfoList.length; i++) {
+      const volumeInfo = this.volumeInfoList.item(i);
+      if (volumeInfo.devicePath && volumeInfo.devicePath === devicePath) {
+        return this.filterDisallowedVolume_(volumeInfo);
+      }
+    }
     return null;
   }
-};
+
+  /**
+   * Returns a promise that will be resolved when volume info, identified
+   * by {@code volumeId} is created.
+   *
+   * @param {string} volumeId
+   * @return {!Promise<!VolumeInfo>} The VolumeInfo. Will not resolve
+   *     if the volume is never mounted.
+   */
+  whenVolumeInfoReady(volumeId) {
+    return new Promise(resolve => {
+      this.volumeManager_.whenVolumeInfoReady(volumeId).then((volumeInfo) => {
+        volumeInfo = this.filterDisallowedVolume_(volumeInfo);
+        if (volumeInfo) {
+          resolve(volumeInfo);
+        }
+      });
+    });
+  }
+
+  /**
+   * Requests to mount the archive file.
+   * @param {string} fileUrl The path to the archive file to be mounted.
+   * @param {function(VolumeInfo)} successCallback Called with the VolumeInfo
+   *     instance.
+   * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Called
+   *     when an error occurs.
+   */
+  mountArchive(fileUrl, successCallback, errorCallback) {
+    if (this.pendingTasks_) {
+      this.pendingTasks_.push(this.mountArchive.bind(
+          this, fileUrl, successCallback, errorCallback));
+      return;
+    }
+
+    this.volumeManager_.mountArchive(fileUrl, successCallback, errorCallback);
+  }
+
+  /**
+   * Requests unmount the specified volume.
+   * @param {!VolumeInfo} volumeInfo Volume to be unmounted.
+   * @param {function()} successCallback Called on success.
+   * @param {function(VolumeManagerCommon.VolumeError)} errorCallback Called
+   *     when an error occurs.
+   */
+  unmount(volumeInfo, successCallback, errorCallback) {
+    if (this.pendingTasks_) {
+      this.pendingTasks_.push(
+          this.unmount.bind(this, volumeInfo, successCallback, errorCallback));
+      return;
+    }
+
+    this.volumeManager_.unmount(volumeInfo, successCallback, errorCallback);
+  }
+
+  /**
+   * Requests configuring of the specified volume.
+   * @param {!VolumeInfo} volumeInfo Volume to be configured.
+   * @return {!Promise} Fulfilled on success, otherwise rejected with an error
+   *     message.
+   */
+  configure(volumeInfo) {
+    if (this.pendingTasks_) {
+      return new Promise((fulfill, reject) => {
+        this.pendingTasks_.push(() => {
+          return this.volumeManager_.configure(volumeInfo)
+              .then(fulfill, reject);
+        });
+      });
+    }
+
+    return this.volumeManager_.configure(volumeInfo);
+  }
+
+  /**
+   * Filters volume info by isAllowedVolume_().
+   *
+   * @param {VolumeInfo} volumeInfo Volume info.
+   * @return {VolumeInfo} Null if the volume is disallowed. Otherwise just
+   *     returns the volume.
+   * @private
+   */
+  filterDisallowedVolume_(volumeInfo) {
+    if (volumeInfo && this.isAllowedVolume_(volumeInfo)) {
+      return volumeInfo;
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/ui/file_manager/file_manager/foreground/js/directory_model.js b/ui/file_manager/file_manager/foreground/js/directory_model.js
index 767e989..5ab1553b 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_model.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_model.js
@@ -10,1442 +10,1450 @@
 
 /**
  * Data model of the file manager.
- *
- * @constructor
- * @extends {cr.EventTarget}
- *
- * @param {boolean} singleSelection True if only one file could be selected
- *                                  at the time.
- * @param {FileFilter} fileFilter Instance of FileFilter.
- * @param {!MetadataModel} metadataModel Metadata model.
- *     service.
- * @param {!VolumeManager} volumeManager The volume manager.
- * @param {!FileOperationManager} fileOperationManager File operation manager.
  */
-function DirectoryModel(
-    singleSelection, fileFilter, metadataModel, volumeManager,
-    fileOperationManager) {
-  this.fileListSelection_ = singleSelection ?
-      new FileListSingleSelectionModel() :
-      new FileListSelectionModel();
-
-  this.runningScan_ = null;
-  this.pendingScan_ = null;
-  this.rescanTime_ = null;
-  this.scanFailures_ = 0;
-  this.changeDirectorySequence_ = 0;
-
+class DirectoryModel extends cr.EventTarget {
   /**
-   * @private {boolean}
+   * @param {boolean} singleSelection True if only one file could be selected
+   *                                  at the time.
+   * @param {FileFilter} fileFilter Instance of FileFilter.
+   * @param {!MetadataModel} metadataModel Metadata model.
+   *     service.
+   * @param {!VolumeManager} volumeManager The volume manager.
+   * @param {!FileOperationManager} fileOperationManager File operation manager.
    */
-  this.ignoreCurrentDirectoryDeletion_ = false;
+  constructor(
+      singleSelection, fileFilter, metadataModel, volumeManager,
+      fileOperationManager) {
+    super();
 
-  this.directoryChangeQueue_ = new AsyncUtil.Queue();
-  this.rescanAggregator_ =
-      new AsyncUtil.Aggregator(this.rescanSoon.bind(this, true), 500);
-
-  this.fileFilter_ = fileFilter;
-  this.fileFilter_.addEventListener(
-      'changed', this.onFilterChanged_.bind(this));
-
-  this.currentFileListContext_ =
-      new FileListContext(fileFilter, metadataModel, volumeManager);
-  this.currentDirContents_ =
-      DirectoryContents.createForDirectory(this.currentFileListContext_, null);
-  /**
-   * Empty file list which is used as a dummy for inactive view of file list.
-   * @private {!FileListModel}
-   */
-  this.emptyFileList_ = new FileListModel(metadataModel);
-
-  this.metadataModel_ = metadataModel;
-
-  this.volumeManager_ = volumeManager;
-  this.volumeManager_.volumeInfoList.addEventListener(
-      'splice', this.onVolumeInfoListUpdated_.bind(this));
-
-  /**
-   * File watcher.
-   * @private {!FileWatcher}
-   * @const
-   */
-  this.fileWatcher_ = new FileWatcher();
-  this.fileWatcher_.addEventListener(
-      'watcher-directory-changed', this.onWatcherDirectoryChanged_.bind(this));
-  util.addEventListenerToBackgroundComponent(
-      fileOperationManager, 'entries-changed',
-      this.onEntriesChanged_.bind(this));
-
-  /** @private {string} */
-  this.lastSearchQuery_ = '';
-
-  /** @private {FilesAppDirEntry} */
-  this.myFilesEntry_ = null;
-}
-
-/**
- * DirectoryModel extends cr.EventTarget.
- */
-DirectoryModel.prototype.__proto__ = cr.EventTarget.prototype;
-
-/**
- * Disposes the directory model by removing file watchers.
- */
-DirectoryModel.prototype.dispose = function() {
-  this.fileWatcher_.dispose();
-};
-
-/**
- * @return {FileListModel} Files in the current directory.
- */
-DirectoryModel.prototype.getFileList = function() {
-  return this.currentFileListContext_.fileList;
-};
-
-/**
- * @return {!FileListModel} File list which is always empty.
- */
-DirectoryModel.prototype.getEmptyFileList = function() {
-  return this.emptyFileList_;
-};
-
-/**
- * @return {!FileListSelectionModel|!FileListSingleSelectionModel} Selection
- * in the fileList.
- */
-DirectoryModel.prototype.getFileListSelection = function() {
-  return this.fileListSelection_;
-};
-
-/**
- * Obtains current volume information.
- * @return {VolumeInfo}
- */
-DirectoryModel.prototype.getCurrentVolumeInfo = function() {
-  const entry = this.getCurrentDirEntry();
-  if (!entry) {
-    return null;
-  }
-  return this.volumeManager_.getVolumeInfo(entry);
-};
-
-/**
- * @return {?VolumeManagerCommon.RootType} Root type of current root, or null if
- *     not found.
- */
-DirectoryModel.prototype.getCurrentRootType = function() {
-  const entry = this.currentDirContents_.getDirectoryEntry();
-  if (!entry) {
-    return null;
-  }
-
-  const locationInfo = this.volumeManager_.getLocationInfo(entry);
-  if (!locationInfo) {
-    return null;
-  }
-
-  return locationInfo.rootType;
-};
-
-/**
- * Metadata property names that are expected to be Prefetched.
- * @return {!Array<string>}
- */
-DirectoryModel.prototype.getPrefetchPropertyNames = function() {
-  return this.currentFileListContext_.prefetchPropertyNames;
-};
-
-/**
- * @return {boolean} True if the current directory is read only. If there is
- *     no entry set, then returns true.
- */
-DirectoryModel.prototype.isReadOnly = function() {
-  const currentDirEntry = this.getCurrentDirEntry();
-  if (currentDirEntry) {
-    const locationInfo = this.volumeManager_.getLocationInfo(currentDirEntry);
-    if (locationInfo) {
-      return locationInfo.isReadOnly;
-    }
-  }
-  return true;
-};
-
-/**
- * @return {boolean} True if the a scan is active.
- */
-DirectoryModel.prototype.isScanning = function() {
-  return this.currentDirContents_.isScanning();
-};
-
-/**
- * @return {boolean} True if search is in progress.
- */
-DirectoryModel.prototype.isSearching = function() {
-  return this.currentDirContents_.isSearch();
-};
-
-/**
- * @return {boolean} True if it's on Drive.
- */
-DirectoryModel.prototype.isOnDrive = function() {
-  return this.isCurrentRootVolumeType_(VolumeManagerCommon.VolumeType.DRIVE);
-};
-
-/**
- * @return {boolean} True if it's on MTP volume.
- */
-DirectoryModel.prototype.isOnMTP = function() {
-  return this.isCurrentRootVolumeType_(VolumeManagerCommon.VolumeType.MTP);
-};
-
-/**
- * @param {VolumeManagerCommon.VolumeType} volumeType Volume Type
- * @return {boolean} True if current root volume type is equal to specified
- *     volume type.
- * @private
- */
-DirectoryModel.prototype.isCurrentRootVolumeType_ = function(volumeType) {
-  const rootType = this.getCurrentRootType();
-  return rootType != null && rootType != VolumeManagerCommon.RootType.RECENT &&
-      VolumeManagerCommon.getVolumeTypeFromRootType(rootType) === volumeType;
-};
-
-/**
- * Updates the selection by using the updateFunc and publish the change event.
- * If updateFunc returns true, it force to dispatch the change event even if the
- * selection index is not changed.
- *
- * @param {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} selection
- *     Selection to be updated.
- * @param {function(): boolean} updateFunc Function updating the selection.
- * @private
- */
-DirectoryModel.prototype.updateSelectionAndPublishEvent_ =
-    (selection, updateFunc) => {
-      // Begin change.
-      selection.beginChange();
-
-      // If dispatchNeeded is true, we should ensure the change event is
-      // dispatched.
-      let dispatchNeeded = updateFunc();
-
-      // Check if the change event is dispatched in the endChange function
-      // or not.
-      const eventDispatched = () => {
-        dispatchNeeded = false;
-      };
-      selection.addEventListener('change', eventDispatched);
-      selection.endChange();
-      selection.removeEventListener('change', eventDispatched);
-
-      // If the change event have been already dispatched, dispatchNeeded is
-      // false.
-      if (dispatchNeeded) {
-        const event = new Event('change');
-        // The selection status (selected or not) is not changed because
-        // this event is caused by the change of selected item.
-        event.changes = [];
-        selection.dispatchEvent(event);
-      }
-    };
-
-/**
- * Sets to ignore current directory deletion. This method is used to prevent
- * going up to the volume root with the deletion of current directory by rename
- * operation in directory tree.
- * @param {boolean} value True to ignore current directory deletion.
- */
-DirectoryModel.prototype.setIgnoringCurrentDirectoryDeletion = function(value) {
-  this.ignoreCurrentDirectoryDeletion_ = value;
-};
-
-/**
- * Invoked when a change in the directory is detected by the watcher.
- * @param {Event} event Event object.
- * @private
- */
-DirectoryModel.prototype.onWatcherDirectoryChanged_ = function(event) {
-  const directoryEntry = this.getCurrentDirEntry();
-
-  if (!this.ignoreCurrentDirectoryDeletion_) {
-    // If the change is deletion of currentDir, move up to its parent directory.
-    directoryEntry.getDirectory(
-        directoryEntry.fullPath, {create: false}, () => {}, () => {
-          const volumeInfo =
-              this.volumeManager_.getVolumeInfo(assert(directoryEntry));
-          if (volumeInfo) {
-            volumeInfo.resolveDisplayRoot().then(displayRoot => {
-              this.changeDirectoryEntry(displayRoot);
-            });
-          }
-        });
-  }
-
-  if (event.changedFiles) {
-    const addedOrUpdatedFileUrls = [];
-    let deletedFileUrls = [];
-    event.changedFiles.forEach(change => {
-      if (change.changes.length === 1 && change.changes[0] === 'delete') {
-        deletedFileUrls.push(change.url);
-      } else {
-        addedOrUpdatedFileUrls.push(change.url);
-      }
-    });
-
-    util.URLsToEntries(addedOrUpdatedFileUrls)
-        .then(result => {
-          deletedFileUrls = deletedFileUrls.concat(result.failureUrls);
-
-          // Passing the resolved entries and failed URLs as the removed files.
-          // The URLs are removed files and they chan't be resolved.
-          this.partialUpdate_(result.entries, deletedFileUrls);
-        })
-        .catch(error => {
-          console.error(
-              'Error in proceeding the changed event.', error,
-              'Fallback to force-refresh');
-          this.rescanAggregator_.run();
-        });
-  } else {
-    // Invokes force refresh if the detailed information isn't provided.
-    // This can occur very frequently (e.g. when copying files into Downlaods)
-    // and rescan is heavy operation, so we keep some interval for each rescan.
-    this.rescanAggregator_.run();
-  }
-};
-
-/**
- * Invoked when filters are changed.
- * @private
- */
-DirectoryModel.prototype.onFilterChanged_ = function() {
-  const currentDirectory = this.getCurrentDirEntry();
-  if (currentDirectory && util.isNativeEntry(currentDirectory) &&
-      !this.fileFilter_.filter(
-          /** @type {!DirectoryEntry} */ (currentDirectory))) {
-    // If the current directory should be hidden in the new filter setting,
-    // change the current directory to the current volume's root.
-    const volumeInfo = this.volumeManager_.getVolumeInfo(currentDirectory);
-    if (volumeInfo) {
-      volumeInfo.resolveDisplayRoot().then(displayRoot => {
-        this.changeDirectoryEntry(displayRoot);
-      });
-    }
-  } else {
-    this.rescanSoon(false);
-  }
-};
-
-/**
- * Returns the filter.
- * @return {FileFilter} The file filter.
- */
-DirectoryModel.prototype.getFileFilter = function() {
-  return this.fileFilter_;
-};
-
-/**
- * @return {DirectoryEntry|FakeEntry|FilesAppDirEntry} Current directory.
- */
-DirectoryModel.prototype.getCurrentDirEntry = function() {
-  return this.currentDirContents_.getDirectoryEntry();
-};
-
-/**
- * @return {Array<Entry>} Array of selected entries.
- * @private
- */
-DirectoryModel.prototype.getSelectedEntries_ = function() {
-  const indexes = this.fileListSelection_.selectedIndexes;
-  const fileList = this.getFileList();
-  if (fileList) {
-    return indexes.map(i => {
-      return fileList.item(i);
-    });
-  }
-  return [];
-};
-
-/**
- * @param {Array<Entry>} value List of selected entries.
- * @private
- */
-DirectoryModel.prototype.setSelectedEntries_ = function(value) {
-  const indexes = [];
-  const fileList = this.getFileList();
-  const urls = util.entriesToURLs(value);
-
-  for (let i = 0; i < fileList.length; i++) {
-    if (urls.indexOf(fileList.item(i).toURL()) !== -1) {
-      indexes.push(i);
-    }
-  }
-  this.fileListSelection_.selectedIndexes = indexes;
-};
-
-/**
- * @return {Entry} Lead entry.
- * @private
- */
-DirectoryModel.prototype.getLeadEntry_ = function() {
-  const index = this.fileListSelection_.leadIndex;
-  return index >= 0 ?
-      /** @type {Entry} */ (this.getFileList().item(index)) :
-      null;
-};
-
-/**
- * @param {Entry} value The new lead entry.
- * @private
- */
-DirectoryModel.prototype.setLeadEntry_ = function(value) {
-  const fileList = this.getFileList();
-  for (let i = 0; i < fileList.length; i++) {
-    if (util.isSameEntry(/** @type {Entry} */ (fileList.item(i)), value)) {
-      this.fileListSelection_.leadIndex = i;
-      return;
-    }
-  }
-};
-
-/**
- * Schedule rescan with short delay.
- * @param {boolean} refresh True to refresh metadata, or false to use cached
- *     one.
- */
-DirectoryModel.prototype.rescanSoon = function(refresh) {
-  this.scheduleRescan(SHORT_RESCAN_INTERVAL, refresh);
-};
-
-/**
- * Schedule rescan with delay. Designed to handle directory change
- * notification.
- * @param {boolean} refresh True to refresh metadata, or false to use cached
- *     one.
- */
-DirectoryModel.prototype.rescanLater = function(refresh) {
-  this.scheduleRescan(SIMULTANEOUS_RESCAN_INTERVAL, refresh);
-};
-
-/**
- * Schedule rescan with delay. If another rescan has been scheduled does
- * nothing. File operation may cause a few notifications what should cause
- * a single refresh.
- * @param {number} delay Delay in ms after which the rescan will be performed.
- * @param {boolean} refresh True to refresh metadata, or false to use cached
- *     one.
- */
-DirectoryModel.prototype.scheduleRescan = function(delay, refresh) {
-  if (this.rescanTime_) {
-    if (this.rescanTime_ <= Date.now() + delay) {
-      return;
-    }
-    clearTimeout(this.rescanTimeoutId_);
-  }
-
-  const sequence = this.changeDirectorySequence_;
-
-  this.rescanTime_ = Date.now() + delay;
-  this.rescanTimeoutId_ = setTimeout(() => {
-    this.rescanTimeoutId_ = null;
-    if (sequence === this.changeDirectorySequence_) {
-      this.rescan(refresh);
-    }
-  }, delay);
-};
-
-/**
- * Cancel a rescan on timeout if it is scheduled.
- * @private
- */
-DirectoryModel.prototype.clearRescanTimeout_ = function() {
-  this.rescanTime_ = null;
-  if (this.rescanTimeoutId_) {
-    clearTimeout(this.rescanTimeoutId_);
-    this.rescanTimeoutId_ = null;
-  }
-};
-
-/**
- * Rescan current directory. May be called indirectly through rescanLater or
- * directly in order to reflect user action. Will first cache all the directory
- * contents in an array, then seamlessly substitute the fileList contents,
- * preserving the select element etc.
- *
- * This should be to scan the contents of current directory (or search).
- *
- * @param {boolean} refresh True to refresh metadata, or false to use cached
- *     one.
- */
-DirectoryModel.prototype.rescan = function(refresh) {
-  this.clearRescanTimeout_();
-  if (this.runningScan_) {
-    this.pendingRescan_ = true;
-    return;
-  }
-
-  const dirContents = this.currentDirContents_.clone();
-  dirContents.setFileList(new FileListModel(this.metadataModel_));
-  dirContents.setMetadataSnapshot(
-      this.currentDirContents_.createMetadataSnapshot());
-
-  const sequence = this.changeDirectorySequence_;
-
-  const successCallback = () => {
-    if (sequence === this.changeDirectorySequence_) {
-      this.replaceDirectoryContents_(dirContents);
-      cr.dispatchSimpleEvent(this, 'rescan-completed');
-    }
-  };
-
-  this.scan_(
-      dirContents, refresh, successCallback, () => {}, () => {}, () => {});
-};
-
-/**
- * Run scan on the current DirectoryContents. The active fileList is cleared and
- * the entries are added directly.
- *
- * This should be used when changing directory or initiating a new search.
- *
- * @param {DirectoryContents} newDirContents New DirectoryContents instance to
- *     replace currentDirContents_.
- * @param {function(boolean)} callback Callback with result. True if the scan
- *     is completed successfully, false if the scan is failed.
- * @private
- */
-DirectoryModel.prototype.clearAndScan_ = function(newDirContents, callback) {
-  if (this.currentDirContents_.isScanning()) {
-    this.currentDirContents_.cancelScan();
-  }
-  this.currentDirContents_ = newDirContents;
-  this.clearRescanTimeout_();
-
-  if (this.pendingScan_) {
-    this.pendingScan_ = false;
-  }
-
-  if (this.runningScan_) {
-    if (this.runningScan_.isScanning()) {
-      this.runningScan_.cancelScan();
-    }
-    this.runningScan_ = null;
-  }
-
-  const sequence = this.changeDirectorySequence_;
-  let cancelled = false;
-
-  const onDone = () => {
-    if (cancelled) {
-      return;
-    }
-
-    cr.dispatchSimpleEvent(this, 'scan-completed');
-    callback(true);
-  };
-
-  /** @param {DOMError} error error. */
-  const onFailed = error => {
-    if (cancelled) {
-      return;
-    }
-
-    const event = new Event('scan-failed');
-    event.error = error;
-    this.dispatchEvent(event);
-    callback(false);
-  };
-
-  const onUpdated = () => {
-    if (cancelled) {
-      return;
-    }
-
-    if (this.changeDirectorySequence_ !== sequence) {
-      cancelled = true;
-      cr.dispatchSimpleEvent(this, 'scan-cancelled');
-      callback(false);
-      return;
-    }
-
-    cr.dispatchSimpleEvent(this, 'scan-updated');
-  };
-
-  const onCancelled = () => {
-    if (cancelled) {
-      return;
-    }
-
-    cancelled = true;
-    cr.dispatchSimpleEvent(this, 'scan-cancelled');
-    callback(false);
-  };
-
-  // Clear metadata information for the old (no longer visible) items in the
-  // file list.
-  const fileList = this.getFileList();
-  let removedUrls = [];
-  for (let i = 0; i < fileList.length; i++) {
-    removedUrls.push(fileList.item(i).toURL());
-  }
-  this.metadataModel_.notifyEntriesRemoved(removedUrls);
-
-  // Retrieve metadata information for the newly selected directory.
-  const currentEntry = this.currentDirContents_.getDirectoryEntry();
-  if (currentEntry && !util.isFakeEntry(assert(currentEntry))) {
-    this.metadataModel_.get(
-        [currentEntry],
-        constants.LIST_CONTAINER_METADATA_PREFETCH_PROPERTY_NAMES);
-  }
-
-  // Clear the table, and start scanning.
-  cr.dispatchSimpleEvent(this, 'scan-started');
-  fileList.splice(0, fileList.length);
-  this.scan_(
-      this.currentDirContents_, false, onDone, onFailed, onUpdated,
-      onCancelled);
-};
-
-/**
- * Adds/removes/updates items of file list.
- * @param {Array<Entry>} changedEntries Entries of updated/added files.
- * @param {Array<string>} removedUrls URLs of removed files.
- * @private
- */
-DirectoryModel.prototype.partialUpdate_ = function(
-    changedEntries, removedUrls) {
-  // This update should be included in the current running update.
-  if (this.pendingScan_) {
-    return;
-  }
-
-  if (this.runningScan_) {
-    // Do update after the current scan is finished.
-    const previousScan = this.runningScan_;
-    const onPreviousScanCompleted = () => {
-      previousScan.removeEventListener(
-          'scan-completed', onPreviousScanCompleted);
-      // Run the update asynchronously.
-      Promise.resolve().then(() => {
-        this.partialUpdate_(changedEntries, removedUrls);
-      });
-    };
-    previousScan.addEventListener('scan-completed', onPreviousScanCompleted);
-    return;
-  }
-
-  const onFinish = () => {
-    this.runningScan_ = null;
-
-    this.currentDirContents_.removeEventListener('scan-completed', onCompleted);
-    this.currentDirContents_.removeEventListener('scan-failed', onFailure);
-    this.currentDirContents_.removeEventListener('scan-cancelled', onCancelled);
-  };
-
-  const onCompleted = () => {
-    onFinish();
-    cr.dispatchSimpleEvent(this, 'rescan-completed');
-  };
-
-  const onFailure = () => {
-    onFinish();
-  };
-
-  const onCancelled = () => {
-    onFinish();
-  };
-
-  this.runningScan_ = this.currentDirContents_;
-  this.currentDirContents_.addEventListener('scan-completed', onCompleted);
-  this.currentDirContents_.addEventListener('scan-failed', onFailure);
-  this.currentDirContents_.addEventListener('scan-cancelled', onCancelled);
-  this.currentDirContents_.update(changedEntries, removedUrls);
-};
-
-/**
- * Perform a directory contents scan. Should be called only from rescan() and
- * clearAndScan_().
- *
- * @param {DirectoryContents} dirContents DirectoryContents instance on which
- *     the scan will be run.
- * @param {boolean} refresh True to refresh metadata, or false to use cached
- *     one.
- * @param {function()} successCallback Callback on success.
- * @param {function(DOMError)} failureCallback Callback on failure.
- * @param {function()} updatedCallback Callback on update. Only on the last
- *     update, {@code successCallback} is called instead of this.
- * @param {function()} cancelledCallback Callback on cancel.
- * @private
- */
-DirectoryModel.prototype.scan_ = function(
-    dirContents, refresh, successCallback, failureCallback, updatedCallback,
-    cancelledCallback) {
-  const self = this;
-
-  /**
-   * Runs pending scan if there is one.
-   *
-   * @return {boolean} Did pending scan exist.
-   */
-  const maybeRunPendingRescan = () => {
-    if (this.pendingRescan_) {
-      this.rescanSoon(refresh);
-      this.pendingRescan_ = false;
-      return true;
-    }
-    return false;
-  };
-
-  const onFinished = () => {
-    dirContents.removeEventListener('scan-completed', onSuccess);
-    dirContents.removeEventListener('scan-updated', updatedCallback);
-    dirContents.removeEventListener('scan-failed', onFailure);
-    dirContents.removeEventListener('scan-cancelled', cancelledCallback);
-  };
-
-  const onSuccess = () => {
-    onFinished();
-
-    // Record metric for Downloads directory.
-    if (!dirContents.isSearch()) {
-      const locationInfo = this.volumeManager_.getLocationInfo(
-          assert(dirContents.getDirectoryEntry()));
-      const volumeInfo = locationInfo && locationInfo.volumeInfo;
-      if (volumeInfo &&
-          volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DOWNLOADS &&
-          locationInfo.isRootEntry) {
-        metrics.recordMediumCount(
-            'DownloadsCount', dirContents.getFileListLength());
-      }
-    }
+    this.fileListSelection_ = singleSelection ?
+        new FileListSingleSelectionModel() :
+        new FileListSelectionModel();
 
     this.runningScan_ = null;
-    successCallback();
+    this.pendingScan_ = null;
+    this.pendingRescan_ = null;
+    this.rescanTime_ = null;
     this.scanFailures_ = 0;
-    maybeRunPendingRescan();
-  };
+    this.changeDirectorySequence_ = 0;
 
-  const onFailure = event => {
-    onFinished();
+    /** @private {?function(Event): void} */
+    this.onSearchCompleted_ = null;
+    /** @private {?Function} */
+    this.onClearSearch_ = null;
 
-    this.runningScan_ = null;
-    this.scanFailures_++;
-    failureCallback(event.error);
+    /**
+     * @private {boolean}
+     */
+    this.ignoreCurrentDirectoryDeletion_ = false;
 
-    if (maybeRunPendingRescan()) {
-      return;
-    }
+    this.directoryChangeQueue_ = new AsyncUtil.Queue();
+    this.rescanAggregator_ =
+        new AsyncUtil.Aggregator(this.rescanSoon.bind(this, true), 500);
 
-    // Do not rescan for crostini errors.
-    if (event.error.name === DirectoryModel.CROSTINI_CONNECT_ERR) {
-      return;
-    }
+    this.fileFilter_ = fileFilter;
+    this.fileFilter_.addEventListener(
+        'changed', this.onFilterChanged_.bind(this));
 
-    if (this.scanFailures_ <= 1) {
-      this.rescanLater(refresh);
-    }
-  };
+    this.currentFileListContext_ =
+        new FileListContext(fileFilter, metadataModel, volumeManager);
+    this.currentDirContents_ = DirectoryContents.createForDirectory(
+        this.currentFileListContext_, null);
+    /**
+     * Empty file list which is used as a dummy for inactive view of file list.
+     * @private {!FileListModel}
+     */
+    this.emptyFileList_ = new FileListModel(metadataModel);
 
-  const onCancelled = () => {
-    onFinished();
-    cancelledCallback();
-  };
+    this.metadataModel_ = metadataModel;
 
-  this.runningScan_ = dirContents;
+    this.volumeManager_ = volumeManager;
+    this.volumeManager_.volumeInfoList.addEventListener(
+        'splice', this.onVolumeInfoListUpdated_.bind(this));
 
-  dirContents.addEventListener('scan-completed', onSuccess);
-  dirContents.addEventListener('scan-updated', updatedCallback);
-  dirContents.addEventListener('scan-failed', onFailure);
-  dirContents.addEventListener('scan-cancelled', onCancelled);
-  dirContents.scan(refresh);
-};
+    /**
+     * File watcher.
+     * @private {!FileWatcher}
+     * @const
+     */
+    this.fileWatcher_ = new FileWatcher();
+    this.fileWatcher_.addEventListener(
+        'watcher-directory-changed',
+        this.onWatcherDirectoryChanged_.bind(this));
+    util.addEventListenerToBackgroundComponent(
+        fileOperationManager, 'entries-changed',
+        this.onEntriesChanged_.bind(this));
 
-/**
- * @param {DirectoryContents} dirContents DirectoryContents instance. This must
- *     be a different instance from this.currentDirContents_.
- * @private
- */
-DirectoryModel.prototype.replaceDirectoryContents_ = function(dirContents) {
-  console.assert(
-      this.currentDirContents_ !== dirContents,
-      'Give directory contents instance must be different from current one.');
-  cr.dispatchSimpleEvent(this, 'begin-update-files');
-  this.updateSelectionAndPublishEvent_(this.fileListSelection_, () => {
-    const selectedEntries = this.getSelectedEntries_();
-    const selectedIndices = this.fileListSelection_.selectedIndexes;
+    /** @private {string} */
+    this.lastSearchQuery_ = '';
 
-    // Restore leadIndex in case leadName no longer exists.
-    const leadIndex = this.fileListSelection_.leadIndex;
-    const leadEntry = this.getLeadEntry_();
-    const isCheckSelectMode = this.fileListSelection_.getCheckSelectMode();
-
-    const previousDirContents = this.currentDirContents_;
-    this.currentDirContents_ = dirContents;
-    this.currentDirContents_.replaceContextFileList();
-
-    this.setSelectedEntries_(selectedEntries);
-    this.fileListSelection_.leadIndex = leadIndex;
-    this.setLeadEntry_(leadEntry);
-
-    // If nothing is selected after update, then select file next to the
-    // latest selection
-    let forceChangeEvent = false;
-    if (this.fileListSelection_.selectedIndexes.length == 0 &&
-        selectedIndices.length != 0) {
-      const maxIdx = Math.max.apply(null, selectedIndices);
-      this.selectIndex(
-          Math.min(
-              maxIdx - selectedIndices.length + 2, this.getFileList().length) -
-          1);
-      forceChangeEvent = true;
-    } else if (isCheckSelectMode) {
-      // Otherwise, ensure check select mode is retained if it was previously
-      // active.
-      this.fileListSelection_.setCheckSelectMode(true);
-    }
-    return forceChangeEvent;
-  });
-
-  cr.dispatchSimpleEvent(this, 'end-update-files');
-};
-
-/**
- * Callback when an entry is changed.
- * @param {EntriesChangedEvent} event Entry change event.
- * @private
- */
-DirectoryModel.prototype.onEntriesChanged_ = function(event) {
-  const kind = event.kind;
-  const entries = event.entries;
-  // TODO(hidehiko): We should update directory model even the search result
-  // is shown.
-  const rootType = this.getCurrentRootType();
-  if ((rootType === VolumeManagerCommon.RootType.DRIVE ||
-       rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
-       rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
-       rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE) &&
-      this.isSearching()) {
-    return;
+    /** @private {FilesAppDirEntry} */
+    this.myFilesEntry_ = null;
   }
 
-  switch (kind) {
-    case util.EntryChangedKind.CREATED:
-      const parentPromises = [];
-      for (let i = 0; i < entries.length; i++) {
-        parentPromises.push(new Promise((resolve, reject) => {
-          entries[i].getParent(resolve, reject);
-        }));
+  /**
+   * Disposes the directory model by removing file watchers.
+   */
+  dispose() {
+    this.fileWatcher_.dispose();
+  }
+
+  /**
+   * @return {FileListModel} Files in the current directory.
+   */
+  getFileList() {
+    return this.currentFileListContext_.fileList;
+  }
+
+  /**
+   * @return {!FileListModel} File list which is always empty.
+   */
+  getEmptyFileList() {
+    return this.emptyFileList_;
+  }
+
+  /**
+   * @return {!FileListSelectionModel|!FileListSingleSelectionModel} Selection
+   * in the fileList.
+   */
+  getFileListSelection() {
+    return this.fileListSelection_;
+  }
+
+  /**
+   * Obtains current volume information.
+   * @return {VolumeInfo}
+   */
+  getCurrentVolumeInfo() {
+    const entry = this.getCurrentDirEntry();
+    if (!entry) {
+      return null;
+    }
+    return this.volumeManager_.getVolumeInfo(entry);
+  }
+
+  /**
+   * @return {?VolumeManagerCommon.RootType} Root type of current root, or null
+   *     if not found.
+   */
+  getCurrentRootType() {
+    const entry = this.currentDirContents_.getDirectoryEntry();
+    if (!entry) {
+      return null;
+    }
+
+    const locationInfo = this.volumeManager_.getLocationInfo(entry);
+    if (!locationInfo) {
+      return null;
+    }
+
+    return locationInfo.rootType;
+  }
+
+  /**
+   * Metadata property names that are expected to be Prefetched.
+   * @return {!Array<string>}
+   */
+  getPrefetchPropertyNames() {
+    return this.currentFileListContext_.prefetchPropertyNames;
+  }
+
+  /**
+   * @return {boolean} True if the current directory is read only. If there is
+   *     no entry set, then returns true.
+   */
+  isReadOnly() {
+    const currentDirEntry = this.getCurrentDirEntry();
+    if (currentDirEntry) {
+      const locationInfo = this.volumeManager_.getLocationInfo(currentDirEntry);
+      if (locationInfo) {
+        return locationInfo.isReadOnly;
       }
-      Promise.all(parentPromises)
-          .then(parents => {
-            const entriesToAdd = [];
-            for (let i = 0; i < parents.length; i++) {
-              if (!util.isSameEntry(parents[i], this.getCurrentDirEntry())) {
-                continue;
-              }
-              const index = this.findIndexByEntry_(entries[i]);
-              if (index >= 0) {
-                this.getFileList().replaceItem(
-                    this.getFileList().item(index), entries[i]);
-              } else {
-                entriesToAdd.push(entries[i]);
-              }
+    }
+    return true;
+  }
+
+  /**
+   * @return {boolean} True if the a scan is active.
+   */
+  isScanning() {
+    return this.currentDirContents_.isScanning();
+  }
+
+  /**
+   * @return {boolean} True if search is in progress.
+   */
+  isSearching() {
+    return this.currentDirContents_.isSearch();
+  }
+
+  /**
+   * @return {boolean} True if it's on Drive.
+   */
+  isOnDrive() {
+    return this.isCurrentRootVolumeType_(VolumeManagerCommon.VolumeType.DRIVE);
+  }
+
+  /**
+   * @return {boolean} True if it's on MTP volume.
+   */
+  isOnMTP() {
+    return this.isCurrentRootVolumeType_(VolumeManagerCommon.VolumeType.MTP);
+  }
+
+  /**
+   * @param {VolumeManagerCommon.VolumeType} volumeType Volume Type
+   * @return {boolean} True if current root volume type is equal to specified
+   *     volume type.
+   * @private
+   */
+  isCurrentRootVolumeType_(volumeType) {
+    const rootType = this.getCurrentRootType();
+    return rootType != null &&
+        rootType != VolumeManagerCommon.RootType.RECENT &&
+        VolumeManagerCommon.getVolumeTypeFromRootType(rootType) === volumeType;
+  }
+
+  /**
+   * Updates the selection by using the updateFunc and publish the change event.
+   * If updateFunc returns true, it force to dispatch the change event even if
+   * the selection index is not changed.
+   *
+   * @param {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} selection
+   *     Selection to be updated.
+   * @param {function(): boolean} updateFunc Function updating the selection.
+   * @private
+   */
+  updateSelectionAndPublishEvent_(selection, updateFunc) {
+    // Begin change.
+    selection.beginChange();
+
+    // If dispatchNeeded is true, we should ensure the change event is
+    // dispatched.
+    let dispatchNeeded = updateFunc();
+
+    // Check if the change event is dispatched in the endChange function
+    // or not.
+    const eventDispatched = () => {
+      dispatchNeeded = false;
+    };
+    selection.addEventListener('change', eventDispatched);
+    selection.endChange();
+    selection.removeEventListener('change', eventDispatched);
+
+    // If the change event have been already dispatched, dispatchNeeded is
+    // false.
+    if (dispatchNeeded) {
+      const event = new Event('change');
+      // The selection status (selected or not) is not changed because
+      // this event is caused by the change of selected item.
+      event.changes = [];
+      selection.dispatchEvent(event);
+    }
+  }
+
+  /**
+   * Sets to ignore current directory deletion. This method is used to prevent
+   * going up to the volume root with the deletion of current directory by
+   * rename operation in directory tree.
+   * @param {boolean} value True to ignore current directory deletion.
+   */
+  setIgnoringCurrentDirectoryDeletion(value) {
+    this.ignoreCurrentDirectoryDeletion_ = value;
+  }
+
+  /**
+   * Invoked when a change in the directory is detected by the watcher.
+   * @param {Event} event Event object.
+   * @private
+   */
+  onWatcherDirectoryChanged_(event) {
+    const directoryEntry = this.getCurrentDirEntry();
+
+    if (!this.ignoreCurrentDirectoryDeletion_) {
+      // If the change is deletion of currentDir, move up to its parent
+      // directory.
+      directoryEntry.getDirectory(
+          directoryEntry.fullPath, {create: false}, () => {}, () => {
+            const volumeInfo =
+                this.volumeManager_.getVolumeInfo(assert(directoryEntry));
+            if (volumeInfo) {
+              volumeInfo.resolveDisplayRoot().then(displayRoot => {
+                this.changeDirectoryEntry(displayRoot);
+              });
             }
-            this.partialUpdate_(entriesToAdd, []);
+          });
+    }
+
+    if (event.changedFiles) {
+      const addedOrUpdatedFileUrls = [];
+      let deletedFileUrls = [];
+      event.changedFiles.forEach(change => {
+        if (change.changes.length === 1 && change.changes[0] === 'delete') {
+          deletedFileUrls.push(change.url);
+        } else {
+          addedOrUpdatedFileUrls.push(change.url);
+        }
+      });
+
+      util.URLsToEntries(addedOrUpdatedFileUrls)
+          .then(result => {
+            deletedFileUrls = deletedFileUrls.concat(result.failureUrls);
+
+            // Passing the resolved entries and failed URLs as the removed
+            // files. The URLs are removed files and they chan't be resolved.
+            this.partialUpdate_(result.entries, deletedFileUrls);
           })
           .catch(error => {
-            console.error(error.stack || error);
+            console.error(
+                'Error in proceeding the changed event.', error,
+                'Fallback to force-refresh');
+            this.rescanAggregator_.run();
           });
-      break;
-
-    case util.EntryChangedKind.DELETED:
-      // This is the delete event.
-      this.partialUpdate_([], util.entriesToURLs(entries));
-      break;
-
-    default:
-      console.error('Invalid EntryChangedKind: ' + kind);
-      break;
-  }
-};
-
-/**
- * @param {Entry} entry The entry to be searched.
- * @return {number} The index in the fileList, or -1 if not found.
- * @private
- */
-DirectoryModel.prototype.findIndexByEntry_ = function(entry) {
-  const fileList = this.getFileList();
-  for (let i = 0; i < fileList.length; i++) {
-    if (util.isSameEntry(/** @type {Entry} */ (fileList.item(i)), entry)) {
-      return i;
-    }
-  }
-  return -1;
-};
-
-/**
- * Called when rename is done successfully.
- * Note: conceptually, DirectoryModel should work without this, because entries
- * can be renamed by other systems anytime and the Files app should reflect it
- * correctly.
- * TODO(hidehiko): investigate more background, and remove this if possible.
- *
- * @param {!Entry} oldEntry The old entry.
- * @param {!Entry} newEntry The new entry.
- * @param {function()=} opt_callback Called on completion.
- */
-DirectoryModel.prototype.onRenameEntry = function(
-    oldEntry, newEntry, opt_callback) {
-  this.currentDirContents_.prefetchMetadata([newEntry], true, () => {
-    // If the current directory is the old entry, then quietly change to the
-    // new one.
-    if (util.isSameEntry(oldEntry, this.getCurrentDirEntry())) {
-      this.changeDirectoryEntry(
-          /** @type {!DirectoryEntry|!FilesAppDirEntry} */ (newEntry));
-    }
-
-    // Replace the old item with the new item. oldEntry instance itself may
-    // have been removed/replaced from the list during the async process, we
-    // find an entry which should be replaced by checking toURL().
-    const list = this.getFileList();
-    let oldEntryExist = false;
-    let newEntryExist = false;
-    const oldEntryUrl = oldEntry.toURL();
-    const newEntryUrl = newEntry.toURL();
-
-    for (let i = 0; i < list.length; i++) {
-      const item = list.item(i);
-      const url = item.toURL();
-      if (url === oldEntryUrl) {
-        list.replaceItem(item, newEntry);
-        oldEntryExist = true;
-        break;
-      }
-
-      if (url === newEntryUrl) {
-        newEntryExist = true;
-      }
-    }
-
-    // When both old and new entries don't exist, it may be in the middle of
-    // update process. In DirectoryContent.update deletion is executed at first
-    // and insertion is executed as a async call. There is a chance that this
-    // method is called in the middle of update process.
-    if (!oldEntryExist && !newEntryExist) {
-      list.push(newEntry);
-    }
-
-    // Run callback, finally.
-    if (opt_callback) {
-      opt_callback();
-    }
-  });
-};
-
-/**
- * Updates data model and selects new directory.
- * @param {!DirectoryEntry} newDirectory Directory entry to be selected.
- * @return {Promise} A promise which is resolved when new directory is selected.
- *     If current directory has changed during the operation, this will be
- *     rejected.
- */
-DirectoryModel.prototype.updateAndSelectNewDirectory = function(newDirectory) {
-  // Refresh the cache.
-  this.metadataModel_.notifyEntriesCreated([newDirectory]);
-  const dirContents = this.currentDirContents_;
-
-  return new Promise((onFulfilled, onRejected) => {
-           dirContents.prefetchMetadata([newDirectory], false, onFulfilled);
-         })
-      .then((sequence => {
-              // If current directory has changed during the prefetch, do not
-              // try to select new directory.
-              if (sequence !== this.changeDirectorySequence_) {
-                return Promise.reject();
-              }
-
-              // If target directory is already in the list, just select it.
-              const existing = this.getFileList().slice().filter(e => {
-                return e.name === newDirectory.name;
-              });
-              if (existing.length) {
-                this.selectEntry(newDirectory);
-              } else {
-                this.fileListSelection_.beginChange();
-                this.getFileList().splice(0, 0, newDirectory);
-                this.selectEntry(newDirectory);
-                this.fileListSelection_.endChange();
-              }
-            }).bind(null, this.changeDirectorySequence_));
-};
-
-/**
- * Sets the current MyFilesEntry.
- * @param {FilesAppDirEntry} myFilesEntry
- */
-DirectoryModel.prototype.setMyFiles = function(myFilesEntry) {
-  this.myFilesEntry_ = myFilesEntry;
-};
-
-/**
- * Changes the current directory to the directory represented by
- * a DirectoryEntry or a fake entry.
- *
- * Dispatches the 'directory-changed' event when the directory is successfully
- * changed.
- *
- * Note : if this is called from UI, please consider to use DirectoryModel.
- * activateDirectoryEntry instead of this, which is higher-level function and
- * cares about the selection.
- *
- * @param {!DirectoryEntry|!FilesAppDirEntry} dirEntry The entry of the new
- *     directory to be opened.
- * @param {function()=} opt_callback Executed if the directory loads
- *     successfully.
- */
-DirectoryModel.prototype.changeDirectoryEntry = function(
-    dirEntry, opt_callback) {
-  // Increment the sequence value.
-  this.changeDirectorySequence_++;
-  this.clearSearch_();
-
-  // When switching to MyFiles volume, we should use a FilesAppEntry if
-  // available because it returns UI-only entries too, like Linux files and Play
-  // files.
-  const locationInfo = this.volumeManager_.getLocationInfo(dirEntry);
-  if (util.isMyFilesVolumeEnabled() && locationInfo && this.myFilesEntry_ &&
-      locationInfo.rootType === VolumeManagerCommon.RootType.DOWNLOADS &&
-      locationInfo.isRootEntry) {
-    dirEntry = this.myFilesEntry_;
-  }
-
-  // If there is on-going scan, cancel it.
-  if (this.currentDirContents_.isScanning()) {
-    this.currentDirContents_.cancelScan();
-  }
-
-  this.directoryChangeQueue_.run(
-      ((sequence, queueTaskCallback) => {
-        this.fileWatcher_.changeWatchedDirectory(dirEntry).then(() => {
-          if (this.changeDirectorySequence_ !== sequence) {
-            queueTaskCallback();
-            return;
-          }
-
-          const newDirectoryContents = this.createDirectoryContents_(
-              this.currentFileListContext_, dirEntry, '');
-          if (!newDirectoryContents) {
-            queueTaskCallback();
-            return;
-          }
-
-          const previousDirEntry = this.currentDirContents_.getDirectoryEntry();
-          this.clearAndScan_(newDirectoryContents, result => {
-            // Calls the callback of the method when successful.
-            if (result && opt_callback) {
-              opt_callback();
-            }
-
-            // Notify that the current task of this.directoryChangeQueue_
-            // is completed.
-            setTimeout(queueTaskCallback, 0);
-          });
-
-          // For tests that open the dialog to empty directories, everything
-          // is loaded at this point.
-          util.testSendMessage('directory-change-complete');
-          const previousVolumeInfo = previousDirEntry ?
-              this.volumeManager_.getVolumeInfo(previousDirEntry) :
-              null;
-          // VolumeInfo for dirEntry.
-          const currentVolumeInfo = this.getCurrentVolumeInfo();
-          const event = new Event('directory-changed');
-          event.previousDirEntry = previousDirEntry;
-          event.newDirEntry = dirEntry;
-          event.volumeChanged = previousVolumeInfo !== currentVolumeInfo;
-          this.dispatchEvent(event);
-        });
-      }).bind(null, this.changeDirectorySequence_));
-};
-
-/**
- * Activates the given directory.
- * This method:
- *  - Changes the current directory, if the given directory is not the current
- *    directory.
- *  - Clears the selection, if the given directory is the current directory.
- *
- * @param {!DirectoryEntry|!FilesAppDirEntry} dirEntry The entry of the new
- *     directory to be opened.
- * @param {function()=} opt_callback Executed if the directory loads
- *     successfully.
- */
-DirectoryModel.prototype.activateDirectoryEntry = function(
-    dirEntry, opt_callback) {
-  const currentDirectoryEntry = this.getCurrentDirEntry();
-  if (currentDirectoryEntry &&
-      util.isSameEntry(dirEntry, currentDirectoryEntry)) {
-    // On activating the current directory, clear the selection on the filelist.
-    this.clearSelection();
-  } else {
-    // Otherwise, changes the current directory.
-    this.changeDirectoryEntry(dirEntry, opt_callback);
-  }
-};
-
-/**
- * Clears the selection in the file list.
- */
-DirectoryModel.prototype.clearSelection = function() {
-  this.setSelectedEntries_([]);
-};
-
-/**
- * Creates an object which could say whether directory has changed while it has
- * been active or not. Designed for long operations that should be cancelled
- * if the used change current directory.
- * @return {Object} Created object.
- */
-DirectoryModel.prototype.createDirectoryChangeTracker = function() {
-  const tracker = {
-    dm_: this,
-    active_: false,
-    hasChanged: false,
-
-    start: function() {
-      if (!this.active_) {
-        this.dm_.addEventListener('directory-changed', this.onDirectoryChange_);
-        this.active_ = true;
-        this.hasChanged = false;
-      }
-    },
-
-    stop: function() {
-      if (this.active_) {
-        this.dm_.removeEventListener(
-            'directory-changed', this.onDirectoryChange_);
-        this.active_ = false;
-      }
-    },
-
-    onDirectoryChange_: function(event) {
-      tracker.stop();
-      tracker.hasChanged = true;
-    }
-  };
-  return tracker;
-};
-
-/**
- * @param {Entry} entry Entry to be selected.
- */
-DirectoryModel.prototype.selectEntry = function(entry) {
-  const fileList = this.getFileList();
-  for (let i = 0; i < fileList.length; i++) {
-    if (fileList.item(i).toURL() === entry.toURL()) {
-      this.selectIndex(i);
-      return;
-    }
-  }
-};
-
-/**
- * @param {Array<Entry>} entries Array of entries.
- */
-DirectoryModel.prototype.selectEntries = function(entries) {
-  // URLs are needed here, since we are comparing Entries by URLs.
-  const urls = util.entriesToURLs(entries);
-  const fileList = this.getFileList();
-  this.fileListSelection_.beginChange();
-  this.fileListSelection_.unselectAll();
-  for (let i = 0; i < fileList.length; i++) {
-    if (urls.indexOf(fileList.item(i).toURL()) >= 0) {
-      this.fileListSelection_.setIndexSelected(i, true);
-    }
-  }
-  this.fileListSelection_.endChange();
-};
-
-/**
- * @param {number} index Index of file.
- */
-DirectoryModel.prototype.selectIndex = function(index) {
-  // this.focusCurrentList_();
-  if (index >= this.getFileList().length) {
-    return;
-  }
-
-  // If a list bound with the model it will do scrollIndexIntoView(index).
-  this.fileListSelection_.selectedIndex = index;
-};
-
-/**
- * Handles update of VolumeInfoList.
- * @param {Event} event Event of VolumeInfoList's 'splice'.
- * @private
- */
-DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) {
-  // Fallback to the default volume's root if the current volume is unmounted.
-  if (this.hasCurrentDirEntryBeenUnmounted_(event.removed)) {
-    this.volumeManager_.getDefaultDisplayRoot((displayRoot) => {
-      if (displayRoot) {
-        this.changeDirectoryEntry(displayRoot);
-      }
-    });
-  }
-
-  // If a volume within My files is mounted, rescan the contents.
-  // TODO(crbug.com/901690): Remove this special case.
-  if (this.getCurrentRootType() === VolumeManagerCommon.RootType.MY_FILES) {
-    for (let newVolume of event.added) {
-      if (newVolume.volumeType === VolumeManagerCommon.VolumeType.DOWNLOADS ||
-          newVolume.volumeType ===
-              VolumeManagerCommon.VolumeType.ANDROID_FILES ||
-          newVolume.volumeType === VolumeManagerCommon.VolumeType.CROSTINI) {
-        this.rescan(false);
-        break;
-      }
+    } else {
+      // Invokes force refresh if the detailed information isn't provided.
+      // This can occur very frequently (e.g. when copying files into Downlaods)
+      // and rescan is heavy operation, so we keep some interval for each
+      // rescan.
+      this.rescanAggregator_.run();
     }
   }
 
-  // If the current directory is the Drive placeholder and the real Drive is
-  // mounted, switch to it.
-  if (this.getCurrentRootType() ===
-      VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT) {
-    for (let newVolume of event.added) {
-      if (newVolume.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
-        newVolume.resolveDisplayRoot().then((displayRoot) => {
+  /**
+   * Invoked when filters are changed.
+   * @private
+   */
+  onFilterChanged_() {
+    const currentDirectory = this.getCurrentDirEntry();
+    if (currentDirectory && util.isNativeEntry(currentDirectory) &&
+        !this.fileFilter_.filter(
+            /** @type {!DirectoryEntry} */ (currentDirectory))) {
+      // If the current directory should be hidden in the new filter setting,
+      // change the current directory to the current volume's root.
+      const volumeInfo = this.volumeManager_.getVolumeInfo(currentDirectory);
+      if (volumeInfo) {
+        volumeInfo.resolveDisplayRoot().then(displayRoot => {
           this.changeDirectoryEntry(displayRoot);
         });
       }
+    } else {
+      this.rescanSoon(false);
     }
   }
-  // If a new file backed provided volume is mounted,
-  // then redirect to it in the focused window.
-  // Note, that this is a temporary solution for https://crbug.com/427776.
-  // If crostini is mounted, redirect if it is the currently selected dir.
-  if (event.added.length !== 1) {
-    return;
+
+  /**
+   * Returns the filter.
+   * @return {FileFilter} The file filter.
+   */
+  getFileFilter() {
+    return this.fileFilter_;
   }
-  if ((window.isFocused() &&
-       event.added[0].volumeType === VolumeManagerCommon.VolumeType.PROVIDED &&
-       event.added[0].source === VolumeManagerCommon.Source.FILE) ||
-      (event.added[0].volumeType === VolumeManagerCommon.VolumeType.CROSTINI &&
-       this.getCurrentRootType() === VolumeManagerCommon.RootType.CROSTINI)) {
-    event.added[0].resolveDisplayRoot().then((displayRoot) => {
-      // Resolving a display root on FSP volumes is instant, despite the
-      // asynchronous call.
-      this.changeDirectoryEntry(event.added[0].displayRoot);
+
+  /**
+   * @return {DirectoryEntry|FakeEntry|FilesAppDirEntry} Current directory.
+   */
+  getCurrentDirEntry() {
+    return this.currentDirContents_.getDirectoryEntry();
+  }
+
+  /**
+   * @return {Array<Entry>} Array of selected entries.
+   * @private
+   */
+  getSelectedEntries_() {
+    const indexes = this.fileListSelection_.selectedIndexes;
+    const fileList = this.getFileList();
+    if (fileList) {
+      return indexes.map(i => {
+        return fileList.item(i);
+      });
+    }
+    return [];
+  }
+
+  /**
+   * @param {Array<Entry>} value List of selected entries.
+   * @private
+   */
+  setSelectedEntries_(value) {
+    const indexes = [];
+    const fileList = this.getFileList();
+    const urls = util.entriesToURLs(value);
+
+    for (let i = 0; i < fileList.length; i++) {
+      if (urls.indexOf(fileList.item(i).toURL()) !== -1) {
+        indexes.push(i);
+      }
+    }
+    this.fileListSelection_.selectedIndexes = indexes;
+  }
+
+  /**
+   * @return {Entry} Lead entry.
+   * @private
+   */
+  getLeadEntry_() {
+    const index = this.fileListSelection_.leadIndex;
+    return index >= 0 ?
+        /** @type {Entry} */ (this.getFileList().item(index)) :
+        null;
+  }
+
+  /**
+   * @param {Entry} value The new lead entry.
+   * @private
+   */
+  setLeadEntry_(value) {
+    const fileList = this.getFileList();
+    for (let i = 0; i < fileList.length; i++) {
+      if (util.isSameEntry(/** @type {Entry} */ (fileList.item(i)), value)) {
+        this.fileListSelection_.leadIndex = i;
+        return;
+      }
+    }
+  }
+
+  /**
+   * Schedule rescan with short delay.
+   * @param {boolean} refresh True to refresh metadata, or false to use cached
+   *     one.
+   */
+  rescanSoon(refresh) {
+    this.scheduleRescan(SHORT_RESCAN_INTERVAL, refresh);
+  }
+
+  /**
+   * Schedule rescan with delay. Designed to handle directory change
+   * notification.
+   * @param {boolean} refresh True to refresh metadata, or false to use cached
+   *     one.
+   */
+  rescanLater(refresh) {
+    this.scheduleRescan(SIMULTANEOUS_RESCAN_INTERVAL, refresh);
+  }
+
+  /**
+   * Schedule rescan with delay. If another rescan has been scheduled does
+   * nothing. File operation may cause a few notifications what should cause
+   * a single refresh.
+   * @param {number} delay Delay in ms after which the rescan will be performed.
+   * @param {boolean} refresh True to refresh metadata, or false to use cached
+   *     one.
+   */
+  scheduleRescan(delay, refresh) {
+    if (this.rescanTime_) {
+      if (this.rescanTime_ <= Date.now() + delay) {
+        return;
+      }
+      clearTimeout(this.rescanTimeoutId_);
+    }
+
+    const sequence = this.changeDirectorySequence_;
+
+    this.rescanTime_ = Date.now() + delay;
+    this.rescanTimeoutId_ = setTimeout(() => {
+      this.rescanTimeoutId_ = null;
+      if (sequence === this.changeDirectorySequence_) {
+        this.rescan(refresh);
+      }
+    }, delay);
+  }
+
+  /**
+   * Cancel a rescan on timeout if it is scheduled.
+   * @private
+   */
+  clearRescanTimeout_() {
+    this.rescanTime_ = null;
+    if (this.rescanTimeoutId_) {
+      clearTimeout(this.rescanTimeoutId_);
+      this.rescanTimeoutId_ = null;
+    }
+  }
+
+  /**
+   * Rescan current directory. May be called indirectly through rescanLater or
+   * directly in order to reflect user action. Will first cache all the
+   * directory contents in an array, then seamlessly substitute the fileList
+   * contents, preserving the select element etc.
+   *
+   * This should be to scan the contents of current directory (or search).
+   *
+   * @param {boolean} refresh True to refresh metadata, or false to use cached
+   *     one.
+   */
+  rescan(refresh) {
+    this.clearRescanTimeout_();
+    if (this.runningScan_) {
+      this.pendingRescan_ = true;
+      return;
+    }
+
+    const dirContents = this.currentDirContents_.clone();
+    dirContents.setFileList(new FileListModel(this.metadataModel_));
+    dirContents.setMetadataSnapshot(
+        this.currentDirContents_.createMetadataSnapshot());
+
+    const sequence = this.changeDirectorySequence_;
+
+    const successCallback = () => {
+      if (sequence === this.changeDirectorySequence_) {
+        this.replaceDirectoryContents_(dirContents);
+        cr.dispatchSimpleEvent(this, 'rescan-completed');
+      }
+    };
+
+    this.scan_(
+        dirContents, refresh, successCallback, () => {}, () => {}, () => {});
+  }
+
+  /**
+   * Run scan on the current DirectoryContents. The active fileList is cleared
+   * and the entries are added directly.
+   *
+   * This should be used when changing directory or initiating a new search.
+   *
+   * @param {DirectoryContents} newDirContents New DirectoryContents instance to
+   *     replace currentDirContents_.
+   * @param {function(boolean)} callback Callback with result. True if the scan
+   *     is completed successfully, false if the scan is failed.
+   * @private
+   */
+  clearAndScan_(newDirContents, callback) {
+    if (this.currentDirContents_.isScanning()) {
+      this.currentDirContents_.cancelScan();
+    }
+    this.currentDirContents_ = newDirContents;
+    this.clearRescanTimeout_();
+
+    if (this.pendingScan_) {
+      this.pendingScan_ = false;
+    }
+
+    if (this.runningScan_) {
+      if (this.runningScan_.isScanning()) {
+        this.runningScan_.cancelScan();
+      }
+      this.runningScan_ = null;
+    }
+
+    const sequence = this.changeDirectorySequence_;
+    let cancelled = false;
+
+    const onDone = () => {
+      if (cancelled) {
+        return;
+      }
+
+      cr.dispatchSimpleEvent(this, 'scan-completed');
+      callback(true);
+    };
+
+    /** @param {DOMError} error error. */
+    const onFailed = error => {
+      if (cancelled) {
+        return;
+      }
+
+      const event = new Event('scan-failed');
+      event.error = error;
+      this.dispatchEvent(event);
+      callback(false);
+    };
+
+    const onUpdated = () => {
+      if (cancelled) {
+        return;
+      }
+
+      if (this.changeDirectorySequence_ !== sequence) {
+        cancelled = true;
+        cr.dispatchSimpleEvent(this, 'scan-cancelled');
+        callback(false);
+        return;
+      }
+
+      cr.dispatchSimpleEvent(this, 'scan-updated');
+    };
+
+    const onCancelled = () => {
+      if (cancelled) {
+        return;
+      }
+
+      cancelled = true;
+      cr.dispatchSimpleEvent(this, 'scan-cancelled');
+      callback(false);
+    };
+
+    // Clear metadata information for the old (no longer visible) items in the
+    // file list.
+    const fileList = this.getFileList();
+    let removedUrls = [];
+    for (let i = 0; i < fileList.length; i++) {
+      removedUrls.push(fileList.item(i).toURL());
+    }
+    this.metadataModel_.notifyEntriesRemoved(removedUrls);
+
+    // Retrieve metadata information for the newly selected directory.
+    const currentEntry = this.currentDirContents_.getDirectoryEntry();
+    if (currentEntry && !util.isFakeEntry(assert(currentEntry))) {
+      this.metadataModel_.get(
+          [currentEntry],
+          constants.LIST_CONTAINER_METADATA_PREFETCH_PROPERTY_NAMES);
+    }
+
+    // Clear the table, and start scanning.
+    cr.dispatchSimpleEvent(this, 'scan-started');
+    fileList.splice(0, fileList.length);
+    this.scan_(
+        this.currentDirContents_, false, onDone, onFailed, onUpdated,
+        onCancelled);
+  }
+
+  /**
+   * Adds/removes/updates items of file list.
+   * @param {Array<Entry>} changedEntries Entries of updated/added files.
+   * @param {Array<string>} removedUrls URLs of removed files.
+   * @private
+   */
+  partialUpdate_(changedEntries, removedUrls) {
+    // This update should be included in the current running update.
+    if (this.pendingScan_) {
+      return;
+    }
+
+    if (this.runningScan_) {
+      // Do update after the current scan is finished.
+      const previousScan = this.runningScan_;
+      const onPreviousScanCompleted = () => {
+        previousScan.removeEventListener(
+            'scan-completed', onPreviousScanCompleted);
+        // Run the update asynchronously.
+        Promise.resolve().then(() => {
+          this.partialUpdate_(changedEntries, removedUrls);
+        });
+      };
+      previousScan.addEventListener('scan-completed', onPreviousScanCompleted);
+      return;
+    }
+
+    const onFinish = () => {
+      this.runningScan_ = null;
+
+      this.currentDirContents_.removeEventListener(
+          'scan-completed', onCompleted);
+      this.currentDirContents_.removeEventListener('scan-failed', onFailure);
+      this.currentDirContents_.removeEventListener(
+          'scan-cancelled', onCancelled);
+    };
+
+    const onCompleted = () => {
+      onFinish();
+      cr.dispatchSimpleEvent(this, 'rescan-completed');
+    };
+
+    const onFailure = () => {
+      onFinish();
+    };
+
+    const onCancelled = () => {
+      onFinish();
+    };
+
+    this.runningScan_ = this.currentDirContents_;
+    this.currentDirContents_.addEventListener('scan-completed', onCompleted);
+    this.currentDirContents_.addEventListener('scan-failed', onFailure);
+    this.currentDirContents_.addEventListener('scan-cancelled', onCancelled);
+    this.currentDirContents_.update(changedEntries, removedUrls);
+  }
+
+  /**
+   * Perform a directory contents scan. Should be called only from rescan() and
+   * clearAndScan_().
+   *
+   * @param {DirectoryContents} dirContents DirectoryContents instance on which
+   *     the scan will be run.
+   * @param {boolean} refresh True to refresh metadata, or false to use cached
+   *     one.
+   * @param {function()} successCallback Callback on success.
+   * @param {function(DOMError)} failureCallback Callback on failure.
+   * @param {function()} updatedCallback Callback on update. Only on the last
+   *     update, {@code successCallback} is called instead of this.
+   * @param {function()} cancelledCallback Callback on cancel.
+   * @private
+   */
+  scan_(
+      dirContents, refresh, successCallback, failureCallback, updatedCallback,
+      cancelledCallback) {
+    const self = this;
+
+    /**
+     * Runs pending scan if there is one.
+     *
+     * @return {boolean} Did pending scan exist.
+     */
+    const maybeRunPendingRescan = () => {
+      if (this.pendingRescan_) {
+        this.rescanSoon(refresh);
+        this.pendingRescan_ = false;
+        return true;
+      }
+      return false;
+    };
+
+    const onFinished = () => {
+      dirContents.removeEventListener('scan-completed', onSuccess);
+      dirContents.removeEventListener('scan-updated', updatedCallback);
+      dirContents.removeEventListener('scan-failed', onFailure);
+      dirContents.removeEventListener('scan-cancelled', cancelledCallback);
+    };
+
+    const onSuccess = () => {
+      onFinished();
+
+      // Record metric for Downloads directory.
+      if (!dirContents.isSearch()) {
+        const locationInfo = this.volumeManager_.getLocationInfo(
+            assert(dirContents.getDirectoryEntry()));
+        const volumeInfo = locationInfo && locationInfo.volumeInfo;
+        if (volumeInfo &&
+            volumeInfo.volumeType ===
+                VolumeManagerCommon.VolumeType.DOWNLOADS &&
+            locationInfo.isRootEntry) {
+          metrics.recordMediumCount(
+              'DownloadsCount', dirContents.getFileListLength());
+        }
+      }
+
+      this.runningScan_ = null;
+      successCallback();
+      this.scanFailures_ = 0;
+      maybeRunPendingRescan();
+    };
+
+    const onFailure = event => {
+      onFinished();
+
+      this.runningScan_ = null;
+      this.scanFailures_++;
+      failureCallback(event.error);
+
+      if (maybeRunPendingRescan()) {
+        return;
+      }
+
+      // Do not rescan for crostini errors.
+      if (event.error.name === DirectoryModel.CROSTINI_CONNECT_ERR) {
+        return;
+      }
+
+      if (this.scanFailures_ <= 1) {
+        this.rescanLater(refresh);
+      }
+    };
+
+    const onCancelled = () => {
+      onFinished();
+      cancelledCallback();
+    };
+
+    this.runningScan_ = dirContents;
+
+    dirContents.addEventListener('scan-completed', onSuccess);
+    dirContents.addEventListener('scan-updated', updatedCallback);
+    dirContents.addEventListener('scan-failed', onFailure);
+    dirContents.addEventListener('scan-cancelled', onCancelled);
+    dirContents.scan(refresh);
+  }
+
+  /**
+   * @param {DirectoryContents} dirContents DirectoryContents instance. This
+   *     must be a different instance from this.currentDirContents_.
+   * @private
+   */
+  replaceDirectoryContents_(dirContents) {
+    console.assert(
+        this.currentDirContents_ !== dirContents,
+        'Give directory contents instance must be different from current one.');
+    cr.dispatchSimpleEvent(this, 'begin-update-files');
+    this.updateSelectionAndPublishEvent_(this.fileListSelection_, () => {
+      const selectedEntries = this.getSelectedEntries_();
+      const selectedIndices = this.fileListSelection_.selectedIndexes;
+
+      // Restore leadIndex in case leadName no longer exists.
+      const leadIndex = this.fileListSelection_.leadIndex;
+      const leadEntry = this.getLeadEntry_();
+      const isCheckSelectMode = this.fileListSelection_.getCheckSelectMode();
+
+      const previousDirContents = this.currentDirContents_;
+      this.currentDirContents_ = dirContents;
+      this.currentDirContents_.replaceContextFileList();
+
+      this.setSelectedEntries_(selectedEntries);
+      this.fileListSelection_.leadIndex = leadIndex;
+      this.setLeadEntry_(leadEntry);
+
+      // If nothing is selected after update, then select file next to the
+      // latest selection
+      let forceChangeEvent = false;
+      if (this.fileListSelection_.selectedIndexes.length == 0 &&
+          selectedIndices.length != 0) {
+        const maxIdx = Math.max.apply(null, selectedIndices);
+        this.selectIndex(
+            Math.min(
+                maxIdx - selectedIndices.length + 2,
+                this.getFileList().length) -
+            1);
+        forceChangeEvent = true;
+      } else if (isCheckSelectMode) {
+        // Otherwise, ensure check select mode is retained if it was previously
+        // active.
+        this.fileListSelection_.setCheckSelectMode(true);
+      }
+      return forceChangeEvent;
+    });
+
+    cr.dispatchSimpleEvent(this, 'end-update-files');
+  }
+
+  /**
+   * Callback when an entry is changed.
+   * @param {EntriesChangedEvent} event Entry change event.
+   * @private
+   */
+  onEntriesChanged_(event) {
+    const kind = event.kind;
+    const entries = event.entries;
+    // TODO(hidehiko): We should update directory model even the search result
+    // is shown.
+    const rootType = this.getCurrentRootType();
+    if ((rootType === VolumeManagerCommon.RootType.DRIVE ||
+         rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
+         rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
+         rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE) &&
+        this.isSearching()) {
+      return;
+    }
+
+    switch (kind) {
+      case util.EntryChangedKind.CREATED:
+        const parentPromises = [];
+        for (let i = 0; i < entries.length; i++) {
+          parentPromises.push(new Promise((resolve, reject) => {
+            entries[i].getParent(resolve, reject);
+          }));
+        }
+        Promise.all(parentPromises)
+            .then(parents => {
+              const entriesToAdd = [];
+              for (let i = 0; i < parents.length; i++) {
+                if (!util.isSameEntry(parents[i], this.getCurrentDirEntry())) {
+                  continue;
+                }
+                const index = this.findIndexByEntry_(entries[i]);
+                if (index >= 0) {
+                  this.getFileList().replaceItem(
+                      this.getFileList().item(index), entries[i]);
+                } else {
+                  entriesToAdd.push(entries[i]);
+                }
+              }
+              this.partialUpdate_(entriesToAdd, []);
+            })
+            .catch(error => {
+              console.error(error.stack || error);
+            });
+        break;
+
+      case util.EntryChangedKind.DELETED:
+        // This is the delete event.
+        this.partialUpdate_([], util.entriesToURLs(entries));
+        break;
+
+      default:
+        console.error('Invalid EntryChangedKind: ' + kind);
+        break;
+    }
+  }
+
+  /**
+   * @param {Entry} entry The entry to be searched.
+   * @return {number} The index in the fileList, or -1 if not found.
+   * @private
+   */
+  findIndexByEntry_(entry) {
+    const fileList = this.getFileList();
+    for (let i = 0; i < fileList.length; i++) {
+      if (util.isSameEntry(/** @type {Entry} */ (fileList.item(i)), entry)) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Called when rename is done successfully.
+   * Note: conceptually, DirectoryModel should work without this, because
+   * entries can be renamed by other systems anytime and the Files app should
+   * reflect it correctly.
+   * TODO(hidehiko): investigate more background, and remove this if possible.
+   *
+   * @param {!Entry} oldEntry The old entry.
+   * @param {!Entry} newEntry The new entry.
+   * @param {function()=} opt_callback Called on completion.
+   */
+  onRenameEntry(oldEntry, newEntry, opt_callback) {
+    this.currentDirContents_.prefetchMetadata([newEntry], true, () => {
+      // If the current directory is the old entry, then quietly change to the
+      // new one.
+      if (util.isSameEntry(oldEntry, this.getCurrentDirEntry())) {
+        this.changeDirectoryEntry(
+            /** @type {!DirectoryEntry|!FilesAppDirEntry} */ (newEntry));
+      }
+
+      // Replace the old item with the new item. oldEntry instance itself may
+      // have been removed/replaced from the list during the async process, we
+      // find an entry which should be replaced by checking toURL().
+      const list = this.getFileList();
+      let oldEntryExist = false;
+      let newEntryExist = false;
+      const oldEntryUrl = oldEntry.toURL();
+      const newEntryUrl = newEntry.toURL();
+
+      for (let i = 0; i < list.length; i++) {
+        const item = list.item(i);
+        const url = item.toURL();
+        if (url === oldEntryUrl) {
+          list.replaceItem(item, newEntry);
+          oldEntryExist = true;
+          break;
+        }
+
+        if (url === newEntryUrl) {
+          newEntryExist = true;
+        }
+      }
+
+      // When both old and new entries don't exist, it may be in the middle of
+      // update process. In DirectoryContent.update deletion is executed at
+      // first and insertion is executed as a async call. There is a chance that
+      // this method is called in the middle of update process.
+      if (!oldEntryExist && !newEntryExist) {
+        list.push(newEntry);
+      }
+
+      // Run callback, finally.
+      if (opt_callback) {
+        opt_callback();
+      }
     });
   }
-};
 
-/**
- * Returns whether the current directory entry has been unmounted.
- *
- * @param {!Array<!VolumeInfo>} removedVolumes The removed volumes.
- * @private
- */
-DirectoryModel.prototype.hasCurrentDirEntryBeenUnmounted_ = function(
-    removedVolumes) {
-  const entry = this.getCurrentDirEntry();
-  if (!entry) {
+  /**
+   * Updates data model and selects new directory.
+   * @param {!DirectoryEntry} newDirectory Directory entry to be selected.
+   * @return {Promise} A promise which is resolved when new directory is
+   *     selected. If current directory has changed during the operation, this
+   *     will be rejected.
+   */
+  updateAndSelectNewDirectory(newDirectory) {
+    // Refresh the cache.
+    this.metadataModel_.notifyEntriesCreated([newDirectory]);
+    const dirContents = this.currentDirContents_;
+
+    return new Promise((onFulfilled, onRejected) => {
+             dirContents.prefetchMetadata([newDirectory], false, onFulfilled);
+           })
+        .then((sequence => {
+                // If current directory has changed during the prefetch, do not
+                // try to select new directory.
+                if (sequence !== this.changeDirectorySequence_) {
+                  return Promise.reject();
+                }
+
+                // If target directory is already in the list, just select it.
+                const existing = this.getFileList().slice().filter(e => {
+                  return e.name === newDirectory.name;
+                });
+                if (existing.length) {
+                  this.selectEntry(newDirectory);
+                } else {
+                  this.fileListSelection_.beginChange();
+                  this.getFileList().splice(0, 0, newDirectory);
+                  this.selectEntry(newDirectory);
+                  this.fileListSelection_.endChange();
+                }
+              }).bind(null, this.changeDirectorySequence_));
+  }
+
+  /**
+   * Sets the current MyFilesEntry.
+   * @param {FilesAppDirEntry} myFilesEntry
+   */
+  setMyFiles(myFilesEntry) {
+    this.myFilesEntry_ = myFilesEntry;
+  }
+
+  /**
+   * Changes the current directory to the directory represented by
+   * a DirectoryEntry or a fake entry.
+   *
+   * Dispatches the 'directory-changed' event when the directory is successfully
+   * changed.
+   *
+   * Note : if this is called from UI, please consider to use DirectoryModel.
+   * activateDirectoryEntry instead of this, which is higher-level function and
+   * cares about the selection.
+   *
+   * @param {!DirectoryEntry|!FilesAppDirEntry} dirEntry The entry of the new
+   *     directory to be opened.
+   * @param {function()=} opt_callback Executed if the directory loads
+   *     successfully.
+   */
+  changeDirectoryEntry(dirEntry, opt_callback) {
+    // Increment the sequence value.
+    this.changeDirectorySequence_++;
+    this.clearSearch_();
+
+    // When switching to MyFiles volume, we should use a FilesAppEntry if
+    // available because it returns UI-only entries too, like Linux files and
+    // Play files.
+    const locationInfo = this.volumeManager_.getLocationInfo(dirEntry);
+    if (util.isMyFilesVolumeEnabled() && locationInfo && this.myFilesEntry_ &&
+        locationInfo.rootType === VolumeManagerCommon.RootType.DOWNLOADS &&
+        locationInfo.isRootEntry) {
+      dirEntry = this.myFilesEntry_;
+    }
+
+    // If there is on-going scan, cancel it.
+    if (this.currentDirContents_.isScanning()) {
+      this.currentDirContents_.cancelScan();
+    }
+
+    this.directoryChangeQueue_.run(
+        ((sequence, queueTaskCallback) => {
+          this.fileWatcher_.changeWatchedDirectory(dirEntry).then(() => {
+            if (this.changeDirectorySequence_ !== sequence) {
+              queueTaskCallback();
+              return;
+            }
+
+            const newDirectoryContents = this.createDirectoryContents_(
+                this.currentFileListContext_, dirEntry, '');
+            if (!newDirectoryContents) {
+              queueTaskCallback();
+              return;
+            }
+
+            const previousDirEntry =
+                this.currentDirContents_.getDirectoryEntry();
+            this.clearAndScan_(newDirectoryContents, result => {
+              // Calls the callback of the method when successful.
+              if (result && opt_callback) {
+                opt_callback();
+              }
+
+              // Notify that the current task of this.directoryChangeQueue_
+              // is completed.
+              setTimeout(queueTaskCallback, 0);
+            });
+
+            // For tests that open the dialog to empty directories, everything
+            // is loaded at this point.
+            util.testSendMessage('directory-change-complete');
+            const previousVolumeInfo = previousDirEntry ?
+                this.volumeManager_.getVolumeInfo(previousDirEntry) :
+                null;
+            // VolumeInfo for dirEntry.
+            const currentVolumeInfo = this.getCurrentVolumeInfo();
+            const event = new Event('directory-changed');
+            event.previousDirEntry = previousDirEntry;
+            event.newDirEntry = dirEntry;
+            event.volumeChanged = previousVolumeInfo !== currentVolumeInfo;
+            this.dispatchEvent(event);
+          });
+        }).bind(null, this.changeDirectorySequence_));
+  }
+
+  /**
+   * Activates the given directory.
+   * This method:
+   *  - Changes the current directory, if the given directory is not the current
+   *    directory.
+   *  - Clears the selection, if the given directory is the current directory.
+   *
+   * @param {!DirectoryEntry|!FilesAppDirEntry} dirEntry The entry of the new
+   *     directory to be opened.
+   * @param {function()=} opt_callback Executed if the directory loads
+   *     successfully.
+   */
+  activateDirectoryEntry(dirEntry, opt_callback) {
+    const currentDirectoryEntry = this.getCurrentDirEntry();
+    if (currentDirectoryEntry &&
+        util.isSameEntry(dirEntry, currentDirectoryEntry)) {
+      // On activating the current directory, clear the selection on the
+      // filelist.
+      this.clearSelection();
+    } else {
+      // Otherwise, changes the current directory.
+      this.changeDirectoryEntry(dirEntry, opt_callback);
+    }
+  }
+
+  /**
+   * Clears the selection in the file list.
+   */
+  clearSelection() {
+    this.setSelectedEntries_([]);
+  }
+
+  /**
+   * Creates an object which could say whether directory has changed while it
+   * has been active or not. Designed for long operations that should be
+   * cancelled if the used change current directory.
+   * @return {Object} Created object.
+   */
+  createDirectoryChangeTracker() {
+    const tracker = {
+      dm_: this,
+      active_: false,
+      hasChanged: false,
+
+      start: function() {
+        if (!this.active_) {
+          this.dm_.addEventListener(
+              'directory-changed', this.onDirectoryChange_);
+          this.active_ = true;
+          this.hasChanged = false;
+        }
+      },
+
+      stop: function() {
+        if (this.active_) {
+          this.dm_.removeEventListener(
+              'directory-changed', this.onDirectoryChange_);
+          this.active_ = false;
+        }
+      },
+
+      onDirectoryChange_: function(event) {
+        tracker.stop();
+        tracker.hasChanged = true;
+      }
+    };
+    return tracker;
+  }
+
+  /**
+   * @param {Entry} entry Entry to be selected.
+   */
+  selectEntry(entry) {
+    const fileList = this.getFileList();
+    for (let i = 0; i < fileList.length; i++) {
+      if (fileList.item(i).toURL() === entry.toURL()) {
+        this.selectIndex(i);
+        return;
+      }
+    }
+  }
+
+  /**
+   * @param {Array<Entry>} entries Array of entries.
+   */
+  selectEntries(entries) {
+    // URLs are needed here, since we are comparing Entries by URLs.
+    const urls = util.entriesToURLs(entries);
+    const fileList = this.getFileList();
+    this.fileListSelection_.beginChange();
+    this.fileListSelection_.unselectAll();
+    for (let i = 0; i < fileList.length; i++) {
+      if (urls.indexOf(fileList.item(i).toURL()) >= 0) {
+        this.fileListSelection_.setIndexSelected(i, true);
+      }
+    }
+    this.fileListSelection_.endChange();
+  }
+
+  /**
+   * @param {number} index Index of file.
+   */
+  selectIndex(index) {
+    // this.focusCurrentList_();
+    if (index >= this.getFileList().length) {
+      return;
+    }
+
+    // If a list bound with the model it will do scrollIndexIntoView(index).
+    this.fileListSelection_.selectedIndex = index;
+  }
+
+  /**
+   * Handles update of VolumeInfoList.
+   * @param {Event} event Event of VolumeInfoList's 'splice'.
+   * @private
+   */
+  onVolumeInfoListUpdated_(event) {
+    // Fallback to the default volume's root if the current volume is unmounted.
+    if (this.hasCurrentDirEntryBeenUnmounted_(event.removed)) {
+      this.volumeManager_.getDefaultDisplayRoot((displayRoot) => {
+        if (displayRoot) {
+          this.changeDirectoryEntry(displayRoot);
+        }
+      });
+    }
+
+    // If a volume within My files is mounted, rescan the contents.
+    // TODO(crbug.com/901690): Remove this special case.
+    if (this.getCurrentRootType() === VolumeManagerCommon.RootType.MY_FILES) {
+      for (let newVolume of event.added) {
+        if (newVolume.volumeType === VolumeManagerCommon.VolumeType.DOWNLOADS ||
+            newVolume.volumeType ===
+                VolumeManagerCommon.VolumeType.ANDROID_FILES ||
+            newVolume.volumeType === VolumeManagerCommon.VolumeType.CROSTINI) {
+          this.rescan(false);
+          break;
+        }
+      }
+    }
+
+    // If the current directory is the Drive placeholder and the real Drive is
+    // mounted, switch to it.
+    if (this.getCurrentRootType() ===
+        VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT) {
+      for (let newVolume of event.added) {
+        if (newVolume.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
+          newVolume.resolveDisplayRoot().then((displayRoot) => {
+            this.changeDirectoryEntry(displayRoot);
+          });
+        }
+      }
+    }
+    // If a new file backed provided volume is mounted,
+    // then redirect to it in the focused window.
+    // Note, that this is a temporary solution for https://crbug.com/427776.
+    // If crostini is mounted, redirect if it is the currently selected dir.
+    if (event.added.length !== 1) {
+      return;
+    }
+    if ((window.isFocused() &&
+         event.added[0].volumeType ===
+             VolumeManagerCommon.VolumeType.PROVIDED &&
+         event.added[0].source === VolumeManagerCommon.Source.FILE) ||
+        (event.added[0].volumeType ===
+             VolumeManagerCommon.VolumeType.CROSTINI &&
+         this.getCurrentRootType() === VolumeManagerCommon.RootType.CROSTINI)) {
+      event.added[0].resolveDisplayRoot().then((displayRoot) => {
+        // Resolving a display root on FSP volumes is instant, despite the
+        // asynchronous call.
+        this.changeDirectoryEntry(event.added[0].displayRoot);
+      });
+    }
+  }
+
+  /**
+   * Returns whether the current directory entry has been unmounted.
+   *
+   * @param {!Array<!VolumeInfo>} removedVolumes The removed volumes.
+   * @private
+   */
+  hasCurrentDirEntryBeenUnmounted_(removedVolumes) {
+    const entry = this.getCurrentDirEntry();
+    if (!entry) {
+      return false;
+    }
+
+    if (!util.isFakeEntry(entry)) {
+      return !this.volumeManager_.getVolumeInfo(entry);
+    }
+
+    const rootType = this.getCurrentRootType();
+    for (let volume of removedVolumes) {
+      if (volume.fakeEntries[rootType]) {
+        return true;
+      }
+      // The removable root is selected and one of its child partitions has been
+      // unmounted.
+      if (volume.prefixEntry === entry) {
+        return true;
+      }
+    }
     return false;
   }
 
-  if (!util.isFakeEntry(entry)) {
-    return !this.volumeManager_.getVolumeInfo(entry);
-  }
+  /**
+   * Creates directory contents for the entry and query.
+   *
+   * @param {FileListContext} context File list context.
+   * @param {!DirectoryEntry|!FilesAppEntry} entry Current directory.
+   * @param {string=} opt_query Search query string.
+   * @return {DirectoryContents} Directory contents.
+   * @private
+   */
+  createDirectoryContents_(context, entry, opt_query) {
+    const query = (opt_query || '').trimLeft();
+    const locationInfo = this.volumeManager_.getLocationInfo(entry);
+    const canUseDriveSearch =
+        this.volumeManager_.getDriveConnectionState().type !==
+            VolumeManagerCommon.DriveConnectionType.OFFLINE &&
+        (locationInfo && locationInfo.isDriveBased);
 
-  const rootType = this.getCurrentRootType();
-  for (let volume of removedVolumes) {
-    if (volume.fakeEntries[rootType]) {
-      return true;
+    if (entry.rootType == VolumeManagerCommon.RootType.RECENT) {
+      return DirectoryContents.createForRecent(
+          context, /** @type {!FakeEntry} */ (entry), query);
     }
-    // The removable root is selected and one of its child partitions has been
-    // unmounted.
-    if (volume.prefixEntry === entry) {
-      return true;
+    if (entry.rootType == VolumeManagerCommon.RootType.CROSTINI) {
+      return DirectoryContents.createForCrostiniMounter(
+          context, /** @type {!FakeEntry} */ (entry));
     }
-  }
-  return false;
-};
+    if (entry.rootType == VolumeManagerCommon.RootType.MY_FILES) {
+      return DirectoryContents.createForDirectory(
+          context, /** @type {!FilesAppDirEntry} */ (entry));
+    }
+    if (entry.rootType == VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT) {
+      return DirectoryContents.createForFakeDrive(
+          context, /** @type {!FakeEntry} */ (entry));
+    }
+    if (query && canUseDriveSearch) {
+      // Drive search.
+      return DirectoryContents.createForDriveSearch(
+          context, /** @type {!DirectoryEntry} */ (entry), query);
+    }
+    if (query) {
+      // Local search.
+      return DirectoryContents.createForLocalSearch(
+          context, /** @type {!DirectoryEntry} */ (entry), query);
+    }
 
-/**
- * Creates directory contents for the entry and query.
- *
- * @param {FileListContext} context File list context.
- * @param {!DirectoryEntry|!FilesAppEntry} entry Current directory.
- * @param {string=} opt_query Search query string.
- * @return {DirectoryContents} Directory contents.
- * @private
- */
-DirectoryModel.prototype.createDirectoryContents_ = function(
-    context, entry, opt_query) {
-  const query = (opt_query || '').trimLeft();
-  const locationInfo = this.volumeManager_.getLocationInfo(entry);
-  const canUseDriveSearch =
-      this.volumeManager_.getDriveConnectionState().type !==
-          VolumeManagerCommon.DriveConnectionType.OFFLINE &&
-      (locationInfo && locationInfo.isDriveBased);
+    if (!locationInfo) {
+      return null;
+    }
 
-  if (entry.rootType == VolumeManagerCommon.RootType.RECENT) {
-    return DirectoryContents.createForRecent(
-        context, /** @type {!FakeEntry} */ (entry), query);
-  }
-  if (entry.rootType == VolumeManagerCommon.RootType.CROSTINI) {
-    return DirectoryContents.createForCrostiniMounter(
-        context, /** @type {!FakeEntry} */ (entry));
-  }
-  if (entry.rootType == VolumeManagerCommon.RootType.MY_FILES) {
+    if (locationInfo.rootType == VolumeManagerCommon.RootType.MEDIA_VIEW) {
+      return DirectoryContents.createForMediaView(
+          context, /** @type {!DirectoryEntry} */ (entry));
+    }
+
+    if (locationInfo.isSpecialSearchRoot) {
+      // Drive special search.
+      let searchType;
+      switch (locationInfo.rootType) {
+        case VolumeManagerCommon.RootType.DRIVE_OFFLINE:
+          searchType =
+              DriveMetadataSearchContentScanner.SearchType.SEARCH_OFFLINE;
+          break;
+        case VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME:
+          searchType = DriveMetadataSearchContentScanner.SearchType
+                           .SEARCH_SHARED_WITH_ME;
+          break;
+        case VolumeManagerCommon.RootType.DRIVE_RECENT:
+          searchType =
+              DriveMetadataSearchContentScanner.SearchType.SEARCH_RECENT_FILES;
+          break;
+        default:
+          // Unknown special search entry.
+          throw new Error('Unknown special search type.');
+      }
+      return DirectoryContents.createForDriveMetadataSearch(
+          context,
+          /** @type {!FakeEntry} */ (entry), searchType);
+    }
+    // Local fetch or search.
     return DirectoryContents.createForDirectory(
-        context, /** @type {!FilesAppDirEntry} */ (entry));
-  }
-  if (entry.rootType == VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT) {
-    return DirectoryContents.createForFakeDrive(
-        context, /** @type {!FakeEntry} */ (entry));
-  }
-  if (query && canUseDriveSearch) {
-    // Drive search.
-    return DirectoryContents.createForDriveSearch(
-        context, /** @type {!DirectoryEntry} */ (entry), query);
-  }
-  if (query) {
-    // Local search.
-    return DirectoryContents.createForLocalSearch(
-        context, /** @type {!DirectoryEntry} */ (entry), query);
-  }
-
-  if (!locationInfo) {
-    return null;
-  }
-
-  if (locationInfo.rootType == VolumeManagerCommon.RootType.MEDIA_VIEW) {
-    return DirectoryContents.createForMediaView(
         context, /** @type {!DirectoryEntry} */ (entry));
   }
 
-  if (locationInfo.isSpecialSearchRoot) {
-    // Drive special search.
-    let searchType;
-    switch (locationInfo.rootType) {
-      case VolumeManagerCommon.RootType.DRIVE_OFFLINE:
-        searchType =
-            DriveMetadataSearchContentScanner.SearchType.SEARCH_OFFLINE;
-        break;
-      case VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME:
-        searchType =
-            DriveMetadataSearchContentScanner.SearchType.SEARCH_SHARED_WITH_ME;
-        break;
-      case VolumeManagerCommon.RootType.DRIVE_RECENT:
-        searchType =
-            DriveMetadataSearchContentScanner.SearchType.SEARCH_RECENT_FILES;
-        break;
-      default:
-        // Unknown special search entry.
-        throw new Error('Unknown special search type.');
+  /**
+   * Gets the last search query.
+   * @return {string} the last search query.
+   */
+  getLastSearchQuery() {
+    return this.lastSearchQuery_;
+  }
+
+  /**
+   * Clears the last search query with the empty string.
+   */
+  clearLastSearchQuery() {
+    this.lastSearchQuery_ = '';
+  }
+
+  /**
+   * Performs search and displays results. The search type is dependent on the
+   * current directory. If we are currently on drive, server side content search
+   * over drive mount point. If the current directory is not on the drive, file
+   * name search over current directory will be performed.
+   *
+   * @param {string} query Query that will be searched for.
+   * @param {function(Event)} onSearchRescan Function that will be called when
+   *     the search directory is rescanned (i.e. search results are displayed).
+   * @param {function()} onClearSearch Function to be called when search state
+   *     gets cleared.
+   * TODO(olege): Change callbacks to events.
+   */
+  search(query, onSearchRescan, onClearSearch) {
+    this.lastSearchQuery_ = query;
+    this.clearSearch_();
+    const currentDirEntry = this.getCurrentDirEntry();
+    if (!currentDirEntry) {
+      // Not yet initialized. Do nothing.
+      return;
     }
-    return DirectoryContents.createForDriveMetadataSearch(
-        context,
-        /** @type {!FakeEntry} */ (entry), searchType);
-  }
-  // Local fetch or search.
-  return DirectoryContents.createForDirectory(
-      context, /** @type {!DirectoryEntry} */ (entry));
-};
 
-/**
- * Gets the last search query.
- * @return {string} the last search query.
- */
-DirectoryModel.prototype.getLastSearchQuery = function() {
-  return this.lastSearchQuery_;
-};
-
-/**
- * Clears the last search query with the empty string.
- */
-DirectoryModel.prototype.clearLastSearchQuery = function() {
-  this.lastSearchQuery_ = '';
-};
-
-/**
- * Performs search and displays results. The search type is dependent on the
- * current directory. If we are currently on drive, server side content search
- * over drive mount point. If the current directory is not on the drive, file
- * name search over current directory will be performed.
- *
- * @param {string} query Query that will be searched for.
- * @param {function(Event)} onSearchRescan Function that will be called when the
- *     search directory is rescanned (i.e. search results are displayed).
- * @param {function()} onClearSearch Function to be called when search state
- *     gets cleared.
- * TODO(olege): Change callbacks to events.
- */
-DirectoryModel.prototype.search = function(
-    query, onSearchRescan, onClearSearch) {
-  this.lastSearchQuery_ = query;
-  this.clearSearch_();
-  const currentDirEntry = this.getCurrentDirEntry();
-  if (!currentDirEntry) {
-    // Not yet initialized. Do nothing.
-    return;
-  }
-
-  this.changeDirectorySequence_++;
-  this.directoryChangeQueue_.run(
-      ((sequence, callback) => {
-        if (this.changeDirectorySequence_ !== sequence) {
-          callback();
-          return;
-        }
-
-        if (!(query || '').trimLeft()) {
-          if (this.isSearching()) {
-            const newDirContents = this.createDirectoryContents_(
-                this.currentFileListContext_, assert(currentDirEntry));
-            this.clearAndScan_(newDirContents, callback);
-          } else {
+    this.changeDirectorySequence_++;
+    this.directoryChangeQueue_.run(
+        ((sequence, callback) => {
+          if (this.changeDirectorySequence_ !== sequence) {
             callback();
+            return;
           }
-          return;
-        }
 
-        const newDirContents = this.createDirectoryContents_(
-            this.currentFileListContext_, assert(currentDirEntry), query);
-        if (!newDirContents) {
-          callback();
-          return;
-        }
+          if (!(query || '').trimLeft()) {
+            if (this.isSearching()) {
+              const newDirContents = this.createDirectoryContents_(
+                  this.currentFileListContext_, assert(currentDirEntry));
+              this.clearAndScan_(newDirContents, callback);
+            } else {
+              callback();
+            }
+            return;
+          }
 
-        this.onSearchCompleted_ = onSearchRescan;
-        this.onClearSearch_ = onClearSearch;
-        this.addEventListener('scan-completed', this.onSearchCompleted_);
-        this.clearAndScan_(newDirContents, callback);
-      }).bind(null, this.changeDirectorySequence_));
-};
+          const newDirContents = this.createDirectoryContents_(
+              this.currentFileListContext_, assert(currentDirEntry), query);
+          if (!newDirContents) {
+            callback();
+            return;
+          }
 
-/**
- * In case the search was active, remove listeners and send notifications on
- * its canceling.
- * @private
- */
-DirectoryModel.prototype.clearSearch_ = function() {
-  if (!this.isSearching()) {
-    return;
+          this.onSearchCompleted_ = onSearchRescan;
+          this.onClearSearch_ = onClearSearch;
+          this.addEventListener('scan-completed', this.onSearchCompleted_);
+          this.clearAndScan_(newDirContents, callback);
+        }).bind(null, this.changeDirectorySequence_));
   }
 
-  if (this.onSearchCompleted_) {
-    this.removeEventListener('scan-completed', this.onSearchCompleted_);
-    this.onSearchCompleted_ = null;
-  }
+  /**
+   * In case the search was active, remove listeners and send notifications on
+   * its canceling.
+   * @private
+   */
+  clearSearch_() {
+    if (!this.isSearching()) {
+      return;
+    }
 
-  if (this.onClearSearch_) {
-    this.onClearSearch_();
-    this.onClearSearch_ = null;
+    if (this.onSearchCompleted_) {
+      this.removeEventListener('scan-completed', this.onSearchCompleted_);
+      this.onSearchCompleted_ = null;
+    }
+
+    if (this.onClearSearch_) {
+      this.onClearSearch_();
+      this.onClearSearch_ = null;
+    }
   }
-};
+}
 
 /**
  * DOMError type for crostini connection failure.
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
index a02d46d..0c59bfb 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -2115,6 +2115,7 @@
   }
 
   // DirectoryTree is always expanded.
+  /** @return {boolean} */
   get expanded() {
     return true;
   }
diff --git a/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.cc b/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.cc
index 9c3f13f..3477f34c8 100644
--- a/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.cc
+++ b/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.cc
@@ -10,6 +10,7 @@
 #include "base/native_library.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
 #include "gpu/vulkan/vulkan_instance.h"
+#include "gpu/vulkan/vulkan_posix_util.h"
 #include "gpu/vulkan/vulkan_surface.h"
 #include "ui/gfx/gpu_fence.h"
 
@@ -82,11 +83,11 @@
 
 std::vector<const char*>
 VulkanImplementationGbm::GetRequiredDeviceExtensions() {
-  return {
-      "VK_KHR_external_fence", "VK_KHR_external_fence_fd",
-  };
+  return {VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME,
+          VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME,
+          VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME,
+          VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME};
 }
-
 VkFence VulkanImplementationGbm::CreateVkFenceForGpuFence(VkDevice vk_device) {
   VkFenceCreateInfo fence_create_info = {};
   fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
@@ -128,4 +129,17 @@
   return std::make_unique<gfx::GpuFence>(gpu_fence_handle);
 }
 
+VkSemaphore VulkanImplementationGbm::ImportSemaphoreHandle(
+    VkDevice vk_device,
+    gpu::SemaphoreHandle sync_handle) {
+  return gpu::ImportVkSemaphoreHandlePosix(vk_device, std::move(sync_handle));
+}
+
+gpu::SemaphoreHandle VulkanImplementationGbm::GetSemaphoreHandle(
+    VkDevice vk_device,
+    VkSemaphore vk_semaphore) {
+  return gpu::GetVkSemaphoreHandlePosix(
+      vk_device, vk_semaphore, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT);
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.h b/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.h
index 1202d6a..0fe36cb 100644
--- a/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.h
+++ b/ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.h
@@ -31,6 +31,10 @@
   std::unique_ptr<gfx::GpuFence> ExportVkFenceToGpuFence(
       VkDevice vk_device,
       VkFence vk_fence) override;
+  VkSemaphore ImportSemaphoreHandle(VkDevice vk_device,
+                                    gpu::SemaphoreHandle handle) override;
+  gpu::SemaphoreHandle GetSemaphoreHandle(VkDevice vk_device,
+                                          VkSemaphore vk_semaphore) override;
 
  private:
   gpu::VulkanInstance vulkan_instance_;
diff --git a/ui/ozone/platform/scenic/vulkan_implementation_scenic.cc b/ui/ozone/platform/scenic/vulkan_implementation_scenic.cc
index a75a9ebd..54d52ac 100644
--- a/ui/ozone/platform/scenic/vulkan_implementation_scenic.cc
+++ b/ui/ozone/platform/scenic/vulkan_implementation_scenic.cc
@@ -15,6 +15,7 @@
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/macros.h"
 #include "base/native_library.h"
+#include "gpu/vulkan/fuchsia/vulkan_fuchsia_ext.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
 #include "gpu/vulkan/vulkan_instance.h"
 #include "gpu/vulkan/vulkan_surface.h"
@@ -108,7 +109,8 @@
 
 std::vector<const char*>
 VulkanImplementationScenic::GetRequiredDeviceExtensions() {
-  return {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
+  return {VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+          VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME};
 }
 
 VkFence VulkanImplementationScenic::CreateVkFenceForGpuFence(
@@ -124,4 +126,64 @@
   return nullptr;
 }
 
+VkSemaphore VulkanImplementationScenic::ImportSemaphoreHandle(
+    VkDevice vk_device,
+    gpu::SemaphoreHandle handle) {
+  if (!handle.is_valid())
+    return VK_NULL_HANDLE;
+
+  if (handle.vk_handle_type() !=
+      VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_TEMP_ZIRCON_EVENT_BIT_FUCHSIA) {
+    return VK_NULL_HANDLE;
+  }
+
+  VkSemaphore semaphore = VK_NULL_HANDLE;
+  VkSemaphoreCreateInfo info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
+  VkResult result = vkCreateSemaphore(vk_device, &info, nullptr, &semaphore);
+  if (result != VK_SUCCESS)
+    return VK_NULL_HANDLE;
+
+  zx::event event = handle.TakeHandle();
+  VkImportSemaphoreZirconHandleInfoFUCHSIA import = {
+      VK_STRUCTURE_TYPE_TEMP_IMPORT_SEMAPHORE_ZIRCON_HANDLE_INFO_FUCHSIA};
+  import.semaphore = semaphore;
+  import.handleType =
+      VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_TEMP_ZIRCON_EVENT_BIT_FUCHSIA;
+  import.handle = event.get();
+
+  result = vkImportSemaphoreZirconHandleFUCHSIA(vk_device, &import);
+  if (result != VK_SUCCESS) {
+    vkDestroySemaphore(vk_device, semaphore, nullptr);
+    return VK_NULL_HANDLE;
+  }
+
+  // Vulkan took ownership of the handle.
+  ignore_result(event.release());
+
+  return semaphore;
+}
+
+gpu::SemaphoreHandle VulkanImplementationScenic::GetSemaphoreHandle(
+    VkDevice vk_device,
+    VkSemaphore vk_semaphore) {
+  // Create VkSemaphoreGetFdInfoKHR structure.
+  VkSemaphoreGetZirconHandleInfoFUCHSIA info = {
+      VK_STRUCTURE_TYPE_TEMP_SEMAPHORE_GET_ZIRCON_HANDLE_INFO_FUCHSIA};
+  info.semaphore = vk_semaphore;
+  info.handleType =
+      VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_TEMP_ZIRCON_EVENT_BIT_FUCHSIA;
+
+  zx_handle_t handle;
+  VkResult result =
+      vkGetSemaphoreZirconHandleFUCHSIA(vk_device, &info, &handle);
+  if (result != VK_SUCCESS) {
+    LOG(ERROR) << "vkGetSemaphoreFuchsiaHandleKHR failed : " << result;
+    return gpu::SemaphoreHandle();
+  }
+
+  return gpu::SemaphoreHandle(
+      VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_TEMP_ZIRCON_EVENT_BIT_FUCHSIA,
+      zx::event(handle));
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/scenic/vulkan_implementation_scenic.h b/ui/ozone/platform/scenic/vulkan_implementation_scenic.h
index 8aba7af8..3e245998 100644
--- a/ui/ozone/platform/scenic/vulkan_implementation_scenic.h
+++ b/ui/ozone/platform/scenic/vulkan_implementation_scenic.h
@@ -35,6 +35,10 @@
   std::unique_ptr<gfx::GpuFence> ExportVkFenceToGpuFence(
       VkDevice vk_device,
       VkFence vk_fence) override;
+  VkSemaphore ImportSemaphoreHandle(VkDevice vk_device,
+                                    gpu::SemaphoreHandle handle) override;
+  gpu::SemaphoreHandle GetSemaphoreHandle(VkDevice vk_device,
+                                          VkSemaphore vk_semaphore) override;
 
  private:
   ScenicSurfaceFactory* const scenic_surface_factory_;
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 4751133..d5ab87b 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -891,6 +891,7 @@
     "test/widget_test_mac.mm",
     "test/x11_property_change_waiter.cc",
     "test/x11_property_change_waiter.h",
+    "view_test_api.h",
     "views_test_suite.cc",
     "views_test_suite.h",
   ]
diff --git a/ui/views/bubble/bubble_frame_view.cc b/ui/views/bubble/bubble_frame_view.cc
index 57f4b69..3828c024 100644
--- a/ui/views/bubble/bubble_frame_view.cc
+++ b/ui/views/bubble/bubble_frame_view.cc
@@ -262,7 +262,7 @@
                                !delegate->GetWindowTitle().empty());
     default_title_->SetText(delegate->GetWindowTitle());
   }  // custom_title_'s updates are handled by its creator.
-  Layout();
+  InvalidateLayout();
 }
 
 void BubbleFrameView::SizeConstraintsChanged() {}
@@ -533,8 +533,7 @@
         GetOffScreenLength(available_bounds, window_bounds, vertical)) {
       bubble_border_->set_arrow(arrow);
     } else {
-      if (parent())
-        parent()->Layout();
+      InvalidateLayout();
       SchedulePaint();
     }
   }
diff --git a/ui/views/cocoa/bridged_native_widget_host_impl.h b/ui/views/cocoa/bridged_native_widget_host_impl.h
index 9403ce40..69f1c98 100644
--- a/ui/views/cocoa/bridged_native_widget_host_impl.h
+++ b/ui/views/cocoa/bridged_native_widget_host_impl.h
@@ -362,6 +362,7 @@
   void OnPaintLayer(const ui::PaintContext& context) override;
   void OnDeviceScaleFactorChanged(float old_device_scale_factor,
                                   float new_device_scale_factor) override;
+  void UpdateVisualState() override;
 
   // ui::AcceleratedWidgetMacNSView:
   void AcceleratedWidgetCALayerParamsUpdated() override;
diff --git a/ui/views/cocoa/bridged_native_widget_host_impl.mm b/ui/views/cocoa/bridged_native_widget_host_impl.mm
index 4d7d94a..db8c010 100644
--- a/ui/views/cocoa/bridged_native_widget_host_impl.mm
+++ b/ui/views/cocoa/bridged_native_widget_host_impl.mm
@@ -1407,6 +1407,10 @@
       old_device_scale_factor, new_device_scale_factor);
 }
 
+void BridgedNativeWidgetHostImpl::UpdateVisualState() {
+  native_widget_mac_->GetWidget()->LayoutRootViewIfNecessary();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // BridgedNativeWidgetHostImpl, AcceleratedWidgetMac:
 
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index 967342b..da6d3116c 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -385,7 +385,7 @@
   ResetLabelEnabledColor();
   label_->SetEnabled(state() != STATE_DISABLED);
   if (image_->GetPreferredSize() != previous_image_size)
-    Layout();
+    InvalidateLayout();
   Button::StateChanged(old_state);
 }
 
@@ -480,7 +480,6 @@
 void LabelButton::ChildPreferredSizeChanged(View* child) {
   ResetCachedPreferredSize();
   PreferredSizeChanged();
-  Layout();
 }
 
 ui::NativeTheme::Part LabelButton::GetThemePart() const {
diff --git a/ui/views/controls/button/label_button_unittest.cc b/ui/views/controls/button/label_button_unittest.cc
index 375c6a5..be3f7fe9 100644
--- a/ui/views/controls/button/label_button_unittest.cc
+++ b/ui/views/controls/button/label_button_unittest.cc
@@ -29,6 +29,7 @@
 #include "ui/views/style/platform_style.h"
 #include "ui/views/test/views_test_base.h"
 #include "ui/views/test/widget_test.h"
+#include "ui/views/view_test_api.h"
 #include "ui/views/widget/widget_utils.h"
 
 using base::ASCIIToUTF16;
@@ -459,12 +460,16 @@
   // The button preferred size and the label size increase when the text size
   // is increased.
   button_->SetText(longer_text);
+  EXPECT_TRUE(ViewTestApi(button_).needs_layout());
+  button_->Layout();
   EXPECT_GT(button_->label()->bounds().width(), original_label_width * 2);
   EXPECT_GT(button_->GetPreferredSize().width(), original_width * 2);
 
   // The button and the label view return to its original size when the original
   // text is restored.
   button_->SetText(text);
+  EXPECT_TRUE(ViewTestApi(button_).needs_layout());
+  button_->Layout();
   EXPECT_EQ(original_label_width, button_->label()->bounds().width());
   EXPECT_EQ(original_width, button_->GetPreferredSize().width());
 }
diff --git a/ui/views/controls/focus_ring.cc b/ui/views/controls/focus_ring.cc
index b1bd6f1c..2346789 100644
--- a/ui/views/controls/focus_ring.cc
+++ b/ui/views/controls/focus_ring.cc
@@ -32,7 +32,7 @@
   auto ring = base::WrapUnique<FocusRing>(new FocusRing());
   ring->set_owned_by_client();
   parent->AddChildView(ring.get());
-  ring->Layout();
+  ring->InvalidateLayout();
   ring->SchedulePaint();
   return ring;
 }
diff --git a/ui/views/controls/label.cc b/ui/views/controls/label.cc
index 09c24be..bba4490 100644
--- a/ui/views/controls/label.cc
+++ b/ui/views/controls/label.cc
@@ -831,7 +831,6 @@
 }
 
 void Label::ResetLayout() {
-  InvalidateLayout();
   PreferredSizeChanged();
   SchedulePaint();
   ClearDisplayText();
diff --git a/ui/views/controls/menu/menu_item_view.cc b/ui/views/controls/menu/menu_item_view.cc
index 95049b9..5ca281f 100644
--- a/ui/views/controls/menu/menu_item_view.cc
+++ b/ui/views/controls/menu/menu_item_view.cc
@@ -486,7 +486,7 @@
     AddChildView(icon_view);
     icon_view_ = icon_view;
   }
-  Layout();
+  InvalidateLayout();
   SchedulePaint();
 }
 
@@ -607,9 +607,10 @@
     controller->MenuChildrenChanged(this);
 
     if (submenu_) {
-      // Force a paint and layout. This handles the case of the top
-      // level window's size remaining the same, resulting in no
-      // change to the submenu's size and no layout.
+      // Force a paint and a synchronous layout. This needs a synchronous layout
+      // as UpdateSubmenuSelection() looks at bounds. This handles the case of
+      // the top level window's size remaining the same, resulting in no change
+      // to the submenu's size and no layout.
       submenu_->Layout();
       submenu_->SchedulePaint();
       // Update the menu selection after layout.
@@ -1211,7 +1212,7 @@
     dimensions.standard_width = menu_config.touchable_menu_width;
 
     if (icon_view_) {
-      dimensions.height = icon_view_->height() +
+      dimensions.height = icon_view_->GetPreferredSize().height() +
                           2 * menu_config.vertical_touchable_menu_item_padding;
     }
     return dimensions;
diff --git a/ui/views/controls/menu/menu_item_view.h b/ui/views/controls/menu/menu_item_view.h
index db677e0ce..d182c277 100644
--- a/ui/views/controls/menu/menu_item_view.h
+++ b/ui/views/controls/menu/menu_item_view.h
@@ -381,8 +381,8 @@
   // MenuRunner owns MenuItemView and should be the only one deleting it.
   ~MenuItemView() override;
 
+  // View:
   void ChildPreferredSizeChanged(View* child) override;
-
   const char* GetClassName() const override;
 
   // Returns the preferred size (and padding) of any children.
diff --git a/ui/views/controls/menu/menu_scroll_view_container.cc b/ui/views/controls/menu/menu_scroll_view_container.cc
index 85ae4ec9..f3ff29e3 100644
--- a/ui/views/controls/menu/menu_scroll_view_container.cc
+++ b/ui/views/controls/menu/menu_scroll_view_container.cc
@@ -282,7 +282,7 @@
   gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize();
   scroll_up_button_->SetVisible(content_pref.height() > height());
   scroll_down_button_->SetVisible(content_pref.height() > height());
-  Layout();
+  InvalidateLayout();
 }
 
 void MenuScrollViewContainer::CreateBorder() {
diff --git a/ui/views/controls/native/native_view_host.cc b/ui/views/controls/native/native_view_host.cc
index e64c679..3ec401e 100644
--- a/ui/views/controls/native/native_view_host.cc
+++ b/ui/views/controls/native/native_view_host.cc
@@ -33,6 +33,12 @@
   DCHECK(!native_view_);
   native_view_ = native_view;
   native_wrapper_->AttachNativeView();
+  // This does not use InvalidateLayout() to ensure the visibility state of
+  // the NativeView is correctly set (if this View isn't visible, Layout()
+  // won't, be called, resulting in the NativeView potentially having the wrong
+  // visibility state).
+  // TODO(https://crbug.com/947051): inestigate removing updating visibility
+  // immediately and calling InvalidateLayout() to update bounds.
   Layout();
 
   Widget* widget = Widget::GetWidgetForNativeView(native_view);
@@ -150,6 +156,8 @@
 }
 
 void NativeViewHost::VisibilityChanged(View* starting_from, bool is_visible) {
+  // This does not use InvalidateLayout() to ensure the visibility state is
+  // correctly set (if this View isn't visible, Layout() won't be called).
   Layout();
 }
 
@@ -161,7 +169,7 @@
 }
 
 void NativeViewHost::OnVisibleBoundsChanged() {
-  Layout();
+  InvalidateLayout();
 }
 
 void NativeViewHost::ViewHierarchyChanged(
diff --git a/ui/views/controls/native/native_view_host_aura.cc b/ui/views/controls/native/native_view_host_aura.cc
index f11658d..c15db47 100644
--- a/ui/views/controls/native/native_view_host_aura.cc
+++ b/ui/views/controls/native/native_view_host_aura.cc
@@ -144,7 +144,7 @@
     host_->native_view()->Show();
   else
     host_->native_view()->Hide();
-  host_->Layout();
+  host_->InvalidateLayout();
 }
 
 void NativeViewHostAura::RemovedFromWidget() {
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index 5518ed93..0cf1032 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -135,6 +135,8 @@
     scroll_view_->ScrollContentsRegionToBeVisible(scroll_rect);
   }
 
+  // TODO(https://crbug.com/947053): this override should not be necessary, but
+  // there are some assumptions that this calls Layout().
   void ChildPreferredSizeChanged(View* child) override {
     if (parent())
       parent()->Layout();
@@ -701,6 +703,9 @@
     *member = parent->AddChildView(std::move(new_view));
   else
     *member = nullptr;
+  // TODO(https://crbug.com/947053): this should call InvalidateLayout(), but
+  // there are some assumptions that it call Layout(). These assumptions should
+  // be updated.
   Layout();
 }
 
diff --git a/ui/views/controls/scroll_view_unittest.cc b/ui/views/controls/scroll_view_unittest.cc
index 6ca5557..a40ef51a 100644
--- a/ui/views/controls/scroll_view_unittest.cc
+++ b/ui/views/controls/scroll_view_unittest.cc
@@ -27,6 +27,7 @@
 #include "ui/views/test/test_views.h"
 #include "ui/views/test/views_test_base.h"
 #include "ui/views/test/widget_test.h"
+#include "ui/views/view_test_api.h"
 
 #if defined(OS_MACOSX)
 #include "ui/base/test/scoped_preferred_scroller_style_mac.h"
@@ -567,6 +568,8 @@
 
   // Get the header a height of 20.
   header->SetPreferredSize(gfx::Size(10, 20));
+  EXPECT_TRUE(ViewTestApi(scroll_view_.get()).needs_layout());
+  scroll_view_->Layout();
   EXPECT_EQ("0,0 100x20", header->parent()->bounds().ToString());
   EXPECT_EQ("0,20 100x80", contents->parent()->bounds().ToString());
   if (contents->layer()) {
@@ -989,6 +992,8 @@
   // Switch to the non-overlay style and check that the ViewPort is now sized
   // to be smaller, and ScrollbarWidth and ScrollbarHeight are non-zero.
   SetOverlayScrollersEnabled(false);
+  EXPECT_TRUE(ViewTestApi(scroll_view_.get()).needs_layout());
+  scroll_view_->Layout();
   EXPECT_EQ(100 - VerticalScrollBarWidth(), contents->parent()->width());
   EXPECT_EQ(100 - HorizontalScrollBarHeight(), contents->parent()->height());
   EXPECT_NE(0, VerticalScrollBarWidth());
diff --git a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
index 99a0392..a942b40 100644
--- a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
+++ b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
@@ -413,7 +413,7 @@
 
   // Ensure that the ScrollView updates the scrollbar's layout.
   if (parent())
-    parent()->Layout();
+    parent()->InvalidateLayout();
 
   if (scroller_style_ == NSScrollerStyleOverlay) {
     // Hide the scrollbar, but don't fade out.
diff --git a/ui/views/controls/table/table_view.cc b/ui/views/controls/table/table_view.cc
index 4a83681..9b6f2567 100644
--- a/ui/views/controls/table/table_view.cc
+++ b/ui/views/controls/table/table_view.cc
@@ -193,7 +193,7 @@
 
 void TableView::SetGrouper(TableGrouper* grouper) {
   grouper_ = grouper;
-  SortItemsAndUpdateMapping();
+  SortItemsAndUpdateMapping(/*schedule_paint=*/true);
 }
 
 int TableView::RowCount() const {
@@ -267,7 +267,7 @@
 
 void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
   sort_descriptors_ = sort_descriptors;
-  SortItemsAndUpdateMapping();
+  SortItemsAndUpdateMapping(/*schedule_paint=*/true);
   if (header_)
     header_->SchedulePaint();
 }
@@ -632,7 +632,7 @@
 }
 
 void TableView::OnItemsChanged(int start, int length) {
-  SortItemsAndUpdateMapping();
+  SortItemsAndUpdateMapping(/*schedule_paint=*/true);
 }
 
 void TableView::OnItemsAdded(int start, int length) {
@@ -643,7 +643,7 @@
 
 void TableView::OnItemsMoved(int old_start, int length, int new_start) {
   selection_model_.Move(old_start, new_start, length);
-  SortItemsAndUpdateMapping();
+  SortItemsAndUpdateMapping(/*schedule_paint=*/true);
 }
 
 void TableView::OnItemsRemoved(int start, int length) {
@@ -696,6 +696,9 @@
 void TableView::OnPaint(gfx::Canvas* canvas) {
   // Don't invoke View::OnPaint so that we can render our own focus border.
 
+  if (sort_on_paint_)
+    SortItemsAndUpdateMapping(/*schedule_paint=*/false);
+
   canvas->DrawColor(GetNativeTheme()->GetSystemColor(
                         ui::NativeTheme::kColorId_TableBackground));
 
@@ -825,13 +828,11 @@
 }
 
 void TableView::NumRowsChanged() {
-  SortItemsAndUpdateMapping();
+  SortItemsAndUpdateMapping(/*schedule_paint=*/true);
   PreferredSizeChanged();
-  SchedulePaint();
-  UpdateVirtualAccessibilityChildren();
 }
 
-void TableView::SortItemsAndUpdateMapping() {
+void TableView::SortItemsAndUpdateMapping(bool schedule_paint) {
   if (!is_sorted()) {
     view_to_model_.clear();
     model_to_view_.clear();
@@ -855,8 +856,9 @@
       model_to_view_[view_to_model_[i]] = i;
     model_->ClearCollator();
   }
-  SchedulePaint();
   UpdateVirtualAccessibilityChildren();
+  if (schedule_paint)
+    SchedulePaint();
 }
 
 int TableView::CompareRows(int model_row1, int model_row2) {
diff --git a/ui/views/controls/table/table_view.h b/ui/views/controls/table/table_view.h
index 80bd11ec..eeee3de 100644
--- a/ui/views/controls/table/table_view.h
+++ b/ui/views/controls/table/table_view.h
@@ -201,6 +201,15 @@
     select_on_remove_ = select_on_remove;
   }
 
+  // WARNING: this function forces a sort on every paint, and is therefore
+  // expensive! It assumes you are calling SchedulePaint() at intervals for
+  // the whole table. If your model is properly notifying the table, this is
+  // not needed. This is only used in th extremely rare case, where between the
+  // time the SchedulePaint() is called and the paint is processed, the
+  // underlying data may change. Also, this only works if the number of rows
+  // remains the same.
+  void set_sort_on_paint(bool sort_on_paint) { sort_on_paint_ = sort_on_paint; }
+
   // View overrides:
   void Layout() override;
   const char* GetClassName() const override;
@@ -257,8 +266,10 @@
   void NumRowsChanged();
 
   // Does the actual sort and updates the mappings (|view_to_model_| and
-  // |model_to_view_|) appropriately.
-  void SortItemsAndUpdateMapping();
+  // |model_to_view_|) appropriately. If |schedule_paint| is true,
+  // schedules a paint. This should be true, unless called from
+  // OnPaint.
+  void SortItemsAndUpdateMapping(bool schedule_paint);
 
   // Used to sort the two rows. Returns a value < 0, == 0 or > 0 indicating
   // whether the row2 comes before row1, row2 is the same as row1 or row1 comes
@@ -376,6 +387,8 @@
   bool select_on_remove_ = true;
 
   TableViewObserver* observer_ = nullptr;
+  // If |sort_on_paint_| is true, table will sort before painting.
+  bool sort_on_paint_ = false;
 
   // The selection, in terms of the model.
   ui::ListSelectionModel selection_model_;
diff --git a/ui/views/controls/textfield/textfield_model.cc b/ui/views/controls/textfield/textfield_model.cc
index 206f8bc..e50e979 100644
--- a/ui/views/controls/textfield/textfield_model.cc
+++ b/ui/views/controls/textfield/textfield_model.cc
@@ -63,7 +63,7 @@
   }
 
   // Commits the edit and marks as un-mergeable.
-  void Commit() { merge_type_ = DO_NOT_MERGE; }
+  void Commit() { merge_type_ = MergeType::kDoNotMerge; }
 
  private:
   friend class InsertEdit;
@@ -95,10 +95,10 @@
   Type type() const { return type_; }
 
   // Can this edit be merged?
-  bool mergeable() const { return merge_type_ == MERGEABLE; }
+  bool mergeable() const { return merge_type_ == MergeType::kMergeable; }
 
   // Should this edit be forcibly merged with the previous edit?
-  bool force_merge() const { return merge_type_ == FORCE_MERGE; }
+  bool force_merge() const { return merge_type_ == MergeType::kForceMerge; }
 
   // Returns the end index of the |old_text_|.
   size_t old_text_end() const { return old_text_start_ + old_text_.length(); }
@@ -123,7 +123,7 @@
 
     new_text_ = edit->new_text_;
     new_text_start_ = edit->new_text_start_;
-    merge_type_ = DO_NOT_MERGE;
+    merge_type_ = MergeType::kDoNotMerge;
   }
 
   Type type_;
@@ -152,7 +152,7 @@
  public:
   InsertEdit(bool mergeable, const base::string16& new_text, size_t at)
       : Edit(INSERT_EDIT,
-             mergeable ? MERGEABLE : DO_NOT_MERGE,
+             mergeable ? MergeType::kMergeable : MergeType::kDoNotMerge,
              base::string16(),
              at,
              gfx::Range(at, at),
@@ -215,7 +215,7 @@
              bool backward,
              gfx::Range old_selection)
       : Edit(DELETE_EDIT,
-             mergeable ? MERGEABLE : DO_NOT_MERGE,
+             mergeable ? MergeType::kMergeable : MergeType::kDoNotMerge,
              text,
              text_start,
              old_selection,
@@ -303,10 +303,6 @@
 using internal::DeleteEdit;
 using internal::InsertEdit;
 using internal::ReplaceEdit;
-using internal::MergeType;
-using internal::DO_NOT_MERGE;
-using internal::FORCE_MERGE;
-using internal::MERGEABLE;
 
 /////////////////////////////////////////////////////////////////
 // TextfieldModel: public
@@ -324,6 +320,7 @@
 }
 
 bool TextfieldModel::SetText(const base::string16& new_text) {
+  using MergeType = internal::MergeType;
   bool changed = false;
   if (HasCompositionText()) {
     ConfirmCompositionText();
@@ -336,9 +333,9 @@
     size_t new_cursor = new_text.length();
     // If there is a composition text, don't merge with previous edit.
     // Otherwise, force merge the edits.
-    ExecuteAndRecordReplace(changed ? DO_NOT_MERGE : FORCE_MERGE,
-                            gfx::Range(0, text().length()), new_cursor,
-                            new_text, 0U);
+    ExecuteAndRecordReplace(
+        changed ? MergeType::kDoNotMerge : MergeType::kForceMerge,
+        gfx::Range(0, text().length()), new_cursor, new_text, 0U);
     render_text_->SetCursorPosition(new_cursor);
   }
   ClearSelection();
@@ -622,9 +619,10 @@
 void TextfieldModel::DeleteSelectionAndInsertTextAt(
     const base::string16& new_text,
     size_t position) {
+  using MergeType = internal::MergeType;
   if (HasCompositionText())
     CancelCompositionText();
-  ExecuteAndRecordReplace(DO_NOT_MERGE, render_text_->selection(),
+  ExecuteAndRecordReplace(MergeType::kDoNotMerge, render_text_->selection(),
                           position + new_text.length(), new_text, position);
 }
 
@@ -723,12 +721,13 @@
 
 void TextfieldModel::InsertTextInternal(const base::string16& new_text,
                                         bool mergeable) {
+  using MergeType = internal::MergeType;
   if (HasCompositionText()) {
     CancelCompositionText();
     ExecuteAndRecordInsert(new_text, mergeable);
   } else if (HasSelection()) {
-    ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
-                                     new_text);
+    ExecuteAndRecordReplaceSelection(
+        mergeable ? MergeType::kMergeable : MergeType::kDoNotMerge, new_text);
   } else {
     ExecuteAndRecordInsert(new_text, mergeable);
   }
@@ -778,7 +777,7 @@
 }
 
 void TextfieldModel::ExecuteAndRecordReplaceSelection(
-    MergeType merge_type,
+    internal::MergeType merge_type,
     const base::string16& new_text) {
   size_t new_text_start = render_text_->selection().GetMin();
   size_t new_cursor_pos = new_text_start + new_text.length();
@@ -786,7 +785,7 @@
                           new_text, new_text_start);
 }
 
-void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type,
+void TextfieldModel::ExecuteAndRecordReplace(internal::MergeType merge_type,
                                              gfx::Range replacement_range,
                                              size_t new_cursor_pos,
                                              const base::string16& new_text,
diff --git a/ui/views/controls/textfield/textfield_model.h b/ui/views/controls/textfield/textfield_model.h
index 771d7a9..19d434d5 100644
--- a/ui/views/controls/textfield/textfield_model.h
+++ b/ui/views/controls/textfield/textfield_model.h
@@ -26,13 +26,13 @@
 class Edit;
 
 // The types of merge behavior implemented by Edit operations.
-enum MergeType {
+enum class MergeType {
   // The edit should not usually be merged with next edit.
-  DO_NOT_MERGE,
+  kDoNotMerge,
   // The edit should be merged with next edit when possible.
-  MERGEABLE,
-  // The edit should be merged with the prior edit, even if marked DO_NOT_MERGE.
-  FORCE_MERGE,
+  kMergeable,
+  // The edit should be merged with the prior edit, even if marked kDoNotMerge.
+  kForceMerge,
 };
 
 }  // namespace internal
diff --git a/ui/views/examples/animated_image_view_example.cc b/ui/views/examples/animated_image_view_example.cc
index 8e9ac20..09bf0eea 100644
--- a/ui/views/examples/animated_image_view_example.cc
+++ b/ui/views/examples/animated_image_view_example.cc
@@ -113,7 +113,7 @@
       animated_image_view_->SetImageSize(gfx::Size(size_, size_));
     else
       animated_image_view_->ResetImageSize();
-    Layout();
+    InvalidateLayout();
   }
 
   AnimatedImageView* animated_image_view_;
diff --git a/ui/views/examples/examples_window.cc b/ui/views/examples/examples_window.cc
index 53cfe77..e70e1f4c 100644
--- a/ui/views/examples/examples_window.cc
+++ b/ui/views/examples/examples_window.cc
@@ -208,7 +208,7 @@
         combobox_model_->GetItemViewAt(combobox->selected_index()));
     example_shown_->RequestFocus();
     SetStatus(std::string());
-    Layout();
+    InvalidateLayout();
   }
 
   static ExamplesWindowContents* instance_;
diff --git a/ui/views/examples/label_example.cc b/ui/views/examples/label_example.cc
index ba39175..0f80eb2 100644
--- a/ui/views/examples/label_example.cc
+++ b/ui/views/examples/label_example.cc
@@ -133,7 +133,7 @@
   } else if (button == selectable_) {
     custom_label_->SetSelectable(selectable_->checked());
   }
-  custom_label_->parent()->parent()->Layout();
+  custom_label_->parent()->parent()->InvalidateLayout();
   custom_label_->SchedulePaint();
 }
 
@@ -150,7 +150,7 @@
 void LabelExample::ContentsChanged(Textfield* sender,
                                    const base::string16& new_contents) {
   custom_label_->SetText(new_contents);
-  custom_label_->parent()->parent()->Layout();
+  custom_label_->parent()->parent()->InvalidateLayout();
 }
 
 void LabelExample::AddCustomLabel(View* container) {
diff --git a/ui/views/examples/layout_example_base.cc b/ui/views/examples/layout_example_base.cc
index 67d85d0..df727979 100644
--- a/ui/views/examples/layout_example_base.cc
+++ b/ui/views/examples/layout_example_base.cc
@@ -310,7 +310,7 @@
 void LayoutExampleBase::RefreshLayoutPanel(bool update_layout) {
   if (update_layout)
     UpdateLayoutManager();
-  layout_panel_->Layout();
+  layout_panel_->InvalidateLayout();
   layout_panel_->SchedulePaint();
 }
 
diff --git a/ui/views/examples/multiline_example.cc b/ui/views/examples/multiline_example.cc
index 3517a36..bbff7cd 100644
--- a/ui/views/examples/multiline_example.cc
+++ b/ui/views/examples/multiline_example.cc
@@ -180,7 +180,7 @@
   render_text_view_->SetText(new_contents);
   if (label_checkbox_->checked())
     label_->SetText(new_contents);
-  container()->Layout();
+  container()->InvalidateLayout();
   container()->SchedulePaint();
 }
 
@@ -191,7 +191,7 @@
   } else if (sender == elision_checkbox_) {
     render_text_view_->SetMaxLines(elision_checkbox_->checked() ? 3 : 0);
   }
-  container()->Layout();
+  container()->InvalidateLayout();
   container()->SchedulePaint();
 }
 
diff --git a/ui/views/examples/scroll_view_example.cc b/ui/views/examples/scroll_view_example.cc
index 59a3662..f254ca6 100644
--- a/ui/views/examples/scroll_view_example.cc
+++ b/ui/views/examples/scroll_view_example.cc
@@ -126,7 +126,7 @@
     scroll_view_->contents()->ScrollRectToVisible(
         gfx::Rect(20, 500, 1000, 500));
   }
-  scroll_view_->Layout();
+  scroll_view_->InvalidateLayout();
 }
 
 }  // namespace examples
diff --git a/ui/views/examples/vector_example.cc b/ui/views/examples/vector_example.cc
index e05e8f6..56b77985 100644
--- a/ui/views/examples/vector_example.cc
+++ b/ui/views/examples/vector_example.cc
@@ -124,7 +124,7 @@
       image_view_->SetImage(
           gfx::CreateVectorIconFromSource(contents_, size_, color_));
     }
-    Layout();
+    InvalidateLayout();
   }
 
   // 36dp is one of the natural sizes for MD icons, and corresponds roughly to a
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index fa32589..bfbeadb 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -134,10 +134,8 @@
   void OnWindowPropertyChanged(aura::Window* window,
                                const void* key,
                                intptr_t old) override {
-    if (key == aura::client::kTopViewInset) {
+    if (key == aura::client::kTopViewInset)
       InvalidateLayout();
-      widget_->GetRootView()->Layout();
-    }
   }
 
   aura::Window* window() const {
@@ -968,7 +966,7 @@
   NonClientView* non_client_view =
       native_widget_delegate_->AsWidget()->non_client_view();
   if (non_client_view) {
-    non_client_view->Layout();
+    non_client_view->InvalidateLayout();
     non_client_view->SchedulePaint();
   }
 
diff --git a/ui/views/test/views_test_base.cc b/ui/views/test/views_test_base.cc
index d24485b..464992a 100644
--- a/ui/views/test/views_test_base.cc
+++ b/ui/views/test/views_test_base.cc
@@ -246,6 +246,11 @@
 #endif
 }
 
+void ViewsTestBaseWithNativeWidgetType::SetUp() {
+  set_native_widget_type(GetParam());
+  ViewsTestBase::SetUp();
+}
+
 void ViewsTestWithDesktopNativeWidget::SetUp() {
   set_native_widget_type(NativeWidgetType::kDesktop);
   ViewsTestBase::SetUp();
diff --git a/ui/views/test/views_test_base.h b/ui/views/test/views_test_base.h
index 6a169ce..e2e3d8e 100644
--- a/ui/views/test/views_test_base.h
+++ b/ui/views/test/views_test_base.h
@@ -140,6 +140,20 @@
   DISALLOW_COPY_AND_ASSIGN(ViewsTestBase);
 };
 
+class ViewsTestBaseWithNativeWidgetType
+    : public ViewsTestBase,
+      public testing::WithParamInterface<ViewsTestBase::NativeWidgetType> {
+ public:
+  ViewsTestBaseWithNativeWidgetType() = default;
+  ~ViewsTestBaseWithNativeWidgetType() override = default;
+
+  // ViewsTestBase:
+  void SetUp() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ViewsTestBaseWithNativeWidgetType);
+};
+
 // A helper that makes it easier to declare basic views tests that want to test
 // desktop native widgets. See |ViewsTestBase::native_wiget_type_| and
 // |ViewsTestBase::CreateNativeWidgetForTest|. In short, for Aura, this will
diff --git a/ui/views/touchui/touch_selection_menu_views.cc b/ui/views/touchui/touch_selection_menu_views.cc
index bbde208..d5caea6 100644
--- a/ui/views/touchui/touch_selection_menu_views.cc
+++ b/ui/views/touchui/touch_selection_menu_views.cc
@@ -129,7 +129,7 @@
   // Finally, add ellipses button.
   AddChildView(
       CreateButton(base::UTF8ToUTF16(kEllipsesButtonText), kEllipsesButtonTag));
-  Layout();
+  InvalidateLayout();
 }
 
 LabelButton* TouchSelectionMenuViews::CreateButton(const base::string16& title,
diff --git a/ui/views/view.cc b/ui/views/view.cc
index f6f61c6..eebe9cf 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -605,8 +605,13 @@
   if (layout_manager_)
     layout_manager_->InvalidateLayout();
 
-  if (parent_)
+  if (parent_) {
     parent_->InvalidateLayout();
+  } else {
+    Widget* widget = GetWidget();
+    if (widget)
+      widget->ScheduleLayout();
+  }
 }
 
 LayoutManager* View::GetLayoutManager() const {
diff --git a/ui/views/view.h b/ui/views/view.h
index d95d572..125ac49 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -1432,6 +1432,7 @@
   friend class FocusManager;
   friend class ViewLayerTest;
   friend class ViewLayerPixelCanvasTest;
+  friend class ViewTestApi;
   friend class Widget;
   FRIEND_TEST_ALL_PREFIXES(ViewTest, PaintWithMovedViewUsesCache);
   FRIEND_TEST_ALL_PREFIXES(ViewTest, PaintWithMovedViewUsesCacheInRTL);
diff --git a/ui/views/view_test_api.h b/ui/views/view_test_api.h
new file mode 100644
index 0000000..9c0f7acc
--- /dev/null
+++ b/ui/views/view_test_api.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_VIEW_TEST_API_H_
+#define UI_VIEWS_VIEW_TEST_API_H_
+
+#include "ui/views/view.h"
+
+namespace views {
+
+class VIEWS_EXPORT ViewTestApi {
+ public:
+  explicit ViewTestApi(View* view) : view_(view) {}
+  ~ViewTestApi() = default;
+
+  bool needs_layout() { return view_->needs_layout(); }
+
+ private:
+  View* view_;
+
+  DISALLOW_COPY_AND_ASSIGN(ViewTestApi);
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_VIEW_TEST_API_H_
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index 5b0508b..6c19974 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -911,6 +911,13 @@
     content_window_->SchedulePaintInRect(rect);
 }
 
+void DesktopNativeWidgetAura::ScheduleLayout() {
+  // ScheduleDraw() triggers a callback to
+  // WindowDelegate::UpdateVisualState().
+  if (content_window_)
+    content_window_->ScheduleDraw();
+}
+
 void DesktopNativeWidgetAura::SetCursor(gfx::NativeCursor cursor) {
   cursor_ = cursor;
   aura::client::CursorClient* cursor_client =
@@ -1084,6 +1091,10 @@
   native_widget_delegate_->GetHitTestMask(mask);
 }
 
+void DesktopNativeWidgetAura::UpdateVisualState() {
+  native_widget_delegate_->LayoutRootViewIfNecessary();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // DesktopNativeWidgetAura, ui::EventHandler implementation:
 
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
index 45319529..bed6fa5 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
@@ -173,6 +173,7 @@
                     int operation,
                     ui::DragDropTypes::DragEventSource source) override;
   void SchedulePaintInRect(const gfx::Rect& rect) override;
+  void ScheduleLayout() override;
   void SetCursor(gfx::NativeCursor cursor) override;
   bool IsMouseEventsEnabled() const override;
   bool IsMouseButtonDown() const override;
@@ -213,6 +214,7 @@
   void OnWindowTargetVisibilityChanged(bool visible) override;
   bool HasHitTestMask() const override;
   void GetHitTestMask(SkPath* mask) const override;
+  void UpdateVisualState() override;
 
   // Overridden from ui::EventHandler:
   void OnKeyEvent(ui::KeyEvent* event) override;
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index 1afcdd74..ac7c92d 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -549,7 +549,6 @@
     non_client_view->client_view()->InvalidateLayout();
     non_client_view->InvalidateLayout();
   }
-  widget->GetRootView()->Layout();
 }
 
 void DesktopWindowTreeHostPlatform::RemoveNonClientEventFilter() {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index 867867b53..26290c4 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -2009,7 +2009,6 @@
     non_client_view->client_view()->InvalidateLayout();
     non_client_view->InvalidateLayout();
   }
-  widget->GetRootView()->Layout();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/views/widget/native_widget_aura.cc b/ui/views/widget/native_widget_aura.cc
index 6de855c..1659e91 100644
--- a/ui/views/widget/native_widget_aura.cc
+++ b/ui/views/widget/native_widget_aura.cc
@@ -700,6 +700,12 @@
     window_->SchedulePaintInRect(rect);
 }
 
+void NativeWidgetAura::ScheduleLayout() {
+  // ScheduleDraw() triggers a callback to WindowDelegate::UpdateVisualState().
+  if (window_)
+    window_->ScheduleDraw();
+}
+
 void NativeWidgetAura::SetCursor(gfx::NativeCursor cursor) {
   cursor_ = cursor;
   aura::client::CursorClient* cursor_client =
@@ -916,6 +922,10 @@
   delegate_->GetHitTestMask(mask);
 }
 
+void NativeWidgetAura::UpdateVisualState() {
+  delegate_->LayoutRootViewIfNecessary();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // NativeWidgetAura, aura::WindowObserver implementation:
 
diff --git a/ui/views/widget/native_widget_aura.h b/ui/views/widget/native_widget_aura.h
index c71289dc..9f50fed 100644
--- a/ui/views/widget/native_widget_aura.h
+++ b/ui/views/widget/native_widget_aura.h
@@ -134,6 +134,7 @@
                     int operation,
                     ui::DragDropTypes::DragEventSource source) override;
   void SchedulePaintInRect(const gfx::Rect& rect) override;
+  void ScheduleLayout() override;
   void SetCursor(gfx::NativeCursor cursor) override;
   bool IsMouseEventsEnabled() const override;
   bool IsMouseButtonDown() const override;
@@ -173,6 +174,7 @@
   void OnWindowTargetVisibilityChanged(bool visible) override;
   bool HasHitTestMask() const override;
   void GetHitTestMask(SkPath* mask) const override;
+  void UpdateVisualState() override;
 
   // Overridden from aura::WindowObserver:
   void OnWindowPropertyChanged(aura::Window* window,
diff --git a/ui/views/widget/native_widget_delegate.h b/ui/views/widget/native_widget_delegate.h
index 8965822..57fdde7 100644
--- a/ui/views/widget/native_widget_delegate.h
+++ b/ui/views/widget/native_widget_delegate.h
@@ -155,6 +155,9 @@
       gfx::NativeView child,
       ui::Layer* child_layer,
       const gfx::Point& location) = 0;
+
+  // Called to process a previous call to ScheduleLayout().
+  virtual void LayoutRootViewIfNecessary() = 0;
 };
 
 }  // namespace internal
diff --git a/ui/views/widget/native_widget_mac.h b/ui/views/widget/native_widget_mac.h
index da7a220..dba6b57 100644
--- a/ui/views/widget/native_widget_mac.h
+++ b/ui/views/widget/native_widget_mac.h
@@ -80,6 +80,11 @@
                               WindowOpenDisposition window_open_disposition,
                               bool is_before_first_responder);
 
+  ui::Compositor* GetCompositor() {
+    return const_cast<ui::Compositor*>(
+        const_cast<const NativeWidgetMac*>(this)->GetCompositor());
+  }
+
   // internal::NativeWidgetPrivate:
   void InitNativeWidget(const Widget::InitParams& params) override;
   void OnWidgetInitDone() override;
@@ -151,6 +156,7 @@
                     int operation,
                     ui::DragDropTypes::DragEventSource source) override;
   void SchedulePaintInRect(const gfx::Rect& rect) override;
+  void ScheduleLayout() override;
   void SetCursor(gfx::NativeCursor cursor) override;
   void ShowEmojiPanel() override;
   bool IsMouseEventsEnabled() const override;
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index be935af..c464e86 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -576,6 +576,12 @@
     bridge_host_->layer()->SchedulePaint(rect);
 }
 
+void NativeWidgetMac::ScheduleLayout() {
+  ui::Compositor* compositor = GetCompositor();
+  if (compositor)
+    compositor->ScheduleDraw();
+}
+
 void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) {
   if (bridge_impl())
     bridge_impl()->SetCursor(cursor);
diff --git a/ui/views/widget/native_widget_private.h b/ui/views/widget/native_widget_private.h
index e594c69..c02942b 100644
--- a/ui/views/widget/native_widget_private.h
+++ b/ui/views/widget/native_widget_private.h
@@ -213,6 +213,7 @@
                             int operation,
                             ui::DragDropTypes::DragEventSource source) = 0;
   virtual void SchedulePaintInRect(const gfx::Rect& rect) = 0;
+  virtual void ScheduleLayout() = 0;
   virtual void SetCursor(gfx::NativeCursor cursor) = 0;
   virtual void ShowEmojiPanel();
   virtual bool IsMouseEventsEnabled() const = 0;
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 1bf59a1..4e00aae2 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -700,7 +700,7 @@
   native_widget_->SetFullscreen(fullscreen);
 
   if (non_client_view_)
-    non_client_view_->Layout();
+    non_client_view_->InvalidateLayout();
 }
 
 bool Widget::IsFullscreen() const {
@@ -815,6 +815,10 @@
   native_widget_->SchedulePaintInRect(rect);
 }
 
+void Widget::ScheduleLayout() {
+  native_widget_->ScheduleLayout();
+}
+
 void Widget::SetCursor(gfx::NativeCursor cursor) {
   native_widget_->SetCursor(cursor);
 }
@@ -1413,6 +1417,11 @@
   return true;
 }
 
+void Widget::LayoutRootViewIfNecessary() {
+  if (root_view_ && root_view_->needs_layout())
+    root_view_->Layout();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Widget, ui::EventSource implementation:
 ui::EventSink* Widget::GetEventSink() {
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
index 6f1f45f..06119b2 100644
--- a/ui/views/widget/widget.h
+++ b/ui/views/widget/widget.h
@@ -632,6 +632,10 @@
   // redrawn.
   virtual void SchedulePaintInRect(const gfx::Rect& rect);
 
+  // Schedule a layout to occur. This is called by RootView, client code should
+  // not need to call this.
+  void ScheduleLayout();
+
   // Sets the currently visible cursor. If |cursor| is NULL, the cursor used
   // before the current is restored.
   void SetCursor(gfx::NativeCursor cursor);
@@ -853,6 +857,7 @@
       gfx::NativeView child,
       ui::Layer* child_layer,
       const gfx::Point& location) override;
+  void LayoutRootViewIfNecessary() override;
 
   // Overridden from ui::EventSource:
   ui::EventSink* GetEventSink() override;
@@ -883,8 +888,8 @@
   virtual void OnDragComplete();
 
  private:
-  friend class ComboboxTest;
   friend class ButtonTest;
+  friend class ComboboxTest;
   friend class TextfieldTest;
   friend class ViewAuraTest;
   friend void DisableActivationChangeHandlingForTests();
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 2f05546..e90e907 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -634,12 +634,15 @@
 // Tests mouse move outside of the window into the "resize controller" and back
 // will still generate an OnMouseEntered and OnMouseExited event..
 TEST_F(WidgetTestInteractive, CheckResizeControllerEvents) {
-  Widget* toplevel = CreateTopLevelPlatformWidget();
+  Widget* toplevel = CreateTopLevelFramelessPlatformWidget();
 
   toplevel->SetBounds(gfx::Rect(0, 0, 100, 100));
 
   MouseView* view = new MouseView();
   view->SetBounds(90, 90, 10, 10);
+  // |view| needs to be a particular size. Reset the LayoutManager so that
+  // it doesn't get resized.
+  toplevel->GetRootView()->SetLayoutManager(nullptr);
   toplevel->GetRootView()->AddChildView(view);
 
   toplevel->Show();
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index fea8ac0c..ee89998 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -17,6 +17,7 @@
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/compositor/test/draw_waiter_for_test.h"
 #include "ui/events/event_observer.h"
 #include "ui/events/event_utils.h"
 #include "ui/events/test/event_generator.h"
@@ -25,10 +26,12 @@
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/event_monitor.h"
+#include "ui/views/layout/fill_layout.h"
 #include "ui/views/test/native_widget_factory.h"
 #include "ui/views/test/test_views.h"
 #include "ui/views/test/test_widget_observer.h"
 #include "ui/views/test/widget_test.h"
+#include "ui/views/view_test_api.h"
 #include "ui/views/widget/native_widget_delegate.h"
 #include "ui/views/widget/native_widget_private.h"
 #include "ui/views/widget/root_view.h"
@@ -557,68 +560,7 @@
 // Test to verify using various Widget methods doesn't crash when the underlying
 // NativeView is destroyed.
 //
-class WidgetWithDestroyedNativeViewTest
-    : public ViewsTestBase,
-      public testing::WithParamInterface<ViewsTestBase::NativeWidgetType> {
- public:
-  WidgetWithDestroyedNativeViewTest() = default;
-  ~WidgetWithDestroyedNativeViewTest() override = default;
-
-  // ViewsTestBase:
-  void SetUp() override {
-    set_native_widget_type(GetParam());
-    ViewsTestBase::SetUp();
-  }
-
-  void InvokeWidgetMethods(Widget* widget) {
-    widget->GetNativeView();
-    widget->GetNativeWindow();
-    ui::Accelerator accelerator;
-    widget->GetAccelerator(0, &accelerator);
-    widget->GetTopLevelWidget();
-    widget->GetWindowBoundsInScreen();
-    widget->GetClientAreaBoundsInScreen();
-    widget->SetBounds(gfx::Rect(0, 0, 100, 80));
-    widget->SetSize(gfx::Size(10, 11));
-    widget->SetBoundsConstrained(gfx::Rect(0, 0, 120, 140));
-    widget->SetVisibilityChangedAnimationsEnabled(false);
-    widget->StackAtTop();
-    widget->IsClosed();
-    widget->Close();
-    widget->Hide();
-    widget->Activate();
-    widget->Deactivate();
-    widget->IsActive();
-    widget->SetAlwaysOnTop(true);
-    widget->IsAlwaysOnTop();
-    widget->Maximize();
-    widget->Minimize();
-    widget->Restore();
-    widget->IsMaximized();
-    widget->IsFullscreen();
-    widget->SetOpacity(0.f);
-    widget->FlashFrame(true);
-    widget->IsVisible();
-    widget->GetThemeProvider();
-    widget->GetNativeTheme();
-    widget->GetFocusManager();
-    widget->SchedulePaintInRect(gfx::Rect(0, 0, 1, 2));
-    widget->IsMouseEventsEnabled();
-    widget->SetNativeWindowProperty("xx", widget);
-    widget->GetNativeWindowProperty("xx");
-    widget->GetFocusTraversable();
-    widget->GetLayer();
-    widget->ReorderNativeViews();
-    widget->SetCapture(widget->GetRootView());
-    widget->ReleaseCapture();
-    widget->HasCapture();
-    widget->GetWorkAreaBoundsInScreen();
-    widget->IsTranslucentWindowOpacitySupported();
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(WidgetWithDestroyedNativeViewTest);
-};
+using WidgetWithDestroyedNativeViewTest = ViewsTestBaseWithNativeWidgetType;
 
 TEST_P(WidgetWithDestroyedNativeViewTest, Test) {
   Widget widget;
@@ -628,7 +570,49 @@
   widget.Show();
 
   widget.native_widget_private()->CloseNow();
-  InvokeWidgetMethods(&widget);
+  widget.GetNativeView();
+  widget.GetNativeWindow();
+  ui::Accelerator accelerator;
+  widget.GetAccelerator(0, &accelerator);
+  widget.GetTopLevelWidget();
+  widget.GetWindowBoundsInScreen();
+  widget.GetClientAreaBoundsInScreen();
+  widget.SetBounds(gfx::Rect(0, 0, 100, 80));
+  widget.SetSize(gfx::Size(10, 11));
+  widget.SetBoundsConstrained(gfx::Rect(0, 0, 120, 140));
+  widget.SetVisibilityChangedAnimationsEnabled(false);
+  widget.StackAtTop();
+  widget.IsClosed();
+  widget.Close();
+  widget.Hide();
+  widget.Activate();
+  widget.Deactivate();
+  widget.IsActive();
+  widget.SetAlwaysOnTop(true);
+  widget.IsAlwaysOnTop();
+  widget.Maximize();
+  widget.Minimize();
+  widget.Restore();
+  widget.IsMaximized();
+  widget.IsFullscreen();
+  widget.SetOpacity(0.f);
+  widget.FlashFrame(true);
+  widget.IsVisible();
+  widget.GetThemeProvider();
+  widget.GetNativeTheme();
+  widget.GetFocusManager();
+  widget.SchedulePaintInRect(gfx::Rect(0, 0, 1, 2));
+  widget.IsMouseEventsEnabled();
+  widget.SetNativeWindowProperty("xx", &widget);
+  widget.GetNativeWindowProperty("xx");
+  widget.GetFocusTraversable();
+  widget.GetLayer();
+  widget.ReorderNativeViews();
+  widget.SetCapture(widget.GetRootView());
+  widget.ReleaseCapture();
+  widget.HasCapture();
+  widget.GetWorkAreaBoundsInScreen();
+  widget.IsTranslucentWindowOpacitySupported();
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -3340,6 +3324,8 @@
   EXPECT_FALSE(frame->fullscreen_layout_called());
   widget->SetFullscreen(true);
   widget->Show();
+  EXPECT_TRUE(ViewTestApi(frame).needs_layout());
+  widget->LayoutRootViewIfNecessary();
   RunPendingMessages();
 
   EXPECT_TRUE(frame->fullscreen_layout_called());
@@ -3811,6 +3797,84 @@
   EXPECT_EQ(1, event_count_view->GetEventCount(ui::ET_MOUSEWHEEL));
 }
 
+class LayoutCountingView : public View {
+ public:
+  LayoutCountingView() = default;
+  ~LayoutCountingView() override = default;
+
+  void set_layout_closure(base::OnceClosure layout_closure) {
+    layout_closure_ = std::move(layout_closure);
+  }
+
+  size_t GetAndClearLayoutCount() {
+    const size_t count = layout_count_;
+    layout_count_ = 0u;
+    return count;
+  }
+
+  // View:
+  void Layout() override {
+    ++layout_count_;
+    View::Layout();
+    if (layout_closure_)
+      std::move(layout_closure_).Run();
+  }
+
+ private:
+  size_t layout_count_ = 0u;
+
+  // If valid, this is run when Layout() is called.
+  base::OnceClosure layout_closure_;
+
+  DISALLOW_COPY_AND_ASSIGN(LayoutCountingView);
+};
+
+using WidgetInvalidateLayoutTest = ViewsTestBaseWithNativeWidgetType;
+
+TEST_P(WidgetInvalidateLayoutTest, InvalidateLayout) {
+  Widget widget;
+  Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
+  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  widget.Init(params);
+  widget.SetBounds(gfx::Rect(0, 0, 600, 600));
+  LayoutCountingView* view =
+      widget.widget_delegate()->GetContentsView()->AddChildView(
+          std::make_unique<LayoutCountingView>());
+  view->parent()->SetLayoutManager(std::make_unique<FillLayout>());
+  // Force an initial Layout().
+  // TODO(sky): this shouldn't be necessary, adding a child view should trigger
+  // ScheduleLayout().
+  view->Layout();
+  widget.Show();
+
+  ui::Compositor* compositor = widget.GetCompositor();
+  ASSERT_TRUE(compositor);
+  compositor->ScheduleDraw();
+  ui::DrawWaiterForTest::WaitForCompositingEnded(compositor);
+
+  base::RunLoop run_loop;
+  view->GetAndClearLayoutCount();
+  // Don't use WaitForCompositingEnded() here as it's entirely possible nothing
+  // will be drawn (which means WaitForCompositingEnded() isn't run). Instead
+  // wait for Layout() to be called.
+  view->set_layout_closure(run_loop.QuitClosure());
+  EXPECT_FALSE(ViewTestApi(view).needs_layout());
+  EXPECT_FALSE(ViewTestApi(widget.GetRootView()).needs_layout());
+  view->InvalidateLayout();
+  EXPECT_TRUE(ViewTestApi(view).needs_layout());
+  EXPECT_TRUE(ViewTestApi(widget.GetRootView()).needs_layout());
+  run_loop.Run();
+  EXPECT_EQ(1u, view->GetAndClearLayoutCount());
+  EXPECT_FALSE(ViewTestApi(view).needs_layout());
+  EXPECT_FALSE(ViewTestApi(widget.GetRootView()).needs_layout());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    WidgetInvalidateLayoutTest,
+    WidgetInvalidateLayoutTest,
+    ::testing::Values(ViewsTestBase::NativeWidgetType::kDefault,
+                      ViewsTestBase::NativeWidgetType::kDesktop));
+
 class WidgetShadowTest : public WidgetTest {
  public:
   WidgetShadowTest() = default;
diff --git a/ui/views/window/dialog_client_view.cc b/ui/views/window/dialog_client_view.cc
index 068aac6..bf21e227 100644
--- a/ui/views/window/dialog_client_view.cc
+++ b/ui/views/window/dialog_client_view.cc
@@ -264,16 +264,11 @@
   return GetWidget()->widget_delegate()->AsDialogDelegate();
 }
 
-void DialogClientView::ChildPreferredSizeChanged(View* child) {
-  if (!adding_or_removing_views_ && child == extra_view_)
-    Layout();
-}
-
 void DialogClientView::ChildVisibilityChanged(View* child) {
   // Showing or hiding |extra_view_| can alter which columns have linked sizes.
   if (child == extra_view_)
     UpdateDialogButtons();
-  ChildPreferredSizeChanged(child);
+  InvalidateLayout();
 }
 
 void DialogClientView::OnDialogChanged() {
diff --git a/ui/views/window/dialog_client_view.h b/ui/views/window/dialog_client_view.h
index 47c80e6e6..6a894a0 100644
--- a/ui/views/window/dialog_client_view.h
+++ b/ui/views/window/dialog_client_view.h
@@ -84,7 +84,6 @@
   DialogDelegate* GetDialogDelegate() const;
 
   // View implementation.
-  void ChildPreferredSizeChanged(View* child) override;
   void ChildVisibilityChanged(View* child) override;
 
   // DialogObserver:
diff --git a/ui/views/window/dialog_delegate_unittest.cc b/ui/views/window/dialog_delegate_unittest.cc
index 61f2f236..b8e92a1 100644
--- a/ui/views/window/dialog_delegate_unittest.cc
+++ b/ui/views/window/dialog_delegate_unittest.cc
@@ -263,6 +263,7 @@
   const NonClientView* view = dialog()->GetWidget()->non_client_view();
   dialog()->set_title(base::ASCIIToUTF16("Title"));
   dialog()->GetWidget()->UpdateWindowTitle();
+  dialog()->GetWidget()->LayoutRootViewIfNecessary();
   BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view());
 
   struct {
diff --git a/ui/views/window/non_client_view.cc b/ui/views/window/non_client_view.cc
index 8786480..c8cb1ba 100644
--- a/ui/views/window/non_client_view.cc
+++ b/ui/views/window/non_client_view.cc
@@ -87,7 +87,7 @@
   Widget* widget = GetWidget();
   SetFrameView(widget->CreateNonClientFrameView());
   widget->ThemeChanged();
-  Layout();
+  InvalidateLayout();
   SchedulePaint();
 }